1 /* 2 * Copyright (C) 2021 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 17 package com.android.wm.shell.taskview; 18 19 import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW; 20 import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED; 21 import static android.view.WindowManager.TRANSIT_CHANGE; 22 import static android.view.WindowManager.TRANSIT_CLOSE; 23 import static android.view.WindowManager.TRANSIT_NONE; 24 import static android.view.WindowManager.TRANSIT_OPEN; 25 import static android.view.WindowManager.TRANSIT_TO_BACK; 26 import static android.view.WindowManager.TRANSIT_TO_FRONT; 27 28 import android.annotation.NonNull; 29 import android.annotation.Nullable; 30 import android.app.ActivityManager; 31 import android.app.ActivityOptions; 32 import android.app.PendingIntent; 33 import android.content.Context; 34 import android.content.Intent; 35 import android.content.pm.LauncherApps; 36 import android.content.pm.ShortcutInfo; 37 import android.graphics.Rect; 38 import android.os.Binder; 39 import android.os.IBinder; 40 import android.util.ArrayMap; 41 import android.util.Slog; 42 import android.view.SurfaceControl; 43 import android.view.WindowManager; 44 import android.window.TransitionInfo; 45 import android.window.TransitionRequestInfo; 46 import android.window.WindowContainerToken; 47 import android.window.WindowContainerTransaction; 48 49 import androidx.annotation.VisibleForTesting; 50 51 import com.android.wm.shell.Flags; 52 import com.android.wm.shell.ShellTaskOrganizer; 53 import com.android.wm.shell.common.SyncTransactionQueue; 54 import com.android.wm.shell.shared.TransitionUtil; 55 import com.android.wm.shell.shared.bubbles.BubbleAnythingFlagHelper; 56 import com.android.wm.shell.transition.Transitions; 57 58 import java.util.ArrayList; 59 import java.util.Map; 60 import java.util.Objects; 61 import java.util.WeakHashMap; 62 import java.util.concurrent.Executor; 63 64 /** 65 * Handles Shell Transitions that involve TaskView tasks. 66 */ 67 public class TaskViewTransitions implements Transitions.TransitionHandler, TaskViewController { 68 static final String TAG = "TaskViewTransitions"; 69 70 /** 71 * Map of {@link TaskViewTaskController} to {@link TaskViewRepository.TaskViewState}. 72 * <p> 73 * {@link TaskView} keeps a reference to the {@link TaskViewTaskController} instance and 74 * manages its lifecycle. 75 * Only keep a weak reference to the controller instance here to allow for it to be cleaned 76 * up when its TaskView is no longer used. 77 */ 78 private final Map<TaskViewTaskController, TaskViewRepository.TaskViewState> mTaskViews; 79 private final TaskViewRepository mTaskViewRepo; 80 private final ArrayList<PendingTransition> mPending = new ArrayList<>(); 81 private final Transitions mTransitions; 82 private final boolean[] mRegistered = new boolean[]{false}; 83 private final ShellTaskOrganizer mTaskOrganizer; 84 private final Executor mShellExecutor; 85 private final SyncTransactionQueue mSyncQueue; 86 87 /** A temp transaction used for quick things. */ 88 private final SurfaceControl.Transaction mTransaction = new SurfaceControl.Transaction(); 89 90 /** 91 * TaskView makes heavy use of startTransition. Only one shell-initiated transition can be 92 * in-flight (collecting) at a time (because otherwise, the operations could get merged into 93 * a single transition). So, keep a queue here until we add a queue in server-side. 94 */ 95 @VisibleForTesting 96 static class PendingTransition { 97 final @WindowManager.TransitionType int mType; 98 final WindowContainerTransaction mWct; 99 final @NonNull TaskViewTaskController mTaskView; 100 ExternalTransition mExternalTransition; 101 IBinder mClaimed; 102 103 /** 104 * This is needed because arbitrary activity launches can still "intrude" into any 105 * transition since `startActivity` is a synchronous call. Once that is solved, we can 106 * remove this. 107 */ 108 final IBinder mLaunchCookie; 109 PendingTransition(@indowManager.TransitionType int type, @Nullable WindowContainerTransaction wct, @NonNull TaskViewTaskController taskView, @Nullable IBinder launchCookie)110 PendingTransition(@WindowManager.TransitionType int type, 111 @Nullable WindowContainerTransaction wct, 112 @NonNull TaskViewTaskController taskView, 113 @Nullable IBinder launchCookie) { 114 mType = type; 115 mWct = wct; 116 mTaskView = taskView; 117 mLaunchCookie = launchCookie; 118 } 119 } 120 TaskViewTransitions(Transitions transitions, TaskViewRepository repository, ShellTaskOrganizer taskOrganizer, SyncTransactionQueue syncQueue)121 public TaskViewTransitions(Transitions transitions, TaskViewRepository repository, 122 ShellTaskOrganizer taskOrganizer, SyncTransactionQueue syncQueue) { 123 mTransitions = transitions; 124 mTaskOrganizer = taskOrganizer; 125 mShellExecutor = taskOrganizer.getExecutor(); 126 mSyncQueue = syncQueue; 127 if (useRepo()) { 128 mTaskViews = null; 129 } else if (Flags.enableTaskViewControllerCleanup()) { 130 mTaskViews = new WeakHashMap<>(); 131 } else { 132 mTaskViews = new ArrayMap<>(); 133 } 134 mTaskViewRepo = repository; 135 // Defer registration until the first TaskView because we want this to be the "first" in 136 // priority when handling requests. 137 // TODO(210041388): register here once we have an explicit ordering mechanism. 138 } 139 140 /** @return whether the shared taskview repository is being used. */ useRepo()141 public static boolean useRepo() { 142 return Flags.taskViewRepository() || Flags.enableBubbleAnything(); 143 } 144 getRepository()145 public TaskViewRepository getRepository() { 146 return mTaskViewRepo; 147 } 148 149 @Override registerTaskView(TaskViewTaskController tv)150 public void registerTaskView(TaskViewTaskController tv) { 151 synchronized (mRegistered) { 152 if (!mRegistered[0]) { 153 mRegistered[0] = true; 154 mTransitions.addHandler(this); 155 } 156 } 157 if (useRepo()) { 158 mTaskViewRepo.add(tv); 159 } else { 160 mTaskViews.put(tv, new TaskViewRepository.TaskViewState(null)); 161 } 162 } 163 164 @Override unregisterTaskView(TaskViewTaskController tv)165 public void unregisterTaskView(TaskViewTaskController tv) { 166 if (useRepo()) { 167 mTaskViewRepo.remove(tv); 168 } else { 169 mTaskViews.remove(tv); 170 } 171 // Note: Don't unregister handler since this is a singleton with lifetime bound to Shell 172 } 173 174 @Override isUsingShellTransitions()175 public boolean isUsingShellTransitions() { 176 return mTransitions.isRegistered(); 177 } 178 179 /** 180 * Starts a transition outside of the handler associated with {@link TaskViewTransitions}. 181 */ startInstantTransition(@indowManager.TransitionType int type, WindowContainerTransaction wct)182 public void startInstantTransition(@WindowManager.TransitionType int type, 183 WindowContainerTransaction wct) { 184 mTransitions.startTransition(type, wct, null); 185 } 186 187 /** 188 * Starts or queues an "external" runnable into the pending queue. This means it will run 189 * in order relative to the local transitions. 190 * 191 * The external operation *must* call {@link #onExternalDone} once it has finished. 192 * 193 * In practice, the external is usually another transition on a different handler. 194 */ enqueueExternal(@onNull TaskViewTaskController taskView, ExternalTransition ext)195 public void enqueueExternal(@NonNull TaskViewTaskController taskView, ExternalTransition ext) { 196 final PendingTransition pending = new PendingTransition( 197 TRANSIT_NONE, null /* wct */, taskView, null /* cookie */); 198 pending.mExternalTransition = ext; 199 mPending.add(pending); 200 startNextTransition(); 201 } 202 203 /** 204 * An external transition run in this "queue" is required to call this once it becomes ready. 205 */ onExternalDone(IBinder key)206 public void onExternalDone(IBinder key) { 207 final PendingTransition pending = findPending(key); 208 if (pending == null) return; 209 mPending.remove(pending); 210 startNextTransition(); 211 } 212 213 /** 214 * Looks through the pending transitions for a opening transaction that matches the provided 215 * `taskView`. 216 * 217 * @param taskView the pending transition should be for this. 218 */ 219 @VisibleForTesting findPendingOpeningTransition(TaskViewTaskController taskView)220 PendingTransition findPendingOpeningTransition(TaskViewTaskController taskView) { 221 for (int i = mPending.size() - 1; i >= 0; --i) { 222 if (mPending.get(i).mTaskView != taskView) continue; 223 if (mPending.get(i).mExternalTransition != null) continue; 224 if (TransitionUtil.isOpeningType(mPending.get(i).mType)) { 225 return mPending.get(i); 226 } 227 } 228 return null; 229 } 230 231 /** 232 * Looks through the pending transitions for one matching `taskView`. 233 * 234 * @param taskView the pending transition should be for this. 235 * @param type the type of transition it's looking for 236 */ findPending(TaskViewTaskController taskView, int type)237 PendingTransition findPending(TaskViewTaskController taskView, int type) { 238 for (int i = mPending.size() - 1; i >= 0; --i) { 239 if (mPending.get(i).mTaskView != taskView) continue; 240 if (mPending.get(i).mExternalTransition != null) continue; 241 if (mPending.get(i).mType == type) { 242 return mPending.get(i); 243 } 244 } 245 return null; 246 } 247 findPending(IBinder claimed)248 private PendingTransition findPending(IBinder claimed) { 249 for (int i = 0; i < mPending.size(); ++i) { 250 if (mPending.get(i).mClaimed != claimed) continue; 251 return mPending.get(i); 252 } 253 return null; 254 } 255 256 /** @return whether there are pending transitions on TaskViews. */ hasPending()257 public boolean hasPending() { 258 return !mPending.isEmpty(); 259 } 260 261 @Override handleRequest(@onNull IBinder transition, @Nullable TransitionRequestInfo request)262 public WindowContainerTransaction handleRequest(@NonNull IBinder transition, 263 @Nullable TransitionRequestInfo request) { 264 final ActivityManager.RunningTaskInfo triggerTask = request.getTriggerTask(); 265 if (triggerTask == null) { 266 return null; 267 } 268 final TaskViewTaskController taskView = findTaskView(triggerTask); 269 if (taskView == null) return null; 270 // Opening types should all be initiated by shell 271 if (!TransitionUtil.isClosingType(request.getType())) return null; 272 PendingTransition pending = new PendingTransition(request.getType(), null, 273 taskView, null /* cookie */); 274 pending.mClaimed = transition; 275 mPending.add(pending); 276 return new WindowContainerTransaction(); 277 } 278 findTaskView(ActivityManager.RunningTaskInfo taskInfo)279 private TaskViewTaskController findTaskView(ActivityManager.RunningTaskInfo taskInfo) { 280 if (useRepo()) { 281 final TaskViewRepository.TaskViewState state = mTaskViewRepo.byToken(taskInfo.token); 282 return state != null ? state.getTaskView() : null; 283 } 284 if (Flags.enableTaskViewControllerCleanup()) { 285 for (TaskViewTaskController controller : mTaskViews.keySet()) { 286 if (controller.getTaskInfo() == null) continue; 287 if (taskInfo.token.equals(controller.getTaskInfo().token)) { 288 return controller; 289 } 290 } 291 } else { 292 ArrayMap<TaskViewTaskController, TaskViewRepository.TaskViewState> taskViews = 293 (ArrayMap<TaskViewTaskController, TaskViewRepository.TaskViewState>) mTaskViews; 294 for (int i = 0; i < taskViews.size(); ++i) { 295 if (taskViews.keyAt(i).getTaskInfo() == null) continue; 296 if (taskInfo.token.equals(taskViews.keyAt(i).getTaskInfo().token)) { 297 return taskViews.keyAt(i); 298 } 299 } 300 } 301 return null; 302 } 303 304 /** Returns true if the given {@code taskInfo} belongs to a task view. */ isTaskViewTask(ActivityManager.RunningTaskInfo taskInfo)305 public boolean isTaskViewTask(ActivityManager.RunningTaskInfo taskInfo) { 306 return findTaskView(taskInfo) != null; 307 } 308 prepareActivityOptions(ActivityOptions options, Rect launchBounds, @NonNull TaskViewTaskController destination)309 private void prepareActivityOptions(ActivityOptions options, Rect launchBounds, 310 @NonNull TaskViewTaskController destination) { 311 final Binder launchCookie = new Binder(); 312 mShellExecutor.execute(() -> { 313 mTaskOrganizer.setPendingLaunchCookieListener(launchCookie, destination); 314 }); 315 options.setLaunchBounds(launchBounds); 316 options.setLaunchCookie(launchCookie); 317 options.setLaunchWindowingMode(WINDOWING_MODE_MULTI_WINDOW); 318 options.setRemoveWithTaskOrganizer(true); 319 } 320 321 @Override startShortcutActivity(@onNull TaskViewTaskController destination, @NonNull ShortcutInfo shortcut, @NonNull ActivityOptions options, @Nullable Rect launchBounds)322 public void startShortcutActivity(@NonNull TaskViewTaskController destination, 323 @NonNull ShortcutInfo shortcut, @NonNull ActivityOptions options, 324 @Nullable Rect launchBounds) { 325 prepareActivityOptions(options, launchBounds, destination); 326 final Context context = destination.getContext(); 327 if (isUsingShellTransitions()) { 328 mShellExecutor.execute(() -> { 329 final WindowContainerTransaction wct = new WindowContainerTransaction(); 330 wct.startShortcut(context.getPackageName(), shortcut, options.toBundle()); 331 startTaskView(wct, destination, options.getLaunchCookie()); 332 }); 333 return; 334 } 335 try { 336 LauncherApps service = context.getSystemService(LauncherApps.class); 337 service.startShortcut(shortcut, null /* sourceBounds */, options.toBundle()); 338 } catch (Exception e) { 339 throw new RuntimeException(e); 340 } 341 } 342 343 @Override startActivity(@onNull TaskViewTaskController destination, @NonNull PendingIntent pendingIntent, @Nullable Intent fillInIntent, @NonNull ActivityOptions options, @Nullable Rect launchBounds)344 public void startActivity(@NonNull TaskViewTaskController destination, 345 @NonNull PendingIntent pendingIntent, @Nullable Intent fillInIntent, 346 @NonNull ActivityOptions options, @Nullable Rect launchBounds) { 347 prepareActivityOptions(options, launchBounds, destination); 348 if (isUsingShellTransitions()) { 349 mShellExecutor.execute(() -> { 350 WindowContainerTransaction wct = new WindowContainerTransaction(); 351 wct.sendPendingIntent(pendingIntent, fillInIntent, options.toBundle()); 352 startTaskView(wct, destination, options.getLaunchCookie()); 353 }); 354 return; 355 } 356 try { 357 pendingIntent.send(destination.getContext(), 0 /* code */, fillInIntent, 358 null /* onFinished */, null /* handler */, null /* requiredPermission */, 359 options.toBundle()); 360 } catch (Exception e) { 361 throw new RuntimeException(e); 362 } 363 } 364 365 @Override startRootTask(@onNull TaskViewTaskController destination, ActivityManager.RunningTaskInfo taskInfo, SurfaceControl leash, @Nullable WindowContainerTransaction wct)366 public void startRootTask(@NonNull TaskViewTaskController destination, 367 ActivityManager.RunningTaskInfo taskInfo, SurfaceControl leash, 368 @Nullable WindowContainerTransaction wct) { 369 if (wct == null) { 370 wct = new WindowContainerTransaction(); 371 } 372 // This method skips the regular flow where an activity task is launched as part of a new 373 // transition in taskview and then transition is intercepted using the launchcookie. 374 // The task here is already created and running, it just needs to be reparented, resized 375 // and tracked correctly inside taskview. Which is done by calling 376 // prepareOpenAnimationInternal() and then manually enqueuing the resulting window container 377 // transaction. 378 prepareOpenAnimation(destination, true /* newTask */, mTransaction /* startTransaction */, 379 null /* finishTransaction */, taskInfo, leash, wct); 380 mTransaction.apply(); 381 mTransitions.startTransition(TRANSIT_CHANGE, wct, null); 382 } 383 384 @VisibleForTesting startTaskView(@onNull WindowContainerTransaction wct, @NonNull TaskViewTaskController taskView, @NonNull IBinder launchCookie)385 void startTaskView(@NonNull WindowContainerTransaction wct, 386 @NonNull TaskViewTaskController taskView, @NonNull IBinder launchCookie) { 387 updateVisibilityState(taskView, true /* visible */); 388 mPending.add(new PendingTransition(TRANSIT_OPEN, wct, taskView, launchCookie)); 389 startNextTransition(); 390 } 391 392 @Override removeTaskView(@onNull TaskViewTaskController taskView, @Nullable WindowContainerToken taskToken)393 public void removeTaskView(@NonNull TaskViewTaskController taskView, 394 @Nullable WindowContainerToken taskToken) { 395 final WindowContainerToken token = taskToken != null ? taskToken : taskView.getTaskToken(); 396 if (token == null) { 397 // We don't have a task yet, so just clean up records 398 if (!Flags.enableTaskViewControllerCleanup()) { 399 // Call to remove task before we have one, do nothing 400 Slog.w(TAG, "Trying to remove a task that was never added? (no taskToken)"); 401 return; 402 } 403 unregisterTaskView(taskView); 404 return; 405 } 406 final WindowContainerTransaction wct = new WindowContainerTransaction(); 407 wct.removeTask(token); 408 updateVisibilityState(taskView, false /* visible */); 409 mShellExecutor.execute(() -> { 410 mPending.add(new PendingTransition(TRANSIT_CLOSE, wct, taskView, null /* cookie */)); 411 startNextTransition(); 412 }); 413 } 414 415 @Override moveTaskViewToFullscreen(@onNull TaskViewTaskController taskView)416 public void moveTaskViewToFullscreen(@NonNull TaskViewTaskController taskView) { 417 final WindowContainerToken taskToken = taskView.getTaskToken(); 418 if (taskToken == null) return; 419 final WindowContainerTransaction wct = new WindowContainerTransaction(); 420 wct.setWindowingMode(taskToken, WINDOWING_MODE_UNDEFINED); 421 wct.setAlwaysOnTop(taskToken, false); 422 mShellExecutor.execute(() -> { 423 mTaskOrganizer.setInterceptBackPressedOnTaskRoot(taskToken, false); 424 mPending.add(new PendingTransition(TRANSIT_CHANGE, wct, taskView, null /* cookie */)); 425 startNextTransition(); 426 taskView.notifyTaskRemovalStarted(taskView.getTaskInfo()); 427 }); 428 } 429 430 @Override setTaskViewVisible(TaskViewTaskController taskView, boolean visible)431 public void setTaskViewVisible(TaskViewTaskController taskView, boolean visible) { 432 setTaskViewVisible(taskView, visible, false /* reorder */); 433 } 434 435 /** 436 * Starts a new transition to make the given {@code taskView} visible and optionally 437 * reordering it. 438 * 439 * @param reorder whether to reorder the task or not. If this is {@code true}, the task will 440 * be reordered as per the given {@code visible}. For {@code visible = true}, 441 * task will be reordered to top. For {@code visible = false}, task will be 442 * reordered to the bottom 443 */ setTaskViewVisible(TaskViewTaskController taskView, boolean visible, boolean reorder)444 public void setTaskViewVisible(TaskViewTaskController taskView, boolean visible, 445 boolean reorder) { 446 final TaskViewRepository.TaskViewState state = useRepo() 447 ? mTaskViewRepo.byTaskView(taskView) 448 : mTaskViews.get(taskView); 449 if (state == null) return; 450 if (state.mVisible == visible) return; 451 if (taskView.getTaskInfo() == null) { 452 // Nothing to update, task is not yet available 453 return; 454 } 455 state.mVisible = visible; 456 final WindowContainerTransaction wct = new WindowContainerTransaction(); 457 wct.setHidden(taskView.getTaskInfo().token, !visible /* hidden */); 458 wct.setBounds(taskView.getTaskInfo().token, state.mBounds); 459 if (reorder) { 460 wct.reorder(taskView.getTaskInfo().token, visible /* onTop */); 461 } 462 PendingTransition pending = new PendingTransition( 463 visible ? TRANSIT_TO_FRONT : TRANSIT_TO_BACK, wct, taskView, null /* cookie */); 464 mPending.add(pending); 465 startNextTransition(); 466 // visibility is reported in transition. 467 } 468 469 /** Starts a new transition to reorder the given {@code taskView}'s task. */ reorderTaskViewTask(TaskViewTaskController taskView, boolean onTop)470 public void reorderTaskViewTask(TaskViewTaskController taskView, boolean onTop) { 471 final TaskViewRepository.TaskViewState state = useRepo() 472 ? mTaskViewRepo.byTaskView(taskView) 473 : mTaskViews.get(taskView); 474 if (state == null) return; 475 if (taskView.getTaskInfo() == null) { 476 // Nothing to update, task is not yet available 477 return; 478 } 479 final WindowContainerTransaction wct = new WindowContainerTransaction(); 480 wct.reorder(taskView.getTaskInfo().token, onTop /* onTop */); 481 PendingTransition pending = new PendingTransition( 482 onTop ? TRANSIT_TO_FRONT : TRANSIT_TO_BACK, wct, taskView, null /* cookie */); 483 mPending.add(pending); 484 startNextTransition(); 485 // visibility is reported in transition. 486 } 487 updateBoundsState(TaskViewTaskController taskView, Rect boundsOnScreen)488 void updateBoundsState(TaskViewTaskController taskView, Rect boundsOnScreen) { 489 if (useRepo()) return; 490 final TaskViewRepository.TaskViewState state = mTaskViews.get(taskView); 491 if (state == null) return; 492 state.mBounds.set(boundsOnScreen); 493 } 494 updateVisibilityState(TaskViewTaskController taskView, boolean visible)495 void updateVisibilityState(TaskViewTaskController taskView, boolean visible) { 496 final TaskViewRepository.TaskViewState state = useRepo() 497 ? mTaskViewRepo.byTaskView(taskView) 498 : mTaskViews.get(taskView); 499 if (state == null) return; 500 state.mVisible = visible; 501 } 502 503 @Override setTaskBounds(TaskViewTaskController taskView, Rect boundsOnScreen)504 public void setTaskBounds(TaskViewTaskController taskView, Rect boundsOnScreen) { 505 if (taskView.getTaskToken() == null) { 506 return; 507 } 508 509 if (isUsingShellTransitions()) { 510 mShellExecutor.execute(() -> { 511 // Sync Transactions can't operate simultaneously with shell transition collection. 512 setTaskBoundsInTransition(taskView, boundsOnScreen); 513 }); 514 return; 515 } 516 517 WindowContainerTransaction wct = new WindowContainerTransaction(); 518 wct.setBounds(taskView.getTaskToken(), boundsOnScreen); 519 mSyncQueue.queue(wct); 520 } 521 setTaskBoundsInTransition(TaskViewTaskController taskView, Rect boundsOnScreen)522 private void setTaskBoundsInTransition(TaskViewTaskController taskView, Rect boundsOnScreen) { 523 final TaskViewRepository.TaskViewState state = useRepo() 524 ? mTaskViewRepo.byTaskView(taskView) 525 : mTaskViews.get(taskView); 526 if (state == null || Objects.equals(boundsOnScreen, state.mBounds)) { 527 return; 528 } 529 state.mBounds.set(boundsOnScreen); 530 if (!state.mVisible) { 531 // Task view isn't visible, the bounds will next visibility update. 532 return; 533 } 534 if (hasPending()) { 535 // There is already a transition in-flight, the window bounds will be set in 536 // prepareOpenAnimation. 537 return; 538 } 539 WindowContainerTransaction wct = new WindowContainerTransaction(); 540 wct.setBounds(taskView.getTaskInfo().token, boundsOnScreen); 541 mPending.add(new PendingTransition(TRANSIT_CHANGE, wct, taskView, null /* cookie */)); 542 startNextTransition(); 543 } 544 startNextTransition()545 private void startNextTransition() { 546 if (mPending.isEmpty()) return; 547 final PendingTransition pending = mPending.get(0); 548 if (pending.mClaimed != null) { 549 // Wait for this to start animating. 550 return; 551 } 552 if (pending.mExternalTransition != null) { 553 pending.mClaimed = pending.mExternalTransition.start(); 554 } else { 555 pending.mClaimed = mTransitions.startTransition(pending.mType, pending.mWct, this); 556 } 557 } 558 559 @Override onTransitionConsumed(@onNull IBinder transition, boolean aborted, @NonNull SurfaceControl.Transaction finishTransaction)560 public void onTransitionConsumed(@NonNull IBinder transition, boolean aborted, 561 @NonNull SurfaceControl.Transaction finishTransaction) { 562 if (!aborted) return; 563 final PendingTransition pending = findPending(transition); 564 if (pending == null) return; 565 mPending.remove(pending); 566 startNextTransition(); 567 } 568 569 @Override startAnimation(@onNull IBinder transition, @NonNull TransitionInfo info, @NonNull SurfaceControl.Transaction startTransaction, @NonNull SurfaceControl.Transaction finishTransaction, @NonNull Transitions.TransitionFinishCallback finishCallback)570 public boolean startAnimation(@NonNull IBinder transition, 571 @NonNull TransitionInfo info, 572 @NonNull SurfaceControl.Transaction startTransaction, 573 @NonNull SurfaceControl.Transaction finishTransaction, 574 @NonNull Transitions.TransitionFinishCallback finishCallback) { 575 final PendingTransition pending = findPending(transition); 576 if (pending != null) { 577 mPending.remove(pending); 578 } 579 if (useRepo() ? mTaskViewRepo.isEmpty() : mTaskViews.isEmpty()) { 580 if (pending != null) { 581 Slog.e(TAG, "Pending taskview transition but no task-views"); 582 } 583 return false; 584 } 585 boolean stillNeedsMatchingLaunch = pending != null && pending.mLaunchCookie != null; 586 int changesHandled = 0; 587 WindowContainerTransaction wct = null; 588 for (int i = 0; i < info.getChanges().size(); ++i) { 589 final TransitionInfo.Change chg = info.getChanges().get(i); 590 final ActivityManager.RunningTaskInfo taskInfo = chg.getTaskInfo(); 591 if (taskInfo == null) continue; 592 if (TransitionUtil.isClosingType(chg.getMode())) { 593 final boolean isHide = chg.getMode() == TRANSIT_TO_BACK; 594 TaskViewTaskController tv = findTaskView(taskInfo); 595 if (tv == null && !isHide) { 596 // TaskView can be null when closing 597 changesHandled++; 598 continue; 599 } 600 if (tv == null) { 601 if (pending != null) { 602 Slog.w(TAG, "Found a non-TaskView task in a TaskView Transition. This " 603 + "shouldn't happen, so there may be a visual artifact: " 604 + taskInfo.taskId); 605 } 606 continue; 607 } 608 if (isHide) { 609 if (pending != null && pending.mType == TRANSIT_TO_BACK) { 610 // TO_BACK is only used when setting the task view visibility immediately, 611 // so in that case we can also hide the surface immediately 612 startTransaction.hide(chg.getLeash()); 613 } 614 tv.prepareHideAnimation(finishTransaction); 615 } else { 616 tv.prepareCloseAnimation(); 617 } 618 changesHandled++; 619 } else if (TransitionUtil.isOpeningType(chg.getMode())) { 620 boolean isNewInTaskView = false; 621 TaskViewTaskController tv; 622 if (chg.getMode() == TRANSIT_OPEN) { 623 isNewInTaskView = true; 624 if (pending == null || !taskInfo.containsLaunchCookie(pending.mLaunchCookie)) { 625 Slog.e(TAG, "Found a launching TaskView in the wrong transition. All " 626 + "TaskView launches should be initiated by shell and in their " 627 + "own transition: " + taskInfo.taskId); 628 continue; 629 } 630 stillNeedsMatchingLaunch = false; 631 tv = pending.mTaskView; 632 } else { 633 tv = findTaskView(taskInfo); 634 if (tv == null && pending != null) { 635 if (BubbleAnythingFlagHelper.enableCreateAnyBubble() 636 && chg.getMode() == TRANSIT_TO_FRONT 637 && pending.mTaskView.getPendingInfo() != null 638 && pending.mTaskView.getPendingInfo().taskId == taskInfo.taskId) { 639 // In this case an existing task, not currently in TaskView, is 640 // brought to the front to be moved into TaskView. This is still 641 // "new" from TaskView's perspective. (e.g. task being moved into a 642 // bubble) 643 isNewInTaskView = true; 644 stillNeedsMatchingLaunch = false; 645 tv = pending.mTaskView; 646 } else { 647 Slog.w(TAG, "Found a non-TaskView task in a TaskView Transition. " 648 + "This shouldn't happen, so there may be a visual " 649 + "artifact: " + taskInfo.taskId); 650 } 651 } 652 if (tv == null) continue; 653 } 654 if (wct == null) wct = new WindowContainerTransaction(); 655 prepareOpenAnimation(tv, isNewInTaskView, startTransaction, finishTransaction, 656 taskInfo, chg.getLeash(), wct); 657 changesHandled++; 658 } else if (chg.getMode() == TRANSIT_CHANGE) { 659 TaskViewTaskController tv = findTaskView(taskInfo); 660 if (tv == null) { 661 if (pending != null) { 662 Slog.w(TAG, "Found a non-TaskView task in a TaskView Transition. This " 663 + "shouldn't happen, so there may be a visual artifact: " 664 + taskInfo.taskId); 665 } 666 continue; 667 } 668 final Rect boundsOnScreen = tv.prepareOpen(chg.getTaskInfo(), chg.getLeash()); 669 if (boundsOnScreen != null) { 670 if (wct == null) wct = new WindowContainerTransaction(); 671 updateBounds(tv, boundsOnScreen, startTransaction, finishTransaction, 672 chg.getTaskInfo(), chg.getLeash(), wct); 673 } else { 674 startTransaction.reparent(chg.getLeash(), tv.getSurfaceControl()); 675 finishTransaction.reparent(chg.getLeash(), tv.getSurfaceControl()) 676 .setPosition(chg.getLeash(), 0, 0); 677 } 678 changesHandled++; 679 } 680 } 681 if (stillNeedsMatchingLaunch) { 682 Slog.w(TAG, "Expected a TaskView launch in this transition but didn't get one, " 683 + "cleaning up the task view"); 684 // Didn't find a task so the task must have never launched 685 pending.mTaskView.setTaskNotFound(); 686 } else if (wct == null && pending == null && changesHandled != info.getChanges().size()) { 687 // Just some house-keeping, let another handler animate. 688 return false; 689 } 690 // No animation, just show it immediately. 691 startTransaction.apply(); 692 finishCallback.onTransitionFinished(wct); 693 startNextTransition(); 694 return true; 695 } 696 697 @VisibleForTesting prepareOpenAnimation(TaskViewTaskController taskView, final boolean newTask, SurfaceControl.Transaction startTransaction, SurfaceControl.Transaction finishTransaction, ActivityManager.RunningTaskInfo taskInfo, SurfaceControl leash, WindowContainerTransaction wct)698 public void prepareOpenAnimation(TaskViewTaskController taskView, 699 final boolean newTask, 700 SurfaceControl.Transaction startTransaction, 701 SurfaceControl.Transaction finishTransaction, 702 ActivityManager.RunningTaskInfo taskInfo, SurfaceControl leash, 703 WindowContainerTransaction wct) { 704 final Rect boundsOnScreen = taskView.prepareOpen(taskInfo, leash); 705 if (boundsOnScreen != null) { 706 updateBounds(taskView, boundsOnScreen, startTransaction, finishTransaction, taskInfo, 707 leash, wct); 708 } else { 709 // The surface has already been destroyed before the task has appeared, 710 // so go ahead and hide the task entirely 711 wct.setHidden(taskInfo.token, true /* hidden */); 712 updateVisibilityState(taskView, false /* visible */); 713 // listener callback is below 714 } 715 if (newTask) { 716 mTaskOrganizer.setInterceptBackPressedOnTaskRoot(taskInfo.token, true /* intercept */); 717 } 718 719 if (taskInfo.taskDescription != null) { 720 int backgroundColor = taskInfo.taskDescription.getBackgroundColor(); 721 taskView.setResizeBgColor(startTransaction, backgroundColor); 722 } 723 724 // After the embedded task has appeared, set it to non-trimmable. This is important 725 // to prevent recents from trimming and removing the embedded task. 726 wct.setTaskTrimmableFromRecents(taskInfo.token, false /* isTrimmableFromRecents */); 727 728 taskView.notifyAppeared(newTask); 729 } 730 731 /** 732 * Updates bounds for the task view during an unfold transition. 733 * 734 * @return true if the task was found and a transition for this task is pending. false 735 * otherwise. 736 */ updateBoundsForUnfold(Rect bounds, SurfaceControl.Transaction startTransaction, SurfaceControl.Transaction finishTransaction, ActivityManager.RunningTaskInfo taskInfo, SurfaceControl leash)737 public boolean updateBoundsForUnfold(Rect bounds, SurfaceControl.Transaction startTransaction, 738 SurfaceControl.Transaction finishTransaction, 739 ActivityManager.RunningTaskInfo taskInfo, SurfaceControl leash) { 740 final TaskViewTaskController taskView = findTaskView(taskInfo); 741 if (taskView == null) { 742 return false; 743 } 744 745 final PendingTransition pendingTransition = findPending(taskView, TRANSIT_CHANGE); 746 if (pendingTransition == null) { 747 return false; 748 } 749 750 mPending.remove(pendingTransition); 751 752 // reparent the task under the task view surface and set the bounds on it 753 startTransaction.reparent(leash, taskView.getSurfaceControl()) 754 .setPosition(leash, 0, 0) 755 .setWindowCrop(leash, bounds.width(), bounds.height()) 756 .show(leash); 757 // the finish transaction would reparent the task back to the transition root, so reparent 758 // it again to the task view surface 759 finishTransaction.reparent(leash, taskView.getSurfaceControl()) 760 .setPosition(leash, 0, 0) 761 .setWindowCrop(leash, bounds.width(), bounds.height()); 762 if (useRepo()) { 763 final TaskViewRepository.TaskViewState state = mTaskViewRepo.byTaskView(taskView); 764 if (state != null) { 765 state.mBounds.set(bounds); 766 } 767 } else { 768 updateBoundsState(taskView, bounds); 769 } 770 return true; 771 } 772 updateBounds(TaskViewTaskController taskView, Rect boundsOnScreen, SurfaceControl.Transaction startTransaction, SurfaceControl.Transaction finishTransaction, ActivityManager.RunningTaskInfo taskInfo, SurfaceControl leash, WindowContainerTransaction wct)773 private void updateBounds(TaskViewTaskController taskView, Rect boundsOnScreen, 774 SurfaceControl.Transaction startTransaction, 775 SurfaceControl.Transaction finishTransaction, 776 ActivityManager.RunningTaskInfo taskInfo, SurfaceControl leash, 777 WindowContainerTransaction wct) { 778 final SurfaceControl tvSurface = taskView.getSurfaceControl(); 779 // Surface is ready, so just reparent the task to this surface control 780 startTransaction.reparent(leash, tvSurface) 781 .show(leash); 782 // Also reparent on finishTransaction since the finishTransaction will reparent back 783 // to its "original" parent by default. 784 if (finishTransaction != null) { 785 finishTransaction.reparent(leash, tvSurface) 786 .setPosition(leash, 0, 0) 787 .setWindowCrop(leash, boundsOnScreen.width(), boundsOnScreen.height()); 788 } 789 if (useRepo()) { 790 final TaskViewRepository.TaskViewState state = mTaskViewRepo.byTaskView(taskView); 791 if (state != null) { 792 state.mBounds.set(boundsOnScreen); 793 state.mVisible = true; 794 } 795 } else { 796 updateBoundsState(taskView, boundsOnScreen); 797 updateVisibilityState(taskView, true /* visible */); 798 } 799 wct.setBounds(taskInfo.token, boundsOnScreen); 800 taskView.applyCaptionInsetsIfNeeded(); 801 } 802 803 /** Interface for running an external transition in this object's pending queue. */ 804 public interface ExternalTransition { 805 /** Starts a transition and returns an identifying key for lookup. */ start()806 IBinder start(); 807 } 808 } 809