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;
private static final int INVALID_LAUNCH_MODE = -1;
private final ActivityManagerService mService;
+ private final IPackageManager mPackageManager;
private final ActivityStackSupervisor mSupervisor;
private final ActivityStartInterceptor mInterceptor;
mIntentDelivered = false;
}
- ActivityStarter(ActivityManagerService service) {
+ ActivityStarter(ActivityManagerService service, IPackageManager packageManager) {
mService = service;
+ mPackageManager = packageManager;
mSupervisor = mService.mStackSupervisor;
mInterceptor = new ActivityStartInterceptor(mService, mSupervisor);
}
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. */
}
}
- 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)
&& 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: "
// 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: "
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;
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.
*
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
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();
+ }
+ }
}
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;
}
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) {
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";
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 */,
private String mPackage;
private int mFlags = 0;
private int mTaskId = 0;
+ private IVoiceInteractionSession mVoiceSession;
private ActivityStack mStack;
return this;
}
+ TaskBuilder setVoiceSession(IVoiceInteractionSession session) {
+ mVoiceSession = session;
+ return this;
+ }
+
TaskBuilder setFlags(int flags) {
mFlags = flags;
return this;
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);