diff options
| author | 2023-02-13 09:45:15 -0800 | |
|---|---|---|
| committer | 2023-02-15 16:35:42 -0800 | |
| commit | 333b72b7b5ba4f6179b1d3fa50d5406dcb9af777 (patch) | |
| tree | ca79e15c439cbf6201ed6c9b0ef0d2c0fd85d1fd | |
| parent | 9e31351327f707a9c13be5736988d99a2179c529 (diff) | |
Add VIService ST module selection TestApis
Allow VIService to enumerate available underlying ST modules for test
purposes.
Allow VIService to select a specific STModule (via ModuleProperties)
when creating an AOHD.
If no STModule is passed, AOHD will attach to the first available
module -- retaining existing behavior.
Test: Manual verification of assistant recognition flow
Bug: 269358382
Change-Id: Id18240ba8db2c096b5703d068917fc022f0cd61e
5 files changed, 126 insertions, 22 deletions
diff --git a/core/api/test-current.txt b/core/api/test-current.txt index d5ced049e9cd..a0835e3f5aea 100644 --- a/core/api/test-current.txt +++ b/core/api/test-current.txt @@ -2800,6 +2800,12 @@ package android.service.voice { ctor public VisibleActivityInfo(int, @NonNull android.os.IBinder); } + public class VoiceInteractionService extends android.app.Service { + method @NonNull @RequiresPermission(android.Manifest.permission.MANAGE_HOTWORD_DETECTION) public final android.service.voice.AlwaysOnHotwordDetector createAlwaysOnHotwordDetectorForTest(@NonNull String, @NonNull java.util.Locale, @NonNull android.hardware.soundtrigger.SoundTrigger.ModuleProperties, @NonNull java.util.concurrent.Executor, @NonNull android.service.voice.AlwaysOnHotwordDetector.Callback); + method @NonNull @RequiresPermission(android.Manifest.permission.MANAGE_HOTWORD_DETECTION) public final android.service.voice.AlwaysOnHotwordDetector createAlwaysOnHotwordDetectorForTest(@NonNull String, @NonNull java.util.Locale, @Nullable android.os.PersistableBundle, @Nullable android.os.SharedMemory, @NonNull android.hardware.soundtrigger.SoundTrigger.ModuleProperties, @NonNull java.util.concurrent.Executor, @NonNull android.service.voice.AlwaysOnHotwordDetector.Callback); + method @NonNull public final java.util.List<android.hardware.soundtrigger.SoundTrigger.ModuleProperties> listModuleProperties(); + } + public static class VoiceInteractionSession.ActivityId { method @NonNull public android.os.IBinder getAssistToken(); method public int getTaskId(); diff --git a/core/java/android/service/voice/AlwaysOnHotwordDetector.java b/core/java/android/service/voice/AlwaysOnHotwordDetector.java index bcc3f58a3bbf..48b7a5915658 100644 --- a/core/java/android/service/voice/AlwaysOnHotwordDetector.java +++ b/core/java/android/service/voice/AlwaysOnHotwordDetector.java @@ -784,8 +784,13 @@ public class AlwaysOnHotwordDetector extends AbstractDetector { mSupportSandboxedDetectionService = supportSandboxedDetectionService; } + // Do nothing. This method should not be abstract. + // TODO (b/269355519) un-subclass AOHD. @Override - void initialize(@Nullable PersistableBundle options, @Nullable SharedMemory sharedMemory) { + void initialize(@Nullable PersistableBundle options, @Nullable SharedMemory sharedMemory) {} + + void initialize(@Nullable PersistableBundle options, @Nullable SharedMemory sharedMemory, + @Nullable SoundTrigger.ModuleProperties moduleProperties) { if (mSupportSandboxedDetectionService) { initAndVerifyDetector(options, sharedMemory, mInternalCallback, DETECTOR_TYPE_TRUSTED_HOTWORD_DSP); @@ -793,9 +798,18 @@ public class AlwaysOnHotwordDetector extends AbstractDetector { try { Identity identity = new Identity(); identity.packageName = ActivityThread.currentOpPackageName(); + if (moduleProperties == null) { + List<SoundTrigger.ModuleProperties> modulePropList = + mModelManagementService.listModuleProperties(identity); + if (modulePropList.size() > 0) { + moduleProperties = modulePropList.get(0); + } + // (@atneya) intentionally let a null moduleProperties through until + // all CTS tests are fixed + } mSoundTriggerSession = mModelManagementService.createSoundTriggerSessionAsOriginator( - identity, mBinder); + identity, mBinder, moduleProperties); } catch (RemoteException e) { throw e.rethrowAsRuntimeException(); } diff --git a/core/java/android/service/voice/VoiceInteractionService.java b/core/java/android/service/voice/VoiceInteractionService.java index 12853cbaf6e1..a684e41a2a95 100644 --- a/core/java/android/service/voice/VoiceInteractionService.java +++ b/core/java/android/service/voice/VoiceInteractionService.java @@ -24,6 +24,8 @@ import android.annotation.RequiresPermission; import android.annotation.SdkConstant; import android.annotation.SuppressLint; import android.annotation.SystemApi; +import android.annotation.TestApi; +import android.app.ActivityThread; import android.app.Service; import android.app.compat.CompatChanges; import android.compat.annotation.ChangeId; @@ -33,7 +35,9 @@ import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.hardware.soundtrigger.KeyphraseEnrollmentInfo; +import android.hardware.soundtrigger.SoundTrigger; import android.media.voice.KeyphraseModelManager; +import android.media.permission.Identity; import android.os.Build; import android.os.Bundle; import android.os.Handler; @@ -386,6 +390,22 @@ public class VoiceInteractionService extends Service { } /** + * List available ST modules to attach to for test purposes. + * @hide + */ + @TestApi + @NonNull + public final List<SoundTrigger.ModuleProperties> listModuleProperties() { + Identity identity = new Identity(); + identity.packageName = ActivityThread.currentOpPackageName(); + try { + return mSystemService.listModuleProperties(identity); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** * Creates an {@link AlwaysOnHotwordDetector} for the given keyphrase and locale. * This instance must be retained and used by the client. * Calling this a second time invalidates the previously created hotword detector @@ -422,7 +442,8 @@ public class VoiceInteractionService extends Service { @SuppressLint("MissingNullability") AlwaysOnHotwordDetector.Callback callback) { return createAlwaysOnHotwordDetectorInternal(keyphrase, locale, /* supportHotwordDetectionService= */ false, /* options= */ null, - /* sharedMemory= */ null, /* executor= */ null, callback); + /* sharedMemory= */ null, /* moduleProperties */ null, + /* executor= */ null, callback); } /** @@ -461,10 +482,37 @@ public class VoiceInteractionService extends Service { Objects.requireNonNull(callback); return createAlwaysOnHotwordDetectorInternal(keyphrase, locale, /* supportHotwordDetectionService= */ false, /* options= */ null, - /* sharedMemory= */ null, executor, callback); + /* sharedMemory= */ null, /* moduleProperties */ null, executor, callback); } /** + * Same as {@link createAlwaysOnHotwordDetector(String, Locale, Executor, + * AlwaysOnHotwordDetector.Callback)}, but allow explicit selection of the underlying ST + * module to attach to. + * Use {@link listModuleProperties} to get available modules to attach to. + * @hide + */ + @TestApi + @RequiresPermission(Manifest.permission.MANAGE_HOTWORD_DETECTION) + @NonNull + public final AlwaysOnHotwordDetector createAlwaysOnHotwordDetectorForTest( + @NonNull String keyphrase, @SuppressLint("UseIcu") @NonNull Locale locale, + @NonNull SoundTrigger.ModuleProperties moduleProperties, + @NonNull @CallbackExecutor Executor executor, + @NonNull AlwaysOnHotwordDetector.Callback callback) { + + Objects.requireNonNull(keyphrase); + Objects.requireNonNull(locale); + Objects.requireNonNull(moduleProperties); + Objects.requireNonNull(executor); + Objects.requireNonNull(callback); + return createAlwaysOnHotwordDetectorInternal(keyphrase, locale, + /* supportHotwordDetectionService= */ false, /* options= */ null, + /* sharedMemory= */ null, moduleProperties, executor, callback); + } + + + /** * Create an {@link AlwaysOnHotwordDetector} and trigger a {@link HotwordDetectionService} * service, then it will also pass the read-only data to hotword detection service. * @@ -515,7 +563,7 @@ public class VoiceInteractionService extends Service { @SuppressLint("MissingNullability") AlwaysOnHotwordDetector.Callback callback) { return createAlwaysOnHotwordDetectorInternal(keyphrase, locale, /* supportHotwordDetectionService= */ true, options, sharedMemory, - /* executor= */ null, callback); + /* modulProperties */ null, /* executor= */ null, callback); } /** @@ -566,16 +614,46 @@ public class VoiceInteractionService extends Service { Objects.requireNonNull(executor); Objects.requireNonNull(callback); return createAlwaysOnHotwordDetectorInternal(keyphrase, locale, - /* supportHotwordDetectionService= */ true, options, sharedMemory, executor, - callback); + /* supportHotwordDetectionService= */ true, options, sharedMemory, + /* moduleProperties */ null, executor, callback); } + /** + * Same as {@link createAlwaysOnHotwordDetector(String, Locale, + * PersistableBundle, SharedMemory, Executor, AlwaysOnHotwordDetector.Callback)}, + * but allow explicit selection of the underlying ST module to attach to. + * Use {@link listModuleProperties} to get available modules to attach to. + * @hide + */ + @TestApi + @RequiresPermission(Manifest.permission.MANAGE_HOTWORD_DETECTION) + @NonNull + public final AlwaysOnHotwordDetector createAlwaysOnHotwordDetectorForTest( + @NonNull String keyphrase, @SuppressLint("UseIcu") @NonNull Locale locale, + @Nullable PersistableBundle options, @Nullable SharedMemory sharedMemory, + @NonNull SoundTrigger.ModuleProperties moduleProperties, + @NonNull @CallbackExecutor Executor executor, + @NonNull AlwaysOnHotwordDetector.Callback callback) { + + Objects.requireNonNull(keyphrase); + Objects.requireNonNull(locale); + Objects.requireNonNull(moduleProperties); + Objects.requireNonNull(executor); + Objects.requireNonNull(callback); + return createAlwaysOnHotwordDetectorInternal(keyphrase, locale, + /* supportHotwordDetectionService= */ true, options, sharedMemory, + moduleProperties, executor, callback); + } + + + private AlwaysOnHotwordDetector createAlwaysOnHotwordDetectorInternal( @SuppressLint("MissingNullability") String keyphrase, // TODO: nullability properly @SuppressLint({"MissingNullability", "UseIcu"}) Locale locale, boolean supportHotwordDetectionService, @Nullable PersistableBundle options, @Nullable SharedMemory sharedMemory, + @Nullable SoundTrigger.ModuleProperties moduleProperties, @Nullable @CallbackExecutor Executor executor, @SuppressLint("MissingNullability") AlwaysOnHotwordDetector.Callback callback) { @@ -609,7 +687,8 @@ public class VoiceInteractionService extends Service { try { dspDetector.registerOnDestroyListener(this::onHotwordDetectorDestroyed); - dspDetector.initialize(options, sharedMemory); + // If moduleProperties is null, the default STModule is used. + dspDetector.initialize(options, sharedMemory, moduleProperties); } catch (Exception e) { mActiveDetectors.remove(dspDetector); dspDetector.destroy(); diff --git a/core/java/com/android/internal/app/IVoiceInteractionManagerService.aidl b/core/java/com/android/internal/app/IVoiceInteractionManagerService.aidl index 619b4208048c..5eb97862c79f 100644 --- a/core/java/com/android/internal/app/IVoiceInteractionManagerService.aidl +++ b/core/java/com/android/internal/app/IVoiceInteractionManagerService.aidl @@ -243,7 +243,14 @@ interface IVoiceInteractionManagerService { */ IVoiceInteractionSoundTriggerSession createSoundTriggerSessionAsOriginator( in Identity originatorIdentity, - IBinder client); + IBinder client, + in SoundTrigger.ModuleProperties moduleProperties); + + /** + * Lists properties of SoundTrigger modules that can be attached to by + * @{link createSoundTriggerSessionAsOriginator}. + */ + List<SoundTrigger.ModuleProperties> listModuleProperties(in Identity originatorIdentity); /** * Set configuration and pass read-only data to hotword detection service. diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java index a08eaca25713..d4374a9c2654 100644 --- a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java +++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java @@ -377,7 +377,8 @@ public class VoiceInteractionManagerService extends SystemService { @Override public @NonNull IVoiceInteractionSoundTriggerSession createSoundTriggerSessionAsOriginator( - @NonNull Identity originatorIdentity, IBinder client) { + @NonNull Identity originatorIdentity, IBinder client, + @NonNull ModuleProperties moduleProperties) { Objects.requireNonNull(originatorIdentity); boolean forHotwordDetectionService; synchronized (VoiceInteractionManagerServiceStub.this) { @@ -396,7 +397,7 @@ public class VoiceInteractionManagerService extends SystemService { originatorIdentity.uid = Binder.getCallingUid(); originatorIdentity.pid = Binder.getCallingPid(); session = new SoundTriggerSessionPermissionsDecorator( - createSoundTriggerSessionForSelfIdentity(client), + createSoundTriggerSessionForSelfIdentity(client, moduleProperties), mContext, originatorIdentity); } else { @@ -406,14 +407,14 @@ public class VoiceInteractionManagerService extends SystemService { try (SafeCloseable ignored = PermissionUtil.establishIdentityDirect( originatorIdentity)) { session = new SoundTriggerSession(mSoundTriggerInternal.attach(client, - getDefaultModuleProperties(originatorIdentity))); + moduleProperties)); } } return new SoundTriggerSessionBinderProxy(session); } private IVoiceInteractionSoundTriggerSession createSoundTriggerSessionForSelfIdentity( - IBinder client) { + IBinder client, ModuleProperties moduleProperties) { Identity identity = new Identity(); identity.uid = Process.myUid(); identity.pid = Process.myPid(); @@ -421,20 +422,17 @@ public class VoiceInteractionManagerService extends SystemService { return Binder.withCleanCallingIdentity(() -> { try (SafeCloseable ignored = IdentityContext.create(identity)) { return new SoundTriggerSession( - mSoundTriggerInternal.attach(client, - getDefaultModuleProperties(identity))); + mSoundTriggerInternal.attach(client, moduleProperties)); } }); } - private ModuleProperties getDefaultModuleProperties(Identity originatorIdentity) { - List<ModuleProperties> modulePropList = mSoundTriggerInternal - .listModuleProperties(originatorIdentity); - if (modulePropList.isEmpty()) { - return null; - } else { - return modulePropList.get(0); + @Override + public List<ModuleProperties> listModuleProperties(Identity originatorIdentity) { + synchronized (VoiceInteractionManagerServiceStub.this) { + enforceIsCurrentVoiceInteractionService(); } + return mSoundTriggerInternal.listModuleProperties(originatorIdentity); } // TODO: VI Make sure the caller is the current user or profile |