Loading core/api/test-current.txt +78 −0 Original line number Diff line number Diff line Loading @@ -1651,6 +1651,11 @@ package android.hardware.soundtrigger { field @NonNull public static final android.os.Parcelable.Creator<android.hardware.soundtrigger.KeyphraseMetadata> CREATOR; } public class SoundTrigger { field public static final int MODEL_PARAM_INVALID = -1; // 0xffffffff field public static final int MODEL_PARAM_THRESHOLD_FACTOR = 0; // 0x0 } public static final class SoundTrigger.KeyphraseRecognitionExtra implements android.os.Parcelable { ctor public SoundTrigger.KeyphraseRecognitionExtra(int, int, int); } Loading @@ -1663,6 +1668,19 @@ package android.hardware.soundtrigger { ctor public SoundTrigger.ModuleProperties(int, @NonNull String, @NonNull String, @NonNull String, int, @NonNull String, int, int, int, int, boolean, int, boolean, int, boolean, int); } public static final class SoundTrigger.RecognitionConfig implements android.os.Parcelable { ctor public SoundTrigger.RecognitionConfig(boolean, boolean, @Nullable android.hardware.soundtrigger.SoundTrigger.KeyphraseRecognitionExtra[], @Nullable byte[], int); ctor public SoundTrigger.RecognitionConfig(boolean, boolean, @Nullable android.hardware.soundtrigger.SoundTrigger.KeyphraseRecognitionExtra[], @Nullable byte[]); method public int describeContents(); method public void writeToParcel(@NonNull android.os.Parcel, int); field @NonNull public static final android.os.Parcelable.Creator<android.hardware.soundtrigger.SoundTrigger.RecognitionConfig> CREATOR; field public final boolean allowMultipleTriggers; field public final int audioCapabilities; field public final boolean captureRequested; field @NonNull public final byte[] data; field @NonNull public final android.hardware.soundtrigger.SoundTrigger.KeyphraseRecognitionExtra[] keyphrases; } public static class SoundTrigger.RecognitionEvent { ctor public SoundTrigger.RecognitionEvent(int, int, boolean, int, int, int, boolean, @NonNull android.media.AudioFormat, @Nullable byte[], long); } Loading Loading @@ -2000,6 +2018,57 @@ package android.media.metrics { } package android.media.soundtrigger { public final class SoundTriggerInstrumentation { method public void setResourceContention(boolean); method public void triggerOnResourcesAvailable(); method public void triggerRestart(); } public static interface SoundTriggerInstrumentation.GlobalCallback { method public default void onClientAttached(); method public default void onClientDetached(); method public default void onFrameworkDetached(); method public void onModelLoaded(@NonNull android.media.soundtrigger.SoundTriggerInstrumentation.ModelSession); method public default void onPreempted(); method public default void onRestarted(); } public static interface SoundTriggerInstrumentation.ModelCallback { method public default void onModelUnloaded(); method public default void onParamSet(int, int); method public void onRecognitionStarted(@NonNull android.media.soundtrigger.SoundTriggerInstrumentation.RecognitionSession); } public class SoundTriggerInstrumentation.ModelSession { method public void clearModelCallback(); method @NonNull public java.util.List<android.hardware.soundtrigger.SoundTrigger.Keyphrase> getPhrases(); method @NonNull public android.media.soundtrigger.SoundTriggerManager.Model getSoundModel(); method public boolean isKeyphrase(); method public void setModelCallback(@NonNull java.util.concurrent.Executor, @NonNull android.media.soundtrigger.SoundTriggerInstrumentation.ModelCallback); method public void triggerUnloadModel(); } public static interface SoundTriggerInstrumentation.RecognitionCallback { method public void onRecognitionStopped(); } public class SoundTriggerInstrumentation.RecognitionSession { method public void clearRecognitionCallback(); method public int getAudioSession(); method @NonNull public android.hardware.soundtrigger.SoundTrigger.RecognitionConfig getRecognitionConfig(); method public void setRecognitionCallback(@NonNull java.util.concurrent.Executor, @NonNull android.media.soundtrigger.SoundTriggerInstrumentation.RecognitionCallback); method public void triggerAbortRecognition(); method public void triggerRecognitionEvent(@NonNull byte[], @Nullable java.util.List<android.hardware.soundtrigger.SoundTrigger.KeyphraseRecognitionExtra>); } public final class SoundTriggerManager { method @NonNull @RequiresPermission(android.Manifest.permission.MANAGE_SOUND_TRIGGER) public static android.media.soundtrigger.SoundTriggerInstrumentation attachInstrumentation(@NonNull java.util.concurrent.Executor, @NonNull android.media.soundtrigger.SoundTriggerInstrumentation.GlobalCallback); } } package android.media.tv { public final class TvInputManager { Loading @@ -2024,6 +2093,14 @@ package android.media.tv.tuner { } package android.media.voice { public final class KeyphraseModelManager { method @RequiresPermission("android.permission.MANAGE_VOICE_KEYPHRASES") public void setModelDatabaseForTestEnabled(boolean); } } package android.net { public class NetworkPolicyManager { Loading Loading @@ -2944,6 +3021,7 @@ package android.service.voice { 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(); method public final void setTestModuleForAlwaysOnHotwordDetectorEnabled(boolean); } public static class VoiceInteractionSession.ActivityId { Loading core/java/android/hardware/soundtrigger/SoundTrigger.java +31 −5 Original line number Diff line number Diff line Loading @@ -1051,6 +1051,29 @@ public class SoundTrigger { return "ModelParamRange [start=" + mStart + ", end=" + mEnd + "]"; } } /** * SoundTrigger model parameter types. * @hide */ @Retention(RetentionPolicy.SOURCE) @IntDef(flag = true, prefix = { "MODEL_PARAM" }, value = { MODEL_PARAM_INVALID, MODEL_PARAM_THRESHOLD_FACTOR }) public @interface ModelParamTypes {} /** * See {@link ModelParams.INVALID} * @hide */ @TestApi public static final int MODEL_PARAM_INVALID = ModelParams.INVALID; /** * See {@link ModelParams.THRESHOLD_FACTOR} * @hide */ @TestApi public static final int MODEL_PARAM_THRESHOLD_FACTOR = ModelParams.THRESHOLD_FACTOR; /** * Modes for key phrase recognition Loading Loading @@ -1450,7 +1473,8 @@ public class SoundTrigger { * * @hide */ public static class RecognitionConfig implements Parcelable { @TestApi public static final class RecognitionConfig implements Parcelable { /** True if the DSP should capture the trigger sound and make it available for further * capture. */ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) Loading @@ -1464,6 +1488,7 @@ public class SoundTrigger { * options for each keyphrase. */ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) @NonNull @SuppressLint("ArrayReturn") public final KeyphraseRecognitionExtra keyphrases[]; /** Opaque data for use by system applications who know about voice engine internals, * typically during enrollment. */ Loading @@ -1479,8 +1504,8 @@ public class SoundTrigger { public final int audioCapabilities; public RecognitionConfig(boolean captureRequested, boolean allowMultipleTriggers, @Nullable KeyphraseRecognitionExtra[] keyphrases, @Nullable byte[] data, int audioCapabilities) { @SuppressLint("ArrayReturn") @Nullable KeyphraseRecognitionExtra[] keyphrases, @Nullable byte[] data, int audioCapabilities) { this.captureRequested = captureRequested; this.allowMultipleTriggers = allowMultipleTriggers; this.keyphrases = keyphrases != null ? keyphrases : new KeyphraseRecognitionExtra[0]; Loading @@ -1490,7 +1515,8 @@ public class SoundTrigger { @UnsupportedAppUsage public RecognitionConfig(boolean captureRequested, boolean allowMultipleTriggers, @Nullable KeyphraseRecognitionExtra[] keyphrases, @Nullable byte[] data) { @SuppressLint("ArrayReturn") @Nullable KeyphraseRecognitionExtra[] keyphrases, @Nullable byte[] data) { this(captureRequested, allowMultipleTriggers, keyphrases, data, 0); } Loading @@ -1517,7 +1543,7 @@ public class SoundTrigger { } @Override public void writeToParcel(Parcel dest, int flags) { public void writeToParcel(@NonNull Parcel dest, int flags) { dest.writeByte((byte) (captureRequested ? 1 : 0)); dest.writeByte((byte) (allowMultipleTriggers ? 1 : 0)); dest.writeTypedArray(keyphrases, flags); Loading core/java/android/service/voice/VoiceInteractionService.java +50 −4 Original line number Diff line number Diff line Loading @@ -50,6 +50,7 @@ import android.provider.Settings; import android.util.ArraySet; import android.util.Log; import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.app.IVoiceActionCheckCallback; import com.android.internal.app.IVoiceInteractionManagerService; Loading Loading @@ -200,6 +201,9 @@ public class VoiceInteractionService extends Service { private final Set<HotwordDetector> mActiveDetectors = new ArraySet<>(); // True if any of the createAOHD methods should use the test ST module. @GuardedBy("mLock") private boolean mTestModuleForAlwaysOnHotwordDetectorEnabled = false; private void onDetectorRemoteException(@NonNull IBinder token, int detectorType) { Log.d(TAG, "onDetectorRemoteException for " + HotwordDetector.detectorTypeToString( Loading Loading @@ -512,14 +516,14 @@ public class VoiceInteractionService extends Service { Objects.requireNonNull(callback); return createAlwaysOnHotwordDetectorInternal(keyphrase, locale, /* supportHotwordDetectionService= */ false, /* options= */ null, /* sharedMemory= */ null, /* moduleProperties */ 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. * Use {@link #listModuleProperties()} to get available modules to attach to. * @hide */ @TestApi Loading Loading @@ -645,14 +649,14 @@ public class VoiceInteractionService extends Service { Objects.requireNonNull(callback); return createAlwaysOnHotwordDetectorInternal(keyphrase, locale, /* supportHotwordDetectionService= */ true, options, sharedMemory, /* moduleProperties */ null, executor, callback); /* 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. * Use {@link #listModuleProperties()} to get available modules to attach to. * @hide */ @TestApi Loading Loading @@ -717,6 +721,10 @@ public class VoiceInteractionService extends Service { try { dspDetector.registerOnDestroyListener(this::onHotwordDetectorDestroyed); // Check if we are currently overridden, and should use the test module. if (mTestModuleForAlwaysOnHotwordDetectorEnabled) { moduleProperties = getTestModuleProperties(); } // If moduleProperties is null, the default STModule is used. dspDetector.initialize(options, sharedMemory, moduleProperties); } catch (Exception e) { Loading Loading @@ -990,6 +998,44 @@ public class VoiceInteractionService extends Service { return mKeyphraseEnrollmentInfo; } /** * Configure {@link createAlwaysOnHotwordDetector(String, Locale, * SoundTrigger.ModuleProperties, Executor, AlwaysOnHotwordDetector.Callback)} * and similar overloads to utilize the test SoundTrigger module instead of the * actual DSP module. * @param isEnabled - {@code true} if subsequently created {@link AlwaysOnHotwordDetector} * objects should attach to a test module. {@code false} if subsequently created * {@link AlwaysOnHotwordDetector} should attach to the actual DSP module. * @hide */ @TestApi public final void setTestModuleForAlwaysOnHotwordDetectorEnabled(boolean isEnabled) { synchronized (mLock) { mTestModuleForAlwaysOnHotwordDetectorEnabled = isEnabled; } } /** * Get the {@link SoundTrigger.ModuleProperties} representing the fake * STHAL to attach to via {@link createAlwaysOnHotwordDetector(String, Locale, * SoundTrigger.ModuleProperties, Executor, AlwaysOnHotwordDetector.Callback)} and * similar overloads for test purposes. * @return ModuleProperties to use for test purposes. */ private final @NonNull SoundTrigger.ModuleProperties getTestModuleProperties() { var moduleProps = listModuleProperties() .stream() .filter((SoundTrigger.ModuleProperties prop) -> prop.getSupportedModelArch().equals(SoundTrigger.FAKE_HAL_ARCH)) .findFirst() .orElse(null); if (moduleProps == null) { throw new IllegalStateException("Fake ST HAL should always be available"); } return moduleProps; } /** * Checks if a given keyphrase and locale are supported to create an * {@link AlwaysOnHotwordDetector}. Loading core/java/com/android/internal/app/ISoundTriggerService.aidl +6 −1 Original line number Diff line number Diff line Loading @@ -16,8 +16,9 @@ package com.android.internal.app; import android.media.permission.Identity; import android.hardware.soundtrigger.SoundTrigger; import android.media.permission.Identity; import android.media.soundtrigger_middleware.ISoundTriggerInjection; import com.android.internal.app.ISoundTriggerSession; /** Loading Loading @@ -74,4 +75,8 @@ interface ISoundTriggerService { */ List<SoundTrigger.ModuleProperties> listModuleProperties(in Identity originatorIdentity); /** * Attach an HAL injection interface. */ void attachInjection(ISoundTriggerInjection injection); } core/java/com/android/internal/app/IVoiceInteractionManagerService.aidl +16 −0 Original line number Diff line number Diff line Loading @@ -96,6 +96,21 @@ interface IVoiceInteractionManagerService { * @RequiresPermission Manifest.permission.MANAGE_VOICE_KEYPHRASES */ int deleteKeyphraseSoundModel(int keyphraseId, in String bcp47Locale); /** * Override the persistent enrolled model database with an in-memory * fake for testing purposes. * * @param enabled - {@code true} to enable the test database. {@code false} to enable * the real, persistent database. * @param token - IBinder used to register a death listener to clean-up the override * if tests do not clean up gracefully. */ @EnforcePermission("MANAGE_VOICE_KEYPHRASES") @JavaPassthrough(annotation= "@android.annotation.RequiresPermission(" + "android.Manifest.permission.MANAGE_VOICE_KEYPHRASES)") void setModelDatabaseForTestEnabled(boolean enabled, IBinder token); /** * Indicates if there's a keyphrase sound model available for the given keyphrase ID and the * user ID of the caller. Loading @@ -106,6 +121,7 @@ interface IVoiceInteractionManagerService { * @param bcp47Locale The BCP47 language tag for the keyphrase's locale. */ boolean isEnrolledForKeyphrase(int keyphraseId, String bcp47Locale); /** * Generates KeyphraseMetadata for an enrolled sound model based on keyphrase string, locale, * and the user ID of the caller. Loading Loading
core/api/test-current.txt +78 −0 Original line number Diff line number Diff line Loading @@ -1651,6 +1651,11 @@ package android.hardware.soundtrigger { field @NonNull public static final android.os.Parcelable.Creator<android.hardware.soundtrigger.KeyphraseMetadata> CREATOR; } public class SoundTrigger { field public static final int MODEL_PARAM_INVALID = -1; // 0xffffffff field public static final int MODEL_PARAM_THRESHOLD_FACTOR = 0; // 0x0 } public static final class SoundTrigger.KeyphraseRecognitionExtra implements android.os.Parcelable { ctor public SoundTrigger.KeyphraseRecognitionExtra(int, int, int); } Loading @@ -1663,6 +1668,19 @@ package android.hardware.soundtrigger { ctor public SoundTrigger.ModuleProperties(int, @NonNull String, @NonNull String, @NonNull String, int, @NonNull String, int, int, int, int, boolean, int, boolean, int, boolean, int); } public static final class SoundTrigger.RecognitionConfig implements android.os.Parcelable { ctor public SoundTrigger.RecognitionConfig(boolean, boolean, @Nullable android.hardware.soundtrigger.SoundTrigger.KeyphraseRecognitionExtra[], @Nullable byte[], int); ctor public SoundTrigger.RecognitionConfig(boolean, boolean, @Nullable android.hardware.soundtrigger.SoundTrigger.KeyphraseRecognitionExtra[], @Nullable byte[]); method public int describeContents(); method public void writeToParcel(@NonNull android.os.Parcel, int); field @NonNull public static final android.os.Parcelable.Creator<android.hardware.soundtrigger.SoundTrigger.RecognitionConfig> CREATOR; field public final boolean allowMultipleTriggers; field public final int audioCapabilities; field public final boolean captureRequested; field @NonNull public final byte[] data; field @NonNull public final android.hardware.soundtrigger.SoundTrigger.KeyphraseRecognitionExtra[] keyphrases; } public static class SoundTrigger.RecognitionEvent { ctor public SoundTrigger.RecognitionEvent(int, int, boolean, int, int, int, boolean, @NonNull android.media.AudioFormat, @Nullable byte[], long); } Loading Loading @@ -2000,6 +2018,57 @@ package android.media.metrics { } package android.media.soundtrigger { public final class SoundTriggerInstrumentation { method public void setResourceContention(boolean); method public void triggerOnResourcesAvailable(); method public void triggerRestart(); } public static interface SoundTriggerInstrumentation.GlobalCallback { method public default void onClientAttached(); method public default void onClientDetached(); method public default void onFrameworkDetached(); method public void onModelLoaded(@NonNull android.media.soundtrigger.SoundTriggerInstrumentation.ModelSession); method public default void onPreempted(); method public default void onRestarted(); } public static interface SoundTriggerInstrumentation.ModelCallback { method public default void onModelUnloaded(); method public default void onParamSet(int, int); method public void onRecognitionStarted(@NonNull android.media.soundtrigger.SoundTriggerInstrumentation.RecognitionSession); } public class SoundTriggerInstrumentation.ModelSession { method public void clearModelCallback(); method @NonNull public java.util.List<android.hardware.soundtrigger.SoundTrigger.Keyphrase> getPhrases(); method @NonNull public android.media.soundtrigger.SoundTriggerManager.Model getSoundModel(); method public boolean isKeyphrase(); method public void setModelCallback(@NonNull java.util.concurrent.Executor, @NonNull android.media.soundtrigger.SoundTriggerInstrumentation.ModelCallback); method public void triggerUnloadModel(); } public static interface SoundTriggerInstrumentation.RecognitionCallback { method public void onRecognitionStopped(); } public class SoundTriggerInstrumentation.RecognitionSession { method public void clearRecognitionCallback(); method public int getAudioSession(); method @NonNull public android.hardware.soundtrigger.SoundTrigger.RecognitionConfig getRecognitionConfig(); method public void setRecognitionCallback(@NonNull java.util.concurrent.Executor, @NonNull android.media.soundtrigger.SoundTriggerInstrumentation.RecognitionCallback); method public void triggerAbortRecognition(); method public void triggerRecognitionEvent(@NonNull byte[], @Nullable java.util.List<android.hardware.soundtrigger.SoundTrigger.KeyphraseRecognitionExtra>); } public final class SoundTriggerManager { method @NonNull @RequiresPermission(android.Manifest.permission.MANAGE_SOUND_TRIGGER) public static android.media.soundtrigger.SoundTriggerInstrumentation attachInstrumentation(@NonNull java.util.concurrent.Executor, @NonNull android.media.soundtrigger.SoundTriggerInstrumentation.GlobalCallback); } } package android.media.tv { public final class TvInputManager { Loading @@ -2024,6 +2093,14 @@ package android.media.tv.tuner { } package android.media.voice { public final class KeyphraseModelManager { method @RequiresPermission("android.permission.MANAGE_VOICE_KEYPHRASES") public void setModelDatabaseForTestEnabled(boolean); } } package android.net { public class NetworkPolicyManager { Loading Loading @@ -2944,6 +3021,7 @@ package android.service.voice { 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(); method public final void setTestModuleForAlwaysOnHotwordDetectorEnabled(boolean); } public static class VoiceInteractionSession.ActivityId { Loading
core/java/android/hardware/soundtrigger/SoundTrigger.java +31 −5 Original line number Diff line number Diff line Loading @@ -1051,6 +1051,29 @@ public class SoundTrigger { return "ModelParamRange [start=" + mStart + ", end=" + mEnd + "]"; } } /** * SoundTrigger model parameter types. * @hide */ @Retention(RetentionPolicy.SOURCE) @IntDef(flag = true, prefix = { "MODEL_PARAM" }, value = { MODEL_PARAM_INVALID, MODEL_PARAM_THRESHOLD_FACTOR }) public @interface ModelParamTypes {} /** * See {@link ModelParams.INVALID} * @hide */ @TestApi public static final int MODEL_PARAM_INVALID = ModelParams.INVALID; /** * See {@link ModelParams.THRESHOLD_FACTOR} * @hide */ @TestApi public static final int MODEL_PARAM_THRESHOLD_FACTOR = ModelParams.THRESHOLD_FACTOR; /** * Modes for key phrase recognition Loading Loading @@ -1450,7 +1473,8 @@ public class SoundTrigger { * * @hide */ public static class RecognitionConfig implements Parcelable { @TestApi public static final class RecognitionConfig implements Parcelable { /** True if the DSP should capture the trigger sound and make it available for further * capture. */ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) Loading @@ -1464,6 +1488,7 @@ public class SoundTrigger { * options for each keyphrase. */ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) @NonNull @SuppressLint("ArrayReturn") public final KeyphraseRecognitionExtra keyphrases[]; /** Opaque data for use by system applications who know about voice engine internals, * typically during enrollment. */ Loading @@ -1479,8 +1504,8 @@ public class SoundTrigger { public final int audioCapabilities; public RecognitionConfig(boolean captureRequested, boolean allowMultipleTriggers, @Nullable KeyphraseRecognitionExtra[] keyphrases, @Nullable byte[] data, int audioCapabilities) { @SuppressLint("ArrayReturn") @Nullable KeyphraseRecognitionExtra[] keyphrases, @Nullable byte[] data, int audioCapabilities) { this.captureRequested = captureRequested; this.allowMultipleTriggers = allowMultipleTriggers; this.keyphrases = keyphrases != null ? keyphrases : new KeyphraseRecognitionExtra[0]; Loading @@ -1490,7 +1515,8 @@ public class SoundTrigger { @UnsupportedAppUsage public RecognitionConfig(boolean captureRequested, boolean allowMultipleTriggers, @Nullable KeyphraseRecognitionExtra[] keyphrases, @Nullable byte[] data) { @SuppressLint("ArrayReturn") @Nullable KeyphraseRecognitionExtra[] keyphrases, @Nullable byte[] data) { this(captureRequested, allowMultipleTriggers, keyphrases, data, 0); } Loading @@ -1517,7 +1543,7 @@ public class SoundTrigger { } @Override public void writeToParcel(Parcel dest, int flags) { public void writeToParcel(@NonNull Parcel dest, int flags) { dest.writeByte((byte) (captureRequested ? 1 : 0)); dest.writeByte((byte) (allowMultipleTriggers ? 1 : 0)); dest.writeTypedArray(keyphrases, flags); Loading
core/java/android/service/voice/VoiceInteractionService.java +50 −4 Original line number Diff line number Diff line Loading @@ -50,6 +50,7 @@ import android.provider.Settings; import android.util.ArraySet; import android.util.Log; import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.app.IVoiceActionCheckCallback; import com.android.internal.app.IVoiceInteractionManagerService; Loading Loading @@ -200,6 +201,9 @@ public class VoiceInteractionService extends Service { private final Set<HotwordDetector> mActiveDetectors = new ArraySet<>(); // True if any of the createAOHD methods should use the test ST module. @GuardedBy("mLock") private boolean mTestModuleForAlwaysOnHotwordDetectorEnabled = false; private void onDetectorRemoteException(@NonNull IBinder token, int detectorType) { Log.d(TAG, "onDetectorRemoteException for " + HotwordDetector.detectorTypeToString( Loading Loading @@ -512,14 +516,14 @@ public class VoiceInteractionService extends Service { Objects.requireNonNull(callback); return createAlwaysOnHotwordDetectorInternal(keyphrase, locale, /* supportHotwordDetectionService= */ false, /* options= */ null, /* sharedMemory= */ null, /* moduleProperties */ 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. * Use {@link #listModuleProperties()} to get available modules to attach to. * @hide */ @TestApi Loading Loading @@ -645,14 +649,14 @@ public class VoiceInteractionService extends Service { Objects.requireNonNull(callback); return createAlwaysOnHotwordDetectorInternal(keyphrase, locale, /* supportHotwordDetectionService= */ true, options, sharedMemory, /* moduleProperties */ null, executor, callback); /* 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. * Use {@link #listModuleProperties()} to get available modules to attach to. * @hide */ @TestApi Loading Loading @@ -717,6 +721,10 @@ public class VoiceInteractionService extends Service { try { dspDetector.registerOnDestroyListener(this::onHotwordDetectorDestroyed); // Check if we are currently overridden, and should use the test module. if (mTestModuleForAlwaysOnHotwordDetectorEnabled) { moduleProperties = getTestModuleProperties(); } // If moduleProperties is null, the default STModule is used. dspDetector.initialize(options, sharedMemory, moduleProperties); } catch (Exception e) { Loading Loading @@ -990,6 +998,44 @@ public class VoiceInteractionService extends Service { return mKeyphraseEnrollmentInfo; } /** * Configure {@link createAlwaysOnHotwordDetector(String, Locale, * SoundTrigger.ModuleProperties, Executor, AlwaysOnHotwordDetector.Callback)} * and similar overloads to utilize the test SoundTrigger module instead of the * actual DSP module. * @param isEnabled - {@code true} if subsequently created {@link AlwaysOnHotwordDetector} * objects should attach to a test module. {@code false} if subsequently created * {@link AlwaysOnHotwordDetector} should attach to the actual DSP module. * @hide */ @TestApi public final void setTestModuleForAlwaysOnHotwordDetectorEnabled(boolean isEnabled) { synchronized (mLock) { mTestModuleForAlwaysOnHotwordDetectorEnabled = isEnabled; } } /** * Get the {@link SoundTrigger.ModuleProperties} representing the fake * STHAL to attach to via {@link createAlwaysOnHotwordDetector(String, Locale, * SoundTrigger.ModuleProperties, Executor, AlwaysOnHotwordDetector.Callback)} and * similar overloads for test purposes. * @return ModuleProperties to use for test purposes. */ private final @NonNull SoundTrigger.ModuleProperties getTestModuleProperties() { var moduleProps = listModuleProperties() .stream() .filter((SoundTrigger.ModuleProperties prop) -> prop.getSupportedModelArch().equals(SoundTrigger.FAKE_HAL_ARCH)) .findFirst() .orElse(null); if (moduleProps == null) { throw new IllegalStateException("Fake ST HAL should always be available"); } return moduleProps; } /** * Checks if a given keyphrase and locale are supported to create an * {@link AlwaysOnHotwordDetector}. Loading
core/java/com/android/internal/app/ISoundTriggerService.aidl +6 −1 Original line number Diff line number Diff line Loading @@ -16,8 +16,9 @@ package com.android.internal.app; import android.media.permission.Identity; import android.hardware.soundtrigger.SoundTrigger; import android.media.permission.Identity; import android.media.soundtrigger_middleware.ISoundTriggerInjection; import com.android.internal.app.ISoundTriggerSession; /** Loading Loading @@ -74,4 +75,8 @@ interface ISoundTriggerService { */ List<SoundTrigger.ModuleProperties> listModuleProperties(in Identity originatorIdentity); /** * Attach an HAL injection interface. */ void attachInjection(ISoundTriggerInjection injection); }
core/java/com/android/internal/app/IVoiceInteractionManagerService.aidl +16 −0 Original line number Diff line number Diff line Loading @@ -96,6 +96,21 @@ interface IVoiceInteractionManagerService { * @RequiresPermission Manifest.permission.MANAGE_VOICE_KEYPHRASES */ int deleteKeyphraseSoundModel(int keyphraseId, in String bcp47Locale); /** * Override the persistent enrolled model database with an in-memory * fake for testing purposes. * * @param enabled - {@code true} to enable the test database. {@code false} to enable * the real, persistent database. * @param token - IBinder used to register a death listener to clean-up the override * if tests do not clean up gracefully. */ @EnforcePermission("MANAGE_VOICE_KEYPHRASES") @JavaPassthrough(annotation= "@android.annotation.RequiresPermission(" + "android.Manifest.permission.MANAGE_VOICE_KEYPHRASES)") void setModelDatabaseForTestEnabled(boolean enabled, IBinder token); /** * Indicates if there's a keyphrase sound model available for the given keyphrase ID and the * user ID of the caller. Loading @@ -106,6 +121,7 @@ interface IVoiceInteractionManagerService { * @param bcp47Locale The BCP47 language tag for the keyphrase's locale. */ boolean isEnrolledForKeyphrase(int keyphraseId, String bcp47Locale); /** * Generates KeyphraseMetadata for an enrolled sound model based on keyphrase string, locale, * and the user ID of the caller. Loading