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