570 lines
28 KiB
Java
570 lines
28 KiB
Java
/*
|
|
* 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.app.wearable;
|
|
|
|
import android.Manifest;
|
|
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.SystemService;
|
|
import android.app.PendingIntent;
|
|
import android.app.ambientcontext.AmbientContextEvent;
|
|
import android.app.compat.CompatChanges;
|
|
import android.companion.CompanionDeviceManager;
|
|
import android.compat.annotation.ChangeId;
|
|
import android.compat.annotation.EnabledSince;
|
|
import android.content.ComponentName;
|
|
import android.content.Context;
|
|
import android.content.Intent;
|
|
import android.os.Binder;
|
|
import android.os.Build;
|
|
import android.os.ParcelFileDescriptor;
|
|
import android.os.PersistableBundle;
|
|
import android.os.RemoteCallback;
|
|
import android.os.RemoteException;
|
|
import android.os.SharedMemory;
|
|
import android.service.wearable.WearableSensingService;
|
|
import android.system.OsConstants;
|
|
import android.util.Slog;
|
|
|
|
import com.android.internal.infra.AndroidFuture;
|
|
|
|
import java.io.File;
|
|
import java.io.FileNotFoundException;
|
|
import java.io.IOException;
|
|
import java.io.InputStream;
|
|
import java.io.OutputStream;
|
|
import java.lang.annotation.Retention;
|
|
import java.lang.annotation.RetentionPolicy;
|
|
import java.util.concurrent.Executor;
|
|
import java.util.function.Consumer;
|
|
|
|
/**
|
|
* Allows granted apps to manage the WearableSensingService.
|
|
* Applications are responsible for managing the connection to Wearables. Applications can choose
|
|
* to provide a data stream to the WearableSensingService to use for
|
|
* computing {@link AmbientContextEvent}s. Applications can also optionally provide their own
|
|
* defined data to power the detection of {@link AmbientContextEvent}s.
|
|
* Methods on this class requires the caller to hold and be granted the
|
|
* {@link Manifest.permission.MANAGE_WEARABLE_SENSING_SERVICE}.
|
|
*
|
|
* <p>The use of "Wearable" here is not the same as the Android Wear platform and should be treated
|
|
* separately. </p>
|
|
*
|
|
* @hide
|
|
*/
|
|
@SystemApi
|
|
@SystemService(Context.WEARABLE_SENSING_SERVICE)
|
|
public class WearableSensingManager {
|
|
/**
|
|
* The bundle key for the service status query result, used in
|
|
* {@code RemoteCallback#sendResult}.
|
|
*
|
|
* @hide
|
|
*/
|
|
public static final String STATUS_RESPONSE_BUNDLE_KEY =
|
|
"android.app.wearable.WearableSensingStatusBundleKey";
|
|
|
|
/**
|
|
* The Intent extra key for the data request in the Intent sent to the PendingIntent registered
|
|
* with {@link #registerDataRequestObserver(int, PendingIntent, Executor, Consumer)}.
|
|
*
|
|
* @hide
|
|
*/
|
|
public static final String EXTRA_WEARABLE_SENSING_DATA_REQUEST =
|
|
"android.app.wearable.extra.WEARABLE_SENSING_DATA_REQUEST";
|
|
|
|
/**
|
|
* An unknown status.
|
|
*/
|
|
public static final int STATUS_UNKNOWN = 0;
|
|
|
|
/**
|
|
* The value of the status code that indicates success.
|
|
*/
|
|
public static final int STATUS_SUCCESS = 1;
|
|
|
|
/**
|
|
* The value of the status code that indicates one or more of the requested events are not
|
|
* supported.
|
|
*
|
|
* @deprecated WearableSensingManager does not deal with events. Use {@link
|
|
* STATUS_UNSUPPORTED_OPERATION} instead for operations not supported by the implementation of
|
|
* {@link WearableSensingService}.
|
|
*/
|
|
@Deprecated
|
|
public static final int STATUS_UNSUPPORTED = 2;
|
|
|
|
/**
|
|
* The value of the status code that indicates service not available.
|
|
*/
|
|
public static final int STATUS_SERVICE_UNAVAILABLE = 3;
|
|
|
|
/**
|
|
* The value of the status code that there's no connection to the wearable.
|
|
*/
|
|
public static final int STATUS_WEARABLE_UNAVAILABLE = 4;
|
|
|
|
/**
|
|
* The value of the status code that the app is not granted access.
|
|
*/
|
|
public static final int STATUS_ACCESS_DENIED = 5;
|
|
|
|
/**
|
|
* The value of the status code that indicates the method called is not supported by the
|
|
* implementation of {@link WearableSensingService}.
|
|
*/
|
|
@FlaggedApi(Flags.FLAG_ENABLE_UNSUPPORTED_OPERATION_STATUS_CODE)
|
|
public static final int STATUS_UNSUPPORTED_OPERATION = 6;
|
|
|
|
/**
|
|
* The value of the status code that indicates an error occurred in the encrypted channel backed
|
|
* by the provided connection. See {@link #provideConnection(ParcelFileDescriptor,
|
|
* Executor, Consumer)}.
|
|
*/
|
|
@FlaggedApi(Flags.FLAG_ENABLE_PROVIDE_WEARABLE_CONNECTION_API)
|
|
public static final int STATUS_CHANNEL_ERROR = 7;
|
|
|
|
/** The value of the status code that indicates the provided data type is not supported. */
|
|
@FlaggedApi(Flags.FLAG_ENABLE_DATA_REQUEST_OBSERVER_API)
|
|
public static final int STATUS_UNSUPPORTED_DATA_TYPE = 8;
|
|
|
|
/** @hide */
|
|
@IntDef(
|
|
prefix = {"STATUS_"},
|
|
value = {
|
|
STATUS_UNKNOWN,
|
|
STATUS_SUCCESS,
|
|
STATUS_UNSUPPORTED,
|
|
STATUS_SERVICE_UNAVAILABLE,
|
|
STATUS_WEARABLE_UNAVAILABLE,
|
|
STATUS_ACCESS_DENIED,
|
|
STATUS_UNSUPPORTED_OPERATION,
|
|
STATUS_CHANNEL_ERROR,
|
|
STATUS_UNSUPPORTED_DATA_TYPE
|
|
})
|
|
@Retention(RetentionPolicy.SOURCE)
|
|
public @interface StatusCode {}
|
|
|
|
/**
|
|
* If the WearableSensingService implementation belongs to the same APK as the caller, calling
|
|
* {@link #provideDataStream(ParcelFileDescriptor, Executor, Consumer)} will allow
|
|
* WearableSensingService to read from the caller's file directory via {@link
|
|
* Context#openFileInput(String)}. The read will be proxied via the caller's process and
|
|
* executed by the {@code executor} provided to this method.
|
|
*/
|
|
@ChangeId
|
|
@EnabledSince(targetSdkVersion = Build.VERSION_CODES.VANILLA_ICE_CREAM)
|
|
static final long ALLOW_WEARABLE_SENSING_SERVICE_FILE_READ = 330701114L;
|
|
|
|
/**
|
|
* Retrieves a {@link WearableSensingDataRequest} from the Intent sent to the PendingIntent
|
|
* provided to {@link #registerDataRequestObserver(int, PendingIntent, Executor, Consumer)}.
|
|
*
|
|
* @param intent The Intent received from the PendingIntent.
|
|
* @return The WearableSensingDataRequest in the provided Intent, or null if the Intent does not
|
|
* contain a WearableSensingDataRequest.
|
|
*/
|
|
@FlaggedApi(Flags.FLAG_ENABLE_DATA_REQUEST_OBSERVER_API)
|
|
@Nullable
|
|
public static WearableSensingDataRequest getDataRequestFromIntent(@NonNull Intent intent) {
|
|
return intent.getParcelableExtra(
|
|
EXTRA_WEARABLE_SENSING_DATA_REQUEST, WearableSensingDataRequest.class);
|
|
}
|
|
|
|
private static final String TAG = WearableSensingManager.class.getSimpleName();
|
|
private final Context mContext;
|
|
private final IWearableSensingManager mService;
|
|
|
|
/**
|
|
* {@hide}
|
|
*/
|
|
public WearableSensingManager(Context context, IWearableSensingManager service) {
|
|
mContext = context;
|
|
mService = service;
|
|
}
|
|
|
|
/**
|
|
* Provides a remote wearable device connection to the WearableSensingService and sends the
|
|
* resulting status to the {@code statusConsumer} after the call.
|
|
*
|
|
* <p>This is used by applications that will also provide an implementation of the isolated
|
|
* WearableSensingService.
|
|
*
|
|
* <p>The provided {@code wearableConnection} is expected to be a connection to a remotely
|
|
* connected wearable device. This {@code wearableConnection} will be attached to
|
|
* CompanionDeviceManager via {@link CompanionDeviceManager#attachSystemDataTransport(int,
|
|
* InputStream, OutputStream)}, which will create an encrypted channel using {@code
|
|
* wearableConnection} as the raw underlying connection. The wearable device is expected to
|
|
* attach its side of the raw connection to its CompanionDeviceManager via the same method so
|
|
* that the two CompanionDeviceManagers on the two devices can perform attestation and set up
|
|
* the encrypted channel. Attestation requirements are listed in
|
|
* com.android.server.security.AttestationVerificationPeerDeviceVerifier
|
|
*
|
|
* <p>A proxy to the encrypted channel will be provided to the WearableSensingService, which is
|
|
* referred to as the secureWearableConnection in WearableSensingService. Any data written to
|
|
* secureWearableConnection will be encrypted by CompanionDeviceManager and sent over the raw
|
|
* {@code wearableConnection} to the remote wearable device, which is expected to use its
|
|
* CompanionDeviceManager to decrypt the data. Encrypted data arriving at the raw {@code
|
|
* wearableConnection} will be decrypted by CompanionDeviceManager and be readable as plain text
|
|
* from secureWearableConnection. The raw {@code wearableConnection} provided to this method
|
|
* will not be directly available to the WearableSensingService.
|
|
*
|
|
* <p>If an error occurred in the encrypted channel (such as the underlying stream closed), the
|
|
* system will send a status code of {@link STATUS_CHANNEL_ERROR} to the {@code statusConsumer}
|
|
* and kill the WearableSensingService process.
|
|
*
|
|
* <p>Before providing the secureWearableConnection, the system will restart the
|
|
* WearableSensingService process if it has not been restarted since the last
|
|
* secureWearableConnection was provided. Other method calls into WearableSensingService may be
|
|
* dropped during the restart. The caller is responsible for ensuring other method calls are
|
|
* queued until a success status is returned from the {@code statusConsumer}.
|
|
*
|
|
* <p>If the WearableSensingService implementation belongs to the same APK as the caller,
|
|
* calling this method will allow WearableSensingService to read from the caller's file
|
|
* directory via {@link Context#openFileInput(String)}. The read will be proxied via the
|
|
* caller's process and executed by the {@code executor} provided to this method.
|
|
*
|
|
* @param wearableConnection The connection to provide
|
|
* @param executor Executor on which to run the consumer callback
|
|
* @param statusConsumer A consumer that handles the status codes for providing the connection
|
|
* and errors in the encrypted channel.
|
|
*/
|
|
@RequiresPermission(Manifest.permission.MANAGE_WEARABLE_SENSING_SERVICE)
|
|
@FlaggedApi(Flags.FLAG_ENABLE_PROVIDE_WEARABLE_CONNECTION_API)
|
|
public void provideConnection(
|
|
@NonNull ParcelFileDescriptor wearableConnection,
|
|
@NonNull @CallbackExecutor Executor executor,
|
|
@NonNull @StatusCode Consumer<Integer> statusConsumer) {
|
|
RemoteCallback statusCallback = createStatusCallback(executor, statusConsumer);
|
|
try {
|
|
// The wearableSensingCallback is included in this method call even though it is not
|
|
// semantically related to the connection because we want to avoid race conditions
|
|
// during the process restart triggered by this method call. See
|
|
// com.android.server.wearable.RemoteWearableSensingService for details.
|
|
mService.provideConnection(
|
|
wearableConnection, createWearableSensingCallback(executor), statusCallback);
|
|
} catch (RemoteException e) {
|
|
throw e.rethrowFromSystemServer();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Provides a data stream to the WearableSensingService that's backed by the
|
|
* parcelFileDescriptor, and sends the result to the {@link Consumer} right after the call. This
|
|
* is used by applications that will also provide an implementation of an isolated
|
|
* WearableSensingService. If the data stream was provided successfully {@link
|
|
* WearableSensingManager#STATUS_SUCCESS} will be provided.
|
|
*
|
|
* <p>Starting from target SDK level 35, if the WearableSensingService implementation belongs to
|
|
* the same APK as the caller, calling this method will allow WearableSensingService to read
|
|
* from the caller's file directory via {@link Context#openFileInput(String)}. The read will be
|
|
* proxied via the caller's process and executed by the {@code executor} provided to this
|
|
* method.
|
|
*
|
|
* @param parcelFileDescriptor The data stream to provide
|
|
* @param executor Executor on which to run the consumer callback
|
|
* @param statusConsumer A consumer that handles the status codes, which is returned right after
|
|
* the call.
|
|
* @deprecated Use {@link #provideConnection(ParcelFileDescriptor, Executor, Consumer)} instead
|
|
* to provide a remote wearable device connection to the WearableSensingService
|
|
*/
|
|
@Deprecated
|
|
@RequiresPermission(Manifest.permission.MANAGE_WEARABLE_SENSING_SERVICE)
|
|
public void provideDataStream(
|
|
@NonNull ParcelFileDescriptor parcelFileDescriptor,
|
|
@NonNull @CallbackExecutor Executor executor,
|
|
@NonNull @StatusCode Consumer<Integer> statusConsumer) {
|
|
RemoteCallback statusCallback = createStatusCallback(executor, statusConsumer);
|
|
IWearableSensingCallback wearableSensingCallback = null;
|
|
if (CompatChanges.isChangeEnabled(ALLOW_WEARABLE_SENSING_SERVICE_FILE_READ)) {
|
|
wearableSensingCallback = createWearableSensingCallback(executor);
|
|
}
|
|
try {
|
|
mService.provideDataStream(
|
|
parcelFileDescriptor, wearableSensingCallback, statusCallback);
|
|
} catch (RemoteException e) {
|
|
throw e.rethrowFromSystemServer();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Sets configuration and provides read-only data in a {@link PersistableBundle} that may be
|
|
* used by the WearableSensingService, and sends the result to the {@link Consumer}
|
|
* right after the call. It is dependent on the application to
|
|
* define the type of data to provide. This is used by applications that will also
|
|
* provide an implementation of an isolated WearableSensingService. If the data was
|
|
* provided successfully {@link WearableSensingManager#STATUS_SUCCESS} will be povided.
|
|
*
|
|
* @param data Application configuration data to provide to the {@link WearableSensingService}.
|
|
* PersistableBundle does not allow any remotable objects or other contents
|
|
* that can be used to communicate with other processes.
|
|
* @param sharedMemory The unrestricted data blob to
|
|
* provide to the {@link WearableSensingService}. Use this to provide the
|
|
* sensing models data or other such data to the trusted process.
|
|
* The sharedMemory must be read only and protected with
|
|
* {@link OsConstants.PROT_READ}.
|
|
* Other operations will be removed by the system.
|
|
* @param executor Executor on which to run the consumer callback
|
|
* @param statusConsumer A consumer that handles the status codes, which is returned
|
|
* right after the call
|
|
*/
|
|
@RequiresPermission(Manifest.permission.MANAGE_WEARABLE_SENSING_SERVICE)
|
|
public void provideData(
|
|
@NonNull PersistableBundle data, @Nullable SharedMemory sharedMemory,
|
|
@NonNull @CallbackExecutor Executor executor,
|
|
@NonNull @StatusCode Consumer<Integer> statusConsumer) {
|
|
try {
|
|
RemoteCallback callback = createStatusCallback(executor, statusConsumer);
|
|
mService.provideData(data, sharedMemory, callback);
|
|
} catch (RemoteException e) {
|
|
throw e.rethrowFromSystemServer();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Registers a data request observer for the provided data type.
|
|
*
|
|
* <p>When data is requested, the provided {@code dataRequestPendingIntent} will be invoked. A
|
|
* {@link WearableSensingDataRequest} can be extracted from the Intent sent to {@code
|
|
* dataRequestPendingIntent} by calling {@link #getDataRequestFromIntent(Intent)}. The observer
|
|
* can then provide the requested data via {@link #provideData(PersistableBundle, SharedMemory,
|
|
* Executor, Consumer)}.
|
|
*
|
|
* <p>There is no limit to the number of observers registered for a data type. How they are
|
|
* handled depends on the implementation of WearableSensingService.
|
|
*
|
|
* <p>When the observer is no longer needed, {@link #unregisterDataRequestObserver(int,
|
|
* PendingIntent, Executor, Consumer)} should be called with the same {@code
|
|
* dataRequestPendingIntent}. It should be done regardless of the status code returned from
|
|
* {@code statusConsumer} in order to clean up housekeeping data for the {@code
|
|
* dataRequestPendingIntent} maintained by the system.
|
|
*
|
|
* <p>Example:
|
|
*
|
|
* <pre>{@code
|
|
* // Create a PendingIntent for MyDataRequestBroadcastReceiver
|
|
* Intent intent =
|
|
* new Intent(actionString).setClass(context, MyDataRequestBroadcastReceiver.class);
|
|
* PendingIntent pendingIntent = PendingIntent.getBroadcast(
|
|
* context, 0, intent, PendingIntent.FLAG_MUTABLE);
|
|
*
|
|
* // Register the PendingIntent as a data request observer
|
|
* wearableSensingManager.registerDataRequestObserver(
|
|
* dataType, pendingIntent, executor, statusConsumer);
|
|
*
|
|
* // Within MyDataRequestBroadcastReceiver, receive the broadcast Intent and extract the
|
|
* // WearableSensingDataRequest
|
|
* {@literal @}Override
|
|
* public void onReceive(Context context, Intent intent) {
|
|
* WearableSensingDataRequest dataRequest =
|
|
* WearableSensingManager.getDataRequestFromIntent(intent);
|
|
* // After parsing the dataRequest, provide the data
|
|
* wearableSensingManager.provideData(data, sharedMemory, executor, statusConsumer);
|
|
* }
|
|
* }</pre>
|
|
*
|
|
* @param dataType The data type to listen to. Values are defined by the application that
|
|
* implements {@link WearableSensingService}.
|
|
* @param dataRequestPendingIntent A mutable {@link PendingIntent} that will be invoked when
|
|
* data is requested. See {@link #getDataRequestFromIntent(Intent)}. Activities are not
|
|
* allowed to be launched using this PendingIntent.
|
|
* @param executor Executor on which to run the consumer callback.
|
|
* @param statusConsumer A consumer that handles the status code for the observer registration.
|
|
*/
|
|
@FlaggedApi(Flags.FLAG_ENABLE_DATA_REQUEST_OBSERVER_API)
|
|
@RequiresPermission(Manifest.permission.MANAGE_WEARABLE_SENSING_SERVICE)
|
|
public void registerDataRequestObserver(
|
|
int dataType,
|
|
@NonNull PendingIntent dataRequestPendingIntent,
|
|
@NonNull @CallbackExecutor Executor executor,
|
|
@NonNull @StatusCode Consumer<Integer> statusConsumer) {
|
|
try {
|
|
RemoteCallback statusCallback = createStatusCallback(executor, statusConsumer);
|
|
mService.registerDataRequestObserver(
|
|
dataType, dataRequestPendingIntent, statusCallback);
|
|
} catch (RemoteException e) {
|
|
throw e.rethrowFromSystemServer();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Unregisters a previously registered data request observer. If the provided {@link
|
|
* PendingIntent} was not registered, or is already unregistered, the {@link
|
|
* WearableSensingService} will not be notified.
|
|
*
|
|
* @param dataType The data type the observer is for.
|
|
* @param dataRequestPendingIntent The observer to unregister.
|
|
* @param executor Executor on which to run the consumer callback.
|
|
* @param statusConsumer A consumer that handles the status code for the observer
|
|
* unregistration.
|
|
*/
|
|
@FlaggedApi(Flags.FLAG_ENABLE_DATA_REQUEST_OBSERVER_API)
|
|
@RequiresPermission(Manifest.permission.MANAGE_WEARABLE_SENSING_SERVICE)
|
|
public void unregisterDataRequestObserver(
|
|
int dataType,
|
|
@NonNull PendingIntent dataRequestPendingIntent,
|
|
@NonNull @CallbackExecutor Executor executor,
|
|
@NonNull @StatusCode Consumer<Integer> statusConsumer) {
|
|
try {
|
|
RemoteCallback statusCallback = createStatusCallback(executor, statusConsumer);
|
|
mService.unregisterDataRequestObserver(
|
|
dataType, dataRequestPendingIntent, statusCallback);
|
|
} catch (RemoteException e) {
|
|
throw e.rethrowFromSystemServer();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Requests the wearable to start hotword recognition.
|
|
*
|
|
* <p>When this method is called, the system will attempt to provide a {@link
|
|
* android.service.wearable.WearableHotwordAudioConsumer} to {@link WearableSensingService}.
|
|
* After first-stage hotword is detected on a wearable, {@link WearableSensingService} should
|
|
* send the hotword audio to the {@link android.service.wearable.WearableHotwordAudioConsumer},
|
|
* which will forward the data to the {@link android.service.voice.HotwordDetectionService} for
|
|
* second-stage hotword validation. If hotword is detected there, the audio data will be
|
|
* forwarded to the {@link android.service.voice.VoiceInteractionService}.
|
|
*
|
|
* <p>If the {@code targetVisComponentName} provided here is not null, when {@link
|
|
* WearableSensingService} sends hotword audio to the {@link
|
|
* android.service.wearable.WearableHotwordAudioConsumer}, the system will check whether the
|
|
* {@link android.service.voice.VoiceInteractionService} at that time is {@code
|
|
* targetVisComponentName}. If not, the system will call {@link
|
|
* WearableSensingService#onActiveHotwordAudioStopRequested()} and will not forward the audio
|
|
* data to the current {@link android.service.voice.HotwordDetectionService} nor {@link
|
|
* android.service.voice.VoiceInteractionService}. The system will not send a status code to
|
|
* {@code statusConsumer} regarding the {@code targetVisComponentName} check. The caller is
|
|
* responsible for determining whether the system's {@link
|
|
* android.service.voice.VoiceInteractionService} is the same as {@code targetVisComponentName}.
|
|
* The check here is just a protection against race conditions.
|
|
*
|
|
* <p>Calling this method again will send a new {@link
|
|
* android.service.wearable.WearableHotwordAudioConsumer} to {@link WearableSensingService}. For
|
|
* audio data sent to the new consumer, the system will perform the above check using the newly
|
|
* provided {@code targetVisComponentName}. The {@link WearableSensingService} should not
|
|
* continue to use the previous consumers after receiving a new one.
|
|
*
|
|
* <p>If the {@code statusConsumer} returns {@link STATUS_SUCCESS}, the caller should call
|
|
* {@link #stopListeningForHotword(Executor, Consumer)} when it wants the wearable to stop
|
|
* listening for hotword. If the {@code statusConsumer} returns any other status code, a failure
|
|
* has occurred and calling {@link #stopListeningForHotword(Executor, Consumer)} is not
|
|
* required. The system will not retry listening automatically. The caller should call this
|
|
* method again if they want to retry.
|
|
*
|
|
* <p>If a failure occurred after the {@link statusConsumer} returns {@link STATUS_SUCCESS},
|
|
* {@link statusConsumer} will be invoked again with a status code other than {@link
|
|
* STATUS_SUCCESS}.
|
|
*
|
|
* @param targetVisComponentName The ComponentName of the target VoiceInteractionService.
|
|
* @param executor Executor on which to run the consumer callback.
|
|
* @param statusConsumer A consumer that handles the status codes.
|
|
*/
|
|
@FlaggedApi(Flags.FLAG_ENABLE_HOTWORD_WEARABLE_SENSING_API)
|
|
@RequiresPermission(Manifest.permission.MANAGE_WEARABLE_SENSING_SERVICE)
|
|
public void startHotwordRecognition(
|
|
@Nullable ComponentName targetVisComponentName,
|
|
@NonNull @CallbackExecutor Executor executor,
|
|
@NonNull @StatusCode Consumer<Integer> statusConsumer) {
|
|
try {
|
|
mService.startHotwordRecognition(
|
|
targetVisComponentName, createStatusCallback(executor, statusConsumer));
|
|
} catch (RemoteException e) {
|
|
throw e.rethrowFromSystemServer();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Requests the wearable to stop hotword recognition.
|
|
*
|
|
* @param executor Executor on which to run the consumer callback.
|
|
* @param statusConsumer A consumer that handles the status codes.
|
|
*/
|
|
@FlaggedApi(Flags.FLAG_ENABLE_HOTWORD_WEARABLE_SENSING_API)
|
|
@RequiresPermission(Manifest.permission.MANAGE_WEARABLE_SENSING_SERVICE)
|
|
public void stopHotwordRecognition(
|
|
@NonNull @CallbackExecutor Executor executor,
|
|
@NonNull @StatusCode Consumer<Integer> statusConsumer) {
|
|
try {
|
|
mService.stopHotwordRecognition(createStatusCallback(executor, statusConsumer));
|
|
} catch (RemoteException e) {
|
|
throw e.rethrowFromSystemServer();
|
|
}
|
|
}
|
|
|
|
private static RemoteCallback createStatusCallback(
|
|
Executor executor, Consumer<Integer> statusConsumer) {
|
|
return new RemoteCallback(
|
|
result -> {
|
|
int status = result.getInt(STATUS_RESPONSE_BUNDLE_KEY);
|
|
final long identity = Binder.clearCallingIdentity();
|
|
try {
|
|
executor.execute(() -> statusConsumer.accept(status));
|
|
} finally {
|
|
Binder.restoreCallingIdentity(identity);
|
|
}
|
|
});
|
|
}
|
|
|
|
private IWearableSensingCallback createWearableSensingCallback(Executor executor) {
|
|
return new IWearableSensingCallback.Stub() {
|
|
|
|
@Override
|
|
public void openFile(String filename, AndroidFuture<ParcelFileDescriptor> future) {
|
|
Slog.d(TAG, "IWearableSensingCallback#openFile " + filename);
|
|
Binder.withCleanCallingIdentity(
|
|
() ->
|
|
executor.execute(
|
|
() -> {
|
|
File file = new File(mContext.getFilesDir(), filename);
|
|
ParcelFileDescriptor pfd = null;
|
|
try {
|
|
pfd =
|
|
ParcelFileDescriptor.open(
|
|
file,
|
|
ParcelFileDescriptor
|
|
.MODE_READ_ONLY);
|
|
Slog.d(
|
|
TAG,
|
|
"Successfully opened a file with"
|
|
+ " ParcelFileDescriptor.");
|
|
} catch (FileNotFoundException e) {
|
|
Slog.e(TAG, "Cannot open file.", e);
|
|
} finally {
|
|
future.complete(pfd);
|
|
if (pfd != null) {
|
|
try {
|
|
pfd.close();
|
|
} catch (IOException ex) {
|
|
Slog.e(
|
|
TAG,
|
|
"Error closing"
|
|
+ " ParcelFileDescriptor.",
|
|
ex);
|
|
}
|
|
}
|
|
}
|
|
}));
|
|
}
|
|
};
|
|
}
|
|
}
|