diff --git a/app/build.gradle b/app/build.gradle
index bed0377..ea5eeaa 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -8,8 +8,8 @@ android {
applicationId "news.androidtv.tvapprepo"
minSdkVersion 21
targetSdkVersion 25
- versionCode 11
- versionName "1.0.7-b"
+ versionCode 12
+ versionName "1.0.7-c"
}
buildTypes {
release {
@@ -60,6 +60,7 @@ dependencies {
compile 'com.google.firebase:firebase-database:10.0.1'
compile 'com.google.android.gms:play-services-ads:10.0.1'
compile 'com.google.firebase:firebase-config:10.0.1'
+ compile 'com.google.firebase:firebase-ads:10.0.1'
// compile 'com.colortv:android-sdk:2.1.0'
compile 'com.github.bumptech.glide:glide:3.7.0'
diff --git a/app/src/androidTest/java/news/androidtv/tvapprepo/ApplicationTest.java b/app/src/androidTest/java/news/androidtv/tvapprepo/ApplicationTest.java
index ef3c7a1..35db6e0 100644
--- a/app/src/androidTest/java/news/androidtv/tvapprepo/ApplicationTest.java
+++ b/app/src/androidTest/java/news/androidtv/tvapprepo/ApplicationTest.java
@@ -1,13 +1,52 @@
package news.androidtv.tvapprepo;
import android.app.Application;
+import android.content.ComponentName;
+import android.content.Intent;
import android.test.ApplicationTestCase;
+import android.util.Log;
+
+import java.io.File;
+import java.net.URISyntaxException;
+
+import dalvik.annotation.TestTargetClass;
+import news.androidtv.tvapprepo.intents.IntentUriGenerator;
/**
* Testing Fundamentals
*/
public class ApplicationTest extends ApplicationTestCase {
+ public static final String TAG = ApplicationTest.class.getSimpleName();
+
public ApplicationTest() {
super(Application.class);
}
+
+ public void testWebBookmarks() {
+ final String expected = "intent://google.com#Intent;scheme=http;end";
+ String actual = IntentUriGenerator.generateWebBookmark("http://google.com");
+ Log.d(TAG, actual);
+ assertEquals(expected, actual);
+ }
+
+ public void testActivityShortcut() {
+ final String expected = "intent:#Intent;component=news.androidtv.tvapprepo/.activities.SettingsActivity;end";
+ String actual = IntentUriGenerator.generateActivityShortcut(new ComponentName("news.androidtv.tvapprepo", ".activities.SettingsActivity"));
+ Log.d(TAG, actual);
+ assertEquals(expected, actual);
+ }
+
+ public void testFileOpening() {
+ // Note: This can be flaky if your device doesn't have this file. Future versions of this
+ // test should create and delete a temporary file.
+ final String expected = "intent:///storage/emulated/0/Download/com.felkertech.n.cumulustv.test.apk#Intent;scheme=file;launchFlags=0x10000000;end";
+ String actual = IntentUriGenerator.generateVideoPlayback(new File("/storage/emulated/0/Download/com.felkertech.n.cumulustv.test.apk"));
+ Log.d(TAG, actual);
+ assertEquals(expected, actual);
+ }
+
+ public void testOpenGoogle() throws URISyntaxException {
+ String string = "intent:#Intent;component=news.androidtv.tvapprepo/.activities.SettingsActivity;end";
+ getContext().startActivity(Intent.parseUri(string, Intent.URI_INTENT_SCHEME));
+ }
}
\ No newline at end of file
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index 397ea01..46d25f7 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -55,6 +55,15 @@
+
+
+
+
+
+
diff --git a/app/src/main/java/news/androidtv/tvapprepo/activities/AdvancedShortcutActivity.java b/app/src/main/java/news/androidtv/tvapprepo/activities/AdvancedShortcutActivity.java
new file mode 100644
index 0000000..9b87316
--- /dev/null
+++ b/app/src/main/java/news/androidtv/tvapprepo/activities/AdvancedShortcutActivity.java
@@ -0,0 +1,129 @@
+package news.androidtv.tvapprepo.activities;
+
+import android.app.Activity;
+import android.content.ComponentName;
+import android.content.DialogInterface;
+import android.content.Intent;
+import android.content.pm.ResolveInfo;
+import android.os.Bundle;
+import android.support.annotation.Nullable;
+import android.support.v7.app.AlertDialog;
+import android.util.Log;
+import android.view.ContextThemeWrapper;
+import android.view.Gravity;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.WindowManager;
+import android.widget.EditText;
+import android.widget.ImageButton;
+import android.widget.LinearLayout;
+import android.widget.Switch;
+import android.widget.Toast;
+
+import news.androidtv.tvapprepo.R;
+import news.androidtv.tvapprepo.iconography.IconsTask;
+import news.androidtv.tvapprepo.iconography.PackedIcon;
+import news.androidtv.tvapprepo.model.AdvancedOptions;
+import news.androidtv.tvapprepo.utils.GenerateShortcutHelper;
+
+/**
+ * Created by Nick on 4/24/2017.
+ *
+ * Dialogs are not a very good user interface if they start nesting. Instead, we will use a pull-out
+ * panel that comes in from the right and shows a variety of settings. This will scale a lot better
+ * as we can have more real-estate.
+ */
+public class AdvancedShortcutActivity extends Activity {
+ private static final String TAG = AdvancedShortcutActivity.class.getSimpleName();
+
+ public static final String EXTRA_RESOLVE_INFO = "resolveInfo";
+ public static final String EXTRA_ADVANCED_OPTIONS = "advancedOptions";
+
+ private AdvancedOptions advancedOptions;
+ private ResolveInfo resolveInfo;
+ private IconsTask.IconsReceivedCallback callback = new IconsTask.IconsReceivedCallback() {
+ @Override
+ public void onIcons(PackedIcon[] icons) {
+ Log.d(TAG, icons.length + "<<<");
+ // Show all icons for the user to select (or let them do their own)
+ LinearLayout iconDialogLayout = (LinearLayout) findViewById(R.id.icon_list);
+ iconDialogLayout.removeAllViews();
+ for (final PackedIcon icon : icons) {
+ ImageButton imageButton = new ImageButton(AdvancedShortcutActivity.this);
+ imageButton.setImageDrawable(icon.icon);
+ imageButton.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ if (icon.isBanner) {
+ advancedOptions.setBannerBitmap(icon.getBitmap());
+ } else {
+ advancedOptions.setIconBitmap(icon.getBitmap());
+ }
+ Log.d(TAG, advancedOptions.toString());
+ }
+ });
+ iconDialogLayout.addView(imageButton);
+ }
+ }
+ };
+
+ @Override
+ protected void onCreate(@Nullable Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ if (getActionBar() != null) {
+ getActionBar().hide();
+ }
+ setContentView(R.layout.activity_advanced);
+
+ if (getIntent().hasExtra(EXTRA_RESOLVE_INFO)) {
+ resolveInfo = getIntent().getParcelableExtra(EXTRA_RESOLVE_INFO);
+ }
+ if (getIntent().hasExtra(EXTRA_ADVANCED_OPTIONS)) {
+ advancedOptions = getIntent().getParcelableExtra(EXTRA_ADVANCED_OPTIONS);
+ }
+
+ if (advancedOptions == null) {
+ advancedOptions = new AdvancedOptions(this);
+ }
+
+ loadCustomIconography();
+
+ findViewById(R.id.generate).setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ publish();
+ finish();
+ }
+ });
+
+ // Turn into side-panel
+ // Sets the size and position of dialog activity.
+ WindowManager.LayoutParams layoutParams = getWindow().getAttributes();
+ layoutParams.gravity = Gravity.END | Gravity.CENTER_VERTICAL;
+ layoutParams.width = getResources().getDimensionPixelSize(R.dimen.side_panel_width);
+ layoutParams.height = ViewGroup.LayoutParams.MATCH_PARENT;
+ getWindow().setAttributes(layoutParams);
+ }
+
+ private void publish() {
+ boolean isGame = ((Switch) findViewById(R.id.switch_isgame)).isChecked();
+ String bannerUrl =
+ ((EditText) findViewById(R.id.edit_banner)).getText().toString();
+ if (!bannerUrl.isEmpty()) {
+ advancedOptions.setBannerUrl(bannerUrl);
+ }
+ advancedOptions.setIsGame(isGame);
+ GenerateShortcutHelper.generateShortcut(this, resolveInfo, advancedOptions);
+ }
+
+ private void loadCustomIconography() {
+ if (resolveInfo != null) {
+ IconsTask.getIconsForComponentName(this,
+ new ComponentName(resolveInfo.activityInfo.packageName,
+ resolveInfo.activityInfo.name), callback);
+
+ } else {
+ Toast.makeText(this, "Cannot set banner of non-app yet", Toast.LENGTH_SHORT).show();
+ }
+ }
+}
diff --git a/app/src/main/java/news/androidtv/tvapprepo/fragments/MainFragment.java b/app/src/main/java/news/androidtv/tvapprepo/fragments/MainFragment.java
index 11e9038..582e05d 100644
--- a/app/src/main/java/news/androidtv/tvapprepo/fragments/MainFragment.java
+++ b/app/src/main/java/news/androidtv/tvapprepo/fragments/MainFragment.java
@@ -1,17 +1,3 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
- * in compliance with the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software distributed under the License
- * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
- * or implied. See the License for the specific language governing permissions and limitations under
- * the License.
- */
-
package news.androidtv.tvapprepo.fragments;
import android.app.Activity;
@@ -24,7 +10,6 @@
import android.os.Environment;
import android.os.Handler;
import android.os.Looper;
-import android.preference.PreferenceActivity;
import android.support.annotation.NonNull;
import android.support.v17.leanback.app.BackgroundManager;
import android.support.v17.leanback.app.BrowseFragment;
@@ -40,24 +25,19 @@
import android.support.v17.leanback.widget.RowPresenter;
import android.support.v4.app.ActivityOptionsCompat;
import android.support.v7.app.AlertDialog;
-import android.support.v7.view.ContextThemeWrapper;
import android.util.DisplayMetrics;
import android.util.Log;
+import android.view.ContextThemeWrapper;
import android.widget.EditText;
import android.widget.Toast;
import com.afollestad.materialdialogs.DialogAction;
import com.afollestad.materialdialogs.MaterialDialog;
-import com.android.volley.NetworkResponse;
-import com.android.volley.VolleyError;
import com.bumptech.glide.Glide;
import com.bumptech.glide.load.resource.drawable.GlideDrawable;
import com.bumptech.glide.request.animation.GlideAnimation;
import com.bumptech.glide.request.target.SimpleTarget;
-import org.json.JSONException;
-import org.json.JSONObject;
-
import java.io.File;
import java.net.URI;
import java.util.ArrayList;
@@ -82,20 +62,15 @@
import news.androidtv.tvapprepo.presenters.DownloadedFilesPresenter;
import news.androidtv.tvapprepo.presenters.LauncherActivitiesPresenter;
import news.androidtv.tvapprepo.presenters.OptionsCardPresenter;
+import news.androidtv.tvapprepo.ui.ShortcutGeneratorDialogs;
import news.androidtv.tvapprepo.utils.GenerateShortcutHelper;
import news.androidtv.tvapprepo.utils.PackageInstallerUtils;
-import news.androidtv.tvapprepo.utils.ShortcutPostTask;
import tv.puppetmaster.tinydl.PackageInstaller;
public class MainFragment extends BrowseFragment {
private static final String TAG = MainFragment.class.getSimpleName();
- private static final boolean DEBUG_SHOW_APKS = true;
private static final int BACKGROUND_UPDATE_DELAY = 300;
- private static final int GRID_ITEM_WIDTH = 200;
- private static final int GRID_ITEM_HEIGHT = 200;
- private static final int NUM_ROWS = 6;
- private static final int NUM_COLS = 15;
private boolean checkedForUpdates = true;
private Activity mMainActivity;
@@ -174,7 +149,6 @@ public void onDestroy() {
@Override
public void onStart() {
super.onStart();
-// loadRows();
}
@Override
@@ -217,6 +191,8 @@ private void loadRows() {
createRowShortcutGenerator();
+ createRowCustomShortcuts();
+
createRowMisc();
setAdapter(mRowsAdapter);
@@ -324,6 +300,126 @@ private void createRowShortcutGenerator() {
mRowsAdapter.add(new ListRow(launcherActivitiesHeader, launcherActivitiesAdapter));
}
+ private void createRowCustomShortcuts() {
+ if (!getResources().getBoolean(R.bool.ENABLE_RAW_INTENTS)) {
+ return; // Don't do this at all.
+ }
+ // Add a row for credits
+ OptionsCardPresenter optionsCardPresenter = new OptionsCardPresenter();
+ ArrayObjectAdapter optionsRowAdapter = new ArrayObjectAdapter(optionsCardPresenter);
+ if (getResources().getBoolean(R.bool.ENABLE_WEB_BOOKMARKS)) {
+ optionsRowAdapter.add(new SettingOption(
+ getResources().getDrawable(R.drawable.web_bookmark),
+ getString(R.string.generate_web_bookmark),
+ new SettingOption.OnClickListener() {
+ @Override
+ public void onClick() {
+ ShortcutGeneratorDialogs.initWebBookmarkDialog(getActivity());
+ }
+ }
+ ));
+ }
+ if (getResources().getBoolean(R.bool.ENABLE_FOLDERS)) {
+ optionsRowAdapter.add(new SettingOption(
+ getResources().getDrawable(R.drawable.folder),
+ getString(R.string.generate_folder),
+ new SettingOption.OnClickListener() {
+ @Override
+ public void onClick() {
+ new MaterialDialog.Builder(new ContextThemeWrapper(getActivity(), R.style.dialog_theme))
+ .title(R.string.generate_folder)
+ .customView(R.layout.dialog_folder, false)
+ .onNegative(new MaterialDialog.SingleButtonCallback() {
+ @Override
+ public void onClick(@NonNull MaterialDialog dialog, @NonNull DialogAction which) {
+ String tag = ((EditText) dialog.getCustomView().findViewById(R.id.tag)).getText().toString();
+
+ Toast.makeText(getActivity(), R.string.starting_download, Toast.LENGTH_SHORT).show();
+ }
+ })
+ .positiveText(R.string.generate_shortcut)
+ .show();
+ }
+ }
+ ));
+ }
+ if (getResources().getBoolean(R.bool.ENABLE_SETTINGS)) {
+ optionsRowAdapter.add(new SettingOption(
+ getResources().getDrawable(R.drawable.sys_settings),
+ getString(R.string.generate_settings),
+ new SettingOption.OnClickListener() {
+ @Override
+ public void onClick() {
+ new MaterialDialog.Builder(new ContextThemeWrapper(getActivity(), R.style.dialog_theme))
+ .title(R.string.generate_settings)
+ .customView(R.layout.dialog_folder, false)
+ .onNegative(new MaterialDialog.SingleButtonCallback() {
+ @Override
+ public void onClick(@NonNull MaterialDialog dialog, @NonNull DialogAction which) {
+ String tag = ((EditText) dialog.getCustomView().findViewById(R.id.tag)).getText().toString();
+
+ Toast.makeText(getActivity(), R.string.starting_download, Toast.LENGTH_SHORT).show();
+ }
+ })
+ .positiveText(R.string.generate_shortcut)
+ .show();
+ }
+ }
+ ));
+ }
+ if (getResources().getBoolean(R.bool.ENABLE_APP_DEEPLINKS)) {
+ optionsRowAdapter.add(new SettingOption(
+ getResources().getDrawable(R.drawable.deep_link),
+ getString(R.string.generate_deep_link),
+ new SettingOption.OnClickListener() {
+ @Override
+ public void onClick() {
+ new MaterialDialog.Builder(new ContextThemeWrapper(getActivity(), R.style.dialog_theme))
+ .title(R.string.generate_deep_link)
+ .customView(R.layout.dialog_folder, false)
+ .onNegative(new MaterialDialog.SingleButtonCallback() {
+ @Override
+ public void onClick(@NonNull MaterialDialog dialog, @NonNull DialogAction which) {
+ String tag = ((EditText) dialog.getCustomView().findViewById(R.id.tag)).getText().toString();
+
+ Toast.makeText(getActivity(), R.string.starting_download, Toast.LENGTH_SHORT).show();
+ }
+ })
+ .positiveText(R.string.generate_shortcut)
+ .show();
+ }
+ }
+ ));
+ }
+ if (getResources().getBoolean(R.bool.ENABLE_FILE_URIS)) {
+ optionsRowAdapter.add(new SettingOption(
+ getResources().getDrawable(R.drawable.file_location),
+ getString(R.string.generate_file_shortcut),
+ new SettingOption.OnClickListener() {
+ @Override
+ public void onClick() {
+ new MaterialDialog.Builder(new ContextThemeWrapper(getActivity(), R.style.dialog_theme))
+ .title(R.string.generate_file_shortcut)
+ .customView(R.layout.dialog_folder, false)
+ .onNegative(new MaterialDialog.SingleButtonCallback() {
+ @Override
+ public void onClick(@NonNull MaterialDialog dialog, @NonNull DialogAction which) {
+ String tag = ((EditText) dialog.getCustomView().findViewById(R.id.tag)).getText().toString();
+
+ Toast.makeText(getActivity(), R.string.starting_download, Toast.LENGTH_SHORT).show();
+ }
+ })
+ .positiveText(R.string.generate_shortcut)
+ .show();
+ }
+ }
+ ));
+ }
+
+ HeaderItem header = new HeaderItem(2, getString(R.string.header_custom));
+ mRowsAdapter.add(new ListRow(header, optionsRowAdapter));
+ }
+
private void createRowMisc() {
// Add a row for credits
OptionsCardPresenter optionsCardPresenter = new OptionsCardPresenter();
@@ -338,7 +434,7 @@ public void onClick() {
new MaterialDialog.Builder(new ContextThemeWrapper(getActivity(), R.style.dialog_theme))
.title(R.string.sideloadtag)
.customView(R.layout.dialog_sideload_tag, false)
- .onNegative(new MaterialDialog.SingleButtonCallback() {
+ .onPositive(new MaterialDialog.SingleButtonCallback() {
@Override
public void onClick(@NonNull MaterialDialog dialog, @NonNull DialogAction which) {
String tag = ((EditText) dialog.getCustomView().findViewById(R.id.tag)).getText().toString();
@@ -474,6 +570,7 @@ public void onItemClicked(Presenter.ViewHolder itemViewHolder, final Object item
} else if (item instanceof SettingOption) {
((SettingOption) item).getClickListener().onClick();
} else if (item instanceof File) {
+ Log.d(TAG, "Open file " + ((File) item).getAbsolutePath());
mApkDownloadHelper.install((File) item);
} else if (item instanceof ResolveInfo) {
GenerateShortcutHelper.begin(mMainActivity, (ResolveInfo) item);
diff --git a/app/src/main/java/news/androidtv/tvapprepo/iconography/IconsTask.java b/app/src/main/java/news/androidtv/tvapprepo/iconography/IconsTask.java
new file mode 100644
index 0000000..635e9c2
--- /dev/null
+++ b/app/src/main/java/news/androidtv/tvapprepo/iconography/IconsTask.java
@@ -0,0 +1,130 @@
+package news.androidtv.tvapprepo.iconography;
+
+import android.app.Activity;
+import android.content.ComponentName;
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
+import android.content.res.Resources;
+import android.content.res.XmlResourceParser;
+import android.graphics.drawable.Drawable;
+import android.util.Log;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Created by Nick on 4/23/2017.
+ *
+ * This class has methods that allow it to traverse icon packs installed on your device. These
+ * icon packs are ultimately XML files that contain certain properties in the `appfilter.xml`.
+ *
+ *
+ *
+ * We are adding more properties to support TVs. Each XML file can have a `banner` boolean attribute.
+ * Alternatively, one can them placed in `appfilterbanners.xml`
+ *
+ * We need to remove the "ComponentInfo" from that attribute.
+ * ComponentInfo{com.android.browser/com.android.browser.BrowserActivity}
+ * ^ (14) ^ (-1)
+ *
+ *
+ * For some additional references:
+ * * https://github.com/iamareebjamal/scratch_icon_pack_source/blob/master/app/src/main/res/xml/appfilter.xml
+ * * http://stackoverflow.com/questions/24937890/using-icon-packs-in-my-app
+ * * https://github.com/CyanogenMod/android_packages_apps_Trebuchet/
+ * * https://github.com/googlesamples/androidtv-sample-inputs/blob/master/app/src/main/res/raw/rich_tv_input_xmltv_feed.xml
+ */
+
+public class IconsTask {
+ private static final boolean DEBUG = true;
+ private static final String TAG = IconsTask.class.getSimpleName();
+
+ private static final String INTENT_FILTER_ICON_PACKS = "org.adw.launcher.THEMES";
+
+ public static void getIconsForComponentName(Activity activity, ComponentName filter, IconsReceivedCallback iconsReceivedCallback) {
+ List iconList = new ArrayList<>();
+ List iconPacks =
+ activity.getPackageManager().queryIntentActivities(new Intent(INTENT_FILTER_ICON_PACKS), PackageManager.GET_META_DATA);
+ if (DEBUG) {
+ Log.d(TAG, "Found " + iconPacks.size() + " icon packs");
+ }
+ for (ResolveInfo app : iconPacks) {
+ if (DEBUG) {
+ Log.d(TAG, "Scan icon pack " + app.activityInfo.packageName + ", " + app.activityInfo.applicationInfo.packageName);
+ }
+ iconList.addAll(scanIconPack(activity, "appfilter", app.activityInfo.applicationInfo.packageName, filter));
+ iconList.addAll(scanIconPack(activity, "appfilterbanner", app.activityInfo.applicationInfo.packageName, filter));
+ }
+ if (DEBUG) {
+ Log.d(TAG, "Sending callback with " + iconList.size() + " items");
+ }
+ iconsReceivedCallback.onIcons(iconList.toArray(new PackedIcon[iconList.size()]));
+ }
+
+ private static List scanIconPack(Activity activity, String filename, String packageName, ComponentName filter) {
+ List iconList = new ArrayList<>();
+ try {
+ Resources iconResources = activity.getPackageManager().getResourcesForApplication(packageName);
+ int xmlId = iconResources.getIdentifier(filename, "xml", packageName);
+ if (DEBUG) {
+ Log.d(TAG, "Read XML file " + xmlId);
+ }
+ if (xmlId == 0) {
+ return iconList;
+ }
+ XmlResourceParser resourceParser = iconResources.getXml(xmlId);
+ try {
+ while (resourceParser.next() != XmlPullParser.END_DOCUMENT) {
+ if (resourceParser.getName() != null && resourceParser.getName().equals("item")) {
+ // Get properties
+ boolean validApp = false;
+ String drawableName = "";
+ boolean banner = false;
+ for (int i = 0; i < resourceParser.getAttributeCount(); i++) {
+ String attr = resourceParser.getAttributeName(i);
+ String value = resourceParser.getAttributeValue(i);
+
+ if (attr.equals("component") &&
+ value.substring(14, value.length() - 1).equals(filter.flattenToString())) {
+ validApp = true;
+ } else if (attr.equals("drawable")) {
+ drawableName = value;
+ } else if (attr.equals("banner")) {
+ banner = value.toLowerCase().equals("true");
+ }
+
+ if (i + 1 == resourceParser.getAttributeCount()) {
+ // Last element
+ if (validApp) {
+ int drawableId = iconResources.getIdentifier(drawableName, "drawable", packageName);
+ Drawable icon = iconResources.getDrawable(drawableId);
+ iconList.add(new PackedIcon(icon, banner));
+ if (DEBUG) {
+ Log.d(TAG, "Adding an icon for " + drawableName + " from " + packageName + ": " + drawableId);
+ }
+ }
+ validApp = false;
+ drawableName = "";
+ banner = false;
+ }
+ }
+ }
+ }
+ } catch (XmlPullParserException | IOException e) {
+ e.printStackTrace();
+ }
+ } catch (PackageManager.NameNotFoundException e) {
+ e.printStackTrace();
+ }
+ return iconList;
+ }
+
+ public interface IconsReceivedCallback {
+ void onIcons(PackedIcon[] icons);
+ }
+}
diff --git a/app/src/main/java/news/androidtv/tvapprepo/iconography/PackedIcon.java b/app/src/main/java/news/androidtv/tvapprepo/iconography/PackedIcon.java
new file mode 100644
index 0000000..9ba7c5d
--- /dev/null
+++ b/app/src/main/java/news/androidtv/tvapprepo/iconography/PackedIcon.java
@@ -0,0 +1,39 @@
+package news.androidtv.tvapprepo.iconography;
+
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.drawable.BitmapDrawable;
+import android.graphics.drawable.Drawable;
+
+/**
+ * Created by Nick on 4/23/2017.
+ *
+ * A simple model abstraction that contains a few custom parameters.
+ */
+public class PackedIcon {
+ public final Drawable icon;
+ public final boolean isBanner; // For 'banner-packs'
+
+ public PackedIcon(Drawable icon, boolean isBanner) {
+ this.icon = icon;
+ this.isBanner = isBanner;
+ }
+
+ public Bitmap getBitmap() {
+ if (icon instanceof BitmapDrawable) {
+ return ((BitmapDrawable) icon).getBitmap();
+ }
+
+ int width = icon.getIntrinsicWidth();
+ width = width > 0 ? width : 1;
+ int height = icon.getIntrinsicHeight();
+ height = height > 0 ? height : 1;
+
+ Bitmap bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
+ Canvas canvas = new Canvas(bitmap);
+ icon.setBounds(0, 0, canvas.getWidth(), canvas.getHeight());
+ icon.draw(canvas);
+
+ return bitmap;
+ }
+}
diff --git a/app/src/main/java/news/androidtv/tvapprepo/intents/IntentUriGenerator.java b/app/src/main/java/news/androidtv/tvapprepo/intents/IntentUriGenerator.java
new file mode 100644
index 0000000..90a0d46
--- /dev/null
+++ b/app/src/main/java/news/androidtv/tvapprepo/intents/IntentUriGenerator.java
@@ -0,0 +1,58 @@
+package news.androidtv.tvapprepo.intents;
+
+import android.content.ComponentName;
+import android.content.Intent;
+import android.net.Uri;
+import android.webkit.MimeTypeMap;
+
+import java.io.File;
+
+/**
+ * Created by Nick on 4/20/2017.
+ *
+ * Generates Intent URIs through static methods.
+ */
+public class IntentUriGenerator {
+ public static String generateWebBookmark(String url) {
+ Intent i = new Intent(Intent.ACTION_VIEW);
+ i.setData(Uri.parse(url));
+ return i.toUri(Intent.URI_INTENT_SCHEME);
+ }
+
+ public static String generateVideoPlayback(File myFile) {
+ MimeTypeMap myMime = MimeTypeMap.getSingleton();
+ Intent newIntent = new Intent(Intent.ACTION_VIEW);
+ String mimeType = myMime.getMimeTypeFromExtension(fileExt(myFile.getAbsolutePath()).substring(1));
+ newIntent.setDataAndType(Uri.fromFile(myFile),mimeType);
+ newIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+ return newIntent.toUri(Intent.URI_INTENT_SCHEME);
+ }
+
+ public static String generateFileOpen(File myFile) {
+ return generateVideoPlayback(myFile);
+ }
+
+ public static String generateActivityShortcut(ComponentName componentName) {
+ Intent newIntent = new Intent();
+ newIntent.setComponent(componentName);
+ return newIntent.toUri(Intent.URI_INTENT_SCHEME);
+ }
+
+ private static String fileExt(String url) {
+ if (url.indexOf("?") > -1) {
+ url = url.substring(0, url.indexOf("?"));
+ }
+ if (url.lastIndexOf(".") == -1) {
+ return null;
+ } else {
+ String ext = url.substring(url.lastIndexOf(".") + 1);
+ if (ext.indexOf("%") > -1) {
+ ext = ext.substring(0, ext.indexOf("%"));
+ }
+ if (ext.indexOf("/") > -1) {
+ ext = ext.substring(0, ext.indexOf("/"));
+ }
+ return ext.toLowerCase();
+ }
+ }
+}
diff --git a/app/src/main/java/news/androidtv/tvapprepo/model/AdvancedOptions.java b/app/src/main/java/news/androidtv/tvapprepo/model/AdvancedOptions.java
index a1d1aab..50e935f 100644
--- a/app/src/main/java/news/androidtv/tvapprepo/model/AdvancedOptions.java
+++ b/app/src/main/java/news/androidtv/tvapprepo/model/AdvancedOptions.java
@@ -1,12 +1,18 @@
package news.androidtv.tvapprepo.model;
+import android.app.Activity;
import android.content.Context;
import android.graphics.Bitmap;
import android.os.Handler;
import android.os.Looper;
+import android.os.Parcel;
+import android.os.Parcelable;
import com.bumptech.glide.Glide;
+import org.json.JSONException;
+import org.json.JSONObject;
+
import java.io.ByteArrayOutputStream;
import java.util.concurrent.ExecutionException;
@@ -15,10 +21,15 @@
/**
* Created by Nick on 3/20/2017. A model for storing advanced options in generating shortcuts.
*/
-public class AdvancedOptions {
+public class AdvancedOptions implements Parcelable {
private volatile int mReady = 0;
private String mCategory = "";
+ private String mIconUrl = "";
private String mBannerUrl = "";
+ private String mIntentUri = "";
+ private String mCustomLabel = "";
+ private boolean mUnique = false;
+ private byte[] mIconData = null;
private byte[] mBannerData = null;
private Context mContext = null;
@@ -49,10 +60,66 @@ public AdvancedOptions setIsGame(boolean isGame) {
return this;
}
+ public AdvancedOptions setIntentUri(String intentUri) {
+ if (intentUri.length() < 20 || intentUri.length() > 300) {
+ throw new StringLengthException();
+ }
+ mIntentUri = intentUri;
+ return this;
+ }
+
+ public AdvancedOptions setUniquePackageName(boolean isUnique) {
+ mUnique = isUnique;
+ return this;
+ }
+
+ public AdvancedOptions setCustomLabel(String label) {
+ mCustomLabel = label;
+ return this;
+ }
+
+ public AdvancedOptions setIconUrl(String iconUrl) {
+ if (iconUrl == null || iconUrl.isEmpty()) {
+ // Exit early.
+ return this;
+ }
+ mReady++;
+ mIconUrl = iconUrl;
+ // Download from Glide.
+ downloadBanner(mContext, iconUrl, new GlideCallback() {
+ @Override
+ public void onDone(byte[] binaryData) {
+ mIconData = binaryData;
+ mReady--;
+ }
+ });
+ return this;
+ }
+
+ public AdvancedOptions setBannerBitmap(Bitmap bitmap) {
+ ByteArrayOutputStream stream = new ByteArrayOutputStream();
+ bitmap.compress(Bitmap.CompressFormat.PNG, 100, stream);
+ byte[] results = stream.toByteArray();
+ mBannerData = results;
+ return this;
+ }
+
+ public AdvancedOptions setIconBitmap(Bitmap bitmap) {
+ ByteArrayOutputStream stream = new ByteArrayOutputStream();
+ bitmap.compress(Bitmap.CompressFormat.PNG, 100, stream);
+ byte[] results = stream.toByteArray();
+ mIconData = results;
+ return this;
+ }
+
public boolean isReady() {
return mReady == 0;
}
+ public byte[] getIcon() {
+ return mIconData;
+ }
+
public byte[] getBanner() {
return mBannerData;
}
@@ -65,6 +132,22 @@ public String getCategory() {
return mCategory;
}
+ public String getIntentUri() {
+ return mIntentUri;
+ }
+
+ public String getCustomLabel() {
+ return mCustomLabel;
+ }
+
+ public boolean isUnique() {
+ return mUnique;
+ }
+
+ public String getIconUrl() {
+ return mIconUrl;
+ }
+
private void downloadBanner(final Context context, final String url, final GlideCallback callback) {
new Thread(new Runnable() {
@Override
@@ -87,7 +170,100 @@ public void run() {
}).start();
}
+ @Override
+ public String toString() {
+ return "Name=" + mCustomLabel + ", category=" + mCategory + ", iconUrl=" + mIconUrl +
+ ", bannerUrl=" + mBannerUrl + ", iconData=" + (mIconData != null) +
+ ", bannerData=" + (mBannerData != null);
+ }
+
+ public String serialize() {
+ JSONObject object = new JSONObject();
+ try {
+ object.put("customLabel", mCustomLabel);
+ object.put("category", mCategory);
+ object.put("iconUrl", mIconUrl);
+ object.put("bannerUrl", mBannerUrl);
+ object.put("isUnique", mUnique);
+ object.put("intentUri", mIntentUri);
+ } catch (JSONException e) {
+ e.printStackTrace();
+ }
+ return object.toString();
+ }
+
+ public static AdvancedOptions deserialize(Activity activity, String serialization) {
+ AdvancedOptions options = new AdvancedOptions(activity);
+ try {
+ JSONObject object = new JSONObject(serialization);
+ options.setCustomLabel(object.optString("customLabel"));
+ options.mCategory = object.optString("category");
+ options.setIconUrl(object.optString("iconUrl"));
+ options.setBannerUrl(object.optString("bannerUrl"));
+ options.setUniquePackageName(object.optBoolean("isUnique"));
+ options.setIntentUri(object.optString("intentUri"));
+ } catch (JSONException e) {
+ e.printStackTrace();
+ }
+ return options;
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeString(mCustomLabel);
+ dest.writeString(mCategory);
+ dest.writeString(mIconUrl);
+ dest.writeString(mBannerUrl);
+ dest.writeByte((byte) (mUnique ? 1 : 0));
+ dest.writeString(mIntentUri);
+ if (mIconData == null) {
+ mIconData = new byte[0];
+ }
+ if (mBannerData == null) {
+ mBannerData = new byte[0];
+ }
+ dest.writeInt(mIconData.length);
+ dest.writeByteArray(mIconData);
+ dest.writeInt(mBannerData.length);
+ dest.writeByteArray(mBannerData);
+ }
+
+ public static final Parcelable.Creator CREATOR
+ = new Parcelable.Creator() {
+ public AdvancedOptions createFromParcel(Parcel in) {
+ return new AdvancedOptions(in);
+ }
+
+ public AdvancedOptions[] newArray(int size) {
+ return new AdvancedOptions[size];
+ }
+ };
+
+ private AdvancedOptions(Parcel in) {
+ mCustomLabel = in.readString();
+ mCategory = in.readString();
+ mIconUrl = in.readString();
+ mBannerUrl = in.readString();
+ mUnique = in.readByte() == 1;
+ mIntentUri = in.readString();
+ mIconData = new byte[in.readInt()];
+ in.readByteArray(mIconData);
+ mBannerData = new byte[in.readInt()];
+ in.readByteArray(mBannerData);
+ }
+
private interface GlideCallback {
void onDone(byte[] binaryData);
}
+
+ public class StringLengthException extends RuntimeException {
+ public StringLengthException() {
+ super("Intent URI length must be between 20 and 300 characters");
+ }
+ }
}
diff --git a/app/src/main/java/news/androidtv/tvapprepo/ui/ShortcutGeneratorDialogs.java b/app/src/main/java/news/androidtv/tvapprepo/ui/ShortcutGeneratorDialogs.java
new file mode 100644
index 0000000..5f915cd
--- /dev/null
+++ b/app/src/main/java/news/androidtv/tvapprepo/ui/ShortcutGeneratorDialogs.java
@@ -0,0 +1,49 @@
+package news.androidtv.tvapprepo.ui;
+
+import android.app.Activity;
+import android.content.Intent;
+import android.support.annotation.NonNull;
+import android.util.Log;
+import android.view.ContextThemeWrapper;
+import android.widget.EditText;
+import android.widget.Toast;
+
+import com.afollestad.materialdialogs.DialogAction;
+import com.afollestad.materialdialogs.MaterialDialog;
+
+import news.androidtv.tvapprepo.R;
+import news.androidtv.tvapprepo.intents.IntentUriGenerator;
+import news.androidtv.tvapprepo.model.AdvancedOptions;
+import news.androidtv.tvapprepo.utils.GenerateShortcutHelper;
+
+/**
+ * Created by Nick on 4/23/2017.
+ */
+
+public class ShortcutGeneratorDialogs {
+ private static final String TAG = ShortcutGeneratorDialogs.class.getSimpleName();
+
+ public static void initWebBookmarkDialog(final Activity activity) {
+ new MaterialDialog.Builder(new ContextThemeWrapper(activity, R.style.dialog_theme))
+ .title(R.string.generate_web_bookmark)
+ .customView(R.layout.dialog_web_bookmark, false)
+ .onPositive(new MaterialDialog.SingleButtonCallback() {
+ @Override
+ public void onClick(@NonNull MaterialDialog dialog, @NonNull DialogAction which) {
+ String tag = ((EditText) dialog.getCustomView().findViewById(R.id.tag)).getText().toString();
+ if (!tag.contains("http://") || !tag.contains("https://")) {
+ tag = "http://" + tag;
+ }
+ String label = tag.replaceAll("(http://)|(https://)", "");
+ Log.d(TAG, IntentUriGenerator.generateWebBookmark(tag));
+ AdvancedOptions options = new AdvancedOptions(activity)
+ .setIntentUri(IntentUriGenerator.generateWebBookmark(tag))
+ .setIconUrl("https://raw.githubusercontent.com/ITVlab/TvAppRepo/master/promo/graphics/icon.png") // TODO Replace icon url
+ .setCustomLabel(label);
+ GenerateShortcutHelper.begin(activity, label, options);
+ }
+ })
+ .positiveText(R.string.generate_shortcut)
+ .show();
+ }
+}
diff --git a/app/src/main/java/news/androidtv/tvapprepo/utils/GenerateShortcutHelper.java b/app/src/main/java/news/androidtv/tvapprepo/utils/GenerateShortcutHelper.java
index d1000b3..b71c367 100644
--- a/app/src/main/java/news/androidtv/tvapprepo/utils/GenerateShortcutHelper.java
+++ b/app/src/main/java/news/androidtv/tvapprepo/utils/GenerateShortcutHelper.java
@@ -2,37 +2,51 @@
import android.app.Activity;
import android.app.Dialog;
+import android.content.ComponentName;
import android.content.DialogInterface;
+import android.content.Intent;
import android.content.pm.ResolveInfo;
import android.os.Handler;
import android.os.Looper;
import android.support.v7.app.AlertDialog;
+import android.text.Layout;
+import android.util.Log;
import android.view.ContextThemeWrapper;
import android.view.View;
import android.widget.EditText;
+import android.widget.ImageButton;
+import android.widget.LinearLayout;
import android.widget.Switch;
import android.widget.Toast;
import com.android.volley.NetworkResponse;
import com.android.volley.VolleyError;
+import com.google.android.gms.ads.AdListener;
+import com.google.android.gms.ads.AdRequest;
+import com.google.android.gms.ads.InterstitialAd;
+import com.google.android.gms.ads.MobileAds;
import org.json.JSONException;
import org.json.JSONObject;
import news.androidtv.tvapprepo.R;
+import news.androidtv.tvapprepo.activities.AdvancedShortcutActivity;
import news.androidtv.tvapprepo.download.ApkDownloadHelper;
+import news.androidtv.tvapprepo.iconography.IconsTask;
+import news.androidtv.tvapprepo.iconography.PackedIcon;
import news.androidtv.tvapprepo.model.AdvancedOptions;
/**
* Created by Nick Felker on 3/20/2017.
*/
public class GenerateShortcutHelper {
+ private static final String TAG = GenerateShortcutHelper.class.getSimpleName();
+
private static final String KEY_BUILD_STATUS = "build_ok";
private static final String KEY_APP_OBJ = "app";
private static final String KEY_DOWNLOAD_URL = "download_link";
public static void begin(final Activity activity, final ResolveInfo resolveInfo) {
-
new AlertDialog.Builder(new ContextThemeWrapper(activity, R.style.dialog_theme))
.setTitle(activity.getString(R.string.title_shortcut_generator,
resolveInfo.activityInfo.applicationInfo.loadLabel(activity.getPackageManager())))
@@ -54,33 +68,45 @@ public void onClick(DialogInterface dialog, int which) {
.show();
}
- private static void openAdvancedOptions(final Activity activity, final ResolveInfo resolveInfo) {
- final AlertDialog dialog = new AlertDialog.Builder(new ContextThemeWrapper(activity, R.style.dialog_theme))
- .setTitle(R.string.advanced_options)
- .setView(R.layout.dialog_app_shortcut_editor)
+ public static void begin(final Activity activity, final String label, final AdvancedOptions options) {
+ new AlertDialog.Builder(new ContextThemeWrapper(activity, R.style.dialog_theme))
+ .setTitle(activity.getString(R.string.title_shortcut_generator, label))
+ .setMessage(R.string.shortcut_generator_info)
+ .setPositiveButton(R.string.create_shortcut, new DialogInterface.OnClickListener() {
+ @Override
+ public void onClick(DialogInterface dialogInterface, int i) {
+ generateShortcut(activity, null, options);
+ }
+ })
+ .setNeutralButton(R.string.advanced, new DialogInterface.OnClickListener() {
+ @Override
+ public void onClick(DialogInterface dialog, int which) {
+ // Open a new dialog
+ openAdvancedOptions(activity, null, options);
+ }
+ })
.setNegativeButton(R.string.cancel, null)
- .create();
- dialog.setButton(Dialog.BUTTON_POSITIVE,
- activity.getString(R.string.create_shortcut),
- new DialogInterface.OnClickListener() {
- @Override
- public void onClick(DialogInterface dialogInterface, int which) {
- View editor = dialog.getWindow().getDecorView();
- AdvancedOptions options = new AdvancedOptions(activity);
- String bannerUrl =
- ((EditText) editor.findViewById(R.id.edit_banner)).getText().toString();
- boolean isGame = ((Switch) editor.findViewById(R.id.switch_isgame)).isChecked();
- options.setBannerUrl(bannerUrl).setIsGame(isGame);
- generateShortcut(activity, resolveInfo, options);
- }
- });
- dialog.show();
+ .show();
+ }
+
+ private static void openAdvancedOptions(final Activity activity, final ResolveInfo resolveInfo) {
+ openAdvancedOptions(activity, resolveInfo, null);
+ }
+
+ private static void openAdvancedOptions(final Activity activity, final ResolveInfo resolveInfo, AdvancedOptions options) {
+ Intent editorPanel = new Intent(activity, AdvancedShortcutActivity.class);
+ editorPanel.putExtra(AdvancedShortcutActivity.EXTRA_RESOLVE_INFO, resolveInfo);
+ if (options != null) {
+ editorPanel.putExtra(AdvancedShortcutActivity.EXTRA_ADVANCED_OPTIONS, options);
+ }
+ activity.startActivity(editorPanel);
}
private static void downloadShortcutApk(Activity activity, NetworkResponse response, Object item) {
JSONObject data = null;
try {
data = new JSONObject(new String(response.data));
+ Log.d(TAG, data.toString());
if (data.getBoolean(KEY_BUILD_STATUS)) {
String downloadLink = data.getJSONObject(KEY_APP_OBJ).getString(KEY_DOWNLOAD_URL);
ApkDownloadHelper apkDownloadHelper = new ApkDownloadHelper(activity);
@@ -88,27 +114,31 @@ private static void downloadShortcutApk(Activity activity, NetworkResponse respo
if (activity == null) {
throw new NullPointerException("Activity variable doesn't exist");
}
- apkDownloadHelper.startDownload(downloadLink,
- ((ResolveInfo) item).activityInfo.applicationInfo
- .loadLabel(activity.getPackageManager()).toString());
+ apkDownloadHelper.startDownload(downloadLink);
} else {
Toast.makeText(activity, R.string.err_build_failed, Toast.LENGTH_SHORT).show();
}
} catch (JSONException e) {
e.printStackTrace();
} catch (NullPointerException e) {
- throw new NullPointerException(e.getMessage() +
- "\nSomething odd is happening for " +
- ((ResolveInfo) item).activityInfo.packageName
- + "\n" + data.toString());
+ if (item instanceof ResolveInfo) {
+ throw new NullPointerException(e.getMessage() +
+ "\nSomething odd is happening for " +
+ ((ResolveInfo) item).activityInfo.packageName
+ + "\n" + data.toString());
+ } else {
+ throw new NullPointerException(e.getMessage() +
+ "\nSomething odd is happening"
+ + "\n" + data.toString());
+ }
}
}
- private static void generateShortcut(final Activity activity, final ResolveInfo resolveInfo) {
+ public static void generateShortcut(final Activity activity, final ResolveInfo resolveInfo) {
generateShortcut(activity, resolveInfo, new AdvancedOptions(activity));
}
- private static void generateShortcut(final Activity activity, final ResolveInfo resolveInfo,
+ public static void generateShortcut(final Activity activity, final ResolveInfo resolveInfo,
final AdvancedOptions options) {
if (!options.isReady()) {
// Delay until we complete all web operations
@@ -123,13 +153,15 @@ public void run() {
Toast.makeText(activity,
R.string.msg_pls_wait,
Toast.LENGTH_SHORT).show();
+
+ final InterstitialAd video = showVisualAd(activity);
+
ShortcutPostTask.generateShortcut(activity,
resolveInfo,
options,
new ShortcutPostTask.Callback() {
@Override
public void onResponse(NetworkResponse response) {
- // TODO Hide ad
downloadShortcutApk(activity, response, resolveInfo);
}
@@ -140,6 +172,33 @@ public void onError(VolleyError error) {
Toast.LENGTH_SHORT).show();
}
});
- // TODO Show visual ad
+ }
+
+ static InterstitialAd showVisualAd(Activity activity) {
+ final InterstitialAd video = new InterstitialAd(activity);
+ video.setAdUnitId(activity.getString(R.string.interstitial_ad_unit_id));
+ AdRequest adRequest = new AdRequest.Builder()
+ .addTestDevice(AdRequest.DEVICE_ID_EMULATOR)
+ .build();
+ video.loadAd(adRequest);
+ Log.d(TAG, "Loading ad");
+
+ video.setAdListener(new AdListener() {
+ @Override
+ public void onAdLoaded() {
+ super.onAdLoaded();
+ Log.d(TAG, "Ad loaded");
+ // Show video as soon as possible
+ video.show();
+ }
+
+ @Override
+ public void onAdClosed() {
+ super.onAdClosed();
+ Log.d(TAG, "Ad closed");
+ }
+ });
+
+ return video;
}
}
diff --git a/app/src/main/java/news/androidtv/tvapprepo/utils/ShortcutPostTask.java b/app/src/main/java/news/androidtv/tvapprepo/utils/ShortcutPostTask.java
index d4a197a..1d9e02b 100644
--- a/app/src/main/java/news/androidtv/tvapprepo/utils/ShortcutPostTask.java
+++ b/app/src/main/java/news/androidtv/tvapprepo/utils/ShortcutPostTask.java
@@ -54,12 +54,14 @@ public class ShortcutPostTask {
private static final String TAG = ShortcutPostTask.class.getSimpleName();
private static final String SUBMISSION_URL =
- "http://atvlauncher.trekgonewild.de/index_test.php";
+ "http://atvlauncher.trekgonewild.de/index_tvapprepo.php";
private static final String FORM_APP_NAME = "app_name";
private static final String FORM_APP_PACKAGE = "app_package";
private static final String FORM_APP_CATEGORY = "app_category";
private static final String FORM_APP_LOGO = "app_logo";
private static final String FORM_APP_BANNER = "app_banner";
+ private static final String FORM_UNIQUE_PACKAGE_NAME = "unique";
+ private static final String FORM_INTENT_URI = "app_intent";
private static final String FORM_JSON = "json";
public static final String CATEGORY_GAMES = "games";
public static final String CATEGORY_APPS = "apps";
@@ -94,12 +96,23 @@ public void onErrorResponse(VolleyError error) {
@Override
protected Map getParams(){
Map params = new HashMap<>();
- params.put(FORM_APP_NAME, app.activityInfo.loadLabel(context.getPackageManager()).toString());
- params.put(FORM_APP_PACKAGE, app.activityInfo.applicationInfo.packageName);
- params.put(FORM_APP_CATEGORY, CATEGORY_APPS);
+ if (app != null) {
+ params.put(FORM_APP_NAME, app.activityInfo.loadLabel(context.getPackageManager()).toString());
+ params.put(FORM_APP_PACKAGE, app.activityInfo.applicationInfo.packageName);
+ } else if (!options.getCustomLabel().isEmpty()) {
+ params.put(FORM_APP_NAME, options.getCustomLabel());
+ params.put(FORM_APP_PACKAGE, "news.androidtv.tvapprepo"); // Use our own package name
+ options.setUniquePackageName(true); // Force to unique.
+ }
if (!options.getCategory().isEmpty()) {
params.put(FORM_APP_CATEGORY, options.getCategory());
+ } else {
+ params.put(FORM_APP_CATEGORY, CATEGORY_APPS);
+ }
+ if (!options.getIntentUri().isEmpty()) {
+ params.put(FORM_INTENT_URI, options.getIntentUri());
}
+ params.put(FORM_UNIQUE_PACKAGE_NAME, options.isUnique()+"");
params.put(FORM_JSON, "true");
return params;
}
@@ -109,10 +122,15 @@ protected Map getByteData() {
Map params = new HashMap<>();
// file name could found file base or direct access from real path
// for now just get bitmap data from ImageView
- params.put(FORM_APP_LOGO, new DataPart("file_avatar.png",
- VolleyMultipartRequest.getFileDataFromDrawable(context,
- app.activityInfo.loadIcon(context.getPackageManager())),
- "image/png"));
+ if (options.getIcon() != null) {
+ params.put(FORM_APP_LOGO, new DataPart("file_avatar.png", options.getIcon(),
+ "image/png"));
+ } else if (app != null) {
+ params.put(FORM_APP_LOGO, new DataPart("file_avatar.png",
+ VolleyMultipartRequest.getFileDataFromDrawable(context,
+ app.activityInfo.loadIcon(context.getPackageManager())),
+ "image/png"));
+ }
if (options.getBanner() != null) {
params.put(FORM_APP_BANNER, new DataPart("file_avatar.png", options.getBanner(),
"image/png"));
diff --git a/app/src/main/res/anim/side_panel_enter.xml b/app/src/main/res/anim/side_panel_enter.xml
new file mode 100644
index 0000000..a7e036d
--- /dev/null
+++ b/app/src/main/res/anim/side_panel_enter.xml
@@ -0,0 +1,13 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/anim/side_panel_exit.xml b/app/src/main/res/anim/side_panel_exit.xml
new file mode 100644
index 0000000..1b31cd8
--- /dev/null
+++ b/app/src/main/res/anim/side_panel_exit.xml
@@ -0,0 +1,13 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/drawable/deep_link.png b/app/src/main/res/drawable/deep_link.png
new file mode 100644
index 0000000..6721f48
Binary files /dev/null and b/app/src/main/res/drawable/deep_link.png differ
diff --git a/app/src/main/res/drawable/file_location.png b/app/src/main/res/drawable/file_location.png
new file mode 100644
index 0000000..aa1bfdd
Binary files /dev/null and b/app/src/main/res/drawable/file_location.png differ
diff --git a/app/src/main/res/drawable/folder.png b/app/src/main/res/drawable/folder.png
new file mode 100644
index 0000000..4df08dc
Binary files /dev/null and b/app/src/main/res/drawable/folder.png differ
diff --git a/app/src/main/res/drawable/sys_settings.png b/app/src/main/res/drawable/sys_settings.png
new file mode 100644
index 0000000..b6b42af
Binary files /dev/null and b/app/src/main/res/drawable/sys_settings.png differ
diff --git a/app/src/main/res/drawable/web_bookmark.png b/app/src/main/res/drawable/web_bookmark.png
new file mode 100644
index 0000000..f0a4cd4
Binary files /dev/null and b/app/src/main/res/drawable/web_bookmark.png differ
diff --git a/app/src/main/res/layout/activity_advanced.xml b/app/src/main/res/layout/activity_advanced.xml
new file mode 100644
index 0000000..228b17e
--- /dev/null
+++ b/app/src/main/res/layout/activity_advanced.xml
@@ -0,0 +1,53 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/dialog_app_shortcut_editor.xml b/app/src/main/res/layout/dialog_app_shortcut_editor.xml
index c48d7fe..e4df194 100644
--- a/app/src/main/res/layout/dialog_app_shortcut_editor.xml
+++ b/app/src/main/res/layout/dialog_app_shortcut_editor.xml
@@ -8,6 +8,12 @@
android:layout_width="match_parent"
android:hint="Banner URL (Optional)"
android:id="@+id/edit_banner"
+ android:visibility="gone"
+ android:layout_height="wrap_content" />
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/dialog_folder.xml b/app/src/main/res/layout/dialog_folder.xml
new file mode 100644
index 0000000..b867ce1
--- /dev/null
+++ b/app/src/main/res/layout/dialog_folder.xml
@@ -0,0 +1,23 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/dialog_web_bookmark.xml b/app/src/main/res/layout/dialog_web_bookmark.xml
new file mode 100644
index 0000000..994c430
--- /dev/null
+++ b/app/src/main/res/layout/dialog_web_bookmark.xml
@@ -0,0 +1,22 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/values/admob.xml b/app/src/main/res/values/admob.xml
new file mode 100644
index 0000000..a620c47
--- /dev/null
+++ b/app/src/main/res/values/admob.xml
@@ -0,0 +1,5 @@
+
+
+ ca-app-pub-1944443832257008/3772577578
+ ca-app-pub-1944443832257008~2295844375
+
\ No newline at end of file
diff --git a/app/src/main/res/values/bools.xml b/app/src/main/res/values/bools.xml
index 2a4f921..3a727b2 100644
--- a/app/src/main/res/values/bools.xml
+++ b/app/src/main/res/values/bools.xml
@@ -3,4 +3,10 @@
true
true
true
+ true
+ true
+ false
+ false
+ false
+ false
\ No newline at end of file
diff --git a/app/src/main/res/values/colors.xml b/app/src/main/res/values/colors.xml
index e0dc68a..fa48260 100644
--- a/app/src/main/res/values/colors.xml
+++ b/app/src/main/res/values/colors.xml
@@ -1,7 +1,7 @@
#000000
#DDDDDD
- #0096a6
+ #00695C
#ffaa3f
#ffaa3f
#0096a6
@@ -16,6 +16,8 @@
#EEFF41
#3d3d3d
- @color/default_background
- @color/orange
+ #2E7D32
+ #4CAF50
+ #E0E0E0
+ #333333
diff --git a/app/src/main/res/values/dimens.xml b/app/src/main/res/values/dimens.xml
new file mode 100644
index 0000000..e423c0a
--- /dev/null
+++ b/app/src/main/res/values/dimens.xml
@@ -0,0 +1,14 @@
+
+
+ 360dp
+ 32dp
+ 56dp
+ 8dp
+ 87dp
+ 27dp
+ 4dp
+ 24sp
+ 16sp
+ 11dp
+ 12.5dp
+
\ No newline at end of file
diff --git a/app/src/main/res/values/integers.xml b/app/src/main/res/values/integers.xml
new file mode 100644
index 0000000..823293b
--- /dev/null
+++ b/app/src/main/res/values/integers.xml
@@ -0,0 +1,4 @@
+
+
+ 500
+
\ No newline at end of file
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index 6bdc5a9..ad2c134 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -8,25 +8,45 @@
apk_string
shouldStart
+ Downloaded APKs
+ Custom Shortcuts
+ More
+ Browse
+
+ Update
+ Uninstall
+ Install
+ Dismiss
+
+ Generate Web Bookmark
+ Generate Shortcut
+ Generate Folder
+ Shortcut to Settings
+ Activity Deep Link
+
+ Enter a URL and a shortcut will be created to open that in any third-party web browser.
+ A folder will appear on the side of the screen with a list of apps inside. You can continue to change the apps in the folder, but the title cannot change.
+
+
Media loading timed out
Media server was not reachable
Failed to load video
An error occurred
- Dismiss
Oops
- Update
- Uninstall
- Install
+
This app is not optimized for TVs.
Leanback app
+
Download Complete
Invalid Tag
Error starting intent
Error - Download Failed
- Directory
Error deleting file
- Downloaded APKs
+ Build failed
+ Build failed: %1$s
+
+ Directory
Install through tag
#SIDELOADTAG
Starting download…
@@ -34,8 +54,7 @@
Credits
Built by ITV Lab.\n\nUses open source code from #SIDELOADTAG.
There is an update for the Tv App Repo
- More
- Browse
+
Debug variant
"There are several different versions of this app for various purposes.\n\n- Open Community: This version enables APK downloads from a central APK host. It can be downloaded on GitHub, but is not available on Google Play.\n\n- Play Store: This version is distributed on the Play Store, and includes every feature except the central APK host.\n\n- Debug: Unsigned version of the app."
Clean Downloads Folder
@@ -56,7 +75,7 @@
Advanced
Cancel
Advanced Options
- Build failed
- Build failed: %1$s
Please wait. This may take up to 20 seconds.
+ File Link
+
diff --git a/app/src/main/res/values/styles.xml b/app/src/main/res/values/styles.xml
index 48538e6..a185f81 100644
--- a/app/src/main/res/values/styles.xml
+++ b/app/src/main/res/values/styles.xml
@@ -19,6 +19,17 @@
- true
- @android:color/transparent
- true
- - @color/fastlane_background
+ - @color/primary
+
+
+
+
+
diff --git a/promo/graphics/deep-link.psd b/promo/graphics/deep-link.psd
new file mode 100644
index 0000000..8c7d1ac
Binary files /dev/null and b/promo/graphics/deep-link.psd differ
diff --git a/promo/graphics/file-location.psd b/promo/graphics/file-location.psd
new file mode 100644
index 0000000..ca4d929
Binary files /dev/null and b/promo/graphics/file-location.psd differ
diff --git a/promo/graphics/folder.psd b/promo/graphics/folder.psd
new file mode 100644
index 0000000..c3c48b8
Binary files /dev/null and b/promo/graphics/folder.psd differ
diff --git a/promo/graphics/sys_settings.psd b/promo/graphics/sys_settings.psd
new file mode 100644
index 0000000..d9edb57
Binary files /dev/null and b/promo/graphics/sys_settings.psd differ
diff --git a/promo/graphics/web_bookmark.psd b/promo/graphics/web_bookmark.psd
new file mode 100644
index 0000000..89d8198
Binary files /dev/null and b/promo/graphics/web_bookmark.psd differ
diff --git a/promo/screenshots/device-2017-04-22-214027.png b/promo/screenshots/device-2017-04-22-214027.png
new file mode 100644
index 0000000..065ea4e
Binary files /dev/null and b/promo/screenshots/device-2017-04-22-214027.png differ
diff --git a/promo/screenshots/device-2017-04-23-191739.png b/promo/screenshots/device-2017-04-23-191739.png
new file mode 100644
index 0000000..00574ba
Binary files /dev/null and b/promo/screenshots/device-2017-04-23-191739.png differ
diff --git a/promo/screenshots/device-2017-04-23-192613.png b/promo/screenshots/device-2017-04-23-192613.png
new file mode 100644
index 0000000..516736d
Binary files /dev/null and b/promo/screenshots/device-2017-04-23-192613.png differ
diff --git a/promo/screenshots/device-2017-04-24-215436.png b/promo/screenshots/device-2017-04-24-215436.png
new file mode 100644
index 0000000..6b5cdff
Binary files /dev/null and b/promo/screenshots/device-2017-04-24-215436.png differ
diff --git a/promo/screenshots/device-2017-04-24-221554.png b/promo/screenshots/device-2017-04-24-221554.png
new file mode 100644
index 0000000..21010ac
Binary files /dev/null and b/promo/screenshots/device-2017-04-24-221554.png differ
diff --git a/promo/screenshots/device-2017-04-24-221608.png b/promo/screenshots/device-2017-04-24-221608.png
new file mode 100644
index 0000000..3a8d173
Binary files /dev/null and b/promo/screenshots/device-2017-04-24-221608.png differ
diff --git a/promo/screenshots/device-2017-04-24-225659.png b/promo/screenshots/device-2017-04-24-225659.png
new file mode 100644
index 0000000..3959275
Binary files /dev/null and b/promo/screenshots/device-2017-04-24-225659.png differ