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