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 android.car.remoteaccess; 18 19 import static android.car.feature.Flags.FLAG_SERVERLESS_REMOTE_ACCESS; 20 21 import static com.android.car.internal.common.CommonConstants.EMPTY_INT_ARRAY; 22 23 import android.annotation.CallbackExecutor; 24 import android.annotation.FlaggedApi; 25 import android.annotation.IntDef; 26 import android.annotation.NonNull; 27 import android.annotation.Nullable; 28 import android.annotation.RequiresPermission; 29 import android.annotation.SystemApi; 30 import android.annotation.TestApi; 31 import android.car.Car; 32 import android.car.CarManagerBase; 33 import android.car.feature.FeatureFlags; 34 import android.car.feature.FeatureFlagsImpl; 35 import android.os.Binder; 36 import android.os.IBinder; 37 import android.os.RemoteException; 38 import android.os.ServiceSpecificException; 39 import android.util.Slog; 40 41 import com.android.car.internal.ICarBase; 42 import com.android.internal.annotations.GuardedBy; 43 import com.android.internal.annotations.VisibleForTesting; 44 import com.android.internal.util.Preconditions; 45 46 import java.lang.annotation.ElementType; 47 import java.lang.annotation.Retention; 48 import java.lang.annotation.RetentionPolicy; 49 import java.lang.annotation.Target; 50 import java.time.Duration; 51 import java.util.ArrayList; 52 import java.util.List; 53 import java.util.concurrent.Executor; 54 55 /** 56 * CarRemoteAccessManager allows applications to listen to remote task requests even while Android 57 * System is not running. 58 * 59 * <p>The remote task client registers to {@link CarRemoteAccessManager} to listen to remote access 60 * events. At {@link RemoteTaskClientCallback#onRegistrationUpdated} it is required to share 61 * {@code serviceId}, {@code deviceId} and {@code clientId} with the cloud service which will use 62 * the IDs to wake the vehicle. At {@link RemoteTaskClientCallback#onRemoteTaskRequested}, it starts 63 * executing the given task. It is supposed to call {@link #reportRemoteTaskDone(String)} when it 64 * finishes the given task. Once the task completion is reported or the timeout expires, Android 65 * System goes back to either the previous power state or the specified power state. 66 * 67 * <p>Note that the remote task will be executed even when the vehicle is actively in use, not 68 * nessarily when the vehicle is off. Remote task client must make sure the task to be executed 69 * will not affect the system performance or affect driving safety. If certain task must be 70 * executed while the vehicle is off, the remote task client must check VHAL property 71 * {@code VEHICLE_IN_USE} and/or check igntition state via {@code VehicleIgnitionState}. 72 * 73 * <p>Note: all remote task clients must run as system user. 74 * 75 * <p>A serverless setup might also be supported if the RRO overlay for 76 * remote_access_serverless_client_map exists which provides a map from serverless client ID to 77 * their package names. 78 * 79 * <p>Here the term 'remote' refers to the source of task coming from outside of the 80 * Android system, it does not necessarily means the task comes from Internet. In the serverless 81 * setup, no cloud service is required. Another device within the same vehicle, but outside the 82 * Android system is the issuer for the remote task. 83 * 84 * <p>For serverless setup, there is a pre-configured set of serverless remote task clients. They 85 * register to {@link CarRemoteAccessManager} to listen to remote access events. 86 * {@link RemoteTaskClientCallback#onServerlessClientRegistered} will be called instead of 87 * {@link RemoteTaskClientCallback#onRegistrationUpdated} and there is no cloud service involved. 88 * {@link RemoteTaskClientCallback#onRemoteTaskRequested} will be invoked when the task is to be 89 * executed. It is supposed to call {@link #reportRemoteTaskDone(String)} when it 90 * finishes the given task. Once the task completion is reported or the timeout expires, Android 91 * system goes back to either the previous power state or the specified power state. 92 * 93 * <p>For serverless setup, if {@link isTaskScheduleSupported} returns {@code true}, client may 94 * use {@link InVehicleTaskScheduler#scheduleTask} to schedule a remote task to be executed later. 95 * If {@link isTaskScheduleSupported} returns {@code false}, it is assumed there exists some other 96 * channel outside of the Android system for task scheduling. 97 */ 98 public final class CarRemoteAccessManager extends CarManagerBase { 99 100 private static final String TAG = CarRemoteAccessManager.class.getSimpleName(); 101 102 private final InVehicleTaskScheduler mInVehicleTaskScheduler = new InVehicleTaskScheduler(); 103 104 /** 105 * The system remains ON after completing the remote tasks. 106 * 107 * @hide 108 */ 109 @SystemApi 110 public static final int NEXT_POWER_STATE_ON = 1; 111 112 /** 113 * The system shuts down to power off after completing the remote tasks. 114 * 115 * @hide 116 */ 117 @SystemApi 118 public static final int NEXT_POWER_STATE_OFF = 2; 119 120 /** 121 * The system goes into deep sleep after completing the remote tasks. 122 * 123 * @hide 124 */ 125 @SystemApi 126 public static final int NEXT_POWER_STATE_SUSPEND_TO_RAM = 3; 127 128 /** 129 * The system goes into hibernation after completing the remote tasks. 130 * 131 * @hide 132 */ 133 @SystemApi 134 public static final int NEXT_POWER_STATE_SUSPEND_TO_DISK = 4; 135 136 /** @hide */ 137 @Retention(RetentionPolicy.SOURCE) 138 @IntDef(prefix = "NEXT_POWER_STATE_", value = { 139 NEXT_POWER_STATE_ON, 140 NEXT_POWER_STATE_OFF, 141 NEXT_POWER_STATE_SUSPEND_TO_RAM, 142 NEXT_POWER_STATE_SUSPEND_TO_DISK, 143 }) 144 @Target({ElementType.TYPE_USE}) 145 public @interface NextPowerState {} 146 147 /** 148 * Custom task. The task data is opaque to framework. 149 * 150 * @hide 151 */ 152 @SystemApi 153 @FlaggedApi(FLAG_SERVERLESS_REMOTE_ACCESS) 154 public static final int TASK_TYPE_CUSTOM = 0; 155 156 /** 157 * Schedule to enter garage mode if the vehicle is off. 158 * 159 * taskData is ignore for this type. 160 * 161 * @hide 162 */ 163 @SystemApi 164 @FlaggedApi(FLAG_SERVERLESS_REMOTE_ACCESS) 165 public static final int TASK_TYPE_ENTER_GARAGE_MODE = 1; 166 167 /** @hide */ 168 @IntDef(prefix = {"TASK_TYPE_"}, value = { 169 TASK_TYPE_CUSTOM, 170 TASK_TYPE_ENTER_GARAGE_MODE, 171 }) 172 @Retention(RetentionPolicy.SOURCE) 173 public @interface TaskType {} 174 175 private final ICarRemoteAccessService mService; 176 private final Object mLock = new Object(); 177 private FeatureFlags mFeatureFlags = new FeatureFlagsImpl(); 178 179 /** 180 * Sets fake feature flag for unit testing. 181 * 182 * @hide 183 */ 184 @VisibleForTesting setFeatureFlags(FeatureFlags fakeFeatureFlags)185 public void setFeatureFlags(FeatureFlags fakeFeatureFlags) { 186 mFeatureFlags = fakeFeatureFlags; 187 } 188 189 private final class CarRemoteAccessCallback extends ICarRemoteAccessCallback.Stub { 190 @Override onClientRegistrationUpdated(RemoteTaskClientRegistrationInfo registrationInfo)191 public void onClientRegistrationUpdated(RemoteTaskClientRegistrationInfo registrationInfo) { 192 RemoteTaskClientCallback callback; 193 Executor executor; 194 synchronized (mLock) { 195 if (mRemoteTaskClientCallback == null || mExecutor == null) { 196 Slog.w(TAG, "Cannot call onRegistrationUpdated because no remote task client " 197 + "is registered"); 198 return; 199 } 200 mCurrentClientId = registrationInfo.getClientId(); 201 callback = mRemoteTaskClientCallback; 202 executor = mExecutor; 203 } 204 Binder.clearCallingIdentity(); 205 executor.execute(() -> callback.onRegistrationUpdated(registrationInfo)); 206 } 207 208 @Override onServerlessClientRegistered(String clientId)209 public void onServerlessClientRegistered(String clientId) { 210 RemoteTaskClientCallback callback; 211 Executor executor; 212 synchronized (mLock) { 213 if (mRemoteTaskClientCallback == null || mExecutor == null) { 214 Slog.w(TAG, "Cannot call onRegistrationUpdated because no remote task client " 215 + "is registered"); 216 return; 217 } 218 mCurrentClientId = clientId; 219 callback = mRemoteTaskClientCallback; 220 executor = mExecutor; 221 } 222 if (mFeatureFlags.serverlessRemoteAccess()) { 223 Binder.clearCallingIdentity(); 224 executor.execute(() -> callback.onServerlessClientRegistered()); 225 } else { 226 Slog.e(TAG, "Serverless remote access flag is not enabled, " 227 + "the callback must not be called"); 228 } 229 } 230 231 @Override onClientRegistrationFailed()232 public void onClientRegistrationFailed() { 233 RemoteTaskClientCallback callback; 234 Executor executor; 235 synchronized (mLock) { 236 if (mRemoteTaskClientCallback == null || mExecutor == null) { 237 Slog.w(TAG, "Cannot call onRegistrationFailed because no remote task client " 238 + "is registered"); 239 return; 240 } 241 callback = mRemoteTaskClientCallback; 242 executor = mExecutor; 243 } 244 Binder.clearCallingIdentity(); 245 executor.execute(() -> callback.onRegistrationFailed()); 246 } 247 248 @Override onRemoteTaskRequested(String clientId, String taskId, byte[] data, int taskMaxDurationInSec)249 public void onRemoteTaskRequested(String clientId, String taskId, byte[] data, 250 int taskMaxDurationInSec) { 251 RemoteTaskClientCallback callback; 252 Executor executor; 253 synchronized (mLock) { 254 if (mCurrentClientId == null || !mCurrentClientId.equals(clientId)) { 255 Slog.w(TAG, "Received a task for a mismatched client ID(" + clientId 256 + "): the current client ID = " + mCurrentClientId); 257 return; 258 } 259 callback = mRemoteTaskClientCallback; 260 executor = mExecutor; 261 } 262 if (callback == null || executor == null) { 263 Slog.w(TAG, "Cannot call onRemoteTaskRequested because no remote task client is " 264 + "registered"); 265 return; 266 } 267 Binder.clearCallingIdentity(); 268 executor.execute(() -> callback.onRemoteTaskRequested(taskId, data, 269 taskMaxDurationInSec)); 270 } 271 272 @Override onShutdownStarting()273 public void onShutdownStarting() { 274 String clientId; 275 RemoteTaskClientCallback callback; 276 Executor executor; 277 synchronized (mLock) { 278 clientId = mCurrentClientId; 279 callback = mRemoteTaskClientCallback; 280 executor = mExecutor; 281 } 282 if (clientId == null || callback == null || executor == null) { 283 Slog.w(TAG, "Cannot call onShutdownStarting because no remote task client is " 284 + "registered"); 285 return; 286 } 287 Binder.clearCallingIdentity(); 288 executor.execute(() -> 289 callback.onShutdownStarting(new MyCompletableRemoteTaskFuture(clientId))); 290 } 291 } 292 293 private final ICarRemoteAccessCallback mCarRemoteAccessCallback = new CarRemoteAccessCallback(); 294 295 @GuardedBy("mLock") 296 private RemoteTaskClientCallback mRemoteTaskClientCallback; 297 @GuardedBy("mLock") 298 private Executor mExecutor; 299 @GuardedBy("mLock") 300 private String mCurrentClientId; 301 302 /** 303 * An interface passed from {@link RemoteTaskClientCallback}. 304 * 305 * <p>The remote task client uses this interface to tell {@link CarRemoteAccessManager} that it 306 * finalized the pending remote tasks. 307 */ 308 public interface CompletableRemoteTaskFuture { 309 /** 310 * Tells {@link CarRemoteAccessManager} that the remote task client finalized the pending 311 * remoate tasks. 312 */ complete()313 void complete(); 314 } 315 316 private final class MyCompletableRemoteTaskFuture implements CompletableRemoteTaskFuture { 317 private final String mClientIdToComplete; 318 MyCompletableRemoteTaskFuture(String clientId)319 MyCompletableRemoteTaskFuture(String clientId) { 320 mClientIdToComplete = clientId; 321 } 322 323 @Override complete()324 public void complete() { 325 try { 326 mService.confirmReadyForShutdown(mClientIdToComplete); 327 } catch (RemoteException e) { 328 handleRemoteExceptionFromCarService(e); 329 } 330 } 331 } 332 333 /** 334 * Listener for remote task events. 335 */ 336 public interface RemoteTaskClientCallback { 337 /** 338 * This is called when the remote task client is successfully registered or the client ID is 339 * updated by AAOS. 340 * 341 * <p>For a serverless remote task client, the {@link onServerlessClientRegistered} will be 342 * called instead of this. 343 * 344 * @param info {@link RemoteTaskClientRegistrationInfo} which contains wake-up service ID, 345 * vehicle ID, processor ID and client ID. 346 */ onRegistrationUpdated(@onNull RemoteTaskClientRegistrationInfo info)347 void onRegistrationUpdated(@NonNull RemoteTaskClientRegistrationInfo info); 348 349 /** 350 * This is called when a pre-configured serverless remote task client is registered. 351 * 352 * <p>The serverless remote task client is configured via including a runtime config file 353 * at {@code /vendor/etc/} 354 */ 355 @FlaggedApi(FLAG_SERVERLESS_REMOTE_ACCESS) onServerlessClientRegistered()356 default void onServerlessClientRegistered() { 357 Slog.i(TAG, "onServerlessClientRegistered called"); 358 } 359 360 /** 361 * This is called when registering the remote task client fails. 362 */ onRegistrationFailed()363 void onRegistrationFailed(); 364 365 /** 366 * This is called when a wake-up request is received/processed. 367 * 368 * @param taskId ID of the task that is requested by the remote task server. 369 * @param data Extra data passed along with the wake-up request. 370 * @param taskMaxDurationInSec The timeout before AAOS goes back to the previous power 371 * state. 372 */ onRemoteTaskRequested(@onNull String taskId, @Nullable byte[] data, int taskMaxDurationInSec)373 void onRemoteTaskRequested(@NonNull String taskId, @Nullable byte[] data, 374 int taskMaxDurationInSec); 375 376 /** 377 * This is called when the device is about to shutdown. 378 * 379 * <p>The remote task client should finalize the ongoing tasks, if any, and complete the 380 * given future within 5 seconds. After the given timeout, the Android system will shutdown, 381 * anyway. 382 * 383 * @param future {@link CompletableRemoteTaskFuture} used by the remote task client to 384 * notify CarRemoteAccessManager that all pending remote tasks are finalized. 385 */ onShutdownStarting(@onNull CompletableRemoteTaskFuture future)386 void onShutdownStarting(@NonNull CompletableRemoteTaskFuture future); 387 } 388 389 /** @hide */ CarRemoteAccessManager(ICarBase car, IBinder service)390 public CarRemoteAccessManager(ICarBase car, IBinder service) { 391 super(car); 392 mService = ICarRemoteAccessService.Stub.asInterface(service); 393 } 394 395 /** @hide */ 396 @Override onCarDisconnected()397 public void onCarDisconnected() { 398 // Nothing to do. 399 } 400 401 /** 402 * Sets the remote task client represented as {@link RemoteTaskClientCallback}. 403 * 404 * @param executor Executor on which {@code callback} is executed. 405 * @param callback {@link RemoteTaskClientCallback} that listens to remote task events. 406 * @throws IllegalStateException When a remote task client is already set. 407 * @throws IllegalArgumentException When the given callback or the executor is {@code null}. 408 */ 409 @RequiresPermission(Car.PERMISSION_USE_REMOTE_ACCESS) setRemoteTaskClient(@onNull @allbackExecutor Executor executor, @NonNull RemoteTaskClientCallback callback)410 public void setRemoteTaskClient(@NonNull @CallbackExecutor Executor executor, 411 @NonNull RemoteTaskClientCallback callback) { 412 Preconditions.checkArgument(executor != null, "Executor cannot be null"); 413 Preconditions.checkArgument(callback != null, "Callback cannot be null"); 414 415 synchronized (mLock) { 416 if (mRemoteTaskClientCallback != null) { 417 throw new IllegalStateException("Remote task client must be cleared first"); 418 } 419 mRemoteTaskClientCallback = callback; 420 mExecutor = executor; 421 } 422 423 try { 424 mService.addCarRemoteTaskClient(mCarRemoteAccessCallback); 425 } catch (RemoteException e) { 426 synchronized (mLock) { 427 mRemoteTaskClientCallback = null; 428 mExecutor = null; 429 } 430 handleRemoteExceptionFromCarService(e); 431 } 432 } 433 434 /** 435 * Clears the remote task client previously set via {@link #setRemoteTaskClient(Executor, 436 * RemoteTaskClientCallback)}. 437 * 438 * <p>After the remote task client is cleared, all tasks associated with the previous client 439 * will not be delivered and the client must not call {@code reportRemoteTaskDone} with the 440 * task ID associated with the previous client ID. 441 * 442 * @throws IllegalStateException if {@code callback} is not registered. 443 */ 444 @RequiresPermission(Car.PERMISSION_USE_REMOTE_ACCESS) clearRemoteTaskClient()445 public void clearRemoteTaskClient() { 446 synchronized (mLock) { 447 if (mRemoteTaskClientCallback == null) { 448 Slog.w(TAG, "No registered remote task client to clear"); 449 return; 450 } 451 mRemoteTaskClientCallback = null; 452 mExecutor = null; 453 mCurrentClientId = null; 454 } 455 try { 456 mService.removeCarRemoteTaskClient(mCarRemoteAccessCallback); 457 } catch (RemoteException e) { 458 handleRemoteExceptionFromCarService(e); 459 } 460 } 461 462 /** 463 * Reports that remote task execution is completed, so that the vehicle will go back to the 464 * power state before the wake-up. 465 * 466 * @param taskId ID of the remote task which has been completed. 467 * @throws IllegalArgumentException If {@code taskId} is null. 468 * @throws IllegalStateException If the remote task client is not registered or not woken up. 469 */ 470 @RequiresPermission(Car.PERMISSION_USE_REMOTE_ACCESS) reportRemoteTaskDone(@onNull String taskId)471 public void reportRemoteTaskDone(@NonNull String taskId) { 472 Preconditions.checkArgument(taskId != null, "Task ID cannot be null"); 473 474 String currentClientId; 475 synchronized (mLock) { 476 if (mCurrentClientId == null) { 477 Slog.w(TAG, "Failed to report remote task completion: no remote task client is " 478 + "registered"); 479 throw new IllegalStateException("No remote task client is registered"); 480 } 481 currentClientId = mCurrentClientId; 482 } 483 try { 484 mService.reportRemoteTaskDone(currentClientId, taskId); 485 } catch (IllegalStateException e) { 486 Slog.w(TAG, "Task ID(" + taskId + ") is not valid", e); 487 throw e; 488 } catch (RemoteException e) { 489 handleRemoteExceptionFromCarService(e); 490 } 491 } 492 493 /** 494 * Sets the power state after all the remote tasks are completed. 495 * 496 * <p>By default, the system returns to the previous power state from which the system woke up. 497 * If the given power state is {@code NEXT_POWER_STATE_ON}, Garage Mode is not executed. 498 * 499 * @param nextPowerState The next power state after the remote task is completed. 500 * @param runGarageMode Whether to run Garage Mode when switching to the next power state. 501 * @throws IllegalArgumentException If {@code nextPowerState} is not valid. 502 * @throws IllegalStateException If the remote task client is not registered or not woken up. 503 * 504 * @hide 505 */ 506 @SystemApi 507 @RequiresPermission(Car.PERMISSION_CONTROL_REMOTE_ACCESS) setPowerStatePostTaskExecution(@extPowerState int nextPowerState, boolean runGarageMode)508 public void setPowerStatePostTaskExecution(@NextPowerState int nextPowerState, 509 boolean runGarageMode) { 510 try { 511 mService.setPowerStatePostTaskExecution(nextPowerState, runGarageMode); 512 } catch (RemoteException e) { 513 handleRemoteExceptionFromCarService(e); 514 } 515 } 516 517 /** 518 * Returns whether task scheduling is supported. 519 * 520 * <p>If this returns {@code true}, user may use 521 * {@link InVehicleTaskScheduler#scheduleTask} to schedule a task to be executed at a later 522 * time. If the device is off when the task is scheduled to be executed, the device will be 523 * woken up to execute the task. 524 * 525 * @return {@code true} if serverless remote task scheduling is supported by the HAL and the 526 * caller is a pre-configured serverless remote task client. 527 * 528 * @hide 529 */ 530 @SystemApi 531 @FlaggedApi(FLAG_SERVERLESS_REMOTE_ACCESS) 532 @RequiresPermission(Car.PERMISSION_CONTROL_REMOTE_ACCESS) isTaskScheduleSupported()533 public boolean isTaskScheduleSupported() { 534 try { 535 return mService.isTaskScheduleSupported(); 536 } catch (RemoteException e) { 537 return handleRemoteExceptionFromCarService(e, false); 538 } 539 } 540 541 /** 542 * Gets a in vehicle task scheduler that can be used to schedule a task to be executed later. 543 * 544 * <p>This is only supported for pre-configured remote task serverless clients. 545 * 546 * <p>See {@link InVehicleTaskScheduler.scheduleTask} for usage. 547 * 548 * @return An in vehicle task scheduler or {@code null} if {@link isTaskScheduleSupported} is 549 * {@code false}. 550 * 551 * @hide 552 */ 553 @SystemApi 554 @FlaggedApi(FLAG_SERVERLESS_REMOTE_ACCESS) 555 @RequiresPermission(Car.PERMISSION_CONTROL_REMOTE_ACCESS) 556 @Nullable getInVehicleTaskScheduler()557 public InVehicleTaskScheduler getInVehicleTaskScheduler() { 558 if (!isTaskScheduleSupported()) { 559 Slog.w(TAG, "getInVehicleTaskScheduler: Task schedule is not supported, return null"); 560 return null; 561 } 562 return mInVehicleTaskScheduler; 563 } 564 565 /** 566 * For testing only. Adds a package as a new serverless remote task client. 567 * 568 * @param packageName The package name for the serverless remote task client. This should be a 569 * test package name. 570 * @param clientId An arbitrary client ID picked for the client. Client should add some test 571 * identifier to the ID to avoid conflict with an existing real client ID. 572 * @throws IllegalArgumentException If the packageName is already an serverless remote task 573 * client or if the client ID is already used. 574 * 575 * @hide 576 */ 577 @TestApi 578 @FlaggedApi(FLAG_SERVERLESS_REMOTE_ACCESS) 579 @RequiresPermission(Car.PERMISSION_CONTROL_REMOTE_ACCESS) addServerlessRemoteTaskClient(@onNull String packageName, @NonNull String clientId)580 public void addServerlessRemoteTaskClient(@NonNull String packageName, 581 @NonNull String clientId) { 582 try { 583 mService.addServerlessRemoteTaskClient(packageName, clientId); 584 } catch (RemoteException e) { 585 handleRemoteExceptionFromCarService(e); 586 } 587 } 588 589 /** 590 * For testing only. Removes a package as serverless remote task client. 591 * 592 * @param packageName The package name for the previously added test serverless remote task 593 * client. 594 * 595 * @hide 596 */ 597 @TestApi 598 @FlaggedApi(FLAG_SERVERLESS_REMOTE_ACCESS) 599 @RequiresPermission(Car.PERMISSION_CONTROL_REMOTE_ACCESS) removeServerlessRemoteTaskClient(@onNull String packageName)600 public void removeServerlessRemoteTaskClient(@NonNull String packageName) { 601 try { 602 mService.removeServerlessRemoteTaskClient(packageName); 603 } catch (RemoteException e) { 604 handleRemoteExceptionFromCarService(e); 605 } 606 } 607 608 /** 609 * The schedule information, which contains the schedule ID, the task data, the scheduling 610 * start time, frequency and counts. 611 * 612 * @hide 613 */ 614 @SystemApi 615 @FlaggedApi(FLAG_SERVERLESS_REMOTE_ACCESS) 616 public static final class ScheduleInfo { 617 @NonNull 618 public static final Duration PERIODIC_DAILY = Duration.ofDays(1); 619 @NonNull 620 public static final Duration PERIODIC_WEEKLY = Duration.ofDays(7); 621 622 /** 623 * The builder for {@link ScheduleInfo}. 624 */ 625 @FlaggedApi(FLAG_SERVERLESS_REMOTE_ACCESS) 626 public static final class Builder { 627 private String mScheduleId; 628 private @TaskType int mTaskType; 629 private byte[] mTaskData = new byte[0]; 630 // By default the task is to be executed once. 631 private int mCount = 1; 632 private long mStartTimeInEpochSeconds; 633 private Duration mPeriodic = Duration.ZERO; 634 private boolean mBuilderUsed; 635 636 /** 637 * Creates the builder for {@link ScheduleInfo}. 638 * 639 * <p>By default the task will be executed once at the {@code startTime}. 640 * 641 * <p>If {@code count} is not 1, the task will be executed multiple times at 642 * {@code startTime}, {@code startTime + periodic}, {@code startTime + periodic * 2} 643 * etc. 644 * 645 * <p>Note that all tasks scheduled in the past will not be executed. E.g. if the 646 * {@code startTime} is in the past and task is scheduled to be executed once, the task 647 * will not be executed. If the {@code startTime} is 6:00 am, the {@code periodic} is 648 * 1h, the {@code count} is 3, and the current time is 7:30 am. The first two scheduled 649 * time: 6:00am, 7:00am already past, so the task will only be executed once at 8:00am. 650 * 651 * <p>Note that {@code startTime} is an eopch time not containing time zone info. 652 * Changing the timezone will not affect the scheduling. 653 * 654 * <p>If the client always want the task to be executed at 3:00pm relative to the 655 * current Android timezone, the client must listen to {@code ACTION_TIMEZONE_CHANGED}. 656 * It must unschedule and reschedule the task when time zone is changed. 657 * 658 * <p>It is expected that the other device has the same time concept as Android. 659 * Optionally, the VHAL property {@code EPOCH_TIME} can be used to sync the time. 660 * 661 * @param scheduleId A unique ID to identify this scheduling. Must be unique among all 662 * pending schedules for this client. 663 * @param mStartTimeInEpochSeconds When the task is scheduled to be executed in epoch 664 * seconds. It is not guaranteed that the task will be executed exactly at this 665 * time (or be executed at all). Typically the task will be executed at a time 666 * slightly later than the scheduled time due to the time spent waking up Android 667 * system and starting the remote task client. 668 */ 669 @FlaggedApi(FLAG_SERVERLESS_REMOTE_ACCESS) Builder(@onNull String scheduleId, @TaskType int taskType, long startTimeInEpochSeconds)670 public Builder(@NonNull String scheduleId, @TaskType int taskType, 671 long startTimeInEpochSeconds) { 672 Preconditions.checkArgument(scheduleId != null, "scheduleId must not be null"); 673 Preconditions.checkArgument( 674 taskType == TASK_TYPE_CUSTOM || taskType == TASK_TYPE_ENTER_GARAGE_MODE, 675 "unsupported task type: " + taskType); 676 Preconditions.checkArgument(startTimeInEpochSeconds > 0, 677 "startTimeInEpochSeconds must > 0"); 678 mScheduleId = scheduleId; 679 mTaskType = taskType; 680 mStartTimeInEpochSeconds = startTimeInEpochSeconds; 681 } 682 683 /** 684 * Sets the task data. 685 * 686 * @param taskData The opaque task data that will be sent back via 687 * {@link onRemoteTaskRequested} when the task is to be executed. This field is 688 * ignored if task type is ENTER_GARAGE_MODE. If this is not set, task data is 689 * empty. 690 */ 691 @FlaggedApi(FLAG_SERVERLESS_REMOTE_ACCESS) 692 @NonNull setTaskData(@onNull byte[] taskData)693 public Builder setTaskData(@NonNull byte[] taskData) { 694 Preconditions.checkArgument(taskData != null, "taskData must not be null"); 695 mTaskData = taskData.clone(); 696 return this; 697 } 698 699 /** 700 * Sets how many times the task is scheduled to be executed. 701 * 702 * <p>Note that if {@code startTime} is in the past, the actual task execution count 703 * might be lower than the specified value since all the scheduled tasks in the past 704 * will be ignored. 705 * 706 * @param count How many times the task is scheduled to be executed. A special value of 707 * 0 means the count is infinite. 708 * @return the builder. 709 */ 710 @FlaggedApi(FLAG_SERVERLESS_REMOTE_ACCESS) 711 @NonNull setCount(int count)712 public Builder setCount(int count) { 713 Preconditions.checkArgument(count >= 0, "count must not be negative"); 714 mCount = count; 715 return this; 716 } 717 718 /** 719 * Sets the interval between two scheduled tasks. 720 * 721 * <p>This is ignored if {@code count} is 1. 722 * 723 * @param periodic The interval between two scheduled tasks. Can be 724 * {@link PERIODIC_DAILY} or {@link PERIODIC_WEEKLY} or any custom interval. 725 * @return the builder. 726 */ 727 @FlaggedApi(FLAG_SERVERLESS_REMOTE_ACCESS) 728 @NonNull setPeriodic(@onNull Duration periodic)729 public Builder setPeriodic(@NonNull Duration periodic) { 730 Preconditions.checkArgument(periodic != null, "periodic must not be null"); 731 Preconditions.checkArgument(!periodic.isNegative(), 732 "periodic must not be negative"); 733 mPeriodic = periodic; 734 return this; 735 } 736 737 /** 738 * Builds the {@link ScheduleInfo}. 739 */ 740 @FlaggedApi(FLAG_SERVERLESS_REMOTE_ACCESS) 741 @NonNull build()742 public ScheduleInfo build() { 743 if (mBuilderUsed) { 744 throw new IllegalStateException( 745 "build is only supposed to be called once on one builder, use a new " 746 + "builder instance instead"); 747 } 748 mBuilderUsed = true; 749 if (mCount == 1) { 750 mPeriodic = Duration.ZERO; 751 } 752 return new ScheduleInfo(this); 753 } 754 }; 755 756 private String mScheduleId; 757 private @TaskType int mTaskType; 758 private byte[] mTaskData; 759 private int mCount; 760 private long mStartTimeInEpochSeconds; 761 private Duration mPeriodic; 762 763 @FlaggedApi(FLAG_SERVERLESS_REMOTE_ACCESS) 764 @NonNull getScheduleId()765 public String getScheduleId() { 766 return mScheduleId; 767 } 768 769 @FlaggedApi(FLAG_SERVERLESS_REMOTE_ACCESS) getTaskType()770 public @TaskType int getTaskType() { 771 return mTaskType; 772 } 773 774 @FlaggedApi(FLAG_SERVERLESS_REMOTE_ACCESS) 775 @NonNull getTaskData()776 public byte[] getTaskData() { 777 return mTaskData; 778 } 779 780 @FlaggedApi(FLAG_SERVERLESS_REMOTE_ACCESS) getCount()781 public int getCount() { 782 return mCount; 783 } 784 785 @FlaggedApi(FLAG_SERVERLESS_REMOTE_ACCESS) getStartTimeInEpochSeconds()786 public long getStartTimeInEpochSeconds() { 787 return mStartTimeInEpochSeconds; 788 } 789 790 @FlaggedApi(FLAG_SERVERLESS_REMOTE_ACCESS) 791 @NonNull getPeriodic()792 public Duration getPeriodic() { 793 return mPeriodic; 794 } 795 ScheduleInfo(Builder builder)796 private ScheduleInfo(Builder builder) { 797 mScheduleId = builder.mScheduleId; 798 mTaskType = builder.mTaskType; 799 if (builder.mTaskData != null) { 800 mTaskData = builder.mTaskData; 801 } else { 802 mTaskData = new byte[0]; 803 } 804 mCount = builder.mCount; 805 mStartTimeInEpochSeconds = builder.mStartTimeInEpochSeconds; 806 mPeriodic = builder.mPeriodic; 807 } 808 }; 809 810 /** 811 * Exception that might be thrown by {@link InVehicleTaskScheduler} methods. 812 * 813 * <p>This indicates that something is wrong while communicating with the external device 814 * which is responsible for managing task schedule or the external device reports some error. 815 * 816 * @hide 817 */ 818 @SystemApi 819 @FlaggedApi(FLAG_SERVERLESS_REMOTE_ACCESS) 820 public static final class InVehicleTaskSchedulerException extends Exception { InVehicleTaskSchedulerException(Throwable cause)821 InVehicleTaskSchedulerException(Throwable cause) { 822 super(cause); 823 } 824 } 825 826 /** 827 * For testing only. Check whether the VHAL property: {@code VEHICLE_IN_USE} is supported. 828 * 829 * This property must be supported if serverless remote access is supported. 830 * 831 * @hide 832 */ 833 @TestApi 834 @FlaggedApi(FLAG_SERVERLESS_REMOTE_ACCESS) 835 @RequiresPermission(Car.PERMISSION_CONTROL_REMOTE_ACCESS) isVehicleInUseSupported()836 public boolean isVehicleInUseSupported() { 837 try { 838 return mService.isVehicleInUseSupported(); 839 } catch (RemoteException e) { 840 return handleRemoteExceptionFromCarService(e, false); 841 } 842 } 843 844 /** 845 * For testing only. Check whether the VHAL property: {@code SHUTDOWN_REQUEST} is supported. 846 * 847 * This property must be supported if serverless remote access is supported. 848 * 849 * @hide 850 */ 851 @TestApi 852 @FlaggedApi(FLAG_SERVERLESS_REMOTE_ACCESS) 853 @RequiresPermission(Car.PERMISSION_CONTROL_REMOTE_ACCESS) isShutdownRequestSupported()854 public boolean isShutdownRequestSupported() { 855 try { 856 return mService.isShutdownRequestSupported(); 857 } catch (RemoteException e) { 858 return handleRemoteExceptionFromCarService(e, false); 859 } 860 } 861 862 /** 863 * A scheduler for scheduling a task to be executed later. 864 * 865 * <p>It schedules a task via sending a scheduled task message to a device in the same vehicle, 866 * but external to Android. 867 * 868 * <p>This is only supported for pre-configured remote task serverless clients. 869 * 870 * @hide 871 */ 872 @SystemApi 873 @FlaggedApi(FLAG_SERVERLESS_REMOTE_ACCESS) 874 public final class InVehicleTaskScheduler { 875 /** 876 * Please use {@link getInVehicleTaskScheduler} to create. 877 */ InVehicleTaskScheduler()878 private InVehicleTaskScheduler() {} 879 880 /** 881 * Gets supported schedule task type. 882 * 883 * @return a list of supported task types as defined by {@link TaskType}. 884 * @throws InVehicleTaskSchedulerException if unable to get supported schedule task type. 885 * 886 * @hide 887 */ 888 @SystemApi 889 @FlaggedApi(FLAG_SERVERLESS_REMOTE_ACCESS) 890 @RequiresPermission(Car.PERMISSION_CONTROL_REMOTE_ACCESS) getSupportedTaskTypes()891 public @NonNull int[] getSupportedTaskTypes() throws InVehicleTaskSchedulerException { 892 try { 893 return mService.getSupportedTaskTypesForScheduling(); 894 } catch (RemoteException e) { 895 return handleRemoteExceptionFromCarService(e, EMPTY_INT_ARRAY); 896 } catch (ServiceSpecificException e) { 897 throw new InVehicleTaskSchedulerException(e); 898 } 899 } 900 901 /** 902 * Schedules a task to be executed later even when the vehicle is off. 903 * 904 * <p>This sends a scheduled task message to a device external to Android so that the device 905 * can wake up Android and deliver the task through {@code onRemoteTaskRequested}. 906 * 907 * <p>Note that the remote task will be executed even when the vehicle is in use. The task 908 * must not affect the system performance or driving safety. 909 * 910 * <p>Note that the scheduled task execution is on a best-effort basis. Multiple situations 911 * might cause the task not to execute successfully: 912 * 913 * <ul> 914 * <li>The vehicle is low on battery and the other device decides not to wake up Android. 915 * <li>User turns off vehicle while the task is executing. 916 * <li>The task logic itself fails. 917 * 918 * <p>The framework does not provide a mechanism to report the task status or task result. 919 * In order for the user to check whether the task is executed successfully, the client must 920 * record in its persistent storage the task's status/result during 921 * {@code onRemoteTaskRequested}. 922 * 923 * <p>For example, if the scheduled task is to update some of the application's resources. 924 * When the application is opened by the user, it may check whether the resource is the 925 * latest version, if not, it may check whether a scheduled update task exists. If not, it 926 * may schedule a new task at midnight at the same day to update the resource. The 927 * application uses the persistent resource version to decide whether the previous update 928 * succeeded. 929 * 930 * <p>All the tasks for this client will be unscheduled if the client app is removed. 931 * 932 * <p>If the client app is updated, the task will still be scheduled, so the client app must 933 * make sure the logic handling remote task is backward compatible. 934 * 935 * <p>It is possible that the client app's persistent data might be removed, e.g. during 936 * a factory reset. If the client app stores task-related information, it will be lost. The 937 * scheduled task will still be delivered to the app as expected unless client explicitly 938 * unschedule all the scheduled tasks when it detects that the data is erased. 939 * 940 * @param scheduleInfo The schedule information. 941 * 942 * @throws IllegalArgumentException if a pending schedule with the same {@code scheduleId} 943 * for this client exists. 944 * @throws InVehicleTaskSchedulerException if unable to schedule the task. 945 * 946 * @hide 947 */ 948 @SystemApi 949 @FlaggedApi(FLAG_SERVERLESS_REMOTE_ACCESS) 950 @RequiresPermission(Car.PERMISSION_CONTROL_REMOTE_ACCESS) scheduleTask(@onNull ScheduleInfo scheduleInfo)951 public void scheduleTask(@NonNull ScheduleInfo scheduleInfo) 952 throws InVehicleTaskSchedulerException { 953 Preconditions.checkArgument(scheduleInfo != null, "scheduleInfo cannot be null"); 954 TaskScheduleInfo taskScheduleInfo = toTaskScheduleInfo(scheduleInfo); 955 try { 956 mService.scheduleTask(taskScheduleInfo); 957 } catch (RemoteException e) { 958 handleRemoteExceptionFromCarService(e); 959 } catch (ServiceSpecificException e) { 960 throw new InVehicleTaskSchedulerException(e); 961 } 962 } 963 964 /** 965 * Unschedules a scheduled task. 966 * 967 * @param scheduleId The ID for the schedule. 968 * 969 * <p>Does nothing if a pending schedule with {@code scheduleId} does not exist. 970 * 971 * @throws InVehicleTaskSchedulerException if failed to unschedule the tasks. 972 * 973 * @hide 974 */ 975 @SystemApi 976 @FlaggedApi(FLAG_SERVERLESS_REMOTE_ACCESS) 977 @RequiresPermission(Car.PERMISSION_CONTROL_REMOTE_ACCESS) unscheduleTask(@onNull String scheduleId)978 public void unscheduleTask(@NonNull String scheduleId) 979 throws InVehicleTaskSchedulerException { 980 Preconditions.checkArgument(scheduleId != null, "scheduleId cannot be null"); 981 try { 982 mService.unscheduleTask(scheduleId); 983 } catch (RemoteException e) { 984 handleRemoteExceptionFromCarService(e); 985 } catch (ServiceSpecificException e) { 986 throw new InVehicleTaskSchedulerException(e); 987 } 988 } 989 990 /** 991 * Unschedules all scheduled tasks for this client. 992 * 993 * @hide 994 * 995 * @throws InVehicleTaskSchedulerException if failed to unschedule the tasks. 996 */ 997 @SystemApi 998 @FlaggedApi(FLAG_SERVERLESS_REMOTE_ACCESS) 999 @RequiresPermission(Car.PERMISSION_CONTROL_REMOTE_ACCESS) unscheduleAllTasks()1000 public void unscheduleAllTasks() throws InVehicleTaskSchedulerException { 1001 try { 1002 mService.unscheduleAllTasks(); 1003 } catch (RemoteException e) { 1004 handleRemoteExceptionFromCarService(e); 1005 } catch (ServiceSpecificException e) { 1006 throw new InVehicleTaskSchedulerException(e); 1007 } 1008 } 1009 1010 /** 1011 * Returns whether the specified task is scheduled. 1012 * 1013 * @param scheduleId The ID for the schedule. 1014 * @return {@code true} if the task was scheduled and pending to be executed. 1015 * @throws InVehicleTaskSchedulerException if failed to check whether the task is scheduled. 1016 * 1017 * @hide 1018 */ 1019 @SystemApi 1020 @FlaggedApi(FLAG_SERVERLESS_REMOTE_ACCESS) 1021 @RequiresPermission(Car.PERMISSION_CONTROL_REMOTE_ACCESS) isTaskScheduled(@onNull String scheduleId)1022 public boolean isTaskScheduled(@NonNull String scheduleId) 1023 throws InVehicleTaskSchedulerException { 1024 Preconditions.checkArgument(scheduleId != null, "scheduleId cannot be null"); 1025 try { 1026 return mService.isTaskScheduled(scheduleId); 1027 } catch (RemoteException e) { 1028 return handleRemoteExceptionFromCarService(e, false); 1029 } catch (ServiceSpecificException e) { 1030 throw new InVehicleTaskSchedulerException(e); 1031 } 1032 } 1033 1034 /** 1035 * Gets all pending scheduled tasks for this client. 1036 * 1037 * <p>The finished scheduled tasks will not be included. 1038 * 1039 * @return A list of schedule info. 1040 * 1041 * @throws InVehicleTaskSchedulerException if unable to get the scheduled tasks. 1042 * 1043 * @hide 1044 */ 1045 @SystemApi 1046 @FlaggedApi(FLAG_SERVERLESS_REMOTE_ACCESS) 1047 @RequiresPermission(Car.PERMISSION_CONTROL_REMOTE_ACCESS) 1048 @NonNull getAllPendingScheduledTasks()1049 public List<ScheduleInfo> getAllPendingScheduledTasks() 1050 throws InVehicleTaskSchedulerException { 1051 List<ScheduleInfo> scheduleInfoList = new ArrayList<>(); 1052 try { 1053 List<TaskScheduleInfo> taskScheduleInfoList = 1054 mService.getAllPendingScheduledTasks(); 1055 for (int i = 0; i < taskScheduleInfoList.size(); i++) { 1056 scheduleInfoList.add(fromTaskScheduleInfo(taskScheduleInfoList.get(i))); 1057 } 1058 return scheduleInfoList; 1059 } catch (RemoteException e) { 1060 return handleRemoteExceptionFromCarService(e, scheduleInfoList); 1061 } catch (ServiceSpecificException e) { 1062 throw new InVehicleTaskSchedulerException(e); 1063 } 1064 } 1065 fromTaskScheduleInfo(TaskScheduleInfo taskScheduleInfo)1066 private static ScheduleInfo fromTaskScheduleInfo(TaskScheduleInfo taskScheduleInfo) { 1067 return new ScheduleInfo.Builder(taskScheduleInfo.scheduleId, 1068 taskScheduleInfo.taskType, taskScheduleInfo.startTimeInEpochSeconds) 1069 .setTaskData(taskScheduleInfo.taskData).setCount(taskScheduleInfo.count) 1070 .setPeriodic(Duration.ofSeconds(taskScheduleInfo.periodicInSeconds)).build(); 1071 } 1072 toTaskScheduleInfo(ScheduleInfo scheduleInfo)1073 private static TaskScheduleInfo toTaskScheduleInfo(ScheduleInfo scheduleInfo) { 1074 TaskScheduleInfo taskScheduleInfo = new TaskScheduleInfo(); 1075 taskScheduleInfo.taskType = scheduleInfo.getTaskType(); 1076 taskScheduleInfo.scheduleId = scheduleInfo.getScheduleId(); 1077 taskScheduleInfo.taskData = scheduleInfo.getTaskData(); 1078 taskScheduleInfo.count = scheduleInfo.getCount(); 1079 taskScheduleInfo.startTimeInEpochSeconds = scheduleInfo.getStartTimeInEpochSeconds(); 1080 taskScheduleInfo.periodicInSeconds = scheduleInfo.getPeriodic().getSeconds(); 1081 return taskScheduleInfo; 1082 } 1083 } 1084 } 1085