summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--services/core/java/com/android/server/wm/ActivityRecord.java497
-rw-r--r--services/core/java/com/android/server/wm/ActivityStack.java571
-rw-r--r--services/core/java/com/android/server/wm/ActivityStackSupervisor.java10
-rw-r--r--services/core/java/com/android/server/wm/ActivityStarter.java50
-rw-r--r--services/core/java/com/android/server/wm/ActivityTaskManagerService.java17
-rw-r--r--services/core/java/com/android/server/wm/RecentsAnimation.java4
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java136
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/ActivityStackTests.java4
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.