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
}
/**
+ * 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()}.
*/
// 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;
}
return true;
}
- void moveActivityToPinnedStackLocked(ActivityRecord r, Rect sourceBounds, float aspectRatio,
+ void moveActivityToPinnedStackLocked(ActivityRecord r, Rect sourceHintBounds, float aspectRatio,
boolean moveHomeStackToFront, String reason) {
mWindowManager.deferSurfaceLayout();
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);
}
}
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) {
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) {
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
import android.animation.Animator;
import android.animation.ValueAnimator;
+import android.annotation.IntDef;
import android.content.Context;
import android.graphics.Rect;
import android.os.Handler;
import com.android.internal.annotations.VisibleForTesting;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
/**
* Enables animating bounds of objects.
*
* 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
*/
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 {
@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);
@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);
// 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
// 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
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
}
}
- 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)) {
// 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
--- /dev/null
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wm;
+
+import android.graphics.Rect;
+
+/**
+ * The target for a BoundsAnimation.
+ * @see BoundsAnimationController
+ */
+interface BoundsAnimationTarget {
+
+ /**
+ * Callback for the target to inform it that the animation has started, so it can do some
+ * necessary preparation.
+ *
+ * @param schedulePipModeChangedCallback whether or not to schedule the PiP mode changed
+ * callbacks
+ */
+ void onAnimationStart(boolean schedulePipModeChangedCallback);
+
+ /**
+ * 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. This will
+ * only be called between onAnimationStart() and onAnimationEnd().
+ *
+ * @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 stackBounds, Rect taskBounds);
+
+ /**
+ * Callback for the target to inform it that the animation has ended, so it can do some
+ * necessary cleanup.
+ *
+ * @param schedulePipModeChangedCallback whether or not to schedule the PiP mode changed
+ * callbacks
+ * @param finalStackSize the final stack bounds to set on the target (can be to indicate that
+ * the animation was cancelled and the target does not need to update to the final stack bounds)
+ * @param moveToFullscreen whether or the target should reparent itself to the fullscreen stack
+ * when the animation completes
+ */
+ void onAnimationEnd(boolean schedulePipModeChangedCallback, Rect finalStackSize,
+ boolean moveToFullscreen);
+}
package com.android.server.wm;
import static android.app.ActivityManager.StackId.FULLSCREEN_WORKSPACE_STACK_ID;
+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;
+import static com.android.server.wm.BoundsAnimationController.SchedulePipModeChangedState;
import android.app.RemoteAction;
import android.graphics.Rect;
/**
* Animates the pinned stack.
*/
- public void animateResizePinnedStack(Rect sourceBounds, Rect destBounds,
- int animationDuration) {
+ public void animateResizePinnedStack(Rect toBounds, Rect sourceHintBounds,
+ int animationDuration, boolean schedulePipModeChangedOnAnimationEnd) {
synchronized (mWindowMap) {
if (mContainer == null) {
throw new IllegalArgumentException("Pinned stack container not found :(");
}
- // Get non-null fullscreen bounds if the bounds are null
- final boolean moveToFullscreen = destBounds == null;
- destBounds = getPinnedStackAnimationBounds(destBounds);
-
- // If the bounds are truly null, then there was no fullscreen stack at this time, so
- // animate this to the full display bounds
- final Rect toBounds;
- if (destBounds == null) {
- toBounds = new Rect();
- mContainer.getDisplayContent().getLogicalDisplayRect(toBounds);
- } else {
- toBounds = destBounds;
+ // Get the from-bounds
+ final Rect fromBounds = new Rect();
+ mContainer.getBounds(fromBounds);
+
+ // Get non-null fullscreen to-bounds for animating if the bounds are null
+ @SchedulePipModeChangedState int schedulePipModeChangedState =
+ NO_PIP_MODE_CHANGED_CALLBACKS;
+ final boolean toFullscreen = toBounds == null;
+ if (toFullscreen) {
+ if (schedulePipModeChangedOnAnimationEnd) {
+ throw new IllegalArgumentException("Should not defer scheduling PiP mode"
+ + " change on animation to fullscreen.");
+ }
+ schedulePipModeChangedState = SCHEDULE_PIP_MODE_CHANGED_ON_START;
+
+ mService.getStackBounds(FULLSCREEN_WORKSPACE_STACK_ID, mTmpBoundsRect);
+ if (!mTmpBoundsRect.isEmpty()) {
+ // If there is a fullscreen bounds, use that
+ toBounds = new Rect(mTmpBoundsRect);
+ } else {
+ // Otherwise, use the display bounds
+ toBounds = new Rect();
+ mContainer.getDisplayContent().getLogicalDisplayRect(toBounds);
+ }
+ } else if (schedulePipModeChangedOnAnimationEnd) {
+ schedulePipModeChangedState = SCHEDULE_PIP_MODE_CHANGED_ON_END;
}
- final Rect originalBounds = new Rect();
- mContainer.getBounds(originalBounds);
- mContainer.setAnimationFinalBounds(sourceBounds, toBounds);
+ mContainer.setAnimationFinalBounds(sourceHintBounds, toBounds, toFullscreen);
+
+ final Rect finalToBounds = toBounds;
+ final @SchedulePipModeChangedState int finalSchedulePipModeChangedState =
+ schedulePipModeChangedState;
UiThread.getHandler().post(() -> {
if (mContainer == null) {
return;
}
- mService.mBoundsAnimationController.animateBounds(mContainer, originalBounds,
- toBounds, animationDuration, moveToFullscreen);
+ mService.mBoundsAnimationController.animateBounds(mContainer, fromBounds,
+ finalToBounds, animationDuration, finalSchedulePipModeChangedState,
+ toFullscreen);
});
}
}
if (Float.compare(aspectRatio, pinnedStackController.getAspectRatio()) != 0) {
if (!toBounds.equals(targetBounds)) {
- animateResizePinnedStack(null /* sourceBounds */, toBounds, -1 /* duration */);
+ animateResizePinnedStack(toBounds, null /* sourceHintBounds */,
+ -1 /* duration */, false /* schedulePipModeChangedOnAnimationEnd */);
}
pinnedStackController.setAspectRatio(
pinnedStackController.isValidPictureInPictureAspectRatio(aspectRatio)
}
/**
- * @return whether the bounds are currently animating to fullscreen.
+ * @return whether the multi-window mode change should be deferred as a part of a transition
+ * from fullscreen to non-fullscreen bounds.
*/
- public boolean isAnimatingBoundsToFullscreen() {
- return mContainer.isAnimatingBoundsToFullscreen();
+ public boolean deferScheduleMultiWindowModeChanged() {
+ synchronized(mWindowMap) {
+ return mContainer.deferScheduleMultiWindowModeChanged();
+ }
}
- public boolean pinnedStackResizeAllowed() {
- return mContainer.pinnedStackResizeAllowed();
+ /**
+ * @return whether the bounds are currently animating to fullscreen.
+ */
+ public boolean isAnimatingBoundsToFullscreen() {
+ synchronized (mWindowMap) {
+ return mContainer.isAnimatingBoundsToFullscreen();
+ }
}
/**
- * Checks the {@param bounds} and retirms non-null fullscreen bounds for the pinned stack
- * animation if necessary.
+ * @return whether the stack can be resized from the bounds animation.
*/
- private Rect getPinnedStackAnimationBounds(Rect bounds) {
- mService.getStackBounds(FULLSCREEN_WORKSPACE_STACK_ID, mTmpBoundsRect);
- if (bounds == null && !mTmpBoundsRect.isEmpty()) {
- bounds = new Rect(mTmpBoundsRect);
+ public boolean pinnedStackResizeDisallowed() {
+ synchronized (mWindowMap) {
+ return mContainer.pinnedStackResizeDisallowed();
}
- return bounds;
}
/**
import java.io.PrintWriter;
public class TaskStack extends WindowContainer<Task> implements DimLayer.DimLayerUser,
- BoundsAnimationController.AnimateBoundsUser {
+ BoundsAnimationTarget {
/** Minimum size of an adjusted stack bounds relative to original stack bounds. Used to
* restrict IME adjustment so that a min portion of top stack remains visible.*/
private static final float ADJUSTED_STACK_FRACTION_MIN = 0.3f;
private boolean mBoundsAnimatingToFullscreen = false;
private boolean mCancelCurrentBoundsAnimation = false;
private Rect mBoundsAnimationTarget = new Rect();
- private Rect mBoundsAnimationSourceBounds = new Rect();
+ private Rect mBoundsAnimationSourceHintBounds = new Rect();
// Temporary storage for the new bounds that should be used after the configuration change.
// Will be cleared once the client retrieves the new bounds via getBoundsForNewConfiguration().
* Sets the bounds animation target bounds ahead of an animation. This can't currently be done
* in onAnimationStart() since that is started on the UiThread.
*/
- void setAnimationFinalBounds(Rect sourceBounds, Rect destBounds) {
+ void setAnimationFinalBounds(Rect sourceHintBounds, Rect destBounds, boolean toFullscreen) {
mBoundsAnimatingRequested = true;
- if (sourceBounds != null) {
- mBoundsAnimationSourceBounds.set(sourceBounds);
- } else {
- mBoundsAnimationSourceBounds.setEmpty();
- }
+ mBoundsAnimatingToFullscreen = toFullscreen;
if (destBounds != null) {
mBoundsAnimationTarget.set(destBounds);
} else {
mBoundsAnimationTarget.setEmpty();
}
+ if (sourceHintBounds != null) {
+ mBoundsAnimationSourceHintBounds.set(sourceHintBounds);
+ } else {
+ mBoundsAnimationSourceHintBounds.setEmpty();
+ }
}
/**
/**
* @return the final source bounds for the bounds animation.
*/
- void getFinalAnimationSourceBounds(Rect outBounds) {
- outBounds.set(mBoundsAnimationSourceBounds);
+ void getFinalAnimationSourceHintBounds(Rect outBounds) {
+ outBounds.set(mBoundsAnimationSourceHintBounds);
}
/**
// orientation, clear the animation target bounds since they are obsolete, and
// cancel any currently running animations
mBoundsAnimationTarget.setEmpty();
- mBoundsAnimationSourceBounds.setEmpty();
+ mBoundsAnimationSourceHintBounds.setEmpty();
mCancelCurrentBoundsAnimation = true;
return true;
}
}
}
- public boolean setPinnedStackSize(Rect bounds, Rect tempTaskBounds) {
- if (mCancelCurrentBoundsAnimation) {
- return false;
+ public boolean setPinnedStackSize(Rect stackBounds, Rect tempTaskBounds) {
+ // Hold the lock since this is called from the BoundsAnimator running on the UiThread
+ synchronized (mService.mWindowMap) {
+ if (mCancelCurrentBoundsAnimation) {
+ return false;
+ }
}
try {
- mService.mActivityManager.resizePinnedStack(bounds, tempTaskBounds);
+ mService.mActivityManager.resizePinnedStack(stackBounds, tempTaskBounds);
} catch (RemoteException e) {
// I don't believe you.
}
}
@Override // AnimatesBounds
- public void onAnimationStart(boolean toFullscreen) {
+ public void onAnimationStart(boolean schedulePipModeChangedCallback) {
+ // Hold the lock since this is called from the BoundsAnimator running on the UiThread
synchronized (mService.mWindowMap) {
mBoundsAnimatingRequested = false;
mBoundsAnimating = true;
- mBoundsAnimatingToFullscreen = toFullscreen;
mCancelCurrentBoundsAnimation = false;
}
} catch (RemoteException e) {
// I don't believe you...
}
- }
- }
- @Override // AnimatesBounds
- public void updatePictureInPictureMode(Rect targetStackBounds) {
- final PinnedStackWindowController controller =
- (PinnedStackWindowController) getController();
- if (controller != null) {
- controller.updatePictureInPictureModeForPinnedStackAnimation(targetStackBounds);
+ final PinnedStackWindowController controller =
+ (PinnedStackWindowController) getController();
+ if (schedulePipModeChangedCallback && controller != null) {
+ // We need to schedule the PiP mode change after the animation down, so use the
+ // final bounds
+ controller.updatePictureInPictureModeForPinnedStackAnimation(null);
+ }
}
}
@Override // AnimatesBounds
- public void onAnimationEnd() {
+ public void onAnimationEnd(boolean schedulePipModeChangedCallback, Rect finalStackSize,
+ boolean moveToFullscreen) {
+ // Hold the lock since this is called from the BoundsAnimator running on the UiThread
synchronized (mService.mWindowMap) {
mBoundsAnimating = false;
mService.requestTraversal();
}
if (mStackId == PINNED_STACK_ID) {
+ final PinnedStackWindowController controller =
+ (PinnedStackWindowController) getController();
+ if (schedulePipModeChangedCallback && controller != null) {
+ // We need to schedule the PiP mode change after the animation down, so use the
+ // final bounds
+ controller.updatePictureInPictureModeForPinnedStackAnimation(
+ mBoundsAnimationTarget);
+ }
+
+ // 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
+ if (finalStackSize != null) {
+ setPinnedStackSize(finalStackSize, null);
+ }
+
try {
mService.mActivityManager.notifyPinnedStackAnimationEnded();
+ if (moveToFullscreen) {
+ mService.mActivityManager.moveTasksToFullscreenStack(mStackId,
+ true /* onTop */);
+ }
} catch (RemoteException e) {
// I don't believe you...
}
}
}
- @Override
- public void moveToFullscreen() {
- try {
- mService.mActivityManager.moveTasksToFullscreenStack(mStackId, true);
- } catch (RemoteException e) {
- e.printStackTrace();
+ /**
+ * @return True if we are currently animating the pinned stack from fullscreen to non-fullscreen
+ * bounds and we have a deferred PiP mode changed callback set with the animation.
+ */
+ public boolean deferScheduleMultiWindowModeChanged() {
+ if (mStackId == PINNED_STACK_ID) {
+ return (mBoundsAnimatingRequested || mBoundsAnimating);
}
+ return false;
}
public boolean hasMovementAnimations() {
return mBoundsAnimating && mBoundsAnimatingToFullscreen;
}
- public boolean pinnedStackResizeAllowed() {
+ public boolean pinnedStackResizeDisallowed() {
if (mBoundsAnimating && mCancelCurrentBoundsAnimation) {
return true;
}
// When we change the Surface size, in scenarios which may require changing
// the surface position in sync with the resize, we use a preserved surface
// so we can freeze it while waiting for the client to report draw on the newly
- // sized surface.
+ // sized surface. Don't preserve surfaces if the insets change while animating the pinned
+ // stack since it can lead to issues if a new surface is created while calculating the
+ // scale for the animation using the source hint rect
+ // (see WindowStateAnimator#setSurfaceBoundariesLocked()).
if (isDragResizeChanged() || isResizedWhileNotDragResizing()
- || surfaceInsetsChanging()) {
+ || (surfaceInsetsChanging() && !inPinnedWorkspace())) {
mLastSurfaceInsets.set(mAttrs.surfaceInsets);
setDragResizing();
int posX = mTmpSize.left;
int posY = mTmpSize.top;
task.mStack.getDimBounds(mTmpStackBounds);
- task.mStack.getFinalAnimationSourceBounds(mTmpSourceBounds);
+ task.mStack.getFinalAnimationSourceHintBounds(mTmpSourceBounds);
if (!mTmpSourceBounds.isEmpty()) {
// Get the final target stack bounds, if we are not animating, this is just the
// current stack bounds
package com.android.server.wm;
+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;
+import static com.android.server.wm.BoundsAnimationController.SchedulePipModeChangedState;
+
import android.animation.ValueAnimator;
import android.content.Context;
import android.graphics.Rect;
* Test class for {@link BoundsAnimationController} to ensure that it sends the right callbacks
* depending on the various interactions.
*
+ * We are really concerned about only three of the transition states [F = fullscreen, !F = floating]
+ * F->!F, !F->!F, and !F->F. Each animation can only be cancelled from the target mid-transition,
+ * or if a new animation starts on the same target. The tests below verifies that the target is
+ * notified of all the cases where it is animating and cancelled so that it can respond
+ * appropriately.
+ *
* Build/Install/Run:
* bit FrameworksServicesTests:com.android.server.wm.BoundsAnimationControllerTests
*/
/**
* A test animate bounds user to track callbacks from the bounds animation.
*/
- private class TestAnimateBoundsUser implements BoundsAnimationController.AnimateBoundsUser {
+ private class TestBoundsAnimationTarget implements BoundsAnimationTarget {
+ boolean mAwaitingAnimationStart;
boolean mMovedToFullscreen;
boolean mAnimationStarted;
- boolean mAnimationStartedToFullscreen;
+ boolean mSchedulePipModeChangedOnStart;
boolean mAnimationEnded;
- boolean mUpdatedPictureInPictureModeWithBounds;
+ Rect mAnimationEndFinalStackBounds;
+ boolean mSchedulePipModeChangedOnEnd;
boolean mBoundsUpdated;
+ boolean mCancelRequested;
Rect mStackBounds;
Rect mTaskBounds;
- boolean mRequestCancelAnimation = false;
-
- void reinitialize(Rect stackBounds, Rect taskBounds) {
+ void initialize(Rect from) {
+ mAwaitingAnimationStart = true;
mMovedToFullscreen = false;
mAnimationStarted = false;
- mAnimationStartedToFullscreen = false;
mAnimationEnded = false;
- mUpdatedPictureInPictureModeWithBounds = false;
- mStackBounds = stackBounds;
- mTaskBounds = taskBounds;
+ mAnimationEndFinalStackBounds = null;
+ mSchedulePipModeChangedOnStart = false;
+ mSchedulePipModeChangedOnEnd = false;
+ mStackBounds = from;
+ mTaskBounds = null;
mBoundsUpdated = false;
- mRequestCancelAnimation = false;
}
@Override
- public void onAnimationStart(boolean toFullscreen) {
+ public void onAnimationStart(boolean schedulePipModeChangedCallback) {
+ mAwaitingAnimationStart = false;
mAnimationStarted = true;
- mAnimationStartedToFullscreen = toFullscreen;
- }
-
- @Override
- public void updatePictureInPictureMode(Rect targetStackBounds) {
- mUpdatedPictureInPictureModeWithBounds = true;
+ mSchedulePipModeChangedOnStart = schedulePipModeChangedCallback;
}
@Override
public boolean setPinnedStackSize(Rect stackBounds, Rect taskBounds) {
// TODO: Once we break the runs apart, we should fail() here if this is called outside
// of onAnimationStart() and onAnimationEnd()
- if (mRequestCancelAnimation) {
+ if (mCancelRequested) {
+ mCancelRequested = false;
return false;
} else {
mBoundsUpdated = true;
}
@Override
- public void onAnimationEnd() {
+ public void onAnimationEnd(boolean schedulePipModeChangedCallback, Rect finalStackBounds,
+ boolean moveToFullscreen) {
mAnimationEnded = true;
+ mAnimationEndFinalStackBounds = finalStackBounds;
+ mSchedulePipModeChangedOnEnd = schedulePipModeChangedCallback;
+ mMovedToFullscreen = moveToFullscreen;
+ mTaskBounds = null;
}
+ }
- @Override
- public void moveToFullscreen() {
- mMovedToFullscreen = true;
+ /**
+ * Drives the animations, makes common assertions along the way.
+ */
+ private class BoundsAnimationDriver {
+
+ private BoundsAnimationController mController;
+ private TestBoundsAnimationTarget mTarget;
+ private BoundsAnimator mAnimator;
+
+ private Rect mFrom;
+ private Rect mTo;
+ private Rect mLargerBounds;
+ private Rect mExpectedFinalBounds;
+
+ BoundsAnimationDriver(BoundsAnimationController controller,
+ TestBoundsAnimationTarget target) {
+ mController = controller;
+ mTarget = target;
+ }
+
+ BoundsAnimationDriver start(Rect from, Rect to) {
+ if (mAnimator != null) {
+ throw new IllegalArgumentException("Call restart() to restart an animation");
+ }
+
+ mTarget.initialize(from);
+
+ // Started, not running
+ assertTrue(mTarget.mAwaitingAnimationStart);
+ assertTrue(!mTarget.mAnimationStarted);
+
+ startImpl(from, to);
+
+ // Started and running
+ assertTrue(!mTarget.mAwaitingAnimationStart);
+ assertTrue(mTarget.mAnimationStarted);
+
+ return this;
+ }
+
+ BoundsAnimationDriver restart(Rect to) {
+ if (mAnimator == null) {
+ throw new IllegalArgumentException("Call start() to start a new animation");
+ }
+
+ BoundsAnimator oldAnimator = mAnimator;
+ boolean toSameBounds = mAnimator.isStarted() && to.equals(mTo);
+
+ // Reset the animation start state
+ mTarget.mAnimationStarted = false;
+
+ // Start animation
+ startImpl(mTarget.mStackBounds, to);
+
+ if (toSameBounds) {
+ // Same animator if same final bounds
+ assertSame(oldAnimator, mAnimator);
+ }
+
+ // No animation start for replacing animation
+ assertTrue(!mTarget.mAnimationStarted);
+ mTarget.mAnimationStarted = true;
+ return this;
+ }
+
+ private BoundsAnimationDriver startImpl(Rect from, Rect to) {
+ 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);
+
+ // Start animation
+ final @SchedulePipModeChangedState int schedulePipModeChangedState = toFullscreen
+ ? SCHEDULE_PIP_MODE_CHANGED_ON_START
+ : fromFullscreen
+ ? SCHEDULE_PIP_MODE_CHANGED_ON_END
+ : NO_PIP_MODE_CHANGED_CALLBACKS;
+ mAnimator = mController.animateBoundsImpl(mTarget, from, to, DURATION,
+ schedulePipModeChangedState, toFullscreen);
+
+ // Original stack bounds, frozen task bounds
+ assertEquals(mFrom, mTarget.mStackBounds);
+ assertEqualSizeAtOffset(mLargerBounds, mTarget.mTaskBounds);
+
+ // Animating to larger size
+ if (mFrom.equals(mLargerBounds)) {
+ assertTrue(!mAnimator.animatingToLargerSize());
+ } else if (mTo.equals(mLargerBounds)) {
+ assertTrue(mAnimator.animatingToLargerSize());
+ }
+
+ return this;
+ }
+
+ BoundsAnimationDriver expectStarted(boolean schedulePipModeChanged) {
+ // Callback made
+ assertTrue(mTarget.mAnimationStarted);
+
+ assertEquals(schedulePipModeChanged, mTarget.mSchedulePipModeChangedOnStart);
+ return this;
+ }
+
+ 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);
+ } else {
+ assertNotEquals(mFrom, mTarget.mStackBounds);
+ assertNotEquals(mTo, mTarget.mStackBounds);
+ }
+ assertEqualSizeAtOffset(mLargerBounds, mTarget.mTaskBounds);
+ return this;
+ }
+
+ BoundsAnimationDriver cancel() {
+ // Cancel
+ mTarget.mCancelRequested = true;
+ mTarget.mBoundsUpdated = false;
+ mExpectedFinalBounds = null;
+
+ // Update
+ mAnimator.onAnimationUpdate(mMockAnimator.getWithValue(0.5f));
+
+ // Not started, not running, cancel reset
+ assertTrue(!mTarget.mCancelRequested);
+
+ // Stack/task bounds not updated
+ assertTrue(!mTarget.mBoundsUpdated);
+
+ // Callback made
+ assertTrue(mTarget.mAnimationEnded);
+ assertNull(mTarget.mAnimationEndFinalStackBounds);
+
+ return this;
+ }
+
+ BoundsAnimationDriver end() {
+ mAnimator.end();
+
+ // Final stack bounds
+ assertEquals(mTo, mTarget.mStackBounds);
+ assertEquals(mExpectedFinalBounds, mTarget.mAnimationEndFinalStackBounds);
+ assertNull(mTarget.mTaskBounds);
+
+ return this;
+ }
+
+ BoundsAnimationDriver expectEnded(boolean schedulePipModeChanged,
+ boolean moveToFullscreen) {
+ // Callback made
+ assertTrue(mTarget.mAnimationEnded);
+
+ assertEquals(schedulePipModeChanged, mTarget.mSchedulePipModeChangedOnEnd);
+ assertEquals(moveToFullscreen, mTarget.mMovedToFullscreen);
+ return this;
+ }
+
+ private Rect getLargerBounds(Rect r1, Rect r2) {
+ int r1Area = r1.width() * r1.height();
+ int r2Area = r2.width() * r2.height();
+ if (r1Area <= r2Area) {
+ return r2;
+ } else {
+ return r1;
+ }
}
}
// Constants
+ private static final boolean SCHEDULE_PIP_MODE_CHANGED = true;
private static final boolean MOVE_TO_FULLSCREEN = true;
+ private static final int DURATION = 100;
// Some dummy bounds to represent fullscreen and floating bounds
private static final Rect BOUNDS_FULL = new Rect(0, 0, 100, 100);
- private static final Rect BOUNDS_FLOATING = new Rect(80, 80, 95, 95);
- private static final Rect BOUNDS_ALT_FLOATING = new Rect(60, 60, 95, 95);
-
- // Some dummy duration
- private static final int DURATION = 100;
+ private static final Rect BOUNDS_FLOATING = new Rect(60, 60, 95, 95);
+ private static final Rect BOUNDS_SMALLER_FLOATING = new Rect(80, 80, 95, 95);
// Common
- private MockAppTransition mAppTransition;
- private MockValueAnimator mAnimator;
- private TestAnimateBoundsUser mTarget;
+ private MockAppTransition mMockAppTransition;
+ private MockValueAnimator mMockAnimator;
+ private TestBoundsAnimationTarget mTarget;
private BoundsAnimationController mController;
+ private BoundsAnimationDriver mDriver;
// Temp
private Rect mTmpRect = new Rect();
final Context context = InstrumentationRegistry.getTargetContext();
final Handler handler = new Handler(Looper.getMainLooper());
- mAppTransition = new MockAppTransition(context);
- mAnimator = new MockValueAnimator();
- mTarget = new TestAnimateBoundsUser();
- mController = new BoundsAnimationController(context, mAppTransition, handler);
+ mMockAppTransition = new MockAppTransition(context);
+ mMockAnimator = new MockValueAnimator();
+ mTarget = new TestBoundsAnimationTarget();
+ mController = new BoundsAnimationController(context, mMockAppTransition, handler);
+ mDriver = new BoundsAnimationDriver(mController, mTarget);
}
+ /** BASE TRANSITIONS **/
+
@UiThreadTest
@Test
public void testFullscreenToFloatingTransition() throws Exception {
- // Create and start the animation
- mTarget.reinitialize(BOUNDS_FULL, null);
- final BoundsAnimator boundsAnimator = mController.animateBoundsImpl(mTarget, BOUNDS_FULL,
- BOUNDS_FLOATING, DURATION, !MOVE_TO_FULLSCREEN);
-
- // Assert that when we are started, and that we are not going to fullscreen
- assertTrue(mTarget.mAnimationStarted);
- assertFalse(mTarget.mAnimationStartedToFullscreen);
- // Ensure we are not triggering a PiP mode change
- assertFalse(mTarget.mUpdatedPictureInPictureModeWithBounds);
- // Ensure that the task stack bounds are already frozen to the larger source stack bounds
- assertEquals(BOUNDS_FULL, mTarget.mStackBounds);
- assertEquals(BOUNDS_FULL, offsetToZero(mTarget.mTaskBounds));
-
- // Drive some animation updates, ensure that only the stack bounds change and the task
- // bounds are frozen to the original stack bounds (adjusted for the offset)
- boundsAnimator.onAnimationUpdate(mAnimator.getWithValue(0.5f));
- assertNotEquals(BOUNDS_FULL, mTarget.mStackBounds);
- assertEquals(BOUNDS_FULL, offsetToZero(mTarget.mTaskBounds));
- boundsAnimator.onAnimationUpdate(mAnimator.getWithValue(1f));
- assertNotEquals(BOUNDS_FULL, mTarget.mStackBounds);
- assertEquals(BOUNDS_FULL, offsetToZero(mTarget.mTaskBounds));
-
- // Finish the animation, ensure that it reaches the final bounds with the given state
- boundsAnimator.end();
- assertTrue(mTarget.mAnimationEnded);
- assertEquals(BOUNDS_FLOATING, mTarget.mStackBounds);
- assertNull(mTarget.mTaskBounds);
+ mDriver.start(BOUNDS_FULL, BOUNDS_FLOATING)
+ .expectStarted(!SCHEDULE_PIP_MODE_CHANGED)
+ .update(0f)
+ .update(0.5f)
+ .update(1f)
+ .end()
+ .expectEnded(SCHEDULE_PIP_MODE_CHANGED, !MOVE_TO_FULLSCREEN);
}
@UiThreadTest
@Test
public void testFloatingToFullscreenTransition() throws Exception {
- // Create and start the animation
- mTarget.reinitialize(BOUNDS_FULL, null);
- final BoundsAnimator boundsAnimator = mController.animateBoundsImpl(mTarget, BOUNDS_FLOATING,
- BOUNDS_FULL, DURATION, MOVE_TO_FULLSCREEN);
-
- // Assert that when we are started, and that we are going to fullscreen
- assertTrue(mTarget.mAnimationStarted);
- assertTrue(mTarget.mAnimationStartedToFullscreen);
- // Ensure that we update the PiP mode change with the new fullscreen bounds
- assertTrue(mTarget.mUpdatedPictureInPictureModeWithBounds);
- // Ensure that the task stack bounds are already frozen to the larger target stack bounds
- assertEquals(BOUNDS_FLOATING, mTarget.mStackBounds);
- assertEquals(BOUNDS_FULL, offsetToZero(mTarget.mTaskBounds));
-
- // Drive some animation updates, ensure that only the stack bounds change and the task
- // bounds are frozen to the original stack bounds (adjusted for the offset)
- boundsAnimator.onAnimationUpdate(mAnimator.getWithValue(0.5f));
- assertNotEquals(BOUNDS_FLOATING, mTarget.mStackBounds);
- assertEquals(BOUNDS_FULL, offsetToZero(mTarget.mTaskBounds));
- boundsAnimator.onAnimationUpdate(mAnimator.getWithValue(1f));
- assertNotEquals(BOUNDS_FLOATING, mTarget.mStackBounds);
- assertEquals(BOUNDS_FULL, offsetToZero(mTarget.mTaskBounds));
-
- // Finish the animation, ensure that it reaches the final bounds with the given state
- boundsAnimator.end();
- assertTrue(mTarget.mAnimationEnded);
- assertEquals(BOUNDS_FULL, mTarget.mStackBounds);
- assertNull(mTarget.mTaskBounds);
+ mDriver.start(BOUNDS_FLOATING, BOUNDS_FULL)
+ .expectStarted(SCHEDULE_PIP_MODE_CHANGED)
+ .update(0f)
+ .update(0.5f)
+ .update(1f)
+ .end()
+ .expectEnded(!SCHEDULE_PIP_MODE_CHANGED, MOVE_TO_FULLSCREEN);
+ }
+
+ @UiThreadTest
+ @Test
+ public void testFloatingToSmallerFloatingTransition() throws Exception {
+ mDriver.start(BOUNDS_FLOATING, BOUNDS_SMALLER_FLOATING)
+ .expectStarted(!SCHEDULE_PIP_MODE_CHANGED)
+ .update(0f)
+ .update(0.5f)
+ .update(1f)
+ .end()
+ .expectEnded(!SCHEDULE_PIP_MODE_CHANGED, !MOVE_TO_FULLSCREEN);
+ }
+
+ @UiThreadTest
+ @Test
+ public void testFloatingToLargerFloatingTransition() throws Exception {
+ mDriver.start(BOUNDS_SMALLER_FLOATING, BOUNDS_FLOATING)
+ .expectStarted(!SCHEDULE_PIP_MODE_CHANGED)
+ .update(0f)
+ .update(0.5f)
+ .update(1f)
+ .end()
+ .expectEnded(!SCHEDULE_PIP_MODE_CHANGED, !MOVE_TO_FULLSCREEN);
+ }
+
+ /** F->!F w/ CANCEL **/
+
+ @UiThreadTest
+ @Test
+ public void testFullscreenToFloatingCancelFromTarget() throws Exception {
+ mDriver.start(BOUNDS_FULL, BOUNDS_FLOATING)
+ .expectStarted(!SCHEDULE_PIP_MODE_CHANGED)
+ .update(0.25f)
+ .cancel()
+ .expectEnded(SCHEDULE_PIP_MODE_CHANGED, !MOVE_TO_FULLSCREEN);
+ }
+
+ @UiThreadTest
+ @Test
+ public void testFullscreenToFloatingCancelFromAnimationToSameBounds() throws Exception {
+ mDriver.start(BOUNDS_FULL, BOUNDS_FLOATING)
+ .expectStarted(!SCHEDULE_PIP_MODE_CHANGED)
+ .update(0.25f)
+ .restart(BOUNDS_FLOATING)
+ .end()
+ .expectEnded(SCHEDULE_PIP_MODE_CHANGED, !MOVE_TO_FULLSCREEN);
+ }
+
+ @UiThreadTest
+ @Test
+ public void testFullscreenToFloatingCancelFromAnimationToFloatingBounds() throws Exception {
+ mDriver.start(BOUNDS_FULL, BOUNDS_FLOATING)
+ .expectStarted(!SCHEDULE_PIP_MODE_CHANGED)
+ .update(0.25f)
+ .restart(BOUNDS_SMALLER_FLOATING)
+ .end()
+ .expectEnded(SCHEDULE_PIP_MODE_CHANGED, !MOVE_TO_FULLSCREEN);
+ }
+
+ @UiThreadTest
+ @Test
+ public void testFullscreenToFloatingCancelFromAnimationToFullscreenBounds() throws Exception {
+ mDriver.start(BOUNDS_FULL, BOUNDS_FLOATING)
+ .expectStarted(!SCHEDULE_PIP_MODE_CHANGED)
+ .update(0.25f)
+ .restart(BOUNDS_FULL)
+ .end()
+ .expectEnded(!SCHEDULE_PIP_MODE_CHANGED, MOVE_TO_FULLSCREEN);
+ }
+
+ /** !F->F w/ CANCEL **/
+
+ @UiThreadTest
+ @Test
+ public void testFloatingToFullscreenCancelFromTarget() throws Exception {
+ mDriver.start(BOUNDS_FLOATING, BOUNDS_FULL)
+ .expectStarted(SCHEDULE_PIP_MODE_CHANGED)
+ .update(0.25f)
+ .cancel()
+ .expectEnded(SCHEDULE_PIP_MODE_CHANGED, !MOVE_TO_FULLSCREEN);
+ }
+
+ @UiThreadTest
+ @Test
+ public void testFloatingToFullscreenCancelFromAnimationToSameBounds() throws Exception {
+ mDriver.start(BOUNDS_FLOATING, BOUNDS_FULL)
+ .expectStarted(SCHEDULE_PIP_MODE_CHANGED)
+ .update(0.25f)
+ .restart(BOUNDS_FULL)
+ .end()
+ .expectEnded(!SCHEDULE_PIP_MODE_CHANGED, MOVE_TO_FULLSCREEN);
}
@UiThreadTest
@Test
- public void testInterruptAnimationFromUser() throws Exception {
- // Create and start the animation
- mTarget.reinitialize(BOUNDS_FULL, null);
- final BoundsAnimator boundsAnimator = mController.animateBoundsImpl(mTarget, BOUNDS_FULL,
- BOUNDS_FLOATING, DURATION, !MOVE_TO_FULLSCREEN);
-
- // Cancel the animation on the next update from the user
- mTarget.mRequestCancelAnimation = true;
- mTarget.mBoundsUpdated = false;
- boundsAnimator.onAnimationUpdate(mAnimator.getWithValue(0.5f));
- // Ensure that we got no more updates after returning false and the bounds are not updated
- // to the end value
- assertFalse(mTarget.mBoundsUpdated);
- assertNotEquals(BOUNDS_FLOATING, mTarget.mStackBounds);
- assertNotEquals(BOUNDS_FLOATING, mTarget.mTaskBounds);
- // Ensure that we received the animation end call
- assertTrue(mTarget.mAnimationEnded);
+ public void testFloatingToFullscreenCancelFromAnimationToFloatingBounds() throws Exception {
+ mDriver.start(BOUNDS_FLOATING, BOUNDS_FULL)
+ .expectStarted(SCHEDULE_PIP_MODE_CHANGED)
+ .update(0.25f)
+ .restart(BOUNDS_SMALLER_FLOATING)
+ .end()
+ .expectEnded(SCHEDULE_PIP_MODE_CHANGED, !MOVE_TO_FULLSCREEN);
}
+ /** !F->!F w/ CANCEL **/
+
@UiThreadTest
@Test
- public void testCancelAnimationFromNewAnimationToExistingBounds() throws Exception {
- // Create and start the animation
- mTarget.reinitialize(BOUNDS_FULL, null);
- final BoundsAnimator boundsAnimator = mController.animateBoundsImpl(mTarget, BOUNDS_FULL,
- BOUNDS_FLOATING, DURATION, !MOVE_TO_FULLSCREEN);
-
- // Drive some animation updates
- boundsAnimator.onAnimationUpdate(mAnimator.getWithValue(0.5f));
-
- // Cancel the animation as a restart to the same bounds
- mTarget.reinitialize(null, null);
- final BoundsAnimator altBoundsAnimator = mController.animateBoundsImpl(mTarget, BOUNDS_FULL,
- BOUNDS_FLOATING, DURATION, !MOVE_TO_FULLSCREEN);
- // Ensure the animator is the same
- assertSame(boundsAnimator, altBoundsAnimator);
- // Ensure we haven't restarted or finished the animation
- assertFalse(mTarget.mAnimationStarted);
- assertFalse(mTarget.mAnimationEnded);
- // Ensure that we haven't tried to update the PiP mode
- assertFalse(mTarget.mUpdatedPictureInPictureModeWithBounds);
+ public void testFloatingToSmallerFloatingCancelFromTarget() throws Exception {
+ mDriver.start(BOUNDS_FLOATING, BOUNDS_SMALLER_FLOATING)
+ .expectStarted(!SCHEDULE_PIP_MODE_CHANGED)
+ .update(0.25f)
+ .cancel()
+ .expectEnded(!SCHEDULE_PIP_MODE_CHANGED, !MOVE_TO_FULLSCREEN);
}
@UiThreadTest
@Test
- public void testCancelAnimationFromNewAnimationToNewBounds() throws Exception {
- // Create and start the animation
- mTarget.reinitialize(BOUNDS_FULL, null);
- final BoundsAnimator boundsAnimator = mController.animateBoundsImpl(mTarget, BOUNDS_FULL,
- BOUNDS_FLOATING, DURATION, !MOVE_TO_FULLSCREEN);
-
- // Drive some animation updates
- boundsAnimator.onAnimationUpdate(mAnimator.getWithValue(0.5f));
-
- // Cancel the animation as a restart to new bounds
- mTarget.reinitialize(null, null);
- final BoundsAnimator altBoundsAnimator = mController.animateBoundsImpl(mTarget, BOUNDS_FULL,
- BOUNDS_ALT_FLOATING, DURATION, !MOVE_TO_FULLSCREEN);
- // Ensure the animator is not the same
- assertNotSame(boundsAnimator, altBoundsAnimator);
- // Ensure that we did not get an animation start/end callback
- assertFalse(mTarget.mAnimationStarted);
- assertFalse(mTarget.mAnimationEnded);
- // Ensure that we haven't tried to update the PiP mode
- assertFalse(mTarget.mUpdatedPictureInPictureModeWithBounds);
+ public void testFloatingToLargerFloatingCancelFromTarget() throws Exception {
+ mDriver.start(BOUNDS_SMALLER_FLOATING, BOUNDS_FLOATING)
+ .expectStarted(!SCHEDULE_PIP_MODE_CHANGED)
+ .update(0.25f)
+ .cancel()
+ .expectEnded(!SCHEDULE_PIP_MODE_CHANGED, !MOVE_TO_FULLSCREEN);
+ }
+
+ /** MISC **/
+
+ @UiThreadTest
+ @Test
+ public void testBoundsAreCopied() throws Exception {
+ Rect from = new Rect(0, 0, 100, 100);
+ Rect to = new Rect(25, 25, 75, 75);
+ mDriver.start(from, to)
+ .update(0.25f)
+ .end();
+ assertEquals(new Rect(0, 0, 100, 100), from);
+ assertEquals(new Rect(25, 25, 75, 75), to);
}
/**
- * @return the bounds offset to zero/zero.
+ * @return whether the task and stack bounds would be the same if they were at the same offset.
*/
- private Rect offsetToZero(Rect bounds) {
- mTmpRect.set(bounds);
- mTmpRect.offsetTo(0, 0);
- return mTmpRect;
+ private boolean assertEqualSizeAtOffset(Rect stackBounds, Rect taskBounds) {
+ mTmpRect.set(taskBounds);
+ mTmpRect.offsetTo(stackBounds.left, stackBounds.top);
+ return stackBounds.equals(mTmpRect);
}
}