/* * 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.service.wearable; import android.annotation.BinderThread; import android.annotation.FlaggedApi; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.SuppressLint; import android.annotation.SystemApi; import android.app.Service; import android.app.ambientcontext.AmbientContextEvent; import android.app.ambientcontext.AmbientContextEventRequest; import android.app.wearable.Flags; import android.app.wearable.IWearableSensingCallback; import android.app.wearable.WearableSensingDataRequest; import android.app.wearable.WearableSensingManager; import android.content.Context; import android.content.Intent; import android.os.Bundle; import android.os.IBinder; import android.os.ParcelFileDescriptor; import android.os.PersistableBundle; import android.os.Process; import android.os.RemoteCallback; import android.os.RemoteException; import android.os.SharedMemory; import android.service.ambientcontext.AmbientContextDetectionResult; import android.service.ambientcontext.AmbientContextDetectionServiceStatus; import android.service.voice.HotwordAudioStream; import android.text.TextUtils; import android.util.Slog; import android.util.SparseArray; import com.android.internal.infra.AndroidFuture; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.time.Duration; import java.util.Arrays; import java.util.HashSet; import java.util.Objects; import java.util.Set; import java.util.concurrent.ExecutionException; import java.util.concurrent.Executor; import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; import java.util.function.Consumer; /** * Abstract base class for sensing with wearable devices. An example of this is {@link *AmbientContextEvent} detection. * *
A service that provides requested sensing events to the system, such as a {@link *AmbientContextEvent}. The system's default WearableSensingService implementation is configured in * {@code config_defaultWearableSensingService}. If this config has no value, a stub is * returned. * *
An implementation of a WearableSensingService should be an isolated service. Using the * "isolatedProcess=true" attribute in the service's configurations.
** ** {@literal ** ** } *
The use of "Wearable" here is not the same as the Android Wear platform and should be treated * separately.
* * @hide */ @SystemApi public abstract class WearableSensingService extends Service { private static final String TAG = WearableSensingService.class.getSimpleName(); /** * The bundle key for this class of object, used in {@code RemoteCallback#sendResult}. * * @hide */ public static final String STATUS_RESPONSE_BUNDLE_KEY = "android.app.wearable.WearableSensingStatusBundleKey"; /** * The bundle key for hotword audio stream, used in {@code RemoteCallback#sendResult}. * * @hide */ public static final String HOTWORD_AUDIO_STREAM_BUNDLE_KEY = "android.app.wearable.HotwordAudioStreamBundleKey"; /** * The {@link Intent} that must be declared as handled by the service. To be supported, the * service must also require the * {@link android.Manifest.permission#BIND_WEARABLE_SENSING_SERVICE} * permission so that other applications can not abuse it. */ public static final String SERVICE_INTERFACE = "android.service.wearable.WearableSensingService"; // Timeout to prevent thread from waiting on the openFile future indefinitely. private static final Duration OPEN_FILE_TIMEOUT = Duration.ofSeconds(5); private final SparseArrayWhen the {@code secureWearableConnection} is closed, the system will send a {@link * WearableSensingManager#STATUS_CHANNEL_ERROR} status code to the status consumer provided by * the caller of {@link WearableSensingManager#provideConnection(ParcelFileDescriptor, * Executor, Consumer)}. * *
The implementing class should override this method. It should return an appropriate status
* code via {@code statusConsumer} after receiving the {@code secureWearableConnection}.
*
* @param secureWearableConnection The secure connection to the wearable.
* @param statusConsumer The consumer for the service status.
*/
@FlaggedApi(Flags.FLAG_ENABLE_PROVIDE_WEARABLE_CONNECTION_API)
@BinderThread
public void onSecureConnectionProvided(
@NonNull ParcelFileDescriptor secureWearableConnection,
@NonNull Consumer The implementing class should override this method. After the data requester is received,
* it should send a {@link WearableSensingManager#STATUS_SUCCESS} status code to the {@code
* statusConsumer} unless it encounters an error condition described by a status code listed in
* {@link WearableSensingManager}, such as {@link
* WearableSensingManager#STATUS_WEARABLE_UNAVAILABLE}, in which case it should return the
* corresponding status code.
*
* @param dataType The data type the observer is registered for. Values are defined by the
* application that implements this class.
* @param packageName The package name of the app that will receive the requests.
* @param dataRequester A handle to the observer registered. It can be used to request data of
* the specified data type.
* @param statusConsumer the consumer for the status of the data request observer registration.
* This is different from the status for each data request.
*/
@FlaggedApi(Flags.FLAG_ENABLE_DATA_REQUEST_OBSERVER_API)
@BinderThread
public void onDataRequestObserverRegistered(
int dataType,
@NonNull String packageName,
@NonNull WearableSensingDataRequester dataRequester,
@NonNull Consumer The implementing class should override this method. It should send a {@link
* WearableSensingManager#STATUS_SUCCESS} status code to the {@code statusConsumer} unless it
* encounters an error condition described by a status code listed in {@link
* WearableSensingManager}, such as {@link WearableSensingManager#STATUS_WEARABLE_UNAVAILABLE},
* in which case it should return the corresponding status code.
*
* @param dataType The data type the observer is for.
* @param packageName The package name of the app that will receive the requests sent to the
* dataRequester.
* @param dataRequester A handle to the observer to be unregistered. It is the exact same
* instance provided in a previous {@link #onDataRequestConsumerRegistered(int, String,
* WearableSensingDataRequester, Consumer)} invocation.
* @param statusConsumer the consumer for the status of the data request observer
* unregistration. This is different from the status for each data request.
*/
@FlaggedApi(Flags.FLAG_ENABLE_DATA_REQUEST_OBSERVER_API)
@BinderThread
public void onDataRequestObserverUnregistered(
int dataType,
@NonNull String packageName,
@NonNull WearableSensingDataRequester dataRequester,
@NonNull Consumer This method is expected to be overridden by a derived class. The implementation should
* store the {@code hotwordAudioConsumer} and send it the audio data when first-stage hotword is
* detected from the wearable. It should also send a {@link
* WearableSensingManager#STATUS_SUCCESS} status code to the {@code statusConsumer} unless it
* encounters an error condition described by a status code listed in {@link
* WearableSensingManager}, such as {@link WearableSensingManager#STATUS_WEARABLE_UNAVAILABLE},
* in which case it should return the corresponding status code.
*
* The implementation should also store the {@code statusConsumer}. If the wearable stops
* listening for hotword for any reason other than {@link #onStopListeningForHotword(Consumer)}
* being invoked, it should send an appropriate status code listed in {@link
* WearableSensingManager} to {@code statusConsumer}. If the error condition cannot be described
* by any of those status codes, it should send a {@link WearableSensingManager#STATUS_UNKNOWN}.
*
* If this method is called again, the implementation should use the new {@code
* hotwordAudioConsumer} and discard any previous ones it received.
*
* At this time, the {@code timestamp} field in the {@link HotwordAudioStream} is not used
* and will be discarded by the system.
*
* @param hotwordAudioConsumer The consumer for the wearable hotword audio data.
* @param statusConsumer The consumer for the service status.
*/
@FlaggedApi(Flags.FLAG_ENABLE_HOTWORD_WEARABLE_SENSING_API)
@BinderThread
public void onStartHotwordRecognition(
@NonNull Consumer This method is expected to be overridden by a derived class. It should send a {@link
* WearableSensingManager#STATUS_SUCCESS} status code to the {@code statusConsumer} unless it
* encounters an error condition described by a status code listed in {@link
* WearableSensingManager}, such as {@link WearableSensingManager#STATUS_WEARABLE_UNAVAILABLE},
* in which case it should return the corresponding status code.
*
* @param statusConsumer The consumer for the service status.
*/
@FlaggedApi(Flags.FLAG_ENABLE_HOTWORD_WEARABLE_SENSING_API)
@BinderThread
public void onStopHotwordRecognition(@NonNull Consumer After the implementation of this class sends the hotword audio data to the {@code
* hotwordAudioConsumer} in {@link #onStartListeningForHotword(Consumer,
* Consumer)}, the system will forward the data into {@link
* android.service.voice.HotwordDetectionService} (which runs in an isolated process) for
* second-stage hotword detection. If accepted as valid hotword there, this method will be
* called, and then the system will send the data to the currently active {@link
* android.service.voice.AlwaysOnHotwordDetector} (which may not run in an isolated process).
*
* This method is expected to be overridden by a derived class. The implementation must
* request the wearable to turn on the microphone indicator to notify the user that audio data
* is being used outside of the isolated environment.
*/
@FlaggedApi(Flags.FLAG_ENABLE_HOTWORD_WEARABLE_SENSING_API)
@BinderThread
public void onValidatedByHotwordDetectionService() {}
/**
* Called when the currently active hotword audio stream is no longer needed.
*
* This method can be called as a result of hotword rejection by {@link
* android.service.voice.HotwordDetectionService}, or the {@link
* android.service.voice.AlwaysOnHotwordDetector} closing the data stream it received, or a
* non-recoverable error occurred before the data reaches the {@link
* android.service.voice.HotwordDetectionService} or the {@link
* android.service.voice.AlwaysOnHotwordDetector}.
*
* This method is expected to be overridden by a derived class. The implementation should
* stop sending hotword audio data to the {@code hotwordAudioConsumer} in {@link
* #onStartListeningForHotword(Consumer, Consumer)}
*/
@FlaggedApi(Flags.FLAG_ENABLE_HOTWORD_WEARABLE_SENSING_API)
@BinderThread
public void onStopHotwordAudioStream() {}
/**
* Called when a client app requests starting detection of the events in the request. The
* implementation should keep track of whether the user has explicitly consented to detecting
* the events using on-going ambient sensor (e.g. microphone), and agreed to share the
* detection results with this client app. If the user has not consented, the detection
* should not start, and the statusConsumer should get a response with STATUS_ACCESS_DENIED.
* If the user has made the consent and the underlying services are available, the
* implementation should start detection and provide detected events to the
* detectionResultConsumer. If the type of event needs immediate attention, the implementation
* should send result as soon as detected. Otherwise, the implementation can batch response.
* The ongoing detection will keep running, until onStopDetection is called. If there were
* previously requested detections from the same package, regardless of the type of events in
* the request, the previous request will be replaced with the new request and pending events
* are discarded.
*
* @param request The request with events to detect.
* @param packageName the requesting app's package name
* @param statusConsumer the consumer for the service status.
* @param detectionResultConsumer the consumer for the detected event
*/
@BinderThread
public abstract void onStartDetection(@NonNull AmbientContextEventRequest request,
@NonNull String packageName,
@NonNull Consumer This method is only functional after {@link
* #onSecureConnectionProvided(ParcelFileDescriptor, Consumer)} or {@link
* #onDataStreamProvided(ParcelFileDescriptor, Consumer)} has been called as a result of a
* process owned by the same APK calling {@link
* WearableSensingManager#provideConnection(ParcelFileDescriptor, Executor, Consumer)} or {@link
* WearableSensingManager#provideDataStream(ParcelFileDescriptor, Executor, Consumer)}.
* Otherwise, it will throw an {@link IllegalStateException}. This is because this method
* proxies the file read via that process. Also, the APK needs to have a targetSdkVersion of 35
* or newer.
*
* @param fileName Relative path of a file under {@link Context#getFilesDir()}.
* @throws IllegalStateException if the above condition is not satisfied.
* @throws FileNotFoundException if the file does not exist or cannot be opened, or an error
* occurred during the RPC to proxy the file read via a non-isolated process.
*/
// SuppressLint is needed because the parent Context class does not specify the nullability of
// the parameter filename. If we remove the @NonNull annotation, the linter will complain about
// MissingNullability
@Override
public @NonNull FileInputStream openFileInput(
@SuppressLint("InvalidNullabilityOverride") @NonNull String fileName)
throws FileNotFoundException {
if (fileName == null) {
throw new IllegalArgumentException("filename cannot be null");
}
try {
if (mWearableSensingCallback == null) {
throw new IllegalStateException(
"Cannot open file from WearableSensingService. WearableSensingCallback is"
+ " not available.");
}
AndroidFuture