From 7ace395d65edb4764fc537ac1c9ed3c07bc72c33 Mon Sep 17 00:00:00 2001 From: Bryce Lee Date: Fri, 16 Feb 2018 14:34:32 -0800 Subject: Always finish activity when moving to a destroyed state. There is a possibility that an activity will not be marked as finishing when its state is moved to the destroying/destroyed state. This opens up the possibility of future lifecycle actions that are gated by the finishing flag. As a result, errant signals can be sent to the client for a destroyed activity. This changelist addresses the issue by limiting interaction with ActivityRecord's state to accessors. When the state is changed to destroyed or destroying, the activity is subsequently marked as finished. Bug: 71506345 Test: atest FrameworksServicesTests:com.android.server.am.ActivityRecordTests#testFinishingAfterDestroying Test: atest FrameworksServicesTests:com.android.server.am.ActivityRecordTests#testFinishingAfterDestroyed Change-Id: Iae8766201477103c9d632a16ecb9f6e95f796a45 --- .../android/server/am/ActivityManagerService.java | 12 +- .../java/com/android/server/am/ActivityRecord.java | 112 ++++++++++++++----- .../java/com/android/server/am/ActivityStack.java | 122 +++++++++++---------- .../android/server/am/ActivityStackSupervisor.java | 22 ++-- .../com/android/server/am/ActivityStarter.java | 4 +- .../java/com/android/server/am/TaskRecord.java | 2 +- .../com/android/server/am/ActivityRecordTests.java | 30 ++++- 7 files changed, 195 insertions(+), 109 deletions(-) diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java index efe51723612e..6fcdc3ee0d42 100644 --- a/services/core/java/com/android/server/am/ActivityManagerService.java +++ b/services/core/java/com/android/server/am/ActivityManagerService.java @@ -5689,8 +5689,7 @@ public class ActivityManagerService extends IActivityManager.Stub final long origId = Binder.clearCallingIdentity(); - if (self.state == ActivityState.RESUMED - || self.state == ActivityState.PAUSING) { + if (self.isState(ActivityState.RESUMED, ActivityState.PAUSING)) { mWindowManager.overridePendingAppTransition(packageName, enterAnim, exitAnim, null); } @@ -22774,7 +22773,7 @@ public class ActivityManagerService extends IActivityManager.Stub } } break; - } else if (r.state == ActivityState.PAUSING || r.state == ActivityState.PAUSED) { + } else if (r.isState(ActivityState.PAUSING, ActivityState.PAUSED)) { if (adj > ProcessList.PERCEPTIBLE_APP_ADJ) { adj = ProcessList.PERCEPTIBLE_APP_ADJ; app.adjType = "pause-activity"; @@ -22789,7 +22788,7 @@ public class ActivityManagerService extends IActivityManager.Stub app.cached = false; app.empty = false; foregroundActivities = true; - } else if (r.state == ActivityState.STOPPING) { + } else if (r.isState(ActivityState.STOPPING)) { if (adj > ProcessList.PERCEPTIBLE_APP_ADJ) { adj = ProcessList.PERCEPTIBLE_APP_ADJ; app.adjType = "stop-activity"; @@ -23183,9 +23182,8 @@ public class ActivityManagerService extends IActivityManager.Stub } final ActivityRecord a = cr.activity; if ((cr.flags&Context.BIND_ADJUST_WITH_ACTIVITY) != 0) { - if (a != null && adj > ProcessList.FOREGROUND_APP_ADJ && - (a.visible || a.state == ActivityState.RESUMED || - a.state == ActivityState.PAUSING)) { + if (a != null && adj > ProcessList.FOREGROUND_APP_ADJ && (a.visible + || a.isState(ActivityState.RESUMED, ActivityState.PAUSING))) { adj = ProcessList.FOREGROUND_APP_ADJ; if ((cr.flags&Context.BIND_NOT_FOREGROUND) == 0) { if ((cr.flags&Context.BIND_IMPORTANT) != 0) { diff --git a/services/core/java/com/android/server/am/ActivityRecord.java b/services/core/java/com/android/server/am/ActivityRecord.java index e6c1cd5ccfc0..06faeb9398b3 100644 --- a/services/core/java/com/android/server/am/ActivityRecord.java +++ b/services/core/java/com/android/server/am/ActivityRecord.java @@ -288,7 +288,7 @@ final class ActivityRecord extends ConfigurationContainer implements AppWindowCo HashSet connections; // All ConnectionRecord we hold UriPermissionOwner uriPermissions; // current special URI access perms. ProcessRecord app; // if non-null, hosting application - ActivityState state; // current state we are in + private ActivityState mState; // current state we are in Bundle icicle; // last saved activity state PersistableBundle persistentState; // last persistently saved activity state // TODO: See if this is still needed. @@ -381,8 +381,8 @@ final class ActivityRecord extends ConfigurationContainer implements AppWindowCo String getLifecycleDescription(String reason) { return "name= " + this + ", component=" + intent.getComponent().flattenToShortString() - + ", package=" + packageName + ", state=" + state + ", reason=" + reason + ", time=" - + System.currentTimeMillis(); + + ", package=" + packageName + ", state=" + mState + ", reason=" + reason + + ", time=" + System.currentTimeMillis(); } void dump(PrintWriter pw, String prefix) { @@ -503,7 +503,7 @@ final class ActivityRecord extends ConfigurationContainer implements AppWindowCo pw.println(); pw.print(prefix); pw.print("haveState="); pw.print(haveState); pw.print(" icicle="); pw.println(icicle); - pw.print(prefix); pw.print("state="); pw.print(state); + pw.print(prefix); pw.print("state="); pw.print(mState); pw.print(" stopped="); pw.print(stopped); pw.print(" delayedResume="); pw.print(delayedResume); pw.print(" finishing="); pw.println(finishing); @@ -841,7 +841,7 @@ final class ActivityRecord extends ConfigurationContainer implements AppWindowCo resultTo = _resultTo; resultWho = _resultWho; requestCode = _reqCode; - state = INITIALIZING; + setState(INITIALIZING, "ActivityRecord ctor"); frontOfTask = false; launchFailed = false; stopped = false; @@ -1000,6 +1000,11 @@ final class ActivityRecord extends ConfigurationContainer implements AppWindowCo } void removeWindowContainer() { + // Do not try to remove a window container if we have already removed it. + if (mWindowContainerController == null) { + return; + } + // Resume key dispatching if it is currently paused before we remove the container. resumeKeyDispatchingLocked(); @@ -1259,7 +1264,7 @@ final class ActivityRecord extends ConfigurationContainer implements AppWindowCo return false; } - switch (state) { + switch (mState) { case RESUMED: // When visible, allow entering PiP if the app is not locked. If it is over the // keyguard, then we will prompt to unlock in the caller before entering PiP. @@ -1385,13 +1390,13 @@ final class ActivityRecord extends ConfigurationContainer implements AppWindowCo // - It is currently resumed or paused. i.e. it is currently visible to the user and we want // the user to see the visual effects caused by the intent delivery now. // - The device is sleeping and it is the top activity behind the lock screen (b/6700897). - if ((state == RESUMED || state == PAUSED + if ((mState == RESUMED || mState == PAUSED || isTopActivityWhileSleeping) && app != null && app.thread != null) { try { ArrayList ar = new ArrayList<>(1); ar.add(rintent); service.mLifecycleManager.scheduleTransaction(app.thread, appToken, - NewIntentItem.obtain(ar, state == PAUSED)); + NewIntentItem.obtain(ar, mState == PAUSED)); unsent = false; } catch (RemoteException e) { Slog.w(TAG, "Exception thrown sending new intent to " + this, e); @@ -1573,6 +1578,63 @@ final class ActivityRecord extends ConfigurationContainer implements AppWindowCo mStackSupervisor.mAppVisibilitiesChangedSinceLastPause = true; } + void setState(ActivityState state, String reason) { + if (DEBUG_STATES) Slog.v(TAG_STATES, "State movement: " + this + " from:" + getState() + + " to:" + state + " reason:" + reason); + final boolean stateChanged = mState != state; + mState = state; + + if (stateChanged && isState(DESTROYING, DESTROYED)) { + makeFinishingLocked(); + + // When moving to the destroyed state, immediately destroy the activity in the + // associated stack. Most paths for finishing an activity will handle an activity's path + // to destroy through mechanisms such as ActivityStackSupervisor#mFinishingActivities. + // However, moving to the destroyed state directly (as in the case of an app dying) and + // marking it as finished will lead to cleanup steps that will prevent later handling + // from happening. + if (isState(DESTROYED)) { + final ActivityStack stack = getStack(); + if (stack != null) { + stack.activityDestroyedLocked(this, reason); + } + } + } + } + + ActivityState getState() { + return mState; + } + + /** + * Returns {@code true} if the Activity is in the specified state. + */ + boolean isState(ActivityState state) { + return state == mState; + } + + /** + * Returns {@code true} if the Activity is in one of the specified states. + */ + boolean isState(ActivityState state1, ActivityState state2) { + return state1 == mState || state2 == mState; + } + + /** + * Returns {@code true} if the Activity is in one of the specified states. + */ + boolean isState(ActivityState state1, ActivityState state2, ActivityState state3) { + return state1 == mState || state2 == mState || state3 == mState; + } + + /** + * Returns {@code true} if the Activity is in one of the specified states. + */ + boolean isState(ActivityState state1, ActivityState state2, ActivityState state3, + ActivityState state4) { + return state1 == mState || state2 == mState || state3 == mState || state4 == mState; + } + void notifyAppResumed(boolean wasStopped) { mWindowContainerController.notifyAppResumed(wasStopped); } @@ -1602,9 +1664,9 @@ final class ActivityRecord extends ConfigurationContainer implements AppWindowCo void makeVisibleIfNeeded(ActivityRecord starting) { // This activity is not currently visible, but is running. Tell it to become visible. - if (state == RESUMED || this == starting) { + if (mState == RESUMED || this == starting) { if (DEBUG_VISIBILITY) Slog.d(TAG_VISIBILITY, - "Not making visible, r=" + this + " state=" + state + " starting=" + starting); + "Not making visible, r=" + this + " state=" + mState + " starting=" + starting); return; } @@ -1627,13 +1689,13 @@ final class ActivityRecord extends ConfigurationContainer implements AppWindowCo mStackSupervisor.mGoingToSleepActivities.remove(this); // If the activity is stopped or stopping, cycle to the paused state. - if (state == STOPPED || state == STOPPING) { + if (isState(STOPPED, STOPPING)) { // Capture reason before state change final String reason = getLifecycleDescription("makeVisibleIfNeeded"); // An activity must be in the {@link PAUSING} state for the system to validate // the move to {@link PAUSED}. - state = PAUSING; + setState(PAUSING, "makeVisibleIfNeeded"); service.mLifecycleManager.scheduleTransaction(app.thread, appToken, PauseActivityItem.obtain(finishing, false /* userLeaving */, configChangeFlags, false /* dontReport */) @@ -1654,7 +1716,7 @@ final class ActivityRecord extends ConfigurationContainer implements AppWindowCo } } catch(RemoteException e) { } - return state == RESUMED; + return mState == RESUMED; } static void activityResumedLocked(IBinder token) { @@ -1728,7 +1790,7 @@ final class ActivityRecord extends ConfigurationContainer implements AppWindowCo final void activityStoppedLocked(Bundle newIcicle, PersistableBundle newPersistentState, CharSequence description) { final ActivityStack stack = getStack(); - if (state != STOPPING) { + if (mState != STOPPING) { Slog.i(TAG, "Activity reported stop, but no longer stopping: " + this); stack.mHandler.removeMessages(STOP_TIMEOUT_MSG, this); return; @@ -1751,7 +1813,7 @@ final class ActivityRecord extends ConfigurationContainer implements AppWindowCo if (DEBUG_STATES) Slog.v(TAG_STATES, "Moving to STOPPED: " + this + " (stop complete)"); stack.mHandler.removeMessages(STOP_TIMEOUT_MSG, this); stopped = true; - state = STOPPED; + setState(STOPPED, "activityStoppedLocked"); mWindowContainerController.notifyAppStopped(); @@ -2021,8 +2083,7 @@ final class ActivityRecord extends ConfigurationContainer implements AppWindowCo * currently pausing, or is resumed. */ public boolean isInterestingToUserLocked() { - return visible || nowVisible || state == PAUSING || - state == RESUMED; + return visible || nowVisible || mState == PAUSING || mState == RESUMED; } void setSleeping(boolean _sleeping) { @@ -2084,8 +2145,7 @@ final class ActivityRecord extends ConfigurationContainer implements AppWindowCo } final boolean isDestroyable() { - if (finishing || app == null || state == DESTROYING - || state == DESTROYED) { + if (finishing || app == null) { // This would be redundant. return false; } @@ -2151,7 +2211,7 @@ final class ActivityRecord extends ConfigurationContainer implements AppWindowCo compatInfo, nonLocalizedLabel, labelRes, icon, logo, windowFlags, prev != null ? prev.appToken : null, newTask, taskSwitch, isProcessRunning(), allowTaskSnapshot(), - state.ordinal() >= RESUMED.ordinal() && state.ordinal() <= STOPPED.ordinal(), + mState.ordinal() >= RESUMED.ordinal() && mState.ordinal() <= STOPPED.ordinal(), fromRecents); if (shown) { mStartingWindowState = STARTING_WINDOW_SHOWN; @@ -2328,7 +2388,7 @@ final class ActivityRecord extends ConfigurationContainer implements AppWindowCo } // Skip updating configuration for activity that are stopping or stopped. - if (state == STOPPING || state == STOPPED) { + if (mState == STOPPING || mState == STOPPED) { if (DEBUG_SWITCH || DEBUG_CONFIGURATION) Slog.v(TAG_CONFIGURATION, "Skipping config check stopped or stopping: " + this); return true; @@ -2378,7 +2438,7 @@ final class ActivityRecord extends ConfigurationContainer implements AppWindowCo setLastReportedConfiguration(service.getGlobalConfiguration(), newMergedOverrideConfig); - if (state == INITIALIZING) { + if (mState == INITIALIZING) { // No need to relaunch or schedule new config for activity that hasn't been launched // yet. We do, however, return after applying the config to activity record, so that // it will use it for launch transaction. @@ -2431,7 +2491,7 @@ final class ActivityRecord extends ConfigurationContainer implements AppWindowCo if (DEBUG_SWITCH || DEBUG_CONFIGURATION) Slog.v(TAG_CONFIGURATION, "Config is destroying non-running " + this); stack.destroyActivityLocked(this, true, "config"); - } else if (state == PAUSING) { + } else if (mState == PAUSING) { // A little annoying: we are waiting for this activity to finish pausing. Let's not // do anything now, but just flag that it needs to be restarted when done pausing. if (DEBUG_SWITCH || DEBUG_CONFIGURATION) Slog.v(TAG_CONFIGURATION, @@ -2439,7 +2499,7 @@ final class ActivityRecord extends ConfigurationContainer implements AppWindowCo deferRelaunchUntilPaused = true; preserveWindowOnDeferredRelaunch = preserveWindow; return true; - } else if (state == RESUMED) { + } else if (mState == RESUMED) { // Try to optimize this case: the configuration is changing and we need to restart // the top, resumed activity. Instead of doing the normal handshaking, just say // "restart!". @@ -2609,7 +2669,7 @@ final class ActivityRecord extends ConfigurationContainer implements AppWindowCo service.showAskCompatModeDialogLocked(this); } else { service.mHandler.removeMessages(PAUSE_TIMEOUT_MSG, this); - state = PAUSED; + setState(PAUSED, "relaunchActivityLocked"); // if the app is relaunched when it's stopped, and we're not resuming, // put it back into stopped state. if (stopped) { @@ -2853,7 +2913,7 @@ final class ActivityRecord extends ConfigurationContainer implements AppWindowCo final long token = proto.start(fieldId); super.writeToProto(proto, CONFIGURATION_CONTAINER, false /* trim */); writeIdentifierToProto(proto, IDENTIFIER); - proto.write(STATE, state.toString()); + proto.write(STATE, mState.toString()); proto.write(VISIBLE, visible); proto.write(FRONT_OF_TASK, frontOfTask); if (app != null) { diff --git a/services/core/java/com/android/server/am/ActivityStack.java b/services/core/java/com/android/server/am/ActivityStack.java index fe1067096609..817b69964f24 100644 --- a/services/core/java/com/android/server/am/ActivityStack.java +++ b/services/core/java/com/android/server/am/ActivityStack.java @@ -70,7 +70,12 @@ import static com.android.server.am.ActivityManagerDebugConfig.POSTFIX_USER_LEAV import static com.android.server.am.ActivityManagerDebugConfig.POSTFIX_VISIBILITY; import static com.android.server.am.ActivityManagerDebugConfig.TAG_AM; import static com.android.server.am.ActivityManagerDebugConfig.TAG_WITH_CLASS_NAME; +import static com.android.server.am.ActivityStack.ActivityState.DESTROYED; +import static com.android.server.am.ActivityStack.ActivityState.DESTROYING; +import static com.android.server.am.ActivityStack.ActivityState.FINISHING; import static com.android.server.am.ActivityStack.ActivityState.PAUSED; +import static com.android.server.am.ActivityStack.ActivityState.PAUSING; +import static com.android.server.am.ActivityStack.ActivityState.RESUMED; import static com.android.server.am.ActivityStack.ActivityState.STOPPED; import static com.android.server.am.ActivityStack.ActivityState.STOPPING; import static com.android.server.am.ActivityStackSupervisor.FindTaskResult; @@ -1353,8 +1358,7 @@ class ActivityStack extends ConfigurationContai final ArrayList activities = mTaskHistory.get(taskNdx).mActivities; for (int activityNdx = activities.size() - 1; activityNdx >= 0; --activityNdx) { final ActivityRecord r = activities.get(activityNdx); - if (r.state == STOPPING || r.state == STOPPED - || r.state == ActivityState.PAUSED || r.state == ActivityState.PAUSING) { + if (r.isState(STOPPING, STOPPED, PAUSED, PAUSING)) { r.setSleeping(true); } } @@ -1401,7 +1405,7 @@ class ActivityStack extends ConfigurationContai ActivityRecord resuming, boolean pauseImmediately) { if (mPausingActivity != null) { Slog.wtf(TAG, "Going to pause when pause is already pending for " + mPausingActivity - + " state=" + mPausingActivity.state); + + " state=" + mPausingActivity.getState()); if (!shouldSleepActivities()) { // Avoid recursion among check for sleep and complete pause during sleeping. // Because activity will be paused immediately after resume, just let pause @@ -1431,7 +1435,7 @@ class ActivityStack extends ConfigurationContai mLastPausedActivity = prev; mLastNoHistoryActivity = (prev.intent.getFlags() & Intent.FLAG_ACTIVITY_NO_HISTORY) != 0 || (prev.info.flags & ActivityInfo.FLAG_NO_HISTORY) != 0 ? prev : null; - prev.state = ActivityState.PAUSING; + prev.setState(PAUSING, "startPausingLocked"); prev.getTask().touchActiveTime(); clearLaunchTime(prev); final ActivityRecord next = mStackSupervisor.topRunningActivityLocked(); @@ -1525,8 +1529,8 @@ class ActivityStack extends ConfigurationContai r.userId, System.identityHashCode(r), r.shortComponentName, mPausingActivity != null ? mPausingActivity.shortComponentName : "(none)"); - if (r.state == ActivityState.PAUSING) { - r.state = ActivityState.PAUSED; + if (r.isState(PAUSING)) { + r.setState(PAUSED, "activityPausedLocked"); if (r.finishing) { if (DEBUG_PAUSE) Slog.v(TAG, "Executing finish of failed to pause activity: " + r); @@ -1544,8 +1548,8 @@ class ActivityStack extends ConfigurationContai if (DEBUG_PAUSE) Slog.v(TAG_PAUSE, "Complete pause: " + prev); if (prev != null) { - final boolean wasStopping = prev.state == STOPPING; - prev.state = ActivityState.PAUSED; + final boolean wasStopping = prev.isState(STOPPING); + prev.setState(PAUSED, "completePausedLocked"); if (prev.finishing) { if (DEBUG_PAUSE) Slog.v(TAG_PAUSE, "Executing finish of activity: " + prev); prev = finishCurrentActivityLocked(prev, FINISH_AFTER_VISIBLE, false, @@ -1566,7 +1570,7 @@ class ActivityStack extends ConfigurationContai // We are also stopping, the stop request must have gone soon after the pause. // We can't clobber it, because the stop confirmation will not be handled. // We don't need to schedule another stop, we only need to let it happen. - prev.state = STOPPING; + prev.setState(STOPPING, "completePausedLocked"); } else if (!prev.visible || shouldSleepOrShutDownActivities()) { // Clear out any deferred client hide we might currently have. prev.setDeferHidingClient(false); @@ -1843,7 +1847,7 @@ class ActivityStack extends ConfigurationContai } if (reallyVisible) { if (DEBUG_VISIBILITY) Slog.v(TAG_VISIBILITY, "Make visible? " + r - + " finishing=" + r.finishing + " state=" + r.state); + + " finishing=" + r.finishing + " state=" + r.getState()); // First: if this is not the current activity being started, make // sure it matches the current configuration. if (r != starting) { @@ -1875,7 +1879,7 @@ class ActivityStack extends ConfigurationContai configChanges |= r.configChangeFlags; } else { if (DEBUG_VISIBILITY) Slog.v(TAG_VISIBILITY, "Make invisible? " + r - + " finishing=" + r.finishing + " state=" + r.state + + " finishing=" + r.finishing + " state=" + r.getState() + " stackShouldBeVisible=" + stackShouldBeVisible + " behindFullscreenActivity=" + behindFullscreenActivity + " mLaunchTaskBehind=" + r.mLaunchTaskBehind); @@ -2059,7 +2063,7 @@ class ActivityStack extends ConfigurationContai } // Now for any activities that aren't visible to the user, make sure they no longer are // keeping the screen frozen. - if (DEBUG_VISIBILITY) Slog.v(TAG_VISIBILITY, "Making invisible: " + r + " " + r.state); + if (DEBUG_VISIBILITY) Slog.v(TAG_VISIBILITY, "Making invisible: " + r + " " + r.getState()); try { final boolean canEnterPictureInPicture = r.checkEnterPictureInPictureState( "makeInvisible", true /* beforeStopping */); @@ -2071,11 +2075,11 @@ class ActivityStack extends ConfigurationContai // the current contract for "auto-Pip" is that the app should enter it before onPause // returns. Just need to confirm this reasoning makes sense. final boolean deferHidingClient = canEnterPictureInPicture - && r.state != STOPPING && r.state != STOPPED && r.state != PAUSED; + && !r.isState(STOPPING, STOPPED, PAUSED); r.setDeferHidingClient(deferHidingClient); r.setVisible(false); - switch (r.state) { + switch (r.getState()) { case STOPPING: case STOPPED: if (r.app != null && r.app.thread != null) { @@ -2255,7 +2259,7 @@ class ActivityStack extends ConfigurationContai // TODO: move mResumedActivity to stack supervisor, // there should only be 1 global copy of resumed activity. mResumedActivity = r; - r.state = ActivityState.RESUMED; + r.setState(RESUMED, "setResumedActivityLocked"); mService.setResumedActivityUncheckLocked(r, reason); mStackSupervisor.mRecentTasks.add(r.getTask()); } @@ -2294,8 +2298,8 @@ class ActivityStack extends ConfigurationContai next.delayedResume = false; // If the top activity is the resumed one, nothing to do. - if (mResumedActivity == next && next.state == ActivityState.RESUMED && - mStackSupervisor.allResumedActivitiesComplete()) { + if (mResumedActivity == next && next.isState(RESUMED) + && mStackSupervisor.allResumedActivitiesComplete()) { // Make sure we have executed any pending transitions, since there // should be nothing left to do at this point. executeAppTransition(options); @@ -2389,8 +2393,8 @@ class ActivityStack extends ConfigurationContai } if (DEBUG_STACK) mStackSupervisor.validateTopActivitiesLocked(); return true; - } else if (mResumedActivity == next && next.state == ActivityState.RESUMED && - mStackSupervisor.allResumedActivitiesComplete()) { + } else if (mResumedActivity == next && next.isState(RESUMED) + && mStackSupervisor.allResumedActivitiesComplete()) { // It is possible for the activity to be resumed when we paused back stacks above if the // next activity doesn't have to wait for pause to complete. // So, nothing else to-do except: @@ -2539,7 +2543,7 @@ class ActivityStack extends ConfigurationContai ActivityRecord lastResumedActivity = lastStack == null ? null :lastStack.mResumedActivity; - ActivityState lastState = next.state; + final ActivityState lastState = next.getState(); mService.updateCpuStats(); @@ -2645,7 +2649,7 @@ class ActivityStack extends ConfigurationContai // Whoops, need to restart this activity! if (DEBUG_STATES) Slog.v(TAG_STATES, "Resume failed; resetting state to " + lastState + ": " + next); - next.state = lastState; + next.setState(lastState, "resumeTopActivityInnerLocked"); if (lastStack != null) { lastStack.mResumedActivity = lastResumedActivity; } @@ -3408,7 +3412,7 @@ class ActivityStack extends ConfigurationContai r.stopped = false; if (DEBUG_STATES) Slog.v(TAG_STATES, "Moving to STOPPING: " + r + " (stop requested)"); - r.state = STOPPING; + r.setState(STOPPING, "stopActivityLocked"); if (DEBUG_VISIBILITY) Slog.v(TAG_VISIBILITY, "Stopping visible=" + r.visible + " for " + r); if (!r.visible) { @@ -3432,7 +3436,7 @@ class ActivityStack extends ConfigurationContai // Just in case, assume it to be stopped. r.stopped = true; if (DEBUG_STATES) Slog.v(TAG_STATES, "Stop failed; moving to STOPPED: " + r); - r.state = STOPPED; + r.setState(STOPPED, "stopActivityLocked"); if (r.deferRelaunchUntilPaused) { destroyActivityLocked(r, true, "stop-except"); } @@ -3505,9 +3509,7 @@ class ActivityStack extends ConfigurationContai } if (activityNdx >= 0) { r = mTaskHistory.get(taskNdx).mActivities.get(activityNdx); - if (r.state == ActivityState.RESUMED - || r.state == ActivityState.PAUSING - || r.state == ActivityState.PAUSED) { + if (r.isState(RESUMED, PAUSING, PAUSED)) { if (!r.isActivityTypeHome() || mService.mHomeProcess != r.app) { Slog.w(TAG, " Force finishing activity " + r.intent.getComponent().flattenToShortString()); @@ -3671,7 +3673,7 @@ class ActivityStack extends ConfigurationContai if (endTask) { mService.mLockTaskController.clearLockedTask(task); } - } else if (r.state != ActivityState.PAUSING) { + } else if (!r.isState(PAUSING)) { // If the activity is PAUSING, we will complete the finish once // it is done pausing; else we can just directly finish it here. if (DEBUG_PAUSE) Slog.v(TAG_PAUSE, "Finish not pausing: " + r); @@ -3738,7 +3740,7 @@ class ActivityStack extends ConfigurationContai } if (DEBUG_STATES) Slog.v(TAG_STATES, "Moving to STOPPING: "+ r + " (finish requested)"); - r.state = STOPPING; + r.setState(STOPPING, "finishCurrentActivityLocked"); if (oomAdj) { mService.updateOomAdjLocked(); } @@ -3752,15 +3754,15 @@ class ActivityStack extends ConfigurationContai if (mResumedActivity == r) { mResumedActivity = null; } - final ActivityState prevState = r.state; + final ActivityState prevState = r.getState(); if (DEBUG_STATES) Slog.v(TAG_STATES, "Moving to FINISHING: " + r); - r.state = ActivityState.FINISHING; + r.setState(FINISHING, "finishCurrentActivityLocked"); final boolean finishingActivityInNonFocusedStack = r.getStack() != mStackSupervisor.getFocusedStack() - && prevState == ActivityState.PAUSED && mode == FINISH_AFTER_VISIBLE; + && prevState == PAUSED && mode == FINISH_AFTER_VISIBLE; if (mode == FINISH_IMMEDIATELY - || (prevState == ActivityState.PAUSED + || (prevState == PAUSED && (mode == FINISH_AFTER_PAUSE || inPinnedWindowingMode())) || finishingActivityInNonFocusedStack || prevState == STOPPING @@ -3981,7 +3983,7 @@ class ActivityStack extends ConfigurationContai if (setState) { if (DEBUG_STATES) Slog.v(TAG_STATES, "Moving to DESTROYED: " + r + " (cleaning up)"); - r.state = ActivityState.DESTROYED; + r.setState(DESTROYED, "cleanupActivityLocked"); if (DEBUG_APP) Slog.v(TAG_APP, "Clearing app during cleanUp for activity " + r); r.app = null; } @@ -4030,7 +4032,7 @@ class ActivityStack extends ConfigurationContai removeTimeoutsForActivityLocked(r); if (DEBUG_STATES) Slog.v(TAG_STATES, "Moving to DESTROYED: " + r + " (removed from history)"); - r.state = ActivityState.DESTROYED; + r.setState(DESTROYED, "removeActivityFromHistoryLocked"); if (DEBUG_APP) Slog.v(TAG_APP, "Clearing app during remove for activity " + r); r.app = null; r.removeWindowContainer(); @@ -4114,7 +4116,8 @@ class ActivityStack extends ConfigurationContai continue; } if (r.isDestroyable()) { - if (DEBUG_SWITCH) Slog.v(TAG_SWITCH, "Destroying " + r + " in state " + r.state + if (DEBUG_SWITCH) Slog.v(TAG_SWITCH, "Destroying " + r + + " in state " + r.getState() + " resumed=" + mResumedActivity + " pausing=" + mPausingActivity + " for reason " + reason); if (destroyActivityLocked(r, true, reason)) { @@ -4131,7 +4134,7 @@ class ActivityStack extends ConfigurationContai final boolean safelyDestroyActivityLocked(ActivityRecord r, String reason) { if (r.isDestroyable()) { if (DEBUG_SWITCH) Slog.v(TAG_SWITCH, - "Destroying " + r + " in state " + r.state + " resumed=" + mResumedActivity + "Destroying " + r + " in state " + r.getState() + " resumed=" + mResumedActivity + " pausing=" + mPausingActivity + " for reason " + reason); return destroyActivityLocked(r, true, reason); } @@ -4159,7 +4162,7 @@ class ActivityStack extends ConfigurationContai final ActivityRecord activity = activities.get(actNdx); if (activity.app == app && activity.isDestroyable()) { if (DEBUG_RELEASE) Slog.v(TAG_RELEASE, "Destroying " + activity - + " in state " + activity.state + " resumed=" + mResumedActivity + + " in state " + activity.getState() + " resumed=" + mResumedActivity + " pausing=" + mPausingActivity + " for reason " + reason); destroyActivityLocked(activity, true, reason); if (activities.get(actNdx) != activity) { @@ -4253,13 +4256,15 @@ class ActivityStack extends ConfigurationContai if (r.finishing && !skipDestroy) { if (DEBUG_STATES) Slog.v(TAG_STATES, "Moving to DESTROYING: " + r + " (destroy requested)"); - r.state = ActivityState.DESTROYING; + r.setState(DESTROYING, + "destroyActivityLocked. finishing and not skipping destroy"); Message msg = mHandler.obtainMessage(DESTROY_TIMEOUT_MSG, r); mHandler.sendMessageDelayed(msg, DESTROY_TIMEOUT); } else { if (DEBUG_STATES) Slog.v(TAG_STATES, "Moving to DESTROYED: " + r + " (destroy skipped)"); - r.state = ActivityState.DESTROYED; + r.setState(DESTROYED, + "destroyActivityLocked. not finishing or skipping destroy"); if (DEBUG_APP) Slog.v(TAG_APP, "Clearing app during destroy for activity " + r); r.app = null; } @@ -4270,7 +4275,7 @@ class ActivityStack extends ConfigurationContai removedFromHistory = true; } else { if (DEBUG_STATES) Slog.v(TAG_STATES, "Moving to DESTROYED: " + r + " (no app)"); - r.state = ActivityState.DESTROYED; + r.setState(DESTROYED, "destroyActivityLocked. not finishing and had no app"); if (DEBUG_APP) Slog.v(TAG_APP, "Clearing app during destroy for activity " + r); r.app = null; } @@ -4288,24 +4293,29 @@ class ActivityStack extends ConfigurationContai final void activityDestroyedLocked(IBinder token, String reason) { final long origId = Binder.clearCallingIdentity(); try { - ActivityRecord r = ActivityRecord.forTokenLocked(token); - if (r != null) { - mHandler.removeMessages(DESTROY_TIMEOUT_MSG, r); - } - if (DEBUG_CONTAINERS) Slog.d(TAG_CONTAINERS, "activityDestroyedLocked: r=" + r); - - if (isInStackLocked(r) != null) { - if (r.state == ActivityState.DESTROYING) { - cleanUpActivityLocked(r, true, false); - removeActivityFromHistoryLocked(r, reason); - } - } - mStackSupervisor.resumeFocusedStackTopActivityLocked(); + activityDestroyedLocked(ActivityRecord.forTokenLocked(token), reason); } finally { Binder.restoreCallingIdentity(origId); } } + final void activityDestroyedLocked(ActivityRecord record, String reason) { + if (record != null) { + mHandler.removeMessages(DESTROY_TIMEOUT_MSG, record); + } + + if (DEBUG_CONTAINERS) Slog.d(TAG_CONTAINERS, "activityDestroyedLocked: r=" + record); + + if (isInStackLocked(record) != null) { + if (record.isState(DESTROYING, DESTROYED)) { + cleanUpActivityLocked(record, true, false); + removeActivityFromHistoryLocked(record, reason); + } + } + + mStackSupervisor.resumeFocusedStackTopActivityLocked(); + } + private void removeHistoryRecordsForAppLocked(ArrayList list, ProcessRecord app, String listName) { int i = list.size(); @@ -4374,14 +4384,14 @@ class ActivityStack extends ConfigurationContai + ": haveState=" + r.haveState + " stateNotNeeded=" + r.stateNotNeeded + " finishing=" + r.finishing - + " state=" + r.state + " callers=" + Debug.getCallers(5)); + + " state=" + r.getState() + " callers=" + Debug.getCallers(5)); if (!r.finishing) { Slog.w(TAG, "Force removing " + r + ": app died, no saved state"); EventLog.writeEvent(EventLogTags.AM_FINISH_ACTIVITY, r.userId, System.identityHashCode(r), r.getTask().taskId, r.shortComponentName, "proc died without state saved"); - if (r.state == ActivityState.RESUMED) { + if (r.getState() == RESUMED) { mService.updateUsageStats(r, false); } } @@ -4417,7 +4427,7 @@ class ActivityStack extends ConfigurationContai private void updateTransitLocked(int transit, ActivityOptions options) { if (options != null) { ActivityRecord r = topRunningActivityLocked(); - if (r != null && r.state != ActivityState.RESUMED) { + if (r != null && !r.isState(RESUMED)) { r.updateOptionsLocked(options); } else { ActivityOptions.abort(options); diff --git a/services/core/java/com/android/server/am/ActivityStackSupervisor.java b/services/core/java/com/android/server/am/ActivityStackSupervisor.java index 9a3b10299155..55771867302c 100644 --- a/services/core/java/com/android/server/am/ActivityStackSupervisor.java +++ b/services/core/java/com/android/server/am/ActivityStackSupervisor.java @@ -1005,7 +1005,7 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D final ActivityStack stack = display.getChildAt(stackNdx); if (isFocusedStack(stack)) { final ActivityRecord r = stack.mResumedActivity; - if (r != null && r.state != RESUMED) { + if (r != null && !r.isState(RESUMED)) { return false; } } @@ -1069,10 +1069,10 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D for (int stackNdx = display.getChildCount() - 1; stackNdx >= 0; --stackNdx) { final ActivityStack stack = display.getChildAt(stackNdx); final ActivityRecord r = stack.mPausingActivity; - if (r != null && r.state != PAUSED && r.state != STOPPED && r.state != STOPPING) { + if (r != null && !r.isState(PAUSED, STOPPED, STOPPING)) { if (DEBUG_STATES) { Slog.d(TAG_STATES, - "allPausedActivitiesComplete: r=" + r + " state=" + r.state); + "allPausedActivitiesComplete: r=" + r + " state=" + r.getState()); pausing = false; } else { return false; @@ -1522,7 +1522,7 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D // current icicle and other state. if (DEBUG_STATES) Slog.v(TAG_STATES, "Moving to PAUSED: " + r + " (starting in paused state)"); - r.state = PAUSED; + r.setState(PAUSED, "realStartActivityLocked"); } // Launch the new version setup screen if needed. We do this -after- @@ -2099,9 +2099,9 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D } final ActivityRecord r = mFocusedStack.topRunningActivityLocked(); - if (r == null || r.state != RESUMED) { + if (r == null || !r.isState(RESUMED)) { mFocusedStack.resumeTopActivityUncheckedLocked(null, null); - } else if (r.state == RESUMED) { + } else if (r.isState(RESUMED)) { // Kick off any lingering app transitions form the MoveTaskToFront operation. mFocusedStack.executeAppTransition(targetOptions); } @@ -3540,14 +3540,14 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D // First, if we find an activity that is in the process of being destroyed, // then we just aren't going to do anything for now; we want things to settle // down before we try to prune more activities. - if (r.finishing || r.state == DESTROYING || r.state == DESTROYED) { + if (r.finishing || r.isState(DESTROYING, DESTROYED)) { if (DEBUG_RELEASE) Slog.d(TAG_RELEASE, "Abort release; already destroying: " + r); return; } // Don't consider any activies that are currently not in a state where they // can be destroyed. - if (r.visible || !r.stopped || !r.haveState || r.state == RESUMED || r.state == PAUSING - || r.state == PAUSED || r.state == STOPPING) { + if (r.visible || !r.stopped || !r.haveState + || r.isState(RESUMED, PAUSING, PAUSED, STOPPING)) { if (DEBUG_RELEASE) Slog.d(TAG_RELEASE, "Not releasing in-use activity: " + r); continue; } @@ -3683,7 +3683,7 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D ? stack.shouldSleepOrShutDownActivities() : mService.isSleepingOrShuttingDownLocked(); if (!waitingVisible || shouldSleepOrShutDown) { - if (!processPausingActivities && s.state == PAUSING) { + if (!processPausingActivities && s.isState(PAUSING)) { // Defer processing pausing activities in this iteration and reschedule // a delayed idle to reprocess it again removeTimeoutsForActivityLocked(idleActivity); @@ -3710,7 +3710,7 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D for (int stackNdx = display.getChildCount() - 1; stackNdx >= 0; --stackNdx) { final ActivityStack stack = display.getChildAt(stackNdx); final ActivityRecord r = stack.topRunningActivityLocked(); - final ActivityState state = r == null ? DESTROYED : r.state; + final ActivityState state = r == null ? DESTROYED : r.getState(); if (isFocusedStack(stack)) { if (r == null) Slog.e(TAG, "validateTop...: null top activity, stack=" + stack); diff --git a/services/core/java/com/android/server/am/ActivityStarter.java b/services/core/java/com/android/server/am/ActivityStarter.java index 8205265ba047..fcdf3d2918ed 100644 --- a/services/core/java/com/android/server/am/ActivityStarter.java +++ b/services/core/java/com/android/server/am/ActivityStarter.java @@ -1107,7 +1107,7 @@ class ActivityStarter { case START_TASK_TO_FRONT: { // ActivityRecord may represent a different activity, but it should not be // in the resumed state. - if (r.nowVisible && r.state == RESUMED) { + if (r.nowVisible && r.isState(RESUMED)) { outResult.timeout = false; outResult.who = r.realActivity; outResult.totalTime = 0; @@ -1516,7 +1516,7 @@ class ActivityStarter { final TaskRecord task = mSupervisor.anyTaskForIdLocked( mOptions.getLaunchTaskId()); final ActivityRecord top = task != null ? task.getTopActivity() : null; - if (top != null && top.state != RESUMED) { + if (top != null && !top.isState(RESUMED)) { // The caller specifies that we'd like to be avoided to be moved to the // front, so be it! diff --git a/services/core/java/com/android/server/am/TaskRecord.java b/services/core/java/com/android/server/am/TaskRecord.java index d679439d3b7d..a07afde44db2 100644 --- a/services/core/java/com/android/server/am/TaskRecord.java +++ b/services/core/java/com/android/server/am/TaskRecord.java @@ -1103,7 +1103,7 @@ class TaskRecord extends ConfigurationContainer implements TaskWindowContainerLi // Increment the total number of non-finishing activities reportOut.numActivities++; - if (reportOut.top == null || (reportOut.top.state == ActivityState.INITIALIZING)) { + if (reportOut.top == null || (reportOut.top.isState(ActivityState.INITIALIZING))) { reportOut.top = r; // Reset the number of running activities until we hit the first non-initializing // activity diff --git a/services/tests/servicestests/src/com/android/server/am/ActivityRecordTests.java b/services/tests/servicestests/src/com/android/server/am/ActivityRecordTests.java index d3df9241283a..5161114baf0f 100644 --- a/services/tests/servicestests/src/com/android/server/am/ActivityRecordTests.java +++ b/services/tests/servicestests/src/com/android/server/am/ActivityRecordTests.java @@ -22,6 +22,8 @@ import static android.content.pm.ActivityInfo.RESIZE_MODE_RESIZEABLE; import static android.content.pm.ActivityInfo.RESIZE_MODE_UNRESIZEABLE; import static android.view.Display.DEFAULT_DISPLAY; +import static com.android.server.am.ActivityStack.ActivityState.DESTROYED; +import static com.android.server.am.ActivityStack.ActivityState.DESTROYING; import static com.android.server.am.ActivityStack.ActivityState.INITIALIZING; import static com.android.server.am.ActivityStack.ActivityState.PAUSING; import static com.android.server.am.ActivityStack.ActivityState.STOPPED; @@ -119,20 +121,20 @@ public class ActivityRecordTests extends ActivityTestsBase { } return null; }).when(mActivity.app.thread).scheduleTransaction(any()); - mActivity.state = STOPPED; + mActivity.setState(STOPPED, "testPausingWhenVisibleFromStopped"); mActivity.makeVisibleIfNeeded(null /* starting */); - assertEquals(mActivity.state, PAUSING); + assertTrue(mActivity.isState(PAUSING)); assertTrue(pauseFound.value); // Make sure that the state does not change for current non-stopping states. - mActivity.state = INITIALIZING; + mActivity.setState(INITIALIZING, "testPausingWhenVisibleFromStopped"); mActivity.makeVisibleIfNeeded(null /* starting */); - assertEquals(mActivity.state, INITIALIZING); + assertTrue(mActivity.isState(INITIALIZING)); } @Test @@ -197,7 +199,23 @@ public class ActivityRecordTests extends ActivityTestsBase { record.canBeLaunchedOnDisplay(DEFAULT_DISPLAY); - verify(mService.mStackSupervisor, times(1)).canPlaceEntityOnDisplay(anyInt(), eq(expected), anyInt(), anyInt(), - eq(record.info)); + verify(mService.mStackSupervisor, times(1)).canPlaceEntityOnDisplay(anyInt(), eq(expected), + anyInt(), anyInt(), eq(record.info)); + } + + @Test + public void testFinishingAfterDestroying() throws Exception { + assertFalse(mActivity.finishing); + mActivity.setState(DESTROYING, "testFinishingAfterDestroying"); + assertTrue(mActivity.isState(DESTROYING)); + assertTrue(mActivity.finishing); + } + + @Test + public void testFinishingAfterDestroyed() throws Exception { + assertFalse(mActivity.finishing); + mActivity.setState(DESTROYED, "testFinishingAfterDestroyed"); + assertTrue(mActivity.isState(DESTROYED)); + assertTrue(mActivity.finishing); } } -- cgit v1.2.3-59-g8ed1b