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