• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2022 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.quickstep;
18 
19 import static android.util.MathUtils.lerp;
20 import static android.view.RemoteAnimationTarget.MODE_CLOSING;
21 import static android.view.RemoteAnimationTarget.MODE_OPENING;
22 import static android.view.WindowInsetsController.APPEARANCE_LIGHT_STATUS_BARS;
23 
24 import static com.android.launcher3.AbstractFloatingView.TYPE_REBIND_SAFE;
25 import static com.android.launcher3.BaseActivity.INVISIBLE_ALL;
26 import static com.android.launcher3.BaseActivity.INVISIBLE_BY_PENDING_FLAGS;
27 import static com.android.launcher3.BaseActivity.PENDING_INVISIBLE_BY_WALLPAPER_ANIMATION;
28 import static com.android.window.flags.Flags.predictiveBackThreeButtonNav;
29 import static com.android.window.flags.Flags.removeDepartTargetFromMotion;
30 
31 import android.animation.Animator;
32 import android.animation.AnimatorListenerAdapter;
33 import android.animation.ValueAnimator;
34 import android.content.ComponentCallbacks;
35 import android.content.res.Configuration;
36 import android.graphics.Matrix;
37 import android.graphics.PointF;
38 import android.graphics.Rect;
39 import android.graphics.RectF;
40 import android.os.Handler;
41 import android.os.RemoteException;
42 import android.util.Log;
43 import android.view.Choreographer;
44 import android.view.IRemoteAnimationFinishedCallback;
45 import android.view.IRemoteAnimationRunner;
46 import android.view.RemoteAnimationTarget;
47 import android.view.SurfaceControl;
48 import android.view.View;
49 import android.view.animation.DecelerateInterpolator;
50 import android.view.animation.Interpolator;
51 import android.window.BackEvent;
52 import android.window.BackMotionEvent;
53 import android.window.BackProgressAnimator;
54 import android.window.IBackAnimationHandoffHandler;
55 import android.window.IOnBackInvokedCallback;
56 
57 import com.android.app.animation.Animations;
58 import com.android.app.animation.Interpolators;
59 import com.android.internal.policy.SystemBarUtils;
60 import com.android.internal.view.AppearanceRegion;
61 import com.android.launcher3.AbstractFloatingView;
62 import com.android.launcher3.BubbleTextView;
63 import com.android.launcher3.Flags;
64 import com.android.launcher3.LauncherState;
65 import com.android.launcher3.QuickstepTransitionManager;
66 import com.android.launcher3.R;
67 import com.android.launcher3.Utilities;
68 import com.android.launcher3.taskbar.LauncherTaskbarUIController;
69 import com.android.launcher3.uioverrides.QuickstepLauncher;
70 import com.android.launcher3.util.DisplayController;
71 import com.android.launcher3.util.NavigationMode;
72 import com.android.launcher3.widget.LauncherAppWidgetHostView;
73 import com.android.quickstep.util.BackAnimState;
74 import com.android.quickstep.util.ScalingWorkspaceRevealAnim;
75 import com.android.systemui.shared.system.QuickStepContract;
76 
77 import java.lang.ref.WeakReference;
78 
79 /**
80  * Controls the animation of swiping back and returning to launcher.
81  *
82  * This is a two part animation. The first part is an animation that tracks gesture location to
83  * scale and move the leaving app window. Once the gesture is committed, the second part takes over
84  * the app window and plays the rest of app close transitions in one go.
85  *
86  * This animation is used only for apps that enable back dispatching via
87  * {@link android.window.OnBackInvokedDispatcher}. The controller registers
88  * an {@link IOnBackInvokedCallback} with WM Shell and receives back dispatches when a back
89  * navigation to launcher starts.
90  *
91  * Apps using the legacy back dispatching will keep triggering the WALLPAPER_OPEN remote
92  * transition registered in {@link QuickstepTransitionManager}.
93  *
94  */
95 public class LauncherBackAnimationController {
96     private static final int SCRIM_FADE_DURATION = 233;
97     private static final float MIN_WINDOW_SCALE =
98             Flags.predictiveBackToHomePolish() ? 0.75f : 0.85f;
99     private static final float MAX_SCRIM_ALPHA_DARK = 0.8f;
100     private static final float MAX_SCRIM_ALPHA_LIGHT = 0.2f;
101     private static final int MAX_BLUR_RADIUS = 20;
102     private static final int MIN_BLUR_RADIUS_PRE_COMMIT = 10;
103 
104     private final QuickstepTransitionManager mQuickstepTransitionManager;
105     private final Matrix mTransformMatrix = new Matrix();
106     /** The window position at the beginning of the back animation. */
107     private final Rect mStartRect = new Rect();
108     /** The current window position. */
109     private final RectF mCurrentRect = new RectF();
110     private final QuickstepLauncher mLauncher;
111     private final int mWindowScaleMarginX;
112     private float mWindowScaleEndCornerRadius;
113     private float mWindowScaleStartCornerRadius;
114     private int mStatusBarHeight;
115     private final Interpolator mProgressInterpolator = Interpolators.BACK_GESTURE;
116     private final Interpolator mVerticalMoveInterpolator = new DecelerateInterpolator();
117     private final PointF mInitialTouchPos = new PointF();
118 
119     private RemoteAnimationTarget mBackTarget;
120     private RemoteAnimationTarget mLauncherTarget;
121     private View mLauncherTargetView;
122     private final SurfaceControl.Transaction mTransaction = new SurfaceControl.Transaction();
123     private float mBackProgress = 0;
124     private boolean mBackInProgress = false;
125     private boolean mWaitStartTransition = false;
126     private OnBackInvokedCallbackStub mBackCallback;
127     private IRemoteAnimationFinishedCallback mAnimationFinishedCallback;
128     private final BackProgressAnimator mProgressAnimator = new BackProgressAnimator();
129     private SurfaceControl mScrimLayer;
130     private ValueAnimator mScrimAlphaAnimator;
131     private float mScrimAlpha;
132     private boolean mOverridingStatusBarFlags;
133     private int mLastBlurRadius = 0;
134 
135     private final ComponentCallbacks mComponentCallbacks = new ComponentCallbacks() {
136         @Override
137         public void onConfigurationChanged(Configuration newConfig) {
138             loadResources();
139         }
140 
141         @Override
142         public void onLowMemory() {}
143     };
144 
LauncherBackAnimationController( QuickstepLauncher launcher, QuickstepTransitionManager quickstepTransitionManager)145     public LauncherBackAnimationController(
146             QuickstepLauncher launcher,
147             QuickstepTransitionManager quickstepTransitionManager) {
148         mLauncher = launcher;
149         mQuickstepTransitionManager = quickstepTransitionManager;
150         loadResources();
151         mWindowScaleMarginX = mLauncher.getResources().getDimensionPixelSize(
152                 R.dimen.swipe_back_window_scale_x_margin);
153     }
154 
155     /**
156      * Registers {@link IOnBackInvokedCallback} to receive back dispatches from shell.
157      * @param handler Handler to the thread to run the animations on.
158      */
registerBackCallbacks(Handler handler)159     public void registerBackCallbacks(Handler handler) {
160         mBackCallback = new OnBackInvokedCallbackStub(handler, mProgressAnimator,
161                 mProgressInterpolator, this);
162         SystemUiProxy.INSTANCE.get(mLauncher).setBackToLauncherCallback(mBackCallback,
163                 new RemoteAnimationRunnerStub(this,
164                         removeDepartTargetFromMotion() ? handler : null));
165     }
166 
167     private static class OnBackInvokedCallbackStub extends IOnBackInvokedCallback.Stub {
168         private Handler mHandler;
169         private BackProgressAnimator mProgressAnimator;
170         private final Interpolator mProgressInterpolator;
171         // LauncherBackAnimationController has strong reference to Launcher activity, the binder
172         // callback should not hold strong reference to it to avoid memory leak.
173         private WeakReference<LauncherBackAnimationController> mControllerRef;
174 
OnBackInvokedCallbackStub( Handler handler, BackProgressAnimator progressAnimator, Interpolator progressInterpolator, LauncherBackAnimationController controller)175         private OnBackInvokedCallbackStub(
176                 Handler handler,
177                 BackProgressAnimator progressAnimator,
178                 Interpolator progressInterpolator,
179                 LauncherBackAnimationController controller) {
180             mHandler = handler;
181             mProgressAnimator = progressAnimator;
182             mProgressInterpolator = progressInterpolator;
183             mControllerRef = new WeakReference<>(controller);
184         }
185 
186         @Override
onBackCancelled()187         public void onBackCancelled() {
188             mHandler.post(() -> {
189                 LauncherBackAnimationController controller = mControllerRef.get();
190                 if (controller != null) {
191                     mProgressAnimator.onBackCancelled(controller::onCancelFinished);
192                 }
193             });
194         }
195 
196         @Override
onBackInvoked()197         public void onBackInvoked() {
198             mHandler.post(() -> {
199                 LauncherBackAnimationController controller = mControllerRef.get();
200                 if (controller != null) {
201                     if (!removeDepartTargetFromMotion()) {
202                         controller.startTransition();
203                     } else {
204                         controller.mWaitStartTransition = true;
205                         if (controller.mBackTarget != null && controller.mBackInProgress) {
206                             controller.startTransition();
207                         }
208                     }
209                 }
210                 mProgressAnimator.reset();
211             });
212         }
213 
214         @Override
onBackProgressed(BackMotionEvent backMotionEvent)215         public void onBackProgressed(BackMotionEvent backMotionEvent) {
216             mHandler.post(() -> {
217                 LauncherBackAnimationController controller = mControllerRef.get();
218                 if (controller == null
219                         || controller.mLauncher == null
220                         || !controller.mLauncher.isStarted()) {
221                     // Skip animating back progress if Launcher isn't visible yet.
222                     return;
223                 }
224                 mProgressAnimator.onBackProgressed(backMotionEvent);
225             });
226         }
227 
228         @Override
onBackStarted(BackMotionEvent backEvent)229         public void onBackStarted(BackMotionEvent backEvent) {
230             mHandler.post(() -> {
231                 LauncherBackAnimationController controller = mControllerRef.get();
232                 if (controller != null) {
233                     controller.initBackMotion(backEvent);
234                     controller.tryStartBackAnimation();
235                     mProgressAnimator.onBackStarted(backEvent, event -> {
236                         float backProgress = event.getProgress();
237                         controller.mBackProgress =
238                                 mProgressInterpolator.getInterpolation(backProgress);
239                         controller.updateBackProgress(controller.mBackProgress, event);
240                     });
241                 }
242             });
243         }
244 
245         @Override
setTriggerBack(boolean triggerBack)246         public void setTriggerBack(boolean triggerBack) {
247             // TODO(b/261654570): track touch from the Launcher process.
248         }
249 
250         @Override
setHandoffHandler(IBackAnimationHandoffHandler unused)251         public void setHandoffHandler(IBackAnimationHandoffHandler unused) {
252             // For now, Launcher handles this internally so it doesn't need to hand off the
253             // animation.
254         }
255     }
256 
257     private static class RemoteAnimationRunnerStub extends IRemoteAnimationRunner.Stub {
258 
259         // LauncherBackAnimationController has strong reference to Launcher activity, the binder
260         // callback should not hold strong reference to it to avoid memory leak.
261         private WeakReference<LauncherBackAnimationController> mControllerRef;
262         private final Handler mHandler;
263 
RemoteAnimationRunnerStub(LauncherBackAnimationController controller, Handler handler)264         private RemoteAnimationRunnerStub(LauncherBackAnimationController controller,
265                 Handler handler) {
266             mControllerRef = new WeakReference<>(controller);
267             mHandler = handler;
268         }
269 
270         @Override
onAnimationStart(int transit, RemoteAnimationTarget[] apps, RemoteAnimationTarget[] wallpapers, RemoteAnimationTarget[] nonApps, IRemoteAnimationFinishedCallback finishedCallback)271         public void onAnimationStart(int transit, RemoteAnimationTarget[] apps,
272                 RemoteAnimationTarget[] wallpapers, RemoteAnimationTarget[] nonApps,
273                 IRemoteAnimationFinishedCallback finishedCallback) {
274             LauncherBackAnimationController controller = mControllerRef.get();
275             if (controller == null) {
276                 return;
277             }
278             final Runnable r = () -> {
279                 for (final RemoteAnimationTarget target : apps) {
280                     if (MODE_CLOSING == target.mode) {
281                         controller.mBackTarget = target;
282                     }
283                     if (MODE_OPENING == target.mode) {
284                         controller.mLauncherTarget = target;
285                     }
286                 }
287                 controller.mAnimationFinishedCallback = finishedCallback;
288                 if (!removeDepartTargetFromMotion()) {
289                     return;
290                 }
291                 controller.tryStartBackAnimation();
292                 if (controller.mWaitStartTransition) {
293                     controller.startTransition();
294                 }
295             };
296             if (mHandler != null) {
297                 mHandler.post(r);
298             } else {
299                 r.run();
300             }
301         }
302 
303         @Override
onAnimationCancelled()304         public void onAnimationCancelled() {}
305     }
306 
onCancelFinished()307     private void onCancelFinished() {
308         customizeStatusBarAppearance(false);
309         if (Flags.predictiveBackToHomePolish() && !mLauncher.getWorkspace().isOverlayShown()
310                 && !mLauncher.isInState(LauncherState.ALL_APPS)) {
311             setLauncherScale(ScalingWorkspaceRevealAnim.MAX_SIZE);
312         }
313         finishAnimation();
314     }
315 
316     /** Unregisters the back to launcher callback in shell. */
unregisterBackCallbacks()317     public void unregisterBackCallbacks() {
318         if (mBackCallback != null) {
319             SystemUiProxy.INSTANCE.get(mLauncher).clearBackToLauncherCallback(mBackCallback);
320         }
321         mProgressAnimator.reset();
322         mBackCallback = null;
323     }
324 
initBackMotion(BackMotionEvent backEvent)325     private void initBackMotion(BackMotionEvent backEvent) {
326         // in case we're still animating an onBackCancelled event, let's remove the finish-
327         // callback from the progress animator to prevent calling finishAnimation() before
328         // restarting a new animation
329         // Side note: initBackMotion is never called during the post-commit phase if the back
330         // gesture was committed (not cancelled). BackAnimationController prevents that. Therefore
331         // we don't have to handle that case.
332         mProgressAnimator.removeOnBackCancelledFinishCallback();
333 
334         if (!removeDepartTargetFromMotion()) {
335             RemoteAnimationTarget appTarget = backEvent.getDepartingAnimationTarget();
336             if (appTarget == null || appTarget.leash == null || !appTarget.leash.isValid()) {
337                 return;
338             }
339             mBackTarget = appTarget;
340         }
341         mBackInProgress = true;
342         mInitialTouchPos.set(backEvent.getTouchX(), backEvent.getTouchY());
343     }
tryStartBackAnimation()344     private void tryStartBackAnimation() {
345         if (mBackTarget == null || (removeDepartTargetFromMotion() && !mBackInProgress)) {
346             return;
347         }
348 
349         mTransaction
350                 .show(mBackTarget.leash)
351                 .setAnimationTransaction();
352         mStartRect.set(mBackTarget.windowConfiguration.getMaxBounds());
353 
354         // inset bottom in case of taskbar being present
355         if (!predictiveBackThreeButtonNav() || mLauncher.getDeviceProfile().isTaskbarPresent
356                 || DisplayController.getNavigationMode(mLauncher) == NavigationMode.NO_BUTTON) {
357             mStartRect.inset(0, 0, 0, mBackTarget.contentInsets.bottom);
358         }
359 
360         mLauncherTargetView = mQuickstepTransitionManager.findLauncherView(
361                 new RemoteAnimationTarget[]{ mBackTarget });
362         setLauncherTargetViewVisible(false);
363         mCurrentRect.set(mStartRect);
364         if (Flags.predictiveBackToHomePolish() && !mLauncher.getWorkspace().isOverlayShown()
365                 && !mLauncher.isInState(LauncherState.ALL_APPS)) {
366             Animations.cancelOngoingAnimation(mLauncher.getWorkspace());
367             Animations.cancelOngoingAnimation(mLauncher.getHotseat());
368             if (Flags.predictiveBackToHomeBlur()) {
369                 mLauncher.getDepthController().pauseBlursOnWindows(true);
370             }
371             mLauncher.getDepthController().stateDepth.setValue(
372                     LauncherState.BACKGROUND_APP.getDepth(mLauncher));
373             setLauncherScale(ScalingWorkspaceRevealAnim.MIN_SIZE);
374         }
375         if (mScrimLayer == null) {
376             addScrimLayer();
377         }
378         applyTransaction();
379     }
380 
setLauncherTargetViewVisible(boolean isVisible)381     private void setLauncherTargetViewVisible(boolean isVisible) {
382         if (mLauncherTargetView instanceof BubbleTextView) {
383             ((BubbleTextView) mLauncherTargetView).setIconVisible(isVisible);
384         } else if (mLauncherTargetView instanceof LauncherAppWidgetHostView) {
385             mLauncherTargetView.setAlpha(isVisible ? 1f : 0f);
386         }
387     }
388 
setLauncherScale(float scale)389     private void setLauncherScale(float scale) {
390         mLauncher.getWorkspace().setScaleX(scale);
391         mLauncher.getWorkspace().setScaleY(scale);
392         mLauncher.getHotseat().setScaleX(scale);
393         mLauncher.getHotseat().setScaleY(scale);
394     }
395 
addScrimLayer()396     void addScrimLayer() {
397         SurfaceControl parent = mLauncherTarget != null ? mLauncherTarget.leash : null;
398         if (parent == null || !parent.isValid()) {
399             // Parent surface is not ready at the moment. Retry later.
400             return;
401         }
402         boolean isDarkTheme = Utilities.isDarkTheme(mLauncher);
403         mScrimLayer = new SurfaceControl.Builder()
404                 .setName("Back to launcher background scrim")
405                 .setCallsite("LauncherBackAnimationController")
406                 .setColorLayer()
407                 .setParent(parent)
408                 .setOpaque(false)
409                 .setHidden(false)
410                 .build();
411         final float[] colorComponents = new float[] { 0f, 0f, 0f };
412         mScrimAlpha = (isDarkTheme)
413                 ? MAX_SCRIM_ALPHA_DARK : MAX_SCRIM_ALPHA_LIGHT;
414         setBlur(MAX_BLUR_RADIUS);
415         mTransaction
416                 .setColor(mScrimLayer, colorComponents)
417                 .setAlpha(mScrimLayer, mScrimAlpha)
418                 .show(mScrimLayer)
419                 // Ensure the scrim layer occludes opening task & wallpaper
420                 .setLayer(mScrimLayer, 1000);
421     }
422 
removeScrimLayer()423     void removeScrimLayer() {
424         if (mScrimLayer == null) {
425             return;
426         }
427         if (mScrimLayer.isValid()) {
428             mTransaction.remove(mScrimLayer);
429             applyTransaction();
430         }
431         mScrimLayer = null;
432     }
433 
updateBackProgress(float progress, BackEvent event)434     private void updateBackProgress(float progress, BackEvent event) {
435         if (!mBackInProgress || mBackTarget == null) {
436             return;
437         }
438         if (mScrimLayer == null) {
439             // Scrim hasn't been attached yet. Let's attach it.
440             addScrimLayer();
441         } else {
442             mLastBlurRadius = (int) lerp(MAX_BLUR_RADIUS, MIN_BLUR_RADIUS_PRE_COMMIT, progress);
443             setBlur(mLastBlurRadius);
444         }
445         float screenWidth = mStartRect.width();
446         float screenHeight = mStartRect.height();
447         float width = Utilities.mapRange(progress, 1, MIN_WINDOW_SCALE) * screenWidth;
448         float height = screenHeight / screenWidth * width;
449 
450         // Base the window movement in the Y axis on the touch movement in the Y axis.
451         float rawYDelta = event.getTouchY() - mInitialTouchPos.y;
452         float yDirection = rawYDelta < 0 ? -1 : 1;
453         // limit yDelta interpretation to 1/2 of screen height in either direction
454         float deltaYRatio = Math.min(screenHeight / 2f, Math.abs(rawYDelta)) / (screenHeight / 2f);
455         float interpolatedYRatio = mVerticalMoveInterpolator.getInterpolation(deltaYRatio);
456         // limit y-shift so surface never passes 8dp screen margin
457         float deltaY = yDirection * interpolatedYRatio * Math.max(0f, (screenHeight - height)
458                 / 2f - mWindowScaleMarginX);
459         // Move the window along the Y axis.
460         float top = (screenHeight - height) * 0.5f + deltaY;
461         // Move the window along the X axis.
462         float left = switch (event.getSwipeEdge()) {
463             case BackEvent.EDGE_RIGHT -> progress * mWindowScaleMarginX;
464             case BackEvent.EDGE_LEFT -> screenWidth - progress * mWindowScaleMarginX - width;
465             default -> (screenWidth - width) / 2;
466         };
467         mCurrentRect.set(left, top, left + width, top + height);
468         float cornerRadius = Utilities.mapRange(
469                 progress, mWindowScaleStartCornerRadius, mWindowScaleEndCornerRadius);
470         applyTransform(mCurrentRect, cornerRadius);
471 
472         customizeStatusBarAppearance(top > mStatusBarHeight / 2);
473     }
474 
setBlur(int blurRadius)475     private void setBlur(int blurRadius) {
476         if (Flags.predictiveBackToHomeBlur()) {
477             mTransaction.setBackgroundBlurRadius(mScrimLayer, blurRadius);
478         }
479     }
480 
481     /** Transform the target window to match the target rect. */
applyTransform(RectF targetRect, float cornerRadius)482     private void applyTransform(RectF targetRect, float cornerRadius) {
483         final float scale = targetRect.width() / mStartRect.width();
484         mTransformMatrix.reset();
485         mTransformMatrix.setScale(scale, scale);
486         mTransformMatrix.postTranslate(targetRect.left, targetRect.top);
487 
488         if (mBackTarget.leash.isValid()) {
489             mTransaction.setMatrix(mBackTarget.leash, mTransformMatrix, new float[9]);
490             mTransaction.setWindowCrop(mBackTarget.leash, mStartRect);
491             mTransaction.setCornerRadius(mBackTarget.leash, cornerRadius);
492         }
493         applyTransaction();
494     }
495 
applyTransaction()496     private void applyTransaction() {
497         mTransaction.setFrameTimelineVsync(Choreographer.getInstance().getVsyncId());
498         mTransaction.apply();
499     }
500 
startTransition()501     private void startTransition() {
502         if (!removeDepartTargetFromMotion()) {
503             if (mBackTarget == null) {
504                 // Trigger transition system instead of custom transition animation.
505                 finishAnimation();
506                 return;
507             }
508         } else {
509             mWaitStartTransition = false;
510         }
511         if (mLauncher.isDestroyed()) {
512             return;
513         }
514         mLauncher.setPredictiveBackToHomeInProgress(true);
515         LauncherTaskbarUIController taskbarUIController = mLauncher.getTaskbarUIController();
516         if (taskbarUIController != null) {
517             taskbarUIController.onLauncherVisibilityChanged(true);
518         }
519         // TODO: Catch the moment when launcher becomes visible after the top app un-occludes
520         //  launcher and start animating afterwards. Currently we occasionally get a flicker from
521         //  animating when launcher is still invisible.
522         if (mLauncher.hasSomeInvisibleFlag(PENDING_INVISIBLE_BY_WALLPAPER_ANIMATION)) {
523             mLauncher.addForceInvisibleFlag(INVISIBLE_BY_PENDING_FLAGS);
524             mLauncher.getStateManager().moveToRestState();
525         }
526 
527         setLauncherTargetViewVisible(true);
528 
529         // Explicitly close opened floating views (which is typically called from
530         // Launcher#onResumed, but in the predictive back flow launcher is not resumed until
531         // the transition is fully finished.)
532         AbstractFloatingView.closeAllOpenViewsExcept(mLauncher, false, TYPE_REBIND_SAFE);
533         float cornerRadius = Utilities.mapRange(
534                 mBackProgress, mWindowScaleStartCornerRadius, mWindowScaleEndCornerRadius);
535         final RectF resolveRectF = new RectF();
536         mQuickstepTransitionManager.transferRectToTargetCoordinate(
537                 mBackTarget, mCurrentRect, true, resolveRectF);
538 
539         BackAnimState backAnim =
540                 mQuickstepTransitionManager.createWallpaperOpenAnimations(
541                     new RemoteAnimationTarget[]{mBackTarget},
542                     new RemoteAnimationTarget[0],
543                     new RemoteAnimationTarget[0],
544                     resolveRectF,
545                     cornerRadius,
546                     mBackInProgress /* fromPredictiveBack */);
547         startTransitionAnimations(backAnim);
548         mLauncher.clearForceInvisibleFlag(INVISIBLE_ALL);
549         customizeStatusBarAppearance(true);
550     }
551 
finishAnimation()552     private void finishAnimation() {
553         mLauncher.setPredictiveBackToHomeInProgress(false);
554         if (mBackTarget != null && mBackTarget.leash.isValid()) {
555             mBackTarget.leash.release();
556         }
557         mBackTarget = null;
558         if (mLauncherTarget != null && mLauncherTarget.leash.isValid()) {
559             mLauncherTarget.leash.release();
560         }
561         mLauncherTarget = null;
562         mBackInProgress = false;
563         mBackProgress = 0;
564         mTransformMatrix.reset();
565         mCurrentRect.setEmpty();
566         mStartRect.setEmpty();
567         mInitialTouchPos.set(0, 0);
568         setLauncherTargetViewVisible(true);
569         mLauncherTargetView = null;
570         // We don't call customizeStatusBarAppearance here to prevent the status bar update with
571         // the legacy appearance. It should be refreshed after the transition done.
572         mOverridingStatusBarFlags = false;
573         if (mAnimationFinishedCallback != null) {
574             try {
575                 mAnimationFinishedCallback.onAnimationFinished();
576             } catch (RemoteException e) {
577                 Log.w("ShellBackPreview", "Failed call onBackAnimationFinished", e);
578             }
579             mAnimationFinishedCallback = null;
580         }
581         if (mScrimAlphaAnimator != null && mScrimAlphaAnimator.isRunning()) {
582             mScrimAlphaAnimator.cancel();
583             mScrimAlphaAnimator = null;
584         }
585         if (mScrimLayer != null) {
586             removeScrimLayer();
587         }
588         if (Flags.predictiveBackToHomePolish() && Flags.predictiveBackToHomeBlur()
589                 && !mLauncher.getWorkspace().isOverlayShown()
590                 && !mLauncher.isInState(LauncherState.ALL_APPS)) {
591             mLauncher.getDepthController().pauseBlursOnWindows(false);
592         }
593         mLastBlurRadius = 0;
594     }
595 
startTransitionAnimations(BackAnimState backAnim)596     private void startTransitionAnimations(BackAnimState backAnim) {
597         backAnim.addOnAnimCompleteCallback(this::finishAnimation);
598         if (mScrimLayer == null) {
599             // Scrim hasn't been attached yet. Let's attach it.
600             addScrimLayer();
601         }
602         mScrimAlphaAnimator = new ValueAnimator().ofFloat(1, 0);
603         mScrimAlphaAnimator.addUpdateListener(animation -> {
604             float value = (Float) animation.getAnimatedValue();
605             if (mScrimLayer != null && mScrimLayer.isValid()) {
606                 mTransaction.setAlpha(mScrimLayer, value * mScrimAlpha);
607                 setBlur((int) lerp(mLastBlurRadius, 0, 1f - value));
608                 applyTransaction();
609             }
610         });
611         mScrimAlphaAnimator.addListener(new AnimatorListenerAdapter() {
612             @Override
613             public void onAnimationEnd(Animator animation) {
614                 resetScrim();
615             }
616         });
617         mScrimAlphaAnimator.setDuration(SCRIM_FADE_DURATION).start();
618         backAnim.start();
619     }
620 
loadResources()621     private void loadResources() {
622         mWindowScaleEndCornerRadius = QuickStepContract.supportsRoundedCornersOnWindows(
623                 mLauncher.getResources())
624                 ? mLauncher.getResources().getDimensionPixelSize(
625                 R.dimen.swipe_back_window_corner_radius)
626                 : 0;
627         mWindowScaleStartCornerRadius = QuickStepContract.getWindowCornerRadius(mLauncher);
628         mStatusBarHeight = SystemBarUtils.getStatusBarHeight(mLauncher);
629     }
630 
631     /**
632      * Called when launcher is destroyed. Unregisters component callbacks to avoid memory leaks.
633      */
unregisterComponentCallbacks()634     public void unregisterComponentCallbacks() {
635         mLauncher.unregisterComponentCallbacks(mComponentCallbacks);
636     }
637 
638     /**
639      * Registers component callbacks with the launcher to receive configuration change events.
640      */
registerComponentCallbacks()641     public void registerComponentCallbacks() {
642         mLauncher.registerComponentCallbacks(mComponentCallbacks);
643     }
644 
645 
resetScrim()646     private void resetScrim() {
647         removeScrimLayer();
648         mScrimAlpha = 0;
649     }
650 
customizeStatusBarAppearance(boolean overridingStatusBarFlags)651     private void customizeStatusBarAppearance(boolean overridingStatusBarFlags) {
652         if (mOverridingStatusBarFlags == overridingStatusBarFlags) {
653             return;
654         }
655 
656         mOverridingStatusBarFlags = overridingStatusBarFlags;
657         final boolean isBackgroundDark =
658                 (mLauncher.getWindow().getDecorView().getSystemUiVisibility()
659                         & View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR) == 0;
660         final AppearanceRegion region = mOverridingStatusBarFlags
661                 ? new AppearanceRegion(!isBackgroundDark ? APPEARANCE_LIGHT_STATUS_BARS : 0,
662                         mBackTarget.windowConfiguration.getBounds())
663                 : null;
664         SystemUiProxy.INSTANCE.get(mLauncher).customizeStatusBarAppearance(region);
665     }
666 }
667