script-astra/Android/Sdk/sources/android-35/android/service/ondeviceintelligence/OnDeviceIntelligenceService.java

545 lines
23 KiB
Java
Raw Permalink Normal View History

2025-01-20 15:15:20 +00:00
/*
* 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);
}