Add an am command to disable service restart backoff policy.

So service restart could happen sooner in testings.

BYPASS_INCLUSIVE_LANGUAGE_REASON=legacy API name

Bug: 170309420
Bug: 182832497
Test: atest FrameworksServicesTests:ServiceRestarterTest
Change-Id: I1180333f628c2bb3d974ae826a1657b0ada00594
diff --git a/services/core/java/com/android/server/am/ActiveServices.java b/services/core/java/com/android/server/am/ActiveServices.java
index 7f8d944..93cf6ce 100644
--- a/services/core/java/com/android/server/am/ActiveServices.java
+++ b/services/core/java/com/android/server/am/ActiveServices.java
@@ -70,6 +70,7 @@
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.annotation.UptimeMillisLong;
 import android.app.ActivityManager;
 import android.app.ActivityManagerInternal;
 import android.app.ActivityThread;
@@ -237,6 +238,12 @@
     private final SparseArray<AppOpCallback> mFgsAppOpCallbacks = new SparseArray<>();
 
     /**
+     * The list of packages with the service restart backoff disabled.
+     */
+    @GuardedBy("mAm")
+    private final ArraySet<String> mRestartBackoffDisabledPackages = new ArraySet<>();
+
+    /**
      * For keeping ActiveForegroundApps retaining state while the screen is off.
      */
     boolean mScreenOn = true;
@@ -3272,25 +3279,32 @@
                 }
             }
 
-            r.nextRestartTime = now + r.restartDelay;
+            if (isServiceRestartBackoffEnabledLocked(r.packageName)) {
+                r.nextRestartTime = now + r.restartDelay;
 
-            // Make sure that we don't end up restarting a bunch of services
-            // all at the same time.
-            boolean repeat;
-            do {
-                repeat = false;
+                // Make sure that we don't end up restarting a bunch of services
+                // all at the same time.
+                boolean repeat;
                 final long restartTimeBetween = mAm.mConstants.SERVICE_MIN_RESTART_TIME_BETWEEN;
-                for (int i=mRestartingServices.size()-1; i>=0; i--) {
-                    ServiceRecord r2 = mRestartingServices.get(i);
-                    if (r2 != r && r.nextRestartTime >= (r2.nextRestartTime-restartTimeBetween)
-                            && r.nextRestartTime < (r2.nextRestartTime+restartTimeBetween)) {
-                        r.nextRestartTime = r2.nextRestartTime + restartTimeBetween;
-                        r.restartDelay = r.nextRestartTime - now;
-                        repeat = true;
-                        break;
+                do {
+                    repeat = false;
+                    for (int i = mRestartingServices.size() - 1; i >= 0; i--) {
+                        final ServiceRecord r2 = mRestartingServices.get(i);
+                        if (r2 != r
+                                && r.nextRestartTime >= (r2.nextRestartTime - restartTimeBetween)
+                                && r.nextRestartTime < (r2.nextRestartTime + restartTimeBetween)) {
+                            r.nextRestartTime = r2.nextRestartTime + restartTimeBetween;
+                            r.restartDelay = r.nextRestartTime - now;
+                            repeat = true;
+                            break;
+                        }
                     }
-                }
-            } while (repeat);
+                } while (repeat);
+            } else {
+                // It's been forced to ignore the restart backoff, fix the delay here.
+                r.restartDelay = mAm.mConstants.SERVICE_RESTART_DURATION;
+                r.nextRestartTime = now + r.restartDelay;
+            }
 
         } else {
             // Persistent processes are immediately restarted, so there is no
@@ -3310,15 +3324,22 @@
 
         cancelForegroundNotificationLocked(r);
 
+        performScheduleRestartLocked(r, "Scheduling", reason, SystemClock.uptimeMillis());
+
+        return true;
+    }
+
+    @VisibleForTesting
+    @GuardedBy("mAm")
+    void performScheduleRestartLocked(ServiceRecord r, @NonNull String scheduling,
+            @NonNull String reason, @UptimeMillisLong long now) {
         mAm.mHandler.removeCallbacks(r.restarter);
         mAm.mHandler.postAtTime(r.restarter, r.nextRestartTime);
-        r.nextRestartTime = SystemClock.uptimeMillis() + r.restartDelay;
-        Slog.w(TAG, "Scheduling restart of crashed service "
+        r.nextRestartTime = now + r.restartDelay;
+        Slog.w(TAG, scheduling + " restart of crashed service "
                 + r.shortInstanceName + " in " + r.restartDelay + "ms for " + reason);
         EventLog.writeEvent(EventLogTags.AM_SCHEDULE_SERVICE_RESTART,
                 r.userId, r.shortInstanceName, r.restartDelay);
-
-        return true;
     }
 
     final void performServiceRestartLocked(ServiceRecord r) {
@@ -3383,6 +3404,52 @@
         }
     }
 
