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