From 9c675d4f4b682fd1f18a7dcc52559516214f9c5e Mon Sep 17 00:00:00 2001 From: Tracy Zhou Date: Mon, 8 Apr 2019 00:32:40 -0700 Subject: [PATCH] Implement the new PiP animation (fade-in). The new fully gestural navigation changes how user goes home from an app as well as the animation of the app transition. The current PiP animation (bounds) is not compatible with the new model because of the direction of movements. Instead of animating bounds, we can fade the PiP window in after app closing animation finishes. Fixes: 122609330 Test: 1. Open youtube, play a video, tap home to observe the new PiP animation (demo video attached in the ticket b/122609330) 2. With gestural nav on, observe pip transition when swiping up to home. atest ActivityManagerPinnedStackTests atest BoundsAnimationControllerTests atest RecentsAnimationTestTest Change-Id: I28eeb1aa99c4fd569845ca7a42561f6b20796f9b --- .../view/IRecentsAnimationController.aidl | 4 +- .../RecentsAnimationControllerCompat.java | 10 +- .../server/wm/BoundsAnimationController.java | 93 +++++++++++--- .../server/wm/BoundsAnimationTarget.java | 6 +- .../android/server/wm/RecentsAnimation.java | 28 ++++- .../server/wm/RecentsAnimationController.java | 17 ++- .../java/com/android/server/wm/TaskStack.java | 53 +++++++- .../wm/BoundsAnimationControllerTests.java | 117 ++++++++++++------ .../wm/RecentsAnimationControllerTest.java | 2 +- .../server/wm/RecentsAnimationTest.java | 1 + 10 files changed, 254 insertions(+), 77 deletions(-) diff --git a/core/java/android/view/IRecentsAnimationController.aidl b/core/java/android/view/IRecentsAnimationController.aidl index 597b34bf85549..956161acd7623 100644 --- a/core/java/android/view/IRecentsAnimationController.aidl +++ b/core/java/android/view/IRecentsAnimationController.aidl @@ -41,9 +41,11 @@ interface IRecentsAnimationController { * with remote animation targets should be relinquished. If {@param moveHomeToTop} is true, then * the home activity should be moved to the top. Otherwise, the home activity is hidden and the * user is returned to the app. + * @param sendUserLeaveHint If set to true, {@link Activity#onUserLeaving} will be sent to the + * top resumed app, false otherwise. */ @UnsupportedAppUsage - void finish(boolean moveHomeToTop); + void finish(boolean moveHomeToTop, boolean sendUserLeaveHint); /** * Called by the handler to indicate that the recents animation input consumer should be diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/RecentsAnimationControllerCompat.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/RecentsAnimationControllerCompat.java index 1d9105c35ac5e..d2fe5cd9ef64e 100644 --- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/RecentsAnimationControllerCompat.java +++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/RecentsAnimationControllerCompat.java @@ -77,9 +77,15 @@ public class RecentsAnimationControllerCompat { } } - public void finish(boolean toHome) { + /** + * Finish the current recents animation. + * @param toHome Going to home or back to the previous app. + * @param sendUserLeaveHint determines whether userLeaveHint will be set true to the previous + * app. + */ + public void finish(boolean toHome, boolean sendUserLeaveHint) { try { - mAnimationController.finish(toHome); + mAnimationController.finish(toHome, sendUserLeaveHint); } catch (RemoteException e) { Log.e(TAG, "Failed to finish recents animation", e); } diff --git a/services/core/java/com/android/server/wm/BoundsAnimationController.java b/services/core/java/com/android/server/wm/BoundsAnimationController.java index f9980bebca9e5..c1d872f23f0f1 100644 --- a/services/core/java/com/android/server/wm/BoundsAnimationController.java +++ b/services/core/java/com/android/server/wm/BoundsAnimationController.java @@ -73,6 +73,13 @@ public class BoundsAnimationController { /** Schedule a PiP mode changed callback when this animation ends. */ public static final int SCHEDULE_PIP_MODE_CHANGED_ON_END = 2; + public static final int BOUNDS = 0; + public static final int FADE_IN = 1; + + @IntDef({BOUNDS, FADE_IN}) public @interface AnimationType {} + + private static final int FADE_IN_DURATION = 500; + // Only accessed on UI thread. private ArrayMap mRunningAnimations = new ArrayMap<>(); @@ -115,6 +122,7 @@ public class BoundsAnimationController { private boolean mFinishAnimationAfterTransition = false; private final AnimationHandler mAnimationHandler; private Choreographer mChoreographer; + private @AnimationType int mAnimationType; private static final int WAIT_FOR_DRAW_TIMEOUT_MS = 3000; @@ -140,6 +148,7 @@ public class BoundsAnimationController { implements ValueAnimator.AnimatorUpdateListener, ValueAnimator.AnimatorListener { private final BoundsAnimationTarget mTarget; + private final @AnimationType int mAnimationType; private final Rect mFrom = new Rect(); private final Rect mTo = new Rect(); private final Rect mTmpRect = new Rect(); @@ -166,8 +175,8 @@ public class BoundsAnimationController { // Depending on whether we are animating from // a smaller to a larger size - private final int mFrozenTaskWidth; - private final int mFrozenTaskHeight; + private int mFrozenTaskWidth; + private int mFrozenTaskHeight; // Timeout callback to ensure we continue the animation if waiting for resuming or app // windows drawn fails @@ -176,12 +185,13 @@ public class BoundsAnimationController { resume(); }; - BoundsAnimator(BoundsAnimationTarget target, Rect from, Rect to, - @SchedulePipModeChangedState int schedulePipModeChangedState, + BoundsAnimator(BoundsAnimationTarget target, @AnimationType int animationType, Rect from, + Rect to, @SchedulePipModeChangedState int schedulePipModeChangedState, @SchedulePipModeChangedState int prevShedulePipModeChangedState, boolean moveFromFullscreen, boolean moveToFullscreen, Rect frozenTask) { super(); mTarget = target; + mAnimationType = animationType; mFrom.set(from); mTo.set(to); mSchedulePipModeChangedState = schedulePipModeChangedState; @@ -195,12 +205,14 @@ public class BoundsAnimationController { // to their final size immediately so we can use scaling to make the window // larger. Likewise if we are going from bigger to smaller, we want to wait until // the end so we don't have to upscale from the smaller finished size. - if (animatingToLargerSize()) { - mFrozenTaskWidth = mTo.width(); - mFrozenTaskHeight = mTo.height(); - } else { - mFrozenTaskWidth = frozenTask.isEmpty() ? mFrom.width() : frozenTask.width(); - mFrozenTaskHeight = frozenTask.isEmpty() ? mFrom.height() : frozenTask.height(); + if (mAnimationType == BOUNDS) { + if (animatingToLargerSize()) { + mFrozenTaskWidth = mTo.width(); + mFrozenTaskHeight = mTo.height(); + } else { + mFrozenTaskWidth = frozenTask.isEmpty() ? mFrom.width() : frozenTask.width(); + mFrozenTaskHeight = frozenTask.isEmpty() ? mFrom.height() : frozenTask.height(); + } } } @@ -222,8 +234,9 @@ public class BoundsAnimationController { // otherwise. boolean continueAnimation; if (mPrevSchedulePipModeChangedState == NO_PIP_MODE_CHANGED_CALLBACKS) { - continueAnimation = mTarget.onAnimationStart(mSchedulePipModeChangedState == - SCHEDULE_PIP_MODE_CHANGED_ON_START, false /* forceUpdate */); + continueAnimation = mTarget.onAnimationStart( + mSchedulePipModeChangedState == SCHEDULE_PIP_MODE_CHANGED_ON_START, + false /* forceUpdate */, mAnimationType); // When starting an animation from fullscreen, pause here and wait for the // windows-drawn signal before we start the rest of the transition down into PiP. @@ -238,7 +251,8 @@ public class BoundsAnimationController { // However, we still need to report to them that they are leaving PiP, so this will // force an update via a mode changed callback. continueAnimation = mTarget.onAnimationStart( - true /* schedulePipModeChangedCallback */, true /* forceUpdate */); + true /* schedulePipModeChangedCallback */, true /* forceUpdate */, + mAnimationType); } else { // The animation is already running, but we should check that the TaskStack is still // valid before continuing with the animation @@ -285,6 +299,13 @@ public class BoundsAnimationController { @Override public void onAnimationUpdate(ValueAnimator animation) { final float value = (Float) animation.getAnimatedValue(); + if (mAnimationType == FADE_IN) { + if (!mTarget.setPinnedStackAlpha(value)) { + cancelAndCallAnimationEnd(); + } + return; + } + final float remains = 1 - value; mTmpRect.left = (int) (mFrom.left * remains + mTo.left * value + 0.5f); mTmpRect.top = (int) (mFrom.top * remains + mTo.top * value + 0.5f); @@ -408,16 +429,29 @@ public class BoundsAnimationController { public void animateBounds(final BoundsAnimationTarget target, Rect from, Rect to, int animationDuration, @SchedulePipModeChangedState int schedulePipModeChangedState, - boolean moveFromFullscreen, boolean moveToFullscreen) { + boolean moveFromFullscreen, boolean moveToFullscreen, + @AnimationType int animationType) { animateBoundsImpl(target, from, to, animationDuration, schedulePipModeChangedState, - moveFromFullscreen, moveToFullscreen); + moveFromFullscreen, moveToFullscreen, animationType); } @VisibleForTesting BoundsAnimator animateBoundsImpl(final BoundsAnimationTarget target, Rect from, Rect to, int animationDuration, @SchedulePipModeChangedState int schedulePipModeChangedState, - boolean moveFromFullscreen, boolean moveToFullscreen) { + boolean moveFromFullscreen, boolean moveToFullscreen, + @AnimationType int animationType) { final BoundsAnimator existing = mRunningAnimations.get(target); + // animateBoundsImpl gets called twice for each animation. The second time we get the final + // to rect that respects the shelf, which is when we want to resize. Our signal for fade in + // comes in from how to enter into pip, but we also need to use the to and from rect to + // decide which animation we want to run finally. + boolean shouldResize = false; + if (isRunningFadeInAnimation(target)) { + shouldResize = true; + if (from.contains(to)) { + animationType = FADE_IN; + } + } final boolean replacing = existing != null; @SchedulePipModeChangedState int prevSchedulePipModeChangedState = NO_PIP_MODE_CHANGED_CALLBACKS; @@ -477,18 +511,34 @@ public class BoundsAnimationController { // Since we are replacing, we skip both animation start and end callbacks existing.cancel(); } - final BoundsAnimator animator = new BoundsAnimator(target, from, to, + if (shouldResize) { + target.setPinnedStackSize(to, null); + } + final BoundsAnimator animator = new BoundsAnimator(target, animationType, from, to, schedulePipModeChangedState, prevSchedulePipModeChangedState, moveFromFullscreen, moveToFullscreen, frozenTask); mRunningAnimations.put(target, animator); animator.setFloatValues(0f, 1f); - animator.setDuration((animationDuration != -1 ? animationDuration - : DEFAULT_TRANSITION_DURATION) * DEBUG_ANIMATION_SLOW_DOWN_FACTOR); + animator.setDuration(animationType == FADE_IN ? FADE_IN_DURATION + : (animationDuration != -1 ? animationDuration : DEFAULT_TRANSITION_DURATION) + * DEBUG_ANIMATION_SLOW_DOWN_FACTOR); animator.setInterpolator(mFastOutSlowInInterpolator); animator.start(); return animator; } + public void setAnimationType(@AnimationType int animationType) { + mAnimationType = animationType; + } + + /** return the current animation type. */ + public @AnimationType int getAnimationType() { + @AnimationType int animationType = mAnimationType; + // Default to BOUNDS. + mAnimationType = BOUNDS; + return animationType; + } + public Handler getHandler() { return mHandler; } @@ -498,6 +548,11 @@ public class BoundsAnimationController { mHandler.post(this::resume); } + private boolean isRunningFadeInAnimation(final BoundsAnimationTarget target) { + final BoundsAnimator existing = mRunningAnimations.get(target); + return existing != null && existing.mAnimationType == FADE_IN && existing.isStarted(); + } + private void resume() { for (int i = 0; i < mRunningAnimations.size(); i++) { final BoundsAnimator b = mRunningAnimations.valueAt(i); diff --git a/services/core/java/com/android/server/wm/BoundsAnimationTarget.java b/services/core/java/com/android/server/wm/BoundsAnimationTarget.java index 5cb80de1a36d7..9f54e49e0022d 100644 --- a/services/core/java/com/android/server/wm/BoundsAnimationTarget.java +++ b/services/core/java/com/android/server/wm/BoundsAnimationTarget.java @@ -32,7 +32,8 @@ interface BoundsAnimationTarget { * callbacks * @return whether to continue the animation */ - boolean onAnimationStart(boolean schedulePipModeChangedCallback, boolean forceUpdate); + boolean onAnimationStart(boolean schedulePipModeChangedCallback, boolean forceUpdate, + @BoundsAnimationController.AnimationType int animationType); /** * @return Whether the animation should be paused waiting for the windows to draw before @@ -53,6 +54,9 @@ interface BoundsAnimationTarget { */ boolean setPinnedStackSize(Rect stackBounds, Rect taskBounds); + /** Sets the alpha of the animation target */ + boolean setPinnedStackAlpha(float alpha); + /** * Callback for the target to inform it that the animation has ended, so it can do some * necessary cleanup. diff --git a/services/core/java/com/android/server/wm/RecentsAnimation.java b/services/core/java/com/android/server/wm/RecentsAnimation.java index 144efb49e84a2..07d3fb990470d 100644 --- a/services/core/java/com/android/server/wm/RecentsAnimation.java +++ b/services/core/java/com/android/server/wm/RecentsAnimation.java @@ -26,6 +26,8 @@ import static android.os.Trace.TRACE_TAG_ACTIVITY_MANAGER; import static android.view.WindowManager.TRANSIT_NONE; import static com.android.server.wm.ActivityStackSupervisor.PRESERVE_WINDOWS; +import static com.android.server.wm.BoundsAnimationController.BOUNDS; +import static com.android.server.wm.BoundsAnimationController.FADE_IN; import static com.android.server.wm.RecentsAnimationController.REORDER_KEEP_IN_PLACE; import static com.android.server.wm.RecentsAnimationController.REORDER_MOVE_TO_ORIGINAL_POSITION; import static com.android.server.wm.RecentsAnimationController.REORDER_MOVE_TO_TOP; @@ -201,7 +203,8 @@ class RecentsAnimation implements RecentsAnimationCallbacks, } } - private void finishAnimation(@RecentsAnimationController.ReorderMode int reorderMode) { + private void finishAnimation(@RecentsAnimationController.ReorderMode int reorderMode, + boolean sendUserLeaveHint) { synchronized (mService.mGlobalLock) { if (DEBUG) Slog.d(TAG, "onAnimationFinished(): controller=" + mWindowManager.getRecentsAnimationController() @@ -246,7 +249,18 @@ class RecentsAnimation implements RecentsAnimationCallbacks, if (reorderMode == REORDER_MOVE_TO_TOP) { // Bring the target stack to the front mStackSupervisor.mNoAnimActivities.add(targetActivity); - targetStack.moveToFront("RecentsAnimation.onAnimationFinished()"); + + if (sendUserLeaveHint) { + // Setting this allows the previous app to PiP. + mStackSupervisor.mUserLeaving = true; + targetStack.moveTaskToFrontLocked(targetActivity.getTaskRecord(), + true /* noAnimation */, null /* activityOptions */, + targetActivity.appTimeTracker, + "RecentsAnimation.onAnimationFinished()"); + } else { + targetStack.moveToFront("RecentsAnimation.onAnimationFinished()"); + } + if (DEBUG) { final ActivityStack topStack = getTopNonAlwaysOnTopStack(); if (topStack != targetStack) { @@ -300,11 +314,11 @@ class RecentsAnimation implements RecentsAnimationCallbacks, @Override public void onAnimationFinished(@RecentsAnimationController.ReorderMode int reorderMode, - boolean runSychronously) { + boolean runSychronously, boolean sendUserLeaveHint) { if (runSychronously) { - finishAnimation(reorderMode); + finishAnimation(reorderMode, sendUserLeaveHint); } else { - mService.mH.post(() -> finishAnimation(reorderMode)); + mService.mH.post(() -> finishAnimation(reorderMode, sendUserLeaveHint)); } } @@ -317,6 +331,10 @@ class RecentsAnimation implements RecentsAnimationCallbacks, } final RecentsAnimationController controller = mWindowManager.getRecentsAnimationController(); + final DisplayContent dc = + mService.mRootActivityContainer.getDefaultDisplay().mDisplayContent; + dc.mBoundsAnimationController.setAnimationType( + controller.shouldCancelWithDeferredScreenshot() ? FADE_IN : BOUNDS); // Cancel running recents animation and screenshot previous task when the next // transition starts in below cases: diff --git a/services/core/java/com/android/server/wm/RecentsAnimationController.java b/services/core/java/com/android/server/wm/RecentsAnimationController.java index 381366995dd51..d2c510fa89026 100644 --- a/services/core/java/com/android/server/wm/RecentsAnimationController.java +++ b/services/core/java/com/android/server/wm/RecentsAnimationController.java @@ -27,6 +27,7 @@ import static android.view.WindowManager.INPUT_CONSUMER_RECENTS_ANIMATION; import static com.android.server.policy.WindowManagerPolicy.FINISH_LAYOUT_REDO_WALLPAPER; import static com.android.server.wm.ActivityTaskManagerInternal.APP_TRANSITION_RECENTS_ANIM; import static com.android.server.wm.AnimationAdapterProto.REMOTE; +import static com.android.server.wm.BoundsAnimationController.FADE_IN; import static com.android.server.wm.RemoteAnimationAdapterWrapperProto.TARGET; import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_RECENTS_ANIMATIONS; import static com.android.server.wm.WindowManagerInternal.AppTransitionListener; @@ -142,7 +143,9 @@ public class RecentsAnimationController implements DeathRecipient { }; public interface RecentsAnimationCallbacks { - void onAnimationFinished(@ReorderMode int reorderMode, boolean runSychronously); + /** Callback when recents animation is finished. */ + void onAnimationFinished(@ReorderMode int reorderMode, boolean runSychronously, + boolean sendUserLeaveHint); } private final IRecentsAnimationController mController = @@ -179,7 +182,7 @@ public class RecentsAnimationController implements DeathRecipient { } @Override - public void finish(boolean moveHomeToTop) { + public void finish(boolean moveHomeToTop, boolean sendUserLeaveHint) { if (DEBUG_RECENTS_ANIMATIONS) Slog.d(TAG, "finish(" + moveHomeToTop + "):" + " mCanceled=" + mCanceled); final long token = Binder.clearCallingIdentity(); @@ -195,7 +198,9 @@ public class RecentsAnimationController implements DeathRecipient { mCallbacks.onAnimationFinished(moveHomeToTop ? REORDER_MOVE_TO_TOP : REORDER_MOVE_TO_ORIGINAL_POSITION, - true /* runSynchronously */); + true /* runSynchronously */, sendUserLeaveHint); + final DisplayContent dc = mService.mRoot.getDisplayContent(mDisplayId); + dc.mBoundsAnimationController.setAnimationType(FADE_IN); } finally { Binder.restoreCallingIdentity(token); } @@ -497,7 +502,8 @@ public class RecentsAnimationController implements DeathRecipient { Slog.e(TAG, "Failed to cancel recents animation", e); } // Clean up and return to the previous app - mCallbacks.onAnimationFinished(reorderMode, runSynchronously); + mCallbacks.onAnimationFinished(reorderMode, runSynchronously, + false /* sendUserLeaveHint */); } } @@ -542,7 +548,8 @@ public class RecentsAnimationController implements DeathRecipient { if (DEBUG_RECENTS_ANIMATIONS) { Slog.d(TAG, "mRecentScreenshotAnimator finish"); } - mCallbacks.onAnimationFinished(reorderMode, runSynchronously); + mCallbacks.onAnimationFinished(reorderMode, runSynchronously, + false /* sendUserLeaveHint */); }, mService); mRecentScreenshotAnimator.transferAnimation(task.mSurfaceAnimator); } diff --git a/services/core/java/com/android/server/wm/TaskStack.java b/services/core/java/com/android/server/wm/TaskStack.java index 09baf8cf11110..bdb4d04748655 100644 --- a/services/core/java/com/android/server/wm/TaskStack.java +++ b/services/core/java/com/android/server/wm/TaskStack.java @@ -34,6 +34,7 @@ import static android.view.WindowManager.DOCKED_LEFT; import static android.view.WindowManager.DOCKED_RIGHT; import static android.view.WindowManager.DOCKED_TOP; +import static com.android.server.wm.BoundsAnimationController.FADE_IN; import static com.android.server.wm.BoundsAnimationController.NO_PIP_MODE_CHANGED_CALLBACKS; import static com.android.server.wm.BoundsAnimationController.SCHEDULE_PIP_MODE_CHANGED_ON_END; import static com.android.server.wm.BoundsAnimationController.SCHEDULE_PIP_MODE_CHANGED_ON_START; @@ -148,6 +149,7 @@ public class TaskStack extends WindowContainer implements private boolean mCancelCurrentBoundsAnimation = false; private Rect mBoundsAnimationTarget = new Rect(); private Rect mBoundsAnimationSourceHintBounds = new Rect(); + private @BoundsAnimationController.AnimationType int mAnimationType; Rect mPreAnimationBounds = new Rect(); @@ -1572,7 +1574,8 @@ public class TaskStack extends WindowContainer implements } @Override // AnimatesBounds - public boolean onAnimationStart(boolean schedulePipModeChangedCallback, boolean forceUpdate) { + public boolean onAnimationStart(boolean schedulePipModeChangedCallback, boolean forceUpdate, + @BoundsAnimationController.AnimationType int animationType) { // Hold the lock since this is called from the BoundsAnimator running on the UiThread synchronized (mWmService.mGlobalLock) { if (!isAttached()) { @@ -1583,6 +1586,7 @@ public class TaskStack extends WindowContainer implements mBoundsAnimatingRequested = false; mBoundsAnimating = true; mCancelCurrentBoundsAnimation = false; + mAnimationType = animationType; // If we are changing UI mode, as in the PiP to fullscreen // transition, then we need to wait for the window to draw. @@ -1599,7 +1603,8 @@ public class TaskStack extends WindowContainer implements // I don't believe you... } - if (schedulePipModeChangedCallback && mActivityStack != null) { + if ((schedulePipModeChangedCallback || animationType == FADE_IN) + && mActivityStack != null) { // We need to schedule the PiP mode change before the animation up. It is possible // in this case for the animation down to not have been completed, so always // force-schedule and update to the client to ensure that it is notified that it @@ -1614,6 +1619,21 @@ public class TaskStack extends WindowContainer implements @Override // AnimatesBounds public void onAnimationEnd(boolean schedulePipModeChangedCallback, Rect finalStackSize, boolean moveToFullscreen) { + if (mAnimationType == BoundsAnimationController.FADE_IN) { + setPinnedStackAlpha(1f); + try { + mWmService.mActivityTaskManager.notifyPinnedStackAnimationEnded(); + } catch (RemoteException e) { + // I don't believe you... + } + return; + } + + onBoundAnimationEnd(schedulePipModeChangedCallback, finalStackSize, moveToFullscreen); + } + + private void onBoundAnimationEnd(boolean schedulePipModeChangedCallback, Rect finalStackSize, + boolean moveToFullscreen) { if (inPinnedWindowingMode()) { // Update to the final bounds if requested. This is done here instead of in the bounds // animator to allow us to coordinate this after we notify the PiP mode changed @@ -1725,10 +1745,23 @@ public class TaskStack extends WindowContainer implements final @SchedulePipModeChangedState int finalSchedulePipModeChangedState = schedulePipModeChangedState; final DisplayContent displayContent = getDisplayContent(); + @BoundsAnimationController.AnimationType int intendedAnimationType = + displayContent.mBoundsAnimationController.getAnimationType(); + if (intendedAnimationType == FADE_IN) { + if (fromFullscreen) { + setPinnedStackAlpha(0f); + } + if (toBounds.width() == fromBounds.width() + && toBounds.height() == fromBounds.height()) { + intendedAnimationType = BoundsAnimationController.BOUNDS; + } + } + + final @BoundsAnimationController.AnimationType int animationType = intendedAnimationType; displayContent.mBoundsAnimationController.getHandler().post(() -> { displayContent.mBoundsAnimationController.animateBounds(this, fromBounds, finalToBounds, animationDuration, finalSchedulePipModeChangedState, - fromFullscreen, toFullscreen); + fromFullscreen, toFullscreen, animationType); }); } @@ -1905,6 +1938,20 @@ public class TaskStack extends WindowContainer implements } } + @Override + public boolean setPinnedStackAlpha(float alpha) { + // Hold the lock since this is called from the BoundsAnimator running on the UiThread + synchronized (mWmService.mGlobalLock) { + if (mCancelCurrentBoundsAnimation) { + return false; + } + getPendingTransaction().setAlpha(getSurfaceControl(), alpha); + scheduleAnimation(); + } + + return true; + } + public DisplayInfo getDisplayInfo() { return mDisplayContent.getDisplayInfo(); } diff --git a/services/tests/wmtests/src/com/android/server/wm/BoundsAnimationControllerTests.java b/services/tests/wmtests/src/com/android/server/wm/BoundsAnimationControllerTests.java index 9ce579512eda4..beec1a8b89428 100644 --- a/services/tests/wmtests/src/com/android/server/wm/BoundsAnimationControllerTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/BoundsAnimationControllerTests.java @@ -18,6 +18,8 @@ package com.android.server.wm; import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation; +import static com.android.server.wm.BoundsAnimationController.BOUNDS; +import static com.android.server.wm.BoundsAnimationController.FADE_IN; import static com.android.server.wm.BoundsAnimationController.NO_PIP_MODE_CHANGED_CALLBACKS; import static com.android.server.wm.BoundsAnimationController.SCHEDULE_PIP_MODE_CHANGED_ON_END; import static com.android.server.wm.BoundsAnimationController.SCHEDULE_PIP_MODE_CHANGED_ON_START; @@ -131,6 +133,8 @@ public class BoundsAnimationControllerTests extends WindowTestsBase { boolean mCancelRequested; Rect mStackBounds; Rect mTaskBounds; + float mAlpha; + @BoundsAnimationController.AnimationType int mAnimationType; void initialize(Rect from) { mAwaitingAnimationStart = true; @@ -148,11 +152,12 @@ public class BoundsAnimationControllerTests extends WindowTestsBase { @Override public boolean onAnimationStart(boolean schedulePipModeChangedCallback, - boolean forceUpdate) { + boolean forceUpdate, @BoundsAnimationController.AnimationType int animationType) { mAwaitingAnimationStart = false; mAnimationStarted = true; mSchedulePipModeChangedOnStart = schedulePipModeChangedCallback; mForcePipModeChangedCallback = forceUpdate; + mAnimationType = animationType; return true; } @@ -185,6 +190,12 @@ public class BoundsAnimationControllerTests extends WindowTestsBase { mMovedToFullscreen = moveToFullscreen; mTaskBounds = null; } + + @Override + public boolean setPinnedStackAlpha(float alpha) { + mAlpha = alpha; + return true; + } } /** @@ -201,6 +212,7 @@ public class BoundsAnimationControllerTests extends WindowTestsBase { private Rect mTo; private Rect mLargerBounds; private Rect mExpectedFinalBounds; + private @BoundsAnimationController.AnimationType int mAnimationType; BoundsAnimationDriver(BoundsAnimationController controller, TestBoundsAnimationTarget target, MockValueAnimator mockValueAnimator) { @@ -209,7 +221,8 @@ public class BoundsAnimationControllerTests extends WindowTestsBase { mMockAnimator = mockValueAnimator; } - BoundsAnimationDriver start(Rect from, Rect to) { + BoundsAnimationDriver start(Rect from, Rect to, + @BoundsAnimationController.AnimationType int animationType) { if (mAnimator != null) { throw new IllegalArgumentException("Call restart() to restart an animation"); } @@ -223,7 +236,7 @@ public class BoundsAnimationControllerTests extends WindowTestsBase { assertTrue(mTarget.mAwaitingAnimationStart); assertFalse(mTarget.mAnimationStarted); - startImpl(from, to); + startImpl(from, to, animationType); // Ensure that the animator is paused for the all windows drawn signal when animating // to/from fullscreen @@ -253,7 +266,7 @@ public class BoundsAnimationControllerTests extends WindowTestsBase { mTarget.mAnimationStarted = false; // Start animation - startImpl(mTarget.mStackBounds, to); + startImpl(mTarget.mStackBounds, to, BOUNDS); if (toSameBounds) { // Same animator if same final bounds @@ -273,13 +286,15 @@ public class BoundsAnimationControllerTests extends WindowTestsBase { return this; } - private BoundsAnimationDriver startImpl(Rect from, Rect to) { + private BoundsAnimationDriver startImpl(Rect from, Rect to, + @BoundsAnimationController.AnimationType int animationType) { boolean fromFullscreen = from.equals(BOUNDS_FULL); boolean toFullscreen = to.equals(BOUNDS_FULL); mFrom = new Rect(from); mTo = new Rect(to); mExpectedFinalBounds = new Rect(to); mLargerBounds = getLargerBounds(mFrom, mTo); + mAnimationType = animationType; // Start animation final @SchedulePipModeChangedState int schedulePipModeChangedState = toFullscreen @@ -288,17 +303,19 @@ public class BoundsAnimationControllerTests extends WindowTestsBase { ? SCHEDULE_PIP_MODE_CHANGED_ON_END : NO_PIP_MODE_CHANGED_CALLBACKS; mAnimator = mController.animateBoundsImpl(mTarget, from, to, DURATION, - schedulePipModeChangedState, fromFullscreen, toFullscreen); + schedulePipModeChangedState, fromFullscreen, toFullscreen, animationType); - // Original stack bounds, frozen task bounds - assertEquals(mFrom, mTarget.mStackBounds); - assertEqualSizeAtOffset(mLargerBounds, mTarget.mTaskBounds); + if (animationType == BOUNDS) { + // Original stack bounds, frozen task bounds + assertEquals(mFrom, mTarget.mStackBounds); + assertEqualSizeAtOffset(mLargerBounds, mTarget.mTaskBounds); - // Animating to larger size - if (mFrom.equals(mLargerBounds)) { - assertFalse(mAnimator.animatingToLargerSize()); - } else if (mTo.equals(mLargerBounds)) { - assertTrue(mAnimator.animatingToLargerSize()); + // Animating to larger size + if (mFrom.equals(mLargerBounds)) { + assertFalse(mAnimator.animatingToLargerSize()); + } else if (mTo.equals(mLargerBounds)) { + assertTrue(mAnimator.animatingToLargerSize()); + } } return this; @@ -315,16 +332,20 @@ public class BoundsAnimationControllerTests extends WindowTestsBase { BoundsAnimationDriver update(float t) { mAnimator.onAnimationUpdate(mMockAnimator.getWithValue(t)); - // Temporary stack bounds, frozen task bounds - if (t == 0f) { - assertEquals(mFrom, mTarget.mStackBounds); - } else if (t == 1f) { - assertEquals(mTo, mTarget.mStackBounds); + if (mAnimationType == BOUNDS) { + // Temporary stack bounds, frozen task bounds + if (t == 0f) { + assertEquals(mFrom, mTarget.mStackBounds); + } else if (t == 1f) { + assertEquals(mTo, mTarget.mStackBounds); + } else { + assertNotEquals(mFrom, mTarget.mStackBounds); + assertNotEquals(mTo, mTarget.mStackBounds); + } + assertEqualSizeAtOffset(mLargerBounds, mTarget.mTaskBounds); } else { - assertNotEquals(mFrom, mTarget.mStackBounds); - assertNotEquals(mTo, mTarget.mStackBounds); + assertEquals((float) mMockAnimator.getAnimatedValue(), mTarget.mAlpha, 0.01f); } - assertEqualSizeAtOffset(mLargerBounds, mTarget.mTaskBounds); return this; } @@ -353,10 +374,14 @@ public class BoundsAnimationControllerTests extends WindowTestsBase { BoundsAnimationDriver end() { mAnimator.end(); - // Final stack bounds - assertEquals(mTo, mTarget.mStackBounds); - assertEquals(mExpectedFinalBounds, mTarget.mAnimationEndFinalStackBounds); - assertNull(mTarget.mTaskBounds); + if (mAnimationType == BOUNDS) { + // Final stack bounds + assertEquals(mTo, mTarget.mStackBounds); + assertEquals(mExpectedFinalBounds, mTarget.mAnimationEndFinalStackBounds); + assertNull(mTarget.mTaskBounds); + } else { + assertEquals(mTarget.mAlpha, 1f, 0.01f); + } return this; } @@ -413,7 +438,7 @@ public class BoundsAnimationControllerTests extends WindowTestsBase { @UiThreadTest @Test public void testFullscreenToFloatingTransition() { - mDriver.start(BOUNDS_FULL, BOUNDS_FLOATING) + mDriver.start(BOUNDS_FULL, BOUNDS_FLOATING, BOUNDS) .expectStarted(!SCHEDULE_PIP_MODE_CHANGED) .update(0f) .update(0.5f) @@ -425,7 +450,7 @@ public class BoundsAnimationControllerTests extends WindowTestsBase { @UiThreadTest @Test public void testFloatingToFullscreenTransition() { - mDriver.start(BOUNDS_FLOATING, BOUNDS_FULL) + mDriver.start(BOUNDS_FLOATING, BOUNDS_FULL, BOUNDS) .expectStarted(SCHEDULE_PIP_MODE_CHANGED) .update(0f) .update(0.5f) @@ -437,7 +462,7 @@ public class BoundsAnimationControllerTests extends WindowTestsBase { @UiThreadTest @Test public void testFloatingToSmallerFloatingTransition() { - mDriver.start(BOUNDS_FLOATING, BOUNDS_SMALLER_FLOATING) + mDriver.start(BOUNDS_FLOATING, BOUNDS_SMALLER_FLOATING, BOUNDS) .expectStarted(!SCHEDULE_PIP_MODE_CHANGED) .update(0f) .update(0.5f) @@ -449,7 +474,7 @@ public class BoundsAnimationControllerTests extends WindowTestsBase { @UiThreadTest @Test public void testFloatingToLargerFloatingTransition() { - mDriver.start(BOUNDS_SMALLER_FLOATING, BOUNDS_FLOATING) + mDriver.start(BOUNDS_SMALLER_FLOATING, BOUNDS_FLOATING, BOUNDS) .expectStarted(!SCHEDULE_PIP_MODE_CHANGED) .update(0f) .update(0.5f) @@ -463,7 +488,7 @@ public class BoundsAnimationControllerTests extends WindowTestsBase { @UiThreadTest @Test public void testFullscreenToFloatingCancelFromTarget() { - mDriver.start(BOUNDS_FULL, BOUNDS_FLOATING) + mDriver.start(BOUNDS_FULL, BOUNDS_FLOATING, BOUNDS) .expectStarted(!SCHEDULE_PIP_MODE_CHANGED) .update(0.25f) .cancel() @@ -473,7 +498,7 @@ public class BoundsAnimationControllerTests extends WindowTestsBase { @UiThreadTest @Test public void testFullscreenToFloatingCancelFromAnimationToSameBounds() { - mDriver.start(BOUNDS_FULL, BOUNDS_FLOATING) + mDriver.start(BOUNDS_FULL, BOUNDS_FLOATING, BOUNDS) .expectStarted(!SCHEDULE_PIP_MODE_CHANGED) .update(0.25f) .restart(BOUNDS_FLOATING, false /* expectStartedAndPipModeChangedCallback */) @@ -484,7 +509,7 @@ public class BoundsAnimationControllerTests extends WindowTestsBase { @UiThreadTest @Test public void testFullscreenToFloatingCancelFromAnimationToFloatingBounds() { - mDriver.start(BOUNDS_FULL, BOUNDS_FLOATING) + mDriver.start(BOUNDS_FULL, BOUNDS_FLOATING, BOUNDS) .expectStarted(!SCHEDULE_PIP_MODE_CHANGED) .update(0.25f) .restart(BOUNDS_SMALLER_FLOATING, @@ -498,7 +523,7 @@ public class BoundsAnimationControllerTests extends WindowTestsBase { public void testFullscreenToFloatingCancelFromAnimationToFullscreenBounds() { // When animating from fullscreen and the animation is interruped, we expect the animation // start callback to be made, with a forced pip mode change callback - mDriver.start(BOUNDS_FULL, BOUNDS_FLOATING) + mDriver.start(BOUNDS_FULL, BOUNDS_FLOATING, BOUNDS) .expectStarted(!SCHEDULE_PIP_MODE_CHANGED) .update(0.25f) .restart(BOUNDS_FULL, true /* expectStartedAndPipModeChangedCallback */) @@ -511,7 +536,7 @@ public class BoundsAnimationControllerTests extends WindowTestsBase { @UiThreadTest @Test public void testFloatingToFullscreenCancelFromTarget() { - mDriver.start(BOUNDS_FLOATING, BOUNDS_FULL) + mDriver.start(BOUNDS_FLOATING, BOUNDS_FULL, BOUNDS) .expectStarted(SCHEDULE_PIP_MODE_CHANGED) .update(0.25f) .cancel() @@ -521,7 +546,7 @@ public class BoundsAnimationControllerTests extends WindowTestsBase { @UiThreadTest @Test public void testFloatingToFullscreenCancelFromAnimationToSameBounds() { - mDriver.start(BOUNDS_FLOATING, BOUNDS_FULL) + mDriver.start(BOUNDS_FLOATING, BOUNDS_FULL, BOUNDS) .expectStarted(SCHEDULE_PIP_MODE_CHANGED) .update(0.25f) .restart(BOUNDS_FULL, false /* expectStartedAndPipModeChangedCallback */) @@ -532,7 +557,7 @@ public class BoundsAnimationControllerTests extends WindowTestsBase { @UiThreadTest @Test public void testFloatingToFullscreenCancelFromAnimationToFloatingBounds() { - mDriver.start(BOUNDS_FLOATING, BOUNDS_FULL) + mDriver.start(BOUNDS_FLOATING, BOUNDS_FULL, BOUNDS) .expectStarted(SCHEDULE_PIP_MODE_CHANGED) .update(0.25f) .restart(BOUNDS_SMALLER_FLOATING, @@ -546,7 +571,7 @@ public class BoundsAnimationControllerTests extends WindowTestsBase { @UiThreadTest @Test public void testFloatingToSmallerFloatingCancelFromTarget() { - mDriver.start(BOUNDS_FLOATING, BOUNDS_SMALLER_FLOATING) + mDriver.start(BOUNDS_FLOATING, BOUNDS_SMALLER_FLOATING, BOUNDS) .expectStarted(!SCHEDULE_PIP_MODE_CHANGED) .update(0.25f) .cancel() @@ -556,13 +581,25 @@ public class BoundsAnimationControllerTests extends WindowTestsBase { @UiThreadTest @Test public void testFloatingToLargerFloatingCancelFromTarget() { - mDriver.start(BOUNDS_SMALLER_FLOATING, BOUNDS_FLOATING) + mDriver.start(BOUNDS_SMALLER_FLOATING, BOUNDS_FLOATING, BOUNDS) .expectStarted(!SCHEDULE_PIP_MODE_CHANGED) .update(0.25f) .cancel() .expectEnded(!SCHEDULE_PIP_MODE_CHANGED, !MOVE_TO_FULLSCREEN); } + @UiThreadTest + @Test + public void testFadeIn() { + mDriver.start(BOUNDS_FULL, BOUNDS_FLOATING, FADE_IN) + .expectStarted(!SCHEDULE_PIP_MODE_CHANGED) + .update(0f) + .update(0.5f) + .update(1f) + .end() + .expectEnded(SCHEDULE_PIP_MODE_CHANGED, !MOVE_TO_FULLSCREEN); + } + /** MISC **/ @UiThreadTest @@ -570,7 +607,7 @@ public class BoundsAnimationControllerTests extends WindowTestsBase { public void testBoundsAreCopied() { Rect from = new Rect(0, 0, 100, 100); Rect to = new Rect(25, 25, 75, 75); - mDriver.start(from, to) + mDriver.start(from, to, BOUNDS) .update(0.25f) .end(); assertEquals(new Rect(0, 0, 100, 100), from); diff --git a/services/tests/wmtests/src/com/android/server/wm/RecentsAnimationControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/RecentsAnimationControllerTest.java index e392353a88758..0c2ce614b7727 100644 --- a/services/tests/wmtests/src/com/android/server/wm/RecentsAnimationControllerTest.java +++ b/services/tests/wmtests/src/com/android/server/wm/RecentsAnimationControllerTest.java @@ -163,7 +163,7 @@ public class RecentsAnimationControllerTest extends WindowTestsBase { // Assume IRecentsAnimationController#cleanupScreenshot called to finish screenshot // animation. mController.mRecentScreenshotAnimator.cancelAnimation(); - verify(mAnimationCallbacks).onAnimationFinished(REORDER_KEEP_IN_PLACE, true); + verify(mAnimationCallbacks).onAnimationFinished(REORDER_KEEP_IN_PLACE, true, false); } private static void verifyNoMoreInteractionsExceptAsBinder(IInterface binder) { diff --git a/services/tests/wmtests/src/com/android/server/wm/RecentsAnimationTest.java b/services/tests/wmtests/src/com/android/server/wm/RecentsAnimationTest.java index 5625ea42726f0..f615823a645c8 100644 --- a/services/tests/wmtests/src/com/android/server/wm/RecentsAnimationTest.java +++ b/services/tests/wmtests/src/com/android/server/wm/RecentsAnimationTest.java @@ -71,6 +71,7 @@ public class RecentsAnimationTest extends ActivityTestsBase { @Test public void testCancelAnimationOnVisibleStackOrderChange() { ActivityDisplay display = mService.mRootActivityContainer.getDefaultDisplay(); + display.mDisplayContent.mBoundsAnimationController = mock(BoundsAnimationController.class); ActivityStack fullscreenStack = display.createStack(WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, true /* onTop */); new ActivityBuilder(mService)