Ensure that windows are drawn before starting transition into PiP.
- Building upon ag/2125930, we ensure that all windows are drawn before
starting the enter PiP animation.
Bug: 37420370
Test: bit FrameworksServicesTests:com.android.server.wm.BoundsAnimationControllerTests
Test: android.server.cts.ActivityManagerPinnedStackTests
Change-Id: I73fb71681f62bbc684efedbd3d40c3e1a670db46
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index bb6637d..fbab26a 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -10589,8 +10589,7 @@
mStackSupervisor.getStack(PINNED_STACK_ID);
if (pinnedStack != null) {
pinnedStack.animateResizePinnedStack(null /* sourceHintBounds */,
- destBounds, animationDuration,
- false /* schedulePipModeChangedOnAnimationEnd */);
+ destBounds, animationDuration, false /* fromFullscreen */);
}
} else {
throw new IllegalArgumentException("Stack: " + stackId
diff --git a/services/core/java/com/android/server/am/ActivityStackSupervisor.java b/services/core/java/com/android/server/am/ActivityStackSupervisor.java
index 79ea7ba..e180aef 100644
--- a/services/core/java/com/android/server/am/ActivityStackSupervisor.java
+++ b/services/core/java/com/android/server/am/ActivityStackSupervisor.java
@@ -2986,18 +2986,21 @@
mWindowManager.continueSurfaceLayout();
}
- // The task might have already been running and its visibility needs to be synchronized
- // with the visibility of the stack / windows.
+ // Calculate the default bounds (don't use existing stack bounds as we may have just created
+ // the stack, and schedule the start of the animation into PiP (the bounds animator that
+ // is triggered by this is posted on another thread)
+ final Rect destBounds = stack.getPictureInPictureBounds(aspectRatio,
+ false /* useExistingStackBounds */);
+ stack.animateResizePinnedStack(sourceHintBounds, destBounds, -1 /* animationDuration */,
+ true /* fromFullscreen */);
+
+ // Update the visibility of all activities after the they have been reparented to the new
+ // stack. This MUST run after the animation above is scheduled to ensure that the windows
+ // drawn signal is scheduled after the bounds animation start call on the bounds animator
+ // thread.
ensureActivitiesVisibleLocked(null, 0, !PRESERVE_WINDOWS);
resumeFocusedStackTopActivityLocked();
- // Calculate the default bounds (don't use existing stack bounds as we may have just created
- // the stack
- final Rect destBounds = stack.getPictureInPictureBounds(aspectRatio,
- false /* useExistingStackBounds */);
-
- stack.animateResizePinnedStack(sourceHintBounds, destBounds, -1 /* animationDuration */,
- true /* schedulePipModeChangedOnAnimationEnd */);
mService.mTaskChangeNotificationController.notifyActivityPinned(r.packageName,
r.getTask().taskId);
}
diff --git a/services/core/java/com/android/server/am/PinnedActivityStack.java b/services/core/java/com/android/server/am/PinnedActivityStack.java
index 34cdb54..702bf92 100644
--- a/services/core/java/com/android/server/am/PinnedActivityStack.java
+++ b/services/core/java/com/android/server/am/PinnedActivityStack.java
@@ -50,12 +50,12 @@
}
void animateResizePinnedStack(Rect sourceHintBounds, Rect toBounds, int animationDuration,
- boolean schedulePipModeChangedOnAnimationEnd) {
+ boolean fromFullscreen) {
if (skipResizeAnimation(toBounds == null /* toFullscreen */)) {
mService.moveTasksToFullscreenStack(mStackId, true /* onTop */);
} else {
getWindowContainerController().animateResizePinnedStack(toBounds, sourceHintBounds,
- animationDuration, schedulePipModeChangedOnAnimationEnd);
+ animationDuration, fromFullscreen);
}
}
diff --git a/services/core/java/com/android/server/wm/AppWindowToken.java b/services/core/java/com/android/server/wm/AppWindowToken.java
index 17db253..4e38e3d 100644
--- a/services/core/java/com/android/server/wm/AppWindowToken.java
+++ b/services/core/java/com/android/server/wm/AppWindowToken.java
@@ -17,6 +17,7 @@
package com.android.server.wm;
import static android.app.ActivityManager.StackId;
+import static android.app.ActivityManager.StackId.PINNED_STACK_ID;
import static android.content.pm.ActivityInfo.CONFIG_ORIENTATION;
import static android.content.pm.ActivityInfo.CONFIG_SCREEN_SIZE;
import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_BEHIND;
@@ -51,6 +52,7 @@
import android.annotation.NonNull;
import android.app.Activity;
+import android.app.ActivityManager;
import android.content.res.Configuration;
import android.graphics.Rect;
import android.os.Binder;
@@ -1343,9 +1345,11 @@
}
mService.mH.obtainMessage(NOTIFY_ACTIVITY_DRAWN, token).sendToTarget();
- final TaskStack s = getStack();
- if (s != null) {
- s.onAllWindowsDrawn();
+ // Notify the pinned stack upon all windows drawn. If there was an animation in
+ // progress then this signal will resume that animation.
+ final TaskStack pinnedStack = mDisplayContent.getStackById(PINNED_STACK_ID);
+ if (pinnedStack != null) {
+ pinnedStack.onAllWindowsDrawn();
}
}
}
diff --git a/services/core/java/com/android/server/wm/BoundsAnimationController.java b/services/core/java/com/android/server/wm/BoundsAnimationController.java
index 7f3c89c..410efcd 100644
--- a/services/core/java/com/android/server/wm/BoundsAnimationController.java
+++ b/services/core/java/com/android/server/wm/BoundsAnimationController.java
@@ -139,8 +139,14 @@
private final boolean mSkipAnimationStart;
// True if this animation was canceled by the user, not as a part of a replacing animation
private boolean mSkipAnimationEnd;
+
+ // True if the animation target is animating from the fullscreen. Only one of
+ // {@link mMoveToFullscreen} or {@link mMoveFromFullscreen} can be true at any time in the
+ // animation.
+ private boolean mMoveFromFullscreen;
// True if the animation target should be moved to the fullscreen stack at the end of this
- // animation
+ // animation. Only one of {@link mMoveToFullscreen} or {@link mMoveFromFullscreen} can be
+ // true at any time in the animation.
private boolean mMoveToFullscreen;
// Whether to schedule PiP mode changes on animation start/end
@@ -151,15 +157,21 @@
private final int mFrozenTaskWidth;
private final int mFrozenTaskHeight;
+ // Timeout callback to ensure we continue the animation if waiting for resuming or app
+ // windows drawn fails
+ private final Runnable mResumeRunnable = () -> resume();
+
BoundsAnimator(BoundsAnimationTarget target, Rect from, Rect to,
@SchedulePipModeChangedState int schedulePipModeChangedState,
- boolean moveToFullscreen, boolean replacingExistingAnimation) {
+ boolean moveFromFullscreen, boolean moveToFullscreen,
+ boolean replacingExistingAnimation) {
super();
mTarget = target;
mFrom.set(from);
mTo.set(to);
mSkipAnimationStart = replacingExistingAnimation;
mSchedulePipModeChangedState = schedulePipModeChangedState;
+ mMoveFromFullscreen = moveFromFullscreen;
mMoveToFullscreen = moveToFullscreen;
addUpdateListener(this);
addListener(this);
@@ -177,13 +189,6 @@
}
}
- final Runnable mResumeRunnable = new Runnable() {
- @Override
- public void run() {
- resume();
- }
- };
-
@Override
public void onAnimationStart(Animator animation) {
if (DEBUG) Slog.d(TAG, "onAnimationStart: mTarget=" + mTarget
@@ -199,6 +204,12 @@
if (!mSkipAnimationStart) {
mTarget.onAnimationStart(mSchedulePipModeChangedState ==
SCHEDULE_PIP_MODE_CHANGED_ON_START);
+
+ // When starting an animation from fullscreen, pause here and wait for the
+ // windows-drawn signal before we start the rest of the transition down into PiP.
+ if (mMoveFromFullscreen) {
+ pause();
+ }
}
// Immediately update the task bounds if they have to become larger, but preserve
@@ -213,13 +224,20 @@
// correct logic to make this resize seamless.
if (mMoveToFullscreen) {
pause();
- mHandler.postDelayed(mResumeRunnable, WAIT_FOR_DRAW_TIMEOUT_MS);
}
}
}
@Override
+ public void pause() {
+ if (DEBUG) Slog.d(TAG, "pause: waiting for windows drawn");
+ super.pause();
+ mHandler.postDelayed(mResumeRunnable, WAIT_FOR_DRAW_TIMEOUT_MS);
+ }
+
+ @Override
public void resume() {
+ if (DEBUG) Slog.d(TAG, "resume:");
mHandler.removeCallbacks(mResumeRunnable);
super.resume();
}
@@ -336,15 +354,15 @@
public void animateBounds(final BoundsAnimationTarget target, Rect from, Rect to,
int animationDuration, @SchedulePipModeChangedState int schedulePipModeChangedState,
- boolean moveToFullscreen) {
+ boolean moveFromFullscreen, boolean moveToFullscreen) {
animateBoundsImpl(target, from, to, animationDuration, schedulePipModeChangedState,
- moveToFullscreen);
+ moveFromFullscreen, moveToFullscreen);
}
@VisibleForTesting
BoundsAnimator animateBoundsImpl(final BoundsAnimationTarget target, Rect from, Rect to,
int animationDuration, @SchedulePipModeChangedState int schedulePipModeChangedState,
- boolean moveToFullscreen) {
+ boolean moveFromFullscreen, boolean moveToFullscreen) {
final BoundsAnimator existing = mRunningAnimations.get(target);
final boolean replacing = existing != null;
@@ -387,7 +405,7 @@
existing.cancel();
}
final BoundsAnimator animator = new BoundsAnimator(target, from, to,
- schedulePipModeChangedState, moveToFullscreen, replacing);
+ schedulePipModeChangedState, moveFromFullscreen, moveToFullscreen, replacing);
mRunningAnimations.put(target, animator);
animator.setFloatValues(0f, 1f);
animator.setDuration((animationDuration != -1 ? animationDuration
@@ -397,14 +415,19 @@
return animator;
}
+ public Handler getHandler() {
+ return mHandler;
+ }
+
+ public void onAllWindowsDrawn() {
+ if (DEBUG) Slog.d(TAG, "onAllWindowsDrawn:");
+ mHandler.post(this::resume);
+ }
+
private void resume() {
for (int i = 0; i < mRunningAnimations.size(); i++) {
final BoundsAnimator b = mRunningAnimations.valueAt(i);
b.resume();
}
}
-
- public void onAllWindowsDrawn() {
- mHandler.post(this::resume);
- }
}
diff --git a/services/core/java/com/android/server/wm/PinnedStackWindowController.java b/services/core/java/com/android/server/wm/PinnedStackWindowController.java
index b0b93ab..0c628ac 100644
--- a/services/core/java/com/android/server/wm/PinnedStackWindowController.java
+++ b/services/core/java/com/android/server/wm/PinnedStackWindowController.java
@@ -83,7 +83,7 @@
* Animates the pinned stack.
*/
public void animateResizePinnedStack(Rect toBounds, Rect sourceHintBounds,
- int animationDuration, boolean schedulePipModeChangedOnAnimationEnd) {
+ int animationDuration, boolean fromFullscreen) {
synchronized (mWindowMap) {
if (mContainer == null) {
throw new IllegalArgumentException("Pinned stack container not found :(");
@@ -98,7 +98,7 @@
NO_PIP_MODE_CHANGED_CALLBACKS;
final boolean toFullscreen = toBounds == null;
if (toFullscreen) {
- if (schedulePipModeChangedOnAnimationEnd) {
+ if (fromFullscreen) {
throw new IllegalArgumentException("Should not defer scheduling PiP mode"
+ " change on animation to fullscreen.");
}
@@ -113,7 +113,7 @@
toBounds = new Rect();
mContainer.getDisplayContent().getLogicalDisplayRect(toBounds);
}
- } else if (schedulePipModeChangedOnAnimationEnd) {
+ } else if (fromFullscreen) {
schedulePipModeChangedState = SCHEDULE_PIP_MODE_CHANGED_ON_END;
}
@@ -122,13 +122,13 @@
final Rect finalToBounds = toBounds;
final @SchedulePipModeChangedState int finalSchedulePipModeChangedState =
schedulePipModeChangedState;
- UiThread.getHandler().post(() -> {
+ mService.mBoundsAnimationController.getHandler().post(() -> {
if (mContainer == null) {
return;
}
mService.mBoundsAnimationController.animateBounds(mContainer, fromBounds,
finalToBounds, animationDuration, finalSchedulePipModeChangedState,
- toFullscreen);
+ fromFullscreen, toFullscreen);
});
}
}
@@ -152,7 +152,7 @@
if (Float.compare(aspectRatio, pinnedStackController.getAspectRatio()) != 0) {
if (!toBounds.equals(targetBounds)) {
animateResizePinnedStack(toBounds, null /* sourceHintBounds */,
- -1 /* duration */, false /* schedulePipModeChangedOnAnimationEnd */);
+ -1 /* duration */, false /* fromFullscreen */);
}
pinnedStackController.setAspectRatio(
pinnedStackController.isValidPictureInPictureAspectRatio(aspectRatio)
diff --git a/services/core/java/com/android/server/wm/TaskStack.java b/services/core/java/com/android/server/wm/TaskStack.java
index 9d48ce5..34a0cd6 100644
--- a/services/core/java/com/android/server/wm/TaskStack.java
+++ b/services/core/java/com/android/server/wm/TaskStack.java
@@ -1488,7 +1488,7 @@
}
void onAllWindowsDrawn() {
- if (!mBoundsAnimating) {
+ if (!mBoundsAnimating && !mBoundsAnimatingRequested) {
return;
}
diff --git a/services/tests/servicestests/src/com/android/server/wm/BoundsAnimationControllerTests.java b/services/tests/servicestests/src/com/android/server/wm/BoundsAnimationControllerTests.java
index cd7a7c7..ee09f4b 100644
--- a/services/tests/servicestests/src/com/android/server/wm/BoundsAnimationControllerTests.java
+++ b/services/tests/servicestests/src/com/android/server/wm/BoundsAnimationControllerTests.java
@@ -152,8 +152,6 @@
mAwaitingAnimationStart = false;
mAnimationStarted = true;
mSchedulePipModeChangedOnStart = schedulePipModeChangedCallback;
-
- mController.onAllWindowsDrawn();
}
@Override
@@ -207,6 +205,9 @@
throw new IllegalArgumentException("Call restart() to restart an animation");
}
+ boolean fromFullscreen = from.equals(BOUNDS_FULL);
+ boolean toFullscreen = to.equals(BOUNDS_FULL);
+
mTarget.initialize(from);
// Started, not running
@@ -215,6 +216,15 @@
startImpl(from, to);
+ // Ensure that the animator is paused for the all windows drawn signal when animating
+ // to/from fullscreen
+ if (fromFullscreen || toFullscreen) {
+ assertTrue(mAnimator.isPaused());
+ mController.onAllWindowsDrawn();
+ } else {
+ assertTrue(!mAnimator.isPaused());
+ }
+
// Started and running
assertTrue(!mTarget.mAwaitingAnimationStart);
assertTrue(mTarget.mAnimationStarted);
@@ -262,7 +272,7 @@
? SCHEDULE_PIP_MODE_CHANGED_ON_END
: NO_PIP_MODE_CHANGED_CALLBACKS;
mAnimator = mController.animateBoundsImpl(mTarget, from, to, DURATION,
- schedulePipModeChangedState, toFullscreen);
+ schedulePipModeChangedState, fromFullscreen, toFullscreen);
// Original stack bounds, frozen task bounds
assertEquals(mFrom, mTarget.mStackBounds);