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();