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