1 /* 2 * Copyright (C) 2018 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.launcher3; 18 19 import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME; 20 import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD; 21 import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW; 22 import static android.app.role.RoleManager.ROLE_HOME; 23 import static android.provider.Settings.Secure.LAUNCHER_TASKBAR_EDUCATION_SHOWING; 24 import static android.view.RemoteAnimationTarget.MODE_CLOSING; 25 import static android.view.RemoteAnimationTarget.MODE_OPENING; 26 import static android.view.Surface.ROTATION_0; 27 import static android.view.Surface.ROTATION_180; 28 import static android.view.WindowManager.TRANSIT_CLOSE; 29 import static android.view.WindowManager.TRANSIT_FLAG_KEYGUARD_GOING_AWAY; 30 import static android.view.WindowManager.TRANSIT_OPEN; 31 import static android.view.WindowManager.TRANSIT_TO_BACK; 32 import static android.view.WindowManager.TRANSIT_TO_FRONT; 33 import static android.window.StartingWindowInfo.STARTING_WINDOW_TYPE_NONE; 34 import static android.window.StartingWindowInfo.STARTING_WINDOW_TYPE_SPLASH_SCREEN; 35 import static android.window.TransitionFilter.CONTAINER_ORDER_TOP; 36 37 import static com.android.app.animation.Interpolators.ACCELERATE_1_5; 38 import static com.android.app.animation.Interpolators.AGGRESSIVE_EASE; 39 import static com.android.app.animation.Interpolators.DECELERATE_1_5; 40 import static com.android.app.animation.Interpolators.DECELERATE_1_7; 41 import static com.android.app.animation.Interpolators.EXAGGERATED_EASE; 42 import static com.android.app.animation.Interpolators.LINEAR; 43 import static com.android.launcher3.BaseActivity.EVENT_DESTROYED; 44 import static com.android.launcher3.BaseActivity.INVISIBLE_ALL; 45 import static com.android.launcher3.BaseActivity.INVISIBLE_BY_APP_TRANSITIONS; 46 import static com.android.launcher3.BaseActivity.INVISIBLE_BY_PENDING_FLAGS; 47 import static com.android.launcher3.BaseActivity.PENDING_INVISIBLE_BY_WALLPAPER_ANIMATION; 48 import static com.android.launcher3.Flags.enableContainerReturnAnimations; 49 import static com.android.launcher3.Flags.enableScalingRevealHomeAnimation; 50 import static com.android.launcher3.Flags.syncAppLaunchWithTaskbarStash; 51 import static com.android.launcher3.LauncherAnimUtils.SCALE_PROPERTY; 52 import static com.android.launcher3.LauncherState.ALL_APPS; 53 import static com.android.launcher3.LauncherState.BACKGROUND_APP; 54 import static com.android.launcher3.LauncherState.NORMAL; 55 import static com.android.launcher3.LauncherState.OVERVIEW; 56 import static com.android.launcher3.Utilities.mapBoundToRange; 57 import static com.android.launcher3.config.FeatureFlags.SEPARATE_RECENTS_ACTIVITY; 58 import static com.android.launcher3.testing.shared.TestProtocol.WALLPAPER_OPEN_ANIMATION_FINISHED_MESSAGE; 59 import static com.android.launcher3.util.DisplayController.isTransientTaskbar; 60 import static com.android.launcher3.util.Executors.MAIN_EXECUTOR; 61 import static com.android.launcher3.util.MultiPropertyFactory.MULTI_PROPERTY_VALUE; 62 import static com.android.launcher3.util.window.RefreshRateTracker.getSingleFrameMs; 63 import static com.android.launcher3.views.FloatingIconView.SHAPE_PROGRESS_DURATION; 64 import static com.android.launcher3.views.FloatingIconView.getFloatingIconView; 65 import static com.android.quickstep.TaskViewUtils.findTaskViewToLaunch; 66 import static com.android.quickstep.util.AnimUtils.clampToDuration; 67 import static com.android.quickstep.util.AnimUtils.completeRunnableListCallback; 68 import static com.android.systemui.shared.Flags.returnAnimationFrameworkLibrary; 69 import static com.android.systemui.shared.system.QuickStepContract.getWindowCornerRadius; 70 import static com.android.systemui.shared.system.QuickStepContract.supportsRoundedCornersOnWindows; 71 import static com.android.wm.shell.Flags.enableDynamicInsetsForAppLaunch; 72 73 import android.animation.Animator; 74 import android.animation.AnimatorListenerAdapter; 75 import android.animation.AnimatorSet; 76 import android.animation.ObjectAnimator; 77 import android.animation.ValueAnimator; 78 import android.app.ActivityOptions; 79 import android.app.WindowConfiguration; 80 import android.app.role.RoleManager; 81 import android.content.ComponentName; 82 import android.content.Context; 83 import android.content.res.Resources; 84 import android.graphics.Color; 85 import android.graphics.Matrix; 86 import android.graphics.Point; 87 import android.graphics.PointF; 88 import android.graphics.Rect; 89 import android.graphics.RectF; 90 import android.graphics.drawable.Drawable; 91 import android.os.Handler; 92 import android.os.IBinder; 93 import android.os.IRemoteCallback; 94 import android.os.Looper; 95 import android.os.SystemProperties; 96 import android.os.UserHandle; 97 import android.provider.Settings; 98 import android.util.Log; 99 import android.util.Pair; 100 import android.util.Size; 101 import android.view.CrossWindowBlurListeners; 102 import android.view.IRemoteAnimationFinishedCallback; 103 import android.view.RemoteAnimationAdapter; 104 import android.view.RemoteAnimationDefinition; 105 import android.view.RemoteAnimationTarget; 106 import android.view.SurfaceControl; 107 import android.view.View; 108 import android.view.ViewRootImpl; 109 import android.view.ViewTreeObserver; 110 import android.view.WindowManager; 111 import android.view.animation.AnimationUtils; 112 import android.view.animation.Interpolator; 113 import android.view.animation.PathInterpolator; 114 import android.window.DesktopModeFlags; 115 import android.window.RemoteTransition; 116 import android.window.TransitionFilter; 117 import android.window.WindowAnimationState; 118 119 import androidx.annotation.NonNull; 120 import androidx.annotation.Nullable; 121 import androidx.core.graphics.ColorUtils; 122 123 import com.android.app.animation.Animations; 124 import com.android.internal.jank.Cuj; 125 import com.android.launcher3.DeviceProfile.OnDeviceProfileChangeListener; 126 import com.android.launcher3.LauncherAnimationRunner.RemoteAnimationFactory; 127 import com.android.launcher3.anim.AnimationSuccessListener; 128 import com.android.launcher3.anim.AnimatorListeners; 129 import com.android.launcher3.compat.AccessibilityManagerCompat; 130 import com.android.launcher3.dragndrop.DragLayer; 131 import com.android.launcher3.icons.FastBitmapDrawable; 132 import com.android.launcher3.model.data.ItemInfo; 133 import com.android.launcher3.shortcuts.DeepShortcutView; 134 import com.android.launcher3.statehandlers.DepthController; 135 import com.android.launcher3.taskbar.LauncherTaskbarUIController; 136 import com.android.launcher3.testing.shared.ResourceUtils; 137 import com.android.launcher3.touch.PagedOrientationHandler; 138 import com.android.launcher3.uioverrides.QuickstepLauncher; 139 import com.android.launcher3.util.ActivityOptionsWrapper; 140 import com.android.launcher3.util.DynamicResource; 141 import com.android.launcher3.util.RunnableList; 142 import com.android.launcher3.util.StableViewInfo; 143 import com.android.launcher3.views.FloatingIconView; 144 import com.android.launcher3.widget.LauncherAppWidgetHostView; 145 import com.android.quickstep.LauncherBackAnimationController; 146 import com.android.quickstep.RemoteAnimationTargets; 147 import com.android.quickstep.SystemUiProxy; 148 import com.android.quickstep.TaskViewUtils; 149 import com.android.quickstep.util.AlreadyStartedBackAnimState; 150 import com.android.quickstep.util.AnimatorBackState; 151 import com.android.quickstep.util.BackAnimState; 152 import com.android.quickstep.util.MultiValueUpdateListener; 153 import com.android.quickstep.util.RectFSpringAnim; 154 import com.android.quickstep.util.RectFSpringAnim.DefaultSpringConfig; 155 import com.android.quickstep.util.RectFSpringAnim.TaskbarHotseatSpringConfig; 156 import com.android.quickstep.util.ScalingWorkspaceRevealAnim; 157 import com.android.quickstep.util.StaggeredWorkspaceAnim; 158 import com.android.quickstep.util.SurfaceTransaction; 159 import com.android.quickstep.util.SurfaceTransaction.SurfaceProperties; 160 import com.android.quickstep.util.SurfaceTransactionApplier; 161 import com.android.quickstep.util.TaskRestartedDuringLaunchListener; 162 import com.android.quickstep.util.WorkspaceRevealAnim; 163 import com.android.quickstep.views.FloatingWidgetView; 164 import com.android.quickstep.views.RecentsView; 165 import com.android.systemui.animation.ActivityTransitionAnimator; 166 import com.android.systemui.animation.DelegateTransitionAnimatorController; 167 import com.android.systemui.animation.LaunchableView; 168 import com.android.systemui.animation.RemoteAnimationDelegate; 169 import com.android.systemui.animation.RemoteAnimationRunnerCompat; 170 import com.android.systemui.shared.system.BlurUtils; 171 import com.android.systemui.shared.system.InteractionJankMonitorWrapper; 172 import com.android.systemui.shared.system.QuickStepContract; 173 import com.android.wm.shell.shared.desktopmode.DesktopModeStatus; 174 import com.android.wm.shell.startingsurface.IStartingWindowListener; 175 176 import java.io.PrintWriter; 177 import java.lang.ref.WeakReference; 178 import java.util.ArrayList; 179 import java.util.Arrays; 180 import java.util.Collections; 181 import java.util.LinkedHashMap; 182 import java.util.List; 183 import java.util.Map.Entry; 184 185 /** 186 * Manages the opening and closing app transitions from Launcher 187 */ 188 public class QuickstepTransitionManager implements OnDeviceProfileChangeListener { 189 private static final String TAG = "QuickstepTransitionManager"; 190 191 private static final boolean ENABLE_SHELL_STARTING_SURFACE = 192 SystemProperties.getBoolean("persist.debug.shell_starting_surface", true); 193 194 /** Duration of status bar animations. */ 195 public static final int STATUS_BAR_TRANSITION_DURATION = 120; 196 197 /** 198 * Since our animations decelerate heavily when finishing, we want to start status bar 199 * animations x ms before the ending. 200 */ 201 public static final int STATUS_BAR_TRANSITION_PRE_DELAY = 96; 202 203 public static final long APP_LAUNCH_DURATION = 500; 204 205 private static final long APP_LAUNCH_ALPHA_DURATION = 50; 206 private static final long APP_LAUNCH_ALPHA_START_DELAY = 25; 207 208 public static final int ANIMATION_NAV_FADE_IN_DURATION = 266; 209 public static final int ANIMATION_NAV_FADE_OUT_DURATION = 133; 210 public static final long ANIMATION_DELAY_NAV_FADE_IN = 211 APP_LAUNCH_DURATION - ANIMATION_NAV_FADE_IN_DURATION; 212 public static final Interpolator NAV_FADE_IN_INTERPOLATOR = 213 new PathInterpolator(0f, 0f, 0f, 1f); 214 public static final Interpolator NAV_FADE_OUT_INTERPOLATOR = 215 new PathInterpolator(0.2f, 0f, 1f, 1f); 216 217 public static final int RECENTS_LAUNCH_DURATION = 336; 218 private static final int LAUNCHER_RESUME_START_DELAY = 100; 219 private static final int CLOSING_TRANSITION_DURATION_MS = 250; 220 public static final int SPLIT_LAUNCH_DURATION = 370; 221 public static final int SPLIT_DIVIDER_ANIM_DURATION = 100; 222 223 public static final int CONTENT_ALPHA_DURATION = 217; 224 public static final int TRANSIENT_TASKBAR_TRANSITION_DURATION = 417; 225 public static final int PINNED_TASKBAR_TRANSITION_DURATION = 600; 226 public static final int TASKBAR_TO_APP_DURATION = 600; 227 // TODO(b/236145847): Tune TASKBAR_TO_HOME_DURATION to 383 after conflict with unlock animation 228 // is solved. 229 private static final int TASKBAR_TO_HOME_DURATION_FAST = 300; 230 private static final int TASKBAR_TO_HOME_DURATION_SLOW = 1000; 231 protected static final int CONTENT_SCALE_DURATION = 350; 232 233 private static final int MAX_NUM_TASKS = 5; 234 235 // Cross-fade duration between App Widget and App 236 private static final int WIDGET_CROSSFADE_DURATION_MILLIS = 125; 237 238 protected final QuickstepLauncher mLauncher; 239 protected final DragLayer mDragLayer; 240 241 protected final Handler mHandler; 242 243 private final float mClosingWindowTransY; 244 private final float mClosingFreeformWindowTransY; 245 private final float mMaxShadowRadius; 246 247 private final StartingWindowListener mStartingWindowListener = 248 new StartingWindowListener(this); 249 250 // TODO(b/397690719): Investigate the memory leak from TaskStackChangeListeners#mImpl 251 // This is a temporary fix of memory leak b/397690719. We track registered 252 // {@link TaskRestartedDuringLaunchListener}, and remove them on activity destroy. 253 private final List<TaskRestartedDuringLaunchListener> mRegisteredTaskStackChangeListener = 254 new ArrayList<>(); 255 256 private DeviceProfile mDeviceProfile; 257 258 // Strong refs to runners which are cleared when the launcher activity is destroyed 259 private RemoteAnimationFactory mWallpaperOpenRunner; 260 private RemoteAnimationFactory mAppLaunchRunner; 261 262 private RemoteAnimationFactory mWallpaperOpenTransitionRunner; 263 private RemoteTransition mLauncherOpenTransition; 264 265 private final RemoteAnimationCoordinateTransfer mCoordinateTransfer; 266 267 private LauncherBackAnimationController mBackAnimationController; 268 private final AnimatorListenerAdapter mForceInvisibleListener = new AnimatorListenerAdapter() { 269 @Override 270 public void onAnimationStart(Animator animation) { 271 mLauncher.addForceInvisibleFlag(INVISIBLE_BY_APP_TRANSITIONS); 272 } 273 274 @Override 275 public void onAnimationEnd(Animator animation) { 276 mLauncher.clearForceInvisibleFlag(INVISIBLE_BY_APP_TRANSITIONS); 277 } 278 }; 279 280 // Pairs of window starting type and starting window background color for starting tasks 281 // Will never be larger than MAX_NUM_TASKS 282 private LinkedHashMap<Integer, Pair<Integer, Integer>> mTaskStartParams; 283 284 private final Interpolator mOpeningXInterpolator; 285 private final Interpolator mOpeningInterpolator; 286 287 private final SystemUiProxy mSystemUiProxy; 288 QuickstepTransitionManager(Context context)289 public QuickstepTransitionManager(Context context) { 290 mLauncher = Launcher.cast(Launcher.getLauncher(context)); 291 mDragLayer = mLauncher.getDragLayer(); 292 mHandler = new Handler(Looper.getMainLooper()); 293 mDeviceProfile = mLauncher.getDeviceProfile(); 294 mBackAnimationController = new LauncherBackAnimationController(mLauncher, this); 295 296 Resources res = mLauncher.getResources(); 297 mClosingWindowTransY = res.getDimensionPixelSize(R.dimen.closing_window_trans_y); 298 mClosingFreeformWindowTransY = 299 res.getDimensionPixelSize(R.dimen.closing_freeform_window_trans_y); 300 mMaxShadowRadius = res.getDimensionPixelSize(R.dimen.max_shadow_radius); 301 302 mLauncher.addOnDeviceProfileChangeListener(this); 303 mSystemUiProxy = SystemUiProxy.INSTANCE.get(mLauncher); 304 305 if (ENABLE_SHELL_STARTING_SURFACE) { 306 mTaskStartParams = new LinkedHashMap<>(MAX_NUM_TASKS) { 307 @Override 308 protected boolean removeEldestEntry(Entry<Integer, Pair<Integer, Integer>> entry) { 309 return size() > MAX_NUM_TASKS; 310 } 311 }; 312 313 mSystemUiProxy.setStartingWindowListener(mStartingWindowListener); 314 } 315 316 mOpeningXInterpolator = AnimationUtils.loadInterpolator(context, R.interpolator.app_open_x); 317 mOpeningInterpolator = AnimationUtils.loadInterpolator(context, 318 R.interpolator.emphasized_interpolator); 319 mCoordinateTransfer = new RemoteAnimationCoordinateTransfer(mLauncher); 320 } 321 322 @Override onDeviceProfileChanged(DeviceProfile dp)323 public void onDeviceProfileChanged(DeviceProfile dp) { 324 mDeviceProfile = dp; 325 } 326 327 /** 328 * @return ActivityOptions with remote animations that controls how the window of the opening 329 * targets are displayed. 330 */ getActivityLaunchOptions(View v, ItemInfo itemInfo)331 public ActivityOptionsWrapper getActivityLaunchOptions(View v, ItemInfo itemInfo) { 332 boolean fromRecents = isLaunchingFromRecents(v, null /* targets */); 333 RunnableList onEndCallback = new RunnableList(); 334 335 // Handle the case where an already visible task is launched which results in no transition 336 TaskRestartedDuringLaunchListener restartedListener = 337 new TaskRestartedDuringLaunchListener(); 338 restartedListener.register(onEndCallback::executeAllAndDestroy); 339 mRegisteredTaskStackChangeListener.add(restartedListener); 340 onEndCallback.add(new Runnable() { 341 @Override 342 public void run() { 343 restartedListener.unregister(); 344 mRegisteredTaskStackChangeListener.remove(restartedListener); 345 } 346 }); 347 348 RemoteAnimationRunnerCompat runner = createAppLaunchRunner(v, onEndCallback); 349 350 // Note that this duration is a guess as we do not know if the animation will be a 351 // recents launch or not for sure until we know the opening app targets. 352 long duration = fromRecents 353 ? RECENTS_LAUNCH_DURATION 354 : APP_LAUNCH_DURATION; 355 356 long statusBarTransitionDelay = duration - STATUS_BAR_TRANSITION_DURATION 357 - STATUS_BAR_TRANSITION_PRE_DELAY; 358 ActivityOptions options = ActivityOptions.makeRemoteAnimation( 359 new RemoteAnimationAdapter(runner, duration, statusBarTransitionDelay), 360 new RemoteTransition(runner.toRemoteTransition(), 361 mLauncher.getIApplicationThread(), "QuickstepLaunch")); 362 IRemoteCallback endCallback = completeRunnableListCallback(onEndCallback, mLauncher); 363 options.setOnAnimationAbortListener(endCallback); 364 options.setOnAnimationFinishedListener(endCallback); 365 options.setLaunchCookie(StableViewInfo.toLaunchCookie(itemInfo)); 366 367 // Prepare taskbar for animation synchronization. This needs to happen here before any 368 // app transition is created. 369 LauncherTaskbarUIController taskbarController = mLauncher.getTaskbarUIController(); 370 if (syncAppLaunchWithTaskbarStash() 371 && enableScalingRevealHomeAnimation() 372 && taskbarController != null) { 373 taskbarController.setIgnoreInAppFlagForSync(true); 374 mLauncher.addEventCallback(EVENT_DESTROYED, onEndCallback::executeAllAndDestroy); 375 onEndCallback.add(() -> { 376 taskbarController.setIgnoreInAppFlagForSync(false); 377 }); 378 } 379 380 return new ActivityOptionsWrapper(options, onEndCallback); 381 } 382 383 /** 384 * Selects the appropriate type of launch runner for the given view, builds it, and returns it. 385 * {@link QuickstepTransitionManager#mAppLaunchRunner} is updated as a by-product of this 386 * method. 387 */ createAppLaunchRunner(View v, RunnableList onEndCallback)388 private RemoteAnimationRunnerCompat createAppLaunchRunner(View v, RunnableList onEndCallback) { 389 ItemInfo tag = (ItemInfo) v.getTag(); 390 ContainerAnimationRunner containerRunner = null; 391 if (tag != null && tag.shouldUseBackgroundAnimation()) { 392 containerRunner = ContainerAnimationRunner.fromView( 393 v, true /* forLaunch */, mLauncher, mStartingWindowListener, onEndCallback, 394 null /* windowState */); 395 } 396 397 mAppLaunchRunner = containerRunner != null 398 ? containerRunner : new AppLaunchAnimationRunner(v, onEndCallback); 399 return new LauncherAnimationRunner( 400 mHandler, mAppLaunchRunner, true /* startAtFrontOfQueue */); 401 } 402 403 /** 404 * Whether the launch is a recents app transition and we should do a launch animation 405 * from the recents view. Note that if the remote animation targets are not provided, this 406 * may not always be correct as we may resolve the opening app to a task when the animation 407 * starts. 408 * 409 * @param v the view to launch from 410 * @param targets apps that are opening/closing 411 * @return true if the app is launching from recents, false if it most likely is not 412 */ isLaunchingFromRecents(@onNull View v, @Nullable RemoteAnimationTarget[] targets)413 protected boolean isLaunchingFromRecents(@NonNull View v, 414 @Nullable RemoteAnimationTarget[] targets) { 415 return mLauncher.getStateManager().getState().isRecentsViewVisible 416 && findTaskViewToLaunch(mLauncher.getOverviewPanel(), v, targets) != null; 417 } 418 419 /** 420 * Composes the animations for a launch from the recents list. 421 * 422 * @param anim the animator set to add to 423 * @param v the launching view 424 * @param appTargets the apps that are opening/closing 425 * @param launcherClosing true if the launcher app is closing 426 */ composeRecentsLaunchAnimator(@onNull AnimatorSet anim, @NonNull View v, @NonNull RemoteAnimationTarget[] appTargets, @NonNull RemoteAnimationTarget[] wallpaperTargets, @NonNull RemoteAnimationTarget[] nonAppTargets, boolean launcherClosing)427 protected void composeRecentsLaunchAnimator(@NonNull AnimatorSet anim, @NonNull View v, 428 @NonNull RemoteAnimationTarget[] appTargets, 429 @NonNull RemoteAnimationTarget[] wallpaperTargets, 430 @NonNull RemoteAnimationTarget[] nonAppTargets, boolean launcherClosing) { 431 TaskViewUtils.composeRecentsLaunchAnimator(anim, v, appTargets, wallpaperTargets, 432 nonAppTargets, launcherClosing, mLauncher.getStateManager(), 433 mLauncher.getOverviewPanel(), mLauncher.getDepthController(), 434 /* transitionInfo= */ null); 435 } 436 areAllTargetsTranslucent(@onNull RemoteAnimationTarget[] targets)437 private boolean areAllTargetsTranslucent(@NonNull RemoteAnimationTarget[] targets) { 438 boolean isAllOpeningTargetTrs = true; 439 for (int i = 0; i < targets.length; i++) { 440 RemoteAnimationTarget target = targets[i]; 441 if (target.mode == MODE_OPENING) { 442 isAllOpeningTargetTrs &= target.isTranslucent; 443 } 444 if (!isAllOpeningTargetTrs) break; 445 } 446 return isAllOpeningTargetTrs; 447 } 448 449 /** 450 * Compose the animations for a launch from the app icon. 451 * 452 * @param anim the animation to add to 453 * @param v the launching view with the icon 454 * @param appTargets the list of opening/closing apps 455 * @param launcherClosing true if launcher is closing 456 */ composeIconLaunchAnimator(@onNull AnimatorSet anim, @NonNull View v, @NonNull RemoteAnimationTarget[] appTargets, @NonNull RemoteAnimationTarget[] wallpaperTargets, @NonNull RemoteAnimationTarget[] nonAppTargets, boolean launcherClosing)457 private void composeIconLaunchAnimator(@NonNull AnimatorSet anim, @NonNull View v, 458 @NonNull RemoteAnimationTarget[] appTargets, 459 @NonNull RemoteAnimationTarget[] wallpaperTargets, 460 @NonNull RemoteAnimationTarget[] nonAppTargets, 461 boolean launcherClosing) { 462 // Set the state animation first so that any state listeners are called 463 // before our internal listeners. 464 mLauncher.getStateManager().setCurrentAnimation(anim); 465 466 // Note: the targetBounds are relative to the launcher 467 int startDelay = getSingleFrameMs(mLauncher); 468 Animator windowAnimator = getOpeningWindowAnimators( 469 v, appTargets, wallpaperTargets, nonAppTargets, launcherClosing); 470 windowAnimator.setStartDelay(startDelay); 471 anim.play(windowAnimator); 472 if (launcherClosing) { 473 // Delay animation by a frame to avoid jank. 474 Pair<AnimatorSet, Runnable> launcherContentAnimator = 475 getLauncherContentAnimator(true /* isAppOpening */, startDelay, false); 476 anim.play(launcherContentAnimator.first); 477 anim.addListener(new AnimatorListenerAdapter() { 478 @Override 479 public void onAnimationEnd(Animator animation) { 480 launcherContentAnimator.second.run(); 481 } 482 }); 483 } 484 } 485 composeWidgetLaunchAnimator( @onNull AnimatorSet anim, @NonNull LauncherAppWidgetHostView v, @NonNull RemoteAnimationTarget[] appTargets, @NonNull RemoteAnimationTarget[] wallpaperTargets, @NonNull RemoteAnimationTarget[] nonAppTargets, boolean launcherClosing)486 private void composeWidgetLaunchAnimator( 487 @NonNull AnimatorSet anim, 488 @NonNull LauncherAppWidgetHostView v, 489 @NonNull RemoteAnimationTarget[] appTargets, 490 @NonNull RemoteAnimationTarget[] wallpaperTargets, 491 @NonNull RemoteAnimationTarget[] nonAppTargets, 492 boolean launcherClosing) { 493 mLauncher.getStateManager().setCurrentAnimation(anim); 494 anim.play(getOpeningWindowAnimatorsForWidget( 495 v, appTargets, wallpaperTargets, nonAppTargets, launcherClosing)); 496 } 497 498 /** 499 * Return the window bounds of the opening target. 500 * In multiwindow mode, we need to get the final size of the opening app window target to help 501 * figure out where the floating view should animate to. 502 */ getWindowTargetBounds(@onNull RemoteAnimationTarget[] appTargets, int rotationChange)503 private Rect getWindowTargetBounds(@NonNull RemoteAnimationTarget[] appTargets, 504 int rotationChange) { 505 RemoteAnimationTarget target = null; 506 for (RemoteAnimationTarget t : appTargets) { 507 if (t.mode != MODE_OPENING) continue; 508 target = t; 509 break; 510 } 511 if (target == null) return new Rect(0, 0, mDeviceProfile.widthPx, mDeviceProfile.heightPx); 512 final Rect bounds = new Rect(target.screenSpaceBounds); 513 if (target.localBounds != null) { 514 bounds.set(target.localBounds); 515 } else { 516 bounds.offsetTo(target.position.x, target.position.y); 517 } 518 if (rotationChange != 0) { 519 if ((rotationChange % 2) == 1) { 520 // undoing rotation, so our "original" parent size is actually flipped 521 Utilities.rotateBounds(bounds, mDeviceProfile.heightPx, mDeviceProfile.widthPx, 522 4 - rotationChange); 523 } else { 524 Utilities.rotateBounds(bounds, mDeviceProfile.widthPx, mDeviceProfile.heightPx, 525 4 - rotationChange); 526 } 527 } 528 return bounds; 529 } 530 531 /** Dump debug logs to bug report. */ dump(@onNull String prefix, @NonNull PrintWriter printWriter)532 public void dump(@NonNull String prefix, @NonNull PrintWriter printWriter) {} 533 534 /** 535 * Content is everything on screen except the background and the floating view (if any). 536 * 537 * @param isAppOpening True when this is called when an app is opening. 538 * False when this is called when an app is closing. 539 * @param startDelay Start delay duration. 540 * @param skipAllAppsScale True if we want to avoid scaling All Apps 541 */ getLauncherContentAnimator(boolean isAppOpening, int startDelay, boolean skipAllAppsScale)542 private Pair<AnimatorSet, Runnable> getLauncherContentAnimator(boolean isAppOpening, 543 int startDelay, boolean skipAllAppsScale) { 544 AnimatorSet launcherAnimator = new AnimatorSet(); 545 Runnable endListener; 546 547 float[] alphas = isAppOpening 548 ? new float[]{1, 0} 549 : new float[]{0, 1}; 550 551 float[] scales = isAppOpening 552 ? new float[]{1, mDeviceProfile.workspaceContentScale} 553 : new float[]{mDeviceProfile.workspaceContentScale, 1}; 554 555 // Pause expensive view updates as they can lead to layer thrashing and skipped frames. 556 mLauncher.pauseExpensiveViewUpdates(); 557 558 if (mLauncher.isInState(ALL_APPS)) { 559 // All Apps in portrait mode is full screen, so we only animate AllAppsContainerView. 560 final View appsView = mLauncher.getAppsView(); 561 final float startAlpha = appsView.getAlpha(); 562 final float startScale = SCALE_PROPERTY.get(appsView); 563 if (mDeviceProfile.isTablet) { 564 // AllApps should not fade at all in tablets. 565 alphas = new float[]{1, 1}; 566 } 567 appsView.setAlpha(alphas[0]); 568 569 ObjectAnimator alpha = ObjectAnimator.ofFloat(appsView, View.ALPHA, alphas); 570 alpha.setDuration(CONTENT_ALPHA_DURATION); 571 alpha.setInterpolator(LINEAR); 572 appsView.setLayerType(View.LAYER_TYPE_HARDWARE, null); 573 alpha.addListener(new AnimatorListenerAdapter() { 574 @Override 575 public void onAnimationEnd(Animator animation) { 576 appsView.setLayerType(View.LAYER_TYPE_NONE, null); 577 } 578 }); 579 580 if (!skipAllAppsScale) { 581 SCALE_PROPERTY.set(appsView, scales[0]); 582 ObjectAnimator scale = ObjectAnimator.ofFloat(appsView, SCALE_PROPERTY, scales); 583 scale.setInterpolator(AGGRESSIVE_EASE); 584 scale.setDuration(CONTENT_SCALE_DURATION); 585 launcherAnimator.play(scale); 586 } 587 588 launcherAnimator.play(alpha); 589 590 endListener = () -> { 591 appsView.setAlpha(startAlpha); 592 SCALE_PROPERTY.set(appsView, startScale); 593 appsView.setLayerType(View.LAYER_TYPE_NONE, null); 594 mLauncher.resumeExpensiveViewUpdates(); 595 }; 596 } else if (mLauncher.isInState(OVERVIEW)) { 597 endListener = composeViewContentAnimator(launcherAnimator, alphas, scales); 598 } else { 599 List<View> viewsToAnimate = new ArrayList<>(); 600 Workspace<?> workspace = mLauncher.getWorkspace(); 601 if (Flags.coordinateWorkspaceScale()) { 602 viewsToAnimate.add(workspace); 603 } else { 604 workspace.forEachVisiblePage( 605 view -> viewsToAnimate.add(((CellLayout) view).getShortcutsAndWidgets())); 606 } 607 608 Hotseat hotseat = mLauncher.getHotseat(); 609 // Do not scale hotseat as a whole when taskbar is present, and scale QSB only if it's 610 // not inline. 611 if (mDeviceProfile.isTaskbarPresent) { 612 if (!mDeviceProfile.isQsbInline) { 613 viewsToAnimate.add(hotseat.getQsb()); 614 } 615 } else { 616 viewsToAnimate.add(hotseat); 617 } 618 619 viewsToAnimate.forEach(view -> { 620 view.setLayerType(View.LAYER_TYPE_HARDWARE, null); 621 622 float[] scale = scales; 623 if (Flags.coordinateWorkspaceScale()) { 624 // Start the animation from the current value, instead of assuming the views are 625 // in their resting state, so interrupted animations merge seamlessly. 626 // TODO(b/367591368): ideally these animations would be refactored to be 627 // controlled centrally so each instances doesn't need to care about this 628 // coordination. 629 scale = new float[]{view.getScaleX(), scales[1]}; 630 631 // Cancel any ongoing animations. This is necessary to avoid a conflict between 632 // e.g. the unfinished animation triggered when closing an app back to Home and 633 // this animation caused by a launch. 634 Animations.Companion.cancelOngoingAnimation(view); 635 // Make sure to cache the current animation, so it can be properly interrupted. 636 Animations.Companion.setOngoingAnimation(view, launcherAnimator); 637 } 638 639 ObjectAnimator scaleAnim = ObjectAnimator.ofFloat(view, SCALE_PROPERTY, scale) 640 .setDuration(CONTENT_SCALE_DURATION); 641 scaleAnim.setInterpolator(DECELERATE_1_5); 642 launcherAnimator.play(scaleAnim); 643 }); 644 645 endListener = () -> { 646 viewsToAnimate.forEach(view -> { 647 SCALE_PROPERTY.set(view, 1f); 648 view.setLayerType(View.LAYER_TYPE_NONE, null); 649 650 if (Flags.coordinateWorkspaceScale()) { 651 // Reset the cached animation. 652 Animations.Companion.setOngoingAnimation(view, null /* animation */); 653 } 654 }); 655 mLauncher.resumeExpensiveViewUpdates(); 656 }; 657 } 658 659 launcherAnimator.setStartDelay(startDelay); 660 return new Pair<>(launcherAnimator, endListener); 661 } 662 663 /** 664 * Compose recents view alpha and translation Y animation when launcher opens/closes apps. 665 * 666 * @param anim the animator set to add to 667 * @param alphas the alphas to animate to over time 668 * @param scales the scale values to animator to over time 669 * @return listener to run when the animation ends 670 */ composeViewContentAnimator(@onNull AnimatorSet anim, float[] alphas, float[] scales)671 protected Runnable composeViewContentAnimator(@NonNull AnimatorSet anim, 672 float[] alphas, float[] scales) { 673 RecentsView overview = mLauncher.getOverviewPanel(); 674 ObjectAnimator alpha = ObjectAnimator.ofFloat(overview, 675 RecentsView.CONTENT_ALPHA, alphas); 676 alpha.setDuration(CONTENT_ALPHA_DURATION); 677 alpha.setInterpolator(LINEAR); 678 anim.play(alpha); 679 overview.setFreezeViewVisibility(true); 680 681 ObjectAnimator scaleAnim = ObjectAnimator.ofFloat(overview, SCALE_PROPERTY, scales); 682 scaleAnim.setInterpolator(AGGRESSIVE_EASE); 683 scaleAnim.setDuration(CONTENT_SCALE_DURATION); 684 anim.play(scaleAnim); 685 686 return () -> { 687 overview.setFreezeViewVisibility(false); 688 SCALE_PROPERTY.set(overview, 1f); 689 mLauncher.getStateManager().reapplyState(); 690 mLauncher.resumeExpensiveViewUpdates(); 691 }; 692 } 693 shouldCropToInset(RemoteAnimationTarget target)694 private boolean shouldCropToInset(RemoteAnimationTarget target) { 695 return enableDynamicInsetsForAppLaunch() 696 && mDeviceProfile.isTaskbarPresentInApps 697 && target != null && !target.willShowImeOnTarget 698 && !isTransientTaskbar(mLauncher); 699 } 700 701 /** 702 * @return Animator that controls the window of the opening targets from app icons. 703 */ getOpeningWindowAnimators(View v, RemoteAnimationTarget[] appTargets, RemoteAnimationTarget[] wallpaperTargets, RemoteAnimationTarget[] nonAppTargets, boolean launcherClosing)704 private Animator getOpeningWindowAnimators(View v, 705 RemoteAnimationTarget[] appTargets, 706 RemoteAnimationTarget[] wallpaperTargets, 707 RemoteAnimationTarget[] nonAppTargets, 708 boolean launcherClosing) { 709 RemoteAnimationTargets openingTargets = new RemoteAnimationTargets(appTargets, 710 wallpaperTargets, nonAppTargets, MODE_OPENING); 711 int rotationChange = getRotationChange(appTargets); 712 Rect windowTargetBounds = getWindowTargetBounds(appTargets, rotationChange); 713 final int[] bottomInsetPos = new int[]{ 714 mSystemUiProxy.getHomeVisibilityState().getNavbarInsetPosition()}; 715 final RemoteAnimationTarget target = openingTargets.getFirstAppTarget(); 716 final boolean cropToInset = shouldCropToInset(target); 717 if (cropToInset) { 718 // Animate to above the taskbar. 719 windowTargetBounds.bottom = Math.min(bottomInsetPos[0], 720 windowTargetBounds.bottom); 721 } 722 boolean appTargetsAreTranslucent = areAllTargetsTranslucent(appTargets); 723 724 RectF launcherIconBounds = new RectF(); 725 FloatingIconView floatingView = getFloatingIconView(mLauncher, v, 726 (mLauncher.getTaskbarUIController() == null || !isTransientTaskbar(mLauncher)) 727 ? null 728 : mLauncher.getTaskbarUIController().findMatchingView(v), 729 null /* fadeOutView */, !appTargetsAreTranslucent, launcherIconBounds, 730 true /* isOpening */); 731 Rect crop = new Rect(); 732 Matrix matrix = new Matrix(); 733 734 SurfaceTransactionApplier surfaceApplier = 735 new SurfaceTransactionApplier(floatingView); 736 openingTargets.addReleaseCheck(surfaceApplier); 737 RemoteAnimationTarget navBarTarget = openingTargets.getNavBarRemoteAnimationTarget(); 738 739 int[] dragLayerBounds = new int[2]; 740 mDragLayer.getLocationOnScreen(dragLayerBounds); 741 742 final boolean hasSplashScreen; 743 if (ENABLE_SHELL_STARTING_SURFACE) { 744 int taskId = openingTargets.getFirstAppTargetTaskId(); 745 Pair<Integer, Integer> defaultParams = Pair.create(STARTING_WINDOW_TYPE_NONE, 0); 746 Pair<Integer, Integer> taskParams = 747 mTaskStartParams.getOrDefault(taskId, defaultParams); 748 mTaskStartParams.remove(taskId); 749 hasSplashScreen = taskParams.first == STARTING_WINDOW_TYPE_SPLASH_SCREEN; 750 } else { 751 hasSplashScreen = false; 752 } 753 754 AnimOpenProperties prop = new AnimOpenProperties(mLauncher.getResources(), mDeviceProfile, 755 windowTargetBounds, launcherIconBounds, v, dragLayerBounds[0], dragLayerBounds[1], 756 hasSplashScreen, floatingView.isDifferentFromAppIcon()); 757 int left = prop.cropCenterXStart - prop.cropWidthStart / 2; 758 int top = prop.cropCenterYStart - prop.cropHeightStart / 2; 759 int right = left + prop.cropWidthStart; 760 int bottom = top + prop.cropHeightStart; 761 // Set the crop here so we can calculate the corner radius below. 762 crop.set(left, top, right, bottom); 763 764 RectF floatingIconBounds = new RectF(); 765 RectF tmpRectF = new RectF(); 766 Point tmpPos = new Point(); 767 768 AnimatorSet animatorSet = new AnimatorSet(); 769 ValueAnimator appAnimator = ValueAnimator.ofFloat(0, 1); 770 appAnimator.setDuration(APP_LAUNCH_DURATION); 771 appAnimator.setInterpolator(LINEAR); 772 appAnimator.addListener(floatingView); 773 appAnimator.addListener(new AnimatorListenerAdapter() { 774 @Override 775 public void onAnimationStart(Animator animation) { 776 LauncherTaskbarUIController taskbarController = mLauncher.getTaskbarUIController(); 777 if (taskbarController != null && taskbarController.shouldShowEduOnAppLaunch()) { 778 // LAUNCHER_TASKBAR_EDUCATION_SHOWING is set to true here, when the education 779 // flow is about to start, to avoid a race condition with other components 780 // that would show something else to the user as soon as the app is opened. 781 Settings.Secure.putInt(mLauncher.getContentResolver(), 782 LAUNCHER_TASKBAR_EDUCATION_SHOWING, 1); 783 } 784 } 785 786 @Override 787 public void onAnimationEnd(Animator animation) { 788 if (v instanceof BubbleTextView) { 789 ((BubbleTextView) v).setStayPressed(false); 790 } 791 LauncherTaskbarUIController taskbarController = mLauncher.getTaskbarUIController(); 792 if (taskbarController != null) { 793 taskbarController.showEduOnAppLaunch(); 794 } 795 openingTargets.release(); 796 } 797 }); 798 799 final float initialWindowRadius = supportsRoundedCornersOnWindows(mLauncher.getResources()) 800 ? Math.max(crop.width(), crop.height()) / 2f 801 : 0f; 802 final float finalWindowRadius = mDeviceProfile.isMultiWindowMode 803 ? 0 : getWindowCornerRadius(mLauncher); 804 final float finalShadowRadius = appTargetsAreTranslucent ? 0 : mMaxShadowRadius; 805 806 MultiValueUpdateListener listener = new MultiValueUpdateListener() { 807 FloatProp mDx = new FloatProp(0, prop.dX, mOpeningXInterpolator); 808 FloatProp mDy = new FloatProp(0, prop.dY, mOpeningInterpolator); 809 810 FloatProp mIconScaleToFitScreen = new FloatProp(prop.initialAppIconScale, 811 prop.finalAppIconScale, mOpeningInterpolator); 812 FloatProp mIconAlpha = new FloatProp(prop.iconAlphaStart, 0f, 813 clampToDuration(LINEAR, APP_LAUNCH_ALPHA_START_DELAY, APP_LAUNCH_ALPHA_DURATION, 814 APP_LAUNCH_DURATION)); 815 816 FloatProp mWindowRadius = new FloatProp(initialWindowRadius, finalWindowRadius, 817 mOpeningInterpolator); 818 FloatProp mShadowRadius = new FloatProp(0, finalShadowRadius, 819 mOpeningInterpolator); 820 821 FloatProp mCropRectCenterX = new FloatProp(prop.cropCenterXStart, prop.cropCenterXEnd, 822 mOpeningInterpolator); 823 FloatProp mCropRectCenterY = new FloatProp(prop.cropCenterYStart, prop.cropCenterYEnd, 824 mOpeningInterpolator); 825 FloatProp mCropRectWidth = new FloatProp(prop.cropWidthStart, prop.cropWidthEnd, 826 mOpeningInterpolator); 827 FloatProp mCropRectHeight = new FloatProp(prop.cropHeightStart, prop.cropHeightEnd, 828 mOpeningInterpolator); 829 830 FloatProp mNavFadeOut = new FloatProp(1f, 0f, clampToDuration( 831 NAV_FADE_OUT_INTERPOLATOR, 0, ANIMATION_NAV_FADE_OUT_DURATION, 832 APP_LAUNCH_DURATION)); 833 FloatProp mNavFadeIn = new FloatProp(0f, 1f, clampToDuration( 834 NAV_FADE_IN_INTERPOLATOR, ANIMATION_DELAY_NAV_FADE_IN, 835 ANIMATION_NAV_FADE_IN_DURATION, APP_LAUNCH_DURATION)); 836 837 @Override 838 public void onUpdate(float percent, boolean initOnly) { 839 if (cropToInset && bottomInsetPos[0] != mSystemUiProxy.getHomeVisibilityState() 840 .getNavbarInsetPosition()) { 841 final RemoteAnimationTarget target = openingTargets.getFirstAppTarget(); 842 bottomInsetPos[0] = mSystemUiProxy.getHomeVisibilityState() 843 .getNavbarInsetPosition(); 844 final Rect bounds = target != null 845 ? target.screenSpaceBounds : windowTargetBounds; 846 // Animate to above the taskbar. 847 int bottomLevel = Math.min(bottomInsetPos[0], bounds.bottom); 848 windowTargetBounds.bottom = bottomLevel; 849 final int endHeight = bottomLevel - bounds.top; 850 851 AnimOpenProperties prop = new AnimOpenProperties(mLauncher.getResources(), 852 mDeviceProfile, windowTargetBounds, launcherIconBounds, v, 853 dragLayerBounds[0], dragLayerBounds[1], hasSplashScreen, 854 floatingView.isDifferentFromAppIcon()); 855 mCropRectCenterY = new FloatProp(prop.cropCenterYStart, prop.cropCenterYEnd, 856 mOpeningInterpolator); 857 mCropRectHeight = new FloatProp(prop.cropHeightStart, prop.cropHeightEnd, 858 mOpeningInterpolator); 859 mDy = new FloatProp(0, prop.dY, mOpeningInterpolator); 860 mIconScaleToFitScreen = new FloatProp(prop.initialAppIconScale, 861 prop.finalAppIconScale, mOpeningInterpolator); 862 float interpolatedPercent = mOpeningInterpolator.getInterpolation(percent); 863 mCropRectHeight.value = Utilities.mapRange(interpolatedPercent, 864 prop.cropHeightStart, prop.cropHeightEnd); 865 mCropRectCenterY.value = Utilities.mapRange(interpolatedPercent, 866 prop.cropCenterYStart, prop.cropCenterYEnd); 867 mDy.value = Utilities.mapRange(interpolatedPercent, 0, prop.dY); 868 mIconScaleToFitScreen.value = Utilities.mapRange(interpolatedPercent, 869 prop.initialAppIconScale, prop.finalAppIconScale); 870 } 871 872 // Calculate the size of the scaled icon. 873 float iconWidth = launcherIconBounds.width() * mIconScaleToFitScreen.value; 874 float iconHeight = launcherIconBounds.height() * mIconScaleToFitScreen.value; 875 876 int left = (int) (mCropRectCenterX.value - mCropRectWidth.value / 2); 877 int top = (int) (mCropRectCenterY.value - mCropRectHeight.value / 2); 878 int right = (int) (left + mCropRectWidth.value); 879 int bottom = (int) (top + mCropRectHeight.value); 880 crop.set(left, top, right, bottom); 881 882 final int windowCropWidth = crop.width(); 883 final int windowCropHeight = crop.height(); 884 if (rotationChange != 0) { 885 Utilities.rotateBounds(crop, mDeviceProfile.widthPx, 886 mDeviceProfile.heightPx, rotationChange); 887 } 888 889 // Scale the size of the icon to match the size of the window crop. 890 float scaleX = iconWidth / windowCropWidth; 891 float scaleY = iconHeight / windowCropHeight; 892 float scale = Math.min(1f, Math.max(scaleX, scaleY)); 893 894 float scaledCropWidth = windowCropWidth * scale; 895 float scaledCropHeight = windowCropHeight * scale; 896 float offsetX = (scaledCropWidth - iconWidth) / 2; 897 float offsetY = (scaledCropHeight - iconHeight) / 2; 898 899 // Calculate the window position to match the icon position. 900 tmpRectF.set(launcherIconBounds); 901 tmpRectF.offset(dragLayerBounds[0], dragLayerBounds[1]); 902 tmpRectF.offset(mDx.value, mDy.value); 903 Utilities.scaleRectFAboutCenter(tmpRectF, mIconScaleToFitScreen.value); 904 float windowTransX0 = tmpRectF.left - offsetX - crop.left * scale; 905 float windowTransY0 = tmpRectF.top - offsetY - crop.top * scale; 906 907 // Calculate the icon position. 908 floatingIconBounds.set(launcherIconBounds); 909 floatingIconBounds.offset(mDx.value, mDy.value); 910 Utilities.scaleRectFAboutCenter(floatingIconBounds, mIconScaleToFitScreen.value); 911 floatingIconBounds.left -= offsetX; 912 floatingIconBounds.top -= offsetY; 913 floatingIconBounds.right += offsetX; 914 floatingIconBounds.bottom += offsetY; 915 916 if (initOnly) { 917 // For the init pass, we want full alpha since the window is not yet ready. 918 floatingView.update(1f, floatingIconBounds, percent, 0f, 919 mWindowRadius.value * scale, true /* isOpening */); 920 return; 921 } 922 923 SurfaceTransaction transaction = new SurfaceTransaction(); 924 925 for (int i = appTargets.length - 1; i >= 0; i--) { 926 RemoteAnimationTarget target = appTargets[i]; 927 SurfaceProperties builder = transaction.forSurface(target.leash); 928 929 if (target.mode == MODE_OPENING) { 930 matrix.setScale(scale, scale); 931 if (rotationChange == 1) { 932 matrix.postTranslate(windowTransY0, 933 mDeviceProfile.widthPx - (windowTransX0 + scaledCropWidth)); 934 } else if (rotationChange == 2) { 935 matrix.postTranslate( 936 mDeviceProfile.widthPx - (windowTransX0 + scaledCropWidth), 937 mDeviceProfile.heightPx - (windowTransY0 + scaledCropHeight)); 938 } else if (rotationChange == 3) { 939 matrix.postTranslate( 940 mDeviceProfile.heightPx - (windowTransY0 + scaledCropHeight), 941 windowTransX0); 942 } else { 943 matrix.postTranslate(windowTransX0, windowTransY0); 944 } 945 946 floatingView.update(mIconAlpha.value, floatingIconBounds, percent, 0f, 947 mWindowRadius.value * scale, true /* isOpening */); 948 builder.setMatrix(matrix) 949 .setWindowCrop(crop) 950 .setAlpha(1f - mIconAlpha.value) 951 .setCornerRadius(mWindowRadius.value) 952 .setShadowRadius(mShadowRadius.value); 953 } else if (target.mode == MODE_CLOSING) { 954 if (target.localBounds != null) { 955 tmpPos.set(target.localBounds.left, target.localBounds.top); 956 } else { 957 tmpPos.set(target.position.x, target.position.y); 958 } 959 final Rect crop = new Rect(target.screenSpaceBounds); 960 crop.offsetTo(0, 0); 961 962 if ((rotationChange % 2) == 1) { 963 int tmp = crop.right; 964 crop.right = crop.bottom; 965 crop.bottom = tmp; 966 tmp = tmpPos.x; 967 tmpPos.x = tmpPos.y; 968 tmpPos.y = tmp; 969 } 970 matrix.setTranslate(tmpPos.x, tmpPos.y); 971 builder.setMatrix(matrix) 972 .setWindowCrop(crop) 973 .setAlpha(1f); 974 } 975 } 976 977 if (navBarTarget != null) { 978 SurfaceProperties navBuilder = 979 transaction.forSurface(navBarTarget.leash); 980 if (mNavFadeIn.value > mNavFadeIn.getStartValue()) { 981 matrix.setScale(scale, scale); 982 matrix.postTranslate(windowTransX0, windowTransY0); 983 navBuilder.setMatrix(matrix) 984 .setWindowCrop(crop) 985 .setAlpha(mNavFadeIn.value); 986 } else { 987 navBuilder.setAlpha(mNavFadeOut.value); 988 } 989 } 990 surfaceApplier.scheduleApply(transaction); 991 } 992 }; 993 appAnimator.addUpdateListener(listener); 994 // Since we added a start delay, call update here to init the FloatingIconView properly. 995 listener.onUpdate(0, true /* initOnly */); 996 997 // If app targets are translucent, do not animate the background as it causes a visible 998 // flicker when it resets itself at the end of its animation. 999 if (appTargetsAreTranslucent || !launcherClosing) { 1000 animatorSet.play(appAnimator); 1001 } else { 1002 animatorSet.playTogether(appAnimator, getBackgroundAnimator()); 1003 } 1004 return animatorSet; 1005 } 1006 getOpeningWindowAnimatorsForWidget(LauncherAppWidgetHostView v, RemoteAnimationTarget[] appTargets, RemoteAnimationTarget[] wallpaperTargets, RemoteAnimationTarget[] nonAppTargets, boolean launcherClosing)1007 private Animator getOpeningWindowAnimatorsForWidget(LauncherAppWidgetHostView v, 1008 RemoteAnimationTarget[] appTargets, 1009 RemoteAnimationTarget[] wallpaperTargets, 1010 RemoteAnimationTarget[] nonAppTargets, boolean launcherClosing) { 1011 Rect windowTargetBounds = getWindowTargetBounds(appTargets, getRotationChange(appTargets)); 1012 boolean appTargetsAreTranslucent = areAllTargetsTranslucent(appTargets); 1013 1014 final RectF widgetBackgroundBounds = new RectF(); 1015 final Rect appWindowCrop = new Rect(); 1016 final Matrix matrix = new Matrix(); 1017 RemoteAnimationTargets openingTargets = new RemoteAnimationTargets(appTargets, 1018 wallpaperTargets, nonAppTargets, MODE_OPENING); 1019 1020 RemoteAnimationTarget openingTarget = openingTargets.getFirstAppTarget(); 1021 int fallbackBackgroundColor = 0; 1022 if (openingTarget != null && ENABLE_SHELL_STARTING_SURFACE) { 1023 fallbackBackgroundColor = mTaskStartParams.containsKey(openingTarget.taskId) 1024 ? mTaskStartParams.get(openingTarget.taskId).second : 0; 1025 mTaskStartParams.remove(openingTarget.taskId); 1026 } 1027 if (fallbackBackgroundColor == 0) { 1028 fallbackBackgroundColor = 1029 FloatingWidgetView.getDefaultBackgroundColor(mLauncher, openingTarget); 1030 } 1031 1032 final float finalWindowRadius = mDeviceProfile.isMultiWindowMode 1033 ? 0 : getWindowCornerRadius(mLauncher); 1034 final FloatingWidgetView floatingView = FloatingWidgetView.getFloatingWidgetView(mLauncher, 1035 v, widgetBackgroundBounds, 1036 new Size(windowTargetBounds.width(), windowTargetBounds.height()), 1037 finalWindowRadius, appTargetsAreTranslucent, fallbackBackgroundColor); 1038 final float initialWindowRadius = supportsRoundedCornersOnWindows(mLauncher.getResources()) 1039 ? floatingView.getInitialCornerRadius() : 0; 1040 1041 SurfaceTransactionApplier surfaceApplier = new SurfaceTransactionApplier(floatingView); 1042 openingTargets.addReleaseCheck(surfaceApplier); 1043 1044 RemoteAnimationTarget navBarTarget = openingTargets.getNavBarRemoteAnimationTarget(); 1045 1046 AnimatorSet animatorSet = new AnimatorSet(); 1047 ValueAnimator appAnimator = ValueAnimator.ofFloat(0, 1); 1048 appAnimator.setDuration(APP_LAUNCH_DURATION); 1049 appAnimator.setInterpolator(LINEAR); 1050 appAnimator.addListener(floatingView); 1051 appAnimator.addListener(new AnimatorListenerAdapter() { 1052 @Override 1053 public void onAnimationEnd(Animator animation) { 1054 openingTargets.release(); 1055 } 1056 }); 1057 floatingView.setFastFinishRunnable(animatorSet::end); 1058 1059 appAnimator.addUpdateListener(new MultiValueUpdateListener() { 1060 float mAppWindowScale = 1; 1061 final FloatProp mWidgetForegroundAlpha = new FloatProp(1, 0, clampToDuration( 1062 LINEAR, 0, WIDGET_CROSSFADE_DURATION_MILLIS / 2, APP_LAUNCH_DURATION)); 1063 1064 final FloatProp mWidgetFallbackBackgroundAlpha = new FloatProp(0, 1, 1065 clampToDuration(LINEAR, 0, 75, APP_LAUNCH_DURATION)); 1066 final FloatProp mPreviewAlpha = new FloatProp(0, 1, clampToDuration( 1067 LINEAR, 1068 WIDGET_CROSSFADE_DURATION_MILLIS / 2 /* delay */, 1069 WIDGET_CROSSFADE_DURATION_MILLIS / 2 /* duration */, 1070 APP_LAUNCH_DURATION)); 1071 final FloatProp mWindowRadius = new FloatProp(initialWindowRadius, finalWindowRadius, 1072 mOpeningInterpolator); 1073 final FloatProp mCornerRadiusProgress = new FloatProp(0, 1, mOpeningInterpolator); 1074 1075 // Window & widget background positioning bounds 1076 final FloatProp mDx = new FloatProp(widgetBackgroundBounds.centerX(), 1077 windowTargetBounds.centerX(), mOpeningXInterpolator); 1078 final FloatProp mDy = new FloatProp(widgetBackgroundBounds.centerY(), 1079 windowTargetBounds.centerY(), mOpeningInterpolator); 1080 final FloatProp mWidth = new FloatProp(widgetBackgroundBounds.width(), 1081 windowTargetBounds.width(), mOpeningInterpolator); 1082 final FloatProp mHeight = new FloatProp(widgetBackgroundBounds.height(), 1083 windowTargetBounds.height(), mOpeningInterpolator); 1084 1085 final FloatProp mNavFadeOut = new FloatProp(1f, 0f, clampToDuration( 1086 NAV_FADE_OUT_INTERPOLATOR, 0, ANIMATION_NAV_FADE_OUT_DURATION, 1087 APP_LAUNCH_DURATION)); 1088 final FloatProp mNavFadeIn = new FloatProp(0f, 1f, clampToDuration( 1089 NAV_FADE_IN_INTERPOLATOR, ANIMATION_DELAY_NAV_FADE_IN, 1090 ANIMATION_NAV_FADE_IN_DURATION, APP_LAUNCH_DURATION)); 1091 1092 @Override 1093 public void onUpdate(float percent, boolean initOnly) { 1094 widgetBackgroundBounds.set(mDx.value - mWidth.value / 2f, 1095 mDy.value - mHeight.value / 2f, mDx.value + mWidth.value / 2f, 1096 mDy.value + mHeight.value / 2f); 1097 // Set app window scaling factor to match widget background width 1098 mAppWindowScale = widgetBackgroundBounds.width() / windowTargetBounds.width(); 1099 // Crop scaled app window to match widget 1100 appWindowCrop.set(0 /* left */, 0 /* top */, 1101 windowTargetBounds.width() /* right */, 1102 Math.round(widgetBackgroundBounds.height() / mAppWindowScale) /* bottom */); 1103 matrix.setTranslate(widgetBackgroundBounds.left, widgetBackgroundBounds.top); 1104 matrix.postScale(mAppWindowScale, mAppWindowScale, widgetBackgroundBounds.left, 1105 widgetBackgroundBounds.top); 1106 1107 SurfaceTransaction transaction = new SurfaceTransaction(); 1108 float floatingViewAlpha = appTargetsAreTranslucent ? 1 - mPreviewAlpha.value : 1; 1109 for (int i = appTargets.length - 1; i >= 0; i--) { 1110 RemoteAnimationTarget target = appTargets[i]; 1111 SurfaceProperties builder = transaction.forSurface(target.leash); 1112 if (target.mode == MODE_OPENING) { 1113 floatingView.update(widgetBackgroundBounds, floatingViewAlpha, 1114 mWidgetForegroundAlpha.value, mWidgetFallbackBackgroundAlpha.value, 1115 mCornerRadiusProgress.value); 1116 builder.setMatrix(matrix) 1117 .setWindowCrop(appWindowCrop) 1118 .setAlpha(mPreviewAlpha.value) 1119 .setCornerRadius(mWindowRadius.value / mAppWindowScale); 1120 } 1121 } 1122 1123 if (navBarTarget != null) { 1124 SurfaceProperties navBuilder = transaction.forSurface(navBarTarget.leash); 1125 if (mNavFadeIn.value > mNavFadeIn.getStartValue()) { 1126 navBuilder.setMatrix(matrix) 1127 .setWindowCrop(appWindowCrop) 1128 .setAlpha(mNavFadeIn.value); 1129 } else { 1130 navBuilder.setAlpha(mNavFadeOut.value); 1131 } 1132 } 1133 surfaceApplier.scheduleApply(transaction); 1134 } 1135 }); 1136 1137 // If app targets are translucent, do not animate the background as it causes a visible 1138 // flicker when it resets itself at the end of its animation. 1139 if (appTargetsAreTranslucent || !launcherClosing) { 1140 animatorSet.play(appAnimator); 1141 } else { 1142 animatorSet.playTogether(appAnimator, getBackgroundAnimator()); 1143 } 1144 return animatorSet; 1145 } 1146 1147 /** 1148 * Returns animator that controls depth/blur of the background. 1149 */ getBackgroundAnimator()1150 private ObjectAnimator getBackgroundAnimator() { 1151 // When launching an app from overview that doesn't map to a task, we still want to just 1152 // blur the wallpaper instead of the launcher surface as well 1153 boolean allowBlurringLauncher = mLauncher.getStateManager().getState() != OVERVIEW 1154 && BlurUtils.supportsBlursOnWindows(); 1155 1156 LaunchDepthController depthController = new LaunchDepthController(mLauncher); 1157 ObjectAnimator backgroundRadiusAnim = ObjectAnimator.ofFloat(depthController.stateDepth, 1158 MULTI_PROPERTY_VALUE, BACKGROUND_APP.getDepth(mLauncher)) 1159 .setDuration(APP_LAUNCH_DURATION); 1160 1161 if (allowBlurringLauncher) { 1162 // Create a temporary effect layer, that lives on top of launcher, so we can apply 1163 // the blur to it. The EffectLayer will be fullscreen, which will help with caching 1164 // optimizations on the SurfaceFlinger side: 1165 // - Results would be able to be cached as a texture 1166 // - There won't be texture allocation overhead, because EffectLayers don't have 1167 // buffers 1168 ViewRootImpl viewRootImpl = mLauncher.getDragLayer().getViewRootImpl(); 1169 SurfaceControl parent = viewRootImpl != null 1170 ? viewRootImpl.getSurfaceControl() 1171 : null; 1172 SurfaceControl dimLayer = new SurfaceControl.Builder() 1173 .setName("Blur layer") 1174 .setParent(parent) 1175 .setOpaque(false) 1176 .setHidden(false) 1177 .setEffectLayer() 1178 .build(); 1179 1180 backgroundRadiusAnim.addListener(AnimatorListeners.forEndCallback(() -> 1181 new SurfaceControl.Transaction().remove(dimLayer).apply())); 1182 } 1183 1184 backgroundRadiusAnim.addListener( 1185 AnimatorListeners.forEndCallback(() -> { 1186 // reset the depth to match the main depth controller's depth 1187 depthController.stateDepth 1188 .setValue(mLauncher.getDepthController().stateDepth.getValue()); 1189 depthController.dispose(); 1190 })); 1191 1192 return backgroundRadiusAnim; 1193 } 1194 1195 /** 1196 * Registers remote animations used when closing apps to home screen. 1197 */ registerRemoteAnimations()1198 public void registerRemoteAnimations() { 1199 if (SEPARATE_RECENTS_ACTIVITY.get()) { 1200 return; 1201 } 1202 RemoteAnimationDefinition definition = new RemoteAnimationDefinition(); 1203 addRemoteAnimations(definition); 1204 mLauncher.registerRemoteAnimations(definition); 1205 } 1206 1207 /** 1208 * Adds remote animations to a {@link RemoteAnimationDefinition}. May be overridden to add 1209 * additional animations. 1210 */ addRemoteAnimations(RemoteAnimationDefinition definition)1211 private void addRemoteAnimations(RemoteAnimationDefinition definition) { 1212 mWallpaperOpenRunner = new WallpaperOpenLauncherAnimationRunner(); 1213 definition.addRemoteAnimation(WindowManager.TRANSIT_OLD_WALLPAPER_OPEN, 1214 WindowConfiguration.ACTIVITY_TYPE_STANDARD, 1215 new RemoteAnimationAdapter( 1216 new LauncherAnimationRunner(mHandler, mWallpaperOpenRunner, 1217 false /* startAtFrontOfQueue */), 1218 CLOSING_TRANSITION_DURATION_MS, 0 /* statusBarTransitionDelay */)); 1219 } 1220 1221 /** 1222 * Registers remote animations used when closing apps to home screen. 1223 */ registerRemoteTransitions()1224 public void registerRemoteTransitions() { 1225 SystemUiProxy.INSTANCE.get(mLauncher).shareTransactionQueue(); 1226 if (SEPARATE_RECENTS_ACTIVITY.get()) { 1227 return; 1228 } 1229 1230 mWallpaperOpenTransitionRunner = new WallpaperOpenLauncherAnimationRunner(); 1231 mLauncherOpenTransition = new RemoteTransition( 1232 new LauncherAnimationRunner(mHandler, mWallpaperOpenTransitionRunner, 1233 false /* startAtFrontOfQueue */).toRemoteTransition(), 1234 mLauncher.getIApplicationThread(), "QuickstepLaunchHome"); 1235 1236 TransitionFilter homeCheck = new TransitionFilter(); 1237 // No need to handle the transition that also dismisses keyguard. 1238 homeCheck.mNotFlags = TRANSIT_FLAG_KEYGUARD_GOING_AWAY; 1239 1240 homeCheck.mRequirements = 1241 new TransitionFilter.Requirement[]{new TransitionFilter.Requirement(), 1242 new TransitionFilter.Requirement(), 1243 new TransitionFilter.Requirement()}; 1244 1245 homeCheck.mRequirements[0].mActivityType = ACTIVITY_TYPE_HOME; 1246 homeCheck.mRequirements[0].mTopActivity = mLauncher.getComponentName(); 1247 homeCheck.mRequirements[0].mModes = new int[]{TRANSIT_OPEN, TRANSIT_TO_FRONT}; 1248 homeCheck.mRequirements[0].mOrder = CONTAINER_ORDER_TOP; 1249 1250 homeCheck.mRequirements[1].mActivityType = ACTIVITY_TYPE_STANDARD; 1251 homeCheck.mRequirements[1].mModes = new int[]{TRANSIT_CLOSE, TRANSIT_TO_BACK}; 1252 1253 homeCheck.mRequirements[2].mNot = true; 1254 homeCheck.mRequirements[2].mCustomAnimation = true; 1255 homeCheck.mRequirements[2].mMustBeTask = true; 1256 homeCheck.mRequirements[2].mMustBeIndependent = true; 1257 1258 SystemUiProxy.INSTANCE.get(mLauncher) 1259 .registerRemoteTransition(mLauncherOpenTransition, homeCheck); 1260 if (mBackAnimationController != null) { 1261 mBackAnimationController.registerComponentCallbacks(); 1262 if (isHomeRoleHeld()) { 1263 mBackAnimationController.registerBackCallbacks(mHandler); 1264 } 1265 } 1266 } 1267 onActivityDestroyed()1268 public void onActivityDestroyed() { 1269 unregisterRemoteAnimations(); 1270 unregisterRemoteTransitions(); 1271 mLauncher.removeOnDeviceProfileChangeListener(this); 1272 SystemUiProxy.INSTANCE.get(mLauncher).setStartingWindowListener(null); 1273 if (BuildConfig.IS_STUDIO_BUILD && !mRegisteredTaskStackChangeListener.isEmpty()) { 1274 Log.e(TAG, "IllegalState: Failed to run onEndCallback created from" 1275 + " getActivityLaunchOptions()"); 1276 } 1277 mRegisteredTaskStackChangeListener.forEach(TaskRestartedDuringLaunchListener::unregister); 1278 mRegisteredTaskStackChangeListener.clear(); 1279 } 1280 1281 /** 1282 * Called when the overview-target changes. Updates the back callback registration state. 1283 */ onOverviewTargetChange()1284 public void onOverviewTargetChange() { 1285 if (isHomeRoleHeld()) { 1286 mBackAnimationController.registerBackCallbacks(mHandler); 1287 } else { 1288 mBackAnimationController.unregisterBackCallbacks(); 1289 } 1290 } 1291 isHomeRoleHeld()1292 private boolean isHomeRoleHeld() { 1293 RoleManager roleManager = mLauncher.getSystemService(RoleManager.class); 1294 return roleManager == null || roleManager.isRoleHeld(ROLE_HOME); 1295 } 1296 unregisterRemoteAnimations()1297 private void unregisterRemoteAnimations() { 1298 if (SEPARATE_RECENTS_ACTIVITY.get()) { 1299 return; 1300 } 1301 mLauncher.unregisterRemoteAnimations(); 1302 1303 // Also clear strong references to the runners registered with the remote animation 1304 // definition so we don't have to wait for the system gc 1305 mWallpaperOpenRunner = null; 1306 mAppLaunchRunner = null; 1307 } 1308 unregisterRemoteTransitions()1309 protected void unregisterRemoteTransitions() { 1310 SystemUiProxy.INSTANCE.get(mLauncher).unshareTransactionQueue(); 1311 if (SEPARATE_RECENTS_ACTIVITY.get()) { 1312 return; 1313 } 1314 if (mLauncherOpenTransition == null) return; 1315 SystemUiProxy.INSTANCE.get(mLauncher).unregisterRemoteTransition( 1316 mLauncherOpenTransition); 1317 mLauncherOpenTransition = null; 1318 mWallpaperOpenTransitionRunner = null; 1319 if (mBackAnimationController != null) { 1320 mBackAnimationController.unregisterBackCallbacks(); 1321 mBackAnimationController.unregisterComponentCallbacks(); 1322 mBackAnimationController = null; 1323 } 1324 } 1325 launcherIsATargetWithMode(RemoteAnimationTarget[] targets, int mode)1326 private boolean launcherIsATargetWithMode(RemoteAnimationTarget[] targets, int mode) { 1327 for (RemoteAnimationTarget target : targets) { 1328 if (target.mode == mode && target.taskInfo != null 1329 // Compare component name instead of task-id because transitions will promote 1330 // the target up to the root task while getTaskId returns the leaf. 1331 && target.taskInfo.topActivity != null 1332 && target.taskInfo.topActivity.equals(mLauncher.getComponentName())) { 1333 return true; 1334 } 1335 } 1336 return false; 1337 } 1338 shouldPlayFallbackClosingAnimation(RemoteAnimationTarget[] targets)1339 private boolean shouldPlayFallbackClosingAnimation(RemoteAnimationTarget[] targets) { 1340 int numTargets = 0; 1341 for (RemoteAnimationTarget target : targets) { 1342 if (target.mode == MODE_CLOSING) { 1343 numTargets++; 1344 if (numTargets > 1 || target.windowConfiguration.getWindowingMode() 1345 == WINDOWING_MODE_MULTI_WINDOW) { 1346 return true; 1347 } 1348 } 1349 } 1350 return false; 1351 } 1352 getRotationChange(RemoteAnimationTarget[] appTargets)1353 private static int getRotationChange(RemoteAnimationTarget[] appTargets) { 1354 int rotationChange = 0; 1355 for (RemoteAnimationTarget target : appTargets) { 1356 if (Math.abs(target.rotationChange) > Math.abs(rotationChange)) { 1357 rotationChange = target.rotationChange; 1358 } 1359 } 1360 return rotationChange; 1361 } 1362 1363 /** 1364 * Returns view on launcher that corresponds to the closing app in the list of app targets 1365 */ findLauncherView(RemoteAnimationTarget[] appTargets)1366 public @Nullable View findLauncherView(RemoteAnimationTarget[] appTargets) { 1367 for (RemoteAnimationTarget appTarget : appTargets) { 1368 if (appTarget.mode == MODE_CLOSING) { 1369 View launcherView = findLauncherView(appTarget); 1370 if (launcherView != null) { 1371 return launcherView; 1372 } 1373 } 1374 } 1375 return null; 1376 } 1377 1378 /** 1379 * Returns view on launcher that corresponds to the {@param runningTaskTarget}. 1380 */ findLauncherView(RemoteAnimationTarget runningTaskTarget)1381 private @Nullable View findLauncherView(RemoteAnimationTarget runningTaskTarget) { 1382 if (runningTaskTarget == null || runningTaskTarget.taskInfo == null) { 1383 return null; 1384 } 1385 1386 final ComponentName[] taskInfoActivities = new ComponentName[]{ 1387 runningTaskTarget.taskInfo.baseActivity, 1388 runningTaskTarget.taskInfo.origActivity, 1389 runningTaskTarget.taskInfo.realActivity, 1390 runningTaskTarget.taskInfo.topActivity}; 1391 1392 String packageName = null; 1393 for (ComponentName component : taskInfoActivities) { 1394 if (component != null && component.getPackageName() != null) { 1395 packageName = component.getPackageName(); 1396 break; 1397 } 1398 } 1399 1400 if (packageName == null) { 1401 return null; 1402 } 1403 1404 // Find the associated item info for the launch cookie (if available), note that predicted 1405 // apps actually have an id of -1, so use another default id here 1406 final List<IBinder> launchCookies = runningTaskTarget.taskInfo.launchCookies == null 1407 ? Collections.EMPTY_LIST 1408 : runningTaskTarget.taskInfo.launchCookies; 1409 1410 return mLauncher.getFirstVisibleElementForAppClose( 1411 StableViewInfo.fromLaunchCookies(launchCookies), packageName, 1412 UserHandle.of(runningTaskTarget.taskInfo.userId)); 1413 } 1414 getDefaultWindowTargetRect()1415 private @NonNull RectF getDefaultWindowTargetRect() { 1416 RecentsView recentsView = mLauncher.getOverviewPanel(); 1417 PagedOrientationHandler orientationHandler = recentsView.getPagedOrientationHandler(); 1418 DeviceProfile dp = mLauncher.getDeviceProfile(); 1419 final int halfIconSize = dp.iconSizePx / 2; 1420 float primaryDimension = orientationHandler 1421 .getPrimaryValue(dp.availableWidthPx, dp.availableHeightPx); 1422 float secondaryDimension = orientationHandler 1423 .getSecondaryValue(dp.availableWidthPx, dp.availableHeightPx); 1424 final float targetX = primaryDimension / 2f; 1425 final float targetY = secondaryDimension - dp.hotseatBarSizePx; 1426 return new RectF(targetX - halfIconSize, targetY - halfIconSize, 1427 targetX + halfIconSize, targetY + halfIconSize); 1428 } 1429 1430 /** 1431 * Closing animator that animates the window into its final location on the workspace. 1432 */ getClosingWindowAnimators(AnimatorSet animation, RemoteAnimationTarget[] targets, View launcherView, PointF velocityPxPerS, RectF closingWindowStartRectF, float startWindowCornerRadius)1433 protected RectFSpringAnim getClosingWindowAnimators(AnimatorSet animation, 1434 RemoteAnimationTarget[] targets, View launcherView, PointF velocityPxPerS, 1435 RectF closingWindowStartRectF, float startWindowCornerRadius) { 1436 FloatingIconView floatingIconView = null; 1437 FloatingWidgetView floatingWidget = null; 1438 RectF targetRect = new RectF(); 1439 1440 RemoteAnimationTarget runningTaskTarget = null; 1441 boolean isTransluscent = false; 1442 for (RemoteAnimationTarget target : targets) { 1443 if (target.mode == MODE_CLOSING) { 1444 runningTaskTarget = target; 1445 isTransluscent = runningTaskTarget.isTranslucent; 1446 break; 1447 } 1448 } 1449 1450 // Get floating view and target rect. 1451 boolean isInHotseat = false; 1452 if (launcherView instanceof LauncherAppWidgetHostView) { 1453 Size windowSize = new Size(mDeviceProfile.availableWidthPx, 1454 mDeviceProfile.availableHeightPx); 1455 int fallbackBackgroundColor = 1456 FloatingWidgetView.getDefaultBackgroundColor(mLauncher, runningTaskTarget); 1457 floatingWidget = FloatingWidgetView.getFloatingWidgetView(mLauncher, 1458 (LauncherAppWidgetHostView) launcherView, targetRect, windowSize, 1459 mDeviceProfile.isMultiWindowMode ? 0 : getWindowCornerRadius(mLauncher), 1460 isTransluscent, fallbackBackgroundColor); 1461 } else if (launcherView != null && !RemoveAnimationSettingsTracker.INSTANCE.get( 1462 mLauncher).isRemoveAnimationEnabled()) { 1463 floatingIconView = getFloatingIconView(mLauncher, launcherView, null, 1464 mLauncher.getTaskbarUIController() == null 1465 ? null 1466 : mLauncher.getTaskbarUIController().findMatchingView(launcherView), 1467 true /* hideOriginal */, targetRect, false /* isOpening */); 1468 if (launcherView.getTag() instanceof ItemInfo itemInfo) { 1469 isInHotseat = itemInfo.isInHotseat(); 1470 if (isInHotseat) { 1471 int dx = mLauncher.getHotseatItemTranslationX(itemInfo); 1472 targetRect.offset(dx, 0); 1473 } 1474 } 1475 } else { 1476 targetRect.set(getDefaultWindowTargetRect()); 1477 } 1478 1479 boolean useTaskbarHotseatParams = mDeviceProfile.isTaskbarPresent && isInHotseat; 1480 RectFSpringAnim anim = new RectFSpringAnim(useTaskbarHotseatParams 1481 ? new TaskbarHotseatSpringConfig(mLauncher, closingWindowStartRectF, targetRect) 1482 : new DefaultSpringConfig(mLauncher, mDeviceProfile, closingWindowStartRectF, 1483 targetRect)); 1484 1485 // Hook up floating views to the closing window animators. 1486 // note the coordinate of closingWindowStartRect is based on launcher 1487 Rect closingWindowStartRect = new Rect(); 1488 closingWindowStartRectF.round(closingWindowStartRect); 1489 Rect closingWindowOriginalRect = 1490 new Rect(0, 0, mDeviceProfile.widthPx, mDeviceProfile.heightPx); 1491 if (floatingIconView != null) { 1492 anim.addAnimatorListener(floatingIconView); 1493 floatingIconView.setOnTargetChangeListener(anim::onTargetPositionChanged); 1494 floatingIconView.setFastFinishRunnable(anim::end); 1495 FloatingIconView finalFloatingIconView = floatingIconView; 1496 1497 // We want the window alpha to be 0 once this threshold is met, so that the 1498 // FolderIconView can be seen morphing into the icon shape. 1499 final float windowAlphaThreshold = 1f - SHAPE_PROGRESS_DURATION; 1500 1501 RectFSpringAnim.OnUpdateListener runner = new SpringAnimRunner(targets, targetRect, 1502 closingWindowStartRect, closingWindowOriginalRect, startWindowCornerRadius) { 1503 @Override 1504 public void onUpdate(RectF currentRectF, float progress) { 1505 finalFloatingIconView.update(1f, currentRectF, progress, windowAlphaThreshold, 1506 getCornerRadius(progress), false); 1507 1508 super.onUpdate(currentRectF, progress); 1509 } 1510 }; 1511 anim.addOnUpdateListener(runner); 1512 } else if (floatingWidget != null) { 1513 anim.addAnimatorListener(floatingWidget); 1514 floatingWidget.setOnTargetChangeListener(anim::onTargetPositionChanged); 1515 floatingWidget.setFastFinishRunnable(anim::end); 1516 1517 final float floatingWidgetAlpha = isTransluscent ? 0 : 1; 1518 FloatingWidgetView finalFloatingWidget = floatingWidget; 1519 RectFSpringAnim.OnUpdateListener runner = new SpringAnimRunner(targets, targetRect, 1520 closingWindowStartRect, closingWindowOriginalRect, startWindowCornerRadius) { 1521 @Override 1522 public void onUpdate(RectF currentRectF, float progress) { 1523 final float fallbackBackgroundAlpha = 1524 1 - mapBoundToRange(progress, 0.8f, 1, 0, 1, EXAGGERATED_EASE); 1525 final float foregroundAlpha = 1526 mapBoundToRange(progress, 0.5f, 1, 0, 1, EXAGGERATED_EASE); 1527 finalFloatingWidget.update(currentRectF, floatingWidgetAlpha, foregroundAlpha, 1528 fallbackBackgroundAlpha, 1 - progress); 1529 1530 super.onUpdate(currentRectF, progress); 1531 } 1532 }; 1533 anim.addOnUpdateListener(runner); 1534 } else { 1535 // If no floating icon or widget is present, animate the to the default window 1536 // target rect. 1537 anim.addOnUpdateListener(new SpringAnimRunner( 1538 targets, targetRect, closingWindowStartRect, closingWindowOriginalRect, 1539 startWindowCornerRadius)); 1540 } 1541 1542 // Use a fixed velocity to start the animation. 1543 animation.addListener(new AnimatorListenerAdapter() { 1544 @Override 1545 public void onAnimationStart(Animator animation) { 1546 anim.start(mLauncher, mDeviceProfile, velocityPxPerS); 1547 } 1548 }); 1549 return anim; 1550 } 1551 1552 /** 1553 * Closing window animator that moves the window down and offscreen. 1554 */ getFallbackClosingWindowAnimators(RemoteAnimationTarget[] appTargets)1555 private Animator getFallbackClosingWindowAnimators(RemoteAnimationTarget[] appTargets) { 1556 final int rotationChange = getRotationChange(appTargets); 1557 SurfaceTransactionApplier surfaceApplier = new SurfaceTransactionApplier(mDragLayer); 1558 Matrix matrix = new Matrix(); 1559 Point tmpPos = new Point(); 1560 Rect tmpRect = new Rect(); 1561 ValueAnimator closingAnimator = ValueAnimator.ofFloat(0, 1); 1562 int duration = CLOSING_TRANSITION_DURATION_MS; 1563 float windowCornerRadius = mDeviceProfile.isMultiWindowMode 1564 ? 0 : getWindowCornerRadius(mLauncher); 1565 float startShadowRadius = areAllTargetsTranslucent(appTargets) ? 0 : mMaxShadowRadius; 1566 closingAnimator.setDuration(duration); 1567 boolean isFreeform = isFreeformAnimation(appTargets); 1568 float translateY = isFreeform ? mClosingFreeformWindowTransY : mClosingWindowTransY; 1569 float endScale = isFreeform ? 0.95f : 1f; 1570 Interpolator alphaInterpolator = isFreeform 1571 ? clampToDuration(LINEAR, 0, 100, duration) 1572 : clampToDuration(LINEAR, 25, 125, duration); 1573 closingAnimator.addUpdateListener(new MultiValueUpdateListener() { 1574 FloatProp mDy = new FloatProp(0, translateY, DECELERATE_1_7); 1575 FloatProp mScale = new FloatProp(1f, endScale, DECELERATE_1_7); 1576 FloatProp mAlpha = new FloatProp(1f, 0f, alphaInterpolator); 1577 FloatProp mShadowRadius = new FloatProp(startShadowRadius, 0, DECELERATE_1_7); 1578 1579 @Override 1580 public void onUpdate(float percent, boolean initOnly) { 1581 SurfaceTransaction transaction = new SurfaceTransaction(); 1582 for (int i = appTargets.length - 1; i >= 0; i--) { 1583 RemoteAnimationTarget target = appTargets[i]; 1584 SurfaceProperties builder = transaction.forSurface(target.leash); 1585 1586 if (target.screenSpaceBounds != null) { 1587 tmpPos.set(target.screenSpaceBounds.left, target.screenSpaceBounds.top); 1588 } else { 1589 tmpPos.set(target.position.x, target.position.y); 1590 } 1591 1592 final Rect crop = new Rect(target.localBounds); 1593 crop.offsetTo(0, 0); 1594 if (target.mode == MODE_CLOSING) { 1595 tmpRect.set(target.screenSpaceBounds); 1596 if ((rotationChange % 2) != 0) { 1597 final int right = crop.right; 1598 crop.right = crop.bottom; 1599 crop.bottom = right; 1600 } 1601 matrix.setScale(mScale.value, mScale.value, 1602 tmpRect.centerX(), 1603 tmpRect.centerY()); 1604 matrix.postTranslate(0, mDy.value); 1605 matrix.postTranslate(tmpPos.x, tmpPos.y); 1606 builder.setMatrix(matrix) 1607 .setWindowCrop(crop) 1608 .setAlpha(mAlpha.value) 1609 .setCornerRadius(windowCornerRadius) 1610 .setShadowRadius(mShadowRadius.value); 1611 } else if (target.mode == MODE_OPENING) { 1612 matrix.setTranslate(tmpPos.x, tmpPos.y); 1613 builder.setMatrix(matrix) 1614 .setWindowCrop(crop) 1615 .setAlpha(1f); 1616 } 1617 } 1618 surfaceApplier.scheduleApply(transaction); 1619 } 1620 }); 1621 1622 return closingAnimator; 1623 } 1624 isFreeformAnimation(RemoteAnimationTarget[] appTargets)1625 private boolean isFreeformAnimation(RemoteAnimationTarget[] appTargets) { 1626 return DesktopModeStatus.canEnterDesktopMode(mLauncher.getApplicationContext()) 1627 && DesktopModeFlags.ENABLE_DESKTOP_WINDOWING_EXIT_TRANSITIONS_BUGFIX.isTrue() 1628 && Arrays.stream(appTargets) 1629 .anyMatch(app -> app.taskInfo != null && app.taskInfo.isFreeform()); 1630 } 1631 addCujInstrumentation(Animator anim, int cuj)1632 private void addCujInstrumentation(Animator anim, int cuj) { 1633 anim.addListener(getCujAnimationSuccessListener(cuj)); 1634 } 1635 addCujInstrumentation(RectFSpringAnim anim, int cuj)1636 private void addCujInstrumentation(RectFSpringAnim anim, int cuj) { 1637 anim.addAnimatorListener(getCujAnimationSuccessListener(cuj)); 1638 } 1639 getCujAnimationSuccessListener(int cuj)1640 private AnimationSuccessListener getCujAnimationSuccessListener(int cuj) { 1641 return new AnimationSuccessListener() { 1642 @Override 1643 public void onAnimationStart(Animator animation) { 1644 mDragLayer.getViewTreeObserver().addOnDrawListener( 1645 new ViewTreeObserver.OnDrawListener() { 1646 boolean mHandled = false; 1647 1648 @Override 1649 public void onDraw() { 1650 if (mHandled) { 1651 return; 1652 } 1653 mHandled = true; 1654 1655 InteractionJankMonitorWrapper.begin(mDragLayer, cuj); 1656 1657 mDragLayer.post(() -> 1658 mDragLayer.getViewTreeObserver().removeOnDrawListener( 1659 this)); 1660 } 1661 }); 1662 super.onAnimationStart(animation); 1663 } 1664 1665 @Override 1666 public void onAnimationCancel(Animator animation) { 1667 super.onAnimationCancel(animation); 1668 InteractionJankMonitorWrapper.cancel(cuj); 1669 } 1670 1671 @Override 1672 public void onAnimationSuccess(Animator animator) { 1673 InteractionJankMonitorWrapper.end(cuj); 1674 } 1675 }; 1676 } 1677 1678 /** 1679 * Creates the {@link RectFSpringAnim} and {@link AnimatorSet} required to animate 1680 * the transition. 1681 */ 1682 @NonNull 1683 public BackAnimState createWallpaperOpenAnimations( 1684 RemoteAnimationTarget[] appTargets, 1685 RemoteAnimationTarget[] wallpapers, 1686 RemoteAnimationTarget[] nonAppTargets, 1687 RectF startRect, 1688 float startWindowCornerRadius, 1689 boolean fromPredictiveBack) { 1690 View launcherView = findLauncherView(appTargets); 1691 if (checkReturnAnimationsFlags() 1692 && launcherView != null 1693 && launcherView.getTag() instanceof ItemInfo info 1694 && info.shouldUseBackgroundAnimation()) { 1695 // Try to create a return animation 1696 RunnableList onEndCallback = new RunnableList(); 1697 WindowAnimationState windowState = new WindowAnimationState(); 1698 windowState.bounds = startRect; 1699 windowState.bottomLeftRadius = windowState.bottomRightRadius = 1700 windowState.topLeftRadius = windowState.topRightRadius = 1701 startWindowCornerRadius; 1702 ContainerAnimationRunner runner = ContainerAnimationRunner.fromView( 1703 launcherView, false /* forLaunch */, mLauncher, mStartingWindowListener, 1704 onEndCallback, windowState); 1705 if (runner != null) { 1706 runner.startAnimation(TRANSIT_CLOSE, 1707 appTargets, wallpapers, nonAppTargets, 1708 new IRemoteAnimationFinishedCallback.Stub() { 1709 @Override 1710 public void onAnimationFinished() { 1711 onEndCallback.executeAllAndDestroy(); 1712 } 1713 }); 1714 return new AlreadyStartedBackAnimState(onEndCallback); 1715 } 1716 } 1717 1718 AnimatorSet anim = new AnimatorSet(); 1719 RectFSpringAnim rectFSpringAnim = null; 1720 1721 final boolean launcherIsForceInvisibleOrOpening = mLauncher.isForceInvisible() 1722 || launcherIsATargetWithMode(appTargets, MODE_OPENING); 1723 1724 boolean playFallBackAnimation = (launcherView == null 1725 && launcherIsForceInvisibleOrOpening) 1726 || mLauncher.getWorkspace().isOverlayShown() 1727 || shouldPlayFallbackClosingAnimation(appTargets); 1728 1729 boolean playWorkspaceReveal = true; 1730 if (!Flags.predictiveBackToHomePolish()) { 1731 playWorkspaceReveal = !fromPredictiveBack; 1732 } 1733 boolean skipAllAppsScale = false; 1734 if (!playFallBackAnimation) { 1735 PointF velocity; 1736 if (enableScalingRevealHomeAnimation()) { 1737 velocity = new PointF(); 1738 } else { 1739 // Use a fixed velocity to start the animation. 1740 float velocityPxPerS = DynamicResource.provider(mLauncher) 1741 .getDimension(R.dimen.unlock_staggered_velocity_dp_per_s); 1742 velocity = new PointF(0, -velocityPxPerS); 1743 } 1744 rectFSpringAnim = getClosingWindowAnimators( 1745 anim, appTargets, launcherView, velocity, startRect, 1746 startWindowCornerRadius); 1747 if (mLauncher.isInState(LauncherState.ALL_APPS)) { 1748 // Skip scaling all apps, otherwise FloatingIconView will get wrong 1749 // layout bounds. 1750 skipAllAppsScale = true; 1751 } else if (Flags.predictiveBackToHomePolish() || !fromPredictiveBack) { 1752 if (enableScalingRevealHomeAnimation()) { 1753 anim.play( 1754 new ScalingWorkspaceRevealAnim(mLauncher, rectFSpringAnim, 1755 rectFSpringAnim.getTargetRect(), 1756 !fromPredictiveBack /* playAlphaReveal */).getAnimators()); 1757 } else { 1758 anim.play(new StaggeredWorkspaceAnim(mLauncher, velocity.y, 1759 true /* animateOverviewScrim */, launcherView).getAnimators()); 1760 } 1761 1762 if (!areAllTargetsTranslucent(appTargets)) { 1763 anim.play(ObjectAnimator.ofFloat(mLauncher.getDepthController().stateDepth, 1764 MULTI_PROPERTY_VALUE, 1765 BACKGROUND_APP.getDepth(mLauncher), NORMAL.getDepth(mLauncher))); 1766 } 1767 1768 // We play StaggeredWorkspaceAnim as a part of the closing window animation. 1769 playWorkspaceReveal = false; 1770 } 1771 } else { 1772 anim.play(getFallbackClosingWindowAnimators(appTargets)); 1773 } 1774 1775 if (Flags.predictiveBackToHomePolish()) { 1776 AnimatorListenerAdapter endListener = new AnimatorListenerAdapter() { 1777 @Override 1778 public void onAnimationEnd(Animator animation) { 1779 super.onAnimationEnd(animation); 1780 AccessibilityManagerCompat.sendTestProtocolEventToTest( 1781 mLauncher, WALLPAPER_OPEN_ANIMATION_FINISHED_MESSAGE); 1782 } 1783 }; 1784 if (rectFSpringAnim != null) { 1785 rectFSpringAnim.addAnimatorListener(endListener); 1786 } else { 1787 anim.addListener(endListener); 1788 } 1789 } 1790 1791 // Normally, we run the launcher content animation when we are transitioning 1792 // home, but if home is already visible, then we don't want to animate the 1793 // contents of launcher unless we know that we are animating home as a result 1794 // of the home button press with quickstep, which will result in launcher being 1795 // started on touch down, prior to the animation home (and won't be in the 1796 // targets list because it is already visible). In that case, we force 1797 // invisibility on touch down, and only reset it after the animation to home 1798 // is initialized. 1799 boolean legacyFromPredictiveBack = 1800 !Flags.predictiveBackToHomePolish() && fromPredictiveBack; 1801 if (launcherIsForceInvisibleOrOpening || legacyFromPredictiveBack) { 1802 if (rectFSpringAnim != null && anim.getChildAnimations().isEmpty()) { 1803 addCujInstrumentation(rectFSpringAnim, Cuj.CUJ_LAUNCHER_APP_CLOSE_TO_HOME); 1804 } else { 1805 if (isFreeformAnimation(appTargets)) { 1806 addCujInstrumentation(anim, 1807 Cuj.CUJ_DESKTOP_MODE_EXIT_MODE_ON_LAST_WINDOW_CLOSE); 1808 } 1809 addCujInstrumentation(anim, playFallBackAnimation 1810 ? Cuj.CUJ_LAUNCHER_APP_CLOSE_TO_HOME_FALLBACK 1811 : Cuj.CUJ_LAUNCHER_APP_CLOSE_TO_HOME); 1812 } 1813 if (!Flags.predictiveBackToHomePolish()) { 1814 AnimatorListenerAdapter endListener = new AnimatorListenerAdapter() { 1815 @Override 1816 public void onAnimationEnd(Animator animation) { 1817 super.onAnimationEnd(animation); 1818 AccessibilityManagerCompat.sendTestProtocolEventToTest( 1819 mLauncher, WALLPAPER_OPEN_ANIMATION_FINISHED_MESSAGE); 1820 } 1821 }; 1822 if (fromPredictiveBack && rectFSpringAnim != null) { 1823 rectFSpringAnim.addAnimatorListener(endListener); 1824 } else { 1825 anim.addListener(endListener); 1826 } 1827 } 1828 1829 // Only register the content animation for cancellation when state changes 1830 mLauncher.getStateManager().setCurrentAnimation(anim); 1831 1832 if (mLauncher.isInState(LauncherState.ALL_APPS) && !legacyFromPredictiveBack) { 1833 Pair<AnimatorSet, Runnable> contentAnimator = 1834 getLauncherContentAnimator(false, LAUNCHER_RESUME_START_DELAY, 1835 skipAllAppsScale); 1836 anim.play(contentAnimator.first); 1837 anim.addListener(new AnimatorListenerAdapter() { 1838 @Override 1839 public void onAnimationEnd(Animator animation) { 1840 contentAnimator.second.run(); 1841 } 1842 }); 1843 } else if (playWorkspaceReveal) { 1844 anim.play(new WorkspaceRevealAnim(mLauncher, false).getAnimators()); 1845 } 1846 } 1847 1848 return new AnimatorBackState(rectFSpringAnim, anim); 1849 } 1850 1851 /** Get animation duration for taskbar for going to home. */ 1852 public static int getTaskbarToHomeDuration(boolean isPinnedTaskbarAndNotInDesktopMode) { 1853 return getTaskbarToHomeDuration(false, isPinnedTaskbarAndNotInDesktopMode); 1854 } 1855 1856 /** 1857 * Get animation duration for taskbar for going to home. 1858 * 1859 * @param shouldOverrideToFastAnimation should overwrite scaling reveal home animation duration 1860 */ 1861 public static int getTaskbarToHomeDuration(boolean shouldOverrideToFastAnimation, 1862 boolean isPinnedTaskbarAndNotInDesktopMode) { 1863 if (isPinnedTaskbarAndNotInDesktopMode) { 1864 return PINNED_TASKBAR_TRANSITION_DURATION; 1865 } else if (enableScalingRevealHomeAnimation() && !shouldOverrideToFastAnimation) { 1866 return TASKBAR_TO_HOME_DURATION_SLOW; 1867 } else { 1868 return TASKBAR_TO_HOME_DURATION_FAST; 1869 } 1870 } 1871 1872 private static boolean checkReturnAnimationsFlags() { 1873 return enableContainerReturnAnimations() && returnAnimationFrameworkLibrary(); 1874 } 1875 1876 /** 1877 * Remote animation runner for animation from the app to Launcher, including recents. 1878 */ 1879 protected class WallpaperOpenLauncherAnimationRunner implements RemoteAnimationFactory { 1880 1881 @Override 1882 public void onAnimationStart(int transit, 1883 RemoteAnimationTarget[] appTargets, 1884 RemoteAnimationTarget[] wallpaperTargets, 1885 RemoteAnimationTarget[] nonAppTargets, 1886 LauncherAnimationRunner.AnimationResult result) { 1887 if (mLauncher.isDestroyed()) { 1888 AnimatorSet anim = new AnimatorSet(); 1889 anim.play(getFallbackClosingWindowAnimators(appTargets)); 1890 result.setAnimation(anim, mLauncher.getApplicationContext()); 1891 return; 1892 } 1893 1894 if (mLauncher.hasSomeInvisibleFlag(PENDING_INVISIBLE_BY_WALLPAPER_ANIMATION)) { 1895 mLauncher.addForceInvisibleFlag(INVISIBLE_BY_PENDING_FLAGS); 1896 mLauncher.getStateManager().moveToRestState(); 1897 } 1898 1899 RectF windowTargetBounds = 1900 new RectF(getWindowTargetBounds(appTargets, getRotationChange(appTargets))); 1901 1902 final RectF resolveRectF = new RectF(windowTargetBounds); 1903 for (RemoteAnimationTarget t : appTargets) { 1904 if (t.mode == MODE_CLOSING) { 1905 transferRectToTargetCoordinate( 1906 t, windowTargetBounds, true, resolveRectF); 1907 break; 1908 } 1909 } 1910 1911 BackAnimState bankAnimState = createWallpaperOpenAnimations( 1912 appTargets, wallpaperTargets, nonAppTargets, resolveRectF, 1913 QuickStepContract.getWindowCornerRadius(mLauncher), 1914 false /* fromPredictiveBack */); 1915 1916 TaskViewUtils.createSplitAuxiliarySurfacesAnimator(nonAppTargets, false, null); 1917 mLauncher.clearForceInvisibleFlag(INVISIBLE_ALL); 1918 bankAnimState.applyToAnimationResult(result, mLauncher); 1919 } 1920 } 1921 1922 /** 1923 * Remote animation runner for animation to launch an app. 1924 */ 1925 private class AppLaunchAnimationRunner implements RemoteAnimationFactory { 1926 1927 private final View mV; 1928 private final RunnableList mOnEndCallback; 1929 1930 AppLaunchAnimationRunner(View v, RunnableList onEndCallback) { 1931 mV = v; 1932 mOnEndCallback = onEndCallback; 1933 } 1934 1935 @Override 1936 public void onAnimationStart(int transit, 1937 RemoteAnimationTarget[] appTargets, 1938 RemoteAnimationTarget[] wallpaperTargets, 1939 RemoteAnimationTarget[] nonAppTargets, 1940 LauncherAnimationRunner.AnimationResult result) { 1941 AnimatorSet anim = new AnimatorSet(); 1942 boolean launcherClosing = 1943 launcherIsATargetWithMode(appTargets, MODE_CLOSING); 1944 1945 final boolean launchingFromWidget = mV instanceof LauncherAppWidgetHostView; 1946 final boolean launchingFromRecents = isLaunchingFromRecents(mV, appTargets); 1947 final boolean skipFirstFrame; 1948 if (launchingFromWidget) { 1949 composeWidgetLaunchAnimator(anim, (LauncherAppWidgetHostView) mV, appTargets, 1950 wallpaperTargets, nonAppTargets, launcherClosing); 1951 addCujInstrumentation(anim, Cuj.CUJ_LAUNCHER_APP_LAUNCH_FROM_WIDGET); 1952 skipFirstFrame = true; 1953 } else if (launchingFromRecents) { 1954 composeRecentsLaunchAnimator(anim, mV, appTargets, wallpaperTargets, nonAppTargets, 1955 launcherClosing); 1956 addCujInstrumentation( 1957 anim, Cuj.CUJ_LAUNCHER_APP_LAUNCH_FROM_RECENTS); 1958 skipFirstFrame = true; 1959 } else { 1960 composeIconLaunchAnimator(anim, mV, appTargets, wallpaperTargets, nonAppTargets, 1961 launcherClosing); 1962 addCujInstrumentation(anim, Cuj.CUJ_LAUNCHER_APP_LAUNCH_FROM_ICON); 1963 skipFirstFrame = false; 1964 } 1965 1966 if (launcherClosing) { 1967 anim.addListener(mForceInvisibleListener); 1968 } 1969 1970 // Syncs the app launch animation and taskbar stash animation (if exists). 1971 if (syncAppLaunchWithTaskbarStash() && enableScalingRevealHomeAnimation()) { 1972 LauncherTaskbarUIController taskbarController = mLauncher.getTaskbarUIController(); 1973 if (taskbarController != null) { 1974 taskbarController.setIgnoreInAppFlagForSync(false); 1975 1976 if (launcherClosing) { 1977 Animator taskbar = taskbarController.createAnimToApp(); 1978 if (taskbar != null) { 1979 anim.play(taskbar); 1980 } 1981 } 1982 } 1983 } 1984 1985 result.setAnimation(anim, mLauncher, mOnEndCallback::executeAllAndDestroy, 1986 skipFirstFrame); 1987 } 1988 1989 @Override 1990 public void onAnimationCancelled() { 1991 mOnEndCallback.executeAllAndDestroy(); 1992 } 1993 } 1994 1995 /** Remote animation runner to launch an app using System UI's animation library. */ 1996 private static class ContainerAnimationRunner implements RemoteAnimationFactory { 1997 1998 /** The delegate runner that handles the actual animation. */ 1999 private final RemoteAnimationDelegate<IRemoteAnimationFinishedCallback> mDelegate; 2000 2001 private ContainerAnimationRunner( 2002 RemoteAnimationDelegate<IRemoteAnimationFinishedCallback> delegate) { 2003 mDelegate = delegate; 2004 } 2005 2006 @Nullable 2007 static ContainerAnimationRunner fromView( 2008 View v, 2009 boolean forLaunch, 2010 Launcher launcher, 2011 StartingWindowListener startingWindowListener, 2012 RunnableList onEndCallback, 2013 @Nullable WindowAnimationState windowState) { 2014 if (!forLaunch && !checkReturnAnimationsFlags()) { 2015 throw new IllegalStateException( 2016 "forLaunch cannot be false when the enableContainerReturnAnimations or " 2017 + "returnAnimationFrameworkLibrary flag is disabled"); 2018 } 2019 2020 // First the controller is created. This is used by the runner to animate the 2021 // origin/target view. 2022 ActivityTransitionAnimator.Controller controller = 2023 buildController(v, forLaunch, windowState); 2024 if (controller == null) { 2025 return null; 2026 } 2027 2028 // The callback is used to make sure that we use the right color to fade between view 2029 // and the window. 2030 ActivityTransitionAnimator.Callback callback = task -> { 2031 final int backgroundColor = 2032 startingWindowListener.mBackgroundColor == Color.TRANSPARENT 2033 ? launcher.getScrimView().getBackgroundColor() 2034 : startingWindowListener.mBackgroundColor; 2035 return ColorUtils.setAlphaComponent(backgroundColor, 255); 2036 }; 2037 2038 ActivityTransitionAnimator.Listener listener = 2039 new ActivityTransitionAnimator.Listener() { 2040 @Override 2041 public void onTransitionAnimationEnd() { 2042 onEndCallback.executeAllAndDestroy(); 2043 } 2044 }; 2045 2046 return new ContainerAnimationRunner( 2047 new ActivityTransitionAnimator.AnimationDelegate( 2048 MAIN_EXECUTOR, controller, callback, listener)); 2049 } 2050 2051 /** 2052 * Constructs a {@link ActivityTransitionAnimator.Controller} that can be used by a 2053 * {@link ContainerAnimationRunner} to animate a view into an opening window or from a 2054 * closing one. 2055 */ 2056 @Nullable 2057 private static ActivityTransitionAnimator.Controller buildController( 2058 View v, boolean isLaunching, @Nullable WindowAnimationState windowState) { 2059 View viewToUse = findLaunchableViewWithBackground(v); 2060 if (viewToUse == null) { 2061 return null; 2062 } 2063 2064 // The CUJ is logged by the click handler, so we don't log it inside the animation 2065 // library. TODO: figure out return CUJ. 2066 ActivityTransitionAnimator.Controller controllerDelegate = 2067 ActivityTransitionAnimator.Controller.fromView(viewToUse, null /* cujType */); 2068 2069 if (controllerDelegate == null) { 2070 return null; 2071 } 2072 2073 // This wrapper allows us to override the default value, telling the controller that the 2074 // current window is below the animating window as well as information about the return 2075 // animation. 2076 return new DelegateTransitionAnimatorController(controllerDelegate) { 2077 @Override 2078 public boolean isLaunching() { 2079 return isLaunching; 2080 } 2081 2082 @Override 2083 public boolean isBelowAnimatingWindow() { 2084 return true; 2085 } 2086 2087 @Nullable 2088 @Override 2089 public WindowAnimationState getWindowAnimatorState() { 2090 return windowState; 2091 } 2092 }; 2093 } 2094 2095 /** 2096 * Finds the closest parent of [view] (inclusive) that implements {@link LaunchableView} and 2097 * has a background drawable. 2098 */ 2099 @Nullable 2100 private static <T extends View & LaunchableView> T findLaunchableViewWithBackground( 2101 View view) { 2102 View current = view; 2103 while (current.getBackground() == null || !(current instanceof LaunchableView)) { 2104 if (current.getParent() instanceof View v) { 2105 current = v; 2106 } else { 2107 return null; 2108 } 2109 } 2110 return (T) current; 2111 } 2112 2113 @Override 2114 public void onAnimationStart(int transit, RemoteAnimationTarget[] appTargets, 2115 RemoteAnimationTarget[] wallpaperTargets, RemoteAnimationTarget[] nonAppTargets, 2116 LauncherAnimationRunner.AnimationResult result) { 2117 startAnimation( 2118 transit, appTargets, wallpaperTargets, nonAppTargets, result); 2119 } 2120 2121 public void startAnimation(int transit, RemoteAnimationTarget[] appTargets, 2122 RemoteAnimationTarget[] wallpaperTargets, RemoteAnimationTarget[] nonAppTargets, 2123 IRemoteAnimationFinishedCallback result) { 2124 mDelegate.onAnimationStart( 2125 transit, appTargets, wallpaperTargets, nonAppTargets, result); 2126 } 2127 2128 @Override 2129 public void onAnimationCancelled() { 2130 mDelegate.onAnimationCancelled(); 2131 } 2132 } 2133 2134 /** 2135 * Class that holds all the variables for the app open animation. 2136 */ 2137 static class AnimOpenProperties { 2138 2139 public final int cropCenterXStart; 2140 public final int cropCenterYStart; 2141 public final int cropWidthStart; 2142 public final int cropHeightStart; 2143 2144 public final int cropCenterXEnd; 2145 public final int cropCenterYEnd; 2146 public final int cropWidthEnd; 2147 public final int cropHeightEnd; 2148 2149 public final float dX; 2150 public final float dY; 2151 2152 public final float initialAppIconScale; 2153 public final float finalAppIconScale; 2154 2155 public final float iconAlphaStart; 2156 2157 AnimOpenProperties(Resources r, DeviceProfile dp, Rect windowTargetBounds, 2158 RectF launcherIconBounds, View view, int dragLayerLeft, int dragLayerTop, 2159 boolean hasSplashScreen, boolean hasDifferentAppIcon) { 2160 // Scale the app icon to take up the entire screen. This simplifies the math when 2161 // animating the app window position / scale. 2162 float smallestSize = Math.min(windowTargetBounds.height(), windowTargetBounds.width()); 2163 float maxScaleX = smallestSize / launcherIconBounds.width(); 2164 float maxScaleY = smallestSize / launcherIconBounds.height(); 2165 float iconStartScale = 1f; 2166 if (view instanceof BubbleTextView && !(view.getParent() instanceof DeepShortcutView)) { 2167 Drawable dr = ((BubbleTextView) view).getIcon(); 2168 if (dr instanceof FastBitmapDrawable) { 2169 iconStartScale = ((FastBitmapDrawable) dr).getAnimatedScale(); 2170 } 2171 } 2172 2173 initialAppIconScale = iconStartScale; 2174 finalAppIconScale = Math.max(maxScaleX, maxScaleY); 2175 2176 // Animate the app icon to the center of the window bounds in screen coordinates. 2177 float centerX = windowTargetBounds.centerX() - dragLayerLeft; 2178 float centerY = windowTargetBounds.centerY() - dragLayerTop; 2179 2180 dX = centerX - launcherIconBounds.centerX(); 2181 dY = centerY - launcherIconBounds.centerY(); 2182 2183 iconAlphaStart = hasSplashScreen && !hasDifferentAppIcon ? 0 : 1f; 2184 2185 final int windowIconSize = ResourceUtils.getDimenByName("starting_surface_icon_size", 2186 r, 108); 2187 2188 cropCenterXStart = windowTargetBounds.centerX(); 2189 cropCenterYStart = windowTargetBounds.centerY(); 2190 2191 cropWidthStart = windowIconSize; 2192 cropHeightStart = windowIconSize; 2193 2194 cropWidthEnd = windowTargetBounds.width(); 2195 cropHeightEnd = windowTargetBounds.height(); 2196 2197 cropCenterXEnd = windowTargetBounds.centerX(); 2198 cropCenterYEnd = windowTargetBounds.centerY(); 2199 } 2200 } 2201 2202 private static class StartingWindowListener extends IStartingWindowListener.Stub { 2203 private final WeakReference<QuickstepTransitionManager> mTransitionManagerRef; 2204 private int mBackgroundColor; 2205 2206 private StartingWindowListener(QuickstepTransitionManager transitionManager) { 2207 mTransitionManagerRef = new WeakReference<>(transitionManager); 2208 } 2209 2210 @Override 2211 public void onTaskLaunching(int taskId, int supportedType, int color) { 2212 QuickstepTransitionManager transitionManager = mTransitionManagerRef.get(); 2213 if (transitionManager != null) { 2214 transitionManager.mTaskStartParams.put(taskId, Pair.create(supportedType, color)); 2215 } 2216 mBackgroundColor = color; 2217 } 2218 } 2219 2220 /** 2221 * Transfer the rectangle to another coordinate if needed. 2222 * 2223 * @param toLauncher which one is the anchor of this transfer, if true then transfer from 2224 * animation target to launcher, false transfer from launcher to animation 2225 * target. 2226 */ 2227 public void transferRectToTargetCoordinate(RemoteAnimationTarget target, RectF currentRect, 2228 boolean toLauncher, RectF resultRect) { 2229 mCoordinateTransfer.transferRectToTargetCoordinate( 2230 target, currentRect, toLauncher, resultRect); 2231 } 2232 2233 private static class RemoteAnimationCoordinateTransfer { 2234 private final QuickstepLauncher mLauncher; 2235 private final Rect mDisplayRect = new Rect(); 2236 private final Rect mTmpResult = new Rect(); 2237 2238 RemoteAnimationCoordinateTransfer(QuickstepLauncher launcher) { 2239 mLauncher = launcher; 2240 } 2241 2242 void transferRectToTargetCoordinate(RemoteAnimationTarget target, RectF currentRect, 2243 boolean toLauncher, RectF resultRect) { 2244 final int taskRotation = target.windowConfiguration.getRotation(); 2245 final DeviceProfile profile = mLauncher.getDeviceProfile(); 2246 2247 final int rotationDelta = toLauncher 2248 ? android.util.RotationUtils.deltaRotation(taskRotation, profile.rotationHint) 2249 : android.util.RotationUtils.deltaRotation(profile.rotationHint, taskRotation); 2250 if (rotationDelta != ROTATION_0) { 2251 // Get original display size when task is on top but with different rotation 2252 if (rotationDelta % 2 != 0 && toLauncher && (profile.rotationHint == ROTATION_0 2253 || profile.rotationHint == ROTATION_180)) { 2254 mDisplayRect.set(0, 0, profile.heightPx, profile.widthPx); 2255 } else { 2256 mDisplayRect.set(0, 0, profile.widthPx, profile.heightPx); 2257 } 2258 currentRect.round(mTmpResult); 2259 android.util.RotationUtils.rotateBounds(mTmpResult, mDisplayRect, rotationDelta); 2260 resultRect.set(mTmpResult); 2261 } else { 2262 resultRect.set(currentRect); 2263 } 2264 } 2265 } 2266 2267 /** 2268 * RectFSpringAnim update listener to be used for app to home animation. 2269 */ 2270 private class SpringAnimRunner implements RectFSpringAnim.OnUpdateListener { 2271 private final RemoteAnimationTarget[] mAppTargets; 2272 private final Matrix mMatrix = new Matrix(); 2273 private final Point mTmpPos = new Point(); 2274 private final RectF mCurrentRectF = new RectF(); 2275 private final float mStartRadius; 2276 private final float mEndRadius; 2277 private final SurfaceTransactionApplier mSurfaceApplier; 2278 private final Rect mWindowStartBounds = new Rect(); 2279 private final Rect mWindowOriginalBounds = new Rect(); 2280 2281 private final Rect mTmpRect = new Rect(); 2282 2283 /** 2284 * Constructor for SpringAnimRunner 2285 * 2286 * @param appTargets the list of opening/closing apps 2287 * @param targetRect target rectangle 2288 * @param closingWindowStartRect start position of the window when the spring animation 2289 * is started. In the predictive back to home case this 2290 * will be smaller than closingWindowOriginalRect because 2291 * the window is already scaled by the user gesture 2292 * @param closingWindowOriginalRect Original unscaled window rect 2293 * @param startWindowCornerRadius corner radius of window at the start position 2294 */ 2295 SpringAnimRunner(RemoteAnimationTarget[] appTargets, RectF targetRect, 2296 Rect closingWindowStartRect, Rect closingWindowOriginalRect, 2297 float startWindowCornerRadius) { 2298 mAppTargets = appTargets; 2299 mStartRadius = startWindowCornerRadius; 2300 mEndRadius = Math.max(1, targetRect.width()) / 2f; 2301 mSurfaceApplier = new SurfaceTransactionApplier(mDragLayer); 2302 mWindowStartBounds.set(closingWindowStartRect); 2303 mWindowOriginalBounds.set(closingWindowOriginalRect); 2304 2305 // transfer the coordinate based on animation target. 2306 if (mAppTargets != null) { 2307 for (RemoteAnimationTarget t : mAppTargets) { 2308 if (t.mode == MODE_CLOSING) { 2309 final RectF transferRect = new RectF(mWindowStartBounds); 2310 final RectF result = new RectF(); 2311 transferRectToTargetCoordinate(t, transferRect, false, result); 2312 result.round(mWindowStartBounds); 2313 2314 transferRect.set(closingWindowOriginalRect); 2315 transferRectToTargetCoordinate(t, transferRect, false, result); 2316 result.round(mWindowOriginalBounds); 2317 break; 2318 } 2319 } 2320 } 2321 } 2322 2323 public float getCornerRadius(float progress) { 2324 return Utilities.mapRange(progress, mStartRadius, mEndRadius); 2325 } 2326 2327 @Override 2328 public void onUpdate(RectF currentRectF, float progress) { 2329 SurfaceTransaction transaction = new SurfaceTransaction(); 2330 for (int i = mAppTargets.length - 1; i >= 0; i--) { 2331 RemoteAnimationTarget target = mAppTargets[i]; 2332 SurfaceProperties builder = transaction.forSurface(target.leash); 2333 2334 if (target.localBounds != null) { 2335 mTmpPos.set(target.localBounds.left, target.localBounds.top); 2336 } else { 2337 mTmpPos.set(target.position.x, target.position.y); 2338 } 2339 2340 if (target.mode == MODE_CLOSING) { 2341 transferRectToTargetCoordinate(target, currentRectF, false, mCurrentRectF); 2342 2343 // Scale the target window to match the currentRectF. 2344 final float scale; 2345 2346 // We need to infer the crop (we crop the window to match the currentRectF). 2347 if (mWindowStartBounds.height() > mWindowStartBounds.width()) { 2348 scale = Math.min(1f, mCurrentRectF.width() / mWindowOriginalBounds.width()); 2349 2350 int unscaledHeight = (int) (mCurrentRectF.height() * (1f / scale)); 2351 int croppedHeight = mWindowStartBounds.height() - unscaledHeight; 2352 mTmpRect.set(0, 0, mWindowOriginalBounds.width(), 2353 mWindowStartBounds.height() - croppedHeight); 2354 } else { 2355 scale = Math.min(1f, mCurrentRectF.height() 2356 / mWindowOriginalBounds.height()); 2357 2358 int unscaledWidth = (int) (mCurrentRectF.width() * (1f / scale)); 2359 int croppedWidth = mWindowStartBounds.width() - unscaledWidth; 2360 mTmpRect.set(0, 0, mWindowStartBounds.width() - croppedWidth, 2361 mWindowOriginalBounds.height()); 2362 } 2363 2364 // Match size and position of currentRect. 2365 mMatrix.setScale(scale, scale); 2366 mMatrix.postTranslate(mCurrentRectF.left, mCurrentRectF.top); 2367 2368 builder.setMatrix(mMatrix) 2369 .setWindowCrop(mTmpRect) 2370 .setAlpha(getWindowAlpha(progress)) 2371 .setCornerRadius(getCornerRadius(progress) / scale); 2372 } else if (target.mode == MODE_OPENING) { 2373 mMatrix.setTranslate(mTmpPos.x, mTmpPos.y); 2374 builder.setMatrix(mMatrix) 2375 .setAlpha(1f); 2376 } 2377 } 2378 mSurfaceApplier.scheduleApply(transaction); 2379 } 2380 2381 protected float getWindowAlpha(float progress) { 2382 // Alpha interpolates between [1, 0] between progress values [start, end] 2383 final float start = 0f; 2384 final float end = 0.85f; 2385 2386 if (progress <= start) { 2387 return 1f; 2388 } 2389 if (progress >= end) { 2390 return 0f; 2391 } 2392 return Utilities.mapToRange(progress, start, end, 1, 0, ACCELERATE_1_5); 2393 } 2394 } 2395 2396 private static class LaunchDepthController extends DepthController { 2397 LaunchDepthController(QuickstepLauncher launcher) { 2398 super(launcher); 2399 setCrossWindowBlursEnabled( 2400 CrossWindowBlurListeners.getInstance().isCrossWindowBlurEnabled()); 2401 // Make sure that the starting value matches the current depth set by the main 2402 // controller. 2403 stateDepth.setValue(launcher.getDepthController().stateDepth.getValue()); 2404 } 2405 } 2406 } 2407