/*
 * 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
     * WearableSensingDataRequest#getRateLimit()} requests can be sent every rolling {@link
     * WearableSensingDataRequest#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 #onDataRequestObserverRegistered(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 #onStopHotwordRecognition(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
     * #onStartHotwordRecognition(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 #onStartHotwordRecognition(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
     * #onStartHotwordRecognition(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);
        };
    }


}
