• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2022 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.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW;
20 import static android.view.Display.DEFAULT_DISPLAY;
21 
22 import static com.android.car.carlauncher.TaskViewManager.DBG;
23 
24 import android.app.Activity;
25 import android.app.ActivityManager;
26 import android.car.app.CarActivityManager;
27 import android.content.ComponentName;
28 import android.util.Log;
29 import android.view.SurfaceControl;
30 import android.window.WindowContainerTransaction;
31 
32 import androidx.annotation.VisibleForTesting;
33 
34 import com.android.wm.shell.ShellTaskOrganizer;
35 import com.android.wm.shell.common.SyncTransactionQueue;
36 import com.android.wm.shell.taskview.TaskViewTransitions;
37 
38 import java.util.ArrayList;
39 import java.util.Iterator;
40 import java.util.LinkedHashMap;
41 import java.util.List;
42 import java.util.concurrent.Executor;
43 import java.util.concurrent.atomic.AtomicReference;
44 
45 /**
46  * A Semi-controlled {@link CarTaskView} is where the apps are meant to stay temporarily. It always
47  * works when a {@link LaunchRootCarTaskView} has been set up.
48  *
49  * It serves these use-cases:
50  * <ul>
51  *     <li>Should be used when the apps that are meant to be in it can be started from anywhere
52  *     in the system. i.e. when the host app has no control over their launching.</li>
53  *     <li>Suitable for apps like Assistant or Setup-Wizard.</li>
54  * </ul>
55  */
56 final class SemiControlledCarTaskView extends CarTaskView {
57     private static final String TAG = SemiControlledCarTaskView.class.getSimpleName();
58     private final Executor mCallbackExecutor;
59     private final SemiControlledCarTaskViewCallbacks mCallbacks;
60     private final ShellTaskOrganizer mShellTaskOrganizer;
61     private final SyncTransactionQueue mSyncQueue;
62     private final LinkedHashMap<Integer, ActivityManager.RunningTaskInfo> mChildrenTaskStack =
63             new LinkedHashMap<>();
64     private final ArrayList<ComponentName> mAllowListedActivities;
65     private final AtomicReference<CarActivityManager> mCarActivityManagerRef;
66 
67     private ActivityManager.RunningTaskInfo mRootTask;
68 
69     private final ShellTaskOrganizer.TaskListener mRootTaskListener =
70             new ShellTaskOrganizer.TaskListener() {
71                 @Override
72                 public void onTaskAppeared(ActivityManager.RunningTaskInfo taskInfo,
73                         SurfaceControl leash) {
74                     // The first call to onTaskAppeared() is always for the root-task.
75                     if (mRootTask == null && !taskInfo.hasParentTask()) {
76                         setRootTask(taskInfo);
77                         SemiControlledCarTaskView.this.dispatchTaskAppeared(taskInfo, leash);
78 
79                         CarActivityManager carAm = mCarActivityManagerRef.get();
80                         if (carAm != null) {
81                             carAm.setPersistentActivitiesOnRootTask(
82                                     mAllowListedActivities,
83                                     taskInfo.token.asBinder());
84                         } else {
85                             Log.wtf(TAG, "CarActivityManager is null, cannot call "
86                                     + "setPersistentActivitiesOnRootTask " + taskInfo);
87                         }
88                         mCallbackExecutor.execute(() -> mCallbacks.onTaskViewReady());
89                         return;
90                     }
91 
92                     if (DBG) {
93                         Log.d(TAG, "onTaskAppeared " + taskInfo.taskId + " - "
94                                 + taskInfo.baseActivity);
95                     }
96 
97                     mChildrenTaskStack.put(taskInfo.taskId, taskInfo);
98                 }
99 
100                 @Override
101                 public void onTaskInfoChanged(ActivityManager.RunningTaskInfo taskInfo) {
102                     if (mRootTask != null && mRootTask.taskId == taskInfo.taskId) {
103                         SemiControlledCarTaskView.this.dispatchTaskInfoChanged(taskInfo);
104                     }
105                     if (DBG) {
106                         Log.d(TAG, "onTaskInfoChanged " + taskInfo.taskId + " - "
107                                 + taskInfo.baseActivity);
108                     }
109                     if (taskInfo.isVisible && mChildrenTaskStack.containsKey(taskInfo.taskId)) {
110                         // Remove the task and insert again so that it jumps to the end of
111                         // the queue.
112                         mChildrenTaskStack.remove(taskInfo.taskId);
113                         mChildrenTaskStack.put(taskInfo.taskId, taskInfo);
114                     }
115                 }
116 
117                 @Override
118                 public void onTaskVanished(ActivityManager.RunningTaskInfo taskInfo) {
119                     if (mRootTask != null && mRootTask.taskId == taskInfo.taskId) {
120                         SemiControlledCarTaskView.this.dispatchTaskVanished(taskInfo);
121                     }
122                     if (DBG) {
123                         Log.d(TAG, "onTaskVanished " + taskInfo.taskId + " - "
124                                 + taskInfo.baseActivity);
125                     }
126                     if (mChildrenTaskStack.containsKey(taskInfo.taskId)) {
127                         mChildrenTaskStack.remove(taskInfo.taskId);
128                     }
129                 }
130 
131                 @Override
132                 public void onBackPressedOnTaskRoot(ActivityManager.RunningTaskInfo taskInfo) {
133                     if (mChildrenTaskStack.size() == 0) {
134                         Log.d(TAG, "Root task is empty, do nothing.");
135                         return;
136                     }
137 
138                     ActivityManager.RunningTaskInfo topTask = getTopTaskInTheRootTask();
139                     WindowContainerTransaction wct = new WindowContainerTransaction();
140                     // removeTask() will trigger onTaskVanished which will remove the task locally
141                     // from mChildrenTaskStack
142                     wct.removeTask(topTask.token);
143                     mSyncQueue.queue(wct);
144                 }
145             };
146 
SemiControlledCarTaskView(Activity context, ShellTaskOrganizer organizer, TaskViewTransitions taskViewTransitions, SyncTransactionQueue syncQueue, Executor callbackExecutor, List<ComponentName> allowListedActivities, SemiControlledCarTaskViewCallbacks callbacks, AtomicReference<CarActivityManager> carActivityManager)147     public SemiControlledCarTaskView(Activity context,
148             ShellTaskOrganizer organizer,
149             TaskViewTransitions taskViewTransitions,
150             SyncTransactionQueue syncQueue,
151             Executor callbackExecutor,
152             List<ComponentName> allowListedActivities,
153             SemiControlledCarTaskViewCallbacks callbacks,
154             AtomicReference<CarActivityManager> carActivityManager) {
155         super(context, organizer, taskViewTransitions, syncQueue, true);
156         mCallbacks = callbacks;
157         mCallbackExecutor = callbackExecutor;
158         mCallbackExecutor.execute(() -> mCallbacks.onTaskViewCreated(this));
159         mShellTaskOrganizer = organizer;
160         mSyncQueue = syncQueue;
161         mAllowListedActivities = new ArrayList<>(allowListedActivities);
162         mCarActivityManagerRef = carActivityManager;
163     }
164 
165     /**
166      * @return the underlying {@link SemiControlledCarTaskViewCallbacks}.
167      */
getCallbacks()168     SemiControlledCarTaskViewCallbacks getCallbacks() {
169         return mCallbacks;
170     }
171 
172     @Override
onCarTaskViewInitialized()173     protected void onCarTaskViewInitialized() {
174         super.onCarTaskViewInitialized();
175         mShellTaskOrganizer.getExecutor().execute(() -> {
176             // removeWithTaskOrganizer should be true to signal the system that this root task is
177             // inside a TaskView and should not be animated by the core.
178             mShellTaskOrganizer.createRootTask(DEFAULT_DISPLAY,
179                     WINDOWING_MODE_MULTI_WINDOW,
180                     mRootTaskListener, /* removeWithTaskOrganizer= */ true);
181         });
182     }
183 
184     @Override
release()185     public void release() {
186         super.release();
187         clearRootTask();
188     }
189 
getTopTaskInTheRootTask()190     public ActivityManager.RunningTaskInfo getTopTaskInTheRootTask() {
191         if (mChildrenTaskStack.isEmpty()) {
192             return null;
193         }
194         ActivityManager.RunningTaskInfo topTask = null;
195         Iterator<ActivityManager.RunningTaskInfo> iterator = mChildrenTaskStack.values().iterator();
196         while (iterator.hasNext()) {
197             topTask = iterator.next();
198         }
199         return topTask;
200     }
201 
clearRootTask()202     private void clearRootTask() {
203         if (mRootTask == null) {
204             Log.w(TAG, "Unable to clear root task because it is not created.");
205             return;
206         }
207         // Should run on shell's executor
208         mShellTaskOrganizer.deleteRootTask(mRootTask.token);
209         mRootTask = null;
210     }
211 
setRootTask(ActivityManager.RunningTaskInfo taskInfo)212     private void setRootTask(ActivityManager.RunningTaskInfo taskInfo) {
213         mRootTask = taskInfo;
214     }
215 
216     /**
217      * Designates the given {@code activities} to be launched in this SemiControlledCarTaskView.
218      * <p>Note: If an activity is already associated with another SemiControlledCarTaskView, it's
219      * designates will be overridden.
220      *
221      * @param activities list of {@link ComponentName} of activities to be designated on the
222      *                   SemiControlledCarTaskView
223      */
addAllowListedActivities(List<ComponentName> activities)224     public void addAllowListedActivities(List<ComponentName> activities) {
225         CarActivityManager carAm = mCarActivityManagerRef.get();
226         if (carAm == null) {
227             Log.wtf(TAG,
228                     "CarActivityManager is null, cannot call setPersistentActivitiesOnRootTask to"
229                             + " add activities");
230             return;
231         }
232         if (mRootTask == null) {
233             Log.wtf(TAG,
234                     "RootTask is null, cannot call setPersistentActivitiesOnRootTask to"
235                             + " add activities");
236             return;
237         }
238         List<ComponentName> activitiesToAdd = new ArrayList<>();
239         for (ComponentName activity : activities) {
240             if (!mAllowListedActivities.contains(activity)) {
241                 activitiesToAdd.add(activity);
242             } else {
243                 if (DBG) {
244                     Log.d(TAG, "Activity " + activity
245                             + " is already designated to this SemiControlledCarTaskView");
246                 }
247             }
248         }
249         mAllowListedActivities.addAll(activitiesToAdd);
250         carAm.setPersistentActivitiesOnRootTask(activitiesToAdd, mRootTask.token.asBinder());
251     }
252 
253     /**
254      * Remove the designation of the given {@code activities} to SemiControlledCarTaskView.
255      * <p>Note: If an activity is already associated with another SemiControlledCarTaskView, it's
256      * designates will be overridden.
257      *
258      * @param activities list of {@link ComponentName} of activities to be designated on the
259      *                   SemiControlledCarTaskView
260      */
removeAllowListedActivities(List<ComponentName> activities)261     public void removeAllowListedActivities(List<ComponentName> activities) {
262         CarActivityManager carAm = mCarActivityManagerRef.get();
263         if (carAm == null) {
264             Log.wtf(TAG,
265                     "CarActivityManager is null, cannot call setPersistentActivitiesOnRootTask to"
266                             + " remove activities");
267             return;
268         }
269         if (mRootTask == null) {
270             Log.wtf(TAG,
271                     "RootTask is null, cannot call setPersistentActivitiesOnRootTask to"
272                             + " add activities");
273             return;
274         }
275         List<ComponentName> activitiesToRemove = new ArrayList<>();
276         for (ComponentName activity : activities) {
277             if (mAllowListedActivities.contains(activity)) {
278                 activitiesToRemove.add(activity);
279             } else {
280                 if (DBG) {
281                     Log.d(TAG, "Activity " + activity
282                             + " was not designated to this SemiControlledCarTaskView");
283                 }
284             }
285         }
286         mAllowListedActivities.removeAll(activitiesToRemove);
287         carAm.setPersistentActivitiesOnRootTask(activitiesToRemove, /* rootTaskToken= */ null);
288     }
289 
290     /**
291      * Sets the designations for SemiControlledCarTaskView. Adds the designation of the given
292      * {@code activities} to SemiControlledCarTaskView and removes the designations of activities
293      * that are not in {@code activities}.
294      *
295      * <p>Note:
296      * If an activity is already associated with another SemiControlledCarTaskView, it's
297      * designates will be overridden.
298      *
299      * @param activities list of {@link ComponentName} of activities to be designated on the
300      *                   SemiControlledCarTaskView
301      */
setAllowListedActivities(List<ComponentName> activities)302     public void setAllowListedActivities(List<ComponentName> activities) {
303         CarActivityManager carAm = mCarActivityManagerRef.get();
304         if (carAm == null) {
305             Log.wtf(TAG,
306                     "CarActivityManager is null, cannot call setPersistentActivitiesOnRootTask to"
307                             + " remove activities");
308             return;
309         }
310         if (mRootTask == null) {
311             Log.wtf(TAG,
312                     "RootTask is null, cannot call setPersistentActivitiesOnRootTask to"
313                             + " add activities");
314             return;
315         }
316         List<ComponentName> activitiesToRemove = new ArrayList<>(mAllowListedActivities);
317         carAm.setPersistentActivitiesOnRootTask(activitiesToRemove, /* rootTaskToken= */ null);
318         carAm.setPersistentActivitiesOnRootTask(activities, mRootTask.token.asBinder());
319         mAllowListedActivities.clear();
320         mAllowListedActivities.addAll(activities);
321     }
322 
323     @VisibleForTesting
getPersistentActivities()324     List<ComponentName> getPersistentActivities() {
325         return mAllowListedActivities;
326     }
327 }
328