Add timeout to abort PiP if needed
Make sure we abort enter-PiP via abort
upon a timeout after moveActivityToPinnedRootTask()
We remove any timeout callbacks set up if we detect
that activity windowing mode is being changed directly
in WindowOrganizerController (applies to PiP1 only) and
if the windowing mode is requested to be the same as its task.
Bug: 334038395
Test: N/A until a proof-of-concept repro path is produced
Change-Id: I334bb336e11c774a4e22d646e16ba99cace42a7d
diff --git a/services/core/java/com/android/server/wm/RootWindowContainer.java b/services/core/java/com/android/server/wm/RootWindowContainer.java
index 3eea6ac..b7a85a1 100644
--- a/services/core/java/com/android/server/wm/RootWindowContainer.java
+++ b/services/core/java/com/android/server/wm/RootWindowContainer.java
@@ -175,6 +175,7 @@
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;
@@ -298,6 +299,9 @@
};
+ // 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> {
@@ -2247,8 +2251,67 @@
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.
*
@@ -2867,6 +2930,14 @@
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 d79d113..a386355 100644
--- a/services/core/java/com/android/server/wm/Task.java
+++ b/services/core/java/com/android/server/wm/Task.java
@@ -4851,11 +4851,13 @@
/**
* 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);
@@ -4877,6 +4879,7 @@
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 e1ad1be..65641ba 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 @@
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;
@@ -848,6 +849,17 @@
}
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); });
}