Allow service auto-restarts when its dependency is force-stopped
While force-stopping a package, if it's being loaded as dependency
of another package, previously the processes of the latter package
will be stopped too, now retain the auto-restartable services in
the latter package and restart them later.
Bug: 190749907
Bug: 200137455
Test: atest FrameworksServicesTests#ServiceRestarterTest
Change-Id: Ieef1fcee01f3009251f5a8e147063665cedaae90
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index ded115b..bee4f39f 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -4275,7 +4275,7 @@
didSomething |= mProcessList.killPackageProcessesLSP(packageName, appId, userId,
ProcessList.INVALID_ADJ, callerWillRestart, false /* allowRestart */, doit,
- evenPersistent, true /* setRemoved */,
+ evenPersistent, true /* setRemoved */, uninstalling,
packageName == null ? ApplicationExitInfo.REASON_USER_STOPPED
: ApplicationExitInfo.REASON_USER_REQUESTED,
ApplicationExitInfo.SUBREASON_UNKNOWN,
@@ -7358,6 +7358,7 @@
ProcessList.PERSISTENT_PROC_ADJ, false /* callerWillRestart */,
true /* callerWillRestart */, true /* doit */,
true /* evenPersistent */, false /* setRemoved */,
+ false /* uninstalling */,
ApplicationExitInfo.REASON_OTHER,
ApplicationExitInfo.SUBREASON_KILL_UID,
reason != null ? reason : "kill uid");
@@ -7379,6 +7380,7 @@
ProcessList.PERSISTENT_PROC_ADJ, false /* callerWillRestart */,
true /* callerWillRestart */, true /* doit */,
true /* evenPersistent */, false /* setRemoved */,
+ false /* uninstalling */,
ApplicationExitInfo.REASON_PERMISSION_CHANGE,
ApplicationExitInfo.SUBREASON_UNKNOWN,
reason != null ? reason : "kill uid");
diff --git a/services/core/java/com/android/server/am/ProcessList.java b/services/core/java/com/android/server/am/ProcessList.java
index bbd41f7..80a8d63 100644
--- a/services/core/java/com/android/server/am/ProcessList.java
+++ b/services/core/java/com/android/server/am/ProcessList.java
@@ -135,7 +135,6 @@
import com.android.server.am.ActivityManagerService.ProcessChangeItem;
import com.android.server.compat.PlatformCompat;
import com.android.server.pm.dex.DexManager;
-import com.android.server.pm.parsing.pkg.AndroidPackage;
import com.android.server.pm.pkg.PackageStateInternal;
import com.android.server.wm.ActivityServiceConnectionsHolder;
import com.android.server.wm.WindowManagerService;
@@ -2794,8 +2793,8 @@
int reasonCode, int subReason, String reason) {
return killPackageProcessesLSP(packageName, appId, userId, minOomAdj,
false /* callerWillRestart */, true /* allowRestart */, true /* doit */,
- false /* evenPersistent */, false /* setRemoved */, reasonCode,
- subReason, reason);
+ false /* evenPersistent */, false /* setRemoved */, false /* uninstalling */,
+ reasonCode, subReason, reason);
}
@GuardedBy("mService")
@@ -2828,9 +2827,10 @@
@GuardedBy({"mService", "mProcLock"})
boolean killPackageProcessesLSP(String packageName, int appId,
int userId, int minOomAdj, boolean callerWillRestart, boolean allowRestart,
- boolean doit, boolean evenPersistent, boolean setRemoved, int reasonCode,
- int subReason, String reason) {
- ArrayList<ProcessRecord> procs = new ArrayList<>();
+ boolean doit, boolean evenPersistent, boolean setRemoved, boolean uninstalling,
+ int reasonCode, int subReason, String reason) {
+ final PackageManagerInternal pm = mService.getPackageManagerInternal();
+ final ArrayList<Pair<ProcessRecord, Boolean>> procs = new ArrayList<>();
// Remove all processes this package may have touched: all with the
// same UID (except for the system or root user), and all whose name
@@ -2847,7 +2847,18 @@
}
if (app.isRemoved()) {
if (doit) {
- procs.add(app);
+ boolean shouldAllowRestart = false;
+ if (!uninstalling && packageName != null) {
+ // This package has a dependency on the given package being stopped,
+ // while it's not being frozen nor uninstalled, allow to restart it.
+ shouldAllowRestart = !app.getPkgList().containsKey(packageName)
+ && app.getPkgDeps() != null
+ && app.getPkgDeps().contains(packageName)
+ && app.info != null
+ && !pm.isPackageFrozen(app.info.packageName, app.uid,
+ app.userId);
+ }
+ procs.add(new Pair<>(app, shouldAllowRestart));
}
continue;
}
@@ -2862,6 +2873,8 @@
continue;
}
+ boolean shouldAllowRestart = false;
+
// If no package is specified, we call all processes under the
// give user id.
if (packageName == null) {
@@ -2883,9 +2896,16 @@
if (userId != UserHandle.USER_ALL && app.userId != userId) {
continue;
}
- if (!app.getPkgList().containsKey(packageName) && !isDep) {
+ final boolean isInPkgList = app.getPkgList().containsKey(packageName);
+ if (!isInPkgList && !isDep) {
continue;
}
+ if (!isInPkgList && isDep && !uninstalling && app.info != null
+ && !pm.isPackageFrozen(app.info.packageName, app.uid, app.userId)) {
+ // This package has a dependency on the given package being stopped,
+ // while it's not being frozen nor uninstalled, allow to restart it.
+ shouldAllowRestart = true;
+ }
}
// Process has passed all conditions, kill it!
@@ -2895,13 +2915,14 @@
if (setRemoved) {
app.setRemoved(true);
}
- procs.add(app);
+ procs.add(new Pair<>(app, shouldAllowRestart));
}
}
int N = procs.size();
for (int i=0; i<N; i++) {
- removeProcessLocked(procs.get(i), callerWillRestart, allowRestart,
+ final Pair<ProcessRecord, Boolean> proc = procs.get(i);
+ removeProcessLocked(proc.first, callerWillRestart, allowRestart || proc.second,
reasonCode, subReason, reason);
}
killAppZygotesLocked(packageName, appId, userId, false /* force */);
diff --git a/services/tests/servicestests/AndroidTest.xml b/services/tests/servicestests/AndroidTest.xml
index 5a0f1ee..a95dad0 100644
--- a/services/tests/servicestests/AndroidTest.xml
+++ b/services/tests/servicestests/AndroidTest.xml
@@ -16,6 +16,13 @@
<configuration description="Runs Frameworks Services Tests.">
<option name="test-suite-tag" value="apct" />
<option name="test-suite-tag" value="apct-instrumentation" />
+
+ <target_preparer class="com.android.tradefed.targetprep.PushFilePreparer">
+ <option name="cleanup" value="true" />
+ <option name="push-file" key="SimpleServiceTestApp3.apk"
+ value="/data/local/tmp/cts/content/SimpleServiceTestApp3.apk" />
+ </target_preparer>
+
<target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
<option name="cleanup-apks" value="true" />
<option name="install-arg" value="-t" />
diff --git a/services/tests/servicestests/src/com/android/server/am/ServiceRestarterTest.java b/services/tests/servicestests/src/com/android/server/am/ServiceRestarterTest.java
index 9dd413b..4b359eb 100644
--- a/services/tests/servicestests/src/com/android/server/am/ServiceRestarterTest.java
+++ b/services/tests/servicestests/src/com/android/server/am/ServiceRestarterTest.java
@@ -20,6 +20,7 @@
import static com.android.internal.app.procstats.ProcessStats.ADJ_MEM_FACTOR_MODERATE;
import static com.android.internal.app.procstats.ProcessStats.ADJ_MEM_FACTOR_NORMAL;
+import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
@@ -30,7 +31,9 @@
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
+import android.content.ServiceConnection;
import android.content.pm.ApplicationInfo;
+import android.os.IBinder;
import android.os.SystemClock;
import android.provider.DeviceConfig;
import android.provider.Settings;
@@ -50,6 +53,8 @@
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
/**
* Build/Install/Run:
@@ -83,6 +88,12 @@
private static final int ACTION_STOPPKG = 8;
private static final int ACTION_ALL = ACTION_START | ACTION_KILL | ACTION_WAIT | ACTION_STOPPKG;
+ private static final String LOCAL_APK_BASE_PATH = "/data/local/tmp/cts/content/";
+ private static final String TEST_PACKAGE3_APK = "SimpleServiceTestApp3.apk";
+ private static final String ACTION_SERVICE_WITH_DEP_PKG =
+ "com.android.servicestests.apps.simpleservicetestapp.ACTION_SERVICE_WITH_DEP_PKG";
+ private static final String EXTRA_TARGET_PACKAGE = "target_package";
+
private SettingsSession<String> mAMConstantsSettings;
private DeviceConfigSession<String> mExtraDelaysDeviceConfig;
private DeviceConfigSession<Boolean> mEnableExtraDelaysDeviceConfig;
@@ -348,6 +359,83 @@
return res;
}
+ @Test
+ public void testServiceWithDepPkgStopped() throws Exception {
+ final CountDownLatch[] latchHolder = new CountDownLatch[1];
+ final ServiceConnection conn = new ServiceConnection() {
+ @Override
+ public void onServiceConnected(ComponentName name, IBinder service) {
+ latchHolder[0].countDown();
+ }
+
+ @Override
+ public void onServiceDisconnected(ComponentName name) {
+ latchHolder[0].countDown();
+ }
+ };
+
+ final long timeout = 5_000;
+ final long shortTimeout = 2_000;
+ final Intent intent = new Intent(ACTION_SERVICE_WITH_DEP_PKG);
+ final String testPkg = TEST_PACKAGE2_NAME;
+ final String libPkg = TEST_PACKAGE3_NAME;
+ final String apkPath = LOCAL_APK_BASE_PATH + TEST_PACKAGE3_APK;
+ final ActivityManager am = mContext.getSystemService(ActivityManager.class);
+
+ intent.setComponent(ComponentName.unflattenFromString(testPkg + "/" + TEST_SERVICE_NAME));
+ intent.putExtra(EXTRA_TARGET_PACKAGE, libPkg);
+ try {
+ executeShellCmd("am service-restart-backoff disable " + testPkg);
+
+ latchHolder[0] = new CountDownLatch(1);
+ assertTrue("Unable to bind to test service in " + testPkg,
+ mContext.bindService(intent, conn, Context.BIND_AUTO_CREATE));
+ assertTrue("Timed out to bind service in " + testPkg,
+ latchHolder[0].await(timeout, TimeUnit.MILLISECONDS));
+
+ Thread.sleep(shortTimeout);
+ assertTrue(libPkg + " should be a dependency package of " + testPkg,
+ isPackageDependency(testPkg, libPkg));
+
+ // Force-stop lib package, the test service should be restarted.
+ latchHolder[0] = new CountDownLatch(2);
+ am.forceStopPackage(libPkg);
+ assertTrue("Test service in didn't restart in " + testPkg,
+ latchHolder[0].await(timeout, TimeUnit.MILLISECONDS));
+
+ Thread.sleep(shortTimeout);
+
+ // Re-install the lib package, the test service should be restarted.
+ latchHolder[0] = new CountDownLatch(2);
+ assertTrue("Unable to install package " + apkPath, installPackage(apkPath));
+ assertTrue("Test service in didn't restart in " + testPkg,
+ latchHolder[0].await(timeout, TimeUnit.MILLISECONDS));
+
+ Thread.sleep(shortTimeout);
+
+ // Force-stop the service package, the test service should not be restarted.
+ latchHolder[0] = new CountDownLatch(2);
+ am.forceStopPackage(testPkg);
+ assertFalse("Test service should not be restarted in " + testPkg,
+ latchHolder[0].await(timeout * 2, TimeUnit.MILLISECONDS));
+ } finally {
+ executeShellCmd("am service-restart-backoff enable " + testPkg);
+ mContext.unbindService(conn);
+ am.forceStopPackage(testPkg);
+ }
+ }
+
+ private boolean isPackageDependency(String pkgName, String libPackage) throws Exception {
+ final String output = SystemUtil.runShellCommand("dumpsys activity processes " + pkgName);
+ final Matcher matcher = Pattern.compile("packageDependencies=\\{.*?\\b"
+ + libPackage + "\\b.*?\\}").matcher(output);
+ return matcher.find();
+ }
+
+ private boolean installPackage(String apkPath) throws Exception {
+ return executeShellCmd("pm install -t " + apkPath).equals("Success\n");
+ }
+
private void startServiceAndWait(String pkgName, MyUidImportanceListener uidListener,
long timeout) throws Exception {
final Intent intent = new Intent();
diff --git a/services/tests/servicestests/test-apps/SimpleServiceTestApp/AndroidManifest.xml b/services/tests/servicestests/test-apps/SimpleServiceTestApp/AndroidManifest.xml
index fdaf7cc..1bc4775 100644
--- a/services/tests/servicestests/test-apps/SimpleServiceTestApp/AndroidManifest.xml
+++ b/services/tests/servicestests/test-apps/SimpleServiceTestApp/AndroidManifest.xml
@@ -18,6 +18,7 @@
package="com.android.servicestests.apps.simpleservicetestapp">
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
+ <uses-permission android:name="android.permission.QUERY_ALL_PACKAGES" />
<application>
<service android:name=".SimpleService"
diff --git a/services/tests/servicestests/test-apps/SimpleServiceTestApp/src/com/android/servicestests/apps/simpleservicetestapp/SimpleService.java b/services/tests/servicestests/test-apps/SimpleServiceTestApp/src/com/android/servicestests/apps/simpleservicetestapp/SimpleService.java
index ae46f52..8270583 100644
--- a/services/tests/servicestests/test-apps/SimpleServiceTestApp/src/com/android/servicestests/apps/simpleservicetestapp/SimpleService.java
+++ b/services/tests/servicestests/test-apps/SimpleServiceTestApp/src/com/android/servicestests/apps/simpleservicetestapp/SimpleService.java
@@ -17,8 +17,10 @@
import android.app.Service;
import android.content.ComponentName;
+import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
+import android.content.pm.PackageManager;
import android.os.Bundle;
import android.os.IBinder;
import android.os.IRemoteCallback;
@@ -33,6 +35,9 @@
private static final String TEST_CLASS =
"com.android.servicestests.apps.simpleservicetestapp.SimpleService";
+ private static final String ACTION_SERVICE_WITH_DEP_PKG =
+ "com.android.servicestests.apps.simpleservicetestapp.ACTION_SERVICE_WITH_DEP_PKG";
+
private static final String EXTRA_CALLBACK = "callback";
private static final String EXTRA_COMMAND = "command";
private static final String EXTRA_FLAGS = "flags";
@@ -121,6 +126,21 @@
@Override
public IBinder onBind(Intent intent) {
+ if (ACTION_SERVICE_WITH_DEP_PKG.equals(intent.getAction())) {
+ final String targetPkg = intent.getStringExtra(EXTRA_TARGET_PACKAGE);
+ Log.i(TAG, "SimpleService.onBind: " + ACTION_SERVICE_WITH_DEP_PKG + " " + targetPkg);
+ if (targetPkg != null) {
+ Context pkgContext = null;
+ try {
+ pkgContext = createPackageContext(targetPkg,
+ Context.CONTEXT_INCLUDE_CODE | Context.CONTEXT_IGNORE_SECURITY);
+ } catch (PackageManager.NameNotFoundException e) {
+ Log.e(TAG, "Unable to create package context for " + pkgContext, e);
+ }
+ // This effectively loads the target package as a dependency.
+ pkgContext.getClassLoader();
+ }
+ }
return mBinder;
}
}