From 79090d69306a6a94491e1ffa8329d48ad6e4760a Mon Sep 17 00:00:00 2001 From: Jerry Chang Date: Thu, 4 Nov 2021 00:09:25 +0800 Subject: [PATCH] Snapshot all tasks in pending recents animation when rotating devices Since both of splitting tasks in split screen go to overview together now, update to snapshot all pending animations when rotating while showing overview to ensure both of splitting task snapshots are taken with proper bounds before rotation. Bug: 200813008 Test: atest RecentsAnimationControllerTest Test: enter overview after activated split screen, observed task thumbnails showing with correct bounds after roation. Change-Id: Id1101bf8be67593f85a43c1d4afae769ffc737d3 --- .../android/wm/RecentsAnimationPerfTest.java | 5 +- .../android/view/IRecentsAnimationRunner.aidl | 14 ++-- .../shared/recents/model/ThumbnailData.java | 16 +++- .../shared/system/ActivityManagerWrapper.java | 6 +- .../RecentsAnimationControllerCompat.java | 8 +- .../system/RecentsAnimationListener.java | 4 +- .../android/server/wm/RecentsAnimation.java | 3 +- .../server/wm/RecentsAnimationController.java | 74 +++++++++++-------- .../server/wm/TaskSnapshotController.java | 3 +- .../wm/RecentsAnimationControllerTest.java | 18 +++-- 10 files changed, 93 insertions(+), 58 deletions(-) diff --git a/apct-tests/perftests/windowmanager/src/android/wm/RecentsAnimationPerfTest.java b/apct-tests/perftests/windowmanager/src/android/wm/RecentsAnimationPerfTest.java index 98b5938e4026c..1c40a06c1c867 100644 --- a/apct-tests/perftests/windowmanager/src/android/wm/RecentsAnimationPerfTest.java +++ b/apct-tests/perftests/windowmanager/src/android/wm/RecentsAnimationPerfTest.java @@ -25,7 +25,6 @@ import static org.hamcrest.core.Is.is; import android.app.ActivityManager; import android.app.ActivityTaskManager; -import android.window.TaskSnapshot; import android.app.IActivityTaskManager; import android.content.ComponentName; import android.content.Context; @@ -41,6 +40,7 @@ import android.util.Pair; import android.view.IRecentsAnimationController; import android.view.IRecentsAnimationRunner; import android.view.RemoteAnimationTarget; +import android.window.TaskSnapshot; import androidx.test.filters.LargeTest; import androidx.test.runner.lifecycle.Stage; @@ -210,7 +210,8 @@ public class RecentsAnimationPerfTest extends WindowManagerPerfTestBase } @Override - public void onAnimationCanceled(TaskSnapshot taskSnapshot) throws RemoteException { + public void onAnimationCanceled(int[] taskIds, TaskSnapshot[] taskSnapshots) + throws RemoteException { Assume.assumeNoException( new AssertionError("onAnimationCanceled should not be called")); } diff --git a/core/java/android/view/IRecentsAnimationRunner.aidl b/core/java/android/view/IRecentsAnimationRunner.aidl index 811175566a90f..aca17e448b82c 100644 --- a/core/java/android/view/IRecentsAnimationRunner.aidl +++ b/core/java/android/view/IRecentsAnimationRunner.aidl @@ -35,15 +35,17 @@ oneway interface IRecentsAnimationRunner { * wallpaper not drawing in time, or the handler not finishing the animation within a predefined * amount of time. * - * @param taskSnapshot If the snapshot is null, the animation will be cancelled and the leash - * will be inactive immediately. Otherwise, the contents of the task will be - * replaced with {@param taskSnapshot}, such that the runner's leash is - * still active. As soon as the runner doesn't need the leash anymore, it - * must call {@link IRecentsAnimationController#cleanupScreenshot). + * @param taskIds Indicates tasks with cancelling snapshot. + * @param taskSnapshots If the snapshots is null, the animation will be cancelled and the leash + * will be inactive immediately. Otherwise, the contents of the tasks will + * be replaced with {@param taskSnapshots}, such that the runner's leash is + * still active. As soon as the runner doesn't need the leash anymore, it + * must call {@link IRecentsAnimationController#cleanupScreenshot). * * @see {@link RecentsAnimationController#cleanupScreenshot} */ - void onAnimationCanceled(in @nullable TaskSnapshot taskSnapshot) = 1; + void onAnimationCanceled(in @nullable int[] taskIds, + in @nullable TaskSnapshot[] taskSnapshots) = 1; /** * Called when the system is ready for the handler to start animating all the visible tasks. diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/model/ThumbnailData.java b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/model/ThumbnailData.java index 6594d5f51af3c..2f2876d2e9f32 100644 --- a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/model/ThumbnailData.java +++ b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/model/ThumbnailData.java @@ -22,7 +22,6 @@ import static android.graphics.Bitmap.Config.ARGB_8888; import static com.android.systemui.shared.system.WindowManagerWrapper.WINDOWING_MODE_UNDEFINED; -import android.window.TaskSnapshot; import android.graphics.Bitmap; import android.graphics.Color; import android.graphics.Point; @@ -30,6 +29,9 @@ import android.graphics.Rect; import android.hardware.HardwareBuffer; import android.util.Log; import android.view.WindowInsetsController.Appearance; +import android.window.TaskSnapshot; + +import java.util.HashMap; /** * Data for a single thumbnail. @@ -80,6 +82,18 @@ public class ThumbnailData { return thumbnail; } + public static HashMap wrap(int[] taskIds, TaskSnapshot[] snapshots) { + HashMap temp = new HashMap<>(); + if (taskIds == null || snapshots == null || taskIds.length != snapshots.length) { + return temp; + } + + for (int i = snapshots.length - 1; i >= 0; i--) { + temp.put(taskIds[i], new ThumbnailData(snapshots[i])); + } + return temp; + } + public ThumbnailData(TaskSnapshot snapshot) { thumbnail = makeThumbnail(snapshot); insets = new Rect(snapshot.getContentInsets()); diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/ActivityManagerWrapper.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/ActivityManagerWrapper.java index 9164137feb418..b95123d2fa41c 100644 --- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/ActivityManagerWrapper.java +++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/ActivityManagerWrapper.java @@ -28,7 +28,6 @@ import android.app.ActivityClient; import android.app.ActivityManager; import android.app.ActivityManager.RecentTaskInfo; import android.app.ActivityManager.RunningTaskInfo; -import android.window.TaskSnapshot; import android.app.ActivityOptions; import android.app.ActivityTaskManager; import android.app.AppGlobals; @@ -50,6 +49,7 @@ import android.util.Log; import android.view.IRecentsAnimationController; import android.view.IRecentsAnimationRunner; import android.view.RemoteAnimationTarget; +import android.window.TaskSnapshot; import com.android.internal.app.IVoiceInteractionManagerService; import com.android.systemui.shared.recents.model.Task; @@ -189,9 +189,9 @@ public class ActivityManagerWrapper { } @Override - public void onAnimationCanceled(TaskSnapshot taskSnapshot) { + public void onAnimationCanceled(int[] taskIds, TaskSnapshot[] taskSnapshots) { animationHandler.onAnimationCanceled( - taskSnapshot != null ? new ThumbnailData(taskSnapshot) : null); + ThumbnailData.wrap(taskIds, taskSnapshots)); } @Override diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/RecentsAnimationControllerCompat.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/RecentsAnimationControllerCompat.java index 8e65560f7c735..13f1db4a08316 100644 --- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/RecentsAnimationControllerCompat.java +++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/RecentsAnimationControllerCompat.java @@ -39,12 +39,14 @@ public class RecentsAnimationControllerCompat { public ThumbnailData screenshotTask(int taskId) { try { - TaskSnapshot snapshot = mAnimationController.screenshotTask(taskId); - return snapshot != null ? new ThumbnailData(snapshot) : new ThumbnailData(); + final TaskSnapshot snapshot = mAnimationController.screenshotTask(taskId); + if (snapshot != null) { + return new ThumbnailData(snapshot); + } } catch (RemoteException e) { Log.e(TAG, "Failed to screenshot task", e); - return new ThumbnailData(); } + return new ThumbnailData(); } public void setInputConsumerEnabled(boolean enabled) { diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/RecentsAnimationListener.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/RecentsAnimationListener.java index c4cd192212a0e..a74de2e0c0853 100644 --- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/RecentsAnimationListener.java +++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/RecentsAnimationListener.java @@ -20,6 +20,8 @@ import android.graphics.Rect; import com.android.systemui.shared.recents.model.ThumbnailData; +import java.util.HashMap; + public interface RecentsAnimationListener { /** * Called when the animation into Recents can start. This call is made on the binder thread. @@ -31,7 +33,7 @@ public interface RecentsAnimationListener { /** * Called when the animation into Recents was canceled. This call is made on the binder thread. */ - void onAnimationCanceled(ThumbnailData thumbnailData); + void onAnimationCanceled(HashMap thumbnailDatas); /** * Called when the task of an activity that has been started while the recents animation diff --git a/services/core/java/com/android/server/wm/RecentsAnimation.java b/services/core/java/com/android/server/wm/RecentsAnimation.java index ee05523b3f2ac..6d96cf06a00b9 100644 --- a/services/core/java/com/android/server/wm/RecentsAnimation.java +++ b/services/core/java/com/android/server/wm/RecentsAnimation.java @@ -473,7 +473,8 @@ class RecentsAnimation implements RecentsAnimationCallbacks, OnRootTaskOrderChan */ static void notifyAnimationCancelBeforeStart(IRecentsAnimationRunner recentsAnimationRunner) { try { - recentsAnimationRunner.onAnimationCanceled(null /* taskSnapshot */); + recentsAnimationRunner.onAnimationCanceled(null /* taskIds */, + null /* taskSnapshots */); } catch (RemoteException e) { Slog.e(TAG, "Failed to cancel recents animation before start", e); } diff --git a/services/core/java/com/android/server/wm/RecentsAnimationController.java b/services/core/java/com/android/server/wm/RecentsAnimationController.java index 67b3ec8c2b90f..2057b1cdf24b7 100644 --- a/services/core/java/com/android/server/wm/RecentsAnimationController.java +++ b/services/core/java/com/android/server/wm/RecentsAnimationController.java @@ -853,37 +853,37 @@ 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 TaskSnapshot taskSnapshot = screenshotRecentTask(task); + final ArrayMap snapshotMap = screenshotRecentTasks(); mPendingCancelWithScreenshotReorderMode = reorderMode; - try { - mRunner.onAnimationCanceled(taskSnapshot); - } catch (RemoteException e) { - Slog.e(TAG, "Failed to cancel recents animation", e); - } - if (taskSnapshot != null) { - // Defer until the runner calls back to cleanupScreenshot() - adapter.setSnapshotOverlay(taskSnapshot); + + if (!snapshotMap.isEmpty()) { + try { + int[] taskIds = new int[snapshotMap.size()]; + TaskSnapshot[] snapshots = new TaskSnapshot[snapshotMap.size()]; + for (int i = snapshotMap.size() - 1; i >= 0; i--) { + taskIds[i] = snapshotMap.keyAt(i).mTaskId; + snapshots[i] = snapshotMap.valueAt(i); + } + mRunner.onAnimationCanceled(taskIds, snapshots); + } catch (RemoteException e) { + Slog.e(TAG, "Failed to cancel recents animation", e); + } // Schedule a new failsafe for if the runner doesn't clean up the screenshot scheduleFailsafe(); - } else { - // Do a normal cancel since we couldn't screenshot - mCallbacks.onAnimationFinished(reorderMode, false /* sendUserLeaveHint */); + return; } - } else { - // Otherwise, notify the runner and clean up the animation immediately - // Note: In the fallback case, this can trigger multiple onAnimationCancel() calls - // to the runner if we this actually triggers cancel twice on the caller - try { - mRunner.onAnimationCanceled(null /* taskSnapshot */); - } catch (RemoteException e) { - Slog.e(TAG, "Failed to cancel recents animation", e); - } - mCallbacks.onAnimationFinished(reorderMode, false /* sendUserLeaveHint */); + // Fallback to a normal cancel since we couldn't screenshot } + + // Notify the runner and clean up the animation immediately + // Note: In the fallback case, this can trigger multiple onAnimationCancel() calls + // to the runner if we this actually triggers cancel twice on the caller + try { + mRunner.onAnimationCanceled(null /* taskIds */, null /* taskSnapshots */); + } catch (RemoteException e) { + Slog.e(TAG, "Failed to cancel recents animation", e); + } + mCallbacks.onAnimationFinished(reorderMode, false /* sendUserLeaveHint */); } } @@ -957,13 +957,23 @@ public class RecentsAnimationController implements DeathRecipient { return mRequestDeferCancelUntilNextTransition && mCancelDeferredWithScreenshot; } - TaskSnapshot screenshotRecentTask(Task task) { + private ArrayMap screenshotRecentTasks() { final TaskSnapshotController snapshotController = mService.mTaskSnapshotController; - final ArraySet tasks = Sets.newArraySet(task); - snapshotController.snapshotTasks(tasks); - snapshotController.addSkipClosingAppSnapshotTasks(tasks); - return snapshotController.getSnapshot(task.mTaskId, task.mUserId, - false /* restoreFromDisk */, false /* isLowResolution */); + final ArrayMap snapshotMap = new ArrayMap<>(); + for (int i = mPendingAnimations.size() - 1; i >= 0; i--) { + final TaskAnimationAdapter adapter = mPendingAnimations.get(i); + final Task task = adapter.mTask; + snapshotController.recordTaskSnapshot(task, false /* allowSnapshotHome */); + final TaskSnapshot snapshot = snapshotController.getSnapshot(task.mTaskId, task.mUserId, + false /* restoreFromDisk */, false /* isLowResolution */); + if (snapshot != null) { + snapshotMap.put(task, snapshot); + // Defer until the runner calls back to cleanupScreenshot() + adapter.setSnapshotOverlay(snapshot); + } + } + snapshotController.addSkipClosingAppSnapshotTasks(snapshotMap.keySet()); + return snapshotMap; } void cleanupAnimation(@ReorderMode int reorderMode) { diff --git a/services/core/java/com/android/server/wm/TaskSnapshotController.java b/services/core/java/com/android/server/wm/TaskSnapshotController.java index ce93f2495c223..83ea1e22e6ea1 100644 --- a/services/core/java/com/android/server/wm/TaskSnapshotController.java +++ b/services/core/java/com/android/server/wm/TaskSnapshotController.java @@ -54,6 +54,7 @@ import com.android.server.wm.utils.InsetUtils; import com.google.android.collect.Sets; import java.io.PrintWriter; +import java.util.Set; /** * When an app token becomes invisible, we take a snapshot (bitmap) of the corresponding task and @@ -167,7 +168,7 @@ class TaskSnapshotController { * calling {@link #snapshotTasks} to ensure that the task has an up-to-date snapshot. */ @VisibleForTesting - void addSkipClosingAppSnapshotTasks(ArraySet tasks) { + void addSkipClosingAppSnapshotTasks(Set tasks) { if (shouldDisableSnapshots()) { return; } 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 9d2a69197013e..b4c449a94a405 100644 --- a/services/tests/wmtests/src/com/android/server/wm/RecentsAnimationControllerTest.java +++ b/services/tests/wmtests/src/com/android/server/wm/RecentsAnimationControllerTest.java @@ -124,7 +124,7 @@ public class RecentsAnimationControllerTest extends WindowTestsBase { // Verify that the finish callback to reparent the leash is called verify(mFinishedCallback).onAnimationFinished(eq(ANIMATION_TYPE_RECENTS), eq(adapter)); // Verify the animation canceled callback to the app was made - verify(mMockRunner).onAnimationCanceled(null /* taskSnapshot */); + verify(mMockRunner).onAnimationCanceled(null /* taskIds */, null /* taskSnapshots */); verifyNoMoreInteractionsExceptAsBinder(mMockRunner); } @@ -207,7 +207,8 @@ public class RecentsAnimationControllerTest extends WindowTestsBase { wallpaperWindowToken.cancelAnimation(); assertTrue(mController.isAnimatingTask(activity.getTask())); assertFalse(mController.isAnimatingWallpaper(wallpaperWindowToken)); - verify(mMockRunner, never()).onAnimationCanceled(null /* taskSnapshot */); + verify(mMockRunner, never()).onAnimationCanceled(null /* taskIds */, + null /* taskSnapshots */); } @Test @@ -254,7 +255,7 @@ public class RecentsAnimationControllerTest extends WindowTestsBase { mController.setDeferredCancel(true /* deferred */, false /* screenshot */); mController.cancelAnimationWithScreenshot(false /* screenshot */); - verify(mMockRunner).onAnimationCanceled(null /* taskSnapshot */); + verify(mMockRunner).onAnimationCanceled(null /* taskIds */, null /* taskSnapshots */); // Simulate the app transition finishing mController.mAppTransitionListener.onAppTransitionStartingLocked(false, false, 0, 0, 0); @@ -281,7 +282,8 @@ public class RecentsAnimationControllerTest extends WindowTestsBase { anyInt(), eq(false) /* restoreFromDisk */, eq(false) /* isLowResolution */); mController.setDeferredCancel(true /* deferred */, true /* screenshot */); mController.cancelAnimationWithScreenshot(true /* screenshot */); - verify(mMockRunner).onAnimationCanceled(mMockTaskSnapshot /* taskSnapshot */); + verify(mMockRunner).onAnimationCanceled(any(int[].class) /* taskIds */, + any(TaskSnapshot[].class) /* taskSnapshots */); // Continue the animation (simulating a call to cleanupScreenshot()) mController.continueDeferredCancelAnimation(); @@ -322,7 +324,7 @@ public class RecentsAnimationControllerTest extends WindowTestsBase { doReturn(mMockTaskSnapshot).when(mWm.mTaskSnapshotController).getSnapshot(anyInt(), anyInt(), eq(false) /* restoreFromDisk */, eq(false) /* isLowResolution */); mController.cancelAnimationWithScreenshot(true /* screenshot */); - verify(mMockRunner).onAnimationCanceled(any()); + verify(mMockRunner).onAnimationCanceled(any(), any()); // Simulate process crashing and ensure the animation is still canceled mController.binderDied(); @@ -700,7 +702,7 @@ public class RecentsAnimationControllerTest extends WindowTestsBase { mController.setWillFinishToHome(true); mController.cancelAnimationForDisplayChange(); - verify(mMockRunner).onAnimationCanceled(any()); + verify(mMockRunner).onAnimationCanceled(any(), any()); verify(mAnimationCallbacks).onAnimationFinished(REORDER_MOVE_TO_TOP, false); } @@ -715,7 +717,7 @@ public class RecentsAnimationControllerTest extends WindowTestsBase { mController.setWillFinishToHome(false); mController.cancelAnimationForDisplayChange(); - verify(mMockRunner).onAnimationCanceled(any()); + verify(mMockRunner).onAnimationCanceled(any(), any()); verify(mAnimationCallbacks).onAnimationFinished(REORDER_MOVE_TO_ORIGINAL_POSITION, false); } @@ -735,7 +737,7 @@ public class RecentsAnimationControllerTest extends WindowTestsBase { doReturn(mMockTaskSnapshot).when(mWm.mTaskSnapshotController).getSnapshot(anyInt(), anyInt(), eq(false) /* restoreFromDisk */, eq(false) /* isLowResolution */); mController.cancelAnimationForHomeStart(); - verify(mMockRunner).onAnimationCanceled(any()); + verify(mMockRunner).onAnimationCanceled(any(), any()); // Continue the animation (simulating a call to cleanupScreenshot()) mController.continueDeferredCancelAnimation();