OSDN Git Service

Add tests to exercise ActivityStarter precondition failures.
authorBryce Lee <brycelee@google.com>
Wed, 25 Oct 2017 22:54:55 +0000 (15:54 -0700)
committerBryce Lee <brycelee@google.com>
Tue, 14 Nov 2017 22:48:04 +0000 (14:48 -0800)
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

services/core/java/com/android/server/am/ActivityManagerService.java
services/core/java/com/android/server/am/ActivityStarter.java
services/tests/servicestests/src/com/android/server/am/ActivityStarterTests.java
services/tests/servicestests/src/com/android/server/am/ActivityTestsBase.java

index e43c3b4..6c7b3e2 100644 (file)
@@ -2784,7 +2784,7 @@ public class ActivityManagerService extends IActivityManager.Stub
         mIntentFirewall = new IntentFirewall(new IntentFirewallInterface(), mHandler);
         mTaskChangeNotificationController =
                 new TaskChangeNotificationController(this, mStackSupervisor, mHandler);
-        mActivityStarter = new ActivityStarter(this);
+        mActivityStarter = new ActivityStarter(this, AppGlobals.getPackageManager());
         mRecentTasks = createRecentTasks();
         mStackSupervisor.setRecentTasks(mRecentTasks);
         mLockTaskController = new LockTaskController(mContext, mStackSupervisor, mHandler);
@@ -5352,8 +5352,7 @@ public class ActivityManagerService extends IActivityManager.Stub
         return -1;
     }
 
