diff options
8 files changed, 717 insertions, 572 deletions
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java index 563a1fed9a0d..fc36e9984c1b 100644 --- a/services/core/java/com/android/server/wm/ActivityRecord.java +++ b/services/core/java/com/android/server/wm/ActivityRecord.java @@ -106,6 +106,7 @@ import static com.android.server.am.ActivityRecordProto.VISIBLE; import static com.android.server.am.EventLogTags.AM_RELAUNCH_ACTIVITY; import static com.android.server.am.EventLogTags.AM_RELAUNCH_RESUME_ACTIVITY; import static com.android.server.wm.ActivityStack.ActivityState.DESTROYED; +import static com.android.server.wm.ActivityStack.ActivityState.DESTROYING; import static com.android.server.wm.ActivityStack.ActivityState.FINISHING; import static com.android.server.wm.ActivityStack.ActivityState.INITIALIZING; import static com.android.server.wm.ActivityStack.ActivityState.PAUSED; @@ -115,13 +116,13 @@ import static com.android.server.wm.ActivityStack.ActivityState.RESUMED; import static com.android.server.wm.ActivityStack.ActivityState.STARTED; import static com.android.server.wm.ActivityStack.ActivityState.STOPPED; import static com.android.server.wm.ActivityStack.ActivityState.STOPPING; -import static com.android.server.wm.ActivityStack.LAUNCH_TICK; -import static com.android.server.wm.ActivityStack.LAUNCH_TICK_MSG; -import static com.android.server.wm.ActivityStack.PAUSE_TIMEOUT_MSG; +import static com.android.server.wm.ActivityStack.REMOVE_TASK_MODE_DESTROYING; import static com.android.server.wm.ActivityStack.STACK_VISIBILITY_VISIBLE; -import static com.android.server.wm.ActivityStack.STOP_TIMEOUT_MSG; import static com.android.server.wm.ActivityStackSupervisor.PAUSE_IMMEDIATELY; import static com.android.server.wm.ActivityStackSupervisor.PRESERVE_WINDOWS; +import static com.android.server.wm.ActivityStackSupervisor.REMOVE_FROM_RECENTS; +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_CONFIGURATION; import static com.android.server.wm.ActivityTaskManagerDebugConfig.DEBUG_CONTAINERS; import static com.android.server.wm.ActivityTaskManagerDebugConfig.DEBUG_FOCUS; @@ -133,6 +134,8 @@ import static com.android.server.wm.ActivityTaskManagerDebugConfig.DEBUG_SWITCH; import static com.android.server.wm.ActivityTaskManagerDebugConfig.DEBUG_TRANSITION; import static com.android.server.wm.ActivityTaskManagerDebugConfig.DEBUG_USER_LEAVING; import static com.android.server.wm.ActivityTaskManagerDebugConfig.DEBUG_VISIBILITY; +import static com.android.server.wm.ActivityTaskManagerDebugConfig.POSTFIX_ADD_REMOVE; +import static com.android.server.wm.ActivityTaskManagerDebugConfig.POSTFIX_APP; import static com.android.server.wm.ActivityTaskManagerDebugConfig.POSTFIX_CONFIGURATION; import static com.android.server.wm.ActivityTaskManagerDebugConfig.POSTFIX_CONTAINERS; import static com.android.server.wm.ActivityTaskManagerDebugConfig.POSTFIX_FOCUS; @@ -177,8 +180,10 @@ import android.app.WaitResult.LaunchState; import android.app.servertransaction.ActivityConfigurationChangeItem; import android.app.servertransaction.ActivityLifecycleItem; import android.app.servertransaction.ActivityRelaunchItem; +import android.app.servertransaction.ActivityResultItem; import android.app.servertransaction.ClientTransaction; import android.app.servertransaction.ClientTransactionItem; +import android.app.servertransaction.DestroyActivityItem; import android.app.servertransaction.MoveToDisplayItem; import android.app.servertransaction.MultiWindowModeChangeItem; import android.app.servertransaction.NewIntentItem; @@ -198,12 +203,12 @@ import android.content.res.Configuration; import android.graphics.Bitmap; import android.graphics.GraphicBuffer; import android.graphics.Rect; +import android.net.Uri; import android.os.Binder; import android.os.Build; import android.os.Bundle; import android.os.Debug; import android.os.IBinder; -import android.os.Message; import android.os.PersistableBundle; import android.os.Process; import android.os.RemoteException; @@ -260,6 +265,8 @@ import java.util.Objects; */ final class ActivityRecord extends ConfigurationContainer { private static final String TAG = TAG_WITH_CLASS_NAME ? "ActivityRecord" : TAG_ATM; + private static final String TAG_ADD_REMOVE = TAG + POSTFIX_ADD_REMOVE; + private static final String TAG_APP = TAG + POSTFIX_APP; private static final String TAG_CONFIGURATION = TAG + POSTFIX_CONFIGURATION; private static final String TAG_CONTAINERS = TAG + POSTFIX_CONTAINERS; private static final String TAG_FOCUS = TAG + POSTFIX_FOCUS; @@ -282,6 +289,9 @@ final class ActivityRecord extends ConfigurationContainer { private static final String ATTR_COMPONENTSPECIFIED = "component_specified"; static final String ACTIVITY_ICON_SUFFIX = "_activity_icon_"; + // How many activities have to be scheduled to stop to force a stop pass. + private static final int MAX_STOPPING_TO_FORCE = 3; + final ActivityTaskManagerService mAtmService; // owner final IApplicationToken.Stub appToken; // window manager token // TODO: Remove after unification @@ -1837,14 +1847,12 @@ final class ActivityRecord extends ConfigurationContainer { final ActivityStack stack = getActivityStack(); final boolean notFocusedStack = stack != mRootActivityContainer.getTopDisplayFocusedStack(); if (isVisible && next != null && !next.nowVisible) { - if (!mStackSupervisor.mStoppingActivities.contains(this)) { - getActivityStack().addToStopping(this, false /* scheduleIdle */, - false /* idleDelayed */, "finishCurrentActivityLocked"); - } + addToStopping(false /* scheduleIdle */, false /* idleDelayed */, + "completeFinishing"); if (DEBUG_STATES) { Slog.v(TAG_STATES, "Moving to STOPPING: " + this + " (finish requested)"); } - setState(STOPPING, "finishCurrentActivityLocked"); + setState(STOPPING, "completeFinishing"); if (notFocusedStack) { mRootActivityContainer.ensureVisibilityAndConfig(next, getDisplayId(), false /* markFrozenIfConfigChanged */, true /* deferResume */); @@ -1890,8 +1898,8 @@ final class ActivityRecord extends ConfigurationContainer { } makeFinishingLocked(); - boolean activityRemoved = getActivityStack().destroyActivityLocked(this, - true /* removeFromApp */, "finish-imm:" + reason); + final boolean activityRemoved = destroyImmediately(true /* removeFromApp */, + "finish-imm:" + reason); // If the display does not have running activity, the configuration may need to be // updated for restoring original orientation of the display. @@ -1920,6 +1928,183 @@ final class ActivityRecord extends ConfigurationContainer { mRootActivityContainer.resumeFocusedStacksTopActivities(); } + /** + * Destroy the current CLIENT SIDE instance of an activity. This may be called both when + * actually finishing an activity, or when performing a configuration switch where we destroy + * the current client-side object but then create a new client-side object for this same + * HistoryRecord. + * Normally the server-side record will be removed when the client reports back after + * destruction. If, however, at this point there is no client process attached, the record will + * removed immediately. + */ + boolean destroyImmediately(boolean removeFromApp, String reason) { + if (DEBUG_SWITCH || DEBUG_CLEANUP) { + Slog.v(TAG_SWITCH, "Removing activity from " + reason + ": token=" + this + + ", app=" + (hasProcess() ? app.mName : "(null)")); + } + + if (isState(DESTROYING, DESTROYED)) { + if (DEBUG_STATES) { + Slog.v(TAG_STATES, "activity " + this + " already destroying." + + "skipping request with reason:" + reason); + } + return false; + } + + EventLog.writeEvent(EventLogTags.AM_DESTROY_ACTIVITY, mUserId, + System.identityHashCode(this), getTaskRecord().taskId, shortComponentName, reason); + + boolean removedFromHistory = false; + + cleanUp(false /* cleanServices */, false /* setState */); + + final ActivityStack stack = getActivityStack(); + final boolean hadApp = hasProcess(); + + if (hadApp) { + if (removeFromApp) { + app.removeActivity(this); + if (!app.hasActivities()) { + mAtmService.clearHeavyWeightProcessIfEquals(app); + // Update any services we are bound to that might care about whether + // their client may have activities. + // No longer have activities, so update LRU list and oom adj. + app.updateProcessInfo(true /* updateServiceConnectionActivities */, + false /* activityChange */, true /* updateOomAdj */); + } + } + + boolean skipDestroy = false; + + try { + if (DEBUG_SWITCH) Slog.i(TAG_SWITCH, "Destroying: " + this); + mAtmService.getLifecycleManager().scheduleTransaction(app.getThread(), appToken, + DestroyActivityItem.obtain(finishing, configChangeFlags)); + } catch (Exception e) { + // We can just ignore exceptions here... if the process has crashed, our death + // notification will clean things up. + if (finishing) { + removeFromHistory(reason + " exceptionInScheduleDestroy"); + removedFromHistory = true; + skipDestroy = true; + } + } + + nowVisible = false; + + // If the activity is finishing, we need to wait on removing it from the list to give it + // a chance to do its cleanup. During that time it may make calls back with its token + // so we need to be able to find it on the list and so we don't want to remove it from + // the list yet. Otherwise, we can just immediately put it in the destroyed state since + // we are not removing it from the list. + if (finishing && !skipDestroy) { + if (DEBUG_STATES) { + Slog.v(TAG_STATES, "Moving to DESTROYING: " + this + " (destroy requested)"); + } + setState(DESTROYING, + "destroyActivityLocked. finishing and not skipping destroy"); + stack.scheduleDestroyTimeoutForActivity(this); + } else { + if (DEBUG_STATES) { + Slog.v(TAG_STATES, "Moving to DESTROYED: " + this + " (destroy skipped)"); + } + setState(DESTROYED, + "destroyActivityLocked. not finishing or skipping destroy"); + if (DEBUG_APP) Slog.v(TAG_APP, "Clearing app during destroy for activity " + this); + app = null; + } + } else { + // Remove this record from the history. + if (finishing) { + removeFromHistory(reason + " hadNoApp"); + removedFromHistory = true; + } else { + if (DEBUG_STATES) Slog.v(TAG_STATES, "Moving to DESTROYED: " + this + " (no app)"); + setState(DESTROYED, "destroyActivityLocked. not finishing and had no app"); + } + } + + configChangeFlags = 0; + + if (!stack.removeActivityFromLRUList(this) && hadApp) { + Slog.w(TAG, "Activity " + this + " being finished, but not in LRU list"); + } + + return removedFromHistory; + } + + boolean safelyDestroy(String reason) { + if (isDestroyable()) { + if (DEBUG_SWITCH) { + final ActivityStack stack = getActivityStack(); + Slog.v(TAG_SWITCH, "Safely destroying " + this + " in state " + getState() + + " resumed=" + stack.mResumedActivity + + " pausing=" + stack.mPausingActivity + + " for reason " + reason); + } + return destroyImmediately(true /* removeFromApp */, reason); + } + return false; + } + + /** Note: call {@link #cleanUp(boolean, boolean)} before this method. */ + void removeFromHistory(String reason) { + finishActivityResults(Activity.RESULT_CANCELED, null /* resultData */); + makeFinishingLocked(); + if (ActivityTaskManagerDebugConfig.DEBUG_ADD_REMOVE) { + Slog.i(TAG_ADD_REMOVE, "Removing activity " + this + " from stack callers=" + + Debug.getCallers(5)); + } + + takeFromHistory(); + final ActivityStack stack = getActivityStack(); + stack.removeTimeoutsForActivity(this); + if (DEBUG_STATES) { + Slog.v(TAG_STATES, "Moving to DESTROYED: " + this + " (removed from history)"); + } + setState(DESTROYED, "removeFromHistory"); + if (DEBUG_APP) Slog.v(TAG_APP, "Clearing app during remove for activity " + this); + app = null; + removeWindowContainer(); + final TaskRecord task = getTaskRecord(); + final boolean lastActivity = task.removeActivity(this); + // If we are removing the last activity in the task, not including task overlay activities, + // then fall through into the block below to remove the entire task itself + final boolean onlyHasTaskOverlays = + task.onlyHasTaskOverlayActivities(false /* excludingFinishing */); + + if (lastActivity || onlyHasTaskOverlays) { + if (DEBUG_STATES) { + Slog.i(TAG, "removeFromHistory: last activity removed from " + this + + " onlyHasTaskOverlays=" + onlyHasTaskOverlays); + } + + // The following block can be executed multiple times if there is more than one overlay. + // {@link ActivityStackSupervisor#removeTaskByIdLocked} handles this by reverse lookup + // of the task by id and exiting early if not found. + if (onlyHasTaskOverlays) { + // When destroying a task, tell the supervisor to remove it so that any activity it + // has can be cleaned up correctly. This is currently the only place where we remove + // a task with the DESTROYING mode, so instead of passing the onlyHasTaskOverlays + // state into removeTask(), we just clear the task here before the other residual + // work. + // TODO: If the callers to removeTask() changes such that we have multiple places + // where we are destroying the task, move this back into removeTask() + mStackSupervisor.removeTaskByIdLocked(task.taskId, false /* killProcess */, + !REMOVE_FROM_RECENTS, PAUSE_IMMEDIATELY, reason); + } + + // We must keep the task around until all activities are destroyed. The following + // statement will only execute once since overlays are also considered activities. + if (lastActivity) { + stack.removeTask(task, reason, REMOVE_TASK_MODE_DESTROYING); + } + } + + cleanUpActivityServices(); + removeUriPermissionsLocked(); + } + void makeFinishingLocked() { if (finishing) { return; @@ -1934,6 +2119,96 @@ final class ActivityRecord extends ConfigurationContainer { } } + /** + * This method is to only be called from the client via binder when the activity is destroyed + * AND finished. + */ + void destroyed(String reason) { + getActivityStack().removeDestroyTimeoutForActivity(this); + + if (DEBUG_CONTAINERS) Slog.d(TAG_CONTAINERS, "activityDestroyedLocked: r=" + this); + + if (!isState(DESTROYING, DESTROYED)) { + throw new IllegalStateException( + "Reported destroyed for activity that is not destroying: r=" + this); + } + + if (isInStackLocked()) { + cleanUp(true /* cleanServices */, false /* setState */); + removeFromHistory(reason); + } + + mRootActivityContainer.resumeFocusedStacksTopActivities(); + } + + /** + * Perform the common clean-up of an activity record. This is called both as part of + * destroyActivityLocked() (when destroying the client-side representation) and cleaning things + * up as a result of its hosting processing going away, in which case there is no remaining + * client-side state to destroy so only the cleanup here is needed. + * + * Note: Call before {@link #removeFromHistory(String)}. + */ + void cleanUp(boolean cleanServices, boolean setState) { + final ActivityStack stack = getActivityStack(); + stack.onActivityRemovedFromStack(this); + + deferRelaunchUntilPaused = false; + frozenBeforeDestroy = false; + + if (setState) { + setState(DESTROYED, "cleanUp"); + if (DEBUG_APP) Slog.v(TAG_APP, "Clearing app during cleanUp for activity " + this); + app = null; + } + + // Inform supervisor the activity has been removed. + mStackSupervisor.cleanupActivity(this); + + // Remove any pending results. + if (finishing && pendingResults != null) { + for (WeakReference<PendingIntentRecord> apr : pendingResults) { + PendingIntentRecord rec = apr.get(); + if (rec != null) { + mAtmService.mPendingIntentController.cancelIntentSender(rec, + false /* cleanActivity */); + } + } + pendingResults = null; + } + + if (cleanServices) { + cleanUpActivityServices(); + } + + // Get rid of any pending idle timeouts. + stack.removeTimeoutsForActivity(this); + // Clean-up activities are no longer relaunching (e.g. app process died). Notify window + // manager so it can update its bookkeeping. + mAtmService.mWindowManager.notifyAppRelaunchesCleared(appToken); + } + + /** + * Perform clean-up of service connections in an activity record. + */ + private void cleanUpActivityServices() { + if (mServiceConnectionsHolder == null) { + return; + } + // Throw away any services that have been bound by this activity. + mServiceConnectionsHolder.disconnectActivityFromServices(); + } + + void logStartActivity(int tag, TaskRecord task) { + final Uri data = intent.getData(); + final String strData = data != null ? data.toSafeString() : null; + + EventLog.writeEvent(tag, + mUserId, System.identityHashCode(this), task.taskId, + shortComponentName, intent.getAction(), + intent.getType(), strData, intent.getFlags()); + } + UriPermissionOwner getUriPermissionsLocked() { if (uriPermissions == null) { uriPermissions = new UriPermissionOwner(mAtmService.mUgmInternal, this); @@ -1970,6 +2245,33 @@ final class ActivityRecord extends ConfigurationContainer { } } + void sendResult(int callingUid, String resultWho, int requestCode, int resultCode, + Intent data) { + if (callingUid > 0) { + mAtmService.mUgmInternal.grantUriPermissionFromIntent(callingUid, packageName, + data, getUriPermissionsLocked(), mUserId); + } + + if (DEBUG_RESULTS) { + Slog.v(TAG, "Send activity result to " + this + + " : who=" + resultWho + " req=" + requestCode + + " res=" + resultCode + " data=" + data); + } + if (isState(RESUMED) && attachedToProcess()) { + try { + final ArrayList<ResultInfo> list = new ArrayList<ResultInfo>(); + list.add(new ResultInfo(resultWho, requestCode, resultCode, data)); + mAtmService.getLifecycleManager().scheduleTransaction(app.getThread(), appToken, + ActivityResultItem.obtain(list)); + return; + } catch (Exception e) { + Slog.w(TAG, "Exception thrown sending result to " + this, e); + } + } + + addResultLocked(null /* from */, resultWho, requestCode, resultCode, data); + } + private void addNewIntentLocked(ReferrerIntent intent) { if (newIntents == null) { newIntents = new ArrayList<>(); @@ -2449,6 +2751,65 @@ final class ActivityRecord extends ConfigurationContainer { } } + void makeInvisible() { + if (!visible) { + if (DEBUG_VISIBILITY) Slog.v(TAG_VISIBILITY, "Already invisible: " + this); + return; + } + // 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: " + this + ", state=" + getState()); + } + try { + final boolean canEnterPictureInPicture = checkEnterPictureInPictureState( + "makeInvisible", true /* beforeStopping */); + // Defer telling the client it is hidden if it can enter Pip and isn't current paused, + // stopped or stopping. This gives it a chance to enter Pip in onPause(). + // TODO: There is still a question surrounding activities in multi-window mode that want + // to enter Pip after they are paused, but are still visible. I they should be okay to + // enter Pip in those cases, but not "auto-Pip" which is what this condition covers and + // 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 + && !isState(STOPPING, STOPPED, PAUSED); + setDeferHidingClient(deferHidingClient); + setVisible(false); + + switch (getState()) { + case STOPPING: + case STOPPED: + if (attachedToProcess()) { + if (DEBUG_VISIBILITY) { + Slog.v(TAG_VISIBILITY, "Scheduling invisibility: " + this); + } + mAtmService.getLifecycleManager().scheduleTransaction(app.getThread(), + appToken, WindowVisibilityItem.obtain(false /* showWindow */)); + } + + // Reset the flag indicating that an app can enter picture-in-picture once the + // activity is hidden + supportsEnterPipOnTaskSwitch = false; + break; + + case INITIALIZING: + case RESUMED: + case PAUSING: + case PAUSED: + case STARTED: + addToStopping(true /* scheduleIdle */, + canEnterPictureInPicture /* idleDelayed */, "makeInvisible"); + break; + + default: + break; + } + } catch (Exception e) { + // Just skip on any failure; we'll make it visible when it next restarts. + Slog.w(TAG, "Exception thrown making hidden: " + intent.getComponent(), e); + } + } + /** * Make activity resumed or paused if needed. * @param activeActivity an activity that is resumed or just completed pause action. @@ -2639,13 +3000,75 @@ final class ActivityRecord extends ConfigurationContainer { } } + void stopIfPossible() { + if (DEBUG_SWITCH) Slog.d(TAG_SWITCH, "Stopping: " + this); + final ActivityStack stack = getActivityStack(); + if ((intent.getFlags() & Intent.FLAG_ACTIVITY_NO_HISTORY) != 0 + || (info.flags & ActivityInfo.FLAG_NO_HISTORY) != 0) { + if (!finishing) { + if (!stack.shouldSleepActivities()) { + if (DEBUG_STATES) Slog.d(TAG_STATES, "no-history finish of " + this); + if (finishIfPossible("stop-no-history", false /* oomAdj */) + != FINISH_RESULT_CANCELLED) { + // {@link adjustFocusedActivityStack} must have been already called. + resumeKeyDispatchingLocked(); + return; + } + } else { + if (DEBUG_STATES) { + Slog.d(TAG_STATES, "Not finishing noHistory " + this + + " on stop because we're just sleeping"); + } + } + } + } + + if (!attachedToProcess()) { + return; + } + stack.adjustFocusedActivityStack(this, "stopActivity"); + resumeKeyDispatchingLocked(); + try { + stopped = false; + if (DEBUG_STATES) { + Slog.v(TAG_STATES, "Moving to STOPPING: " + this + " (stop requested)"); + } + setState(STOPPING, "stopIfPossible"); + if (DEBUG_VISIBILITY) { + Slog.v(TAG_VISIBILITY, "Stopping visible=" + visible + " for " + this); + } + if (!visible) { + setVisible(false); + } + EventLogTags.writeAmStopActivity( + mUserId, System.identityHashCode(this), shortComponentName); + mAtmService.getLifecycleManager().scheduleTransaction(app.getThread(), appToken, + StopActivityItem.obtain(visible, configChangeFlags)); + if (stack.shouldSleepOrShutDownActivities()) { + setSleeping(true); + } + stack.scheduleStopTimeoutForActivity(this); + } catch (Exception e) { + // Maybe just ignore exceptions here... if the process has crashed, our death + // notification will clean things up. + Slog.w(TAG, "Exception thrown during pause", e); + // Just in case, assume it to be stopped. + stopped = true; + if (DEBUG_STATES) Slog.v(TAG_STATES, "Stop failed; moving to STOPPED: " + this); + setState(STOPPED, "stopIfPossible"); + if (deferRelaunchUntilPaused) { + destroyImmediately(true /* removeFromApp */, "stop-except"); + } + } + } + final void activityStoppedLocked(Bundle newIcicle, PersistableBundle newPersistentState, CharSequence description) { final ActivityStack stack = getActivityStack(); final boolean isStopping = mState == STOPPING; if (!isStopping && mState != RESTARTING_PROCESS) { Slog.i(TAG, "Activity reported stop, but no longer stopping: " + this); - stack.mHandler.removeMessages(STOP_TIMEOUT_MSG, this); + stack.removeStopTimeoutForActivity(this); return; } if (newPersistentState != null) { @@ -2663,7 +3086,7 @@ final class ActivityRecord extends ConfigurationContainer { if (DEBUG_SAVED_STATE) Slog.i(TAG_SAVED_STATE, "Saving icicle of " + this + ": " + mIcicle); if (!stopped) { if (DEBUG_STATES) Slog.v(TAG_STATES, "Moving to STOPPED: " + this + " (stop complete)"); - stack.mHandler.removeMessages(STOP_TIMEOUT_MSG, this); + stack.removeStopTimeoutForActivity(this); stopped = true; if (isStopping) { setState(STOPPED, "activityStoppedLocked"); @@ -2677,7 +3100,7 @@ final class ActivityRecord extends ConfigurationContainer { clearOptionsLocked(); } else { if (deferRelaunchUntilPaused) { - stack.destroyActivityLocked(this, true /* removeFromApp */, "stop-config"); + destroyImmediately(true /* removeFromApp */, "stop-config"); mRootActivityContainer.resumeFocusedStacksTopActivities(); } else { mRootActivityContainer.updatePreviousProcess(this); @@ -2686,6 +3109,34 @@ final class ActivityRecord extends ConfigurationContainer { } } + void addToStopping(boolean scheduleIdle, boolean idleDelayed, String reason) { + if (!mStackSupervisor.mStoppingActivities.contains(this)) { + EventLog.writeEvent(EventLogTags.AM_ADD_TO_STOPPING, mUserId, + System.identityHashCode(this), shortComponentName, reason); + mStackSupervisor.mStoppingActivities.add(this); + } + + final ActivityStack stack = getActivityStack(); + // If we already have a few activities waiting to stop, then give up on things going idle + // and start clearing them out. Or if r is the last of activity of the last task the stack + // will be empty and must be cleared immediately. + boolean forceIdle = mStackSupervisor.mStoppingActivities.size() > MAX_STOPPING_TO_FORCE + || (isRootOfTask() && stack.getChildCount() <= 1); + if (scheduleIdle || forceIdle) { + if (DEBUG_PAUSE) { + Slog.v(TAG_PAUSE, "Scheduling idle now: forceIdle=" + forceIdle + + "immediate=" + !idleDelayed); + } + if (!idleDelayed) { + mStackSupervisor.scheduleIdleLocked(); + } else { + mStackSupervisor.scheduleIdleTimeoutLocked(this); + } + } else { + stack.checkReadyForSleep(); + } + } + void startLaunchTickingLocked() { if (Build.IS_USER) { return; @@ -2706,18 +3157,18 @@ final class ActivityRecord extends ConfigurationContainer { return false; } - Message msg = stack.mHandler.obtainMessage(LAUNCH_TICK_MSG, this); - stack.mHandler.removeMessages(LAUNCH_TICK_MSG); - stack.mHandler.sendMessageDelayed(msg, LAUNCH_TICK); + stack.removeLaunchTickMessages(); + stack.scheduleLaunchTickForActivity(this); return true; } void finishLaunchTickingLocked() { launchTickTime = 0; final ActivityStack stack = getActivityStack(); - if (stack != null) { - stack.mHandler.removeMessages(LAUNCH_TICK_MSG); + if (stack == null) { + return; } + stack.removeLaunchTickMessages(); } // IApplicationToken @@ -3694,7 +4145,7 @@ final class ActivityRecord extends ConfigurationContainer { if (!attachedToProcess()) { if (DEBUG_SWITCH || DEBUG_CONFIGURATION) Slog.v(TAG_CONFIGURATION, "Config is destroying non-running " + this); - stack.destroyActivityLocked(this, true, "config"); + destroyImmediately(true /* removeFromApp */, "config"); } 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. @@ -3878,7 +4329,7 @@ final class ActivityRecord extends ConfigurationContainer { } else { final ActivityStack stack = getActivityStack(); if (stack != null) { - stack.mHandler.removeMessages(PAUSE_TIMEOUT_MSG, this); + stack.removePauseTimeoutForActivity(this); } setState(PAUSED, "relaunchActivityLocked"); } @@ -3918,7 +4369,7 @@ final class ActivityRecord extends ConfigurationContainer { if (!visible || mHaveState) { // Kill its process immediately because the activity should be in background. // The activity state will be update to {@link #DESTROYED} in - // {@link ActivityStack#cleanUpActivityLocked} when handling process died. + // {@link ActivityStack#cleanUp} when handling process died. mAtmService.mH.post(() -> { final WindowProcessController wpc; synchronized (mAtmService.mGlobalLock) { diff --git a/services/core/java/com/android/server/wm/ActivityStack.java b/services/core/java/com/android/server/wm/ActivityStack.java index ba166ea391be..8bdedffa581a 100644 --- a/services/core/java/com/android/server/wm/ActivityStack.java +++ b/services/core/java/com/android/server/wm/ActivityStack.java @@ -56,10 +56,7 @@ import static com.android.server.am.ActivityStackProto.RESUMED_ACTIVITY; import static com.android.server.am.ActivityStackProto.TASKS; import static com.android.server.wm.ActivityDisplay.POSITION_BOTTOM; import static com.android.server.wm.ActivityDisplay.POSITION_TOP; -import static com.android.server.wm.ActivityRecord.FINISH_RESULT_CANCELLED; import static com.android.server.wm.ActivityRecord.FINISH_RESULT_REMOVED; -import static com.android.server.wm.ActivityStack.ActivityState.DESTROYED; -import static com.android.server.wm.ActivityStack.ActivityState.DESTROYING; import static com.android.server.wm.ActivityStack.ActivityState.PAUSED; import static com.android.server.wm.ActivityStack.ActivityState.PAUSING; import static com.android.server.wm.ActivityStack.ActivityState.RESUMED; @@ -68,14 +65,12 @@ import static com.android.server.wm.ActivityStack.ActivityState.STOPPED; import static com.android.server.wm.ActivityStack.ActivityState.STOPPING; import static com.android.server.wm.ActivityStackSupervisor.PAUSE_IMMEDIATELY; import static com.android.server.wm.ActivityStackSupervisor.PRESERVE_WINDOWS; -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_CONTAINERS; import static com.android.server.wm.ActivityTaskManagerDebugConfig.DEBUG_PAUSE; import static com.android.server.wm.ActivityTaskManagerDebugConfig.DEBUG_RELEASE; import static com.android.server.wm.ActivityTaskManagerDebugConfig.DEBUG_RESULTS; @@ -89,7 +84,6 @@ import static com.android.server.wm.ActivityTaskManagerDebugConfig.DEBUG_VISIBIL import static com.android.server.wm.ActivityTaskManagerDebugConfig.POSTFIX_ADD_REMOVE; import static com.android.server.wm.ActivityTaskManagerDebugConfig.POSTFIX_APP; import static com.android.server.wm.ActivityTaskManagerDebugConfig.POSTFIX_CLEANUP; -import static com.android.server.wm.ActivityTaskManagerDebugConfig.POSTFIX_CONTAINERS; import static com.android.server.wm.ActivityTaskManagerDebugConfig.POSTFIX_PAUSE; import static com.android.server.wm.ActivityTaskManagerDebugConfig.POSTFIX_RELEASE; import static com.android.server.wm.ActivityTaskManagerDebugConfig.POSTFIX_RESULTS; @@ -123,12 +117,9 @@ import android.app.WindowConfiguration.ActivityType; import android.app.WindowConfiguration.WindowingMode; import android.app.servertransaction.ActivityResultItem; import android.app.servertransaction.ClientTransaction; -import android.app.servertransaction.DestroyActivityItem; import android.app.servertransaction.NewIntentItem; import android.app.servertransaction.PauseActivityItem; import android.app.servertransaction.ResumeActivityItem; -import android.app.servertransaction.StopActivityItem; -import android.app.servertransaction.WindowVisibilityItem; import android.content.ComponentName; import android.content.Intent; import android.content.pm.ActivityInfo; @@ -163,11 +154,9 @@ import com.android.server.am.ActivityManagerService; import com.android.server.am.ActivityManagerService.ItemMatcher; import com.android.server.am.AppTimeTracker; import com.android.server.am.EventLogTags; -import com.android.server.am.PendingIntentRecord; import java.io.FileDescriptor; import java.io.PrintWriter; -import java.lang.ref.WeakReference; import java.util.ArrayList; import java.util.List; import java.util.Objects; @@ -181,7 +170,6 @@ class ActivityStack extends ConfigurationContainer { private static final String TAG_ADD_REMOVE = TAG + POSTFIX_ADD_REMOVE; private static final String TAG_APP = TAG + POSTFIX_APP; private static final String TAG_CLEANUP = TAG + POSTFIX_CLEANUP; - private static final String TAG_CONTAINERS = TAG + POSTFIX_CONTAINERS; private static final String TAG_PAUSE = TAG + POSTFIX_PAUSE; private static final String TAG_RELEASE = TAG + POSTFIX_RELEASE; private static final String TAG_RESULTS = TAG + POSTFIX_RESULTS; @@ -194,7 +182,7 @@ class ActivityStack extends ConfigurationContainer { private static final String TAG_VISIBILITY = TAG + POSTFIX_VISIBILITY; // Ticks during which we check progress while waiting for an app to launch. - static final int LAUNCH_TICK = 500; + private static final int LAUNCH_TICK = 500; // How long we wait until giving up on the last activity to pause. This // is short because it directly impacts the responsiveness of starting the @@ -222,9 +210,6 @@ class ActivityStack extends ConfigurationContainer { // convertToTranslucent(). private static final long TRANSLUCENT_CONVERSION_TIMEOUT = 2000; - // How many activities have to be scheduled to stop to force a stop pass. - private static final int MAX_STOPPING_TO_FORCE = 3; - @IntDef(prefix = {"STACK_VISIBILITY"}, value = { STACK_VISIBILITY_VISIBLE, STACK_VISIBILITY_VISIBLE_BEHIND_TRANSLUCENT, @@ -411,12 +396,12 @@ class ActivityStack extends ConfigurationContainer { private boolean mTopActivityOccludesKeyguard; private ActivityRecord mTopDismissingKeyguardActivity; - static final int PAUSE_TIMEOUT_MSG = FIRST_ACTIVITY_STACK_MSG + 1; - static final int DESTROY_TIMEOUT_MSG = FIRST_ACTIVITY_STACK_MSG + 2; - static final int LAUNCH_TICK_MSG = FIRST_ACTIVITY_STACK_MSG + 3; - static final int STOP_TIMEOUT_MSG = FIRST_ACTIVITY_STACK_MSG + 4; - static final int DESTROY_ACTIVITIES_MSG = FIRST_ACTIVITY_STACK_MSG + 5; - static final int TRANSLUCENT_TIMEOUT_MSG = FIRST_ACTIVITY_STACK_MSG + 6; + private static final int PAUSE_TIMEOUT_MSG = FIRST_ACTIVITY_STACK_MSG + 1; + private static final int DESTROY_TIMEOUT_MSG = FIRST_ACTIVITY_STACK_MSG + 2; + private static final int LAUNCH_TICK_MSG = FIRST_ACTIVITY_STACK_MSG + 3; + private static final int STOP_TIMEOUT_MSG = FIRST_ACTIVITY_STACK_MSG + 4; + private static final int DESTROY_ACTIVITIES_MSG = FIRST_ACTIVITY_STACK_MSG + 5; + private static final int TRANSLUCENT_TIMEOUT_MSG = FIRST_ACTIVITY_STACK_MSG + 6; // TODO: remove after unification. TaskStack mTaskStack; @@ -430,7 +415,7 @@ class ActivityStack extends ConfigurationContainer { } } - final Handler mHandler; + private final Handler mHandler; private class ActivityStackHandler extends Handler { @@ -467,7 +452,9 @@ class ActivityStack extends ConfigurationContainer { // so we need to be conservative and assume it isn't. Slog.w(TAG, "Activity destroy timeout for " + r); synchronized (mService.mGlobalLock) { - activityDestroyedLocked(r != null ? r.appToken : null, "destroyTimeout"); + if (r != null) { + r.destroyed("destroyTimeout"); + } } } break; case STOP_TIMEOUT_MSG: { @@ -1214,12 +1201,17 @@ class ActivityStack extends ConfigurationContainer { return display != null && display.isSingleTaskInstance(); } - final void removeActivitiesFromLRUListLocked(TaskRecord task) { + private void removeActivitiesFromLRUList(TaskRecord task) { for (ActivityRecord r : task.mActivities) { mLRUActivities.remove(r); } } + /** @return {@code true} if LRU list contained the specified activity. */ + final boolean removeActivityFromLRUList(ActivityRecord activity) { + return mLRUActivities.remove(activity); + } + final boolean updateLRUListLocked(ActivityRecord r) { final boolean hadit = mLRUActivities.remove(r); mLRUActivities.add(r); @@ -1616,18 +1608,6 @@ class ActivityStack extends ConfigurationContainer { } /** - * Schedule a pause timeout in case the app doesn't respond. We don't give it much time because - * this directly impacts the responsiveness seen by the user. - */ - private void schedulePauseTimeout(ActivityRecord r) { - final Message msg = mHandler.obtainMessage(PAUSE_TIMEOUT_MSG); - msg.obj = r; - r.pauseTime = SystemClock.uptimeMillis(); - mHandler.sendMessageDelayed(msg, PAUSE_TIMEOUT); - if (DEBUG_PAUSE) Slog.v(TAG_PAUSE, "Waiting for pause to complete..."); - } - - /** * Start pausing the currently resumed activity. It is an error to call this if there * is already an activity being paused or there is no resumed activity. * @@ -1727,7 +1707,7 @@ class ActivityStack extends ConfigurationContainer { return false; } else { - schedulePauseTimeout(prev); + schedulePauseTimeoutForActivity(prev); return true; } @@ -1807,7 +1787,7 @@ class ActivityStack extends ConfigurationContainer { prev.setDeferHidingClient(false); // If we were visible then resumeTopActivities will release resources before // stopping. - addToStopping(prev, true /* scheduleIdle */, false /* idleDelayed */, + prev.addToStopping(true /* scheduleIdle */, false /* idleDelayed */, "completePauseLocked"); } } else { @@ -1869,32 +1849,6 @@ class ActivityStack extends ConfigurationContainer { mRootActivityContainer.ensureActivitiesVisible(resuming, 0, !PRESERVE_WINDOWS); } - void addToStopping(ActivityRecord r, boolean scheduleIdle, boolean idleDelayed, String reason) { - if (!mStackSupervisor.mStoppingActivities.contains(r)) { - EventLog.writeEvent(EventLogTags.AM_ADD_TO_STOPPING, r.mUserId, - System.identityHashCode(r), r.shortComponentName, reason); - mStackSupervisor.mStoppingActivities.add(r); - } - - // If we already have a few activities waiting to stop, then give up - // on things going idle and start clearing them out. Or if r is the - // last of activity of the last task the stack will be empty and must - // be cleared immediately. - boolean forceIdle = mStackSupervisor.mStoppingActivities.size() > MAX_STOPPING_TO_FORCE - || (r.isRootOfTask() && mTaskHistory.size() <= 1); - if (scheduleIdle || forceIdle) { - if (DEBUG_PAUSE) Slog.v(TAG_PAUSE, "Scheduling idle now: forceIdle=" - + forceIdle + "immediate=" + !idleDelayed); - if (!idleDelayed) { - mStackSupervisor.scheduleIdleLocked(); - } else { - mStackSupervisor.scheduleIdleTimeoutLocked(r); - } - } else { - checkReadyForSleep(); - } - } - /** * Returns true if the stack is translucent and can have other contents visible behind it if * needed. A stack is considered translucent if it don't contain a visible or @@ -2204,7 +2158,7 @@ class ActivityStack extends ConfigurationContainer { + " stackShouldBeVisible=" + stackShouldBeVisible + " behindFullscreenActivity=" + behindFullscreenActivity + " mLaunchTaskBehind=" + r.mLaunchTaskBehind); - makeInvisible(r); + r.makeInvisible(); } } final int windowingMode = getWindowingMode(); @@ -2381,63 +2335,6 @@ class ActivityStack extends ConfigurationContainer { return false; } - // TODO: Should probably be moved into ActivityRecord. - private void makeInvisible(ActivityRecord r) { - if (!r.visible) { - if (DEBUG_VISIBILITY) Slog.v(TAG_VISIBILITY, "Already invisible: " + r); - return; - } - // 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.getState()); - try { - final boolean canEnterPictureInPicture = r.checkEnterPictureInPictureState( - "makeInvisible", true /* beforeStopping */); - // Defer telling the client it is hidden if it can enter Pip and isn't current paused, - // stopped or stopping. This gives it a chance to enter Pip in onPause(). - // TODO: There is still a question surrounding activities in multi-window mode that want - // to enter Pip after they are paused, but are still visible. I they should be okay to - // enter Pip in those cases, but not "auto-Pip" which is what this condition covers and - // 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.isState(STOPPING, STOPPED, PAUSED); - r.setDeferHidingClient(deferHidingClient); - r.setVisible(false); - - switch (r.getState()) { - case STOPPING: - case STOPPED: - if (r.attachedToProcess()) { - if (DEBUG_VISIBILITY) Slog.v(TAG_VISIBILITY, - "Scheduling invisibility: " + r); - mService.getLifecycleManager().scheduleTransaction(r.app.getThread(), - r.appToken, WindowVisibilityItem.obtain(false /* showWindow */)); - } - - // Reset the flag indicating that an app can enter picture-in-picture once the - // activity is hidden - r.supportsEnterPipOnTaskSwitch = false; - break; - - case INITIALIZING: - case RESUMED: - case PAUSING: - case PAUSED: - case STARTED: - addToStopping(r, true /* scheduleIdle */, - canEnterPictureInPicture /* idleDelayed */, "makeInvisible"); - break; - - default: - break; - } - } catch (Exception e) { - // Just skip on any failure; we'll make it visible when it next restarts. - Slog.w(TAG, "Exception thrown making hidden: " + r.intent.getComponent(), e); - } - } - private boolean updateBehindFullscreen(boolean stackInvisible, boolean behindFullscreenActivity, ActivityRecord r) { if (r.fullscreen) { @@ -3655,49 +3552,6 @@ class ActivityStack extends ConfigurationContainer { return taskTop; } - void sendActivityResultLocked(int callingUid, ActivityRecord r, - String resultWho, int requestCode, int resultCode, Intent data) { - - if (callingUid > 0) { - mService.mUgmInternal.grantUriPermissionFromIntent(callingUid, r.packageName, - data, r.getUriPermissionsLocked(), r.mUserId); - } - - if (DEBUG_RESULTS) Slog.v(TAG, "Send activity result to " + r - + " : who=" + resultWho + " req=" + requestCode - + " res=" + resultCode + " data=" + data); - if (mResumedActivity == r && r.attachedToProcess()) { - try { - ArrayList<ResultInfo> list = new ArrayList<ResultInfo>(); - list.add(new ResultInfo(resultWho, requestCode, - resultCode, data)); - mService.getLifecycleManager().scheduleTransaction(r.app.getThread(), r.appToken, - ActivityResultItem.obtain(list)); - return; - } catch (Exception e) { - Slog.w(TAG, "Exception thrown sending result to " + r, e); - } - } - - r.addResultLocked(null, resultWho, requestCode, resultCode, data); - } - - /** Returns true if the task is one of the task finishing on-top of the top running task. */ - private boolean isATopFinishingTask(TaskRecord task) { - for (int i = mTaskHistory.size() - 1; i >= 0; --i) { - final TaskRecord current = mTaskHistory.get(i); - final ActivityRecord r = current.topRunningActivityLocked(); - if (r != null) { - // We got a top running activity, so there isn't a top finishing task... - return false; - } - if (current == task) { - return true; - } - } - return false; - } - void adjustFocusedActivityStack(ActivityRecord r, String reason) { if (!mRootActivityContainer.isTopDisplayFocusedStack(this) || ((mResumedActivity != r) && (mResumedActivity != null))) { @@ -3776,64 +3630,6 @@ class ActivityStack extends ConfigurationContainer { return stack; } - final void stopActivityLocked(ActivityRecord r) { - if (DEBUG_SWITCH) Slog.d(TAG_SWITCH, "Stopping: " + r); - if ((r.intent.getFlags()&Intent.FLAG_ACTIVITY_NO_HISTORY) != 0 - || (r.info.flags&ActivityInfo.FLAG_NO_HISTORY) != 0) { - if (!r.finishing) { - if (!shouldSleepActivities()) { - if (DEBUG_STATES) Slog.d(TAG_STATES, "no-history finish of " + r); - if (r.finishIfPossible("stop-no-history", false /* oomAdj */) - != FINISH_RESULT_CANCELLED) { - // {@link adjustFocusedActivityStack} must have been already called. - r.resumeKeyDispatchingLocked(); - return; - } - } else { - if (DEBUG_STATES) Slog.d(TAG_STATES, "Not finishing noHistory " + r - + " on stop because we're just sleeping"); - } - } - } - - if (r.attachedToProcess()) { - adjustFocusedActivityStack(r, "stopActivity"); - r.resumeKeyDispatchingLocked(); - try { - r.stopped = false; - if (DEBUG_STATES) Slog.v(TAG_STATES, - "Moving to STOPPING: " + r + " (stop requested)"); - r.setState(STOPPING, "stopActivityLocked"); - if (DEBUG_VISIBILITY) Slog.v(TAG_VISIBILITY, - "Stopping visible=" + r.visible + " for " + r); - if (!r.visible) { - r.setVisible(false); - } - EventLogTags.writeAmStopActivity( - r.mUserId, System.identityHashCode(r), r.shortComponentName); - mService.getLifecycleManager().scheduleTransaction(r.app.getThread(), r.appToken, - StopActivityItem.obtain(r.visible, r.configChangeFlags)); - if (shouldSleepOrShutDownActivities()) { - r.setSleeping(true); - } - Message msg = mHandler.obtainMessage(STOP_TIMEOUT_MSG, r); - mHandler.sendMessageDelayed(msg, STOP_TIMEOUT); - } catch (Exception e) { - // Maybe just ignore exceptions here... if the process - // has crashed, our death notification will clean things - // up. - Slog.w(TAG, "Exception thrown during pause", e); - // 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.setState(STOPPED, "stopActivityLocked"); - if (r.deferRelaunchUntilPaused) { - destroyActivityLocked(r, true, "stop-except"); - } - } - } - } - /** Finish all activities that were started for result from the specified activity. */ final void finishSubActivityLocked(ActivityRecord self, String resultWho, int requestCode) { for (int taskNdx = mTaskHistory.size() - 1; taskNdx >= 0; --taskNdx) { @@ -4103,7 +3899,7 @@ class ActivityStack extends ConfigurationContainer { * an activity moves away from the stack. */ void onActivityRemovedFromStack(ActivityRecord r) { - removeTimeoutsForActivityLocked(r); + removeTimeoutsForActivity(r); if (mResumedActivity != null && mResumedActivity == r) { setResumedActivity(null, "onActivityRemovedFromStack"); @@ -4119,132 +3915,64 @@ class ActivityStack extends ConfigurationContainer { } } - /** - * Perform the common clean-up of an activity record. This is called both - * as part of destroyActivityLocked() (when destroying the client-side - * representation) and cleaning things up as a result of its hosting - * processing going away, in which case there is no remaining client-side - * state to destroy so only the cleanup here is needed. - * - * Note: Call before #removeActivityFromHistoryLocked. - */ - private void cleanUpActivityLocked(ActivityRecord r, boolean cleanServices, boolean setState) { - onActivityRemovedFromStack(r); - - r.deferRelaunchUntilPaused = false; - r.frozenBeforeDestroy = false; - - if (setState) { - if (DEBUG_STATES) Slog.v(TAG_STATES, "Moving to DESTROYED: " + r + " (cleaning up)"); - r.setState(DESTROYED, "cleanupActivityLocked"); - if (DEBUG_APP) Slog.v(TAG_APP, "Clearing app during cleanUp for activity " + r); - r.app = null; - } - - // Inform supervisor the activity has been removed. - mStackSupervisor.cleanupActivity(r); - - - // Remove any pending results. - if (r.finishing && r.pendingResults != null) { - for (WeakReference<PendingIntentRecord> apr : r.pendingResults) { - PendingIntentRecord rec = apr.get(); - if (rec != null) { - mService.mPendingIntentController.cancelIntentSender(rec, false); - } - } - r.pendingResults = null; - } + /// HANDLER INTERFACE BEGIN + void removeTimeoutsForActivity(ActivityRecord r) { + mStackSupervisor.removeTimeoutsForActivityLocked(r); + removePauseTimeoutForActivity(r); + removeStopTimeoutForActivity(r); + removeDestroyTimeoutForActivity(r); + r.finishLaunchTickingLocked(); + } - if (cleanServices) { - cleanUpActivityServicesLocked(r); - } + void scheduleDestroyActivities(WindowProcessController owner, String reason) { + final Message msg = mHandler.obtainMessage(DESTROY_ACTIVITIES_MSG); + msg.obj = new ScheduleDestroyArgs(owner, reason); + mHandler.sendMessage(msg); + } - // Get rid of any pending idle timeouts. - removeTimeoutsForActivityLocked(r); - // Clean-up activities are no longer relaunching (e.g. app process died). Notify window - // manager so it can update its bookkeeping. - mWindowManager.notifyAppRelaunchesCleared(r.appToken); + void scheduleDestroyTimeoutForActivity(ActivityRecord r) { + final Message msg = mHandler.obtainMessage(DESTROY_TIMEOUT_MSG, r); + mHandler.sendMessageDelayed(msg, DESTROY_TIMEOUT); } - private void removeTimeoutsForActivityLocked(ActivityRecord r) { - mStackSupervisor.removeTimeoutsForActivityLocked(r); - mHandler.removeMessages(PAUSE_TIMEOUT_MSG, r); - mHandler.removeMessages(STOP_TIMEOUT_MSG, r); + void removeDestroyTimeoutForActivity(ActivityRecord r) { mHandler.removeMessages(DESTROY_TIMEOUT_MSG, r); - r.finishLaunchTickingLocked(); } - private void removeActivityFromHistoryLocked(ActivityRecord r, String reason) { - r.finishActivityResults(Activity.RESULT_CANCELED, null /* resultData */); - r.makeFinishingLocked(); - if (DEBUG_ADD_REMOVE) Slog.i(TAG_ADD_REMOVE, - "Removing activity " + r + " from stack callers=" + Debug.getCallers(5)); - - r.takeFromHistory(); - removeTimeoutsForActivityLocked(r); - if (DEBUG_STATES) Slog.v(TAG_STATES, - "Moving to DESTROYED: " + r + " (removed from history)"); - r.setState(DESTROYED, "removeActivityFromHistoryLocked"); - if (DEBUG_APP) Slog.v(TAG_APP, "Clearing app during remove for activity " + r); - r.app = null; - r.removeWindowContainer(); - final TaskRecord task = r.getTaskRecord(); - final boolean lastActivity = task != null ? task.removeActivity(r) : false; - // If we are removing the last activity in the task, not including task overlay activities, - // then fall through into the block below to remove the entire task itself - final boolean onlyHasTaskOverlays = task != null - ? task.onlyHasTaskOverlayActivities(false /* excludingFinishing */) : false; - - if (lastActivity || onlyHasTaskOverlays) { - if (DEBUG_STACK) { - Slog.i(TAG_STACK, - "removeActivityFromHistoryLocked: last activity removed from " + this - + " onlyHasTaskOverlays=" + onlyHasTaskOverlays); - } - - // The following block can be executed multiple times if there is more than one overlay. - // {@link ActivityStackSupervisor#removeTaskByIdLocked} handles this by reverse lookup - // of the task by id and exiting early if not found. - if (onlyHasTaskOverlays) { - // When destroying a task, tell the supervisor to remove it so that any activity it - // has can be cleaned up correctly. This is currently the only place where we remove - // a task with the DESTROYING mode, so instead of passing the onlyHasTaskOverlays - // state into removeTask(), we just clear the task here before the other residual - // work. - // TODO: If the callers to removeTask() changes such that we have multiple places - // where we are destroying the task, move this back into removeTask() - mStackSupervisor.removeTaskByIdLocked(task.taskId, false /* killProcess */, - !REMOVE_FROM_RECENTS, PAUSE_IMMEDIATELY, reason); - } + void scheduleStopTimeoutForActivity(ActivityRecord r) { + final Message msg = mHandler.obtainMessage(STOP_TIMEOUT_MSG, r); + mHandler.sendMessageDelayed(msg, STOP_TIMEOUT); + } - // We must keep the task around until all activities are destroyed. The following - // statement will only execute once since overlays are also considered activities. - if (lastActivity) { - removeTask(task, reason, REMOVE_TASK_MODE_DESTROYING); - } - } - cleanUpActivityServicesLocked(r); - r.removeUriPermissionsLocked(); + void removeStopTimeoutForActivity(ActivityRecord r) { + mHandler.removeMessages(STOP_TIMEOUT_MSG, r); } /** - * Perform clean-up of service connections in an activity record. + * Schedule a pause timeout in case the app doesn't respond. We don't give it much time because + * this directly impacts the responsiveness seen by the user. */ - private void cleanUpActivityServicesLocked(ActivityRecord r) { - if (r.mServiceConnectionsHolder == null) { - return; - } - // Throw away any services that have been bound by this activity. - r.mServiceConnectionsHolder.disconnectActivityFromServices(); + private void schedulePauseTimeoutForActivity(ActivityRecord r) { + final Message msg = mHandler.obtainMessage(PAUSE_TIMEOUT_MSG, r); + r.pauseTime = SystemClock.uptimeMillis(); + mHandler.sendMessageDelayed(msg, PAUSE_TIMEOUT); + if (DEBUG_PAUSE) Slog.v(TAG_PAUSE, "Waiting for pause to complete..."); } - final void scheduleDestroyActivities(WindowProcessController owner, String reason) { - Message msg = mHandler.obtainMessage(DESTROY_ACTIVITIES_MSG); - msg.obj = new ScheduleDestroyArgs(owner, reason); - mHandler.sendMessage(msg); + void removePauseTimeoutForActivity(ActivityRecord r) { + mHandler.removeMessages(PAUSE_TIMEOUT_MSG, r); } + void scheduleLaunchTickForActivity(ActivityRecord r) { + final Message msg = mHandler.obtainMessage(LAUNCH_TICK_MSG, r); + mHandler.sendMessageDelayed(msg, LAUNCH_TICK); + } + + void removeLaunchTickMessages() { + mHandler.removeMessages(LAUNCH_TICK_MSG); + } + /// HANDLER INTERFACE END + private void destroyActivitiesLocked(WindowProcessController owner, String reason) { boolean lastIsOpaque = false; boolean activityRemoved = false; @@ -4269,7 +3997,7 @@ class ActivityStack extends ConfigurationContainer { + " in state " + r.getState() + " resumed=" + mResumedActivity + " pausing=" + mPausingActivity + " for reason " + reason); - if (destroyActivityLocked(r, true, reason)) { + if (r.destroyImmediately(true /* removeFromTask */, reason)) { activityRemoved = true; } } @@ -4280,16 +4008,6 @@ class ActivityStack extends ConfigurationContainer { } } - final boolean safelyDestroyActivityLocked(ActivityRecord r, String reason) { - if (r.isDestroyable()) { - if (DEBUG_SWITCH) Slog.v(TAG_SWITCH, - "Destroying " + r + " in state " + r.getState() + " resumed=" + mResumedActivity - + " pausing=" + mPausingActivity + " for reason " + reason); - return destroyActivityLocked(r, true, reason); - } - return false; - } - final int releaseSomeActivitiesLocked(WindowProcessController app, ArraySet<TaskRecord> tasks, String reason) { // Iterate over tasks starting at the back (oldest) first. @@ -4313,7 +4031,7 @@ class ActivityStack extends ConfigurationContainer { if (DEBUG_RELEASE) Slog.v(TAG_RELEASE, "Destroying " + activity + " in state " + activity.getState() + " resumed=" + mResumedActivity + " pausing=" + mPausingActivity + " for reason " + reason); - destroyActivityLocked(activity, true, reason); + activity.destroyImmediately(true /* removeFromApp */, reason); if (activities.get(actNdx) != activity) { // Was removed from list, back up so we don't miss the next one. actNdx--; @@ -4335,145 +4053,6 @@ class ActivityStack extends ConfigurationContainer { return numReleased; } - /** - * Destroy the current CLIENT SIDE instance of an activity. This may be * called both when - * actually finishing an activity, or when performing a configuration switch where we destroy - * the current client-side object but then create a new client-side object for this same - * HistoryRecord. - * Normally the server-side record will be removed when the client reports back after - * destruction. If, however, at this point there is no client process attached, the record will - * removed immediately. - */ - final boolean destroyActivityLocked(ActivityRecord r, boolean removeFromApp, String reason) { - if (DEBUG_SWITCH || DEBUG_CLEANUP) Slog.v(TAG_SWITCH, - "Removing activity from " + reason + ": token=" + r - + ", app=" + (r.hasProcess() ? r.app.mName : "(null)")); - - if (r.isState(DESTROYING, DESTROYED)) { - if (DEBUG_STATES) Slog.v(TAG_STATES, "activity " + r + " already destroying." - + "skipping request with reason:" + reason); - return false; - } - - EventLog.writeEvent(EventLogTags.AM_DESTROY_ACTIVITY, - r.mUserId, System.identityHashCode(r), - r.getTaskRecord().taskId, r.shortComponentName, reason); - - boolean removedFromHistory = false; - - cleanUpActivityLocked(r, false, false); - - final boolean hadApp = r.hasProcess(); - - if (hadApp) { - if (removeFromApp) { - r.app.removeActivity(r); - if (!r.app.hasActivities()) { - mService.clearHeavyWeightProcessIfEquals(r.app); - } - if (!r.app.hasActivities()) { - // Update any services we are bound to that might care about whether - // their client may have activities. - // No longer have activities, so update LRU list and oom adj. - r.app.updateProcessInfo(true /* updateServiceConnectionActivities */, - false /* activityChange */, true /* updateOomAdj */); - } - } - - boolean skipDestroy = false; - - try { - if (DEBUG_SWITCH) Slog.i(TAG_SWITCH, "Destroying: " + r); - mService.getLifecycleManager().scheduleTransaction(r.app.getThread(), r.appToken, - DestroyActivityItem.obtain(r.finishing, r.configChangeFlags)); - } catch (Exception e) { - // We can just ignore exceptions here... if the process - // has crashed, our death notification will clean things - // up. - //Slog.w(TAG, "Exception thrown during finish", e); - if (r.finishing) { - removeActivityFromHistoryLocked(r, reason + " exceptionInScheduleDestroy"); - removedFromHistory = true; - skipDestroy = true; - } - } - - r.nowVisible = false; - - // If the activity is finishing, we need to wait on removing it - // from the list to give it a chance to do its cleanup. During - // that time it may make calls back with its token so we need to - // be able to find it on the list and so we don't want to remove - // it from the list yet. Otherwise, we can just immediately put - // it in the destroyed state since we are not removing it from the - // list. - if (r.finishing && !skipDestroy) { - if (DEBUG_STATES) Slog.v(TAG_STATES, "Moving to DESTROYING: " + r - + " (destroy requested)"); - 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.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; - } - } else { - // remove this record from the history. - if (r.finishing) { - removeActivityFromHistoryLocked(r, reason + " hadNoApp"); - removedFromHistory = true; - } else { - if (DEBUG_STATES) Slog.v(TAG_STATES, "Moving to DESTROYED: " + r + " (no app)"); - 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; - } - } - - r.configChangeFlags = 0; - - if (!mLRUActivities.remove(r) && hadApp) { - Slog.w(TAG, "Activity " + r + " being finished, but not in LRU list"); - } - - return removedFromHistory; - } - - final void activityDestroyedLocked(IBinder token, String reason) { - final long origId = Binder.clearCallingIdentity(); - try { - activityDestroyedLocked(ActivityRecord.forTokenLocked(token), reason); - } finally { - Binder.restoreCallingIdentity(origId); - } - } - - /** - * This method is to only be called from the client via binder when the activity is destroyed - * AND finished. - */ - 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); - } - } - - mRootActivityContainer.resumeFocusedStacksTopActivities(); - } - private void removeHistoryRecordsForAppLocked(ArrayList<ActivityRecord> list, WindowProcessController app, String listName) { int i = list.size(); @@ -4486,7 +4065,7 @@ class ActivityStack extends ConfigurationContainer { if (r.app == app) { if (DEBUG_CLEANUP) Slog.v(TAG_CLEANUP, "---> REMOVING this entry!"); list.remove(i); - removeTimeoutsForActivityLocked(r); + removeTimeoutsForActivity(r); } } } @@ -4581,9 +4160,9 @@ class ActivityStack extends ConfigurationContainer { // other apps when user transfers focus to the restarted activity. r.nowVisible = r.visible; } - cleanUpActivityLocked(r, true, true); + r.cleanUp(true /* cleanServices */, true /* setState */); if (remove) { - removeActivityFromHistoryLocked(r, "appDied"); + r.removeFromHistory("appDied"); } } } @@ -4774,16 +4353,6 @@ class ActivityStack extends ConfigurationContainer { return true; } - static void logStartActivity(int tag, ActivityRecord r, TaskRecord task) { - final Uri data = r.intent.getData(); - final String strData = data != null ? data.toSafeString() : null; - - EventLog.writeEvent(tag, - r.mUserId, System.identityHashCode(r), task.taskId, - r.shortComponentName, r.intent.getAction(), - r.intent.getType(), strData, r.intent.getFlags()); - } - /** * Ensures all visible activities at or below the input activity have the right configuration. */ @@ -5192,7 +4761,7 @@ class ActivityStack extends ConfigurationContainer { EventLog.writeEvent(EventLogTags.AM_REMOVE_TASK, task.taskId, getStackId()); } - removeActivitiesFromLRUListLocked(task); + removeActivitiesFromLRUList(task); updateTaskMovement(task, true); if (mode == REMOVE_TASK_MODE_DESTROYING) { @@ -5375,7 +4944,7 @@ class ActivityStack extends ConfigurationContainer { // If the activity was previously pausing, then ensure we transfer that as well if (setPause) { mPausingActivity = r; - schedulePauseTimeout(r); + schedulePauseTimeoutForActivity(r); } // Move the stack in which we are placing the activity to the front. moveToFront(reason); diff --git a/services/core/java/com/android/server/wm/ActivityStackSupervisor.java b/services/core/java/com/android/server/wm/ActivityStackSupervisor.java index d0c70de7e8ac..a06b9ce5d645 100644 --- a/services/core/java/com/android/server/wm/ActivityStackSupervisor.java +++ b/services/core/java/com/android/server/wm/ActivityStackSupervisor.java @@ -42,6 +42,7 @@ import static android.content.pm.PackageManager.PERMISSION_DENIED; import static android.content.pm.PackageManager.PERMISSION_GRANTED; import static android.graphics.Rect.copyOrNull; import static android.os.PowerManager.PARTIAL_WAKE_LOCK; +import static android.os.Process.INVALID_UID; import static android.os.Process.SYSTEM_UID; import static android.os.Trace.TRACE_TAG_ACTIVITY_MANAGER; import static android.view.Display.DEFAULT_DISPLAY; @@ -1028,9 +1029,8 @@ public class ActivityStackSupervisor implements RecentTasks.Callbacks { if (componentRestriction == ACTIVITY_RESTRICTION_PERMISSION || actionRestriction == ACTIVITY_RESTRICTION_PERMISSION) { if (resultRecord != null) { - resultStack.sendActivityResultLocked(-1, - resultRecord, resultWho, requestCode, - Activity.RESULT_CANCELED, null); + resultRecord.sendResult(INVALID_UID, resultWho, requestCode, + Activity.RESULT_CANCELED, null /* data */); } final String msg; if (actionRestriction == ACTIVITY_RESTRICTION_PERMISSION) { @@ -1349,7 +1349,7 @@ public class ActivityStackSupervisor implements RecentTasks.Callbacks { // TODO(b/137329632): Wait for idle of the right activity, not just any. r.destroyIfPossible("activityIdleInternalLocked"); } else { - stack.stopActivityLocked(r); + r.stopIfPossible(); } } } @@ -1360,7 +1360,7 @@ public class ActivityStackSupervisor implements RecentTasks.Callbacks { r = finishes.get(i); final ActivityStack stack = r.getActivityStack(); if (stack != null) { - activityRemoved |= stack.destroyActivityLocked(r, true, "finish-idle"); + activityRemoved |= r.destroyImmediately(true /* removeFromApp */, "finish-idle"); } } diff --git a/services/core/java/com/android/server/wm/ActivityStarter.java b/services/core/java/com/android/server/wm/ActivityStarter.java index d6250f6e0cf2..48bc96346e9e 100644 --- a/services/core/java/com/android/server/wm/ActivityStarter.java +++ b/services/core/java/com/android/server/wm/ActivityStarter.java @@ -55,6 +55,7 @@ import static android.content.pm.ActivityInfo.LAUNCH_SINGLE_INSTANCE; import static android.content.pm.ActivityInfo.LAUNCH_SINGLE_TASK; import static android.content.pm.ActivityInfo.LAUNCH_SINGLE_TOP; import static android.content.pm.PackageManager.PERMISSION_GRANTED; +import static android.os.Process.INVALID_UID; import static android.view.Display.DEFAULT_DISPLAY; import static android.view.Display.INVALID_DISPLAY; @@ -752,8 +753,8 @@ class ActivityStarter { if (err != START_SUCCESS) { if (resultRecord != null) { - resultStack.sendActivityResultLocked( - -1, resultRecord, resultWho, requestCode, RESULT_CANCELED, null); + resultRecord.sendResult(INVALID_UID, resultWho, requestCode, RESULT_CANCELED, + null /* data */); } SafeActivityOptions.abort(options); return err; @@ -817,8 +818,8 @@ class ActivityStarter { if (abort) { if (resultRecord != null) { - resultStack.sendActivityResultLocked(-1, resultRecord, resultWho, requestCode, - RESULT_CANCELED, null); + resultRecord.sendResult(INVALID_UID, resultWho, requestCode, RESULT_CANCELED, + null /* data */); } // We pretend to the caller that it was really started, but // they will just get a cancel result. @@ -1450,18 +1451,17 @@ class ActivityStarter { * TODO(b/131748165): Refactor the logic so we don't need to call this method everywhere. */ private boolean handleBackgroundActivityAbort(ActivityRecord r) { - // TODO(b/131747138): Remove toast and refactor related code in Q release. - boolean abort = !mService.isBackgroundActivityStartsEnabled(); + // TODO(b/131747138): Remove toast and refactor related code in R release. + final boolean abort = !mService.isBackgroundActivityStartsEnabled(); if (!abort) { return false; } - ActivityRecord resultRecord = r.resultTo; - String resultWho = r.resultWho; + final ActivityRecord resultRecord = r.resultTo; + final String resultWho = r.resultWho; int requestCode = r.requestCode; if (resultRecord != null) { - ActivityStack resultStack = resultRecord.getActivityStack(); - resultStack.sendActivityResultLocked(-1, resultRecord, resultWho, requestCode, - RESULT_CANCELED, null); + resultRecord.sendResult(INVALID_UID, resultWho, requestCode, RESULT_CANCELED, + null /* data */); } // We pretend to the caller that it was really started to make it backward compatible, but // they will just get a cancel result. @@ -1626,12 +1626,9 @@ class ActivityStarter { } if (mStartActivity.packageName == null) { - final ActivityStack sourceStack = mStartActivity.resultTo != null - ? mStartActivity.resultTo.getActivityStack() : null; - if (sourceStack != null) { - sourceStack.sendActivityResultLocked(-1 /* callingUid */, mStartActivity.resultTo, - mStartActivity.resultWho, mStartActivity.requestCode, RESULT_CANCELED, - null /* data */); + if (mStartActivity.resultTo != null) { + mStartActivity.resultTo.sendResult(INVALID_UID, mStartActivity.resultWho, + mStartActivity.requestCode, RESULT_CANCELED, null /* data */); } ActivityOptions.abort(mOptions); return START_CLASS_NOT_FOUND; @@ -1708,8 +1705,8 @@ class ActivityStarter { EventLog.writeEvent(EventLogTags.AM_CREATE_TASK, mStartActivity.mUserId, mStartActivity.getTaskRecord().taskId); } - ActivityStack.logStartActivity( - EventLogTags.AM_CREATE_ACTIVITY, mStartActivity, mStartActivity.getTaskRecord()); + mStartActivity.logStartActivity( + EventLogTags.AM_CREATE_ACTIVITY, mStartActivity.getTaskRecord()); mTargetStack.mLastPausedActivity = null; mRootActivityContainer.sendPowerHintForLaunchStartIfNeeded( @@ -1927,17 +1924,14 @@ class ActivityStarter { } private void sendNewTaskResultRequestIfNeeded() { - final ActivityStack sourceStack = mStartActivity.resultTo != null - ? mStartActivity.resultTo.getActivityStack() : null; - if (sourceStack != null && (mLaunchFlags & FLAG_ACTIVITY_NEW_TASK) != 0) { + if (mStartActivity.resultTo != null && (mLaunchFlags & FLAG_ACTIVITY_NEW_TASK) != 0) { // For whatever reason this activity is being launched into a new task... // yet the caller has requested a result back. Well, that is pretty messed up, // so instead immediately send back a cancel and let the new task continue launched // as normal without a dependency on its originator. Slog.w(TAG, "Activity is launching as a new task, so cancelling activity result."); - sourceStack.sendActivityResultLocked(-1 /* callingUid */, mStartActivity.resultTo, - mStartActivity.resultWho, mStartActivity.requestCode, RESULT_CANCELED, - null /* data */); + mStartActivity.resultTo.sendResult(INVALID_UID, mStartActivity.resultWho, + mStartActivity.requestCode, RESULT_CANCELED, null /* data */); mStartActivity.resultTo = null; } } @@ -2362,7 +2356,7 @@ class ActivityStarter { return; } - ActivityStack.logStartActivity(AM_NEW_INTENT, activity, activity.getTaskRecord()); + activity.logStartActivity(AM_NEW_INTENT, activity.getTaskRecord()); activity.deliverNewIntentLocked(mCallingUid, mStartActivity.intent, mStartActivity.launchedFromPackage); mIntentDelivered = true; @@ -2429,7 +2423,7 @@ class ActivityStarter { ActivityRecord top = sourceTask.performClearTaskLocked(mStartActivity, mLaunchFlags); mKeepCurTransition = true; if (top != null) { - ActivityStack.logStartActivity(AM_NEW_INTENT, mStartActivity, top.getTaskRecord()); + mStartActivity.logStartActivity(AM_NEW_INTENT, top.getTaskRecord()); deliverNewIntent(top); // For paranoia, make sure we have correctly resumed the top activity. mTargetStack.mLastPausedActivity = null; @@ -2448,7 +2442,7 @@ class ActivityStarter { final TaskRecord task = top.getTaskRecord(); task.moveActivityToFrontLocked(top); top.updateOptionsLocked(mOptions); - ActivityStack.logStartActivity(AM_NEW_INTENT, mStartActivity, task); + mStartActivity.logStartActivity(AM_NEW_INTENT, task); deliverNewIntent(top); mTargetStack.mLastPausedActivity = null; if (mDoResume) { diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java index 2da2ebc55e6f..3c5947a51e11 100644 --- a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java +++ b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java @@ -46,7 +46,6 @@ import static android.content.pm.PackageManager.FEATURE_FREEFORM_WINDOW_MANAGEME import static android.content.pm.PackageManager.FEATURE_PICTURE_IN_PICTURE; import static android.content.pm.PackageManager.PERMISSION_GRANTED; import static android.content.res.Configuration.UI_MODE_TYPE_TELEVISION; -import static android.os.Build.VERSION_CODES.N; import static android.os.FactoryTest.FACTORY_TEST_HIGH_LEVEL; import static android.os.FactoryTest.FACTORY_TEST_LOW_LEVEL; import static android.os.FactoryTest.FACTORY_TEST_OFF; @@ -1754,9 +1753,14 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub { public final void activityDestroyed(IBinder token) { if (DEBUG_SWITCH) Slog.v(TAG_SWITCH, "ACTIVITY DESTROYED: " + token); synchronized (mGlobalLock) { - ActivityStack stack = ActivityRecord.getStackLocked(token); - if (stack != null) { - stack.activityDestroyedLocked(token, "activityDestroyed"); + final long origId = Binder.clearCallingIdentity(); + try { + final ActivityRecord activity = ActivityRecord.forTokenLocked(token); + if (activity != null) { + activity.destroyed("activityDestroyed"); + } + } finally { + Binder.restoreCallingIdentity(origId); } } } @@ -3237,7 +3241,7 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub { if (r == null) { return false; } - return r.getActivityStack().safelyDestroyActivityLocked(r, "app-req"); + return r.safelyDestroy("app-req"); } finally { Binder.restoreCallingIdentity(origId); } @@ -6485,8 +6489,7 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub { synchronized (mGlobalLock) { final ActivityRecord r = ActivityRecord.isInStackLocked(activityToken); if (r != null && r.getActivityStack() != null) { - r.getActivityStack().sendActivityResultLocked(callingUid, r, resultWho, - requestCode, resultCode, data); + r.sendResult(callingUid, resultWho, requestCode, resultCode, data); } } } diff --git a/services/core/java/com/android/server/wm/RecentsAnimation.java b/services/core/java/com/android/server/wm/RecentsAnimation.java index caa836376248..1a8944ab415b 100644 --- a/services/core/java/com/android/server/wm/RecentsAnimation.java +++ b/services/core/java/com/android/server/wm/RecentsAnimation.java @@ -149,8 +149,8 @@ class RecentsAnimation implements RecentsAnimationCallbacks, // traversal in non-stopped state (ViewRootImpl.mStopped) that would initialize more // things (e.g. the measure can be done earlier). The actual stop will be performed when // it reports idle. - targetStack.addToStopping(targetActivity, true /* scheduleIdle */, - true /* idleDelayed */, "preloadRecents"); + targetActivity.addToStopping(true /* scheduleIdle */, true /* idleDelayed */, + "preloadRecents"); } } 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 eaffd77198ff..13748cbf8c9c 100644 --- a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java @@ -43,6 +43,7 @@ import static com.android.dx.mockito.inline.extended.ExtendedMockito.when; import static com.android.server.wm.ActivityRecord.FINISH_RESULT_CANCELLED; import static com.android.server.wm.ActivityRecord.FINISH_RESULT_REMOVED; import static com.android.server.wm.ActivityRecord.FINISH_RESULT_REQUESTED; +import static com.android.server.wm.ActivityStack.ActivityState.DESTROYED; import static com.android.server.wm.ActivityStack.ActivityState.DESTROYING; import static com.android.server.wm.ActivityStack.ActivityState.FINISHING; import static com.android.server.wm.ActivityStack.ActivityState.INITIALIZING; @@ -1047,8 +1048,7 @@ public class ActivityRecordTests extends ActivityTestsBase { assertEquals(DESTROYING, mActivity.getState()); assertTrue(mActivity.finishing); - verify(mStack).destroyActivityLocked(eq(mActivity), eq(true) /* removeFromApp */, - anyString()); + verify(mActivity).destroyImmediately(eq(true) /* removeFromApp */, anyString()); } /** @@ -1072,12 +1072,140 @@ public class ActivityRecordTests extends ActivityTestsBase { // Verify that the activity was not actually destroyed, but waits for next one to come up // instead. - verify(mStack, never()).destroyActivityLocked(eq(mActivity), eq(true) /* removeFromApp */, - anyString()); + verify(mActivity, never()).destroyImmediately(eq(true) /* removeFromApp */, anyString()); assertEquals(FINISHING, mActivity.getState()); assertTrue(mActivity.mStackSupervisor.mFinishingActivities.contains(mActivity)); } + /** + * Test that the activity will be moved to destroying state and the message to destroy will be + * sent to the client. + */ + @Test + public void testDestroyImmediately_hadApp_finishing() { + mActivity.finishing = true; + mActivity.destroyImmediately(false /* removeFromApp */, "test"); + + assertEquals(DESTROYING, mActivity.getState()); + } + + /** + * Test that the activity will be moved to destroyed state immediately if it was not marked as + * finishing before {@link ActivityRecord#destroyImmediately(boolean, String)}. + */ + @Test + public void testDestroyImmediately_hadApp_notFinishing() { + mActivity.finishing = false; + mActivity.destroyImmediately(false /* removeFromApp */, "test"); + + assertEquals(DESTROYED, mActivity.getState()); + } + + /** + * Test that an activity with no process attached and that is marked as finishing will be + * removed from task when {@link ActivityRecord#destroyImmediately(boolean, String)} is called. + */ + @Test + public void testDestroyImmediately_noApp_finishing() { + mActivity.app = null; + mActivity.finishing = true; + final TaskRecord task = mActivity.getTaskRecord(); + + mActivity.destroyImmediately(false /* removeFromApp */, "test"); + + assertEquals(DESTROYED, mActivity.getState()); + assertNull(mActivity.getTaskRecord()); + assertEquals(0, task.getChildCount()); + } + + /** + * Test that an activity with no process attached and that is not marked as finishing will be + * marked as DESTROYED but not removed from task. + */ + @Test + public void testDestroyImmediately_noApp_notFinishing() { + mActivity.app = null; + mActivity.finishing = false; + final TaskRecord task = mActivity.getTaskRecord(); + + mActivity.destroyImmediately(false /* removeFromApp */, "test"); + + assertEquals(DESTROYED, mActivity.getState()); + assertEquals(task, mActivity.getTaskRecord()); + assertEquals(1, task.getChildCount()); + } + + /** + * Test that an activity will not be destroyed if it is marked as non-destroyable. + */ + @Test + public void testSafelyDestroy_nonDestroyable() { + doReturn(false).when(mActivity).isDestroyable(); + + mActivity.safelyDestroy("test"); + + verify(mActivity, never()).destroyImmediately(eq(true) /* removeFromApp */, anyString()); + } + + /** + * Test that an activity will not be destroyed if it is marked as non-destroyable. + */ + @Test + public void testSafelyDestroy_destroyable() { + doReturn(true).when(mActivity).isDestroyable(); + + mActivity.safelyDestroy("test"); + + verify(mActivity).destroyImmediately(eq(true) /* removeFromApp */, anyString()); + } + + @Test + public void testRemoveFromHistory() { + final ActivityStack stack = mActivity.getActivityStack(); + final TaskRecord task = mActivity.getTaskRecord(); + + mActivity.removeFromHistory("test"); + + assertEquals(DESTROYED, mActivity.getState()); + assertNull(mActivity.app); + assertNull(mActivity.getTaskRecord()); + assertEquals(0, task.getChildCount()); + assertNull(task.getStack()); + assertEquals(0, stack.getChildCount()); + } + + /** + * Test that it's not allowed to call {@link ActivityRecord#destroyed(String)} if activity is + * not in destroying or destroyed state. + */ + @Test(expected = IllegalStateException.class) + public void testDestroyed_notDestroying() { + mActivity.setState(STOPPED, "test"); + mActivity.destroyed("test"); + } + + /** + * Test that {@link ActivityRecord#destroyed(String)} can be called if an activity is destroying + */ + @Test + public void testDestroyed_destroying() { + mActivity.setState(DESTROYING, "test"); + mActivity.destroyed("test"); + + verify(mActivity).removeFromHistory(anyString()); + } + + /** + * Test that {@link ActivityRecord#destroyed(String)} can be called if an activity is destroyed. + */ + @Test + public void testDestroyed_destroyed() { + mActivity.setState(DESTROYED, "test"); + mActivity.destroyed("test"); + + verify(mActivity).removeFromHistory(anyString()); + } + /** Setup {@link #mActivity} as a size-compat-mode-able activity without fixed orientation. */ private void prepareFixedAspectRatioUnresizableActivity() { setupDisplayContentForCompatDisplayInsets(); 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 d31945088996..60c5f0bd7188 100644 --- a/services/tests/wmtests/src/com/android/server/wm/ActivityStackTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/ActivityStackTests.java @@ -233,7 +233,7 @@ public class ActivityStackTests extends ActivityTestsBase { final ActivityRecord r = new ActivityBuilder(mService).setTask(mTask).build(); r.info.flags |= ActivityInfo.FLAG_NO_HISTORY; mStack.moveToFront("testStopActivityWithDestroy"); - mStack.stopActivityLocked(r); + r.stopIfPossible(); // Mostly testing to make sure there is a crash in the call part, so if we get here we are // good-to-go! } @@ -879,7 +879,7 @@ public class ActivityStackTests extends ActivityTestsBase { final ActivityRecord overlayActivity = new ActivityBuilder(mService).setTask(mTask) .setComponent(new ComponentName("package.overlay", ".OverlayActivity")).build(); // If the task only remains overlay activity, the task should also be removed. - // See {@link ActivityStack#removeActivityFromHistoryLocked}. + // See {@link ActivityStack#removeFromHistory}. overlayActivity.mTaskOverlay = true; // The activity without an app means it will be removed immediately. |