diff options
| author | 2023-02-15 14:03:25 -0800 | |
|---|---|---|
| committer | 2023-02-17 21:17:03 +0000 | |
| commit | 66a2adc46af2b2a9d92fe0161ec083eb9a5d159c (patch) | |
| tree | ad187482e0dabc4cfea8fd6c0c8904daaa93e879 | |
| parent | 74efadf7387845552752eeade855b47bc283b6b3 (diff) | |
Add a fallback check to prevent half-way pip anims
This situation leaves the device in an unusable state, so
add a hard-check for this to recover if it happens unexpectedly.
This code should never run; however, just to be safe, its
preferable to fallback and log an error instead of make the device
unusable if an edge-case does get hit.
Bug: 267897440
Test: purposefully disable some pip handling in shell and verify
that the fallback occurs
Change-Id: I619b49d5c4dedd92fac5ea3b8e133c8c50a72d9b
3 files changed, 72 insertions, 0 deletions
diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java index 10e4929ded6a..2d54111028a6 100644 --- a/services/core/java/com/android/server/wm/Task.java +++ b/services/core/java/com/android/server/wm/Task.java @@ -4588,6 +4588,36 @@ 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. + */ + void abortPipEnter(ActivityRecord top) { + // an incomplete state has the task PINNED but the activity not. + if (!inPinnedWindowingMode() || top.inPinnedWindowingMode() || !canMoveTaskToBack(this)) { + return; + } + final Transition transition = new Transition(TRANSIT_TO_BACK, 0 /* flags */, + mTransitionController, mWmService.mSyncEngine); + mTransitionController.moveToCollecting(transition); + mTransitionController.requestStartTransition(transition, this, null /* remoteTransition */, + null /* displayChange */); + if (top.getLastParentBeforePip() != null) { + final Task lastParentBeforePip = top.getLastParentBeforePip(); + if (lastParentBeforePip.isAttached()) { + top.reparent(lastParentBeforePip, lastParentBeforePip.getChildCount() /* top */, + "movePinnedActivityToOriginalTask"); + } + } + if (isAttached()) { + setWindowingMode(WINDOWING_MODE_UNDEFINED); + moveTaskToBackInner(this); + } + if (top.isAttached()) { + top.setWindowingMode(WINDOWING_MODE_UNDEFINED); + } + } + void resumeNextFocusAfterReparent() { adjustFocusToNextFocusableTask("reparent", true /* allowFocusSelf */, true /* moveDisplayToTop */); diff --git a/services/core/java/com/android/server/wm/Transition.java b/services/core/java/com/android/server/wm/Transition.java index 4e41da0e5d0b..a68b3cb2a2ea 100644 --- a/services/core/java/com/android/server/wm/Transition.java +++ b/services/core/java/com/android/server/wm/Transition.java @@ -878,6 +878,25 @@ class Transition implements BLASTSyncEngine.TransactionReadyListener { " Commit wallpaper becoming invisible: %s", wt); wt.commitVisibility(false /* visible */); } + continue; + } + final Task tr = participant.asTask(); + if (tr != null && tr.isVisibleRequested() && tr.inPinnedWindowingMode()) { + final ActivityRecord top = tr.getTopNonFinishingActivity(); + if (top != null && !top.inPinnedWindowingMode()) { + mController.mStateValidators.add(() -> { + if (!tr.isAttached() || !tr.isVisibleRequested() + || !tr.inPinnedWindowingMode()) return; + final ActivityRecord currTop = tr.getTopNonFinishingActivity(); + if (currTop.inPinnedWindowingMode()) return; + Slog.e(TAG, "Enter-PIP was started but not completed, this is a Shell/SysUI" + + " bug. This state breaks gesture-nav, so attempting clean-up."); + // We don't know the destination bounds, so we can't actually finish the + // operation. So, to prevent the half-pipped task from covering everything, + // abort the action (which moves the task to back). + tr.abortPipEnter(currTop); + }); + } } } diff --git a/services/core/java/com/android/server/wm/TransitionController.java b/services/core/java/com/android/server/wm/TransitionController.java index ed7e9ed78446..397abdb6e501 100644 --- a/services/core/java/com/android/server/wm/TransitionController.java +++ b/services/core/java/com/android/server/wm/TransitionController.java @@ -97,6 +97,12 @@ class TransitionController { new ArrayList<>(); /** + * List of runnables to run when there are no ongoing transitions. Use this for state-validation + * checks (eg. to recover from incomplete states). Eventually this should be removed. + */ + final ArrayList<Runnable> mStateValidators = new ArrayList<>(); + + /** * Currently playing transitions (in the order they were started). When finished, records are * removed from this list. */ @@ -659,6 +665,23 @@ class TransitionController { updateRunningRemoteAnimation(record, false /* isPlaying */); record.finishTransition(); mRunningLock.doNotifyLocked(); + // Run state-validation checks when no transitions are active anymore. + if (!inTransition()) { + validateStates(); + } + } + + private void validateStates() { + for (int i = 0; i < mStateValidators.size(); ++i) { + mStateValidators.get(i).run(); + if (inTransition()) { + // the validator may have started a new transition, so wait for that before + // checking the rest. + mStateValidators.subList(0, i + 1).clear(); + return; + } + } + mStateValidators.clear(); } void moveToPlaying(Transition transition) { |