Synchronize PIP menu fade out with task dismiss

This CL addreses two issues:
1. PipMenuView.dismissPip was previously fading out the PIP
menu, waiting for that to complete, then notifying the task
organizer to remove and fade out the window, and then it would
fade the menu out again even though it was already hidden.
This resulted in a quick flash because the menu and the window
are not faded out in sync.
2. The menu fade out animation had a fixed duration of 150ms,
meaning that even if it starting fading out at the same time
as the PIP window, it would not finish at the same time because
the PIP exit anim duration is longer.

To fix both of them, the X button now hides the menu and starts
dismissing the task at the same time (once), and the dismiss
animation duration is equal to the task fade out animation
duration.

Previous: http://recall/-/e8RgaQ0dFlwFuh0c7GRpyy/g7XMbvBGMyAOKZMuFlYpW0
With CL: http://recall/-/e8RgaQ0dFlwFuh0c7GRpyy/g7XMbvBGMyAOKZMuFlYpW0

Bug: 182727923
Test: Tap X to dismiss PIP, no flashes seen
Change-Id: If9bdda8d0fd8b819c00ec4a592d1b2441d642d00
This commit is contained in:
jorgegil@google.com
2021-04-29 14:54:46 -07:00
parent a2bbcf9523
commit b2f5a2f985
5 changed files with 71 additions and 30 deletions

View File

