From 93e7f79ff066fee6650c826f039c2c77f33852be Mon Sep 17 00:00:00 2001 From: Bryce Lee Date: Wed, 25 Oct 2017 15:54:55 -0700 Subject: [PATCH] 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 --- .../android/server/am/ActivityManagerService.java | 5 +- .../com/android/server/am/ActivityStarter.java | 18 +- .../android/server/am/ActivityStarterTests.java | 189 ++++++++++++++++++++- .../com/android/server/am/ActivityTestsBase.java | 17 +- 4 files changed, 216 insertions(+), 13 deletions(-) diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java index e43c3b4d6250..6c7b3e2b3a2a 100644 --- a/services/core/java/com/android/server/am/ActivityManagerService.java +++ b/services/core/java/com/android/server/am/ActivityManagerService.java @@ -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; } diff --git a/services/core/java/com/android/server/am/ActivityStarter.java b/services/core/java/com/android/server/am/ActivityStarter.java index 44e6baea14ac..f3e8f17ae15b 100644 --- a/services/core/java/com/android/server/am/ActivityStarter.java +++ b/services/core/java/com/android/server/am/ActivityStarter.java @@ -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: " 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 5b1e4b731d38..f9933fb63f12 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.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(); + } + } } diff --git a/services/tests/servicestests/src/com/android/server/am/ActivityTestsBase.java b/services/tests/servicestests/src/com/android/server/am/ActivityTestsBase.java index f04b6d89d51e..74b0c9e91574 100644 --- a/services/tests/servicestests/src/com/android/server/am/ActivityTestsBase.java +++ b/services/tests/servicestests/src/com/android/server/am/ActivityTestsBase.java @@ -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); -- 2.11.0