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 android.annotation.NonNull; 20 import android.annotation.Nullable; 21 import android.app.ActivityManager; 22 import android.content.ComponentName; 23 import android.content.Context; 24 import android.graphics.Rect; 25 import android.gui.TrustedOverlay; 26 import android.os.Binder; 27 import android.util.CloseGuard; 28 import android.view.SurfaceControl; 29 import android.view.WindowInsets; 30 import android.window.WindowContainerToken; 31 import android.window.WindowContainerTransaction; 32 33 import com.android.internal.annotations.VisibleForTesting; 34 import com.android.wm.shell.ShellTaskOrganizer; 35 import com.android.wm.shell.common.SyncTransactionQueue; 36 37 import java.io.PrintWriter; 38 import java.util.concurrent.Executor; 39 40 /** 41 * This class represents the visible aspect of a task in a {@link TaskView}. All the {@link 42 * TaskView} to {@link TaskViewTaskController} interactions are done via direct method calls. 43 * 44 * The reverse communication is done via the {@link TaskViewBase} interface. 45 */ 46 public class TaskViewTaskController implements ShellTaskOrganizer.TaskListener { 47 48 private static final String TAG = TaskViewTaskController.class.getSimpleName(); 49 50 private final CloseGuard mGuard = new CloseGuard(); 51 private final SurfaceControl.Transaction mTransaction = new SurfaceControl.Transaction(); 52 /** Used to inset the activity content to allow space for a caption bar. */ 53 private final Binder mCaptionInsetsOwner = new Binder(); 54 private final ShellTaskOrganizer mTaskOrganizer; 55 private final Executor mShellExecutor; 56 private final SyncTransactionQueue mSyncQueue; 57 private final TaskViewController mTaskViewController; 58 private final Context mContext; 59 60 /** 61 * There could be a situation where we have task info and receive 62 * {@link #onTaskAppeared(ActivityManager.RunningTaskInfo, SurfaceControl)}, however, the 63 * activity might fail to open, and in this case we need to clean up the task view / notify 64 * listeners of a task removal. This requires task info, so we save the info from onTaskAppeared 65 * in this situation to allow us to notify listeners correctly if the task failed to open. 66 */ 67 private ActivityManager.RunningTaskInfo mPendingInfo; 68 private TaskViewBase mTaskViewBase; 69 protected ActivityManager.RunningTaskInfo mTaskInfo; 70 private WindowContainerToken mTaskToken; 71 private SurfaceControl mTaskLeash; 72 /* Indicates that the task we attempted to launch in the task view failed to launch. */ 73 private boolean mTaskNotFound; 74 private boolean mSurfaceCreated; 75 private SurfaceControl mSurfaceControl; 76 private boolean mIsInitialized; 77 private boolean mNotifiedForInitialized; 78 private boolean mHideTaskWithSurface = true; 79 private TaskView.Listener mListener; 80 private Executor mListenerExecutor; 81 private Rect mCaptionInsets; 82 TaskViewTaskController(Context context, ShellTaskOrganizer organizer, TaskViewController taskViewController, SyncTransactionQueue syncQueue)83 public TaskViewTaskController(Context context, ShellTaskOrganizer organizer, 84 TaskViewController taskViewController, SyncTransactionQueue syncQueue) { 85 mContext = context; 86 mTaskOrganizer = organizer; 87 mShellExecutor = organizer.getExecutor(); 88 mSyncQueue = syncQueue; 89 mTaskViewController = taskViewController; 90 mShellExecutor.execute(() -> { 91 if (mTaskViewController != null) { 92 mTaskViewController.registerTaskView(this); 93 } 94 }); 95 mGuard.open("release"); 96 } 97 98 /** 99 * Specifies if the task should be hidden when the surface is destroyed. 100 * <p>This is {@code true} by default. 101 * 102 * @param hideTaskWithSurface {@code false} if task needs to remain visible even when the 103 * surface is destroyed, {@code true} otherwise. 104 */ setHideTaskWithSurface(boolean hideTaskWithSurface)105 public void setHideTaskWithSurface(boolean hideTaskWithSurface) { 106 // TODO(b/299535374): Remove mHideTaskWithSurface once the taskviews with launch root tasks 107 // are moved to a window in SystemUI in auto. 108 mHideTaskWithSurface = hideTaskWithSurface; 109 } 110 getSurfaceControl()111 SurfaceControl getSurfaceControl() { 112 return mSurfaceControl; 113 } 114 getContext()115 Context getContext() { 116 return mContext; 117 } 118 119 /** 120 * Sets the provided {@link TaskViewBase}, which is used to notify the client part about the 121 * task related changes and getting the current bounds. 122 */ setTaskViewBase(TaskViewBase taskViewBase)123 public void setTaskViewBase(TaskViewBase taskViewBase) { 124 mTaskViewBase = taskViewBase; 125 } 126 127 /** 128 * @return {@code True} when the TaskView's surface has been created, {@code False} otherwise. 129 */ isInitialized()130 public boolean isInitialized() { 131 return mIsInitialized; 132 } 133 getTaskToken()134 WindowContainerToken getTaskToken() { 135 return mTaskToken; 136 } 137 setResizeBgColor(SurfaceControl.Transaction t, int bgColor)138 void setResizeBgColor(SurfaceControl.Transaction t, int bgColor) { 139 mTaskViewBase.setResizeBgColor(t, bgColor); 140 } 141 142 /** 143 * Only one listener may be set on the view, throws an exception otherwise. 144 */ setListener(@onNull Executor executor, TaskView.Listener listener)145 void setListener(@NonNull Executor executor, TaskView.Listener listener) { 146 if (mListener != null) { 147 throw new IllegalStateException( 148 "Trying to set a listener when one has already been set"); 149 } 150 mListener = listener; 151 mListenerExecutor = executor; 152 } 153 154 /** 155 * Release this container if it is initialized. 156 */ release()157 public void release() { 158 performRelease(); 159 } 160 161 @Override finalize()162 protected void finalize() throws Throwable { 163 try { 164 if (mGuard != null) { 165 mGuard.warnIfOpen(); 166 performRelease(); 167 } 168 } finally { 169 super.finalize(); 170 } 171 } 172 performRelease()173 private void performRelease() { 174 mShellExecutor.execute(() -> { 175 if (mTaskViewController != null) { 176 mTaskViewController.unregisterTaskView(this); 177 } 178 mTaskOrganizer.removeListener(this); 179 resetTaskInfo(); 180 }); 181 mGuard.close(); 182 mIsInitialized = false; 183 notifyReleased(); 184 } 185 186 /** Called when the {@link TaskViewTaskController} has been released. */ notifyReleased()187 protected void notifyReleased() { 188 if (mListener != null && mNotifiedForInitialized) { 189 mListenerExecutor.execute(() -> { 190 mListener.onReleased(); 191 }); 192 mNotifiedForInitialized = false; 193 } 194 } 195 resetTaskInfo()196 private void resetTaskInfo() { 197 mTaskInfo = null; 198 mTaskToken = null; 199 mTaskLeash = null; 200 mPendingInfo = null; 201 mTaskNotFound = false; 202 } 203 204 /** This method shouldn't be called when shell transitions are enabled. */ updateTaskVisibility()205 private void updateTaskVisibility() { 206 boolean visible = mSurfaceCreated; 207 if (!visible && !mHideTaskWithSurface) { 208 return; 209 } 210 WindowContainerTransaction wct = new WindowContainerTransaction(); 211 wct.setHidden(mTaskToken, !visible /* hidden */); 212 if (!visible) { 213 wct.reorder(mTaskToken, false /* onTop */); 214 } 215 mSyncQueue.queue(wct); 216 if (mListener == null) { 217 return; 218 } 219 int taskId = mTaskInfo.taskId; 220 mSyncQueue.runInSync((t) -> { 221 mListenerExecutor.execute(() -> { 222 mListener.onTaskVisibilityChanged(taskId, mSurfaceCreated); 223 }); 224 }); 225 } 226 227 @Override onTaskAppeared(ActivityManager.RunningTaskInfo taskInfo, SurfaceControl leash)228 public void onTaskAppeared(ActivityManager.RunningTaskInfo taskInfo, 229 SurfaceControl leash) { 230 if (mTaskViewController.isUsingShellTransitions()) { 231 mPendingInfo = taskInfo; 232 if (mTaskNotFound) { 233 // If we were already notified by shell transit that we don't have the 234 // the task, clean it up now. 235 cleanUpPendingTask(); 236 } 237 // Everything else handled by enter transition. 238 return; 239 } 240 mTaskInfo = taskInfo; 241 mTaskToken = taskInfo.token; 242 mTaskLeash = leash; 243 244 if (mSurfaceCreated) { 245 // Surface is ready, so just reparent the task to this surface control 246 mTransaction.reparent(mTaskLeash, mSurfaceControl) 247 .show(mTaskLeash) 248 .apply(); 249 } else { 250 // The surface has already been destroyed before the task has appeared, 251 // so go ahead and hide the task entirely 252 updateTaskVisibility(); 253 } 254 mTaskOrganizer.setInterceptBackPressedOnTaskRoot(mTaskToken, true); 255 mSyncQueue.runInSync((t) -> { 256 mTaskViewBase.onTaskAppeared(taskInfo, leash); 257 }); 258 259 if (mListener != null) { 260 final int taskId = taskInfo.taskId; 261 final ComponentName baseActivity = taskInfo.baseActivity; 262 mListenerExecutor.execute(() -> { 263 mListener.onTaskCreated(taskId, baseActivity); 264 }); 265 } 266 } 267 268 @Override onTaskVanished(ActivityManager.RunningTaskInfo taskInfo)269 public void onTaskVanished(ActivityManager.RunningTaskInfo taskInfo) { 270 // Unlike Appeared, we can't yet guarantee that vanish will happen within a transition that 271 // we know about -- so leave clean-up here even if shell transitions are enabled. 272 if (mTaskToken == null || !mTaskToken.equals(taskInfo.token)) return; 273 274 final SurfaceControl taskLeash = mTaskLeash; 275 handleAndNotifyTaskRemoval(mTaskInfo); 276 277 mTransaction.reparent(taskLeash, null).apply(); 278 resetTaskInfo(); 279 } 280 281 @Override onTaskInfoChanged(ActivityManager.RunningTaskInfo taskInfo)282 public void onTaskInfoChanged(ActivityManager.RunningTaskInfo taskInfo) { 283 mTaskViewBase.onTaskInfoChanged(taskInfo); 284 } 285 286 @Override onBackPressedOnTaskRoot(ActivityManager.RunningTaskInfo taskInfo)287 public void onBackPressedOnTaskRoot(ActivityManager.RunningTaskInfo taskInfo) { 288 if (mTaskToken == null || !mTaskToken.equals(taskInfo.token)) return; 289 if (mListener != null) { 290 final int taskId = taskInfo.taskId; 291 mListenerExecutor.execute(() -> { 292 mListener.onBackPressedOnTaskRoot(taskId); 293 }); 294 } 295 } 296 297 @Override attachChildSurfaceToTask(int taskId, SurfaceControl.Builder b)298 public void attachChildSurfaceToTask(int taskId, SurfaceControl.Builder b) { 299 b.setParent(findTaskSurface(taskId)); 300 } 301 302 @Override reparentChildSurfaceToTask(int taskId, SurfaceControl sc, SurfaceControl.Transaction t)303 public void reparentChildSurfaceToTask(int taskId, SurfaceControl sc, 304 SurfaceControl.Transaction t) { 305 t.reparent(sc, findTaskSurface(taskId)); 306 } 307 findTaskSurface(int taskId)308 private SurfaceControl findTaskSurface(int taskId) { 309 if (mTaskInfo == null || mTaskLeash == null || mTaskInfo.taskId != taskId) { 310 throw new IllegalArgumentException("There is no surface for taskId=" + taskId); 311 } 312 return mTaskLeash; 313 } 314 315 @Override dump(@ndroidx.annotation.NonNull PrintWriter pw, String prefix)316 public void dump(@androidx.annotation.NonNull PrintWriter pw, String prefix) { 317 final String innerPrefix = prefix + " "; 318 final String childPrefix = innerPrefix + " "; 319 pw.println(prefix + this); 320 } 321 322 @Override toString()323 public String toString() { 324 return "TaskViewTaskController" + ":" + (mTaskInfo != null ? mTaskInfo.taskId : "null"); 325 } 326 327 /** 328 * Should be called when the client surface is created. 329 * 330 * @param surfaceControl the {@link SurfaceControl} for the underlying surface. 331 */ surfaceCreated(SurfaceControl surfaceControl)332 public void surfaceCreated(SurfaceControl surfaceControl) { 333 mSurfaceCreated = true; 334 mIsInitialized = true; 335 mSurfaceControl = surfaceControl; 336 // SurfaceControl is expected to be null only in the case of unit tests. Guard against it 337 // to avoid runtime exception in SurfaceControl.Transaction. 338 if (surfaceControl != null) { 339 // TaskView is meant to contain app activities which shouldn't have trusted overlays 340 // flag set even when itself reparented in a window which is trusted. 341 mTransaction.setTrustedOverlay(surfaceControl, TrustedOverlay.DISABLED) 342 .apply(); 343 } 344 notifyInitialized(); 345 mShellExecutor.execute(() -> { 346 if (mTaskToken == null) { 347 // Nothing to update, task is not yet available 348 return; 349 } 350 if (mTaskViewController.isUsingShellTransitions()) { 351 mTaskViewController.setTaskViewVisible(this, true /* visible */); 352 return; 353 } 354 // Reparent the task when this surface is created 355 mTransaction.reparent(mTaskLeash, mSurfaceControl) 356 .show(mTaskLeash) 357 .apply(); 358 updateTaskVisibility(); 359 }); 360 } 361 362 /** 363 * Sets a region of the task to inset to allow for a caption bar. 364 * 365 * @param captionInsets the rect for the insets in screen coordinates. 366 */ setCaptionInsets(Rect captionInsets)367 void setCaptionInsets(Rect captionInsets) { 368 if (mCaptionInsets != null && mCaptionInsets.equals(captionInsets)) { 369 return; 370 } 371 mCaptionInsets = captionInsets; 372 applyCaptionInsetsIfNeeded(); 373 } 374 applyCaptionInsetsIfNeeded()375 void applyCaptionInsetsIfNeeded() { 376 if (mTaskToken == null) return; 377 WindowContainerTransaction wct = new WindowContainerTransaction(); 378 if (mCaptionInsets != null) { 379 wct.addInsetsSource(mTaskToken, mCaptionInsetsOwner, 0, 380 WindowInsets.Type.captionBar(), mCaptionInsets, null /* boundingRects */, 381 0 /* flags */); 382 } else { 383 wct.removeInsetsSource(mTaskToken, mCaptionInsetsOwner, 0, 384 WindowInsets.Type.captionBar()); 385 } 386 mTaskOrganizer.applyTransaction(wct); 387 } 388 389 /** Should be called when the client surface is destroyed. */ surfaceDestroyed()390 public void surfaceDestroyed() { 391 mSurfaceCreated = false; 392 mSurfaceControl = null; 393 mShellExecutor.execute(() -> { 394 if (mTaskToken == null) { 395 // Nothing to update, task is not yet available 396 return; 397 } 398 399 if (mTaskViewController.isUsingShellTransitions()) { 400 mTaskViewController.setTaskViewVisible(this, false /* visible */); 401 return; 402 } 403 404 // Unparent the task when this surface is destroyed 405 mTransaction.reparent(mTaskLeash, null).apply(); 406 updateTaskVisibility(); 407 }); 408 } 409 410 /** Called when the {@link TaskViewTaskController} is initialized. */ notifyInitialized()411 protected void notifyInitialized() { 412 if (mListener != null && !mNotifiedForInitialized) { 413 mNotifiedForInitialized = true; 414 mListenerExecutor.execute(() -> { 415 mListener.onInitialized(); 416 }); 417 } 418 } 419 420 /** Notifies listeners of a task being removed. */ notifyTaskRemovalStarted(@onNull ActivityManager.RunningTaskInfo taskInfo)421 public void notifyTaskRemovalStarted(@NonNull ActivityManager.RunningTaskInfo taskInfo) { 422 if (mListener == null) return; 423 final int taskId = taskInfo.taskId; 424 mListenerExecutor.execute(() -> mListener.onTaskRemovalStarted(taskId)); 425 } 426 427 /** Notifies listeners of a task being removed and stops intercepting back presses on it. */ handleAndNotifyTaskRemoval(ActivityManager.RunningTaskInfo taskInfo)428 private void handleAndNotifyTaskRemoval(ActivityManager.RunningTaskInfo taskInfo) { 429 if (taskInfo != null) { 430 notifyTaskRemovalStarted(taskInfo); 431 mTaskViewBase.onTaskVanished(taskInfo); 432 } 433 } 434 435 /** Returns the task info for the task in the TaskView. */ 436 @Nullable getTaskInfo()437 public ActivityManager.RunningTaskInfo getTaskInfo() { 438 return mTaskInfo; 439 } 440 441 @VisibleForTesting getPendingInfo()442 ActivityManager.RunningTaskInfo getPendingInfo() { 443 return mPendingInfo; 444 } 445 446 /** 447 * Indicates that the task was not found in the start animation for the transition. 448 * In this case we should clean up the task if we have the pending info. If we don't 449 * have the pending info, we'll do it when we receive it in 450 * {@link #onTaskAppeared(ActivityManager.RunningTaskInfo, SurfaceControl)}. 451 */ setTaskNotFound()452 public void setTaskNotFound() { 453 mTaskNotFound = true; 454 if (mPendingInfo != null) { 455 cleanUpPendingTask(); 456 } 457 } 458 459 /** 460 * Called when a task failed to open and we need to clean up task view / 461 * notify users of task view. 462 */ cleanUpPendingTask()463 void cleanUpPendingTask() { 464 if (mPendingInfo != null) { 465 final ActivityManager.RunningTaskInfo pendingInfo = mPendingInfo; 466 handleAndNotifyTaskRemoval(pendingInfo); 467 468 // Make sure the task is removed 469 mTaskViewController.removeTaskView(this, pendingInfo.token); 470 } 471 resetTaskInfo(); 472 } 473 prepareHideAnimation(@onNull SurfaceControl.Transaction finishTransaction)474 void prepareHideAnimation(@NonNull SurfaceControl.Transaction finishTransaction) { 475 if (mTaskToken == null) { 476 // Nothing to update, task is not yet available 477 return; 478 } 479 480 finishTransaction.reparent(mTaskLeash, null); 481 482 if (mListener != null) { 483 final int taskId = mTaskInfo.taskId; 484 mListener.onTaskVisibilityChanged(taskId, mSurfaceCreated /* visible */); 485 } 486 } 487 488 /** 489 * Called when the associated Task closes. If the TaskView is just being hidden, prepareHide 490 * is used instead. 491 */ prepareCloseAnimation()492 void prepareCloseAnimation() { 493 handleAndNotifyTaskRemoval(mTaskInfo); 494 resetTaskInfo(); 495 } 496 497 /** 498 * Prepare this taskview to open {@param taskInfo}. 499 * @return The bounds of the task or {@code null} on failure (surface is destroyed) 500 */ prepareOpen(ActivityManager.RunningTaskInfo taskInfo, SurfaceControl leash)501 Rect prepareOpen(ActivityManager.RunningTaskInfo taskInfo, SurfaceControl leash) { 502 mPendingInfo = null; 503 mTaskInfo = taskInfo; 504 mTaskToken = mTaskInfo.token; 505 mTaskLeash = leash; 506 if (!mSurfaceCreated) { 507 return null; 508 } 509 return mTaskViewBase.getCurrentBoundsOnScreen(); 510 } 511 512 /** Notify that the associated task has appeared. This will call appropriate listeners. */ notifyAppeared(final boolean newTask)513 void notifyAppeared(final boolean newTask) { 514 mTaskViewBase.onTaskAppeared(mTaskInfo, mTaskLeash); 515 if (mListener != null) { 516 final int taskId = mTaskInfo.taskId; 517 final ComponentName baseActivity = mTaskInfo.baseActivity; 518 519 mListenerExecutor.execute(() -> { 520 if (newTask) { 521 mListener.onTaskCreated(taskId, baseActivity); 522 } 523 // Even if newTask, send a visibilityChange if the surface was destroyed. 524 if (!newTask || !mSurfaceCreated) { 525 mListener.onTaskVisibilityChanged(taskId, mSurfaceCreated /* visible */); 526 } 527 }); 528 } 529 } 530 } 531