diff options
| author | Ytai Ben-tsvi <ytai@google.com> | 2020-09-21 19:07:04 +0000 |
|---|---|---|
| committer | Android (Google) Code Review <android-gerrit@google.com> | 2020-09-21 19:07:04 +0000 |
| commit | 189d6050daddc3be6631d6bb48b97393c5793e6e (patch) | |
| tree | 2a9aff6e2bc5e6c724f608b6c3d38f7438b1816f /core/java | |
| parent | 89008da40e650e7d04bda5419cd28b24d1e137ee (diff) | |
| parent | c4d23a482d9e0d65ef146c2d68ff2ab416a5e47d (diff) | |
Merge changes from topic "new-perm"
* changes:
Demote AlwaysOnHotwordDetector to SystemApi
Sessionize VoiceInteractionManagerService
Sessionize the SoundTriggerService layer.
Use standard API in SoundTriggerTestApp
Fix bug in dumpsys
Require identity information in SoundTrigger.java
Associate an originator identity to sessions
Extract permission checking as a separate aspect
Correctly handle HAL death
Diffstat (limited to 'core/java')
8 files changed, 453 insertions, 142 deletions
diff --git a/core/java/android/hardware/soundtrigger/SoundTrigger.java b/core/java/android/hardware/soundtrigger/SoundTrigger.java index 80f35a0a2e32..0f1c2a59965b 100644 --- a/core/java/android/hardware/soundtrigger/SoundTrigger.java +++ b/core/java/android/hardware/soundtrigger/SoundTrigger.java @@ -16,6 +16,9 @@ package android.hardware.soundtrigger; +import static android.Manifest.permission.CAPTURE_AUDIO_HOTWORD; +import static android.Manifest.permission.RECORD_AUDIO; +import static android.Manifest.permission.SOUNDTRIGGER_DELEGATE_IDENTITY; import static android.system.OsConstants.EINVAL; import static android.system.OsConstants.ENODEV; import static android.system.OsConstants.ENOSYS; @@ -27,6 +30,7 @@ import static java.util.Objects.requireNonNull; import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; +import android.annotation.RequiresPermission; import android.annotation.SuppressLint; import android.annotation.SystemApi; import android.annotation.TestApi; @@ -34,9 +38,13 @@ import android.app.ActivityThread; import android.compat.annotation.UnsupportedAppUsage; import android.content.Context; import android.media.AudioFormat; +import android.media.permission.ClearCallingIdentityContext; +import android.media.permission.Identity; +import android.media.permission.SafeCloseable; import android.media.soundtrigger_middleware.ISoundTriggerMiddlewareService; import android.media.soundtrigger_middleware.SoundTriggerModuleDescriptor; import android.media.soundtrigger_middleware.Status; +import android.os.Binder; import android.os.Handler; import android.os.IBinder; import android.os.Looper; @@ -1943,16 +1951,6 @@ public class SoundTrigger { public static final int SERVICE_STATE_DISABLED = 1; private static Object mServiceLock = new Object(); private static ISoundTriggerMiddlewareService mService; - /** - * @return returns current package name. - */ - static String getCurrentOpPackageName() { - String packageName = ActivityThread.currentOpPackageName(); - if (packageName == null) { - return ""; - } - return packageName; - } /** * Translate an exception thrown from interaction with the underlying service to an error code. @@ -2005,17 +2003,81 @@ public class SoundTrigger { * - {@link #STATUS_BAD_VALUE} if modules is null * - {@link #STATUS_DEAD_OBJECT} if the binder transaction to the native service fails * + * @deprecated Please use {@link #listModulesAsOriginator(ArrayList, Identity)} or + * {@link #listModulesAsMiddleman(ArrayList, Identity, Identity)}, based on whether the + * client is acting on behalf of its own identity or a separate identity. * @hide */ @UnsupportedAppUsage public static int listModules(@NonNull ArrayList<ModuleProperties> modules) { + // TODO(ytai): This is a temporary hack to retain prior behavior, which makes + // assumptions about process affinity and Binder context, namely that the binder calling ID + // reliably reflects the originator identity. + Identity middlemanIdentity = new Identity(); + middlemanIdentity.packageName = ActivityThread.currentOpPackageName(); + + Identity originatorIdentity = new Identity(); + originatorIdentity.pid = Binder.getCallingPid(); + originatorIdentity.uid = Binder.getCallingUid(); + + try (SafeCloseable ignored = ClearCallingIdentityContext.create()) { + return listModulesAsMiddleman(modules, middlemanIdentity, originatorIdentity); + } + } + + /** + * Returns a list of descriptors for all hardware modules loaded. + * This variant is intended for use when the caller itself is the originator of the operation. + * @param modules A ModuleProperties array where the list will be returned. + * @param originatorIdentity The identity of the originator, which will be used for permission + * purposes. + * @return - {@link #STATUS_OK} in case of success + * - {@link #STATUS_ERROR} in case of unspecified error + * - {@link #STATUS_PERMISSION_DENIED} if the caller does not have system permission + * - {@link #STATUS_NO_INIT} if the native service cannot be reached + * - {@link #STATUS_BAD_VALUE} if modules is null + * - {@link #STATUS_DEAD_OBJECT} if the binder transaction to the native service fails + * + * @hide + */ + @RequiresPermission(allOf = {RECORD_AUDIO, CAPTURE_AUDIO_HOTWORD}) + public static int listModulesAsOriginator(@NonNull ArrayList<ModuleProperties> modules, + @NonNull Identity originatorIdentity) { try { - SoundTriggerModuleDescriptor[] descs = getService().listModules(); - modules.clear(); - modules.ensureCapacity(descs.length); - for (SoundTriggerModuleDescriptor desc : descs) { - modules.add(ConversionUtil.aidl2apiModuleDescriptor(desc)); - } + SoundTriggerModuleDescriptor[] descs = getService().listModulesAsOriginator( + originatorIdentity); + convertDescriptorsToModuleProperties(descs, modules); + return STATUS_OK; + } catch (Exception e) { + return handleException(e); + } + } + + /** + * Returns a list of descriptors for all hardware modules loaded. + * This variant is intended for use when the caller is acting on behalf of a different identity + * for permission purposes. + * @param modules A ModuleProperties array where the list will be returned. + * @param middlemanIdentity The identity of the caller, acting as middleman. + * @param originatorIdentity The identity of the originator, which will be used for permission + * purposes. + * @return - {@link #STATUS_OK} in case of success + * - {@link #STATUS_ERROR} in case of unspecified error + * - {@link #STATUS_PERMISSION_DENIED} if the caller does not have system permission + * - {@link #STATUS_NO_INIT} if the native service cannot be reached + * - {@link #STATUS_BAD_VALUE} if modules is null + * - {@link #STATUS_DEAD_OBJECT} if the binder transaction to the native service fails + * + * @hide + */ + @RequiresPermission(SOUNDTRIGGER_DELEGATE_IDENTITY) + public static int listModulesAsMiddleman(@NonNull ArrayList<ModuleProperties> modules, + @NonNull Identity middlemanIdentity, + @NonNull Identity originatorIdentity) { + try { + SoundTriggerModuleDescriptor[] descs = getService().listModulesAsMiddleman( + middlemanIdentity, originatorIdentity); + convertDescriptorsToModuleProperties(descs, modules); return STATUS_OK; } catch (Exception e) { return handleException(e); @@ -2023,6 +2085,22 @@ public class SoundTrigger { } /** + * Converts an array of SoundTriggerModuleDescriptor into an (existing) ArrayList of + * ModuleProperties. + * @param descsIn The input descriptors. + * @param modulesOut The output list. + */ + private static void convertDescriptorsToModuleProperties( + @NonNull SoundTriggerModuleDescriptor[] descsIn, + @NonNull ArrayList<ModuleProperties> modulesOut) { + modulesOut.clear(); + modulesOut.ensureCapacity(descsIn.length); + for (SoundTriggerModuleDescriptor desc : descsIn) { + modulesOut.add(ConversionUtil.aidl2apiModuleDescriptor(desc)); + } + } + + /** * Get an interface on a hardware module to control sound models and recognition on * this module. * @param moduleId Sound module system identifier {@link ModuleProperties#mId}. mandatory. @@ -2031,15 +2109,85 @@ public class SoundTrigger { * is OK. * @return a valid sound module in case of success or null in case of error. * + * @deprecated Please use + * {@link #attachModuleAsOriginator(int, StatusListener, Handler, Identity)} or + * {@link #attachModuleAsMiddleman(int, StatusListener, Handler, Identity, Identity)}, based + * on whether the client is acting on behalf of its own identity or a separate identity. * @hide */ @UnsupportedAppUsage - public static @NonNull SoundTriggerModule attachModule(int moduleId, + private static SoundTriggerModule attachModule(int moduleId, @NonNull StatusListener listener, @Nullable Handler handler) { + // TODO(ytai): This is a temporary hack to retain prior behavior, which makes + // assumptions about process affinity and Binder context, namely that the binder calling ID + // reliably reflects the originator identity. + Identity middlemanIdentity = new Identity(); + middlemanIdentity.packageName = ActivityThread.currentOpPackageName(); + + Identity originatorIdentity = new Identity(); + originatorIdentity.pid = Binder.getCallingPid(); + originatorIdentity.uid = Binder.getCallingUid(); + + try (SafeCloseable ignored = ClearCallingIdentityContext.create()) { + return attachModuleAsMiddleman(moduleId, listener, handler, middlemanIdentity, + originatorIdentity); + } + } + + /** + * Get an interface on a hardware module to control sound models and recognition on + * this module. + * This variant is intended for use when the caller is acting on behalf of a different identity + * for permission purposes. + * @param moduleId Sound module system identifier {@link ModuleProperties#mId}. mandatory. + * @param listener {@link StatusListener} interface. Mandatory. + * @param handler the Handler that will receive the callabcks. Can be null if default handler + * is OK. + * @param middlemanIdentity The identity of the caller, acting as middleman. + * @param originatorIdentity The identity of the originator, which will be used for permission + * purposes. + * @return a valid sound module in case of success or null in case of error. + * + * @hide + */ + @RequiresPermission(SOUNDTRIGGER_DELEGATE_IDENTITY) + public static SoundTriggerModule attachModuleAsMiddleman(int moduleId, + @NonNull SoundTrigger.StatusListener listener, + @Nullable Handler handler, Identity middlemanIdentity, + Identity originatorIdentity) { + Looper looper = handler != null ? handler.getLooper() : Looper.getMainLooper(); + try { + return new SoundTriggerModule(getService(), moduleId, listener, looper, + middlemanIdentity, originatorIdentity); + } catch (Exception e) { + Log.e(TAG, "", e); + return null; + } + } + + /** + * Get an interface on a hardware module to control sound models and recognition on + * this module. + * This variant is intended for use when the caller itself is the originator of the operation. + * @param moduleId Sound module system identifier {@link ModuleProperties#mId}. mandatory. + * @param listener {@link StatusListener} interface. Mandatory. + * @param handler the Handler that will receive the callabcks. Can be null if default handler + * is OK. + * @param originatorIdentity The identity of the originator, which will be used for permission + * purposes. + * @return a valid sound module in case of success or null in case of error. + * + * @hide + */ + @RequiresPermission(allOf = {RECORD_AUDIO, CAPTURE_AUDIO_HOTWORD}) + public static SoundTriggerModule attachModuleAsOriginator(int moduleId, + @NonNull SoundTrigger.StatusListener listener, + @Nullable Handler handler, @NonNull Identity originatorIdentity) { Looper looper = handler != null ? handler.getLooper() : Looper.getMainLooper(); try { - return new SoundTriggerModule(getService(), moduleId, listener, looper); + return new SoundTriggerModule(getService(), moduleId, listener, looper, + originatorIdentity); } catch (Exception e) { Log.e(TAG, "", e); return null; diff --git a/core/java/android/hardware/soundtrigger/SoundTriggerModule.java b/core/java/android/hardware/soundtrigger/SoundTriggerModule.java index a2a15b30d578..05823bf14b63 100644 --- a/core/java/android/hardware/soundtrigger/SoundTriggerModule.java +++ b/core/java/android/hardware/soundtrigger/SoundTriggerModule.java @@ -19,6 +19,9 @@ package android.hardware.soundtrigger; import android.annotation.NonNull; import android.annotation.Nullable; import android.compat.annotation.UnsupportedAppUsage; +import android.media.permission.ClearCallingIdentityContext; +import android.media.permission.Identity; +import android.media.permission.SafeCloseable; import android.media.soundtrigger_middleware.ISoundTriggerCallback; import android.media.soundtrigger_middleware.ISoundTriggerMiddlewareService; import android.media.soundtrigger_middleware.ISoundTriggerModule; @@ -50,12 +53,39 @@ public class SoundTriggerModule { private EventHandlerDelegate mEventHandlerDelegate; private ISoundTriggerModule mService; + /** + * This variant is intended for use when the caller is acting an originator, rather than on + * behalf of a different entity, as far as authorization goes. + */ SoundTriggerModule(@NonNull ISoundTriggerMiddlewareService service, - int moduleId, @NonNull SoundTrigger.StatusListener listener, @NonNull Looper looper) + int moduleId, @NonNull SoundTrigger.StatusListener listener, @NonNull Looper looper, + @NonNull Identity originatorIdentity) throws RemoteException { mId = moduleId; mEventHandlerDelegate = new EventHandlerDelegate(listener, looper); - mService = service.attach(moduleId, mEventHandlerDelegate); + + try (SafeCloseable ignored = ClearCallingIdentityContext.create()) { + mService = service.attachAsOriginator(moduleId, originatorIdentity, + mEventHandlerDelegate); + } + mService.asBinder().linkToDeath(mEventHandlerDelegate, 0); + } + + /** + * This variant is intended for use when the caller is acting as a middleman, i.e. on behalf of + * a different entity, as far as authorization goes. + */ + SoundTriggerModule(@NonNull ISoundTriggerMiddlewareService service, + int moduleId, @NonNull SoundTrigger.StatusListener listener, @NonNull Looper looper, + @NonNull Identity middlemanIdentity, @NonNull Identity originatorIdentity) + throws RemoteException { + mId = moduleId; + mEventHandlerDelegate = new EventHandlerDelegate(listener, looper); + + try (SafeCloseable ignored = ClearCallingIdentityContext.create()) { + mService = service.attachAsMiddleman(moduleId, middlemanIdentity, originatorIdentity, + mEventHandlerDelegate); + } mService.asBinder().linkToDeath(mEventHandlerDelegate, 0); } diff --git a/core/java/android/service/voice/AlwaysOnHotwordDetector.java b/core/java/android/service/voice/AlwaysOnHotwordDetector.java index 6f941121771e..8f8e6cc3d84a 100644 --- a/core/java/android/service/voice/AlwaysOnHotwordDetector.java +++ b/core/java/android/service/voice/AlwaysOnHotwordDetector.java @@ -16,9 +16,15 @@ package android.service.voice; +import static android.Manifest.permission.CAPTURE_AUDIO_HOTWORD; +import static android.Manifest.permission.RECORD_AUDIO; + import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; +import android.app.ActivityThread; +import android.annotation.RequiresPermission; +import android.annotation.SystemApi; import android.compat.annotation.UnsupportedAppUsage; import android.content.Context; import android.content.Intent; @@ -32,6 +38,7 @@ import android.hardware.soundtrigger.SoundTrigger.KeyphraseRecognitionExtra; import android.hardware.soundtrigger.SoundTrigger.ModuleProperties; import android.hardware.soundtrigger.SoundTrigger.RecognitionConfig; import android.media.AudioFormat; +import android.media.permission.Identity; import android.os.AsyncTask; import android.os.Handler; import android.os.Message; @@ -39,6 +46,7 @@ import android.os.RemoteException; import android.util.Slog; import com.android.internal.app.IVoiceInteractionManagerService; +import com.android.internal.app.IVoiceInteractionSoundTriggerSession; import java.io.PrintWriter; import java.lang.annotation.Retention; @@ -48,7 +56,12 @@ import java.util.Locale; /** * A class that lets a VoiceInteractionService implementation interact with * always-on keyphrase detection APIs. + * + * @hide + * TODO(b/168605867): Once Metalava supports expressing a removed public, but current system API, + * mark and track it as such. */ +@SystemApi public class AlwaysOnHotwordDetector { //---- States of Keyphrase availability. Return codes for onAvailabilityChanged() ----// /** @@ -228,6 +241,7 @@ public class AlwaysOnHotwordDetector { private KeyphraseMetadata mKeyphraseMetadata; private final KeyphraseEnrollmentInfo mKeyphraseEnrollmentInfo; private final IVoiceInteractionManagerService mModelManagementService; + private final IVoiceInteractionSoundTriggerSession mSoundTriggerSession; private final SoundTriggerListener mInternalCallback; private final Callback mExternalCallback; private final Object mLock = new Object(); @@ -425,6 +439,14 @@ public class AlwaysOnHotwordDetector { mHandler = new MyHandler(); mInternalCallback = new SoundTriggerListener(mHandler); mModelManagementService = modelManagementService; + try { + Identity identity = new Identity(); + identity.packageName = ActivityThread.currentOpPackageName(); + mSoundTriggerSession = mModelManagementService.createSoundTriggerSessionAsOriginator( + identity); + } catch (RemoteException e) { + throw e.rethrowAsRuntimeException(); + } new RefreshAvailabiltyTask().execute(); } @@ -485,7 +507,7 @@ public class AlwaysOnHotwordDetector { private int getSupportedAudioCapabilitiesLocked() { try { ModuleProperties properties = - mModelManagementService.getDspModuleProperties(); + mSoundTriggerSession.getDspModuleProperties(); if (properties != null) { return properties.getAudioCapabilities(); } @@ -513,6 +535,7 @@ public class AlwaysOnHotwordDetector { * This may happen if another detector has been instantiated or the * {@link VoiceInteractionService} hosting this detector has been shut down. */ + @RequiresPermission(allOf = {RECORD_AUDIO, CAPTURE_AUDIO_HOTWORD}) public boolean startRecognition(@RecognitionFlags int recognitionFlags) { if (DBG) Slog.d(TAG, "startRecognition(" + recognitionFlags + ")"); synchronized (mLock) { @@ -543,6 +566,7 @@ public class AlwaysOnHotwordDetector { * This may happen if another detector has been instantiated or the * {@link VoiceInteractionService} hosting this detector has been shut down. */ + @RequiresPermission(allOf = {RECORD_AUDIO, CAPTURE_AUDIO_HOTWORD}) public boolean stopRecognition() { if (DBG) Slog.d(TAG, "stopRecognition()"); synchronized (mLock) { @@ -577,6 +601,7 @@ public class AlwaysOnHotwordDetector { * - {@link SoundTrigger#STATUS_INVALID_OPERATION} if the call is out of sequence or * if API is not supported by HAL */ + @RequiresPermission(allOf = {RECORD_AUDIO, CAPTURE_AUDIO_HOTWORD}) public int setParameter(@ModelParams int modelParam, int value) { if (DBG) { Slog.d(TAG, "setParameter(" + modelParam + ", " + value + ")"); @@ -604,6 +629,7 @@ public class AlwaysOnHotwordDetector { * @param modelParam {@link ModelParams} * @return value of parameter */ + @RequiresPermission(allOf = {RECORD_AUDIO, CAPTURE_AUDIO_HOTWORD}) public int getParameter(@ModelParams int modelParam) { if (DBG) { Slog.d(TAG, "getParameter(" + modelParam + ")"); @@ -628,6 +654,7 @@ public class AlwaysOnHotwordDetector { * @param modelParam {@link ModelParams} * @return supported range of parameter, null if not supported */ + @RequiresPermission(allOf = {RECORD_AUDIO, CAPTURE_AUDIO_HOTWORD}) @Nullable public ModelParamRange queryParameter(@ModelParams int modelParam) { if (DBG) { @@ -658,6 +685,7 @@ public class AlwaysOnHotwordDetector { * This may happen if another detector has been instantiated or the * {@link VoiceInteractionService} hosting this detector has been shut down. */ + @Nullable public Intent createEnrollIntent() { if (DBG) Slog.d(TAG, "createEnrollIntent"); synchronized (mLock) { @@ -679,6 +707,7 @@ public class AlwaysOnHotwordDetector { * This may happen if another detector has been instantiated or the * {@link VoiceInteractionService} hosting this detector has been shut down. */ + @Nullable public Intent createUnEnrollIntent() { if (DBG) Slog.d(TAG, "createUnEnrollIntent"); synchronized (mLock) { @@ -700,6 +729,7 @@ public class AlwaysOnHotwordDetector { * This may happen if another detector has been instantiated or the * {@link VoiceInteractionService} hosting this detector has been shut down. */ + @Nullable public Intent createReEnrollIntent() { if (DBG) Slog.d(TAG, "createReEnrollIntent"); synchronized (mLock) { @@ -782,7 +812,7 @@ public class AlwaysOnHotwordDetector { int code; try { - code = mModelManagementService.startRecognition( + code = mSoundTriggerSession.startRecognition( mKeyphraseMetadata.getId(), mLocale.toLanguageTag(), mInternalCallback, new RecognitionConfig(captureTriggerAudio, allowMultipleTriggers, recognitionExtra, null /* additional data */, audioCapabilities)); @@ -799,7 +829,7 @@ public class AlwaysOnHotwordDetector { private int stopRecognitionLocked() { int code; try { - code = mModelManagementService.stopRecognition(mKeyphraseMetadata.getId(), + code = mSoundTriggerSession.stopRecognition(mKeyphraseMetadata.getId(), mInternalCallback); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); @@ -813,7 +843,7 @@ public class AlwaysOnHotwordDetector { private int setParameterLocked(@ModelParams int modelParam, int value) { try { - int code = mModelManagementService.setParameter(mKeyphraseMetadata.getId(), modelParam, + int code = mSoundTriggerSession.setParameter(mKeyphraseMetadata.getId(), modelParam, value); if (code != STATUS_OK) { @@ -828,7 +858,7 @@ public class AlwaysOnHotwordDetector { private int getParameterLocked(@ModelParams int modelParam) { try { - return mModelManagementService.getParameter(mKeyphraseMetadata.getId(), modelParam); + return mSoundTriggerSession.getParameter(mKeyphraseMetadata.getId(), modelParam); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } @@ -838,7 +868,7 @@ public class AlwaysOnHotwordDetector { private ModelParamRange queryParameterLocked(@ModelParams int modelParam) { try { SoundTrigger.ModelParamRange modelParamRange = - mModelManagementService.queryParameter(mKeyphraseMetadata.getId(), modelParam); + mSoundTriggerSession.queryParameter(mKeyphraseMetadata.getId(), modelParam); if (modelParamRange == null) { return null; @@ -972,7 +1002,7 @@ public class AlwaysOnHotwordDetector { ModuleProperties dspModuleProperties; try { dspModuleProperties = - mModelManagementService.getDspModuleProperties(); + mSoundTriggerSession.getDspModuleProperties(); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } diff --git a/core/java/android/service/voice/VoiceInteractionService.java b/core/java/android/service/voice/VoiceInteractionService.java index 45d3465fdae8..fb03ed45113e 100644 --- a/core/java/android/service/voice/VoiceInteractionService.java +++ b/core/java/android/service/voice/VoiceInteractionService.java @@ -20,6 +20,7 @@ import android.Manifest; import android.annotation.NonNull; import android.annotation.RequiresPermission; import android.annotation.SdkConstant; +import android.annotation.SuppressLint; import android.annotation.SystemApi; import android.app.Service; import android.compat.annotation.UnsupportedAppUsage; @@ -237,9 +238,8 @@ public class VoiceInteractionService extends Service { /** * Called during service initialization to tell you when the system is ready * to receive interaction from it. You should generally do initialization here - * rather than in {@link #onCreate}. Methods such as {@link #showSession} and - * {@link #createAlwaysOnHotwordDetector} - * will not be operational until this point. + * rather than in {@link #onCreate}. Methods such as {@link #showSession} will + * not be operational until this point. */ public void onReady() { mSystemService = IVoiceInteractionManagerService.Stub.asInterface( @@ -309,9 +309,15 @@ public class VoiceInteractionService extends Service { * @param locale The locale for which the enrollment needs to be performed. * @param callback The callback to notify of detection events. * @return An always-on hotword detector for the given keyphrase and locale. + * + * @hide */ + @SystemApi + @NonNull public final AlwaysOnHotwordDetector createAlwaysOnHotwordDetector( - String keyphrase, Locale locale, AlwaysOnHotwordDetector.Callback callback) { + @SuppressLint("MissingNullability") String keyphrase, // TODO: annotate nullability properly + @SuppressLint({"MissingNullability", "UseIcu"}) Locale locale, + @SuppressLint("MissingNullability") AlwaysOnHotwordDetector.Callback callback) { if (mSystemService == null) { throw new IllegalStateException("Not available until onReady() is called"); } diff --git a/core/java/com/android/internal/app/ISoundTriggerService.aidl b/core/java/com/android/internal/app/ISoundTriggerService.aidl index 74bfb963c6b0..f8aac42b8018 100644 --- a/core/java/com/android/internal/app/ISoundTriggerService.aidl +++ b/core/java/com/android/internal/app/ISoundTriggerService.aidl @@ -16,53 +16,45 @@ package com.android.internal.app; -import android.app.PendingIntent; -import android.content.ComponentName; -import android.hardware.soundtrigger.IRecognitionStatusCallback; -import android.hardware.soundtrigger.SoundTrigger; -import android.hardware.soundtrigger.ModelParams; -import android.os.Bundle; -import android.os.ParcelUuid; +import android.media.permission.Identity; +import com.android.internal.app.ISoundTriggerSession; /** * Service interface for a generic sound recognition model. + * + * This interface serves as an entry point to establish a session, associated with a client + * identity, which exposes the actual functionality. + * * @hide */ interface ISoundTriggerService { - - SoundTrigger.GenericSoundModel getSoundModel(in ParcelUuid soundModelId); - - void updateSoundModel(in SoundTrigger.GenericSoundModel soundModel); - - void deleteSoundModel(in ParcelUuid soundModelId); - - int startRecognition(in ParcelUuid soundModelId, in IRecognitionStatusCallback callback, - in SoundTrigger.RecognitionConfig config); - - int stopRecognition(in ParcelUuid soundModelId, in IRecognitionStatusCallback callback); - - int loadGenericSoundModel(in SoundTrigger.GenericSoundModel soundModel); - int loadKeyphraseSoundModel(in SoundTrigger.KeyphraseSoundModel soundModel); - - int startRecognitionForService(in ParcelUuid soundModelId, in Bundle params, - in ComponentName callbackIntent,in SoundTrigger.RecognitionConfig config); - - int stopRecognitionForService(in ParcelUuid soundModelId); - - int unloadSoundModel(in ParcelUuid soundModelId); - - /** For both ...Intent and ...Service based usage */ - boolean isRecognitionActive(in ParcelUuid parcelUuid); - - int getModelState(in ParcelUuid soundModelId); - - @nullable SoundTrigger.ModuleProperties getModuleProperties(); - - int setParameter(in ParcelUuid soundModelId, in ModelParams modelParam, - int value); - - int getParameter(in ParcelUuid soundModelId, in ModelParams modelParam); - - @nullable SoundTrigger.ModelParamRange queryParameter(in ParcelUuid soundModelId, - in ModelParams modelParam); + /** + * Creates a new session. + * + * This version is intended to be used when the caller itself is the originator of the + * operations, for authorization purposes. + * + * The pid/uid fields are ignored and will be replaced by those provided by binder. + * + * It is good practice to clear the binder calling identity prior to calling this, in case the + * caller is ever in the same process as the callee. + */ + ISoundTriggerSession attachAsOriginator(in Identity originatorIdentity); + + /** + * Creates a new session. + * + * This version is intended to be used when the caller is acting on behalf of a separate entity + * (the originator) and the sessions operations are to be accounted against that originator for + * authorization purposes. + * + * The caller must hold the SOUNDTRIGGER_DELEGATE_IDENTITY permission in order to be trusted to + * provide a reliable originator identity. It should follow the best practices for reliably and + * securely verifying the identity of the originator. + * + * It is good practice to clear the binder calling identity prior to calling this, in case the + * caller is ever in the same process as the callee. + */ + ISoundTriggerSession attachAsMiddleman(in Identity middlemanIdentity, + in Identity originatorIdentity); } diff --git a/core/java/com/android/internal/app/ISoundTriggerSession.aidl b/core/java/com/android/internal/app/ISoundTriggerSession.aidl new file mode 100644 index 000000000000..ec7d282522c8 --- /dev/null +++ b/core/java/com/android/internal/app/ISoundTriggerSession.aidl @@ -0,0 +1,68 @@ +/* + * Copyright (C) 2014 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.internal.app; + +import android.app.PendingIntent; +import android.content.ComponentName; +import android.hardware.soundtrigger.IRecognitionStatusCallback; +import android.hardware.soundtrigger.SoundTrigger; +import android.hardware.soundtrigger.ModelParams; +import android.os.Bundle; +import android.os.ParcelUuid; + +/** + * Service interface for a generic sound recognition model. + * @hide + */ +interface ISoundTriggerSession { + + SoundTrigger.GenericSoundModel getSoundModel(in ParcelUuid soundModelId); + + void updateSoundModel(in SoundTrigger.GenericSoundModel soundModel); + + void deleteSoundModel(in ParcelUuid soundModelId); + + int startRecognition(in ParcelUuid soundModelId, in IRecognitionStatusCallback callback, + in SoundTrigger.RecognitionConfig config); + + int stopRecognition(in ParcelUuid soundModelId, in IRecognitionStatusCallback callback); + + int loadGenericSoundModel(in SoundTrigger.GenericSoundModel soundModel); + int loadKeyphraseSoundModel(in SoundTrigger.KeyphraseSoundModel soundModel); + + int startRecognitionForService(in ParcelUuid soundModelId, in Bundle params, + in ComponentName callbackIntent,in SoundTrigger.RecognitionConfig config); + + int stopRecognitionForService(in ParcelUuid soundModelId); + + int unloadSoundModel(in ParcelUuid soundModelId); + + /** For both ...Intent and ...Service based usage */ + boolean isRecognitionActive(in ParcelUuid parcelUuid); + + int getModelState(in ParcelUuid soundModelId); + + @nullable SoundTrigger.ModuleProperties getModuleProperties(); + + int setParameter(in ParcelUuid soundModelId, in ModelParams modelParam, + int value); + + int getParameter(in ParcelUuid soundModelId, in ModelParams modelParam); + + @nullable SoundTrigger.ModelParamRange queryParameter(in ParcelUuid soundModelId, + in ModelParams modelParam); +} diff --git a/core/java/com/android/internal/app/IVoiceInteractionManagerService.aidl b/core/java/com/android/internal/app/IVoiceInteractionManagerService.aidl index 15ba8e8c11f7..49b4cd1e6a73 100644 --- a/core/java/com/android/internal/app/IVoiceInteractionManagerService.aidl +++ b/core/java/com/android/internal/app/IVoiceInteractionManagerService.aidl @@ -18,6 +18,7 @@ package com.android.internal.app; import android.content.ComponentName; import android.content.Intent; +import android.media.permission.Identity; import android.os.Bundle; import android.os.RemoteCallback; @@ -25,9 +26,8 @@ import com.android.internal.app.IVoiceActionCheckCallback; import com.android.internal.app.IVoiceInteractionSessionShowCallback; import com.android.internal.app.IVoiceInteractor; import com.android.internal.app.IVoiceInteractionSessionListener; -import android.hardware.soundtrigger.IRecognitionStatusCallback; +import com.android.internal.app.IVoiceInteractionSoundTriggerSession; import android.hardware.soundtrigger.KeyphraseMetadata; -import android.hardware.soundtrigger.ModelParams; import android.hardware.soundtrigger.SoundTrigger; import android.service.voice.IVoiceInteractionService; import android.service.voice.IVoiceInteractionSession; @@ -86,13 +86,6 @@ interface IVoiceInteractionManagerService { * @RequiresPermission Manifest.permission.MANAGE_VOICE_KEYPHRASES */ int deleteKeyphraseSoundModel(int keyphraseId, in String bcp47Locale); - - /** - * Gets the properties of the DSP hardware on this device, null if not present. - * Caller must be the active voice interaction service via - * {@link Settings.Secure.VOICE_INTERACTION_SERVICE}. - */ - SoundTrigger.ModuleProperties getDspModuleProperties(); /** * Indicates if there's a keyphrase sound model available for the given keyphrase ID and the * user ID of the caller. @@ -116,65 +109,6 @@ interface IVoiceInteractionManagerService { */ KeyphraseMetadata getEnrolledKeyphraseMetadata(String keyphrase, String bcp47Locale); /** - * Starts a recognition for the given keyphrase. - * Caller must be the active voice interaction service via - * {@link Settings.Secure.VOICE_INTERACTION_SERVICE}. - */ - int startRecognition(int keyphraseId, in String bcp47Locale, - in IRecognitionStatusCallback callback, - in SoundTrigger.RecognitionConfig recognitionConfig); - /** - * Stops a recognition for the given keyphrase. - * Caller must be the active voice interaction service via - * {@link Settings.Secure.VOICE_INTERACTION_SERVICE}. - */ - int stopRecognition(int keyphraseId, in IRecognitionStatusCallback callback); - /** - * Set a model specific ModelParams with the given value. This - * parameter will keep its value for the duration the model is loaded regardless of starting and - * stopping recognition. Once the model is unloaded, the value will be lost. - * queryParameter should be checked first before calling this method. - * Caller must be the active voice interaction service via - * {@link Settings.Secure.VOICE_INTERACTION_SERVICE}. - * - * @param keyphraseId The unique identifier for the keyphrase. - * @param modelParam ModelParams - * @param value Value to set - * @return - {@link SoundTrigger#STATUS_OK} in case of success - * - {@link SoundTrigger#STATUS_NO_INIT} if the native service cannot be reached - * - {@link SoundTrigger#STATUS_BAD_VALUE} invalid input parameter - * - {@link SoundTrigger#STATUS_INVALID_OPERATION} if the call is out of sequence or - * if API is not supported by HAL - */ - int setParameter(int keyphraseId, in ModelParams modelParam, int value); - /** - * Get a model specific ModelParams. This parameter will keep its value - * for the duration the model is loaded regardless of starting and stopping recognition. - * Once the model is unloaded, the value will be lost. If the value is not set, a default - * value is returned. See ModelParams for parameter default values. - * queryParameter should be checked first before calling this method. - * Caller must be the active voice interaction service via - * {@link Settings.Secure.VOICE_INTERACTION_SERVICE}. - * - * @param keyphraseId The unique identifier for the keyphrase. - * @param modelParam ModelParams - * @return value of parameter - */ - int getParameter(int keyphraseId, in ModelParams modelParam); - /** - * Determine if parameter control is supported for the given model handle. - * This method should be checked prior to calling setParameter or getParameter. - * Caller must be the active voice interaction service via - * {@link Settings.Secure.VOICE_INTERACTION_SERVICE}. - * - * @param keyphraseId The unique identifier for the keyphrase. - * @param modelParam ModelParams - * @return supported range of parameter, null if not supported - */ - @nullable SoundTrigger.ModelParamRange queryParameter(int keyphraseId, - in ModelParams modelParam); - - /** * @return the component name for the currently active voice interaction service * @RequiresPermission Manifest.permission.ACCESS_VOICE_INTERACTION_SERVICE */ @@ -277,4 +211,12 @@ interface IVoiceInteractionManagerService { */ void setDisabled(boolean disabled); + /** + * Creates a session, allowing controlling running sound models on detection hardware. + * Caller must provide an identity, used for permission tracking purposes. + * The uid/pid elements of the identity will be ignored by the server and replaced with the ones + * provided by binder. + */ + IVoiceInteractionSoundTriggerSession createSoundTriggerSessionAsOriginator( + in Identity originatorIdentity); } diff --git a/core/java/com/android/internal/app/IVoiceInteractionSoundTriggerSession.aidl b/core/java/com/android/internal/app/IVoiceInteractionSoundTriggerSession.aidl new file mode 100644 index 000000000000..33aab4132150 --- /dev/null +++ b/core/java/com/android/internal/app/IVoiceInteractionSoundTriggerSession.aidl @@ -0,0 +1,95 @@ +/* + * Copyright (C) 2020 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.internal.app; + +import android.hardware.soundtrigger.IRecognitionStatusCallback; +import android.hardware.soundtrigger.ModelParams; +import android.hardware.soundtrigger.SoundTrigger; + +/** + * This interface allows performing sound-trigger related operations with the actual sound trigger + * hardware. + * + * Every instance of this interface is associated with a client identity ("originator"), established + * upon the creation of the instance and used for permission accounting. + */ +interface IVoiceInteractionSoundTriggerSession { + /** + * Gets the properties of the DSP hardware on this device, null if not present. + * Caller must be the active voice interaction service via + * {@link Settings.Secure.VOICE_INTERACTION_SERVICE}. + */ + SoundTrigger.ModuleProperties getDspModuleProperties(); + /** + * Starts a recognition for the given keyphrase. + * Caller must be the active voice interaction service via + * {@link Settings.Secure.VOICE_INTERACTION_SERVICE}. + */ + int startRecognition(int keyphraseId, in String bcp47Locale, + in IRecognitionStatusCallback callback, + in SoundTrigger.RecognitionConfig recognitionConfig); + /** + * Stops a recognition for the given keyphrase. + * Caller must be the active voice interaction service via + * {@link Settings.Secure.VOICE_INTERACTION_SERVICE}. + */ + int stopRecognition(int keyphraseId, in IRecognitionStatusCallback callback); + /** + * Set a model specific ModelParams with the given value. This + * parameter will keep its value for the duration the model is loaded regardless of starting and + * stopping recognition. Once the model is unloaded, the value will be lost. + * queryParameter should be checked first before calling this method. + * Caller must be the active voice interaction service via + * {@link Settings.Secure.VOICE_INTERACTION_SERVICE}. + * + * @param keyphraseId The unique identifier for the keyphrase. + * @param modelParam ModelParams + * @param value Value to set + * @return - {@link SoundTrigger#STATUS_OK} in case of success + * - {@link SoundTrigger#STATUS_NO_INIT} if the native service cannot be reached + * - {@link SoundTrigger#STATUS_BAD_VALUE} invalid input parameter + * - {@link SoundTrigger#STATUS_INVALID_OPERATION} if the call is out of sequence or + * if API is not supported by HAL + */ + int setParameter(int keyphraseId, in ModelParams modelParam, int value); + /** + * Get a model specific ModelParams. This parameter will keep its value + * for the duration the model is loaded regardless of starting and stopping recognition. + * Once the model is unloaded, the value will be lost. If the value is not set, a default + * value is returned. See ModelParams for parameter default values. + * queryParameter should be checked first before calling this method. + * Caller must be the active voice interaction service via + * {@link Settings.Secure.VOICE_INTERACTION_SERVICE}. + * + * @param keyphraseId The unique identifier for the keyphrase. + * @param modelParam ModelParams + * @return value of parameter + */ + int getParameter(int keyphraseId, in ModelParams modelParam); + /** + * Determine if parameter control is supported for the given model handle. + * This method should be checked prior to calling setParameter or getParameter. + * Caller must be the active voice interaction service via + * {@link Settings.Secure.VOICE_INTERACTION_SERVICE}. + * + * @param keyphraseId The unique identifier for the keyphrase. + * @param modelParam ModelParams + * @return supported range of parameter, null if not supported + */ + @nullable SoundTrigger.ModelParamRange queryParameter(int keyphraseId, + in ModelParams modelParam); +} |