+    /**
+     * Toggle service restart backoff policy, used by {@link ActivityManagerShellCommand}.
+     */
+    @GuardedBy("mAm")
+    void setServiceRestartBackoffEnabledLocked(@NonNull String packageName, boolean enable,
+            @NonNull String reason) {
+        if (!enable) {
+            if (mRestartBackoffDisabledPackages.contains(packageName)) {
+                // Already disabled, do nothing.
+                return;
+            }
+            mRestartBackoffDisabledPackages.add(packageName);
+
+            final long now = SystemClock.uptimeMillis();
+            for (int i = 0, size = mRestartingServices.size(); i < size; i++) {
+                final ServiceRecord r = mRestartingServices.get(i);
+                if (TextUtils.equals(r.packageName, packageName)) {
+                    final long remaining = r.nextRestartTime - now;
+                    if (remaining > mAm.mConstants.SERVICE_RESTART_DURATION) {
+                        r.restartDelay = mAm.mConstants.SERVICE_RESTART_DURATION;
+                        r.nextRestartTime = now + r.restartDelay;
+                        performScheduleRestartLocked(r, "Rescheduling", reason, now);
+                    }
+                }
+            }
+        } else {
+            removeServiceRestartBackoffEnabledLocked(packageName);
+            // For the simplicity, we are not going to reschedule its pending restarts
+            // when we turn the backoff policy back on.
+        }
+    }
+
+    @GuardedBy("mAm")
+    private void removeServiceRestartBackoffEnabledLocked(@NonNull String packageName) {
+        mRestartBackoffDisabledPackages.remove(packageName);
+    }
+
+    /**
+     * @return {@code false} if the given package has been disable from enforcing the service
+     * restart backoff policy, used by {@link ActivityManagerShellCommand}.
+     */
+    @GuardedBy("mAm")
+    boolean isServiceRestartBackoffEnabledLocked(@NonNull String packageName) {
+        return !mRestartBackoffDisabledPackages.contains(packageName);
+    }
+
     private String bringUpServiceLocked(ServiceRecord r, int intentFlags, boolean execInFg,
             boolean whileRestarting, boolean permissionsReviewRequired, boolean packageFrozen,
             boolean enqueueOomAdj)
@@ -4431,6 +4498,7 @@
                 mPendingBringups.removeAt(i);
             }
         }
+        removeServiceRestartBackoffEnabledLocked(packageName);
     }
 
     void cleanUpServices(int userId, ComponentName component, Intent baseIntent) {
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index 0f72c3e..14ca9da 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -8436,6 +8436,26 @@
         }
     }
 
+    /**
+     * Toggle service restart backoff policy, used by {@link ActivityManagerShellCommand}.
+     */
+    void setServiceRestartBackoffEnabled(@NonNull String packageName, boolean enable,
+            @NonNull String reason) {
+        synchronized (this) {
+            mServices.setServiceRestartBackoffEnabledLocked(packageName, enable, reason);
+        }
+    }
+
+    /**
+     * @return {@code false} if the given package has been disable from enforcing the service
+     * restart backoff policy, used by {@link ActivityManagerShellCommand}.
+     */
+    boolean isServiceRestartBackoffEnabled(@NonNull String packageName) {
+        synchronized (this) {
+            return mServices.isServiceRestartBackoffEnabledLocked(packageName);
+        }
+    }
+
     @Override
     public void onShellCommand(FileDescriptor in, FileDescriptor out,
             FileDescriptor err, String[] args, ShellCallback callback,
diff --git a/services/core/java/com/android/server/am/ActivityManagerShellCommand.java b/services/core/java/com/android/server/am/ActivityManagerShellCommand.java
index b8e06ee..42aac29 100644
--- a/services/core/java/com/android/server/am/ActivityManagerShellCommand.java
+++ b/services/core/java/com/android/server/am/ActivityManagerShellCommand.java
@@ -318,6 +318,8 @@
                     return runRefreshSettingsCache();
                 case "memory-factor":
                     return runMemoryFactor(pw);
+                case "service-restart-backoff":
+                    return runServiceRestartBackoff(pw);
                 default:
                     return handleDefaultCommands(cmd);
             }
