package com.mopub.common.util; import android.annotation.TargetApi; import android.app.Activity; import android.content.Context; import android.content.Intent; import android.content.pm.ActivityInfo; import android.content.pm.ApplicationInfo; import android.content.pm.ResolveInfo; import android.os.Build; import com.mopub.common.MoPubBrowser; import com.mopub.mobileads.MoPubActivity; import com.mopub.mobileads.MraidActivity; import com.mopub.mobileads.MraidVideoPlayerActivity; import org.junit.After; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mock; import org.mockito.invocation.InvocationOnMock; import org.mockito.stubbing.Answer; import org.robolectric.Robolectric; import org.robolectric.RobolectricTestRunner; import org.robolectric.annotation.Config; import org.robolectric.shadows.ShadowLog; import org.robolectric.shadows.ShadowToast; import java.util.List; import static org.fest.assertions.api.Assertions.assertThat; import static org.mockito.Matchers.any; import static org.mockito.Matchers.anyInt; import static org.mockito.Matchers.eq; import static org.mockito.Mockito.doAnswer; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.when; @RunWith(RobolectricTestRunner.class) public class ManifestUtilsTest { private Context context; private List<Class<? extends Activity>> requiredWebViewSdkActivities; private List<Class<? extends Activity>> requiredNativeSdkActivities; @Mock private ResolveInfo mockResolveInfo; @Before public void setUp() throws Exception { context = spy(new Activity()); requiredWebViewSdkActivities = ManifestUtils.getRequiredWebViewSdkActivities(); requiredNativeSdkActivities = ManifestUtils.getRequiredNativeSdkActivities(); setDebugMode(false); } @After public void tearDown() throws Exception { setDebugMode(false); // This may have been set to a mock during testing. Reset this class back to normal ManifestUtils.setFlagCheckUtil(new ManifestUtils.FlagCheckUtil()); } @Test public void checkWebViewSdkActivitiesDeclared_shouldIncludeFourActivityDeclarations() throws Exception { ShadowLog.setupLogging(); ManifestUtils.checkWebViewActivitiesDeclared(context); assertLogIncludes( "com.mopub.mobileads.MoPubActivity", "com.mopub.mobileads.MraidActivity", "com.mopub.mobileads.MraidVideoPlayerActivity", "com.mopub.common.MoPubBrowser" ); } @Test public void checkNativeSdkActivitiesDeclared_shouldIncludeOneActivityDeclaration() throws Exception { ShadowLog.setupLogging(); ManifestUtils.checkNativeActivitiesDeclared(context); assertLogIncludes("com.mopub.common.MoPubBrowser"); assertLogDoesntInclude( "com.mopub.mobileads.MoPubActivity", "com.mopub.mobileads.MraidActivity", "com.mopub.mobileads.MraidVideoPlayerActivity" ); } @Test public void displayWarningForMissingActivities_withAllActivitiesDeclared_shouldNotShowLogOrToast() throws Exception { Robolectric.packageManager.addResolveInfoForIntent(new Intent(context, MoPubActivity.class), mockResolveInfo); Robolectric.packageManager.addResolveInfoForIntent(new Intent(context, MraidActivity.class), mockResolveInfo); Robolectric.packageManager.addResolveInfoForIntent(new Intent(context, MraidVideoPlayerActivity.class), mockResolveInfo); Robolectric.packageManager.addResolveInfoForIntent(new Intent(context, MoPubBrowser.class), mockResolveInfo); ShadowLog.setupLogging(); setDebugMode(true); ManifestUtils.displayWarningForMissingActivities(context, requiredWebViewSdkActivities); assertThat(ShadowToast.getLatestToast()).isNull(); assertThat(ShadowLog.getLogs()).isEmpty(); } @Test public void displayWarningForMissingActivities_withOneMissingActivity_shouldLogOnlyThatOne() throws Exception { Robolectric.packageManager.addResolveInfoForIntent(new Intent(context, MoPubActivity.class), mockResolveInfo); Robolectric.packageManager.addResolveInfoForIntent(new Intent(context, MraidActivity.class), mockResolveInfo); Robolectric.packageManager.addResolveInfoForIntent(new Intent(context, MraidVideoPlayerActivity.class), mockResolveInfo); // Here, we leave out MoPubBrowser on purpose ShadowLog.setupLogging(); ManifestUtils.displayWarningForMissingActivities(context, requiredWebViewSdkActivities); assertLogIncludes("com.mopub.common.MoPubBrowser"); assertLogDoesntInclude( "com.mopub.mobileads.MoPubActivity", "com.mopub.mobileads.MraidActivity", "com.mopub.mobileads.MraidVideoPlayerActivity" ); } @Test public void displayWarningForMissingActivities_withAllMissingActivities_shouldLogMessage() throws Exception { setDebugMode(true); ShadowLog.setupLogging(); ManifestUtils.displayWarningForMissingActivities(context, requiredWebViewSdkActivities); final List<ShadowLog.LogItem> logs = ShadowLog.getLogs(); assertLogIncludes( "com.mopub.mobileads.MoPubActivity", "com.mopub.mobileads.MraidActivity", "com.mopub.mobileads.MraidVideoPlayerActivity", "com.mopub.common.MoPubBrowser" ); } @Test public void displayWarningForMissingActivities_withMissingActivities_withDebugTrue_shouldShowToast() throws Exception { setDebugMode(true); ManifestUtils.displayWarningForMissingActivities(context, requiredWebViewSdkActivities); assertThat(ShadowToast.getLatestToast()).isNotNull(); final String toastText = ShadowToast.getTextOfLatestToast(); assertThat(toastText).isEqualTo("ERROR: YOUR MOPUB INTEGRATION IS INCOMPLETE.\nCheck logcat and update your AndroidManifest.xml with the correct activities and configuration."); } @Test public void displayWarningForMissingActivities_withMissingActivities_withDebugFalse_shouldNotShowToast() throws Exception { setDebugMode(false); ManifestUtils.displayWarningForMissingActivities(context, requiredWebViewSdkActivities); assertThat(ShadowToast.getLatestToast()).isNull(); } @SuppressWarnings("unchecked") @TargetApi(13) @Test public void displayWarningForMisconfiguredActivities_withAllActivitiesConfigured_shouldNotLogOrShowToast() throws Exception { ManifestUtils.FlagCheckUtil mockActivitiyConfigCheck = mock(ManifestUtils.FlagCheckUtil.class); when(mockActivitiyConfigCheck.hasFlag(any(Class.class), anyInt(), eq(ActivityInfo.CONFIG_KEYBOARD_HIDDEN))).thenReturn(true); when(mockActivitiyConfigCheck.hasFlag(any(Class.class), anyInt(), eq(ActivityInfo.CONFIG_ORIENTATION))).thenReturn(true); when(mockActivitiyConfigCheck.hasFlag(any(Class.class), anyInt(), eq(ActivityInfo.CONFIG_SCREEN_SIZE))).thenReturn(true); ManifestUtils.setFlagCheckUtil(mockActivitiyConfigCheck); Robolectric.packageManager.addResolveInfoForIntent(new Intent(context, MoPubActivity.class), mockResolveInfo); Robolectric.packageManager.addResolveInfoForIntent(new Intent(context, MraidActivity.class), mockResolveInfo); Robolectric.packageManager.addResolveInfoForIntent(new Intent(context, MraidVideoPlayerActivity.class), mockResolveInfo); Robolectric.packageManager.addResolveInfoForIntent(new Intent(context, MoPubBrowser.class), mockResolveInfo); ShadowLog.setupLogging(); setDebugMode(true); ManifestUtils.displayWarningForMisconfiguredActivities(context, requiredWebViewSdkActivities); assertThat(ShadowToast.getLatestToast()).isNull(); assertThat(ShadowLog.getLogs()).isEmpty(); } @SuppressWarnings("unchecked") @TargetApi(13) @Test public void displayWarningForMisconfiguredActivities_withOneMisconfiguredActivity_shouldLogOnlyThatOne() throws Exception { ManifestUtils.FlagCheckUtil mockActivitiyConfigCheck = mock(ManifestUtils.FlagCheckUtil.class); // Misconfigure the first activity; only return false if the activity is MoPubActivity doAnswer(new Answer() { @Override public Object answer(InvocationOnMock invocationOnMock) throws Throwable { Object[] args = invocationOnMock.getArguments(); return MoPubActivity.class != args[0]; } }).when(mockActivitiyConfigCheck).hasFlag(any(Class.class), anyInt(), eq(ActivityInfo.CONFIG_KEYBOARD_HIDDEN)); when(mockActivitiyConfigCheck.hasFlag(any(Class.class), anyInt(), eq(ActivityInfo.CONFIG_ORIENTATION))).thenReturn(true); when(mockActivitiyConfigCheck.hasFlag(any(Class.class), anyInt(), eq(ActivityInfo.CONFIG_SCREEN_SIZE))).thenReturn(true); ManifestUtils.setFlagCheckUtil(mockActivitiyConfigCheck); Robolectric.packageManager.addResolveInfoForIntent(new Intent(context, MoPubActivity.class), mockResolveInfo); Robolectric.packageManager.addResolveInfoForIntent(new Intent(context, MraidActivity.class), mockResolveInfo); Robolectric.packageManager.addResolveInfoForIntent(new Intent(context, MraidVideoPlayerActivity.class), mockResolveInfo); Robolectric.packageManager.addResolveInfoForIntent(new Intent(context, MoPubBrowser.class), mockResolveInfo); ShadowLog.setupLogging(); ManifestUtils.displayWarningForMisconfiguredActivities(context, requiredWebViewSdkActivities); assertLogIncludes("com.mopub.mobileads.MoPubActivity"); assertLogIncludes("The android:configChanges param for activity " + MoPubActivity.class.getName() + " must include keyboardHidden."); assertLogDoesntInclude( "com.mopub.mobileads.MraidActivity", "com.mopub.mobileads.MraidVideoPlayerActivity", "com.mopub.common.MoPubBrowser" ); assertLogDoesntInclude("The android:configChanges param for activity " + MoPubActivity.class.getName() + " must include orientation."); assertLogDoesntInclude("The android:configChanges param for activity " + MoPubActivity.class.getName() + " must include screenSize."); } @SuppressWarnings("unchecked") @TargetApi(13) @Test public void displayWarningForMisconfiguredActivities_withOneMisconfiguredActivity_withMissingAllConfigChangesValues_shouldLogAllConfigChangesValues() throws Exception { ManifestUtils.FlagCheckUtil mockActivitiyConfigCheck = mock(ManifestUtils.FlagCheckUtil.class); when(mockActivitiyConfigCheck.hasFlag(any(Class.class), anyInt(), eq(ActivityInfo.CONFIG_KEYBOARD_HIDDEN))).thenReturn(false); when(mockActivitiyConfigCheck.hasFlag(any(Class.class), anyInt(), eq(ActivityInfo.CONFIG_ORIENTATION))).thenReturn(false); when(mockActivitiyConfigCheck.hasFlag(any(Class.class), anyInt(), eq(ActivityInfo.CONFIG_SCREEN_SIZE))).thenReturn(false); ManifestUtils.setFlagCheckUtil(mockActivitiyConfigCheck); Robolectric.packageManager.addResolveInfoForIntent(new Intent(context, MoPubActivity.class), mockResolveInfo); ShadowLog.setupLogging(); ManifestUtils.displayWarningForMisconfiguredActivities(context, requiredWebViewSdkActivities); assertLogIncludes("The android:configChanges param for activity " + MoPubActivity.class.getName() + " must include keyboardHidden."); assertLogIncludes("The android:configChanges param for activity " + MoPubActivity.class.getName() + " must include orientation."); assertLogIncludes("The android:configChanges param for activity " + MoPubActivity.class.getName() + " must include screenSize."); } @SuppressWarnings("unchecked") @Config(reportSdk = Build.VERSION_CODES.HONEYCOMB_MR1) @TargetApi(13) @Test public void displayWarningForMisconfiguredActivities_withMissingScreenSize_withApiLessThan13_shouldNotLogOrShowToast() throws Exception { ManifestUtils.FlagCheckUtil mockActivitiyConfigCheck = mock(ManifestUtils.FlagCheckUtil.class); when(mockActivitiyConfigCheck.hasFlag(any(Class.class), anyInt(), eq(ActivityInfo.CONFIG_KEYBOARD_HIDDEN))).thenReturn(true); when(mockActivitiyConfigCheck.hasFlag(any(Class.class), anyInt(), eq(ActivityInfo.CONFIG_ORIENTATION))).thenReturn(true); when(mockActivitiyConfigCheck.hasFlag(any(Class.class), anyInt(), eq(ActivityInfo.CONFIG_SCREEN_SIZE))).thenReturn(false); ManifestUtils.setFlagCheckUtil(mockActivitiyConfigCheck); Robolectric.packageManager.addResolveInfoForIntent(new Intent(context, MoPubActivity.class), mockResolveInfo); ShadowLog.setupLogging(); setDebugMode(true); ManifestUtils.displayWarningForMisconfiguredActivities(context, requiredWebViewSdkActivities); assertThat(ShadowToast.getLatestToast()).isNull(); assertThat(ShadowLog.getLogs()).isEmpty(); } @SuppressWarnings("unchecked") @TargetApi(13) @Test public void displayWarningForMisconfiguredActivities_withMissingScreenSize_withTargetApiLessThan13_shouldNotLogOrShowToast() throws Exception { // Set target API to < 13 ApplicationInfo applicationInfo = context.getApplicationInfo(); applicationInfo.targetSdkVersion = Build.VERSION_CODES.HONEYCOMB_MR1; when(context.getApplicationInfo()).thenReturn(applicationInfo); ManifestUtils.FlagCheckUtil mockActivitiyConfigCheck = mock(ManifestUtils.FlagCheckUtil.class); when(mockActivitiyConfigCheck.hasFlag(any(Class.class), anyInt(), eq(ActivityInfo.CONFIG_KEYBOARD_HIDDEN))).thenReturn(true); when(mockActivitiyConfigCheck.hasFlag(any(Class.class), anyInt(), eq(ActivityInfo.CONFIG_ORIENTATION))).thenReturn(true); when(mockActivitiyConfigCheck.hasFlag(any(Class.class), anyInt(), eq(ActivityInfo.CONFIG_SCREEN_SIZE))).thenReturn(false); ManifestUtils.setFlagCheckUtil(mockActivitiyConfigCheck); Robolectric.packageManager.addResolveInfoForIntent(new Intent(context, MoPubActivity.class), mockResolveInfo); ShadowLog.setupLogging(); setDebugMode(true); ManifestUtils.displayWarningForMisconfiguredActivities(context, requiredWebViewSdkActivities); assertThat(ShadowToast.getLatestToast()).isNull(); assertThat(ShadowLog.getLogs()).isEmpty(); } @SuppressWarnings("unchecked") @TargetApi(13) @Test public void displayWarningForMisconfiguredActivities_withMisconfiguredActivities_withDebugTrue_shouldShowToast() throws Exception { ManifestUtils.FlagCheckUtil mockActivitiyConfigCheck = mock(ManifestUtils.FlagCheckUtil.class); when(mockActivitiyConfigCheck.hasFlag(any(Class.class), anyInt(), eq(ActivityInfo.CONFIG_KEYBOARD_HIDDEN))).thenReturn(false); when(mockActivitiyConfigCheck.hasFlag(any(Class.class), anyInt(), eq(ActivityInfo.CONFIG_ORIENTATION))).thenReturn(false); when(mockActivitiyConfigCheck.hasFlag(any(Class.class), anyInt(), eq(ActivityInfo.CONFIG_SCREEN_SIZE))).thenReturn(false); ManifestUtils.setFlagCheckUtil(mockActivitiyConfigCheck); Robolectric.packageManager.addResolveInfoForIntent(new Intent(context, MoPubActivity.class), mockResolveInfo); setDebugMode(true); ManifestUtils.displayWarningForMisconfiguredActivities(context, requiredWebViewSdkActivities); assertThat(ShadowToast.getLatestToast()).isNotNull(); final String toastText = ShadowToast.getTextOfLatestToast(); assertThat(toastText).isEqualTo("ERROR: YOUR MOPUB INTEGRATION IS INCOMPLETE.\nCheck logcat and update your AndroidManifest.xml with the correct activities and configuration."); } @SuppressWarnings("unchecked") @TargetApi(13) @Test public void displayWarningForMisconfiguredActivities_withMisconfiguredActivities_withDebugFalse_shouldNotShowToast() throws Exception { ManifestUtils.FlagCheckUtil mockActivitiyConfigCheck = mock(ManifestUtils.FlagCheckUtil.class); when(mockActivitiyConfigCheck.hasFlag(any(Class.class), anyInt(), eq(ActivityInfo.CONFIG_KEYBOARD_HIDDEN))).thenReturn(false); when(mockActivitiyConfigCheck.hasFlag(any(Class.class), anyInt(), eq(ActivityInfo.CONFIG_ORIENTATION))).thenReturn(false); when(mockActivitiyConfigCheck.hasFlag(any(Class.class), anyInt(), eq(ActivityInfo.CONFIG_SCREEN_SIZE))).thenReturn(false); ManifestUtils.setFlagCheckUtil(mockActivitiyConfigCheck); Robolectric.packageManager.addResolveInfoForIntent(new Intent(context, MoPubActivity.class), mockResolveInfo); setDebugMode(false); ManifestUtils.displayWarningForMissingActivities(context, requiredWebViewSdkActivities); assertThat(ShadowToast.getLatestToast()).isNull(); } @Test public void isDebuggable_whenApplicationIsDebuggable_shouldReturnTrue() throws Exception { setDebugMode(true); assertThat(ManifestUtils.isDebuggable(context)).isTrue(); } @Test public void isDebuggable_whenApplicationIsNotDebuggable_shouldReturnFalse() throws Exception { setDebugMode(false); assertThat(ManifestUtils.isDebuggable(context)).isFalse(); } @SuppressWarnings("unchecked") @Test public void getRequiredWebViewSdkActivities_shouldIncludeRequiredActivities() throws Exception { assertThat(requiredWebViewSdkActivities).containsOnly( MoPubActivity.class, MraidActivity.class, MraidVideoPlayerActivity.class, MoPubBrowser.class ); } @SuppressWarnings("unchecked") @Test public void getRequiredNativeSdkActivities_shouldIncludeRequiredActivities() throws Exception { assertThat(requiredNativeSdkActivities).containsOnly( MoPubBrowser.class ); } private void setDebugMode(boolean enabled) { final ApplicationInfo applicationInfo = context.getApplicationInfo(); if (enabled) { applicationInfo.flags |= ApplicationInfo.FLAG_DEBUGGABLE; } else { applicationInfo.flags &= ~ApplicationInfo.FLAG_DEBUGGABLE; } when(context.getApplicationInfo()).thenReturn(applicationInfo); } private void assertLogIncludes(final String... messages) { final String logText = ShadowLog.getLogs().get(0).msg; for (final String message : messages) { assertThat(logText).containsOnlyOnce(message); } } private void assertLogDoesntInclude(final String... messages) { final String logText = ShadowLog.getLogs().get(0).msg; for (final String message : messages) { assertThat(logText).doesNotContain(message); } } }