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.RemoteAnimationTarget.MODE_CLOSING; 19 import static android.view.RemoteAnimationTarget.MODE_OPENING; 20 import static android.view.WindowManager.LayoutParams.TYPE_DOCK_DIVIDER; 21 22 import static com.android.app.animation.Interpolators.LINEAR; 23 import static com.android.app.animation.Interpolators.TOUCH_RESPONSE; 24 import static com.android.app.animation.Interpolators.clampToProgress; 25 import static com.android.launcher3.LauncherAnimUtils.VIEW_ALPHA; 26 import static com.android.launcher3.LauncherAnimUtils.VIEW_TRANSLATE_X; 27 import static com.android.launcher3.LauncherAnimUtils.VIEW_TRANSLATE_Y; 28 import static com.android.launcher3.LauncherState.BACKGROUND_APP; 29 import static com.android.launcher3.LauncherState.NORMAL; 30 import static com.android.launcher3.QuickstepTransitionManager.ANIMATION_DELAY_NAV_FADE_IN; 31 import static com.android.launcher3.QuickstepTransitionManager.ANIMATION_NAV_FADE_IN_DURATION; 32 import static com.android.launcher3.QuickstepTransitionManager.ANIMATION_NAV_FADE_OUT_DURATION; 33 import static com.android.launcher3.QuickstepTransitionManager.NAV_FADE_IN_INTERPOLATOR; 34 import static com.android.launcher3.QuickstepTransitionManager.NAV_FADE_OUT_INTERPOLATOR; 35 import static com.android.launcher3.QuickstepTransitionManager.RECENTS_LAUNCH_DURATION; 36 import static com.android.launcher3.QuickstepTransitionManager.SPLIT_DIVIDER_ANIM_DURATION; 37 import static com.android.launcher3.QuickstepTransitionManager.SPLIT_LAUNCH_DURATION; 38 import static com.android.launcher3.Utilities.getDescendantCoordRelativeToAncestor; 39 import static com.android.launcher3.util.MultiPropertyFactory.MULTI_PROPERTY_VALUE; 40 import static com.android.quickstep.util.AnimUtils.clampToDuration; 41 42 import android.animation.Animator; 43 import android.animation.AnimatorListenerAdapter; 44 import android.animation.AnimatorSet; 45 import android.animation.ValueAnimator; 46 import android.content.ComponentName; 47 import android.content.Context; 48 import android.graphics.Matrix; 49 import android.graphics.Matrix.ScaleToFit; 50 import android.graphics.Rect; 51 import android.graphics.RectF; 52 import android.view.RemoteAnimationTarget; 53 import android.view.SurfaceControl; 54 import android.view.View; 55 import android.window.TransitionInfo; 56 57 import androidx.annotation.NonNull; 58 import androidx.annotation.Nullable; 59 60 import com.android.app.animation.Interpolators; 61 import com.android.internal.jank.Cuj; 62 import com.android.launcher3.BaseActivity; 63 import com.android.launcher3.DeviceProfile; 64 import com.android.launcher3.anim.AnimatedFloat; 65 import com.android.launcher3.anim.AnimationSuccessListener; 66 import com.android.launcher3.anim.AnimatorPlaybackController; 67 import com.android.launcher3.anim.PendingAnimation; 68 import com.android.launcher3.model.data.ItemInfo; 69 import com.android.launcher3.statehandlers.DepthController; 70 import com.android.launcher3.statemanager.StateManager; 71 import com.android.launcher3.taskbar.TaskbarUIController; 72 import com.android.launcher3.util.DisplayController; 73 import com.android.quickstep.RemoteTargetGluer.RemoteTargetHandle; 74 import com.android.quickstep.util.MultiValueUpdateListener; 75 import com.android.quickstep.util.SurfaceTransaction; 76 import com.android.quickstep.util.SurfaceTransaction.SurfaceProperties; 77 import com.android.quickstep.util.SurfaceTransactionApplier; 78 import com.android.quickstep.util.TaskViewSimulator; 79 import com.android.quickstep.util.TransformParams; 80 import com.android.quickstep.views.DesktopTaskView; 81 import com.android.quickstep.views.GroupedTaskView; 82 import com.android.quickstep.views.RecentsView; 83 import com.android.quickstep.views.TaskThumbnailViewDeprecated; 84 import com.android.quickstep.views.TaskView; 85 import com.android.systemui.animation.RemoteAnimationTargetCompat; 86 import com.android.systemui.shared.recents.model.Task; 87 import com.android.systemui.shared.system.InteractionJankMonitorWrapper; 88 89 import java.util.ArrayList; 90 import java.util.List; 91 import java.util.function.Consumer; 92 93 /** 94 * Utility class for helpful methods related to {@link TaskView} objects and their tasks. 95 */ 96 public final class TaskViewUtils { 97 TaskViewUtils()98 private TaskViewUtils() {} 99 100 /** 101 * Try to find a TaskView that corresponds with the component of the launched view. 102 * 103 * If this method returns a non-null TaskView, it will be used in composeRecentsLaunchAnimation. 104 * Otherwise, we will assume we are using a normal app transition, but it's possible that the 105 * opening remote target (which we don't get until onAnimationStart) will resolve to a TaskView. 106 */ findTaskViewToLaunch( RecentsView recentsView, View v, RemoteAnimationTarget[] targets)107 public static TaskView findTaskViewToLaunch( 108 RecentsView recentsView, View v, RemoteAnimationTarget[] targets) { 109 if (v instanceof TaskView) { 110 TaskView taskView = (TaskView) v; 111 return recentsView.isTaskViewVisible(taskView) ? taskView : null; 112 } 113 114 // It's possible that the launched view can still be resolved to a visible task view, check 115 // the task id of the opening task and see if we can find a match. 116 if (v.getTag() instanceof ItemInfo) { 117 ItemInfo itemInfo = (ItemInfo) v.getTag(); 118 ComponentName componentName = itemInfo.getTargetComponent(); 119 int userId = itemInfo.user.getIdentifier(); 120 if (componentName != null) { 121 for (int i = 0; i < recentsView.getTaskViewCount(); i++) { 122 TaskView taskView = recentsView.getTaskViewAt(i); 123 if (recentsView.isTaskViewVisible(taskView)) { 124 Task.TaskKey key = taskView.getFirstTask().key; 125 if (componentName.equals(key.getComponent()) && userId == key.userId) { 126 return taskView; 127 } 128 } 129 } 130 } 131 } 132 133 if (targets == null) { 134 return null; 135 } 136 // Resolve the opening task id 137 int openingTaskId = -1; 138 for (RemoteAnimationTarget target : targets) { 139 if (target.mode == MODE_OPENING) { 140 openingTaskId = target.taskId; 141 break; 142 } 143 } 144 145 // If there is no opening task id, fall back to the normal app icon launch animation 146 if (openingTaskId == -1) { 147 return null; 148 } 149 150 // If the opening task id is not currently visible in overview, then fall back to normal app 151 // icon launch animation 152 TaskView taskView = recentsView.getTaskViewByTaskId(openingTaskId); 153 if (taskView == null || !recentsView.isTaskViewVisible(taskView)) { 154 return null; 155 } 156 return taskView; 157 } 158 createRecentsWindowAnimator( @onNull RecentsView recentsView, @NonNull TaskView v, boolean skipViewChanges, @NonNull RemoteAnimationTarget[] appTargets, @NonNull RemoteAnimationTarget[] wallpaperTargets, @NonNull RemoteAnimationTarget[] nonAppTargets, @Nullable DepthController depthController, PendingAnimation out)159 public static void createRecentsWindowAnimator( 160 @NonNull RecentsView recentsView, 161 @NonNull TaskView v, 162 boolean skipViewChanges, 163 @NonNull RemoteAnimationTarget[] appTargets, 164 @NonNull RemoteAnimationTarget[] wallpaperTargets, 165 @NonNull RemoteAnimationTarget[] nonAppTargets, 166 @Nullable DepthController depthController, 167 PendingAnimation out) { 168 boolean isQuickSwitch = v.isEndQuickSwitchCuj(); 169 v.setEndQuickSwitchCuj(false); 170 171 final RemoteAnimationTargets targets = 172 new RemoteAnimationTargets(appTargets, wallpaperTargets, nonAppTargets, 173 MODE_OPENING); 174 final RemoteAnimationTarget navBarTarget = targets.getNavBarRemoteAnimationTarget(); 175 176 SurfaceTransactionApplier applier = new SurfaceTransactionApplier(v); 177 targets.addReleaseCheck(applier); 178 179 RemoteTargetHandle[] remoteTargetHandles; 180 RemoteTargetHandle[] recentsViewHandles = recentsView.getRemoteTargetHandles(); 181 if (v.isRunningTask() && recentsViewHandles != null) { 182 // Re-use existing handles 183 remoteTargetHandles = recentsViewHandles; 184 } else { 185 boolean forDesktop = v instanceof DesktopTaskView; 186 RemoteTargetGluer gluer = new RemoteTargetGluer(v.getContext(), 187 recentsView.getSizeStrategy(), targets, forDesktop); 188 if (forDesktop) { 189 remoteTargetHandles = gluer.assignTargetsForDesktop(targets); 190 } else if (v.containsMultipleTasks()) { 191 remoteTargetHandles = gluer.assignTargetsForSplitScreen(targets, 192 ((GroupedTaskView) v).getSplitBoundsConfig()); 193 } else { 194 remoteTargetHandles = gluer.assignTargets(targets); 195 } 196 } 197 198 final int recentsActivityRotation = 199 recentsView.getPagedViewOrientedState().getRecentsActivityRotation(); 200 for (RemoteTargetHandle remoteTargetHandle : remoteTargetHandles) { 201 remoteTargetHandle.getTaskViewSimulator().getOrientationState() 202 .setRecentsRotation(recentsActivityRotation); 203 remoteTargetHandle.getTransformParams().setSyncTransactionApplier(applier); 204 } 205 206 int taskIndex = recentsView.indexOfChild(v); 207 Context context = v.getContext(); 208 BaseActivity baseActivity = BaseActivity.fromContext(context); 209 DeviceProfile dp = baseActivity.getDeviceProfile(); 210 boolean showAsGrid = dp.isTablet; 211 boolean parallaxCenterAndAdjacentTask = 212 !showAsGrid && taskIndex != recentsView.getCurrentPage(); 213 int taskRectTranslationPrimary = recentsView.getScrollOffset(taskIndex); 214 int taskRectTranslationSecondary = showAsGrid ? (int) v.getGridTranslationY() : 0; 215 216 RemoteTargetHandle[] topMostSimulators = null; 217 218 if (!v.isRunningTask()) { 219 // TVSs already initialized from the running task, no need to re-init 220 for (RemoteTargetHandle targetHandle : remoteTargetHandles) { 221 TaskViewSimulator tvsLocal = targetHandle.getTaskViewSimulator(); 222 tvsLocal.setDp(dp); 223 224 // RecentsView never updates the display rotation until swipe-up so the value may 225 // be stale. Use the display value instead. 226 int displayRotation = DisplayController.INSTANCE.get(context).getInfo().rotation; 227 tvsLocal.getOrientationState().update(displayRotation, displayRotation); 228 229 tvsLocal.fullScreenProgress.value = 0; 230 tvsLocal.recentsViewScale.value = 1; 231 tvsLocal.setIsGridTask(v.isGridTask()); 232 tvsLocal.getOrientationState().getOrientationHandler().set(tvsLocal, 233 TaskViewSimulator::setTaskRectTranslation, taskRectTranslationPrimary, 234 taskRectTranslationSecondary); 235 236 // Fade in the task during the initial 20% of the animation 237 out.addFloat(targetHandle.getTransformParams(), TransformParams.TARGET_ALPHA, 0, 1, 238 clampToProgress(LINEAR, 0, 0.2f)); 239 } 240 } 241 242 for (RemoteTargetHandle targetHandle : remoteTargetHandles) { 243 TaskViewSimulator tvsLocal = targetHandle.getTaskViewSimulator(); 244 out.setFloat(tvsLocal.fullScreenProgress, 245 AnimatedFloat.VALUE, 1, TOUCH_RESPONSE); 246 out.setFloat(tvsLocal.recentsViewScale, 247 AnimatedFloat.VALUE, tvsLocal.getFullScreenScale(), 248 TOUCH_RESPONSE); 249 out.setFloat(tvsLocal.recentsViewScroll, AnimatedFloat.VALUE, 0, 250 TOUCH_RESPONSE); 251 out.addListener(new AnimatorListenerAdapter() { 252 @Override 253 public void onAnimationStart(Animator animation) { 254 super.onAnimationStart(animation); 255 final SurfaceTransaction showTransaction = new SurfaceTransaction(); 256 for (int i = targets.apps.length - 1; i >= 0; --i) { 257 showTransaction.getTransaction().show(targets.apps[i].leash); 258 } 259 applier.scheduleApply(showTransaction); 260 } 261 }); 262 out.addOnFrameCallback(() -> { 263 for (RemoteTargetHandle handle : remoteTargetHandles) { 264 handle.getTaskViewSimulator().apply(handle.getTransformParams()); 265 } 266 }); 267 if (navBarTarget != null) { 268 final Rect cropRect = new Rect(); 269 out.addOnFrameListener(new MultiValueUpdateListener() { 270 FloatProp mNavFadeOut = new FloatProp(1f, 0f, clampToDuration( 271 NAV_FADE_OUT_INTERPOLATOR, 272 0, 273 ANIMATION_NAV_FADE_OUT_DURATION, 274 out.getDuration())); 275 FloatProp mNavFadeIn = new FloatProp(0f, 1f, clampToDuration( 276 NAV_FADE_IN_INTERPOLATOR, 277 ANIMATION_DELAY_NAV_FADE_IN, 278 ANIMATION_NAV_FADE_IN_DURATION, 279 out.getDuration())); 280 281 @Override 282 public void onUpdate(float percent, boolean initOnly) { 283 284 285 // TODO Do we need to operate over multiple TVSs for the navbar leash? 286 for (RemoteTargetHandle handle : remoteTargetHandles) { 287 SurfaceTransaction transaction = new SurfaceTransaction(); 288 SurfaceProperties navBuilder = 289 transaction.forSurface(navBarTarget.leash); 290 291 if (mNavFadeIn.value > mNavFadeIn.getStartValue()) { 292 TaskViewSimulator taskViewSimulator = handle.getTaskViewSimulator(); 293 taskViewSimulator.getCurrentCropRect().round(cropRect); 294 navBuilder.setMatrix(taskViewSimulator.getCurrentMatrix()) 295 .setWindowCrop(cropRect) 296 .setAlpha(mNavFadeIn.value); 297 } else { 298 navBuilder.setAlpha(mNavFadeOut.value); 299 } 300 handle.getTransformParams().applySurfaceParams(transaction); 301 } 302 } 303 }); 304 } else { 305 // There is no transition animation for app launch from recent in live tile mode so 306 // we have to trigger the navigation bar animation from system here. 307 final RecentsAnimationController controller = 308 recentsView.getRecentsAnimationController(); 309 if (controller != null) { 310 controller.animateNavigationBarToApp(RECENTS_LAUNCH_DURATION); 311 } 312 } 313 topMostSimulators = remoteTargetHandles; 314 } 315 316 if (!skipViewChanges && parallaxCenterAndAdjacentTask && topMostSimulators != null 317 && topMostSimulators.length > 0) { 318 out.addFloat(v, VIEW_ALPHA, 1, 0, clampToProgress(LINEAR, 0.2f, 0.4f)); 319 320 RemoteTargetHandle[] simulatorCopies = topMostSimulators; 321 for (RemoteTargetHandle handle : simulatorCopies) { 322 handle.getTaskViewSimulator().apply(handle.getTransformParams()); 323 } 324 325 // Mt represents the overall transformation on the thumbnailView relative to the 326 // Launcher's rootView 327 // K(t) represents transformation on the running window by the taskViewSimulator at 328 // any time t. 329 // at t = 0, we know that the simulator matches the thumbnailView. So if we apply K(0)` 330 // on the Launcher's rootView, the thumbnailView would match the full running task 331 // window. If we apply "K(0)` K(t)" thumbnailView will match the final transformed 332 // window at any time t. This gives the overall matrix on thumbnailView to be: 333 // Mt K(0)` K(t) 334 // During animation we apply transformation on the thumbnailView (and not the rootView) 335 // to follow the TaskViewSimulator. So the final matrix applied on the thumbnailView is: 336 // Mt K(0)` K(t) Mt` 337 TaskThumbnailViewDeprecated[] thumbnails = v.getThumbnailViews(); 338 339 // In case simulator copies and thumbnail size do no match, ensure we get the lesser. 340 // This ensures we do not create arrays with empty elements or attempt to references 341 // indexes out of array bounds. 342 final int matrixSize = Math.min(simulatorCopies.length, thumbnails.length); 343 344 Matrix[] mt = new Matrix[matrixSize]; 345 Matrix[] mti = new Matrix[matrixSize]; 346 for (int i = 0; i < matrixSize; i++) { 347 TaskThumbnailViewDeprecated ttv = thumbnails[i]; 348 RectF localBounds = new RectF(0, 0, ttv.getWidth(), ttv.getHeight()); 349 float[] tvBoundsMapped = new float[]{0, 0, ttv.getWidth(), ttv.getHeight()}; 350 getDescendantCoordRelativeToAncestor(ttv, ttv.getRootView(), tvBoundsMapped, false); 351 RectF localBoundsInRoot = new RectF( 352 tvBoundsMapped[0], tvBoundsMapped[1], 353 tvBoundsMapped[2], tvBoundsMapped[3]); 354 Matrix localMt = new Matrix(); 355 localMt.setRectToRect(localBounds, localBoundsInRoot, ScaleToFit.FILL); 356 mt[i] = localMt; 357 358 Matrix localMti = new Matrix(); 359 localMt.invert(localMti); 360 mti[i] = localMti; 361 362 // Translations for child thumbnails also get scaled as the parent taskView scales 363 // Add inverse scaling to keep translations the same 364 float translationY = ttv.getTranslationY(); 365 float translationX = ttv.getTranslationX(); 366 float fullScreenScale = 367 topMostSimulators[i].getTaskViewSimulator().getFullScreenScale(); 368 out.addFloat(ttv, VIEW_TRANSLATE_Y, translationY, 369 translationY / fullScreenScale, TOUCH_RESPONSE); 370 out.addFloat(ttv, VIEW_TRANSLATE_X, translationX, 371 translationX / fullScreenScale, TOUCH_RESPONSE); 372 } 373 374 Matrix[] k0i = new Matrix[matrixSize]; 375 for (int i = 0; i < matrixSize; i++) { 376 k0i[i] = new Matrix(); 377 simulatorCopies[i].getTaskViewSimulator().getCurrentMatrix().invert(k0i[i]); 378 } 379 Matrix animationMatrix = new Matrix(); 380 out.addOnFrameCallback(() -> { 381 for (int i = 0; i < matrixSize; i++) { 382 animationMatrix.set(mt[i]); 383 animationMatrix.postConcat(k0i[i]); 384 animationMatrix.postConcat(simulatorCopies[i] 385 .getTaskViewSimulator().getCurrentMatrix()); 386 animationMatrix.postConcat(mti[i]); 387 thumbnails[i].setAnimationMatrix(animationMatrix); 388 } 389 }); 390 391 out.addListener(new AnimatorListenerAdapter() { 392 @Override 393 public void onAnimationEnd(Animator animation) { 394 for (TaskThumbnailViewDeprecated ttv : thumbnails) { 395 ttv.setAnimationMatrix(null); 396 } 397 } 398 }); 399 } 400 401 out.addListener(new AnimationSuccessListener() { 402 @Override 403 public void onAnimationStart(Animator animation) { 404 for (RemoteTargetHandle remoteTargetHandle : remoteTargetHandles) { 405 remoteTargetHandle.getTaskViewSimulator().setDrawsBelowRecents(false); 406 } 407 } 408 409 @Override 410 public void onAnimationSuccess(Animator animator) { 411 if (isQuickSwitch) { 412 InteractionJankMonitorWrapper.end(Cuj.CUJ_LAUNCHER_QUICK_SWITCH); 413 } 414 } 415 416 @Override 417 public void onAnimationEnd(Animator animation) { 418 targets.release(); 419 super.onAnimationEnd(animation); 420 } 421 }); 422 423 if (depthController != null) { 424 out.setFloat(depthController.stateDepth, MULTI_PROPERTY_VALUE, 425 BACKGROUND_APP.getDepth(baseActivity), TOUCH_RESPONSE); 426 } 427 } 428 429 /** 430 * If {@param launchingTaskView} is not null, then this will play the tasks launch animation 431 * from the position of the GroupedTaskView (when user taps on the TaskView to start it). 432 * Technically this case should be taken care of by 433 * {@link #composeRecentsSplitLaunchAnimatorLegacy} below, but the way we launch tasks whether 434 * it's a single task or multiple tasks results in different entry-points. 435 */ composeRecentsSplitLaunchAnimator(GroupedTaskView launchingTaskView, @NonNull StateManager stateManager, @Nullable DepthController depthController, @NonNull TransitionInfo transitionInfo, SurfaceControl.Transaction t, @NonNull Runnable finishCallback)436 public static void composeRecentsSplitLaunchAnimator(GroupedTaskView launchingTaskView, 437 @NonNull StateManager stateManager, @Nullable DepthController depthController, 438 @NonNull TransitionInfo transitionInfo, SurfaceControl.Transaction t, 439 @NonNull Runnable finishCallback) { 440 AnimatorSet animatorSet = new AnimatorSet(); 441 animatorSet.addListener(new AnimatorListenerAdapter() { 442 @Override 443 public void onAnimationEnd(Animator animation) { 444 finishCallback.run(); 445 } 446 }); 447 448 final RemoteAnimationTarget[] appTargets = 449 RemoteAnimationTargetCompat.wrapApps(transitionInfo, t, null /* leashMap */); 450 final RemoteAnimationTarget[] wallpaperTargets = 451 RemoteAnimationTargetCompat.wrapNonApps( 452 transitionInfo, true /* wallpapers */, t, null /* leashMap */); 453 final RemoteAnimationTarget[] nonAppTargets = 454 RemoteAnimationTargetCompat.wrapNonApps( 455 transitionInfo, false /* wallpapers */, t, null /* leashMap */); 456 final RecentsView recentsView = launchingTaskView.getRecentsView(); 457 composeRecentsLaunchAnimator(animatorSet, launchingTaskView, appTargets, wallpaperTargets, 458 nonAppTargets, /* launcherClosing */ true, stateManager, recentsView, 459 depthController); 460 461 t.apply(); 462 animatorSet.start(); 463 } 464 465 /** 466 * Legacy version (until shell transitions are enabled) 467 * 468 * If {@param launchingTaskView} is not null, then this will play the tasks launch animation 469 * from the position of the GroupedTaskView (when user taps on the TaskView to start it). 470 * Technically this case should be taken care of by 471 * {@link #composeRecentsSplitLaunchAnimatorLegacy} below, but the way we launch tasks whether 472 * it's a single task or multiple tasks results in different entry-points. 473 * 474 * If it is null, then it will simply fade in the starting apps and fade out launcher (for the 475 * case where launcher handles animating starting split tasks from app icon) 476 * @deprecated with shell transitions 477 */ composeRecentsSplitLaunchAnimatorLegacy( @ullable GroupedTaskView launchingTaskView, int initialTaskId, int secondTaskId, @NonNull RemoteAnimationTarget[] appTargets, @NonNull RemoteAnimationTarget[] wallpaperTargets, @NonNull RemoteAnimationTarget[] nonAppTargets, @NonNull StateManager stateManager, @Nullable DepthController depthController, @NonNull Runnable finishCallback)478 public static void composeRecentsSplitLaunchAnimatorLegacy( 479 @Nullable GroupedTaskView launchingTaskView, int initialTaskId, int secondTaskId, 480 @NonNull RemoteAnimationTarget[] appTargets, 481 @NonNull RemoteAnimationTarget[] wallpaperTargets, 482 @NonNull RemoteAnimationTarget[] nonAppTargets, 483 @NonNull StateManager stateManager, 484 @Nullable DepthController depthController, 485 @NonNull Runnable finishCallback) { 486 if (launchingTaskView != null) { 487 AnimatorSet animatorSet = new AnimatorSet(); 488 RecentsView recentsView = launchingTaskView.getRecentsView(); 489 animatorSet.addListener(new AnimatorListenerAdapter() { 490 @Override 491 public void onAnimationEnd(Animator animation) { 492 finishCallback.run(); 493 } 494 }); 495 composeRecentsLaunchAnimator(animatorSet, launchingTaskView, 496 appTargets, wallpaperTargets, nonAppTargets, 497 true, stateManager, 498 recentsView, depthController); 499 animatorSet.start(); 500 return; 501 } 502 503 final ArrayList<SurfaceControl> openingTargets = new ArrayList<>(); 504 final ArrayList<SurfaceControl> closingTargets = new ArrayList<>(); 505 for (RemoteAnimationTarget appTarget : appTargets) { 506 final int taskId = appTarget.taskInfo != null ? appTarget.taskInfo.taskId : -1; 507 final int mode = appTarget.mode; 508 final SurfaceControl leash = appTarget.leash; 509 if (leash == null) { 510 continue; 511 } 512 513 if (mode == MODE_OPENING) { 514 openingTargets.add(leash); 515 } else if (taskId == initialTaskId || taskId == secondTaskId) { 516 throw new IllegalStateException("Expected task to be opening, but it is " + mode); 517 } else if (mode == MODE_CLOSING) { 518 closingTargets.add(leash); 519 } 520 } 521 522 for (int i = 0; i < nonAppTargets.length; ++i) { 523 final SurfaceControl leash = nonAppTargets[i].leash; 524 if (nonAppTargets[i].windowType == TYPE_DOCK_DIVIDER && leash != null) { 525 openingTargets.add(leash); 526 } 527 } 528 529 final SurfaceControl.Transaction t = new SurfaceControl.Transaction(); 530 ValueAnimator animator = ValueAnimator.ofFloat(0f, 1f); 531 animator.setDuration(SPLIT_LAUNCH_DURATION); 532 animator.addUpdateListener(valueAnimator -> { 533 float progress = valueAnimator.getAnimatedFraction(); 534 for (SurfaceControl leash: openingTargets) { 535 t.setAlpha(leash, progress); 536 } 537 t.apply(); 538 }); 539 animator.addListener(new AnimatorListenerAdapter() { 540 @Override 541 public void onAnimationStart(Animator animation) { 542 for (SurfaceControl leash: openingTargets) { 543 t.show(leash).setAlpha(leash, 0.0f); 544 } 545 t.apply(); 546 } 547 548 @Override 549 public void onAnimationEnd(Animator animation) { 550 for (SurfaceControl leash: closingTargets) { 551 t.hide(leash); 552 } 553 finishCallback.run(); 554 } 555 }); 556 animator.start(); 557 } 558 559 /** 560 * Start recents to desktop animation 561 */ composeRecentsDesktopLaunchAnimator( @onNull DesktopTaskView launchingTaskView, @NonNull StateManager stateManager, @Nullable DepthController depthController, @NonNull TransitionInfo transitionInfo, SurfaceControl.Transaction t, @NonNull Runnable finishCallback)562 public static void composeRecentsDesktopLaunchAnimator( 563 @NonNull DesktopTaskView launchingTaskView, 564 @NonNull StateManager stateManager, @Nullable DepthController depthController, 565 @NonNull TransitionInfo transitionInfo, 566 SurfaceControl.Transaction t, @NonNull Runnable finishCallback) { 567 568 AnimatorSet animatorSet = new AnimatorSet(); 569 animatorSet.addListener(new AnimatorListenerAdapter() { 570 @Override 571 public void onAnimationStart(Animator animation) { 572 t.apply(); 573 } 574 575 @Override 576 public void onAnimationEnd(Animator animation) { 577 finishCallback.run(); 578 } 579 }); 580 581 final RemoteAnimationTarget[] apps = RemoteAnimationTargetCompat.wrapApps( 582 transitionInfo, t, null /* leashMap */); 583 final RemoteAnimationTarget[] wallpaper = RemoteAnimationTargetCompat.wrapNonApps( 584 transitionInfo, true /* wallpapers */, t, null /* leashMap */); 585 final RemoteAnimationTarget[] nonApps = RemoteAnimationTargetCompat.wrapNonApps( 586 transitionInfo, false /* wallpapers */, t, null /* leashMap */); 587 588 composeRecentsLaunchAnimator(animatorSet, launchingTaskView, apps, wallpaper, nonApps, 589 true /* launcherClosing */, stateManager, launchingTaskView.getRecentsView(), 590 depthController); 591 592 animatorSet.start(); 593 } 594 composeRecentsLaunchAnimator(@onNull AnimatorSet anim, @NonNull View v, @NonNull RemoteAnimationTarget[] appTargets, @NonNull RemoteAnimationTarget[] wallpaperTargets, @NonNull RemoteAnimationTarget[] nonAppTargets, boolean launcherClosing, @NonNull StateManager stateManager, @NonNull RecentsView recentsView, @Nullable DepthController depthController)595 public static void composeRecentsLaunchAnimator(@NonNull AnimatorSet anim, @NonNull View v, 596 @NonNull RemoteAnimationTarget[] appTargets, 597 @NonNull RemoteAnimationTarget[] wallpaperTargets, 598 @NonNull RemoteAnimationTarget[] nonAppTargets, boolean launcherClosing, 599 @NonNull StateManager stateManager, @NonNull RecentsView recentsView, 600 @Nullable DepthController depthController) { 601 boolean skipLauncherChanges = !launcherClosing; 602 603 TaskView taskView = findTaskViewToLaunch(recentsView, v, appTargets); 604 PendingAnimation pa = new PendingAnimation(RECENTS_LAUNCH_DURATION); 605 createRecentsWindowAnimator(recentsView, taskView, skipLauncherChanges, appTargets, 606 wallpaperTargets, nonAppTargets, depthController, pa); 607 if (launcherClosing) { 608 // TODO(b/182592057): differentiate between "restore split" vs "launch fullscreen app" 609 TaskViewUtils.createSplitAuxiliarySurfacesAnimator(nonAppTargets, true /*shown*/, 610 (dividerAnimator) -> { 611 // If split apps are launching, we want to delay showing the divider bar 612 // until the very end once the apps are mostly in place. This is because we 613 // aren't moving the divider leash in the relative position with the 614 // launching apps. 615 dividerAnimator.setStartDelay(pa.getDuration() 616 - SPLIT_DIVIDER_ANIM_DURATION); 617 pa.add(dividerAnimator); 618 }); 619 } 620 621 Animator childStateAnimation = null; 622 // Found a visible recents task that matches the opening app, lets launch the app from there 623 Animator launcherAnim; 624 final AnimatorListenerAdapter windowAnimEndListener; 625 if (launcherClosing) { 626 // Since Overview is in launcher, just opening overview sets willFinishToHome to true. 627 // Now that we are closing the launcher, we need to (re)set willFinishToHome back to 628 // false. Otherwise, RecentsAnimationController can't differentiate between closing 629 // overview to 3p home vs closing overview to app. 630 final RecentsAnimationController raController = 631 recentsView.getRecentsAnimationController(); 632 if (raController != null) { 633 raController.setWillFinishToHome(false); 634 } 635 launcherAnim = recentsView.createAdjacentPageAnimForTaskLaunch(taskView); 636 launcherAnim.setInterpolator(Interpolators.TOUCH_RESPONSE); 637 launcherAnim.setDuration(RECENTS_LAUNCH_DURATION); 638 639 windowAnimEndListener = new AnimationSuccessListener() { 640 @Override 641 public void onAnimationStart(Animator animation) { 642 recentsView.onTaskLaunchedInLiveTileMode(); 643 } 644 645 // Make sure recents gets fixed up by resetting task alphas and scales, etc. 646 // This should only be run onAnimationSuccess, otherwise finishRecentsAnimation will 647 // interfere with a rapid swipe up to home in the live tile + running task case. 648 @Override 649 public void onAnimationSuccess(Animator animation) { 650 recentsView.finishRecentsAnimation(false /* toRecents */, () -> { 651 recentsView.post(() -> { 652 stateManager.moveToRestState(); 653 stateManager.reapplyState(); 654 655 // We may have notified launcher is not visible so that taskbar can 656 // stash immediately. Now that the animation is over, we can update 657 // that launcher is still visible. 658 TaskbarUIController controller = recentsView.getSizeStrategy() 659 .getTaskbarController(); 660 if (controller != null) { 661 boolean launcherVisible = true; 662 for (RemoteAnimationTarget target : appTargets) { 663 launcherVisible &= target.isTranslucent; 664 } 665 if (launcherVisible) { 666 controller.onLauncherVisibilityChanged(true); 667 } 668 } 669 }); 670 }); 671 } 672 673 @Override 674 public void onAnimationCancel(Animator animation) { 675 super.onAnimationCancel(animation); 676 recentsView.onTaskLaunchedInLiveTileModeCancelled(); 677 } 678 679 @Override 680 public void onAnimationEnd(Animator animation) { 681 super.onAnimationEnd(animation); 682 recentsView.setTaskLaunchCancelledRunnable(null); 683 } 684 }; 685 } else { 686 AnimatorPlaybackController controller = 687 stateManager.createAnimationToNewWorkspace(NORMAL, RECENTS_LAUNCH_DURATION); 688 controller.dispatchOnStart(); 689 childStateAnimation = controller.getTarget(); 690 launcherAnim = controller.getAnimationPlayer().setDuration(RECENTS_LAUNCH_DURATION); 691 windowAnimEndListener = new AnimatorListenerAdapter() { 692 @Override 693 public void onAnimationEnd(Animator animation) { 694 recentsView.finishRecentsAnimation(false /* toRecents */, 695 () -> stateManager.goToState(NORMAL, false)); 696 } 697 }; 698 } 699 pa.add(launcherAnim); 700 if (recentsView.getRunningTaskIndex() != -1) { 701 pa.addOnFrameCallback(recentsView::redrawLiveTile); 702 } 703 anim.play(pa.buildAnim()); 704 705 // Set the current animation first, before adding windowAnimEndListener. Setting current 706 // animation adds some listeners which need to be called before windowAnimEndListener 707 // (the ordering of listeners matter in this case). 708 stateManager.setCurrentAnimation(anim, childStateAnimation); 709 anim.addListener(windowAnimEndListener); 710 } 711 712 /** 713 * Creates an animation to show/hide the auxiliary surfaces (aka. divider bar), only calling 714 * {@param animatorHandler} if there are valid surfaces to animate. 715 * Passing null handler to apply the visibility immediately. 716 * 717 * @return the animator animating the surfaces 718 */ createSplitAuxiliarySurfacesAnimator( @ullable RemoteAnimationTarget[] nonApps, boolean shown, @Nullable Consumer<ValueAnimator> animatorHandler)719 public static ValueAnimator createSplitAuxiliarySurfacesAnimator( 720 @Nullable RemoteAnimationTarget[] nonApps, boolean shown, 721 @Nullable Consumer<ValueAnimator> animatorHandler) { 722 if (nonApps == null || nonApps.length == 0) { 723 return null; 724 } 725 726 List<SurfaceControl> auxiliarySurfaces = new ArrayList<>(); 727 for (RemoteAnimationTarget target : nonApps) { 728 final SurfaceControl leash = target.leash; 729 if (target.windowType == TYPE_DOCK_DIVIDER && leash != null && leash.isValid()) { 730 auxiliarySurfaces.add(leash); 731 } 732 } 733 if (auxiliarySurfaces.isEmpty()) { 734 return null; 735 } 736 737 SurfaceControl.Transaction t = new SurfaceControl.Transaction(); 738 if (animatorHandler == null) { 739 // Apply the visibility directly without fade animation. 740 for (SurfaceControl leash : auxiliarySurfaces) { 741 t.setVisibility(leash, shown); 742 } 743 t.apply(); 744 t.close(); 745 return null; 746 } 747 748 ValueAnimator dockFadeAnimator = ValueAnimator.ofFloat(0f, 1f); 749 dockFadeAnimator.addUpdateListener(valueAnimator -> { 750 float progress = valueAnimator.getAnimatedFraction(); 751 for (SurfaceControl leash : auxiliarySurfaces) { 752 if (leash != null && leash.isValid()) { 753 t.setAlpha(leash, shown ? progress : 1 - progress); 754 } 755 } 756 t.apply(); 757 }); 758 dockFadeAnimator.addListener(new AnimatorListenerAdapter() { 759 @Override 760 public void onAnimationStart(Animator animation) { 761 if (shown) { 762 for (SurfaceControl leash : auxiliarySurfaces) { 763 t.setLayer(leash, Integer.MAX_VALUE); 764 t.setAlpha(leash, 0); 765 t.show(leash); 766 } 767 t.apply(); 768 } 769 } 770 771 @Override 772 public void onAnimationEnd(Animator animation) { 773 if (!shown) { 774 for (SurfaceControl leash : auxiliarySurfaces) { 775 if (leash != null && leash.isValid()) { 776 t.hide(leash); 777 } 778 } 779 t.apply(); 780 } 781 t.close(); 782 } 783 }); 784 dockFadeAnimator.setDuration(SPLIT_DIVIDER_ANIM_DURATION); 785 animatorHandler.accept(dockFadeAnimator); 786 return dockFadeAnimator; 787 } 788 } 789