1 /* 2 * Copyright (C) 2020 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 21 import android.annotation.NonNull; 22 import android.annotation.Nullable; 23 import android.app.ActivityManager; 24 import android.app.ActivityOptions; 25 import android.app.PendingIntent; 26 import android.content.ComponentName; 27 import android.content.Context; 28 import android.content.Intent; 29 import android.content.pm.LauncherApps; 30 import android.content.pm.ShortcutInfo; 31 import android.graphics.Rect; 32 import android.os.Binder; 33 import android.util.CloseGuard; 34 import android.util.Slog; 35 import android.view.SurfaceControl; 36 import android.view.WindowInsets; 37 import android.window.WindowContainerToken; 38 import android.window.WindowContainerTransaction; 39 40 import com.android.wm.shell.ShellTaskOrganizer; 41 import com.android.wm.shell.common.SyncTransactionQueue; 42 43 import java.io.PrintWriter; 44 import java.util.concurrent.Executor; 45 46 /** 47 * This class implements the core logic to show a task on the {@link TaskView}. All the {@link 48 * TaskView} to {@link TaskViewTaskController} interactions are done via direct method calls. 49 * 50 * The reverse communication is done via the {@link TaskViewBase} interface. 51 */ 52 public class TaskViewTaskController implements ShellTaskOrganizer.TaskListener { 53 54 private static final String TAG = TaskViewTaskController.class.getSimpleName(); 55 56 private final CloseGuard mGuard = new CloseGuard(); 57 private final SurfaceControl.Transaction mTransaction = new SurfaceControl.Transaction(); 58 /** Used to inset the activity content to allow space for a caption bar. */ 59 private final Binder mCaptionInsetsOwner = new Binder(); 60 private final ShellTaskOrganizer mTaskOrganizer; 61 private final Executor mShellExecutor; 62 private final SyncTransactionQueue mSyncQueue; 63 private final TaskViewTransitions mTaskViewTransitions; 64 private final Context mContext; 65 66 /** 67 * There could be a situation where we have task info and receive 68 * {@link #onTaskAppeared(ActivityManager.RunningTaskInfo, SurfaceControl)}, however, the 69 * activity might fail to open, and in this case we need to clean up the task view / notify 70 * listeners of a task removal. This requires task info, so we save the info from onTaskAppeared 71 * in this situation to allow us to notify listeners correctly if the task failed to open. 72 */ 73 private ActivityManager.RunningTaskInfo mPendingInfo; 74 private TaskViewBase mTaskViewBase; 75 protected ActivityManager.RunningTaskInfo mTaskInfo; 76 private WindowContainerToken mTaskToken; 77 private SurfaceControl mTaskLeash; 78 /* Indicates that the task we attempted to launch in the task view failed to launch. */ 79 private boolean mTaskNotFound; 80 private boolean mSurfaceCreated; 81 private SurfaceControl mSurfaceControl; 82 private boolean mIsInitialized; 83 private boolean mNotifiedForInitialized; 84 private boolean mHideTaskWithSurface = true; 85 private TaskView.Listener mListener; 86 private Executor mListenerExecutor; 87 private Rect mCaptionInsets; 88 TaskViewTaskController(Context context, ShellTaskOrganizer organizer, TaskViewTransitions taskViewTransitions, SyncTransactionQueue syncQueue)89 public TaskViewTaskController(Context context, ShellTaskOrganizer organizer, 90 TaskViewTransitions taskViewTransitions, SyncTransactionQueue syncQueue) { 91 mContext = context; 92 mTaskOrganizer = organizer; 93 mShellExecutor = organizer.getExecutor(); 94 mSyncQueue = syncQueue; 95 mTaskViewTransitions = taskViewTransitions; 96 mShellExecutor.execute(() -> { 97 if (mTaskViewTransitions != null) { 98 mTaskViewTransitions.addTaskView(this); 99 } 100 }); 101 mGuard.open("release"); 102 } 103 104 /** 105 * Specifies if the task should be hidden when the surface is destroyed. 106 * <p>This is {@code true} by default. 107 * 108 * @param hideTaskWithSurface {@code false} if task needs to remain visible even when the 109 * surface is destroyed, {@code true} otherwise. 110 */ setHideTaskWithSurface(boolean hideTaskWithSurface)111 public void setHideTaskWithSurface(boolean hideTaskWithSurface) { 112 // TODO(b/299535374): Remove mHideTaskWithSurface once the taskviews with launch root tasks 113 // are moved to a window in SystemUI in auto. 114 mHideTaskWithSurface = hideTaskWithSurface; 115 } 116 getSurfaceControl()117 SurfaceControl getSurfaceControl() { 118 return mSurfaceControl; 119 } 120 121 /** 122 * Sets the provided {@link TaskViewBase}, which is used to notify the client part about the 123 * task related changes and getting the current bounds. 124 */ setTaskViewBase(TaskViewBase taskViewBase)125 public void setTaskViewBase(TaskViewBase taskViewBase) { 126 mTaskViewBase = taskViewBase; 127 } 128 129 /** 130 * @return {@code True} when the TaskView's surface has been created, {@code False} otherwise. 131 */ isInitialized()132 public boolean isInitialized() { 133 return mIsInitialized; 134 } 135 136 /** Until all users are converted, we may have mixed-use (eg. Car). */ isUsingShellTransitions()137 public boolean isUsingShellTransitions() { 138 return mTaskViewTransitions != null && mTaskViewTransitions.isEnabled(); 139 } 140 141 /** 142 * Only one listener may be set on the view, throws an exception otherwise. 143 */ setListener(@onNull Executor executor, TaskView.Listener listener)144 void setListener(@NonNull Executor executor, TaskView.Listener listener) { 145 if (mListener != null) { 146 throw new IllegalStateException( 147 "Trying to set a listener when one has already been set"); 148 } 149 mListener = listener; 150 mListenerExecutor = executor; 151 } 152 153 /** 154 * Launch an activity represented by {@link ShortcutInfo}. 155 * <p>The owner of this container must be allowed to access the shortcut information, 156 * as defined in {@link LauncherApps#hasShortcutHostPermission()} to use this method. 157 * 158 * @param shortcut the shortcut used to launch the activity. 159 * @param options options for the activity. 160 * @param launchBounds the bounds (window size and position) that the activity should be 161 * launched in, in pixels and in screen coordinates. 162 */ startShortcutActivity(@onNull ShortcutInfo shortcut, @NonNull ActivityOptions options, @Nullable Rect launchBounds)163 public void startShortcutActivity(@NonNull ShortcutInfo shortcut, 164 @NonNull ActivityOptions options, @Nullable Rect launchBounds) { 165 prepareActivityOptions(options, launchBounds); 166 LauncherApps service = mContext.getSystemService(LauncherApps.class); 167 if (isUsingShellTransitions()) { 168 mShellExecutor.execute(() -> { 169 final WindowContainerTransaction wct = new WindowContainerTransaction(); 170 wct.startShortcut(mContext.getPackageName(), shortcut, options.toBundle()); 171 mTaskViewTransitions.startTaskView(wct, this, options.getLaunchCookie()); 172 }); 173 return; 174 } 175 try { 176 service.startShortcut(shortcut, null /* sourceBounds */, options.toBundle()); 177 } catch (Exception e) { 178 throw new RuntimeException(e); 179 } 180 } 181 182 /** 183 * Launch a new activity. 184 * 185 * @param pendingIntent Intent used to launch an activity. 186 * @param fillInIntent Additional Intent data, see {@link Intent#fillIn Intent.fillIn()} 187 * @param options options for the activity. 188 * @param launchBounds the bounds (window size and position) that the activity should be 189 * launched in, in pixels and in screen coordinates. 190 */ startActivity(@onNull PendingIntent pendingIntent, @Nullable Intent fillInIntent, @NonNull ActivityOptions options, @Nullable Rect launchBounds)191 public void startActivity(@NonNull PendingIntent pendingIntent, @Nullable Intent fillInIntent, 192 @NonNull ActivityOptions options, @Nullable Rect launchBounds) { 193 prepareActivityOptions(options, launchBounds); 194 if (isUsingShellTransitions()) { 195 mShellExecutor.execute(() -> { 196 WindowContainerTransaction wct = new WindowContainerTransaction(); 197 wct.sendPendingIntent(pendingIntent, fillInIntent, options.toBundle()); 198 mTaskViewTransitions.startTaskView(wct, this, options.getLaunchCookie()); 199 }); 200 return; 201 } 202 try { 203 pendingIntent.send(mContext, 0 /* code */, fillInIntent, 204 null /* onFinished */, null /* handler */, null /* requiredPermission */, 205 options.toBundle()); 206 } catch (Exception e) { 207 throw new RuntimeException(e); 208 } 209 } 210 prepareActivityOptions(ActivityOptions options, Rect launchBounds)211 private void prepareActivityOptions(ActivityOptions options, Rect launchBounds) { 212 final Binder launchCookie = new Binder(); 213 mShellExecutor.execute(() -> { 214 mTaskOrganizer.setPendingLaunchCookieListener(launchCookie, this); 215 }); 216 options.setLaunchBounds(launchBounds); 217 options.setLaunchCookie(launchCookie); 218 options.setLaunchWindowingMode(WINDOWING_MODE_MULTI_WINDOW); 219 options.setRemoveWithTaskOrganizer(true); 220 } 221 222 /** 223 * Release this container if it is initialized. 224 */ release()225 public void release() { 226 performRelease(); 227 } 228 229 @Override finalize()230 protected void finalize() throws Throwable { 231 try { 232 if (mGuard != null) { 233 mGuard.warnIfOpen(); 234 performRelease(); 235 } 236 } finally { 237 super.finalize(); 238 } 239 } 240 performRelease()241 private void performRelease() { 242 mShellExecutor.execute(() -> { 243 if (mTaskViewTransitions != null) { 244 mTaskViewTransitions.removeTaskView(this); 245 } 246 mTaskOrganizer.removeListener(this); 247 resetTaskInfo(); 248 }); 249 mGuard.close(); 250 mIsInitialized = false; 251 notifyReleased(); 252 } 253 254 /** Called when the {@link TaskViewTaskController} has been released. */ notifyReleased()255 protected void notifyReleased() { 256 if (mListener != null && mNotifiedForInitialized) { 257 mListenerExecutor.execute(() -> { 258 mListener.onReleased(); 259 }); 260 mNotifiedForInitialized = false; 261 } 262 } 263 resetTaskInfo()264 private void resetTaskInfo() { 265 mTaskInfo = null; 266 mTaskToken = null; 267 mTaskLeash = null; 268 mPendingInfo = null; 269 mTaskNotFound = false; 270 } 271 272 /** This method shouldn't be called when shell transitions are enabled. */ updateTaskVisibility()273 private void updateTaskVisibility() { 274 boolean visible = mSurfaceCreated; 275 if (!visible && !mHideTaskWithSurface) { 276 return; 277 } 278 WindowContainerTransaction wct = new WindowContainerTransaction(); 279 wct.setHidden(mTaskToken, !visible /* hidden */); 280 if (!visible) { 281 wct.reorder(mTaskToken, false /* onTop */); 282 } 283 mSyncQueue.queue(wct); 284 if (mListener == null) { 285 return; 286 } 287 int taskId = mTaskInfo.taskId; 288 mSyncQueue.runInSync((t) -> { 289 mListenerExecutor.execute(() -> { 290 mListener.onTaskVisibilityChanged(taskId, mSurfaceCreated); 291 }); 292 }); 293 } 294 295 @Override onTaskAppeared(ActivityManager.RunningTaskInfo taskInfo, SurfaceControl leash)296 public void onTaskAppeared(ActivityManager.RunningTaskInfo taskInfo, 297 SurfaceControl leash) { 298 if (isUsingShellTransitions()) { 299 mPendingInfo = taskInfo; 300 if (mTaskNotFound) { 301 // If we were already notified by shell transit that we don't have the 302 // the task, clean it up now. 303 cleanUpPendingTask(); 304 } 305 // Everything else handled by enter transition. 306 return; 307 } 308 mTaskInfo = taskInfo; 309 mTaskToken = taskInfo.token; 310 mTaskLeash = leash; 311 312 if (mSurfaceCreated) { 313 // Surface is ready, so just reparent the task to this surface control 314 mTransaction.reparent(mTaskLeash, mSurfaceControl) 315 .show(mTaskLeash) 316 .apply(); 317 } else { 318 // The surface has already been destroyed before the task has appeared, 319 // so go ahead and hide the task entirely 320 updateTaskVisibility(); 321 } 322 mTaskOrganizer.setInterceptBackPressedOnTaskRoot(mTaskToken, true); 323 mSyncQueue.runInSync((t) -> { 324 mTaskViewBase.onTaskAppeared(taskInfo, leash); 325 }); 326 327 if (mListener != null) { 328 final int taskId = taskInfo.taskId; 329 final ComponentName baseActivity = taskInfo.baseActivity; 330 mListenerExecutor.execute(() -> { 331 mListener.onTaskCreated(taskId, baseActivity); 332 }); 333 } 334 } 335 336 @Override onTaskVanished(ActivityManager.RunningTaskInfo taskInfo)337 public void onTaskVanished(ActivityManager.RunningTaskInfo taskInfo) { 338 // Unlike Appeared, we can't yet guarantee that vanish will happen within a transition that 339 // we know about -- so leave clean-up here even if shell transitions are enabled. 340 if (mTaskToken == null || !mTaskToken.equals(taskInfo.token)) return; 341 342 final SurfaceControl taskLeash = mTaskLeash; 343 handleAndNotifyTaskRemoval(mTaskInfo); 344 345 // Unparent the task when this surface is destroyed 346 mTransaction.reparent(taskLeash, null).apply(); 347 resetTaskInfo(); 348 } 349 350 @Override onTaskInfoChanged(ActivityManager.RunningTaskInfo taskInfo)351 public void onTaskInfoChanged(ActivityManager.RunningTaskInfo taskInfo) { 352 mTaskViewBase.onTaskInfoChanged(taskInfo); 353 } 354 355 @Override onBackPressedOnTaskRoot(ActivityManager.RunningTaskInfo taskInfo)356 public void onBackPressedOnTaskRoot(ActivityManager.RunningTaskInfo taskInfo) { 357 if (mTaskToken == null || !mTaskToken.equals(taskInfo.token)) return; 358 if (mListener != null) { 359 final int taskId = taskInfo.taskId; 360 mListenerExecutor.execute(() -> { 361 mListener.onBackPressedOnTaskRoot(taskId); 362 }); 363 } 364 } 365 366 @Override attachChildSurfaceToTask(int taskId, SurfaceControl.Builder b)367 public void attachChildSurfaceToTask(int taskId, SurfaceControl.Builder b) { 368 b.setParent(findTaskSurface(taskId)); 369 } 370 371 @Override reparentChildSurfaceToTask(int taskId, SurfaceControl sc, SurfaceControl.Transaction t)372 public void reparentChildSurfaceToTask(int taskId, SurfaceControl sc, 373 SurfaceControl.Transaction t) { 374 t.reparent(sc, findTaskSurface(taskId)); 375 } 376 findTaskSurface(int taskId)377 private SurfaceControl findTaskSurface(int taskId) { 378 if (mTaskInfo == null || mTaskLeash == null || mTaskInfo.taskId != taskId) { 379 throw new IllegalArgumentException("There is no surface for taskId=" + taskId); 380 } 381 return mTaskLeash; 382 } 383 384 @Override dump(@ndroidx.annotation.NonNull PrintWriter pw, String prefix)385 public void dump(@androidx.annotation.NonNull PrintWriter pw, String prefix) { 386 final String innerPrefix = prefix + " "; 387 final String childPrefix = innerPrefix + " "; 388 pw.println(prefix + this); 389 } 390 391 @Override toString()392 public String toString() { 393 return "TaskViewTaskController" + ":" + (mTaskInfo != null ? mTaskInfo.taskId : "null"); 394 } 395 396 /** 397 * Should be called when the client surface is created. 398 * 399 * @param surfaceControl the {@link SurfaceControl} for the underlying surface. 400 */ surfaceCreated(SurfaceControl surfaceControl)401 public void surfaceCreated(SurfaceControl surfaceControl) { 402 mSurfaceCreated = true; 403 mIsInitialized = true; 404 mSurfaceControl = surfaceControl; 405 notifyInitialized(); 406 mShellExecutor.execute(() -> { 407 if (mTaskToken == null) { 408 // Nothing to update, task is not yet available 409 return; 410 } 411 if (isUsingShellTransitions()) { 412 mTaskViewTransitions.setTaskViewVisible(this, true /* visible */); 413 return; 414 } 415 // Reparent the task when this surface is created 416 mTransaction.reparent(mTaskLeash, mSurfaceControl) 417 .show(mTaskLeash) 418 .apply(); 419 updateTaskVisibility(); 420 }); 421 } 422 423 /** 424 * Sets the window bounds to {@code boundsOnScreen}. 425 * Call when view position or size has changed. Can also be called before the animation when 426 * the final bounds are known. 427 * Do not call during the animation. 428 * 429 * @param boundsOnScreen the on screen bounds of the surface view. 430 */ setWindowBounds(Rect boundsOnScreen)431 public void setWindowBounds(Rect boundsOnScreen) { 432 if (mTaskToken == null) { 433 return; 434 } 435 436 if (isUsingShellTransitions()) { 437 mShellExecutor.execute(() -> { 438 // Sync Transactions can't operate simultaneously with shell transition collection. 439 mTaskViewTransitions.setTaskBounds(this, boundsOnScreen); 440 }); 441 return; 442 } 443 444 WindowContainerTransaction wct = new WindowContainerTransaction(); 445 wct.setBounds(mTaskToken, boundsOnScreen); 446 mSyncQueue.queue(wct); 447 } 448 449 /** 450 * Call to remove the task from window manager. This task will not appear in recents. 451 */ removeTask()452 void removeTask() { 453 if (mTaskToken == null) { 454 // Call to remove task before we have one, do nothing 455 Slog.w(TAG, "Trying to remove a task that was never added? (no taskToken)"); 456 return; 457 } 458 mShellExecutor.execute(() -> { 459 WindowContainerTransaction wct = new WindowContainerTransaction(); 460 wct.removeTask(mTaskToken); 461 mTaskViewTransitions.closeTaskView(wct, this); 462 }); 463 } 464 465 /** 466 * Sets a region of the task to inset to allow for a caption bar. 467 * 468 * @param captionInsets the rect for the insets in screen coordinates. 469 */ setCaptionInsets(Rect captionInsets)470 void setCaptionInsets(Rect captionInsets) { 471 if (mCaptionInsets != null && mCaptionInsets.equals(captionInsets)) { 472 return; 473 } 474 mCaptionInsets = captionInsets; 475 applyCaptionInsetsIfNeeded(); 476 } 477 applyCaptionInsetsIfNeeded()478 void applyCaptionInsetsIfNeeded() { 479 if (mTaskToken == null) return; 480 WindowContainerTransaction wct = new WindowContainerTransaction(); 481 if (mCaptionInsets != null) { 482 wct.addInsetsSource(mTaskToken, mCaptionInsetsOwner, 0, 483 WindowInsets.Type.captionBar(), mCaptionInsets); 484 } else { 485 wct.removeInsetsSource(mTaskToken, mCaptionInsetsOwner, 0, 486 WindowInsets.Type.captionBar()); 487 } 488 mTaskOrganizer.applyTransaction(wct); 489 } 490 491 /** Should be called when the client surface is destroyed. */ surfaceDestroyed()492 public void surfaceDestroyed() { 493 mSurfaceCreated = false; 494 mSurfaceControl = null; 495 mShellExecutor.execute(() -> { 496 if (mTaskToken == null) { 497 // Nothing to update, task is not yet available 498 return; 499 } 500 501 if (isUsingShellTransitions()) { 502 mTaskViewTransitions.setTaskViewVisible(this, false /* visible */); 503 return; 504 } 505 506 // Unparent the task when this surface is destroyed 507 mTransaction.reparent(mTaskLeash, null).apply(); 508 updateTaskVisibility(); 509 }); 510 } 511 512 /** Called when the {@link TaskViewTaskController} is initialized. */ notifyInitialized()513 protected void notifyInitialized() { 514 if (mListener != null && !mNotifiedForInitialized) { 515 mNotifiedForInitialized = true; 516 mListenerExecutor.execute(() -> { 517 mListener.onInitialized(); 518 }); 519 } 520 } 521 522 /** Notifies listeners of a task being removed and stops intercepting back presses on it. */ handleAndNotifyTaskRemoval(ActivityManager.RunningTaskInfo taskInfo)523 private void handleAndNotifyTaskRemoval(ActivityManager.RunningTaskInfo taskInfo) { 524 if (taskInfo != null) { 525 if (mListener != null) { 526 final int taskId = taskInfo.taskId; 527 mListenerExecutor.execute(() -> { 528 mListener.onTaskRemovalStarted(taskId); 529 }); 530 } 531 mTaskViewBase.onTaskVanished(taskInfo); 532 mTaskOrganizer.setInterceptBackPressedOnTaskRoot(taskInfo.token, false); 533 } 534 } 535 536 /** Returns the task info for the task in the TaskView. */ 537 @Nullable getTaskInfo()538 public ActivityManager.RunningTaskInfo getTaskInfo() { 539 return mTaskInfo; 540 } 541 542 /** 543 * Indicates that the task was not found in the start animation for the transition. 544 * In this case we should clean up the task if we have the pending info. If we don't 545 * have the pending info, we'll do it when we receive it in 546 * {@link #onTaskAppeared(ActivityManager.RunningTaskInfo, SurfaceControl)}. 547 */ setTaskNotFound()548 void setTaskNotFound() { 549 mTaskNotFound = true; 550 if (mPendingInfo != null) { 551 cleanUpPendingTask(); 552 } 553 } 554 555 /** 556 * Called when a task failed to open and we need to clean up task view / 557 * notify users of task view. 558 */ cleanUpPendingTask()559 void cleanUpPendingTask() { 560 if (mPendingInfo != null) { 561 final ActivityManager.RunningTaskInfo pendingInfo = mPendingInfo; 562 handleAndNotifyTaskRemoval(pendingInfo); 563 564 // Make sure the task is removed 565 WindowContainerTransaction wct = new WindowContainerTransaction(); 566 wct.removeTask(pendingInfo.token); 567 mTaskViewTransitions.closeTaskView(wct, this); 568 } 569 resetTaskInfo(); 570 } 571 prepareHideAnimation(@onNull SurfaceControl.Transaction finishTransaction)572 void prepareHideAnimation(@NonNull SurfaceControl.Transaction finishTransaction) { 573 if (mTaskToken == null) { 574 // Nothing to update, task is not yet available 575 return; 576 } 577 578 finishTransaction.reparent(mTaskLeash, null); 579 580 if (mListener != null) { 581 final int taskId = mTaskInfo.taskId; 582 mListener.onTaskVisibilityChanged(taskId, mSurfaceCreated /* visible */); 583 } 584 } 585 586 /** 587 * Called when the associated Task closes. If the TaskView is just being hidden, prepareHide 588 * is used instead. 589 */ prepareCloseAnimation()590 void prepareCloseAnimation() { 591 handleAndNotifyTaskRemoval(mTaskInfo); 592 resetTaskInfo(); 593 } 594 prepareOpenAnimation(final boolean newTask, @NonNull SurfaceControl.Transaction startTransaction, @NonNull SurfaceControl.Transaction finishTransaction, ActivityManager.RunningTaskInfo taskInfo, SurfaceControl leash, WindowContainerTransaction wct)595 void prepareOpenAnimation(final boolean newTask, 596 @NonNull SurfaceControl.Transaction startTransaction, 597 @NonNull SurfaceControl.Transaction finishTransaction, 598 ActivityManager.RunningTaskInfo taskInfo, SurfaceControl leash, 599 WindowContainerTransaction wct) { 600 mPendingInfo = null; 601 mTaskInfo = taskInfo; 602 mTaskToken = mTaskInfo.token; 603 mTaskLeash = leash; 604 if (mSurfaceCreated) { 605 // Surface is ready, so just reparent the task to this surface control 606 startTransaction.reparent(mTaskLeash, mSurfaceControl) 607 .show(mTaskLeash); 608 // Also reparent on finishTransaction since the finishTransaction will reparent back 609 // to its "original" parent by default. 610 Rect boundsOnScreen = mTaskViewBase.getCurrentBoundsOnScreen(); 611 finishTransaction.reparent(mTaskLeash, mSurfaceControl) 612 .setPosition(mTaskLeash, 0, 0) 613 // TODO: maybe once b/280900002 is fixed this will be unnecessary 614 .setWindowCrop(mTaskLeash, boundsOnScreen.width(), boundsOnScreen.height()); 615 mTaskViewTransitions.updateBoundsState(this, boundsOnScreen); 616 mTaskViewTransitions.updateVisibilityState(this, true /* visible */); 617 wct.setBounds(mTaskToken, boundsOnScreen); 618 applyCaptionInsetsIfNeeded(); 619 } else { 620 // The surface has already been destroyed before the task has appeared, 621 // so go ahead and hide the task entirely 622 wct.setHidden(mTaskToken, true /* hidden */); 623 mTaskViewTransitions.updateVisibilityState(this, false /* visible */); 624 // listener callback is below 625 } 626 if (newTask) { 627 mTaskOrganizer.setInterceptBackPressedOnTaskRoot(mTaskToken, true /* intercept */); 628 } 629 630 if (mTaskInfo.taskDescription != null) { 631 int backgroundColor = mTaskInfo.taskDescription.getBackgroundColor(); 632 mTaskViewBase.setResizeBgColor(startTransaction, backgroundColor); 633 } 634 635 if (mListener != null) { 636 final int taskId = mTaskInfo.taskId; 637 final ComponentName baseActivity = mTaskInfo.baseActivity; 638 639 mListenerExecutor.execute(() -> { 640 if (newTask) { 641 mListener.onTaskCreated(taskId, baseActivity); 642 } 643 // Even if newTask, send a visibilityChange if the surface was destroyed. 644 if (!newTask || !mSurfaceCreated) { 645 mListener.onTaskVisibilityChanged(taskId, mSurfaceCreated /* visible */); 646 } 647 }); 648 } 649 } 650 } 651