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