Add a way to instrument sdk sandbox processes
The following restrictions applies to the instrumentation of the sdk
sandbox processes:
* Instrumentation must be signed with the same certificate as the client
app the instrumented sdk sandbox belongs to.
* If there is a running instance of to-be-instrumented sdk sandbox
process, then it will be killed before the instrumentation starts.
* While instrumentation is running the client app won't be allowed to
connect to the instrumented sdk sandbox process.
* The --no-restart instrumentation of the sdk sandbox processes is not
supported.
Bug: 209061624
Test: atest SdkSandboxInprocessTests
Change-Id: Ia4b145c091bf8da600a77ea82fc9e3cd97757275
diff --git a/cmds/am/src/com/android/commands/am/Am.java b/cmds/am/src/com/android/commands/am/Am.java
index c5410a0..b8d24e3 100644
--- a/cmds/am/src/com/android/commands/am/Am.java
+++ b/cmds/am/src/com/android/commands/am/Am.java
@@ -191,6 +191,8 @@
instrument.noRestart = true;
} else if (opt.equals("--always-check-signature")) {
instrument.alwaysCheckSignature = true;
+ } else if (opt.equals("--instrument-sdk-sandbox")) {
+ instrument.instrumentSdkSandbox = true;
} else {
System.err.println("Error: Unknown option: " + opt);
return;
diff --git a/cmds/am/src/com/android/commands/am/Instrument.java b/cmds/am/src/com/android/commands/am/Instrument.java
index a0562d9..7ff4bc4 100644
--- a/cmds/am/src/com/android/commands/am/Instrument.java
+++ b/cmds/am/src/com/android/commands/am/Instrument.java
@@ -20,6 +20,7 @@
import static android.app.ActivityManager.INSTR_FLAG_DISABLE_HIDDEN_API_CHECKS;
import static android.app.ActivityManager.INSTR_FLAG_DISABLE_ISOLATED_STORAGE;
import static android.app.ActivityManager.INSTR_FLAG_DISABLE_TEST_API_CHECKS;
+import static android.app.ActivityManager.INSTR_FLAG_INSTRUMENT_SDK_SANDBOX;
import static android.app.ActivityManager.INSTR_FLAG_NO_RESTART;
import android.app.IActivityManager;
@@ -97,6 +98,7 @@
// Required
public String componentNameArg;
public boolean alwaysCheckSignature = false;
+ public boolean instrumentSdkSandbox = false;
/**
* Construct the instrument command runner.
@@ -524,6 +526,9 @@
if (alwaysCheckSignature) {
flags |= INSTR_FLAG_ALWAYS_CHECK_SIGNATURE;
}
+ if (instrumentSdkSandbox) {
+ flags |= INSTR_FLAG_INSTRUMENT_SDK_SANDBOX;
+ }
if (!mAm.startInstrumentation(cn, profileFile, flags, args, watcher, connection, userId,
abi)) {
throw new AndroidException("INSTRUMENTATION_FAILED: " + cn.flattenToString());
diff --git a/core/java/android/app/ActivityManager.java b/core/java/android/app/ActivityManager.java
index 9f15105..c8ecfaa 100644
--- a/core/java/android/app/ActivityManager.java
+++ b/core/java/android/app/ActivityManager.java
@@ -185,6 +185,11 @@
* @hide
*/
public static final int INSTR_FLAG_ALWAYS_CHECK_SIGNATURE = 1 << 4;
+ /**
+ * Instrument Sdk Sandbox process that corresponds to the target package.
+ * @hide
+ */
+ public static final int INSTR_FLAG_INSTRUMENT_SDK_SANDBOX = 1 << 5;
static final class UidObserver extends IUidObserver.Stub {
final OnUidImportanceListener mListener;
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index 9d9ee8c..f92fb06 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -406,6 +406,7 @@
import com.android.server.pm.pkg.SELinuxUtil;
import com.android.server.pm.pkg.parsing.ParsingPackageUtils;
import com.android.server.pm.snapshot.PackageDataSnapshot;
+import com.android.server.sdksandbox.SdkSandboxManagerLocal;
import com.android.server.uri.GrantUri;
import com.android.server.uri.NeededUriGrants;
import com.android.server.uri.UriGrantsManagerInternal;
@@ -6584,6 +6585,30 @@
final ProcessRecord addAppLocked(ApplicationInfo info, String customProcess, boolean isolated,
boolean disableHiddenApiChecks, boolean disableTestApiChecks,
String abiOverride, int zygotePolicyFlags) {
+ return addAppLocked(
+ info,
+ customProcess,
+ isolated,
+ /* isSdkSandbox= */ false,
+ /* sdkSandboxUid= */ 0,
+ /* sdkSandboxClientAppPackage= */ null,
+ disableHiddenApiChecks,
+ disableTestApiChecks,
+ abiOverride,
+ zygotePolicyFlags);
+ }
+
+ final ProcessRecord addAppLocked(
+ ApplicationInfo info,
+ String customProcess,
+ boolean isolated,
+ boolean isSdkSandbox,
+ int sdkSandboxUid,
+ @Nullable String sdkSandboxClientAppPackage,
+ boolean disableHiddenApiChecks,
+ boolean disableTestApiChecks,
+ String abiOverride,
+ int zygotePolicyFlags) {
ProcessRecord app;
if (!isolated) {
app = getProcessRecordLocked(customProcess != null ? customProcess : info.processName,
@@ -6593,10 +6618,16 @@
}
if (app == null) {
- app = mProcessList.newProcessRecordLocked(info, customProcess, isolated, 0,
- false, 0, null,
+ app = mProcessList.newProcessRecordLocked(
+ info,
+ customProcess,
+ isolated,
+ /* isolatedUid= */0,
+ isSdkSandbox,
+ sdkSandboxUid,
+ sdkSandboxClientAppPackage,
new HostingRecord("added application",
- customProcess != null ? customProcess : info.processName));
+ customProcess != null ? customProcess : info.processName));
updateLruProcessLocked(app, false, null);
updateOomAdjLocked(app, OomAdjuster.OOM_ADJ_REASON_PROCESS_BEGIN);
}
@@ -6606,13 +6637,16 @@
Event.APP_COMPONENT_USED);
// This package really, really can not be stopped.
- try {
- AppGlobals.getPackageManager().setPackageStoppedState(
- info.packageName, false, UserHandle.getUserId(app.uid));
- } catch (RemoteException e) {
- } catch (IllegalArgumentException e) {
- Slog.w(TAG, "Failed trying to unstop package "
- + info.packageName + ": " + e);
+ // TODO: how set package stopped state should work for sdk sandboxes?
+ if (!isSdkSandbox) {
+ try {
+ AppGlobals.getPackageManager().setPackageStoppedState(
+ info.packageName, false, UserHandle.getUserId(app.uid));
+ } catch (RemoteException e) {
+ } catch (IllegalArgumentException e) {
+ Slog.w(TAG, "Failed trying to unstop package "
+ + info.packageName + ": " + e);
+ }
}
if ((info.flags & PERSISTENT_MASK) == PERSISTENT_MASK) {
@@ -14410,6 +14444,32 @@
}
}
+ boolean disableHiddenApiChecks = ai.usesNonSdkApi()
+ || (flags & INSTR_FLAG_DISABLE_HIDDEN_API_CHECKS) != 0;
+ boolean disableTestApiChecks = disableHiddenApiChecks
+ || (flags & INSTR_FLAG_DISABLE_TEST_API_CHECKS) != 0;
+
+ if (disableHiddenApiChecks || disableTestApiChecks) {
+ enforceCallingPermission(android.Manifest.permission.DISABLE_HIDDEN_API_CHECKS,
+ "disable hidden API checks");
+ }
+
+ if ((flags & ActivityManager.INSTR_FLAG_INSTRUMENT_SDK_SANDBOX) != 0) {
+ return startInstrumentationOfSdkSandbox(
+ className,
+ profileFile,
+ arguments,
+ watcher,
+ uiAutomationConnection,
+ userId,
+ abiOverride,
+ ii,
+ ai,
+ noRestart,
+ disableHiddenApiChecks,
+ disableTestApiChecks);
+ }
+
ActiveInstrumentation activeInstr = new ActiveInstrumentation(this);
activeInstr.mClass = className;
String defProcess = ai.processName;;
@@ -14434,15 +14494,6 @@
START_FOREGROUND_SERVICES_FROM_BACKGROUND, callingPid, callingUid)
== PackageManager.PERMISSION_GRANTED;
activeInstr.mNoRestart = noRestart;
- boolean disableHiddenApiChecks = ai.usesNonSdkApi()
- || (flags & INSTR_FLAG_DISABLE_HIDDEN_API_CHECKS) != 0;
- boolean disableTestApiChecks = disableHiddenApiChecks
- || (flags & INSTR_FLAG_DISABLE_TEST_API_CHECKS) != 0;
-
- if (disableHiddenApiChecks || disableTestApiChecks) {
- enforceCallingPermission(android.Manifest.permission.DISABLE_HIDDEN_API_CHECKS,
- "disable hidden API checks");
- }
final long origId = Binder.clearCallingIdentity();
@@ -14488,6 +14539,111 @@
return true;
}
+ @GuardedBy("this")
+ private boolean startInstrumentationOfSdkSandbox(
+ ComponentName className,
+ String profileFile,
+ Bundle arguments,
+ IInstrumentationWatcher watcher,
+ IUiAutomationConnection uiAutomationConnection,
+ int userId,
+ String abiOverride,
+ InstrumentationInfo instrumentationInfo,
+ ApplicationInfo sdkSandboxClientAppInfo,
+ boolean noRestart,
+ boolean disableHiddenApiChecks,
+ boolean disableTestApiChecks) {
+
+ if (noRestart) {
+ reportStartInstrumentationFailureLocked(
+ watcher,
+ className,
+ "Instrumenting sdk sandbox with --no-restart flag is not supported");
+ return false;
+ }
+
+ final ApplicationInfo sdkSandboxInfo;
+ try {
+ final PackageManager pm = mContext.getPackageManager();
+ sdkSandboxInfo = pm.getApplicationInfoAsUser(pm.getSdkSandboxPackageName(), 0, userId);
+ } catch (NameNotFoundException e) {
+ reportStartInstrumentationFailureLocked(
+ watcher, className, "Can't find SdkSandbox package");
+ return false;
+ }
+
+ final SdkSandboxManagerLocal sandboxManagerLocal =
+ LocalManagerRegistry.getManager(SdkSandboxManagerLocal.class);
+ if (sandboxManagerLocal == null) {
+ reportStartInstrumentationFailureLocked(
+ watcher, className, "Can't locate SdkSandboxManagerLocal");
+ return false;
+ }
+
+ final String processName = sandboxManagerLocal.getSdkSandboxProcessNameForInstrumentation(
+ sdkSandboxClientAppInfo);
+
+ ActiveInstrumentation activeInstr = new ActiveInstrumentation(this);
+ activeInstr.mClass = className;
+ activeInstr.mTargetProcesses = new String[]{processName};
+ activeInstr.mTargetInfo = sdkSandboxInfo;
+ activeInstr.mProfileFile = profileFile;
+ activeInstr.mArguments = arguments;
+ activeInstr.mWatcher = watcher;
+ activeInstr.mUiAutomationConnection = uiAutomationConnection;
+ activeInstr.mResultClass = className;
+ activeInstr.mHasBackgroundActivityStartsPermission = false;
+ activeInstr.mHasBackgroundForegroundServiceStartsPermission = false;
+ // Instrumenting sdk sandbox without a restart is not supported
+ activeInstr.mNoRestart = false;
+
+ final int callingUid = Binder.getCallingUid();
+ final long token = Binder.clearCallingIdentity();
+ try {
+ sandboxManagerLocal.notifyInstrumentationStarted(
+ sdkSandboxClientAppInfo.packageName, sdkSandboxClientAppInfo.uid);
+ synchronized (mProcLock) {
+ int sdkSandboxUid = Process.toSdkSandboxUid(sdkSandboxClientAppInfo.uid);
+ // Kill the package sdk sandbox process belong to. At this point sdk sandbox is
+ // already killed.
+ forceStopPackageLocked(
+ instrumentationInfo.targetPackage,
+ /* appId= */ -1,
+ /* callerWillRestart= */ true,
+ /* purgeCache= */ false,
+ /* doIt= */ true,
+ /* evenPersistent= */ true,
+ /* uninstalling= */ false,
+ userId,
+ "start instr");
+
+ ProcessRecord app = addAppLocked(
+ sdkSandboxInfo,
+ processName,
+ /* isolated= */ false,
+ /* isSdkSandbox= */ true,
+ sdkSandboxUid,
+ sdkSandboxClientAppInfo.packageName,
+ disableHiddenApiChecks,
+ disableTestApiChecks,
+ abiOverride,
+ ZYGOTE_POLICY_FLAG_EMPTY);
+
+ app.setActiveInstrumentation(activeInstr);
+ activeInstr.mFinished = false;
+ activeInstr.mSourceUid = callingUid;
+ activeInstr.mRunningProcesses.add(app);
+ if (!mActiveInstrumentation.contains(activeInstr)) {
+ mActiveInstrumentation.add(activeInstr);
+ }
+ }
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+
+ return true;
+ }
+
private void instrumentWithoutRestart(ActiveInstrumentation activeInstr,
ApplicationInfo targetInfo) {
ProcessRecord pr;
@@ -14610,7 +14766,17 @@
app.setActiveInstrumentation(null);
}
- if (!instr.mNoRestart) {
+ if (app.isSdkSandbox) {
+ // For sharedUid apps this will kill all sdk sandbox processes, which is not ideal.
+ // TODO(b/209061624): should we call ProcessList.removeProcessLocked instead?
+ killUid(UserHandle.getAppId(app.uid), UserHandle.getUserId(app.uid), "finished instr");
+ final SdkSandboxManagerLocal sandboxManagerLocal =
+ LocalManagerRegistry.getManager(SdkSandboxManagerLocal.class);
+ if (sandboxManagerLocal != null) {
+ sandboxManagerLocal.notifyInstrumentationFinished(
+ app.sdkSandboxClientAppPackage, Process.getAppUidForSdkSandboxUid(app.uid));
+ }
+ } else if (!instr.mNoRestart) {
forceStopPackageLocked(app.info.packageName, -1, false, false, true, true, false,
app.userId,
"finished inst");