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.util.TypedValue.COMPLEX_UNIT_DIP; 20 21 import static com.android.launcher3.BaseActivity.INVISIBLE_ALL; 22 import static com.android.launcher3.BaseActivity.INVISIBLE_BY_APP_TRANSITIONS; 23 import static com.android.launcher3.BaseActivity.INVISIBLE_BY_PENDING_FLAGS; 24 import static com.android.launcher3.BaseActivity.PENDING_INVISIBLE_BY_WALLPAPER_ANIMATION; 25 import static com.android.launcher3.LauncherState.ALL_APPS; 26 import static com.android.launcher3.LauncherState.BACKGROUND_APP; 27 import static com.android.launcher3.LauncherState.OVERVIEW; 28 import static com.android.launcher3.Utilities.postAsyncCallback; 29 import static com.android.launcher3.allapps.AllAppsTransitionController.ALL_APPS_PROGRESS; 30 import static com.android.launcher3.anim.Interpolators.AGGRESSIVE_EASE; 31 import static com.android.launcher3.anim.Interpolators.DEACCEL_1_7; 32 import static com.android.launcher3.anim.Interpolators.EXAGGERATED_EASE; 33 import static com.android.launcher3.anim.Interpolators.LINEAR; 34 import static com.android.launcher3.config.FeatureFlags.KEYGUARD_ANIMATION; 35 import static com.android.launcher3.config.FeatureFlags.SEPARATE_RECENTS_ACTIVITY; 36 import static com.android.launcher3.dragndrop.DragLayer.ALPHA_INDEX_TRANSITIONS; 37 import static com.android.launcher3.statehandlers.DepthController.DEPTH; 38 import static com.android.launcher3.views.FloatingIconView.SHAPE_PROGRESS_DURATION; 39 import static com.android.quickstep.TaskUtils.taskIsATargetWithMode; 40 import static com.android.systemui.shared.system.QuickStepContract.getWindowCornerRadius; 41 import static com.android.systemui.shared.system.QuickStepContract.supportsRoundedCornersOnWindows; 42 import static com.android.systemui.shared.system.RemoteAnimationTargetCompat.MODE_CLOSING; 43 import static com.android.systemui.shared.system.RemoteAnimationTargetCompat.MODE_OPENING; 44 45 import android.animation.Animator; 46 import android.animation.AnimatorListenerAdapter; 47 import android.animation.AnimatorSet; 48 import android.animation.ObjectAnimator; 49 import android.animation.ValueAnimator; 50 import android.annotation.TargetApi; 51 import android.app.ActivityOptions; 52 import android.content.Context; 53 import android.content.pm.PackageManager; 54 import android.content.res.Resources; 55 import android.graphics.Matrix; 56 import android.graphics.Point; 57 import android.graphics.Rect; 58 import android.graphics.RectF; 59 import android.graphics.drawable.Drawable; 60 import android.os.Build; 61 import android.os.CancellationSignal; 62 import android.os.Handler; 63 import android.os.Looper; 64 import android.util.Pair; 65 import android.util.TypedValue; 66 import android.view.View; 67 68 import androidx.annotation.NonNull; 69 import androidx.annotation.Nullable; 70 71 import com.android.launcher3.DeviceProfile.OnDeviceProfileChangeListener; 72 import com.android.launcher3.allapps.AllAppsTransitionController; 73 import com.android.launcher3.config.FeatureFlags; 74 import com.android.launcher3.dragndrop.DragLayer; 75 import com.android.launcher3.shortcuts.DeepShortcutView; 76 import com.android.launcher3.statehandlers.DepthController; 77 import com.android.launcher3.util.DynamicResource; 78 import com.android.launcher3.util.MultiValueAlpha; 79 import com.android.launcher3.util.MultiValueAlpha.AlphaProperty; 80 import com.android.launcher3.views.FloatingIconView; 81 import com.android.quickstep.RemoteAnimationTargets; 82 import com.android.quickstep.util.MultiValueUpdateListener; 83 import com.android.quickstep.util.RemoteAnimationProvider; 84 import com.android.quickstep.util.StaggeredWorkspaceAnim; 85 import com.android.quickstep.util.SurfaceTransactionApplier; 86 import com.android.systemui.shared.system.ActivityCompat; 87 import com.android.systemui.shared.system.ActivityOptionsCompat; 88 import com.android.systemui.shared.system.QuickStepContract; 89 import com.android.systemui.shared.system.RemoteAnimationAdapterCompat; 90 import com.android.systemui.shared.system.RemoteAnimationDefinitionCompat; 91 import com.android.systemui.shared.system.RemoteAnimationRunnerCompat; 92 import com.android.systemui.shared.system.RemoteAnimationTargetCompat; 93 import com.android.systemui.shared.system.SyncRtSurfaceTransactionApplierCompat.SurfaceParams; 94 import com.android.systemui.shared.system.WindowManagerWrapper; 95 96 /** 97 * {@link LauncherAppTransitionManager} with Quickstep-specific app transitions for launching from 98 * home and/or all-apps. Not used for 3p launchers. 99 */ 100 @TargetApi(Build.VERSION_CODES.O) 101 @SuppressWarnings("unused") 102 public abstract class QuickstepAppTransitionManagerImpl extends LauncherAppTransitionManager 103 implements OnDeviceProfileChangeListener { 104 105 private static final String TAG = "QuickstepTransition"; 106 107 /** Duration of status bar animations. */ 108 public static final int STATUS_BAR_TRANSITION_DURATION = 120; 109 110 /** 111 * Since our animations decelerate heavily when finishing, we want to start status bar animations 112 * x ms before the ending. 113 */ 114 public static final int STATUS_BAR_TRANSITION_PRE_DELAY = 96; 115 116 private static final String CONTROL_REMOTE_APP_TRANSITION_PERMISSION = 117 "android.permission.CONTROL_REMOTE_APP_TRANSITION_ANIMATIONS"; 118 119 private static final long APP_LAUNCH_DURATION = 450; 120 // Use a shorter duration for x or y translation to create a curve effect 121 private static final long APP_LAUNCH_CURVED_DURATION = 250; 122 private static final long APP_LAUNCH_ALPHA_DURATION = 50; 123 private static final long APP_LAUNCH_ALPHA_START_DELAY = 25; 124 125 // We scale the durations for the downward app launch animations (minus the scale animation). 126 private static final float APP_LAUNCH_DOWN_DUR_SCALE_FACTOR = 0.8f; 127 private static final long APP_LAUNCH_DOWN_DURATION = 128 (long) (APP_LAUNCH_DURATION * APP_LAUNCH_DOWN_DUR_SCALE_FACTOR); 129 private static final long APP_LAUNCH_DOWN_CURVED_DURATION = 130 (long) (APP_LAUNCH_CURVED_DURATION * APP_LAUNCH_DOWN_DUR_SCALE_FACTOR); 131 private static final long APP_LAUNCH_ALPHA_DOWN_DURATION = 132 (long) (APP_LAUNCH_ALPHA_DURATION * APP_LAUNCH_DOWN_DUR_SCALE_FACTOR); 133 134 private static final long CROP_DURATION = 375; 135 private static final long RADIUS_DURATION = 375; 136 137 public static final int RECENTS_LAUNCH_DURATION = 336; 138 private static final int LAUNCHER_RESUME_START_DELAY = 100; 139 private static final int CLOSING_TRANSITION_DURATION_MS = 250; 140 141 protected static final int CONTENT_ALPHA_DURATION = 217; 142 protected static final int CONTENT_TRANSLATION_DURATION = 350; 143 144 // Progress = 0: All apps is fully pulled up, Progress = 1: All apps is fully pulled down. 145 public static final float ALL_APPS_PROGRESS_OFF_SCREEN = 1.3059858f; 146 147 protected final BaseQuickstepLauncher mLauncher; 148 149 private final DragLayer mDragLayer; 150 private final AlphaProperty mDragLayerAlpha; 151 152 final Handler mHandler; 153 private final boolean mIsRtl; 154 155 private final float mContentTransY; 156 private final float mWorkspaceTransY; 157 private final float mClosingWindowTransY; 158 159 private DeviceProfile mDeviceProfile; 160 161 private RemoteAnimationProvider mRemoteAnimationProvider; 162 // Strong refs to runners which are cleared when the launcher activity is destroyed 163 private WrappedAnimationRunnerImpl mWallpaperOpenRunner; 164 private WrappedAnimationRunnerImpl mAppLaunchRunner; 165 private WrappedAnimationRunnerImpl mKeyguardGoingAwayRunner; 166 167 private final AnimatorListenerAdapter mForceInvisibleListener = new AnimatorListenerAdapter() { 168 @Override 169 public void onAnimationStart(Animator animation) { 170 mLauncher.addForceInvisibleFlag(INVISIBLE_BY_APP_TRANSITIONS); 171 } 172 173 @Override 174 public void onAnimationEnd(Animator animation) { 175 mLauncher.clearForceInvisibleFlag(INVISIBLE_BY_APP_TRANSITIONS); 176 } 177 }; 178 QuickstepAppTransitionManagerImpl(Context context)179 public QuickstepAppTransitionManagerImpl(Context context) { 180 mLauncher = Launcher.cast(Launcher.getLauncher(context)); 181 mDragLayer = mLauncher.getDragLayer(); 182 mDragLayerAlpha = mDragLayer.getAlphaProperty(ALPHA_INDEX_TRANSITIONS); 183 mHandler = new Handler(Looper.getMainLooper()); 184 mIsRtl = Utilities.isRtl(mLauncher.getResources()); 185 mDeviceProfile = mLauncher.getDeviceProfile(); 186 187 Resources res = mLauncher.getResources(); 188 mContentTransY = res.getDimensionPixelSize(R.dimen.content_trans_y); 189 mWorkspaceTransY = res.getDimensionPixelSize(R.dimen.workspace_trans_y); 190 mClosingWindowTransY = res.getDimensionPixelSize(R.dimen.closing_window_trans_y); 191 192 mLauncher.addOnDeviceProfileChangeListener(this); 193 } 194 195 @Override onDeviceProfileChanged(DeviceProfile dp)196 public void onDeviceProfileChanged(DeviceProfile dp) { 197 mDeviceProfile = dp; 198 } 199 200 @Override supportsAdaptiveIconAnimation()201 public boolean supportsAdaptiveIconAnimation() { 202 return hasControlRemoteAppTransitionPermission() 203 && FeatureFlags.ADAPTIVE_ICON_WINDOW_ANIM.get(); 204 } 205 206 /** 207 * @return ActivityOptions with remote animations that controls how the window of the opening 208 * targets are displayed. 209 */ 210 @Override getActivityLaunchOptions(Launcher launcher, View v)211 public ActivityOptions getActivityLaunchOptions(Launcher launcher, View v) { 212 if (hasControlRemoteAppTransitionPermission()) { 213 boolean fromRecents = isLaunchingFromRecents(v, null /* targets */); 214 mAppLaunchRunner = new AppLaunchAnimationRunner(mHandler, v); 215 RemoteAnimationRunnerCompat runner = new WrappedLauncherAnimationRunner<>( 216 mAppLaunchRunner, true /* startAtFrontOfQueue */); 217 218 // Note that this duration is a guess as we do not know if the animation will be a 219 // recents launch or not for sure until we know the opening app targets. 220 long duration = fromRecents 221 ? RECENTS_LAUNCH_DURATION 222 : APP_LAUNCH_DURATION; 223 224 long statusBarTransitionDelay = duration - STATUS_BAR_TRANSITION_DURATION 225 - STATUS_BAR_TRANSITION_PRE_DELAY; 226 return ActivityOptionsCompat.makeRemoteAnimation(new RemoteAnimationAdapterCompat( 227 runner, duration, statusBarTransitionDelay)); 228 } 229 return super.getActivityLaunchOptions(launcher, v); 230 } 231 232 /** 233 * Whether the launch is a recents app transition and we should do a launch animation 234 * from the recents view. Note that if the remote animation targets are not provided, this 235 * may not always be correct as we may resolve the opening app to a task when the animation 236 * starts. 237 * 238 * @param v the view to launch from 239 * @param targets apps that are opening/closing 240 * @return true if the app is launching from recents, false if it most likely is not 241 */ isLaunchingFromRecents(@onNull View v, @Nullable RemoteAnimationTargetCompat[] targets)242 protected abstract boolean isLaunchingFromRecents(@NonNull View v, 243 @Nullable RemoteAnimationTargetCompat[] targets); 244 245 /** 246 * Composes the animations for a launch from the recents list. 247 * 248 * @param anim the animator set to add to 249 * @param v the launching view 250 * @param appTargets the apps that are opening/closing 251 * @param launcherClosing true if the launcher app is closing 252 */ composeRecentsLaunchAnimator(@onNull AnimatorSet anim, @NonNull View v, @NonNull RemoteAnimationTargetCompat[] appTargets, @NonNull RemoteAnimationTargetCompat[] wallpaperTargets, boolean launcherClosing)253 protected abstract void composeRecentsLaunchAnimator(@NonNull AnimatorSet anim, @NonNull View v, 254 @NonNull RemoteAnimationTargetCompat[] appTargets, 255 @NonNull RemoteAnimationTargetCompat[] wallpaperTargets, boolean launcherClosing); 256 257 /** 258 * Compose the animations for a launch from the app icon. 259 * 260 * @param anim the animation to add to 261 * @param v the launching view with the icon 262 * @param appTargets the list of opening/closing apps 263 * @param launcherClosing true if launcher is closing 264 */ composeIconLaunchAnimator(@onNull AnimatorSet anim, @NonNull View v, @NonNull RemoteAnimationTargetCompat[] appTargets, @NonNull RemoteAnimationTargetCompat[] wallpaperTargets, boolean launcherClosing)265 private void composeIconLaunchAnimator(@NonNull AnimatorSet anim, @NonNull View v, 266 @NonNull RemoteAnimationTargetCompat[] appTargets, 267 @NonNull RemoteAnimationTargetCompat[] wallpaperTargets, 268 boolean launcherClosing) { 269 // Set the state animation first so that any state listeners are called 270 // before our internal listeners. 271 mLauncher.getStateManager().setCurrentAnimation(anim); 272 273 Rect windowTargetBounds = getWindowTargetBounds(appTargets); 274 boolean isAllOpeningTargetTrs = true; 275 for (int i = 0; i < appTargets.length; i++) { 276 RemoteAnimationTargetCompat target = appTargets[i]; 277 if (target.mode == MODE_OPENING) { 278 isAllOpeningTargetTrs &= target.isTranslucent; 279 } 280 if (!isAllOpeningTargetTrs) break; 281 } 282 anim.play(getOpeningWindowAnimators(v, appTargets, wallpaperTargets, windowTargetBounds, 283 !isAllOpeningTargetTrs)); 284 if (launcherClosing) { 285 Pair<AnimatorSet, Runnable> launcherContentAnimator = 286 getLauncherContentAnimator(true /* isAppOpening */, 287 new float[] {0, -mContentTransY}); 288 anim.play(launcherContentAnimator.first); 289 anim.addListener(new AnimatorListenerAdapter() { 290 @Override 291 public void onAnimationEnd(Animator animation) { 292 launcherContentAnimator.second.run(); 293 } 294 }); 295 } else { 296 anim.addListener(new AnimatorListenerAdapter() { 297 @Override 298 public void onAnimationStart(Animator animation) { 299 mLauncher.addOnResumeCallback(() -> 300 ObjectAnimator.ofFloat(mLauncher.getDepthController(), DEPTH, 301 mLauncher.getStateManager().getState().getDepth(mLauncher)).start()); 302 } 303 }); 304 } 305 } 306 307 /** 308 * Return the window bounds of the opening target. 309 * In multiwindow mode, we need to get the final size of the opening app window target to help 310 * figure out where the floating view should animate to. 311 */ getWindowTargetBounds(RemoteAnimationTargetCompat[] appTargets)312 private Rect getWindowTargetBounds(RemoteAnimationTargetCompat[] appTargets) { 313 Rect bounds = new Rect(0, 0, mDeviceProfile.widthPx, mDeviceProfile.heightPx); 314 if (mLauncher.isInMultiWindowMode()) { 315 for (RemoteAnimationTargetCompat target : appTargets) { 316 if (target.mode == MODE_OPENING) { 317 bounds.set(target.screenSpaceBounds); 318 if (target.localBounds != null) { 319 bounds.set(target.localBounds); 320 } else { 321 bounds.offsetTo(target.position.x, target.position.y); 322 } 323 return bounds; 324 } 325 } 326 } 327 return bounds; 328 } 329 setRemoteAnimationProvider(final RemoteAnimationProvider animationProvider, CancellationSignal cancellationSignal)330 public void setRemoteAnimationProvider(final RemoteAnimationProvider animationProvider, 331 CancellationSignal cancellationSignal) { 332 mRemoteAnimationProvider = animationProvider; 333 cancellationSignal.setOnCancelListener(() -> { 334 if (animationProvider == mRemoteAnimationProvider) { 335 mRemoteAnimationProvider = null; 336 } 337 }); 338 } 339 340 /** 341 * Content is everything on screen except the background and the floating view (if any). 342 * 343 * @param isAppOpening True when this is called when an app is opening. 344 * False when this is called when an app is closing. 345 * @param trans Array that contains the start and end translation values for the content. 346 */ getLauncherContentAnimator(boolean isAppOpening, float[] trans)347 private Pair<AnimatorSet, Runnable> getLauncherContentAnimator(boolean isAppOpening, 348 float[] trans) { 349 AnimatorSet launcherAnimator = new AnimatorSet(); 350 Runnable endListener; 351 352 float[] alphas = isAppOpening 353 ? new float[] {1, 0} 354 : new float[] {0, 1}; 355 356 if (mLauncher.isInState(ALL_APPS)) { 357 // All Apps in portrait mode is full screen, so we only animate AllAppsContainerView. 358 final View appsView = mLauncher.getAppsView(); 359 final float startAlpha = appsView.getAlpha(); 360 final float startY = appsView.getTranslationY(); 361 appsView.setAlpha(alphas[0]); 362 appsView.setTranslationY(trans[0]); 363 364 ObjectAnimator alpha = ObjectAnimator.ofFloat(appsView, View.ALPHA, alphas); 365 alpha.setDuration(CONTENT_ALPHA_DURATION); 366 alpha.setInterpolator(LINEAR); 367 appsView.setLayerType(View.LAYER_TYPE_HARDWARE, null); 368 alpha.addListener(new AnimatorListenerAdapter() { 369 @Override 370 public void onAnimationEnd(Animator animation) { 371 appsView.setLayerType(View.LAYER_TYPE_NONE, null); 372 } 373 }); 374 ObjectAnimator transY = ObjectAnimator.ofFloat(appsView, View.TRANSLATION_Y, trans); 375 transY.setInterpolator(AGGRESSIVE_EASE); 376 transY.setDuration(CONTENT_TRANSLATION_DURATION); 377 378 launcherAnimator.play(alpha); 379 launcherAnimator.play(transY); 380 381 endListener = () -> { 382 appsView.setAlpha(startAlpha); 383 appsView.setTranslationY(startY); 384 appsView.setLayerType(View.LAYER_TYPE_NONE, null); 385 }; 386 } else if (mLauncher.isInState(OVERVIEW)) { 387 AllAppsTransitionController allAppsController = mLauncher.getAllAppsController(); 388 launcherAnimator.play(ObjectAnimator.ofFloat(allAppsController, ALL_APPS_PROGRESS, 389 allAppsController.getProgress(), ALL_APPS_PROGRESS_OFF_SCREEN)); 390 endListener = composeViewContentAnimator(launcherAnimator, alphas, trans); 391 } else { 392 mDragLayerAlpha.setValue(alphas[0]); 393 ObjectAnimator alpha = 394 ObjectAnimator.ofFloat(mDragLayerAlpha, MultiValueAlpha.VALUE, alphas); 395 alpha.setDuration(CONTENT_ALPHA_DURATION); 396 alpha.setInterpolator(LINEAR); 397 launcherAnimator.play(alpha); 398 399 Workspace workspace = mLauncher.getWorkspace(); 400 View currentPage = ((CellLayout) workspace.getChildAt(workspace.getCurrentPage())) 401 .getShortcutsAndWidgets(); 402 View hotseat = mLauncher.getHotseat(); 403 View qsb = mLauncher.findViewById(R.id.search_container_all_apps); 404 405 currentPage.setLayerType(View.LAYER_TYPE_HARDWARE, null); 406 hotseat.setLayerType(View.LAYER_TYPE_HARDWARE, null); 407 qsb.setLayerType(View.LAYER_TYPE_HARDWARE, null); 408 409 launcherAnimator.play(ObjectAnimator.ofFloat(currentPage, View.TRANSLATION_Y, trans)); 410 launcherAnimator.play(ObjectAnimator.ofFloat(hotseat, View.TRANSLATION_Y, trans)); 411 launcherAnimator.play(ObjectAnimator.ofFloat(qsb, View.TRANSLATION_Y, trans)); 412 413 // Pause page indicator animations as they lead to layer trashing. 414 mLauncher.getWorkspace().getPageIndicator().pauseAnimations(); 415 416 endListener = () -> { 417 currentPage.setTranslationY(0); 418 hotseat.setTranslationY(0); 419 qsb.setTranslationY(0); 420 421 currentPage.setLayerType(View.LAYER_TYPE_NONE, null); 422 hotseat.setLayerType(View.LAYER_TYPE_NONE, null); 423 qsb.setLayerType(View.LAYER_TYPE_NONE, null); 424 425 mDragLayerAlpha.setValue(1f); 426 mLauncher.getWorkspace().getPageIndicator().skipAnimationsToEnd(); 427 }; 428 } 429 return new Pair<>(launcherAnimator, endListener); 430 } 431 432 /** 433 * Compose recents view alpha and translation Y animation when launcher opens/closes apps. 434 * 435 * @param anim the animator set to add to 436 * @param alphas the alphas to animate to over time 437 * @param trans the translation Y values to animator to over time 438 * @return listener to run when the animation ends 439 */ composeViewContentAnimator(@onNull AnimatorSet anim, float[] alphas, float[] trans)440 protected abstract Runnable composeViewContentAnimator(@NonNull AnimatorSet anim, 441 float[] alphas, float[] trans); 442 443 /** 444 * @return Animator that controls the window of the opening targets from app icons. 445 */ getOpeningWindowAnimators(View v, RemoteAnimationTargetCompat[] appTargets, RemoteAnimationTargetCompat[] wallpaperTargets, Rect windowTargetBounds, boolean toggleVisibility)446 private Animator getOpeningWindowAnimators(View v, 447 RemoteAnimationTargetCompat[] appTargets, 448 RemoteAnimationTargetCompat[] wallpaperTargets, 449 Rect windowTargetBounds, boolean toggleVisibility) { 450 RectF bounds = new RectF(); 451 FloatingIconView floatingView = FloatingIconView.getFloatingIconView(mLauncher, v, 452 toggleVisibility, bounds, true /* isOpening */); 453 Rect crop = new Rect(); 454 Matrix matrix = new Matrix(); 455 456 RemoteAnimationTargets openingTargets = new RemoteAnimationTargets(appTargets, 457 wallpaperTargets, MODE_OPENING); 458 SurfaceTransactionApplier surfaceApplier = 459 new SurfaceTransactionApplier(floatingView); 460 openingTargets.addReleaseCheck(surfaceApplier); 461 462 // Scale the app icon to take up the entire screen. This simplifies the math when 463 // animating the app window position / scale. 464 float smallestSize = Math.min(windowTargetBounds.height(), windowTargetBounds.width()); 465 float maxScaleX = smallestSize / bounds.width(); 466 float maxScaleY = smallestSize / bounds.height(); 467 float scale = Math.max(maxScaleX, maxScaleY); 468 float startScale = 1f; 469 if (v instanceof BubbleTextView && !(v.getParent() instanceof DeepShortcutView)) { 470 Drawable dr = ((BubbleTextView) v).getIcon(); 471 if (dr instanceof FastBitmapDrawable) { 472 startScale = ((FastBitmapDrawable) dr).getAnimatedScale(); 473 } 474 } 475 final float initialStartScale = startScale; 476 477 int[] dragLayerBounds = new int[2]; 478 mDragLayer.getLocationOnScreen(dragLayerBounds); 479 480 // Animate the app icon to the center of the window bounds in screen coordinates. 481 float centerX = windowTargetBounds.centerX() - dragLayerBounds[0]; 482 float centerY = windowTargetBounds.centerY() - dragLayerBounds[1]; 483 484 float dX = centerX - bounds.centerX(); 485 float dY = centerY - bounds.centerY(); 486 487 boolean useUpwardAnimation = bounds.top > centerY 488 || Math.abs(dY) < mLauncher.getDeviceProfile().cellHeightPx; 489 final long xDuration = useUpwardAnimation ? APP_LAUNCH_CURVED_DURATION 490 : APP_LAUNCH_DOWN_DURATION; 491 final long yDuration = useUpwardAnimation ? APP_LAUNCH_DURATION 492 : APP_LAUNCH_DOWN_CURVED_DURATION; 493 final long alphaDuration = useUpwardAnimation ? APP_LAUNCH_ALPHA_DURATION 494 : APP_LAUNCH_ALPHA_DOWN_DURATION; 495 496 RectF targetBounds = new RectF(windowTargetBounds); 497 RectF iconBounds = new RectF(); 498 RectF temp = new RectF(); 499 Point tmpPos = new Point(); 500 501 AnimatorSet animatorSet = new AnimatorSet(); 502 ValueAnimator appAnimator = ValueAnimator.ofFloat(0, 1); 503 appAnimator.setDuration(APP_LAUNCH_DURATION); 504 appAnimator.setInterpolator(LINEAR); 505 appAnimator.addListener(floatingView); 506 appAnimator.addListener(new AnimatorListenerAdapter() { 507 @Override 508 public void onAnimationEnd(Animator animation) { 509 if (v instanceof BubbleTextView) { 510 ((BubbleTextView) v).setStayPressed(false); 511 } 512 openingTargets.release(); 513 } 514 }); 515 516 float shapeRevealDuration = APP_LAUNCH_DURATION * SHAPE_PROGRESS_DURATION; 517 518 final float startCrop; 519 final float endCrop; 520 if (mDeviceProfile.isVerticalBarLayout()) { 521 startCrop = windowTargetBounds.height(); 522 endCrop = windowTargetBounds.width(); 523 } else { 524 startCrop = windowTargetBounds.width(); 525 endCrop = windowTargetBounds.height(); 526 } 527 528 final float initialWindowRadius = supportsRoundedCornersOnWindows(mLauncher.getResources()) 529 ? startCrop / 2f : 0f; 530 final float windowRadius = mDeviceProfile.isMultiWindowMode 531 ? 0 : getWindowCornerRadius(mLauncher.getResources()); 532 appAnimator.addUpdateListener(new MultiValueUpdateListener() { 533 FloatProp mDx = new FloatProp(0, dX, 0, xDuration, AGGRESSIVE_EASE); 534 FloatProp mDy = new FloatProp(0, dY, 0, yDuration, AGGRESSIVE_EASE); 535 FloatProp mScale = new FloatProp(initialStartScale, scale, 0, APP_LAUNCH_DURATION, 536 EXAGGERATED_EASE); 537 FloatProp mIconAlpha = new FloatProp(1f, 0f, APP_LAUNCH_ALPHA_START_DELAY, 538 alphaDuration, LINEAR); 539 FloatProp mCroppedSize = new FloatProp(startCrop, endCrop, 0, CROP_DURATION, 540 EXAGGERATED_EASE); 541 FloatProp mWindowRadius = new FloatProp(initialWindowRadius, windowRadius, 0, 542 RADIUS_DURATION, EXAGGERATED_EASE); 543 544 @Override 545 public void onUpdate(float percent) { 546 // Calculate the size. 547 float width = bounds.width() * mScale.value; 548 float height = bounds.height() * mScale.value; 549 550 // Animate the crop so that it starts off as a square. 551 final int cropWidth; 552 final int cropHeight; 553 if (mDeviceProfile.isVerticalBarLayout()) { 554 cropWidth = (int) mCroppedSize.value; 555 cropHeight = windowTargetBounds.height(); 556 } else { 557 cropWidth = windowTargetBounds.width(); 558 cropHeight = (int) mCroppedSize.value; 559 } 560 crop.set(0, 0, cropWidth, cropHeight); 561 562 // Scale the size to match the crop. 563 float scaleX = width / cropWidth; 564 float scaleY = height / cropHeight; 565 float scale = Math.min(1f, Math.max(scaleX, scaleY)); 566 567 float scaledCropWidth = cropWidth * scale; 568 float scaledCropHeight = cropHeight * scale; 569 float offsetX = (scaledCropWidth - width) / 2; 570 float offsetY = (scaledCropHeight - height) / 2; 571 572 // Calculate the window position. 573 temp.set(bounds); 574 temp.offset(dragLayerBounds[0], dragLayerBounds[1]); 575 temp.offset(mDx.value, mDy.value); 576 Utilities.scaleRectFAboutCenter(temp, mScale.value); 577 float windowTransX0 = temp.left - offsetX; 578 float windowTransY0 = temp.top - offsetY; 579 580 // Calculate the icon position. 581 iconBounds.set(bounds); 582 iconBounds.offset(mDx.value, mDy.value); 583 Utilities.scaleRectFAboutCenter(iconBounds, mScale.value); 584 iconBounds.left -= offsetX; 585 iconBounds.top -= offsetY; 586 iconBounds.right += offsetX; 587 iconBounds.bottom += offsetY; 588 589 float croppedHeight = (windowTargetBounds.height() - crop.height()) * scale; 590 float croppedWidth = (windowTargetBounds.width() - crop.width()) * scale; 591 SurfaceParams[] params = new SurfaceParams[appTargets.length]; 592 for (int i = appTargets.length - 1; i >= 0; i--) { 593 RemoteAnimationTargetCompat target = appTargets[i]; 594 SurfaceParams.Builder builder = new SurfaceParams.Builder(target.leash); 595 596 if (target.mode == MODE_OPENING) { 597 matrix.setScale(scale, scale); 598 matrix.postTranslate(windowTransX0, windowTransY0); 599 600 floatingView.update(iconBounds, mIconAlpha.value, percent, 0f, 601 mWindowRadius.value * scale, true /* isOpening */); 602 builder.withMatrix(matrix) 603 .withWindowCrop(crop) 604 .withAlpha(1f - mIconAlpha.value) 605 .withCornerRadius(mWindowRadius.value); 606 } else { 607 tmpPos.set(target.position.x, target.position.y); 608 if (target.localBounds != null) { 609 final Rect localBounds = target.localBounds; 610 tmpPos.set(target.localBounds.left, target.localBounds.top); 611 } 612 613 matrix.setTranslate(tmpPos.x, tmpPos.y); 614 final Rect crop = new Rect(target.screenSpaceBounds); 615 crop.offsetTo(0, 0); 616 builder.withMatrix(matrix) 617 .withWindowCrop(crop) 618 .withAlpha(1f); 619 } 620 params[i] = builder.build(); 621 } 622 surfaceApplier.scheduleApply(params); 623 } 624 }); 625 626 // When launching an app from overview that doesn't map to a task, we still want to just 627 // blur the wallpaper instead of the launcher surface as well 628 boolean allowBlurringLauncher = mLauncher.getStateManager().getState() != OVERVIEW; 629 DepthController depthController = mLauncher.getDepthController(); 630 ObjectAnimator backgroundRadiusAnim = ObjectAnimator.ofFloat(depthController, DEPTH, 631 BACKGROUND_APP.getDepth(mLauncher)) 632 .setDuration(APP_LAUNCH_DURATION); 633 if (allowBlurringLauncher) { 634 depthController.setSurfaceToApp(RemoteAnimationProvider.findLowestOpaqueLayerTarget( 635 appTargets, MODE_OPENING)); 636 backgroundRadiusAnim.addListener(new AnimatorListenerAdapter() { 637 @Override 638 public void onAnimationEnd(Animator animation) { 639 depthController.setSurfaceToApp(null); 640 } 641 }); 642 } 643 644 animatorSet.playTogether(appAnimator, backgroundRadiusAnim); 645 return animatorSet; 646 } 647 648 /** 649 * Registers remote animations used when closing apps to home screen. 650 */ 651 @Override 652 public void registerRemoteAnimations() { 653 if (SEPARATE_RECENTS_ACTIVITY.get()) { 654 return; 655 } 656 if (hasControlRemoteAppTransitionPermission()) { 657 mWallpaperOpenRunner = createWallpaperOpenRunner(false /* fromUnlock */); 658 659 RemoteAnimationDefinitionCompat definition = new RemoteAnimationDefinitionCompat(); 660 definition.addRemoteAnimation(WindowManagerWrapper.TRANSIT_WALLPAPER_OPEN, 661 WindowManagerWrapper.ACTIVITY_TYPE_STANDARD, 662 new RemoteAnimationAdapterCompat( 663 new WrappedLauncherAnimationRunner<>(mWallpaperOpenRunner, 664 false /* startAtFrontOfQueue */), 665 CLOSING_TRANSITION_DURATION_MS, 0 /* statusBarTransitionDelay */)); 666 667 if (KEYGUARD_ANIMATION.get()) { 668 mKeyguardGoingAwayRunner = createWallpaperOpenRunner(true /* fromUnlock */); 669 definition.addRemoteAnimation( 670 WindowManagerWrapper.TRANSIT_KEYGUARD_GOING_AWAY_ON_WALLPAPER, 671 new RemoteAnimationAdapterCompat( 672 new WrappedLauncherAnimationRunner<>(mKeyguardGoingAwayRunner, 673 true /* startAtFrontOfQueue */), 674 CLOSING_TRANSITION_DURATION_MS, 0 /* statusBarTransitionDelay */)); 675 } 676 677 new ActivityCompat(mLauncher).registerRemoteAnimations(definition); 678 } 679 } 680 681 /** 682 * Unregisters all remote animations. 683 */ 684 @Override 685 public void unregisterRemoteAnimations() { 686 if (SEPARATE_RECENTS_ACTIVITY.get()) { 687 return; 688 } 689 if (hasControlRemoteAppTransitionPermission()) { 690 new ActivityCompat(mLauncher).unregisterRemoteAnimations(); 691 692 // Also clear strong references to the runners registered with the remote animation 693 // definition so we don't have to wait for the system gc 694 mWallpaperOpenRunner = null; 695 mAppLaunchRunner = null; 696 mKeyguardGoingAwayRunner = null; 697 } 698 } 699 700 private boolean launcherIsATargetWithMode(RemoteAnimationTargetCompat[] targets, int mode) { 701 return taskIsATargetWithMode(targets, mLauncher.getTaskId(), mode); 702 } 703 704 /** 705 * @return Runner that plays when user goes to Launcher 706 * ie. pressing home, swiping up from nav bar. 707 */ 708 WrappedAnimationRunnerImpl createWallpaperOpenRunner(boolean fromUnlock) { 709 return new WallpaperOpenLauncherAnimationRunner(mHandler, fromUnlock); 710 } 711 712 /** 713 * Animator that controls the transformations of the windows when unlocking the device. 714 */ 715 private Animator getUnlockWindowAnimator(RemoteAnimationTargetCompat[] appTargets, 716 RemoteAnimationTargetCompat[] wallpaperTargets) { 717 SurfaceTransactionApplier surfaceApplier = new SurfaceTransactionApplier(mDragLayer); 718 ValueAnimator unlockAnimator = ValueAnimator.ofFloat(0, 1); 719 unlockAnimator.setDuration(CLOSING_TRANSITION_DURATION_MS); 720 float cornerRadius = mDeviceProfile.isMultiWindowMode ? 0 : 721 QuickStepContract.getWindowCornerRadius(mLauncher.getResources()); 722 unlockAnimator.addListener(new AnimatorListenerAdapter() { 723 @Override 724 public void onAnimationStart(Animator animation) { 725 SurfaceParams[] params = new SurfaceParams[appTargets.length]; 726 for (int i = appTargets.length - 1; i >= 0; i--) { 727 RemoteAnimationTargetCompat target = appTargets[i]; 728 params[i] = new SurfaceParams.Builder(target.leash) 729 .withAlpha(1f) 730 .withWindowCrop(target.screenSpaceBounds) 731 .withCornerRadius(cornerRadius) 732 .build(); 733 } 734 surfaceApplier.scheduleApply(params); 735 } 736 }); 737 return unlockAnimator; 738 } 739 740 /** 741 * Animator that controls the transformations of the windows the targets that are closing. 742 */ 743 private Animator getClosingWindowAnimators(RemoteAnimationTargetCompat[] appTargets, 744 RemoteAnimationTargetCompat[] wallpaperTargets) { 745 SurfaceTransactionApplier surfaceApplier = new SurfaceTransactionApplier(mDragLayer); 746 Matrix matrix = new Matrix(); 747 Point tmpPos = new Point(); 748 ValueAnimator closingAnimator = ValueAnimator.ofFloat(0, 1); 749 int duration = CLOSING_TRANSITION_DURATION_MS; 750 float windowCornerRadius = mDeviceProfile.isMultiWindowMode 751 ? 0 : getWindowCornerRadius(mLauncher.getResources()); 752 closingAnimator.setDuration(duration); 753 closingAnimator.addUpdateListener(new MultiValueUpdateListener() { 754 FloatProp mDy = new FloatProp(0, mClosingWindowTransY, 0, duration, DEACCEL_1_7); 755 FloatProp mScale = new FloatProp(1f, 1f, 0, duration, DEACCEL_1_7); 756 FloatProp mAlpha = new FloatProp(1f, 0f, 25, 125, LINEAR); 757 758 @Override 759 public void onUpdate(float percent) { 760 SurfaceParams[] params = new SurfaceParams[appTargets.length]; 761 for (int i = appTargets.length - 1; i >= 0; i--) { 762 RemoteAnimationTargetCompat target = appTargets[i]; 763 SurfaceParams.Builder builder = new SurfaceParams.Builder(target.leash); 764 765 tmpPos.set(target.position.x, target.position.y); 766 if (target.localBounds != null) { 767 tmpPos.set(target.localBounds.left, target.localBounds.top); 768 } 769 770 if (target.mode == MODE_CLOSING) { 771 matrix.setScale(mScale.value, mScale.value, 772 target.screenSpaceBounds.centerX(), 773 target.screenSpaceBounds.centerY()); 774 matrix.postTranslate(0, mDy.value); 775 matrix.postTranslate(tmpPos.x, tmpPos.y); 776 builder.withMatrix(matrix) 777 .withAlpha(mAlpha.value) 778 .withCornerRadius(windowCornerRadius); 779 } else { 780 matrix.setTranslate(tmpPos.x, tmpPos.y); 781 builder.withMatrix(matrix) 782 .withAlpha(1f); 783 } 784 final Rect crop = new Rect(target.screenSpaceBounds); 785 crop.offsetTo(0, 0); 786 params[i] = builder 787 .withWindowCrop(crop) 788 .build(); 789 } 790 surfaceApplier.scheduleApply(params); 791 } 792 }); 793 794 return closingAnimator; 795 } 796 797 private boolean hasControlRemoteAppTransitionPermission() { 798 return mLauncher.checkSelfPermission(CONTROL_REMOTE_APP_TRANSITION_PERMISSION) 799 == PackageManager.PERMISSION_GRANTED; 800 } 801 802 /** 803 * Remote animation runner for animation from the app to Launcher, including recents. 804 */ 805 protected class WallpaperOpenLauncherAnimationRunner implements WrappedAnimationRunnerImpl { 806 807 private final Handler mHandler; 808 private final boolean mFromUnlock; 809 810 public WallpaperOpenLauncherAnimationRunner(Handler handler, boolean fromUnlock) { 811 mHandler = handler; 812 mFromUnlock = fromUnlock; 813 } 814 815 @Override 816 public Handler getHandler() { 817 return mHandler; 818 } 819 820 @Override 821 public void onCreateAnimation(RemoteAnimationTargetCompat[] appTargets, 822 RemoteAnimationTargetCompat[] wallpaperTargets, 823 LauncherAnimationRunner.AnimationResult result) { 824 if (mLauncher.isDestroyed()) { 825 AnimatorSet anim = new AnimatorSet(); 826 anim.play(getClosingWindowAnimators(appTargets, wallpaperTargets)); 827 result.setAnimation(anim, mLauncher.getApplicationContext()); 828 return; 829 } 830 831 if (!mLauncher.hasBeenResumed()) { 832 // If launcher is not resumed, wait until new async-frame after resume 833 mLauncher.addOnResumeCallback(() -> 834 postAsyncCallback(mHandler, () -> 835 onCreateAnimation(appTargets, wallpaperTargets, result))); 836 return; 837 } 838 839 if (mLauncher.hasSomeInvisibleFlag(PENDING_INVISIBLE_BY_WALLPAPER_ANIMATION)) { 840 mLauncher.addForceInvisibleFlag(INVISIBLE_BY_PENDING_FLAGS); 841 mLauncher.getStateManager().moveToRestState(); 842 } 843 844 AnimatorSet anim = null; 845 RemoteAnimationProvider provider = mRemoteAnimationProvider; 846 if (provider != null) { 847 anim = provider.createWindowAnimation(appTargets, wallpaperTargets); 848 } 849 850 if (anim == null) { 851 anim = new AnimatorSet(); 852 anim.play(mFromUnlock 853 ? getUnlockWindowAnimator(appTargets, wallpaperTargets) 854 : getClosingWindowAnimators(appTargets, wallpaperTargets)); 855 856 // Normally, we run the launcher content animation when we are transitioning 857 // home, but if home is already visible, then we don't want to animate the 858 // contents of launcher unless we know that we are animating home as a result 859 // of the home button press with quickstep, which will result in launcher being 860 // started on touch down, prior to the animation home (and won't be in the 861 // targets list because it is already visible). In that case, we force 862 // invisibility on touch down, and only reset it after the animation to home 863 // is initialized. 864 if (launcherIsATargetWithMode(appTargets, MODE_OPENING) 865 || mLauncher.isForceInvisible()) { 866 // Only register the content animation for cancellation when state changes 867 mLauncher.getStateManager().setCurrentAnimation(anim); 868 869 if (mLauncher.isInState(LauncherState.ALL_APPS)) { 870 Pair<AnimatorSet, Runnable> contentAnimator = 871 getLauncherContentAnimator(false /* isAppOpening */, 872 new float[] {-mContentTransY, 0}); 873 contentAnimator.first.setStartDelay(LAUNCHER_RESUME_START_DELAY); 874 anim.play(contentAnimator.first); 875 anim.addListener(new AnimatorListenerAdapter() { 876 @Override 877 public void onAnimationEnd(Animator animation) { 878 contentAnimator.second.run(); 879 } 880 }); 881 } else { 882 float velocityDpPerS = DynamicResource.provider(mLauncher) 883 .getDimension(R.dimen.unlock_staggered_velocity_dp_per_s); 884 float velocityPxPerS = TypedValue.applyDimension(COMPLEX_UNIT_DIP, 885 velocityDpPerS, mLauncher.getResources().getDisplayMetrics()); 886 anim.play(new StaggeredWorkspaceAnim(mLauncher, velocityPxPerS, false) 887 .getAnimators()); 888 } 889 } 890 } 891 892 mLauncher.clearForceInvisibleFlag(INVISIBLE_ALL); 893 result.setAnimation(anim, mLauncher); 894 } 895 } 896 897 /** 898 * Remote animation runner for animation to launch an app. 899 */ 900 private class AppLaunchAnimationRunner implements WrappedAnimationRunnerImpl { 901 902 private final Handler mHandler; 903 private final View mV; 904 905 AppLaunchAnimationRunner(Handler handler, View v) { 906 mHandler = handler; 907 mV = v; 908 } 909 910 @Override 911 public Handler getHandler() { 912 return mHandler; 913 } 914 915 @Override 916 public void onCreateAnimation(RemoteAnimationTargetCompat[] appTargets, 917 RemoteAnimationTargetCompat[] wallpaperTargets, 918 LauncherAnimationRunner.AnimationResult result) { 919 AnimatorSet anim = new AnimatorSet(); 920 921 boolean launcherClosing = 922 launcherIsATargetWithMode(appTargets, MODE_CLOSING); 923 924 if (isLaunchingFromRecents(mV, appTargets)) { 925 composeRecentsLaunchAnimator(anim, mV, appTargets, wallpaperTargets, 926 launcherClosing); 927 } else { 928 composeIconLaunchAnimator(anim, mV, appTargets, wallpaperTargets, 929 launcherClosing); 930 } 931 932 if (launcherClosing) { 933 anim.addListener(mForceInvisibleListener); 934 } 935 936 result.setAnimation(anim, mLauncher); 937 } 938 } 939 } 940