diff options
13 files changed, 1505 insertions, 1 deletions
diff --git a/core/api/system-current.txt b/core/api/system-current.txt index a5e62ace7278..627b70355f50 100644 --- a/core/api/system-current.txt +++ b/core/api/system-current.txt @@ -2276,6 +2276,7 @@ package android.app.ondeviceintelligence { method @RequiresPermission(android.Manifest.permission.USE_ON_DEVICE_INTELLIGENCE) public void processRequestStreaming(@NonNull android.app.ondeviceintelligence.Feature, @NonNull android.app.ondeviceintelligence.Content, int, @Nullable android.os.CancellationSignal, @Nullable android.app.ondeviceintelligence.ProcessingSignal, @NonNull java.util.concurrent.Executor, @NonNull android.app.ondeviceintelligence.StreamingResponseReceiver<android.app.ondeviceintelligence.Content,android.app.ondeviceintelligence.Content,android.app.ondeviceintelligence.OnDeviceIntelligenceManager.OnDeviceIntelligenceManagerProcessingException>); method @RequiresPermission(android.Manifest.permission.USE_ON_DEVICE_INTELLIGENCE) public void requestFeatureDownload(@NonNull android.app.ondeviceintelligence.Feature, @Nullable android.os.CancellationSignal, @NonNull java.util.concurrent.Executor, @NonNull android.app.ondeviceintelligence.DownloadCallback); method @RequiresPermission(android.Manifest.permission.USE_ON_DEVICE_INTELLIGENCE) public void requestTokenCount(@NonNull android.app.ondeviceintelligence.Feature, @NonNull android.app.ondeviceintelligence.Content, @Nullable android.os.CancellationSignal, @NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<java.lang.Long,android.app.ondeviceintelligence.OnDeviceIntelligenceManager.OnDeviceIntelligenceManagerException>); + field public static final String API_VERSION_BUNDLE_KEY = "ApiVersionBundleKey"; field public static final int REQUEST_TYPE_EMBEDDINGS = 2; // 0x2 field public static final int REQUEST_TYPE_INFERENCE = 0; // 0x0 field public static final int REQUEST_TYPE_PREPARE = 1; // 0x1 @@ -12957,6 +12958,34 @@ package android.service.oemlock { } +package android.service.ondeviceintelligence { + + @FlaggedApi("android.app.ondeviceintelligence.flags.enable_on_device_intelligence") public abstract class OnDeviceIntelligenceService extends android.app.Service { + ctor public OnDeviceIntelligenceService(); + method @Nullable public final android.os.IBinder onBind(@NonNull android.content.Intent); + method public abstract void onDownloadFeature(@NonNull android.app.ondeviceintelligence.Feature, @Nullable android.os.CancellationSignal, @NonNull android.app.ondeviceintelligence.DownloadCallback); + method public abstract void onGetFeature(int, @NonNull android.os.OutcomeReceiver<android.app.ondeviceintelligence.Feature,android.app.ondeviceintelligence.OnDeviceIntelligenceManager.OnDeviceIntelligenceManagerException>); + method public abstract void onGetFeatureDetails(@NonNull android.app.ondeviceintelligence.Feature, @NonNull android.os.OutcomeReceiver<android.app.ondeviceintelligence.FeatureDetails,android.app.ondeviceintelligence.OnDeviceIntelligenceManager.OnDeviceIntelligenceManagerException>); + method public abstract void onGetReadOnlyFeatureFileDescriptorMap(@NonNull android.app.ondeviceintelligence.Feature, @NonNull java.util.function.Consumer<java.util.Map<java.lang.String,android.os.ParcelFileDescriptor>>); + method public abstract void onGetVersion(@NonNull java.util.function.LongConsumer); + method public abstract void onListFeatures(@NonNull android.os.OutcomeReceiver<java.util.List<android.app.ondeviceintelligence.Feature>,android.app.ondeviceintelligence.OnDeviceIntelligenceManager.OnDeviceIntelligenceManagerException>); + field public static final String SERVICE_INTERFACE = "android.service.ondeviceintelligence.OnDeviceIntelligenceService"; + } + + @FlaggedApi("android.app.ondeviceintelligence.flags.enable_on_device_intelligence") public abstract class OnDeviceTrustedInferenceService extends android.app.Service { + ctor public OnDeviceTrustedInferenceService(); + method public final void fetchFeatureFileInputStreamMap(@NonNull android.app.ondeviceintelligence.Feature, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.util.Map<java.lang.String,java.io.FileInputStream>>); + method @Nullable public final android.os.IBinder onBind(@NonNull android.content.Intent); + method @NonNull public abstract void onCountTokens(@NonNull android.app.ondeviceintelligence.Feature, @NonNull android.app.ondeviceintelligence.Content, @Nullable android.os.CancellationSignal, @NonNull android.os.OutcomeReceiver<java.lang.Long,android.app.ondeviceintelligence.OnDeviceIntelligenceManager.OnDeviceIntelligenceManagerProcessingException>); + method @NonNull public abstract void onProcessRequest(@NonNull android.app.ondeviceintelligence.Feature, @NonNull android.app.ondeviceintelligence.Content, int, @Nullable android.os.CancellationSignal, @Nullable android.app.ondeviceintelligence.ProcessingSignal, @NonNull android.os.OutcomeReceiver<android.app.ondeviceintelligence.Content,android.app.ondeviceintelligence.OnDeviceIntelligenceManager.OnDeviceIntelligenceManagerProcessingException>); + method @NonNull public abstract void onProcessRequestStreaming(@NonNull android.app.ondeviceintelligence.Feature, @NonNull android.app.ondeviceintelligence.Content, int, @Nullable android.os.CancellationSignal, @Nullable android.app.ondeviceintelligence.ProcessingSignal, @NonNull android.app.ondeviceintelligence.StreamingResponseReceiver<android.app.ondeviceintelligence.Content,android.app.ondeviceintelligence.Content,android.app.ondeviceintelligence.OnDeviceIntelligenceManager.OnDeviceIntelligenceManagerProcessingException>); + method public final java.io.FileInputStream openFileInput(@NonNull String) throws java.io.FileNotFoundException; + method public final void openFileInputAsync(@NonNull String, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.io.FileInputStream>) throws java.io.FileNotFoundException; + field public static final String SERVICE_INTERFACE = "android.service.ondeviceintelligence.OnDeviceTrustedInferenceService"; + } + +} + package android.service.persistentdata { @FlaggedApi("android.security.frp_enforcement") public class PersistentDataBlockManager { diff --git a/core/api/system-lint-baseline.txt b/core/api/system-lint-baseline.txt index ccfb17a12261..1923641e2d4e 100644 --- a/core/api/system-lint-baseline.txt +++ b/core/api/system-lint-baseline.txt @@ -509,6 +509,12 @@ GenericException: android.service.autofill.augmented.FillWindow#finalize(): Methods must not throw generic exceptions (`java.lang.Throwable`) +InvalidNullabilityOverride: android.service.ondeviceintelligence.OnDeviceIntelligenceService#onBind(android.content.Intent) parameter #0: + Invalid nullability on parameter `intent` in method `onBind`. Parameters of overrides cannot be NonNull if the super parameter is unannotated. +InvalidNullabilityOverride: android.service.ondeviceintelligence.OnDeviceTrustedInferenceService#onBind(android.content.Intent) parameter #0: + Invalid nullability on parameter `intent` in method `onBind`. Parameters of overrides cannot be NonNull if the super parameter is unannotated. +InvalidNullabilityOverride: android.service.ondeviceintelligence.OnDeviceTrustedInferenceService#openFileInput(String) parameter #0: + Invalid nullability on parameter `filename` in method `openFileInput`. Parameters of overrides cannot be NonNull if the super parameter is unannotated. InvalidNullabilityOverride: android.service.textclassifier.TextClassifierService#onUnbind(android.content.Intent) parameter #0: Invalid nullability on parameter `intent` in method `onUnbind`. Parameters of overrides cannot be NonNull if the super parameter is unannotated. InvalidNullabilityOverride: android.service.voice.HotwordDetectionService#getSystemService(String) parameter #0: @@ -565,6 +571,8 @@ MissingNullability: android.service.contentcapture.ContentCaptureService#dump(ja Missing nullability on parameter `args` in method `dump` MissingNullability: android.service.notification.NotificationAssistantService#attachBaseContext(android.content.Context) parameter #0: Missing nullability on parameter `base` in method `attachBaseContext` +MissingNullability: android.service.ondeviceintelligence.OnDeviceTrustedInferenceService#openFileInput(String): + Missing nullability on method `openFileInput` return MissingNullability: android.telephony.NetworkService#onUnbind(android.content.Intent) parameter #0: Missing nullability on parameter `intent` in method `onUnbind` MissingNullability: android.telephony.data.DataService#onUnbind(android.content.Intent) parameter #0: diff --git a/core/java/android/app/ondeviceintelligence/OnDeviceIntelligenceManager.java b/core/java/android/app/ondeviceintelligence/OnDeviceIntelligenceManager.java index d34ab60068c8..4d8e0d55e355 100644 --- a/core/java/android/app/ondeviceintelligence/OnDeviceIntelligenceManager.java +++ b/core/java/android/app/ondeviceintelligence/OnDeviceIntelligenceManager.java @@ -60,7 +60,7 @@ import java.util.function.LongConsumer; @SystemService(Context.ON_DEVICE_INTELLIGENCE_SERVICE) @FlaggedApi(FLAG_ENABLE_ON_DEVICE_INTELLIGENCE) public class OnDeviceIntelligenceManager { - private static final String API_VERSION_BUNDLE_KEY = "ApiVersionBundleKey"; + public static final String API_VERSION_BUNDLE_KEY = "ApiVersionBundleKey"; private final Context mContext; private final IOnDeviceIntelligenceManager mService; @@ -86,6 +86,10 @@ public class OnDeviceIntelligenceManager { // version and package name of the remote service implementing this. try { RemoteCallback callback = new RemoteCallback(result -> { + if (result == null) { + Binder.withCleanCallingIdentity( + () -> callbackExecutor.execute(() -> versionConsumer.accept(0))); + } long version = result.getLong(API_VERSION_BUNDLE_KEY); Binder.withCleanCallingIdentity( () -> callbackExecutor.execute(() -> versionConsumer.accept(version))); diff --git a/core/java/android/service/ondeviceintelligence/IOnDeviceIntelligenceService.aidl b/core/java/android/service/ondeviceintelligence/IOnDeviceIntelligenceService.aidl new file mode 100644 index 000000000000..bbb4bc6c0272 --- /dev/null +++ b/core/java/android/service/ondeviceintelligence/IOnDeviceIntelligenceService.aidl @@ -0,0 +1,44 @@ +/* + * 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.ondeviceintelligence; + +import android.os.PersistableBundle; +import android.os.ParcelFileDescriptor; +import android.os.ICancellationSignal; +import android.os.RemoteCallback; +import android.app.ondeviceintelligence.IDownloadCallback; +import android.app.ondeviceintelligence.Feature; +import android.app.ondeviceintelligence.IFeatureCallback; +import android.app.ondeviceintelligence.IListFeaturesCallback; +import android.app.ondeviceintelligence.IFeatureDetailsCallback; +import com.android.internal.infra.AndroidFuture; + + +/** + * Interface for a concrete implementation to provide on device intelligence services. + * + * @hide + */ +oneway interface IOnDeviceIntelligenceService { + void getVersion(in RemoteCallback remoteCallback); + void getFeature(in int featureId, in IFeatureCallback featureCallback); + void listFeatures(in IListFeaturesCallback listFeaturesCallback); + void getFeatureDetails(in Feature feature, in IFeatureDetailsCallback featureDetailsCallback); + void getReadOnlyFileDescriptor(in String fileName, in AndroidFuture<ParcelFileDescriptor> future); + void getReadOnlyFeatureFileDescriptorMap(in Feature feature, in RemoteCallback remoteCallback); + void requestFeatureDownload(in Feature feature, in ICancellationSignal cancellationSignal, in IDownloadCallback downloadCallback); +}
\ No newline at end of file diff --git a/core/java/android/service/ondeviceintelligence/IOnDeviceTrustedInferenceService.aidl b/core/java/android/service/ondeviceintelligence/IOnDeviceTrustedInferenceService.aidl new file mode 100644 index 000000000000..08eb9278fcc4 --- /dev/null +++ b/core/java/android/service/ondeviceintelligence/IOnDeviceTrustedInferenceService.aidl @@ -0,0 +1,44 @@ +/* + * 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.ondeviceintelligence; + +import android.app.ondeviceintelligence.IStreamingResponseCallback; +import android.app.ondeviceintelligence.IResponseCallback; +import android.app.ondeviceintelligence.ITokenCountCallback; +import android.app.ondeviceintelligence.IProcessingSignal; +import android.app.ondeviceintelligence.Content; +import android.app.ondeviceintelligence.Feature; +import android.os.ICancellationSignal; +import android.service.ondeviceintelligence.IRemoteStorageService; + + +/** + * Interface for a concrete implementation to provide on device trusted inference. + * + * @hide + */ +oneway interface IOnDeviceTrustedInferenceService { + void registerRemoteStorageService(in IRemoteStorageService storageService); + void requestTokenCount(in Feature feature, in Content request, in ICancellationSignal cancellationSignal, + in ITokenCountCallback tokenCountCallback); + void processRequest(in Feature feature, in Content request, in int requestType, + in ICancellationSignal cancellationSignal, in IProcessingSignal processingSignal, + in IResponseCallback callback); + void processRequestStreaming(in Feature feature, in Content request, in int requestType, + in ICancellationSignal cancellationSignal, in IProcessingSignal processingSignal, + in IStreamingResponseCallback callback); +}
\ No newline at end of file diff --git a/core/java/android/service/ondeviceintelligence/IRemoteStorageService.aidl b/core/java/android/service/ondeviceintelligence/IRemoteStorageService.aidl new file mode 100644 index 000000000000..a6f49e17d80e --- /dev/null +++ b/core/java/android/service/ondeviceintelligence/IRemoteStorageService.aidl @@ -0,0 +1,34 @@ +/* + * 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.ondeviceintelligence; + +import android.app.ondeviceintelligence.Feature; +import android.os.ParcelFileDescriptor; +import android.os.RemoteCallback; + +import com.android.internal.infra.AndroidFuture; + +/** + * Interface for a concrete implementation to provide access to storage read access + * for the isolated process. + * + * @hide + */ +oneway interface IRemoteStorageService { + void getReadOnlyFileDescriptor(in String filePath, in AndroidFuture<ParcelFileDescriptor> future); + void getReadOnlyFeatureFileDescriptorMap(in Feature feature, in RemoteCallback remoteCallback); +}
\ No newline at end of file diff --git a/core/java/android/service/ondeviceintelligence/OnDeviceIntelligenceService.java b/core/java/android/service/ondeviceintelligence/OnDeviceIntelligenceService.java new file mode 100644 index 000000000000..0cba1d37721a --- /dev/null +++ b/core/java/android/service/ondeviceintelligence/OnDeviceIntelligenceService.java @@ -0,0 +1,383 @@ +/* + * 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 android.annotation.FlaggedApi; +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.annotation.SdkConstant; +import android.annotation.SystemApi; +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.OnDeviceIntelligenceManager; +import android.content.Intent; +import android.os.Binder; +import android.os.Bundle; +import android.os.CancellationSignal; +import android.os.IBinder; +import android.os.ICancellationSignal; +import android.os.OutcomeReceiver; +import android.os.ParcelFileDescriptor; +import android.os.PersistableBundle; +import android.os.RemoteCallback; +import android.os.RemoteException; +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.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 OnDeviceTrustedInferenceService}. + * + * <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. + * + * <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(); + + /** + * 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 getVersion(RemoteCallback remoteCallback) { + Objects.requireNonNull(remoteCallback); + OnDeviceIntelligenceService.this.onGetVersion(l -> { + Bundle b = new Bundle(); + b.putLong(OnDeviceIntelligenceManager.API_VERSION_BUNDLE_KEY, l); + remoteCallback.sendResult(b); + }); + } + + @Override + public void listFeatures(IListFeaturesCallback listFeaturesCallback) { + Objects.requireNonNull(listFeaturesCallback); + OnDeviceIntelligenceService.this.onListFeatures( + wrapListFeaturesCallback(listFeaturesCallback)); + } + + @Override + public void getFeature(int id, IFeatureCallback featureCallback) { + Objects.requireNonNull(featureCallback); + OnDeviceIntelligenceService.this.onGetFeature(id, + wrapFeatureCallback(featureCallback)); + } + + + @Override + public void getFeatureDetails(Feature feature, + IFeatureDetailsCallback featureDetailsCallback) { + Objects.requireNonNull(feature); + Objects.requireNonNull(featureDetailsCallback); + + OnDeviceIntelligenceService.this.onGetFeatureDetails(feature, + wrapFeatureDetailsCallback(featureDetailsCallback)); + } + + @Override + public void requestFeatureDownload(Feature feature, + ICancellationSignal cancellationSignal, + IDownloadCallback downloadCallback) { + Objects.requireNonNull(feature); + Objects.requireNonNull(downloadCallback); + + OnDeviceIntelligenceService.this.onDownloadFeature(feature, + CancellationSignal.fromTransport(cancellationSignal), + wrapDownloadCallback(downloadCallback)); + } + + @Override + public void getReadOnlyFileDescriptor(String fileName, + AndroidFuture<ParcelFileDescriptor> future) { + Objects.requireNonNull(fileName); + Objects.requireNonNull(future); + + OnDeviceIntelligenceService.this.onGetReadOnlyFileDescriptor(fileName, + future); + } + + @Override + public void getReadOnlyFeatureFileDescriptorMap( + Feature feature, RemoteCallback remoteCallback) { + Objects.requireNonNull(feature); + Objects.requireNonNull(remoteCallback); + + OnDeviceIntelligenceService.this.onGetReadOnlyFeatureFileDescriptorMap( + feature, parcelFileDescriptorMap -> { + Bundle bundle = new Bundle(); + parcelFileDescriptorMap.forEach(bundle::putParcelable); + remoteCallback.sendResult(bundle); + }); + } + }; + } + Slog.w(TAG, "Incorrect service interface, returning null."); + return null; + } + + private OutcomeReceiver<Feature, + OnDeviceIntelligenceManager.OnDeviceIntelligenceManagerException> 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 OnDeviceIntelligenceManager.OnDeviceIntelligenceManagerException 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>, + OnDeviceIntelligenceManager.OnDeviceIntelligenceManagerException> 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 OnDeviceIntelligenceManager.OnDeviceIntelligenceManagerException exception) { + try { + listFeaturesCallback.onFailure(exception.getErrorCode(), exception.getMessage(), + exception.getErrorParams()); + } catch (RemoteException e) { + Slog.e(TAG, "Error sending download feature: " + e); + } + } + }; + } + + private OutcomeReceiver<FeatureDetails, + OnDeviceIntelligenceManager.OnDeviceIntelligenceManagerException> 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 OnDeviceIntelligenceManager.OnDeviceIntelligenceManagerException 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); + 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."); + } finally { + future.complete(pfd); + } + }); + } + + /** + * 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 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( + @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 feature the feature for which status needs to be known. + * @param featureStatusCallback callback to populate the resulting feature status. + */ + public abstract void onGetFeatureDetails(@NonNull Feature feature, + @NonNull OutcomeReceiver<FeatureDetails, + OnDeviceIntelligenceManager.OnDeviceIntelligenceManagerException> featureStatusCallback); + + + /** + * Get feature using the provided identifier to the remote implementation. + * + * @param featureCallback callback to populate the features list. + */ + public abstract void onGetFeature(int featureId, + @NonNull OutcomeReceiver<Feature, + OnDeviceIntelligenceManager.OnDeviceIntelligenceManagerException> 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 listFeaturesCallback callback to populate the features list. + */ + public abstract void onListFeatures(@NonNull OutcomeReceiver<List<Feature>, + OnDeviceIntelligenceManager.OnDeviceIntelligenceManagerException> 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); +} diff --git a/core/java/android/service/ondeviceintelligence/OnDeviceTrustedInferenceService.java b/core/java/android/service/ondeviceintelligence/OnDeviceTrustedInferenceService.java new file mode 100644 index 000000000000..96982e3d7829 --- /dev/null +++ b/core/java/android/service/ondeviceintelligence/OnDeviceTrustedInferenceService.java @@ -0,0 +1,410 @@ +/* + * 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 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.app.Service; +import android.app.ondeviceintelligence.Content; +import android.app.ondeviceintelligence.Feature; +import android.app.ondeviceintelligence.IProcessingSignal; +import android.app.ondeviceintelligence.IResponseCallback; +import android.app.ondeviceintelligence.IStreamingResponseCallback; +import android.app.ondeviceintelligence.ITokenCountCallback; +import android.app.ondeviceintelligence.OnDeviceIntelligenceManager; +import android.app.ondeviceintelligence.ProcessingSignal; +import android.app.ondeviceintelligence.StreamingResponseReceiver; +import android.content.Context; +import android.content.Intent; +import android.os.CancellationSignal; +import android.os.IBinder; +import android.os.ICancellationSignal; +import android.os.OutcomeReceiver; +import android.os.ParcelFileDescriptor; +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.FileInputStream; +import java.io.FileNotFoundException; +import java.util.HashMap; +import java.util.Map; +import java.util.Objects; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.Executor; +import java.util.function.Consumer; + +/** + * Abstract base class for performing inference in a isolated process. This service exposes its + * methods via {@link android.app.ondeviceintelligence.OnDeviceIntelligenceManager}. + * + * <p> A service that provides methods to perform on-device inference both in streaming and + * non-streaming fashion. Also, provides a way to register a storage service that will be used to + * read-only access files from the {@link OnDeviceIntelligenceService} counterpart. </p> + * + * <pre> + * {@literal + * <service android:name=".SampleTrustedInferenceService" + * android:permission="android.permission.BIND_ONDEVICE_TRUSTED_INFERENCE_SERVICE" + * android:isolatedProcess="true"> + * </service>} + * </pre> + * + * @hide + */ +@SystemApi +@FlaggedApi(FLAG_ENABLE_ON_DEVICE_INTELLIGENCE) +public abstract class OnDeviceTrustedInferenceService extends Service { + private static final String TAG = OnDeviceTrustedInferenceService.class.getSimpleName(); + + /** + * 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_TRUSTED_INFERENCE_SERVICE} + * permission so that other applications can not abuse it. + */ + @SdkConstant(SdkConstant.SdkConstantType.SERVICE_ACTION) + public static final String SERVICE_INTERFACE = + "android.service.ondeviceintelligence.OnDeviceTrustedInferenceService"; + + private IRemoteStorageService mRemoteStorageService; + + /** + * @hide + */ + @Nullable + @Override + public final IBinder onBind(@NonNull Intent intent) { + if (SERVICE_INTERFACE.equals(intent.getAction())) { + return new IOnDeviceTrustedInferenceService.Stub() { + @Override + public void registerRemoteStorageService(IRemoteStorageService storageService) { + Objects.requireNonNull(storageService); + mRemoteStorageService = storageService; + } + + @Override + public void requestTokenCount(Feature feature, Content request, + ICancellationSignal cancellationSignal, + ITokenCountCallback tokenCountCallback) { + Objects.requireNonNull(feature); + Objects.requireNonNull(tokenCountCallback); + OnDeviceTrustedInferenceService.this.onCountTokens(feature, + request, + CancellationSignal.fromTransport(cancellationSignal), + wrapTokenCountCallback(tokenCountCallback)); + } + + @Override + public void processRequestStreaming(Feature feature, Content request, + int requestType, ICancellationSignal cancellationSignal, + IProcessingSignal processingSignal, + IStreamingResponseCallback callback) { + Objects.requireNonNull(feature); + Objects.requireNonNull(request); + Objects.requireNonNull(callback); + + OnDeviceTrustedInferenceService.this.onProcessRequestStreaming(feature, + request, + requestType, + CancellationSignal.fromTransport(cancellationSignal), + ProcessingSignal.fromTransport(processingSignal), + wrapStreamingResponseCallback(callback) + ); + } + + @Override + public void processRequest(Feature feature, Content request, + int requestType, ICancellationSignal cancellationSignal, + IProcessingSignal processingSignal, + IResponseCallback callback) { + Objects.requireNonNull(feature); + Objects.requireNonNull(request); + Objects.requireNonNull(callback); + + + OnDeviceTrustedInferenceService.this.onProcessRequest(feature, request, + requestType, CancellationSignal.fromTransport(cancellationSignal), + ProcessingSignal.fromTransport(processingSignal), + wrapResponseCallback(callback) + ); + } + }; + } + Slog.w(TAG, "Incorrect service interface, returning null."); + return null; + } + + /** + * Invoked when caller wants to obtain a count of number of tokens present in the passed in + * Request associated with the provided feature. + * The expectation from the implementation is that when processing is complete, it + * should provide the token count in the {@link OutcomeReceiver#onResult}. + * + * @param feature feature which is associated with the request. + * @param request request that requires processing. + * @param cancellationSignal Cancellation Signal to receive cancellation events from client and + * configure a listener to. + * @param callback callback to populate failure and full response for the provided + * request. + */ + @NonNull + public abstract void onCountTokens( + @NonNull Feature feature, + @NonNull Content request, + @Nullable CancellationSignal cancellationSignal, + @NonNull OutcomeReceiver<Long, + OnDeviceIntelligenceManager.OnDeviceIntelligenceManagerProcessingException> callback); + + /** + * Invoked when caller provides a request for a particular feature to be processed in a + * streaming manner. The expectation from the implementation is that when processing the + * request, + * it periodically populates the {@link StreamingResponseReceiver#onNewContent} to continuously + * provide partial Content results for the caller to utilize. Optionally the implementation can + * provide the complete response in the {@link StreamingResponseReceiver#onResult} upon + * processing completion. + * + * @param feature feature which is associated with the request. + * @param request request that requires processing. + * @param requestType identifier representing the type of request. + * @param cancellationSignal Cancellation Signal to receive cancellation events from client and + * configure a listener to. + * @param processingSignal Signal to receive custom action instructions from client. + * @param callback callback to populate the partial responses, failure and optionally + * full response for the provided request. + */ + @NonNull + public abstract void onProcessRequestStreaming( + @NonNull Feature feature, + @NonNull Content request, + @OnDeviceIntelligenceManager.RequestType int requestType, + @Nullable CancellationSignal cancellationSignal, + @Nullable ProcessingSignal processingSignal, + @NonNull StreamingResponseReceiver<Content, Content, + OnDeviceIntelligenceManager.OnDeviceIntelligenceManagerProcessingException> callback); + + /** + * Invoked when caller provides a request for a particular feature to be processed in one shot + * completely. + * The expectation from the implementation is that when processing the request is complete, it + * should + * provide the complete response in the {@link OutcomeReceiver#onResult}. + * + * @param feature feature which is associated with the request. + * @param request request that requires processing. + * @param requestType identifier representing the type of request. + * @param cancellationSignal Cancellation Signal to receive cancellation events from client and + * configure a listener to. + * @param processingSignal Signal to receive custom action instructions from client. + * @param callback callback to populate failure and full response for the provided + * request. + */ + @NonNull + public abstract void onProcessRequest( + @NonNull Feature feature, + @NonNull Content request, + @OnDeviceIntelligenceManager.RequestType int requestType, + @Nullable CancellationSignal cancellationSignal, + @Nullable ProcessingSignal processingSignal, + @NonNull OutcomeReceiver<Content, + OnDeviceIntelligenceManager.OnDeviceIntelligenceManagerProcessingException> callback); + + /** + * Overrides {@link Context#openFileInput} to read files with the given file names under the + * internal app storage of the {@link OnDeviceIntelligenceService}, i.e., only files stored in + * {@link Context#getFilesDir()} can be opened. + */ + @Override + public final FileInputStream openFileInput(@NonNull String filename) throws + FileNotFoundException { + try { + AndroidFuture<ParcelFileDescriptor> future = new AndroidFuture<>(); + mRemoteStorageService.getReadOnlyFileDescriptor(filename, future); + ParcelFileDescriptor pfd = future.get(); + return new FileInputStream(pfd.getFileDescriptor()); + } catch (RemoteException | ExecutionException | InterruptedException e) { + Log.w(TAG, "Cannot open file due to remote service failure"); + throw new FileNotFoundException(e.getMessage()); + } + } + + /** + * Provides read-only access to the internal app storage via the + * {@link OnDeviceIntelligenceService}. This is an asynchronous implementation for + * {@link #openFileInput(String)}. + * + * @param fileName File name relative to the {@link Context#getFilesDir()}. + * @param resultConsumer Consumer to populate the corresponding file stream in. + */ + public final void openFileInputAsync(@NonNull String fileName, + @NonNull @CallbackExecutor Executor executor, + @NonNull Consumer<FileInputStream> resultConsumer) throws FileNotFoundException { + AndroidFuture<ParcelFileDescriptor> future = new AndroidFuture<>(); + try { + mRemoteStorageService.getReadOnlyFileDescriptor(fileName, future); + } catch (RemoteException e) { + Log.w(TAG, "Cannot open file due to remote service failure"); + throw new FileNotFoundException(e.getMessage()); + } + future.whenCompleteAsync((pfd, err) -> { + if (err != null) { + Log.e(TAG, "Failure when reading file: " + fileName + err); + executor.execute(() -> resultConsumer.accept(null)); + } else { + executor.execute( + () -> resultConsumer.accept(new FileInputStream(pfd.getFileDescriptor()))); + } + }, executor); + } + + /** + * Provides access to all file streams required for feature via the + * {@link OnDeviceIntelligenceService}. + * + * @param feature Feature for which the associated files should be fetched. + * @param executor Executor to run the consumer callback on. + * @param resultConsumer Consumer to receive a map of filePath to the corresponding file input + * stream. + */ + public final void fetchFeatureFileInputStreamMap(@NonNull Feature feature, + @NonNull @CallbackExecutor Executor executor, + @NonNull Consumer<Map<String, FileInputStream>> resultConsumer) { + try { + mRemoteStorageService.getReadOnlyFeatureFileDescriptorMap(feature, + wrapResultReceiverAsReadOnly(resultConsumer, executor)); + } catch (RemoteException e) { + throw new RuntimeException(e); + } + } + + private RemoteCallback wrapResultReceiverAsReadOnly( + @NonNull Consumer<Map<String, FileInputStream>> resultConsumer, + @NonNull Executor executor) { + return new RemoteCallback(result -> { + if (result == null) { + executor.execute(() -> resultConsumer.accept(new HashMap<>())); + } else { + Map<String, FileInputStream> bundleMap = new HashMap<>(); + result.keySet().forEach(key -> { + ParcelFileDescriptor pfd = result.getParcelable(key, + ParcelFileDescriptor.class); + if (pfd != null) { + bundleMap.put(key, new FileInputStream(pfd.getFileDescriptor())); + } + }); + executor.execute(() -> resultConsumer.accept(bundleMap)); + } + }); + } + + private OutcomeReceiver<Content, + OnDeviceIntelligenceManager.OnDeviceIntelligenceManagerProcessingException> wrapResponseCallback( + IResponseCallback callback) { + return new OutcomeReceiver<>() { + @Override + public void onResult(@androidx.annotation.NonNull Content response) { + try { + callback.onSuccess(response); + } catch (RemoteException e) { + Slog.e(TAG, "Error sending result: " + e); + } + } + + @Override + public void onError( + OnDeviceIntelligenceManager.OnDeviceIntelligenceManagerProcessingException exception) { + try { + callback.onFailure(exception.getErrorCode(), exception.getMessage(), + exception.getErrorParams()); + } catch (RemoteException e) { + Slog.e(TAG, "Error sending result: " + e); + } + } + }; + } + + private StreamingResponseReceiver<Content, Content, + OnDeviceIntelligenceManager.OnDeviceIntelligenceManagerProcessingException> wrapStreamingResponseCallback( + IStreamingResponseCallback callback) { + return new StreamingResponseReceiver<>() { + @Override + public void onNewContent(@androidx.annotation.NonNull Content content) { + try { + callback.onNewContent(content); + } catch (RemoteException e) { + Slog.e(TAG, "Error sending result: " + e); + } + } + + @Override + public void onResult(@androidx.annotation.NonNull Content response) { + try { + callback.onSuccess(response); + } catch (RemoteException e) { + Slog.e(TAG, "Error sending result: " + e); + } + } + + @Override + public void onError( + OnDeviceIntelligenceManager.OnDeviceIntelligenceManagerProcessingException exception) { + try { + callback.onFailure(exception.getErrorCode(), exception.getMessage(), + exception.getErrorParams()); + } catch (RemoteException e) { + Slog.e(TAG, "Error sending result: " + e); + } + } + }; + } + + private OutcomeReceiver<Long, + OnDeviceIntelligenceManager.OnDeviceIntelligenceManagerProcessingException> wrapTokenCountCallback( + ITokenCountCallback tokenCountCallback) { + return new OutcomeReceiver<>() { + @Override + public void onResult(Long tokenCount) { + try { + tokenCountCallback.onSuccess(tokenCount); + } catch (RemoteException e) { + Slog.e(TAG, "Error sending result: " + e); + } + } + + @Override + public void onError( + OnDeviceIntelligenceManager.OnDeviceIntelligenceManagerProcessingException exception) { + try { + tokenCountCallback.onFailure(exception.getErrorCode(), exception.getMessage(), + exception.getErrorParams()); + } catch (RemoteException e) { + Slog.e(TAG, "Error sending failure: " + e); + } + } + }; + } +} diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml index c6bc589cffcb..1f06b0b7c62b 100644 --- a/core/res/res/values/config.xml +++ b/core/res/res/values/config.xml @@ -4670,6 +4670,13 @@ --> <string name="config_defaultWearableSensingService" translatable="false"></string> + + <!-- The component name for the default system on-device intelligence service, --> + <string name="config_defaultOnDeviceIntelligenceService" translatable="false"></string> + + <!-- The component name for the default system on-device trusted inference service. --> + <string name="config_defaultOnDeviceTrustedInferenceService" translatable="false"></string> + <!-- Component name that accepts ACTION_SEND intents for requesting ambient context consent for wearable sensing. --> <string translatable="false" name="config_defaultWearableSensingConsentComponent"></string> diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml index 9d7acffee68b..cf9c02a93267 100644 --- a/core/res/res/values/symbols.xml +++ b/core/res/res/values/symbols.xml @@ -3909,6 +3909,8 @@ <java-symbol type="string" name="config_ambientContextPackageNameExtraKey" /> <java-symbol type="string" name="config_ambientContextEventArrayExtraKey" /> <java-symbol type="string" name="config_defaultWearableSensingService" /> + <java-symbol type="string" name="config_defaultOnDeviceIntelligenceService" /> + <java-symbol type="string" name="config_defaultOnDeviceTrustedInferenceService" /> <java-symbol type="string" name="config_retailDemoPackage" /> <java-symbol type="string" name="config_retailDemoPackageSignature" /> diff --git a/services/core/java/com/android/server/ondeviceintelligence/OnDeviceIntelligenceManagerService.java b/services/core/java/com/android/server/ondeviceintelligence/OnDeviceIntelligenceManagerService.java new file mode 100644 index 000000000000..a4c4347b7cef --- /dev/null +++ b/services/core/java/com/android/server/ondeviceintelligence/OnDeviceIntelligenceManagerService.java @@ -0,0 +1,419 @@ +/* + * Copyright (C) 2024 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 com.android.server.ondeviceintelligence; + +import android.Manifest; +import android.annotation.NonNull; +import android.app.AppGlobals; +import android.app.ondeviceintelligence.Content; +import android.app.ondeviceintelligence.DownloadCallback; +import android.app.ondeviceintelligence.Feature; +import android.app.ondeviceintelligence.IDownloadCallback; +import android.app.ondeviceintelligence.IFeatureCallback; +import android.app.ondeviceintelligence.IFeatureDetailsCallback; +import android.app.ondeviceintelligence.IListFeaturesCallback; +import android.app.ondeviceintelligence.IOnDeviceIntelligenceManager; +import android.app.ondeviceintelligence.IProcessingSignal; +import android.app.ondeviceintelligence.IResponseCallback; +import android.app.ondeviceintelligence.IStreamingResponseCallback; +import android.app.ondeviceintelligence.ITokenCountCallback; +import android.app.ondeviceintelligence.OnDeviceIntelligenceManager; +import android.content.ComponentName; +import android.content.Context; +import android.content.pm.PackageManager; +import android.content.pm.ServiceInfo; +import android.os.ICancellationSignal; +import android.os.ParcelFileDescriptor; +import android.os.PersistableBundle; +import android.os.RemoteCallback; +import android.os.RemoteException; +import android.os.UserHandle; +import android.provider.DeviceConfig; +import android.service.ondeviceintelligence.IOnDeviceTrustedInferenceService; +import android.service.ondeviceintelligence.IRemoteStorageService; +import android.text.TextUtils; +import android.util.Slog; + +import com.android.internal.R; +import com.android.internal.annotations.GuardedBy; +import com.android.internal.infra.AndroidFuture; +import com.android.internal.infra.ServiceConnector; +import com.android.internal.os.BackgroundThread; +import com.android.server.SystemService; + +import java.util.Objects; +import java.util.Set; + +/** + * This is the system service for handling calls on the {@link OnDeviceIntelligenceManager}. This + * service holds connection references to the underlying remote services i.e. the isolated service + * {@link android.service.ondeviceintelligence.OnDeviceTrustedInferenceService} and a regular + * service counter part {@link android.service.ondeviceintelligence.OnDeviceIntelligenceService}. + * + * Note: Both the remote services run under the SYSTEM user, as we cannot have separate instance of + * the Inference service for each user, due to possible high memory footprint. + * + * @hide + */ +public class OnDeviceIntelligenceManagerService extends SystemService { + + private static final String TAG = OnDeviceIntelligenceManagerService.class.getSimpleName(); + private static final String KEY_SERVICE_ENABLED = "service_enabled"; + + /** Default value in absence of {@link DeviceConfig} override. */ + private static final boolean DEFAULT_SERVICE_ENABLED = true; + private static final String NAMESPACE_ON_DEVICE_INTELLIGENCE = "ondeviceintelligence"; + + private final Context mContext; + protected final Object mLock = new Object(); + + + private RemoteOnDeviceTrustedInferenceService mRemoteInferenceService; + private RemoteOnDeviceIntelligenceService mRemoteOnDeviceIntelligenceService; + volatile boolean mIsServiceEnabled; + + public OnDeviceIntelligenceManagerService(Context context) { + super(context); + mContext = context; + } + + @Override + public void onStart() { + publishBinderService( + Context.ON_DEVICE_INTELLIGENCE_SERVICE, new OnDeviceIntelligenceManagerInternal(), + /* allowIsolated = */true); + } + + + @Override + public void onBootPhase(int phase) { + if (phase == SystemService.PHASE_SYSTEM_SERVICES_READY) { + DeviceConfig.addOnPropertiesChangedListener( + NAMESPACE_ON_DEVICE_INTELLIGENCE, + BackgroundThread.getExecutor(), + (properties) -> onDeviceConfigChange(properties.getKeyset())); + + mIsServiceEnabled = isServiceEnabled(); + } + } + + private void onDeviceConfigChange(@NonNull Set<String> keys) { + if (keys.contains(KEY_SERVICE_ENABLED)) { + mIsServiceEnabled = isServiceEnabled(); + } + } + + private boolean isServiceEnabled() { + return DeviceConfig.getBoolean( + NAMESPACE_ON_DEVICE_INTELLIGENCE, + KEY_SERVICE_ENABLED, DEFAULT_SERVICE_ENABLED); + } + + private final class OnDeviceIntelligenceManagerInternal extends + IOnDeviceIntelligenceManager.Stub { + @Override + public void getVersion(RemoteCallback remoteCallback) throws RemoteException { + Slog.i(TAG, "OnDeviceIntelligenceManagerInternal getVersion"); + Objects.requireNonNull(remoteCallback); + mContext.enforceCallingOrSelfPermission( + Manifest.permission.USE_ON_DEVICE_INTELLIGENCE, TAG); + if (!mIsServiceEnabled) { + Slog.w(TAG, "Service not available"); + remoteCallback.sendResult(null); + return; + } + ensureRemoteIntelligenceServiceInitialized(); + mRemoteOnDeviceIntelligenceService.post( + service -> service.getVersion(remoteCallback)); + } + + @Override + public void getFeature(int id, IFeatureCallback featureCallback) throws RemoteException { + Slog.i(TAG, "OnDeviceIntelligenceManagerInternal getFeatures"); + Objects.requireNonNull(featureCallback); + mContext.enforceCallingOrSelfPermission( + Manifest.permission.USE_ON_DEVICE_INTELLIGENCE, TAG); + if (!mIsServiceEnabled) { + Slog.w(TAG, "Service not available"); + featureCallback.onFailure( + OnDeviceIntelligenceManager.OnDeviceIntelligenceManagerException.ON_DEVICE_INTELLIGENCE_SERVICE_UNAVAILABLE, + "OnDeviceIntelligenceManagerService is unavailable", + new PersistableBundle()); + return; + } + ensureRemoteIntelligenceServiceInitialized(); + mRemoteOnDeviceIntelligenceService.post( + service -> service.getFeature(id, featureCallback)); + } + + @Override + public void listFeatures(IListFeaturesCallback listFeaturesCallback) + throws RemoteException { + Slog.i(TAG, "OnDeviceIntelligenceManagerInternal getFeatures"); + Objects.requireNonNull(listFeaturesCallback); + mContext.enforceCallingOrSelfPermission( + Manifest.permission.USE_ON_DEVICE_INTELLIGENCE, TAG); + if (!mIsServiceEnabled) { + Slog.w(TAG, "Service not available"); + listFeaturesCallback.onFailure( + OnDeviceIntelligenceManager.OnDeviceIntelligenceManagerException.ON_DEVICE_INTELLIGENCE_SERVICE_UNAVAILABLE, + "OnDeviceIntelligenceManagerService is unavailable", + new PersistableBundle()); + return; + } + ensureRemoteIntelligenceServiceInitialized(); + mRemoteOnDeviceIntelligenceService.post( + service -> service.listFeatures(listFeaturesCallback)); + } + + @Override + public void getFeatureDetails(Feature feature, + IFeatureDetailsCallback featureDetailsCallback) + throws RemoteException { + Slog.i(TAG, "OnDeviceIntelligenceManagerInternal getFeatureStatus"); + Objects.requireNonNull(feature); + Objects.requireNonNull(featureDetailsCallback); + mContext.enforceCallingOrSelfPermission( + Manifest.permission.USE_ON_DEVICE_INTELLIGENCE, TAG); + if (!mIsServiceEnabled) { + Slog.w(TAG, "Service not available"); + featureDetailsCallback.onFailure( + OnDeviceIntelligenceManager.OnDeviceIntelligenceManagerException.ON_DEVICE_INTELLIGENCE_SERVICE_UNAVAILABLE, + "OnDeviceIntelligenceManagerService is unavailable", + new PersistableBundle()); + return; + } + ensureRemoteIntelligenceServiceInitialized(); + mRemoteOnDeviceIntelligenceService.post( + service -> service.getFeatureDetails(feature, featureDetailsCallback)); + } + + @Override + public void requestFeatureDownload(Feature feature, ICancellationSignal cancellationSignal, + IDownloadCallback downloadCallback) throws RemoteException { + Slog.i(TAG, "OnDeviceIntelligenceManagerInternal requestFeatureDownload"); + Objects.requireNonNull(feature); + Objects.requireNonNull(downloadCallback); + mContext.enforceCallingOrSelfPermission( + Manifest.permission.USE_ON_DEVICE_INTELLIGENCE, TAG); + if (!mIsServiceEnabled) { + Slog.w(TAG, "Service not available"); + downloadCallback.onDownloadFailed( + DownloadCallback.DOWNLOAD_FAILURE_STATUS_UNAVAILABLE, + "OnDeviceIntelligenceManagerService is unavailable", + new PersistableBundle()); + } + ensureRemoteIntelligenceServiceInitialized(); + mRemoteOnDeviceIntelligenceService.post( + service -> service.requestFeatureDownload(feature, cancellationSignal, + downloadCallback)); + } + + + @Override + public void requestTokenCount(Feature feature, + Content request, ICancellationSignal cancellationSignal, + ITokenCountCallback tokenCountcallback) throws RemoteException { + Slog.i(TAG, "OnDeviceIntelligenceManagerInternal prepareFeatureProcessing"); + Objects.requireNonNull(feature); + Objects.requireNonNull(request); + Objects.requireNonNull(tokenCountcallback); + + mContext.enforceCallingOrSelfPermission( + Manifest.permission.USE_ON_DEVICE_INTELLIGENCE, TAG); + if (!mIsServiceEnabled) { + Slog.w(TAG, "Service not available"); + tokenCountcallback.onFailure( + OnDeviceIntelligenceManager.OnDeviceIntelligenceManagerException.ON_DEVICE_INTELLIGENCE_SERVICE_UNAVAILABLE, + "OnDeviceIntelligenceManagerService is unavailable", + new PersistableBundle()); + } + ensureRemoteTrustedInferenceServiceInitialized(); + mRemoteInferenceService.post( + service -> service.requestTokenCount(feature, request, cancellationSignal, + tokenCountcallback)); + } + + @Override + public void processRequest(Feature feature, + Content request, + int requestType, + ICancellationSignal cancellationSignal, + IProcessingSignal processingSignal, + IResponseCallback responseCallback) + throws RemoteException { + Slog.i(TAG, "OnDeviceIntelligenceManagerInternal processRequest"); + Objects.requireNonNull(feature); + Objects.requireNonNull(responseCallback); + Objects.requireNonNull(request); + mContext.enforceCallingOrSelfPermission( + Manifest.permission.USE_ON_DEVICE_INTELLIGENCE, TAG); + if (!mIsServiceEnabled) { + Slog.w(TAG, "Service not available"); + responseCallback.onFailure( + OnDeviceIntelligenceManager.OnDeviceIntelligenceManagerProcessingException.PROCESSING_ERROR_SERVICE_UNAVAILABLE, + "OnDeviceIntelligenceManagerService is unavailable", + new PersistableBundle()); + } + ensureRemoteTrustedInferenceServiceInitialized(); + mRemoteInferenceService.post( + service -> service.processRequest(feature, request, requestType, + cancellationSignal, processingSignal, + responseCallback)); + } + + @Override + public void processRequestStreaming(Feature feature, + Content request, + int requestType, + ICancellationSignal cancellationSignal, + IProcessingSignal processingSignal, + IStreamingResponseCallback streamingCallback) throws RemoteException { + Slog.i(TAG, "OnDeviceIntelligenceManagerInternal processRequestStreaming"); + Objects.requireNonNull(feature); + Objects.requireNonNull(request); + Objects.requireNonNull(streamingCallback); + mContext.enforceCallingOrSelfPermission( + Manifest.permission.USE_ON_DEVICE_INTELLIGENCE, TAG); + if (!mIsServiceEnabled) { + Slog.w(TAG, "Service not available"); + streamingCallback.onFailure( + OnDeviceIntelligenceManager.OnDeviceIntelligenceManagerProcessingException.PROCESSING_ERROR_SERVICE_UNAVAILABLE, + "OnDeviceIntelligenceManagerService is unavailable", + new PersistableBundle()); + } + ensureRemoteTrustedInferenceServiceInitialized(); + mRemoteInferenceService.post( + service -> service.processRequestStreaming(feature, request, requestType, + cancellationSignal, processingSignal, + streamingCallback)); + } + } + + private void ensureRemoteIntelligenceServiceInitialized() throws RemoteException { + synchronized (mLock) { + if (mRemoteOnDeviceIntelligenceService == null) { + String serviceName = mContext.getResources().getString( + R.string.config_defaultOnDeviceIntelligenceService); + validateService(serviceName, false); + mRemoteOnDeviceIntelligenceService = new RemoteOnDeviceIntelligenceService(mContext, + ComponentName.unflattenFromString(serviceName), + UserHandle.SYSTEM.getIdentifier()); + } + } + } + + private void ensureRemoteTrustedInferenceServiceInitialized() throws RemoteException { + synchronized (mLock) { + if (mRemoteInferenceService == null) { + String serviceName = mContext.getResources().getString( + R.string.config_defaultOnDeviceTrustedInferenceService); + validateService(serviceName, true); + mRemoteInferenceService = new RemoteOnDeviceTrustedInferenceService(mContext, + ComponentName.unflattenFromString(serviceName), + UserHandle.SYSTEM.getIdentifier()); + mRemoteInferenceService.setServiceLifecycleCallbacks( + new ServiceConnector.ServiceLifecycleCallbacks<>() { + @Override + public void onConnected( + @NonNull IOnDeviceTrustedInferenceService service) { + try { + service.registerRemoteStorageService( + getIRemoteStorageService()); + } catch (RemoteException ex) { + Slog.w(TAG, "Failed to send connected event", ex); + } + } + }); + } + } + } + + @NonNull + private IRemoteStorageService.Stub getIRemoteStorageService() { + return new IRemoteStorageService.Stub() { + @Override + public void getReadOnlyFileDescriptor( + String filePath, + AndroidFuture<ParcelFileDescriptor> future) { + mRemoteOnDeviceIntelligenceService.post( + service -> service.getReadOnlyFileDescriptor( + filePath, future)); + } + + @Override + public void getReadOnlyFeatureFileDescriptorMap( + Feature feature, + RemoteCallback remoteCallback) + throws RemoteException { + mRemoteOnDeviceIntelligenceService.post( + service -> service.getReadOnlyFeatureFileDescriptorMap( + feature, remoteCallback)); + } + }; + } + + @GuardedBy("mLock") + private void validateService(String serviceName, boolean checkIsolated) + throws RemoteException { + if (TextUtils.isEmpty(serviceName)) { + throw new RuntimeException(""); + } + ComponentName serviceComponent = ComponentName.unflattenFromString( + serviceName); + ServiceInfo serviceInfo = AppGlobals.getPackageManager().getServiceInfo( + serviceComponent, + PackageManager.MATCH_DIRECT_BOOT_AWARE + | PackageManager.MATCH_DIRECT_BOOT_UNAWARE, 0); + if (serviceInfo != null) { + if (!checkIsolated) { + checkServiceRequiresPermission(serviceInfo, + Manifest.permission.BIND_ON_DEVICE_INTELLIGENCE_SERVICE); + return; + } + + checkServiceRequiresPermission(serviceInfo, + Manifest.permission.BIND_ON_DEVICE_TRUSTED_SERVICE); + if (!isIsolatedService(serviceInfo)) { + throw new SecurityException( + "Call required an isolated service, but the configured service: " + + serviceName + ", is not isolated"); + } + } else { + throw new RuntimeException( + "Could not find service info for serviceName: " + serviceName); + } + } + + private static void checkServiceRequiresPermission(ServiceInfo serviceInfo, + String requiredPermission) { + final String permission = serviceInfo.permission; + if (!requiredPermission.equals(permission)) { + throw new SecurityException(String.format( + "Service %s requires %s permission. Found %s permission", + serviceInfo.getComponentName(), + requiredPermission, + serviceInfo.permission)); + } + } + + @GuardedBy("mLock") + private boolean isIsolatedService(@NonNull ServiceInfo serviceInfo) { + return (serviceInfo.flags & ServiceInfo.FLAG_ISOLATED_PROCESS) != 0 + && (serviceInfo.flags & ServiceInfo.FLAG_EXTERNAL_SERVICE) == 0; + } +} diff --git a/services/core/java/com/android/server/ondeviceintelligence/RemoteOnDeviceIntelligenceService.java b/services/core/java/com/android/server/ondeviceintelligence/RemoteOnDeviceIntelligenceService.java new file mode 100644 index 000000000000..48258d7bea72 --- /dev/null +++ b/services/core/java/com/android/server/ondeviceintelligence/RemoteOnDeviceIntelligenceService.java @@ -0,0 +1,56 @@ +/* + * Copyright (C) 2024 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 com.android.server.ondeviceintelligence; + +import static android.content.Context.BIND_FOREGROUND_SERVICE; +import static android.content.Context.BIND_INCLUDE_CAPABILITIES; + +import android.content.ComponentName; +import android.content.Context; +import android.content.Intent; +import android.service.ondeviceintelligence.IOnDeviceIntelligenceService; +import android.service.ondeviceintelligence.OnDeviceIntelligenceService; + +import com.android.internal.infra.ServiceConnector; + +/** + * Manages the connection to the remote on-device intelligence service. Also, handles unbinding + * logic set by the service implementation via a Secure Settings flag. + */ +public class RemoteOnDeviceIntelligenceService extends + ServiceConnector.Impl<IOnDeviceIntelligenceService> { + private static final String TAG = + RemoteOnDeviceIntelligenceService.class.getSimpleName(); + + RemoteOnDeviceIntelligenceService(Context context, ComponentName serviceName, + int userId) { + super(context, new Intent( + OnDeviceIntelligenceService.SERVICE_INTERFACE).setComponent(serviceName), + BIND_FOREGROUND_SERVICE | BIND_INCLUDE_CAPABILITIES, userId, + IOnDeviceIntelligenceService.Stub::asInterface); + + // Bind right away + connect(); + } + + @Override + protected long getAutoDisconnectTimeoutMs() { + // Disable automatic unbinding. + // TODO: add logic to fetch this flag via SecureSettings. + return -1; + } +} diff --git a/services/core/java/com/android/server/ondeviceintelligence/RemoteOnDeviceTrustedInferenceService.java b/services/core/java/com/android/server/ondeviceintelligence/RemoteOnDeviceTrustedInferenceService.java new file mode 100644 index 000000000000..cc8e78804bb6 --- /dev/null +++ b/services/core/java/com/android/server/ondeviceintelligence/RemoteOnDeviceTrustedInferenceService.java @@ -0,0 +1,64 @@ +/* + * Copyright (C) 2024 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 com.android.server.ondeviceintelligence; + +import static android.content.Context.BIND_FOREGROUND_SERVICE; +import static android.content.Context.BIND_INCLUDE_CAPABILITIES; + +import android.content.ComponentName; +import android.content.Context; +import android.content.Intent; +import android.service.ondeviceintelligence.IOnDeviceTrustedInferenceService; +import android.service.ondeviceintelligence.OnDeviceTrustedInferenceService; + +import com.android.internal.infra.ServiceConnector; + + +/** + * Manages the connection to the remote on-device trusted inference service. Also, handles unbinding + * logic set by the service implementation via a SecureSettings flag. + */ +public class RemoteOnDeviceTrustedInferenceService extends + ServiceConnector.Impl<IOnDeviceTrustedInferenceService> { + /** + * Creates an instance of {@link ServiceConnector} + * + * See {@code protected} methods for optional parameters you can override. + * + * @param context to be used for {@link Context#bindServiceAsUser binding} and + * {@link Context#unbindService unbinding} + * @param userId to be used for {@link Context#bindServiceAsUser binding} + */ + RemoteOnDeviceTrustedInferenceService(Context context, ComponentName serviceName, + int userId) { + super(context, new Intent( + OnDeviceTrustedInferenceService.SERVICE_INTERFACE).setComponent(serviceName), + BIND_FOREGROUND_SERVICE | BIND_INCLUDE_CAPABILITIES, userId, + IOnDeviceTrustedInferenceService.Stub::asInterface); + + // Bind right away + connect(); + } + + + @Override + protected long getAutoDisconnectTimeoutMs() { + // Disable automatic unbinding. + // TODO: add logic to fetch this flag via SecureSettings. + return -1; + } +} |