• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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