@@ -18,6 +18,8 @@ package com.android.wm.shell.pip.phone;
import static android.view.WindowManager.SHELL_ROOT_LAYER_PIP;
import static com.android.wm.shell.pip.phone.PipMenuView.ANIM_TYPE_HIDE;
import android.annotation.Nullable;
import android.app.RemoteAction;
import android.content.Context;
@@ -397,26 +399,26 @@ public class PhonePipMenuController implements PipMenuController {
* Hides the menu view.
*/
public void hideMenu() {
hideMenu(true /* animate */, true /* resize */);
hideMenu(ANIM_TYPE_HIDE, true /* resize */);
}
/**
* Hides the menu view.
*
* @param animate whether to animate the menu fadeout
* @param animationType the animation type to use upon hiding the menu
* @param resize whether or not to resize the PiP with the state change
*/
public void hideMenu(boolean animate, boolean resize) {
public void hideMenu(@PipMenuView.AnimationType int animationType, boolean resize) {
final boolean isMenuVisible = isMenuVisible();
if (DEBUG) {
Log.d(TAG, "hideMenu() state=" + mMenuState
+ " isMenuVisible=" + isMenuVisible
+ " animate=" + animate
+ " animationType=" + animationType
+ " resize=" + resize
+ " callers=\n" + Debug.getCallers(5, " "));
}
if (isMenuVisible) {
mPipMenuView.hideMenu(animate, resize);
mPipMenuView.hideMenu(resize, animationType);
}
}

View File

@@ -32,6 +32,7 @@ import android.animation.AnimatorListenerAdapter;
import android.animation.AnimatorSet;
import android.animation.ObjectAnimator;
import android.animation.ValueAnimator;
import android.annotation.IntDef;
import android.app.PendingIntent.CanceledException;
import android.app.RemoteAction;
import android.content.ComponentName;
@@ -64,6 +65,8 @@ import com.android.wm.shell.animation.Interpolators;
import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.pip.PipUtils;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.ArrayList;
import java.util.List;
@@ -74,9 +77,26 @@ public class PipMenuView extends FrameLayout {
private static final String TAG = "PipMenuView";
private static final int ANIMATION_NONE_DURATION_MS = 0;
private static final int ANIMATION_HIDE_DURATION_MS = 125;
/** No animation performed during menu hide. */
public static final int ANIM_TYPE_NONE = 0;
/** Fade out the menu until it's invisible. Used when the PIP window remains visible. */
public static final int ANIM_TYPE_HIDE = 1;
/** Fade out the menu in sync with the PIP window. */
public static final int ANIM_TYPE_DISMISS = 2;
@IntDef(prefix = { "ANIM_TYPE_" }, value = {
ANIM_TYPE_NONE,
ANIM_TYPE_HIDE,
ANIM_TYPE_DISMISS
})
@Retention(RetentionPolicy.SOURCE)
public @interface AnimationType {}
private static final int INITIAL_DISMISS_DELAY = 3500;
private static final int POST_INTERACTION_DISMISS_DELAY = 2000;
private static final long MENU_FADE_DURATION = 125;
private static final long MENU_SHOW_ON_EXPAND_START_DELAY = 30;
private static final float MENU_BACKGROUND_ALPHA = 0.3f;
@@ -87,6 +107,7 @@ public class PipMenuView extends FrameLayout {
private int mMenuState;
private boolean mAllowMenuTimeout = true;
private boolean mAllowTouches = true;
private int mDismissFadeOutDurationMs;
private final List<RemoteAction> mActions = new ArrayList<>();
@@ -167,6 +188,8 @@ public class PipMenuView extends FrameLayout {
mPipMenuIconsAlgorithm = new PipMenuIconsAlgorithm(mContext);
mPipMenuIconsAlgorithm.bindViews((ViewGroup) mViewRoot, (ViewGroup) mTopEndContainer,
mResizeHandle, mSettingsButton, mDismissButton);
mDismissFadeOutDurationMs = context.getResources()
.getInteger(R.integer.config_pipExitAnimationDuration);
initAccessibility();
}
@@ -258,7 +281,7 @@ public class PipMenuView extends FrameLayout {
mMenuContainerAnimator.playTogether(dismissAnim, resizeAnim);
}
mMenuContainerAnimator.setInterpolator(Interpolators.ALPHA_IN);
mMenuContainerAnimator.setDuration(MENU_FADE_DURATION);
mMenuContainerAnimator.setDuration(ANIMATION_HIDE_DURATION_MS);
if (allowMenuTimeout) {
mMenuContainerAnimator.addListener(new AnimatorListenerAdapter() {
@Override
@@ -320,17 +343,18 @@ public class PipMenuView extends FrameLayout {
hideMenu(null);
}
void hideMenu(boolean animate, boolean resize) {
hideMenu(null, true /* notifyMenuVisibility */, animate, resize);
}
void hideMenu(Runnable animationEndCallback) {
hideMenu(animationEndCallback, true /* notifyMenuVisibility */, true /* animate */,
true /* resize */);
hideMenu(animationEndCallback, true /* notifyMenuVisibility */, true /* resize */,
ANIM_TYPE_HIDE);
}
private void hideMenu(final Runnable animationFinishedRunnable, boolean notifyMenuVisibility,
boolean animate, boolean resize) {
void hideMenu(boolean resize, @AnimationType int animationType) {
hideMenu(null /* animationFinishedRunnable */, true /* notifyMenuVisibility */, resize,
animationType);
}
void hideMenu(final Runnable animationFinishedRunnable, boolean notifyMenuVisibility,
boolean resize, @AnimationType int animationType) {
if (mMenuState != MENU_STATE_NONE) {
cancelDelayedHide();
if (notifyMenuVisibility) {
@@ -348,7 +372,7 @@ public class PipMenuView extends FrameLayout {
mResizeHandle.getAlpha(), 0f);
mMenuContainerAnimator.playTogether(menuAnim, settingsAnim, dismissAnim, resizeAnim);
mMenuContainerAnimator.setInterpolator(Interpolators.ALPHA_OUT);
mMenuContainerAnimator.setDuration(animate ? MENU_FADE_DURATION : 0);
mMenuContainerAnimator.setDuration(getFadeOutDuration(animationType));
mMenuContainerAnimator.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
@@ -478,19 +502,17 @@ public class PipMenuView extends FrameLayout {
private void expandPip() {
// Do not notify menu visibility when hiding the menu, the controller will do this when it
// handles the message
hideMenu(mController::onPipExpand, false /* notifyMenuVisibility */, true /* animate */,
true /* resize */);
hideMenu(mController::onPipExpand, false /* notifyMenuVisibility */, true /* resize */,
ANIM_TYPE_HIDE);
}
private void dismissPip() {
// Since tapping on the close-button invokes a double-tap wait callback in PipTouchHandler,
// we want to disable animating the fadeout animation of the buttons in order to call on
// PipTouchHandler#onPipDismiss fast enough.
final boolean animate = mMenuState != MENU_STATE_CLOSE;
// Do not notify menu visibility when hiding the menu, the controller will do this when it
// handles the message
hideMenu(mController::onPipDismiss, false /* notifyMenuVisibility */, animate,
true /* resize */);
if (mMenuState != MENU_STATE_NONE) {
// Do not call hideMenu() directly. Instead, let the menu controller handle it just as
// any other dismissal that will update the touch state and fade out the PIP task
// and the menu view at the same time.
mController.onPipDismiss();
}
}
private void showSettings() {
@@ -514,4 +536,17 @@ public class PipMenuView extends FrameLayout {
mMainExecutor.removeCallbacks(mHideMenuRunnable);
mMainExecutor.executeDelayed(mHideMenuRunnable, recommendedTimeout);
}
private long getFadeOutDuration(@AnimationType int animationType) {
switch (animationType) {
case ANIM_TYPE_NONE:
return ANIMATION_NONE_DURATION_MS;
case ANIM_TYPE_HIDE:
return ANIMATION_HIDE_DURATION_MS;
case ANIM_TYPE_DISMISS:
return mDismissFadeOutDurationMs;
default:
throw new IllegalStateException("Invalid animation type " + animationType);
}
}
}

View File

@@ -22,6 +22,8 @@ import static androidx.dynamicanimation.animation.SpringForce.STIFFNESS_MEDIUM;
import static com.android.wm.shell.pip.PipAnimationController.TRANSITION_DIRECTION_EXPAND_OR_UNEXPAND;
import static com.android.wm.shell.pip.PipBoundsState.STASH_TYPE_NONE;
import static com.android.wm.shell.pip.phone.PipMenuView.ANIM_TYPE_DISMISS;
import static com.android.wm.shell.pip.phone.PipMenuView.ANIM_TYPE_NONE;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -336,7 +338,7 @@ public class PipMotionHelper implements PipAppOpsListener.Callback,
+ " callers=\n" + Debug.getCallers(5, " "));
}
cancelPhysicsAnimation();
mMenuController.hideMenu(false /* animate */, false /* resize */);
mMenuController.hideMenu(ANIM_TYPE_NONE, false /* resize */);
mPipTaskOrganizer.exitPip(skipAnimation ? 0 : LEAVE_PIP_DURATION);
}
@@ -349,7 +351,7 @@ public class PipMotionHelper implements PipAppOpsListener.Callback,
Log.d(TAG, "removePip: callers=\n" + Debug.getCallers(5, " "));
}
cancelPhysicsAnimation();
mMenuController.hideMenu(true /* animate*/, false /* resize */);
mMenuController.hideMenu(ANIM_TYPE_DISMISS, false /* resize */);
mPipTaskOrganizer.removePip();
}

View File

@@ -21,6 +21,7 @@ import static com.android.internal.policy.TaskResizingAlgorithm.CTRL_LEFT;
import static com.android.internal.policy.TaskResizingAlgorithm.CTRL_NONE;
import static com.android.internal.policy.TaskResizingAlgorithm.CTRL_RIGHT;
import static com.android.internal.policy.TaskResizingAlgorithm.CTRL_TOP;
import static com.android.wm.shell.pip.phone.PipMenuView.ANIM_TYPE_NONE;
import android.content.Context;
import android.content.res.Resources;
@@ -471,7 +472,7 @@ public class PipResizeGestureHandler {
}
if (mThresholdCrossed) {
if (mPhonePipMenuController.isMenuVisible()) {
mPhonePipMenuController.hideMenu(false /* animate */,
mPhonePipMenuController.hideMenu(ANIM_TYPE_NONE,
false /* resize */);
}
final Rect currentPipBounds = mPipBoundsState.getBounds();

View File

@@ -25,6 +25,7 @@ import static com.android.wm.shell.pip.PipBoundsState.STASH_TYPE_RIGHT;
import static com.android.wm.shell.pip.phone.PhonePipMenuController.MENU_STATE_CLOSE;
import static com.android.wm.shell.pip.phone.PhonePipMenuController.MENU_STATE_FULL;
import static com.android.wm.shell.pip.phone.PhonePipMenuController.MENU_STATE_NONE;
import static com.android.wm.shell.pip.phone.PipMenuView.ANIM_TYPE_NONE;
import android.annotation.NonNull;
import android.annotation.SuppressLint;
@@ -881,7 +882,7 @@ public class PipTouchHandler {
&& mPipBoundsState.getBounds().height()
< mPipBoundsState.getMaxSize().y;
if (mMenuController.isMenuVisible()) {
mMenuController.hideMenu(false /* animate */, false /* resize */);
mMenuController.hideMenu(ANIM_TYPE_NONE, false /* resize */);
}
if (toExpand) {
mPipResizeGestureHandler.setUserResizeBounds(mPipBoundsState.getBounds());