Add tests to exercise ActivityStarter precondition failures.

This changelist adds tests which simulate various scenarios
encountered during the initial stages of starting an activity. They
ensure that the expected errors are returned under specified
conditions.

Bug: 64750076
Test: bit FrameworksServicesTests:com.android.server.am.ActivityStarterTests

Change-Id: I93fef3dce020fe03a8b2001b75a875980506a0dd
diff --git a/services/tests/servicestests/src/com/android/server/am/ActivityStarterTests.java b/services/tests/servicestests/src/com/android/server/am/ActivityStarterTests.java
index 5b1e4b7..f9933fb6 100644
--- a/services/tests/servicestests/src/com/android/server/am/ActivityStarterTests.java
+++ b/services/tests/servicestests/src/com/android/server/am/ActivityStarterTests.java
@@ -16,13 +16,28 @@
 
 package com.android.server.am;
 
+import static android.app.ActivityManager.START_ABORTED;
+import static android.app.ActivityManager.START_CLASS_NOT_FOUND;
+import static android.app.ActivityManager.START_FORWARD_AND_REQUEST_CONFLICT;
+import static android.app.ActivityManager.START_NOT_VOICE_COMPATIBLE;
+import static android.app.ActivityManager.START_SUCCESS;
+import static android.app.ActivityManager.START_SWITCHES_CANCELED;
 import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
 import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
 import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
 
+import android.app.ActivityOptions;
+import android.app.IApplicationThread;
 import android.content.ComponentName;
+import android.content.Intent;
+import android.content.pm.ActivityInfo;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.IPackageManager;
 import android.graphics.Rect;
+import android.os.IBinder;
+import android.os.RemoteException;
 import android.platform.test.annotations.Presubmit;
+import android.service.voice.IVoiceInteractionSession;
 import android.support.test.filters.SmallTest;
 import android.support.test.runner.AndroidJUnit4;
 
@@ -36,10 +51,20 @@
 import static org.mockito.Mockito.any;
 import static org.mockito.Mockito.anyBoolean;
 import static org.mockito.Mockito.anyInt;
+import static org.mockito.Mockito.anyObject;
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.doReturn;
 import static org.mockito.Mockito.eq;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.spy;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.times;
 
