Do not depend on task activities decrementing with finish.

It is possible for a task to be cleared when a single activity is
finished. For example, if only task overlays are present after an
activity is removed, the task can be cleared under certain
conditions. As a result, we can not rely on only a single activity
being removed from a task when it is finished.

This changelist addresses this issue by caching the list of actvities
under a task that will not be modified as activities are finished.

Change-Id: Id3b0813deebd0bc31b2ff7ae7f69a2833dcb0a61
Fixes: 64291682
Test: atest FrameworksServicesTests:com.android.server.am.ActivityStackTests#testFinishDisabledPackageActivities
Test: atest FrameworksServicesTests:com.android.server.am.ActivityStackTests#testHandleAppDied
diff --git a/services/core/java/com/android/server/am/ActivityStack.java b/services/core/java/com/android/server/am/ActivityStack.java
index f4a4af2..666f3a2 100644
--- a/services/core/java/com/android/server/am/ActivityStack.java
+++ b/services/core/java/com/android/server/am/ActivityStack.java
@@ -347,6 +347,9 @@
     private final Rect mTmpRect2 = new Rect();
     private final ActivityOptions mTmpOptions = ActivityOptions.makeBasic();
 
+    /** List for processing through a set of activities */
+    private final ArrayList<ActivityRecord> mTmpActivities = new ArrayList<>();
+
     /** Run all ActivityStacks through this */
     protected final ActivityStackSupervisor mStackSupervisor;
 
