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;
     }
 }