Merge "Launcher side implementation of gesture seekable back to home animation." into tm-dev
diff --git a/quickstep/src/com/android/launcher3/QuickstepTransitionManager.java b/quickstep/src/com/android/launcher3/QuickstepTransitionManager.java
index 9a70146..f14e2a2 100644
--- a/quickstep/src/com/android/launcher3/QuickstepTransitionManager.java
+++ b/quickstep/src/com/android/launcher3/QuickstepTransitionManager.java
@@ -111,6 +111,7 @@
import com.android.launcher3.views.FloatingIconView;
import com.android.launcher3.views.ScrimView;
import com.android.launcher3.widget.LauncherAppWidgetHostView;
+import com.android.quickstep.LauncherBackAnimationController;
import com.android.quickstep.RemoteAnimationTargets;
import com.android.quickstep.SystemUiProxy;
import com.android.quickstep.TaskViewUtils;
@@ -216,6 +217,7 @@
private RemoteAnimationFactory mWallpaperOpenTransitionRunner;
private RemoteTransitionCompat mLauncherOpenTransition;
+ private LauncherBackAnimationController mBackAnimationController;
private final AnimatorListenerAdapter mForceInvisibleListener = new AnimatorListenerAdapter() {
@Override
public void onAnimationStart(Animator animation) {
@@ -241,6 +243,8 @@
mDragLayerAlpha = mDragLayer.getAlphaProperty(ALPHA_INDEX_TRANSITIONS);
mHandler = new Handler(Looper.getMainLooper());
mDeviceProfile = mLauncher.getDeviceProfile();
+ mBackAnimationController = new LauncherBackAnimationController(
+ mDeviceProfile, mLauncher, this);
Resources res = mLauncher.getResources();
mContentScale = res.getFloat(R.dimen.content_scale);
@@ -1159,6 +1163,9 @@
mLauncherOpenTransition.addHomeOpenCheck(mLauncher.getComponentName());
SystemUiProxy.INSTANCE.getNoCreate().registerRemoteTransition(mLauncherOpenTransition);
}
+ if (mBackAnimationController != null) {
+ mBackAnimationController.registerBackCallbacks(mHandler);
+ }
}
public void onActivityDestroyed() {
@@ -1194,6 +1201,10 @@
mLauncherOpenTransition = null;
mWallpaperOpenTransitionRunner = null;
}
+ if (mBackAnimationController != null) {
+ mBackAnimationController.unregisterBackCallbacks();
+ mBackAnimationController = null;
+ }
}
private boolean launcherIsATargetWithMode(RemoteAnimationTargetCompat[] targets, int mode) {
@@ -1346,8 +1357,9 @@
/**
* Closing animator that animates the window into its final location on the workspace.
*/
- private void getClosingWindowAnimators(AnimatorSet animation,
- RemoteAnimationTargetCompat[] targets, View launcherView, PointF velocityPxPerS) {
+ private RectFSpringAnim getClosingWindowAnimators(AnimatorSet animation,
+ RemoteAnimationTargetCompat[] targets, View launcherView, PointF velocityPxPerS,
+ RectF closingWindowStartRect) {
FloatingIconView floatingIconView = null;
FloatingWidgetView floatingWidget = null;
RectF targetRect = new RectF();
@@ -1379,8 +1391,7 @@
targetRect.set(getDefaultWindowTargetRect());
}
- final RectF startRect = new RectF(0, 0, mDeviceProfile.widthPx, mDeviceProfile.heightPx);
- RectFSpringAnim anim = new RectFSpringAnim(startRect, targetRect, mLauncher,
+ RectFSpringAnim anim = new RectFSpringAnim(closingWindowStartRect, targetRect, mLauncher,
mDeviceProfile);
// Hook up floating views to the closing window animators.
@@ -1414,7 +1425,7 @@
final float floatingWidgetAlpha = isTransluscent ? 0 : 1;
FloatingWidgetView finalFloatingWidget = floatingWidget;
- RectFSpringAnim.OnUpdateListener runner = new SpringAnimRunner(targets, targetRect,
+ RectFSpringAnim.OnUpdateListener runner = new SpringAnimRunner(targets, targetRect,
windowTargetBounds) {
@Override
public void onUpdate(RectF currentRectF, float progress) {
@@ -1438,6 +1449,7 @@
anim.start(mLauncher, velocityPxPerS);
}
});
+ return anim;
}
/**
@@ -1562,6 +1574,97 @@
}
/**
+ * Creates the {@link RectFSpringAnim} and {@link AnimatorSet} required to animate
+ * the transition.
+ */
+ public Pair<RectFSpringAnim, AnimatorSet> createWallpaperOpenAnimations(
+ RemoteAnimationTargetCompat[] appTargets,
+ RemoteAnimationTargetCompat[] wallpaperTargets,
+ boolean fromUnlock,
+ RectF startRect) {
+ AnimatorSet anim = null;
+ RectFSpringAnim rectFSpringAnim = null;
+
+ RemoteAnimationProvider provider = mRemoteAnimationProvider;
+ if (provider != null) {
+ anim = provider.createWindowAnimation(appTargets, wallpaperTargets);
+ }
+
+ if (anim == null) {
+ anim = new AnimatorSet();
+
+ final boolean launcherIsForceInvisibleOrOpening = mLauncher.isForceInvisible()
+ || launcherIsATargetWithMode(appTargets, MODE_OPENING);
+
+ View launcherView = findLauncherView(appTargets);
+ boolean playFallBackAnimation = (launcherView == null
+ && launcherIsForceInvisibleOrOpening)
+ || mLauncher.getWorkspace().isOverlayShown()
+ || hasMultipleTargetsWithMode(appTargets, MODE_CLOSING);
+
+ boolean playWorkspaceReveal = true;
+ boolean skipAllAppsScale = false;
+ if (fromUnlock) {
+ anim.play(getUnlockWindowAnimator(appTargets, wallpaperTargets));
+ } else if (ENABLE_BACK_SWIPE_HOME_ANIMATION.get()
+ && !playFallBackAnimation) {
+ // Use a fixed velocity to start the animation.
+ float velocityPxPerS = DynamicResource.provider(mLauncher)
+ .getDimension(R.dimen.unlock_staggered_velocity_dp_per_s);
+ PointF velocity = new PointF(0, -velocityPxPerS);
+ rectFSpringAnim = getClosingWindowAnimators(
+ anim, appTargets, launcherView, velocity, startRect);
+ if (!mLauncher.isInState(LauncherState.ALL_APPS)) {
+ anim.play(new StaggeredWorkspaceAnim(mLauncher, velocity.y,
+ true /* animateOverviewScrim */, launcherView).getAnimators());
+ // We play StaggeredWorkspaceAnim as a part of the closing window animation.
+ playWorkspaceReveal = false;
+ } else {
+ // Skip scaling all apps, otherwise FloatingIconView will get wrong
+ // layout bounds.
+ skipAllAppsScale = true;
+ }
+ } else {
+ anim.play(getFallbackClosingWindowAnimators(appTargets));
+ }
+
+ // 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 (launcherIsForceInvisibleOrOpening) {
+ addCujInstrumentation(
+ anim, InteractionJankMonitorWrapper.CUJ_APP_CLOSE_TO_HOME);
+ // Only register the content animation for cancellation when state changes
+ mLauncher.getStateManager().setCurrentAnimation(anim);
+
+ if (mLauncher.isInState(LauncherState.ALL_APPS)) {
+ Pair<AnimatorSet, Runnable> contentAnimator =
+ getLauncherContentAnimator(false, LAUNCHER_RESUME_START_DELAY,
+ skipAllAppsScale);
+ anim.play(contentAnimator.first);
+ anim.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ contentAnimator.second.run();
+ }
+ });
+ } else {
+ if (playWorkspaceReveal) {
+ anim.play(new WorkspaceRevealAnim(mLauncher, false).getAnimators());
+ }
+ }
+ }
+ }
+
+ return new Pair(rectFSpringAnim, anim);
+ }
+
+ /**
* Remote animation runner for animation from the app to Launcher, including recents.
*/
protected class WallpaperOpenLauncherAnimationRunner implements RemoteAnimationFactory {
@@ -1601,84 +1704,12 @@
mLauncher.getStateManager().moveToRestState();
}
- AnimatorSet anim = null;
- RemoteAnimationProvider provider = mRemoteAnimationProvider;
- if (provider != null) {
- anim = provider.createWindowAnimation(appTargets, wallpaperTargets);
- }
-
- if (anim == null) {
- anim = new AnimatorSet();
-
- final boolean launcherIsForceInvisibleOrOpening = mLauncher.isForceInvisible()
- || launcherIsATargetWithMode(appTargets, MODE_OPENING);
-
- View launcherView = findLauncherView(appTargets);
- boolean playFallBackAnimation = (launcherView == null
- && launcherIsForceInvisibleOrOpening)
- || mLauncher.getWorkspace().isOverlayShown()
- || hasMultipleTargetsWithMode(appTargets, MODE_CLOSING);
-
- boolean playWorkspaceReveal = true;
- boolean skipAllAppsScale = false;
- if (mFromUnlock) {
- anim.play(getUnlockWindowAnimator(appTargets, wallpaperTargets));
- } else if (ENABLE_BACK_SWIPE_HOME_ANIMATION.get()
- && !playFallBackAnimation) {
- // Use a fixed velocity to start the animation.
- float velocityPxPerS = DynamicResource.provider(mLauncher)
- .getDimension(R.dimen.unlock_staggered_velocity_dp_per_s);
- PointF velocity = new PointF(0, -velocityPxPerS);
- getClosingWindowAnimators(anim, appTargets, launcherView, velocity);
- if (!mLauncher.isInState(LauncherState.ALL_APPS)) {
- anim.play(new StaggeredWorkspaceAnim(mLauncher, velocity.y,
- true /* animateOverviewScrim */, launcherView).getAnimators());
- // We play StaggeredWorkspaceAnim as a part of the closing window animation.
- playWorkspaceReveal = false;
- } else {
- // Skip scaling all apps, otherwise FloatingIconView will get wrong
- // layout bounds.
- skipAllAppsScale = true;
- }
- } else {
- anim.play(getFallbackClosingWindowAnimators(appTargets));
- }
-
- // 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 (launcherIsForceInvisibleOrOpening) {
- addCujInstrumentation(
- anim, InteractionJankMonitorWrapper.CUJ_APP_CLOSE_TO_HOME);
- // Only register the content animation for cancellation when state changes
- mLauncher.getStateManager().setCurrentAnimation(anim);
-
- if (mLauncher.isInState(LauncherState.ALL_APPS)) {
- Pair<AnimatorSet, Runnable> contentAnimator =
- getLauncherContentAnimator(false, LAUNCHER_RESUME_START_DELAY,
- skipAllAppsScale);
- anim.play(contentAnimator.first);
- anim.addListener(new AnimatorListenerAdapter() {
- @Override
- public void onAnimationEnd(Animator animation) {
- contentAnimator.second.run();
- }
- });
- } else {
- if (playWorkspaceReveal) {
- anim.play(new WorkspaceRevealAnim(mLauncher, false).getAnimators());
- }
- }
- }
- }
+ Pair<RectFSpringAnim, AnimatorSet> pair = createWallpaperOpenAnimations(
+ appTargets, wallpaperTargets, mFromUnlock,
+ new RectF(0, 0, mDeviceProfile.widthPx, mDeviceProfile.heightPx));
mLauncher.clearForceInvisibleFlag(INVISIBLE_ALL);
- result.setAnimation(anim, mLauncher);
+ result.setAnimation(pair.second, mLauncher);
}
}
diff --git a/quickstep/src/com/android/quickstep/LauncherBackAnimationController.java b/quickstep/src/com/android/quickstep/LauncherBackAnimationController.java
new file mode 100644
index 0000000..7abcbdb
--- /dev/null
+++ b/quickstep/src/com/android/quickstep/LauncherBackAnimationController.java
@@ -0,0 +1,308 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.quickstep;
+
+import static com.android.launcher3.BaseActivity.INVISIBLE_ALL;
+import static com.android.launcher3.BaseActivity.INVISIBLE_BY_PENDING_FLAGS;
+import static com.android.launcher3.BaseActivity.PENDING_INVISIBLE_BY_WALLPAPER_ANIMATION;
+
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.animation.AnimatorSet;
+import android.animation.ValueAnimator;
+import android.graphics.Matrix;
+import android.graphics.Rect;
+import android.graphics.RectF;
+import android.os.Handler;
+import android.util.Log;
+import android.util.MathUtils;
+import android.util.Pair;
+import android.view.RemoteAnimationTarget;
+import android.view.SurfaceControl;
+import android.window.BackEvent;
+import android.window.IOnBackInvokedCallback;
+
+import com.android.launcher3.BaseQuickstepLauncher;
+import com.android.launcher3.DeviceProfile;
+import com.android.launcher3.QuickstepTransitionManager;
+import com.android.launcher3.R;
+import com.android.launcher3.Utilities;
+import com.android.quickstep.util.RectFSpringAnim;
+import com.android.systemui.shared.system.QuickStepContract;
+import com.android.systemui.shared.system.RemoteAnimationTargetCompat;
+import com.android.systemui.shared.system.SyncRtSurfaceTransactionApplierCompat;
+/**
+ * Controls the animation of swiping back and returning to launcher.
+ *
+ * This is a two part animation. The first part is an animation that tracks gesture location to
+ * scale and move the leaving app window. Once the gesture is committed, the second part takes over
+ * the app window and plays the rest of app close transitions in one go.
+ *
+ * This animation is used only for apps that enable back dispatching via
+ * {@link android.view.OnBackInvokedDispatcher}. The controller registers
+ * an {@link IOnBackInvokedCallback} with WM Shell and receives back dispatches when a back
+ * navigation to launcher starts.
+ *
+ * Apps using the legacy back dispatching will keep triggering the WALLPAPER_OPEN remote
+ * transition registered in {@link QuickstepTransitionManager}.
+ *
+ */
+public class LauncherBackAnimationController {
+ private static final int CANCEL_TRANSITION_DURATION = 150;
+ private static final String TAG = "LauncherBackAnimationController";
+ private final DeviceProfile mDeviceProfile;
+ private final QuickstepTransitionManager mQuickstepTransitionManager;
+ private final Matrix mTransformMatrix = new Matrix();
+ private final RectF mTargetRectF = new RectF();
+ private final RectF mStartRectF = new RectF();
+ private final RectF mCurrentRect = new RectF();
+ private final BaseQuickstepLauncher mLauncher;
+ private final int mWindowScaleMarginX;
+ private final int mWindowScaleMarginY;
+ private final float mWindowScaleEndCornerRadius;
+ private final float mWindowScaleStartCornerRadius;
+
+ private RemoteAnimationTargetCompat mBackTarget;
+ private SurfaceControl.Transaction mTransaction = new SurfaceControl.Transaction();
+ private boolean mSpringAnimationInProgress = false;
+ private boolean mAnimatorSetInProgress = false;
+ @BackEvent.SwipeEdge
+ private int mSwipeEdge;
+ private float mBackProgress = 0;
+ private boolean mBackInProgress = false;
+
+ public LauncherBackAnimationController(
+ DeviceProfile deviceProfile,
+ BaseQuickstepLauncher launcher,
+ QuickstepTransitionManager quickstepTransitionManager) {
+ mDeviceProfile = deviceProfile;
+ mLauncher = launcher;
+ mQuickstepTransitionManager = quickstepTransitionManager;
+ mWindowScaleEndCornerRadius = QuickStepContract.supportsRoundedCornersOnWindows(
+ mLauncher.getResources())
+ ? mLauncher.getResources().getDimensionPixelSize(
+ R.dimen.swipe_back_window_corner_radius)
+ : 0;
+ mWindowScaleStartCornerRadius = QuickStepContract.getWindowCornerRadius(mLauncher);
+ mWindowScaleMarginX = mLauncher.getResources().getDimensionPixelSize(
+ R.dimen.swipe_back_window_scale_x_margin);
+ mWindowScaleMarginY = mLauncher.getResources().getDimensionPixelSize(
+ R.dimen.swipe_back_window_scale_y_margin);
+ }
+
+ /**
+ * Registers {@link IOnBackInvokedCallback} to receive back dispatches from shell.
+ * @param handler Handler to the thread to run the animations on.
+ */
+ public void registerBackCallbacks(Handler handler) {
+ SystemUiProxy systemUiProxy = SystemUiProxy.INSTANCE.getNoCreate();
+ if (systemUiProxy == null) {
+ Log.e(TAG, "SystemUiProxy is null. Skip registering back invocation callbacks");
+ return;
+ }
+ systemUiProxy.setBackToLauncherCallback(
+ new IOnBackInvokedCallback.Stub() {
+ @Override
+ public void onBackCancelled() {
+ handler.post(() -> resetPositionAnimated());
+ }
+
+ @Override
+ public void onBackInvoked() {
+ handler.post(() -> startTransition());
+ }
+
+ @Override
+ public void onBackProgressed(BackEvent backEvent) {
+ mBackProgress = backEvent.getProgress();
+ // TODO: Update once the interpolation curve spec is finalized.
+ mBackProgress =
+ 1 - (1 - mBackProgress) * (1 - mBackProgress) * (1
+ - mBackProgress);
+ if (!mBackInProgress) {
+ startBack(backEvent);
+ } else {
+ updateBackProgress(mBackProgress);
+ }
+ }
+
+ public void onBackStarted() { }
+ });
+ }
+
+ private void resetPositionAnimated() {
+ ValueAnimator cancelAnimator = ValueAnimator.ofFloat(mBackProgress, 0);
+ cancelAnimator.setDuration(CANCEL_TRANSITION_DURATION);
+ cancelAnimator.addUpdateListener(
+ animation -> {
+ updateBackProgress((float) animation.getAnimatedValue());
+ });
+ cancelAnimator.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ finishAnimation();
+ }
+ });
+ cancelAnimator.start();
+ }
+
+ /** Unregisters the back to launcher callback in shell. */
+ public void unregisterBackCallbacks() {
+ SystemUiProxy systemUiProxy = SystemUiProxy.INSTANCE.getNoCreate();
+ if (systemUiProxy != null) {
+ systemUiProxy.clearBackToLauncherCallback();
+ }
+ }
+
+ private void startBack(BackEvent backEvent) {
+ mBackInProgress = true;
+ RemoteAnimationTarget appTarget = backEvent.getDepartingAnimationTarget();
+
+ if (appTarget == null) {
+ return;
+ }
+
+ mTransaction.show(appTarget.leash).apply();
+ mTransaction.setAnimationTransaction();
+ mBackTarget = new RemoteAnimationTargetCompat(appTarget);
+ mSwipeEdge = backEvent.getSwipeEdge();
+ float screenWidth = mDeviceProfile.widthPx;
+ float screenHeight = mDeviceProfile.heightPx;
+ float targetHeight = screenHeight - 2 * mWindowScaleMarginY;
+ float targetWidth = targetHeight * screenWidth / screenHeight;
+ float left;
+ if (mSwipeEdge == BackEvent.EDGE_LEFT) {
+ left = screenWidth - targetWidth - mWindowScaleMarginX;
+ } else {
+ left = mWindowScaleMarginX;
+ }
+ float top = mWindowScaleMarginY;
+ // TODO(b/218916755): Offset start rectangle in multiwindow mode.
+ mStartRectF.set(0, 0, screenWidth, screenHeight);
+ mTargetRectF.set(left, top, targetWidth + left, targetHeight + top);
+ }
+
+ private void updateBackProgress(float progress) {
+ if (mBackTarget == null) {
+ return;
+ }
+
+ mCurrentRect.set(
+ MathUtils.lerp(mStartRectF.left, mTargetRectF.left, progress),
+ MathUtils.lerp(mStartRectF.top, mTargetRectF.top, progress),
+ MathUtils.lerp(mStartRectF.right, mTargetRectF.right, progress),
+ MathUtils.lerp(mStartRectF.bottom, mTargetRectF.bottom, progress));
+ SyncRtSurfaceTransactionApplierCompat.SurfaceParams.Builder builder =
+ new SyncRtSurfaceTransactionApplierCompat.SurfaceParams.Builder(mBackTarget.leash);
+
+ Rect currentRect = new Rect();
+ mCurrentRect.round(currentRect);
+
+ // Scale the target window to match the currentRectF.
+ final float scale = mCurrentRect.width() / mStartRectF.width();
+ mTransformMatrix.reset();
+ mTransformMatrix.setScale(scale, scale);
+ mTransformMatrix.postTranslate(mCurrentRect.left, mCurrentRect.top);
+ Rect startRect = new Rect();
+ mStartRectF.round(startRect);
+ float cornerRadius = Utilities.mapRange(
+ progress, mWindowScaleStartCornerRadius, mWindowScaleEndCornerRadius);
+ builder.withMatrix(mTransformMatrix)
+ .withWindowCrop(startRect)
+ .withCornerRadius(cornerRadius);
+ SyncRtSurfaceTransactionApplierCompat.SurfaceParams surfaceParams = builder.build();
+
+ if (surfaceParams.surface.isValid()) {
+ surfaceParams.applyTo(mTransaction);
+ }
+ mTransaction.apply();
+ }
+
+ private void startTransition() {
+ if (mBackTarget == null) {
+ // Trigger transition system instead of custom transition animation.
+ finishAnimation();
+ return;
+ }
+ if (mLauncher.isDestroyed()) {
+ return;
+ }
+ // TODO: Catch the moment when launcher becomes visible after the top app un-occludes
+ // launcher and start animating afterwards. Currently we occasionally get a flicker from
+ // animating when launcher is still invisible.
+ if (mLauncher.hasSomeInvisibleFlag(PENDING_INVISIBLE_BY_WALLPAPER_ANIMATION)) {
+ mLauncher.addForceInvisibleFlag(INVISIBLE_BY_PENDING_FLAGS);
+ mLauncher.getStateManager().moveToRestState();
+ }
+
+ Pair<RectFSpringAnim, AnimatorSet> pair =
+ mQuickstepTransitionManager.createWallpaperOpenAnimations(
+ new RemoteAnimationTargetCompat[]{mBackTarget},
+ new RemoteAnimationTargetCompat[]{},
+ false /* fromUnlock */,
+ mCurrentRect);
+ startTransitionAnimations(pair.first, pair.second);
+ mLauncher.clearForceInvisibleFlag(INVISIBLE_ALL);
+ }
+
+ private void finishAnimation() {
+ mBackTarget = null;
+ mBackInProgress = false;
+ mBackProgress = 0;
+ mSwipeEdge = BackEvent.EDGE_LEFT;
+ mTransformMatrix.reset();
+ mTargetRectF.setEmpty();
+ mCurrentRect.setEmpty();
+ mStartRectF.setEmpty();
+ mAnimatorSetInProgress = false;
+ mSpringAnimationInProgress = false;
+ SystemUiProxy systemUiProxy = SystemUiProxy.INSTANCE.getNoCreate();
+ if (systemUiProxy != null) {
+ SystemUiProxy.INSTANCE.getNoCreate().onBackToLauncherAnimationFinished();
+ }
+ }
+
+ private void startTransitionAnimations(RectFSpringAnim springAnim, AnimatorSet anim) {
+ mAnimatorSetInProgress = anim != null;
+ mSpringAnimationInProgress = springAnim != null;
+ if (springAnim != null) {
+ springAnim.addAnimatorListener(
+ new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ mSpringAnimationInProgress = false;
+ tryFinishBackAnimation();
+ }
+ }
+ );
+ }
+ anim.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ mAnimatorSetInProgress = false;
+ tryFinishBackAnimation();
+ }
+ });
+ anim.start();
+ }
+
+ private void tryFinishBackAnimation() {
+ if (!mSpringAnimationInProgress && !mAnimatorSetInProgress) {
+ finishAnimation();
+ }
+ }
+}
diff --git a/quickstep/src/com/android/quickstep/SystemUiProxy.java b/quickstep/src/com/android/quickstep/SystemUiProxy.java
index 667ea14..d8cbd36 100644
--- a/quickstep/src/com/android/quickstep/SystemUiProxy.java
+++ b/quickstep/src/com/android/quickstep/SystemUiProxy.java
@@ -39,6 +39,7 @@
import android.view.RemoteAnimationAdapter;
import android.view.RemoteAnimationTarget;
import android.view.SurfaceControl;
+import android.window.IOnBackInvokedCallback;
import com.android.launcher3.util.DisplayController;
import com.android.launcher3.util.DisplayController.Info;
@@ -50,6 +51,7 @@
import com.android.systemui.shared.system.smartspace.ILauncherUnlockAnimationController;
import com.android.systemui.shared.system.smartspace.ISysuiUnlockAnimationController;
import com.android.systemui.shared.system.smartspace.SmartspaceState;
+import com.android.wm.shell.back.IBackAnimation;
import com.android.wm.shell.onehanded.IOneHanded;
import com.android.wm.shell.pip.IPip;
import com.android.wm.shell.pip.IPipAnimationListener;
@@ -82,6 +84,7 @@
private IShellTransitions mShellTransitions;
private IStartingWindow mStartingWindow;
private IRecentTasks mRecentTasks;
+ private IBackAnimation mBackAnimation;
private final DeathRecipient mSystemUiProxyDeathRecipient = () -> {
MAIN_EXECUTOR.execute(() -> clearProxy());
};
@@ -96,6 +99,7 @@
private ILauncherUnlockAnimationController mPendingLauncherUnlockAnimationController;
private IRecentTasksListener mRecentTasksListener;
private final ArrayList<RemoteTransitionCompat> mRemoteTransitions = new ArrayList<>();
+ private IOnBackInvokedCallback mBackToLaunchCallback;
// Used to dedupe calls to SystemUI
private int mLastShelfHeight;
@@ -162,8 +166,8 @@
public void setProxy(ISystemUiProxy proxy, IPip pip, ISplitScreen splitScreen,
IOneHanded oneHanded, IShellTransitions shellTransitions,
IStartingWindow startingWindow, IRecentTasks recentTasks,
- ISysuiUnlockAnimationController sysuiUnlockAnimationController) {
-
+ ISysuiUnlockAnimationController sysuiUnlockAnimationController,
+ IBackAnimation backAnimation) {
unlinkToDeath();
mSystemUiProxy = proxy;
mPip = pip;
@@ -173,6 +177,7 @@
mStartingWindow = startingWindow;
mSysuiUnlockAnimationController = sysuiUnlockAnimationController;
mRecentTasks = recentTasks;
+ mBackAnimation = backAnimation;
linkToDeath();
// re-attach the listeners once missing due to setProxy has not been initialized yet.
if (mPipAnimationListener != null && mPip != null) {
@@ -195,6 +200,9 @@
if (mRecentTasksListener != null && mRecentTasks != null) {
registerRecentTasksListener(mRecentTasksListener);
}
+ if (mBackAnimation != null && mBackToLaunchCallback != null) {
+ setBackToLauncherCallback(mBackToLaunchCallback);
+ }
if (mPendingSetNavButtonAlpha != null) {
mPendingSetNavButtonAlpha.run();
@@ -203,7 +211,7 @@
}
public void clearProxy() {
- setProxy(null, null, null, null, null, null, null, null);
+ setProxy(null, null, null, null, null, null, null, null, null);
}
// TODO(141886704): Find a way to remove this
@@ -822,6 +830,49 @@
mRecentTasksListener = null;
}
+ //
+ // Back navigation transitions
+ //
+
+ /** Sets the launcher {@link android.window.IOnBackInvokedCallback} to shell */
+ public void setBackToLauncherCallback(IOnBackInvokedCallback callback) {
+ mBackToLaunchCallback = callback;
+ if (mBackAnimation == null) {
+ return;
+ }
+ try {
+ mBackAnimation.setBackToLauncherCallback(callback);
+ } catch (RemoteException e) {
+ Log.e(TAG, "Failed call setBackToLauncherCallback", e);
+ }
+ }
+
+ /** Clears the previously registered {@link IOnBackInvokedCallback}. */
+ public void clearBackToLauncherCallback() {
+ if (mBackAnimation == null) {
+ return;
+ }
+ try {
+ mBackAnimation.clearBackToLauncherCallback();
+ } catch (RemoteException e) {
+ Log.e(TAG, "Failed call clearBackToLauncherCallback", e);
+ }
+ }
+
+ /**
+ * Notifies shell that all back to launcher animations have finished (including the transition
+ * that plays after the gesture is committed and before the app is closed.
+ */
+ public void onBackToLauncherAnimationFinished() {
+ if (mBackAnimation != null) {
+ try {
+ mBackAnimation.onBackToLauncherAnimationFinished();
+ } catch (RemoteException e) {
+ Log.w(TAG, "Failed call onBackAnimationFinished", e);
+ }
+ }
+ }
+
public ArrayList<GroupedRecentTaskInfo> getRecentTasks(int numTasks, int userId) {
if (mRecentTasks != null) {
try {
diff --git a/quickstep/src/com/android/quickstep/TouchInteractionService.java b/quickstep/src/com/android/quickstep/TouchInteractionService.java
index 021048a..ff67b09 100644
--- a/quickstep/src/com/android/quickstep/TouchInteractionService.java
+++ b/quickstep/src/com/android/quickstep/TouchInteractionService.java
@@ -26,6 +26,7 @@
import static com.android.quickstep.GestureState.DEFAULT_STATE;
import static com.android.systemui.shared.system.ActivityManagerWrapper.CLOSE_SYSTEM_WINDOWS_REASON_RECENTS;
import static com.android.systemui.shared.system.QuickStepContract.KEY_EXTRA_RECENT_TASKS;
+import static com.android.systemui.shared.system.QuickStepContract.KEY_EXTRA_SHELL_BACK_ANIMATION;
import static com.android.systemui.shared.system.QuickStepContract.KEY_EXTRA_SHELL_ONE_HANDED;
import static com.android.systemui.shared.system.QuickStepContract.KEY_EXTRA_SHELL_PIP;
import static com.android.systemui.shared.system.QuickStepContract.KEY_EXTRA_SHELL_SHELL_TRANSITIONS;
@@ -106,6 +107,7 @@
import com.android.systemui.shared.system.InputMonitorCompat;
import com.android.systemui.shared.system.smartspace.ISysuiUnlockAnimationController;
import com.android.systemui.shared.tracing.ProtoTraceable;
+import com.android.wm.shell.back.IBackAnimation;
import com.android.wm.shell.onehanded.IOneHanded;
import com.android.wm.shell.pip.IPip;
import com.android.wm.shell.recents.IRecentTasks;
@@ -166,10 +168,12 @@
bundle.getBinder(KEY_EXTRA_UNLOCK_ANIMATION_CONTROLLER));
IRecentTasks recentTasks = IRecentTasks.Stub.asInterface(
bundle.getBinder(KEY_EXTRA_RECENT_TASKS));
+ IBackAnimation backAnimation = IBackAnimation.Stub.asInterface(
+ bundle.getBinder(KEY_EXTRA_SHELL_BACK_ANIMATION));
MAIN_EXECUTOR.execute(() -> {
SystemUiProxy.INSTANCE.get(TouchInteractionService.this).setProxy(proxy, pip,
splitscreen, onehanded, shellTransitions, startingWindow, recentTasks,
- launcherUnlockAnimationController);
+ launcherUnlockAnimationController, backAnimation);
TouchInteractionService.this.initInputMonitor();
preloadOverview(true /* fromInit */);
});
diff --git a/res/values/config.xml b/res/values/config.xml
index 509f363..e2fd0e3 100644
--- a/res/values/config.xml
+++ b/res/values/config.xml
@@ -166,4 +166,8 @@
<!-- Name of the class used to generate colors from the wallpaper colors. Must be implementing the LauncherAppWidgetHostView.ColorGenerator interface. -->
<string name="color_generator_class" translatable="false"/>
+ <!-- Swipe back to home related -->
+ <dimen name="swipe_back_window_scale_x_margin">10dp</dimen>
+ <dimen name="swipe_back_window_scale_y_margin">80dp</dimen>
+ <dimen name="swipe_back_window_corner_radius">40dp</dimen>
</resources>