Fix flash when canceling the recents animation with a screenshot

- When canceling because of starting home, use the screenshot call to
  allow Launcher to handle the cancel more cleanly
- Since the cancel+screenshot path was added, we no longer cancel
  on root task order change (we just get onTaskAppeared now), it's
  purely a deferred path for the runner to handle the cancel in a clean
  way. So instead of having a separate animator, just add a screenshot
  surface to the existing leash, and clean up everything when the
  runner finishes the recents animation. This prevents the issue where
  transferring the animation causes the task to be reparented from the
  leash and made immediately visible (and fullscreen)
- Remove some unused code

Fixes: 185643608
Test: atest RecentsAnimationControllerTests
Test: Manually enable live-tile for 2/3 button, tap home and ensure
      there's no flash
Change-Id: I57775e5df55ae80931ad02c21f190f29ffa76881
This commit is contained in:
Winson Chung
2021-06-23 22:05:51 -07:00
parent 6a930e0643
commit a2eaf83a71
6 changed files with 124 additions and 201 deletions

View File

@@ -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

View File

@@ -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,

View File

@@ -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<Task> 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;

View File

@@ -1525,7 +1525,9 @@ class RootWindowContainer extends WindowContainer<DisplayContent>
// 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);

View File

@@ -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<SurfaceSession, SurfaceControl.Builder> 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;
}
}

View File

@@ -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)