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.car.carlauncher; 18 19 import static android.app.ActivityTaskManager.INVALID_TASK_ID; 20 import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW; 21 import static android.car.user.CarUserManager.USER_LIFECYCLE_EVENT_TYPE_SWITCHING; 22 import static android.car.user.CarUserManager.USER_LIFECYCLE_EVENT_TYPE_UNLOCKED; 23 24 import static com.android.car.carlauncher.CarLauncher.TAG; 25 import static com.android.wm.shell.ShellTaskOrganizer.TASK_LISTENER_TYPE_FULLSCREEN; 26 27 import android.annotation.NonNull; 28 import android.annotation.Nullable; 29 import android.app.Activity; 30 import android.app.ActivityManager; 31 import android.app.ActivityTaskManager; 32 import android.app.Application.ActivityLifecycleCallbacks; 33 import android.app.TaskInfo; 34 import android.app.TaskStackListener; 35 import android.car.Car; 36 import android.car.app.CarActivityManager; 37 import android.car.user.CarUserManager; 38 import android.car.user.UserLifecycleEventFilter; 39 import android.content.BroadcastReceiver; 40 import android.content.Context; 41 import android.content.Intent; 42 import android.content.IntentFilter; 43 import android.os.Bundle; 44 import android.os.UserManager; 45 import android.util.Log; 46 import android.util.Slog; 47 import android.view.SurfaceControl; 48 import android.window.TaskAppearedInfo; 49 import android.window.WindowContainerTransaction; 50 51 import com.android.car.carlauncher.taskstack.TaskStackChangeListeners; 52 import com.android.internal.annotations.VisibleForTesting; 53 import com.android.launcher3.icons.IconProvider; 54 import com.android.wm.shell.ShellTaskOrganizer; 55 import com.android.wm.shell.common.HandlerExecutor; 56 import com.android.wm.shell.common.SyncTransactionQueue; 57 import com.android.wm.shell.common.TransactionPool; 58 import com.android.wm.shell.common.annotations.ShellMainThread; 59 import com.android.wm.shell.fullscreen.FullscreenTaskListener; 60 import com.android.wm.shell.startingsurface.StartingWindowController; 61 import com.android.wm.shell.startingsurface.phone.PhoneStartingWindowTypeAlgorithm; 62 import com.android.wm.shell.sysui.ShellCommandHandler; 63 import com.android.wm.shell.sysui.ShellController; 64 import com.android.wm.shell.sysui.ShellInit; 65 66 import java.util.ArrayList; 67 import java.util.LinkedHashMap; 68 import java.util.List; 69 import java.util.concurrent.Executor; 70 import java.util.concurrent.atomic.AtomicReference; 71 72 73 /** 74 * A manager for creating {@link ControlledCarTaskView}, {@link LaunchRootCarTaskView} & 75 * {@link SemiControlledCarTaskView}. 76 */ 77 public final class TaskViewManager { 78 static final boolean DBG = Log.isLoggable(TAG, Log.DEBUG); 79 private static final String SCHEME_PACKAGE = "package"; 80 81 private final AtomicReference<CarActivityManager> mCarActivityManagerRef = 82 new AtomicReference<>(); 83 @ShellMainThread 84 private final HandlerExecutor mShellExecutor; 85 private final SyncTransactionQueue mSyncQueue; 86 private final ShellTaskOrganizer mTaskOrganizer; 87 private TaskViewInputInterceptor mTaskViewInputInterceptor; 88 private final int mHostTaskId; 89 private final LinkedHashMap<Integer, ActivityManager.RunningTaskInfo> mLaunchRootStack = 90 new LinkedHashMap<>(); 91 92 // All TaskView are bound to the Host Activity if it exists. 93 @ShellMainThread 94 private final List<ControlledCarTaskView> mControlledTaskViews = new ArrayList<>(); 95 @ShellMainThread 96 private final List<SemiControlledCarTaskView> mSemiControlledTaskViews = new ArrayList<>(); 97 @ShellMainThread 98 private LaunchRootCarTaskView mLaunchRootCarTaskView = null; 99 100 private CarUserManager mCarUserManager; 101 private Activity mContext; 102 103 private final ShellTaskOrganizer.TaskListener mRootTaskListener = 104 new ShellTaskOrganizer.TaskListener() { 105 @Override 106 public void onTaskAppeared(ActivityManager.RunningTaskInfo taskInfo, 107 SurfaceControl leash) { 108 // Called for a task appearing the launch root. Route it to the appropriate 109 // semi-controlled taskview; 110 for (SemiControlledCarTaskView taskView : mSemiControlledTaskViews) { 111 if (taskView.getCallbacks().shouldStartInTaskView(taskInfo)) { 112 if (taskView.isInitialized()) { 113 taskView.onTaskAppeared(taskInfo, leash); 114 } 115 return; 116 } 117 } 118 119 // TODO(b/228077499): Fix for the case when a task is started in the 120 // launch-root-task right after the initialization of launch-root-task, it 121 // remains blank. 122 mSyncQueue.runInSync(t -> t.show(leash)); 123 124 CarActivityManager carAm = mCarActivityManagerRef.get(); 125 if (carAm != null) { 126 carAm.onTaskAppeared(taskInfo); 127 mLaunchRootStack.put(taskInfo.taskId, taskInfo); 128 } else { 129 Log.w(TAG, "CarActivityManager is null, skip onTaskAppeared: TaskInfo" 130 + " = " + taskInfo); 131 } 132 } 133 134 @Override 135 public void onTaskInfoChanged(ActivityManager.RunningTaskInfo taskInfo) { 136 for (SemiControlledCarTaskView taskView : mSemiControlledTaskViews) { 137 if (taskView.getCallbacks().shouldStartInTaskView(taskInfo)) { 138 if (taskView.isInitialized()) { 139 // onLocationChanged() is crucial. If this is not called, the 140 // further activities opened by the current activity do not open in 141 // the correct size. 142 // TODO(b/234879199): Explore more for a better solution. 143 taskView.onLocationChanged(); 144 taskView.onTaskInfoChanged(taskInfo); 145 } 146 // Semi-controlled apps are assumed to be Distraction optimised and 147 // hence not reported to CarActivityManager. 148 return; 149 } 150 } 151 152 // Uncontrolled apps by default launch in the launch root so nothing needs to 153 // be done here for them. 154 CarActivityManager carAm = mCarActivityManagerRef.get(); 155 if (carAm != null) { 156 carAm.onTaskInfoChanged(taskInfo); 157 if (taskInfo.isVisible && mLaunchRootStack.containsKey(taskInfo.taskId)) { 158 // Remove the task and insert again so that it jumps to the end of 159 // the queue. 160 mLaunchRootStack.remove(taskInfo.taskId); 161 mLaunchRootStack.put(taskInfo.taskId, taskInfo); 162 } 163 } else { 164 Log.w(TAG, "CarActivityManager is null, skip onTaskInfoChanged: TaskInfo" 165 + " = " + taskInfo); 166 } 167 } 168 169 @Override 170 public void onTaskVanished(ActivityManager.RunningTaskInfo taskInfo) { 171 for (SemiControlledCarTaskView taskView : mSemiControlledTaskViews) { 172 if (taskView.getCallbacks().shouldStartInTaskView(taskInfo)) { 173 if (taskView.isInitialized()) { 174 taskView.onTaskVanished(taskInfo); 175 } 176 return; 177 } 178 } 179 180 CarActivityManager carAm = mCarActivityManagerRef.get(); 181 if (carAm != null) { 182 carAm.onTaskVanished(taskInfo); 183 if (mLaunchRootStack.containsKey(taskInfo.taskId)) { 184 mLaunchRootStack.remove(taskInfo.taskId); 185 } 186 } else { 187 Log.w(TAG, "CarActivityManager is null, skip onTaskAppeared: TaskInfo" 188 + " = " + taskInfo); 189 } 190 } 191 192 @Override 193 public void onBackPressedOnTaskRoot(ActivityManager.RunningTaskInfo taskInfo) { 194 for (SemiControlledCarTaskView taskView : mSemiControlledTaskViews) { 195 if (taskView.getCallbacks().shouldStartInTaskView(taskInfo)) { 196 // Do nothing 197 Log.d(TAG, "onBackPressedOnTaskRoot received for a " 198 + "SemiControlledCarTaskView, do nothing."); 199 return; 200 } 201 } 202 if (mLaunchRootStack.size() == 1) { 203 Log.d(TAG, "Cannot remove last task from launch root."); 204 return; 205 } 206 if (mLaunchRootStack.size() == 0) { 207 Log.d(TAG, "Launch root is empty, do nothing."); 208 return; 209 } 210 211 ActivityManager.RunningTaskInfo topTask = getTopTaskInLaunchRootTask(); 212 WindowContainerTransaction wct = new WindowContainerTransaction(); 213 // removeTask() will trigger onTaskVanished which will remove the task locally 214 // from mLaunchRootStack 215 wct.removeTask(topTask.token); 216 mSyncQueue.queue(wct); 217 } 218 }; 219 220 private final TaskStackListener mTaskStackListener = new TaskStackListener() { 221 @Override 222 public void onTaskFocusChanged(int taskId, boolean focused) { 223 boolean hostFocused = taskId == mHostTaskId && focused; 224 if (DBG) { 225 Log.d(TAG, "onTaskFocusChanged: taskId=" + taskId 226 + ", hostFocused=" + hostFocused); 227 } 228 if (!hostFocused) { 229 return; 230 } 231 232 for (int i = mControlledTaskViews.size() - 1; i >= 0; --i) { 233 ControlledCarTaskView taskView = mControlledTaskViews.get(i); 234 if (taskView.getTaskId() == INVALID_TASK_ID) { 235 // If the task in TaskView is crashed when host is in background, 236 // We'd like to restart it when host becomes foreground and focused. 237 taskView.startActivity(); 238 } 239 } 240 } 241 242 @Override 243 public void onActivityRestartAttempt(ActivityManager.RunningTaskInfo task, 244 boolean homeTaskVisible, boolean clearedTask, boolean wasVisible) { 245 if (DBG) { 246 Log.d(TAG, "onActivityRestartAttempt: taskId=" + task.taskId 247 + ", homeTaskVisible=" + homeTaskVisible + ", wasVisible=" + wasVisible); 248 } 249 if (mHostTaskId != task.taskId) { 250 return; 251 } 252 WindowContainerTransaction wct = new WindowContainerTransaction(); 253 for (int i = mControlledTaskViews.size() - 1; i >= 0; --i) { 254 // showEmbeddedTasks() will restart the crashed tasks too. 255 mControlledTaskViews.get(i).showEmbeddedTask(wct); 256 } 257 if (mLaunchRootCarTaskView != null) { 258 mLaunchRootCarTaskView.showEmbeddedTask(wct); 259 } 260 for (int i = mSemiControlledTaskViews.size() - 1; i >= 0; --i) { 261 mSemiControlledTaskViews.get(i).showEmbeddedTask(wct); 262 } 263 mSyncQueue.queue(wct); 264 } 265 }; 266 267 private final CarUserManager.UserLifecycleListener mUserLifecycleListener = event -> { 268 if (DBG) { 269 Log.d(TAG, "UserLifecycleListener.onEvent: For User " 270 + mContext.getUserId() 271 + ", received an event " + event); 272 } 273 274 // When user-unlocked, if task isn't launched yet, then try to start it. 275 if (event.getEventType() == USER_LIFECYCLE_EVENT_TYPE_UNLOCKED 276 && mContext.getUserId() == event.getUserId()) { 277 for (int i = mControlledTaskViews.size() - 1; i >= 0; --i) { 278 ControlledCarTaskView taskView = mControlledTaskViews.get(i); 279 if (taskView.getTaskId() == INVALID_TASK_ID) { 280 taskView.startActivity(); 281 } 282 } 283 } 284 285 // When user-switching, onDestroy in the previous user's Host app isn't called. 286 // So try to release the resource explicitly. 287 if (event.getEventType() == USER_LIFECYCLE_EVENT_TYPE_SWITCHING 288 && mContext.getUserId() == event.getPreviousUserId()) { 289 release(); 290 } 291 }; 292 293 private final BroadcastReceiver mPackageBroadcastReceiver = new BroadcastReceiver() { 294 @Override 295 public void onReceive(Context context, Intent intent) { 296 if (DBG) Log.d(TAG, "onReceive: intent=" + intent); 297 298 if (!isHostVisible()) { 299 return; 300 } 301 302 String packageName = intent.getData().getSchemeSpecificPart(); 303 for (int i = mControlledTaskViews.size() - 1; i >= 0; --i) { 304 ControlledCarTaskView taskView = mControlledTaskViews.get(i); 305 if (taskView.getTaskId() == INVALID_TASK_ID 306 && taskView.getDependingPackageNames().contains(packageName)) { 307 taskView.startActivity(); 308 } 309 } 310 } 311 }; 312 TaskViewManager(Activity context, HandlerExecutor handlerExecutor)313 public TaskViewManager(Activity context, HandlerExecutor handlerExecutor) { 314 this(context, handlerExecutor, new ShellTaskOrganizer(handlerExecutor), 315 new TransactionPool(), new ShellCommandHandler(), new ShellInit(handlerExecutor)); 316 } 317 TaskViewManager(Activity context, HandlerExecutor handlerExecutor, ShellTaskOrganizer taskOrganizer, TransactionPool transactionPool, ShellCommandHandler shellCommandHandler, ShellInit shellinit)318 private TaskViewManager(Activity context, HandlerExecutor handlerExecutor, 319 ShellTaskOrganizer taskOrganizer, TransactionPool transactionPool, 320 ShellCommandHandler shellCommandHandler, ShellInit shellinit) { 321 this(context, handlerExecutor, taskOrganizer, 322 new SyncTransactionQueue(transactionPool, handlerExecutor), 323 shellinit, 324 new StartingWindowController(context, shellinit, 325 new ShellController(shellinit, shellCommandHandler, handlerExecutor), 326 taskOrganizer, 327 handlerExecutor, 328 new PhoneStartingWindowTypeAlgorithm(), 329 new IconProvider(context), 330 transactionPool)); 331 } 332 333 @VisibleForTesting TaskViewManager(Activity context, HandlerExecutor handlerExecutor, ShellTaskOrganizer shellTaskOrganizer, SyncTransactionQueue syncQueue, ShellInit shellInit, StartingWindowController startingWindowController)334 TaskViewManager(Activity context, HandlerExecutor handlerExecutor, 335 ShellTaskOrganizer shellTaskOrganizer, SyncTransactionQueue syncQueue, 336 ShellInit shellInit, StartingWindowController startingWindowController) { 337 if (DBG) Slog.d(TAG, "TaskViewManager(): " + context); 338 mContext = context; 339 mShellExecutor = handlerExecutor; 340 mTaskOrganizer = shellTaskOrganizer; 341 mHostTaskId = mContext.getTaskId(); 342 mSyncQueue = syncQueue; 343 mTaskViewInputInterceptor = new TaskViewInputInterceptor(context, this); 344 345 initCar(); 346 shellInit.init(); 347 initTaskOrganizer(mCarActivityManagerRef); 348 mContext.registerActivityLifecycleCallbacks(mActivityLifecycleCallbacks); 349 } 350 initCar()351 private void initCar() { 352 Car.createCar(/* context= */ mContext, /* handler= */ null, 353 Car.CAR_WAIT_TIMEOUT_WAIT_FOREVER, 354 (car, ready) -> { 355 if (!ready) { 356 Log.w(TAG, "CarService looks crashed"); 357 mCarActivityManagerRef.set(null); 358 return; 359 } 360 setCarUserManager((CarUserManager) car.getCarManager(Car.CAR_USER_SERVICE)); 361 UserLifecycleEventFilter filter = new UserLifecycleEventFilter.Builder() 362 .addEventType(USER_LIFECYCLE_EVENT_TYPE_UNLOCKED) 363 .addEventType(USER_LIFECYCLE_EVENT_TYPE_SWITCHING).build(); 364 mCarUserManager.addListener(mContext.getMainExecutor(), filter, 365 mUserLifecycleListener); 366 CarActivityManager carAM = (CarActivityManager) car.getCarManager( 367 Car.CAR_ACTIVITY_SERVICE); 368 mCarActivityManagerRef.set(carAM); 369 370 carAM.registerTaskMonitor(); 371 }); 372 373 TaskStackChangeListeners.getInstance().registerTaskStackListener(mTaskStackListener); 374 375 IntentFilter packageIntentFilter = new IntentFilter(Intent.ACTION_PACKAGE_REPLACED); 376 packageIntentFilter.addDataScheme(SCHEME_PACKAGE); 377 mContext.registerReceiver(mPackageBroadcastReceiver, packageIntentFilter); 378 } 379 380 // TODO(b/239958124A): Remove this method when unit tests for TaskViewManager have been added. 381 /** 382 * This method only exists for the container activity to set mock car user manager in tests. 383 */ setCarUserManager(CarUserManager carUserManager)384 void setCarUserManager(CarUserManager carUserManager) { 385 mCarUserManager = carUserManager; 386 } 387 initTaskOrganizer(AtomicReference<CarActivityManager> carActivityManagerRef)388 private void initTaskOrganizer(AtomicReference<CarActivityManager> carActivityManagerRef) { 389 FullscreenTaskListener fullscreenTaskListener = new CarFullscreenTaskMonitorListener( 390 carActivityManagerRef, mSyncQueue); 391 mTaskOrganizer.addListenerForType(fullscreenTaskListener, TASK_LISTENER_TYPE_FULLSCREEN); 392 List<TaskAppearedInfo> taskAppearedInfos = mTaskOrganizer.registerOrganizer(); 393 cleanUpExistingTaskViewTasks(taskAppearedInfos); 394 } 395 396 /** 397 * Creates a {@link ControlledCarTaskView}. 398 * 399 * @param callbackExecutor the executor which the {@link ControlledCarTaskViewCallbacks} will 400 * be executed on. 401 * @param controlledCarTaskViewConfig the configuration for the underlying 402 * {@link ControlledCarTaskView}. 403 * @param taskViewCallbacks the callbacks for the underlying TaskView. 404 */ createControlledCarTaskView( Executor callbackExecutor, ControlledCarTaskViewConfig controlledCarTaskViewConfig, ControlledCarTaskViewCallbacks taskViewCallbacks)405 public void createControlledCarTaskView( 406 Executor callbackExecutor, 407 ControlledCarTaskViewConfig controlledCarTaskViewConfig, 408 ControlledCarTaskViewCallbacks taskViewCallbacks) { 409 mShellExecutor.execute(() -> { 410 ControlledCarTaskView taskView = new ControlledCarTaskView(mContext, mTaskOrganizer, 411 mSyncQueue, callbackExecutor, controlledCarTaskViewConfig, taskViewCallbacks, 412 mContext.getSystemService(UserManager.class), this); 413 mControlledTaskViews.add(taskView); 414 415 if (controlledCarTaskViewConfig.mCaptureGestures 416 || controlledCarTaskViewConfig.mCaptureLongPress) { 417 mTaskViewInputInterceptor.init(); 418 } 419 }); 420 421 } 422 423 /** 424 * Creates a {@link LaunchRootCarTaskView}. 425 * 426 * @param callbackExecutor the executor which the {@link LaunchRootCarTaskViewCallbacks} will be 427 * executed on. 428 * @param taskViewCallbacks the callbacks for the underlying TaskView. 429 */ createLaunchRootTaskView(Executor callbackExecutor, LaunchRootCarTaskViewCallbacks taskViewCallbacks)430 public void createLaunchRootTaskView(Executor callbackExecutor, 431 LaunchRootCarTaskViewCallbacks taskViewCallbacks) { 432 mShellExecutor.execute(() -> { 433 if (mLaunchRootCarTaskView != null) { 434 throw new IllegalStateException("Cannot create more than one launch root task"); 435 } 436 mLaunchRootCarTaskView = new LaunchRootCarTaskView(mContext, mTaskOrganizer, 437 mSyncQueue, callbackExecutor, taskViewCallbacks, mRootTaskListener); 438 }); 439 } 440 441 /** 442 * Creates a {@link SemiControlledCarTaskView}. 443 * 444 * @param callbackExecutor the executor which the {@link SemiControlledCarTaskViewCallbacks} 445 * will be executed on. 446 * @param taskViewCallbacks the callbacks for the underlying TaskView. 447 */ createSemiControlledTaskView(Executor callbackExecutor, SemiControlledCarTaskViewCallbacks taskViewCallbacks)448 public void createSemiControlledTaskView(Executor callbackExecutor, 449 SemiControlledCarTaskViewCallbacks taskViewCallbacks) { 450 mShellExecutor.execute(() -> { 451 if (mLaunchRootCarTaskView == null) { 452 throw new IllegalStateException("Cannot create a semi controlled taskview without a" 453 + " launch root taskview"); 454 } 455 SemiControlledCarTaskView taskView = new SemiControlledCarTaskView(mContext, 456 mTaskOrganizer, mSyncQueue, callbackExecutor, taskViewCallbacks); 457 mSemiControlledTaskViews.add(taskView); 458 }); 459 } 460 461 /** 462 * Releases {@link TaskViewManager} and unregisters the underlying {@link ShellTaskOrganizer}. 463 * It also removes all TaskViews which are created by this {@link TaskViewManager}. 464 */ release()465 private void release() { 466 mShellExecutor.execute(() -> { 467 if (DBG) Slog.d(TAG, "TaskViewManager.release"); 468 469 if (mCarUserManager != null) { 470 mCarUserManager.removeListener(mUserLifecycleListener); 471 } 472 TaskStackChangeListeners.getInstance().unregisterTaskStackListener(mTaskStackListener); 473 mContext.unregisterReceiver(mPackageBroadcastReceiver); 474 475 CarActivityManager carAM = mCarActivityManagerRef.get(); 476 if (carAM != null) { 477 carAM.unregisterTaskMonitor(); 478 mCarActivityManagerRef.set(null); 479 } 480 481 for (int i = mControlledTaskViews.size() - 1; i >= 0; --i) { 482 mControlledTaskViews.get(i).release(); 483 } 484 mControlledTaskViews.clear(); 485 486 for (int i = mSemiControlledTaskViews.size() - 1; i >= 0; --i) { 487 mSemiControlledTaskViews.get(i).release(); 488 } 489 mSemiControlledTaskViews.clear(); 490 491 if (mLaunchRootCarTaskView != null) { 492 mLaunchRootCarTaskView.release(); 493 mLaunchRootCarTaskView = null; 494 } 495 496 mContext.unregisterActivityLifecycleCallbacks(mActivityLifecycleCallbacks); 497 mTaskOrganizer.unregisterOrganizer(); 498 mTaskViewInputInterceptor.release(); 499 }); 500 } 501 isHostVisible()502 boolean isHostVisible() { 503 // This code relies on Activity#isVisibleForAutofill() instead of maintaining a custom 504 // activity state. 505 return mContext.isVisibleForAutofill(); 506 } 507 508 private final ActivityLifecycleCallbacks mActivityLifecycleCallbacks = 509 new ActivityLifecycleCallbacks() { 510 @Override 511 public void onActivityCreated(@NonNull Activity activity, 512 @Nullable Bundle savedInstanceState) {} 513 514 @Override 515 public void onActivityStarted(@NonNull Activity activity) {} 516 517 @Override 518 public void onActivityResumed(@NonNull Activity activity) {} 519 520 @Override 521 public void onActivityPaused(@NonNull Activity activity) {} 522 523 @Override 524 public void onActivityStopped(@NonNull Activity activity) {} 525 526 @Override 527 public void onActivitySaveInstanceState(@NonNull Activity activity, 528 @NonNull Bundle outState) {} 529 530 @Override 531 public void onActivityDestroyed(@NonNull Activity activity) { 532 release(); 533 } 534 }; 535 cleanUpExistingTaskViewTasks(List<TaskAppearedInfo> taskAppearedInfos)536 private static void cleanUpExistingTaskViewTasks(List<TaskAppearedInfo> taskAppearedInfos) { 537 ActivityTaskManager atm = ActivityTaskManager.getInstance(); 538 for (TaskAppearedInfo taskAppearedInfo : taskAppearedInfos) { 539 TaskInfo taskInfo = taskAppearedInfo.getTaskInfo(); 540 // Only TaskView tasks have WINDOWING_MODE_MULTI_WINDOW. 541 if (taskInfo.getWindowingMode() == WINDOWING_MODE_MULTI_WINDOW) { 542 if (DBG) Slog.d(TAG, "Found the dangling task, removing: " + taskInfo.taskId); 543 atm.removeTask(taskInfo.taskId); 544 } 545 } 546 } 547 548 @VisibleForTesting getControlledTaskViews()549 List<ControlledCarTaskView> getControlledTaskViews() { 550 return mControlledTaskViews; 551 } 552 553 @VisibleForTesting getLaunchRootCarTaskView()554 LaunchRootCarTaskView getLaunchRootCarTaskView() { 555 return mLaunchRootCarTaskView; 556 } 557 558 @VisibleForTesting getSemiControlledTaskViews()559 List<SemiControlledCarTaskView> getSemiControlledTaskViews() { 560 return mSemiControlledTaskViews; 561 } 562 563 @VisibleForTesting getPackageBroadcastReceiver()564 BroadcastReceiver getPackageBroadcastReceiver() { 565 return mPackageBroadcastReceiver; 566 } 567 568 @VisibleForTesting 569 /** Only meant for testing, should not be used by real code. */ setTaskViewInputInterceptor(TaskViewInputInterceptor taskViewInputInterceptor)570 void setTaskViewInputInterceptor(TaskViewInputInterceptor taskViewInputInterceptor) { 571 mTaskViewInputInterceptor = taskViewInputInterceptor; 572 } 573 574 //TODO(b/266154272): Move this logic inside LaunchRootCarTaskView getRootTaskCount()575 public int getRootTaskCount() { 576 return mLaunchRootStack.size(); 577 } 578 579 /** 580 * Returns the {@link android.app.ActivityManager.RunningTaskInfo} of the top task inside the 581 * launch root car task view. 582 */ 583 @VisibleForTesting getTopTaskInLaunchRootTask()584 public ActivityManager.RunningTaskInfo getTopTaskInLaunchRootTask() { 585 if (mLaunchRootStack.size() == 0) { 586 return null; 587 } 588 ActivityManager.RunningTaskInfo[] infos = mLaunchRootStack.values().toArray( 589 new ActivityManager.RunningTaskInfo[mLaunchRootStack.size()]); 590 return infos[infos.length - 1]; 591 } 592 } 593