@@ -3095,6 +3097,28 @@
         }
     }
 
+    private int runServiceRestartBackoff(PrintWriter pw) throws RemoteException {
+        mInternal.enforceCallingPermission(android.Manifest.permission.SET_PROCESS_LIMIT,
+                "runServiceRestartBackoff()");
+
+        final String opt = getNextArgRequired();
+        switch (opt) {
+            case "enable":
+                mInternal.setServiceRestartBackoffEnabled(getNextArgRequired(), true, "shell");
+                return 0;
+            case "disable":
+                mInternal.setServiceRestartBackoffEnabled(getNextArgRequired(), false, "shell");
+                return 0;
+            case "show":
+                pw.println(mInternal.isServiceRestartBackoffEnabled(getNextArgRequired())
+                        ? "enabled" : "disabled");
+                return 0;
+            default:
+                getErrPrintWriter().println("Error: unknown command '" + opt + "'");
+                return -1;
+        }
+    }
+
     private Resources getResources(PrintWriter pw) throws RemoteException {
         // system resources does not contain all the device configuration, construct it manually.
         Configuration config = mInterface.getConfiguration();
@@ -3418,6 +3442,11 @@
             pw.println("            Shows the existing memory pressure factor");
             pw.println("         reset");
             pw.println("            Removes existing override for memory pressure factor");
+            pw.println("  service-restart-backoff <COMMAND> [...]: sub-commands to toggle service restart backoff policy.");
+            pw.println("         enable|disable <PACKAGE_NAME>");
+            pw.println("            Toggles the restart backoff policy on/off for <PACKAGE_NAME>.");
+            pw.println("         show <PACKAGE_NAME>");
+            pw.println("            Shows the restart backoff policy state for <PACKAGE_NAME>.");
             pw.println();
             Intent.printIntentArgsHelp(pw, "");
         }
diff --git a/services/tests/servicestests/AndroidManifest.xml b/services/tests/servicestests/AndroidManifest.xml
index 9513c6e..7c30b45 100644
--- a/services/tests/servicestests/AndroidManifest.xml
+++ b/services/tests/servicestests/AndroidManifest.xml
@@ -91,6 +91,7 @@
     <uses-permission android:name="android.permission.VIBRATE_ALWAYS_ON"/>
     <uses-permission android:name="android.permission.CONTROL_DEVICE_STATE"/>
     <uses-permission android:name="android.permission.READ_PROJECTION_STATE"/>
+    <uses-permission android:name="android.permission.KILL_UID"/>
 
     <!-- Uses API introduced in O (26) -->
     <uses-sdk android:minSdkVersion="1"
diff --git a/services/tests/servicestests/AndroidTest.xml b/services/tests/servicestests/AndroidTest.xml
index d34f783..5a0f1ee 100644
--- a/services/tests/servicestests/AndroidTest.xml
+++ b/services/tests/servicestests/AndroidTest.xml
@@ -23,7 +23,9 @@
         <option name="test-file-name" value="JobTestApp.apk" />
         <option name="test-file-name" value="ConnTestApp.apk" />
         <option name="test-file-name" value="SuspendTestApp.apk" />
-        <option name="test-file-name" value="SimpleServiceTestApp.apk" />
+        <option name="test-file-name" value="SimpleServiceTestApp1.apk" />
+        <option name="test-file-name" value="SimpleServiceTestApp2.apk" />
+        <option name="test-file-name" value="SimpleServiceTestApp3.apk" />
     </target_preparer>
 
     <option name="test-tag" value="FrameworksServicesTests" />
