diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java index bb6637d102d73..fbab26aa03398 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 @@ public class ActivityManagerService extends IActivityManager.Stub 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 79ea7baa14429..e180aefcd11a2 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 @@ public class ActivityStackSupervisor extends ConfigurationContainer implements D 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 34cdb54d0b76b..702bf92c65f8f 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 @@ class PinnedActivityStack extends ActivityStack } 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 f0e0e14871311..43e408477b988 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 static com.android.server.wm.WindowManagerService.logWithStack; 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; @@ -1345,9 +1347,11 @@ class AppWindowToken extends WindowToken implements WindowManagerService.AppFree } 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 7f3c89c58a288..410efcdb5038e 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 @@ public class BoundsAnimationController { 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 @@ public class BoundsAnimationController { 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 @@ public class BoundsAnimationController { } } - 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 @@ public class BoundsAnimationController { 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 @@ public class BoundsAnimationController { // 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 class BoundsAnimationController { 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 @@ public class BoundsAnimationController { 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 @@ public class BoundsAnimationController { 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 b0b93aba818d9..0c628acba92c1 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 @@ public class PinnedStackWindowController extends StackWindowController { * 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 @@ public class PinnedStackWindowController extends StackWindowController { 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 @@ public class PinnedStackWindowController extends StackWindowController { toBounds = new Rect(); mContainer.getDisplayContent().getLogicalDisplayRect(toBounds); } - } else if (schedulePipModeChangedOnAnimationEnd) { + } else if (fromFullscreen) { schedulePipModeChangedState = SCHEDULE_PIP_MODE_CHANGED_ON_END; } @@ -122,13 +122,13 @@ public class PinnedStackWindowController extends StackWindowController { 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 @@ public class PinnedStackWindowController extends StackWindowController { 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 d189ff87f351d..eff8ed9a4defb 100644 --- a/services/core/java/com/android/server/wm/TaskStack.java +++ b/services/core/java/com/android/server/wm/TaskStack.java @@ -1489,7 +1489,7 @@ public class TaskStack extends WindowContainer implements DimLayer.DimLaye } 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 cd7a7c7090fc1..ee09f4bf7b946 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 @@ public class BoundsAnimationControllerTests extends WindowTestsBase { mAwaitingAnimationStart = false; mAnimationStarted = true; mSchedulePipModeChangedOnStart = schedulePipModeChangedCallback; - - mController.onAllWindowsDrawn(); } @Override @@ -207,6 +205,9 @@ public class BoundsAnimationControllerTests extends WindowTestsBase { 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 @@ public class BoundsAnimationControllerTests extends WindowTestsBase { 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 @@ public class BoundsAnimationControllerTests extends WindowTestsBase { ? 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);