diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerInternal.java b/services/core/java/com/android/server/wm/ActivityTaskManagerInternal.java index 01ee3be08f5f3..1759cdeb60d7f 100644 --- a/services/core/java/com/android/server/wm/ActivityTaskManagerInternal.java +++ b/services/core/java/com/android/server/wm/ActivityTaskManagerInternal.java @@ -279,11 +279,6 @@ public abstract class ActivityTaskManagerInternal { */ public abstract boolean isRecentsComponentHomeActivity(int userId); - /** - * Cancels any currently running recents animation. - */ - public abstract void cancelRecentsAnimation(boolean restoreHomeRootTaskPosition); - /** * Returns true if the app can close system dialogs. Otherwise it either throws a {@link * SecurityException} or returns false with a logcat message depending on whether the app diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java index 5e75cebc322df..7bd33741311ae 100644 --- a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java +++ b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java @@ -5255,11 +5255,6 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub { return getRecentTasks().isRecentsComponentHomeActivity(userId); } - @Override - public void cancelRecentsAnimation(boolean restoreHomeRootTaskPosition) { - ActivityTaskManagerService.this.cancelRecentsAnimation(restoreHomeRootTaskPosition); - } - @Override public boolean checkCanCloseSystemDialogs(int pid, int uid, @Nullable String packageName) { return ActivityTaskManagerService.this.checkCanCloseSystemDialogs(pid, uid, diff --git a/services/core/java/com/android/server/wm/RecentsAnimationController.java b/services/core/java/com/android/server/wm/RecentsAnimationController.java index a7e633527b541..710dd552f72d2 100644 --- a/services/core/java/com/android/server/wm/RecentsAnimationController.java +++ b/services/core/java/com/android/server/wm/RecentsAnimationController.java @@ -36,8 +36,10 @@ import static com.android.server.wm.WindowManagerInternal.AppTransitionListener; import android.annotation.IntDef; import android.annotation.NonNull; import android.app.WindowConfiguration; +import android.graphics.GraphicBuffer; import android.graphics.Point; import android.graphics.Rect; +import android.hardware.HardwareBuffer; import android.os.Binder; import android.os.IBinder.DeathRecipient; import android.os.RemoteException; @@ -54,6 +56,7 @@ import android.view.InputWindowHandle; import android.view.RemoteAnimationTarget; import android.view.SurfaceControl; import android.view.SurfaceControl.Transaction; +import android.view.SurfaceSession; import android.view.WindowInsets.Type; import android.window.PictureInPictureSurfaceTransaction; import android.window.TaskSnapshot; @@ -150,6 +153,8 @@ public class RecentsAnimationController implements DeathRecipient { private boolean mCancelOnNextTransitionStart; // Whether to take a screenshot when handling a deferred cancel private boolean mCancelDeferredWithScreenshot; + // The reorder mode to apply after the cleanupScreenshot() callback + private int mPendingCancelWithScreenshotReorderMode = REORDER_MOVE_TO_ORIGINAL_POSITION; @VisibleForTesting boolean mIsAddingTaskToTargets; @@ -158,12 +163,6 @@ public class RecentsAnimationController implements DeathRecipient { private boolean mNavigationBarAttachedToApp; private ActivityRecord mNavBarAttachedApp; - /** - * Animates the screenshot of task that used to be controlled by RecentsAnimation. - * @see {@link #setCancelOnNextTransitionStart} - */ - SurfaceAnimator mRecentScreenshotAnimator; - /** * An app transition listener to cancel the recents animation only after the app transition * starts or is canceled. @@ -355,12 +354,9 @@ public class RecentsAnimationController implements DeathRecipient { public void cleanupScreenshot() { final long token = Binder.clearCallingIdentity(); try { - synchronized (mService.mGlobalLock) { - if (mRecentScreenshotAnimator != null) { - mRecentScreenshotAnimator.cancelAnimation(); - mRecentScreenshotAnimator = null; - } - } + // Note, the callback will handle its own synchronization, do not lock on WM lock + // prior to calling the callback + continueDeferredCancelAnimation(); } finally { Binder.restoreCallingIdentity(token); } @@ -519,13 +515,13 @@ public class RecentsAnimationController implements DeathRecipient { } @VisibleForTesting - AnimationAdapter addAnimation(Task task, boolean isRecentTaskInvisible) { + TaskAnimationAdapter addAnimation(Task task, boolean isRecentTaskInvisible) { return addAnimation(task, isRecentTaskInvisible, false /* hidden */, null /* finishedCallback */); } @VisibleForTesting - AnimationAdapter addAnimation(Task task, boolean isRecentTaskInvisible, boolean hidden, + TaskAnimationAdapter addAnimation(Task task, boolean isRecentTaskInvisible, boolean hidden, OnAnimationFinishedCallback finishedCallback) { ProtoLog.d(WM_DEBUG_RECENTS_ANIMATIONS, "addAnimation(%s)", task.getName()); final TaskAnimationAdapter taskAdapter = new TaskAnimationAdapter(task, @@ -541,9 +537,7 @@ public class RecentsAnimationController implements DeathRecipient { void removeAnimation(TaskAnimationAdapter taskAdapter) { ProtoLog.d(WM_DEBUG_RECENTS_ANIMATIONS, "removeAnimation(%d)", taskAdapter.mTask.mTaskId); - taskAdapter.mTask.setCanAffectSystemUiFlags(true); - taskAdapter.mCapturedFinishCallback.onAnimationFinished(taskAdapter.mLastAnimationType, - taskAdapter); + taskAdapter.onRemove(); mPendingAnimations.remove(taskAdapter); } @@ -821,8 +815,22 @@ public class RecentsAnimationController implements DeathRecipient { } /** - * If there is a recents animation running, we need to cancel the animation and snapshot the - * tasks before the change (to ensure they are captured at the right configuration) + * Cancels the running animation when starting home, providing a snapshot for the runner to + * properly handle the cancellation. This call uses the provided hint to determine how to + * finish the animation. + */ + public void cancelAnimationForHomeStart() { + if (mCanceled) { + return; + } + cancelAnimation(mWillFinishToHome ? REORDER_MOVE_TO_TOP : REORDER_KEEP_IN_PLACE, + true /* screenshot */, "cancelAnimationForHomeStart"); + } + + /** + * Cancels the running animation when there is a display change, providing a snapshot for the + * runner to properly handle the cancellation. This call uses the provided hint to determine + * how to finish the animation. */ public void cancelAnimationForDisplayChange() { if (mCanceled) { @@ -843,16 +851,22 @@ public class RecentsAnimationController implements DeathRecipient { mCanceled = true; if (screenshot && !mPendingAnimations.isEmpty()) { + final TaskAnimationAdapter adapter = mPendingAnimations.get(0); + final Task task = adapter.mTask; // Screen shot previous task when next task starts transition and notify the runner. // We will actually finish the animation once the runner calls cleanUpScreenshot(). - final Task task = mPendingAnimations.get(0).mTask; - final TaskSnapshot taskSnapshot = screenshotRecentTask(task, reorderMode); + final TaskSnapshot taskSnapshot = screenshotRecentTask(task); + mPendingCancelWithScreenshotReorderMode = reorderMode; try { mRunner.onAnimationCanceled(taskSnapshot); } catch (RemoteException e) { Slog.e(TAG, "Failed to cancel recents animation", e); } - if (taskSnapshot == null) { + if (taskSnapshot != null) { + // Defer until the runner calls back to cleanupScreenshot() + adapter.setSnapshotOverlay(taskSnapshot); + } else { + // Do a normal cancel since we couldn't screenshot mCallbacks.onAnimationFinished(reorderMode, false /* sendUserLeaveHint */); } } else { @@ -869,6 +883,12 @@ public class RecentsAnimationController implements DeathRecipient { } } + @VisibleForTesting + void continueDeferredCancelAnimation() { + mCallbacks.onAnimationFinished(mPendingCancelWithScreenshotReorderMode, + false /* sendUserLeaveHint */); + } + @VisibleForTesting void setWillFinishToHome(boolean willFinishToHome) { mWillFinishToHome = willFinishToHome; @@ -915,29 +935,13 @@ public class RecentsAnimationController implements DeathRecipient { return mRequestDeferCancelUntilNextTransition && mCancelDeferredWithScreenshot; } - TaskSnapshot screenshotRecentTask(Task task, @ReorderMode int reorderMode) { + TaskSnapshot screenshotRecentTask(Task task) { final TaskSnapshotController snapshotController = mService.mTaskSnapshotController; final ArraySet tasks = Sets.newArraySet(task); snapshotController.snapshotTasks(tasks); snapshotController.addSkipClosingAppSnapshotTasks(tasks); - final TaskSnapshot taskSnapshot = snapshotController.getSnapshot(task.mTaskId, - task.mUserId, false /* restoreFromDisk */, false /* isLowResolution */); - if (taskSnapshot == null) { - return null; - } - - final TaskScreenshotAnimatable animatable = new TaskScreenshotAnimatable( - mService.mSurfaceControlFactory, task, - new SurfaceControl.ScreenshotHardwareBuffer(taskSnapshot.getHardwareBuffer(), - taskSnapshot.getColorSpace(), false /* containsSecureLayers */)); - mRecentScreenshotAnimator = new SurfaceAnimator( - animatable, - (type, anim) -> { - ProtoLog.d(WM_DEBUG_RECENTS_ANIMATIONS, "mRecentScreenshotAnimator finish"); - mCallbacks.onAnimationFinished(reorderMode, false /* sendUserLeaveHint */); - }, mService); - mRecentScreenshotAnimator.transferAnimation(task.mSurfaceAnimator); - return taskSnapshot; + return snapshotController.getSnapshot(task.mTaskId, task.mUserId, + false /* restoreFromDisk */, false /* isLowResolution */); } void cleanupAnimation(@ReorderMode int reorderMode) { @@ -976,12 +980,6 @@ public class RecentsAnimationController implements DeathRecipient { mRunner = null; mCanceled = true; - // Make sure previous animator has cleaned-up. - if (mRecentScreenshotAnimator != null) { - mRecentScreenshotAnimator.cancelAnimation(); - mRecentScreenshotAnimator = null; - } - // Restore IME icon only when moving the original app task to front from recents, in case // IME icon may missing if the moving task has already been the current focused task. if (reorderMode == REORDER_MOVE_TO_ORIGINAL_POSITION && !mIsAddingTaskToTargets) { @@ -1028,16 +1026,7 @@ public class RecentsAnimationController implements DeathRecipient { @Override public void binderDied() { - if (!mCanceled) { - cancelAnimation(REORDER_MOVE_TO_ORIGINAL_POSITION, "binderDied"); - } else { - synchronized (mService.getWindowManagerLock()) { - if (mRecentScreenshotAnimator != null) { - mRecentScreenshotAnimator.cancelAnimation(); - mRecentScreenshotAnimator = null; - } - } - } + cancelAnimation(REORDER_MOVE_TO_ORIGINAL_POSITION, "binderDied"); synchronized (mService.getWindowManagerLock()) { // Clear associated input consumers on runner death @@ -1171,6 +1160,8 @@ public class RecentsAnimationController implements DeathRecipient { private PictureInPictureSurfaceTransaction mFinishTransaction; // An overlay used to mask the content as an app goes into PIP private SurfaceControl mFinishOverlay; + // An overlay used for canceling the animation with a screenshot + private SurfaceControl mSnapshotOverlay; TaskAnimationAdapter(Task task, boolean isRecentTaskInvisible) { mTask = task; @@ -1205,10 +1196,47 @@ public class RecentsAnimationController implements DeathRecipient { return mTarget; } - void onCleanup() { - if (mFinishTransaction != null) { - final Transaction pendingTransaction = mTask.getPendingTransaction(); + void setSnapshotOverlay(TaskSnapshot snapshot) { + // Create a surface control for the snapshot and reparent it to the leash + final HardwareBuffer buffer = snapshot.getHardwareBuffer(); + if (buffer == null) { + return; + } + final SurfaceSession session = new SurfaceSession(); + mSnapshotOverlay = mService.mSurfaceControlFactory.apply(session) + .setName("RecentTaskScreenshotSurface") + .setCallsite("TaskAnimationAdapter.setSnapshotOverlay") + .setFormat(buffer.getFormat()) + .setParent(mCapturedLeash) + .setBLASTLayer() + .build(); + + final float scale = 1.0f * mTask.getBounds().width() / buffer.getWidth(); + mTask.getPendingTransaction() + .setBuffer(mSnapshotOverlay, GraphicBuffer.createFromHardwareBuffer(buffer)) + .setColorSpace(mSnapshotOverlay, snapshot.getColorSpace()) + .setLayer(mSnapshotOverlay, Integer.MAX_VALUE) + .setMatrix(mSnapshotOverlay, scale, 0, 0, scale) + .show(mSnapshotOverlay) + .apply(); + } + + void onRemove() { + if (mSnapshotOverlay != null) { + // Clean up the snapshot overlay if necessary + mTask.getPendingTransaction() + .remove(mSnapshotOverlay) + .apply(); + mSnapshotOverlay = null; + } + mTask.setCanAffectSystemUiFlags(true); + mCapturedFinishCallback.onAnimationFinished(mLastAnimationType, this); + } + + void onCleanup() { + final Transaction pendingTransaction = mTask.getPendingTransaction(); + if (mFinishTransaction != null) { // Reparent the overlay if (mFinishOverlay != null) { pendingTransaction.reparent(mFinishOverlay, mTask.mSurfaceControl); @@ -1236,10 +1264,15 @@ public class RecentsAnimationController implements DeathRecipient { } else if (!mTask.isAttached()) { // Apply the task's pending transaction in case it is detached and its transaction // is not reachable. - mTask.getPendingTransaction().apply(); + pendingTransaction.apply(); } } + @VisibleForTesting + public SurfaceControl getSnapshotOverlay() { + return mSnapshotOverlay; + } + @Override public boolean getShowWallpaper() { return false; diff --git a/services/core/java/com/android/server/wm/RootWindowContainer.java b/services/core/java/com/android/server/wm/RootWindowContainer.java index bc2556a5a497e..6242b97914649 100644 --- a/services/core/java/com/android/server/wm/RootWindowContainer.java +++ b/services/core/java/com/android/server/wm/RootWindowContainer.java @@ -1529,7 +1529,9 @@ class RootWindowContainer extends WindowContainer // Updates the extra information of the intent. if (fromHomeKey) { homeIntent.putExtra(WindowManagerPolicy.EXTRA_FROM_HOME_KEY, true); - mWindowManager.cancelRecentsAnimation(REORDER_KEEP_IN_PLACE, "startHomeActivity"); + if (mWindowManager.getRecentsAnimationController() != null) { + mWindowManager.getRecentsAnimationController().cancelAnimationForHomeStart(); + } } homeIntent.putExtra(WindowManagerPolicy.EXTRA_START_REASON, reason); diff --git a/services/core/java/com/android/server/wm/TaskScreenshotAnimatable.java b/services/core/java/com/android/server/wm/TaskScreenshotAnimatable.java deleted file mode 100644 index 7e992ac263bc2..0000000000000 --- a/services/core/java/com/android/server/wm/TaskScreenshotAnimatable.java +++ /dev/null @@ -1,120 +0,0 @@ -/* - * Copyright (C) 2019 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 static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_RECENTS_ANIMATIONS; - -import android.graphics.GraphicBuffer; -import android.hardware.HardwareBuffer; -import android.view.SurfaceControl; -import android.view.SurfaceSession; - -import com.android.internal.protolog.common.ProtoLog; - -import java.util.function.Function; - -/** - * Class used by {@link RecentsAnimationController} to create a surface control with taking - * screenshot of task when canceling recents animation. - * - * @see {@link RecentsAnimationController#setCancelOnNextTransitionStart} - */ -class TaskScreenshotAnimatable implements SurfaceAnimator.Animatable { - private static final String TAG = "TaskScreenshotAnim"; - private Task mTask; - private SurfaceControl mSurfaceControl; - private int mWidth; - private int mHeight; - - TaskScreenshotAnimatable(Function surfaceControlFactory, - Task task, SurfaceControl.ScreenshotHardwareBuffer screenshotBuffer) { - HardwareBuffer buffer = screenshotBuffer == null - ? null : screenshotBuffer.getHardwareBuffer(); - mTask = task; - mWidth = (buffer != null) ? buffer.getWidth() : 1; - mHeight = (buffer != null) ? buffer.getHeight() : 1; - ProtoLog.d(WM_DEBUG_RECENTS_ANIMATIONS, - "Creating TaskScreenshotAnimatable: task: %s width: %d height: %d", - task, mWidth, mHeight); - mSurfaceControl = surfaceControlFactory.apply(new SurfaceSession()) - .setName("RecentTaskScreenshotSurface") - .setBLASTLayer() - .setCallsite("TaskScreenshotAnimatable") - .build(); - if (buffer != null) { - GraphicBuffer graphicBuffer = GraphicBuffer.createFromHardwareBuffer(buffer); - getPendingTransaction().setBuffer(mSurfaceControl, graphicBuffer); - getPendingTransaction().setColorSpace(mSurfaceControl, - screenshotBuffer.getColorSpace()); - final float scale = 1.0f * mTask.getBounds().width() / mWidth; - getPendingTransaction().setMatrix(mSurfaceControl, scale, 0, 0, scale); - } - getPendingTransaction().show(mSurfaceControl); - } - - @Override - public SurfaceControl.Transaction getPendingTransaction() { - return mTask.getPendingTransaction(); - } - - @Override - public void commitPendingTransaction() { - mTask.commitPendingTransaction(); - } - - @Override - public void onAnimationLeashCreated(SurfaceControl.Transaction t, SurfaceControl leash) { - t.setLayer(leash, 1); - } - - @Override - public void onAnimationLeashLost(SurfaceControl.Transaction t) { - if (mSurfaceControl != null) { - t.remove(mSurfaceControl); - mSurfaceControl = null; - } - } - - @Override - public SurfaceControl.Builder makeAnimationLeash() { - return mTask.makeAnimationLeash(); - } - - @Override - public SurfaceControl getAnimationLeashParent() { - return mTask.getAnimationLeashParent(); - } - - @Override - public SurfaceControl getSurfaceControl() { - return mSurfaceControl; - } - - @Override - public SurfaceControl getParentSurfaceControl() { - return mTask.mSurfaceControl; - } - - @Override - public int getSurfaceWidth() { - return mWidth; - } - - @Override - public int getSurfaceHeight() { - return mHeight; - } -} diff --git a/services/tests/wmtests/src/com/android/server/wm/RecentsAnimationControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/RecentsAnimationControllerTest.java index afd0ecf0fdee5..03944172db7c9 100644 --- a/services/tests/wmtests/src/com/android/server/wm/RecentsAnimationControllerTest.java +++ b/services/tests/wmtests/src/com/android/server/wm/RecentsAnimationControllerTest.java @@ -255,7 +255,6 @@ public class RecentsAnimationControllerTest extends WindowTestsBase { mController.setDeferredCancel(true /* deferred */, false /* screenshot */); mController.cancelAnimationWithScreenshot(false /* screenshot */); verify(mMockRunner).onAnimationCanceled(null /* taskSnapshot */); - assertNull(mController.mRecentScreenshotAnimator); // Simulate the app transition finishing mController.mAppTransitionListener.onAppTransitionStartingLocked(false, 0, 0, 0); @@ -271,7 +270,8 @@ public class RecentsAnimationControllerTest extends WindowTestsBase { assertEquals(activity.getTask().getTopVisibleActivity(), activity); assertEquals(activity.findMainWindow(), win1); - mController.addAnimation(activity.getTask(), false /* isRecentTaskInvisible */); + RecentsAnimationController.TaskAnimationAdapter adapter = mController.addAnimation( + activity.getTask(), false /* isRecentTaskInvisible */); assertTrue(mController.isAnimatingTask(activity.getTask())); spyOn(mWm.mTaskSnapshotController); @@ -282,14 +282,9 @@ public class RecentsAnimationControllerTest extends WindowTestsBase { mController.setDeferredCancel(true /* deferred */, true /* screenshot */); mController.cancelAnimationWithScreenshot(true /* screenshot */); verify(mMockRunner).onAnimationCanceled(mMockTaskSnapshot /* taskSnapshot */); - assertNotNull(mController.mRecentScreenshotAnimator); - assertTrue(mController.mRecentScreenshotAnimator.isAnimating()); - // Assume IRecentsAnimationController#cleanupScreenshot called to finish screenshot - // animation. - spyOn(mController.mRecentScreenshotAnimator.mAnimatable); - mController.mRecentScreenshotAnimator.cancelAnimation(); - verify(mController.mRecentScreenshotAnimator.mAnimatable).onAnimationLeashLost(any()); + // Continue the animation (simulating a call to cleanupScreenshot()) + mController.continueDeferredCancelAnimation(); verify(mAnimationCallbacks).onAnimationFinished(REORDER_KEEP_IN_PLACE, false); } @@ -685,6 +680,29 @@ public class RecentsAnimationControllerTest extends WindowTestsBase { verify(mAnimationCallbacks).onAnimationFinished(REORDER_MOVE_TO_ORIGINAL_POSITION, false); } + @Test + public void testCancelForStartHome() throws Exception { + mWm.setRecentsAnimationController(mController); + final ActivityRecord activity = createActivityRecord(mDefaultDisplay); + final WindowState win1 = createWindow(null, TYPE_BASE_APPLICATION, activity, "win1"); + activity.addWindow(win1); + + RecentsAnimationController.TaskAnimationAdapter adapter = mController.addAnimation( + activity.getTask(), false /* isRecentTaskInvisible */); + mController.setWillFinishToHome(true); + + // Verify cancel is called with a snapshot and that we've created an overlay + spyOn(mWm.mTaskSnapshotController); + doReturn(mMockTaskSnapshot).when(mWm.mTaskSnapshotController).getSnapshot(anyInt(), + anyInt(), eq(false) /* restoreFromDisk */, eq(false) /* isLowResolution */); + mController.cancelAnimationForHomeStart(); + verify(mMockRunner).onAnimationCanceled(any()); + + // Continue the animation (simulating a call to cleanupScreenshot()) + mController.continueDeferredCancelAnimation(); + verify(mAnimationCallbacks).onAnimationFinished(REORDER_MOVE_TO_TOP, false); + } + private ActivityRecord createHomeActivity() { final ActivityRecord homeActivity = new ActivityBuilder(mWm.mAtmService) .setParentTask(mRootHomeTask)