1 /* 2 * Copyright (C) 2019 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 package com.android.quickstep; 17 18 import static android.view.WindowManager.TRANSIT_OPEN; 19 import static android.view.WindowManager.TRANSIT_TO_FRONT; 20 21 import static com.android.launcher3.LauncherAnimUtils.VIEW_ALPHA; 22 import static com.android.launcher3.LauncherState.BACKGROUND_APP; 23 import static com.android.launcher3.LauncherState.NORMAL; 24 import static com.android.launcher3.QuickstepTransitionManager.ANIMATION_DELAY_NAV_FADE_IN; 25 import static com.android.launcher3.QuickstepTransitionManager.ANIMATION_NAV_FADE_IN_DURATION; 26 import static com.android.launcher3.QuickstepTransitionManager.ANIMATION_NAV_FADE_OUT_DURATION; 27 import static com.android.launcher3.QuickstepTransitionManager.NAV_FADE_IN_INTERPOLATOR; 28 import static com.android.launcher3.QuickstepTransitionManager.NAV_FADE_OUT_INTERPOLATOR; 29 import static com.android.launcher3.QuickstepTransitionManager.RECENTS_LAUNCH_DURATION; 30 import static com.android.launcher3.Utilities.getDescendantCoordRelativeToAncestor; 31 import static com.android.launcher3.anim.Interpolators.LINEAR; 32 import static com.android.launcher3.anim.Interpolators.TOUCH_RESPONSE_INTERPOLATOR; 33 import static com.android.launcher3.anim.Interpolators.TOUCH_RESPONSE_INTERPOLATOR_ACCEL_DEACCEL; 34 import static com.android.launcher3.anim.Interpolators.clampToProgress; 35 import static com.android.launcher3.config.FeatureFlags.ENABLE_QUICKSTEP_LIVE_TILE; 36 import static com.android.launcher3.statehandlers.DepthController.DEPTH; 37 import static com.android.systemui.shared.system.RemoteAnimationTargetCompat.MODE_CLOSING; 38 import static com.android.systemui.shared.system.RemoteAnimationTargetCompat.MODE_OPENING; 39 40 import android.animation.Animator; 41 import android.animation.AnimatorListenerAdapter; 42 import android.animation.AnimatorSet; 43 import android.animation.ObjectAnimator; 44 import android.annotation.TargetApi; 45 import android.content.ComponentName; 46 import android.content.Context; 47 import android.graphics.Matrix; 48 import android.graphics.Matrix.ScaleToFit; 49 import android.graphics.Rect; 50 import android.graphics.RectF; 51 import android.os.Build; 52 import android.view.SurfaceControl; 53 import android.view.View; 54 import android.window.TransitionInfo; 55 56 import androidx.annotation.NonNull; 57 import androidx.annotation.Nullable; 58 59 import com.android.launcher3.BaseActivity; 60 import com.android.launcher3.DeviceProfile; 61 import com.android.launcher3.anim.AnimationSuccessListener; 62 import com.android.launcher3.anim.AnimatorPlaybackController; 63 import com.android.launcher3.anim.Interpolators; 64 import com.android.launcher3.anim.PendingAnimation; 65 import com.android.launcher3.config.FeatureFlags; 66 import com.android.launcher3.model.data.ItemInfo; 67 import com.android.launcher3.statehandlers.DepthController; 68 import com.android.launcher3.statemanager.StateManager; 69 import com.android.launcher3.util.DisplayController; 70 import com.android.quickstep.util.MultiValueUpdateListener; 71 import com.android.quickstep.util.SurfaceTransactionApplier; 72 import com.android.quickstep.util.TaskViewSimulator; 73 import com.android.quickstep.util.TransformParams; 74 import com.android.quickstep.views.RecentsView; 75 import com.android.quickstep.views.TaskThumbnailView; 76 import com.android.quickstep.views.TaskView; 77 import com.android.systemui.shared.recents.model.Task; 78 import com.android.systemui.shared.system.InteractionJankMonitorWrapper; 79 import com.android.systemui.shared.system.RemoteAnimationTargetCompat; 80 import com.android.systemui.shared.system.SyncRtSurfaceTransactionApplierCompat.SurfaceParams; 81 82 /** 83 * Utility class for helpful methods related to {@link TaskView} objects and their tasks. 84 */ 85 @TargetApi(Build.VERSION_CODES.R) 86 public final class TaskViewUtils { 87 TaskViewUtils()88 private TaskViewUtils() {} 89 90 /** 91 * Try to find a TaskView that corresponds with the component of the launched view. 92 * 93 * If this method returns a non-null TaskView, it will be used in composeRecentsLaunchAnimation. 94 * Otherwise, we will assume we are using a normal app transition, but it's possible that the 95 * opening remote target (which we don't get until onAnimationStart) will resolve to a TaskView. 96 */ findTaskViewToLaunch( RecentsView recentsView, View v, RemoteAnimationTargetCompat[] targets)97 public static TaskView findTaskViewToLaunch( 98 RecentsView recentsView, View v, RemoteAnimationTargetCompat[] targets) { 99 if (v instanceof TaskView) { 100 TaskView taskView = (TaskView) v; 101 return recentsView.isTaskViewVisible(taskView) ? taskView : null; 102 } 103 104 // It's possible that the launched view can still be resolved to a visible task view, check 105 // the task id of the opening task and see if we can find a match. 106 if (v.getTag() instanceof ItemInfo) { 107 ItemInfo itemInfo = (ItemInfo) v.getTag(); 108 ComponentName componentName = itemInfo.getTargetComponent(); 109 int userId = itemInfo.user.getIdentifier(); 110 if (componentName != null) { 111 for (int i = 0; i < recentsView.getTaskViewCount(); i++) { 112 TaskView taskView = recentsView.getTaskViewAt(i); 113 if (recentsView.isTaskViewVisible(taskView)) { 114 Task.TaskKey key = taskView.getTask().key; 115 if (componentName.equals(key.getComponent()) && userId == key.userId) { 116 return taskView; 117 } 118 } 119 } 120 } 121 } 122 123 if (targets == null) { 124 return null; 125 } 126 // Resolve the opening task id 127 int openingTaskId = -1; 128 for (RemoteAnimationTargetCompat target : targets) { 129 if (target.mode == MODE_OPENING) { 130 openingTaskId = target.taskId; 131 break; 132 } 133 } 134 135 // If there is no opening task id, fall back to the normal app icon launch animation 136 if (openingTaskId == -1) { 137 return null; 138 } 139 140 // If the opening task id is not currently visible in overview, then fall back to normal app 141 // icon launch animation 142 TaskView taskView = recentsView.getTaskView(openingTaskId); 143 if (taskView == null || !recentsView.isTaskViewVisible(taskView)) { 144 return null; 145 } 146 return taskView; 147 } 148 createRecentsWindowAnimator(TaskView v, boolean skipViewChanges, RemoteAnimationTargetCompat[] appTargets, RemoteAnimationTargetCompat[] wallpaperTargets, RemoteAnimationTargetCompat[] nonAppTargets, DepthController depthController, PendingAnimation out)149 public static void createRecentsWindowAnimator(TaskView v, boolean skipViewChanges, 150 RemoteAnimationTargetCompat[] appTargets, 151 RemoteAnimationTargetCompat[] wallpaperTargets, 152 RemoteAnimationTargetCompat[] nonAppTargets, DepthController depthController, 153 PendingAnimation out) { 154 boolean isRunningTask = v.isRunningTask(); 155 TransformParams params = null; 156 TaskViewSimulator tsv = null; 157 if (ENABLE_QUICKSTEP_LIVE_TILE.get() && isRunningTask) { 158 params = v.getRecentsView().getLiveTileParams(); 159 tsv = v.getRecentsView().getLiveTileTaskViewSimulator(); 160 } 161 createRecentsWindowAnimator(v, skipViewChanges, appTargets, wallpaperTargets, nonAppTargets, 162 depthController, out, params, tsv); 163 } 164 165 /** 166 * Creates an animation that controls the window of the opening targets for the recents launch 167 * animation. 168 */ createRecentsWindowAnimator(TaskView v, boolean skipViewChanges, RemoteAnimationTargetCompat[] appTargets, RemoteAnimationTargetCompat[] wallpaperTargets, RemoteAnimationTargetCompat[] nonAppTargets, DepthController depthController, PendingAnimation out, @Nullable TransformParams params, @Nullable TaskViewSimulator tsv)169 public static void createRecentsWindowAnimator(TaskView v, boolean skipViewChanges, 170 RemoteAnimationTargetCompat[] appTargets, 171 RemoteAnimationTargetCompat[] wallpaperTargets, 172 RemoteAnimationTargetCompat[] nonAppTargets, DepthController depthController, 173 PendingAnimation out, @Nullable TransformParams params, 174 @Nullable TaskViewSimulator tsv) { 175 boolean isQuickSwitch = v.isEndQuickswitchCuj(); 176 v.setEndQuickswitchCuj(false); 177 178 boolean inLiveTileMode = 179 ENABLE_QUICKSTEP_LIVE_TILE.get() && v.getRecentsView().getRunningTaskIndex() != -1; 180 final RemoteAnimationTargets targets = 181 new RemoteAnimationTargets(appTargets, wallpaperTargets, nonAppTargets, 182 inLiveTileMode ? MODE_CLOSING : MODE_OPENING); 183 final RemoteAnimationTargetCompat navBarTarget = targets.getNavBarRemoteAnimationTarget(); 184 185 if (params == null) { 186 SurfaceTransactionApplier applier = new SurfaceTransactionApplier(v); 187 targets.addReleaseCheck(applier); 188 189 params = new TransformParams() 190 .setSyncTransactionApplier(applier) 191 .setTargetSet(targets); 192 } 193 194 final RecentsView recentsView = v.getRecentsView(); 195 int taskIndex = recentsView.indexOfChild(v); 196 Context context = v.getContext(); 197 DeviceProfile dp = BaseActivity.fromContext(context).getDeviceProfile(); 198 boolean showAsGrid = dp.isTablet && FeatureFlags.ENABLE_OVERVIEW_GRID.get(); 199 boolean parallaxCenterAndAdjacentTask = 200 taskIndex != recentsView.getCurrentPage() && !showAsGrid; 201 float gridTranslationSecondary = recentsView.getGridTranslationSecondary(taskIndex); 202 int startScroll = recentsView.getScrollOffset(taskIndex); 203 204 TaskViewSimulator topMostSimulator = null; 205 206 if (tsv == null && targets.apps.length > 0) { 207 tsv = new TaskViewSimulator(context, recentsView.getSizeStrategy()); 208 tsv.setDp(dp); 209 210 // RecentsView never updates the display rotation until swipe-up so the value may 211 // be stale. Use the display value instead. 212 int displayRotation = DisplayController.INSTANCE.get(context).getInfo().rotation; 213 tsv.getOrientationState().update(displayRotation, displayRotation); 214 215 tsv.setPreview(targets.apps[targets.apps.length - 1]); 216 tsv.fullScreenProgress.value = 0; 217 tsv.recentsViewScale.value = 1; 218 if (showAsGrid) { 219 tsv.taskSecondaryTranslation.value = gridTranslationSecondary; 220 } 221 tsv.setScroll(startScroll); 222 223 // Fade in the task during the initial 20% of the animation 224 out.addFloat(params, TransformParams.TARGET_ALPHA, 0, 1, 225 clampToProgress(LINEAR, 0, 0.2f)); 226 } 227 228 if (tsv != null) { 229 out.setFloat(tsv.fullScreenProgress, 230 AnimatedFloat.VALUE, 1, TOUCH_RESPONSE_INTERPOLATOR); 231 out.setFloat(tsv.recentsViewScale, 232 AnimatedFloat.VALUE, tsv.getFullScreenScale(), TOUCH_RESPONSE_INTERPOLATOR); 233 if (showAsGrid) { 234 out.setFloat(tsv.taskSecondaryTranslation, AnimatedFloat.VALUE, 0, 235 TOUCH_RESPONSE_INTERPOLATOR_ACCEL_DEACCEL); 236 } 237 out.setFloat(tsv.recentsViewScroll, AnimatedFloat.VALUE, 0, 238 TOUCH_RESPONSE_INTERPOLATOR); 239 240 TaskViewSimulator finalTsv = tsv; 241 TransformParams finalParams = params; 242 out.addOnFrameCallback(() -> finalTsv.apply(finalParams)); 243 if (navBarTarget != null) { 244 final Rect cropRect = new Rect(); 245 out.addOnFrameListener(new MultiValueUpdateListener() { 246 FloatProp mNavFadeOut = new FloatProp(1f, 0f, 0, 247 ANIMATION_NAV_FADE_OUT_DURATION, NAV_FADE_OUT_INTERPOLATOR); 248 FloatProp mNavFadeIn = new FloatProp(0f, 1f, ANIMATION_DELAY_NAV_FADE_IN, 249 ANIMATION_NAV_FADE_IN_DURATION, NAV_FADE_IN_INTERPOLATOR); 250 251 @Override 252 public void onUpdate(float percent, boolean initOnly) { 253 final SurfaceParams.Builder navBuilder = 254 new SurfaceParams.Builder(navBarTarget.leash); 255 if (mNavFadeIn.value > mNavFadeIn.getStartValue()) { 256 finalTsv.getCurrentCropRect().round(cropRect); 257 navBuilder.withMatrix(finalTsv.getCurrentMatrix()) 258 .withWindowCrop(cropRect) 259 .withAlpha(mNavFadeIn.value); 260 } else { 261 navBuilder.withAlpha(mNavFadeOut.value); 262 } 263 finalParams.applySurfaceParams(navBuilder.build()); 264 } 265 }); 266 } else if (inLiveTileMode) { 267 // There is no transition animation for app launch from recent in live tile mode so 268 // we have to trigger the navigation bar animation from system here. 269 final RecentsAnimationController controller = 270 recentsView.getRecentsAnimationController(); 271 if (controller != null) { 272 controller.animateNavigationBarToApp(RECENTS_LAUNCH_DURATION); 273 } 274 } 275 topMostSimulator = tsv; 276 } 277 278 if (!skipViewChanges && parallaxCenterAndAdjacentTask && topMostSimulator != null) { 279 out.addFloat(v, VIEW_ALPHA, 1, 0, clampToProgress(LINEAR, 0.2f, 0.4f)); 280 281 TaskViewSimulator simulatorToCopy = topMostSimulator; 282 simulatorToCopy.apply(params); 283 284 // Mt represents the overall transformation on the thumbnailView relative to the 285 // Launcher's rootView 286 // K(t) represents transformation on the running window by the taskViewSimulator at 287 // any time t. 288 // at t = 0, we know that the simulator matches the thumbnailView. So if we apply K(0)` 289 // on the Launcher's rootView, the thumbnailView would match the full running task 290 // window. If we apply "K(0)` K(t)" thumbnailView will match the final transformed 291 // window at any time t. This gives the overall matrix on thumbnailView to be: 292 // Mt K(0)` K(t) 293 // During animation we apply transformation on the thumbnailView (and not the rootView) 294 // to follow the TaskViewSimulator. So the final matrix applied on the thumbnailView is: 295 // Mt K(0)` K(t) Mt` 296 TaskThumbnailView ttv = v.getThumbnail(); 297 RectF tvBounds = new RectF(0, 0, ttv.getWidth(), ttv.getHeight()); 298 float[] tvBoundsMapped = new float[]{0, 0, ttv.getWidth(), ttv.getHeight()}; 299 getDescendantCoordRelativeToAncestor(ttv, ttv.getRootView(), tvBoundsMapped, false); 300 RectF tvBoundsInRoot = new RectF( 301 tvBoundsMapped[0], tvBoundsMapped[1], 302 tvBoundsMapped[2], tvBoundsMapped[3]); 303 304 Matrix mt = new Matrix(); 305 mt.setRectToRect(tvBounds, tvBoundsInRoot, ScaleToFit.FILL); 306 307 Matrix mti = new Matrix(); 308 mt.invert(mti); 309 310 Matrix k0i = new Matrix(); 311 simulatorToCopy.getCurrentMatrix().invert(k0i); 312 313 Matrix animationMatrix = new Matrix(); 314 out.addOnFrameCallback(() -> { 315 animationMatrix.set(mt); 316 animationMatrix.postConcat(k0i); 317 animationMatrix.postConcat(simulatorToCopy.getCurrentMatrix()); 318 animationMatrix.postConcat(mti); 319 ttv.setAnimationMatrix(animationMatrix); 320 }); 321 322 out.addListener(new AnimatorListenerAdapter() { 323 @Override 324 public void onAnimationEnd(Animator animation) { 325 ttv.setAnimationMatrix(null); 326 } 327 }); 328 } 329 330 out.addListener(new AnimationSuccessListener() { 331 @Override 332 public void onAnimationSuccess(Animator animator) { 333 if (isQuickSwitch) { 334 InteractionJankMonitorWrapper.end( 335 InteractionJankMonitorWrapper.CUJ_QUICK_SWITCH); 336 } 337 } 338 339 @Override 340 public void onAnimationEnd(Animator animation) { 341 targets.release(); 342 super.onAnimationEnd(animation); 343 } 344 }); 345 346 if (depthController != null) { 347 out.setFloat(depthController, DEPTH, BACKGROUND_APP.getDepth(context), 348 TOUCH_RESPONSE_INTERPOLATOR); 349 } 350 } 351 352 /** 353 * TODO: This doesn't animate at present. Feel free to blow out everyhing in this method 354 * if needed 355 * 356 * We could manually try to animate the just the bounds for the leashes we get back, but we try 357 * to do it through TaskViewSimulator(TVS) since that handles a lot of the recents UI stuff for 358 * us. 359 * 360 * First you have to call TVS#setPreview() to indicate which leash it will operate one 361 * Then operations happen in TVS#apply() on each frame callback. 362 * 363 * TVS uses DeviceProfile to try to figure out things like task height and such based on if the 364 * device is in multiWindowMode or not. It's unclear given the two calls to startTask() when the 365 * device is considered in multiWindowMode and things like insets and stuff change 366 * and calculations have to be adjusted in the animations for that 367 */ composeRecentsSplitLaunchAnimator(@onNull TaskView initialView, @NonNull TaskView v, @NonNull TransitionInfo transitionInfo, SurfaceControl.Transaction t, @NonNull Runnable finishCallback)368 public static void composeRecentsSplitLaunchAnimator(@NonNull TaskView initialView, 369 @NonNull TaskView v, @NonNull TransitionInfo transitionInfo, 370 SurfaceControl.Transaction t, @NonNull Runnable finishCallback) { 371 372 final TransitionInfo.Change[] splitRoots = new TransitionInfo.Change[2]; 373 for (int i = 0; i < transitionInfo.getChanges().size(); ++i) { 374 final TransitionInfo.Change change = transitionInfo.getChanges().get(i); 375 final int taskId = change.getTaskInfo() != null ? change.getTaskInfo().taskId : -1; 376 final int mode = change.getMode(); 377 // Find the target tasks' root tasks since those are the split stages that need to 378 // be animated (the tasks themselves are children and thus inherit animation). 379 if (taskId == initialView.getTask().key.id || taskId == v.getTask().key.id) { 380 if (!(mode == TRANSIT_OPEN || mode == TRANSIT_TO_FRONT)) { 381 throw new IllegalStateException( 382 "Expected task to be showing, but it is " + mode); 383 } 384 if (change.getParent() == null) { 385 throw new IllegalStateException("Initiating multi-split launch but the split" 386 + "root of " + taskId + " is already visible or has broken hierarchy."); 387 } 388 splitRoots[taskId == initialView.getTask().key.id ? 0 : 1] = 389 transitionInfo.getChange(change.getParent()); 390 } 391 } 392 393 // This is where we should animate the split roots. For now, though, just make them visible. 394 for (int i = 0; i < 2; ++i) { 395 t.show(splitRoots[i].getLeash()); 396 t.setAlpha(splitRoots[i].getLeash(), 1.f); 397 } 398 399 // This contains the initial state (before animation), so apply this at the beginning of 400 // the animation. 401 t.apply(); 402 403 // Once there is an animation, this should be called AFTER the animation completes. 404 finishCallback.run(); 405 } 406 407 /** Legacy version (until shell transitions are enabled) */ composeRecentsSplitLaunchAnimatorLegacy(@onNull AnimatorSet anim, @NonNull TaskView v, @NonNull RemoteAnimationTargetCompat[] appTargets, @NonNull RemoteAnimationTargetCompat[] wallpaperTargets, @NonNull RemoteAnimationTargetCompat[] nonAppTargets, boolean launcherClosing, @NonNull StateManager stateManager, @NonNull DepthController depthController, int targetStage)408 public static void composeRecentsSplitLaunchAnimatorLegacy(@NonNull AnimatorSet anim, 409 @NonNull TaskView v, @NonNull RemoteAnimationTargetCompat[] appTargets, 410 @NonNull RemoteAnimationTargetCompat[] wallpaperTargets, 411 @NonNull RemoteAnimationTargetCompat[] nonAppTargets, boolean launcherClosing, 412 @NonNull StateManager stateManager, @NonNull DepthController depthController, 413 int targetStage) { 414 PendingAnimation out = new PendingAnimation(RECENTS_LAUNCH_DURATION); 415 boolean isRunningTask = v.isRunningTask(); 416 TransformParams params = null; 417 TaskViewSimulator tvs = null; 418 RecentsView recentsView = v.getRecentsView(); 419 if (ENABLE_QUICKSTEP_LIVE_TILE.get() && isRunningTask) { 420 params = recentsView.getLiveTileParams(); 421 tvs = recentsView.getLiveTileTaskViewSimulator(); 422 } 423 424 boolean inLiveTileMode = 425 ENABLE_QUICKSTEP_LIVE_TILE.get() && recentsView.getRunningTaskIndex() != -1; 426 final RemoteAnimationTargets targets = 427 new RemoteAnimationTargets(appTargets, wallpaperTargets, nonAppTargets, 428 inLiveTileMode ? MODE_CLOSING : MODE_OPENING); 429 430 if (params == null) { 431 SurfaceTransactionApplier applier = new SurfaceTransactionApplier(v); 432 targets.addReleaseCheck(applier); 433 434 params = new TransformParams() 435 .setSyncTransactionApplier(applier) 436 .setTargetSet(targets); 437 } 438 439 Rect crop = new Rect(); 440 Context context = v.getContext(); 441 DeviceProfile dp = BaseActivity.fromContext(context).getDeviceProfile(); 442 if (tvs == null && targets.apps.length > 0) { 443 tvs = new TaskViewSimulator(recentsView.getContext(), recentsView.getSizeStrategy()); 444 tvs.setDp(dp); 445 446 // RecentsView never updates the display rotation until swipe-up so the value may 447 // be stale. Use the display value instead. 448 int displayRotation = DisplayController.INSTANCE.get(recentsView.getContext()) 449 .getInfo().rotation; 450 tvs.getOrientationState().update(displayRotation, displayRotation); 451 452 tvs.setPreview(targets.apps[targets.apps.length - 1]); 453 tvs.fullScreenProgress.value = 0; 454 tvs.recentsViewScale.value = 1; 455 // tvs.setScroll(startScroll); 456 457 // Fade in the task during the initial 20% of the animation 458 out.addFloat(params, TransformParams.TARGET_ALPHA, 0, 1, 459 clampToProgress(LINEAR, 0, 0.2f)); 460 } 461 462 TaskViewSimulator topMostSimulator = null; 463 464 if (tvs != null) { 465 out.setFloat(tvs.fullScreenProgress, 466 AnimatedFloat.VALUE, 1, TOUCH_RESPONSE_INTERPOLATOR); 467 out.setFloat(tvs.recentsViewScale, 468 AnimatedFloat.VALUE, tvs.getFullScreenScale(), TOUCH_RESPONSE_INTERPOLATOR); 469 out.setFloat(tvs.recentsViewScroll, 470 AnimatedFloat.VALUE, 0, TOUCH_RESPONSE_INTERPOLATOR); 471 472 TaskViewSimulator finalTsv = tvs; 473 TransformParams finalParams = params; 474 out.addOnFrameCallback(() -> finalTsv.apply(finalParams)); 475 topMostSimulator = tvs; 476 } 477 478 anim.play(out.buildAnim()); 479 } 480 composeRecentsLaunchAnimator(@onNull AnimatorSet anim, @NonNull View v, @NonNull RemoteAnimationTargetCompat[] appTargets, @NonNull RemoteAnimationTargetCompat[] wallpaperTargets, @NonNull RemoteAnimationTargetCompat[] nonAppTargets, boolean launcherClosing, @NonNull StateManager stateManager, @NonNull RecentsView recentsView, @NonNull DepthController depthController)481 public static void composeRecentsLaunchAnimator(@NonNull AnimatorSet anim, @NonNull View v, 482 @NonNull RemoteAnimationTargetCompat[] appTargets, 483 @NonNull RemoteAnimationTargetCompat[] wallpaperTargets, 484 @NonNull RemoteAnimationTargetCompat[] nonAppTargets, boolean launcherClosing, 485 @NonNull StateManager stateManager, @NonNull RecentsView recentsView, 486 @NonNull DepthController depthController) { 487 boolean skipLauncherChanges = !launcherClosing; 488 489 TaskView taskView = findTaskViewToLaunch(recentsView, v, appTargets); 490 PendingAnimation pa = new PendingAnimation(RECENTS_LAUNCH_DURATION); 491 createRecentsWindowAnimator(taskView, skipLauncherChanges, appTargets, wallpaperTargets, 492 nonAppTargets, depthController, pa); 493 494 Animator childStateAnimation = null; 495 // Found a visible recents task that matches the opening app, lets launch the app from there 496 Animator launcherAnim; 497 final AnimatorListenerAdapter windowAnimEndListener; 498 if (launcherClosing) { 499 Context context = v.getContext(); 500 DeviceProfile dp = BaseActivity.fromContext(context).getDeviceProfile(); 501 launcherAnim = dp.isTablet && FeatureFlags.ENABLE_OVERVIEW_GRID.get() 502 ? ObjectAnimator.ofFloat(recentsView, RecentsView.CONTENT_ALPHA, 0) 503 : recentsView.createAdjacentPageAnimForTaskLaunch(taskView); 504 launcherAnim.setInterpolator(Interpolators.TOUCH_RESPONSE_INTERPOLATOR); 505 launcherAnim.setDuration(RECENTS_LAUNCH_DURATION); 506 507 // Make sure recents gets fixed up by resetting task alphas and scales, etc. 508 windowAnimEndListener = new AnimatorListenerAdapter() { 509 @Override 510 public void onAnimationEnd(Animator animation) { 511 recentsView.finishRecentsAnimation(false /* toRecents */, () -> { 512 recentsView.post(() -> { 513 stateManager.moveToRestState(); 514 stateManager.reapplyState(); 515 }); 516 }); 517 } 518 }; 519 } else { 520 AnimatorPlaybackController controller = 521 stateManager.createAnimationToNewWorkspace(NORMAL, RECENTS_LAUNCH_DURATION); 522 controller.dispatchOnStart(); 523 childStateAnimation = controller.getTarget(); 524 launcherAnim = controller.getAnimationPlayer().setDuration(RECENTS_LAUNCH_DURATION); 525 windowAnimEndListener = new AnimatorListenerAdapter() { 526 @Override 527 public void onAnimationEnd(Animator animation) { 528 recentsView.finishRecentsAnimation(false /* toRecents */, 529 () -> stateManager.goToState(NORMAL, false)); 530 } 531 }; 532 } 533 pa.add(launcherAnim); 534 if (ENABLE_QUICKSTEP_LIVE_TILE.get() && recentsView.getRunningTaskIndex() != -1) { 535 pa.addOnFrameCallback(recentsView::redrawLiveTile); 536 } 537 anim.play(pa.buildAnim()); 538 539 // Set the current animation first, before adding windowAnimEndListener. Setting current 540 // animation adds some listeners which need to be called before windowAnimEndListener 541 // (the ordering of listeners matter in this case). 542 stateManager.setCurrentAnimation(anim, childStateAnimation); 543 anim.addListener(windowAnimEndListener); 544 } 545 } 546