ActivityStack stack = ActivityRecord.getStackLocked(token);
if (stack != null) {
ActivityRecord r =
- mStackSupervisor.activityIdleInternalLocked(token, false, config);
+ mStackSupervisor.activityIdleInternalLocked(token, false /* fromTimeout */,
+ false /* processPausingActivities */, config);
if (stopProfiling) {
if ((mProfileProc == r.app) && (mProfileFd != null)) {
try {
// Activity supports picture-in-picture, now check that we can enter PiP at this
// point, if it is
- if (!r.checkEnterPictureInPictureState("enterPictureInPictureMode")) {
+ if (!r.checkEnterPictureInPictureState("enterPictureInPictureMode",
+ false /* noThrow */)) {
return false;
}
/**
* @return whether this activity is currently allowed to enter PIP, throwing an exception if
- * the activity is not currently visible.
+ * the activity is not currently visible and {@param noThrow} is not set.
*/
- boolean checkEnterPictureInPictureState(String caller) {
+ boolean checkEnterPictureInPictureState(String caller, boolean noThrow) {
boolean isKeyguardLocked = service.isKeyguardLocked();
boolean hasPinnedStack = mStackSupervisor.getStack(PINNED_STACK_ID) != null;
switch (state) {
&& checkEnterPictureInPictureOnHideAppOpsState();
}
default:
- throw new IllegalStateException(caller
- + ": Current activity is not visible (state=" + state.name() + ") "
- + "r=" + this);
+ if (noThrow) {
+ return false;
+ } else {
+ throw new IllegalStateException(caller
+ + ": Current activity is not visible (state=" + state.name() + ") "
+ + "r=" + this);
+ }
}
}
if (!idle) {
// Instead of doing the full stop routine here, let's just hide any activities
// we now can, and let them stop when the normal idle happens.
- mStackSupervisor.processStoppingActivitiesLocked(false);
+ mStackSupervisor.processStoppingActivitiesLocked(null /* idleActivity */,
+ false /* remove */, true /* processPausingActivities */);
} else {
// If this activity was already idle, then we now need to make sure we perform
// the full stop of any activities that are waiting to do so. This is because
// if the app is relaunched when it's stopped, and we're not resuming,
// put it back into stopped state.
if (stopped) {
- getStack().addToStopping(this, true /* immediate */);
+ getStack().addToStopping(this, true /* scheduleIdle */, false /* idleDelayed */);
}
}
}
/**
+ * 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.
*
return false;
} else {
- // 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.
- Message msg = mHandler.obtainMessage(PAUSE_TIMEOUT_MSG);
- msg.obj = prev;
- prev.pauseTime = SystemClock.uptimeMillis();
- mHandler.sendMessageDelayed(msg, PAUSE_TIMEOUT);
- if (DEBUG_PAUSE) Slog.v(TAG_PAUSE, "Waiting for pause to complete...");
+ schedulePauseTimeout(prev);
return true;
}
|| mService.isSleepingOrShuttingDownLocked()) {
// If we were visible then resumeTopActivities will release resources before
// stopping.
- addToStopping(prev, true /* immediate */);
+ addToStopping(prev, true /* scheduleIdle */, false /* idleDelayed */);
}
} else {
if (DEBUG_PAUSE) Slog.v(TAG_PAUSE, "App died during pause, not stopping: " + prev);
mStackSupervisor.ensureActivitiesVisibleLocked(resuming, 0, !PRESERVE_WINDOWS);
}
- void addToStopping(ActivityRecord r, boolean immediate) {
+ void addToStopping(ActivityRecord r, boolean scheduleIdle, boolean idleDelayed) {
if (!mStackSupervisor.mStoppingActivities.contains(r)) {
mStackSupervisor.mStoppingActivities.add(r);
}
// be cleared immediately.
boolean forceIdle = mStackSupervisor.mStoppingActivities.size() > MAX_STOPPING_TO_FORCE
|| (r.frontOfTask && mTaskHistory.size() <= 1);
-
- if (immediate || forceIdle) {
+ if (scheduleIdle || forceIdle) {
if (DEBUG_PAUSE) Slog.v(TAG_PAUSE, "Scheduling idle now: forceIdle="
- + forceIdle + "immediate=" + immediate);
- mStackSupervisor.scheduleIdleLocked();
+ + forceIdle + "immediate=" + !idleDelayed);
+ if (!idleDelayed) {
+ mStackSupervisor.scheduleIdleLocked();
+ } else {
+ mStackSupervisor.scheduleIdleTimeoutLocked(r);
+ }
} else {
mStackSupervisor.checkReadyForSleepLocked();
}
if (visibleBehind == r) {
releaseBackgroundResources(r);
} else {
- addToStopping(r, true /* immediate */);
+ // If this activity is in a state where it can currently enter
+ // picture-in-picture, then don't immediately schedule the idle now in case
+ // the activity tries to enterPictureInPictureMode() later. Otherwise,
+ // we will try and stop the activity next time idle is processed.
+ final boolean canEnterPictureInPicture = r.checkEnterPictureInPictureState(
+ "makeInvisible", true /* noThrow */);
+ addToStopping(r, true /* scheduleIdle */,
+ canEnterPictureInPicture /* idleDelayed */);
}
break;
if (mode == FINISH_AFTER_VISIBLE && (r.visible || r.nowVisible)
&& next != null && !next.nowVisible) {
if (!mStackSupervisor.mStoppingActivities.contains(r)) {
- addToStopping(r, false /* immediate */);
+ addToStopping(r, false /* scheduleIdle */, false /* idleDelayed */);
}
if (DEBUG_STATES) Slog.v(TAG_STATES,
"Moving to STOPPING: "+ r + " (finish requested)");
mWindowManager.notifyAppRelaunchesCleared(r.appToken);
}
- private void removeTimeoutsForActivityLocked(ActivityRecord r) {
+ void removeTimeoutsForActivityLocked(ActivityRecord r) {
mStackSupervisor.removeTimeoutsForActivityLocked(r);
mHandler.removeMessages(PAUSE_TIMEOUT_MSG, r);
mHandler.removeMessages(STOP_TIMEOUT_MSG, r);
// If the activity was previously pausing, then ensure we transfer that as well
if (setPause) {
mPausingActivity = r;
+ schedulePauseTimeout(r);
}
// Move the stack in which we are placing the activity to the front. The call will also
// make sure the activity focus is set.
}
if (wasPaused) {
prevStack.mPausingActivity = null;
+ prevStack.removeTimeoutsForActivityLocked(r);
}
}
// Checked.
final ActivityRecord activityIdleInternalLocked(final IBinder token, boolean fromTimeout,
- Configuration config) {
+ boolean processPausingActivities, Configuration config) {
if (DEBUG_ALL) Slog.v(TAG, "Activity idle: " + token);
ArrayList<ActivityRecord> finishes = null;
}
// Atomically retrieve all of the other things to do.
- final ArrayList<ActivityRecord> stops = processStoppingActivitiesLocked(true);
+ final ArrayList<ActivityRecord> stops = processStoppingActivitiesLocked(r,
+ true /* remove */, processPausingActivities);
NS = stops != null ? stops.size() : 0;
if ((NF = mFinishingActivities.size()) > 0) {
finishes = new ArrayList<>(mFinishingActivities);
// Reset the paused activity on the previous stack
if (wasPaused) {
prevStack.mPausingActivity = null;
+ prevStack.removeTimeoutsForActivityLocked(r);
}
// If the task had focus before (or we're requested to move focus),
return mService.mUserController.isCurrentProfileLocked(userId);
}
- final ArrayList<ActivityRecord> processStoppingActivitiesLocked(boolean remove) {
+ final ArrayList<ActivityRecord> processStoppingActivitiesLocked(ActivityRecord idleActivity,
+ boolean remove, boolean processPausingActivities) {
ArrayList<ActivityRecord> stops = null;
final boolean nowVisible = allResumedActivitiesVisible();
}
}
if ((!waitingVisible || mService.isSleepingOrShuttingDownLocked()) && remove) {
+ if (!processPausingActivities && s.state == PAUSING) {
+ // Defer processing pausing activities in this iteration and reschedule
+ // a delayed idle to reprocess it again
+ removeTimeoutsForActivityLocked(idleActivity);
+ scheduleIdleTimeoutLocked(idleActivity);
+ continue;
+ }
+
if (DEBUG_STATES) Slog.v(TAG, "Ready to stop: " + s);
if (stops == null) {
stops = new ArrayList<>();
super(looper);
}
- void activityIdleInternal(ActivityRecord r) {
+ void activityIdleInternal(ActivityRecord r, boolean processPausingActivities) {
synchronized (mService) {
- activityIdleInternalLocked(r != null ? r.appToken : null, true, null);
+ activityIdleInternalLocked(r != null ? r.appToken : null, true /* fromTimeout */,
+ processPausingActivities, null);
}
}
}
// We don't at this point know if the activity is fullscreen,
// so we need to be conservative and assume it isn't.
- activityIdleInternal((ActivityRecord)msg.obj);
+ activityIdleInternal((ActivityRecord) msg.obj,
+ true /* processPausingActivities */);
} break;
case IDLE_NOW_MSG: {
if (DEBUG_IDLE) Slog.d(TAG_IDLE, "handleMessage: IDLE_NOW_MSG: r=" + msg.obj);
- activityIdleInternal((ActivityRecord)msg.obj);
+ activityIdleInternal((ActivityRecord) msg.obj,
+ false /* processPausingActivities */);
} break;
case RESUME_TOP_ACTIVITY_MSG: {
synchronized (mService) {