script-astra/Android/Sdk/sources/android-35/android/service/wearable/WearableSensingService.java

699 lines
34 KiB
Java
Raw Normal View History

2025-01-20 15:15:20 +00:00
/*
* 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.
*
* <p> 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.
*
* <p> An implementation of a WearableSensingService should be an isolated service. Using the
* "isolatedProcess=true" attribute in the service's configurations. </p>
**
* <pre>
* {@literal
* <service android:name=".YourWearableSensingService"
* android:permission="android.permission.BIND_WEARABLE_SENSING_SERVICE"
* android:isolatedProcess="true">
* </service>}
* </pre>
*
* <p>The use of "Wearable" here is not the same as the Android Wear platform and should be treated
* separately. </p>
*
* @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 SparseArray<WearableSensingDataRequester> mDataRequestObserverIdToRequesterMap =
new SparseArray<>();
private IWearableSensingCallback mWearableSensingCallback;
@Nullable
@Override
public final IBinder onBind(@NonNull Intent intent) {
if (SERVICE_INTERFACE.equals(intent.getAction())) {
return new IWearableSensingService.Stub() {
/** {@inheritDoc} */
@Override
public void provideSecureConnection(
ParcelFileDescriptor secureWearableConnection,
IWearableSensingCallback wearableSensingCallback,
RemoteCallback callback) {
Objects.requireNonNull(secureWearableConnection);
if (wearableSensingCallback != null) {
mWearableSensingCallback = wearableSensingCallback;
}
Consumer<Integer> consumer = createWearableStatusConsumer(callback);
WearableSensingService.this.onSecureConnectionProvided(
secureWearableConnection, consumer);
}
/** {@inheritDoc} */
@Override
public void provideDataStream(
ParcelFileDescriptor parcelFileDescriptor,
IWearableSensingCallback wearableSensingCallback,
RemoteCallback callback) {
Objects.requireNonNull(parcelFileDescriptor);
if (wearableSensingCallback != null) {
mWearableSensingCallback = wearableSensingCallback;
}
Consumer<Integer> consumer = createWearableStatusConsumer(callback);
WearableSensingService.this.onDataStreamProvided(
parcelFileDescriptor, consumer);
}
/** {@inheritDoc} */
@Override
public void provideData(
PersistableBundle data,
SharedMemory sharedMemory,
RemoteCallback callback) {
Objects.requireNonNull(data);
Consumer<Integer> consumer = createWearableStatusConsumer(callback);
WearableSensingService.this.onDataProvided(data, sharedMemory, consumer);
}
/** {@inheritDoc} */
@Override
public void registerDataRequestObserver(
int dataType,
RemoteCallback dataRequestCallback,
int dataRequestObserverId,
String packageName,
RemoteCallback statusCallback) {
Objects.requireNonNull(dataRequestCallback);
Objects.requireNonNull(statusCallback);
WearableSensingDataRequester dataRequester;
synchronized (mDataRequestObserverIdToRequesterMap) {
dataRequester =
mDataRequestObserverIdToRequesterMap.get(dataRequestObserverId);
if (dataRequester == null) {
dataRequester = createDataRequester(dataRequestCallback);
mDataRequestObserverIdToRequesterMap.put(
dataRequestObserverId, dataRequester);
}
}
Consumer<Integer> statusConsumer = createWearableStatusConsumer(statusCallback);
WearableSensingService.this.onDataRequestObserverRegistered(
dataType, packageName, dataRequester, statusConsumer);
}
@Override
public void unregisterDataRequestObserver(
int dataType,
int dataRequestObserverId,
String packageName,
RemoteCallback statusCallback) {
WearableSensingDataRequester dataRequester;
synchronized (mDataRequestObserverIdToRequesterMap) {
dataRequester =
mDataRequestObserverIdToRequesterMap.get(dataRequestObserverId);
if (dataRequester == null) {
Slog.w(
TAG,
"dataRequestObserverId not found, cannot unregister data"
+ " request observer.");
return;
}
mDataRequestObserverIdToRequesterMap.remove(dataRequestObserverId);
}
Consumer<Integer> statusConsumer = createWearableStatusConsumer(statusCallback);
WearableSensingService.this.onDataRequestObserverUnregistered(
dataType, packageName, dataRequester, statusConsumer);
}
@Override
public void startHotwordRecognition(
RemoteCallback wearableHotwordCallback, RemoteCallback statusCallback) {
Consumer<HotwordAudioStream> hotwordAudioConsumer =
(hotwordAudioStream) -> {
Bundle bundle = new Bundle();
bundle.putParcelable(
HOTWORD_AUDIO_STREAM_BUNDLE_KEY, hotwordAudioStream);
wearableHotwordCallback.sendResult(bundle);
};
Consumer<Integer> statusConsumer =
response -> {
Bundle bundle = new Bundle();
bundle.putInt(STATUS_RESPONSE_BUNDLE_KEY, response);
statusCallback.sendResult(bundle);
};
WearableSensingService.this.onStartHotwordRecognition(
hotwordAudioConsumer, statusConsumer);
}
/** {@inheritDoc} */
@Override
public void stopHotwordRecognition(RemoteCallback statusCallback) {
Consumer<Integer> statusConsumer =
response -> {
Bundle bundle = new Bundle();
bundle.putInt(STATUS_RESPONSE_BUNDLE_KEY, response);
statusCallback.sendResult(bundle);
};
WearableSensingService.this.onStopHotwordRecognition(statusConsumer);
}
/** {@inheritDoc} */
@Override
public void onValidatedByHotwordDetectionService() {
WearableSensingService.this.onValidatedByHotwordDetectionService();
}
/** {@inheritDoc} */
@Override
public void stopActiveHotwordAudio() {
WearableSensingService.this.onStopHotwordAudioStream();
}
/** {@inheritDoc} */
@Override
public void startDetection(
@NonNull AmbientContextEventRequest request,
String packageName,
RemoteCallback detectionResultCallback,
RemoteCallback statusCallback) {
Objects.requireNonNull(request);
Objects.requireNonNull(packageName);
Objects.requireNonNull(detectionResultCallback);
Objects.requireNonNull(statusCallback);
Consumer<AmbientContextDetectionResult> detectionResultConsumer =
result -> {
Bundle bundle = new Bundle();
bundle.putParcelable(
AmbientContextDetectionResult.RESULT_RESPONSE_BUNDLE_KEY,
result);
detectionResultCallback.sendResult(bundle);
};
Consumer<AmbientContextDetectionServiceStatus> statusConsumer =
status -> {
Bundle bundle = new Bundle();
bundle.putParcelable(
AmbientContextDetectionServiceStatus
.STATUS_RESPONSE_BUNDLE_KEY,
status);
statusCallback.sendResult(bundle);
};
WearableSensingService.this.onStartDetection(
request, packageName, statusConsumer, detectionResultConsumer);
Slog.d(TAG, "startDetection " + request);
}
/** {@inheritDoc} */
@Override
public void stopDetection(String packageName) {
Objects.requireNonNull(packageName);
WearableSensingService.this.onStopDetection(packageName);
}
/** {@inheritDoc} */
@Override
public void queryServiceStatus(
@AmbientContextEvent.EventCode int[] eventTypes,
String packageName,
RemoteCallback callback) {
Objects.requireNonNull(eventTypes);
Objects.requireNonNull(packageName);
Objects.requireNonNull(callback);
Consumer<AmbientContextDetectionServiceStatus> consumer =
response -> {
Bundle bundle = new Bundle();
bundle.putParcelable(
AmbientContextDetectionServiceStatus
.STATUS_RESPONSE_BUNDLE_KEY,
response);
callback.sendResult(bundle);
};
Integer[] events = intArrayToIntegerArray(eventTypes);
WearableSensingService.this.onQueryServiceStatus(
new HashSet<>(Arrays.asList(events)), packageName, consumer);
}
/** {@inheritDoc} */
@Override
public void killProcess() {
Slog.d(TAG, "#killProcess");
Process.killProcess(Process.myPid());
}
};
}
Slog.w(TAG, "Incorrect service interface, returning null.");
return null;
}
/**
* Called when a secure connection to the wearable is available. See {@link
* WearableSensingManager#provideConnection(ParcelFileDescriptor, Executor, Consumer)}
* for details about the secure connection.
*
* <p>When 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)}.
*
* <p>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<Integer> statusConsumer) {
statusConsumer.accept(WearableSensingManager.STATUS_UNSUPPORTED_OPERATION);
}
/**
* Called when a data stream to the wearable is provided. This data stream can be used to obtain
* data from a wearable device. It is up to the implementation to maintain the data stream and
* close the data stream when it is finished.
*
* @param parcelFileDescriptor The data stream to the wearable
* @param statusConsumer the consumer for the service status.
*/
@BinderThread
public abstract void onDataStreamProvided(@NonNull ParcelFileDescriptor parcelFileDescriptor,
@NonNull Consumer<Integer> statusConsumer);
/**
* Called when configurations and read-only data in a {@link PersistableBundle} can 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
* provided.
*
* @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.
* @param statusConsumer the consumer for the service status.
*/
@BinderThread
public abstract void onDataProvided(
@NonNull PersistableBundle data,
@Nullable SharedMemory sharedMemory,
@NonNull Consumer<Integer> statusConsumer);
/**
* Called when a data request observer is registered. Each request must not be larger than
* {@link WearableSensingDataRequest#getMaxRequestSize()}. In addition, at most {@link
* WearableSensingDataRequester#getRateLimit()} requests can be sent every rolling {@link
* WearableSensingDataRequester#getRateLimitWindowSize()}. Requests that are too large or too
* frequent will be dropped by the system. See {@link
* WearableSensingDataRequester#requestData(WearableSensingDataRequest, Consumer)} for details
* about the status code returned for each request.
*
* <p>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<Integer> statusConsumer) {
statusConsumer.accept(WearableSensingManager.STATUS_UNSUPPORTED_OPERATION);
}
/**
* Called when a data request observer is unregistered.
*
* <p>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<Integer> statusConsumer) {
statusConsumer.accept(WearableSensingManager.STATUS_UNSUPPORTED_OPERATION);
}
/**
* Called when the wearable is requested to start hotword recognition.
*
* <p>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.
*
* <p>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}.
*
* <p>If this method is called again, the implementation should use the new {@code
* hotwordAudioConsumer} and discard any previous ones it received.
*
* <p>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<HotwordAudioStream> hotwordAudioConsumer,
@NonNull Consumer<Integer> statusConsumer) {
if (Flags.enableUnsupportedOperationStatusCode()) {
statusConsumer.accept(WearableSensingManager.STATUS_UNSUPPORTED_OPERATION);
}
}
/**
* Called when the wearable is requested to stop hotword recognition.
*
* <p>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<Integer> statusConsumer) {
if (Flags.enableUnsupportedOperationStatusCode()) {
statusConsumer.accept(WearableSensingManager.STATUS_UNSUPPORTED_OPERATION);
}
}
/**
* Called when hotword audio data sent to the {@code hotwordAudioConsumer} in {@link
* #onStartListeningForHotword(Consumer, Consumer)} is accepted by the
* {@link android.service.voice.HotwordDetectionService} as valid hotword.
*
* <p>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).
*
* <p>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.
*
* <p>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}.
*
* <p>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<AmbientContextDetectionServiceStatus> statusConsumer,
@NonNull Consumer<AmbientContextDetectionResult> detectionResultConsumer);
/**
* Stops detection of the events. Events that are not being detected will be ignored.
*
* @param packageName stops detection for the given package.
*/
public abstract void onStopDetection(@NonNull String packageName);
/**
* Called when a query for the detection status occurs. The implementation should check
* the detection status of the requested events for the package, and provide results in a
* {@link AmbientContextDetectionServiceStatus} for the consumer.
*
* @param eventTypes The events to check for status.
* @param packageName the requesting app's package name
* @param consumer the consumer for the query results
*/
@BinderThread
public abstract void onQueryServiceStatus(@NonNull Set<Integer> eventTypes,
@NonNull String packageName,
@NonNull Consumer<AmbientContextDetectionServiceStatus> consumer);
/**
* Overrides {@link Context#openFileInput} to read files with the given {@code fileName} under
* the internal app storage of the APK providing the implementation for this class. {@link
* Context#getFilesDir()} will be added as a prefix to the provided {@code fileName}.
*
* <p>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<ParcelFileDescriptor> future = new AndroidFuture<>();
mWearableSensingCallback.openFile(fileName, future);
ParcelFileDescriptor pfd =
future.get(OPEN_FILE_TIMEOUT.toMillis(), TimeUnit.MILLISECONDS);
if (pfd == null) {
throw new FileNotFoundException(
TextUtils.formatSimple(
"File %s not found or unable to be opened in read-only mode.",
fileName));
}
return new FileInputStream(pfd.getFileDescriptor());
} catch (RemoteException | ExecutionException | TimeoutException e) {
throw (FileNotFoundException)
new FileNotFoundException("Cannot open file due to remote service failure")
.initCause(e);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
throw (FileNotFoundException)
new FileNotFoundException("Interrupted when opening a file.").initCause(e);
}
}
@NonNull
private static Integer[] intArrayToIntegerArray(@NonNull int[] integerSet) {
Integer[] intArray = new Integer[integerSet.length];
int i = 0;
for (Integer type : integerSet) {
intArray[i++] = type;
}
return intArray;
}
private static WearableSensingDataRequester createDataRequester(
RemoteCallback dataRequestCallback) {
return (request, requestStatusConsumer) -> {
Bundle bundle = new Bundle();
bundle.putParcelable(WearableSensingDataRequest.REQUEST_BUNDLE_KEY, request);
RemoteCallback requestStatusCallback =
new RemoteCallback(
requestStatusBundle -> {
requestStatusConsumer.accept(
requestStatusBundle.getInt(
WearableSensingManager.STATUS_RESPONSE_BUNDLE_KEY));
});
bundle.putParcelable(
WearableSensingDataRequest.REQUEST_STATUS_CALLBACK_BUNDLE_KEY,
requestStatusCallback);
dataRequestCallback.sendResult(bundle);
};
}
@NonNull
private static Consumer<Integer> createWearableStatusConsumer(RemoteCallback statusCallback) {
return response -> {
Bundle bundle = new Bundle();
bundle.putInt(STATUS_RESPONSE_BUNDLE_KEY, response);
statusCallback.sendResult(bundle);
};
}
}