From 04bceb99f12d4abd63d547cbb29602539197b548 Mon Sep 17 00:00:00 2001 From: lumark Date: Sat, 7 Mar 2020 00:03:33 +0800 Subject: [PATCH] Trigger onTaskAppeared when a task started from recents becomes ready. In quick switch flows, launcher will first swipe task snapshot through recents animation, and then start new task with custom animation options through startActivityFromRecents after gesture finish detected, and then finish recents animation finally. But that way user may experience flickering before the new task launch and recents animation finish. To improve quick switch flickering, we ignore the new task's custom animation from recents and generate task remote animation target, and then trigger a callback for launcher to control/animate this task surface, more like a part of RecentsAnimation, Also, adding removeTask method for launcher can flexibility remove the new task animation target once no need to animate, so that launcher can decide when to finish recents animation. Bug: 152480470 Test: manual as below steps: 1) Doing quick switch task. 2) Make sure launcher can receive onTaskAppeared callback. 3) Make sure launcher calls removeTask successfully. 4) Make sure launcher can finish recents animation after 3). Change-Id: I0692a280a49719229fa8871509bad37a1343a00f --- .../android/wm/RecentsAnimationPerfTest.java | 5 ++ .../view/IRecentsAnimationController.aidl | 12 ++++ .../android/view/IRecentsAnimationRunner.aidl | 6 ++ data/etc/services.core.protolog.json | 18 +++++ .../shared/system/ActivityManagerWrapper.java | 5 ++ .../RecentsAnimationControllerCompat.java | 12 ++++ .../system/RecentsAnimationListener.java | 6 ++ .../com/android/server/wm/AppTransition.java | 9 ++- .../android/server/wm/RecentsAnimation.java | 4 -- .../server/wm/RecentsAnimationController.java | 70 ++++++++++++++++++- .../server/wm/RootWindowContainer.java | 2 + .../core/java/com/android/server/wm/Task.java | 21 ++++++ .../android/server/wm/WindowContainer.java | 40 ++++++----- .../server/wm/RecentsAnimationTest.java | 7 -- 14 files changed, 186 insertions(+), 31 deletions(-) diff --git a/apct-tests/perftests/core/src/android/wm/RecentsAnimationPerfTest.java b/apct-tests/perftests/core/src/android/wm/RecentsAnimationPerfTest.java index 73b4a1914ad16..836e6b6173954 100644 --- a/apct-tests/perftests/core/src/android/wm/RecentsAnimationPerfTest.java +++ b/apct-tests/perftests/core/src/android/wm/RecentsAnimationPerfTest.java @@ -192,6 +192,11 @@ public class RecentsAnimationPerfTest extends WindowManagerPerfTestBase { Assume.assumeNoException( new AssertionError("onAnimationCanceled should not be called")); } + + @Override + public void onTaskAppeared(RemoteAnimationTarget app) throws RemoteException { + /* no-op */ + } }; recentsSemaphore.tryAcquire(); diff --git a/core/java/android/view/IRecentsAnimationController.aidl b/core/java/android/view/IRecentsAnimationController.aidl index a60a5cca08bdc..983ab2eedf5ce 100644 --- a/core/java/android/view/IRecentsAnimationController.aidl +++ b/core/java/android/view/IRecentsAnimationController.aidl @@ -114,4 +114,16 @@ interface IRecentsAnimationController { * animation is cancelled through fail safe mechanism. */ void setWillFinishToHome(boolean willFinishToHome); + + /** + * Stops controlling a task that is currently controlled by this recents animation. + * + * This method should be called when a task that has been received via {@link #onAnimationStart} + * or {@link #onTaskAppeared} is no longer needed. After calling this method, the task will + * either disappear from the screen, or jump to its final position in case it was the top task. + * + * @param taskId Id of the Task target to remove + * @return {@code true} when target removed successfully, {@code false} otherwise. + */ + boolean removeTask(int taskId); } diff --git a/core/java/android/view/IRecentsAnimationRunner.aidl b/core/java/android/view/IRecentsAnimationRunner.aidl index 6eb90fc54286a..925786f82e002 100644 --- a/core/java/android/view/IRecentsAnimationRunner.aidl +++ b/core/java/android/view/IRecentsAnimationRunner.aidl @@ -56,4 +56,10 @@ oneway interface IRecentsAnimationRunner { void onAnimationStart(in IRecentsAnimationController controller, in RemoteAnimationTarget[] apps, in RemoteAnimationTarget[] wallpapers, in Rect homeContentInsets, in Rect minimizedHomeBounds) = 2; + + /** + * Called when the task of an activity that has been started while the recents animation + * was running becomes ready for control. + */ + void onTaskAppeared(in RemoteAnimationTarget app) = 3; } diff --git a/data/etc/services.core.protolog.json b/data/etc/services.core.protolog.json index 07cf41560cf3e..618e4c02cfcd7 100644 --- a/data/etc/services.core.protolog.json +++ b/data/etc/services.core.protolog.json @@ -883,6 +883,12 @@ "group": "WM_DEBUG_ORIENTATION", "at": "com\/android\/server\/wm\/ActivityRecord.java" }, + "-242787066": { + "message": "addTaskToRecentsAnimationIfNeeded, control: %s, task: %s, transit: %s", + "level": "DEBUG", + "group": "WM_DEBUG_RECENTS_ANIMATIONS", + "at": "com\/android\/server\/wm\/WindowContainer.java" + }, "-198463978": { "message": "updateRotationUnchecked: alwaysSendConfiguration=%b forceRelayout=%b", "level": "VERBOSE", @@ -901,6 +907,12 @@ "group": "WM_DEBUG_ORIENTATION", "at": "com\/android\/server\/wm\/ScreenRotationAnimation.java" }, + "-172900257": { + "message": "addTaskToTargets, target: %s", + "level": "DEBUG", + "group": "WM_DEBUG_RECENTS_ANIMATIONS", + "at": "com\/android\/server\/wm\/RecentsAnimationController.java" + }, "-167822951": { "message": "Attempted to add starting window to token with already existing starting window", "level": "WARN", @@ -1513,6 +1525,12 @@ "group": "WM_DEBUG_RECENTS_ANIMATIONS", "at": "com\/android\/server\/wm\/RecentsAnimation.java" }, + "854237232": { + "message": "addTaskToRecentsAnimationIfNeeded, control: %s, task: %s, transit: %s", + "level": "DEBUG", + "group": "WM_DEBUG_RECENTS_ANIMATIONS", + "at": "com\/android\/server\/wm\/Task.java" + }, "873914452": { "message": "goodToGo()", "level": "DEBUG", 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 3bda3c8df699d..806678f23bb38 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 @@ -261,6 +261,11 @@ public class ActivityManagerWrapper { animationHandler.onAnimationCanceled( taskSnapshot != null ? new ThumbnailData(taskSnapshot) : null); } + + @Override + public void onTaskAppeared(RemoteAnimationTarget app) { + animationHandler.onTaskAppeared(new RemoteAnimationTargetCompat(app)); + } }; } ActivityTaskManager.getService().startRecentsActivity(intent, receiver, runner); 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 bbb83c73446c2..76513c6ff3d53 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 @@ -109,4 +109,16 @@ public class RecentsAnimationControllerCompat { Log.e(TAG, "Failed to set overview reached state", e); } } + + /** + * @see IRecentsAnimationController#removeTask + */ + public boolean removeTask(int taskId) { + try { + return mAnimationController.removeTask(taskId); + } catch (RemoteException e) { + Log.e(TAG, "Failed to remove remote animation target", e); + return false; + } + } } \ No newline at end of file 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 2c99c5c84015f..c4cd192212a0e 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 @@ -32,4 +32,10 @@ public interface RecentsAnimationListener { * Called when the animation into Recents was canceled. This call is made on the binder thread. */ void onAnimationCanceled(ThumbnailData thumbnailData); + + /** + * Called when the task of an activity that has been started while the recents animation + * was running becomes ready for control. + */ + void onTaskAppeared(RemoteAnimationTargetCompat app); } diff --git a/services/core/java/com/android/server/wm/AppTransition.java b/services/core/java/com/android/server/wm/AppTransition.java index 78ee1de780798..6f1ddcd793a9e 100644 --- a/services/core/java/com/android/server/wm/AppTransition.java +++ b/services/core/java/com/android/server/wm/AppTransition.java @@ -403,11 +403,18 @@ public class AppTransition implements Dump { mNextAppTransitionType == NEXT_TRANSIT_TYPE_THUMBNAIL_ASPECT_SCALE_DOWN; } - boolean isNextAppTransitionOpenCrossProfileApps() { return mNextAppTransitionType == NEXT_TRANSIT_TYPE_OPEN_CROSS_PROFILE_APPS; } + boolean isNextAppTransitionCustomFromRecents() { + final RecentTasks recentTasks = mService.mAtmService.getRecentTasks(); + final String recentsPackageName = + (recentTasks != null) ? recentTasks.getRecentsComponent().getPackageName() : null; + return mNextAppTransitionType == NEXT_TRANSIT_TYPE_CUSTOM + && mNextAppTransitionPackage.equals(recentsPackageName); + } + /** * @return true if and only if we are currently fetching app transition specs from the future * passed into {@link #overridePendingAppTransitionMultiThumbFuture} diff --git a/services/core/java/com/android/server/wm/RecentsAnimation.java b/services/core/java/com/android/server/wm/RecentsAnimation.java index a031fe82d48b2..0a9878dd660bb 100644 --- a/services/core/java/com/android/server/wm/RecentsAnimation.java +++ b/services/core/java/com/android/server/wm/RecentsAnimation.java @@ -442,10 +442,6 @@ class RecentsAnimation implements RecentsAnimationCallbacks, // Always prepare an app transition since we rely on the transition callbacks to cleanup mWindowManager.prepareAppTransition(TRANSIT_NONE, false); controller.setCancelOnNextTransitionStart(); - } else { - // Just cancel directly to unleash from launcher when the next launching task is the - // current top task. - mWindowManager.cancelRecentsAnimation(REORDER_KEEP_IN_PLACE, "stackOrderChanged"); } } diff --git a/services/core/java/com/android/server/wm/RecentsAnimationController.java b/services/core/java/com/android/server/wm/RecentsAnimationController.java index 6fda1170a3f56..54210ae1c0b0f 100644 --- a/services/core/java/com/android/server/wm/RecentsAnimationController.java +++ b/services/core/java/com/android/server/wm/RecentsAnimationController.java @@ -43,6 +43,7 @@ import android.os.RemoteException; import android.os.SystemClock; import android.util.ArrayMap; import android.util.ArraySet; +import android.util.IntArray; import android.util.Slog; import android.util.SparseBooleanArray; import android.util.proto.ProtoOutputStream; @@ -99,6 +100,8 @@ public class RecentsAnimationController implements DeathRecipient { private IRecentsAnimationRunner mRunner; private final RecentsAnimationCallbacks mCallbacks; private final ArrayList mPendingAnimations = new ArrayList<>(); + private final IntArray mPendingNewTaskTargets = new IntArray(0); + private final ArrayList mPendingWallpaperAnimations = new ArrayList<>(); private final int mDisplayId; @@ -220,6 +223,10 @@ public class RecentsAnimationController implements DeathRecipient { if (mCanceled) { return; } + // Remove all new task targets. + for (int i = mPendingNewTaskTargets.size() - 1; i >= 0; i--) { + removeTaskInternal(mPendingNewTaskTargets.get(i)); + } } // Note, the callback will handle its own synchronization, do not lock on WM lock @@ -310,6 +317,18 @@ public class RecentsAnimationController implements DeathRecipient { mWillFinishToHome = willFinishToHome; } } + + @Override + public boolean removeTask(int taskId) { + final long token = Binder.clearCallingIdentity(); + try { + synchronized (mService.getWindowManagerLock()) { + return removeTaskInternal(taskId); + } + } finally { + Binder.restoreCallingIdentity(token); + } + } }; /** @@ -405,11 +424,17 @@ public class RecentsAnimationController implements DeathRecipient { @VisibleForTesting AnimationAdapter addAnimation(Task task, boolean isRecentTaskInvisible) { + return addAnimation(task, isRecentTaskInvisible, null /* finishedCallback */); + } + + @VisibleForTesting + AnimationAdapter addAnimation(Task task, boolean isRecentTaskInvisible, + OnAnimationFinishedCallback finishedCallback) { ProtoLog.d(WM_DEBUG_RECENTS_ANIMATIONS, "addAnimation(%s)", task.getName()); final TaskAnimationAdapter taskAdapter = new TaskAnimationAdapter(task, isRecentTaskInvisible); task.startAnimation(task.getPendingTransaction(), taskAdapter, false /* hidden */, - ANIMATION_TYPE_RECENTS); + ANIMATION_TYPE_RECENTS, finishedCallback); task.commitPendingTransaction(); mPendingAnimations.add(taskAdapter); return taskAdapter; @@ -489,6 +514,49 @@ public class RecentsAnimationController implements DeathRecipient { } } + void addTaskToTargets(Task task, OnAnimationFinishedCallback finishedCallback) { + if (mRunner != null) { + final RemoteAnimationTarget target = createTaskRemoteAnimation(task, finishedCallback); + if (target == null) return; + + ProtoLog.d(WM_DEBUG_RECENTS_ANIMATIONS, "addTaskToTargets, target: %s", target); + try { + mRunner.onTaskAppeared(target); + } catch (RemoteException e) { + Slog.e(TAG, "Failed to report task appeared", e); + } + } + } + + private RemoteAnimationTarget createTaskRemoteAnimation(Task task, + OnAnimationFinishedCallback finishedCallback) { + final SparseBooleanArray recentTaskIds = + mService.mAtmService.getRecentTasks().getRecentTaskIds(); + TaskAnimationAdapter adapter = (TaskAnimationAdapter) addAnimation(task, + !recentTaskIds.get(task.mTaskId), finishedCallback); + mPendingNewTaskTargets.add(task.mTaskId); + return adapter.createRemoteAnimationTarget(); + } + + private boolean removeTaskInternal(int taskId) { + boolean result = false; + for (int i = mPendingAnimations.size() - 1; i >= 0; i--) { + // Only allows when task target has became visible to user, to prevent + // the flickering during remove animation and task visible. + final TaskAnimationAdapter target = mPendingAnimations.get(i); + if (target.mTask.mTaskId == taskId && target.mTask.isOnTop()) { + removeAnimation(target); + final int taskIndex = mPendingNewTaskTargets.indexOf(taskId); + if (taskIndex != -1) { + mPendingNewTaskTargets.remove(taskIndex); + } + result = true; + break; + } + } + return result; + } + private RemoteAnimationTarget[] createAppAnimations() { final ArrayList targets = new ArrayList<>(); for (int i = mPendingAnimations.size() - 1; i >= 0; i--) { diff --git a/services/core/java/com/android/server/wm/RootWindowContainer.java b/services/core/java/com/android/server/wm/RootWindowContainer.java index 80b8b58549669..2acfcbf70f903 100644 --- a/services/core/java/com/android/server/wm/RootWindowContainer.java +++ b/services/core/java/com/android/server/wm/RootWindowContainer.java @@ -66,6 +66,7 @@ import static com.android.server.wm.ProtoLogGroup.WM_DEBUG_KEEP_SCREEN_ON; import static com.android.server.wm.ProtoLogGroup.WM_DEBUG_ORIENTATION; import static com.android.server.wm.ProtoLogGroup.WM_SHOW_SURFACE_ALLOC; import static com.android.server.wm.ProtoLogGroup.WM_SHOW_TRANSACTIONS; +import static com.android.server.wm.RecentsAnimationController.REORDER_KEEP_IN_PLACE; import static com.android.server.wm.RootWindowContainerProto.IS_HOME_RECENTS_COMPONENT; import static com.android.server.wm.RootWindowContainerProto.KEYGUARD_CONTROLLER; import static com.android.server.wm.RootWindowContainerProto.PENDING_ACTIVITIES; @@ -1515,6 +1516,7 @@ 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"); } // Update the reason for ANR debugging to verify if the user activity is the one that // actually launched. diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java index d31939dec509f..4b37bd06f549f 100644 --- a/services/core/java/com/android/server/wm/Task.java +++ b/services/core/java/com/android/server/wm/Task.java @@ -86,6 +86,8 @@ import static com.android.server.wm.IdentifierProto.HASH_CODE; import static com.android.server.wm.IdentifierProto.TITLE; import static com.android.server.wm.IdentifierProto.USER_ID; import static com.android.server.wm.ProtoLogGroup.WM_DEBUG_ADD_REMOVE; +import static com.android.server.wm.ProtoLogGroup.WM_DEBUG_RECENTS_ANIMATIONS; +import static com.android.server.wm.SurfaceAnimator.OnAnimationFinishedCallback; import static com.android.server.wm.WindowContainer.AnimationFlags.CHILDREN; import static com.android.server.wm.WindowContainer.AnimationFlags.TRANSITION; import static com.android.server.wm.WindowContainerChildProto.TASK; @@ -135,6 +137,7 @@ import android.view.RemoteAnimationAdapter; import android.view.RemoteAnimationTarget; import android.view.Surface; import android.view.SurfaceControl; +import android.view.WindowManager; import android.window.ITaskOrganizer; import com.android.internal.annotations.VisibleForTesting; @@ -3403,6 +3406,24 @@ class Task extends WindowContainer { } } + @Override + protected void applyAnimationUnchecked(WindowManager.LayoutParams lp, boolean enter, + int transit, boolean isVoiceInteraction, + @Nullable OnAnimationFinishedCallback finishedCallback) { + final RecentsAnimationController control = mWmService.getRecentsAnimationController(); + if (control != null && enter + && getDisplayContent().mAppTransition.isNextAppTransitionCustomFromRecents()) { + ProtoLog.d(WM_DEBUG_RECENTS_ANIMATIONS, + "addTaskToRecentsAnimationIfNeeded, control: %s, task: %s, transit: %s", + control, asTask(), AppTransition.appTransitionToString(transit)); + // We let the transition to be controlled by RecentsAnimation, and callback task's + // RemoteAnimationTarget for remote runner to animate. + control.addTaskToTargets(getRootTask(), finishedCallback); + } else { + super.applyAnimationUnchecked(lp, enter, transit, isVoiceInteraction, finishedCallback); + } + } + @Override void dump(PrintWriter pw, String prefix, boolean dumpAll) { super.dump(pw, prefix, dumpAll); diff --git a/services/core/java/com/android/server/wm/WindowContainer.java b/services/core/java/com/android/server/wm/WindowContainer.java index f6e952c4cea15..e3cdfa13ba854 100644 --- a/services/core/java/com/android/server/wm/WindowContainer.java +++ b/services/core/java/com/android/server/wm/WindowContainer.java @@ -2076,8 +2076,7 @@ class WindowContainer extends ConfigurationContainer< * @see #getAnimationAdapter */ boolean applyAnimation(WindowManager.LayoutParams lp, int transit, boolean enter, - boolean isVoiceInteraction, - @Nullable OnAnimationFinishedCallback animationFinishedCallback) { + boolean isVoiceInteraction, @Nullable OnAnimationFinishedCallback finishedCallback) { if (mWmService.mDisableTransitionAnimation) { ProtoLog.v(WM_DEBUG_APP_TRANSITIONS_ANIM, "applyAnimation: transition animation is disabled or skipped. " @@ -2092,22 +2091,7 @@ class WindowContainer extends ConfigurationContainer< try { Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "WC#applyAnimation"); if (okToAnimate()) { - final Pair adapters = getAnimationAdapter(lp, - transit, enter, isVoiceInteraction); - AnimationAdapter adapter = adapters.first; - AnimationAdapter thumbnailAdapter = adapters.second; - if (adapter != null) { - startAnimation(getPendingTransaction(), adapter, !isVisible(), - ANIMATION_TYPE_APP_TRANSITION, animationFinishedCallback); - if (adapter.getShowWallpaper()) { - getDisplayContent().pendingLayoutChanges |= FINISH_LAYOUT_REDO_WALLPAPER; - } - if (thumbnailAdapter != null) { - mSurfaceFreezer.mSnapshot.startAnimation(getPendingTransaction(), - thumbnailAdapter, ANIMATION_TYPE_APP_TRANSITION, - (type, anim) -> { }); - } - } + applyAnimationUnchecked(lp, enter, transit, isVoiceInteraction, finishedCallback); } else { cancelAnimation(); } @@ -2201,6 +2185,26 @@ class WindowContainer extends ConfigurationContainer< return resultAdapters; } + protected void applyAnimationUnchecked(WindowManager.LayoutParams lp, boolean enter, + int transit, boolean isVoiceInteraction, + @Nullable OnAnimationFinishedCallback finishedCallback) { + final Pair adapters = getAnimationAdapter(lp, + transit, enter, isVoiceInteraction); + AnimationAdapter adapter = adapters.first; + AnimationAdapter thumbnailAdapter = adapters.second; + if (adapter != null) { + startAnimation(getPendingTransaction(), adapter, !isVisible(), + ANIMATION_TYPE_APP_TRANSITION, finishedCallback); + if (adapter.getShowWallpaper()) { + getDisplayContent().pendingLayoutChanges |= FINISH_LAYOUT_REDO_WALLPAPER; + } + if (thumbnailAdapter != null) { + mSurfaceFreezer.mSnapshot.startAnimation(getPendingTransaction(), + thumbnailAdapter, ANIMATION_TYPE_APP_TRANSITION, (type, anim) -> { }); + } + } + } + final SurfaceAnimationRunner getSurfaceAnimationRunner() { return mWmService.mSurfaceAnimationRunner; } diff --git a/services/tests/wmtests/src/com/android/server/wm/RecentsAnimationTest.java b/services/tests/wmtests/src/com/android/server/wm/RecentsAnimationTest.java index 881561f5750bd..1f6ba7adf1142 100644 --- a/services/tests/wmtests/src/com/android/server/wm/RecentsAnimationTest.java +++ b/services/tests/wmtests/src/com/android/server/wm/RecentsAnimationTest.java @@ -238,9 +238,6 @@ public class RecentsAnimationTest extends ActivityTestsBase { assertTrue(targetActivity.mLaunchTaskBehind); anotherHomeActivity.moveFocusableActivityToTop("launchAnotherHome"); - // The current top activity is not the recents so the animation should be canceled. - verify(mService.mWindowManager, times(1)).cancelRecentsAnimation( - eq(REORDER_KEEP_IN_PLACE), any() /* reason */); // The test uses mocked RecentsAnimationController so we have to invoke the callback // manually to simulate the flow. @@ -279,10 +276,6 @@ public class RecentsAnimationTest extends ActivityTestsBase { fullscreenStack.moveToFront("Activity start"); - // Ensure that the recents animation was canceled by cancelAnimationSynchronously(). - verify(mService.mWindowManager, times(1)).cancelRecentsAnimation( - eq(REORDER_KEEP_IN_PLACE), any()); - // Assume recents animation already started, set a state that cancel recents animation // with screenshot. doReturn(true).when(mRecentsAnimationController).shouldDeferCancelUntilNextTransition();