diff options
3 files changed, 88 insertions, 2 deletions
diff --git a/services/core/java/com/android/server/wm/RootWindowContainer.java b/services/core/java/com/android/server/wm/RootWindowContainer.java index 422cc696224b..622a80920526 100644 --- a/services/core/java/com/android/server/wm/RootWindowContainer.java +++ b/services/core/java/com/android/server/wm/RootWindowContainer.java @@ -174,6 +174,7 @@ class RootWindowContainer extends WindowContainer<DisplayContent> private static final int SET_SCREEN_BRIGHTNESS_OVERRIDE = 1; private static final int SET_USER_ACTIVITY_TIMEOUT = 2; private static final int MSG_SEND_SLEEP_TRANSITION = 3; + private static final int PINNED_TASK_ABORT_TIMEOUT = 1000; static final String TAG_TASKS = TAG + POSTFIX_TASKS; static final String TAG_STATES = TAG + POSTFIX_STATES; @@ -295,6 +296,9 @@ class RootWindowContainer extends WindowContainer<DisplayContent> }; + // TODO: b/335866033 Remove the abort PiP on timeout once PiP2 flag is on. + @Nullable private Runnable mMaybeAbortPipEnterRunnable = null; + private final FindTaskResult mTmpFindTaskResult = new FindTaskResult(); static class FindTaskResult implements Predicate<Task> { @@ -2272,8 +2276,67 @@ class RootWindowContainer extends WindowContainer<DisplayContent> resumeFocusedTasksTopActivities(); notifyActivityPipModeChanged(r.getTask(), r); + + if (!isPip2ExperimentEnabled()) { + // TODO: b/335866033 Remove the abort PiP on timeout once PiP2 flag is on. + // Set up a timeout callback to potentially abort PiP enter if in an inconsistent state. + scheduleTimeoutAbortPipEnter(rootTask); + } + } + + private void scheduleTimeoutAbortPipEnter(Task rootTask) { + if (mMaybeAbortPipEnterRunnable != null) { + // If there is an abort enter PiP check pending already remove it and abort + // immediately since we are trying to enter PiP in an inconsistent state + mHandler.removeCallbacks(mMaybeAbortPipEnterRunnable); + mMaybeAbortPipEnterRunnable.run(); + } + // Snapshot a throwable early on to display the callstack upon abort later on timeout. + final Throwable enterPipThrowable = new Throwable(); + // Set up a timeout to potentially roll back the task change to PINNED mode + // by aborting PiP. + mMaybeAbortPipEnterRunnable = new Runnable() { + @Override + public void run() { + synchronized (mService.mGlobalLock) { + if (mTransitionController.inTransition()) { + // If this task is a part an active transition aborting PiP might break + // it; so run the timeout callback directly once idle. + + final Runnable expectedMaybeAbortAtTimeout = mMaybeAbortPipEnterRunnable; + mTransitionController.mStateValidators.add(() -> { + // If a second PiP transition comes in, it runs the abort runnable for + // the first transition pre-emptively, so we need to avoid calling + // the same runnable twice when validating states. + if (expectedMaybeAbortAtTimeout != mMaybeAbortPipEnterRunnable) return; + mMaybeAbortPipEnterRunnable = null; + run(); + }); + return; + } else { + mMaybeAbortPipEnterRunnable = null; + } + mService.deferWindowLayout(); + final ActivityRecord top = rootTask.getTopMostActivity(); + final ActivityManager.RunningTaskInfo beforeTaskInfo = + rootTask.getTaskInfo(); + if (top != null && !top.inPinnedWindowingMode() + && rootTask.abortPipEnter(top)) { + Slog.wtf(TAG, "Enter PiP was aborted via a scheduled timeout" + + "task_state_before=" + beforeTaskInfo + + "task_state_after=" + rootTask.getTaskInfo(), + enterPipThrowable); + } + mService.continueWindowLayout(); + } + } + }; + mHandler.postDelayed(mMaybeAbortPipEnterRunnable, PINNED_TASK_ABORT_TIMEOUT); + Slog.d(TAG, "a delayed check for potentially aborting PiP if " + + "in a wrong state is scheduled."); } + /** * Notifies when an activity enters or leaves PIP mode. * @@ -2892,6 +2955,14 @@ class RootWindowContainer extends WindowContainer<DisplayContent> mService.mH.post(mDestroyAllActivitiesRunnable); } + void removeAllMaybeAbortPipEnterRunnable() { + if (mMaybeAbortPipEnterRunnable == null) { + return; + } + mHandler.removeCallbacks(mMaybeAbortPipEnterRunnable); + mMaybeAbortPipEnterRunnable = null; + } + // Tries to put all activity tasks to sleep. Returns true if all tasks were // successfully put to sleep. boolean putTasksToSleep(boolean allowDelay, boolean shuttingDown) { diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java index 9d3ffa95f744..f23a440eb0ed 100644 --- a/services/core/java/com/android/server/wm/Task.java +++ b/services/core/java/com/android/server/wm/Task.java @@ -4852,11 +4852,13 @@ class Task extends TaskFragment { /** * Abort an incomplete pip-entry. If left in this state, it will cover everything but remain * paused. If this is needed, there is a bug -- this should only be used for recovery. + * + * @return true if there is an inconsistency in the task and activity state. */ - void abortPipEnter(ActivityRecord top) { + boolean abortPipEnter(ActivityRecord top) { // an incomplete state has the task PINNED but the activity not. if (!inPinnedWindowingMode() || top.inPinnedWindowingMode() || !canMoveTaskToBack(this)) { - return; + return false; } final Transition transition = new Transition(TRANSIT_TO_BACK, 0 /* flags */, mTransitionController, mWmService.mSyncEngine); @@ -4878,6 +4880,7 @@ class Task extends TaskFragment { top.setWindowingMode(WINDOWING_MODE_UNDEFINED); top.mWaitForEnteringPinnedMode = false; } + return true; } void resumeNextFocusAfterReparent() { diff --git a/services/core/java/com/android/server/wm/WindowOrganizerController.java b/services/core/java/com/android/server/wm/WindowOrganizerController.java index e3f06c27da77..1573d09364ea 100644 --- a/services/core/java/com/android/server/wm/WindowOrganizerController.java +++ b/services/core/java/com/android/server/wm/WindowOrganizerController.java @@ -19,6 +19,7 @@ package com.android.server.wm; import static android.Manifest.permission.START_TASKS_FROM_RECENTS; import static android.app.ActivityManager.isStartResultSuccessful; import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED; +import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED; import static android.app.WindowConfiguration.WINDOW_CONFIG_BOUNDS; import static android.view.Display.DEFAULT_DISPLAY; import static android.window.TaskFragmentOperation.OP_TYPE_CLEAR_ADJACENT_TASK_FRAGMENTS; @@ -857,6 +858,17 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub } final int childWindowingMode = c.getActivityWindowingMode(); + if (!ActivityTaskManagerService.isPip2ExperimentEnabled() + && tr.getWindowingMode() == WINDOWING_MODE_PINNED + && (childWindowingMode == WINDOWING_MODE_PINNED + || childWindowingMode == WINDOWING_MODE_UNDEFINED)) { + // If setActivityWindowingMode requested to match its pinned task's windowing mode, + // remove any inconsistency checking timeout callbacks for PiP. + Slog.d(TAG, "Task and activity windowing modes match, so remove any timeout " + + "abort PiP callbacks scheduled if needed; task_win_mode=" + + tr.getWindowingMode() + ", activity_win_mode=" + childWindowingMode); + mService.mRootWindowContainer.removeAllMaybeAbortPipEnterRunnable(); + } if (childWindowingMode > -1) { tr.forAllActivities(a -> { a.setWindowingMode(childWindowingMode); }); } |