+import static android.app.ActivityManager.START_PERMISSION_DENIED;
+import static android.app.ActivityManager.START_INTENT_NOT_RESOLVED;
+
+import com.android.internal.os.BatteryStatsImpl;
+
 /**
  * Tests for the {@link ActivityStack} class.
  *
@@ -52,12 +77,26 @@
 public class ActivityStarterTests extends ActivityTestsBase {
     private ActivityManagerService mService;
     private ActivityStarter mStarter;
+    private IPackageManager mPackageManager;
+
+    private static final int PRECONDITION_NO_CALLER_APP = 1;
+    private static final int PRECONDITION_NO_INTENT_COMPONENT = 1 << 1;
+    private static final int PRECONDITION_NO_ACTIVITY_INFO = 1 << 2;
+    private static final int PRECONDITION_SOURCE_PRESENT = 1 << 3;
+    private static final int PRECONDITION_REQUEST_CODE = 1 << 4;
+    private static final int PRECONDITION_SOURCE_VOICE_SESSION = 1 << 5;
+    private static final int PRECONDITION_NO_VOICE_SESSION_SUPPORT = 1 << 6;
+    private static final int PRECONDITION_DIFFERENT_UID = 1 << 7;
+    private static final int PRECONDITION_ACTIVITY_SUPPORTS_INTENT_EXCEPTION = 1 << 8;
+    private static final int PRECONDITION_CANNOT_START_ANY_ACTIVITY = 1 << 9;
+    private static final int PRECONDITION_DISALLOW_APP_SWITCHING = 1 << 10;
 
     @Override
     public void setUp() throws Exception {
         super.setUp();
         mService = createActivityManagerService();
-        mStarter = new ActivityStarter(mService);
+        mPackageManager = mock(IPackageManager.class);
+        mStarter = new ActivityStarter(mService, mPackageManager);
     }
 
     @Test
@@ -92,4 +131,152 @@
             assertEquals(task2.mBounds, null);
         }
     }
+
+    @Test
+    public void testStartActivityPreconditions() throws Exception {
+        verifyStartActivityPreconditions(PRECONDITION_NO_CALLER_APP, START_PERMISSION_DENIED);
+        verifyStartActivityPreconditions(PRECONDITION_NO_INTENT_COMPONENT,
+                START_INTENT_NOT_RESOLVED);
+        verifyStartActivityPreconditions(PRECONDITION_NO_ACTIVITY_INFO, START_CLASS_NOT_FOUND);
+        verifyStartActivityPreconditions(PRECONDITION_SOURCE_PRESENT | PRECONDITION_REQUEST_CODE,
+                Intent.FLAG_ACTIVITY_FORWARD_RESULT, START_FORWARD_AND_REQUEST_CONFLICT);
+        verifyStartActivityPreconditions(
+                PRECONDITION_SOURCE_PRESENT | PRECONDITION_NO_VOICE_SESSION_SUPPORT
+                        | PRECONDITION_SOURCE_VOICE_SESSION | PRECONDITION_DIFFERENT_UID,
+                START_NOT_VOICE_COMPATIBLE);
+        verifyStartActivityPreconditions(
+                PRECONDITION_SOURCE_PRESENT | PRECONDITION_NO_VOICE_SESSION_SUPPORT
+                        | PRECONDITION_SOURCE_VOICE_SESSION | PRECONDITION_DIFFERENT_UID
+                        | PRECONDITION_ACTIVITY_SUPPORTS_INTENT_EXCEPTION,
+                START_NOT_VOICE_COMPATIBLE);
+        verifyStartActivityPreconditions(PRECONDITION_CANNOT_START_ANY_ACTIVITY, START_ABORTED);
+        verifyStartActivityPreconditions(PRECONDITION_DISALLOW_APP_SWITCHING,
+                START_SWITCHES_CANCELED);
+    }
+
+    private static boolean containsConditions(int preconditions, int mask) {
+        return (preconditions & mask) == mask;
+    }
+
+    private void verifyStartActivityPreconditions(int preconditions, int expectedResult) {
+        verifyStartActivityPreconditions(preconditions, 0 /*launchFlags*/, expectedResult);
+    }
+
+    /**
+     * Excercises how the {@link ActivityStarter} reacts to various preconditions. The caller
+     * provides a bitmask of all the set conditions (such as {@link #PRECONDITION_NO_CALLER_APP})
+     * and the launch flags specified in the intent. The method constructs a call to
+     * {@link ActivityStarter#startActivityLocked} based on these preconditions and ensures the
+     * result matches the expected. It is important to note that the method also checks side effects
+     * of the start, such as ensuring {@link ActivityOptions#abort()} is called in the relevant
+     * scenarios.
+     * @param preconditions A bitmask representing the preconditions for the launch
+     * @param launchFlags The launch flags to be provided by the launch {@link Intent}.
+     * @param expectedResult The expected result from the launch.
+     */
+    private void verifyStartActivityPreconditions(int preconditions, int launchFlags,
+            int expectedResult) {
+        final ActivityManagerService service = createActivityManagerService();
+        final IPackageManager packageManager = mock(IPackageManager.class);
+        final ActivityStarter starter = new ActivityStarter(service, packageManager);
+
+        final IApplicationThread caller = mock(IApplicationThread.class);
+
+        // If no caller app, return {@code null} {@link ProcessRecord}.
+        final ProcessRecord record = containsConditions(preconditions, PRECONDITION_NO_CALLER_APP)
+                ? null : new ProcessRecord(mock(BatteryStatsImpl.class),
+                mock(ApplicationInfo.class), null, 0);
+
+        doReturn(record).when(service).getRecordForAppLocked(anyObject());
+
+        final Intent intent = new Intent();
+        intent.setFlags(launchFlags);
+
+        final ActivityInfo aInfo = containsConditions(preconditions, PRECONDITION_NO_ACTIVITY_INFO)
+                ?  null : new ActivityInfo();
+
+        if (aInfo != null) {
+            aInfo.applicationInfo = new ApplicationInfo();
+            aInfo.applicationInfo.packageName = ActivityBuilder.DEFAULT_PACKAGE;
+        }
+
+        IVoiceInteractionSession voiceSession =
+                containsConditions(preconditions, PRECONDITION_SOURCE_VOICE_SESSION)
+                ? mock(IVoiceInteractionSession.class) : null;
+
+        // Create source token
+        final ActivityBuilder builder = new ActivityBuilder(service).setTask(
+                new TaskBuilder(service.mStackSupervisor).setVoiceSession(voiceSession).build());
+
+        // Offset uid by one from {@link ActivityInfo} to simulate different uids.
+        if (containsConditions(preconditions, PRECONDITION_DIFFERENT_UID)) {
+            builder.setUid(aInfo.applicationInfo.uid + 1);
+        }
+
+        final ActivityRecord source = builder.build();
+
+        if (!containsConditions(preconditions, PRECONDITION_NO_INTENT_COMPONENT)) {
+            intent.setComponent(source.realActivity);
+        }
+
+        if (containsConditions(preconditions, PRECONDITION_DISALLOW_APP_SWITCHING)) {
+            doReturn(false).when(service).checkAppSwitchAllowedLocked(anyInt(), anyInt(), anyInt(),
+                    anyInt(), any());
+        }
+
+        if (containsConditions(preconditions,PRECONDITION_CANNOT_START_ANY_ACTIVITY)) {
+            doReturn(false).when(service.mStackSupervisor).checkStartAnyActivityPermission(
+                    any(), any(), any(), anyInt(), anyInt(), anyInt(), any(), anyBoolean(),
+                    any(), any(), any(), any());
+        }
+
+        try {
+            if (containsConditions(preconditions,
+                    PRECONDITION_ACTIVITY_SUPPORTS_INTENT_EXCEPTION)) {
+                doAnswer((inv) -> {
+                    throw new RemoteException();
+                }).when(packageManager).activitySupportsIntent(eq(source.realActivity), eq(intent),
+                        any());
+            } else {
+                doReturn(!containsConditions(preconditions, PRECONDITION_NO_VOICE_SESSION_SUPPORT))
+                        .when(packageManager).activitySupportsIntent(eq(source.realActivity),
+                        eq(intent), any());
+            }
+        } catch (RemoteException e) {
+        }
+
+        final IBinder resultTo = containsConditions(preconditions, PRECONDITION_SOURCE_PRESENT)
+                || containsConditions(preconditions, PRECONDITION_SOURCE_VOICE_SESSION)
+                ? source.appToken : null;
+
+        final int requestCode = containsConditions(preconditions, PRECONDITION_REQUEST_CODE)
+                ? 1 : 0;
+
+        final int result = starter.startActivityLocked(caller, intent,
+                null /*ephemeralIntent*/, null /*resolvedType*/, aInfo, null /*rInfo*/,
+                null /*voiceSession*/, null /*voiceInteractor*/, resultTo,
+                null /*resultWho*/, requestCode, 0 /*callingPid*/, 0 /*callingUid*/,
+                null /*callingPackage*/, 0 /*realCallingPid*/, 0 /*realCallingUid*/,
+                0 /*startFlags*/, null /*options*/, false /*ignoreTargetSecurity*/,
+                false /*componentSpecified*/, null /*outActivity*/,
+                null /*inTask*/, "testLaunchActivityPermissionDenied");
+
+        // In some cases the expected result internally is different than the published result. We
+        // must use ActivityStarter#getExternalResult to translate.
+        assertEquals(ActivityStarter.getExternalResult(expectedResult), result);
+
+        // Ensure that {@link ActivityOptions} are aborted with unsuccessful result.
+        if (expectedResult != START_SUCCESS) {
+            final ActivityOptions options = spy(ActivityOptions.makeBasic());
+            final int optionResult = starter.startActivityLocked(caller, intent,
+                    null /*ephemeralIntent*/, null /*resolvedType*/, aInfo, null /*rInfo*/,
+                    null /*voiceSession*/, null /*voiceInteractor*/, resultTo,
+                    null /*resultWho*/, requestCode, 0 /*callingPid*/, 0 /*callingUid*/,
+                    null /*callingPackage*/, 0 /*realCallingPid*/, 0 /*realCallingUid*/,
+                    0 /*startFlags*/, options /*options*/, false /*ignoreTargetSecurity*/,
+                    false /*componentSpecified*/, null /*outActivity*/,
+                    null /*inTask*/, "testLaunchActivityPermissionDenied");
+            verify(options, times(1)).abort();
+        }
+    }
 }