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