Allow launching activities via adoptShellPermission in CTS-in-sandbox tests
For context on CTS-in-sandbox:
* go/cts-in-sandbox-how, and
* go/sdk-in-sandbox-activity-support
Flag: TEST_ONLY
Bug: 261864298
Test: atest com.android.server.wm.ActivityStartInterceptorTest && \\
atest com.android.server.wm.ActivityTaskManagerServiceTests
Change-Id: I4267333eaf8a84871d60635bcdd5bb522f8f96a1
diff --git a/core/api/test-current.txt b/core/api/test-current.txt
index a8a6eb9..a4cc446 100644
--- a/core/api/test-current.txt
+++ b/core/api/test-current.txt
@@ -50,6 +50,7 @@
field public static final String SET_AND_VERIFY_LOCKSCREEN_CREDENTIALS = "android.permission.SET_AND_VERIFY_LOCKSCREEN_CREDENTIALS";
field public static final String SET_GAME_SERVICE = "android.permission.SET_GAME_SERVICE";
field public static final String SET_KEYBOARD_LAYOUT = "android.permission.SET_KEYBOARD_LAYOUT";
+ field public static final String START_ACTIVITIES_FROM_SDK_SANDBOX = "android.permission.START_ACTIVITIES_FROM_SDK_SANDBOX";
field public static final String SUSPEND_APPS = "android.permission.SUSPEND_APPS";
field public static final String TEST_BIOMETRIC = "android.permission.TEST_BIOMETRIC";
field public static final String TEST_INPUT_METHOD = "android.permission.TEST_INPUT_METHOD";
diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java
index 00e546a..0191201 100644
--- a/core/java/android/app/ActivityThread.java
+++ b/core/java/android/app/ActivityThread.java
@@ -3720,7 +3720,19 @@
/** Core implementation of activity launch. */
private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) {
ActivityInfo aInfo = r.activityInfo;
- if (r.packageInfo == null) {
+
+ if (getInstrumentation() != null
+ && getInstrumentation().getContext() != null
+ && getInstrumentation().getContext().getApplicationInfo() != null
+ && getInstrumentation().isSdkSandboxAllowedToStartActivities()) {
+ // Activities launched from CTS-in-sandbox tests use a customized ApplicationInfo. See
+ // also {@link SdkSandboxManagerLocal#getSdkSandboxApplicationInfoForInstrumentation}.
+ r.packageInfo =
+ getPackageInfo(
+ getInstrumentation().getContext().getApplicationInfo(),
+ mCompatibilityInfo,
+ Context.CONTEXT_INCLUDE_CODE);
+ } else if (r.packageInfo == null) {
r.packageInfo = getPackageInfo(aInfo.applicationInfo, mCompatibilityInfo,
Context.CONTEXT_INCLUDE_CODE);
}
diff --git a/core/java/android/app/Instrumentation.java b/core/java/android/app/Instrumentation.java
index e31486f..10747bb 100644
--- a/core/java/android/app/Instrumentation.java
+++ b/core/java/android/app/Instrumentation.java
@@ -26,6 +26,7 @@
import android.content.Intent;
import android.content.IntentFilter;
import android.content.pm.ActivityInfo;
+import android.content.pm.PackageManager;
import android.content.res.Configuration;
import android.hardware.input.InputManager;
import android.hardware.input.InputManagerGlobal;
@@ -474,6 +475,56 @@
sr.waitForComplete();
}
+ boolean isSdkSandboxAllowedToStartActivities() {
+ return Process.isSdkSandbox()
+ && mThread != null
+ && mThread.mBoundApplication != null
+ && mThread.mBoundApplication.isSdkInSandbox
+ && getContext() != null
+ && (getContext()
+ .checkSelfPermission(
+ android.Manifest.permission
+ .START_ACTIVITIES_FROM_SDK_SANDBOX)
+ == PackageManager.PERMISSION_GRANTED);
+ }
+
+ /**
+ * Activity name resolution for CTS-in-SdkSandbox tests requires some adjustments. Intents
+ * generated using {@link Context#getPackageName()} use the SDK sandbox package name in the
+ * component field instead of the test package name. An SDK-in-sandbox test attempting to launch
+ * an activity in the test package will encounter name resolution errors when resolving the
+ * activity name in the SDK sandbox package.
+ *
+ * <p>This function replaces the package name of the input intent component to allow activities
+ * belonging to a CTS-in-sandbox test to resolve correctly.
+ *
+ * @param intent the intent to modify to allow CTS-in-sandbox activity resolution.
+ */
+ private void adjustIntentForCtsInSdkSandboxInstrumentation(@NonNull Intent intent) {
+ if (mComponent != null
+ && intent.getComponent() != null
+ && getContext()
+ .getPackageManager()
+ .getSdkSandboxPackageName()
+ .equals(intent.getComponent().getPackageName())) {
+ // Resolve the intent target for the test package, not for the sandbox package.
+ intent.setComponent(
+ new ComponentName(
+ mComponent.getPackageName(), intent.getComponent().getClassName()));
+ }
+ // We match the intent identifier against the running instrumentations for the sandbox.
+ intent.setIdentifier(mComponent.getPackageName());
+ }
+
+ private ActivityInfo resolveActivityInfoForCtsInSandbox(@NonNull Intent intent) {
+ adjustIntentForCtsInSdkSandboxInstrumentation(intent);
+ ActivityInfo ai = intent.resolveActivityInfo(getTargetContext().getPackageManager(), 0);
+ if (ai != null) {
+ ai.processName = mThread.getProcessName();
+ }
+ return ai;
+ }
+
/**
* Start a new activity and wait for it to begin running before returning.
* In addition to being synchronous, this method as some semantic
@@ -531,8 +582,10 @@
synchronized (mSync) {
intent = new Intent(intent);
- ActivityInfo ai = intent.resolveActivityInfo(
- getTargetContext().getPackageManager(), 0);
+ ActivityInfo ai =
+ isSdkSandboxAllowedToStartActivities()
+ ? resolveActivityInfoForCtsInSandbox(intent)
+ : intent.resolveActivityInfo(getTargetContext().getPackageManager(), 0);
if (ai == null) {
throw new RuntimeException("Unable to resolve activity for: " + intent);
}
@@ -1842,6 +1895,9 @@
if (referrer != null) {
intent.putExtra(Intent.EXTRA_REFERRER, referrer);
}
+ if (isSdkSandboxAllowedToStartActivities()) {
+ adjustIntentForCtsInSdkSandboxInstrumentation(intent);
+ }
if (mActivityMonitors != null) {
synchronized (mSync) {
final int N = mActivityMonitors.size();
@@ -1914,6 +1970,11 @@
IBinder token, Activity target, Intent[] intents, Bundle options,
int userId) {
IApplicationThread whoThread = (IApplicationThread) contextThread;
+ if (isSdkSandboxAllowedToStartActivities()) {
+ for (Intent intent : intents) {
+ adjustIntentForCtsInSdkSandboxInstrumentation(intent);
+ }
+ }
if (mActivityMonitors != null) {
synchronized (mSync) {
final int N = mActivityMonitors.size();
@@ -1989,6 +2050,9 @@
Context who, IBinder contextThread, IBinder token, String target,
Intent intent, int requestCode, Bundle options) {
IApplicationThread whoThread = (IApplicationThread) contextThread;
+ if (isSdkSandboxAllowedToStartActivities()) {
+ adjustIntentForCtsInSdkSandboxInstrumentation(intent);
+ }
if (mActivityMonitors != null) {
synchronized (mSync) {
final int N = mActivityMonitors.size();
@@ -2060,6 +2124,9 @@
Context who, IBinder contextThread, IBinder token, String resultWho,
Intent intent, int requestCode, Bundle options, UserHandle user) {
IApplicationThread whoThread = (IApplicationThread) contextThread;
+ if (isSdkSandboxAllowedToStartActivities()) {
+ adjustIntentForCtsInSdkSandboxInstrumentation(intent);
+ }
if (mActivityMonitors != null) {
synchronized (mSync) {
final int N = mActivityMonitors.size();
@@ -2110,6 +2177,9 @@
Intent intent, int requestCode, Bundle options,
boolean ignoreTargetSecurity, int userId) {
IApplicationThread whoThread = (IApplicationThread) contextThread;
+ if (isSdkSandboxAllowedToStartActivities()) {
+ adjustIntentForCtsInSdkSandboxInstrumentation(intent);
+ }
if (mActivityMonitors != null) {
synchronized (mSync) {
final int N = mActivityMonitors.size();
@@ -2161,6 +2231,9 @@
Context who, IBinder contextThread, IAppTask appTask,
Intent intent, Bundle options) {
IApplicationThread whoThread = (IApplicationThread) contextThread;
+ if (isSdkSandboxAllowedToStartActivities()) {
+ adjustIntentForCtsInSdkSandboxInstrumentation(intent);
+ }
if (mActivityMonitors != null) {
synchronized (mSync) {
final int N = mActivityMonitors.size();
diff --git a/core/java/android/content/Intent.java b/core/java/android/content/Intent.java
index e9bbed3..7579d99 100644
--- a/core/java/android/content/Intent.java
+++ b/core/java/android/content/Intent.java
@@ -12516,6 +12516,7 @@
return (mFlags & FLAG_ACTIVITY_NEW_DOCUMENT) == FLAG_ACTIVITY_NEW_DOCUMENT;
}
+ // TODO(b/299109198): Refactor into the {@link SdkSandboxManagerLocal}
/** @hide */
public boolean isSandboxActivity(@NonNull Context context) {
if (mAction != null && mAction.equals(ACTION_START_SANDBOXED_ACTIVITY)) {
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index d000b23..c09f0a3 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -7663,6 +7663,10 @@
<permission android:name="android.permission.DELETE_STAGED_HEALTH_CONNECT_REMOTE_DATA"
android:protectionLevel="signature" />
+ <!-- @hide @TestApi Allows tests running in CTS-in-sandbox mode to launch activities -->
+ <permission android:name="android.permission.START_ACTIVITIES_FROM_SDK_SANDBOX"
+ android:protectionLevel="signature" />
+
<!-- @SystemApi Allows the holder to call health connect migration APIs.
@hide -->
<permission android:name="android.permission.MIGRATE_HEALTH_CONNECT_DATA"
diff --git a/packages/Shell/AndroidManifest.xml b/packages/Shell/AndroidManifest.xml
index d267d80..15620b7 100644
--- a/packages/Shell/AndroidManifest.xml
+++ b/packages/Shell/AndroidManifest.xml
@@ -853,6 +853,9 @@
<!-- Permission required for accessing all content provider mime types -->
<uses-permission android:name="android.permission.GET_ANY_PROVIDER_TYPE" />
+ <!-- Permission required for CTS-in-sandbox tests -->
+ <uses-permission android:name="android.permission.START_ACTIVITIES_FROM_SDK_SANDBOX" />
+
<!-- Permission required for CTS test - CtsWallpaperTestCases -->
<uses-permission android:name="android.permission.ALWAYS_UPDATE_WALLPAPER" />
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index ba1f84a..4c70db8 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -15877,7 +15877,15 @@
activeInstr.mWatcher = watcher;
activeInstr.mUiAutomationConnection = uiAutomationConnection;
activeInstr.mResultClass = className;
- activeInstr.mHasBackgroundActivityStartsPermission = false;
+ activeInstr.mHasBackgroundActivityStartsPermission =
+ isSdkInSandbox
+ // TODO(b/261864298): consider using START_ACTIVITIES_FROM_BACKGROUND.
+ && checkPermission(
+ android.Manifest.permission
+ .START_ACTIVITIES_FROM_SDK_SANDBOX,
+ Binder.getCallingPid(),
+ Binder.getCallingUid())
+ == PackageManager.PERMISSION_GRANTED;
activeInstr.mHasBackgroundForegroundServiceStartsPermission = false;
// Instrumenting sdk sandbox without a restart is not supported
activeInstr.mNoRestart = false;
diff --git a/services/core/java/com/android/server/wm/ActivityStartInterceptor.java b/services/core/java/com/android/server/wm/ActivityStartInterceptor.java
index a5b1132..25c42b4 100644
--- a/services/core/java/com/android/server/wm/ActivityStartInterceptor.java
+++ b/services/core/java/com/android/server/wm/ActivityStartInterceptor.java
@@ -33,9 +33,7 @@
import static android.content.pm.ApplicationInfo.FLAG_SUSPENDED;
import static com.android.server.pm.PackageManagerService.PLATFORM_PACKAGE_NAME;
-import static com.android.server.wm.ActivityInterceptorCallback.MAINLINE_SDK_SANDBOX_ORDER_ID;
-import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.ActivityOptions;
import android.app.KeyguardManager;
@@ -240,11 +238,6 @@
getInterceptorInfo(null /* clearOptionsAnimation */);
for (int i = 0; i < callbacks.size(); i++) {
- final int orderId = callbacks.keyAt(i);
- if (!shouldInterceptActivityLaunch(orderId, interceptorInfo)) {
- continue;
- }
-
final ActivityInterceptorCallback callback = callbacks.valueAt(i);
final ActivityInterceptResult interceptResult = callback.onInterceptActivityLaunch(
interceptorInfo);
@@ -543,11 +536,6 @@
ActivityInterceptorCallback.ActivityInterceptorInfo info = getInterceptorInfo(
r::clearOptionsAnimationForSiblings);
for (int i = 0; i < callbacks.size(); i++) {
- final int orderId = callbacks.keyAt(i);
- if (!shouldNotifyOnActivityLaunch(orderId, info)) {
- continue;
- }
-
final ActivityInterceptorCallback callback = callbacks.valueAt(i);
callback.onActivityLaunched(taskInfo, r.info, info);
}
@@ -565,21 +553,4 @@
.build();
}
- private boolean shouldInterceptActivityLaunch(
- @ActivityInterceptorCallback.OrderedId int orderId,
- @NonNull ActivityInterceptorCallback.ActivityInterceptorInfo info) {
- if (orderId == MAINLINE_SDK_SANDBOX_ORDER_ID) {
- return info.getIntent() != null && info.getIntent().isSandboxActivity(mServiceContext);
- }
- return true;
- }
-
- private boolean shouldNotifyOnActivityLaunch(
- @ActivityInterceptorCallback.OrderedId int orderId,
- @NonNull ActivityInterceptorCallback.ActivityInterceptorInfo info) {
- if (orderId == MAINLINE_SDK_SANDBOX_ORDER_ID) {
- return info.getIntent() != null && info.getIntent().isSandboxActivity(mServiceContext);
- }
- return true;
- }
}
diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityStartInterceptorTest.java b/services/tests/wmtests/src/com/android/server/wm/ActivityStartInterceptorTest.java
index 0989db4..568471d 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityStartInterceptorTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityStartInterceptorTest.java
@@ -16,7 +16,6 @@
package com.android.server.wm;
-import static android.app.sdksandbox.SdkSandboxManager.ACTION_START_SANDBOXED_ACTIVITY;
import static android.content.pm.ActivityInfo.LOCK_TASK_LAUNCH_MODE_DEFAULT;
import static android.content.pm.ApplicationInfo.FLAG_SUSPENDED;
@@ -26,7 +25,6 @@
import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.when;
import static com.android.server.pm.PackageManagerService.PLATFORM_PACKAGE_NAME;
-import static com.android.server.wm.ActivityInterceptorCallback.MAINLINE_SDK_SANDBOX_ORDER_ID;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
@@ -36,7 +34,6 @@
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.ArgumentMatchers.nullable;
import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.never;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -44,13 +41,11 @@
import android.app.ActivityOptions;
import android.app.KeyguardManager;
import android.app.admin.DevicePolicyManagerInternal;
-import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.pm.ActivityInfo;
import android.content.pm.ApplicationInfo;
import android.content.pm.IPackageManager;
-import android.content.pm.PackageManager;
import android.content.pm.PackageManagerInternal;
import android.content.pm.SuspendDialogInfo;
import android.content.pm.UserInfo;
@@ -76,7 +71,6 @@
import org.junit.Rule;
import org.junit.Test;
import org.mockito.Mock;
-import org.mockito.Mockito;
import org.mockito.MockitoAnnotations;
/**
@@ -509,88 +503,4 @@
verify(callback, times(1)).onActivityLaunched(any(), any(), any());
}
-
- @Test
- public void testSandboxServiceInterceptionHappensToIntentWithSandboxActivityAction() {
- ActivityInterceptorCallback spyCallback = Mockito.spy(info -> null);
- mActivityInterceptorCallbacks.put(MAINLINE_SDK_SANDBOX_ORDER_ID, spyCallback);
-
- PackageManager packageManagerMock = mock(PackageManager.class);
- String sandboxPackageNameMock = "com.sandbox.mock";
- when(mContext.getPackageManager()).thenReturn(packageManagerMock);
- when(packageManagerMock.getSdkSandboxPackageName()).thenReturn(sandboxPackageNameMock);
-
- Intent intent = new Intent().setAction(ACTION_START_SANDBOXED_ACTIVITY);
- mInterceptor.intercept(intent, null, mAInfo, null, null, null, 0, 0, null, null);
-
- verify(spyCallback, times(1)).onInterceptActivityLaunch(
- any(ActivityInterceptorCallback.ActivityInterceptorInfo.class));
- }
-
- @Test
- public void testSandboxServiceInterceptionHappensToIntentWithSandboxPackage() {
- ActivityInterceptorCallback spyCallback = Mockito.spy(info -> null);
- mActivityInterceptorCallbacks.put(MAINLINE_SDK_SANDBOX_ORDER_ID, spyCallback);
-
- PackageManager packageManagerMock = mock(PackageManager.class);
- String sandboxPackageNameMock = "com.sandbox.mock";
- when(mContext.getPackageManager()).thenReturn(packageManagerMock);
- when(packageManagerMock.getSdkSandboxPackageName()).thenReturn(sandboxPackageNameMock);
-
- Intent intent = new Intent().setPackage(sandboxPackageNameMock);
- mInterceptor.intercept(intent, null, mAInfo, null, null, null, 0, 0, null, null);
-
- verify(spyCallback, times(1)).onInterceptActivityLaunch(
- any(ActivityInterceptorCallback.ActivityInterceptorInfo.class));
- }
-
- @Test
- public void testSandboxServiceInterceptionHappensToIntentWithComponentNameWithSandboxPackage() {
- ActivityInterceptorCallback spyCallback = Mockito.spy(info -> null);
- mActivityInterceptorCallbacks.put(MAINLINE_SDK_SANDBOX_ORDER_ID, spyCallback);
-
- PackageManager packageManagerMock = mock(PackageManager.class);
- String sandboxPackageNameMock = "com.sandbox.mock";
- when(mContext.getPackageManager()).thenReturn(packageManagerMock);
- when(packageManagerMock.getSdkSandboxPackageName()).thenReturn(sandboxPackageNameMock);
-
- Intent intent = new Intent().setComponent(new ComponentName(sandboxPackageNameMock, ""));
- mInterceptor.intercept(intent, null, mAInfo, null, null, null, 0, 0, null, null);
-
- verify(spyCallback, times(1)).onInterceptActivityLaunch(
- any(ActivityInterceptorCallback.ActivityInterceptorInfo.class));
- }
-
- @Test
- public void testSandboxServiceInterceptionNotCalledWhenIntentNotRelatedToSandbox() {
- ActivityInterceptorCallback spyCallback = Mockito.spy(info -> null);
- mActivityInterceptorCallbacks.put(MAINLINE_SDK_SANDBOX_ORDER_ID, spyCallback);
-
- PackageManager packageManagerMock = mock(PackageManager.class);
- String sandboxPackageNameMock = "com.sandbox.mock";
- when(mContext.getPackageManager()).thenReturn(packageManagerMock);
- when(packageManagerMock.getSdkSandboxPackageName()).thenReturn(sandboxPackageNameMock);
-
- // Intent: null
- mInterceptor.intercept(null, null, mAInfo, null, null, null, 0, 0, null, null);
-
- // Action: null, Package: null, ComponentName: null
- Intent intent = new Intent();
- mInterceptor.intercept(intent, null, mAInfo, null, null, null, 0, 0, null, null);
-
- // Wrong Action
- intent = new Intent().setAction(Intent.ACTION_VIEW);
- mInterceptor.intercept(intent, null, mAInfo, null, null, null, 0, 0, null, null);
-
- // Wrong Package
- intent = new Intent().setPackage("Random");
- mInterceptor.intercept(intent, null, mAInfo, null, null, null, 0, 0, null, null);
-
- // Wrong ComponentName's package
- intent = new Intent().setComponent(new ComponentName("Random", ""));
- mInterceptor.intercept(intent, null, mAInfo, null, null, null, 0, 0, null, null);
-
- verify(spyCallback, never()).onInterceptActivityLaunch(
- any(ActivityInterceptorCallback.ActivityInterceptorInfo.class));
- }
}