summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
author Luca Zuccarini <acul@google.com> 2024-11-18 14:38:59 +0000
committer Luca Zuccarini <acul@google.com> 2024-11-20 10:31:28 +0000
commit33dfe5e7b3345c194621a3b9beb1d7632dc10975 (patch)
treed2790867301704e95587afbcf975b74d5d50820b
parent7241c8c5304d72c585f48eb365fc6c382e17aca2 (diff)
Hand off gesture nav animations to registered remotes.
This is in support of long-lived return animations in the Animation library. Sometimes we want the home gesture to minimize the foreground app into a custom view other than the default Launcher behavior. For example, ongoing call will minimize to the status bar chip, even if the app icon is on Home. This is guaranteed to be handled is the takeover handler is not null, and only happens in this case (which means a custom animation has been registered and is ready to run). Bug: 323863002 Bug: 202516970 Flag: com.android.systemui.shared.return_animation_framework_library Flag: com.android.systemui.shared.return_animation_framework_long_lived Test: manual and unit test included Change-Id: Id7cd1f6e92ad3cbe3c259b3f80c753c91472b455
-rw-r--r--quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java33
-rw-r--r--quickstep/src/com/android/quickstep/LauncherSwipeHandlerV2.java6
-rw-r--r--quickstep/src/com/android/quickstep/RecentsAnimationController.java13
-rw-r--r--quickstep/src/com/android/quickstep/TaskViewUtils.java42
-rw-r--r--quickstep/src_protolog/com/android/quickstep/util/ActiveGestureProtoLogProxy.java6
-rw-r--r--quickstep/tests/multivalentTests/src/com/android/quickstep/AbsSwipeUpHandlerTestCase.java54
6 files changed, 143 insertions, 11 deletions
diff --git a/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java b/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java
index 95e7737906..21c4d8cffb 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_CANCELE
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.graphics.RectF;
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.view.animation.Interpolator;
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.RecentsView;
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.TransactionPool;
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.Optional;
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 @@ public abstract class AbsSwipeUpHandler<
// 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 @@ public abstract class AbsSwipeUpHandler<
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 @@ public abstract class AbsSwipeUpHandler<
}
windowAnim = createWindowAnimationToHome(start, homeAnimFactory);
+ if (mHandOffAnimationToHome) {
+ handOffAnimation(velocityPxPerMs);
+ }
+
windowAnim[0].addAnimatorListener(new AnimationSuccessListener() {
@Override
public void onAnimationSuccess(Animator animator) {
@@ -1711,6 +1727,19 @@ public abstract class AbsSwipeUpHandler<
}
}
+ 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 dacafd47ac..6087dc214e 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.ClipIconView;
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.util.TaskViewSimulator;
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 @@ public class LauncherSwipeHandlerV2 extends AbsSwipeUpHandler<
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 60fcff86f3..145773d0c2 100644
--- a/quickstep/src/com/android/quickstep/RecentsAnimationController.java
+++ b/quickstep/src/com/android/quickstep/RecentsAnimationController.java
@@ -21,9 +21,11 @@ import static com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR;
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.internal.os.IResultReceiver;
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 @@ public class RecentsAnimationController {
}
@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 07ee4793de..783c87c8af 100644
--- a/quickstep/src/com/android/quickstep/TaskViewUtils.java
+++ b/quickstep/src/com/android/quickstep/TaskViewUtils.java
@@ -47,12 +47,15 @@ import android.content.ComponentName;
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 @@ public final class TaskViewUtils {
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 f43a125aa4..00910365ff 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 @@ public class ActiveGestureProtoLogProxy {
+ "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 6b95f8d3dd..970bdecc80 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.ArgumentMatchers.eq;
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.PointF;
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.fallback.window.RecentsWindowManager;
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 @@ public abstract class AbsSwipeUpHandlerTestCase<
/* 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 @@ public abstract class AbsSwipeUpHandlerTestCase<
@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 @@ public abstract class AbsSwipeUpHandlerTestCase<
});
}
+ @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();