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