Ensure Recents Go layout finishes before remote anim begins
We would like to assume a correct up-to-date layout for Go recents
before the remote animation begins to ensure correctness of the app to
overview transition and allow for animating all the newly laid out task
views in sync.
We do this by checking if recents is the remote target we are animating
to and if so, checking if the view is ready. If it is not, then we
delay the remote animation until the layout is finished and everything
is attached.
Bug: 132112131
Fix: 132108983
Test: Run Recents Go, have no tasks in recents, open app, press
overview to go to recents
Test: Test on AOSP and NexusLauncher that animations to Launcher work as
before (both to recents and to home)
Change-Id: Id74d90cffc9fe5db1dbb5fe63b8819c7436fef21
diff --git a/go/quickstep/src/com/android/launcher3/GoLauncherAppTransitionManagerImpl.java b/go/quickstep/src/com/android/launcher3/GoLauncherAppTransitionManagerImpl.java
index d189c50..bcb1f5c 100644
--- a/go/quickstep/src/com/android/launcher3/GoLauncherAppTransitionManagerImpl.java
+++ b/go/quickstep/src/com/android/launcher3/GoLauncherAppTransitionManagerImpl.java
@@ -1,16 +1,20 @@
package com.android.launcher3;
+import static com.android.launcher3.Utilities.postAsyncCallback;
import static com.android.launcher3.anim.Interpolators.AGGRESSIVE_EASE;
import static com.android.launcher3.anim.Interpolators.LINEAR;
+import static com.android.quickstep.TaskUtils.taskIsATargetWithMode;
import static com.android.quickstep.views.IconRecentsView.CONTENT_ALPHA;
+import static com.android.systemui.shared.system.RemoteAnimationTargetCompat.MODE_OPENING;
import android.animation.AnimatorSet;
import android.animation.ObjectAnimator;
-import android.app.ActivityOptions;
import android.content.Context;
+import android.os.Handler;
import android.view.View;
import com.android.quickstep.views.IconRecentsView;
+import com.android.systemui.shared.system.RemoteAnimationRunnerCompat;
import com.android.systemui.shared.system.RemoteAnimationTargetCompat;
/**
@@ -29,6 +33,12 @@
}
@Override
+ RemoteAnimationRunnerCompat getWallpaperOpenRunner(boolean fromUnlock) {
+ return new GoWallpaperOpenLauncherAnimationRunner(mHandler,
+ false /* startAtFrontOfQueue */, fromUnlock);
+ }
+
+ @Override
protected void composeRecentsLaunchAnimator(AnimatorSet anim, View v,
RemoteAnimationTargetCompat[] targets, boolean launcherClosing) {
// Stubbed. Recents launch animation will come from the recents view itself and will not
@@ -51,4 +61,34 @@
return mLauncher.getStateManager()::reapplyState;
}
+
+ /**
+ * Remote animation runner for animation from app to Launcher. For Go, when going to recents,
+ * we need to ensure that the recents view is ready for remote animation before starting.
+ */
+ private final class GoWallpaperOpenLauncherAnimationRunner extends
+ WallpaperOpenLauncherAnimationRunner {
+ public GoWallpaperOpenLauncherAnimationRunner(Handler handler, boolean startAtFrontOfQueue,
+ boolean fromUnlock) {
+ super(handler, startAtFrontOfQueue, fromUnlock);
+ }
+
+ @Override
+ public void onCreateAnimation(RemoteAnimationTargetCompat[] targetCompats,
+ AnimationResult result) {
+ boolean isGoingToRecents =
+ taskIsATargetWithMode(targetCompats, mLauncher.getTaskId(), MODE_OPENING)
+ && (mLauncher.getStateManager().getState() == LauncherState.OVERVIEW);
+ if (isGoingToRecents) {
+ IconRecentsView recentsView = mLauncher.getOverviewPanel();
+ if (!recentsView.isReadyForRemoteAnim()) {
+ recentsView.setOnReadyForRemoteAnimCallback(() ->
+ postAsyncCallback(mHandler, () -> onCreateAnimation(targetCompats, result))
+ );
+ return;
+ }
+ }
+ super.onCreateAnimation(targetCompats, result);
+ }
+ }
}
diff --git a/go/quickstep/src/com/android/quickstep/views/IconRecentsView.java b/go/quickstep/src/com/android/quickstep/views/IconRecentsView.java
index 7225e57..07faa4b 100644
--- a/go/quickstep/src/com/android/quickstep/views/IconRecentsView.java
+++ b/go/quickstep/src/com/android/quickstep/views/IconRecentsView.java
@@ -379,6 +379,36 @@
}
/**
+ * Whether this view has processed all data changes and is ready to animate from the app to
+ * the overview.
+ *
+ * @return true if ready to animate app to overview, false otherwise
+ */
+ public boolean isReadyForRemoteAnim() {
+ return !mTaskRecyclerView.hasPendingAdapterUpdates();
+ }
+
+ /**
+ * Set a callback for whenever this view is ready to do a remote animation from the app to
+ * overview. See {@link #isReadyForRemoteAnim()}.
+ *
+ * @param callback callback to run when view is ready to animate
+ */
+ public void setOnReadyForRemoteAnimCallback(onReadyForRemoteAnimCallback callback) {
+ mTaskRecyclerView.getViewTreeObserver().addOnGlobalLayoutListener(
+ new ViewTreeObserver.OnGlobalLayoutListener() {
+ @Override
+ public void onGlobalLayout() {
+ if (isReadyForRemoteAnim()) {
+ callback.onReadyForRemoteAnim();
+ mTaskRecyclerView.getViewTreeObserver().
+ removeOnGlobalLayoutListener(this);
+ }
+ }
+ });
+ }
+
+ /**
* Clear all tasks and animate out.
*/
private void animateClearAllTasks() {
@@ -557,4 +587,12 @@
mTaskRecyclerView.setPadding(insets.left, insets.top, insets.right, insets.bottom);
mTaskRecyclerView.invalidateItemDecorations();
}
+
+ /**
+ * Callback for when this view is ready for a remote animation from app to overview.
+ */
+ public interface onReadyForRemoteAnimCallback {
+
+ void onReadyForRemoteAnim();
+ }
}
diff --git a/quickstep/src/com/android/launcher3/QuickstepAppTransitionManagerImpl.java b/quickstep/src/com/android/launcher3/QuickstepAppTransitionManagerImpl.java
index e1a115a..3b75304 100644
--- a/quickstep/src/com/android/launcher3/QuickstepAppTransitionManagerImpl.java
+++ b/quickstep/src/com/android/launcher3/QuickstepAppTransitionManagerImpl.java
@@ -132,7 +132,7 @@
private final DragLayer mDragLayer;
private final AlphaProperty mDragLayerAlpha;
- private final Handler mHandler;
+ final Handler mHandler;
private final boolean mIsRtl;
private final float mContentTransY;
@@ -573,70 +573,9 @@
* @return Runner that plays when user goes to Launcher
* ie. pressing home, swiping up from nav bar.
*/
- private RemoteAnimationRunnerCompat getWallpaperOpenRunner(boolean fromUnlock) {
- return new LauncherAnimationRunner(mHandler, false /* startAtFrontOfQueue */) {
- @Override
- public void onCreateAnimation(RemoteAnimationTargetCompat[] targetCompats,
- AnimationResult result) {
- if (!mLauncher.hasBeenResumed()) {
- // If launcher is not resumed, wait until new async-frame after resume
- mLauncher.setOnResumeCallback(() ->
- postAsyncCallback(mHandler, () ->
- onCreateAnimation(targetCompats, result)));
- return;
- }
-
- if (mLauncher.hasSomeInvisibleFlag(PENDING_INVISIBLE_BY_WALLPAPER_ANIMATION)) {
- mLauncher.addForceInvisibleFlag(INVISIBLE_BY_PENDING_FLAGS);
- mLauncher.getStateManager().moveToRestState();
- }
-
- AnimatorSet anim = null;
- RemoteAnimationProvider provider = mRemoteAnimationProvider;
- if (provider != null) {
- anim = provider.createWindowAnimation(targetCompats);
- }
-
- if (anim == null) {
- anim = new AnimatorSet();
- anim.play(fromUnlock
- ? getUnlockWindowAnimator(targetCompats)
- : getClosingWindowAnimators(targetCompats));
-
- // Normally, we run the launcher content animation when we are transitioning
- // home, but if home is already visible, then we don't want to animate the
- // contents of launcher unless we know that we are animating home as a result
- // of the home button press with quickstep, which will result in launcher being
- // started on touch down, prior to the animation home (and won't be in the
- // targets list because it is already visible). In that case, we force
- // invisibility on touch down, and only reset it after the animation to home
- // is initialized.
- if (launcherIsATargetWithMode(targetCompats, MODE_OPENING)
- || mLauncher.isForceInvisible()) {
- // Only register the content animation for cancellation when state changes
- mLauncher.getStateManager().setCurrentAnimation(anim);
- if (fromUnlock) {
- Pair<AnimatorSet, Runnable> contentAnimator =
- getLauncherContentAnimator(false /* isAppOpening */,
- new float[] {mContentTransY, 0});
- contentAnimator.first.setStartDelay(0);
- anim.play(contentAnimator.first);
- anim.addListener(new AnimatorListenerAdapter() {
- @Override
- public void onAnimationEnd(Animator animation) {
- contentAnimator.second.run();
- }
- });
- } else {
- createLauncherResumeAnimation(anim);
- }
- }
- }
-
- mLauncher.clearForceInvisibleFlag(INVISIBLE_ALL);
- result.setAnimation(anim);
- }
- };
+ RemoteAnimationRunnerCompat getWallpaperOpenRunner(boolean fromUnlock) {
+ return new WallpaperOpenLauncherAnimationRunner(mHandler, false /* startAtFrontOfQueue */,
+ fromUnlock);
}
/**
@@ -773,4 +712,79 @@
return mLauncher.checkSelfPermission(CONTROL_REMOTE_APP_TRANSITION_PERMISSION)
== PackageManager.PERMISSION_GRANTED;
}
+
+ /**
+ * Remote animation runner for animation from the app to Launcher, including recents.
+ */
+ class WallpaperOpenLauncherAnimationRunner extends LauncherAnimationRunner {
+ private final boolean mFromUnlock;
+
+ public WallpaperOpenLauncherAnimationRunner(Handler handler, boolean startAtFrontOfQueue,
+ boolean fromUnlock) {
+ super(handler, startAtFrontOfQueue);
+ mFromUnlock = fromUnlock;
+ }
+
+ @Override
+ public void onCreateAnimation(RemoteAnimationTargetCompat[] targetCompats,
+ LauncherAnimationRunner.AnimationResult result) {
+ if (!mLauncher.hasBeenResumed()) {
+ // If launcher is not resumed, wait until new async-frame after resume
+ mLauncher.setOnResumeCallback(() ->
+ postAsyncCallback(mHandler, () ->
+ onCreateAnimation(targetCompats, result)));
+ return;
+ }
+
+ if (mLauncher.hasSomeInvisibleFlag(PENDING_INVISIBLE_BY_WALLPAPER_ANIMATION)) {
+ mLauncher.addForceInvisibleFlag(INVISIBLE_BY_PENDING_FLAGS);
+ mLauncher.getStateManager().moveToRestState();
+ }
+
+ AnimatorSet anim = null;
+ RemoteAnimationProvider provider = mRemoteAnimationProvider;
+ if (provider != null) {
+ anim = provider.createWindowAnimation(targetCompats);
+ }
+
+ if (anim == null) {
+ anim = new AnimatorSet();
+ anim.play(mFromUnlock
+ ? getUnlockWindowAnimator(targetCompats)
+ : getClosingWindowAnimators(targetCompats));
+
+ // Normally, we run the launcher content animation when we are transitioning
+ // home, but if home is already visible, then we don't want to animate the
+ // contents of launcher unless we know that we are animating home as a result
+ // of the home button press with quickstep, which will result in launcher being
+ // started on touch down, prior to the animation home (and won't be in the
+ // targets list because it is already visible). In that case, we force
+ // invisibility on touch down, and only reset it after the animation to home
+ // is initialized.
+ if (launcherIsATargetWithMode(targetCompats, MODE_OPENING)
+ || mLauncher.isForceInvisible()) {
+ // Only register the content animation for cancellation when state changes
+ mLauncher.getStateManager().setCurrentAnimation(anim);
+ if (mFromUnlock) {
+ Pair<AnimatorSet, Runnable> contentAnimator =
+ getLauncherContentAnimator(false /* isAppOpening */,
+ new float[] {mContentTransY, 0});
+ contentAnimator.first.setStartDelay(0);
+ anim.play(contentAnimator.first);
+ anim.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ contentAnimator.second.run();
+ }
+ });
+ } else {
+ createLauncherResumeAnimation(anim);
+ }
+ }
+ }
+
+ mLauncher.clearForceInvisibleFlag(INVISIBLE_ALL);
+ result.setAnimation(anim);
+ }
+ }
}