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; 18 19 import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM; 20 import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN; 21 import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW; 22 import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED; 23 import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED; 24 25 import static com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_TASK_ORG; 26 27 import android.annotation.IntDef; 28 import android.annotation.NonNull; 29 import android.annotation.Nullable; 30 import android.app.ActivityManager.RunningTaskInfo; 31 import android.app.TaskInfo; 32 import android.app.WindowConfiguration; 33 import android.content.Context; 34 import android.content.LocusId; 35 import android.content.pm.ActivityInfo; 36 import android.graphics.Rect; 37 import android.os.Binder; 38 import android.os.IBinder; 39 import android.util.ArrayMap; 40 import android.util.ArraySet; 41 import android.util.Log; 42 import android.util.SparseArray; 43 import android.view.SurfaceControl; 44 import android.window.ITaskOrganizerController; 45 import android.window.StartingWindowInfo; 46 import android.window.StartingWindowRemovalInfo; 47 import android.window.TaskAppearedInfo; 48 import android.window.TaskOrganizer; 49 50 import com.android.internal.annotations.VisibleForTesting; 51 import com.android.internal.protolog.common.ProtoLog; 52 import com.android.internal.util.FrameworkStatsLog; 53 import com.android.wm.shell.common.ScreenshotUtils; 54 import com.android.wm.shell.common.ShellExecutor; 55 import com.android.wm.shell.compatui.CompatUIController; 56 import com.android.wm.shell.recents.RecentTasksController; 57 import com.android.wm.shell.startingsurface.StartingWindowController; 58 59 import java.io.PrintWriter; 60 import java.util.ArrayList; 61 import java.util.Arrays; 62 import java.util.List; 63 import java.util.Objects; 64 import java.util.Optional; 65 import java.util.function.Consumer; 66 67 /** 68 * Unified task organizer for all components in the shell. 69 * TODO(b/167582004): may consider consolidating this class and TaskOrganizer 70 */ 71 public class ShellTaskOrganizer extends TaskOrganizer implements 72 CompatUIController.CompatUICallback { 73 74 // Intentionally using negative numbers here so the positive numbers can be used 75 // for task id specific listeners that will be added later. 76 public static final int TASK_LISTENER_TYPE_UNDEFINED = -1; 77 public static final int TASK_LISTENER_TYPE_FULLSCREEN = -2; 78 public static final int TASK_LISTENER_TYPE_MULTI_WINDOW = -3; 79 public static final int TASK_LISTENER_TYPE_PIP = -4; 80 public static final int TASK_LISTENER_TYPE_FREEFORM = -5; 81 82 @IntDef(prefix = {"TASK_LISTENER_TYPE_"}, value = { 83 TASK_LISTENER_TYPE_UNDEFINED, 84 TASK_LISTENER_TYPE_FULLSCREEN, 85 TASK_LISTENER_TYPE_MULTI_WINDOW, 86 TASK_LISTENER_TYPE_PIP, 87 TASK_LISTENER_TYPE_FREEFORM, 88 }) 89 public @interface TaskListenerType {} 90 91 private static final String TAG = "ShellTaskOrganizer"; 92 93 /** 94 * Callbacks for when the tasks change in the system. 95 */ 96 public interface TaskListener { onTaskAppeared(RunningTaskInfo taskInfo, SurfaceControl leash)97 default void onTaskAppeared(RunningTaskInfo taskInfo, SurfaceControl leash) {} onTaskInfoChanged(RunningTaskInfo taskInfo)98 default void onTaskInfoChanged(RunningTaskInfo taskInfo) {} onTaskVanished(RunningTaskInfo taskInfo)99 default void onTaskVanished(RunningTaskInfo taskInfo) {} onBackPressedOnTaskRoot(RunningTaskInfo taskInfo)100 default void onBackPressedOnTaskRoot(RunningTaskInfo taskInfo) {} 101 /** Whether this task listener supports compat UI. */ supportCompatUI()102 default boolean supportCompatUI() { 103 // All TaskListeners should support compat UI except PIP. 104 return true; 105 } 106 /** Attaches the a child window surface to the task surface. */ attachChildSurfaceToTask(int taskId, SurfaceControl.Builder b)107 default void attachChildSurfaceToTask(int taskId, SurfaceControl.Builder b) { 108 throw new IllegalStateException( 109 "This task listener doesn't support child surface attachment."); 110 } dump(@onNull PrintWriter pw, String prefix)111 default void dump(@NonNull PrintWriter pw, String prefix) {}; 112 } 113 114 /** 115 * Callbacks for events on a task with a locus id. 116 */ 117 public interface LocusIdListener { 118 /** 119 * Notifies when a task with a locusId becomes visible, when a visible task's locusId 120 * changes, or if a previously visible task with a locusId becomes invisible. 121 */ onVisibilityChanged(int taskId, LocusId locus, boolean visible)122 void onVisibilityChanged(int taskId, LocusId locus, boolean visible); 123 } 124 125 /** 126 * Callbacks for events in which the focus has changed. 127 */ 128 public interface FocusListener { 129 /** 130 * Notifies when the task which is focused has changed. 131 */ onFocusTaskChanged(RunningTaskInfo taskInfo)132 void onFocusTaskChanged(RunningTaskInfo taskInfo); 133 } 134 135 /** 136 * Keys map from either a task id or {@link TaskListenerType}. 137 * @see #addListenerForTaskId 138 * @see #addListenerForType 139 */ 140 private final SparseArray<TaskListener> mTaskListeners = new SparseArray<>(); 141 142 // Keeps track of all the tasks reported to this organizer (changes in windowing mode will 143 // require us to report to both old and new listeners) 144 private final SparseArray<TaskAppearedInfo> mTasks = new SparseArray<>(); 145 146 /** @see #setPendingLaunchCookieListener */ 147 private final ArrayMap<IBinder, TaskListener> mLaunchCookieToListener = new ArrayMap<>(); 148 149 // Keeps track of taskId's with visible locusIds. Used to notify any {@link LocusIdListener}s 150 // that might be set. 151 private final SparseArray<LocusId> mVisibleTasksWithLocusId = new SparseArray<>(); 152 153 /** @see #addLocusIdListener */ 154 private final ArraySet<LocusIdListener> mLocusIdListeners = new ArraySet<>(); 155 156 private final ArraySet<FocusListener> mFocusListeners = new ArraySet<>(); 157 158 private final Object mLock = new Object(); 159 private StartingWindowController mStartingWindow; 160 161 /** 162 * In charge of showing compat UI. Can be {@code null} if device doesn't support size 163 * compat. 164 */ 165 @Nullable 166 private final CompatUIController mCompatUI; 167 168 @Nullable 169 private final Optional<RecentTasksController> mRecentTasks; 170 171 @Nullable 172 private RunningTaskInfo mLastFocusedTaskInfo; 173 ShellTaskOrganizer(ShellExecutor mainExecutor, Context context)174 public ShellTaskOrganizer(ShellExecutor mainExecutor, Context context) { 175 this(null /* taskOrganizerController */, mainExecutor, context, null /* compatUI */, 176 Optional.empty() /* recentTasksController */); 177 } 178 ShellTaskOrganizer(ShellExecutor mainExecutor, Context context, @Nullable CompatUIController compatUI)179 public ShellTaskOrganizer(ShellExecutor mainExecutor, Context context, @Nullable 180 CompatUIController compatUI) { 181 this(null /* taskOrganizerController */, mainExecutor, context, compatUI, 182 Optional.empty() /* recentTasksController */); 183 } 184 ShellTaskOrganizer(ShellExecutor mainExecutor, Context context, @Nullable CompatUIController compatUI, Optional<RecentTasksController> recentTasks)185 public ShellTaskOrganizer(ShellExecutor mainExecutor, Context context, @Nullable 186 CompatUIController compatUI, 187 Optional<RecentTasksController> recentTasks) { 188 this(null /* taskOrganizerController */, mainExecutor, context, compatUI, 189 recentTasks); 190 } 191 192 @VisibleForTesting ShellTaskOrganizer(ITaskOrganizerController taskOrganizerController, ShellExecutor mainExecutor, Context context, @Nullable CompatUIController compatUI, Optional<RecentTasksController> recentTasks)193 ShellTaskOrganizer(ITaskOrganizerController taskOrganizerController, ShellExecutor mainExecutor, 194 Context context, @Nullable CompatUIController compatUI, 195 Optional<RecentTasksController> recentTasks) { 196 super(taskOrganizerController, mainExecutor); 197 mCompatUI = compatUI; 198 mRecentTasks = recentTasks; 199 if (compatUI != null) { 200 compatUI.setCompatUICallback(this); 201 } 202 } 203 204 @Override registerOrganizer()205 public List<TaskAppearedInfo> registerOrganizer() { 206 synchronized (mLock) { 207 ProtoLog.v(WM_SHELL_TASK_ORG, "Registering organizer"); 208 final List<TaskAppearedInfo> taskInfos = super.registerOrganizer(); 209 for (int i = 0; i < taskInfos.size(); i++) { 210 final TaskAppearedInfo info = taskInfos.get(i); 211 ProtoLog.v(WM_SHELL_TASK_ORG, "Existing task: id=%d component=%s", 212 info.getTaskInfo().taskId, info.getTaskInfo().baseIntent); 213 onTaskAppeared(info); 214 } 215 return taskInfos; 216 } 217 } 218 219 @Override unregisterOrganizer()220 public void unregisterOrganizer() { 221 super.unregisterOrganizer(); 222 if (mStartingWindow != null) { 223 mStartingWindow.clearAllWindows(); 224 } 225 } 226 createRootTask(int displayId, int windowingMode, TaskListener listener)227 public void createRootTask(int displayId, int windowingMode, TaskListener listener) { 228 ProtoLog.v(WM_SHELL_TASK_ORG, "createRootTask() displayId=%d winMode=%d listener=%s", 229 displayId, windowingMode, listener.toString()); 230 final IBinder cookie = new Binder(); 231 setPendingLaunchCookieListener(cookie, listener); 232 super.createRootTask(displayId, windowingMode, cookie); 233 } 234 235 /** 236 * @hide 237 */ initStartingWindow(StartingWindowController startingWindow)238 public void initStartingWindow(StartingWindowController startingWindow) { 239 mStartingWindow = startingWindow; 240 } 241 242 /** 243 * Adds a listener for a specific task id. 244 */ addListenerForTaskId(TaskListener listener, int taskId)245 public void addListenerForTaskId(TaskListener listener, int taskId) { 246 synchronized (mLock) { 247 ProtoLog.v(WM_SHELL_TASK_ORG, "addListenerForTaskId taskId=%s", taskId); 248 if (mTaskListeners.get(taskId) != null) { 249 throw new IllegalArgumentException( 250 "Listener for taskId=" + taskId + " already exists"); 251 } 252 253 final TaskAppearedInfo info = mTasks.get(taskId); 254 if (info == null) { 255 throw new IllegalArgumentException("addListenerForTaskId unknown taskId=" + taskId); 256 } 257 258 final TaskListener oldListener = getTaskListener(info.getTaskInfo()); 259 mTaskListeners.put(taskId, listener); 260 updateTaskListenerIfNeeded(info.getTaskInfo(), info.getLeash(), oldListener, listener); 261 } 262 } 263 264 /** 265 * Adds a listener for tasks with given types. 266 */ addListenerForType(TaskListener listener, @TaskListenerType int... listenerTypes)267 public void addListenerForType(TaskListener listener, @TaskListenerType int... listenerTypes) { 268 synchronized (mLock) { 269 ProtoLog.v(WM_SHELL_TASK_ORG, "addListenerForType types=%s listener=%s", 270 Arrays.toString(listenerTypes), listener); 271 for (int listenerType : listenerTypes) { 272 if (mTaskListeners.get(listenerType) != null) { 273 throw new IllegalArgumentException("Listener for listenerType=" + listenerType 274 + " already exists"); 275 } 276 mTaskListeners.put(listenerType, listener); 277 } 278 279 // Notify the listener of all existing tasks with the given type. 280 for (int i = mTasks.size() - 1; i >= 0; --i) { 281 final TaskAppearedInfo data = mTasks.valueAt(i); 282 final TaskListener taskListener = getTaskListener(data.getTaskInfo()); 283 if (taskListener != listener) continue; 284 listener.onTaskAppeared(data.getTaskInfo(), data.getLeash()); 285 } 286 } 287 } 288 289 /** 290 * Removes a registered listener. 291 */ removeListener(TaskListener listener)292 public void removeListener(TaskListener listener) { 293 synchronized (mLock) { 294 ProtoLog.v(WM_SHELL_TASK_ORG, "Remove listener=%s", listener); 295 final int index = mTaskListeners.indexOfValue(listener); 296 if (index == -1) { 297 Log.w(TAG, "No registered listener found"); 298 return; 299 } 300 301 // Collect tasks associated with the listener we are about to remove. 302 final ArrayList<TaskAppearedInfo> tasks = new ArrayList<>(); 303 for (int i = mTasks.size() - 1; i >= 0; --i) { 304 final TaskAppearedInfo data = mTasks.valueAt(i); 305 final TaskListener taskListener = getTaskListener(data.getTaskInfo()); 306 if (taskListener != listener) continue; 307 tasks.add(data); 308 } 309 310 // Remove listener, there can be the multiple occurrences, so search the whole list. 311 for (int i = mTaskListeners.size() - 1; i >= 0; --i) { 312 if (mTaskListeners.valueAt(i) == listener) { 313 mTaskListeners.removeAt(i); 314 } 315 } 316 317 // Associate tasks with new listeners if needed. 318 for (int i = tasks.size() - 1; i >= 0; --i) { 319 final TaskAppearedInfo data = tasks.get(i); 320 updateTaskListenerIfNeeded(data.getTaskInfo(), data.getLeash(), 321 null /* oldListener already removed*/, getTaskListener(data.getTaskInfo())); 322 } 323 } 324 } 325 326 /** 327 * Associated a listener to a pending launch cookie so we can route the task later once it 328 * appears. 329 */ setPendingLaunchCookieListener(IBinder cookie, TaskListener listener)330 public void setPendingLaunchCookieListener(IBinder cookie, TaskListener listener) { 331 synchronized (mLock) { 332 mLaunchCookieToListener.put(cookie, listener); 333 } 334 } 335 336 /** 337 * Adds a listener to be notified for {@link LocusId} visibility changes. 338 */ addLocusIdListener(LocusIdListener listener)339 public void addLocusIdListener(LocusIdListener listener) { 340 synchronized (mLock) { 341 mLocusIdListeners.add(listener); 342 for (int i = 0; i < mVisibleTasksWithLocusId.size(); i++) { 343 listener.onVisibilityChanged(mVisibleTasksWithLocusId.keyAt(i), 344 mVisibleTasksWithLocusId.valueAt(i), true /* visible */); 345 } 346 } 347 } 348 349 /** 350 * Removes listener. 351 */ removeLocusIdListener(LocusIdListener listener)352 public void removeLocusIdListener(LocusIdListener listener) { 353 synchronized (mLock) { 354 mLocusIdListeners.remove(listener); 355 } 356 } 357 358 /** 359 * Adds a listener to be notified for task focus changes. 360 */ addFocusListener(FocusListener listener)361 public void addFocusListener(FocusListener listener) { 362 synchronized (mLock) { 363 mFocusListeners.add(listener); 364 if (mLastFocusedTaskInfo != null) { 365 listener.onFocusTaskChanged(mLastFocusedTaskInfo); 366 } 367 } 368 } 369 370 /** 371 * Removes listener. 372 */ removeLocusIdListener(FocusListener listener)373 public void removeLocusIdListener(FocusListener listener) { 374 synchronized (mLock) { 375 mFocusListeners.remove(listener); 376 } 377 } 378 379 @Override addStartingWindow(StartingWindowInfo info, IBinder appToken)380 public void addStartingWindow(StartingWindowInfo info, IBinder appToken) { 381 if (mStartingWindow != null) { 382 mStartingWindow.addStartingWindow(info, appToken); 383 } 384 } 385 386 @Override removeStartingWindow(StartingWindowRemovalInfo removalInfo)387 public void removeStartingWindow(StartingWindowRemovalInfo removalInfo) { 388 if (mStartingWindow != null) { 389 mStartingWindow.removeStartingWindow(removalInfo); 390 } 391 } 392 393 @Override copySplashScreenView(int taskId)394 public void copySplashScreenView(int taskId) { 395 if (mStartingWindow != null) { 396 mStartingWindow.copySplashScreenView(taskId); 397 } 398 } 399 400 @Override onAppSplashScreenViewRemoved(int taskId)401 public void onAppSplashScreenViewRemoved(int taskId) { 402 if (mStartingWindow != null) { 403 mStartingWindow.onAppSplashScreenViewRemoved(taskId); 404 } 405 } 406 407 @Override onImeDrawnOnTask(int taskId)408 public void onImeDrawnOnTask(int taskId) { 409 if (mStartingWindow != null) { 410 mStartingWindow.onImeDrawnOnTask(taskId); 411 } 412 } 413 414 @Override onTaskAppeared(RunningTaskInfo taskInfo, SurfaceControl leash)415 public void onTaskAppeared(RunningTaskInfo taskInfo, SurfaceControl leash) { 416 synchronized (mLock) { 417 onTaskAppeared(new TaskAppearedInfo(taskInfo, leash)); 418 } 419 } 420 onTaskAppeared(TaskAppearedInfo info)421 private void onTaskAppeared(TaskAppearedInfo info) { 422 final int taskId = info.getTaskInfo().taskId; 423 mTasks.put(taskId, info); 424 final TaskListener listener = 425 getTaskListener(info.getTaskInfo(), true /*removeLaunchCookieIfNeeded*/); 426 ProtoLog.v(WM_SHELL_TASK_ORG, "Task appeared taskId=%d listener=%s", taskId, listener); 427 if (listener != null) { 428 listener.onTaskAppeared(info.getTaskInfo(), info.getLeash()); 429 } 430 notifyLocusVisibilityIfNeeded(info.getTaskInfo()); 431 notifyCompatUI(info.getTaskInfo(), listener); 432 } 433 434 /** 435 * Take a screenshot of a task. 436 */ screenshotTask(RunningTaskInfo taskInfo, Rect crop, Consumer<SurfaceControl.ScreenshotHardwareBuffer> consumer)437 public void screenshotTask(RunningTaskInfo taskInfo, Rect crop, 438 Consumer<SurfaceControl.ScreenshotHardwareBuffer> consumer) { 439 final TaskAppearedInfo info = mTasks.get(taskInfo.taskId); 440 if (info == null) { 441 return; 442 } 443 ScreenshotUtils.captureLayer(info.getLeash(), crop, consumer); 444 } 445 446 447 @Override onTaskInfoChanged(RunningTaskInfo taskInfo)448 public void onTaskInfoChanged(RunningTaskInfo taskInfo) { 449 synchronized (mLock) { 450 ProtoLog.v(WM_SHELL_TASK_ORG, "Task info changed taskId=%d", taskInfo.taskId); 451 final TaskAppearedInfo data = mTasks.get(taskInfo.taskId); 452 final TaskListener oldListener = getTaskListener(data.getTaskInfo()); 453 final TaskListener newListener = getTaskListener(taskInfo); 454 mTasks.put(taskInfo.taskId, new TaskAppearedInfo(taskInfo, data.getLeash())); 455 final boolean updated = updateTaskListenerIfNeeded( 456 taskInfo, data.getLeash(), oldListener, newListener); 457 if (!updated && newListener != null) { 458 newListener.onTaskInfoChanged(taskInfo); 459 } 460 notifyLocusVisibilityIfNeeded(taskInfo); 461 if (updated || !taskInfo.equalsForSizeCompat(data.getTaskInfo())) { 462 // Notify the compat UI if the listener or task info changed. 463 notifyCompatUI(taskInfo, newListener); 464 } 465 if (data.getTaskInfo().getWindowingMode() != taskInfo.getWindowingMode()) { 466 // Notify the recent tasks when a task changes windowing modes 467 mRecentTasks.ifPresent(recentTasks -> 468 recentTasks.onTaskWindowingModeChanged(taskInfo)); 469 } 470 // TODO (b/207687679): Remove check for HOME once bug is fixed 471 final boolean isFocusedOrHome = taskInfo.isFocused 472 || (taskInfo.topActivityType == WindowConfiguration.ACTIVITY_TYPE_HOME 473 && taskInfo.isVisible); 474 final boolean focusTaskChanged = (mLastFocusedTaskInfo == null 475 || mLastFocusedTaskInfo.taskId != taskInfo.taskId) && isFocusedOrHome; 476 if (focusTaskChanged) { 477 for (int i = 0; i < mFocusListeners.size(); i++) { 478 mFocusListeners.valueAt(i).onFocusTaskChanged(taskInfo); 479 } 480 mLastFocusedTaskInfo = taskInfo; 481 } 482 } 483 } 484 485 @Override onBackPressedOnTaskRoot(RunningTaskInfo taskInfo)486 public void onBackPressedOnTaskRoot(RunningTaskInfo taskInfo) { 487 synchronized (mLock) { 488 ProtoLog.v(WM_SHELL_TASK_ORG, "Task root back pressed taskId=%d", taskInfo.taskId); 489 final TaskListener listener = getTaskListener(taskInfo); 490 if (listener != null) { 491 listener.onBackPressedOnTaskRoot(taskInfo); 492 } 493 } 494 } 495 496 @Override onTaskVanished(RunningTaskInfo taskInfo)497 public void onTaskVanished(RunningTaskInfo taskInfo) { 498 synchronized (mLock) { 499 ProtoLog.v(WM_SHELL_TASK_ORG, "Task vanished taskId=%d", taskInfo.taskId); 500 final int taskId = taskInfo.taskId; 501 final TaskListener listener = getTaskListener(mTasks.get(taskId).getTaskInfo()); 502 mTasks.remove(taskId); 503 if (listener != null) { 504 listener.onTaskVanished(taskInfo); 505 } 506 notifyLocusVisibilityIfNeeded(taskInfo); 507 // Pass null for listener to remove the compat UI on this task if there is any. 508 notifyCompatUI(taskInfo, null /* taskListener */); 509 // Notify the recent tasks that a task has been removed 510 mRecentTasks.ifPresent(recentTasks -> recentTasks.onTaskRemoved(taskInfo)); 511 } 512 } 513 514 /** Gets running task by taskId. Returns {@code null} if no such task observed. */ 515 @Nullable getRunningTaskInfo(int taskId)516 public RunningTaskInfo getRunningTaskInfo(int taskId) { 517 synchronized (mLock) { 518 final TaskAppearedInfo info = mTasks.get(taskId); 519 return info != null ? info.getTaskInfo() : null; 520 } 521 } 522 523 /** Helper to set int metadata on the Surface corresponding to the task id. */ setSurfaceMetadata(int taskId, int key, int value)524 public void setSurfaceMetadata(int taskId, int key, int value) { 525 synchronized (mLock) { 526 final TaskAppearedInfo info = mTasks.get(taskId); 527 if (info == null || info.getLeash() == null) { 528 return; 529 } 530 SurfaceControl.Transaction t = new SurfaceControl.Transaction(); 531 t.setMetadata(info.getLeash(), key, value); 532 t.apply(); 533 } 534 } 535 updateTaskListenerIfNeeded(RunningTaskInfo taskInfo, SurfaceControl leash, TaskListener oldListener, TaskListener newListener)536 private boolean updateTaskListenerIfNeeded(RunningTaskInfo taskInfo, SurfaceControl leash, 537 TaskListener oldListener, TaskListener newListener) { 538 if (oldListener == newListener) return false; 539 // TODO: We currently send vanished/appeared as the task moves between types, but 540 // we should consider adding a different mode-changed callback 541 if (oldListener != null) { 542 oldListener.onTaskVanished(taskInfo); 543 } 544 if (newListener != null) { 545 newListener.onTaskAppeared(taskInfo, leash); 546 } 547 return true; 548 } 549 notifyLocusVisibilityIfNeeded(TaskInfo taskInfo)550 private void notifyLocusVisibilityIfNeeded(TaskInfo taskInfo) { 551 final int taskId = taskInfo.taskId; 552 final LocusId prevLocus = mVisibleTasksWithLocusId.get(taskId); 553 final boolean sameLocus = Objects.equals(prevLocus, taskInfo.mTopActivityLocusId); 554 if (prevLocus == null) { 555 // New visible locus 556 if (taskInfo.mTopActivityLocusId != null && taskInfo.isVisible) { 557 mVisibleTasksWithLocusId.put(taskId, taskInfo.mTopActivityLocusId); 558 notifyLocusIdChange(taskId, taskInfo.mTopActivityLocusId, true /* visible */); 559 } 560 } else if (sameLocus && !taskInfo.isVisible) { 561 // Hidden locus 562 mVisibleTasksWithLocusId.remove(taskId); 563 notifyLocusIdChange(taskId, taskInfo.mTopActivityLocusId, false /* visible */); 564 } else if (!sameLocus) { 565 // Changed locus 566 if (taskInfo.isVisible) { 567 mVisibleTasksWithLocusId.put(taskId, taskInfo.mTopActivityLocusId); 568 notifyLocusIdChange(taskId, prevLocus, false /* visible */); 569 notifyLocusIdChange(taskId, taskInfo.mTopActivityLocusId, true /* visible */); 570 } else { 571 mVisibleTasksWithLocusId.remove(taskInfo.taskId); 572 notifyLocusIdChange(taskId, prevLocus, false /* visible */); 573 } 574 } 575 } 576 notifyLocusIdChange(int taskId, LocusId locus, boolean visible)577 private void notifyLocusIdChange(int taskId, LocusId locus, boolean visible) { 578 for (int i = 0; i < mLocusIdListeners.size(); i++) { 579 mLocusIdListeners.valueAt(i).onVisibilityChanged(taskId, locus, visible); 580 } 581 } 582 583 @Override onSizeCompatRestartButtonAppeared(int taskId)584 public void onSizeCompatRestartButtonAppeared(int taskId) { 585 final TaskAppearedInfo info; 586 synchronized (mLock) { 587 info = mTasks.get(taskId); 588 } 589 if (info == null) { 590 return; 591 } 592 logSizeCompatRestartButtonEventReported(info, 593 FrameworkStatsLog.SIZE_COMPAT_RESTART_BUTTON_EVENT_REPORTED__EVENT__APPEARED); 594 } 595 596 @Override onSizeCompatRestartButtonClicked(int taskId)597 public void onSizeCompatRestartButtonClicked(int taskId) { 598 final TaskAppearedInfo info; 599 synchronized (mLock) { 600 info = mTasks.get(taskId); 601 } 602 if (info == null) { 603 return; 604 } 605 logSizeCompatRestartButtonEventReported(info, 606 FrameworkStatsLog.SIZE_COMPAT_RESTART_BUTTON_EVENT_REPORTED__EVENT__CLICKED); 607 restartTaskTopActivityProcessIfVisible(info.getTaskInfo().token); 608 } 609 logSizeCompatRestartButtonEventReported(@onNull TaskAppearedInfo info, int event)610 private void logSizeCompatRestartButtonEventReported(@NonNull TaskAppearedInfo info, 611 int event) { 612 ActivityInfo topActivityInfo = info.getTaskInfo().topActivityInfo; 613 if (topActivityInfo == null) { 614 return; 615 } 616 FrameworkStatsLog.write(FrameworkStatsLog.SIZE_COMPAT_RESTART_BUTTON_EVENT_REPORTED, 617 topActivityInfo.applicationInfo.uid, event); 618 } 619 620 /** 621 * Notifies {@link CompatUIController} about the compat info changed on the give Task 622 * to update the UI accordingly. 623 * 624 * @param taskInfo the new Task info 625 * @param taskListener listener to handle the Task Surface placement. {@code null} if task is 626 * vanished. 627 */ notifyCompatUI(RunningTaskInfo taskInfo, @Nullable TaskListener taskListener)628 private void notifyCompatUI(RunningTaskInfo taskInfo, @Nullable TaskListener taskListener) { 629 if (mCompatUI == null) { 630 return; 631 } 632 633 // The task is vanished or doesn't support compat UI, notify to remove compat UI 634 // on this Task if there is any. 635 if (taskListener == null || !taskListener.supportCompatUI() 636 || !taskInfo.topActivityInSizeCompat || !taskInfo.isVisible) { 637 mCompatUI.onCompatInfoChanged(taskInfo.displayId, taskInfo.taskId, 638 null /* taskConfig */, null /* taskListener */); 639 return; 640 } 641 642 mCompatUI.onCompatInfoChanged(taskInfo.displayId, taskInfo.taskId, 643 taskInfo.configuration, taskListener); 644 } 645 getTaskListener(RunningTaskInfo runningTaskInfo)646 private TaskListener getTaskListener(RunningTaskInfo runningTaskInfo) { 647 return getTaskListener(runningTaskInfo, false /*removeLaunchCookieIfNeeded*/); 648 } 649 getTaskListener(RunningTaskInfo runningTaskInfo, boolean removeLaunchCookieIfNeeded)650 private TaskListener getTaskListener(RunningTaskInfo runningTaskInfo, 651 boolean removeLaunchCookieIfNeeded) { 652 653 final int taskId = runningTaskInfo.taskId; 654 TaskListener listener; 655 656 // First priority goes to listener that might be pending for this task. 657 final ArrayList<IBinder> launchCookies = runningTaskInfo.launchCookies; 658 for (int i = launchCookies.size() - 1; i >= 0; --i) { 659 final IBinder cookie = launchCookies.get(i); 660 listener = mLaunchCookieToListener.get(cookie); 661 if (listener == null) continue; 662 663 if (removeLaunchCookieIfNeeded) { 664 // Remove the cookie and add the listener. 665 mLaunchCookieToListener.remove(cookie); 666 mTaskListeners.put(taskId, listener); 667 } 668 return listener; 669 } 670 671 // Next priority goes to taskId specific listeners. 672 listener = mTaskListeners.get(taskId); 673 if (listener != null) return listener; 674 675 // Next priority goes to the listener listening to its parent. 676 if (runningTaskInfo.hasParentTask()) { 677 listener = mTaskListeners.get(runningTaskInfo.parentTaskId); 678 if (listener != null) return listener; 679 } 680 681 // Next we try type specific listeners. 682 final int taskListenerType = taskInfoToTaskListenerType(runningTaskInfo); 683 return mTaskListeners.get(taskListenerType); 684 } 685 686 @VisibleForTesting taskInfoToTaskListenerType(RunningTaskInfo runningTaskInfo)687 static @TaskListenerType int taskInfoToTaskListenerType(RunningTaskInfo runningTaskInfo) { 688 switch (runningTaskInfo.getWindowingMode()) { 689 case WINDOWING_MODE_FULLSCREEN: 690 return TASK_LISTENER_TYPE_FULLSCREEN; 691 case WINDOWING_MODE_MULTI_WINDOW: 692 return TASK_LISTENER_TYPE_MULTI_WINDOW; 693 case WINDOWING_MODE_PINNED: 694 return TASK_LISTENER_TYPE_PIP; 695 case WINDOWING_MODE_FREEFORM: 696 return TASK_LISTENER_TYPE_FREEFORM; 697 case WINDOWING_MODE_UNDEFINED: 698 default: 699 return TASK_LISTENER_TYPE_UNDEFINED; 700 } 701 } 702 taskListenerTypeToString(@askListenerType int type)703 public static String taskListenerTypeToString(@TaskListenerType int type) { 704 switch (type) { 705 case TASK_LISTENER_TYPE_FULLSCREEN: 706 return "TASK_LISTENER_TYPE_FULLSCREEN"; 707 case TASK_LISTENER_TYPE_MULTI_WINDOW: 708 return "TASK_LISTENER_TYPE_MULTI_WINDOW"; 709 case TASK_LISTENER_TYPE_PIP: 710 return "TASK_LISTENER_TYPE_PIP"; 711 case TASK_LISTENER_TYPE_FREEFORM: 712 return "TASK_LISTENER_TYPE_FREEFORM"; 713 case TASK_LISTENER_TYPE_UNDEFINED: 714 return "TASK_LISTENER_TYPE_UNDEFINED"; 715 default: 716 return "taskId#" + type; 717 } 718 } 719 dump(@onNull PrintWriter pw, String prefix)720 public void dump(@NonNull PrintWriter pw, String prefix) { 721 synchronized (mLock) { 722 final String innerPrefix = prefix + " "; 723 final String childPrefix = innerPrefix + " "; 724 pw.println(prefix + TAG); 725 pw.println(innerPrefix + mTaskListeners.size() + " Listeners"); 726 for (int i = mTaskListeners.size() - 1; i >= 0; --i) { 727 final int key = mTaskListeners.keyAt(i); 728 final TaskListener listener = mTaskListeners.valueAt(i); 729 pw.println(innerPrefix + "#" + i + " " + taskListenerTypeToString(key)); 730 listener.dump(pw, childPrefix); 731 } 732 733 pw.println(); 734 pw.println(innerPrefix + mTasks.size() + " Tasks"); 735 for (int i = mTasks.size() - 1; i >= 0; --i) { 736 final int key = mTasks.keyAt(i); 737 final TaskAppearedInfo info = mTasks.valueAt(i); 738 final TaskListener listener = getTaskListener(info.getTaskInfo()); 739 pw.println(innerPrefix + "#" + i + " task=" + key + " listener=" + listener); 740 } 741 742 pw.println(); 743 pw.println(innerPrefix + mLaunchCookieToListener.size() + " Launch Cookies"); 744 for (int i = mLaunchCookieToListener.size() - 1; i >= 0; --i) { 745 final IBinder key = mLaunchCookieToListener.keyAt(i); 746 final TaskListener listener = mLaunchCookieToListener.valueAt(i); 747 pw.println(innerPrefix + "#" + i + " cookie=" + key + " listener=" + listener); 748 } 749 } 750 } 751 } 752