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 android.car.app;
18 
19 
20 import static android.Manifest.permission.INTERACT_ACROSS_USERS;
21 
22 import android.annotation.IntDef;
23 import android.annotation.MainThread;
24 import android.annotation.NonNull;
25 import android.annotation.Nullable;
26 import android.annotation.RequiresPermission;
27 import android.annotation.SystemApi;
28 import android.annotation.UiContext;
29 import android.app.Activity;
30 import android.app.ActivityManager;
31 import android.car.Car;
32 import android.car.CarManagerBase;
33 import android.car.user.CarUserManager;
34 import android.car.view.MirroredSurfaceView;
35 import android.content.ActivityNotFoundException;
36 import android.content.ComponentName;
37 import android.content.Context;
38 import android.graphics.Rect;
39 import android.os.Binder;
40 import android.os.Bundle;
41 import android.os.IBinder;
42 import android.os.RemoteException;
43 import android.os.ServiceSpecificException;
44 import android.util.Pair;
45 import android.util.Slog;
46 import android.view.Display;
47 import android.view.SurfaceControl;
48 
49 import com.android.internal.annotations.VisibleForTesting;
50 import com.android.internal.util.Preconditions;
51 
52 import java.lang.annotation.ElementType;
53 import java.lang.annotation.Retention;
54 import java.lang.annotation.RetentionPolicy;
55 import java.lang.annotation.Target;
56 import java.util.Collections;
57 import java.util.List;
58 import java.util.concurrent.Executor;
59 
60 /**
61  * API to manage {@link android.app.Activity} in Car.
62  *
63  * @hide
64  */
65 @SystemApi
66 public final class CarActivityManager extends CarManagerBase {
67     private static final String TAG = CarActivityManager.class.getSimpleName();
68 
69     /** Indicates that the operation was successful. */
70     public static final int RESULT_SUCCESS = 0;
71     /** Indicates that the operation was failed with the unknown reason. */
72     public static final int RESULT_FAILURE = -1;
73     /**
74      * Indicates that the operation was failed because the requester isn't the current user or
75      * the system user
76      */
77     public static final int RESULT_INVALID_USER = -2;
78 
79     /** @hide */
80     @Retention(RetentionPolicy.SOURCE)
81     @IntDef(prefix = "RESULT_", value = {
82             RESULT_SUCCESS,
83             RESULT_FAILURE,
84             RESULT_INVALID_USER,
85     })
86     @Target({ElementType.TYPE_USE})
87     public @interface ResultTypeEnum {}
88 
89     /**
90      * Internal error code for throwing {@link ActivityNotFoundException} from service.
91      * @hide
92      */
93     public static final int ERROR_CODE_ACTIVITY_NOT_FOUND = -101;
94 
95     private final ICarActivityService mService;
96     private IBinder mTaskMonitorToken;
97     private CarTaskViewControllerSupervisor mCarTaskViewControllerSupervisor;
98 
99     /**
100      * @hide
101      */
CarActivityManager(@onNull Car car, @NonNull IBinder service)102     public CarActivityManager(@NonNull Car car, @NonNull IBinder service) {
103         this(car, ICarActivityService.Stub.asInterface(service));
104     }
105 
106     /**
107      * @hide
108      */
109     @VisibleForTesting
CarActivityManager(@onNull Car car, @NonNull ICarActivityService service)110     public CarActivityManager(@NonNull Car car, @NonNull ICarActivityService service) {
111         super(car);
112         mService = service;
113     }
114 
115     /**
116      * Designates the given {@code activity} to be launched in {@code TaskDisplayArea} of
117      * {@code featureId} in the display of {@code displayId}.
118      * <p>Note: this will not affect the existing {@link Activity}.
119      * Note: You can map assign {@code Activity} to one {@code TaskDisplayArea} only. If
120      * you assign it to the multiple {@code TaskDisplayArea}s, then the last one wins.
121      * Note: The requester should be the current user or the system user, if not, the operation will
122      * be failed with {@code RESULT_INVALID_USER}.
123      *
124      * @param activity {@link Activity} to designate
125      * @param displayId {@code Display} where {@code TaskDisplayArea} is located in
126      * @param featureId {@code TaskDisplayArea} where {@link Activity} is launched in, if it is
127      *         {@code DisplayAreaOrganizer.FEATURE_UNDEFINED}, then it'll remove the existing one.
128      * @return {@code ResultTypeEnum}. {@code RESULT_SUCCESS} if the operation is successful,
129      *         otherwise, {@code RESULT_XXX} depending on the type of the error.
130      * @throws {@link IllegalArgumentException} if {@code displayId} or {@code featureId} is
131      *         invalid. {@link ActivityNotFoundException} if {@code activity} is not found
132      *         when it tries to remove.
133      */
134     @RequiresPermission(Car.PERMISSION_CONTROL_CAR_APP_LAUNCH)
135     @ResultTypeEnum
setPersistentActivity( @onNull ComponentName activity, int displayId, int featureId)136     public int setPersistentActivity(
137             @NonNull ComponentName activity, int displayId, int featureId) {
138         try {
139             return mService.setPersistentActivity(activity, displayId, featureId);
140         } catch (IllegalArgumentException | IllegalStateException | SecurityException e) {
141             throw e;
142         } catch (ServiceSpecificException e) {
143             return handleServiceSpecificFromCarService(e);
144         } catch (RemoteException | RuntimeException e) {
145             return handleExceptionFromCarService(e, RESULT_FAILURE);
146         }
147     }
148 
149     /**
150      * Designates the given {@code activities} to be launched in the root task associated with the
151      * given {@code rootTaskToken}.
152      * <p>Note: If an activity is already persisted on a root task, it will be overridden by the
153      * {@code rootTaskToken} supplied in the latest call.
154      * <p>Note: If {@code rootTaskToken} is null, the designation will be removed and the given
155      * activities will follow default behavior.
156      *
157      * @param activities list of {@link ComponentName} of activities to be designated on the
158      *                   root task.
159      * @param rootTaskToken the binder token of the root task.
160      */
161     @RequiresPermission(Car.PERMISSION_CONTROL_CAR_APP_LAUNCH)
setPersistentActivitiesOnRootTask(@onNull List<ComponentName> activities, @Nullable IBinder rootTaskToken)162     public void setPersistentActivitiesOnRootTask(@NonNull List<ComponentName> activities,
163             @Nullable IBinder rootTaskToken) {
164         try {
165             mService.setPersistentActivitiesOnRootTask(activities, rootTaskToken);
166         } catch (IllegalArgumentException | IllegalStateException | SecurityException e) {
167             throw e;
168         }  catch (RemoteException | RuntimeException e) {
169             handleExceptionFromCarService(e, RESULT_FAILURE);
170         }
171     }
172 
173     /** @hide */
174     @Override
onCarDisconnected()175     protected void onCarDisconnected() {
176         mTaskMonitorToken = null;
177     }
178 
handleServiceSpecificFromCarService(ServiceSpecificException e)179     private int handleServiceSpecificFromCarService(ServiceSpecificException e)
180             throws ActivityNotFoundException {
181         if (e.errorCode == ERROR_CODE_ACTIVITY_NOT_FOUND) {
182             throw new ActivityNotFoundException(e.getMessage());
183         }
184         // don't know what this is
185         throw new IllegalStateException(e);
186     }
187 
188     /**
189      * Registers the caller as TaskMonitor, which can provide Task lifecycle events to CarService.
190      * The caller should provide a binder token, which is used to check if the given TaskMonitor is
191      * live and the reported events are from the legitimate TaskMonitor.
192      * @hide
193      */
194     @RequiresPermission(android.Manifest.permission.MANAGE_ACTIVITY_TASKS)
registerTaskMonitor()195     public boolean registerTaskMonitor() {
196         Preconditions.checkState(
197                 mTaskMonitorToken == null, "Can't register the multiple TaskMonitors");
198         IBinder token = new Binder();
199         try {
200             mService.registerTaskMonitor(token);
201             mTaskMonitorToken = token;
202             return true;
203         } catch (RemoteException e) {
204             handleRemoteExceptionFromCarService(e);
205         }
206         return false;
207     }
208 
209     /**
210      * Reports that a Task is created.
211      * @deprecated Use {@link #onTaskAppeared(ActivityManager.RunningTaskInfo, SurfaceControl)}
212      * @hide
213      */
214     @Deprecated
215     @RequiresPermission(android.Manifest.permission.MANAGE_ACTIVITY_TASKS)
onTaskAppeared(ActivityManager.RunningTaskInfo taskInfo)216     public void onTaskAppeared(ActivityManager.RunningTaskInfo taskInfo) {
217         onTaskAppearedInternal(taskInfo, null);
218     }
219 
220     /**
221      * Reports that a Task is created.
222      * @hide
223      */
224     @RequiresPermission(android.Manifest.permission.MANAGE_ACTIVITY_TASKS)
onTaskAppeared(ActivityManager.RunningTaskInfo taskInfo, @Nullable SurfaceControl leash)225     public void onTaskAppeared(ActivityManager.RunningTaskInfo taskInfo,
226                 @Nullable SurfaceControl leash) {
227         onTaskAppearedInternal(taskInfo, leash);
228     }
229 
onTaskAppearedInternal( ActivityManager.RunningTaskInfo taskInfo, SurfaceControl leash)230     private void onTaskAppearedInternal(
231             ActivityManager.RunningTaskInfo taskInfo, SurfaceControl leash) {
232         if (!hasValidToken()) return;
233         try {
234             mService.onTaskAppeared(mTaskMonitorToken, taskInfo, leash);
235         } catch (RemoteException e) {
236             handleRemoteExceptionFromCarService(e);
237         }
238     }
239 
240     /**
241      * Reports that a Task is vanished.
242      * @hide
243      */
244     @RequiresPermission(android.Manifest.permission.MANAGE_ACTIVITY_TASKS)
onTaskVanished(ActivityManager.RunningTaskInfo taskInfo)245     public void onTaskVanished(ActivityManager.RunningTaskInfo taskInfo) {
246         if (!hasValidToken()) return;
247         try {
248             mService.onTaskVanished(mTaskMonitorToken, taskInfo);
249         } catch (RemoteException e) {
250             handleRemoteExceptionFromCarService(e);
251         }
252     }
253 
254     /**
255      * Reports that some Task's states are changed.
256      * @hide
257      */
258     @RequiresPermission(android.Manifest.permission.MANAGE_ACTIVITY_TASKS)
onTaskInfoChanged(ActivityManager.RunningTaskInfo taskInfo)259     public void onTaskInfoChanged(ActivityManager.RunningTaskInfo taskInfo) {
260         if (!hasValidToken()) return;
261         try {
262             mService.onTaskInfoChanged(mTaskMonitorToken, taskInfo);
263         } catch (RemoteException e) {
264             handleRemoteExceptionFromCarService(e);
265         }
266     }
267 
268     /**
269      * Unregisters the caller from TaskMonitor.
270      * @hide
271      */
272     @RequiresPermission(android.Manifest.permission.MANAGE_ACTIVITY_TASKS)
unregisterTaskMonitor()273     public void unregisterTaskMonitor() {
274         if (!hasValidToken()) return;
275         try {
276             mService.unregisterTaskMonitor(mTaskMonitorToken);
277             mTaskMonitorToken = null;
278         } catch (RemoteException e) {
279             handleRemoteExceptionFromCarService(e);
280         }
281     }
282 
283     /**
284      * Returns all the visible tasks in the all displays. The order is not guaranteed.
285      */
286     @RequiresPermission(android.Manifest.permission.MANAGE_ACTIVITY_TASKS)
287     @NonNull
getVisibleTasks()288     public List<ActivityManager.RunningTaskInfo> getVisibleTasks() {
289         try {
290             return mService.getVisibleTasks(Display.INVALID_DISPLAY);
291         } catch (RemoteException e) {
292             handleRemoteExceptionFromCarService(e);
293         }
294         return Collections.emptyList();
295     }
296 
297     /**
298      * Returns all the visible tasks in the given display. The order is not guaranteed.
299      *
300      * @param displayId the id of {@link Display} to retrieve the tasks,
301      *         {@link Display.INVALID_DISPLAY} to retrieve the tasks in the all displays.
302      */
303     @RequiresPermission(android.Manifest.permission.MANAGE_ACTIVITY_TASKS)
304     @NonNull
getVisibleTasks(int displayId)305     public List<ActivityManager.RunningTaskInfo> getVisibleTasks(int displayId) {
306         try {
307             return mService.getVisibleTasks(displayId);
308         } catch (RemoteException e) {
309             handleRemoteExceptionFromCarService(e);
310         }
311         return Collections.emptyList();
312     }
313 
314     /**
315      * Starts user picker UI (=user selection UI) to the given display.
316      *
317      * <p>User picker UI will run as {@link android.os.UserHandle#SYSTEM} user.
318      */
319     @RequiresPermission(android.Manifest.permission.INTERACT_ACROSS_USERS)
startUserPickerOnDisplay(int displayId)320     public void startUserPickerOnDisplay(int displayId) {
321         try {
322             mService.startUserPickerOnDisplay(displayId);
323         } catch (RemoteException e) {
324             handleRemoteExceptionFromCarService(e);
325         }
326     }
327 
328     /**
329      * Creates the mirroring token of the given Task.
330      *
331      * @param taskId The Task to mirror.
332      * @return A token to access the Task Surface. The token is used to identify the target
333      *     Task's Surface for {@link MirroredSurfaceView}.
334      */
335     @RequiresPermission(android.Manifest.permission.MANAGE_ACTIVITY_TASKS)
336     @Nullable
createTaskMirroringToken(int taskId)337     public IBinder createTaskMirroringToken(int taskId) {
338         try {
339             return mService.createTaskMirroringToken(taskId);
340         } catch (RemoteException e) {
341             return handleRemoteExceptionFromCarService(e, /* returnValue= */ null);
342         }
343     }
344 
345     /**
346      * Creates the mirroring token of the given Display.
347      *
348      * @param displayId The Display to mirror.
349      * @return A token to access the Display Surface. The token is used to identify the target
350      *     Display's Surface for {@link MirroredSurfaceView}.
351      */
352     @RequiresPermission(Car.PERMISSION_MIRROR_DISPLAY)
353     @Nullable
createDisplayMirroringToken(int displayId)354     public IBinder createDisplayMirroringToken(int displayId) {
355         try {
356             return mService.createDisplayMirroringToken(displayId);
357         } catch (RemoteException e) {
358             return handleRemoteExceptionFromCarService(e, /* returnValue= */ null);
359         }
360     }
361 
362     /**
363      * Gets a mirrored {@link SurfaceControl} of the Task identified by the given Token.
364      *
365      * @param token  The token to access the Surface.
366      * @return A Pair of {@link SurfaceControl} and the bounds of the mirrored Task,
367      *     or {code null} if it can't find the target Surface to mirror.
368      *
369      * @hide Used by {@link MirroredSurfaceView} only.
370      */
371     @RequiresPermission(Car.PERMISSION_ACCESS_MIRRORRED_SURFACE)
372     @Nullable
getMirroredSurface(@onNull IBinder token)373     public Pair<SurfaceControl, Rect> getMirroredSurface(@NonNull IBinder token) {
374         try {
375             Rect outBounds = new Rect();
376             // SurfaceControl constructor is hidden api, so we can get it by the return value.
377             SurfaceControl sc = mService.getMirroredSurface(token, outBounds);
378             if (sc == null) {
379                 return null;
380             }
381             return Pair.create(sc, outBounds);
382         } catch (RemoteException e) {
383             return handleRemoteExceptionFromCarService(e, /* returnValue= */ null);
384         }
385     }
386 
387     /**
388      * Registers a system ui proxy which will be used by the client apps to interact with the
389      * system-ui for things like creating task views, getting notified about immersive mode
390      * request, etc.
391      *
392      * <p>This is meant to be called only by the SystemUI.
393      *
394      * @param carSystemUIProxy the implementation of the {@link CarSystemUIProxy}.
395      * @throws UnsupportedOperationException when called more than once for the same SystemUi
396      *         process.
397      * @hide
398      */
399     @SystemApi
400     @RequiresPermission(Car.PERMISSION_REGISTER_CAR_SYSTEM_UI_PROXY)
registerCarSystemUIProxy(@onNull CarSystemUIProxy carSystemUIProxy)401     public void registerCarSystemUIProxy(@NonNull CarSystemUIProxy carSystemUIProxy) {
402         try {
403             mService.registerCarSystemUIProxy(new CarSystemUIProxyAidlWrapper(carSystemUIProxy));
404         } catch (RemoteException e) {
405             handleRemoteExceptionFromCarService(e);
406         }
407     }
408 
409     /**
410      * Returns true if the {@link CarSystemUIProxy} is registered, false otherwise.
411      *
412      * @hide
413      */
414     @SystemApi
isCarSystemUIProxyRegistered()415     public boolean isCarSystemUIProxyRegistered() {
416         try {
417             return mService.isCarSystemUIProxyRegistered();
418         } catch (RemoteException e) {
419             handleRemoteExceptionFromCarService(e);
420             return false;
421         }
422     }
423 
424     /**
425      * Gets the {@link CarTaskViewController} using the {@code carTaskViewControllerCallback}.
426      *
427      * This method is expected to be called from the {@link Activity#onCreate(Bundle)}. It will
428      * take care of freeing up the held resources when activity is destroyed. If an activity is
429      * recreated, it should be called again in the next {@link Activity#onCreate(Bundle)}.
430      *
431      * @param carTaskViewControllerCallback the callback which the client can use to monitor the
432      *                                      lifecycle of the {@link CarTaskViewController}.
433      * @param hostActivity the activity that will host the taskviews.
434      * @hide
435      */
436     @SystemApi
437     @RequiresPermission(allOf = {Car.PERMISSION_MANAGE_CAR_SYSTEM_UI, INTERACT_ACROSS_USERS})
438     @MainThread
getCarTaskViewController( @onNull Activity hostActivity, @NonNull Executor callbackExecutor, @NonNull CarTaskViewControllerCallback carTaskViewControllerCallback)439     public void getCarTaskViewController(
440             @NonNull Activity hostActivity,
441             @NonNull Executor callbackExecutor,
442             @NonNull CarTaskViewControllerCallback carTaskViewControllerCallback) {
443         getCarTaskViewController(
444                 hostActivity,
445                 CarTaskViewControllerHostLifecycleFactory.forActivity(hostActivity),
446                 callbackExecutor,
447                 carTaskViewControllerCallback);
448     }
449 
450     /**
451      * Gets the {@link CarTaskViewController} using the {@code carTaskViewControllerCallback}.
452      *
453      * This method is expected to be called when the container (host) is created. It will
454      * take care of freeing up the held resources when container is destroyed. If the container is
455      * recreated, this method should be called again after it gets created again.
456      *
457      * @param carTaskViewControllerCallback the callback which the client can use to monitor the
458      *                                      lifecycle of the {@link CarTaskViewController}.
459      * @param hostContext the visual hostContext which the container (host) is associated with.
460      * @param callbackExecutor the executor which the {@code carTaskViewControllerCallback} will be
461      *                         executed on.
462      * @param carTaskViewControllerHostLifecycle the lifecycle of the container (host).
463      * @hide
464      */
465     // TODO(b/293297847): Expose this as system API
466     @RequiresPermission(allOf = {Car.PERMISSION_MANAGE_CAR_SYSTEM_UI, INTERACT_ACROSS_USERS})
467     @MainThread
getCarTaskViewController( @iContext @onNull Context hostContext, @NonNull CarTaskViewControllerHostLifecycle carTaskViewControllerHostLifecycle, @NonNull Executor callbackExecutor, @NonNull CarTaskViewControllerCallback carTaskViewControllerCallback)468     public void getCarTaskViewController(
469             @UiContext @NonNull Context hostContext,
470             @NonNull CarTaskViewControllerHostLifecycle carTaskViewControllerHostLifecycle,
471             @NonNull Executor callbackExecutor,
472             @NonNull CarTaskViewControllerCallback carTaskViewControllerCallback) {
473         try {
474             if (mCarTaskViewControllerSupervisor == null) {
475                 // Same supervisor is used for multiple activities.
476                 mCarTaskViewControllerSupervisor = new CarTaskViewControllerSupervisor(mService,
477                         getContext().getMainExecutor(), mCar.getCarManager(CarUserManager.class));
478             }
479             mCarTaskViewControllerSupervisor.createCarTaskViewController(
480                     hostContext,
481                     carTaskViewControllerHostLifecycle,
482                     callbackExecutor,
483                     carTaskViewControllerCallback);
484         } catch (RemoteException e) {
485             handleRemoteExceptionFromCarService(e);
486         }
487     }
488 
489     /**
490      * Moves the given {@code RootTask} with its child {@code Activties} to the specified
491      * {@code Display}.
492      * @param taskId the id of the target {@code RootTask} to move
493      * @param displayId the displayId to move the {@code RootTask} to
494      * @throws IllegalArgumentException if the given {@code taskId} or {@code displayId} is invalid
495      * @throws IllegalArgumentException if the given {@code RootTask} is already in the given
496      *     {@code Display}
497      * Note: the operation can be failed if the given {@code Display} doesn't allow for the type of
498      * the given {@code RootTask} to be launched.
499      */
500     @RequiresPermission(Car.PERMISSION_CONTROL_CAR_APP_LAUNCH)
moveRootTaskToDisplay(int taskId, int displayId)501     public void moveRootTaskToDisplay(int taskId, int displayId) {
502         try {
503             mService.moveRootTaskToDisplay(taskId, displayId);
504         } catch (RemoteException e) {
505             handleRemoteExceptionFromCarService(e);
506         }
507     }
508 
hasValidToken()509     private boolean hasValidToken() {
510         boolean valid = mTaskMonitorToken != null;
511         if (!valid) {
512             Slog.w(TAG, "Has invalid token, skip the operation: "
513                     + new Throwable().getStackTrace()[1].getMethodName());
514         }
515         return valid;
516     }
517 }
518