diff --git a/.idea/modules.xml b/.idea/modules.xml index deb8081..5fd4831 100644 --- a/.idea/modules.xml +++ b/.idea/modules.xml @@ -4,6 +4,7 @@ + \ No newline at end of file diff --git a/app/build.gradle b/app/build.gradle index b090563..c9f2ddd 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -8,8 +8,9 @@ android { applicationId "news.androidtv.tvapprepo" minSdkVersion 21 targetSdkVersion 25 - versionCode 16 - versionName "1.1.3" + versionCode 17 + versionName "1.1.4" + testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" } buildTypes { release { @@ -73,6 +74,11 @@ dependencies { compile('com.crashlytics.sdk.android:crashlytics:2.6.5@aar') { transitive = true; } + + androidTestCompile 'com.android.support:support-annotations:25.3.1' + androidTestCompile 'com.android.support.test:runner:0.5' + androidTestCompile 'com.android.support.test:rules:0.5' + androidTestCompile 'com.android.support.test.espresso:espresso-core:2.2.2' } apply plugin: 'com.google.gms.google-services' diff --git a/app/src/androidTest/java/news/androidtv/tvapprepo/ApplicationTest.java b/app/src/androidTest/java/news/androidtv/tvapprepo/IntentsInstrumentationTest.java similarity index 87% rename from app/src/androidTest/java/news/androidtv/tvapprepo/ApplicationTest.java rename to app/src/androidTest/java/news/androidtv/tvapprepo/IntentsInstrumentationTest.java index 35db6e0..1f086b9 100644 --- a/app/src/androidTest/java/news/androidtv/tvapprepo/ApplicationTest.java +++ b/app/src/androidTest/java/news/androidtv/tvapprepo/IntentsInstrumentationTest.java @@ -1,52 +1,51 @@ -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)); - } +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 news.androidtv.tvapprepo.intents.IntentUriGenerator; + +/** + * Testing Fundamentals + */ +public class IntentsInstrumentationTest extends ApplicationTestCase { + public static final String TAG = IntentsInstrumentationTest.class.getSimpleName(); + + public IntentsInstrumentationTest() { + 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/androidTest/java/news/androidtv/tvapprepo/ShortcutGenerationInstrumentationTest.java b/app/src/androidTest/java/news/androidtv/tvapprepo/ShortcutGenerationInstrumentationTest.java new file mode 100644 index 0000000..71665fc --- /dev/null +++ b/app/src/androidTest/java/news/androidtv/tvapprepo/ShortcutGenerationInstrumentationTest.java @@ -0,0 +1,205 @@ +package news.androidtv.tvapprepo; + +import android.content.pm.ActivityInfo; +import android.content.pm.ApplicationInfo; +import android.content.pm.ResolveInfo; +import android.graphics.Bitmap; +import android.support.test.rule.ActivityTestRule; +import android.support.test.runner.AndroidJUnit4; +import android.util.Log; + +import com.android.volley.VolleyError; +import com.bumptech.glide.Glide; + +import org.junit.Assert; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; + +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.TimeUnit; + +import news.androidtv.tvapprepo.activities.MainActivity; +import news.androidtv.tvapprepo.intents.IntentUriGenerator; +import news.androidtv.tvapprepo.model.AdvancedOptions; +import news.androidtv.tvapprepo.utils.GenerateShortcutHelper; + +/** + * A series of tests relating to generating shortcut apks. + * Right now all tests must be verified manually. + */ +@RunWith(AndroidJUnit4.class) +public class ShortcutGenerationInstrumentationTest { + @Rule + public ActivityTestRule mActivityRule = new ActivityTestRule<>( + MainActivity.class); + + private static final String TAG = ShortcutGenerationInstrumentationTest.class.getSimpleName(); + private static final int TIMEOUT_LENGTH = 90; + private static final TimeUnit TIMEOUT_UNITS = TimeUnit.SECONDS; + private static final ResolveInfo SAMPLE_APK; + private static final String SAMPLE_BANNER = "http://via.placeholder.com/320x180"; + + static { + SAMPLE_APK = new ResolveInfo(); + SAMPLE_APK.activityInfo = new ActivityInfo(); + SAMPLE_APK.activityInfo.applicationInfo = new ApplicationInfo(); + SAMPLE_APK.activityInfo.applicationInfo.packageName = "com.test"; + SAMPLE_APK.activityInfo.nonLocalizedLabel = "Test Activity Label"; + SAMPLE_APK.activityInfo.name = "Test Activity Name"; + SAMPLE_APK.icon = R.drawable.ic_launcher; + } + + private void doOnMainThread(Runnable r) { + mActivityRule.getActivity().runOnUiThread(r); + } + + /** + * Generates an APK shortcut from an on-device APK without advanced options. + */ + @Test + public void testSimpleApkGeneration() throws InterruptedException { + // Set up a CountdownLatch to accommodate for network events + final CountDownLatch latch = new CountDownLatch(1); + + doOnMainThread(new Runnable() { + @Override + public void run() { + GenerateShortcutHelper.generateShortcut(mActivityRule.getActivity(), SAMPLE_APK, + new AdvancedOptions(mActivityRule.getActivity()), new GenerateShortcutHelper.Callback() { + @Override + public void onResponseComplete(String response) { + latch.countDown(); + } + + @Override + public void onResponseFailed(VolleyError error) { + throw new RuntimeException(error.getMessage()); + } + }); + } + }); + + Assert.assertTrue(latch.await(TIMEOUT_LENGTH, TIMEOUT_UNITS)); + } + + @Test + public void testCustomBannerApkGeneration() throws InterruptedException { + // Set up a CountdownLatch to accommodate for network events + final CountDownLatch latch = new CountDownLatch(1); + final AdvancedOptions advancedOptions = new AdvancedOptions(mActivityRule.getActivity()); + advancedOptions.setBannerUrl(SAMPLE_BANNER); + + doOnMainThread(new Runnable() { + @Override + public void run() { + GenerateShortcutHelper.generateShortcut(mActivityRule.getActivity(), SAMPLE_APK, + advancedOptions, new GenerateShortcutHelper.Callback() { + @Override + public void onResponseComplete(String response) { + Log.d(TAG, response); + latch.countDown(); + } + + @Override + public void onResponseFailed(VolleyError error) { + throw new RuntimeException(error.getMessage()); + } + }); + } + }); + + Assert.assertTrue(latch.await(TIMEOUT_LENGTH, TIMEOUT_UNITS)); + } + + @Test + public void testGameApkGeneration() throws InterruptedException { + // Set up a CountdownLatch to accommodate for network events + final CountDownLatch latch = new CountDownLatch(1); + final AdvancedOptions advancedOptions = new AdvancedOptions(mActivityRule.getActivity()); + advancedOptions.setIsGame(true); + + doOnMainThread(new Runnable() { + @Override + public void run() { + GenerateShortcutHelper.generateShortcut(mActivityRule.getActivity(), SAMPLE_APK, + advancedOptions, new GenerateShortcutHelper.Callback() { + @Override + public void onResponseComplete(String response) { + latch.countDown(); + } + + @Override + public void onResponseFailed(VolleyError error) { + throw new RuntimeException(error.getMessage()); + } + }); + } + }); + + Assert.assertTrue(latch.await(TIMEOUT_LENGTH, TIMEOUT_UNITS)); + } + + @Test + public void testBannerBitmapApk() throws InterruptedException, ExecutionException { + // Set up a CountdownLatch to accommodate for network events + final CountDownLatch latch = new CountDownLatch(1); + final AdvancedOptions advancedOptions = new AdvancedOptions(mActivityRule.getActivity()); + Bitmap bitmap = Glide.with(mActivityRule.getActivity()).load(SAMPLE_BANNER).asBitmap() + .into(320, 180).get(); + advancedOptions.setBannerBitmap(bitmap); + + doOnMainThread(new Runnable() { + @Override + public void run() { + GenerateShortcutHelper.generateShortcut(mActivityRule.getActivity(), SAMPLE_APK, + advancedOptions, new GenerateShortcutHelper.Callback() { + @Override + public void onResponseComplete(String response) { + latch.countDown(); + } + + @Override + public void onResponseFailed(VolleyError error) { + throw new RuntimeException(error.getMessage()); + } + }); + } + }); + + Assert.assertTrue(latch.await(TIMEOUT_LENGTH, TIMEOUT_UNITS)); + } + + @Test + public void testWebShortcut() throws InterruptedException, ExecutionException { + // Set up a CountdownLatch to accommodate for network events + final CountDownLatch latch = new CountDownLatch(1); + String url = "http://example.com"; + String label = "Example.Com"; + final AdvancedOptions advancedOptions = new AdvancedOptions(mActivityRule.getActivity()) + .setIntentUri(IntentUriGenerator.generateWebBookmark(url)) + .setIconUrl("https://raw.githubusercontent.com/ITVlab/TvAppRepo/master/promo/graphics/icon.png") + .setCustomLabel(label); + + doOnMainThread(new Runnable() { + @Override + public void run() { + GenerateShortcutHelper.generateShortcut(mActivityRule.getActivity(), SAMPLE_APK, + advancedOptions, new GenerateShortcutHelper.Callback() { + @Override + public void onResponseComplete(String response) { + latch.countDown(); + } + + @Override + public void onResponseFailed(VolleyError error) { + throw new RuntimeException(error.getMessage()); + } + }); + } + }); + + Assert.assertTrue(latch.await(TIMEOUT_LENGTH, TIMEOUT_UNITS)); + } +} 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 1f12d8f..60fad95 100644 --- a/app/src/main/java/news/androidtv/tvapprepo/fragments/MainFragment.java +++ b/app/src/main/java/news/androidtv/tvapprepo/fragments/MainFragment.java @@ -145,6 +145,7 @@ public void onDestroy() { mBackgroundTimer.cancel(); } mApkDownloadHelper.removeListener(mDownloadListener); + mApkDownloadHelper.destroy(); } @Override 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 683f173..bd3ad4f 100644 --- a/app/src/main/java/news/androidtv/tvapprepo/utils/GenerateShortcutHelper.java +++ b/app/src/main/java/news/androidtv/tvapprepo/utils/GenerateShortcutHelper.java @@ -6,6 +6,7 @@ import android.content.pm.ResolveInfo; import android.os.Handler; import android.os.Looper; +import android.support.annotation.VisibleForTesting; import android.support.v7.app.AlertDialog; import android.util.Log; import android.view.ContextThemeWrapper; @@ -133,13 +134,19 @@ public static void generateShortcut(final Activity activity, final ResolveInfo r public static void generateShortcut(final Activity activity, final ResolveInfo resolveInfo, final AdvancedOptions options) { + generateShortcut(activity, resolveInfo, options, null); + } + + @VisibleForTesting + public static void generateShortcut(final Activity activity, final ResolveInfo resolveInfo, + final AdvancedOptions options, final Callback callback) { if (!options.isReady()) { // Delay until we complete all web operations new Handler(Looper.getMainLooper()).postDelayed(new Runnable() { @Override public void run() { Log.d(TAG, "Delaying until web ops are complete"); - generateShortcut(activity, resolveInfo, options); + generateShortcut(activity, resolveInfo, options, callback); } }, 200); return; @@ -157,7 +164,11 @@ public void run() { new ShortcutPostTask.Callback() { @Override public void onResponse(NetworkResponse response) { - downloadShortcutApk(activity, response, resolveInfo); + if (callback != null) { + callback.onResponseComplete(new String(response.data)); + } else { + downloadShortcutApk(activity, response, resolveInfo); + } } @Override @@ -165,6 +176,12 @@ public void onError(VolleyError error) { Toast.makeText(activity, activity.getString(R.string.err_build_failed_reason, error.getMessage()), Toast.LENGTH_SHORT).show(); + Toast.makeText(activity, + new String(error.networkResponse.data), + Toast.LENGTH_LONG).show(); + if (callback != null) { + callback.onResponseFailed(error); + } } }); } @@ -237,4 +254,10 @@ public void onRewardedVideoAdFailedToLoad(int i) { }); ad.loadAd(activity.getString(R.string.reward_video_ad_unit_id), new AdRequest.Builder().build()); } + + @VisibleForTesting + public interface Callback { + void onResponseComplete(String response); + void onResponseFailed(VolleyError error); + } } 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 bb03204..770e5cc 100644 --- a/app/src/main/java/news/androidtv/tvapprepo/utils/ShortcutPostTask.java +++ b/app/src/main/java/news/androidtv/tvapprepo/utils/ShortcutPostTask.java @@ -45,6 +45,8 @@ public class ShortcutPostTask { private static final String TAG = ShortcutPostTask.class.getSimpleName(); + private static final int TIMEOUT = 90 * 1000; // 90 seconds + private static final String SUBMISSION_URL = "http://atvlauncher.trekgonewild.de/index_tvapprepo.php"; private static final String FORM_APP_NAME = "app_name"; @@ -142,7 +144,7 @@ protected Map getByteData() { } catch (AuthFailureError authFailureError) { authFailureError.printStackTrace(); } - sr.setRetryPolicy(new DefaultRetryPolicy(60 * 1000, 1, 0)); + sr.setRetryPolicy(new DefaultRetryPolicy(TIMEOUT, 1, 0)); queue.add(sr); } diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index c76b76c..0e0629b 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -79,5 +79,5 @@ File Link Error - Volley cannot get file data for app icon Cannot find custom banner packs for non-apps - +