From f94db1c8228edbbb3efc94dee29f5f789623be3e Mon Sep 17 00:00:00 2001 From: Nicholas Ambur Date: Sun, 8 Dec 2019 19:04:06 -0800 Subject: [PATCH] add parameter control to AlwaysOnHotwordDetector Per-model control to AlwaysOnHotwordDetector interface. Models are selected by keyphrase and locale associated with the AlwaysOnHotwordDetector instance. Bug: 141929369 Test: tested manually with test app Change-Id: Iffeb0954059fa030d8a4fefb934a3e665323be37 --- api/current.txt | 9 + api/system-current.txt | 5 +- .../hardware/soundtrigger/ConversionUtil.java | 3 +- .../hardware/soundtrigger/SoundTrigger.java | 66 +++++-- .../voice/AlwaysOnHotwordDetector.java | 181 ++++++++++++++++++ .../internal/app/ISoundTriggerService.aidl | 4 - .../app/IVoiceInteractionManagerService.aidl | 44 +++++ .../soundtrigger/SoundTriggerManager.java | 13 +- .../soundtrigger/SoundTriggerHelper.java | 118 +++++++----- .../soundtrigger/SoundTriggerInternal.java | 62 +++++- .../soundtrigger/SoundTriggerService.java | 27 ++- .../VoiceInteractionManagerService.java | 51 +++++ 12 files changed, 493 insertions(+), 90 deletions(-) diff --git a/api/current.txt b/api/current.txt index 36567ecc1a72d..c31610fe5fe7a 100644 --- a/api/current.txt +++ b/api/current.txt @@ -42617,9 +42617,13 @@ package android.service.voice { method public android.content.Intent createEnrollIntent(); method public android.content.Intent createReEnrollIntent(); method public android.content.Intent createUnEnrollIntent(); + method public int getParameter(int); method public int getSupportedRecognitionModes(); + method @Nullable public android.service.voice.AlwaysOnHotwordDetector.ModelParamRange queryParameter(int); + method public int setParameter(int, int); method public boolean startRecognition(int); method public boolean stopRecognition(); + field public static final int MODEL_PARAM_THRESHOLD_FACTOR = 0; // 0x0 field public static final int RECOGNITION_FLAG_ALLOW_MULTIPLE_TRIGGERS = 2; // 0x2 field public static final int RECOGNITION_FLAG_CAPTURE_TRIGGER_AUDIO = 1; // 0x1 field public static final int RECOGNITION_MODE_USER_IDENTIFICATION = 2; // 0x2 @@ -42644,6 +42648,11 @@ package android.service.voice { method @Nullable public byte[] getTriggerAudio(); } + public static final class AlwaysOnHotwordDetector.ModelParamRange { + method public int end(); + method public int start(); + } + public class VoiceInteractionService extends android.app.Service { ctor public VoiceInteractionService(); method public final android.service.voice.AlwaysOnHotwordDetector createAlwaysOnHotwordDetector(String, java.util.Locale, android.service.voice.AlwaysOnHotwordDetector.Callback); diff --git a/api/system-current.txt b/api/system-current.txt index 1146522dafeac..33a86214e5a1d 100755 --- a/api/system-current.txt +++ b/api/system-current.txt @@ -3535,7 +3535,6 @@ package android.hardware.soundtrigger { } public static final class SoundTrigger.ModelParamRange implements android.os.Parcelable { - method public int describeContents(); method public void writeToParcel(@NonNull android.os.Parcel, int); field @NonNull public static final android.os.Parcelable.Creator CREATOR; field public final int end; @@ -4349,9 +4348,9 @@ package android.media.soundtrigger { method public int getDetectionServiceOperationsTimeout(); method @Nullable @RequiresPermission(android.Manifest.permission.MANAGE_SOUND_TRIGGER) public android.media.soundtrigger.SoundTriggerManager.Model getModel(java.util.UUID); method @Nullable @RequiresPermission(android.Manifest.permission.MANAGE_SOUND_TRIGGER) public android.hardware.soundtrigger.SoundTrigger.ModuleProperties getModuleProperties(); - method @RequiresPermission(android.Manifest.permission.MANAGE_SOUND_TRIGGER) public int getParameter(@NonNull java.util.UUID, int) throws java.lang.IllegalArgumentException, java.lang.UnsupportedOperationException; + method @RequiresPermission(android.Manifest.permission.MANAGE_SOUND_TRIGGER) public int getParameter(@NonNull java.util.UUID, int); method @Nullable @RequiresPermission(android.Manifest.permission.MANAGE_SOUND_TRIGGER) public android.hardware.soundtrigger.SoundTrigger.ModelParamRange queryParameter(@Nullable java.util.UUID, int); - method @RequiresPermission(android.Manifest.permission.MANAGE_SOUND_TRIGGER) public int setParameter(@Nullable java.util.UUID, int, int) throws java.lang.IllegalArgumentException, java.lang.UnsupportedOperationException; + method @RequiresPermission(android.Manifest.permission.MANAGE_SOUND_TRIGGER) public int setParameter(@Nullable java.util.UUID, int, int); method @RequiresPermission(android.Manifest.permission.MANAGE_SOUND_TRIGGER) public void updateModel(android.media.soundtrigger.SoundTriggerManager.Model); } diff --git a/core/java/android/hardware/soundtrigger/ConversionUtil.java b/core/java/android/hardware/soundtrigger/ConversionUtil.java index 8231c58a105e6..d2721018ef52c 100644 --- a/core/java/android/hardware/soundtrigger/ConversionUtil.java +++ b/core/java/android/hardware/soundtrigger/ConversionUtil.java @@ -16,6 +16,7 @@ package android.hardware.soundtrigger; +import android.annotation.Nullable; import android.hardware.soundtrigger.ModelParams; import android.media.AudioFormat; import android.media.audio.common.AudioConfig; @@ -32,8 +33,6 @@ import android.media.soundtrigger_middleware.SoundModel; import android.media.soundtrigger_middleware.SoundTriggerModuleDescriptor; import android.media.soundtrigger_middleware.SoundTriggerModuleProperties; -import android.annotation.Nullable; - import java.util.Arrays; import java.util.UUID; diff --git a/core/java/android/hardware/soundtrigger/SoundTrigger.java b/core/java/android/hardware/soundtrigger/SoundTrigger.java index 55505ba2e2781..2f0752b7c209f 100644 --- a/core/java/android/hardware/soundtrigger/SoundTrigger.java +++ b/core/java/android/hardware/soundtrigger/SoundTrigger.java @@ -588,19 +588,19 @@ public class SoundTrigger { } } - /***************************************************************************** + /** * A ModelParamRange is a representation of supported parameter range for a * given loaded model. - ****************************************************************************/ + */ public static final class ModelParamRange implements Parcelable { /** - * start of supported range inclusive + * The inclusive start of supported range. */ public final int start; /** - * end of supported range inclusive + * The inclusive end of supported range. */ public final int end; @@ -609,31 +609,65 @@ public class SoundTrigger { this.end = end; } + /** @hide */ private ModelParamRange(@NonNull Parcel in) { this.start = in.readInt(); this.end = in.readInt(); } @NonNull - public static final Creator CREATOR = new Creator() { - @Override - @NonNull - public ModelParamRange createFromParcel(@NonNull Parcel in) { - return new ModelParamRange(in); - } + public static final Creator CREATOR = + new Creator() { + @Override + @NonNull + public ModelParamRange createFromParcel(@NonNull Parcel in) { + return new ModelParamRange(in); + } - @Override - @NonNull - public ModelParamRange[] newArray(int size) { - return new ModelParamRange[size]; - } - }; + @Override + @NonNull + public ModelParamRange[] newArray(int size) { + return new ModelParamRange[size]; + } + }; + /** @hide */ @Override public int describeContents() { return 0; } + /** @hide */ + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + (start); + result = prime * result + (end); + return result; + } + + @Override + public boolean equals(@Nullable Object obj) { + if (this == obj) { + return true; + } + if (obj == null) { + return false; + } + if (getClass() != obj.getClass()) { + return false; + } + ModelParamRange other = (ModelParamRange) obj; + if (start != other.start) { + return false; + } + if (end != other.end) { + return false; + } + return true; + } + @Override public void writeToParcel(@NonNull Parcel dest, int flags) { dest.writeInt(start); diff --git a/core/java/android/service/voice/AlwaysOnHotwordDetector.java b/core/java/android/service/voice/AlwaysOnHotwordDetector.java index 67925bfea2c23..d7c6d0f265c68 100644 --- a/core/java/android/service/voice/AlwaysOnHotwordDetector.java +++ b/core/java/android/service/voice/AlwaysOnHotwordDetector.java @@ -168,6 +168,22 @@ public class AlwaysOnHotwordDetector { public static final int RECOGNITION_MODE_USER_IDENTIFICATION = SoundTrigger.RECOGNITION_MODE_USER_IDENTIFICATION; + /** @hide */ + @Retention(RetentionPolicy.SOURCE) + @IntDef(flag = true, prefix = { "MODEL_PARAM_" }, value = { + MODEL_PARAM_THRESHOLD_FACTOR, + }) + public @interface ModelParams {} + + /** + * Controls the sensitivity threshold adjustment factor for a given model. + * Negative value corresponds to less sensitive model (high threshold) and + * a positive value corresponds to a more sensitive model (low threshold). + * Default value is 0. + */ + public static final int MODEL_PARAM_THRESHOLD_FACTOR = + android.hardware.soundtrigger.ModelParams.THRESHOLD_FACTOR; + static final String TAG = "AlwaysOnHotwordDetector"; static final boolean DBG = false; @@ -197,6 +213,53 @@ public class AlwaysOnHotwordDetector { private int mAvailability = STATE_NOT_READY; + /** + * A ModelParamRange is a representation of supported parameter range for a + * given loaded model. + */ + public static final class ModelParamRange { + private final SoundTrigger.ModelParamRange mModelParamRange; + + /** @hide */ + ModelParamRange(SoundTrigger.ModelParamRange modelParamRange) { + mModelParamRange = modelParamRange; + } + + /** + * The inclusive start of supported range. + * + * @return start of range + */ + public int start() { + return mModelParamRange.start; + } + + /** + * The inclusive end of supported range. + * + * @return end of range + */ + public int end() { + return mModelParamRange.end; + } + + @Override + @NonNull + public String toString() { + return mModelParamRange.toString(); + } + + @Override + public boolean equals(@Nullable Object obj) { + return mModelParamRange.equals(obj); + } + + @Override + public int hashCode() { + return mModelParamRange.hashCode(); + } + } + /** * Additional payload for {@link Callback#onDetected}. */ @@ -444,6 +507,83 @@ public class AlwaysOnHotwordDetector { } } + /** + * Set a model specific {@link 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. + * {@link AlwaysOnHotwordDetector#queryParameter} should be checked first before calling this + * method. + * + * @param modelParam {@link 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 + */ + public int setParameter(@ModelParams int modelParam, int value) { + if (DBG) { + Slog.d(TAG, "setParameter(" + modelParam + ", " + value + ")"); + } + + synchronized (mLock) { + if (mAvailability == STATE_INVALID) { + throw new IllegalStateException("setParameter called on an invalid detector"); + } + + return setParameterLocked(modelParam, value); + } + } + + /** + * Get a model specific {@link 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 {@link ModelParams} for parameter default values. + * {@link AlwaysOnHotwordDetector#queryParameter} should be checked first before + * calling this method. + * + * @param modelParam {@link ModelParams} + * @return value of parameter + */ + public int getParameter(@ModelParams int modelParam) { + if (DBG) { + Slog.d(TAG, "getParameter(" + modelParam + ")"); + } + + synchronized (mLock) { + if (mAvailability == STATE_INVALID) { + throw new IllegalStateException("getParameter called on an invalid detector"); + } + + return getParameterLocked(modelParam); + } + } + + /** + * Determine if parameter control is supported for the given model handle. + * This method should be checked prior to calling {@link AlwaysOnHotwordDetector#setParameter} + * or {@link AlwaysOnHotwordDetector#getParameter}. + * + * @param modelParam {@link ModelParams} + * @return supported range of parameter, null if not supported + */ + @Nullable + public ModelParamRange queryParameter(@ModelParams int modelParam) { + if (DBG) { + Slog.d(TAG, "queryParameter(" + modelParam + ")"); + } + + synchronized (mLock) { + if (mAvailability == STATE_INVALID) { + throw new IllegalStateException("queryParameter called on an invalid detector"); + } + + return queryParameterLocked(modelParam); + } + } + /** * Creates an intent to start the enrollment for the associated keyphrase. * This intent must be invoked using {@link Context#startForegroundService(Intent)}. @@ -601,6 +741,47 @@ public class AlwaysOnHotwordDetector { return code; } + private int setParameterLocked(@ModelParams int modelParam, int value) { + try { + int code = mModelManagementService.setParameter(mVoiceInteractionService, + mKeyphraseMetadata.id, modelParam, value); + + if (code != STATUS_OK) { + Slog.w(TAG, "setParameter failed with error code " + code); + } + + return code; + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + private int getParameterLocked(@ModelParams int modelParam) { + try { + return mModelManagementService.getParameter(mVoiceInteractionService, + mKeyphraseMetadata.id, modelParam); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + @Nullable + private ModelParamRange queryParameterLocked(@ModelParams int modelParam) { + try { + SoundTrigger.ModelParamRange modelParamRange = + mModelManagementService.queryParameter(mVoiceInteractionService, + mKeyphraseMetadata.id, modelParam); + + if (modelParamRange == null) { + return null; + } + + return new ModelParamRange(modelParamRange); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + private void notifyStateChangedLocked() { Message message = Message.obtain(mHandler, MSG_AVAILABILITY_CHANGED); message.arg1 = mAvailability; diff --git a/core/java/com/android/internal/app/ISoundTriggerService.aidl b/core/java/com/android/internal/app/ISoundTriggerService.aidl index d94294f0aa22d..74bfb963c6b04 100644 --- a/core/java/com/android/internal/app/ISoundTriggerService.aidl +++ b/core/java/com/android/internal/app/ISoundTriggerService.aidl @@ -61,10 +61,6 @@ interface ISoundTriggerService { int setParameter(in ParcelUuid soundModelId, in ModelParams modelParam, int value); - /** - * @throws UnsupportedOperationException if hal or model do not support this API. - * @throws IllegalArgumentException if invalid model handle or parameter is passed. - */ int getParameter(in ParcelUuid soundModelId, in ModelParams modelParam); @nullable SoundTrigger.ModelParamRange queryParameter(in ParcelUuid soundModelId, diff --git a/core/java/com/android/internal/app/IVoiceInteractionManagerService.aidl b/core/java/com/android/internal/app/IVoiceInteractionManagerService.aidl index f462f5d2571da..be2d1d60e9a2c 100644 --- a/core/java/com/android/internal/app/IVoiceInteractionManagerService.aidl +++ b/core/java/com/android/internal/app/IVoiceInteractionManagerService.aidl @@ -26,6 +26,7 @@ import com.android.internal.app.IVoiceInteractionSessionShowCallback; import com.android.internal.app.IVoiceInteractor; import com.android.internal.app.IVoiceInteractionSessionListener; import android.hardware.soundtrigger.IRecognitionStatusCallback; +import android.hardware.soundtrigger.ModelParams; import android.hardware.soundtrigger.SoundTrigger; import android.service.voice.IVoiceInteractionService; import android.service.voice.IVoiceInteractionSession; @@ -91,6 +92,49 @@ interface IVoiceInteractionManagerService { */ int stopRecognition(in IVoiceInteractionService service, 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. + * + * @param service The current VoiceInteractionService. + * @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(in IVoiceInteractionService service, 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. + * + * @param service The current VoiceInteractionService. + * @param keyphraseId The unique identifier for the keyphrase. + * @param modelParam ModelParams + * @return value of parameter + */ + int getParameter(in IVoiceInteractionService service, 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. + * + * @param service The current VoiceInteractionService. + * @param keyphraseId The unique identifier for the keyphrase. + * @param modelParam ModelParams + * @return supported range of parameter, null if not supported + */ + @nullable SoundTrigger.ModelParamRange queryParameter(in IVoiceInteractionService service, + int keyphraseId, in ModelParams modelParam); /** * @return the component name for the currently active voice interaction service diff --git a/media/java/android/media/soundtrigger/SoundTriggerManager.java b/media/java/android/media/soundtrigger/SoundTriggerManager.java index 61b3e76e7cee2..30a44ff324017 100644 --- a/media/java/android/media/soundtrigger/SoundTriggerManager.java +++ b/media/java/android/media/soundtrigger/SoundTriggerManager.java @@ -428,8 +428,7 @@ public final class SoundTriggerManager { */ @RequiresPermission(android.Manifest.permission.MANAGE_SOUND_TRIGGER) public int setParameter(@Nullable UUID soundModelId, - @ModelParams int modelParam, int value) - throws UnsupportedOperationException, IllegalArgumentException { + @ModelParams int modelParam, int value) { try { return mSoundTriggerService.setParameter(new ParcelUuid(soundModelId), modelParam, value); @@ -449,15 +448,10 @@ public final class SoundTriggerManager { * @param soundModelId UUID of model to get parameter * @param modelParam {@link ModelParams} * @return value of parameter - * @throws UnsupportedOperationException if hal or model do not support this API. - * {@link SoundTriggerManager#queryParameter} should be checked first. - * @throws IllegalArgumentException if invalid model handle or parameter is passed. - * {@link SoundTriggerManager#queryParameter} should be checked first. */ @RequiresPermission(android.Manifest.permission.MANAGE_SOUND_TRIGGER) public int getParameter(@NonNull UUID soundModelId, - @ModelParams int modelParam) - throws UnsupportedOperationException, IllegalArgumentException { + @ModelParams int modelParam) { try { return mSoundTriggerService.getParameter(new ParcelUuid(soundModelId), modelParam); } catch (RemoteException e) { @@ -479,8 +473,7 @@ public final class SoundTriggerManager { public ModelParamRange queryParameter(@Nullable UUID soundModelId, @ModelParams int modelParam) { try { - return mSoundTriggerService.queryParameter(new ParcelUuid(soundModelId), - modelParam); + return mSoundTriggerService.queryParameter(new ParcelUuid(soundModelId), modelParam); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } diff --git a/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerHelper.java b/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerHelper.java index 198b4c31249a7..bde2cfd52c0f0 100644 --- a/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerHelper.java +++ b/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerHelper.java @@ -611,65 +611,91 @@ public class SoundTriggerHelper implements SoundTrigger.StatusListener { int setParameter(UUID modelId, @ModelParams int modelParam, int value) { synchronized (mLock) { - MetricsLogger.count(mContext, "sth_set_parameter", 1); - if (modelId == null || mModule == null) { - return SoundTrigger.STATUS_ERROR; - } - ModelData modelData = mModelDataMap.get(modelId); - if (modelData == null) { - Slog.w(TAG, "SetParameter: Invalid model id:" + modelId); - return SoundTrigger.STATUS_BAD_VALUE; - } - if (!modelData.isModelLoaded()) { - Slog.i(TAG, "SetParameter: Given model is not loaded:" + modelId); - return SoundTrigger.STATUS_BAD_VALUE; - } - - return mModule.setParameter(modelData.getHandle(), modelParam, value); + return setParameterLocked(mModelDataMap.get(modelId), modelParam, value); } } - int getParameter(@NonNull UUID modelId, @ModelParams int modelParam) - throws UnsupportedOperationException, IllegalArgumentException { + int setKeyphraseParameter(int keyphraseId, @ModelParams int modelParam, int value) { synchronized (mLock) { - MetricsLogger.count(mContext, "sth_get_parameter", 1); - if (mModule == null) { - throw new UnsupportedOperationException("SoundTriggerModule not initialized"); - } - - ModelData modelData = mModelDataMap.get(modelId); - if (modelData == null) { - throw new IllegalArgumentException("Invalid model id:" + modelId); - } - if (!modelData.isModelLoaded()) { - throw new UnsupportedOperationException("Given model is not loaded:" + modelId); - } - - return mModule.getParameter(modelData.getHandle(), modelParam); + return setParameterLocked(getKeyphraseModelDataLocked(keyphraseId), modelParam, value); } } + private int setParameterLocked(@Nullable ModelData modelData, @ModelParams int modelParam, + int value) { + MetricsLogger.count(mContext, "sth_set_parameter", 1); + if (mModule == null) { + return SoundTrigger.STATUS_NO_INIT; + } + if (modelData == null || !modelData.isModelLoaded()) { + Slog.i(TAG, "SetParameter: Given model is not loaded:" + modelData); + return SoundTrigger.STATUS_BAD_VALUE; + } + + return mModule.setParameter(modelData.getHandle(), modelParam, value); + } + + int getParameter(@NonNull UUID modelId, @ModelParams int modelParam) { + synchronized (mLock) { + return getParameterLocked(mModelDataMap.get(modelId), modelParam); + } + } + + int getKeyphraseParameter(int keyphraseId, @ModelParams int modelParam) { + synchronized (mLock) { + return getParameterLocked(getKeyphraseModelDataLocked(keyphraseId), modelParam); + } + } + + private int getParameterLocked(@Nullable ModelData modelData, @ModelParams int modelParam) { + MetricsLogger.count(mContext, "sth_get_parameter", 1); + if (mModule == null) { + throw new UnsupportedOperationException("SoundTriggerModule not initialized"); + } + + if (modelData == null) { + throw new IllegalArgumentException("Invalid model id"); + } + if (!modelData.isModelLoaded()) { + throw new UnsupportedOperationException("Given model is not loaded:" + modelData); + } + + return mModule.getParameter(modelData.getHandle(), modelParam); + } + @Nullable ModelParamRange queryParameter(@NonNull UUID modelId, @ModelParams int modelParam) { synchronized (mLock) { - MetricsLogger.count(mContext, "sth_query_parameter", 1); - if (mModule == null) { - return null; - } - ModelData modelData = mModelDataMap.get(modelId); - if (modelData == null) { - Slog.w(TAG, "queryParameter: Invalid model id:" + modelId); - return null; - } - if (!modelData.isModelLoaded()) { - Slog.i(TAG, "queryParameter: Given model is not loaded:" + modelId); - return null; - } - - return mModule.queryParameter(modelData.getHandle(), modelParam); + return queryParameterLocked(mModelDataMap.get(modelId), modelParam); } } + @Nullable + ModelParamRange queryKeyphraseParameter(int keyphraseId, @ModelParams int modelParam) { + synchronized (mLock) { + return queryParameterLocked(getKeyphraseModelDataLocked(keyphraseId), modelParam); + } + } + + @Nullable + private ModelParamRange queryParameterLocked(@Nullable ModelData modelData, + @ModelParams int modelParam) { + MetricsLogger.count(mContext, "sth_query_parameter", 1); + if (mModule == null) { + return null; + } + if (modelData == null) { + Slog.w(TAG, "queryParameter: Invalid model id"); + return null; + } + if (!modelData.isModelLoaded()) { + Slog.i(TAG, "queryParameter: Given model is not loaded:" + modelData); + return null; + } + + return mModule.queryParameter(modelData.getHandle(), modelParam); + } + //---- SoundTrigger.StatusListener methods @Override public void onRecognition(RecognitionEvent event) { diff --git a/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerInternal.java b/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerInternal.java index d05e044499ab1..54dffdc4c13ab 100644 --- a/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerInternal.java +++ b/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerInternal.java @@ -16,17 +16,17 @@ package com.android.server.soundtrigger; +import android.annotation.Nullable; import android.hardware.soundtrigger.IRecognitionStatusCallback; +import android.hardware.soundtrigger.ModelParams; import android.hardware.soundtrigger.SoundTrigger; import android.hardware.soundtrigger.SoundTrigger.Keyphrase; -import android.hardware.soundtrigger.SoundTrigger.KeyphraseRecognitionEvent; -import android.hardware.soundtrigger.SoundTrigger.KeyphraseRecognitionExtra; import android.hardware.soundtrigger.SoundTrigger.KeyphraseSoundModel; +import android.hardware.soundtrigger.SoundTrigger.ModelParamRange; import android.hardware.soundtrigger.SoundTrigger.ModuleProperties; import android.hardware.soundtrigger.SoundTrigger.RecognitionConfig; -import android.hardware.soundtrigger.SoundTrigger.RecognitionEvent; -import android.hardware.soundtrigger.SoundTrigger.SoundModelEvent; -import android.hardware.soundtrigger.SoundTriggerModule; + +import com.android.server.voiceinteraction.VoiceInteractionManagerService; import java.io.FileDescriptor; import java.io.PrintWriter; @@ -70,6 +70,58 @@ public abstract class SoundTriggerInternal { public abstract ModuleProperties getModuleProperties(); + /** + * Set a model specific {@link 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. + * {@link SoundTriggerInternal#queryParameter} should be checked first before calling this + * method. + * + * @param keyphraseId The identifier of the keyphrase for which + * to modify model parameters + * @param modelParam {@link 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 + */ + public abstract int setParameter(int keyphraseId, @ModelParams int modelParam, int value); + + /** + * Get a model specific {@link 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. + * {@link SoundTriggerInternal#queryParameter} should be checked first before calling this + * method. + * + * @param keyphraseId The identifier of the keyphrase for which + * to modify model parameters + * @param modelParam {@link ModelParams} + * @return value of parameter + * @throws UnsupportedOperationException if hal or model do not support this API. + * queryParameter should be checked first. + * @throws IllegalArgumentException if invalid model handle or parameter is passed. + * queryParameter should be checked first. + */ + public abstract int getParameter(int keyphraseId, @ModelParams int modelParam); + + /** + * Determine if parameter control is supported for the given model handle. + * This method should be checked prior to calling {@link SoundTriggerInternal#setParameter} + * or {@link SoundTriggerInternal#getParameter}. + * + * @param keyphraseId The identifier of the keyphrase for which + * to modify model parameters + * @param modelParam {@link ModelParams} + * @return supported range of parameter, null if not supported + */ + @Nullable + public abstract ModelParamRange queryParameter(int keyphraseId, + @ModelParams int modelParam); + /** * Unloads (and stops if running) the given keyphraseId */ diff --git a/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerService.java b/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerService.java index 68b16f39e149c..e37755bddcaa9 100644 --- a/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerService.java +++ b/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerService.java @@ -1446,26 +1446,45 @@ public class SoundTriggerService extends SystemService { @Override public int startRecognition(int keyphraseId, KeyphraseSoundModel soundModel, IRecognitionStatusCallback listener, RecognitionConfig recognitionConfig) { - if (!isInitialized()) return STATUS_ERROR; + if (!isInitialized()) throw new UnsupportedOperationException(); return mSoundTriggerHelper.startKeyphraseRecognition(keyphraseId, soundModel, listener, recognitionConfig); } @Override public synchronized int stopRecognition(int keyphraseId, IRecognitionStatusCallback listener) { - if (!isInitialized()) return STATUS_ERROR; + if (!isInitialized()) throw new UnsupportedOperationException(); return mSoundTriggerHelper.stopKeyphraseRecognition(keyphraseId, listener); } @Override public ModuleProperties getModuleProperties() { - if (!isInitialized()) return null; + if (!isInitialized()) throw new UnsupportedOperationException(); return mSoundTriggerHelper.getModuleProperties(); } + @Override + public int setParameter(int keyphraseId, @ModelParams int modelParam, int value) { + if (!isInitialized()) throw new UnsupportedOperationException(); + return mSoundTriggerHelper.setKeyphraseParameter(keyphraseId, modelParam, value); + } + + @Override + public int getParameter(int keyphraseId, @ModelParams int modelParam) { + if (!isInitialized()) throw new UnsupportedOperationException(); + return mSoundTriggerHelper.getKeyphraseParameter(keyphraseId, modelParam); + } + + @Override + @Nullable + public ModelParamRange queryParameter(int keyphraseId, @ModelParams int modelParam) { + if (!isInitialized()) throw new UnsupportedOperationException(); + return mSoundTriggerHelper.queryKeyphraseParameter(keyphraseId, modelParam); + } + @Override public int unloadKeyphraseModel(int keyphraseId) { - if (!isInitialized()) return STATUS_ERROR; + if (!isInitialized()) throw new UnsupportedOperationException(); return mSoundTriggerHelper.unloadKeyphraseSoundModel(keyphraseId); } diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java index e9db31ba3e25c..06c807421d1ab 100644 --- a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java +++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java @@ -41,7 +41,9 @@ import android.content.pm.UserInfo; import android.content.res.Resources; import android.database.ContentObserver; import android.hardware.soundtrigger.IRecognitionStatusCallback; +import android.hardware.soundtrigger.ModelParams; import android.hardware.soundtrigger.SoundTrigger.KeyphraseSoundModel; +import android.hardware.soundtrigger.SoundTrigger.ModelParamRange; import android.hardware.soundtrigger.SoundTrigger.ModuleProperties; import android.hardware.soundtrigger.SoundTrigger.RecognitionConfig; import android.os.Binder; @@ -1084,6 +1086,55 @@ public class VoiceInteractionManagerService extends SystemService { } } + @Override + public int setParameter(IVoiceInteractionService service, int keyphraseId, + @ModelParams int modelParam, int value) { + // Allow the call if this is the current voice interaction service. + synchronized (this) { + enforceIsCurrentVoiceInteractionService(service); + } + + final long caller = Binder.clearCallingIdentity(); + try { + return mSoundTriggerInternal.setParameter(keyphraseId, modelParam, value); + } finally { + Binder.restoreCallingIdentity(caller); + } + } + + @Override + public int getParameter(IVoiceInteractionService service, int keyphraseId, + @ModelParams int modelParam) { + // Allow the call if this is the current voice interaction service. + synchronized (this) { + enforceIsCurrentVoiceInteractionService(service); + } + + final long caller = Binder.clearCallingIdentity(); + try { + return mSoundTriggerInternal.getParameter(keyphraseId, modelParam); + } finally { + Binder.restoreCallingIdentity(caller); + } + } + + @Override + @Nullable + public ModelParamRange queryParameter(IVoiceInteractionService service, + int keyphraseId, @ModelParams int modelParam) { + // Allow the call if this is the current voice interaction service. + synchronized (this) { + enforceIsCurrentVoiceInteractionService(service); + } + + final long caller = Binder.clearCallingIdentity(); + try { + return mSoundTriggerInternal.queryParameter(keyphraseId, modelParam); + } finally { + Binder.restoreCallingIdentity(caller); + } + } + private synchronized void unloadAllKeyphraseModels() { for (int i = 0; i < mLoadedKeyphraseIds.size(); i++) { final long caller = Binder.clearCallingIdentity();