diff --git a/services/core/java/com/android/server/am/ActivityStack.java b/services/core/java/com/android/server/am/ActivityStack.java index 00ebcbd92053a..649d5770f6a9b 100644 --- a/services/core/java/com/android/server/am/ActivityStack.java +++ b/services/core/java/com/android/server/am/ActivityStack.java @@ -3544,7 +3544,15 @@ class ActivityStack extends ConfigurationContai mService.updateOomAdjLocked(); } - final TaskRecord finishTopRunningActivityLocked(ProcessRecord app, String reason) { + /** + * Finish the topmost activity that belongs to the crashed app. We may also finish the activity + * that requested launch of the crashed one to prevent launch-crash loop. + * @param app The app that crashed. + * @param reason Reason to perform this action. + * @return The task that was finished in this stack, {@code null} if top running activity does + * not belong to the crashed app. + */ + final TaskRecord finishTopCrashedActivityLocked(ProcessRecord app, String reason) { ActivityRecord r = topRunningActivityLocked(); TaskRecord finishedTask = null; if (r == null || r.app != app) { diff --git a/services/core/java/com/android/server/am/ActivityStackSupervisor.java b/services/core/java/com/android/server/am/ActivityStackSupervisor.java index d5dfdcf0f5587..ea089e0ce2396 100644 --- a/services/core/java/com/android/server/am/ActivityStackSupervisor.java +++ b/services/core/java/com/android/server/am/ActivityStackSupervisor.java @@ -2126,15 +2126,22 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D } } - TaskRecord finishTopRunningActivityLocked(ProcessRecord app, String reason) { + /** + * Finish the topmost activities in all stacks that belong to the crashed app. + * @param app The app that crashed. + * @param reason Reason to perform this action. + * @return The task that was finished in this stack, {@code null} if haven't found any. + */ + TaskRecord finishTopCrashedActivitiesLocked(ProcessRecord app, String reason) { TaskRecord finishedTask = null; ActivityStack focusedStack = getFocusedStack(); for (int displayNdx = mActivityDisplays.size() - 1; displayNdx >= 0; --displayNdx) { final ActivityDisplay display = mActivityDisplays.valueAt(displayNdx); - final int numStacks = display.getChildCount(); - for (int stackNdx = 0; stackNdx < numStacks; ++stackNdx) { + // It is possible that request to finish activity might also remove its task and stack, + // so we need to be careful with indexes in the loop and check child count every time. + for (int stackNdx = 0; stackNdx < display.getChildCount(); ++stackNdx) { final ActivityStack stack = display.getChildAt(stackNdx); - TaskRecord t = stack.finishTopRunningActivityLocked(app, reason); + final TaskRecord t = stack.finishTopCrashedActivityLocked(app, reason); if (stack == focusedStack || finishedTask == null) { finishedTask = t; } diff --git a/services/core/java/com/android/server/am/AppErrors.java b/services/core/java/com/android/server/am/AppErrors.java index d5bb7ed6fb36f..bd1000ac568ca 100644 --- a/services/core/java/com/android/server/am/AppErrors.java +++ b/services/core/java/com/android/server/am/AppErrors.java @@ -742,8 +742,8 @@ class AppErrors { } mService.mStackSupervisor.resumeFocusedStackTopActivityLocked(); } else { - TaskRecord affectedTask = - mService.mStackSupervisor.finishTopRunningActivityLocked(app, reason); + final TaskRecord affectedTask = + mService.mStackSupervisor.finishTopCrashedActivitiesLocked(app, reason); if (data != null) { data.task = affectedTask; } diff --git a/services/tests/servicestests/src/com/android/server/am/ActivityStackSupervisorTests.java b/services/tests/servicestests/src/com/android/server/am/ActivityStackSupervisorTests.java index cda968a7ec3e8..9663a1b58686a 100644 --- a/services/tests/servicestests/src/com/android/server/am/ActivityStackSupervisorTests.java +++ b/services/tests/servicestests/src/com/android/server/am/ActivityStackSupervisorTests.java @@ -265,4 +265,26 @@ public class ActivityStackSupervisorTests extends ActivityTestsBase { // Supervisor should skip over the non-existent display. assertEquals(null, mSupervisor.topRunningActivityLocked()); } + + /** + * Verifies that removal of activity with task and stack is done correctly. + */ + @Test + public void testRemovingStackOnAppCrash() throws Exception { + final ActivityDisplay defaultDisplay = mService.mStackSupervisor.getDefaultDisplay(); + final int originalStackCount = defaultDisplay.getChildCount(); + final ActivityStack stack = mService.mStackSupervisor.getDefaultDisplay().createStack( + WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, false /* onTop */); + final ActivityRecord firstActivity = new ActivityBuilder(mService).setCreateTask(true) + .setStack(stack).build(); + + assertEquals(originalStackCount + 1, defaultDisplay.getChildCount()); + + // Let's pretend that the app has crashed. + firstActivity.app.thread = null; + mService.mStackSupervisor.finishTopCrashedActivitiesLocked(firstActivity.app, "test"); + + // Verify that the stack was removed. + assertEquals(originalStackCount, defaultDisplay.getChildCount()); + } }