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.window.StartingWindowInfo.STARTING_WINDOW_TYPE_NONE; 20 import static android.window.StartingWindowInfo.STARTING_WINDOW_TYPE_SPLASH_SCREEN; 21 22 import static com.android.launcher3.BaseActivity.INVISIBLE_ALL; 23 import static com.android.launcher3.BaseActivity.INVISIBLE_BY_APP_TRANSITIONS; 24 import static com.android.launcher3.BaseActivity.INVISIBLE_BY_PENDING_FLAGS; 25 import static com.android.launcher3.BaseActivity.PENDING_INVISIBLE_BY_WALLPAPER_ANIMATION; 26 import static com.android.launcher3.LauncherAnimUtils.SCALE_PROPERTY; 27 import static com.android.launcher3.LauncherAnimUtils.VIEW_BACKGROUND_COLOR; 28 import static com.android.launcher3.LauncherState.ALL_APPS; 29 import static com.android.launcher3.LauncherState.BACKGROUND_APP; 30 import static com.android.launcher3.LauncherState.OVERVIEW; 31 import static com.android.launcher3.Utilities.mapBoundToRange; 32 import static com.android.launcher3.Utilities.postAsyncCallback; 33 import static com.android.launcher3.anim.Interpolators.ACCEL_1_5; 34 import static com.android.launcher3.anim.Interpolators.AGGRESSIVE_EASE; 35 import static com.android.launcher3.anim.Interpolators.DEACCEL_1_5; 36 import static com.android.launcher3.anim.Interpolators.DEACCEL_1_7; 37 import static com.android.launcher3.anim.Interpolators.EXAGGERATED_EASE; 38 import static com.android.launcher3.anim.Interpolators.LINEAR; 39 import static com.android.launcher3.config.FeatureFlags.ENABLE_BACK_SWIPE_HOME_ANIMATION; 40 import static com.android.launcher3.config.FeatureFlags.ENABLE_SCRIM_FOR_APP_LAUNCH; 41 import static com.android.launcher3.config.FeatureFlags.KEYGUARD_ANIMATION; 42 import static com.android.launcher3.config.FeatureFlags.SEPARATE_RECENTS_ACTIVITY; 43 import static com.android.launcher3.dragndrop.DragLayer.ALPHA_INDEX_TRANSITIONS; 44 import static com.android.launcher3.model.data.ItemInfo.NO_MATCHING_ID; 45 import static com.android.launcher3.statehandlers.DepthController.DEPTH; 46 import static com.android.launcher3.util.DisplayController.getSingleFrameMs; 47 import static com.android.launcher3.views.FloatingIconView.SHAPE_PROGRESS_DURATION; 48 import static com.android.launcher3.views.FloatingIconView.getFloatingIconView; 49 import static com.android.quickstep.TaskViewUtils.findTaskViewToLaunch; 50 import static com.android.systemui.shared.system.QuickStepContract.getWindowCornerRadius; 51 import static com.android.systemui.shared.system.QuickStepContract.supportsRoundedCornersOnWindows; 52 import static com.android.systemui.shared.system.RemoteAnimationTargetCompat.MODE_CLOSING; 53 import static com.android.systemui.shared.system.RemoteAnimationTargetCompat.MODE_OPENING; 54 55 import android.animation.Animator; 56 import android.animation.AnimatorListenerAdapter; 57 import android.animation.AnimatorSet; 58 import android.animation.ObjectAnimator; 59 import android.animation.ValueAnimator; 60 import android.content.ComponentName; 61 import android.content.Context; 62 import android.content.pm.PackageManager; 63 import android.content.res.Resources; 64 import android.graphics.Color; 65 import android.graphics.Matrix; 66 import android.graphics.Point; 67 import android.graphics.PointF; 68 import android.graphics.Rect; 69 import android.graphics.RectF; 70 import android.graphics.drawable.ColorDrawable; 71 import android.graphics.drawable.Drawable; 72 import android.os.CancellationSignal; 73 import android.os.Handler; 74 import android.os.IBinder; 75 import android.os.Looper; 76 import android.os.SystemProperties; 77 import android.os.UserHandle; 78 import android.util.Pair; 79 import android.util.Size; 80 import android.view.SurfaceControl; 81 import android.view.View; 82 import android.view.ViewRootImpl; 83 import android.view.ViewTreeObserver; 84 import android.view.animation.AnimationUtils; 85 import android.view.animation.Interpolator; 86 import android.view.animation.PathInterpolator; 87 88 import androidx.annotation.NonNull; 89 import androidx.annotation.Nullable; 90 import androidx.core.graphics.ColorUtils; 91 92 import com.android.launcher3.DeviceProfile.OnDeviceProfileChangeListener; 93 import com.android.launcher3.LauncherAnimationRunner.RemoteAnimationFactory; 94 import com.android.launcher3.anim.AnimationSuccessListener; 95 import com.android.launcher3.dragndrop.DragLayer; 96 import com.android.launcher3.icons.FastBitmapDrawable; 97 import com.android.launcher3.shortcuts.DeepShortcutView; 98 import com.android.launcher3.statehandlers.DepthController; 99 import com.android.launcher3.taskbar.LauncherTaskbarUIController; 100 import com.android.launcher3.touch.PagedOrientationHandler; 101 import com.android.launcher3.util.ActivityOptionsWrapper; 102 import com.android.launcher3.util.DynamicResource; 103 import com.android.launcher3.util.MultiValueAlpha.AlphaProperty; 104 import com.android.launcher3.util.ObjectWrapper; 105 import com.android.launcher3.util.RunnableList; 106 import com.android.launcher3.util.Themes; 107 import com.android.launcher3.views.FloatingIconView; 108 import com.android.launcher3.views.ScrimView; 109 import com.android.launcher3.widget.LauncherAppWidgetHostView; 110 import com.android.quickstep.RemoteAnimationTargets; 111 import com.android.quickstep.SystemUiProxy; 112 import com.android.quickstep.TaskViewUtils; 113 import com.android.quickstep.util.MultiValueUpdateListener; 114 import com.android.quickstep.util.RectFSpringAnim; 115 import com.android.quickstep.util.RemoteAnimationProvider; 116 import com.android.quickstep.util.StaggeredWorkspaceAnim; 117 import com.android.quickstep.util.SurfaceTransactionApplier; 118 import com.android.quickstep.util.WorkspaceRevealAnim; 119 import com.android.quickstep.views.FloatingWidgetView; 120 import com.android.quickstep.views.RecentsView; 121 import com.android.systemui.shared.system.ActivityCompat; 122 import com.android.systemui.shared.system.ActivityOptionsCompat; 123 import com.android.systemui.shared.system.BlurUtils; 124 import com.android.systemui.shared.system.InteractionJankMonitorWrapper; 125 import com.android.systemui.shared.system.QuickStepContract; 126 import com.android.systemui.shared.system.RemoteAnimationAdapterCompat; 127 import com.android.systemui.shared.system.RemoteAnimationDefinitionCompat; 128 import com.android.systemui.shared.system.RemoteAnimationRunnerCompat; 129 import com.android.systemui.shared.system.RemoteAnimationTargetCompat; 130 import com.android.systemui.shared.system.RemoteTransitionCompat; 131 import com.android.systemui.shared.system.SyncRtSurfaceTransactionApplierCompat.SurfaceParams; 132 import com.android.systemui.shared.system.WindowManagerWrapper; 133 import com.android.wm.shell.startingsurface.IStartingWindowListener; 134 135 import java.util.ArrayList; 136 import java.util.LinkedHashMap; 137 import java.util.List; 138 139 /** 140 * Manages the opening and closing app transitions from Launcher 141 */ 142 public class QuickstepTransitionManager implements OnDeviceProfileChangeListener { 143 144 private static final String TAG = "QuickstepTransition"; 145 146 private static final boolean ENABLE_SHELL_STARTING_SURFACE = 147 SystemProperties.getBoolean("persist.debug.shell_starting_surface", true); 148 149 /** Duration of status bar animations. */ 150 public static final int STATUS_BAR_TRANSITION_DURATION = 120; 151 152 /** 153 * Since our animations decelerate heavily when finishing, we want to start status bar 154 * animations x ms before the ending. 155 */ 156 public static final int STATUS_BAR_TRANSITION_PRE_DELAY = 96; 157 158 private static final String CONTROL_REMOTE_APP_TRANSITION_PERMISSION = 159 "android.permission.CONTROL_REMOTE_APP_TRANSITION_ANIMATIONS"; 160 161 private static final long APP_LAUNCH_DURATION = 500; 162 163 private static final long APP_LAUNCH_ALPHA_DURATION = 50; 164 private static final long APP_LAUNCH_ALPHA_START_DELAY = 25; 165 166 public static final int ANIMATION_NAV_FADE_IN_DURATION = 266; 167 public static final int ANIMATION_NAV_FADE_OUT_DURATION = 133; 168 public static final long ANIMATION_DELAY_NAV_FADE_IN = 169 APP_LAUNCH_DURATION - ANIMATION_NAV_FADE_IN_DURATION; 170 public static final Interpolator NAV_FADE_IN_INTERPOLATOR = 171 new PathInterpolator(0f, 0f, 0f, 1f); 172 public static final Interpolator NAV_FADE_OUT_INTERPOLATOR = 173 new PathInterpolator(0.2f, 0f, 1f, 1f); 174 175 public static final int RECENTS_LAUNCH_DURATION = 336; 176 private static final int LAUNCHER_RESUME_START_DELAY = 100; 177 private static final int CLOSING_TRANSITION_DURATION_MS = 250; 178 public static final int SPLIT_LAUNCH_DURATION = 370; 179 public static final int SPLIT_DIVIDER_ANIM_DURATION = 100; 180 181 public static final int CONTENT_ALPHA_DURATION = 217; 182 protected static final int CONTENT_SCALE_DURATION = 350; 183 protected static final int CONTENT_SCRIM_DURATION = 350; 184 185 private static final int MAX_NUM_TASKS = 5; 186 187 // Cross-fade duration between App Widget and App 188 private static final int WIDGET_CROSSFADE_DURATION_MILLIS = 125; 189 190 protected final BaseQuickstepLauncher mLauncher; 191 192 private final DragLayer mDragLayer; 193 private final AlphaProperty mDragLayerAlpha; 194 195 final Handler mHandler; 196 197 private final float mContentScale; 198 private final float mClosingWindowTransY; 199 private final float mMaxShadowRadius; 200 201 private final StartingWindowListener mStartingWindowListener = new StartingWindowListener(); 202 203 private DeviceProfile mDeviceProfile; 204 205 private RemoteAnimationProvider mRemoteAnimationProvider; 206 // Strong refs to runners which are cleared when the launcher activity is destroyed 207 private RemoteAnimationFactory mWallpaperOpenRunner; 208 private RemoteAnimationFactory mAppLaunchRunner; 209 private RemoteAnimationFactory mKeyguardGoingAwayRunner; 210 211 private RemoteAnimationFactory mWallpaperOpenTransitionRunner; 212 private RemoteTransitionCompat mLauncherOpenTransition; 213 214 private final AnimatorListenerAdapter mForceInvisibleListener = new AnimatorListenerAdapter() { 215 @Override 216 public void onAnimationStart(Animator animation) { 217 mLauncher.addForceInvisibleFlag(INVISIBLE_BY_APP_TRANSITIONS); 218 } 219 220 @Override 221 public void onAnimationEnd(Animator animation) { 222 mLauncher.clearForceInvisibleFlag(INVISIBLE_BY_APP_TRANSITIONS); 223 } 224 }; 225 226 // Pairs of window starting type and starting window background color for starting tasks 227 // Will never be larger than MAX_NUM_TASKS 228 private LinkedHashMap<Integer, Pair<Integer, Integer>> mTaskStartParams; 229 230 private final Interpolator mOpeningXInterpolator; 231 private final Interpolator mOpeningInterpolator; 232 QuickstepTransitionManager(Context context)233 public QuickstepTransitionManager(Context context) { 234 mLauncher = Launcher.cast(Launcher.getLauncher(context)); 235 mDragLayer = mLauncher.getDragLayer(); 236 mDragLayerAlpha = mDragLayer.getAlphaProperty(ALPHA_INDEX_TRANSITIONS); 237 mHandler = new Handler(Looper.getMainLooper()); 238 mDeviceProfile = mLauncher.getDeviceProfile(); 239 240 Resources res = mLauncher.getResources(); 241 mContentScale = res.getFloat(R.dimen.content_scale); 242 mClosingWindowTransY = res.getDimensionPixelSize(R.dimen.closing_window_trans_y); 243 mMaxShadowRadius = res.getDimensionPixelSize(R.dimen.max_shadow_radius); 244 245 mLauncher.addOnDeviceProfileChangeListener(this); 246 247 if (supportsSSplashScreen()) { 248 mTaskStartParams = new LinkedHashMap<Integer, Pair<Integer, Integer>>(MAX_NUM_TASKS) { 249 @Override 250 protected boolean removeEldestEntry(Entry<Integer, Pair<Integer, Integer>> entry) { 251 return size() > MAX_NUM_TASKS; 252 } 253 }; 254 255 mStartingWindowListener.setTransitionManager(this); 256 SystemUiProxy.INSTANCE.get(mLauncher).setStartingWindowListener( 257 mStartingWindowListener); 258 } 259 260 mOpeningXInterpolator = AnimationUtils.loadInterpolator(context, R.interpolator.app_open_x); 261 mOpeningInterpolator = AnimationUtils.loadInterpolator(context, 262 R.interpolator.three_point_fast_out_extra_slow_in); 263 } 264 265 @Override onDeviceProfileChanged(DeviceProfile dp)266 public void onDeviceProfileChanged(DeviceProfile dp) { 267 mDeviceProfile = dp; 268 } 269 270 /** 271 * @return ActivityOptions with remote animations that controls how the window of the opening 272 * targets are displayed. 273 */ getActivityLaunchOptions(View v)274 public ActivityOptionsWrapper getActivityLaunchOptions(View v) { 275 boolean fromRecents = isLaunchingFromRecents(v, null /* targets */); 276 RunnableList onEndCallback = new RunnableList(); 277 mAppLaunchRunner = new AppLaunchAnimationRunner(v, onEndCallback); 278 RemoteAnimationRunnerCompat runner = new LauncherAnimationRunner( 279 mHandler, mAppLaunchRunner, true /* startAtFrontOfQueue */); 280 281 // Note that this duration is a guess as we do not know if the animation will be a 282 // recents launch or not for sure until we know the opening app targets. 283 long duration = fromRecents 284 ? RECENTS_LAUNCH_DURATION 285 : APP_LAUNCH_DURATION; 286 287 long statusBarTransitionDelay = duration - STATUS_BAR_TRANSITION_DURATION 288 - STATUS_BAR_TRANSITION_PRE_DELAY; 289 RemoteAnimationAdapterCompat adapterCompat = 290 new RemoteAnimationAdapterCompat(runner, duration, statusBarTransitionDelay, 291 mLauncher.getIApplicationThread()); 292 return new ActivityOptionsWrapper( 293 ActivityOptionsCompat.makeRemoteAnimation(adapterCompat), onEndCallback); 294 } 295 296 /** 297 * Whether the launch is a recents app transition and we should do a launch animation 298 * from the recents view. Note that if the remote animation targets are not provided, this 299 * may not always be correct as we may resolve the opening app to a task when the animation 300 * starts. 301 * 302 * @param v the view to launch from 303 * @param targets apps that are opening/closing 304 * @return true if the app is launching from recents, false if it most likely is not 305 */ isLaunchingFromRecents(@onNull View v, @Nullable RemoteAnimationTargetCompat[] targets)306 protected boolean isLaunchingFromRecents(@NonNull View v, 307 @Nullable RemoteAnimationTargetCompat[] targets) { 308 return mLauncher.getStateManager().getState().overviewUi 309 && findTaskViewToLaunch(mLauncher.getOverviewPanel(), v, targets) != null; 310 } 311 312 /** 313 * Composes the animations for a launch from the recents list. 314 * 315 * @param anim the animator set to add to 316 * @param v the launching view 317 * @param appTargets the apps that are opening/closing 318 * @param launcherClosing true if the launcher app is closing 319 */ composeRecentsLaunchAnimator(@onNull AnimatorSet anim, @NonNull View v, @NonNull RemoteAnimationTargetCompat[] appTargets, @NonNull RemoteAnimationTargetCompat[] wallpaperTargets, @NonNull RemoteAnimationTargetCompat[] nonAppTargets, boolean launcherClosing)320 protected void composeRecentsLaunchAnimator(@NonNull AnimatorSet anim, @NonNull View v, 321 @NonNull RemoteAnimationTargetCompat[] appTargets, 322 @NonNull RemoteAnimationTargetCompat[] wallpaperTargets, 323 @NonNull RemoteAnimationTargetCompat[] nonAppTargets, boolean launcherClosing) { 324 TaskViewUtils.composeRecentsLaunchAnimator(anim, v, appTargets, wallpaperTargets, 325 nonAppTargets, launcherClosing, mLauncher.getStateManager(), 326 mLauncher.getOverviewPanel(), mLauncher.getDepthController()); 327 } 328 areAllTargetsTranslucent(@onNull RemoteAnimationTargetCompat[] targets)329 private boolean areAllTargetsTranslucent(@NonNull RemoteAnimationTargetCompat[] targets) { 330 boolean isAllOpeningTargetTrs = true; 331 for (int i = 0; i < targets.length; i++) { 332 RemoteAnimationTargetCompat target = targets[i]; 333 if (target.mode == MODE_OPENING) { 334 isAllOpeningTargetTrs &= target.isTranslucent; 335 } 336 if (!isAllOpeningTargetTrs) break; 337 } 338 return isAllOpeningTargetTrs; 339 } 340 341 /** 342 * Compose the animations for a launch from the app icon. 343 * 344 * @param anim the animation to add to 345 * @param v the launching view with the icon 346 * @param appTargets the list of opening/closing apps 347 * @param launcherClosing true if launcher is closing 348 */ composeIconLaunchAnimator(@onNull AnimatorSet anim, @NonNull View v, @NonNull RemoteAnimationTargetCompat[] appTargets, @NonNull RemoteAnimationTargetCompat[] wallpaperTargets, @NonNull RemoteAnimationTargetCompat[] nonAppTargets, boolean launcherClosing)349 private void composeIconLaunchAnimator(@NonNull AnimatorSet anim, @NonNull View v, 350 @NonNull RemoteAnimationTargetCompat[] appTargets, 351 @NonNull RemoteAnimationTargetCompat[] wallpaperTargets, 352 @NonNull RemoteAnimationTargetCompat[] nonAppTargets, 353 boolean launcherClosing) { 354 // Set the state animation first so that any state listeners are called 355 // before our internal listeners. 356 mLauncher.getStateManager().setCurrentAnimation(anim); 357 358 final int rotationChange = getRotationChange(appTargets); 359 // Note: the targetBounds are relative to the launcher 360 int startDelay = getSingleFrameMs(mLauncher); 361 Rect windowTargetBounds = getWindowTargetBounds(appTargets, rotationChange); 362 Animator windowAnimator = getOpeningWindowAnimators(v, appTargets, wallpaperTargets, 363 nonAppTargets, windowTargetBounds, areAllTargetsTranslucent(appTargets), 364 rotationChange); 365 windowAnimator.setStartDelay(startDelay); 366 anim.play(windowAnimator); 367 if (launcherClosing) { 368 // Delay animation by a frame to avoid jank. 369 Pair<AnimatorSet, Runnable> launcherContentAnimator = 370 getLauncherContentAnimator(true /* isAppOpening */, startDelay, false); 371 anim.play(launcherContentAnimator.first); 372 anim.addListener(new AnimatorListenerAdapter() { 373 @Override 374 public void onAnimationEnd(Animator animation) { 375 launcherContentAnimator.second.run(); 376 } 377 }); 378 } else { 379 anim.addListener(new AnimatorListenerAdapter() { 380 @Override 381 public void onAnimationStart(Animator animation) { 382 mLauncher.addOnResumeCallback(() -> 383 ObjectAnimator.ofFloat(mLauncher.getDepthController(), DEPTH, 384 mLauncher.getStateManager().getState().getDepth( 385 mLauncher)).start()); 386 } 387 }); 388 } 389 } 390 composeWidgetLaunchAnimator( @onNull AnimatorSet anim, @NonNull LauncherAppWidgetHostView v, @NonNull RemoteAnimationTargetCompat[] appTargets, @NonNull RemoteAnimationTargetCompat[] wallpaperTargets, @NonNull RemoteAnimationTargetCompat[] nonAppTargets)391 private void composeWidgetLaunchAnimator( 392 @NonNull AnimatorSet anim, 393 @NonNull LauncherAppWidgetHostView v, 394 @NonNull RemoteAnimationTargetCompat[] appTargets, 395 @NonNull RemoteAnimationTargetCompat[] wallpaperTargets, 396 @NonNull RemoteAnimationTargetCompat[] nonAppTargets) { 397 mLauncher.getStateManager().setCurrentAnimation(anim); 398 399 Rect windowTargetBounds = getWindowTargetBounds(appTargets, getRotationChange(appTargets)); 400 anim.play(getOpeningWindowAnimatorsForWidget(v, appTargets, wallpaperTargets, nonAppTargets, 401 windowTargetBounds, areAllTargetsTranslucent(appTargets))); 402 403 anim.addListener(new AnimatorListenerAdapter() { 404 @Override 405 public void onAnimationStart(Animator animation) { 406 mLauncher.addOnResumeCallback(() -> 407 ObjectAnimator.ofFloat(mLauncher.getDepthController(), DEPTH, 408 mLauncher.getStateManager().getState().getDepth( 409 mLauncher)).start()); 410 } 411 }); 412 } 413 414 /** 415 * Return the window bounds of the opening target. 416 * In multiwindow mode, we need to get the final size of the opening app window target to help 417 * figure out where the floating view should animate to. 418 */ getWindowTargetBounds(@onNull RemoteAnimationTargetCompat[] appTargets, int rotationChange)419 private Rect getWindowTargetBounds(@NonNull RemoteAnimationTargetCompat[] appTargets, 420 int rotationChange) { 421 RemoteAnimationTargetCompat target = null; 422 for (RemoteAnimationTargetCompat t : appTargets) { 423 if (t.mode != MODE_OPENING) continue; 424 target = t; 425 break; 426 } 427 if (target == null) return new Rect(0, 0, mDeviceProfile.widthPx, mDeviceProfile.heightPx); 428 final Rect bounds = new Rect(target.screenSpaceBounds); 429 if (target.localBounds != null) { 430 bounds.set(target.localBounds); 431 } else { 432 bounds.offsetTo(target.position.x, target.position.y); 433 } 434 if (rotationChange != 0) { 435 if ((rotationChange % 2) == 1) { 436 // undoing rotation, so our "original" parent size is actually flipped 437 Utilities.rotateBounds(bounds, mDeviceProfile.heightPx, mDeviceProfile.widthPx, 438 4 - rotationChange); 439 } else { 440 Utilities.rotateBounds(bounds, mDeviceProfile.widthPx, mDeviceProfile.heightPx, 441 4 - rotationChange); 442 } 443 } 444 if (mDeviceProfile.isTaskbarPresentInApps) { 445 // Animate to above the taskbar. 446 bounds.bottom -= target.contentInsets.bottom; 447 } 448 return bounds; 449 } 450 setRemoteAnimationProvider(final RemoteAnimationProvider animationProvider, CancellationSignal cancellationSignal)451 public void setRemoteAnimationProvider(final RemoteAnimationProvider animationProvider, 452 CancellationSignal cancellationSignal) { 453 mRemoteAnimationProvider = animationProvider; 454 cancellationSignal.setOnCancelListener(() -> { 455 if (animationProvider == mRemoteAnimationProvider) { 456 mRemoteAnimationProvider = null; 457 } 458 }); 459 } 460 461 /** 462 * Content is everything on screen except the background and the floating view (if any). 463 * 464 * @param isAppOpening True when this is called when an app is opening. 465 * False when this is called when an app is closing. 466 * @param startDelay Start delay duration. 467 * @param skipAllAppsScale True if we want to avoid scaling All Apps 468 */ getLauncherContentAnimator(boolean isAppOpening, int startDelay, boolean skipAllAppsScale)469 private Pair<AnimatorSet, Runnable> getLauncherContentAnimator(boolean isAppOpening, 470 int startDelay, boolean skipAllAppsScale) { 471 AnimatorSet launcherAnimator = new AnimatorSet(); 472 Runnable endListener; 473 474 float[] alphas = isAppOpening 475 ? new float[]{1, 0} 476 : new float[]{0, 1}; 477 478 float[] scales = isAppOpening 479 ? new float[]{1, mContentScale} 480 : new float[]{mContentScale, 1}; 481 482 if (mLauncher.isInState(ALL_APPS)) { 483 // All Apps in portrait mode is full screen, so we only animate AllAppsContainerView. 484 final View appsView = mLauncher.getAppsView(); 485 final float startAlpha = appsView.getAlpha(); 486 final float startScale = SCALE_PROPERTY.get(appsView); 487 appsView.setAlpha(alphas[0]); 488 489 ObjectAnimator alpha = ObjectAnimator.ofFloat(appsView, View.ALPHA, alphas); 490 alpha.setDuration(CONTENT_ALPHA_DURATION); 491 alpha.setInterpolator(LINEAR); 492 appsView.setLayerType(View.LAYER_TYPE_HARDWARE, null); 493 alpha.addListener(new AnimatorListenerAdapter() { 494 @Override 495 public void onAnimationEnd(Animator animation) { 496 appsView.setLayerType(View.LAYER_TYPE_NONE, null); 497 } 498 }); 499 500 if (!skipAllAppsScale) { 501 SCALE_PROPERTY.set(appsView, scales[0]); 502 ObjectAnimator scale = ObjectAnimator.ofFloat(appsView, SCALE_PROPERTY, scales); 503 scale.setInterpolator(AGGRESSIVE_EASE); 504 scale.setDuration(CONTENT_SCALE_DURATION); 505 launcherAnimator.play(scale); 506 } 507 508 launcherAnimator.play(alpha); 509 510 endListener = () -> { 511 appsView.setAlpha(startAlpha); 512 SCALE_PROPERTY.set(appsView, startScale); 513 appsView.setLayerType(View.LAYER_TYPE_NONE, null); 514 }; 515 } else if (mLauncher.isInState(OVERVIEW)) { 516 endListener = composeViewContentAnimator(launcherAnimator, alphas, scales); 517 } else { 518 List<View> viewsToAnimate = new ArrayList<>(); 519 Workspace workspace = mLauncher.getWorkspace(); 520 workspace.forEachVisiblePage( 521 view -> viewsToAnimate.add(((CellLayout) view).getShortcutsAndWidgets())); 522 523 viewsToAnimate.add(mLauncher.getHotseat()); 524 525 viewsToAnimate.forEach(view -> { 526 view.setLayerType(View.LAYER_TYPE_HARDWARE, null); 527 528 ObjectAnimator scaleAnim = ObjectAnimator.ofFloat(view, SCALE_PROPERTY, scales) 529 .setDuration(CONTENT_SCALE_DURATION); 530 scaleAnim.setInterpolator(DEACCEL_1_5); 531 launcherAnimator.play(scaleAnim); 532 }); 533 534 final boolean scrimEnabled = ENABLE_SCRIM_FOR_APP_LAUNCH.get(); 535 if (scrimEnabled) { 536 boolean useTaskbarColor = mDeviceProfile.isTaskbarPresentInApps; 537 int scrimColor = useTaskbarColor 538 ? mLauncher.getResources().getColor(R.color.taskbar_background) 539 : Themes.getAttrColor(mLauncher, R.attr.overviewScrimColor); 540 int scrimColorTrans = ColorUtils.setAlphaComponent(scrimColor, 0); 541 int[] colors = isAppOpening 542 ? new int[]{scrimColorTrans, scrimColor} 543 : new int[]{scrimColor, scrimColorTrans}; 544 ScrimView scrimView = mLauncher.getScrimView(); 545 if (scrimView.getBackground() instanceof ColorDrawable) { 546 scrimView.setBackgroundColor(colors[0]); 547 548 ObjectAnimator scrim = ObjectAnimator.ofArgb(scrimView, VIEW_BACKGROUND_COLOR, 549 colors); 550 scrim.setDuration(CONTENT_SCRIM_DURATION); 551 scrim.setInterpolator(DEACCEL_1_5); 552 553 if (useTaskbarColor) { 554 // Hide the taskbar background color since it would duplicate the scrim. 555 scrim.addListener(new AnimatorListenerAdapter() { 556 @Override 557 public void onAnimationStart(Animator animation) { 558 LauncherTaskbarUIController taskbarUIController = 559 mLauncher.getTaskbarUIController(); 560 if (taskbarUIController != null) { 561 taskbarUIController.forceHideBackground(true); 562 } 563 } 564 565 @Override 566 public void onAnimationEnd(Animator animation) { 567 LauncherTaskbarUIController taskbarUIController = 568 mLauncher.getTaskbarUIController(); 569 if (taskbarUIController != null) { 570 taskbarUIController.forceHideBackground(false); 571 } 572 } 573 }); 574 } 575 576 launcherAnimator.play(scrim); 577 } 578 } 579 580 // Pause page indicator animations as they lead to layer trashing. 581 mLauncher.getWorkspace().getPageIndicator().pauseAnimations(); 582 583 endListener = () -> { 584 viewsToAnimate.forEach(view -> { 585 SCALE_PROPERTY.set(view, 1f); 586 view.setLayerType(View.LAYER_TYPE_NONE, null); 587 }); 588 if (scrimEnabled) { 589 mLauncher.getScrimView().setBackgroundColor(Color.TRANSPARENT); 590 } 591 mLauncher.getWorkspace().getPageIndicator().skipAnimationsToEnd(); 592 }; 593 } 594 595 launcherAnimator.setStartDelay(startDelay); 596 return new Pair<>(launcherAnimator, endListener); 597 } 598 599 /** 600 * Compose recents view alpha and translation Y animation when launcher opens/closes apps. 601 * 602 * @param anim the animator set to add to 603 * @param alphas the alphas to animate to over time 604 * @param scales the scale values to animator to over time 605 * @return listener to run when the animation ends 606 */ composeViewContentAnimator(@onNull AnimatorSet anim, float[] alphas, float[] scales)607 protected Runnable composeViewContentAnimator(@NonNull AnimatorSet anim, 608 float[] alphas, float[] scales) { 609 RecentsView overview = mLauncher.getOverviewPanel(); 610 ObjectAnimator alpha = ObjectAnimator.ofFloat(overview, 611 RecentsView.CONTENT_ALPHA, alphas); 612 alpha.setDuration(CONTENT_ALPHA_DURATION); 613 alpha.setInterpolator(LINEAR); 614 anim.play(alpha); 615 overview.setFreezeViewVisibility(true); 616 617 ObjectAnimator scaleAnim = ObjectAnimator.ofFloat(overview, SCALE_PROPERTY, scales); 618 scaleAnim.setInterpolator(AGGRESSIVE_EASE); 619 scaleAnim.setDuration(CONTENT_SCALE_DURATION); 620 anim.play(scaleAnim); 621 622 return () -> { 623 overview.setFreezeViewVisibility(false); 624 SCALE_PROPERTY.set(overview, 1f); 625 mLauncher.getStateManager().reapplyState(); 626 }; 627 } 628 629 /** 630 * @return Animator that controls the window of the opening targets from app icons. 631 */ getOpeningWindowAnimators(View v, RemoteAnimationTargetCompat[] appTargets, RemoteAnimationTargetCompat[] wallpaperTargets, RemoteAnimationTargetCompat[] nonAppTargets, Rect windowTargetBounds, boolean appTargetsAreTranslucent, int rotationChange)632 private Animator getOpeningWindowAnimators(View v, 633 RemoteAnimationTargetCompat[] appTargets, 634 RemoteAnimationTargetCompat[] wallpaperTargets, 635 RemoteAnimationTargetCompat[] nonAppTargets, 636 Rect windowTargetBounds, boolean appTargetsAreTranslucent, int rotationChange) { 637 RectF launcherIconBounds = new RectF(); 638 FloatingIconView floatingView = FloatingIconView.getFloatingIconView(mLauncher, v, 639 !appTargetsAreTranslucent, launcherIconBounds, true /* isOpening */); 640 Rect crop = new Rect(); 641 Matrix matrix = new Matrix(); 642 643 RemoteAnimationTargets openingTargets = new RemoteAnimationTargets(appTargets, 644 wallpaperTargets, nonAppTargets, MODE_OPENING); 645 SurfaceTransactionApplier surfaceApplier = 646 new SurfaceTransactionApplier(floatingView); 647 openingTargets.addReleaseCheck(surfaceApplier); 648 RemoteAnimationTargetCompat navBarTarget = openingTargets.getNavBarRemoteAnimationTarget(); 649 650 int[] dragLayerBounds = new int[2]; 651 mDragLayer.getLocationOnScreen(dragLayerBounds); 652 653 final boolean hasSplashScreen; 654 if (supportsSSplashScreen()) { 655 int taskId = openingTargets.getFirstAppTargetTaskId(); 656 Pair<Integer, Integer> defaultParams = Pair.create(STARTING_WINDOW_TYPE_NONE, 0); 657 Pair<Integer, Integer> taskParams = 658 mTaskStartParams.getOrDefault(taskId, defaultParams); 659 mTaskStartParams.remove(taskId); 660 hasSplashScreen = taskParams.first == STARTING_WINDOW_TYPE_SPLASH_SCREEN; 661 } else { 662 hasSplashScreen = false; 663 } 664 665 AnimOpenProperties prop = new AnimOpenProperties(mLauncher.getResources(), mDeviceProfile, 666 windowTargetBounds, launcherIconBounds, v, dragLayerBounds[0], dragLayerBounds[1], 667 hasSplashScreen, floatingView.isDifferentFromAppIcon()); 668 int left = prop.cropCenterXStart - prop.cropWidthStart / 2; 669 int top = prop.cropCenterYStart - prop.cropHeightStart / 2; 670 int right = left + prop.cropWidthStart; 671 int bottom = top + prop.cropHeightStart; 672 // Set the crop here so we can calculate the corner radius below. 673 crop.set(left, top, right, bottom); 674 675 RectF floatingIconBounds = new RectF(); 676 RectF tmpRectF = new RectF(); 677 Point tmpPos = new Point(); 678 679 AnimatorSet animatorSet = new AnimatorSet(); 680 ValueAnimator appAnimator = ValueAnimator.ofFloat(0, 1); 681 appAnimator.setDuration(APP_LAUNCH_DURATION); 682 appAnimator.setInterpolator(LINEAR); 683 appAnimator.addListener(floatingView); 684 appAnimator.addListener(new AnimatorListenerAdapter() { 685 @Override 686 public void onAnimationEnd(Animator animation) { 687 if (v instanceof BubbleTextView) { 688 ((BubbleTextView) v).setStayPressed(false); 689 } 690 LauncherTaskbarUIController taskbarController = mLauncher.getTaskbarUIController(); 691 if (taskbarController != null) { 692 taskbarController.showEdu(); 693 } 694 openingTargets.release(); 695 } 696 }); 697 698 final float initialWindowRadius = supportsRoundedCornersOnWindows(mLauncher.getResources()) 699 ? Math.max(crop.width(), crop.height()) / 2f 700 : 0f; 701 final float finalWindowRadius = mDeviceProfile.isMultiWindowMode 702 ? 0 : getWindowCornerRadius(mLauncher); 703 final float finalShadowRadius = appTargetsAreTranslucent ? 0 : mMaxShadowRadius; 704 705 MultiValueUpdateListener listener = new MultiValueUpdateListener() { 706 FloatProp mDx = new FloatProp(0, prop.dX, 0, APP_LAUNCH_DURATION, 707 mOpeningXInterpolator); 708 FloatProp mDy = new FloatProp(0, prop.dY, 0, APP_LAUNCH_DURATION, 709 mOpeningInterpolator); 710 711 FloatProp mIconScaleToFitScreen = new FloatProp(prop.initialAppIconScale, 712 prop.finalAppIconScale, 0, APP_LAUNCH_DURATION, mOpeningInterpolator); 713 FloatProp mIconAlpha = new FloatProp(prop.iconAlphaStart, 0f, 714 APP_LAUNCH_ALPHA_START_DELAY, APP_LAUNCH_ALPHA_DURATION, LINEAR); 715 716 FloatProp mWindowRadius = new FloatProp(initialWindowRadius, finalWindowRadius, 0, 717 APP_LAUNCH_DURATION, mOpeningInterpolator); 718 FloatProp mShadowRadius = new FloatProp(0, finalShadowRadius, 0, 719 APP_LAUNCH_DURATION, mOpeningInterpolator); 720 721 FloatProp mCropRectCenterX = new FloatProp(prop.cropCenterXStart, prop.cropCenterXEnd, 722 0, APP_LAUNCH_DURATION, mOpeningInterpolator); 723 FloatProp mCropRectCenterY = new FloatProp(prop.cropCenterYStart, prop.cropCenterYEnd, 724 0, APP_LAUNCH_DURATION, mOpeningInterpolator); 725 FloatProp mCropRectWidth = new FloatProp(prop.cropWidthStart, prop.cropWidthEnd, 0, 726 APP_LAUNCH_DURATION, mOpeningInterpolator); 727 FloatProp mCropRectHeight = new FloatProp(prop.cropHeightStart, prop.cropHeightEnd, 0, 728 APP_LAUNCH_DURATION, mOpeningInterpolator); 729 730 FloatProp mNavFadeOut = new FloatProp(1f, 0f, 0, ANIMATION_NAV_FADE_OUT_DURATION, 731 NAV_FADE_OUT_INTERPOLATOR); 732 FloatProp mNavFadeIn = new FloatProp(0f, 1f, ANIMATION_DELAY_NAV_FADE_IN, 733 ANIMATION_NAV_FADE_IN_DURATION, NAV_FADE_IN_INTERPOLATOR); 734 735 @Override 736 public void onUpdate(float percent, boolean initOnly) { 737 // Calculate the size of the scaled icon. 738 float iconWidth = launcherIconBounds.width() * mIconScaleToFitScreen.value; 739 float iconHeight = launcherIconBounds.height() * mIconScaleToFitScreen.value; 740 741 int left = (int) (mCropRectCenterX.value - mCropRectWidth.value / 2); 742 int top = (int) (mCropRectCenterY.value - mCropRectHeight.value / 2); 743 int right = (int) (left + mCropRectWidth.value); 744 int bottom = (int) (top + mCropRectHeight.value); 745 crop.set(left, top, right, bottom); 746 747 final int windowCropWidth = crop.width(); 748 final int windowCropHeight = crop.height(); 749 if (rotationChange != 0) { 750 Utilities.rotateBounds(crop, mDeviceProfile.widthPx, 751 mDeviceProfile.heightPx, rotationChange); 752 } 753 754 // Scale the size of the icon to match the size of the window crop. 755 float scaleX = iconWidth / windowCropWidth; 756 float scaleY = iconHeight / windowCropHeight; 757 float scale = Math.min(1f, Math.max(scaleX, scaleY)); 758 759 float scaledCropWidth = windowCropWidth * scale; 760 float scaledCropHeight = windowCropHeight * scale; 761 float offsetX = (scaledCropWidth - iconWidth) / 2; 762 float offsetY = (scaledCropHeight - iconHeight) / 2; 763 764 // Calculate the window position to match the icon position. 765 tmpRectF.set(launcherIconBounds); 766 tmpRectF.offset(dragLayerBounds[0], dragLayerBounds[1]); 767 tmpRectF.offset(mDx.value, mDy.value); 768 Utilities.scaleRectFAboutCenter(tmpRectF, mIconScaleToFitScreen.value); 769 float windowTransX0 = tmpRectF.left - offsetX - crop.left * scale; 770 float windowTransY0 = tmpRectF.top - offsetY - crop.top * scale; 771 772 // Calculate the icon position. 773 floatingIconBounds.set(launcherIconBounds); 774 floatingIconBounds.offset(mDx.value, mDy.value); 775 Utilities.scaleRectFAboutCenter(floatingIconBounds, mIconScaleToFitScreen.value); 776 floatingIconBounds.left -= offsetX; 777 floatingIconBounds.top -= offsetY; 778 floatingIconBounds.right += offsetX; 779 floatingIconBounds.bottom += offsetY; 780 781 if (initOnly) { 782 // For the init pass, we want full alpha since the window is not yet ready. 783 floatingView.update(1f, 255, floatingIconBounds, percent, 0f, 784 mWindowRadius.value * scale, true /* isOpening */); 785 return; 786 } 787 788 ArrayList<SurfaceParams> params = new ArrayList<>(); 789 for (int i = appTargets.length - 1; i >= 0; i--) { 790 RemoteAnimationTargetCompat target = appTargets[i]; 791 SurfaceParams.Builder builder = new SurfaceParams.Builder(target.leash); 792 793 if (target.mode == MODE_OPENING) { 794 matrix.setScale(scale, scale); 795 if (rotationChange == 1) { 796 matrix.postTranslate(windowTransY0, 797 mDeviceProfile.widthPx - (windowTransX0 + scaledCropWidth)); 798 } else if (rotationChange == 2) { 799 matrix.postTranslate( 800 mDeviceProfile.widthPx - (windowTransX0 + scaledCropWidth), 801 mDeviceProfile.heightPx - (windowTransY0 + scaledCropHeight)); 802 } else if (rotationChange == 3) { 803 matrix.postTranslate( 804 mDeviceProfile.heightPx - (windowTransY0 + scaledCropHeight), 805 windowTransX0); 806 } else { 807 matrix.postTranslate(windowTransX0, windowTransY0); 808 } 809 810 floatingView.update(mIconAlpha.value, 255, floatingIconBounds, percent, 0f, 811 mWindowRadius.value * scale, true /* isOpening */); 812 builder.withMatrix(matrix) 813 .withWindowCrop(crop) 814 .withAlpha(1f - mIconAlpha.value) 815 .withCornerRadius(mWindowRadius.value) 816 .withShadowRadius(mShadowRadius.value); 817 } else if (target.mode == MODE_CLOSING) { 818 if (target.localBounds != null) { 819 final Rect localBounds = target.localBounds; 820 tmpPos.set(target.localBounds.left, target.localBounds.top); 821 } else { 822 tmpPos.set(target.position.x, target.position.y); 823 } 824 final Rect crop = new Rect(target.screenSpaceBounds); 825 crop.offsetTo(0, 0); 826 827 if ((rotationChange % 2) == 1) { 828 int tmp = crop.right; 829 crop.right = crop.bottom; 830 crop.bottom = tmp; 831 tmp = tmpPos.x; 832 tmpPos.x = tmpPos.y; 833 tmpPos.y = tmp; 834 } 835 matrix.setTranslate(tmpPos.x, tmpPos.y); 836 builder.withMatrix(matrix) 837 .withWindowCrop(crop) 838 .withAlpha(1f); 839 } 840 params.add(builder.build()); 841 } 842 843 if (navBarTarget != null) { 844 final SurfaceParams.Builder navBuilder = 845 new SurfaceParams.Builder(navBarTarget.leash); 846 if (mNavFadeIn.value > mNavFadeIn.getStartValue()) { 847 matrix.setScale(scale, scale); 848 matrix.postTranslate(windowTransX0, windowTransY0); 849 navBuilder.withMatrix(matrix) 850 .withWindowCrop(crop) 851 .withAlpha(mNavFadeIn.value); 852 } else { 853 navBuilder.withAlpha(mNavFadeOut.value); 854 } 855 params.add(navBuilder.build()); 856 } 857 858 surfaceApplier.scheduleApply(params.toArray(new SurfaceParams[params.size()])); 859 } 860 }; 861 appAnimator.addUpdateListener(listener); 862 // Since we added a start delay, call update here to init the FloatingIconView properly. 863 listener.onUpdate(0, true /* initOnly */); 864 865 // If app targets are translucent, do not animate the background as it causes a visible 866 // flicker when it resets itself at the end of its animation. 867 if (appTargetsAreTranslucent) { 868 animatorSet.play(appAnimator); 869 } else { 870 animatorSet.playTogether(appAnimator, getBackgroundAnimator()); 871 } 872 return animatorSet; 873 } 874 getOpeningWindowAnimatorsForWidget(LauncherAppWidgetHostView v, RemoteAnimationTargetCompat[] appTargets, RemoteAnimationTargetCompat[] wallpaperTargets, RemoteAnimationTargetCompat[] nonAppTargets, Rect windowTargetBounds, boolean appTargetsAreTranslucent)875 private Animator getOpeningWindowAnimatorsForWidget(LauncherAppWidgetHostView v, 876 RemoteAnimationTargetCompat[] appTargets, 877 RemoteAnimationTargetCompat[] wallpaperTargets, 878 RemoteAnimationTargetCompat[] nonAppTargets, Rect windowTargetBounds, 879 boolean appTargetsAreTranslucent) { 880 final RectF widgetBackgroundBounds = new RectF(); 881 final Rect appWindowCrop = new Rect(); 882 final Matrix matrix = new Matrix(); 883 RemoteAnimationTargets openingTargets = new RemoteAnimationTargets(appTargets, 884 wallpaperTargets, nonAppTargets, MODE_OPENING); 885 886 RemoteAnimationTargetCompat openingTarget = openingTargets.getFirstAppTarget(); 887 int fallbackBackgroundColor = 0; 888 if (openingTarget != null && supportsSSplashScreen()) { 889 fallbackBackgroundColor = mTaskStartParams.containsKey(openingTarget.taskId) 890 ? mTaskStartParams.get(openingTarget.taskId).second : 0; 891 mTaskStartParams.remove(openingTarget.taskId); 892 } 893 if (fallbackBackgroundColor == 0) { 894 fallbackBackgroundColor = 895 FloatingWidgetView.getDefaultBackgroundColor(mLauncher, openingTarget); 896 } 897 898 final float finalWindowRadius = mDeviceProfile.isMultiWindowMode 899 ? 0 : getWindowCornerRadius(mLauncher); 900 final FloatingWidgetView floatingView = FloatingWidgetView.getFloatingWidgetView(mLauncher, 901 v, widgetBackgroundBounds, 902 new Size(windowTargetBounds.width(), windowTargetBounds.height()), 903 finalWindowRadius, appTargetsAreTranslucent, fallbackBackgroundColor); 904 final float initialWindowRadius = supportsRoundedCornersOnWindows(mLauncher.getResources()) 905 ? floatingView.getInitialCornerRadius() : 0; 906 907 SurfaceTransactionApplier surfaceApplier = new SurfaceTransactionApplier(floatingView); 908 openingTargets.addReleaseCheck(surfaceApplier); 909 910 RemoteAnimationTargetCompat navBarTarget = openingTargets.getNavBarRemoteAnimationTarget(); 911 912 AnimatorSet animatorSet = new AnimatorSet(); 913 ValueAnimator appAnimator = ValueAnimator.ofFloat(0, 1); 914 appAnimator.setDuration(APP_LAUNCH_DURATION); 915 appAnimator.setInterpolator(LINEAR); 916 appAnimator.addListener(floatingView); 917 appAnimator.addListener(new AnimatorListenerAdapter() { 918 @Override 919 public void onAnimationEnd(Animator animation) { 920 openingTargets.release(); 921 } 922 }); 923 floatingView.setFastFinishRunnable(animatorSet::end); 924 925 appAnimator.addUpdateListener(new MultiValueUpdateListener() { 926 float mAppWindowScale = 1; 927 final FloatProp mWidgetForegroundAlpha = new FloatProp(1 /* start */, 928 0 /* end */, 0 /* delay */, 929 WIDGET_CROSSFADE_DURATION_MILLIS / 2 /* duration */, LINEAR); 930 final FloatProp mWidgetFallbackBackgroundAlpha = new FloatProp(0 /* start */, 931 1 /* end */, 0 /* delay */, 75 /* duration */, LINEAR); 932 final FloatProp mPreviewAlpha = new FloatProp(0 /* start */, 1 /* end */, 933 WIDGET_CROSSFADE_DURATION_MILLIS / 2 /* delay */, 934 WIDGET_CROSSFADE_DURATION_MILLIS / 2 /* duration */, LINEAR); 935 final FloatProp mWindowRadius = new FloatProp(initialWindowRadius, finalWindowRadius, 936 0 /* start */, APP_LAUNCH_DURATION, mOpeningInterpolator); 937 final FloatProp mCornerRadiusProgress = new FloatProp(0, 1, 0, APP_LAUNCH_DURATION, 938 mOpeningInterpolator); 939 940 // Window & widget background positioning bounds 941 final FloatProp mDx = new FloatProp(widgetBackgroundBounds.centerX(), 942 windowTargetBounds.centerX(), 0 /* delay */, APP_LAUNCH_DURATION, 943 mOpeningXInterpolator); 944 final FloatProp mDy = new FloatProp(widgetBackgroundBounds.centerY(), 945 windowTargetBounds.centerY(), 0 /* delay */, APP_LAUNCH_DURATION, 946 mOpeningInterpolator); 947 final FloatProp mWidth = new FloatProp(widgetBackgroundBounds.width(), 948 windowTargetBounds.width(), 0 /* delay */, APP_LAUNCH_DURATION, 949 mOpeningInterpolator); 950 final FloatProp mHeight = new FloatProp(widgetBackgroundBounds.height(), 951 windowTargetBounds.height(), 0 /* delay */, APP_LAUNCH_DURATION, 952 mOpeningInterpolator); 953 954 final FloatProp mNavFadeOut = new FloatProp(1f, 0f, 0, ANIMATION_NAV_FADE_OUT_DURATION, 955 NAV_FADE_OUT_INTERPOLATOR); 956 final FloatProp mNavFadeIn = new FloatProp(0f, 1f, ANIMATION_DELAY_NAV_FADE_IN, 957 ANIMATION_NAV_FADE_IN_DURATION, NAV_FADE_IN_INTERPOLATOR); 958 959 @Override 960 public void onUpdate(float percent, boolean initOnly) { 961 widgetBackgroundBounds.set(mDx.value - mWidth.value / 2f, 962 mDy.value - mHeight.value / 2f, mDx.value + mWidth.value / 2f, 963 mDy.value + mHeight.value / 2f); 964 // Set app window scaling factor to match widget background width 965 mAppWindowScale = widgetBackgroundBounds.width() / windowTargetBounds.width(); 966 // Crop scaled app window to match widget 967 appWindowCrop.set(0 /* left */, 0 /* top */, 968 Math.round(windowTargetBounds.width()) /* right */, 969 Math.round(widgetBackgroundBounds.height() / mAppWindowScale) /* bottom */); 970 matrix.setTranslate(widgetBackgroundBounds.left, widgetBackgroundBounds.top); 971 matrix.postScale(mAppWindowScale, mAppWindowScale, widgetBackgroundBounds.left, 972 widgetBackgroundBounds.top); 973 974 ArrayList<SurfaceParams> params = new ArrayList<>(); 975 float floatingViewAlpha = appTargetsAreTranslucent ? 1 - mPreviewAlpha.value : 1; 976 for (int i = appTargets.length - 1; i >= 0; i--) { 977 RemoteAnimationTargetCompat target = appTargets[i]; 978 SurfaceParams.Builder builder = new SurfaceParams.Builder(target.leash); 979 if (target.mode == MODE_OPENING) { 980 floatingView.update(widgetBackgroundBounds, floatingViewAlpha, 981 mWidgetForegroundAlpha.value, mWidgetFallbackBackgroundAlpha.value, 982 mCornerRadiusProgress.value); 983 builder.withMatrix(matrix) 984 .withWindowCrop(appWindowCrop) 985 .withAlpha(mPreviewAlpha.value) 986 .withCornerRadius(mWindowRadius.value / mAppWindowScale); 987 } 988 params.add(builder.build()); 989 } 990 991 if (navBarTarget != null) { 992 final SurfaceParams.Builder navBuilder = 993 new SurfaceParams.Builder(navBarTarget.leash); 994 if (mNavFadeIn.value > mNavFadeIn.getStartValue()) { 995 navBuilder.withMatrix(matrix) 996 .withWindowCrop(appWindowCrop) 997 .withAlpha(mNavFadeIn.value); 998 } else { 999 navBuilder.withAlpha(mNavFadeOut.value); 1000 } 1001 params.add(navBuilder.build()); 1002 } 1003 1004 surfaceApplier.scheduleApply(params.toArray(new SurfaceParams[params.size()])); 1005 } 1006 }); 1007 1008 // If app targets are translucent, do not animate the background as it causes a visible 1009 // flicker when it resets itself at the end of its animation. 1010 if (appTargetsAreTranslucent) { 1011 animatorSet.play(appAnimator); 1012 } else { 1013 animatorSet.playTogether(appAnimator, getBackgroundAnimator()); 1014 } 1015 return animatorSet; 1016 } 1017 1018 /** 1019 * Returns animator that controls depth/blur of the background. 1020 */ getBackgroundAnimator()1021 private ObjectAnimator getBackgroundAnimator() { 1022 // When launching an app from overview that doesn't map to a task, we still want to just 1023 // blur the wallpaper instead of the launcher surface as well 1024 boolean allowBlurringLauncher = mLauncher.getStateManager().getState() != OVERVIEW; 1025 DepthController depthController = mLauncher.getDepthController(); 1026 ObjectAnimator backgroundRadiusAnim = ObjectAnimator.ofFloat(depthController, DEPTH, 1027 BACKGROUND_APP.getDepth(mLauncher)) 1028 .setDuration(APP_LAUNCH_DURATION); 1029 if (allowBlurringLauncher) { 1030 final SurfaceControl dimLayer; 1031 if (BlurUtils.supportsBlursOnWindows()) { 1032 // Create a temporary effect layer, that lives on top of launcher, so we can apply 1033 // the blur to it. The EffectLayer will be fullscreen, which will help with caching 1034 // optimizations on the SurfaceFlinger side: 1035 // - Results would be able to be cached as a texture 1036 // - There won't be texture allocation overhead, because EffectLayers don't have 1037 // buffers 1038 ViewRootImpl viewRootImpl = mLauncher.getDragLayer().getViewRootImpl(); 1039 SurfaceControl parent = viewRootImpl != null 1040 ? viewRootImpl.getSurfaceControl() 1041 : null; 1042 dimLayer = new SurfaceControl.Builder() 1043 .setName("Blur layer") 1044 .setParent(parent) 1045 .setOpaque(false) 1046 .setHidden(false) 1047 .setEffectLayer() 1048 .build(); 1049 } else { 1050 dimLayer = null; 1051 } 1052 1053 depthController.setSurface(dimLayer); 1054 backgroundRadiusAnim.addListener(new AnimatorListenerAdapter() { 1055 @Override 1056 public void onAnimationStart(Animator animation) { 1057 depthController.setIsInLaunchTransition(true); 1058 } 1059 1060 @Override 1061 public void onAnimationEnd(Animator animation) { 1062 depthController.setIsInLaunchTransition(false); 1063 depthController.setSurface(null); 1064 if (dimLayer != null) { 1065 new SurfaceControl.Transaction() 1066 .remove(dimLayer) 1067 .apply(); 1068 } 1069 } 1070 }); 1071 } 1072 return backgroundRadiusAnim; 1073 } 1074 1075 /** 1076 * Registers remote animations used when closing apps to home screen. 1077 */ registerRemoteAnimations()1078 public void registerRemoteAnimations() { 1079 if (SEPARATE_RECENTS_ACTIVITY.get()) { 1080 return; 1081 } 1082 if (hasControlRemoteAppTransitionPermission()) { 1083 mWallpaperOpenRunner = createWallpaperOpenRunner(false /* fromUnlock */); 1084 1085 RemoteAnimationDefinitionCompat definition = new RemoteAnimationDefinitionCompat(); 1086 definition.addRemoteAnimation(WindowManagerWrapper.TRANSIT_WALLPAPER_OPEN, 1087 WindowManagerWrapper.ACTIVITY_TYPE_STANDARD, 1088 new RemoteAnimationAdapterCompat( 1089 new LauncherAnimationRunner(mHandler, mWallpaperOpenRunner, 1090 false /* startAtFrontOfQueue */), 1091 CLOSING_TRANSITION_DURATION_MS, 0 /* statusBarTransitionDelay */, 1092 mLauncher.getIApplicationThread())); 1093 1094 if (KEYGUARD_ANIMATION.get()) { 1095 mKeyguardGoingAwayRunner = createWallpaperOpenRunner(true /* fromUnlock */); 1096 definition.addRemoteAnimation( 1097 WindowManagerWrapper.TRANSIT_KEYGUARD_GOING_AWAY_ON_WALLPAPER, 1098 new RemoteAnimationAdapterCompat( 1099 new LauncherAnimationRunner( 1100 mHandler, mKeyguardGoingAwayRunner, 1101 true /* startAtFrontOfQueue */), 1102 CLOSING_TRANSITION_DURATION_MS, 0 /* statusBarTransitionDelay */, 1103 mLauncher.getIApplicationThread())); 1104 } 1105 1106 new ActivityCompat(mLauncher).registerRemoteAnimations(definition); 1107 } 1108 } 1109 1110 /** 1111 * Registers remote animations used when closing apps to home screen. 1112 */ registerRemoteTransitions()1113 public void registerRemoteTransitions() { 1114 if (SEPARATE_RECENTS_ACTIVITY.get()) { 1115 return; 1116 } 1117 if (hasControlRemoteAppTransitionPermission()) { 1118 mWallpaperOpenTransitionRunner = createWallpaperOpenRunner(false /* fromUnlock */); 1119 mLauncherOpenTransition = RemoteAnimationAdapterCompat.buildRemoteTransition( 1120 new LauncherAnimationRunner(mHandler, mWallpaperOpenTransitionRunner, 1121 false /* startAtFrontOfQueue */), mLauncher.getIApplicationThread()); 1122 mLauncherOpenTransition.addHomeOpenCheck(mLauncher.getComponentName()); 1123 SystemUiProxy.INSTANCE.getNoCreate().registerRemoteTransition(mLauncherOpenTransition); 1124 } 1125 } 1126 onActivityDestroyed()1127 public void onActivityDestroyed() { 1128 unregisterRemoteAnimations(); 1129 unregisterRemoteTransitions(); 1130 mStartingWindowListener.setTransitionManager(null); 1131 SystemUiProxy.INSTANCE.getNoCreate().setStartingWindowListener(null); 1132 } 1133 unregisterRemoteAnimations()1134 private void unregisterRemoteAnimations() { 1135 if (SEPARATE_RECENTS_ACTIVITY.get()) { 1136 return; 1137 } 1138 if (hasControlRemoteAppTransitionPermission()) { 1139 new ActivityCompat(mLauncher).unregisterRemoteAnimations(); 1140 1141 // Also clear strong references to the runners registered with the remote animation 1142 // definition so we don't have to wait for the system gc 1143 mWallpaperOpenRunner = null; 1144 mAppLaunchRunner = null; 1145 mKeyguardGoingAwayRunner = null; 1146 } 1147 } 1148 unregisterRemoteTransitions()1149 private void unregisterRemoteTransitions() { 1150 if (SEPARATE_RECENTS_ACTIVITY.get()) { 1151 return; 1152 } 1153 if (hasControlRemoteAppTransitionPermission()) { 1154 if (mLauncherOpenTransition == null) return; 1155 SystemUiProxy.INSTANCE.getNoCreate().unregisterRemoteTransition( 1156 mLauncherOpenTransition); 1157 mLauncherOpenTransition = null; 1158 mWallpaperOpenTransitionRunner = null; 1159 } 1160 } 1161 launcherIsATargetWithMode(RemoteAnimationTargetCompat[] targets, int mode)1162 private boolean launcherIsATargetWithMode(RemoteAnimationTargetCompat[] targets, int mode) { 1163 for (RemoteAnimationTargetCompat target : targets) { 1164 if (target.mode == mode && target.taskInfo != null 1165 // Compare component name instead of task-id because transitions will promote 1166 // the target up to the root task while getTaskId returns the leaf. 1167 && target.taskInfo.topActivity != null 1168 && target.taskInfo.topActivity.equals(mLauncher.getComponentName())) { 1169 return true; 1170 } 1171 } 1172 return false; 1173 } 1174 1175 /** 1176 * @return Runner that plays when user goes to Launcher 1177 * ie. pressing home, swiping up from nav bar. 1178 */ createWallpaperOpenRunner(boolean fromUnlock)1179 RemoteAnimationFactory createWallpaperOpenRunner(boolean fromUnlock) { 1180 return new WallpaperOpenLauncherAnimationRunner(mHandler, fromUnlock); 1181 } 1182 1183 /** 1184 * Animator that controls the transformations of the windows when unlocking the device. 1185 */ getUnlockWindowAnimator(RemoteAnimationTargetCompat[] appTargets, RemoteAnimationTargetCompat[] wallpaperTargets)1186 private Animator getUnlockWindowAnimator(RemoteAnimationTargetCompat[] appTargets, 1187 RemoteAnimationTargetCompat[] wallpaperTargets) { 1188 SurfaceTransactionApplier surfaceApplier = new SurfaceTransactionApplier(mDragLayer); 1189 ValueAnimator unlockAnimator = ValueAnimator.ofFloat(0, 1); 1190 unlockAnimator.setDuration(CLOSING_TRANSITION_DURATION_MS); 1191 float cornerRadius = mDeviceProfile.isMultiWindowMode ? 0 : 1192 QuickStepContract.getWindowCornerRadius(mLauncher); 1193 unlockAnimator.addListener(new AnimatorListenerAdapter() { 1194 @Override 1195 public void onAnimationStart(Animator animation) { 1196 SurfaceParams[] params = new SurfaceParams[appTargets.length]; 1197 for (int i = appTargets.length - 1; i >= 0; i--) { 1198 RemoteAnimationTargetCompat target = appTargets[i]; 1199 params[i] = new SurfaceParams.Builder(target.leash) 1200 .withAlpha(1f) 1201 .withWindowCrop(target.screenSpaceBounds) 1202 .withCornerRadius(cornerRadius) 1203 .build(); 1204 } 1205 surfaceApplier.scheduleApply(params); 1206 } 1207 }); 1208 return unlockAnimator; 1209 } 1210 getRotationChange(RemoteAnimationTargetCompat[] appTargets)1211 private static int getRotationChange(RemoteAnimationTargetCompat[] appTargets) { 1212 int rotationChange = 0; 1213 for (RemoteAnimationTargetCompat target : appTargets) { 1214 if (Math.abs(target.rotationChange) > Math.abs(rotationChange)) { 1215 rotationChange = target.rotationChange; 1216 } 1217 } 1218 return rotationChange; 1219 } 1220 1221 /** 1222 * Returns view on launcher that corresponds to the closing app in the list of app targets 1223 */ findLauncherView(RemoteAnimationTargetCompat[] appTargets)1224 private @Nullable View findLauncherView(RemoteAnimationTargetCompat[] appTargets) { 1225 for (RemoteAnimationTargetCompat appTarget : appTargets) { 1226 if (appTarget.mode == MODE_CLOSING) { 1227 View launcherView = findLauncherView(appTarget); 1228 if (launcherView != null) { 1229 return launcherView; 1230 } 1231 } 1232 } 1233 return null; 1234 } 1235 1236 /** 1237 * Returns view on launcher that corresponds to the {@param runningTaskTarget}. 1238 */ findLauncherView(RemoteAnimationTargetCompat runningTaskTarget)1239 private @Nullable View findLauncherView(RemoteAnimationTargetCompat runningTaskTarget) { 1240 if (runningTaskTarget == null || runningTaskTarget.taskInfo == null) { 1241 return null; 1242 } 1243 1244 final ComponentName[] taskInfoActivities = new ComponentName[] { 1245 runningTaskTarget.taskInfo.baseActivity, 1246 runningTaskTarget.taskInfo.origActivity, 1247 runningTaskTarget.taskInfo.realActivity, 1248 runningTaskTarget.taskInfo.topActivity}; 1249 1250 String packageName = null; 1251 for (ComponentName component : taskInfoActivities) { 1252 if (component != null && component.getPackageName() != null) { 1253 packageName = component.getPackageName(); 1254 break; 1255 } 1256 } 1257 1258 if (packageName == null) { 1259 return null; 1260 } 1261 1262 // Find the associated item info for the launch cookie (if available), note that predicted 1263 // apps actually have an id of -1, so use another default id here 1264 final ArrayList<IBinder> launchCookies = runningTaskTarget.taskInfo.launchCookies == null 1265 ? new ArrayList<>() 1266 : runningTaskTarget.taskInfo.launchCookies; 1267 1268 int launchCookieItemId = NO_MATCHING_ID; 1269 for (IBinder cookie : launchCookies) { 1270 Integer itemId = ObjectWrapper.unwrap(cookie); 1271 if (itemId != null) { 1272 launchCookieItemId = itemId; 1273 break; 1274 } 1275 } 1276 1277 return mLauncher.getFirstMatchForAppClose(launchCookieItemId, 1278 packageName, UserHandle.of(runningTaskTarget.taskInfo.userId)); 1279 } 1280 getDefaultWindowTargetRect()1281 private @NonNull RectF getDefaultWindowTargetRect() { 1282 RecentsView recentsView = mLauncher.getOverviewPanel(); 1283 PagedOrientationHandler orientationHandler = recentsView.getPagedOrientationHandler(); 1284 DeviceProfile dp = mLauncher.getDeviceProfile(); 1285 final int halfIconSize = dp.iconSizePx / 2; 1286 float primaryDimension = orientationHandler 1287 .getPrimaryValue(dp.availableWidthPx, dp.availableHeightPx); 1288 float secondaryDimension = orientationHandler 1289 .getSecondaryValue(dp.availableWidthPx, dp.availableHeightPx); 1290 final float targetX = primaryDimension / 2f; 1291 final float targetY = secondaryDimension - dp.hotseatBarSizePx; 1292 return new RectF(targetX - halfIconSize, targetY - halfIconSize, 1293 targetX + halfIconSize, targetY + halfIconSize); 1294 } 1295 1296 /** 1297 * Closing animator that animates the window into its final location on the workspace. 1298 */ getClosingWindowAnimators(AnimatorSet animation, RemoteAnimationTargetCompat[] targets, View launcherView, PointF velocityPxPerS)1299 private void getClosingWindowAnimators(AnimatorSet animation, 1300 RemoteAnimationTargetCompat[] targets, View launcherView, PointF velocityPxPerS) { 1301 FloatingIconView floatingIconView = null; 1302 FloatingWidgetView floatingWidget = null; 1303 RectF targetRect = new RectF(); 1304 1305 RemoteAnimationTargetCompat runningTaskTarget = null; 1306 boolean isTransluscent = false; 1307 for (RemoteAnimationTargetCompat target : targets) { 1308 if (target.mode == MODE_CLOSING) { 1309 runningTaskTarget = target; 1310 isTransluscent = runningTaskTarget.isTranslucent; 1311 break; 1312 } 1313 } 1314 1315 // Get floating view and target rect. 1316 if (launcherView instanceof LauncherAppWidgetHostView) { 1317 Size windowSize = new Size(mDeviceProfile.availableWidthPx, 1318 mDeviceProfile.availableHeightPx); 1319 int fallbackBackgroundColor = 1320 FloatingWidgetView.getDefaultBackgroundColor(mLauncher, runningTaskTarget); 1321 floatingWidget = FloatingWidgetView.getFloatingWidgetView(mLauncher, 1322 (LauncherAppWidgetHostView) launcherView, targetRect, windowSize, 1323 mDeviceProfile.isMultiWindowMode ? 0 : getWindowCornerRadius(mLauncher), 1324 isTransluscent, fallbackBackgroundColor); 1325 } else if (launcherView != null) { 1326 floatingIconView = getFloatingIconView(mLauncher, launcherView, 1327 true /* hideOriginal */, targetRect, false /* isOpening */); 1328 } else { 1329 targetRect.set(getDefaultWindowTargetRect()); 1330 } 1331 1332 final RectF startRect = new RectF(0, 0, mDeviceProfile.widthPx, mDeviceProfile.heightPx); 1333 RectFSpringAnim anim = new RectFSpringAnim(startRect, targetRect, mLauncher, 1334 mDeviceProfile); 1335 1336 // Hook up floating views to the closing window animators. 1337 final int rotationChange = getRotationChange(targets); 1338 Rect windowTargetBounds = getWindowTargetBounds(targets, rotationChange); 1339 if (floatingIconView != null) { 1340 anim.addAnimatorListener(floatingIconView); 1341 floatingIconView.setOnTargetChangeListener(anim::onTargetPositionChanged); 1342 floatingIconView.setFastFinishRunnable(anim::end); 1343 FloatingIconView finalFloatingIconView = floatingIconView; 1344 1345 // We want the window alpha to be 0 once this threshold is met, so that the 1346 // FolderIconView can be seen morphing into the icon shape. 1347 final float windowAlphaThreshold = 1f - SHAPE_PROGRESS_DURATION; 1348 1349 RectFSpringAnim.OnUpdateListener runner = new SpringAnimRunner(targets, targetRect, 1350 windowTargetBounds) { 1351 @Override 1352 public void onUpdate(RectF currentRectF, float progress) { 1353 finalFloatingIconView.update(1f, 255 /* fgAlpha */, currentRectF, progress, 1354 windowAlphaThreshold, getCornerRadius(progress), false); 1355 1356 super.onUpdate(currentRectF, progress); 1357 } 1358 }; 1359 anim.addOnUpdateListener(runner); 1360 } else if (floatingWidget != null) { 1361 anim.addAnimatorListener(floatingWidget); 1362 floatingWidget.setOnTargetChangeListener(anim::onTargetPositionChanged); 1363 floatingWidget.setFastFinishRunnable(anim::end); 1364 1365 final float floatingWidgetAlpha = isTransluscent ? 0 : 1; 1366 FloatingWidgetView finalFloatingWidget = floatingWidget; 1367 RectFSpringAnim.OnUpdateListener runner = new SpringAnimRunner(targets, targetRect, 1368 windowTargetBounds) { 1369 @Override 1370 public void onUpdate(RectF currentRectF, float progress) { 1371 final float fallbackBackgroundAlpha = 1372 1 - mapBoundToRange(progress, 0.8f, 1, 0, 1, EXAGGERATED_EASE); 1373 final float foregroundAlpha = 1374 mapBoundToRange(progress, 0.5f, 1, 0, 1, EXAGGERATED_EASE); 1375 finalFloatingWidget.update(currentRectF, floatingWidgetAlpha, foregroundAlpha, 1376 fallbackBackgroundAlpha, 1 - progress); 1377 1378 super.onUpdate(currentRectF, progress); 1379 } 1380 }; 1381 anim.addOnUpdateListener(runner); 1382 } 1383 1384 // Use a fixed velocity to start the animation. 1385 animation.addListener(new AnimatorListenerAdapter() { 1386 @Override 1387 public void onAnimationStart(Animator animation) { 1388 anim.start(mLauncher, velocityPxPerS); 1389 } 1390 }); 1391 } 1392 1393 /** 1394 * Closing window animator that moves the window down and offscreen. 1395 */ getFallbackClosingWindowAnimators(RemoteAnimationTargetCompat[] appTargets)1396 private Animator getFallbackClosingWindowAnimators(RemoteAnimationTargetCompat[] appTargets) { 1397 final int rotationChange = getRotationChange(appTargets); 1398 SurfaceTransactionApplier surfaceApplier = new SurfaceTransactionApplier(mDragLayer); 1399 Matrix matrix = new Matrix(); 1400 Point tmpPos = new Point(); 1401 Rect tmpRect = new Rect(); 1402 ValueAnimator closingAnimator = ValueAnimator.ofFloat(0, 1); 1403 int duration = CLOSING_TRANSITION_DURATION_MS; 1404 float windowCornerRadius = mDeviceProfile.isMultiWindowMode 1405 ? 0 : getWindowCornerRadius(mLauncher); 1406 float startShadowRadius = areAllTargetsTranslucent(appTargets) ? 0 : mMaxShadowRadius; 1407 closingAnimator.setDuration(duration); 1408 closingAnimator.addUpdateListener(new MultiValueUpdateListener() { 1409 FloatProp mDy = new FloatProp(0, mClosingWindowTransY, 0, duration, DEACCEL_1_7); 1410 FloatProp mScale = new FloatProp(1f, 1f, 0, duration, DEACCEL_1_7); 1411 FloatProp mAlpha = new FloatProp(1f, 0f, 25, 125, LINEAR); 1412 FloatProp mShadowRadius = new FloatProp(startShadowRadius, 0, 0, duration, 1413 DEACCEL_1_7); 1414 1415 @Override 1416 public void onUpdate(float percent, boolean initOnly) { 1417 SurfaceParams[] params = new SurfaceParams[appTargets.length]; 1418 for (int i = appTargets.length - 1; i >= 0; i--) { 1419 RemoteAnimationTargetCompat target = appTargets[i]; 1420 SurfaceParams.Builder builder = new SurfaceParams.Builder(target.leash); 1421 1422 if (target.localBounds != null) { 1423 tmpPos.set(target.localBounds.left, target.localBounds.top); 1424 } else { 1425 tmpPos.set(target.position.x, target.position.y); 1426 } 1427 1428 final Rect crop = new Rect(target.screenSpaceBounds); 1429 crop.offsetTo(0, 0); 1430 if (target.mode == MODE_CLOSING) { 1431 tmpRect.set(target.screenSpaceBounds); 1432 if ((rotationChange % 2) != 0) { 1433 final int right = crop.right; 1434 crop.right = crop.bottom; 1435 crop.bottom = right; 1436 } 1437 matrix.setScale(mScale.value, mScale.value, 1438 tmpRect.centerX(), 1439 tmpRect.centerY()); 1440 matrix.postTranslate(0, mDy.value); 1441 matrix.postTranslate(tmpPos.x, tmpPos.y); 1442 builder.withMatrix(matrix) 1443 .withWindowCrop(crop) 1444 .withAlpha(mAlpha.value) 1445 .withCornerRadius(windowCornerRadius) 1446 .withShadowRadius(mShadowRadius.value); 1447 } else if (target.mode == MODE_OPENING) { 1448 matrix.setTranslate(tmpPos.x, tmpPos.y); 1449 builder.withMatrix(matrix) 1450 .withWindowCrop(crop) 1451 .withAlpha(1f); 1452 } 1453 params[i] = builder.build(); 1454 } 1455 surfaceApplier.scheduleApply(params); 1456 } 1457 }); 1458 1459 return closingAnimator; 1460 } 1461 supportsSSplashScreen()1462 private boolean supportsSSplashScreen() { 1463 return hasControlRemoteAppTransitionPermission() 1464 && Utilities.ATLEAST_S 1465 && ENABLE_SHELL_STARTING_SURFACE; 1466 } 1467 1468 /** 1469 * Returns true if we have permission to control remote app transisions 1470 */ hasControlRemoteAppTransitionPermission()1471 public boolean hasControlRemoteAppTransitionPermission() { 1472 return mLauncher.checkSelfPermission(CONTROL_REMOTE_APP_TRANSITION_PERMISSION) 1473 == PackageManager.PERMISSION_GRANTED; 1474 } 1475 addCujInstrumentation(Animator anim, int cuj)1476 private void addCujInstrumentation(Animator anim, int cuj) { 1477 anim.addListener(new AnimationSuccessListener() { 1478 @Override 1479 public void onAnimationStart(Animator animation) { 1480 mDragLayer.getViewTreeObserver().addOnDrawListener( 1481 new ViewTreeObserver.OnDrawListener() { 1482 boolean mHandled = false; 1483 1484 @Override 1485 public void onDraw() { 1486 if (mHandled) { 1487 return; 1488 } 1489 mHandled = true; 1490 1491 InteractionJankMonitorWrapper.begin(mDragLayer, cuj); 1492 1493 mDragLayer.post(() -> 1494 mDragLayer.getViewTreeObserver().removeOnDrawListener( 1495 this)); 1496 } 1497 }); 1498 super.onAnimationStart(animation); 1499 } 1500 1501 @Override 1502 public void onAnimationCancel(Animator animation) { 1503 super.onAnimationCancel(animation); 1504 InteractionJankMonitorWrapper.cancel(cuj); 1505 } 1506 1507 @Override 1508 public void onAnimationSuccess(Animator animator) { 1509 InteractionJankMonitorWrapper.end(cuj); 1510 } 1511 }); 1512 } 1513 1514 /** 1515 * Remote animation runner for animation from the app to Launcher, including recents. 1516 */ 1517 protected class WallpaperOpenLauncherAnimationRunner implements RemoteAnimationFactory { 1518 1519 private final Handler mHandler; 1520 private final boolean mFromUnlock; 1521 WallpaperOpenLauncherAnimationRunner(Handler handler, boolean fromUnlock)1522 public WallpaperOpenLauncherAnimationRunner(Handler handler, boolean fromUnlock) { 1523 mHandler = handler; 1524 mFromUnlock = fromUnlock; 1525 } 1526 1527 @Override onCreateAnimation(int transit, RemoteAnimationTargetCompat[] appTargets, RemoteAnimationTargetCompat[] wallpaperTargets, RemoteAnimationTargetCompat[] nonAppTargets, LauncherAnimationRunner.AnimationResult result)1528 public void onCreateAnimation(int transit, 1529 RemoteAnimationTargetCompat[] appTargets, 1530 RemoteAnimationTargetCompat[] wallpaperTargets, 1531 RemoteAnimationTargetCompat[] nonAppTargets, 1532 LauncherAnimationRunner.AnimationResult result) { 1533 if (mLauncher.isDestroyed()) { 1534 AnimatorSet anim = new AnimatorSet(); 1535 anim.play(getFallbackClosingWindowAnimators(appTargets)); 1536 result.setAnimation(anim, mLauncher.getApplicationContext()); 1537 return; 1538 } 1539 1540 if (!mLauncher.hasBeenResumed()) { 1541 // If launcher is not resumed, wait until new async-frame after resume 1542 mLauncher.addOnResumeCallback(() -> 1543 postAsyncCallback(mHandler, () -> 1544 onCreateAnimation(transit, appTargets, wallpaperTargets, 1545 nonAppTargets, result))); 1546 return; 1547 } 1548 1549 if (mLauncher.hasSomeInvisibleFlag(PENDING_INVISIBLE_BY_WALLPAPER_ANIMATION)) { 1550 mLauncher.addForceInvisibleFlag(INVISIBLE_BY_PENDING_FLAGS); 1551 mLauncher.getStateManager().moveToRestState(); 1552 } 1553 1554 AnimatorSet anim = null; 1555 RemoteAnimationProvider provider = mRemoteAnimationProvider; 1556 if (provider != null) { 1557 anim = provider.createWindowAnimation(appTargets, wallpaperTargets); 1558 } 1559 1560 if (anim == null) { 1561 anim = new AnimatorSet(); 1562 1563 final boolean launcherIsForceInvisibleOrOpening = mLauncher.isForceInvisible() 1564 || launcherIsATargetWithMode(appTargets, MODE_OPENING); 1565 1566 View launcherView = findLauncherView(appTargets); 1567 boolean playFallBackAnimation = (launcherView == null 1568 && launcherIsForceInvisibleOrOpening) 1569 || mLauncher.getWorkspace().isOverlayShown(); 1570 1571 boolean playWorkspaceReveal = true; 1572 boolean skipAllAppsScale = false; 1573 if (mFromUnlock) { 1574 anim.play(getUnlockWindowAnimator(appTargets, wallpaperTargets)); 1575 } else if (ENABLE_BACK_SWIPE_HOME_ANIMATION.get() 1576 && !playFallBackAnimation) { 1577 // Use a fixed velocity to start the animation. 1578 float velocityPxPerS = DynamicResource.provider(mLauncher) 1579 .getDimension(R.dimen.unlock_staggered_velocity_dp_per_s); 1580 PointF velocity = new PointF(0, -velocityPxPerS); 1581 getClosingWindowAnimators(anim, appTargets, launcherView, velocity); 1582 if (!mLauncher.isInState(LauncherState.ALL_APPS)) { 1583 anim.play(new StaggeredWorkspaceAnim(mLauncher, velocity.y, 1584 true /* animateOverviewScrim */, launcherView).getAnimators()); 1585 // We play StaggeredWorkspaceAnim as a part of the closing window animation. 1586 playWorkspaceReveal = false; 1587 } else { 1588 // Skip scaling all apps, otherwise FloatingIconView will get wrong 1589 // layout bounds. 1590 skipAllAppsScale = true; 1591 } 1592 } else { 1593 anim.play(getFallbackClosingWindowAnimators(appTargets)); 1594 } 1595 1596 // Normally, we run the launcher content animation when we are transitioning 1597 // home, but if home is already visible, then we don't want to animate the 1598 // contents of launcher unless we know that we are animating home as a result 1599 // of the home button press with quickstep, which will result in launcher being 1600 // started on touch down, prior to the animation home (and won't be in the 1601 // targets list because it is already visible). In that case, we force 1602 // invisibility on touch down, and only reset it after the animation to home 1603 // is initialized. 1604 if (launcherIsForceInvisibleOrOpening) { 1605 addCujInstrumentation( 1606 anim, InteractionJankMonitorWrapper.CUJ_APP_CLOSE_TO_HOME); 1607 // Only register the content animation for cancellation when state changes 1608 mLauncher.getStateManager().setCurrentAnimation(anim); 1609 1610 if (mLauncher.isInState(LauncherState.ALL_APPS)) { 1611 Pair<AnimatorSet, Runnable> contentAnimator = 1612 getLauncherContentAnimator(false, LAUNCHER_RESUME_START_DELAY, 1613 skipAllAppsScale); 1614 anim.play(contentAnimator.first); 1615 anim.addListener(new AnimatorListenerAdapter() { 1616 @Override 1617 public void onAnimationEnd(Animator animation) { 1618 contentAnimator.second.run(); 1619 } 1620 }); 1621 } else { 1622 if (playWorkspaceReveal) { 1623 anim.play(new WorkspaceRevealAnim(mLauncher, false).getAnimators()); 1624 } 1625 } 1626 } 1627 } 1628 1629 mLauncher.clearForceInvisibleFlag(INVISIBLE_ALL); 1630 result.setAnimation(anim, mLauncher); 1631 } 1632 } 1633 1634 /** 1635 * Remote animation runner for animation to launch an app. 1636 */ 1637 private class AppLaunchAnimationRunner implements RemoteAnimationFactory { 1638 1639 private final View mV; 1640 private final RunnableList mOnEndCallback; 1641 AppLaunchAnimationRunner(View v, RunnableList onEndCallback)1642 AppLaunchAnimationRunner(View v, RunnableList onEndCallback) { 1643 mV = v; 1644 mOnEndCallback = onEndCallback; 1645 } 1646 1647 @Override onCreateAnimation(int transit, RemoteAnimationTargetCompat[] appTargets, RemoteAnimationTargetCompat[] wallpaperTargets, RemoteAnimationTargetCompat[] nonAppTargets, LauncherAnimationRunner.AnimationResult result)1648 public void onCreateAnimation(int transit, 1649 RemoteAnimationTargetCompat[] appTargets, 1650 RemoteAnimationTargetCompat[] wallpaperTargets, 1651 RemoteAnimationTargetCompat[] nonAppTargets, 1652 LauncherAnimationRunner.AnimationResult result) { 1653 AnimatorSet anim = new AnimatorSet(); 1654 boolean launcherClosing = 1655 launcherIsATargetWithMode(appTargets, MODE_CLOSING); 1656 1657 final boolean launchingFromWidget = mV instanceof LauncherAppWidgetHostView; 1658 final boolean launchingFromRecents = isLaunchingFromRecents(mV, appTargets); 1659 final boolean skipFirstFrame; 1660 if (launchingFromWidget) { 1661 composeWidgetLaunchAnimator(anim, (LauncherAppWidgetHostView) mV, appTargets, 1662 wallpaperTargets, nonAppTargets); 1663 addCujInstrumentation( 1664 anim, InteractionJankMonitorWrapper.CUJ_APP_LAUNCH_FROM_WIDGET); 1665 skipFirstFrame = true; 1666 } else if (launchingFromRecents) { 1667 composeRecentsLaunchAnimator(anim, mV, appTargets, wallpaperTargets, nonAppTargets, 1668 launcherClosing); 1669 addCujInstrumentation( 1670 anim, InteractionJankMonitorWrapper.CUJ_APP_LAUNCH_FROM_RECENTS); 1671 skipFirstFrame = true; 1672 } else { 1673 composeIconLaunchAnimator(anim, mV, appTargets, wallpaperTargets, nonAppTargets, 1674 launcherClosing); 1675 addCujInstrumentation(anim, InteractionJankMonitorWrapper.CUJ_APP_LAUNCH_FROM_ICON); 1676 skipFirstFrame = false; 1677 } 1678 1679 if (launcherClosing) { 1680 anim.addListener(mForceInvisibleListener); 1681 } 1682 1683 result.setAnimation(anim, mLauncher, mOnEndCallback::executeAllAndDestroy, 1684 skipFirstFrame); 1685 } 1686 1687 @Override onAnimationCancelled()1688 public void onAnimationCancelled() { 1689 mOnEndCallback.executeAllAndDestroy(); 1690 } 1691 } 1692 1693 /** 1694 * Class that holds all the variables for the app open animation. 1695 */ 1696 static class AnimOpenProperties { 1697 1698 public final int cropCenterXStart; 1699 public final int cropCenterYStart; 1700 public final int cropWidthStart; 1701 public final int cropHeightStart; 1702 1703 public final int cropCenterXEnd; 1704 public final int cropCenterYEnd; 1705 public final int cropWidthEnd; 1706 public final int cropHeightEnd; 1707 1708 public final float dX; 1709 public final float dY; 1710 1711 public final float initialAppIconScale; 1712 public final float finalAppIconScale; 1713 1714 public final float iconAlphaStart; 1715 AnimOpenProperties(Resources r, DeviceProfile dp, Rect windowTargetBounds, RectF launcherIconBounds, View view, int dragLayerLeft, int dragLayerTop, boolean hasSplashScreen, boolean hasDifferentAppIcon)1716 AnimOpenProperties(Resources r, DeviceProfile dp, Rect windowTargetBounds, 1717 RectF launcherIconBounds, View view, int dragLayerLeft, int dragLayerTop, 1718 boolean hasSplashScreen, boolean hasDifferentAppIcon) { 1719 // Scale the app icon to take up the entire screen. This simplifies the math when 1720 // animating the app window position / scale. 1721 float smallestSize = Math.min(windowTargetBounds.height(), windowTargetBounds.width()); 1722 float maxScaleX = smallestSize / launcherIconBounds.width(); 1723 float maxScaleY = smallestSize / launcherIconBounds.height(); 1724 float iconStartScale = 1f; 1725 if (view instanceof BubbleTextView && !(view.getParent() instanceof DeepShortcutView)) { 1726 Drawable dr = ((BubbleTextView) view).getIcon(); 1727 if (dr instanceof FastBitmapDrawable) { 1728 iconStartScale = ((FastBitmapDrawable) dr).getAnimatedScale(); 1729 } 1730 } 1731 1732 initialAppIconScale = iconStartScale; 1733 finalAppIconScale = Math.max(maxScaleX, maxScaleY); 1734 1735 // Animate the app icon to the center of the window bounds in screen coordinates. 1736 float centerX = windowTargetBounds.centerX() - dragLayerLeft; 1737 float centerY = windowTargetBounds.centerY() - dragLayerTop; 1738 1739 dX = centerX - launcherIconBounds.centerX(); 1740 dY = centerY - launcherIconBounds.centerY(); 1741 1742 iconAlphaStart = hasSplashScreen && !hasDifferentAppIcon ? 0 : 1f; 1743 1744 final int windowIconSize = ResourceUtils.getDimenByName("starting_surface_icon_size", 1745 r, 108); 1746 1747 cropCenterXStart = windowTargetBounds.centerX(); 1748 cropCenterYStart = windowTargetBounds.centerY(); 1749 1750 cropWidthStart = windowIconSize; 1751 cropHeightStart = windowIconSize; 1752 1753 cropWidthEnd = windowTargetBounds.width(); 1754 cropHeightEnd = windowTargetBounds.height(); 1755 1756 cropCenterXEnd = windowTargetBounds.centerX(); 1757 cropCenterYEnd = windowTargetBounds.centerY(); 1758 } 1759 } 1760 1761 private static class StartingWindowListener extends IStartingWindowListener.Stub { 1762 private QuickstepTransitionManager mTransitionManager; 1763 setTransitionManager(QuickstepTransitionManager transitionManager)1764 public void setTransitionManager(QuickstepTransitionManager transitionManager) { 1765 mTransitionManager = transitionManager; 1766 } 1767 1768 @Override onTaskLaunching(int taskId, int supportedType, int color)1769 public void onTaskLaunching(int taskId, int supportedType, int color) { 1770 mTransitionManager.mTaskStartParams.put(taskId, Pair.create(supportedType, color)); 1771 } 1772 } 1773 1774 /** 1775 * RectFSpringAnim update listener to be used for app to home animation. 1776 */ 1777 private class SpringAnimRunner implements RectFSpringAnim.OnUpdateListener { 1778 private final RemoteAnimationTargetCompat[] mAppTargets; 1779 private final Matrix mMatrix = new Matrix(); 1780 private final Point mTmpPos = new Point(); 1781 private final Rect mCurrentRect = new Rect(); 1782 private final float mStartRadius; 1783 private final float mEndRadius; 1784 private final SurfaceTransactionApplier mSurfaceApplier; 1785 private final Rect mWindowTargetBounds = new Rect(); 1786 1787 private final Rect mTmpRect = new Rect(); 1788 SpringAnimRunner(RemoteAnimationTargetCompat[] appTargets, RectF targetRect, Rect windowTargetBounds)1789 SpringAnimRunner(RemoteAnimationTargetCompat[] appTargets, RectF targetRect, 1790 Rect windowTargetBounds) { 1791 mAppTargets = appTargets; 1792 mStartRadius = QuickStepContract.getWindowCornerRadius(mLauncher); 1793 mEndRadius = Math.max(1, targetRect.width()) / 2f; 1794 mSurfaceApplier = new SurfaceTransactionApplier(mDragLayer); 1795 mWindowTargetBounds.set(windowTargetBounds); 1796 } 1797 getCornerRadius(float progress)1798 public float getCornerRadius(float progress) { 1799 return Utilities.mapRange(progress, mStartRadius, mEndRadius); 1800 } 1801 1802 @Override onUpdate(RectF currentRectF, float progress)1803 public void onUpdate(RectF currentRectF, float progress) { 1804 SurfaceParams[] params = new SurfaceParams[mAppTargets.length]; 1805 for (int i = mAppTargets.length - 1; i >= 0; i--) { 1806 RemoteAnimationTargetCompat target = mAppTargets[i]; 1807 SurfaceParams.Builder builder = new SurfaceParams.Builder(target.leash); 1808 1809 if (target.localBounds != null) { 1810 mTmpPos.set(target.localBounds.left, target.localBounds.top); 1811 } else { 1812 mTmpPos.set(target.position.x, target.position.y); 1813 } 1814 1815 if (target.mode == MODE_CLOSING) { 1816 currentRectF.round(mCurrentRect); 1817 1818 // Scale the target window to match the currentRectF. 1819 final float scale; 1820 1821 // We need to infer the crop (we crop the window to match the currentRectF). 1822 if (mWindowTargetBounds.height() > mWindowTargetBounds.width()) { 1823 scale = Math.min(1f, currentRectF.width() / mWindowTargetBounds.width()); 1824 1825 int unscaledHeight = (int) (mCurrentRect.height() * (1f / scale)); 1826 int croppedHeight = mWindowTargetBounds.height() - unscaledHeight; 1827 mTmpRect.set(0, 0, mWindowTargetBounds.width(), 1828 mWindowTargetBounds.height() - croppedHeight); 1829 } else { 1830 scale = Math.min(1f, currentRectF.height() / mWindowTargetBounds.height()); 1831 1832 int unscaledWidth = (int) (mCurrentRect.width() * (1f / scale)); 1833 int croppedWidth = mWindowTargetBounds.width() - unscaledWidth; 1834 mTmpRect.set(0, 0, mWindowTargetBounds.width() - croppedWidth, 1835 mWindowTargetBounds.height()); 1836 } 1837 1838 // Match size and position of currentRect. 1839 mMatrix.setScale(scale, scale); 1840 mMatrix.postTranslate(mCurrentRect.left, mCurrentRect.top); 1841 1842 builder.withMatrix(mMatrix) 1843 .withWindowCrop(mTmpRect) 1844 .withAlpha(getWindowAlpha(progress)) 1845 .withCornerRadius(getCornerRadius(progress) / scale); 1846 } else if (target.mode == MODE_OPENING) { 1847 mMatrix.setTranslate(mTmpPos.x, mTmpPos.y); 1848 builder.withMatrix(mMatrix) 1849 .withAlpha(1f); 1850 } 1851 params[i] = builder.build(); 1852 } 1853 mSurfaceApplier.scheduleApply(params); 1854 } 1855 getWindowAlpha(float progress)1856 protected float getWindowAlpha(float progress) { 1857 // Alpha interpolates between [1, 0] between progress values [start, end] 1858 final float start = 0f; 1859 final float end = 0.85f; 1860 1861 if (progress <= start) { 1862 return 1f; 1863 } 1864 if (progress >= end) { 1865 return 0f; 1866 } 1867 return Utilities.mapToRange(progress, start, end, 1, 0, ACCEL_1_5); 1868 } 1869 } 1870 } 1871