545 lines
23 KiB
Java
545 lines
23 KiB
Java
/*
|
|
* Copyright (C) 2023 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.ondeviceintelligence;
|
|
|
|
import static android.app.ondeviceintelligence.flags.Flags.FLAG_ENABLE_ON_DEVICE_INTELLIGENCE;
|
|
|
|
import static com.android.internal.util.function.pooled.PooledLambda.obtainMessage;
|
|
|
|
import android.annotation.CallSuper;
|
|
import android.annotation.CallbackExecutor;
|
|
import android.annotation.FlaggedApi;
|
|
import android.annotation.NonNull;
|
|
import android.annotation.Nullable;
|
|
import android.annotation.SdkConstant;
|
|
import android.annotation.SystemApi;
|
|
import android.annotation.TestApi;
|
|
import android.app.Service;
|
|
import android.app.ondeviceintelligence.DownloadCallback;
|
|
import android.app.ondeviceintelligence.Feature;
|
|
import android.app.ondeviceintelligence.FeatureDetails;
|
|
import android.app.ondeviceintelligence.IDownloadCallback;
|
|
import android.app.ondeviceintelligence.IFeatureCallback;
|
|
import android.app.ondeviceintelligence.IFeatureDetailsCallback;
|
|
import android.app.ondeviceintelligence.IListFeaturesCallback;
|
|
import android.app.ondeviceintelligence.OnDeviceIntelligenceException;
|
|
import android.app.ondeviceintelligence.OnDeviceIntelligenceManager;
|
|
import android.app.ondeviceintelligence.OnDeviceIntelligenceManager.StateParams;
|
|
import android.content.Intent;
|
|
import android.os.Binder;
|
|
import android.os.Bundle;
|
|
import android.os.CancellationSignal;
|
|
import android.os.Handler;
|
|
import android.os.IBinder;
|
|
import android.os.ICancellationSignal;
|
|
import android.os.Looper;
|
|
import android.os.OutcomeReceiver;
|
|
import android.os.ParcelFileDescriptor;
|
|
import android.os.PersistableBundle;
|
|
import android.os.RemoteCallback;
|
|
import android.os.RemoteException;
|
|
import android.util.Log;
|
|
import android.util.Slog;
|
|
|
|
import com.android.internal.infra.AndroidFuture;
|
|
|
|
import java.io.File;
|
|
import java.io.FileNotFoundException;
|
|
import java.util.List;
|
|
import java.util.Map;
|
|
import java.util.Objects;
|
|
import java.util.concurrent.Executor;
|
|
import java.util.function.Consumer;
|
|
import java.util.function.LongConsumer;
|
|
|
|
/**
|
|
* Abstract base class for performing setup for on-device inference and providing file access to
|
|
* the isolated counter part {@link OnDeviceSandboxedInferenceService}.
|
|
*
|
|
* <p> A service that provides configuration and model files relevant to performing inference on
|
|
* device. The system's default OnDeviceIntelligenceService implementation is configured in
|
|
* {@code config_defaultOnDeviceIntelligenceService}. If this config has no value, a stub is
|
|
* returned.
|
|
*
|
|
* <p> Similar to {@link OnDeviceIntelligenceManager} class, the contracts in this service are
|
|
* defined to be open-ended in general, to allow interoperability. Therefore, it is recommended
|
|
* that implementations of this system-service expose this API to the clients via a library which
|
|
* has more defined contract.</p>
|
|
* <pre>
|
|
* {@literal
|
|
* <service android:name=".SampleOnDeviceIntelligenceService"
|
|
* android:permission="android.permission.BIND_ON_DEVICE_INTELLIGENCE_SERVICE">
|
|
* </service>}
|
|
* </pre>
|
|
*
|
|
* @hide
|
|
*/
|
|
@SystemApi
|
|
@FlaggedApi(FLAG_ENABLE_ON_DEVICE_INTELLIGENCE)
|
|
public abstract class OnDeviceIntelligenceService extends Service {
|
|
private static final String TAG = OnDeviceIntelligenceService.class.getSimpleName();
|
|
|
|
private volatile IRemoteProcessingService mRemoteProcessingService;
|
|
private Handler mHandler;
|
|
|
|
@CallSuper
|
|
@Override
|
|
public void onCreate() {
|
|
super.onCreate();
|
|
mHandler = new Handler(Looper.getMainLooper(), null /* callback */, true /* async */);
|
|
}
|
|
|
|
/**
|
|
* 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_ON_DEVICE_INTELLIGENCE_SERVICE}
|
|
* permission so that other applications can not abuse it.
|
|
*/
|
|
@SdkConstant(SdkConstant.SdkConstantType.SERVICE_ACTION)
|
|
public static final String SERVICE_INTERFACE =
|
|
"android.service.ondeviceintelligence.OnDeviceIntelligenceService";
|
|
|
|
|
|
/**
|
|
* @hide
|
|
*/
|
|
@Nullable
|
|
@Override
|
|
public final IBinder onBind(@NonNull Intent intent) {
|
|
if (SERVICE_INTERFACE.equals(intent.getAction())) {
|
|
return new IOnDeviceIntelligenceService.Stub() {
|
|
/** {@inheritDoc} */
|
|
@Override
|
|
public void ready() {
|
|
mHandler.executeOrSendMessage(
|
|
obtainMessage(OnDeviceIntelligenceService::onReady,
|
|
OnDeviceIntelligenceService.this));
|
|
}
|
|
|
|
@Override
|
|
public void getVersion(RemoteCallback remoteCallback) {
|
|
Objects.requireNonNull(remoteCallback);
|
|
mHandler.executeOrSendMessage(
|
|
obtainMessage(
|
|
OnDeviceIntelligenceService::onGetVersion,
|
|
OnDeviceIntelligenceService.this, l -> {
|
|
Bundle b = new Bundle();
|
|
b.putLong(
|
|
OnDeviceIntelligenceManager.API_VERSION_BUNDLE_KEY,
|
|
l);
|
|
remoteCallback.sendResult(b);
|
|
}));
|
|
}
|
|
|
|
@Override
|
|
public void listFeatures(int callerUid,
|
|
IListFeaturesCallback listFeaturesCallback) {
|
|
Objects.requireNonNull(listFeaturesCallback);
|
|
mHandler.executeOrSendMessage(
|
|
obtainMessage(
|
|
OnDeviceIntelligenceService::onListFeatures,
|
|
OnDeviceIntelligenceService.this, callerUid,
|
|
wrapListFeaturesCallback(listFeaturesCallback)));
|
|
}
|
|
|
|
@Override
|
|
public void getFeature(int callerUid, int id, IFeatureCallback featureCallback) {
|
|
Objects.requireNonNull(featureCallback);
|
|
mHandler.executeOrSendMessage(
|
|
obtainMessage(
|
|
OnDeviceIntelligenceService::onGetFeature,
|
|
OnDeviceIntelligenceService.this, callerUid,
|
|
id, wrapFeatureCallback(featureCallback)));
|
|
}
|
|
|
|
|
|
@Override
|
|
public void getFeatureDetails(int callerUid, Feature feature,
|
|
IFeatureDetailsCallback featureDetailsCallback) {
|
|
Objects.requireNonNull(feature);
|
|
Objects.requireNonNull(featureDetailsCallback);
|
|
mHandler.executeOrSendMessage(
|
|
obtainMessage(
|
|
OnDeviceIntelligenceService::onGetFeatureDetails,
|
|
OnDeviceIntelligenceService.this, callerUid,
|
|
feature, wrapFeatureDetailsCallback(featureDetailsCallback)));
|
|
}
|
|
|
|
@Override
|
|
public void requestFeatureDownload(int callerUid, Feature feature,
|
|
AndroidFuture cancellationSignalFuture,
|
|
IDownloadCallback downloadCallback) {
|
|
Objects.requireNonNull(feature);
|
|
Objects.requireNonNull(downloadCallback);
|
|
ICancellationSignal transport = null;
|
|
if (cancellationSignalFuture != null) {
|
|
transport = CancellationSignal.createTransport();
|
|
cancellationSignalFuture.complete(transport);
|
|
}
|
|
mHandler.executeOrSendMessage(
|
|
obtainMessage(
|
|
OnDeviceIntelligenceService::onDownloadFeature,
|
|
OnDeviceIntelligenceService.this, callerUid,
|
|
feature,
|
|
CancellationSignal.fromTransport(transport),
|
|
wrapDownloadCallback(downloadCallback)));
|
|
}
|
|
|
|
@Override
|
|
public void getReadOnlyFileDescriptor(String fileName,
|
|
AndroidFuture<ParcelFileDescriptor> future) {
|
|
Objects.requireNonNull(fileName);
|
|
Objects.requireNonNull(future);
|
|
mHandler.executeOrSendMessage(
|
|
obtainMessage(
|
|
OnDeviceIntelligenceService::onGetReadOnlyFileDescriptor,
|
|
OnDeviceIntelligenceService.this, fileName,
|
|
future));
|
|
}
|
|
|
|
@Override
|
|
public void getReadOnlyFeatureFileDescriptorMap(
|
|
Feature feature, RemoteCallback remoteCallback) {
|
|
Objects.requireNonNull(feature);
|
|
Objects.requireNonNull(remoteCallback);
|
|
mHandler.executeOrSendMessage(
|
|
obtainMessage(
|
|
OnDeviceIntelligenceService::onGetReadOnlyFeatureFileDescriptorMap,
|
|
OnDeviceIntelligenceService.this, feature,
|
|
parcelFileDescriptorMap -> {
|
|
Bundle bundle = new Bundle();
|
|
parcelFileDescriptorMap.forEach(bundle::putParcelable);
|
|
remoteCallback.sendResult(bundle);
|
|
}));
|
|
}
|
|
|
|
@Override
|
|
public void registerRemoteServices(
|
|
IRemoteProcessingService remoteProcessingService) {
|
|
mRemoteProcessingService = remoteProcessingService;
|
|
}
|
|
|
|
@Override
|
|
public void notifyInferenceServiceConnected() {
|
|
mHandler.executeOrSendMessage(
|
|
obtainMessage(
|
|
OnDeviceIntelligenceService::onInferenceServiceConnected,
|
|
OnDeviceIntelligenceService.this));
|
|
}
|
|
|
|
@Override
|
|
public void notifyInferenceServiceDisconnected() {
|
|
mHandler.executeOrSendMessage(
|
|
obtainMessage(
|
|
OnDeviceIntelligenceService::onInferenceServiceDisconnected,
|
|
OnDeviceIntelligenceService.this));
|
|
}
|
|
};
|
|
}
|
|
Slog.w(TAG, "Incorrect service interface, returning null.");
|
|
return null;
|
|
}
|
|
|
|
/**
|
|
* Using this signal to assertively a signal each time service binds successfully, used only in
|
|
* tests to get a signal that service instance is ready. This is needed because we cannot rely
|
|
* on {@link #onCreate} or {@link #onBind} to be invoke on each binding.
|
|
*
|
|
* @hide
|
|
*/
|
|
@TestApi
|
|
public void onReady() {
|
|
}
|
|
|
|
|
|
/**
|
|
* Invoked when a new instance of the remote inference service is created.
|
|
* This method should be used as a signal to perform any initialization operations, for e.g. by
|
|
* invoking the {@link #updateProcessingState} method to initialize the remote processing
|
|
* service.
|
|
*/
|
|
public abstract void onInferenceServiceConnected();
|
|
|
|
|
|
/**
|
|
* Invoked when an instance of the remote inference service is disconnected.
|
|
*/
|
|
public abstract void onInferenceServiceDisconnected();
|
|
|
|
|
|
/**
|
|
* Invoked by the {@link OnDeviceIntelligenceService} inorder to send updates to the inference
|
|
* service if there is a state change to be performed. State change could be config updates,
|
|
* performing initialization or cleanup tasks in the remote inference service.
|
|
* The Bundle passed in here is expected to be read-only and will be rejected if it has any
|
|
* writable fields as detailed under {@link StateParams}.
|
|
*
|
|
* @param processingState the updated state to be applied.
|
|
* @param callbackExecutor executor to the run status callback on.
|
|
* @param statusReceiver receiver to get status of the update state operation.
|
|
*/
|
|
public final void updateProcessingState(@NonNull @StateParams Bundle processingState,
|
|
@NonNull @CallbackExecutor Executor callbackExecutor,
|
|
@NonNull OutcomeReceiver<PersistableBundle, OnDeviceIntelligenceException> statusReceiver) {
|
|
Objects.requireNonNull(callbackExecutor);
|
|
if (mRemoteProcessingService == null) {
|
|
throw new IllegalStateException("Remote processing service is unavailable.");
|
|
}
|
|
try {
|
|
mRemoteProcessingService.updateProcessingState(processingState,
|
|
new IProcessingUpdateStatusCallback.Stub() {
|
|
@Override
|
|
public void onSuccess(PersistableBundle result) {
|
|
Binder.withCleanCallingIdentity(() -> {
|
|
callbackExecutor.execute(
|
|
() -> statusReceiver.onResult(result));
|
|
});
|
|
}
|
|
|
|
@Override
|
|
public void onFailure(int errorCode, String errorMessage) {
|
|
Binder.withCleanCallingIdentity(() -> callbackExecutor.execute(
|
|
() -> statusReceiver.onError(
|
|
new OnDeviceIntelligenceException(
|
|
errorCode, errorMessage))));
|
|
}
|
|
});
|
|
} catch (RemoteException e) {
|
|
Slog.e(TAG, "Error in updateProcessingState: " + e);
|
|
throw new RuntimeException(e);
|
|
}
|
|
}
|
|
|
|
private OutcomeReceiver<Feature,
|
|
OnDeviceIntelligenceException> wrapFeatureCallback(
|
|
IFeatureCallback featureCallback) {
|
|
return new OutcomeReceiver<>() {
|
|
@Override
|
|
public void onResult(@NonNull Feature feature) {
|
|
try {
|
|
featureCallback.onSuccess(feature);
|
|
} catch (RemoteException e) {
|
|
Slog.e(TAG, "Error sending feature: " + e);
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public void onError(
|
|
@NonNull OnDeviceIntelligenceException exception) {
|
|
try {
|
|
featureCallback.onFailure(exception.getErrorCode(), exception.getMessage(),
|
|
exception.getErrorParams());
|
|
} catch (RemoteException e) {
|
|
Slog.e(TAG, "Error sending download feature: " + e);
|
|
}
|
|
}
|
|
};
|
|
}
|
|
|
|
private OutcomeReceiver<List<Feature>,
|
|
OnDeviceIntelligenceException> wrapListFeaturesCallback(
|
|
IListFeaturesCallback listFeaturesCallback) {
|
|
return new OutcomeReceiver<>() {
|
|
@Override
|
|
public void onResult(@NonNull List<Feature> features) {
|
|
try {
|
|
listFeaturesCallback.onSuccess(features);
|
|
} catch (RemoteException e) {
|
|
Slog.e(TAG, "Error sending feature: " + e);
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public void onError(
|
|
@NonNull OnDeviceIntelligenceException exception) {
|
|
try {
|
|
listFeaturesCallback.onFailure(exception.getErrorCode(), exception.getMessage(),
|
|
exception.getErrorParams());
|
|
} catch (RemoteException e) {
|
|
Slog.e(TAG, "Error sending download feature: " + e);
|
|
}
|
|
}
|
|
};
|
|
}
|
|
|
|
private OutcomeReceiver<FeatureDetails,
|
|
OnDeviceIntelligenceException> wrapFeatureDetailsCallback(
|
|
IFeatureDetailsCallback featureStatusCallback) {
|
|
return new OutcomeReceiver<>() {
|
|
@Override
|
|
public void onResult(FeatureDetails result) {
|
|
try {
|
|
featureStatusCallback.onSuccess(result);
|
|
} catch (RemoteException e) {
|
|
Slog.e(TAG, "Error sending feature status: " + e);
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public void onError(
|
|
@NonNull OnDeviceIntelligenceException exception) {
|
|
try {
|
|
featureStatusCallback.onFailure(exception.getErrorCode(),
|
|
exception.getMessage(), exception.getErrorParams());
|
|
} catch (RemoteException e) {
|
|
Slog.e(TAG, "Error sending feature status: " + e);
|
|
}
|
|
}
|
|
};
|
|
}
|
|
|
|
|
|
private DownloadCallback wrapDownloadCallback(IDownloadCallback downloadCallback) {
|
|
return new DownloadCallback() {
|
|
@Override
|
|
public void onDownloadStarted(long bytesToDownload) {
|
|
try {
|
|
downloadCallback.onDownloadStarted(bytesToDownload);
|
|
} catch (RemoteException e) {
|
|
Slog.e(TAG, "Error sending download status: " + e);
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public void onDownloadFailed(int failureStatus,
|
|
String errorMessage, @NonNull PersistableBundle errorParams) {
|
|
try {
|
|
downloadCallback.onDownloadFailed(failureStatus, errorMessage, errorParams);
|
|
} catch (RemoteException e) {
|
|
Slog.e(TAG, "Error sending download status: " + e);
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public void onDownloadProgress(long totalBytesDownloaded) {
|
|
try {
|
|
downloadCallback.onDownloadProgress(totalBytesDownloaded);
|
|
} catch (RemoteException e) {
|
|
Slog.e(TAG, "Error sending download status: " + e);
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public void onDownloadCompleted(@NonNull PersistableBundle persistableBundle) {
|
|
try {
|
|
downloadCallback.onDownloadCompleted(persistableBundle);
|
|
} catch (RemoteException e) {
|
|
Slog.e(TAG, "Error sending download status: " + e);
|
|
}
|
|
}
|
|
};
|
|
}
|
|
|
|
private void onGetReadOnlyFileDescriptor(@NonNull String fileName,
|
|
@NonNull AndroidFuture<ParcelFileDescriptor> future) {
|
|
Slog.v(TAG, "onGetReadOnlyFileDescriptor " + fileName);
|
|
Binder.withCleanCallingIdentity(() -> {
|
|
Slog.v(TAG,
|
|
"onGetReadOnlyFileDescriptor: " + fileName + " under internal app storage.");
|
|
File f = new File(getBaseContext().getFilesDir(), fileName);
|
|
if (!f.exists()) {
|
|
f = new File(fileName);
|
|
}
|
|
ParcelFileDescriptor pfd = null;
|
|
try {
|
|
pfd = ParcelFileDescriptor.open(f, ParcelFileDescriptor.MODE_READ_ONLY);
|
|
Slog.d(TAG, "Successfully opened a file with ParcelFileDescriptor.");
|
|
} catch (FileNotFoundException e) {
|
|
Slog.e(TAG, "Cannot open file. No ParcelFileDescriptor returned.");
|
|
future.completeExceptionally(e);
|
|
} finally {
|
|
future.complete(pfd);
|
|
if (pfd != null) {
|
|
pfd.close();
|
|
}
|
|
}
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Provide implementation for a scenario when caller wants to get all feature related
|
|
* file-descriptors that might be required for processing a request for the corresponding the
|
|
* feature.
|
|
*
|
|
* @param feature the feature for which files need to be opened.
|
|
* @param fileDescriptorMapConsumer callback to be populated with a map of file-path and
|
|
* corresponding ParcelDescriptor to be used in a remote
|
|
* service.
|
|
*/
|
|
public abstract void onGetReadOnlyFeatureFileDescriptorMap(
|
|
@NonNull Feature feature,
|
|
@NonNull Consumer<Map<String, ParcelFileDescriptor>> fileDescriptorMapConsumer);
|
|
|
|
/**
|
|
* Request download for feature that is requested and listen to download progress updates. If
|
|
* the download completes successfully, success callback should be populated.
|
|
*
|
|
* @param callerUid UID of the caller that initiated this call chain.
|
|
* @param feature the feature for which files need to be downlaoded.
|
|
* process.
|
|
* @param cancellationSignal signal to attach a listener to, and receive cancellation signals
|
|
* from thw client.
|
|
* @param downloadCallback callback to populate download updates for clients to listen on..
|
|
*/
|
|
public abstract void onDownloadFeature(
|
|
int callerUid, @NonNull Feature feature,
|
|
@Nullable CancellationSignal cancellationSignal,
|
|
@NonNull DownloadCallback downloadCallback);
|
|
|
|
/**
|
|
* Provide feature details for the passed in feature. Usually the client and remote
|
|
* implementation use the {@link Feature#getFeatureParams()} as a hint to communicate what
|
|
* details the client is looking for.
|
|
*
|
|
* @param callerUid UID of the caller that initiated this call chain.
|
|
* @param feature the feature for which status needs to be known.
|
|
* @param featureDetailsCallback callback to populate the resulting feature status.
|
|
*/
|
|
public abstract void onGetFeatureDetails(int callerUid, @NonNull Feature feature,
|
|
@NonNull OutcomeReceiver<FeatureDetails,
|
|
OnDeviceIntelligenceException> featureDetailsCallback);
|
|
|
|
|
|
/**
|
|
* Get feature using the provided identifier to the remote implementation.
|
|
*
|
|
* @param callerUid UID of the caller that initiated this call chain.
|
|
* @param featureCallback callback to populate the features list.
|
|
*/
|
|
public abstract void onGetFeature(int callerUid, int featureId,
|
|
@NonNull OutcomeReceiver<Feature,
|
|
OnDeviceIntelligenceException> featureCallback);
|
|
|
|
/**
|
|
* List all features which are available in the remote implementation. The implementation might
|
|
* choose to provide only a certain list of features based on the caller.
|
|
*
|
|
* @param callerUid UID of the caller that initiated this call chain.
|
|
* @param listFeaturesCallback callback to populate the features list.
|
|
*/
|
|
public abstract void onListFeatures(int callerUid, @NonNull OutcomeReceiver<List<Feature>,
|
|
OnDeviceIntelligenceException> listFeaturesCallback);
|
|
|
|
/**
|
|
* Provides a long value representing the version of the remote implementation processing
|
|
* requests.
|
|
*
|
|
* @param versionConsumer consumer to populate the version.
|
|
*/
|
|
public abstract void onGetVersion(@NonNull LongConsumer versionConsumer);
|
|
}
|