/* * Copyright (C) 2022 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package android.car.remoteaccess; import static android.car.feature.Flags.FLAG_SERVERLESS_REMOTE_ACCESS; import static com.android.car.internal.common.CommonConstants.EMPTY_INT_ARRAY; import android.annotation.CallbackExecutor; import android.annotation.FlaggedApi; import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.RequiresPermission; import android.annotation.SystemApi; import android.annotation.TestApi; import android.car.Car; import android.car.CarManagerBase; import android.car.feature.FeatureFlags; import android.car.feature.FeatureFlagsImpl; import android.os.Binder; import android.os.IBinder; import android.os.RemoteException; import android.os.ServiceSpecificException; import android.util.Slog; import com.android.car.internal.ICarBase; import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.util.Preconditions; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; import java.time.Duration; import java.util.ArrayList; import java.util.List; import java.util.concurrent.Executor; /** * CarRemoteAccessManager allows applications to listen to remote task requests even while Android * System is not running. * *
The remote task client registers to {@link CarRemoteAccessManager} to listen to remote access * events. At {@link RemoteTaskClientCallback#onRegistrationUpdated} it is required to share * {@code serviceId}, {@code deviceId} and {@code clientId} with the cloud service which will use * the IDs to wake the vehicle. At {@link RemoteTaskClientCallback#onRemoteTaskRequested}, it starts * executing the given task. It is supposed to call {@link #reportRemoteTaskDone(String)} when it * finishes the given task. Once the task completion is reported or the timeout expires, Android * System goes back to either the previous power state or the specified power state. * *
Note that the remote task will be executed even when the vehicle is actively in use, not * nessarily when the vehicle is off. Remote task client must make sure the task to be executed * will not affect the system performance or affect driving safety. If certain task must be * executed while the vehicle is off, the remote task client must check VHAL property * {@code VEHICLE_IN_USE} and/or check igntition state via {@code VehicleIgnitionState}. * *
Note: all remote task clients must run as system user. * *
A serverless setup might also be supported if the RRO overlay for * remote_access_serverless_client_map exists which provides a map from serverless client ID to * their package names. * *
Here the term 'remote' refers to the source of task coming from outside of the * Android system, it does not necessarily means the task comes from Internet. In the serverless * setup, no cloud service is required. Another device within the same vehicle, but outside the * Android system is the issuer for the remote task. * *
For serverless setup, there is a pre-configured set of serverless remote task clients. They * register to {@link CarRemoteAccessManager} to listen to remote access events. * {@link RemoteTaskClientCallback#onServerlessClientRegistered} will be called instead of * {@link RemoteTaskClientCallback#onRegistrationUpdated} and there is no cloud service involved. * {@link RemoteTaskClientCallback#onRemoteTaskRequested} will be invoked when the task is to be * executed. It is supposed to call {@link #reportRemoteTaskDone(String)} when it * finishes the given task. Once the task completion is reported or the timeout expires, Android * system goes back to either the previous power state or the specified power state. * *
For serverless setup, if {@link isTaskScheduleSupported} returns {@code true}, client may * use {@link InVehicleTaskScheduler#scheduleTask} to schedule a remote task to be executed later. * If {@link isTaskScheduleSupported} returns {@code false}, it is assumed there exists some other * channel outside of the Android system for task scheduling. */ public final class CarRemoteAccessManager extends CarManagerBase { private static final String TAG = CarRemoteAccessManager.class.getSimpleName(); private final InVehicleTaskScheduler mInVehicleTaskScheduler = new InVehicleTaskScheduler(); /** * The system remains ON after completing the remote tasks. * * @hide */ @SystemApi public static final int NEXT_POWER_STATE_ON = 1; /** * The system shuts down to power off after completing the remote tasks. * * @hide */ @SystemApi public static final int NEXT_POWER_STATE_OFF = 2; /** * The system goes into deep sleep after completing the remote tasks. * * @hide */ @SystemApi public static final int NEXT_POWER_STATE_SUSPEND_TO_RAM = 3; /** * The system goes into hibernation after completing the remote tasks. * * @hide */ @SystemApi public static final int NEXT_POWER_STATE_SUSPEND_TO_DISK = 4; /** @hide */ @Retention(RetentionPolicy.SOURCE) @IntDef(prefix = "NEXT_POWER_STATE_", value = { NEXT_POWER_STATE_ON, NEXT_POWER_STATE_OFF, NEXT_POWER_STATE_SUSPEND_TO_RAM, NEXT_POWER_STATE_SUSPEND_TO_DISK, }) @Target({ElementType.TYPE_USE}) public @interface NextPowerState {} /** * Custom task. The task data is opaque to framework. * * @hide */ @SystemApi @FlaggedApi(FLAG_SERVERLESS_REMOTE_ACCESS) public static final int TASK_TYPE_CUSTOM = 0; /** * Schedule to enter garage mode if the vehicle is off. * * taskData is ignore for this type. * * @hide */ @SystemApi @FlaggedApi(FLAG_SERVERLESS_REMOTE_ACCESS) public static final int TASK_TYPE_ENTER_GARAGE_MODE = 1; /** @hide */ @IntDef(prefix = {"TASK_TYPE_"}, value = { TASK_TYPE_CUSTOM, TASK_TYPE_ENTER_GARAGE_MODE, }) @Retention(RetentionPolicy.SOURCE) public @interface TaskType {} private final ICarRemoteAccessService mService; private final Object mLock = new Object(); private FeatureFlags mFeatureFlags = new FeatureFlagsImpl(); /** * Sets fake feature flag for unit testing. * * @hide */ @VisibleForTesting public void setFeatureFlags(FeatureFlags fakeFeatureFlags) { mFeatureFlags = fakeFeatureFlags; } private final class CarRemoteAccessCallback extends ICarRemoteAccessCallback.Stub { @Override public void onClientRegistrationUpdated(RemoteTaskClientRegistrationInfo registrationInfo) { RemoteTaskClientCallback callback; Executor executor; synchronized (mLock) { if (mRemoteTaskClientCallback == null || mExecutor == null) { Slog.w(TAG, "Cannot call onRegistrationUpdated because no remote task client " + "is registered"); return; } mCurrentClientId = registrationInfo.getClientId(); callback = mRemoteTaskClientCallback; executor = mExecutor; } Binder.clearCallingIdentity(); executor.execute(() -> callback.onRegistrationUpdated(registrationInfo)); } @Override public void onServerlessClientRegistered(String clientId) { RemoteTaskClientCallback callback; Executor executor; synchronized (mLock) { if (mRemoteTaskClientCallback == null || mExecutor == null) { Slog.w(TAG, "Cannot call onRegistrationUpdated because no remote task client " + "is registered"); return; } mCurrentClientId = clientId; callback = mRemoteTaskClientCallback; executor = mExecutor; } if (mFeatureFlags.serverlessRemoteAccess()) { Binder.clearCallingIdentity(); executor.execute(() -> callback.onServerlessClientRegistered()); } else { Slog.e(TAG, "Serverless remote access flag is not enabled, " + "the callback must not be called"); } } @Override public void onClientRegistrationFailed() { RemoteTaskClientCallback callback; Executor executor; synchronized (mLock) { if (mRemoteTaskClientCallback == null || mExecutor == null) { Slog.w(TAG, "Cannot call onRegistrationFailed because no remote task client " + "is registered"); return; } callback = mRemoteTaskClientCallback; executor = mExecutor; } Binder.clearCallingIdentity(); executor.execute(() -> callback.onRegistrationFailed()); } @Override public void onRemoteTaskRequested(String clientId, String taskId, byte[] data, int taskMaxDurationInSec) { RemoteTaskClientCallback callback; Executor executor; synchronized (mLock) { if (mCurrentClientId == null || !mCurrentClientId.equals(clientId)) { Slog.w(TAG, "Received a task for a mismatched client ID(" + clientId + "): the current client ID = " + mCurrentClientId); return; } callback = mRemoteTaskClientCallback; executor = mExecutor; } if (callback == null || executor == null) { Slog.w(TAG, "Cannot call onRemoteTaskRequested because no remote task client is " + "registered"); return; } Binder.clearCallingIdentity(); executor.execute(() -> callback.onRemoteTaskRequested(taskId, data, taskMaxDurationInSec)); } @Override public void onShutdownStarting() { String clientId; RemoteTaskClientCallback callback; Executor executor; synchronized (mLock) { clientId = mCurrentClientId; callback = mRemoteTaskClientCallback; executor = mExecutor; } if (clientId == null || callback == null || executor == null) { Slog.w(TAG, "Cannot call onShutdownStarting because no remote task client is " + "registered"); return; } Binder.clearCallingIdentity(); executor.execute(() -> callback.onShutdownStarting(new MyCompletableRemoteTaskFuture(clientId))); } } private final ICarRemoteAccessCallback mCarRemoteAccessCallback = new CarRemoteAccessCallback(); @GuardedBy("mLock") private RemoteTaskClientCallback mRemoteTaskClientCallback; @GuardedBy("mLock") private Executor mExecutor; @GuardedBy("mLock") private String mCurrentClientId; /** * An interface passed from {@link RemoteTaskClientCallback}. * *
The remote task client uses this interface to tell {@link CarRemoteAccessManager} that it * finalized the pending remote tasks. */ public interface CompletableRemoteTaskFuture { /** * Tells {@link CarRemoteAccessManager} that the remote task client finalized the pending * remoate tasks. */ void complete(); } private final class MyCompletableRemoteTaskFuture implements CompletableRemoteTaskFuture { private final String mClientIdToComplete; MyCompletableRemoteTaskFuture(String clientId) { mClientIdToComplete = clientId; } @Override public void complete() { try { mService.confirmReadyForShutdown(mClientIdToComplete); } catch (RemoteException e) { handleRemoteExceptionFromCarService(e); } } } /** * Listener for remote task events. */ public interface RemoteTaskClientCallback { /** * This is called when the remote task client is successfully registered or the client ID is * updated by AAOS. * *
For a serverless remote task client, the {@link onServerlessClientRegistered} will be * called instead of this. * * @param info {@link RemoteTaskClientRegistrationInfo} which contains wake-up service ID, * vehicle ID, processor ID and client ID. */ void onRegistrationUpdated(@NonNull RemoteTaskClientRegistrationInfo info); /** * This is called when a pre-configured serverless remote task client is registered. * *
The serverless remote task client is configured via including a runtime config file * at {@code /vendor/etc/} */ @FlaggedApi(FLAG_SERVERLESS_REMOTE_ACCESS) default void onServerlessClientRegistered() { Slog.i(TAG, "onServerlessClientRegistered called"); } /** * This is called when registering the remote task client fails. */ void onRegistrationFailed(); /** * This is called when a wake-up request is received/processed. * * @param taskId ID of the task that is requested by the remote task server. * @param data Extra data passed along with the wake-up request. * @param taskMaxDurationInSec The timeout before AAOS goes back to the previous power * state. */ void onRemoteTaskRequested(@NonNull String taskId, @Nullable byte[] data, int taskMaxDurationInSec); /** * This is called when the device is about to shutdown. * *
The remote task client should finalize the ongoing tasks, if any, and complete the * given future within 5 seconds. After the given timeout, the Android system will shutdown, * anyway. * * @param future {@link CompletableRemoteTaskFuture} used by the remote task client to * notify CarRemoteAccessManager that all pending remote tasks are finalized. */ void onShutdownStarting(@NonNull CompletableRemoteTaskFuture future); } /** @hide */ public CarRemoteAccessManager(ICarBase car, IBinder service) { super(car); mService = ICarRemoteAccessService.Stub.asInterface(service); } /** @hide */ @Override public void onCarDisconnected() { // Nothing to do. } /** * Sets the remote task client represented as {@link RemoteTaskClientCallback}. * * @param executor Executor on which {@code callback} is executed. * @param callback {@link RemoteTaskClientCallback} that listens to remote task events. * @throws IllegalStateException When a remote task client is already set. * @throws IllegalArgumentException When the given callback or the executor is {@code null}. */ @RequiresPermission(Car.PERMISSION_USE_REMOTE_ACCESS) public void setRemoteTaskClient(@NonNull @CallbackExecutor Executor executor, @NonNull RemoteTaskClientCallback callback) { Preconditions.checkArgument(executor != null, "Executor cannot be null"); Preconditions.checkArgument(callback != null, "Callback cannot be null"); synchronized (mLock) { if (mRemoteTaskClientCallback != null) { throw new IllegalStateException("Remote task client must be cleared first"); } mRemoteTaskClientCallback = callback; mExecutor = executor; } try { mService.addCarRemoteTaskClient(mCarRemoteAccessCallback); } catch (RemoteException e) { synchronized (mLock) { mRemoteTaskClientCallback = null; mExecutor = null; } handleRemoteExceptionFromCarService(e); } } /** * Clears the remote task client previously set via {@link #setRemoteTaskClient(Executor, * RemoteTaskClientCallback)}. * *
After the remote task client is cleared, all tasks associated with the previous client * will not be delivered and the client must not call {@code reportRemoteTaskDone} with the * task ID associated with the previous client ID. * * @throws IllegalStateException if {@code callback} is not registered. */ @RequiresPermission(Car.PERMISSION_USE_REMOTE_ACCESS) public void clearRemoteTaskClient() { synchronized (mLock) { if (mRemoteTaskClientCallback == null) { Slog.w(TAG, "No registered remote task client to clear"); return; } mRemoteTaskClientCallback = null; mExecutor = null; mCurrentClientId = null; } try { mService.removeCarRemoteTaskClient(mCarRemoteAccessCallback); } catch (RemoteException e) { handleRemoteExceptionFromCarService(e); } } /** * Reports that remote task execution is completed, so that the vehicle will go back to the * power state before the wake-up. * * @param taskId ID of the remote task which has been completed. * @throws IllegalArgumentException If {@code taskId} is null. * @throws IllegalStateException If the remote task client is not registered or not woken up. */ @RequiresPermission(Car.PERMISSION_USE_REMOTE_ACCESS) public void reportRemoteTaskDone(@NonNull String taskId) { Preconditions.checkArgument(taskId != null, "Task ID cannot be null"); String currentClientId; synchronized (mLock) { if (mCurrentClientId == null) { Slog.w(TAG, "Failed to report remote task completion: no remote task client is " + "registered"); throw new IllegalStateException("No remote task client is registered"); } currentClientId = mCurrentClientId; } try { mService.reportRemoteTaskDone(currentClientId, taskId); } catch (IllegalStateException e) { Slog.w(TAG, "Task ID(" + taskId + ") is not valid", e); throw e; } catch (RemoteException e) { handleRemoteExceptionFromCarService(e); } } /** * Sets the power state after all the remote tasks are completed. * *
By default, the system returns to the previous power state from which the system woke up. * If the given power state is {@code NEXT_POWER_STATE_ON}, Garage Mode is not executed. * * @param nextPowerState The next power state after the remote task is completed. * @param runGarageMode Whether to run Garage Mode when switching to the next power state. * @throws IllegalArgumentException If {@code nextPowerState} is not valid. * @throws IllegalStateException If the remote task client is not registered or not woken up. * * @hide */ @SystemApi @RequiresPermission(Car.PERMISSION_CONTROL_REMOTE_ACCESS) public void setPowerStatePostTaskExecution(@NextPowerState int nextPowerState, boolean runGarageMode) { try { mService.setPowerStatePostTaskExecution(nextPowerState, runGarageMode); } catch (RemoteException e) { handleRemoteExceptionFromCarService(e); } } /** * Returns whether task scheduling is supported. * *
If this returns {@code true}, user may use * {@link InVehicleTaskScheduler#scheduleTask} to schedule a task to be executed at a later * time. If the device is off when the task is scheduled to be executed, the device will be * woken up to execute the task. * * @return {@code true} if serverless remote task scheduling is supported by the HAL and the * caller is a pre-configured serverless remote task client. * * @hide */ @SystemApi @FlaggedApi(FLAG_SERVERLESS_REMOTE_ACCESS) @RequiresPermission(Car.PERMISSION_CONTROL_REMOTE_ACCESS) public boolean isTaskScheduleSupported() { try { return mService.isTaskScheduleSupported(); } catch (RemoteException e) { return handleRemoteExceptionFromCarService(e, false); } } /** * Gets a in vehicle task scheduler that can be used to schedule a task to be executed later. * *
This is only supported for pre-configured remote task serverless clients. * *
See {@link InVehicleTaskScheduler.scheduleTask} for usage. * * @return An in vehicle task scheduler or {@code null} if {@link isTaskScheduleSupported} is * {@code false}. * * @hide */ @SystemApi @FlaggedApi(FLAG_SERVERLESS_REMOTE_ACCESS) @RequiresPermission(Car.PERMISSION_CONTROL_REMOTE_ACCESS) @Nullable public InVehicleTaskScheduler getInVehicleTaskScheduler() { if (!isTaskScheduleSupported()) { Slog.w(TAG, "getInVehicleTaskScheduler: Task schedule is not supported, return null"); return null; } return mInVehicleTaskScheduler; } /** * For testing only. Adds a package as a new serverless remote task client. * * @param packageName The package name for the serverless remote task client. This should be a * test package name. * @param clientId An arbitrary client ID picked for the client. Client should add some test * identifier to the ID to avoid conflict with an existing real client ID. * @throws IllegalArgumentException If the packageName is already an serverless remote task * client or if the client ID is already used. * * @hide */ @TestApi @FlaggedApi(FLAG_SERVERLESS_REMOTE_ACCESS) @RequiresPermission(Car.PERMISSION_CONTROL_REMOTE_ACCESS) public void addServerlessRemoteTaskClient(@NonNull String packageName, @NonNull String clientId) { try { mService.addServerlessRemoteTaskClient(packageName, clientId); } catch (RemoteException e) { handleRemoteExceptionFromCarService(e); } } /** * For testing only. Removes a package as serverless remote task client. * * @param packageName The package name for the previously added test serverless remote task * client. * * @hide */ @TestApi @FlaggedApi(FLAG_SERVERLESS_REMOTE_ACCESS) @RequiresPermission(Car.PERMISSION_CONTROL_REMOTE_ACCESS) public void removeServerlessRemoteTaskClient(@NonNull String packageName) { try { mService.removeServerlessRemoteTaskClient(packageName); } catch (RemoteException e) { handleRemoteExceptionFromCarService(e); } } /** * The schedule information, which contains the schedule ID, the task data, the scheduling * start time, frequency and counts. * * @hide */ @SystemApi @FlaggedApi(FLAG_SERVERLESS_REMOTE_ACCESS) public static final class ScheduleInfo { @NonNull public static final Duration PERIODIC_DAILY = Duration.ofDays(1); @NonNull public static final Duration PERIODIC_WEEKLY = Duration.ofDays(7); /** * The builder for {@link ScheduleInfo}. */ @FlaggedApi(FLAG_SERVERLESS_REMOTE_ACCESS) public static final class Builder { private String mScheduleId; private @TaskType int mTaskType; private byte[] mTaskData = new byte[0]; // By default the task is to be executed once. private int mCount = 1; private long mStartTimeInEpochSeconds; private Duration mPeriodic = Duration.ZERO; private boolean mBuilderUsed; /** * Creates the builder for {@link ScheduleInfo}. * *
By default the task will be executed once at the {@code startTime}. * *
If {@code count} is not 1, the task will be executed multiple times at * {@code startTime}, {@code startTime + periodic}, {@code startTime + periodic * 2} * etc. * *
Note that all tasks scheduled in the past will not be executed. E.g. if the * {@code startTime} is in the past and task is scheduled to be executed once, the task * will not be executed. If the {@code startTime} is 6:00 am, the {@code periodic} is * 1h, the {@code count} is 3, and the current time is 7:30 am. The first two scheduled * time: 6:00am, 7:00am already past, so the task will only be executed once at 8:00am. * *
Note that {@code startTime} is an eopch time not containing time zone info. * Changing the timezone will not affect the scheduling. * *
If the client always want the task to be executed at 3:00pm relative to the * current Android timezone, the client must listen to {@code ACTION_TIMEZONE_CHANGED}. * It must unschedule and reschedule the task when time zone is changed. * *
It is expected that the other device has the same time concept as Android. * Optionally, the VHAL property {@code EPOCH_TIME} can be used to sync the time. * * @param scheduleId A unique ID to identify this scheduling. Must be unique among all * pending schedules for this client. * @param mStartTimeInEpochSeconds When the task is scheduled to be executed in epoch * seconds. It is not guaranteed that the task will be executed exactly at this * time (or be executed at all). Typically the task will be executed at a time * slightly later than the scheduled time due to the time spent waking up Android * system and starting the remote task client. */ @FlaggedApi(FLAG_SERVERLESS_REMOTE_ACCESS) public Builder(@NonNull String scheduleId, @TaskType int taskType, long startTimeInEpochSeconds) { Preconditions.checkArgument(scheduleId != null, "scheduleId must not be null"); Preconditions.checkArgument( taskType == TASK_TYPE_CUSTOM || taskType == TASK_TYPE_ENTER_GARAGE_MODE, "unsupported task type: " + taskType); Preconditions.checkArgument(startTimeInEpochSeconds > 0, "startTimeInEpochSeconds must > 0"); mScheduleId = scheduleId; mTaskType = taskType; mStartTimeInEpochSeconds = startTimeInEpochSeconds; } /** * Sets the task data. * * @param taskData The opaque task data that will be sent back via * {@link onRemoteTaskRequested} when the task is to be executed. This field is * ignored if task type is ENTER_GARAGE_MODE. If this is not set, task data is * empty. */ @FlaggedApi(FLAG_SERVERLESS_REMOTE_ACCESS) @NonNull public Builder setTaskData(@NonNull byte[] taskData) { Preconditions.checkArgument(taskData != null, "taskData must not be null"); mTaskData = taskData.clone(); return this; } /** * Sets how many times the task is scheduled to be executed. * *
Note that if {@code startTime} is in the past, the actual task execution count * might be lower than the specified value since all the scheduled tasks in the past * will be ignored. * * @param count How many times the task is scheduled to be executed. A special value of * 0 means the count is infinite. * @return the builder. */ @FlaggedApi(FLAG_SERVERLESS_REMOTE_ACCESS) @NonNull public Builder setCount(int count) { Preconditions.checkArgument(count >= 0, "count must not be negative"); mCount = count; return this; } /** * Sets the interval between two scheduled tasks. * *
This is ignored if {@code count} is 1. * * @param periodic The interval between two scheduled tasks. Can be * {@link PERIODIC_DAILY} or {@link PERIODIC_WEEKLY} or any custom interval. * @return the builder. */ @FlaggedApi(FLAG_SERVERLESS_REMOTE_ACCESS) @NonNull public Builder setPeriodic(@NonNull Duration periodic) { Preconditions.checkArgument(periodic != null, "periodic must not be null"); Preconditions.checkArgument(!periodic.isNegative(), "periodic must not be negative"); mPeriodic = periodic; return this; } /** * Builds the {@link ScheduleInfo}. */ @FlaggedApi(FLAG_SERVERLESS_REMOTE_ACCESS) @NonNull public ScheduleInfo build() { if (mBuilderUsed) { throw new IllegalStateException( "build is only supposed to be called once on one builder, use a new " + "builder instance instead"); } mBuilderUsed = true; if (mCount == 1) { mPeriodic = Duration.ZERO; } return new ScheduleInfo(this); } }; private String mScheduleId; private @TaskType int mTaskType; private byte[] mTaskData; private int mCount; private long mStartTimeInEpochSeconds; private Duration mPeriodic; @FlaggedApi(FLAG_SERVERLESS_REMOTE_ACCESS) @NonNull public String getScheduleId() { return mScheduleId; } @FlaggedApi(FLAG_SERVERLESS_REMOTE_ACCESS) public @TaskType int getTaskType() { return mTaskType; } @FlaggedApi(FLAG_SERVERLESS_REMOTE_ACCESS) @NonNull public byte[] getTaskData() { return mTaskData; } @FlaggedApi(FLAG_SERVERLESS_REMOTE_ACCESS) public int getCount() { return mCount; } @FlaggedApi(FLAG_SERVERLESS_REMOTE_ACCESS) public long getStartTimeInEpochSeconds() { return mStartTimeInEpochSeconds; } @FlaggedApi(FLAG_SERVERLESS_REMOTE_ACCESS) @NonNull public Duration getPeriodic() { return mPeriodic; } private ScheduleInfo(Builder builder) { mScheduleId = builder.mScheduleId; mTaskType = builder.mTaskType; if (builder.mTaskData != null) { mTaskData = builder.mTaskData; } else { mTaskData = new byte[0]; } mCount = builder.mCount; mStartTimeInEpochSeconds = builder.mStartTimeInEpochSeconds; mPeriodic = builder.mPeriodic; } }; /** * Exception that might be thrown by {@link InVehicleTaskScheduler} methods. * *
This indicates that something is wrong while communicating with the external device * which is responsible for managing task schedule or the external device reports some error. * * @hide */ @SystemApi @FlaggedApi(FLAG_SERVERLESS_REMOTE_ACCESS) public static final class InVehicleTaskSchedulerException extends Exception { InVehicleTaskSchedulerException(Throwable cause) { super(cause); } } /** * For testing only. Check whether the VHAL property: {@code VEHICLE_IN_USE} is supported. * * This property must be supported if serverless remote access is supported. * * @hide */ @TestApi @FlaggedApi(FLAG_SERVERLESS_REMOTE_ACCESS) @RequiresPermission(Car.PERMISSION_CONTROL_REMOTE_ACCESS) public boolean isVehicleInUseSupported() { try { return mService.isVehicleInUseSupported(); } catch (RemoteException e) { return handleRemoteExceptionFromCarService(e, false); } } /** * For testing only. Check whether the VHAL property: {@code SHUTDOWN_REQUEST} is supported. * * This property must be supported if serverless remote access is supported. * * @hide */ @TestApi @FlaggedApi(FLAG_SERVERLESS_REMOTE_ACCESS) @RequiresPermission(Car.PERMISSION_CONTROL_REMOTE_ACCESS) public boolean isShutdownRequestSupported() { try { return mService.isShutdownRequestSupported(); } catch (RemoteException e) { return handleRemoteExceptionFromCarService(e, false); } } /** * A scheduler for scheduling a task to be executed later. * *
It schedules a task via sending a scheduled task message to a device in the same vehicle, * but external to Android. * *
This is only supported for pre-configured remote task serverless clients. * * @hide */ @SystemApi @FlaggedApi(FLAG_SERVERLESS_REMOTE_ACCESS) public final class InVehicleTaskScheduler { /** * Please use {@link getInVehicleTaskScheduler} to create. */ private InVehicleTaskScheduler() {} /** * Gets supported schedule task type. * * @return a list of supported task types as defined by {@link TaskType}. * @throws InVehicleTaskSchedulerException if unable to get supported schedule task type. * * @hide */ @SystemApi @FlaggedApi(FLAG_SERVERLESS_REMOTE_ACCESS) @RequiresPermission(Car.PERMISSION_CONTROL_REMOTE_ACCESS) public @NonNull int[] getSupportedTaskTypes() throws InVehicleTaskSchedulerException { try { return mService.getSupportedTaskTypesForScheduling(); } catch (RemoteException e) { return handleRemoteExceptionFromCarService(e, EMPTY_INT_ARRAY); } catch (ServiceSpecificException e) { throw new InVehicleTaskSchedulerException(e); } } /** * Schedules a task to be executed later even when the vehicle is off. * *
This sends a scheduled task message to a device external to Android so that the device * can wake up Android and deliver the task through {@code onRemoteTaskRequested}. * *
Note that the remote task will be executed even when the vehicle is in use. The task * must not affect the system performance or driving safety. * *
Note that the scheduled task execution is on a best-effort basis. Multiple situations * might cause the task not to execute successfully: * *
The framework does not provide a mechanism to report the task status or task result. * In order for the user to check whether the task is executed successfully, the client must * record in its persistent storage the task's status/result during * {@code onRemoteTaskRequested}. * *
For example, if the scheduled task is to update some of the application's resources. * When the application is opened by the user, it may check whether the resource is the * latest version, if not, it may check whether a scheduled update task exists. If not, it * may schedule a new task at midnight at the same day to update the resource. The * application uses the persistent resource version to decide whether the previous update * succeeded. * *
All the tasks for this client will be unscheduled if the client app is removed. * *
If the client app is updated, the task will still be scheduled, so the client app must * make sure the logic handling remote task is backward compatible. * *
It is possible that the client app's persistent data might be removed, e.g. during * a factory reset. If the client app stores task-related information, it will be lost. The * scheduled task will still be delivered to the app as expected unless client explicitly * unschedule all the scheduled tasks when it detects that the data is erased. * * @param scheduleInfo The schedule information. * * @throws IllegalArgumentException if a pending schedule with the same {@code scheduleId} * for this client exists. * @throws InVehicleTaskSchedulerException if unable to schedule the task. * * @hide */ @SystemApi @FlaggedApi(FLAG_SERVERLESS_REMOTE_ACCESS) @RequiresPermission(Car.PERMISSION_CONTROL_REMOTE_ACCESS) public void scheduleTask(@NonNull ScheduleInfo scheduleInfo) throws InVehicleTaskSchedulerException { Preconditions.checkArgument(scheduleInfo != null, "scheduleInfo cannot be null"); TaskScheduleInfo taskScheduleInfo = toTaskScheduleInfo(scheduleInfo); try { mService.scheduleTask(taskScheduleInfo); } catch (RemoteException e) { handleRemoteExceptionFromCarService(e); } catch (ServiceSpecificException e) { throw new InVehicleTaskSchedulerException(e); } } /** * Unschedules a scheduled task. * * @param scheduleId The ID for the schedule. * *
Does nothing if a pending schedule with {@code scheduleId} does not exist. * * @throws InVehicleTaskSchedulerException if failed to unschedule the tasks. * * @hide */ @SystemApi @FlaggedApi(FLAG_SERVERLESS_REMOTE_ACCESS) @RequiresPermission(Car.PERMISSION_CONTROL_REMOTE_ACCESS) public void unscheduleTask(@NonNull String scheduleId) throws InVehicleTaskSchedulerException { Preconditions.checkArgument(scheduleId != null, "scheduleId cannot be null"); try { mService.unscheduleTask(scheduleId); } catch (RemoteException e) { handleRemoteExceptionFromCarService(e); } catch (ServiceSpecificException e) { throw new InVehicleTaskSchedulerException(e); } } /** * Unschedules all scheduled tasks for this client. * * @hide * * @throws InVehicleTaskSchedulerException if failed to unschedule the tasks. */ @SystemApi @FlaggedApi(FLAG_SERVERLESS_REMOTE_ACCESS) @RequiresPermission(Car.PERMISSION_CONTROL_REMOTE_ACCESS) public void unscheduleAllTasks() throws InVehicleTaskSchedulerException { try { mService.unscheduleAllTasks(); } catch (RemoteException e) { handleRemoteExceptionFromCarService(e); } catch (ServiceSpecificException e) { throw new InVehicleTaskSchedulerException(e); } } /** * Returns whether the specified task is scheduled. * * @param scheduleId The ID for the schedule. * @return {@code true} if the task was scheduled and pending to be executed. * @throws InVehicleTaskSchedulerException if failed to check whether the task is scheduled. * * @hide */ @SystemApi @FlaggedApi(FLAG_SERVERLESS_REMOTE_ACCESS) @RequiresPermission(Car.PERMISSION_CONTROL_REMOTE_ACCESS) public boolean isTaskScheduled(@NonNull String scheduleId) throws InVehicleTaskSchedulerException { Preconditions.checkArgument(scheduleId != null, "scheduleId cannot be null"); try { return mService.isTaskScheduled(scheduleId); } catch (RemoteException e) { return handleRemoteExceptionFromCarService(e, false); } catch (ServiceSpecificException e) { throw new InVehicleTaskSchedulerException(e); } } /** * Gets all pending scheduled tasks for this client. * *
The finished scheduled tasks will not be included.
*
* @return A list of schedule info.
*
* @throws InVehicleTaskSchedulerException if unable to get the scheduled tasks.
*
* @hide
*/
@SystemApi
@FlaggedApi(FLAG_SERVERLESS_REMOTE_ACCESS)
@RequiresPermission(Car.PERMISSION_CONTROL_REMOTE_ACCESS)
@NonNull
public List