@@ -4405,11 +4408,15 @@
                 "Removing app " + app + " from history with " + i + " entries");
         for (int taskNdx = mTaskHistory.size() - 1; taskNdx >= 0; --taskNdx) {
             final ArrayList<ActivityRecord> activities = mTaskHistory.get(taskNdx).mActivities;
-            for (int activityNdx = activities.size() - 1; activityNdx >= 0; --activityNdx) {
-                final ActivityRecord r = activities.get(activityNdx);
-                --i;
+            mTmpActivities.clear();
+            mTmpActivities.addAll(activities);
+
+            while (!mTmpActivities.isEmpty()) {
+                final int targetIndex = mTmpActivities.size() - 1;
+                final ActivityRecord r = mTmpActivities.remove(targetIndex);
                 if (DEBUG_CLEANUP) Slog.v(TAG_CLEANUP,
-                        "Record #" + i + " " + r + ": app=" + r.app);
+                        "Record #" + targetIndex + " " + r + ": app=" + r.app);
+
                 if (r.app == app) {
                     if (r.visible) {
                         hasVisibleActivities = true;
@@ -4827,9 +4834,11 @@
         ComponentName homeActivity = null;
         for (int taskNdx = mTaskHistory.size() - 1; taskNdx >= 0; --taskNdx) {
             final ArrayList<ActivityRecord> activities = mTaskHistory.get(taskNdx).mActivities;
-            int numActivities = activities.size();
-            for (int activityNdx = 0; activityNdx < numActivities; ++activityNdx) {
-                ActivityRecord r = activities.get(activityNdx);
+            mTmpActivities.clear();
+            mTmpActivities.addAll(activities);
+
+            while (!mTmpActivities.isEmpty()) {
+                ActivityRecord r = mTmpActivities.remove(0);
                 final boolean sameComponent =
                         (r.packageName.equals(packageName) && (filterByClasses == null
                                 || filterByClasses.contains(r.realActivity.getClassName())))
@@ -4862,12 +4871,8 @@
                         r.app = null;
                     }
                     lastTask = r.getTask();
-                    if (finishActivityLocked(r, Activity.RESULT_CANCELED, null, "force-stop",
-                            true)) {
-                        // r has been deleted from mActivities, accommodate.
-                        --numActivities;
-                        --activityNdx;
-                    }
+                    finishActivityLocked(r, Activity.RESULT_CANCELED, null, "force-stop",
+                            true);
                 }
             }
         }
diff --git a/services/core/java/com/android/server/am/TaskRecord.java b/services/core/java/com/android/server/am/TaskRecord.java
index 6f6e0d9..8174f40 100644
--- a/services/core/java/com/android/server/am/TaskRecord.java
+++ b/services/core/java/com/android/server/am/TaskRecord.java
@@ -2002,7 +2002,7 @@
         } else if (intent != null) {
             sb.append(" I=");
             sb.append(intent.getComponent().flattenToShortString());
-        } else if (affinityIntent != null) {
+        } else if (affinityIntent != null && affinityIntent.getComponent() != null) {
             sb.append(" aI=");
             sb.append(affinityIntent.getComponent().flattenToShortString());
         } else {
diff --git a/services/tests/servicestests/src/com/android/server/am/ActivityStackTests.java b/services/tests/servicestests/src/com/android/server/am/ActivityStackTests.java
index 32a29a2..d60623d 100644
--- a/services/tests/servicestests/src/com/android/server/am/ActivityStackTests.java
+++ b/services/tests/servicestests/src/com/android/server/am/ActivityStackTests.java
@@ -486,4 +486,45 @@
         verify(lifecycleManager, times(1)).scheduleTransaction(eq(app.thread),
                 eq(r.appToken), any(DestroyActivityItem.class));
     }
+
+    @Test
+    public void testFinishDisabledPackageActivities() throws Exception {
+        final ActivityRecord firstActivity = new ActivityBuilder(mService).setTask(mTask).build();
+        final ActivityRecord secondActivity = new ActivityBuilder(mService).setTask(mTask).build();
+
+        // Making the second activity a task overlay without an app means it will be removed from
+        // the task's activities as well once first activity is removed.
+        secondActivity.mTaskOverlay = true;
+        secondActivity.app = null;
+
+        assertEquals(mTask.mActivities.size(), 2);
+
+        mStack.finishDisabledPackageActivitiesLocked(firstActivity.packageName, null,
+                true /* doit */, true /* evenPersistent */, UserHandle.USER_ALL);
+
+        assertTrue(mTask.mActivities.isEmpty());
+        assertTrue(mStack.getAllTasks().isEmpty());
+    }
+
+    @Test
+    public void testHandleAppDied() throws Exception {
+        final ActivityRecord firstActivity = new ActivityBuilder(mService).setTask(mTask).build();
+        final ActivityRecord secondActivity = new ActivityBuilder(mService).setTask(mTask).build();
+
+        // Making the first activity a task overlay means it will be removed from the task's
+        // activities as well once second activity is removed as handleAppDied processes the
+        // activity list in reverse.
+        firstActivity.mTaskOverlay = true;
+        firstActivity.app = null;
+
+        // second activity will be immediately removed as it has no state.
+        secondActivity.haveState = false;
+
+        assertEquals(mTask.mActivities.size(), 2);
+
+        mStack.handleAppDiedLocked(secondActivity.app);
+
+        assertTrue(mTask.mActivities.isEmpty());
+        assertTrue(mStack.getAllTasks().isEmpty());
+    }
 }
diff --git a/services/tests/servicestests/src/com/android/server/am/ActivityStarterTests.java b/services/tests/servicestests/src/com/android/server/am/ActivityStarterTests.java
index b58c700..fdabfb4 100644
--- a/services/tests/servicestests/src/com/android/server/am/ActivityStarterTests.java
+++ b/services/tests/servicestests/src/com/android/server/am/ActivityStarterTests.java
@@ -197,11 +197,6 @@
         final ActivityInfo aInfo = containsConditions(preconditions, PRECONDITION_NO_ACTIVITY_INFO)
                 ?  null : new ActivityInfo();
 
-        if (aInfo != null) {
-            aInfo.applicationInfo = new ApplicationInfo();
-            aInfo.applicationInfo.packageName = ActivityBuilder.DEFAULT_PACKAGE;
-        }
-
         IVoiceInteractionSession voiceSession =
                 containsConditions(preconditions, PRECONDITION_SOURCE_VOICE_SESSION)
                 ? mock(IVoiceInteractionSession.class) : null;
@@ -210,6 +205,11 @@
         final ActivityBuilder builder = new ActivityBuilder(service).setTask(
                 new TaskBuilder(service.mStackSupervisor).setVoiceSession(voiceSession).build());
 
+        if (aInfo != null) {
+            aInfo.applicationInfo = new ApplicationInfo();
+            aInfo.applicationInfo.packageName = builder.getDefaultComponentPackageName();
+        }
+
         // Offset uid by one from {@link ActivityInfo} to simulate different uids.
         if (containsConditions(preconditions, PRECONDITION_DIFFERENT_UID)) {
             builder.setUid(aInfo.applicationInfo.uid + 1);
diff --git a/services/tests/servicestests/src/com/android/server/am/ActivityTestsBase.java b/services/tests/servicestests/src/com/android/server/am/ActivityTestsBase.java
index ff7b1d0..1195188 100644
--- a/services/tests/servicestests/src/com/android/server/am/ActivityTestsBase.java
+++ b/services/tests/servicestests/src/com/android/server/am/ActivityTestsBase.java
@@ -67,6 +67,12 @@
     private final Context mContext = InstrumentationRegistry.getContext();
     private HandlerThread mHandlerThread;
 
+    // Default package name
+    static final String DEFAULT_COMPONENT_PACKAGE_NAME = "com.foo";
+
+    // Default base activity name
+    private static final String DEFAULT_COMPONENT_CLASS_NAME = ".BarActivity";
+
     @Before
     public void setUp() throws Exception {
         if (!sOneTimeSetupDone) {
@@ -106,11 +112,7 @@
         // An id appended to the end of the component name to make it unique
         private static int sCurrentActivityId = 0;
 
-        // Default package name
-        static final String DEFAULT_PACKAGE = "com.foo";
 
-        // Default base activity name
-        private static final String DEFAULT_BASE_ACTIVITY_NAME = ".BarActivity";
 
         private final ActivityManagerService mService;
 
@@ -149,11 +151,15 @@
             return this;
         }
 
+        String getDefaultComponentPackageName() {
+            return DEFAULT_COMPONENT_PACKAGE_NAME;
+        }
+
         ActivityRecord build() {
             if (mComponent == null) {
                 final int id = sCurrentActivityId++;
-                mComponent = ComponentName.createRelative(DEFAULT_PACKAGE,
-                        DEFAULT_BASE_ACTIVITY_NAME + id);
+                mComponent = ComponentName.createRelative(DEFAULT_COMPONENT_PACKAGE_NAME,
+                        DEFAULT_COMPONENT_CLASS_NAME + id);
             }
 
             if (mCreateTask) {
@@ -191,6 +197,9 @@
      * Builder for creating new tasks.
      */
     protected static class TaskBuilder {
+        // Default package name
+        static final String DEFAULT_PACKAGE = "com.bar";
+
         private final ActivityStackSupervisor mSupervisor;
 
         private ComponentName mComponent;
@@ -252,6 +261,11 @@
             aInfo.applicationInfo.packageName = mPackage;
 
             Intent intent = new Intent();
+            if (mComponent == null) {
+                mComponent = ComponentName.createRelative(DEFAULT_COMPONENT_PACKAGE_NAME,
+                        DEFAULT_COMPONENT_CLASS_NAME);
+            }
+
             intent.setComponent(mComponent);
             intent.setFlags(mFlags);
 
@@ -312,6 +326,8 @@
             doNothing().when(supervisor).ensureActivitiesVisibleLocked(any(), anyInt(), anyBoolean());
             // Do not schedule idle timeouts
             doNothing().when(supervisor).scheduleIdleTimeoutLocked(any());
+            // unit test version does not handle launch wake lock
+            doNothing().when(supervisor).acquireLaunchWakelock();
 
             supervisor.initialize();