-    final ProcessRecord getRecordForAppLocked(
-            IApplicationThread thread) {
+    ProcessRecord getRecordForAppLocked(IApplicationThread thread) {
         if (thread == null) {
             return null;
         }
index 44e6bae..f3e8f17 100644 (file)
@@ -90,6 +90,7 @@ import android.content.IntentSender;
 import android.content.pm.ActivityInfo;
 import android.content.pm.ApplicationInfo;
 import android.content.pm.AuxiliaryResolveInfo;
+import android.content.pm.IPackageManager;
 import android.content.pm.PackageManager;
 import android.content.pm.ResolveInfo;
 import android.content.pm.UserInfo;
@@ -133,6 +134,7 @@ class ActivityStarter {
     private static final int INVALID_LAUNCH_MODE = -1;
 
     private final ActivityManagerService mService;
+    private final IPackageManager mPackageManager;
     private final ActivityStackSupervisor mSupervisor;
     private final ActivityStartInterceptor mInterceptor;
 
@@ -232,8 +234,9 @@ class ActivityStarter {
         mIntentDelivered = false;
     }
 
-    ActivityStarter(ActivityManagerService service) {
+    ActivityStarter(ActivityManagerService service, IPackageManager packageManager) {
         mService = service;
+        mPackageManager = packageManager;
         mSupervisor = mService.mStackSupervisor;
         mInterceptor = new ActivityStartInterceptor(mService, mSupervisor);
     }
@@ -264,8 +267,12 @@ class ActivityStarter {
             outActivity[0] = mLastStartActivityRecord[0];
         }
 
+        return getExternalResult(mLastStartActivityResult);
+    }
+
+    public static int getExternalResult(int result) {
         // Aborted results are treated as successes externally, but we must track them internally.
-        return mLastStartActivityResult != START_ABORTED ? mLastStartActivityResult : START_SUCCESS;
+        return result != START_ABORTED ? result : START_SUCCESS;
     }
 
     /** DO NOT call this method directly. Use {@link #startActivityLocked} instead. */
@@ -295,7 +302,8 @@ class ActivityStarter {
             }
         }
 
-        final int userId = aInfo != null ? UserHandle.getUserId(aInfo.applicationInfo.uid) : 0;
+        final int userId = aInfo != null && aInfo.applicationInfo != null
+                ? UserHandle.getUserId(aInfo.applicationInfo.uid) : 0;
 
         if (err == ActivityManager.START_SUCCESS) {
             Slog.i(TAG, "START u" + userId + " {" + intent.toShortString(true, true, true, false)
@@ -371,7 +379,7 @@ class ActivityStarter {
                     && sourceRecord.info.applicationInfo.uid != aInfo.applicationInfo.uid) {
                 try {
                     intent.addCategory(Intent.CATEGORY_VOICE);
-                    if (!AppGlobals.getPackageManager().activitySupportsIntent(
+                    if (!mPackageManager.activitySupportsIntent(
                             intent.getComponent(), intent, resolvedType)) {
                         Slog.w(TAG,
                                 "Activity being started in current voice task does not support voice: "
@@ -389,7 +397,7 @@ class ActivityStarter {
             // If the caller is starting a new voice session, just make sure the target
             // is actually allowing it to run this way.
             try {
-                if (!AppGlobals.getPackageManager().activitySupportsIntent(intent.getComponent(),
+                if (!mPackageManager.activitySupportsIntent(intent.getComponent(),
                         intent, resolvedType)) {
                     Slog.w(TAG,
                             "Activity being started in new voice task does not support: "
index 5b1e4b7..f9933fb 100644 (file)
 
 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.junit.Assert.assertTrue;
 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 @@ import static org.mockito.Mockito.times;
 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 @@ public class ActivityStarterTests extends ActivityTestsBase {
             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();
+        }
+    }
 }
index f04b6d8..74b0c9e 100644 (file)
@@ -41,6 +41,7 @@ import android.graphics.Rect;
 import android.hardware.display.DisplayManager;
 import android.os.HandlerThread;
 import android.os.Looper;
+import android.service.voice.IVoiceInteractionSession;
 import android.support.test.InstrumentationRegistry;
 import com.android.server.AttributeCache;
 import com.android.server.wm.AppWindowContainerController;
@@ -81,7 +82,10 @@ public class ActivityTestsBase {
     }
 
     protected ActivityManagerService createActivityManagerService() {
-        return setupActivityManagerService(new TestActivityManagerService(mContext));
+        final ActivityManagerService service =
+                setupActivityManagerService(new TestActivityManagerService(mContext));
+        AttributeCache.init(mContext);
+        return service;
     }
 
     protected ActivityManagerService setupActivityManagerService(ActivityManagerService service) {
@@ -98,7 +102,7 @@ public class ActivityTestsBase {
         private static int sCurrentActivityId = 0;
 
         // Default package name
-        private static final String DEFAULT_PACKAGE = "com.foo";
+        static final String DEFAULT_PACKAGE = "com.foo";
 
         // Default base activity name
         private static final String DEFAULT_BASE_ACTIVITY_NAME = ".BarActivity";
@@ -159,7 +163,6 @@ public class ActivityTestsBase {
             aInfo.applicationInfo = new ApplicationInfo();
             aInfo.applicationInfo.packageName = mComponent.getPackageName();
             aInfo.applicationInfo.uid = mUid;
-            AttributeCache.init(mService.mContext);
             final ActivityRecord activity = new ActivityRecord(mService, null /* caller */,
                     0 /* launchedFromPid */, 0, null, intent, null,
                     aInfo /*aInfo*/, new Configuration(), null /* resultTo */, null /* resultWho */,
@@ -185,6 +188,7 @@ public class ActivityTestsBase {
         private String mPackage;
         private int mFlags = 0;
         private int mTaskId = 0;
+        private IVoiceInteractionSession mVoiceSession;
 
         private ActivityStack mStack;
 
@@ -202,6 +206,11 @@ public class ActivityTestsBase {
             return this;
         }
 
+        TaskBuilder setVoiceSession(IVoiceInteractionSession session) {
+            mVoiceSession = session;
+            return this;
+        }
+
         TaskBuilder setFlags(int flags) {
             mFlags = flags;
             return this;
@@ -232,7 +241,7 @@ public class ActivityTestsBase {
             intent.setFlags(mFlags);
 
             final TaskRecord task = new TaskRecord(mSupervisor.mService, mTaskId, aInfo,
-                    intent /*intent*/, null /*_taskDescription*/);
+                    intent /*intent*/, mVoiceSession, null /*_voiceInteractor*/);
             mSupervisor.setFocusStackUnchecked("test", mStack);
             mStack.addTask(task, true, "creating test task");
             task.setStack(mStack);