diff options
9 files changed, 173 insertions, 163 deletions
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java index ca34b27611d3..01f2fc6e3c92 100644 --- a/services/core/java/com/android/server/wm/ActivityRecord.java +++ b/services/core/java/com/android/server/wm/ActivityRecord.java @@ -2865,7 +2865,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A if (hasProcess()) { if (removeFromApp) { - app.removeActivity(this); + app.removeActivity(this, true /* keepAssociation */); if (!app.hasActivities()) { mAtmService.clearHeavyWeightProcessIfEquals(app); // Update any services we are bound to that might care about whether @@ -2914,7 +2914,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A setState(DESTROYED, "destroyActivityLocked. not finishing or skipping destroy"); if (DEBUG_APP) Slog.v(TAG_APP, "Clearing app during destroy for activity " + this); - app = null; + detachFromProcess(); } } else { // Remove this record from the history. @@ -2963,13 +2963,20 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A } setState(DESTROYED, "removeFromHistory"); if (DEBUG_APP) Slog.v(TAG_APP, "Clearing app during remove for activity " + this); - app = null; + detachFromProcess(); removeAppTokenFromDisplay(); cleanUpActivityServices(); removeUriPermissionsLocked(); } + void detachFromProcess() { + if (app != null) { + app.removeActivity(this, false /* keepAssociation */); + } + app = null; + } + void makeFinishingLocked() { if (finishing) { return; @@ -3019,7 +3026,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A if (setState) { setState(DESTROYED, "cleanUp"); if (DEBUG_APP) Slog.v(TAG_APP, "Clearing app during cleanUp for activity " + this); - app = null; + detachFromProcess(); } // Inform supervisor the activity has been removed. @@ -3160,6 +3167,64 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A mServiceConnectionsHolder = null; } + /** + * Detach this activity from process and clear the references to it. If the activity is + * finishing or has no saved state or crashed many times, it will also be removed from history. + */ + void handleAppDied() { + final boolean remove; + if ((mRelaunchReason == RELAUNCH_REASON_WINDOWING_MODE_RESIZE + || mRelaunchReason == RELAUNCH_REASON_FREE_RESIZE) + && launchCount < 3 && !finishing) { + // If the process crashed during a resize, always try to relaunch it, unless it has + // failed more than twice. Skip activities that's already finishing cleanly by itself. + remove = false; + } else if ((!mHaveState && !stateNotNeeded + && !isState(ActivityState.RESTARTING_PROCESS)) || finishing) { + // Don't currently have state for the activity, or it is finishing -- always remove it. + remove = true; + } else if (!mVisibleRequested && launchCount > 2 + && lastLaunchTime > (SystemClock.uptimeMillis() - 60000)) { + // We have launched this activity too many times since it was able to run, so give up + // and remove it. (Note if the activity is visible, we don't remove the record. We leave + // the dead window on the screen but the process will not be restarted unless user + // explicitly tap on it.) + remove = true; + } else { + // The process may be gone, but the activity lives on! + remove = false; + } + if (remove) { + if (ActivityTaskManagerDebugConfig.DEBUG_ADD_REMOVE || DEBUG_CLEANUP) { + Slog.i(TAG_ADD_REMOVE, "Removing activity " + this + + " hasSavedState=" + mHaveState + " stateNotNeeded=" + stateNotNeeded + + " finishing=" + finishing + " state=" + mState + + " callers=" + Debug.getCallers(5)); + } + if (!finishing || (app != null && app.isRemoved())) { + Slog.w(TAG, "Force removing " + this + ": app died, no saved state"); + EventLogTags.writeWmFinishActivity(mUserId, System.identityHashCode(this), + task != null ? task.mTaskId : -1, shortComponentName, + "proc died without state saved"); + } + } else { + // We have the current state for this activity, so it can be restarted later + // when needed. + if (DEBUG_APP) { + Slog.v(TAG_APP, "Keeping entry during removeHistory for activity " + this); + } + // Set nowVisible to previous visible state. If the app was visible while it died, we + // leave the dead window on screen so it's basically visible. This is needed when user + // later tap on the dead window, we need to stop other apps when user transfers focus + // to the restarted activity. + nowVisible = mVisibleRequested; + } + cleanUp(true /* cleanServices */, true /* setState */); + if (remove) { + removeFromHistory("appDied"); + } + } + @Override void removeImmediately() { onRemovedFromDisplay(); diff --git a/services/core/java/com/android/server/wm/ActivityStackSupervisor.java b/services/core/java/com/android/server/wm/ActivityStackSupervisor.java index 04b1edc3eede..ed1ea353eb3e 100644 --- a/services/core/java/com/android/server/wm/ActivityStackSupervisor.java +++ b/services/core/java/com/android/server/wm/ActivityStackSupervisor.java @@ -891,7 +891,7 @@ public class ActivityStackSupervisor implements RecentTasks.Callbacks { // This is the first time we failed -- restart process and // retry. r.launchFailed = true; - proc.removeActivity(r); + proc.removeActivity(r, true /* keepAssociation */); throw e; } } finally { diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java index c4af3e2c04c9..f91ff6f1c76c 100644 --- a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java +++ b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java @@ -6789,11 +6789,14 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub { public void handleAppDied(WindowProcessController wpc, boolean restarting, Runnable finishInstrumentationCallback) { synchronized (mGlobalLockWithoutBoost) { - // Remove this application's activities from active lists. - boolean hasVisibleActivities = mRootWindowContainer.handleAppDied(wpc); - - wpc.clearRecentTasks(); - wpc.clearActivities(); + mStackSupervisor.beginDeferResume(); + final boolean hasVisibleActivities; + try { + // Remove this application's activities from active lists. + hasVisibleActivities = wpc.handleAppDied(); + } finally { + mStackSupervisor.endDeferResume(); + } if (wpc.isInstrumenting()) { finishInstrumentationCallback.run(); diff --git a/services/core/java/com/android/server/wm/RootWindowContainer.java b/services/core/java/com/android/server/wm/RootWindowContainer.java index 06dec7c8023d..526ec08930ea 100644 --- a/services/core/java/com/android/server/wm/RootWindowContainer.java +++ b/services/core/java/com/android/server/wm/RootWindowContainer.java @@ -2751,7 +2751,7 @@ class RootWindowContainer extends WindowContainer<DisplayContent> if (r.app != app) return; Slog.w(TAG, " Force finishing activity " + r.intent.getComponent().flattenToShortString()); - r.app = null; + r.detachFromProcess(); r.getDisplay().mDisplayContent.prepareAppTransition( TRANSIT_CRASHING_ACTIVITY_CLOSE, false /* alwaysKeepCurrent */); r.destroyIfPossible("handleAppCrashed"); @@ -3104,22 +3104,6 @@ class RootWindowContainer extends WindowContainer<DisplayContent> return null; } - boolean handleAppDied(WindowProcessController app) { - if (app.isRemoved()) { - // The package of the died process should be force-stopped, so make its activities as - // finishing to prevent the process from being started again if the next top (or being - // visible) activity also resides in the same process. - app.makeFinishingForProcessRemoved(); - } - return reduceOnAllTaskDisplayAreas((taskDisplayArea, result) -> { - for (int sNdx = taskDisplayArea.getStackCount() - 1; sNdx >= 0; --sNdx) { - final Task stack = taskDisplayArea.getStackAt(sNdx); - result |= stack.handleAppDied(app); - } - return result; - }, false /* initValue */); - } - void closeSystemDialogActivities(String reason) { forAllActivities((r) -> { if ((r.info.flags & ActivityInfo.FLAG_FINISH_ON_CLOSE_SYSTEM_DIALOGS) != 0 diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java index 897b68073bd2..f3620050bed2 100644 --- a/services/core/java/com/android/server/wm/Task.java +++ b/services/core/java/com/android/server/wm/Task.java @@ -86,8 +86,6 @@ import static com.android.server.wm.ActivityStackSupervisor.REMOVE_FROM_RECENTS; import static com.android.server.wm.ActivityStackSupervisor.dumpHistoryList; import static com.android.server.wm.ActivityStackSupervisor.printThisActivity; import static com.android.server.wm.ActivityTaskManagerDebugConfig.DEBUG_ADD_REMOVE; -import static com.android.server.wm.ActivityTaskManagerDebugConfig.DEBUG_ALL; -import static com.android.server.wm.ActivityTaskManagerDebugConfig.DEBUG_APP; import static com.android.server.wm.ActivityTaskManagerDebugConfig.DEBUG_CLEANUP; import static com.android.server.wm.ActivityTaskManagerDebugConfig.DEBUG_LOCKTASK; import static com.android.server.wm.ActivityTaskManagerDebugConfig.DEBUG_PAUSE; @@ -115,8 +113,6 @@ import static com.android.server.wm.ActivityTaskManagerDebugConfig.POSTFIX_VISIB import static com.android.server.wm.ActivityTaskManagerDebugConfig.TAG_ATM; import static com.android.server.wm.ActivityTaskManagerDebugConfig.TAG_WITH_CLASS_NAME; import static com.android.server.wm.ActivityTaskManagerService.H.FIRST_ACTIVITY_STACK_MSG; -import static com.android.server.wm.ActivityTaskManagerService.RELAUNCH_REASON_FREE_RESIZE; -import static com.android.server.wm.ActivityTaskManagerService.RELAUNCH_REASON_WINDOWING_MODE_RESIZE; import static com.android.server.wm.IdentifierProto.HASH_CODE; import static com.android.server.wm.IdentifierProto.TITLE; import static com.android.server.wm.IdentifierProto.USER_ID; @@ -746,111 +742,6 @@ class Task extends WindowContainer<WindowContainer> { } } - // TODO: Can we just loop through WindowProcessController#mActivities instead of doing this? - private final RemoveHistoryRecordsForApp mRemoveHistoryRecordsForApp = - new RemoveHistoryRecordsForApp(); - private class RemoveHistoryRecordsForApp { - private boolean mHasVisibleActivities; - private boolean mIsProcessRemoved; - private WindowProcessController mApp; - private ArrayList<ActivityRecord> mToRemove = new ArrayList<>(); - - boolean process(WindowProcessController app) { - mToRemove.clear(); - mHasVisibleActivities = false; - mApp = app; - mIsProcessRemoved = app.isRemoved(); - - final PooledConsumer c = PooledLambda.obtainConsumer( - RemoveHistoryRecordsForApp::addActivityToRemove, this, - PooledLambda.__(ActivityRecord.class)); - forAllActivities(c); - c.recycle(); - - while (!mToRemove.isEmpty()) { - processActivity(mToRemove.remove(0)); - } - - mApp = null; - return mHasVisibleActivities; - } - - private void addActivityToRemove(ActivityRecord r) { - if (r.app == mApp) { - mToRemove.add(r); - } - } - - private void processActivity(ActivityRecord r) { - if (DEBUG_CLEANUP) Slog.v(TAG_CLEANUP, "Record " + r + ": app=" + r.app); - - if (r.app != mApp) { - return; - } - if (r.isVisible() || r.mVisibleRequested) { - // While an activity launches a new activity, it's possible that the old - // activity is already requested to be hidden (mVisibleRequested=false), but - // this visibility is not yet committed, so isVisible()=true. - mHasVisibleActivities = true; - } - final boolean remove; - if ((r.mRelaunchReason == RELAUNCH_REASON_WINDOWING_MODE_RESIZE - || r.mRelaunchReason == RELAUNCH_REASON_FREE_RESIZE) - && r.launchCount < 3 && !r.finishing) { - // If the process crashed during a resize, always try to relaunch it, unless - // it has failed more than twice. Skip activities that's already finishing - // cleanly by itself. - remove = false; - } else if ((!r.hasSavedState() && !r.stateNotNeeded - && !r.isState(ActivityState.RESTARTING_PROCESS)) || r.finishing) { - // Don't currently have state for the activity, or - // it is finishing -- always remove it. - remove = true; - } else if (!r.mVisibleRequested && r.launchCount > 2 - && r.lastLaunchTime > (SystemClock.uptimeMillis() - 60000)) { - // We have launched this activity too many times since it was - // able to run, so give up and remove it. - // (Note if the activity is visible, we don't remove the record. - // We leave the dead window on the screen but the process will - // not be restarted unless user explicitly tap on it.) - remove = true; - } else { - // The process may be gone, but the activity lives on! - remove = false; - } - if (remove) { - if (DEBUG_ADD_REMOVE || DEBUG_CLEANUP) Slog.i(TAG_ADD_REMOVE, - "Removing activity " + r + " from stack " - + ": hasSavedState=" + r.hasSavedState() - + " stateNotNeeded=" + r.stateNotNeeded - + " finishing=" + r.finishing - + " state=" + r.getState() + " callers=" + Debug.getCallers(5)); - if (!r.finishing || mIsProcessRemoved) { - Slog.w(TAG, "Force removing " + r + ": app died, no saved state"); - EventLogTags.writeWmFinishActivity(r.mUserId, - System.identityHashCode(r), r.getTask().mTaskId, - r.shortComponentName, "proc died without state saved"); - } - } else { - // We have the current state for this activity, so - // it can be restarted later when needed. - if (DEBUG_ALL) Slog.v(TAG, "Keeping entry, setting app to null"); - if (DEBUG_APP) Slog.v(TAG_APP, - "Clearing app during removeHistory for activity " + r); - r.app = null; - // Set nowVisible to previous visible state. If the app was visible while - // it died, we leave the dead window on screen so it's basically visible. - // This is needed when user later tap on the dead window, we need to stop - // other apps when user transfers focus to the restarted activity. - r.nowVisible = r.mVisibleRequested; - } - r.cleanUp(true /* cleanServices */, true /* setState */); - if (remove) { - r.removeFromHistory("appDied"); - } - } - } - private final FindRootHelper mFindRootHelper = new FindRootHelper(); private class FindRootHelper { private ActivityRecord mRoot; @@ -7167,9 +7058,8 @@ class Task extends WindowContainer<WindowContainer> { /** * Reset local parameters because an app's activity died. * @param app The app of the activity that died. - * @return result from removeHistoryRecordsForAppLocked. */ - boolean handleAppDied(WindowProcessController app) { + void handleAppDied(WindowProcessController app) { if (mPausingActivity != null && mPausingActivity.app == app) { if (DEBUG_PAUSE || DEBUG_CLEANUP) Slog.v(TAG_PAUSE, "App died while pausing: " + mPausingActivity); @@ -7179,9 +7069,6 @@ class Task extends WindowContainer<WindowContainer> { mLastPausedActivity = null; mLastNoHistoryActivity = null; } - - mStackSupervisor.removeHistoryRecords(app); - return mRemoveHistoryRecordsForApp.process(app); } boolean dump(FileDescriptor fd, PrintWriter pw, boolean dumpAll, boolean dumpClient, diff --git a/services/core/java/com/android/server/wm/WindowProcessController.java b/services/core/java/com/android/server/wm/WindowProcessController.java index 6ba8769842f6..df59c56f5aff 100644 --- a/services/core/java/com/android/server/wm/WindowProcessController.java +++ b/services/core/java/com/android/server/wm/WindowProcessController.java @@ -177,8 +177,10 @@ public class WindowProcessController extends ConfigurationContainer<Configuratio // Whether this process has ever started a service with the BIND_INPUT_METHOD permission. private volatile boolean mHasImeService; - // all activities running in the process + /** All activities running in the process (exclude destroying). */ private final ArrayList<ActivityRecord> mActivities = new ArrayList<>(); + /** The activities will be removed but still belong to this process. */ + private ArrayList<ActivityRecord> mInactiveActivities; // any tasks this process had run root activities in private final ArrayList<Task> mRecentTasks = new ArrayList<>(); // The most recent top-most activity that was resumed in the process for pre-Q app. @@ -635,21 +637,39 @@ public class WindowProcessController extends ConfigurationContainer<Configuratio return; } mActivities.add(r); + if (mInactiveActivities != null) { + mInactiveActivities.remove(r); + } updateActivityConfigurationListener(); } - void removeActivity(ActivityRecord r) { + /** + * Indicates that the given activity is no longer active in this process. + * + * @param r The running activity to be removed. + * @param keepAssociation {@code true} if the activity still belongs to this process but will + * be removed soon, e.g. destroying. From the perspective of process + * priority, the process is not important if it only contains activities + * that are being destroyed. But the association is still needed to + * ensure all activities are reachable from this process. + */ + void removeActivity(ActivityRecord r, boolean keepAssociation) { + if (keepAssociation) { + if (mInactiveActivities == null) { + mInactiveActivities = new ArrayList<>(); + mInactiveActivities.add(r); + } else if (!mInactiveActivities.contains(r)) { + mInactiveActivities.add(r); + } + } else if (mInactiveActivities != null) { + mInactiveActivities.remove(r); + } mActivities.remove(r); updateActivityConfigurationListener(); } - void makeFinishingForProcessRemoved() { - for (int i = mActivities.size() - 1; i >= 0; --i) { - mActivities.get(i).makeFinishingLocked(); - } - } - void clearActivities() { + mInactiveActivities = null; mActivities.clear(); updateActivityConfigurationListener(); } @@ -1142,6 +1162,47 @@ public class WindowProcessController extends ConfigurationContainer<Configuratio mAtm.mH.sendMessage(m); } + /** + * Clean up the activities belonging to this process. + * + * @return {@code true} if the process has any visible activity. + */ + boolean handleAppDied() { + mAtm.mStackSupervisor.removeHistoryRecords(this); + + final boolean isRemoved = isRemoved(); + boolean hasVisibleActivities = false; + if (mInactiveActivities != null && !mInactiveActivities.isEmpty()) { + // Make sure that all activities in this process are handled. + mActivities.addAll(mInactiveActivities); + } + for (int i = mActivities.size() - 1; i >= 0; i--) { + final ActivityRecord r = mActivities.get(i); + if (r.mVisibleRequested || r.isVisible()) { + // While an activity launches a new activity, it's possible that the old activity + // is already requested to be hidden (mVisibleRequested=false), but this visibility + // is not yet committed, so isVisible()=true. + hasVisibleActivities = true; + } + if (isRemoved) { + // The package of the died process should be force-stopped, so make its activities + // as finishing to prevent the process from being started again if the next top (or + // being visible) activity also resides in the same process. + r.makeFinishingLocked(); + } + + final Task rootTask = r.getRootTask(); + if (rootTask != null) { + rootTask.handleAppDied(this); + } + r.handleAppDied(); + } + clearRecentTasks(); + clearActivities(); + + return hasVisibleActivities; + } + void registerDisplayConfigurationListener(DisplayContent displayContent) { if (displayContent == null) { return; diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java index e45ced64c5b6..2f020736d059 100644 --- a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java @@ -1327,12 +1327,15 @@ public class ActivityRecordTests extends ActivityTestsBase { public void testRemoveFromHistory() { final Task stack = mActivity.getRootTask(); final Task task = mActivity.getTask(); + final WindowProcessController wpc = mActivity.app; + assertTrue(wpc.hasActivities()); mActivity.removeFromHistory("test"); assertEquals(DESTROYED, mActivity.getState()); assertNull(mActivity.app); assertNull(mActivity.getTask()); + assertFalse(wpc.hasActivities()); assertEquals(0, task.getChildCount()); assertEquals(task.getRootTask(), task); assertEquals(0, stack.getChildCount()); diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityStackTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityStackTests.java index 775df74f88e0..dfdf686e5502 100644 --- a/services/tests/wmtests/src/com/android/server/wm/ActivityStackTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/ActivityStackTests.java @@ -1072,7 +1072,7 @@ public class ActivityStackTests extends ActivityTestsBase { assertEquals(2, mTask.getChildCount()); - mRootWindowContainer.handleAppDied(secondActivity.app); + secondActivity.app.handleAppDied(); assertFalse(mTask.hasChild()); assertFalse(mStack.hasChild()); @@ -1086,7 +1086,7 @@ public class ActivityStackTests extends ActivityTestsBase { activity.launchCount = 1; activity.setSavedState(null /* savedState */); - mRootWindowContainer.handleAppDied(activity.app); + activity.app.handleAppDied(); assertEquals(1, mTask.getChildCount()); assertEquals(1, mStack.getChildCount()); @@ -1100,7 +1100,7 @@ public class ActivityStackTests extends ActivityTestsBase { activity.launchCount = 3; activity.setSavedState(null /* savedState */); - mRootWindowContainer.handleAppDied(activity.app); + activity.app.handleAppDied(); assertFalse(mTask.hasChild()); assertFalse(mStack.hasChild()); @@ -1114,7 +1114,7 @@ public class ActivityStackTests extends ActivityTestsBase { activity.launchCount = 1; activity.setSavedState(null /* savedState */); - mRootWindowContainer.handleAppDied(activity.app); + activity.app.handleAppDied(); assertEquals(1, mTask.getChildCount()); assertEquals(1, mStack.getChildCount()); @@ -1128,7 +1128,7 @@ public class ActivityStackTests extends ActivityTestsBase { activity.launchCount = 3; activity.setSavedState(null /* savedState */); - mRootWindowContainer.handleAppDied(activity.app); + activity.app.handleAppDied(); assertFalse(mTask.hasChild()); assertFalse(mStack.hasChild()); diff --git a/services/tests/wmtests/src/com/android/server/wm/RootWindowContainerTests.java b/services/tests/wmtests/src/com/android/server/wm/RootWindowContainerTests.java index 2e4c9ea747c5..d9d07d9beb2b 100644 --- a/services/tests/wmtests/src/com/android/server/wm/RootWindowContainerTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/RootWindowContainerTests.java @@ -175,23 +175,30 @@ public class RootWindowContainerTests extends WindowTestsBase { @Test public void testForceStopPackage() { final Task task = new ActivityTestsBase.StackBuilder(mWm.mRoot).build(); - final ActivityRecord activity1 = task.getTopMostActivity(); - final ActivityRecord activity2 = - new ActivityTestsBase.ActivityBuilder(mWm.mAtmService).setStack(task).build(); - final WindowProcessController wpc = activity1.app; + final ActivityRecord activity = task.getTopMostActivity(); + final WindowProcessController wpc = activity.app; + final ActivityRecord[] activities = { + activity, + new ActivityTestsBase.ActivityBuilder(mWm.mAtmService) + .setStack(task).setUseProcess(wpc).build(), + new ActivityTestsBase.ActivityBuilder(mWm.mAtmService) + .setStack(task).setUseProcess(wpc).build() + }; + activities[0].detachFromProcess(); + activities[1].finishing = true; + activities[1].destroyImmediately(true /* removeFromApp */, "test"); spyOn(wpc); - activity1.app = null; - activity2.setProcess(wpc); doReturn(true).when(wpc).isRemoved(); mWm.mAtmService.mInternal.onForceStopPackage(wpc.mInfo.packageName, true /* doit */, false /* evenPersistent */, wpc.mUserId); // The activity without process should be removed. - assertEquals(1, task.getChildCount()); + assertEquals(2, task.getChildCount()); - mWm.mRoot.handleAppDied(wpc); - // The activity with process should be removed because WindowProcessController#isRemoved. + wpc.handleAppDied(); + // The activities with process should be removed because WindowProcessController#isRemoved. assertFalse(task.hasChild()); + assertFalse(wpc.hasActivities()); } } |