diff --git a/services/tests/servicestests/src/com/android/server/am/ActivityManagerTest.java b/services/tests/servicestests/src/com/android/server/am/ActivityManagerTest.java
index e13597d..e04841b 100644
--- a/services/tests/servicestests/src/com/android/server/am/ActivityManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/am/ActivityManagerTest.java
@@ -73,8 +73,9 @@
 public class ActivityManagerTest {
     private static final String TAG = "ActivityManagerTest";
 
-    private static final String TEST_APP = "com.android.servicestests.apps.simpleservicetestapp";
-    private static final String TEST_CLASS = TEST_APP + ".SimpleService";
+    private static final String TEST_APP = "com.android.servicestests.apps.simpleservicetestapp1";
+    private static final String TEST_CLASS =
+            "com.android.servicestests.apps.simpleservicetestapp.SimpleService";
     private static final int TEST_LOOPS = 100;
     private static final long AWAIT_TIMEOUT = 2000;
     private static final long CHECK_INTERVAL = 100;
diff --git a/services/tests/servicestests/src/com/android/server/am/ServiceRestarterTest.java b/services/tests/servicestests/src/com/android/server/am/ServiceRestarterTest.java
new file mode 100644
index 0000000..10f4c05
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/am/ServiceRestarterTest.java
@@ -0,0 +1,263 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.am;
+
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+
+import android.app.ActivityManager;
+import android.app.ActivityManager.OnUidImportanceListener;
+import android.app.ActivityManager.RunningAppProcessInfo;
+import android.app.Instrumentation;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.ApplicationInfo;
+import android.os.SystemClock;
+import android.test.suitebuilder.annotation.LargeTest;
+import android.util.Log;
+
+import androidx.test.InstrumentationRegistry;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.compatibility.common.util.SystemUtil;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * Build/Install/Run:
+ *  atest FrameworksServicesTests:ServiceRestarterTest
+ */
+@RunWith(AndroidJUnit4.class)
+public final class ServiceRestarterTest {
+    private static final String TAG = "ServiceRestarterTest";
+
+    private static final String TEST_PACKAGE1_NAME =
+            "com.android.servicestests.apps.simpleservicetestapp1";
+    private static final String TEST_PACKAGE2_NAME =
+            "com.android.servicestests.apps.simpleservicetestapp2";
+    private static final String TEST_PACKAGE3_NAME =
+            "com.android.servicestests.apps.simpleservicetestapp3";
+    private static final String TEST_SERVICE_NAME =
+            "com.android.servicestests.apps.simpleservicetestapp.SimpleService";
+
+    private static final long WAIT_MS = 5 * 1000;
+    private static final long WAIT_LONG_MS = 30 * 1000;
+
+    private static final int ACTION_START = 1;
+    private static final int ACTION_KILL = 2;
+    private static final int ACTION_WAIT = 4;
+    private static final int ACTION_STOPPKG = 8;
+    private static final int ACTION_ALL = ACTION_START | ACTION_KILL | ACTION_WAIT | ACTION_STOPPKG;
+
+    private Context mContext;
+    private Instrumentation mInstrumentation;
+    private int mTestPackage1Uid;
+    private int mTestPackage2Uid;
+    private int mTestPackage3Uid;
+
+    @Before
+    public void setUp() throws Exception {
+        mInstrumentation = InstrumentationRegistry.getInstrumentation();
+        mContext = InstrumentationRegistry.getContext();
+        ApplicationInfo ai = mContext.getPackageManager().getApplicationInfo(TEST_PACKAGE1_NAME, 0);
+        mTestPackage1Uid = ai.uid;
+        ai = mContext.getPackageManager().getApplicationInfo(TEST_PACKAGE2_NAME, 0);
+        mTestPackage2Uid = ai.uid;
+        ai = mContext.getPackageManager().getApplicationInfo(TEST_PACKAGE3_NAME, 0);
+        mTestPackage3Uid = ai.uid;
+    }
+
+    @LargeTest
+    @Test
+    public void testDisableServiceRestartBackoff() throws Exception {
+        final ActivityManager am = mContext.getSystemService(ActivityManager.class);
+        final MyUidImportanceListener uid1Listener1 = new MyUidImportanceListener(mTestPackage1Uid);
+        final MyUidImportanceListener uid1Listener2 = new MyUidImportanceListener(mTestPackage1Uid);
+        final MyUidImportanceListener uid2Listener1 = new MyUidImportanceListener(mTestPackage2Uid);
+        final MyUidImportanceListener uid2Listener2 = new MyUidImportanceListener(mTestPackage2Uid);
+        final MyUidImportanceListener uid3Listener1 = new MyUidImportanceListener(mTestPackage3Uid);
+        final MyUidImportanceListener uid3Listener2 = new MyUidImportanceListener(mTestPackage3Uid);
+        try {
+            am.addOnUidImportanceListener(uid1Listener1, RunningAppProcessInfo.IMPORTANCE_SERVICE);
+            am.addOnUidImportanceListener(uid1Listener2, RunningAppProcessInfo.IMPORTANCE_GONE);
+            am.addOnUidImportanceListener(uid2Listener1, RunningAppProcessInfo.IMPORTANCE_SERVICE);
+            am.addOnUidImportanceListener(uid2Listener2, RunningAppProcessInfo.IMPORTANCE_GONE);
+            am.addOnUidImportanceListener(uid3Listener1, RunningAppProcessInfo.IMPORTANCE_SERVICE);
+            am.addOnUidImportanceListener(uid3Listener2, RunningAppProcessInfo.IMPORTANCE_GONE);
+            executeShellCmd("cmd deviceidle whitelist +" + TEST_PACKAGE1_NAME);
+            executeShellCmd("cmd deviceidle whitelist +" + TEST_PACKAGE2_NAME);
+            executeShellCmd("cmd deviceidle whitelist +" + TEST_PACKAGE3_NAME);
+
+            // Issue the command to enable service backoff policy for app2.
+            executeShellCmd("am service-restart-backoff enable " + TEST_PACKAGE2_NAME);
+            // Test restarts in normal case
+            final long[] ts1 = startKillAndRestart(am, ACTION_START | ACTION_KILL | ACTION_WAIT,
+                    uid1Listener1, uid1Listener2, uid2Listener1, uid2Listener2,
+                    uid3Listener1, uid3Listener2, Long.MAX_VALUE);
+            assertTrue("app1 restart should be before app2", ts1[1] < ts1[2]);
+            assertTrue("app2 restart should be before app3", ts1[2] < ts1[3]);
+
+            // Issue the command to disable service backoff policy for app2.
+            executeShellCmd("am service-restart-backoff disable " + TEST_PACKAGE2_NAME);
+            // Test restarts again.
+            final long[] ts2 = startKillAndRestart(am, ACTION_KILL | ACTION_WAIT | ACTION_STOPPKG,
+                    uid1Listener1, uid1Listener2, uid2Listener1,
+                    uid2Listener2, uid3Listener1, uid3Listener2, Long.MAX_VALUE);
+            assertTrue("app2 restart should be before app1", ts2[2] < ts2[1]);
+            assertTrue("app1 restart should be before app3", ts2[1] < ts2[3]);
+            assertTrue("app2 should be restart in a very short moment", ts2[2] - ts2[0] < WAIT_MS);
+
+            // Issue the command to enable service backoff policy for app2.
+            executeShellCmd("am service-restart-backoff enable " + TEST_PACKAGE2_NAME);
+            // Test restarts again.
+            final long[] ts3 = startKillAndRestart(am, ACTION_ALL, uid1Listener1, uid1Listener2,
+                    uid2Listener1, uid2Listener2, uid3Listener1, uid3Listener2, Long.MAX_VALUE);
+            assertTrue("app1 restart should be before app2", ts3[1] < ts3[2]);
+            assertTrue("app2 restart should be before app3", ts3[2] < ts3[3]);
+
+        } finally {
+            executeShellCmd("cmd deviceidle whitelist -" + TEST_PACKAGE1_NAME);
+            executeShellCmd("cmd deviceidle whitelist -" + TEST_PACKAGE2_NAME);
+            executeShellCmd("cmd deviceidle whitelist -" + TEST_PACKAGE3_NAME);
+            executeShellCmd("am service-restart-backoff enable " + TEST_PACKAGE2_NAME);
+            am.removeOnUidImportanceListener(uid1Listener1);
+            am.removeOnUidImportanceListener(uid1Listener2);
+            am.removeOnUidImportanceListener(uid2Listener1);
+            am.removeOnUidImportanceListener(uid2Listener2);
+            am.removeOnUidImportanceListener(uid3Listener1);
+            am.removeOnUidImportanceListener(uid3Listener2);
+            am.forceStopPackage(TEST_PACKAGE1_NAME);
+            am.forceStopPackage(TEST_PACKAGE2_NAME);
+            am.forceStopPackage(TEST_PACKAGE3_NAME);
+        }
+    }
+
+    private long[] startKillAndRestart(ActivityManager am, int action,
+            MyUidImportanceListener uid1Listener1, MyUidImportanceListener uid1Listener2,
+            MyUidImportanceListener uid2Listener1, MyUidImportanceListener uid2Listener2,
+            MyUidImportanceListener uid3Listener1, MyUidImportanceListener uid3Listener2,
+            long waitDuration) throws Exception {
+        final long[] res = new long[4];
+        // Test restarts in normal condition.
+        if ((action & ACTION_START) != 0) {
+            startServiceAndWait(TEST_PACKAGE1_NAME, uid1Listener1, WAIT_MS);
+            startServiceAndWait(TEST_PACKAGE2_NAME, uid2Listener1, WAIT_MS);
+            startServiceAndWait(TEST_PACKAGE3_NAME, uid3Listener1, WAIT_MS);
+        }
+
+        if ((action & ACTION_KILL) != 0) {
+            final long now = res[0] = SystemClock.uptimeMillis();
+            killUidAndWait(am, mTestPackage1Uid, uid1Listener2, WAIT_MS);
+            killUidAndWait(am, mTestPackage2Uid, uid2Listener2, WAIT_MS);
+            killUidAndWait(am, mTestPackage3Uid, uid3Listener2, WAIT_MS);
+        }
+
+        if ((action & ACTION_WAIT) != 0) {
+            assertTrue("Timed out to restart " + TEST_PACKAGE1_NAME, uid1Listener1.waitFor(
+                    RunningAppProcessInfo.IMPORTANCE_SERVICE, waitDuration));
+            assertTrue("Timed out to restart " + TEST_PACKAGE2_NAME, uid2Listener1.waitFor(
+                    RunningAppProcessInfo.IMPORTANCE_SERVICE, waitDuration));
+            assertTrue("Timed out to restart " + TEST_PACKAGE3_NAME, uid3Listener1.waitFor(
+                    RunningAppProcessInfo.IMPORTANCE_SERVICE, waitDuration));
+            res[1] = uid1Listener1.mCurrentTimestamp;
+            res[2] = uid2Listener1.mCurrentTimestamp;
+            res[3] = uid3Listener1.mCurrentTimestamp;
+        }
+
+        if ((action & ACTION_STOPPKG) != 0) {
+            // Force stop these packages to reset the backoff delays.
+            am.forceStopPackage(TEST_PACKAGE1_NAME);
+            am.forceStopPackage(TEST_PACKAGE2_NAME);
+            am.forceStopPackage(TEST_PACKAGE3_NAME);
+            assertTrue("Timed out to force-stop " + mTestPackage1Uid,
+                    uid1Listener2.waitFor(RunningAppProcessInfo.IMPORTANCE_GONE, WAIT_MS));
+            assertTrue("Timed out to force-stop " + mTestPackage2Uid,
+                    uid2Listener2.waitFor(RunningAppProcessInfo.IMPORTANCE_GONE, WAIT_MS));
+            assertTrue("Timed out to force-stop " + mTestPackage3Uid,
+                    uid3Listener2.waitFor(RunningAppProcessInfo.IMPORTANCE_GONE, WAIT_MS));
+        }
+        return res;
+    }
+
+    private void startServiceAndWait(String pkgName, MyUidImportanceListener uidListener,
+            long timeout) throws Exception {
+        final Intent intent = new Intent();
+        final ComponentName cn = ComponentName.unflattenFromString(
+                pkgName + "/" + TEST_SERVICE_NAME);
+        intent.setComponent(cn);
+        Log.i(TAG, "Starting service " + cn);
+        assertNotNull(mContext.startService(intent));
+        assertTrue("Timed out to start service " + cn,
+                uidListener.waitFor(RunningAppProcessInfo.IMPORTANCE_SERVICE, timeout));
+    }
+
+    private void killUidAndWait(ActivityManager am, int uid, MyUidImportanceListener uidListener,
+            long timeout) throws Exception {
+        am.killUid(uid, "test service restart");
+        assertTrue("Timed out to kill " + uid,
+                uidListener.waitFor(RunningAppProcessInfo.IMPORTANCE_GONE, timeout));
+    }
+
+    private String executeShellCmd(String cmd) throws Exception {
+        final String result = SystemUtil.runShellCommand(mInstrumentation, cmd);
+        Log.d(TAG, String.format("Output for '%s': %s", cmd, result));
+        return result;
+    }
+
+    private static class MyUidImportanceListener implements OnUidImportanceListener {
+        final CountDownLatch[] mLatchHolder = new CountDownLatch[1];
+        private final int mExpectedUid;
+        private int mExpectedImportance;
+        private int mCurrentImportance = RunningAppProcessInfo.IMPORTANCE_GONE;
+        long mCurrentTimestamp;
+
+        MyUidImportanceListener(int uid) {
+            mExpectedUid = uid;
+        }
+
+        @Override
+        public void onUidImportance(int uid, int importance) {
+            if (uid == mExpectedUid) {
+                mCurrentTimestamp = SystemClock.uptimeMillis();
+                synchronized (this) {
+                    if (importance == mExpectedImportance && mLatchHolder[0] != null) {
+                        mLatchHolder[0].countDown();
+                    }
+                    mCurrentImportance = importance;
+                }
+                Log.i(TAG, "uid " + uid + " importance: " + importance);
+            }
+        }
+
+        boolean waitFor(int expectedImportance, long timeout) throws Exception {
+            synchronized (this) {
+                mExpectedImportance = expectedImportance;
+                if (mCurrentImportance == expectedImportance) {
+                    return true;
+                }
+                mLatchHolder[0] = new CountDownLatch(1);
+            }
+            return mLatchHolder[0].await(timeout, TimeUnit.MILLISECONDS);
+        }
+    }
+}
diff --git a/services/tests/servicestests/test-apps/SimpleServiceTestApp/Android.bp b/services/tests/servicestests/test-apps/SimpleServiceTestApp/Android.bp
index 50b89d4..5550f7d 100644
--- a/services/tests/servicestests/test-apps/SimpleServiceTestApp/Android.bp
+++ b/services/tests/servicestests/test-apps/SimpleServiceTestApp/Android.bp
@@ -22,7 +22,7 @@
 }
 
 android_test_helper_app {
-    name: "SimpleServiceTestApp",
+    name: "SimpleServiceTestApp1",
 
     test_suites: ["device-tests"],
 
@@ -36,4 +36,47 @@
     optimize: {
         enabled: false,
     },
+    aaptflags: [
+        "--rename-manifest-package com.android.servicestests.apps.simpleservicetestapp1",
+    ],
+}
+
+android_test_helper_app {
+    name: "SimpleServiceTestApp2",
+
+    test_suites: ["device-tests"],
+
+    srcs: ["**/*.java"],
+
+    platform_apis: true,
+    certificate: "platform",
+    dex_preopt: {
+        enabled: false,
+    },
+    optimize: {
+        enabled: false,
+    },
+    aaptflags: [
+        "--rename-manifest-package com.android.servicestests.apps.simpleservicetestapp2",
+    ],
+}
+
+android_test_helper_app {
+    name: "SimpleServiceTestApp3",
+
+    test_suites: ["device-tests"],
+
+    srcs: ["**/*.java"],
+
+    platform_apis: true,
+    certificate: "platform",
+    dex_preopt: {
+        enabled: false,
+    },
+    optimize: {
+        enabled: false,
+    },
+    aaptflags: [
+        "--rename-manifest-package com.android.servicestests.apps.simpleservicetestapp3",
+    ],
 }
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 75f71d6..674ce8a 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
@@ -21,8 +21,11 @@
 import android.os.IBinder;
 import android.os.IRemoteCallback;
 import android.os.Process;
+import android.util.Log;
 
 public class SimpleService extends Service {
+    private static final String TAG = "SimpleService";
+
     private final IRemoteCallback.Stub mBinder = new IRemoteCallback.Stub() {
         @Override
         public void sendResult(Bundle bundle) {
@@ -31,6 +34,12 @@
     };
 
     @Override
+    public int onStartCommand(Intent intent, int flags, int startId) {
+        Log.i(TAG, "onStartCommand");
+        return START_STICKY;
+    }
+
+    @Override
     public IBinder onBind(Intent intent) {
         return mBinder;
     }