Merge "Hand off gesture nav animations to registered remotes." into main
diff --git a/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java b/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java
index 95e7737..21c4d8c 100644
--- a/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java
+++ b/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java
@@ -56,9 +56,11 @@
import static com.android.quickstep.GestureState.STATE_RECENTS_ANIMATION_STARTED;
import static com.android.quickstep.GestureState.STATE_RECENTS_SCROLLING_FINISHED;
import static com.android.quickstep.MultiStateCallback.DEBUG_STATES;
+import static com.android.quickstep.TaskViewUtils.extractTargetsAndStates;
import static com.android.quickstep.util.ActiveGestureErrorDetector.GestureEvent.EXPECTING_TASK_APPEARED;
import static com.android.quickstep.views.RecentsView.UPDATE_SYSUI_FLAGS_THRESHOLD;
import static com.android.systemui.shared.system.ActivityManagerWrapper.CLOSE_SYSTEM_WINDOWS_REASON_RECENTS;
+import static com.android.wm.shell.shared.ShellSharedConstants.KEY_EXTRA_SHELL_CAN_HAND_OFF_ANIMATION;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
@@ -77,6 +79,7 @@
import android.os.IBinder;
import android.os.SystemClock;
import android.util.Log;
+import android.util.Pair;
import android.view.MotionEvent;
import android.view.RemoteAnimationTarget;
import android.view.SurfaceControl;
@@ -90,6 +93,7 @@
import android.widget.Toast;
import android.window.DesktopModeFlags;
import android.window.PictureInPictureSurfaceTransaction;
+import android.window.WindowAnimationState;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
@@ -143,6 +147,7 @@
import com.android.quickstep.views.RecentsViewContainer;
import com.android.quickstep.views.TaskContainer;
import com.android.quickstep.views.TaskView;
+import com.android.systemui.animation.TransitionAnimator;
import com.android.systemui.contextualeducation.GestureType;
import com.android.systemui.shared.recents.model.Task;
import com.android.systemui.shared.recents.model.ThumbnailData;
@@ -156,6 +161,8 @@
import com.android.wm.shell.shared.desktopmode.DesktopModeStatus;
import com.android.wm.shell.shared.startingsurface.SplashScreenExitAnimationUtils;
+import kotlin.Unit;
+
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
@@ -165,8 +172,6 @@
import java.util.OptionalInt;
import java.util.function.Consumer;
-import kotlin.Unit;
-
/**
* Handles the navigation gestures when Launcher is the default home activity.
*/
@@ -347,6 +352,9 @@
// Indicates whether the divider is shown, only used when split screen is activated.
private boolean mIsDividerShown = true;
private boolean mStartMovingTasks;
+ // Whether the animation to home should be handed off to another handler once the gesture is
+ // committed.
+ protected boolean mHandOffAnimationToHome = false;
@Nullable
private RemoteAnimationTargets.ReleaseCheck mSwipePipToHomeReleaseCheck = null;
@@ -945,6 +953,10 @@
mSwipePipToHomeReleaseCheck = new RemoteAnimationTargets.ReleaseCheck();
mSwipePipToHomeReleaseCheck.setCanRelease(true);
mRecentsAnimationTargets.addReleaseCheck(mSwipePipToHomeReleaseCheck);
+ if (TransitionAnimator.Companion.longLivedReturnAnimationsEnabled()) {
+ mHandOffAnimationToHome =
+ targets.extras.getBoolean(KEY_EXTRA_SHELL_CAN_HAND_OFF_ANIMATION, false);
+ }
// Only initialize the device profile, if it has not been initialized before, as in some
// configurations targets.homeContentInsets may not be correct.
@@ -1629,6 +1641,10 @@
}
windowAnim = createWindowAnimationToHome(start, homeAnimFactory);
+ if (mHandOffAnimationToHome) {
+ handOffAnimation(velocityPxPerMs);
+ }
+
windowAnim[0].addAnimatorListener(new AnimationSuccessListener() {
@Override
public void onAnimationSuccess(Animator animator) {
@@ -1711,6 +1727,19 @@
}
}
+ private void handOffAnimation(PointF velocityPxPerMs) {
+ if (!TransitionAnimator.Companion.longLivedReturnAnimationsEnabled()
+ || mRecentsAnimationController == null) {
+ return;
+ }
+
+ Pair<RemoteAnimationTarget[], WindowAnimationState[]> targetsAndStates =
+ extractTargetsAndStates(mRemoteTargetHandles, velocityPxPerMs);
+ mRecentsAnimationController.handOffAnimation(
+ targetsAndStates.first, targetsAndStates.second);
+ ActiveGestureProtoLogProxy.logHandOffAnimation();
+ }
+
private int calculateWindowRotation(RemoteAnimationTarget runningTaskTarget,
RecentsOrientedState orientationState) {
if (runningTaskTarget.rotationChange != 0) {
diff --git a/quickstep/src/com/android/quickstep/LauncherSwipeHandlerV2.java b/quickstep/src/com/android/quickstep/LauncherSwipeHandlerV2.java
index dacafd4..6087dc2 100644
--- a/quickstep/src/com/android/quickstep/LauncherSwipeHandlerV2.java
+++ b/quickstep/src/com/android/quickstep/LauncherSwipeHandlerV2.java
@@ -46,7 +46,6 @@
import com.android.launcher3.views.FloatingIconView;
import com.android.launcher3.views.FloatingView;
import com.android.launcher3.widget.LauncherAppWidgetHostView;
-import com.android.quickstep.fallback.window.RecentsWindowManager;
import com.android.quickstep.util.RectFSpringAnim;
import com.android.quickstep.util.ScalingWorkspaceRevealAnim;
import com.android.quickstep.util.StaggeredWorkspaceAnim;
@@ -54,6 +53,7 @@
import com.android.quickstep.views.FloatingWidgetView;
import com.android.quickstep.views.RecentsView;
import com.android.quickstep.views.TaskView;
+import com.android.systemui.animation.TransitionAnimator;
import com.android.systemui.shared.system.InputConsumerController;
import java.util.Collections;
@@ -108,7 +108,9 @@
mContainer.getRootView().setForceHideBackArrow(true);
- if (!canUseWorkspaceView || appCanEnterPip || mIsSwipeForSplit) {
+ boolean handOffAnimation = TransitionAnimator.Companion.longLivedReturnAnimationsEnabled()
+ && mHandOffAnimationToHome;
+ if (handOffAnimation || !canUseWorkspaceView || appCanEnterPip || mIsSwipeForSplit) {
return new LauncherHomeAnimationFactory() {
@Nullable
diff --git a/quickstep/src/com/android/quickstep/RecentsAnimationController.java b/quickstep/src/com/android/quickstep/RecentsAnimationController.java
index 60fcff8..145773d 100644
--- a/quickstep/src/com/android/quickstep/RecentsAnimationController.java
+++ b/quickstep/src/com/android/quickstep/RecentsAnimationController.java
@@ -21,9 +21,11 @@
import android.os.Bundle;
import android.os.RemoteException;
import android.util.Log;
+import android.view.RemoteAnimationTarget;
import android.view.SurfaceControl;
import android.view.WindowManagerGlobal;
import android.window.PictureInPictureSurfaceTransaction;
+import android.window.WindowAnimationState;
import androidx.annotation.UiThread;
@@ -32,6 +34,7 @@
import com.android.launcher3.util.Preconditions;
import com.android.launcher3.util.RunnableList;
import com.android.quickstep.util.ActiveGestureProtoLogProxy;
+import com.android.systemui.animation.TransitionAnimator;
import com.android.systemui.shared.recents.model.ThumbnailData;
import com.android.systemui.shared.system.InteractionJankMonitorWrapper;
import com.android.systemui.shared.system.RecentsAnimationControllerCompat;
@@ -90,6 +93,16 @@
}
@UiThread
+ public void handOffAnimation(RemoteAnimationTarget[] targets, WindowAnimationState[] states) {
+ if (TransitionAnimator.Companion.longLivedReturnAnimationsEnabled()) {
+ UI_HELPER_EXECUTOR.execute(() -> mController.handOffAnimation(targets, states));
+ } else {
+ Log.e(TAG, "Tried to hand off the animation, but the feature is disabled",
+ new Exception());
+ }
+ }
+
+ @UiThread
public void finishAnimationToHome() {
finishController(true /* toRecents */, null, false /* sendUserLeaveHint */);
}
diff --git a/quickstep/src/com/android/quickstep/TaskViewUtils.java b/quickstep/src/com/android/quickstep/TaskViewUtils.java
index 07ee479..783c87c 100644
--- a/quickstep/src/com/android/quickstep/TaskViewUtils.java
+++ b/quickstep/src/com/android/quickstep/TaskViewUtils.java
@@ -47,12 +47,15 @@
import android.content.Context;
import android.graphics.Matrix;
import android.graphics.Matrix.ScaleToFit;
+import android.graphics.PointF;
import android.graphics.Rect;
import android.graphics.RectF;
+import android.util.Pair;
import android.view.RemoteAnimationTarget;
import android.view.SurfaceControl;
import android.view.View;
import android.window.TransitionInfo;
+import android.window.WindowAnimationState;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
@@ -784,4 +787,43 @@
animatorHandler.accept(dockFadeAnimator);
return dockFadeAnimator;
}
+
+ /**
+ * Creates an array of {@link RemoteAnimationTarget}s and a matching array of
+ * {@link WindowAnimationState}s from the provided handles.
+ * Important: the ordering of the two arrays is the same, so the state at each index of the
+ * second applies to the target in the same index of the first.
+ *
+ * @param handles The handles wrapping each target.
+ * @param velocityPxPerMs The current velocity of the target animations.
+ */
+ @NonNull
+ public static Pair<RemoteAnimationTarget[], WindowAnimationState[]> extractTargetsAndStates(
+ @NonNull RemoteTargetHandle[] handles, @NonNull PointF velocityPxPerMs) {
+ RemoteAnimationTarget[] targets = new RemoteAnimationTarget[handles.length];
+ WindowAnimationState[] animationStates = new WindowAnimationState[handles.length];
+ long timestamp = System.currentTimeMillis();
+
+ for (int i = 0; i < handles.length; i++) {
+ targets[i] = handles[i].getTransformParams().getTargetSet().apps[i];
+
+ TaskViewSimulator taskViewSimulator = handles[i].getTaskViewSimulator();
+ RectF startRect = taskViewSimulator.getCurrentRect();
+ float cornerRadius = taskViewSimulator.getCurrentCornerRadius();
+
+ WindowAnimationState state = new WindowAnimationState();
+ state.timestamp = timestamp;
+ state.bounds = new RectF(
+ startRect.left, startRect.top, startRect.right, startRect.bottom);
+ state.topLeftRadius = cornerRadius;
+ state.topRightRadius = cornerRadius;
+ state.bottomRightRadius = cornerRadius;
+ state.bottomLeftRadius = cornerRadius;
+ state.velocityPxPerMs = velocityPxPerMs;
+
+ animationStates[i] = state;
+ }
+
+ return new Pair<>(targets, animationStates);
+ }
}
diff --git a/quickstep/src_protolog/com/android/quickstep/util/ActiveGestureProtoLogProxy.java b/quickstep/src_protolog/com/android/quickstep/util/ActiveGestureProtoLogProxy.java
index f43a125..0091036 100644
--- a/quickstep/src_protolog/com/android/quickstep/util/ActiveGestureProtoLogProxy.java
+++ b/quickstep/src_protolog/com/android/quickstep/util/ActiveGestureProtoLogProxy.java
@@ -96,6 +96,12 @@
+ "force finish recents animation complete; clearing state callback.");
}
+ public static void logHandOffAnimation() {
+ ActiveGestureLog.INSTANCE.addLog("AbsSwipeUpHandler.handOffAnimation");
+ if (!enableActiveGestureProtoLog()) return;
+ ProtoLog.d(ACTIVE_GESTURE_LOG, "AbsSwipeUpHandler.handOffAnimation");
+ }
+
public static void logFinishRecentsAnimationOnTasksAppeared() {
ActiveGestureLog.INSTANCE.addLog("finishRecentsAnimationOnTasksAppeared");
if (!enableActiveGestureProtoLog()) return;
diff --git a/quickstep/tests/multivalentTests/src/com/android/quickstep/AbsSwipeUpHandlerTestCase.java b/quickstep/tests/multivalentTests/src/com/android/quickstep/AbsSwipeUpHandlerTestCase.java
index 6b95f8d..970bdec 100644
--- a/quickstep/tests/multivalentTests/src/com/android/quickstep/AbsSwipeUpHandlerTestCase.java
+++ b/quickstep/tests/multivalentTests/src/com/android/quickstep/AbsSwipeUpHandlerTestCase.java
@@ -17,6 +17,7 @@
package com.android.quickstep;
import static com.android.quickstep.AbsSwipeUpHandler.STATE_HANDLER_INVALIDATED;
+import static com.android.wm.shell.shared.ShellSharedConstants.KEY_EXTRA_SHELL_CAN_HAND_OFF_ANIMATION;
import static junit.framework.TestCase.assertFalse;
import static junit.framework.TestCase.assertTrue;
@@ -28,6 +29,7 @@
import static org.mockito.Mockito.atLeast;
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
@@ -40,6 +42,9 @@
import android.graphics.Rect;
import android.os.Bundle;
import android.os.SystemClock;
+import android.platform.test.annotations.DisableFlags;
+import android.platform.test.annotations.EnableFlags;
+import android.platform.test.flag.junit.SetFlagsRule;
import android.view.RemoteAnimationTarget;
import android.view.SurfaceControl;
import android.view.ViewTreeObserver;
@@ -58,6 +63,7 @@
import com.android.quickstep.util.ContextInitListener;
import com.android.quickstep.views.RecentsView;
import com.android.quickstep.views.RecentsViewContainer;
+import com.android.systemui.shared.Flags;
import com.android.systemui.shared.system.InputConsumerController;
import org.junit.Before;
@@ -103,14 +109,8 @@
/* startBounds= */ null,
/* taskInfo= */ mRunningTaskInfo,
/* allowEnterPip= */ false);
- protected final RecentsAnimationTargets mRecentsAnimationTargets = new RecentsAnimationTargets(
- new RemoteAnimationTarget[] {mRemoteAnimationTarget},
- new RemoteAnimationTarget[] {mRemoteAnimationTarget},
- new RemoteAnimationTarget[] {mRemoteAnimationTarget},
- /* homeContentInsets= */ new Rect(),
- /* minimizedHomeBounds= */ null,
- new Bundle());
+ protected RecentsAnimationTargets mRecentsAnimationTargets;
protected TaskAnimationManager mTaskAnimationManager;
protected RecentsAnimationDeviceState mRecentsAnimationDeviceState;
@@ -127,6 +127,22 @@
@Rule
public final MockitoRule mMockitoRule = MockitoJUnit.rule();
+ @Rule
+ public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
+
+ @Before
+ public void setUpAnimationTargets() {
+ Bundle extras = new Bundle();
+ extras.putBoolean(KEY_EXTRA_SHELL_CAN_HAND_OFF_ANIMATION, true);
+ mRecentsAnimationTargets = new RecentsAnimationTargets(
+ new RemoteAnimationTarget[] {mRemoteAnimationTarget},
+ new RemoteAnimationTarget[] {mRemoteAnimationTarget},
+ new RemoteAnimationTarget[] {mRemoteAnimationTarget},
+ /* homeContentInsets= */ new Rect(),
+ /* minimizedHomeBounds= */ null,
+ extras);
+ }
+
@Before
public void setUpRunningTaskInfo() {
mRunningTaskInfo.baseIntent = new Intent(Intent.ACTION_MAIN)
@@ -237,6 +253,30 @@
});
}
+ @EnableFlags({Flags.FLAG_RETURN_ANIMATION_FRAMEWORK_LIBRARY,
+ Flags.FLAG_RETURN_ANIMATION_FRAMEWORK_LONG_LIVED})
+ @Test
+ public void testHomeGesture_handsOffAnimation() {
+ createSwipeUpHandlerForGesture(GestureState.GestureEndTarget.HOME);
+
+ runOnMainSync(() -> {
+ verify(mRecentsAnimationController).handOffAnimation(any(), any());
+ verifyRecentsAnimationFinishedAndCallCallback();
+ });
+ }
+
+ @DisableFlags({Flags.FLAG_RETURN_ANIMATION_FRAMEWORK_LIBRARY,
+ Flags.FLAG_RETURN_ANIMATION_FRAMEWORK_LONG_LIVED})
+ @Test
+ public void testHomeGesture_doesNotHandOffAnimation_withFlagsDisabled() {
+ createSwipeUpHandlerForGesture(GestureState.GestureEndTarget.HOME);
+
+ runOnMainSync(() -> {
+ verify(mRecentsAnimationController, never()).handOffAnimation(any(), any());
+ verifyRecentsAnimationFinishedAndCallCallback();
+ });
+ }
+
@Test
public void testHomeGesture_invalidatesHandlerAfterParallelAnim() {
ValueAnimator parallelAnim = new ValueAnimator();