Loading services/core/java/com/android/server/am/ActivityManagerService.java +3 −2 Original line number Diff line number Diff line Loading @@ -10535,8 +10535,9 @@ public class ActivityManagerService extends IActivityManager.Stub final PinnedActivityStack pinnedStack = mStackSupervisor.getStack(PINNED_STACK_ID); if (pinnedStack != null) { pinnedStack.animateResizePinnedStack(null /* sourceBounds */, destBounds, animationDuration); pinnedStack.animateResizePinnedStack(null /* sourceHintBounds */, destBounds, animationDuration, false /* schedulePipModeChangedOnAnimationEnd */); } } else { throw new IllegalArgumentException("Stack: " + stackId services/core/java/com/android/server/am/ActivityStack.java +7 −0 Original line number Diff line number Diff line Loading @@ -588,6 +588,13 @@ class ActivityStack<T extends StackWindowController> extends ConfigurationContai true /* includingParents */); } /** * Returns whether to defer the scheduling of the multi-window mode. */ boolean deferScheduleMultiWindowModeChanged() { return false; } /** * Defers updating the bounds of the stack. If the stack was resized/repositioned while * deferring, the bounds will update in {@link #continueUpdateBounds()}. Loading services/core/java/com/android/server/am/ActivityStackSupervisor.java +10 −7 Original line number Diff line number Diff line Loading @@ -2504,7 +2504,7 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D // incorrect if AMS.resizeStackWithBoundsFromWindowManager() is already called while waiting // for the AMS lock to be freed. So check and make sure these bounds are still good. final PinnedStackWindowController stackController = stack.getWindowContainerController(); if (stackController.pinnedStackResizeAllowed()) { if (stackController.pinnedStackResizeDisallowed()) { return; } Loading Loading @@ -2873,7 +2873,7 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D return true; } void moveActivityToPinnedStackLocked(ActivityRecord r, Rect sourceBounds, float aspectRatio, void moveActivityToPinnedStackLocked(ActivityRecord r, Rect sourceHintBounds, float aspectRatio, boolean moveHomeStackToFront, String reason) { mWindowManager.deferSurfaceLayout(); Loading Loading @@ -2948,11 +2948,8 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D final Rect destBounds = mWindowManager.getPictureInPictureBounds(DEFAULT_DISPLAY, aspectRatio, false /* useExistingStackBounds */); // TODO(b/36099777): Schedule the PiP mode change here immediately until we can defer all // callbacks until after the bounds animation scheduleUpdatePictureInPictureModeIfNeeded(r.getTask(), destBounds, true /* immediate */); stack.animateResizePinnedStack(sourceBounds, destBounds, -1 /* animationDuration */); stack.animateResizePinnedStack(sourceHintBounds, destBounds, -1 /* animationDuration */, true /* schedulePipModeChangedOnAnimationEnd */); mService.mTaskChangeNotificationController.notifyActivityPinned(r.packageName); } Loading Loading @@ -4179,6 +4176,12 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D } void scheduleUpdateMultiWindowMode(TaskRecord task) { // If the stack is animating in a way where we will be forcing a multi-mode change at the // end, then ensure that we defer all in between multi-window mode changes if (task.getStack().deferScheduleMultiWindowModeChanged()) { return; } for (int i = task.mActivities.size() - 1; i >= 0; i--) { final ActivityRecord r = task.mActivities.get(i); if (r.app != null && r.app.thread != null) { Loading services/core/java/com/android/server/am/PinnedActivityStack.java +16 −4 Original line number Diff line number Diff line Loading @@ -43,9 +43,10 @@ class PinnedActivityStack extends ActivityStack<PinnedStackWindowController> return new PinnedStackWindowController(mStackId, this, displayId, onTop, outBounds); } void animateResizePinnedStack(Rect sourceBounds, Rect destBounds, int animationDuration) { getWindowContainerController().animateResizePinnedStack(sourceBounds, destBounds, animationDuration); void animateResizePinnedStack(Rect sourceHintBounds, Rect toBounds, int animationDuration, boolean schedulePipModeChangedOnAnimationEnd) { getWindowContainerController().animateResizePinnedStack(toBounds, sourceHintBounds, animationDuration, schedulePipModeChangedOnAnimationEnd); } void setPictureInPictureAspectRatio(float aspectRatio) { Loading @@ -60,7 +61,18 @@ class PinnedActivityStack extends ActivityStack<PinnedStackWindowController> return getWindowContainerController().isAnimatingBoundsToFullscreen(); } @Override /** * Returns whether to defer the scheduling of the multi-window mode. */ boolean deferScheduleMultiWindowModeChanged() { // For the pinned stack, the deferring of the multi-window mode changed is tied to the // transition animation into picture-in-picture, and is called once the animation completes, // or is interrupted in a way that would leave the stack in a non-fullscreen state. // @see BoundsAnimationController // @see BoundsAnimationControllerTests return mWindowContainerController.deferScheduleMultiWindowModeChanged(); } public void updatePictureInPictureModeForPinnedStackAnimation(Rect targetStackBounds) { // It is guaranteed that the activities requiring the update will be in the pinned stack at // this point (either reparented before the animation into PiP, or before reparenting after Loading services/core/java/com/android/server/wm/BoundsAnimationController.java +115 −116 Original line number Diff line number Diff line Loading @@ -22,6 +22,7 @@ import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM; import android.animation.Animator; import android.animation.ValueAnimator; import android.annotation.IntDef; import android.content.Context; import android.graphics.Rect; import android.os.Handler; Loading @@ -35,6 +36,9 @@ import android.view.WindowManagerInternal; import com.android.internal.annotations.VisibleForTesting; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; /** * Enables animating bounds of objects. * Loading @@ -43,7 +47,7 @@ import com.android.internal.annotations.VisibleForTesting; * relaunching it would cause poorer experience), these class provides a way to directly animate * the bounds of the resized object. * * The object that is resized needs to implement {@link AnimateBoundsUser} interface. * The object that is resized needs to implement {@link BoundsAnimationTarget} interface. * * NOTE: All calls to methods in this class should be done on the UI thread */ Loading @@ -56,8 +60,19 @@ public class BoundsAnimationController { private static final int DEFAULT_TRANSITION_DURATION = 425; @Retention(RetentionPolicy.SOURCE) @IntDef({NO_PIP_MODE_CHANGED_CALLBACKS, SCHEDULE_PIP_MODE_CHANGED_ON_START, SCHEDULE_PIP_MODE_CHANGED_ON_END}) public @interface SchedulePipModeChangedState {} /** Do not schedule any PiP mode changed callbacks as a part of this animation. */ public static final int NO_PIP_MODE_CHANGED_CALLBACKS = 0; /** Schedule a PiP mode changed callback when this animation starts. */ public static final int SCHEDULE_PIP_MODE_CHANGED_ON_START = 1; /** Schedule a PiP mode changed callback when this animation ends. */ public static final int SCHEDULE_PIP_MODE_CHANGED_ON_END = 2; // Only accessed on UI thread. private ArrayMap<AnimateBoundsUser, BoundsAnimator> mRunningAnimations = new ArrayMap<>(); private ArrayMap<BoundsAnimationTarget, BoundsAnimator> mRunningAnimations = new ArrayMap<>(); private final class AppTransitionNotifier extends WindowManagerInternal.AppTransitionListener implements Runnable { Loading Loading @@ -108,40 +123,42 @@ public class BoundsAnimationController { @VisibleForTesting final class BoundsAnimator extends ValueAnimator implements ValueAnimator.AnimatorUpdateListener, ValueAnimator.AnimatorListener { private final AnimateBoundsUser mTarget; private final BoundsAnimationTarget mTarget; private final Rect mFrom = new Rect(); private final Rect mTo = new Rect(); private final Rect mTmpRect = new Rect(); private final Rect mTmpTaskBounds = new Rect(); private final boolean mMoveToFullScreen; // True if this this animation was cancelled and will be replaced the another animation from // the same {@link #AnimateBoundsUser} target. // True if this this animation was canceled and will be replaced the another animation from // the same {@link #BoundsAnimationTarget} target. private boolean mSkipFinalResize; // True if this animation replaced a previous animation of the same // {@link #AnimateBoundsUser} target. // {@link #BoundsAnimationTarget} target. private final boolean mSkipAnimationStart; // True if this animation was cancelled by the user, not as a part of a replacing animation // True if this animation was canceled by the user, not as a part of a replacing animation private boolean mSkipAnimationEnd; // True if this animation is not replacing a previous animation, or if the previous // animation is animating to a different fullscreen state than the current animation. // We use this to ensure that we always provide a consistent set/order of callbacks when we // transition to/from PiP. private final boolean mAnimatingToNewFullscreenState; // True if the animation target should be moved to the fullscreen stack at the end of this // animation private boolean mMoveToFullscreen; // Whether to schedule PiP mode changes on animation start/end private @SchedulePipModeChangedState int mSchedulePipModeChangedState; // Depending on whether we are animating from // a smaller to a larger size private final int mFrozenTaskWidth; private final int mFrozenTaskHeight; BoundsAnimator(AnimateBoundsUser target, Rect from, Rect to, boolean moveToFullScreen, boolean replacingExistingAnimation, boolean animatingToNewFullscreenState) { BoundsAnimator(BoundsAnimationTarget target, Rect from, Rect to, @SchedulePipModeChangedState int schedulePipModeChangedState, boolean moveToFullscreen, boolean replacingExistingAnimation) { super(); mTarget = target; mFrom.set(from); mTo.set(to); mMoveToFullScreen = moveToFullScreen; mSkipAnimationStart = replacingExistingAnimation; mAnimatingToNewFullscreenState = animatingToNewFullscreenState; mSchedulePipModeChangedState = schedulePipModeChangedState; mMoveToFullscreen = moveToFullscreen; addUpdateListener(this); addListener(this); Loading @@ -161,7 +178,8 @@ public class BoundsAnimationController { @Override public void onAnimationStart(Animator animation) { if (DEBUG) Slog.d(TAG, "onAnimationStart: mTarget=" + mTarget + " mSkipAnimationStart=" + mSkipAnimationStart); + " mSkipAnimationStart=" + mSkipAnimationStart + " mSchedulePipModeChangedState=" + mSchedulePipModeChangedState); mFinishAnimationAfterTransition = false; mTmpRect.set(mFrom.left, mFrom.top, mFrom.left + mFrozenTaskWidth, mFrom.top + mFrozenTaskHeight); Loading @@ -170,13 +188,8 @@ public class BoundsAnimationController { // we trigger any size changes, so it can swap surfaces // in to appropriate modes, or do as it wishes otherwise. if (!mSkipAnimationStart) { mTarget.onAnimationStart(mMoveToFullScreen); } // If we are animating to a new fullscreen state (either to/from fullscreen), then // notify the target of the change with the new frozen task bounds if (mAnimatingToNewFullscreenState && mMoveToFullScreen) { mTarget.updatePictureInPictureMode(null); mTarget.onAnimationStart(mSchedulePipModeChangedState == SCHEDULE_PIP_MODE_CHANGED_ON_START); } // Immediately update the task bounds if they have to become larger, but preserve Loading Loading @@ -206,20 +219,26 @@ public class BoundsAnimationController { // any further animation. if (DEBUG) Slog.d(TAG, "animateUpdate: cancelled"); // If we have already scheduled a PiP mode changed at the start of the animation, // then we need to clean up and schedule one at the end, since we have canceled the // animation to the final state. if (mSchedulePipModeChangedState == SCHEDULE_PIP_MODE_CHANGED_ON_START) { mSchedulePipModeChangedState = SCHEDULE_PIP_MODE_CHANGED_ON_END; } // Since we are cancelling immediately without a replacement animation, send the // animation end to maintain callback parity, but also skip any further resizes prepareCancel(false /* skipAnimationEnd */, true /* skipFinalResize */); cancel(); cancelAndCallAnimationEnd(); } } @Override public void onAnimationEnd(Animator animation) { if (DEBUG) Slog.d(TAG, "onAnimationEnd: mTarget=" + mTarget + " mMoveToFullScreen=" + mMoveToFullScreen + " mSkipFinalResize=" + mSkipFinalResize + " mFinishAnimationAfterTransition=" + mFinishAnimationAfterTransition + " mAppTransitionIsRunning=" + mAppTransition.isRunning()); + " mAppTransitionIsRunning=" + mAppTransition.isRunning() + " callers=" + Debug.getCallers(2)); // There could be another animation running. For example in the // move to fullscreen case, recents will also be closing while the Loading @@ -231,58 +250,57 @@ public class BoundsAnimationController { return; } if (!mSkipFinalResize) { // If not cancelled, resize the pinned stack to the final size. All calls to // setPinnedStackSize() must be done between onAnimationStart() and onAnimationEnd() mTarget.setPinnedStackSize(mTo, null); if (!mSkipAnimationEnd) { // If this animation has already scheduled the picture-in-picture mode on start, and // we are not skipping the final resize due to being canceled, then move the PiP to // fullscreen once the animation ends if (DEBUG) Slog.d(TAG, "onAnimationEnd: mTarget=" + mTarget + " moveToFullscreen=" + mMoveToFullscreen); mTarget.onAnimationEnd(mSchedulePipModeChangedState == SCHEDULE_PIP_MODE_CHANGED_ON_END, !mSkipFinalResize ? mTo : null, mMoveToFullscreen); } finishAnimation(); if (mMoveToFullScreen && !mSkipFinalResize) { mTarget.moveToFullscreen(); } // Clean up this animation removeListener(this); removeUpdateListener(this); mRunningAnimations.remove(mTarget); } @Override public void onAnimationCancel(Animator animation) { finishAnimation(); // Always skip the final resize when the animation is canceled mSkipFinalResize = true; mMoveToFullscreen = false; } public void prepareCancel(boolean skipAnimationEnd, boolean skipFinalResize) { if (DEBUG) Slog.d(TAG, "prepareCancel: skipAnimationEnd=" + skipAnimationEnd + " skipFinalResize=" + skipFinalResize); mSkipAnimationEnd = skipAnimationEnd; mSkipFinalResize = skipFinalResize; private void cancelAndCallAnimationEnd() { if (DEBUG) Slog.d(TAG, "cancelAndCallAnimationEnd: mTarget=" + mTarget); mSkipAnimationEnd = false; super.cancel(); } @Override public void cancel() { if (DEBUG) Slog.d(TAG, "cancel: mTarget=" + mTarget); mSkipAnimationEnd = true; super.cancel(); } /** Returns true if the animation target is the same as the input bounds. */ /** * @return true if the animation target is the same as the input bounds. */ boolean isAnimatingTo(Rect bounds) { return mTo.equals(bounds); } private boolean animatingToLargerSize() { if (mFrom.width() * mFrom.height() > mTo.width() * mTo.height()) { return false; } return true; } private void finishAnimation() { if (DEBUG) Slog.d(TAG, "finishAnimation: mTarget=" + mTarget + " callers" + Debug.getCallers(2)); if (!mSkipAnimationEnd) { mTarget.onAnimationEnd(); } removeListener(this); removeUpdateListener(this); mRunningAnimations.remove(mTarget); /** * @return true if we are animating to a larger surface size */ @VisibleForTesting boolean animatingToLargerSize() { // TODO: Fix this check for aspect ratio changes return (mFrom.width() * mFrom.height() <= mTo.width() * mTo.height()); } @Override Loading @@ -291,63 +309,23 @@ public class BoundsAnimationController { } } public interface AnimateBoundsUser { /** * Sets the size of the target (without any intermediate steps, like scheduling animation) * but freezes the bounds of any tasks in the target at taskBounds, * to allow for more flexibility during resizing. Only works for the pinned stack at the * moment. * * @return Whether the target should continue to be animated and this call was successful. * If false, the animation will be cancelled because the user has determined that the * animation is now invalid and not required. In such a case, the cancel will trigger the * animation end callback as well, but will not send any further size changes. */ boolean setPinnedStackSize(Rect bounds, Rect taskBounds); /** * Callback for the target to inform it that the animation has started, so it can do some * necessary preparation. */ void onAnimationStart(boolean toFullscreen); /** * Callback for the target to inform it that the animation is going to a new fullscreen * state and should update the picture-in-picture mode accordingly. * * @param targetStackBounds the target stack bounds we are animating to, can be null if * we are animating to fullscreen */ void updatePictureInPictureMode(Rect targetStackBounds); /** * Callback for the target to inform it that the animation has ended, so it can do some * necessary cleanup. */ void onAnimationEnd(); /** * Callback for the target to inform it to reparent to the fullscreen stack. */ void moveToFullscreen(); } public void animateBounds(final AnimateBoundsUser target, Rect from, Rect to, int animationDuration, boolean moveToFullscreen) { animateBoundsImpl(target, from, to, animationDuration, moveToFullscreen); public void animateBounds(final BoundsAnimationTarget target, Rect from, Rect to, int animationDuration, @SchedulePipModeChangedState int schedulePipModeChangedState, boolean moveToFullscreen) { animateBoundsImpl(target, from, to, animationDuration, schedulePipModeChangedState, moveToFullscreen); } @VisibleForTesting BoundsAnimator animateBoundsImpl(final AnimateBoundsUser target, Rect from, Rect to, int animationDuration, boolean moveToFullscreen) { BoundsAnimator animateBoundsImpl(final BoundsAnimationTarget target, Rect from, Rect to, int animationDuration, @SchedulePipModeChangedState int schedulePipModeChangedState, boolean moveToFullscreen) { final BoundsAnimator existing = mRunningAnimations.get(target); final boolean replacing = existing != null; final boolean animatingToNewFullscreenState = (existing == null) || (existing.mMoveToFullScreen != moveToFullscreen); if (DEBUG) Slog.d(TAG, "animateBounds: target=" + target + " from=" + from + " to=" + to + " moveToFullscreen=" + moveToFullscreen + " replacing=" + replacing + " animatingToNewFullscreenState=" + animatingToNewFullscreenState); + " schedulePipModeChangedState=" + schedulePipModeChangedState + " replacing=" + replacing); if (replacing) { if (existing.isAnimatingTo(to)) { Loading @@ -355,15 +333,36 @@ public class BoundsAnimationController { // one we are trying to start. if (DEBUG) Slog.d(TAG, "animateBounds: same destination as existing=" + existing + " ignoring..."); return existing; } // Since we are replacing, we skip both animation start and end callbacks, and don't // animate to the final bounds when cancelling existing.prepareCancel(true /* skipAnimationEnd */, true /* skipFinalResize */); // Update the PiP callback states if we are replacing the animation if (existing.mSchedulePipModeChangedState == SCHEDULE_PIP_MODE_CHANGED_ON_START) { if (schedulePipModeChangedState == SCHEDULE_PIP_MODE_CHANGED_ON_START) { if (DEBUG) Slog.d(TAG, "animateBounds: still animating to fullscreen, keep" + " existing deferred state"); } else { if (DEBUG) Slog.d(TAG, "animateBounds: fullscreen animation canceled, callback" + " on start already processed, schedule deferred update on end"); schedulePipModeChangedState = SCHEDULE_PIP_MODE_CHANGED_ON_END; } } else if (existing.mSchedulePipModeChangedState == SCHEDULE_PIP_MODE_CHANGED_ON_END) { if (schedulePipModeChangedState == SCHEDULE_PIP_MODE_CHANGED_ON_START) { if (DEBUG) Slog.d(TAG, "animateBounds: non-fullscreen animation canceled," + " callback on start will be processed"); } else { if (DEBUG) Slog.d(TAG, "animateBounds: still animating from fullscreen, keep" + " existing deferred state"); schedulePipModeChangedState = SCHEDULE_PIP_MODE_CHANGED_ON_END; } } // Since we are replacing, we skip both animation start and end callbacks existing.cancel(); } final BoundsAnimator animator = new BoundsAnimator(target, from, to, moveToFullscreen, replacing, animatingToNewFullscreenState); final BoundsAnimator animator = new BoundsAnimator(target, from, to, schedulePipModeChangedState, moveToFullscreen, replacing); mRunningAnimations.put(target, animator); animator.setFloatValues(0f, 1f); animator.setDuration((animationDuration != -1 ? animationDuration Loading Loading
services/core/java/com/android/server/am/ActivityManagerService.java +3 −2 Original line number Diff line number Diff line Loading @@ -10535,8 +10535,9 @@ public class ActivityManagerService extends IActivityManager.Stub final PinnedActivityStack pinnedStack = mStackSupervisor.getStack(PINNED_STACK_ID); if (pinnedStack != null) { pinnedStack.animateResizePinnedStack(null /* sourceBounds */, destBounds, animationDuration); pinnedStack.animateResizePinnedStack(null /* sourceHintBounds */, destBounds, animationDuration, false /* schedulePipModeChangedOnAnimationEnd */); } } else { throw new IllegalArgumentException("Stack: " + stackId
services/core/java/com/android/server/am/ActivityStack.java +7 −0 Original line number Diff line number Diff line Loading @@ -588,6 +588,13 @@ class ActivityStack<T extends StackWindowController> extends ConfigurationContai true /* includingParents */); } /** * Returns whether to defer the scheduling of the multi-window mode. */ boolean deferScheduleMultiWindowModeChanged() { return false; } /** * Defers updating the bounds of the stack. If the stack was resized/repositioned while * deferring, the bounds will update in {@link #continueUpdateBounds()}. Loading
services/core/java/com/android/server/am/ActivityStackSupervisor.java +10 −7 Original line number Diff line number Diff line Loading @@ -2504,7 +2504,7 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D // incorrect if AMS.resizeStackWithBoundsFromWindowManager() is already called while waiting // for the AMS lock to be freed. So check and make sure these bounds are still good. final PinnedStackWindowController stackController = stack.getWindowContainerController(); if (stackController.pinnedStackResizeAllowed()) { if (stackController.pinnedStackResizeDisallowed()) { return; } Loading Loading @@ -2873,7 +2873,7 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D return true; } void moveActivityToPinnedStackLocked(ActivityRecord r, Rect sourceBounds, float aspectRatio, void moveActivityToPinnedStackLocked(ActivityRecord r, Rect sourceHintBounds, float aspectRatio, boolean moveHomeStackToFront, String reason) { mWindowManager.deferSurfaceLayout(); Loading Loading @@ -2948,11 +2948,8 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D final Rect destBounds = mWindowManager.getPictureInPictureBounds(DEFAULT_DISPLAY, aspectRatio, false /* useExistingStackBounds */); // TODO(b/36099777): Schedule the PiP mode change here immediately until we can defer all // callbacks until after the bounds animation scheduleUpdatePictureInPictureModeIfNeeded(r.getTask(), destBounds, true /* immediate */); stack.animateResizePinnedStack(sourceBounds, destBounds, -1 /* animationDuration */); stack.animateResizePinnedStack(sourceHintBounds, destBounds, -1 /* animationDuration */, true /* schedulePipModeChangedOnAnimationEnd */); mService.mTaskChangeNotificationController.notifyActivityPinned(r.packageName); } Loading Loading @@ -4179,6 +4176,12 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D } void scheduleUpdateMultiWindowMode(TaskRecord task) { // If the stack is animating in a way where we will be forcing a multi-mode change at the // end, then ensure that we defer all in between multi-window mode changes if (task.getStack().deferScheduleMultiWindowModeChanged()) { return; } for (int i = task.mActivities.size() - 1; i >= 0; i--) { final ActivityRecord r = task.mActivities.get(i); if (r.app != null && r.app.thread != null) { Loading
services/core/java/com/android/server/am/PinnedActivityStack.java +16 −4 Original line number Diff line number Diff line Loading @@ -43,9 +43,10 @@ class PinnedActivityStack extends ActivityStack<PinnedStackWindowController> return new PinnedStackWindowController(mStackId, this, displayId, onTop, outBounds); } void animateResizePinnedStack(Rect sourceBounds, Rect destBounds, int animationDuration) { getWindowContainerController().animateResizePinnedStack(sourceBounds, destBounds, animationDuration); void animateResizePinnedStack(Rect sourceHintBounds, Rect toBounds, int animationDuration, boolean schedulePipModeChangedOnAnimationEnd) { getWindowContainerController().animateResizePinnedStack(toBounds, sourceHintBounds, animationDuration, schedulePipModeChangedOnAnimationEnd); } void setPictureInPictureAspectRatio(float aspectRatio) { Loading @@ -60,7 +61,18 @@ class PinnedActivityStack extends ActivityStack<PinnedStackWindowController> return getWindowContainerController().isAnimatingBoundsToFullscreen(); } @Override /** * Returns whether to defer the scheduling of the multi-window mode. */ boolean deferScheduleMultiWindowModeChanged() { // For the pinned stack, the deferring of the multi-window mode changed is tied to the // transition animation into picture-in-picture, and is called once the animation completes, // or is interrupted in a way that would leave the stack in a non-fullscreen state. // @see BoundsAnimationController // @see BoundsAnimationControllerTests return mWindowContainerController.deferScheduleMultiWindowModeChanged(); } public void updatePictureInPictureModeForPinnedStackAnimation(Rect targetStackBounds) { // It is guaranteed that the activities requiring the update will be in the pinned stack at // this point (either reparented before the animation into PiP, or before reparenting after Loading
services/core/java/com/android/server/wm/BoundsAnimationController.java +115 −116 Original line number Diff line number Diff line Loading @@ -22,6 +22,7 @@ import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM; import android.animation.Animator; import android.animation.ValueAnimator; import android.annotation.IntDef; import android.content.Context; import android.graphics.Rect; import android.os.Handler; Loading @@ -35,6 +36,9 @@ import android.view.WindowManagerInternal; import com.android.internal.annotations.VisibleForTesting; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; /** * Enables animating bounds of objects. * Loading @@ -43,7 +47,7 @@ import com.android.internal.annotations.VisibleForTesting; * relaunching it would cause poorer experience), these class provides a way to directly animate * the bounds of the resized object. * * The object that is resized needs to implement {@link AnimateBoundsUser} interface. * The object that is resized needs to implement {@link BoundsAnimationTarget} interface. * * NOTE: All calls to methods in this class should be done on the UI thread */ Loading @@ -56,8 +60,19 @@ public class BoundsAnimationController { private static final int DEFAULT_TRANSITION_DURATION = 425; @Retention(RetentionPolicy.SOURCE) @IntDef({NO_PIP_MODE_CHANGED_CALLBACKS, SCHEDULE_PIP_MODE_CHANGED_ON_START, SCHEDULE_PIP_MODE_CHANGED_ON_END}) public @interface SchedulePipModeChangedState {} /** Do not schedule any PiP mode changed callbacks as a part of this animation. */ public static final int NO_PIP_MODE_CHANGED_CALLBACKS = 0; /** Schedule a PiP mode changed callback when this animation starts. */ public static final int SCHEDULE_PIP_MODE_CHANGED_ON_START = 1; /** Schedule a PiP mode changed callback when this animation ends. */ public static final int SCHEDULE_PIP_MODE_CHANGED_ON_END = 2; // Only accessed on UI thread. private ArrayMap<AnimateBoundsUser, BoundsAnimator> mRunningAnimations = new ArrayMap<>(); private ArrayMap<BoundsAnimationTarget, BoundsAnimator> mRunningAnimations = new ArrayMap<>(); private final class AppTransitionNotifier extends WindowManagerInternal.AppTransitionListener implements Runnable { Loading Loading @@ -108,40 +123,42 @@ public class BoundsAnimationController { @VisibleForTesting final class BoundsAnimator extends ValueAnimator implements ValueAnimator.AnimatorUpdateListener, ValueAnimator.AnimatorListener { private final AnimateBoundsUser mTarget; private final BoundsAnimationTarget mTarget; private final Rect mFrom = new Rect(); private final Rect mTo = new Rect(); private final Rect mTmpRect = new Rect(); private final Rect mTmpTaskBounds = new Rect(); private final boolean mMoveToFullScreen; // True if this this animation was cancelled and will be replaced the another animation from // the same {@link #AnimateBoundsUser} target. // True if this this animation was canceled and will be replaced the another animation from // the same {@link #BoundsAnimationTarget} target. private boolean mSkipFinalResize; // True if this animation replaced a previous animation of the same // {@link #AnimateBoundsUser} target. // {@link #BoundsAnimationTarget} target. private final boolean mSkipAnimationStart; // True if this animation was cancelled by the user, not as a part of a replacing animation // True if this animation was canceled by the user, not as a part of a replacing animation private boolean mSkipAnimationEnd; // True if this animation is not replacing a previous animation, or if the previous // animation is animating to a different fullscreen state than the current animation. // We use this to ensure that we always provide a consistent set/order of callbacks when we // transition to/from PiP. private final boolean mAnimatingToNewFullscreenState; // True if the animation target should be moved to the fullscreen stack at the end of this // animation private boolean mMoveToFullscreen; // Whether to schedule PiP mode changes on animation start/end private @SchedulePipModeChangedState int mSchedulePipModeChangedState; // Depending on whether we are animating from // a smaller to a larger size private final int mFrozenTaskWidth; private final int mFrozenTaskHeight; BoundsAnimator(AnimateBoundsUser target, Rect from, Rect to, boolean moveToFullScreen, boolean replacingExistingAnimation, boolean animatingToNewFullscreenState) { BoundsAnimator(BoundsAnimationTarget target, Rect from, Rect to, @SchedulePipModeChangedState int schedulePipModeChangedState, boolean moveToFullscreen, boolean replacingExistingAnimation) { super(); mTarget = target; mFrom.set(from); mTo.set(to); mMoveToFullScreen = moveToFullScreen; mSkipAnimationStart = replacingExistingAnimation; mAnimatingToNewFullscreenState = animatingToNewFullscreenState; mSchedulePipModeChangedState = schedulePipModeChangedState; mMoveToFullscreen = moveToFullscreen; addUpdateListener(this); addListener(this); Loading @@ -161,7 +178,8 @@ public class BoundsAnimationController { @Override public void onAnimationStart(Animator animation) { if (DEBUG) Slog.d(TAG, "onAnimationStart: mTarget=" + mTarget + " mSkipAnimationStart=" + mSkipAnimationStart); + " mSkipAnimationStart=" + mSkipAnimationStart + " mSchedulePipModeChangedState=" + mSchedulePipModeChangedState); mFinishAnimationAfterTransition = false; mTmpRect.set(mFrom.left, mFrom.top, mFrom.left + mFrozenTaskWidth, mFrom.top + mFrozenTaskHeight); Loading @@ -170,13 +188,8 @@ public class BoundsAnimationController { // we trigger any size changes, so it can swap surfaces // in to appropriate modes, or do as it wishes otherwise. if (!mSkipAnimationStart) { mTarget.onAnimationStart(mMoveToFullScreen); } // If we are animating to a new fullscreen state (either to/from fullscreen), then // notify the target of the change with the new frozen task bounds if (mAnimatingToNewFullscreenState && mMoveToFullScreen) { mTarget.updatePictureInPictureMode(null); mTarget.onAnimationStart(mSchedulePipModeChangedState == SCHEDULE_PIP_MODE_CHANGED_ON_START); } // Immediately update the task bounds if they have to become larger, but preserve Loading Loading @@ -206,20 +219,26 @@ public class BoundsAnimationController { // any further animation. if (DEBUG) Slog.d(TAG, "animateUpdate: cancelled"); // If we have already scheduled a PiP mode changed at the start of the animation, // then we need to clean up and schedule one at the end, since we have canceled the // animation to the final state. if (mSchedulePipModeChangedState == SCHEDULE_PIP_MODE_CHANGED_ON_START) { mSchedulePipModeChangedState = SCHEDULE_PIP_MODE_CHANGED_ON_END; } // Since we are cancelling immediately without a replacement animation, send the // animation end to maintain callback parity, but also skip any further resizes prepareCancel(false /* skipAnimationEnd */, true /* skipFinalResize */); cancel(); cancelAndCallAnimationEnd(); } } @Override public void onAnimationEnd(Animator animation) { if (DEBUG) Slog.d(TAG, "onAnimationEnd: mTarget=" + mTarget + " mMoveToFullScreen=" + mMoveToFullScreen + " mSkipFinalResize=" + mSkipFinalResize + " mFinishAnimationAfterTransition=" + mFinishAnimationAfterTransition + " mAppTransitionIsRunning=" + mAppTransition.isRunning()); + " mAppTransitionIsRunning=" + mAppTransition.isRunning() + " callers=" + Debug.getCallers(2)); // There could be another animation running. For example in the // move to fullscreen case, recents will also be closing while the Loading @@ -231,58 +250,57 @@ public class BoundsAnimationController { return; } if (!mSkipFinalResize) { // If not cancelled, resize the pinned stack to the final size. All calls to // setPinnedStackSize() must be done between onAnimationStart() and onAnimationEnd() mTarget.setPinnedStackSize(mTo, null); if (!mSkipAnimationEnd) { // If this animation has already scheduled the picture-in-picture mode on start, and // we are not skipping the final resize due to being canceled, then move the PiP to // fullscreen once the animation ends if (DEBUG) Slog.d(TAG, "onAnimationEnd: mTarget=" + mTarget + " moveToFullscreen=" + mMoveToFullscreen); mTarget.onAnimationEnd(mSchedulePipModeChangedState == SCHEDULE_PIP_MODE_CHANGED_ON_END, !mSkipFinalResize ? mTo : null, mMoveToFullscreen); } finishAnimation(); if (mMoveToFullScreen && !mSkipFinalResize) { mTarget.moveToFullscreen(); } // Clean up this animation removeListener(this); removeUpdateListener(this); mRunningAnimations.remove(mTarget); } @Override public void onAnimationCancel(Animator animation) { finishAnimation(); // Always skip the final resize when the animation is canceled mSkipFinalResize = true; mMoveToFullscreen = false; } public void prepareCancel(boolean skipAnimationEnd, boolean skipFinalResize) { if (DEBUG) Slog.d(TAG, "prepareCancel: skipAnimationEnd=" + skipAnimationEnd + " skipFinalResize=" + skipFinalResize); mSkipAnimationEnd = skipAnimationEnd; mSkipFinalResize = skipFinalResize; private void cancelAndCallAnimationEnd() { if (DEBUG) Slog.d(TAG, "cancelAndCallAnimationEnd: mTarget=" + mTarget); mSkipAnimationEnd = false; super.cancel(); } @Override public void cancel() { if (DEBUG) Slog.d(TAG, "cancel: mTarget=" + mTarget); mSkipAnimationEnd = true; super.cancel(); } /** Returns true if the animation target is the same as the input bounds. */ /** * @return true if the animation target is the same as the input bounds. */ boolean isAnimatingTo(Rect bounds) { return mTo.equals(bounds); } private boolean animatingToLargerSize() { if (mFrom.width() * mFrom.height() > mTo.width() * mTo.height()) { return false; } return true; } private void finishAnimation() { if (DEBUG) Slog.d(TAG, "finishAnimation: mTarget=" + mTarget + " callers" + Debug.getCallers(2)); if (!mSkipAnimationEnd) { mTarget.onAnimationEnd(); } removeListener(this); removeUpdateListener(this); mRunningAnimations.remove(mTarget); /** * @return true if we are animating to a larger surface size */ @VisibleForTesting boolean animatingToLargerSize() { // TODO: Fix this check for aspect ratio changes return (mFrom.width() * mFrom.height() <= mTo.width() * mTo.height()); } @Override Loading @@ -291,63 +309,23 @@ public class BoundsAnimationController { } } public interface AnimateBoundsUser { /** * Sets the size of the target (without any intermediate steps, like scheduling animation) * but freezes the bounds of any tasks in the target at taskBounds, * to allow for more flexibility during resizing. Only works for the pinned stack at the * moment. * * @return Whether the target should continue to be animated and this call was successful. * If false, the animation will be cancelled because the user has determined that the * animation is now invalid and not required. In such a case, the cancel will trigger the * animation end callback as well, but will not send any further size changes. */ boolean setPinnedStackSize(Rect bounds, Rect taskBounds); /** * Callback for the target to inform it that the animation has started, so it can do some * necessary preparation. */ void onAnimationStart(boolean toFullscreen); /** * Callback for the target to inform it that the animation is going to a new fullscreen * state and should update the picture-in-picture mode accordingly. * * @param targetStackBounds the target stack bounds we are animating to, can be null if * we are animating to fullscreen */ void updatePictureInPictureMode(Rect targetStackBounds); /** * Callback for the target to inform it that the animation has ended, so it can do some * necessary cleanup. */ void onAnimationEnd(); /** * Callback for the target to inform it to reparent to the fullscreen stack. */ void moveToFullscreen(); } public void animateBounds(final AnimateBoundsUser target, Rect from, Rect to, int animationDuration, boolean moveToFullscreen) { animateBoundsImpl(target, from, to, animationDuration, moveToFullscreen); public void animateBounds(final BoundsAnimationTarget target, Rect from, Rect to, int animationDuration, @SchedulePipModeChangedState int schedulePipModeChangedState, boolean moveToFullscreen) { animateBoundsImpl(target, from, to, animationDuration, schedulePipModeChangedState, moveToFullscreen); } @VisibleForTesting BoundsAnimator animateBoundsImpl(final AnimateBoundsUser target, Rect from, Rect to, int animationDuration, boolean moveToFullscreen) { BoundsAnimator animateBoundsImpl(final BoundsAnimationTarget target, Rect from, Rect to, int animationDuration, @SchedulePipModeChangedState int schedulePipModeChangedState, boolean moveToFullscreen) { final BoundsAnimator existing = mRunningAnimations.get(target); final boolean replacing = existing != null; final boolean animatingToNewFullscreenState = (existing == null) || (existing.mMoveToFullScreen != moveToFullscreen); if (DEBUG) Slog.d(TAG, "animateBounds: target=" + target + " from=" + from + " to=" + to + " moveToFullscreen=" + moveToFullscreen + " replacing=" + replacing + " animatingToNewFullscreenState=" + animatingToNewFullscreenState); + " schedulePipModeChangedState=" + schedulePipModeChangedState + " replacing=" + replacing); if (replacing) { if (existing.isAnimatingTo(to)) { Loading @@ -355,15 +333,36 @@ public class BoundsAnimationController { // one we are trying to start. if (DEBUG) Slog.d(TAG, "animateBounds: same destination as existing=" + existing + " ignoring..."); return existing; } // Since we are replacing, we skip both animation start and end callbacks, and don't // animate to the final bounds when cancelling existing.prepareCancel(true /* skipAnimationEnd */, true /* skipFinalResize */); // Update the PiP callback states if we are replacing the animation if (existing.mSchedulePipModeChangedState == SCHEDULE_PIP_MODE_CHANGED_ON_START) { if (schedulePipModeChangedState == SCHEDULE_PIP_MODE_CHANGED_ON_START) { if (DEBUG) Slog.d(TAG, "animateBounds: still animating to fullscreen, keep" + " existing deferred state"); } else { if (DEBUG) Slog.d(TAG, "animateBounds: fullscreen animation canceled, callback" + " on start already processed, schedule deferred update on end"); schedulePipModeChangedState = SCHEDULE_PIP_MODE_CHANGED_ON_END; } } else if (existing.mSchedulePipModeChangedState == SCHEDULE_PIP_MODE_CHANGED_ON_END) { if (schedulePipModeChangedState == SCHEDULE_PIP_MODE_CHANGED_ON_START) { if (DEBUG) Slog.d(TAG, "animateBounds: non-fullscreen animation canceled," + " callback on start will be processed"); } else { if (DEBUG) Slog.d(TAG, "animateBounds: still animating from fullscreen, keep" + " existing deferred state"); schedulePipModeChangedState = SCHEDULE_PIP_MODE_CHANGED_ON_END; } } // Since we are replacing, we skip both animation start and end callbacks existing.cancel(); } final BoundsAnimator animator = new BoundsAnimator(target, from, to, moveToFullscreen, replacing, animatingToNewFullscreenState); final BoundsAnimator animator = new BoundsAnimator(target, from, to, schedulePipModeChangedState, moveToFullscreen, replacing); mRunningAnimations.put(target, animator); animator.setFloatValues(0f, 1f); animator.setDuration((animationDuration != -1 ? animationDuration Loading