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 com.android.launcher3.BaseActivity.INVISIBLE_ALL; 20 import static com.android.launcher3.BaseActivity.INVISIBLE_BY_APP_TRANSITIONS; 21 import static com.android.launcher3.LauncherAnimUtils.SCALE_PROPERTY; 22 import static com.android.launcher3.LauncherState.ALL_APPS; 23 import static com.android.launcher3.LauncherState.NORMAL; 24 import static com.android.launcher3.LauncherState.OVERVIEW; 25 import static com.android.launcher3.Utilities.postAsyncCallback; 26 import static com.android.launcher3.allapps.AllAppsTransitionController.ALL_APPS_PROGRESS; 27 import static com.android.launcher3.anim.Interpolators.AGGRESSIVE_EASE; 28 import static com.android.launcher3.anim.Interpolators.DEACCEL_1_7; 29 import static com.android.launcher3.anim.Interpolators.LINEAR; 30 import static com.android.launcher3.dragndrop.DragLayer.ALPHA_INDEX_TRANSITIONS; 31 import static com.android.quickstep.TaskUtils.findTaskViewToLaunch; 32 import static com.android.quickstep.TaskUtils.getRecentsWindowAnimator; 33 import static com.android.quickstep.TaskUtils.taskIsATargetWithMode; 34 import static com.android.systemui.shared.recents.utilities.Utilities.getNextFrameNumber; 35 import static com.android.systemui.shared.recents.utilities.Utilities.getSurface; 36 import static com.android.systemui.shared.system.RemoteAnimationTargetCompat.MODE_CLOSING; 37 import static com.android.systemui.shared.system.RemoteAnimationTargetCompat.MODE_OPENING; 38 39 import android.animation.Animator; 40 import android.animation.AnimatorListenerAdapter; 41 import android.animation.AnimatorSet; 42 import android.animation.ObjectAnimator; 43 import android.animation.ValueAnimator; 44 import android.annotation.TargetApi; 45 import android.app.ActivityOptions; 46 import android.content.Context; 47 import android.content.pm.PackageManager; 48 import android.content.res.Resources; 49 import android.graphics.Matrix; 50 import android.graphics.Rect; 51 import android.graphics.drawable.Drawable; 52 import android.os.Build; 53 import android.os.CancellationSignal; 54 import android.os.Handler; 55 import android.os.Looper; 56 import android.util.Log; 57 import android.util.Pair; 58 import android.view.Surface; 59 import android.view.View; 60 import android.view.ViewGroup; 61 62 import com.android.launcher3.DeviceProfile.OnDeviceProfileChangeListener; 63 import com.android.launcher3.InsettableFrameLayout.LayoutParams; 64 import com.android.launcher3.allapps.AllAppsTransitionController; 65 import com.android.launcher3.anim.AnimatorPlaybackController; 66 import com.android.launcher3.anim.Interpolators; 67 import com.android.launcher3.dragndrop.DragLayer; 68 import com.android.launcher3.graphics.DrawableFactory; 69 import com.android.launcher3.shortcuts.DeepShortcutView; 70 import com.android.launcher3.util.MultiValueAlpha; 71 import com.android.launcher3.util.MultiValueAlpha.AlphaProperty; 72 import com.android.quickstep.util.ClipAnimationHelper; 73 import com.android.quickstep.util.MultiValueUpdateListener; 74 import com.android.quickstep.util.RemoteAnimationProvider; 75 import com.android.quickstep.views.RecentsView; 76 import com.android.quickstep.views.TaskView; 77 import com.android.systemui.shared.system.ActivityCompat; 78 import com.android.systemui.shared.system.ActivityOptionsCompat; 79 import com.android.systemui.shared.system.RemoteAnimationAdapterCompat; 80 import com.android.systemui.shared.system.RemoteAnimationDefinitionCompat; 81 import com.android.systemui.shared.system.RemoteAnimationRunnerCompat; 82 import com.android.systemui.shared.system.RemoteAnimationTargetCompat; 83 import com.android.systemui.shared.system.TransactionCompat; 84 import com.android.systemui.shared.system.WindowManagerWrapper; 85 86 /** 87 * Manages the opening and closing app transitions from Launcher. 88 */ 89 @TargetApi(Build.VERSION_CODES.O) 90 @SuppressWarnings("unused") 91 public class LauncherAppTransitionManagerImpl extends LauncherAppTransitionManager 92 implements OnDeviceProfileChangeListener { 93 94 private static final String TAG = "LauncherTransition"; 95 public static final int STATUS_BAR_TRANSITION_DURATION = 120; 96 97 private static final String CONTROL_REMOTE_APP_TRANSITION_PERMISSION = 98 "android.permission.CONTROL_REMOTE_APP_TRANSITION_ANIMATIONS"; 99 100 private static final int APP_LAUNCH_DURATION = 500; 101 // Use a shorter duration for x or y translation to create a curve effect 102 private static final int APP_LAUNCH_CURVED_DURATION = APP_LAUNCH_DURATION / 2; 103 // We scale the durations for the downward app launch animations (minus the scale animation). 104 private static final float APP_LAUNCH_DOWN_DUR_SCALE_FACTOR = 0.8f; 105 private static final int APP_LAUNCH_ALPHA_START_DELAY = 32; 106 private static final int APP_LAUNCH_ALPHA_DURATION = 50; 107 108 public static final int RECENTS_LAUNCH_DURATION = 336; 109 public static final int RECENTS_QUICKSCRUB_LAUNCH_DURATION = 300; 110 private static final int LAUNCHER_RESUME_START_DELAY = 100; 111 private static final int CLOSING_TRANSITION_DURATION_MS = 250; 112 113 // Progress = 0: All apps is fully pulled up, Progress = 1: All apps is fully pulled down. 114 public static final float ALL_APPS_PROGRESS_OFF_SCREEN = 1.3059858f; 115 116 private final Launcher mLauncher; 117 private final DragLayer mDragLayer; 118 private final AlphaProperty mDragLayerAlpha; 119 120 private final Handler mHandler; 121 private final boolean mIsRtl; 122 123 private final float mContentTransY; 124 private final float mWorkspaceTransY; 125 private final float mClosingWindowTransY; 126 127 private DeviceProfile mDeviceProfile; 128 private View mFloatingView; 129 130 private RemoteAnimationProvider mRemoteAnimationProvider; 131 132 private final AnimatorListenerAdapter mForceInvisibleListener = new AnimatorListenerAdapter() { 133 @Override 134 public void onAnimationStart(Animator animation) { 135 mLauncher.addForceInvisibleFlag(INVISIBLE_BY_APP_TRANSITIONS); 136 } 137 138 @Override 139 public void onAnimationEnd(Animator animation) { 140 mLauncher.clearForceInvisibleFlag(INVISIBLE_BY_APP_TRANSITIONS); 141 } 142 }; 143 144 public LauncherAppTransitionManagerImpl(Context context) { 145 mLauncher = Launcher.getLauncher(context); 146 mDragLayer = mLauncher.getDragLayer(); 147 mDragLayerAlpha = mDragLayer.getAlphaProperty(ALPHA_INDEX_TRANSITIONS); 148 mHandler = new Handler(Looper.getMainLooper()); 149 mIsRtl = Utilities.isRtl(mLauncher.getResources()); 150 mDeviceProfile = mLauncher.getDeviceProfile(); 151 152 Resources res = mLauncher.getResources(); 153 mContentTransY = res.getDimensionPixelSize(R.dimen.content_trans_y); 154 mWorkspaceTransY = res.getDimensionPixelSize(R.dimen.workspace_trans_y); 155 mClosingWindowTransY = res.getDimensionPixelSize(R.dimen.closing_window_trans_y); 156 157 mLauncher.addOnDeviceProfileChangeListener(this); 158 registerRemoteAnimations(); 159 } 160 161 @Override 162 public void onDeviceProfileChanged(DeviceProfile dp) { 163 mDeviceProfile = dp; 164 } 165 166 /** 167 * @return ActivityOptions with remote animations that controls how the window of the opening 168 * targets are displayed. 169 */ 170 @Override 171 public ActivityOptions getActivityLaunchOptions(Launcher launcher, View v) { 172 if (hasControlRemoteAppTransitionPermission()) { 173 RemoteAnimationRunnerCompat runner = new LauncherAnimationRunner(mHandler, 174 true /* startAtFrontOfQueue */) { 175 176 @Override 177 public void onCreateAnimation(RemoteAnimationTargetCompat[] targetCompats, 178 AnimationResult result) { 179 AnimatorSet anim = new AnimatorSet(); 180 181 boolean launcherClosing = 182 launcherIsATargetWithMode(targetCompats, MODE_CLOSING); 183 184 if (!composeRecentsLaunchAnimator(v, targetCompats, anim)) { 185 // Set the state animation first so that any state listeners are called 186 // before our internal listeners. 187 mLauncher.getStateManager().setCurrentAnimation(anim); 188 189 Rect windowTargetBounds = getWindowTargetBounds(targetCompats); 190 anim.play(getIconAnimator(v, windowTargetBounds)); 191 if (launcherClosing) { 192 Pair<AnimatorSet, Runnable> launcherContentAnimator = 193 getLauncherContentAnimator(true /* isAppOpening */); 194 anim.play(launcherContentAnimator.first); 195 anim.addListener(new AnimatorListenerAdapter() { 196 @Override 197 public void onAnimationEnd(Animator animation) { 198 launcherContentAnimator.second.run(); 199 } 200 }); 201 } 202 anim.play(getOpeningWindowAnimators(v, targetCompats, windowTargetBounds)); 203 } 204 205 if (launcherClosing) { 206 anim.addListener(mForceInvisibleListener); 207 } 208 209 result.setAnimation(anim); 210 } 211 }; 212 213 int duration = findTaskViewToLaunch(launcher, v, null) != null 214 ? RECENTS_LAUNCH_DURATION : APP_LAUNCH_DURATION; 215 int statusBarTransitionDelay = duration - STATUS_BAR_TRANSITION_DURATION; 216 return ActivityOptionsCompat.makeRemoteAnimation(new RemoteAnimationAdapterCompat( 217 runner, duration, statusBarTransitionDelay)); 218 } 219 return super.getActivityLaunchOptions(launcher, v); 220 } 221 222 /** 223 * Return the window bounds of the opening target. 224 * In multiwindow mode, we need to get the final size of the opening app window target to help 225 * figure out where the floating view should animate to. 226 */ 227 private Rect getWindowTargetBounds(RemoteAnimationTargetCompat[] targets) { 228 Rect bounds = new Rect(0, 0, mDeviceProfile.widthPx, mDeviceProfile.heightPx); 229 if (mLauncher.isInMultiWindowModeCompat()) { 230 for (RemoteAnimationTargetCompat target : targets) { 231 if (target.mode == MODE_OPENING) { 232 bounds.set(target.sourceContainerBounds); 233 bounds.offsetTo(target.position.x, target.position.y); 234 return bounds; 235 } 236 } 237 } 238 return bounds; 239 } 240 241 public void setRemoteAnimationProvider(final RemoteAnimationProvider animationProvider, 242 CancellationSignal cancellationSignal) { 243 mRemoteAnimationProvider = animationProvider; 244 cancellationSignal.setOnCancelListener(() -> { 245 if (animationProvider == mRemoteAnimationProvider) { 246 mRemoteAnimationProvider = null; 247 } 248 }); 249 } 250 251 /** 252 * Composes the animations for a launch from the recents list if possible. 253 */ 254 private boolean composeRecentsLaunchAnimator(View v, 255 RemoteAnimationTargetCompat[] targets, AnimatorSet target) { 256 // Ensure recents is actually visible 257 if (!mLauncher.getStateManager().getState().overviewUi) { 258 return false; 259 } 260 261 RecentsView recentsView = mLauncher.getOverviewPanel(); 262 boolean launcherClosing = launcherIsATargetWithMode(targets, MODE_CLOSING); 263 boolean skipLauncherChanges = !launcherClosing; 264 boolean isLaunchingFromQuickscrub = 265 recentsView.getQuickScrubController().isWaitingForTaskLaunch(); 266 267 TaskView taskView = findTaskViewToLaunch(mLauncher, v, targets); 268 if (taskView == null) { 269 return false; 270 } 271 272 int duration = isLaunchingFromQuickscrub 273 ? RECENTS_QUICKSCRUB_LAUNCH_DURATION 274 : RECENTS_LAUNCH_DURATION; 275 276 ClipAnimationHelper helper = new ClipAnimationHelper(); 277 target.play(getRecentsWindowAnimator(taskView, skipLauncherChanges, targets, helper) 278 .setDuration(duration)); 279 280 Animator childStateAnimation = null; 281 // Found a visible recents task that matches the opening app, lets launch the app from there 282 Animator launcherAnim; 283 final AnimatorListenerAdapter windowAnimEndListener; 284 if (launcherClosing) { 285 launcherAnim = recentsView.createAdjacentPageAnimForTaskLaunch(taskView, helper); 286 launcherAnim.setInterpolator(Interpolators.TOUCH_RESPONSE_INTERPOLATOR); 287 launcherAnim.setDuration(duration); 288 289 // Make sure recents gets fixed up by resetting task alphas and scales, etc. 290 windowAnimEndListener = new AnimatorListenerAdapter() { 291 @Override 292 public void onAnimationEnd(Animator animation) { 293 mLauncher.getStateManager().moveToRestState(); 294 mLauncher.getStateManager().reapplyState(); 295 } 296 }; 297 } else { 298 AnimatorPlaybackController controller = 299 mLauncher.getStateManager().createAnimationToNewWorkspace(NORMAL, duration); 300 controller.dispatchOnStart(); 301 childStateAnimation = controller.getTarget(); 302 launcherAnim = controller.getAnimationPlayer().setDuration(duration); 303 windowAnimEndListener = new AnimatorListenerAdapter() { 304 @Override 305 public void onAnimationEnd(Animator animation) { 306 mLauncher.getStateManager().goToState(NORMAL, false); 307 } 308 }; 309 } 310 target.play(launcherAnim); 311 312 // Set the current animation first, before adding windowAnimEndListener. Setting current 313 // animation adds some listeners which need to be called before windowAnimEndListener 314 // (the ordering of listeners matter in this case). 315 mLauncher.getStateManager().setCurrentAnimation(target, childStateAnimation); 316 target.addListener(windowAnimEndListener); 317 return true; 318 } 319 320 /** 321 * Content is everything on screen except the background and the floating view (if any). 322 * 323 * @param isAppOpening True when this is called when an app is opening. 324 * False when this is called when an app is closing. 325 */ 326 private Pair<AnimatorSet, Runnable> getLauncherContentAnimator(boolean isAppOpening) { 327 AnimatorSet launcherAnimator = new AnimatorSet(); 328 Runnable endListener; 329 330 float[] alphas = isAppOpening 331 ? new float[] {1, 0} 332 : new float[] {0, 1}; 333 float[] trans = isAppOpening 334 ? new float[] {0, mContentTransY} 335 : new float[] {-mContentTransY, 0}; 336 337 if (mLauncher.isInState(ALL_APPS)) { 338 // All Apps in portrait mode is full screen, so we only animate AllAppsContainerView. 339 final View appsView = mLauncher.getAppsView(); 340 final float startAlpha = appsView.getAlpha(); 341 final float startY = appsView.getTranslationY(); 342 appsView.setAlpha(alphas[0]); 343 appsView.setTranslationY(trans[0]); 344 345 ObjectAnimator alpha = ObjectAnimator.ofFloat(appsView, View.ALPHA, alphas); 346 alpha.setDuration(217); 347 alpha.setInterpolator(LINEAR); 348 appsView.setLayerType(View.LAYER_TYPE_HARDWARE, null); 349 alpha.addListener(new AnimatorListenerAdapter() { 350 @Override 351 public void onAnimationEnd(Animator animation) { 352 appsView.setLayerType(View.LAYER_TYPE_NONE, null); 353 } 354 }); 355 ObjectAnimator transY = ObjectAnimator.ofFloat(appsView, View.TRANSLATION_Y, trans); 356 transY.setInterpolator(AGGRESSIVE_EASE); 357 transY.setDuration(350); 358 359 launcherAnimator.play(alpha); 360 launcherAnimator.play(transY); 361 362 endListener = () -> { 363 appsView.setAlpha(startAlpha); 364 appsView.setTranslationY(startY); 365 appsView.setLayerType(View.LAYER_TYPE_NONE, null); 366 }; 367 } else if (mLauncher.isInState(OVERVIEW)) { 368 AllAppsTransitionController allAppsController = mLauncher.getAllAppsController(); 369 launcherAnimator.play(ObjectAnimator.ofFloat(allAppsController, ALL_APPS_PROGRESS, 370 allAppsController.getProgress(), ALL_APPS_PROGRESS_OFF_SCREEN)); 371 372 View overview = mLauncher.getOverviewPanelContainer(); 373 ObjectAnimator alpha = ObjectAnimator.ofFloat(overview, View.ALPHA, alphas); 374 alpha.setDuration(217); 375 alpha.setInterpolator(LINEAR); 376 launcherAnimator.play(alpha); 377 378 ObjectAnimator transY = ObjectAnimator.ofFloat(overview, View.TRANSLATION_Y, trans); 379 transY.setInterpolator(AGGRESSIVE_EASE); 380 transY.setDuration(350); 381 launcherAnimator.play(transY); 382 383 overview.setLayerType(View.LAYER_TYPE_HARDWARE, null); 384 385 endListener = () -> { 386 overview.setLayerType(View.LAYER_TYPE_NONE, null); 387 overview.setAlpha(1f); 388 overview.setTranslationY(0f); 389 mLauncher.getStateManager().reapplyState(); 390 }; 391 } else { 392 mDragLayerAlpha.setValue(alphas[0]); 393 ObjectAnimator alpha = 394 ObjectAnimator.ofFloat(mDragLayerAlpha, MultiValueAlpha.VALUE, alphas); 395 alpha.setDuration(217); 396 alpha.setInterpolator(LINEAR); 397 launcherAnimator.play(alpha); 398 399 mDragLayer.setTranslationY(trans[0]); 400 ObjectAnimator transY = ObjectAnimator.ofFloat(mDragLayer, View.TRANSLATION_Y, trans); 401 transY.setInterpolator(AGGRESSIVE_EASE); 402 transY.setDuration(350); 403 launcherAnimator.play(transY); 404 405 mDragLayer.getScrim().hideSysUiScrim(true); 406 // Pause page indicator animations as they lead to layer trashing. 407 mLauncher.getWorkspace().getPageIndicator().pauseAnimations(); 408 mDragLayer.setLayerType(View.LAYER_TYPE_HARDWARE, null); 409 410 endListener = this::resetContentView; 411 } 412 return new Pair<>(launcherAnimator, endListener); 413 } 414 415 /** 416 * @return Animator that controls the icon used to launch the target. 417 */ 418 private AnimatorSet getIconAnimator(View v, Rect windowTargetBounds) { 419 final boolean isBubbleTextView = v instanceof BubbleTextView; 420 mFloatingView = new View(mLauncher); 421 if (isBubbleTextView && v.getTag() instanceof ItemInfoWithIcon ) { 422 // Create a copy of the app icon 423 mFloatingView.setBackground( 424 DrawableFactory.get(mLauncher).newIcon((ItemInfoWithIcon) v.getTag())); 425 } 426 427 // Position the floating view exactly on top of the original 428 Rect rect = new Rect(); 429 final boolean fromDeepShortcutView = v.getParent() instanceof DeepShortcutView; 430 if (fromDeepShortcutView) { 431 // Deep shortcut views have their icon drawn in a separate view. 432 DeepShortcutView view = (DeepShortcutView) v.getParent(); 433 mDragLayer.getDescendantRectRelativeToSelf(view.getIconView(), rect); 434 } else { 435 mDragLayer.getDescendantRectRelativeToSelf(v, rect); 436 } 437 int viewLocationLeft = rect.left; 438 int viewLocationTop = rect.top; 439 440 float startScale = 1f; 441 if (isBubbleTextView && !fromDeepShortcutView) { 442 BubbleTextView btv = (BubbleTextView) v; 443 btv.getIconBounds(rect); 444 Drawable dr = btv.getIcon(); 445 if (dr instanceof FastBitmapDrawable) { 446 startScale = ((FastBitmapDrawable) dr).getAnimatedScale(); 447 } 448 } else { 449 rect.set(0, 0, rect.width(), rect.height()); 450 } 451 viewLocationLeft += rect.left; 452 viewLocationTop += rect.top; 453 int viewLocationStart = mIsRtl 454 ? windowTargetBounds.width() - rect.right 455 : viewLocationLeft; 456 LayoutParams lp = new LayoutParams(rect.width(), rect.height()); 457 lp.ignoreInsets = true; 458 lp.setMarginStart(viewLocationStart); 459 lp.topMargin = viewLocationTop; 460 mFloatingView.setLayoutParams(lp); 461 462 // Set the properties here already to make sure they'are available when running the first 463 // animation frame. 464 mFloatingView.setLeft(viewLocationLeft); 465 mFloatingView.setTop(viewLocationTop); 466 mFloatingView.setRight(viewLocationLeft + rect.width()); 467 mFloatingView.setBottom(viewLocationTop + rect.height()); 468 469 // Swap the two views in place. 470 ((ViewGroup) mDragLayer.getParent()).addView(mFloatingView); 471 v.setVisibility(View.INVISIBLE); 472 473 AnimatorSet appIconAnimatorSet = new AnimatorSet(); 474 int[] dragLayerBounds = new int[2]; 475 mDragLayer.getLocationOnScreen(dragLayerBounds); 476 477 // Animate the app icon to the center of the window bounds in screen coordinates. 478 float centerX = windowTargetBounds.centerX() - dragLayerBounds[0]; 479 float centerY = windowTargetBounds.centerY() - dragLayerBounds[1]; 480 481 float xPosition = mIsRtl 482 ? windowTargetBounds.width() - lp.getMarginStart() - rect.width() 483 : lp.getMarginStart(); 484 float dX = centerX - xPosition - (lp.width / 2); 485 float dY = centerY - lp.topMargin - (lp.height / 2); 486 487 ObjectAnimator x = ObjectAnimator.ofFloat(mFloatingView, View.TRANSLATION_X, 0f, dX); 488 ObjectAnimator y = ObjectAnimator.ofFloat(mFloatingView, View.TRANSLATION_Y, 0f, dY); 489 490 // Use upward animation for apps that are either on the bottom half of the screen, or are 491 // relatively close to the center. 492 boolean useUpwardAnimation = lp.topMargin > centerY 493 || Math.abs(dY) < mLauncher.getDeviceProfile().cellHeightPx; 494 if (useUpwardAnimation) { 495 x.setDuration(APP_LAUNCH_CURVED_DURATION); 496 y.setDuration(APP_LAUNCH_DURATION); 497 } else { 498 x.setDuration((long) (APP_LAUNCH_DOWN_DUR_SCALE_FACTOR * APP_LAUNCH_DURATION)); 499 y.setDuration((long) (APP_LAUNCH_DOWN_DUR_SCALE_FACTOR * APP_LAUNCH_CURVED_DURATION)); 500 } 501 x.setInterpolator(AGGRESSIVE_EASE); 502 y.setInterpolator(AGGRESSIVE_EASE); 503 appIconAnimatorSet.play(x); 504 appIconAnimatorSet.play(y); 505 506 // Scale the app icon to take up the entire screen. This simplifies the math when 507 // animating the app window position / scale. 508 float maxScaleX = windowTargetBounds.width() / (float) rect.width(); 509 float maxScaleY = windowTargetBounds.height() / (float) rect.height(); 510 float scale = Math.max(maxScaleX, maxScaleY); 511 ObjectAnimator scaleAnim = ObjectAnimator 512 .ofFloat(mFloatingView, SCALE_PROPERTY, startScale, scale); 513 scaleAnim.setDuration(APP_LAUNCH_DURATION) 514 .setInterpolator(Interpolators.EXAGGERATED_EASE); 515 appIconAnimatorSet.play(scaleAnim); 516 517 // Fade out the app icon. 518 ObjectAnimator alpha = ObjectAnimator.ofFloat(mFloatingView, View.ALPHA, 1f, 0f); 519 if (useUpwardAnimation) { 520 alpha.setStartDelay(APP_LAUNCH_ALPHA_START_DELAY); 521 alpha.setDuration(APP_LAUNCH_ALPHA_DURATION); 522 } else { 523 alpha.setStartDelay((long) (APP_LAUNCH_DOWN_DUR_SCALE_FACTOR 524 * APP_LAUNCH_ALPHA_START_DELAY)); 525 alpha.setDuration((long) (APP_LAUNCH_DOWN_DUR_SCALE_FACTOR * APP_LAUNCH_ALPHA_DURATION)); 526 } 527 alpha.setInterpolator(LINEAR); 528 appIconAnimatorSet.play(alpha); 529 530 appIconAnimatorSet.addListener(new AnimatorListenerAdapter() { 531 @Override 532 public void onAnimationEnd(Animator animation) { 533 // Reset launcher to normal state 534 v.setVisibility(View.VISIBLE); 535 ((ViewGroup) mDragLayer.getParent()).removeView(mFloatingView); 536 } 537 }); 538 return appIconAnimatorSet; 539 } 540 541 /** 542 * @return Animator that controls the window of the opening targets. 543 */ 544 private ValueAnimator getOpeningWindowAnimators(View v, RemoteAnimationTargetCompat[] targets, 545 Rect windowTargetBounds) { 546 Rect bounds = new Rect(); 547 if (v.getParent() instanceof DeepShortcutView) { 548 // Deep shortcut views have their icon drawn in a separate view. 549 DeepShortcutView view = (DeepShortcutView) v.getParent(); 550 mDragLayer.getDescendantRectRelativeToSelf(view.getIconView(), bounds); 551 } else if (v instanceof BubbleTextView) { 552 ((BubbleTextView) v).getIconBounds(bounds); 553 } else { 554 mDragLayer.getDescendantRectRelativeToSelf(v, bounds); 555 } 556 int[] floatingViewBounds = new int[2]; 557 558 Rect crop = new Rect(); 559 Matrix matrix = new Matrix(); 560 561 ValueAnimator appAnimator = ValueAnimator.ofFloat(0, 1); 562 appAnimator.setDuration(APP_LAUNCH_DURATION); 563 appAnimator.addUpdateListener(new MultiValueUpdateListener() { 564 // Fade alpha for the app window. 565 FloatProp mAlpha = new FloatProp(0f, 1f, 0, 60, LINEAR); 566 boolean isFirstFrame = true; 567 568 @Override 569 public void onUpdate(float percent) { 570 final Surface surface = getSurface(mFloatingView); 571 final long frameNumber = surface != null ? getNextFrameNumber(surface) : -1; 572 if (frameNumber == -1) { 573 // Booo, not cool! Our surface got destroyed, so no reason to animate anything. 574 Log.w(TAG, "Failed to animate, surface got destroyed."); 575 return; 576 } 577 final float easePercent = AGGRESSIVE_EASE.getInterpolation(percent); 578 579 // Calculate app icon size. 580 float iconWidth = bounds.width() * mFloatingView.getScaleX(); 581 float iconHeight = bounds.height() * mFloatingView.getScaleY(); 582 583 // Scale the app window to match the icon size. 584 float scaleX = iconWidth / windowTargetBounds.width(); 585 float scaleY = iconHeight / windowTargetBounds.height(); 586 float scale = Math.min(1f, Math.min(scaleX, scaleY)); 587 matrix.setScale(scale, scale); 588 589 // Position the scaled window on top of the icon 590 int windowWidth = windowTargetBounds.width(); 591 int windowHeight = windowTargetBounds.height(); 592 float scaledWindowWidth = windowWidth * scale; 593 float scaledWindowHeight = windowHeight * scale; 594 595 float offsetX = (scaledWindowWidth - iconWidth) / 2; 596 float offsetY = (scaledWindowHeight - iconHeight) / 2; 597 mFloatingView.getLocationOnScreen(floatingViewBounds); 598 599 float transX0 = floatingViewBounds[0] - offsetX; 600 float transY0 = floatingViewBounds[1] - offsetY; 601 matrix.postTranslate(transX0, transY0); 602 603 // Animate the window crop so that it starts off as a square, and then reveals 604 // horizontally. 605 float cropHeight = windowHeight * easePercent + windowWidth * (1 - easePercent); 606 float initialTop = (windowHeight - windowWidth) / 2f; 607 crop.left = 0; 608 crop.top = (int) (initialTop * (1 - easePercent)); 609 crop.right = windowWidth; 610 crop.bottom = (int) (crop.top + cropHeight); 611 612 TransactionCompat t = new TransactionCompat(); 613 if (isFirstFrame) { 614 RemoteAnimationProvider.prepareTargetsForFirstFrame(targets, t, MODE_OPENING); 615 isFirstFrame = false; 616 } 617 for (RemoteAnimationTargetCompat target : targets) { 618 if (target.mode == MODE_OPENING) { 619 t.setAlpha(target.leash, mAlpha.value); 620 t.setMatrix(target.leash, matrix); 621 t.setWindowCrop(target.leash, crop); 622 t.deferTransactionUntil(target.leash, surface, getNextFrameNumber(surface)); 623 } 624 } 625 t.setEarlyWakeup(); 626 t.apply(); 627 628 matrix.reset(); 629 } 630 }); 631 return appAnimator; 632 } 633 634 /** 635 * Registers remote animations used when closing apps to home screen. 636 */ 637 private void registerRemoteAnimations() { 638 // Unregister this 639 if (hasControlRemoteAppTransitionPermission()) { 640 RemoteAnimationDefinitionCompat definition = new RemoteAnimationDefinitionCompat(); 641 definition.addRemoteAnimation(WindowManagerWrapper.TRANSIT_WALLPAPER_OPEN, 642 WindowManagerWrapper.ACTIVITY_TYPE_STANDARD, 643 new RemoteAnimationAdapterCompat(getWallpaperOpenRunner(), 644 CLOSING_TRANSITION_DURATION_MS, 0 /* statusBarTransitionDelay */)); 645 646 // TODO: Transition for unlock to home TRANSIT_KEYGUARD_GOING_AWAY_ON_WALLPAPER 647 new ActivityCompat(mLauncher).registerRemoteAnimations(definition); 648 } 649 } 650 651 private boolean launcherIsATargetWithMode(RemoteAnimationTargetCompat[] targets, int mode) { 652 return taskIsATargetWithMode(targets, mLauncher.getTaskId(), mode); 653 } 654 655 /** 656 * @return Runner that plays when user goes to Launcher 657 * ie. pressing home, swiping up from nav bar. 658 */ 659 private RemoteAnimationRunnerCompat getWallpaperOpenRunner() { 660 return new LauncherAnimationRunner(mHandler, false /* startAtFrontOfQueue */) { 661 @Override 662 public void onCreateAnimation(RemoteAnimationTargetCompat[] targetCompats, 663 AnimationResult result) { 664 if (!mLauncher.hasBeenResumed()) { 665 // If launcher is not resumed, wait until new async-frame after resume 666 mLauncher.setOnResumeCallback(() -> 667 postAsyncCallback(mHandler, () -> 668 onCreateAnimation(targetCompats, result))); 669 return; 670 } 671 672 AnimatorSet anim = null; 673 RemoteAnimationProvider provider = mRemoteAnimationProvider; 674 if (provider != null) { 675 anim = provider.createWindowAnimation(targetCompats); 676 } 677 678 if (anim == null) { 679 anim = new AnimatorSet(); 680 anim.play(getClosingWindowAnimators(targetCompats)); 681 682 // Normally, we run the launcher content animation when we are transitioning 683 // home, but if home is already visible, then we don't want to animate the 684 // contents of launcher unless we know that we are animating home as a result 685 // of the home button press with quickstep, which will result in launcher being 686 // started on touch down, prior to the animation home (and won't be in the 687 // targets list because it is already visible). In that case, we force 688 // invisibility on touch down, and only reset it after the animation to home 689 // is initialized. 690 if (launcherIsATargetWithMode(targetCompats, MODE_OPENING) 691 || mLauncher.isForceInvisible()) { 692 // Only register the content animation for cancellation when state changes 693 mLauncher.getStateManager().setCurrentAnimation(anim); 694 createLauncherResumeAnimation(anim); 695 } 696 } 697 698 mLauncher.clearForceInvisibleFlag(INVISIBLE_ALL); 699 result.setAnimation(anim); 700 } 701 }; 702 } 703 704 /** 705 * Animator that controls the transformations of the windows the targets that are closing. 706 */ 707 private Animator getClosingWindowAnimators(RemoteAnimationTargetCompat[] targets) { 708 Matrix matrix = new Matrix(); 709 ValueAnimator closingAnimator = ValueAnimator.ofFloat(0, 1); 710 int duration = CLOSING_TRANSITION_DURATION_MS; 711 closingAnimator.setDuration(duration); 712 closingAnimator.addUpdateListener(new MultiValueUpdateListener() { 713 FloatProp mDy = new FloatProp(0, mClosingWindowTransY, 0, duration, DEACCEL_1_7); 714 FloatProp mScale = new FloatProp(1f, 1f, 0, duration, DEACCEL_1_7); 715 FloatProp mAlpha = new FloatProp(1f, 0f, 25, 125, LINEAR); 716 717 boolean isFirstFrame = true; 718 719 @Override 720 public void onUpdate(float percent) { 721 TransactionCompat t = new TransactionCompat(); 722 if (isFirstFrame) { 723 RemoteAnimationProvider.prepareTargetsForFirstFrame(targets, t, MODE_CLOSING); 724 isFirstFrame = false; 725 } 726 for (RemoteAnimationTargetCompat app : targets) { 727 if (app.mode == RemoteAnimationTargetCompat.MODE_CLOSING) { 728 t.setAlpha(app.leash, mAlpha.value); 729 matrix.setScale(mScale.value, mScale.value, 730 app.sourceContainerBounds.centerX(), 731 app.sourceContainerBounds.centerY()); 732 matrix.postTranslate(0, mDy.value); 733 matrix.postTranslate(app.position.x, app.position.y); 734 t.setMatrix(app.leash, matrix); 735 } 736 } 737 t.setEarlyWakeup(); 738 t.apply(); 739 740 matrix.reset(); 741 } 742 }); 743 744 return closingAnimator; 745 } 746 747 /** 748 * Creates an animator that modifies Launcher as a result from {@link #getWallpaperOpenRunner}. 749 */ 750 private void createLauncherResumeAnimation(AnimatorSet anim) { 751 if (mLauncher.isInState(LauncherState.ALL_APPS)) { 752 Pair<AnimatorSet, Runnable> contentAnimator = 753 getLauncherContentAnimator(false /* isAppOpening */); 754 contentAnimator.first.setStartDelay(LAUNCHER_RESUME_START_DELAY); 755 anim.play(contentAnimator.first); 756 anim.addListener(new AnimatorListenerAdapter() { 757 @Override 758 public void onAnimationEnd(Animator animation) { 759 contentAnimator.second.run(); 760 } 761 }); 762 } else { 763 AnimatorSet workspaceAnimator = new AnimatorSet(); 764 765 mDragLayer.setTranslationY(-mWorkspaceTransY);; 766 workspaceAnimator.play(ObjectAnimator.ofFloat(mDragLayer, View.TRANSLATION_Y, 767 -mWorkspaceTransY, 0)); 768 769 mDragLayerAlpha.setValue(0); 770 workspaceAnimator.play(ObjectAnimator.ofFloat( 771 mDragLayerAlpha, MultiValueAlpha.VALUE, 0, 1f)); 772 773 workspaceAnimator.setStartDelay(LAUNCHER_RESUME_START_DELAY); 774 workspaceAnimator.setDuration(333); 775 workspaceAnimator.setInterpolator(Interpolators.DEACCEL_1_7); 776 777 mDragLayer.getScrim().hideSysUiScrim(true); 778 779 // Pause page indicator animations as they lead to layer trashing. 780 mLauncher.getWorkspace().getPageIndicator().pauseAnimations(); 781 mDragLayer.setLayerType(View.LAYER_TYPE_HARDWARE, null); 782 783 workspaceAnimator.addListener(new AnimatorListenerAdapter() { 784 @Override 785 public void onAnimationEnd(Animator animation) { 786 resetContentView(); 787 } 788 }); 789 anim.play(workspaceAnimator); 790 } 791 } 792 793 private void resetContentView() { 794 mLauncher.getWorkspace().getPageIndicator().skipAnimationsToEnd(); 795 mDragLayerAlpha.setValue(1f); 796 mDragLayer.setLayerType(View.LAYER_TYPE_NONE, null); 797 mDragLayer.setTranslationY(0f); 798 mDragLayer.getScrim().hideSysUiScrim(false); 799 } 800 801 private boolean hasControlRemoteAppTransitionPermission() { 802 return mLauncher.checkSelfPermission(CONTROL_REMOTE_APP_TRANSITION_PERMISSION) 803 == PackageManager.PERMISSION_GRANTED; 804 } 805 } 806