From 150072f401ef3affa558cdca528b4321fba8d6ba Mon Sep 17 00:00:00 2001 From: Ben Lin Date: Mon, 3 May 2021 17:21:19 -0700 Subject: [PATCH] PiP: Update menu state AFTER animation is done for show/hide/resize. When hiding the menu, there is a brief moment where the menu hide animation is running, but the menu is already set as hidden in mMenuState. In that case, other menu operations, such as resizing it, would no-op, since it assumes the menu is not visible to the user anymore. We will update the menu at end of the animation so that before the animation finishes, we will respect that the menu is still in its previous state. This will break apart the callback into two parts - onMenuStateChanging, and onMenuStateChangeFinish. Additionally, PipAnimationController used to only control animating the PiP leash, but not the menu. This CL also adds a handler callback to allow menu controller to apply the transaction. Bug: 185513811 Bug: 186424689 Test: Tap once (show menu), pinch resize - menu resizes & fade along with the resize Test: Resize to smallest size, tap once (so that it grows bigger), and then tap outside region - see that the menu no longer lags behind on fading out. Change-Id: I4432961aff074f4569d5d6d61b0e917511e2cbad --- .../wm/shell/pip/PipAnimationController.java | 37 ++++++++++++++++- .../wm/shell/pip/PipTaskOrganizer.java | 20 +++++++++- .../android/wm/shell/pip/PipTransition.java | 10 ++--- .../pip/phone/PhonePipMenuController.java | 25 +++++++++--- .../wm/shell/pip/phone/PipMenuView.java | 40 +++++++++++-------- .../pip/phone/PipResizeGestureHandler.java | 4 +- .../wm/shell/pip/phone/PipTouchHandler.java | 16 ++++++-- 7 files changed, 117 insertions(+), 35 deletions(-) diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipAnimationController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipAnimationController.java index ca05ff47d5071..17e0d1bc90185 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipAnimationController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipAnimationController.java @@ -207,6 +207,24 @@ public class PipAnimationController { public void onPipAnimationCancel(TaskInfo taskInfo, PipTransitionAnimator animator) {} } + /** + * A handler class that could register itself to apply the transaction instead of the + * animation controller doing it. For example, the menu controller can be one such handler. + */ + public static class PipTransactionHandler { + + /** + * Called when the animation controller is about to apply a transaction. Allow a registered + * handler to apply the transaction instead. + * + * @return true if handled by the handler, false otherwise. + */ + public boolean handlePipTransaction(SurfaceControl leash, SurfaceControl.Transaction tx, + Rect destinationBounds) { + return false; + } + } + /** * Animator for PiP transition animation which supports both alpha and bounds animation. * @param Type of property to animate, either alpha (float) or bounds (Rect) @@ -225,6 +243,7 @@ public class PipAnimationController { private T mEndValue; private float mStartingAngle; private PipAnimationCallback mPipAnimationCallback; + private PipTransactionHandler mPipTransactionHandler; private PipSurfaceTransactionHelper.SurfaceControlTransactionFactory mSurfaceControlTransactionFactory; private PipSurfaceTransactionHelper mSurfaceTransactionHelper; @@ -293,6 +312,20 @@ public class PipAnimationController { mPipAnimationCallback = callback; return this; } + + PipTransitionAnimator setPipTransactionHandler(PipTransactionHandler handler) { + mPipTransactionHandler = handler; + return this; + } + + boolean handlePipTransaction(SurfaceControl leash, SurfaceControl.Transaction tx, + Rect destinationBounds) { + if (mPipTransactionHandler != null) { + return mPipTransactionHandler.handlePipTransaction(leash, tx, destinationBounds); + } + return false; + } + @VisibleForTesting @TransitionDirection public int getTransitionDirection() { return mTransitionDirection; @@ -499,7 +532,9 @@ public class PipAnimationController { getSurfaceTransactionHelper().scaleAndCrop(tx, leash, initialSourceValue, bounds, insets); } - tx.apply(); + if (!handlePipTransaction(leash, tx, bounds)) { + tx.apply(); + } } private void applyRotation(SurfaceControl.Transaction tx, SurfaceControl leash, diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java index 4ce6c9e35e9ee..0633330dc8303 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java @@ -200,6 +200,19 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener, } }; + private final PipAnimationController.PipTransactionHandler mPipTransactionHandler = + new PipAnimationController.PipTransactionHandler() { + @Override + public boolean handlePipTransaction(SurfaceControl leash, + SurfaceControl.Transaction tx, Rect destinationBounds) { + if (mPipMenuController.isMenuVisible()) { + mPipMenuController.movePipMenu(leash, tx, destinationBounds); + return true; + } + return false; + } + }; + private ActivityManager.RunningTaskInfo mTaskInfo; // To handle the edge case that onTaskInfoChanged callback is received during the entering // PiP transition, where we do not want to intercept the transition but still want to apply the @@ -433,8 +446,10 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener, // removePipImmediately is expected when the following animation finishes. ValueAnimator animator = mPipAnimationController - .getAnimator(mTaskInfo, mLeash, mPipBoundsState.getBounds(), 1f, 0f) + .getAnimator(mTaskInfo, mLeash, mPipBoundsState.getBounds(), + 1f /* alphaStart */, 0f /* alphaEnd */) .setTransitionDirection(TRANSITION_DIRECTION_REMOVE_STACK) + .setPipTransactionHandler(mPipTransactionHandler) .setPipAnimationCallback(mPipAnimationCallback); animator.setDuration(mExitAnimationDuration); animator.setInterpolator(Interpolators.ALPHA_OUT); @@ -573,6 +588,7 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener, .getAnimator(mTaskInfo, mLeash, destinationBounds, 0f, 1f) .setTransitionDirection(TRANSITION_DIRECTION_TO_PIP) .setPipAnimationCallback(mPipAnimationCallback) + .setPipTransactionHandler(mPipTransactionHandler) .setDuration(durationMs) .start(); // mState is set right after the animation is kicked off to block any resize @@ -749,6 +765,7 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener, mPipAnimationController .getAnimator(mTaskInfo, mLeash, mPipBoundsState.getBounds(), alphaStart, alphaEnd) .setTransitionDirection(TRANSITION_DIRECTION_SAME) + .setPipTransactionHandler(mPipTransactionHandler) .setDuration(show ? mEnterAnimationDuration : mExitAnimationDuration) .start(); mHasFadeOut = !show; @@ -1226,6 +1243,7 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener, sourceHintRect, direction, startingAngle, rotationDelta); animator.setTransitionDirection(direction) .setPipAnimationCallback(mPipAnimationCallback) + .setPipTransactionHandler(mPipTransactionHandler) .setDuration(durationMs) .start(); if (rotationDelta != Surface.ROTATION_0 && direction == TRANSITION_DIRECTION_TO_PIP) { diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java index 9b6909b2bd512..4759550c35c01 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java @@ -111,14 +111,14 @@ public class PipTransition extends PipTransitionController { final Rect sourceHintRect = PipBoundsAlgorithm.getValidSourceHintRect( taskInfo.pictureInPictureParams, currentBounds); - animator = mPipAnimationController.getAnimator(taskInfo, leash, - currentBounds, currentBounds, destinationBounds, sourceHintRect, - TRANSITION_DIRECTION_TO_PIP, 0 /* startingAngle */, Surface.ROTATION_0); + animator = mPipAnimationController.getAnimator(taskInfo, leash, currentBounds, + currentBounds, destinationBounds, sourceHintRect, TRANSITION_DIRECTION_TO_PIP, + 0 /* startingAngle */, Surface.ROTATION_0); } else if (mOneShotAnimationType == ANIM_TYPE_ALPHA) { t.setAlpha(leash, 0f); t.apply(); - animator = mPipAnimationController.getAnimator(taskInfo, leash, - destinationBounds, 0f, 1f); + animator = mPipAnimationController.getAnimator(taskInfo, leash, destinationBounds, + 0f, 1f); mOneShotAnimationType = ANIM_TYPE_BOUNDS; } else { throw new RuntimeException("Unrecognized animation type: " diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PhonePipMenuController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PhonePipMenuController.java index 9cf0b721cc487..052653e0cc818 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PhonePipMenuController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PhonePipMenuController.java @@ -70,12 +70,19 @@ public class PhonePipMenuController implements PipMenuController { */ public interface Listener { /** - * Called when the PIP menu visibility changes. + * Called when the PIP menu visibility change has started. * - * @param menuState the current state of the menu + * @param menuState the new, about-to-change state of the menu * @param resize whether or not to resize the PiP with the state change */ - void onPipMenuStateChanged(int menuState, boolean resize, Runnable callback); + void onPipMenuStateChangeStart(int menuState, boolean resize, Runnable callback); + + /** + * Called when the PIP menu state has finished changing/animating. + * + * @param menuState the new state of the menu. + */ + void onPipMenuStateChangeFinish(int menuState); /** * Called when the PIP requested to be expanded. @@ -485,15 +492,15 @@ public class PhonePipMenuController implements PipMenuController { /** * Handles changes in menu visibility. */ - void onMenuStateChanged(int menuState, boolean resize, Runnable callback) { + void onMenuStateChangeStart(int menuState, boolean resize, Runnable callback) { if (DEBUG) { - Log.d(TAG, "onMenuStateChanged() mMenuState=" + mMenuState + Log.d(TAG, "onMenuStateChangeStart() mMenuState=" + mMenuState + " menuState=" + menuState + " resize=" + resize + " callers=\n" + Debug.getCallers(5, " ")); } if (menuState != mMenuState) { - mListeners.forEach(l -> l.onPipMenuStateChanged(menuState, resize, callback)); + mListeners.forEach(l -> l.onPipMenuStateChangeStart(menuState, resize, callback)); if (menuState == MENU_STATE_FULL) { // Once visible, start listening for media action changes. This call will trigger // the menu actions to be updated again. @@ -511,6 +518,12 @@ public class PhonePipMenuController implements PipMenuController { Log.e(TAG, "Unable to update focus as menu appears/disappears", e); } } + } + + void onMenuStateChangeFinish(int menuState) { + if (menuState != mMenuState) { + mListeners.forEach(l -> l.onPipMenuStateChangeFinish(menuState)); + } mMenuState = menuState; } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipMenuView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipMenuView.java index 2b45346cfa5c0..bed6b420960bd 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipMenuView.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipMenuView.java @@ -282,17 +282,18 @@ public class PipMenuView extends FrameLayout { } mMenuContainerAnimator.setInterpolator(Interpolators.ALPHA_IN); mMenuContainerAnimator.setDuration(ANIMATION_HIDE_DURATION_MS); - if (allowMenuTimeout) { - mMenuContainerAnimator.addListener(new AnimatorListenerAdapter() { - @Override - public void onAnimationEnd(Animator animation) { + mMenuContainerAnimator.addListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animation) { + notifyMenuStateChangeFinish(menuState); + if (allowMenuTimeout) { repostDelayedHide(INITIAL_DISMISS_DELAY); } - }); - } + } + }); if (withDelay) { // starts the menu container animation after window expansion is completed - notifyMenuStateChange(menuState, resizeMenuOnShow, () -> { + notifyMenuStateChangeStart(menuState, resizeMenuOnShow, () -> { if (mMenuContainerAnimator == null) { return; } @@ -301,11 +302,11 @@ public class PipMenuView extends FrameLayout { mMenuContainerAnimator.start(); }); } else { - notifyMenuStateChange(menuState, resizeMenuOnShow, null); + notifyMenuStateChangeStart(menuState, resizeMenuOnShow, null); setVisibility(VISIBLE); mMenuContainerAnimator.start(); } - updateActionViews(stackBounds); + updateActionViews(menuState, stackBounds); } else { // If we are already visible, then just start the delayed dismiss and unregister any // existing input consumers from the previous drag @@ -358,7 +359,7 @@ public class PipMenuView extends FrameLayout { if (mMenuState != MENU_STATE_NONE) { cancelDelayedHide(); if (notifyMenuVisibility) { - notifyMenuStateChange(MENU_STATE_NONE, resize, null); + notifyMenuStateChangeStart(MENU_STATE_NONE, resize, null); } mMenuContainerAnimator = new AnimatorSet(); ObjectAnimator menuAnim = ObjectAnimator.ofFloat(mMenuContainer, View.ALPHA, @@ -377,6 +378,9 @@ public class PipMenuView extends FrameLayout { @Override public void onAnimationEnd(Animator animation) { setVisibility(GONE); + if (notifyMenuVisibility) { + notifyMenuStateChangeFinish(MENU_STATE_NONE); + } if (animationFinishedRunnable != null) { animationFinishedRunnable.run(); } @@ -405,11 +409,11 @@ public class PipMenuView extends FrameLayout { mActions.clear(); mActions.addAll(actions); if (mMenuState == MENU_STATE_FULL) { - updateActionViews(stackBounds); + updateActionViews(mMenuState, stackBounds); } } - private void updateActionViews(Rect stackBounds) { + private void updateActionViews(int menuState, Rect stackBounds) { ViewGroup expandContainer = findViewById(R.id.expand_container); ViewGroup actionsContainer = findViewById(R.id.actions_container); actionsContainer.setOnTouchListener((v, ev) -> { @@ -418,13 +422,13 @@ public class PipMenuView extends FrameLayout { }); // Update the expand button only if it should show with the menu - expandContainer.setVisibility(mMenuState == MENU_STATE_FULL + expandContainer.setVisibility(menuState == MENU_STATE_FULL ? View.VISIBLE : View.INVISIBLE); FrameLayout.LayoutParams expandedLp = (FrameLayout.LayoutParams) expandContainer.getLayoutParams(); - if (mActions.isEmpty() || mMenuState == MENU_STATE_CLOSE || mMenuState == MENU_STATE_NONE) { + if (mActions.isEmpty() || menuState == MENU_STATE_CLOSE || menuState == MENU_STATE_NONE) { actionsContainer.setVisibility(View.INVISIBLE); // Update the expand container margin to adjust the center of the expand button to @@ -494,9 +498,13 @@ public class PipMenuView extends FrameLayout { expandContainer.requestLayout(); } - private void notifyMenuStateChange(int menuState, boolean resize, Runnable callback) { + private void notifyMenuStateChangeStart(int menuState, boolean resize, Runnable callback) { + mController.onMenuStateChangeStart(menuState, resize, callback); + } + + private void notifyMenuStateChangeFinish(int menuState) { mMenuState = menuState; - mController.onMenuStateChanged(menuState, resize, callback); + mController.onMenuStateChangeFinish(menuState); } private void expandPip() { diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipResizeGestureHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipResizeGestureHandler.java index 8726ee76d29ab..0878f54b7d274 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipResizeGestureHandler.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipResizeGestureHandler.java @@ -419,13 +419,13 @@ public class PipResizeGestureHandler { // Reset the down to begin resizing from this point mDownPoint.set(mLastPoint); mDownSecondPoint.set(mLastSecondPoint); - } - if (mThresholdCrossed) { if (mPhonePipMenuController.isMenuVisible()) { mPhonePipMenuController.hideMenu(); } + } + if (mThresholdCrossed) { mAngle = mPinchResizingAlgorithm.calculateBoundsAndAngle(mDownPoint, mDownSecondPoint, mLastPoint, mLastSecondPoint, mMinSize, mMaxSize, mDownBounds, mLastResizeBounds); diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipTouchHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipTouchHandler.java index 0a0798ef24c12..50f170a7ca861 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipTouchHandler.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipTouchHandler.java @@ -129,8 +129,13 @@ public class PipTouchHandler { */ private class PipMenuListener implements PhonePipMenuController.Listener { @Override - public void onPipMenuStateChanged(int menuState, boolean resize, Runnable callback) { - setMenuState(menuState, resize, callback); + public void onPipMenuStateChangeStart(int menuState, boolean resize, Runnable callback) { + PipTouchHandler.this.onPipMenuStateChangeStart(menuState, resize, callback); + } + + @Override + public void onPipMenuStateChangeFinish(int menuState) { + setMenuState(menuState); } @Override @@ -646,9 +651,9 @@ public class PipTouchHandler { } /** - * Sets the menu visibility. + * Called when the PiP menu state is in the process of animating/changing from one to another. */ - private void setMenuState(int menuState, boolean resize, Runnable callback) { + private void onPipMenuStateChangeStart(int menuState, boolean resize, Runnable callback) { if (mMenuState == menuState && !resize) { return; } @@ -686,6 +691,9 @@ public class PipTouchHandler { mSavedSnapFraction = -1f; } } + } + + private void setMenuState(int menuState) { mMenuState = menuState; updateMovementBounds(); // If pip menu has dismissed, we should register the A11y ActionReplacingConnection for pip