diff --git a/quickstep/src/com/android/launcher3/QuickstepTransitionManager.java b/quickstep/src/com/android/launcher3/QuickstepTransitionManager.java index 8f8ac8e415..4d4143ae0f 100644 --- a/quickstep/src/com/android/launcher3/QuickstepTransitionManager.java +++ b/quickstep/src/com/android/launcher3/QuickstepTransitionManager.java @@ -109,6 +109,7 @@ import com.android.launcher3.util.Themes; 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; @@ -213,6 +214,7 @@ public class QuickstepTransitionManager implements OnDeviceProfileChangeListener private RemoteAnimationFactory mWallpaperOpenTransitionRunner; private RemoteTransitionCompat mLauncherOpenTransition; + private LauncherBackAnimationController mBackAnimationController; private final AnimatorListenerAdapter mForceInvisibleListener = new AnimatorListenerAdapter() { @Override public void onAnimationStart(Animator animation) { @@ -238,6 +240,8 @@ public class QuickstepTransitionManager implements OnDeviceProfileChangeListener 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); @@ -1136,6 +1140,9 @@ public class QuickstepTransitionManager implements OnDeviceProfileChangeListener mLauncherOpenTransition.addHomeOpenCheck(mLauncher.getComponentName()); SystemUiProxy.INSTANCE.getNoCreate().registerRemoteTransition(mLauncherOpenTransition); } + if (mBackAnimationController != null) { + mBackAnimationController.registerBackCallbacks(mHandler); + } } public void onActivityDestroyed() { @@ -1171,6 +1178,10 @@ public class QuickstepTransitionManager implements OnDeviceProfileChangeListener mLauncherOpenTransition = null; mWallpaperOpenTransitionRunner = null; } + if (mBackAnimationController != null) { + mBackAnimationController.unregisterBackCallbacks(); + mBackAnimationController = null; + } } private boolean launcherIsATargetWithMode(RemoteAnimationTargetCompat[] targets, int mode) { @@ -1323,8 +1334,9 @@ public class QuickstepTransitionManager implements OnDeviceProfileChangeListener /** * 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(); @@ -1356,8 +1368,7 @@ public class QuickstepTransitionManager implements OnDeviceProfileChangeListener 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. @@ -1391,7 +1402,7 @@ public class QuickstepTransitionManager implements OnDeviceProfileChangeListener 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) { @@ -1415,6 +1426,7 @@ public class QuickstepTransitionManager implements OnDeviceProfileChangeListener anim.start(mLauncher, velocityPxPerS); } }); + return anim; } /** @@ -1538,6 +1550,97 @@ public class QuickstepTransitionManager implements OnDeviceProfileChangeListener }); } + /** + * Creates the {@link RectFSpringAnim} and {@link AnimatorSet} required to animate + * the transition. + */ + public Pair 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 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. */ @@ -1578,84 +1681,12 @@ public class QuickstepTransitionManager implements OnDeviceProfileChangeListener 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 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 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 0000000000..7abcbdb47a --- /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 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 667ea1484d..d8cbd36cd4 100644 --- a/quickstep/src/com/android/quickstep/SystemUiProxy.java +++ b/quickstep/src/com/android/quickstep/SystemUiProxy.java @@ -39,6 +39,7 @@ import android.view.MotionEvent; 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.RemoteTransitionCompat; 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 @@ public class SystemUiProxy implements ISystemUiProxy, DisplayController.DisplayI private IShellTransitions mShellTransitions; private IStartingWindow mStartingWindow; private IRecentTasks mRecentTasks; + private IBackAnimation mBackAnimation; private final DeathRecipient mSystemUiProxyDeathRecipient = () -> { MAIN_EXECUTOR.execute(() -> clearProxy()); }; @@ -96,6 +99,7 @@ public class SystemUiProxy implements ISystemUiProxy, DisplayController.DisplayI private ILauncherUnlockAnimationController mPendingLauncherUnlockAnimationController; private IRecentTasksListener mRecentTasksListener; private final ArrayList mRemoteTransitions = new ArrayList<>(); + private IOnBackInvokedCallback mBackToLaunchCallback; // Used to dedupe calls to SystemUI private int mLastShelfHeight; @@ -162,8 +166,8 @@ public class SystemUiProxy implements ISystemUiProxy, DisplayController.DisplayI 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 @@ public class SystemUiProxy implements ISystemUiProxy, DisplayController.DisplayI 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 @@ public class SystemUiProxy implements ISystemUiProxy, DisplayController.DisplayI if (mRecentTasksListener != null && mRecentTasks != null) { registerRecentTasksListener(mRecentTasksListener); } + if (mBackAnimation != null && mBackToLaunchCallback != null) { + setBackToLauncherCallback(mBackToLaunchCallback); + } if (mPendingSetNavButtonAlpha != null) { mPendingSetNavButtonAlpha.run(); @@ -203,7 +211,7 @@ public class SystemUiProxy implements ISystemUiProxy, DisplayController.DisplayI } 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 @@ public class SystemUiProxy implements ISystemUiProxy, DisplayController.DisplayI 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 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 021048ad01..ff67b096da 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.launcher3.util.Executors.MAIN_EXECUTOR; 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.InputConsumerController; 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 @@ public class TouchInteractionService extends Service 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 509f3636fe..e2fd0e3bc8 100644 --- a/res/values/config.xml +++ b/res/values/config.xml @@ -166,4 +166,8 @@ + + 10dp + 80dp + 40dp