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