• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2021 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.am;
18 
19 import static android.Manifest.permission.INTERACT_ACROSS_USERS;
20 import static android.Manifest.permission.MANAGE_ACTIVITY_TASKS;
21 import static android.car.Car.PERMISSION_MANAGE_CAR_SYSTEM_UI;
22 import static android.car.Car.PERMISSION_REGISTER_CAR_SYSTEM_UI_PROXY;
23 import static android.car.content.pm.CarPackageManager.BLOCKING_INTENT_EXTRA_DISPLAY_ID;
24 
25 import static com.android.car.internal.ExcludeFromCodeCoverageGeneratedReport.DUMP_INFO;
26 import static com.android.car.internal.util.VersionUtils.isPlatformVersionAtLeastU;
27 
28 import android.annotation.NonNull;
29 import android.annotation.Nullable;
30 import android.annotation.RequiresApi;
31 import android.app.ActivityManager;
32 import android.app.ActivityOptions;
33 import android.app.TaskInfo;
34 import android.car.Car;
35 import android.car.app.CarActivityManager;
36 import android.car.app.ICarActivityService;
37 import android.car.app.ICarSystemUIProxy;
38 import android.car.app.ICarSystemUIProxyCallback;
39 import android.car.builtin.app.ActivityManagerHelper;
40 import android.car.builtin.app.TaskInfoHelper;
41 import android.car.builtin.os.UserManagerHelper;
42 import android.car.builtin.util.Slogf;
43 import android.car.builtin.view.SurfaceControlHelper;
44 import android.content.ComponentName;
45 import android.content.Context;
46 import android.content.Intent;
47 import android.content.pm.PackageManager;
48 import android.graphics.Point;
49 import android.graphics.Rect;
50 import android.hardware.display.DisplayManager;
51 import android.os.Binder;
52 import android.os.Build;
53 import android.os.Handler;
54 import android.os.HandlerThread;
55 import android.os.IBinder;
56 import android.os.RemoteCallbackList;
57 import android.os.RemoteException;
58 import android.os.UserHandle;
59 import android.util.ArrayMap;
60 import android.util.ArraySet;
61 import android.util.Log;
62 import android.util.SparseArray;
63 import android.view.Display;
64 import android.view.SurfaceControl;
65 
66 import com.android.car.CarLog;
67 import com.android.car.CarServiceBase;
68 import com.android.car.CarServiceHelperWrapper;
69 import com.android.car.CarServiceUtils;
70 import com.android.car.R;
71 import com.android.car.SystemActivityMonitoringService;
72 import com.android.car.internal.ExcludeFromCodeCoverageGeneratedReport;
73 import com.android.car.internal.util.IndentingPrintWriter;
74 import com.android.internal.annotations.GuardedBy;
75 import com.android.internal.annotations.VisibleForTesting;
76 import com.android.internal.util.Preconditions;
77 
78 import java.util.ArrayList;
79 import java.util.Collections;
80 import java.util.LinkedHashMap;
81 import java.util.List;
82 import java.util.Objects;
83 
84 /**
85  * Service responsible for Activities in Car.
86  */
87 public final class CarActivityService extends ICarActivityService.Stub
88         implements CarServiceBase {
89 
90     private static final String TAG = CarLog.TAG_AM;
91     private static final boolean DBG = Slogf.isLoggable(TAG, Log.DEBUG);
92 
93     private static final int MAX_RUNNING_TASKS_TO_GET = 100;
94     private static final long MIRRORING_TOKEN_TIMEOUT_MS = 10 * 60 * 1000;  // 10 mins
95 
96     private final Context mContext;
97     private final DisplayManager mDisplayManager;
98     private final long mMirroringTokenTimeoutMs;
99 
100     private final Object mLock = new Object();
101 
102     // LinkedHashMap is used instead of SparseXXX because a predictable iteration order is needed.
103     // The tasks here need be ordered as per their stack order. The stack order is maintained
104     // using a combination of onTaskAppeared and onTaskInfoChanged callbacks.
105     @GuardedBy("mLock")
106     private final LinkedHashMap<Integer, ActivityManager.RunningTaskInfo> mTasks =
107             new LinkedHashMap<>();
108     @GuardedBy("mLock")
109     private final SparseArray<SurfaceControl> mTaskToSurfaceMap = new SparseArray<>();
110 
111     @GuardedBy("mLock")
112     private final ArrayMap<IBinder, IBinder.DeathRecipient> mMonitorTokens = new ArrayMap<>();
113 
114     @GuardedBy("mLock")
115     private final ArraySet<MirroringToken> mMirroringTokens = new ArraySet<>();
116 
117     @GuardedBy("mLock")
118     private ICarSystemUIProxy mCarSystemUIProxy;
119     @GuardedBy("mLock")
120     private final RemoteCallbackList<ICarSystemUIProxyCallback> mCarSystemUIProxyCallbacks =
121             new RemoteCallbackList<ICarSystemUIProxyCallback>();
122 
123     private IBinder mCurrentMonitor;
124 
125     public interface ActivityLaunchListener {
126         /**
127          * Notify launch of activity.
128          *
129          * @param topTask Task information for what is currently launched.
130          */
onActivityLaunch(TaskInfo topTask)131         void onActivityLaunch(TaskInfo topTask);
132     }
133     @GuardedBy("mLock")
134     private ActivityLaunchListener mActivityLaunchListener;
135 
136     private final HandlerThread mMonitorHandlerThread = CarServiceUtils.getHandlerThread(
137             SystemActivityMonitoringService.class.getSimpleName());
138     private final Handler mHandler = new Handler(mMonitorHandlerThread.getLooper());
139 
CarActivityService(Context context)140     public CarActivityService(Context context) {
141         this(context, MIRRORING_TOKEN_TIMEOUT_MS);
142     }
143 
144     @VisibleForTesting
CarActivityService(Context context, long mirroringTokenTimeout)145     CarActivityService(Context context, long mirroringTokenTimeout) {
146         mContext = context;
147         mDisplayManager = context.getSystemService(DisplayManager.class);
148         mMirroringTokenTimeoutMs = mirroringTokenTimeout;
149     }
150 
151     @Override
init()152     public void init() {}
153 
154     @Override
release()155     public void release() {}
156 
157     @Override
setPersistentActivity(ComponentName activity, int displayId, int featureId)158     public int setPersistentActivity(ComponentName activity, int displayId, int featureId) throws
159             RemoteException {
160         ensurePermission(Car.PERMISSION_CONTROL_CAR_APP_LAUNCH);
161         int caller = getCaller();
162         if (caller != UserManagerHelper.USER_SYSTEM && caller != ActivityManager.getCurrentUser()) {
163             return CarActivityManager.RESULT_INVALID_USER;
164         }
165 
166         return CarServiceHelperWrapper.getInstance().setPersistentActivity(activity, displayId,
167                 featureId);
168     }
169 
170     @Override
setPersistentActivitiesOnRootTask(@onNull List<ComponentName> activities, IBinder rootTaskToken)171     public void setPersistentActivitiesOnRootTask(@NonNull List<ComponentName> activities,
172             IBinder rootTaskToken) {
173         ensurePermission(Car.PERMISSION_CONTROL_CAR_APP_LAUNCH);
174         CarServiceHelperWrapper.getInstance().setPersistentActivitiesOnRootTask(activities,
175                 rootTaskToken);
176     }
177 
178     @VisibleForTesting
getCaller()179     int getCaller() {  // Non static for mocking.
180         return UserManagerHelper.getUserId(Binder.getCallingUid());
181     }
182 
registerActivityLaunchListener(ActivityLaunchListener listener)183     public void registerActivityLaunchListener(ActivityLaunchListener listener) {
184         synchronized (mLock) {
185             mActivityLaunchListener = listener;
186         }
187     }
188 
189     @Override
registerTaskMonitor(IBinder token)190     public void registerTaskMonitor(IBinder token) {
191         if (DBG) Slogf.d(TAG, "registerTaskMonitor: %s", token);
192         ensureManageActivityTasksPermission();
193 
194         IBinder.DeathRecipient deathRecipient = new IBinder.DeathRecipient() {
195             @Override
196             public void binderDied() {
197                 cleanUpMonitorToken(token);
198             }
199         };
200         synchronized (mLock) {
201             try {
202                 token.linkToDeath(deathRecipient, /* flags= */ 0);
203             } catch (RemoteException e) {
204                 // 'token' owner might be dead already.
205                 Slogf.e(TAG, "failed to linkToDeath: %s", token);
206                 return;
207             }
208             mMonitorTokens.put(token, deathRecipient);
209             mCurrentMonitor = token;
210             // When new TaskOrganizer takes the control, it'll get the status of the whole tasks
211             // in the system again. So drops the old status.
212             mTasks.clear();
213         }
214     }
215 
ensurePermission(String permission)216     private void ensurePermission(String permission) {
217         if (mContext.checkCallingOrSelfPermission(permission)
218                 != PackageManager.PERMISSION_GRANTED) {
219             throw new SecurityException("requires permission " + permission);
220         }
221     }
222 
ensureManageActivityTasksPermission()223     private void ensureManageActivityTasksPermission() {
224         ensurePermission(MANAGE_ACTIVITY_TASKS);
225     }
226 
cleanUpMonitorToken(IBinder token)227     private void cleanUpMonitorToken(IBinder token) {
228         synchronized (mLock) {
229             if (mCurrentMonitor == token) {
230                 mCurrentMonitor = null;
231             }
232             IBinder.DeathRecipient deathRecipient = mMonitorTokens.remove(token);
233             if (deathRecipient != null) {
234                 token.unlinkToDeath(deathRecipient, /* flags= */ 0);
235             }
236         }
237     }
238 
239     @Override
onTaskAppeared(IBinder token, ActivityManager.RunningTaskInfo taskInfo, SurfaceControl leash)240     public void onTaskAppeared(IBinder token,
241             ActivityManager.RunningTaskInfo taskInfo, SurfaceControl leash) {
242         if (DBG) {
243             Slogf.d(TAG, "onTaskAppeared: %s, %s", token, TaskInfoHelper.toString(taskInfo));
244         }
245         ensureManageActivityTasksPermission();
246         synchronized (mLock) {
247             if (!isAllowedToUpdateLocked(token)) {
248                 return;
249             }
250             mTasks.put(taskInfo.taskId, taskInfo);
251             if (leash != null) {
252                 mTaskToSurfaceMap.put(taskInfo.taskId, leash);
253             }
254         }
255         if (TaskInfoHelper.isVisible(taskInfo)) {
256             mHandler.post(() -> notifyActivityLaunch(taskInfo));
257         }
258     }
259 
notifyActivityLaunch(TaskInfo taskInfo)260     private void notifyActivityLaunch(TaskInfo taskInfo) {
261         ActivityLaunchListener listener;
262         synchronized (mLock) {
263             listener = mActivityLaunchListener;
264         }
265         if (listener != null) {
266             listener.onActivityLaunch(taskInfo);
267         }
268     }
269 
270     @GuardedBy("mLock")
isAllowedToUpdateLocked(IBinder token)271     private boolean isAllowedToUpdateLocked(IBinder token) {
272         if (mCurrentMonitor != null && mCurrentMonitor == token) {
273             return true;
274         }
275         // Fallback during no current Monitor exists.
276         boolean allowed = (mCurrentMonitor == null && mMonitorTokens.containsKey(token));
277         if (!allowed) {
278             Slogf.w(TAG, "Report with the invalid token: %s", token);
279         }
280         return allowed;
281     }
282 
283     @Override
onTaskVanished(IBinder token, ActivityManager.RunningTaskInfo taskInfo)284     public void onTaskVanished(IBinder token, ActivityManager.RunningTaskInfo taskInfo) {
285         if (DBG) {
286             Slogf.d(TAG, "onTaskVanished: %s, %s", token, TaskInfoHelper.toString(taskInfo));
287         }
288         ensureManageActivityTasksPermission();
289         synchronized (mLock) {
290             if (!isAllowedToUpdateLocked(token)) {
291                 return;
292             }
293             mTasks.remove(taskInfo.taskId);
294             mTaskToSurfaceMap.remove(taskInfo.taskId);
295         }
296     }
297 
298     @Override
onTaskInfoChanged(IBinder token, ActivityManager.RunningTaskInfo taskInfo)299     public void onTaskInfoChanged(IBinder token, ActivityManager.RunningTaskInfo taskInfo) {
300         if (DBG) {
301             Slogf.d(TAG, "onTaskInfoChanged: %s, %s", token, TaskInfoHelper.toString(taskInfo));
302         }
303         ensureManageActivityTasksPermission();
304         synchronized (mLock) {
305             if (!isAllowedToUpdateLocked(token)) {
306                 return;
307             }
308             // The key should be removed and added again so that it jumps to the front of the
309             // LinkedHashMap.
310             TaskInfo oldTaskInfo = mTasks.remove(taskInfo.taskId);
311             mTasks.put(taskInfo.taskId, taskInfo);
312             if ((oldTaskInfo == null || !TaskInfoHelper.isVisible(oldTaskInfo)
313                     || !Objects.equals(oldTaskInfo.topActivity, taskInfo.topActivity))
314                     && TaskInfoHelper.isVisible(taskInfo)) {
315                 mHandler.post(() -> notifyActivityLaunch(taskInfo));
316             }
317         }
318     }
319 
320     @Override
unregisterTaskMonitor(IBinder token)321     public void unregisterTaskMonitor(IBinder token) {
322         if (DBG) Slogf.d(TAG, "unregisterTaskMonitor: %s", token);
323         ensureManageActivityTasksPermission();
324         cleanUpMonitorToken(token);
325     }
326 
327     /**
328      * Returns all the visible tasks in the given display. The order is not guaranteed.
329      */
330     @Override
getVisibleTasks(int displayId)331     public List<ActivityManager.RunningTaskInfo> getVisibleTasks(int displayId) {
332         ensureManageActivityTasksPermission();
333         return getVisibleTasksInternal(displayId);
334     }
335 
getVisibleTasksInternal()336     public List<ActivityManager.RunningTaskInfo> getVisibleTasksInternal() {
337         return getVisibleTasksInternal(Display.INVALID_DISPLAY);
338     }
339 
340     /** Car service internal version without the permission enforcement. */
getVisibleTasksInternal(int displayId)341     public List<ActivityManager.RunningTaskInfo> getVisibleTasksInternal(int displayId) {
342         ArrayList<ActivityManager.RunningTaskInfo> tasksToReturn = new ArrayList<>();
343         synchronized (mLock) {
344             for (ActivityManager.RunningTaskInfo taskInfo : mTasks.values()) {
345                 // Activities launched in the private display or non-focusable display can't be
346                 // focusable. So we just monitor all visible Activities/Tasks.
347                 if (TaskInfoHelper.isVisible(taskInfo)
348                         && (displayId == Display.INVALID_DISPLAY
349                                 || displayId == TaskInfoHelper.getDisplayId(taskInfo))) {
350                     tasksToReturn.add(taskInfo);
351                 }
352             }
353         }
354         // Reverse the order so that the resultant order is top to bottom.
355         Collections.reverse(tasksToReturn);
356         return tasksToReturn;
357     }
358 
359     @Override
startUserPickerOnDisplay(int displayId)360     public void startUserPickerOnDisplay(int displayId) {
361         CarServiceUtils.assertAnyPermission(mContext, INTERACT_ACROSS_USERS);
362         Preconditions.checkArgument(displayId != Display.INVALID_DISPLAY, "Invalid display");
363         if (!isPlatformVersionAtLeastU()) {
364             return;
365         }
366         String userPickerName = mContext.getResources().getString(
367                 R.string.config_userPickerActivity);
368         if (userPickerName.isEmpty()) {
369             Slogf.w(TAG, "Cannot launch user picker to display %d, component not specified",
370                     displayId);
371             return;
372         }
373         CarServiceUtils.startUserPickerOnDisplay(mContext, displayId, userPickerName);
374     }
375 
376     private abstract class MirroringToken extends Binder {
MirroringToken()377         private MirroringToken() {
378             CarActivityService.this.registerMirroringToken(this);
379         }
380 
getMirroredSurface(Rect outBounds)381         protected abstract SurfaceControl getMirroredSurface(Rect outBounds);
382     };
383 
registerMirroringToken(MirroringToken token)384     private void registerMirroringToken(MirroringToken token) {
385         synchronized (mLock) {
386             mMirroringTokens.add(token);
387         }
388         mHandler.postDelayed(() -> cleanUpMirroringToken(token), mMirroringTokenTimeoutMs);
389     }
390 
cleanUpMirroringToken(MirroringToken token)391     private void cleanUpMirroringToken(MirroringToken token) {
392         synchronized (mLock) {
393             mMirroringTokens.remove(token);
394         }
395     }
396 
397     @GuardedBy("mLock")
assertMirroringTokenIsValidLocked(MirroringToken token)398     private void assertMirroringTokenIsValidLocked(MirroringToken token) {
399         if (!mMirroringTokens.contains(token)) {
400             throw new IllegalArgumentException("Invalid token: " + token);
401         }
402     }
403 
404     @RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
405     private final class TaskMirroringToken extends MirroringToken {
406         private final int mTaskId;
TaskMirroringToken(int taskId)407         private TaskMirroringToken(int taskId) {
408             super();
409             mTaskId = taskId;
410         }
411 
412         @Override
413         @GuardedBy("CarActivityService.this.mLock")
getMirroredSurface(Rect outBounds)414         protected SurfaceControl getMirroredSurface(Rect outBounds) {
415             TaskInfo taskInfo = mTasks.get(mTaskId);
416             SurfaceControl taskSurface = mTaskToSurfaceMap.get(mTaskId);
417             if (taskInfo == null || taskSurface == null || !taskInfo.isVisible()) {
418                 Slogf.e(TAG, "TaskMirroringToken#getMirroredSurface: no task=%s", taskInfo);
419                 return null;
420             }
421             outBounds.set(TaskInfoHelper.getBounds(taskInfo));
422             return SurfaceControlHelper.mirrorSurface(taskSurface);
423         }
424 
425         @Override
toString()426         public String toString() {
427             return TaskMirroringToken.class.getSimpleName() + "[taskid=" + mTaskId + "]";
428         }
429     };
430 
431     @RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
432     private final class DisplayMirroringToken extends MirroringToken {
433         private final int mDisplayId;
DisplayMirroringToken(int displayId)434         private DisplayMirroringToken(int displayId) {
435             super();
436             mDisplayId = displayId;
437         }
438 
439         @Override
getMirroredSurface(Rect outBounds)440         protected SurfaceControl getMirroredSurface(Rect outBounds) {
441             Display display = mDisplayManager.getDisplay(mDisplayId);
442             Point point = new Point();
443             display.getRealSize(point);
444             outBounds.set(0, 0, point.x, point.y);
445             return SurfaceControlHelper.mirrorDisplay(mDisplayId);
446         }
447 
448         @Override
toString()449         public String toString() {
450             return DisplayMirroringToken.class.getSimpleName() + "[displayId=" + mDisplayId + "]";
451         }
452     };
453 
454     @Override
createTaskMirroringToken(int taskId)455     public IBinder createTaskMirroringToken(int taskId) {
456         ensureManageActivityTasksPermission();
457         if (!isPlatformVersionAtLeastU()) {
458             return null;
459         }
460         synchronized (mLock) {
461             if (!mTaskToSurfaceMap.contains(taskId)) {
462                 throw new IllegalArgumentException("Non-existent Task#" + taskId);
463             }
464         }
465         return new TaskMirroringToken(taskId);
466     }
467 
468     @Override
createDisplayMirroringToken(int displayId)469     public IBinder createDisplayMirroringToken(int displayId) {
470         ensurePermission(Car.PERMISSION_MIRROR_DISPLAY);
471         if (!isPlatformVersionAtLeastU()) {
472             return null;
473         }
474         return new DisplayMirroringToken(displayId);
475     }
476 
477     @Override
478     @Nullable
getMirroredSurface(IBinder token, Rect outBounds)479     public SurfaceControl getMirroredSurface(IBinder token, Rect outBounds) {
480         if (!isPlatformVersionAtLeastU()) {
481             return null;
482         }
483         ensurePermission(Car.PERMISSION_ACCESS_MIRRORRED_SURFACE);
484         MirroringToken mirroringToken;
485         try {
486             mirroringToken = (MirroringToken) token;
487         } catch (ClassCastException e) {
488             throw new IllegalArgumentException("Bad token");
489         }
490         synchronized (mLock) {
491             assertMirroringTokenIsValidLocked(mirroringToken);
492             return mirroringToken.getMirroredSurface(outBounds);
493         }
494     }
495 
496     @Override
registerCarSystemUIProxy(ICarSystemUIProxy carSystemUIProxy)497     public void registerCarSystemUIProxy(ICarSystemUIProxy carSystemUIProxy) {
498         if (DBG) {
499             Slogf.d(TAG, "registerCarSystemUIProxy %s", carSystemUIProxy.toString());
500         }
501         ensurePermission(PERMISSION_REGISTER_CAR_SYSTEM_UI_PROXY);
502         if (!isPlatformVersionAtLeastU()) {
503             return;
504         }
505         synchronized (mLock) {
506             if (mCarSystemUIProxy != null) {
507                 throw new UnsupportedOperationException("Car system UI proxy is already "
508                         + "registered");
509             }
510 
511             mCarSystemUIProxy = carSystemUIProxy;
512             try {
513                 mCarSystemUIProxy.asBinder().linkToDeath(new IBinder.DeathRecipient(){
514                     @Override
515                     public void binderDied() {
516                         synchronized (mLock) {
517                             Slogf.d(TAG, "CarSystemUIProxy died %s",
518                                         mCarSystemUIProxy.toString());
519                             mCarSystemUIProxy.asBinder().unlinkToDeath(this, /* flags= */ 0);
520                             mCarSystemUIProxy = null;
521                         }
522                     }
523                 }, /* flags= */0);
524             } catch (RemoteException remoteException) {
525                 mCarSystemUIProxy = null;
526                 throw new IllegalStateException("Linking to binder death failed for "
527                         + "ICarSystemUIProxy, the System UI might already died", remoteException);
528             }
529 
530             if (DBG) {
531                 Slogf.d(TAG, "CarSystemUIProxy registered.");
532             }
533 
534             int numCallbacks = mCarSystemUIProxyCallbacks.beginBroadcast();
535             if (DBG) {
536                 Slogf.d(TAG, "Broadcasting CarSystemUIProxy connected to %d callbacks",
537                         numCallbacks);
538             }
539             for (int i = 0; i < numCallbacks; i++) {
540                 try {
541                     mCarSystemUIProxyCallbacks.getBroadcastItem(i).onConnected(
542                             mCarSystemUIProxy);
543                 } catch (RemoteException remoteException) {
544                     Slogf.e(TAG, "Error dispatching onConnected", remoteException);
545                 }
546             }
547             mCarSystemUIProxyCallbacks.finishBroadcast();
548         }
549     }
550 
551     @Override
isCarSystemUIProxyRegistered()552     public boolean isCarSystemUIProxyRegistered() {
553         if (!isPlatformVersionAtLeastU()) {
554             return false;
555         }
556         synchronized (mLock) {
557             return mCarSystemUIProxy != null;
558         }
559     }
560 
561     @Override
addCarSystemUIProxyCallback(ICarSystemUIProxyCallback callback)562     public void addCarSystemUIProxyCallback(ICarSystemUIProxyCallback callback) {
563         if (DBG) {
564             Slogf.d(TAG, "addCarSystemUIProxyCallback %s", callback.toString());
565         }
566         ensurePermission(PERMISSION_MANAGE_CAR_SYSTEM_UI);
567         synchronized (mLock) {
568             boolean alreadyExists = mCarSystemUIProxyCallbacks.unregister(callback);
569             mCarSystemUIProxyCallbacks.register(callback);
570 
571             if (alreadyExists) {
572                 // Do not trigger onConnected() if the callback already exists because it is either
573                 // already called or will be called when the mCarSystemUIProxy is registered.
574                 Slogf.d(TAG, "Callback exists already, skip calling onConnected()");
575                 return;
576             }
577 
578             // Trigger onConnected() on the callback.
579             if (mCarSystemUIProxy == null) {
580                 if (DBG) {
581                     Slogf.d(TAG, "Callback stored locally, car system ui proxy not "
582                             + "registered.");
583                 }
584                 return;
585             }
586             try {
587                 callback.onConnected(mCarSystemUIProxy);
588             } catch (RemoteException remoteException) {
589                 Slogf.e(TAG, "Error when dispatching onConnected", remoteException);
590             }
591         }
592     }
593 
594     @Override
removeCarSystemUIProxyCallback(ICarSystemUIProxyCallback callback)595     public void removeCarSystemUIProxyCallback(ICarSystemUIProxyCallback callback) {
596         if (DBG) {
597             Slogf.d(TAG, "removeCarSystemUIProxyCallback %s", callback.toString());
598         }
599         ensurePermission(PERMISSION_MANAGE_CAR_SYSTEM_UI);
600         synchronized (mLock) {
601             mCarSystemUIProxyCallbacks.unregister(callback);
602         }
603     }
604 
605     /**
606      * Attempts to restart a task.
607      *
608      * <p>Restarts a task by removing the task and sending an empty intent with flag
609      * {@link Intent#FLAG_ACTIVITY_NEW_TASK} to its root activity. If the task does not exist, do
610      * nothing.
611      *
612      * @param taskId id of task to be restarted.
613      */
restartTask(int taskId)614     public void restartTask(int taskId) {
615         TaskInfo task;
616         synchronized (mLock) {
617             task = mTasks.get(taskId);
618         }
619         if (task == null) {
620             Slogf.e(CarLog.TAG_AM, "Could not find root activity with task id " + taskId);
621             return;
622         }
623 
624         Intent intent = (Intent) task.baseIntent.clone();
625         // Remove the task the root activity is running in and start it in a new task.
626         // This effectively leads to restarting of the root activity and removal all the other
627         // activities in the task.
628         // FLAG_ACTIVITY_CLEAR_TASK was being used earlier, but it has the side effect where the
629         // last activity in the existing task is visible for a moment until the root activity is
630         // started again.
631         ActivityManagerHelper.removeTask(taskId);
632         intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
633 
634         int userId = TaskInfoHelper.getUserId(task);
635         if (Slogf.isLoggable(CarLog.TAG_AM, Log.INFO)) {
636             Slogf.i(CarLog.TAG_AM, "restarting root activity with user id " + userId);
637         }
638         mContext.startActivityAsUser(intent, UserHandle.of(userId));
639     }
640 
getTaskInfoForTopActivity(ComponentName activity)641     public TaskInfo getTaskInfoForTopActivity(ComponentName activity) {
642         synchronized (mLock) {
643             for (ActivityManager.RunningTaskInfo info : mTasks.values()) {
644                 if (activity.equals(info.topActivity)) {
645                     return info;
646                 }
647             }
648         }
649         return null;
650     }
651 
652     /**
653      * Block the current task: Launch new activity with given Intent and finish the current task.
654      *
655      * @param currentTask task to finish
656      * @param newActivityIntent Intent for new Activity
657      */
blockActivity(TaskInfo currentTask, Intent newActivityIntent)658     public void blockActivity(TaskInfo currentTask, Intent newActivityIntent) {
659         mHandler.post(() -> handleBlockActivity(currentTask, newActivityIntent));
660     }
661 
662     /**
663      * block the current task with the provided new activity.
664      */
handleBlockActivity(TaskInfo currentTask, Intent newActivityIntent)665     private void handleBlockActivity(TaskInfo currentTask, Intent newActivityIntent) {
666         int displayId = newActivityIntent.getIntExtra(BLOCKING_INTENT_EXTRA_DISPLAY_ID,
667                 Display.DEFAULT_DISPLAY);
668         if (Slogf.isLoggable(CarLog.TAG_AM, Log.DEBUG)) {
669             Slogf.d(CarLog.TAG_AM, "Launching blocking activity on display: " + displayId);
670         }
671 
672         ActivityOptions options = ActivityOptions.makeBasic();
673         options.setLaunchDisplayId(displayId);
674         // Starts ABA as User 0 consistenly since the target apps can be any users (User 0 -
675         // UserPicker, Driver/Passegners - general NDO apps) and launching ABA as passengers
676         // have some issue (b/294447050).
677         mContext.startActivity(newActivityIntent, options.toBundle());
678         // Now make stack with new activity focused.
679         findTaskAndGrantFocus(newActivityIntent.getComponent());
680     }
681 
findTaskAndGrantFocus(ComponentName activity)682     private void findTaskAndGrantFocus(ComponentName activity) {
683         TaskInfo taskInfo = getTaskInfoForTopActivity(activity);
684         if (taskInfo != null) {
685             ActivityManagerHelper.setFocusedRootTask(taskInfo.taskId);
686             return;
687         }
688         Slogf.i(CarLog.TAG_AM, "cannot give focus, cannot find Activity:" + activity);
689     }
690 
691     @Override
moveRootTaskToDisplay(int taskId, int displayId)692     public void moveRootTaskToDisplay(int taskId, int displayId) {
693         ensurePermission(Car.PERMISSION_CONTROL_CAR_APP_LAUNCH);
694         if (!isPlatformVersionAtLeastU()) {
695             return;
696         }
697 
698         // Calls moveRootTaskToDisplay() with the system uid.
699         long identity = Binder.clearCallingIdentity();
700         try {
701             ActivityManagerHelper.moveRootTaskToDisplay(taskId, displayId);
702         } finally {
703             Binder.restoreCallingIdentity(identity);
704         }
705     }
706 
707     @Override
708     @ExcludeFromCodeCoverageGeneratedReport(reason = DUMP_INFO)
dump(IndentingPrintWriter writer)709     public void dump(IndentingPrintWriter writer) {
710         writer.println("*CarActivityService*");
711         synchronized (mLock) {
712             writer.println(" CarSystemUIProxy registered:");
713             writer.println(" " + (mCarSystemUIProxy != null));
714             writer.println(" Tasks:");
715             for (ActivityManager.RunningTaskInfo taskInfo : mTasks.values()) {
716                 writer.println("  " + TaskInfoHelper.toString(taskInfo));
717             }
718             writer.println(" Surfaces: " + mTaskToSurfaceMap.toString());
719         }
720     }
721 }
722