diff options
7 files changed, 102 insertions, 49 deletions
diff --git a/api/current.txt b/api/current.txt index fc39cba..a592b0e 100644 --- a/api/current.txt +++ b/api/current.txt @@ -27464,7 +27464,7 @@ package android.service.voice { public class VoiceInteractionService extends android.app.Service { ctor public VoiceInteractionService(); - method public final android.service.voice.AlwaysOnHotwordDetector createAlwaysOnHotwordDetector(java.lang.String, java.lang.String, android.service.voice.AlwaysOnHotwordDetector.Callback); + method public final android.service.voice.AlwaysOnHotwordDetector createAlwaysOnHotwordDetector(java.lang.String, java.util.Locale, android.service.voice.AlwaysOnHotwordDetector.Callback); method public static boolean isActiveService(android.content.Context, android.content.ComponentName); method public android.os.IBinder onBind(android.content.Intent); method public void onReady(); diff --git a/core/java/android/hardware/soundtrigger/KeyphraseEnrollmentInfo.java b/core/java/android/hardware/soundtrigger/KeyphraseEnrollmentInfo.java index 0dbde6b..2d5a271 100644 --- a/core/java/android/hardware/soundtrigger/KeyphraseEnrollmentInfo.java +++ b/core/java/android/hardware/soundtrigger/KeyphraseEnrollmentInfo.java @@ -25,6 +25,8 @@ import android.content.res.Resources; import android.content.res.TypedArray; import android.content.res.XmlResourceParser; import android.service.voice.AlwaysOnHotwordDetector; +import android.text.TextUtils; +import android.util.ArraySet; import android.util.AttributeSet; import android.util.Slog; import android.util.Xml; @@ -35,6 +37,7 @@ import org.xmlpull.v1.XmlPullParserException; import java.io.IOException; import java.util.Arrays; import java.util.List; +import java.util.Locale; /** * Enrollment information about the different available keyphrases. @@ -151,33 +154,8 @@ public class KeyphraseEnrollmentInfo { TypedArray array = res.obtainAttributes(attrs, com.android.internal.R.styleable.VoiceEnrollmentApplication); - int searchKeyphraseId = array.getInt( - com.android.internal.R.styleable.VoiceEnrollmentApplication_searchKeyphraseId, - -1); - if (searchKeyphraseId != -1) { - String searchKeyphrase = array.getString(com.android.internal.R.styleable - .VoiceEnrollmentApplication_searchKeyphrase); - if (searchKeyphrase == null) { - searchKeyphrase = ""; - } - String searchKeyphraseSupportedLocales = - array.getString(com.android.internal.R.styleable - .VoiceEnrollmentApplication_searchKeyphraseSupportedLocales); - String[] supportedLocales = new String[0]; - // Get all the supported locales from the comma-delimted string. - if (searchKeyphraseSupportedLocales != null - && !searchKeyphraseSupportedLocales.isEmpty()) { - supportedLocales = searchKeyphraseSupportedLocales.split(","); - } - int recognitionModes = array.getInt(com.android.internal.R.styleable - .VoiceEnrollmentApplication_searchKeyphraseRecognitionFlags, 0); - mKeyphrases = new KeyphraseMetadata[1]; - mKeyphrases[0] = new KeyphraseMetadata( - searchKeyphraseId, searchKeyphrase, supportedLocales, recognitionModes); - } else { - mParseError = "searchKeyphraseId not specified in meta-data"; - return; - } + initializeKeyphrasesFromTypedArray(array); + array.recycle(); } catch (XmlPullParserException e) { mParseError = "Error parsing keyphrase enrollment meta-data: " + e; Slog.w(TAG, "error parsing keyphrase enrollment meta-data", e); @@ -195,6 +173,65 @@ public class KeyphraseEnrollmentInfo { } } + private void initializeKeyphrasesFromTypedArray(TypedArray array) { + // Get the keyphrase ID. + int searchKeyphraseId = array.getInt( + com.android.internal.R.styleable.VoiceEnrollmentApplication_searchKeyphraseId, -1); + if (searchKeyphraseId <= 0) { + mParseError = "No valid searchKeyphraseId specified in meta-data"; + Slog.w(TAG, mParseError); + return; + } + + // Get the keyphrase text. + String searchKeyphrase = array.getString( + com.android.internal.R.styleable.VoiceEnrollmentApplication_searchKeyphrase); + if (searchKeyphrase == null) { + mParseError = "No valid searchKeyphrase specified in meta-data"; + Slog.w(TAG, mParseError); + return; + } + + // Get the supported locales. + String searchKeyphraseSupportedLocales = array.getString( + com.android.internal.R.styleable + .VoiceEnrollmentApplication_searchKeyphraseSupportedLocales); + if (searchKeyphraseSupportedLocales == null) { + mParseError = "No valid searchKeyphraseSupportedLocales specified in meta-data"; + Slog.w(TAG, mParseError); + return; + } + ArraySet<Locale> locales = new ArraySet<>(); + // Try adding locales if the locale string is non-empty. + if (!TextUtils.isEmpty(searchKeyphraseSupportedLocales)) { + try { + String[] supportedLocalesDelimited = searchKeyphraseSupportedLocales.split(","); + for (int i = 0; i < supportedLocalesDelimited.length; i++) { + locales.add(Locale.forLanguageTag(supportedLocalesDelimited[i])); + } + } catch (Exception ex) { + // We catch a generic exception here because we don't want the system service + // to be affected by a malformed metadata because invalid locales were specified + // by the system application. + mParseError = "Error reading searchKeyphraseSupportedLocales from meta-data"; + Slog.w(TAG, mParseError, ex); + return; + } + } + + // Get the supported recognition modes. + int recognitionModes = array.getInt(com.android.internal.R.styleable + .VoiceEnrollmentApplication_searchKeyphraseRecognitionFlags, -1); + if (recognitionModes < 0) { + mParseError = "No valid searchKeyphraseRecognitionFlags specified in meta-data"; + Slog.w(TAG, mParseError); + return; + } + mKeyphrases = new KeyphraseMetadata[1]; + mKeyphrases[0] = new KeyphraseMetadata(searchKeyphraseId, searchKeyphrase, locales, + recognitionModes); + } + public String getParseError() { return mParseError; } @@ -217,11 +254,10 @@ public class KeyphraseEnrollmentInfo { * or {@link AlwaysOnHotwordDetector#MANAGE_ACTION_UN_ENROLL} * @param keyphrase The keyphrase that the user needs to be enrolled to. * @param locale The locale for which the enrollment needs to be performed. - * This is a Java locale, for example "en_US". * @return An {@link Intent} to manage the keyphrase. This can be null if managing the * given keyphrase/locale combination isn't possible. */ - public Intent getManageKeyphraseIntent(int action, String keyphrase, String locale) { + public Intent getManageKeyphraseIntent(int action, String keyphrase, Locale locale) { if (mEnrollmentPackage == null || mEnrollmentPackage.isEmpty()) { Slog.w(TAG, "No enrollment application exists"); return null; @@ -248,7 +284,7 @@ public class KeyphraseEnrollmentInfo { * @return The metadata, if the enrollment client supports the given keyphrase * and locale, null otherwise. */ - public KeyphraseMetadata getKeyphraseMetadata(String keyphrase, String locale) { + public KeyphraseMetadata getKeyphraseMetadata(String keyphrase, Locale locale) { if (mKeyphrases == null || mKeyphrases.length == 0) { Slog.w(TAG, "Enrollment application doesn't support keyphrases"); return null; diff --git a/core/java/android/hardware/soundtrigger/KeyphraseMetadata.java b/core/java/android/hardware/soundtrigger/KeyphraseMetadata.java index 38305f9..ed8c296 100644 --- a/core/java/android/hardware/soundtrigger/KeyphraseMetadata.java +++ b/core/java/android/hardware/soundtrigger/KeyphraseMetadata.java @@ -18,6 +18,8 @@ package android.hardware.soundtrigger; import android.util.ArraySet; +import java.util.Locale; + /** * A Voice Keyphrase metadata read from the enrollment application. * @@ -26,17 +28,14 @@ import android.util.ArraySet; public class KeyphraseMetadata { public final int id; public final String keyphrase; - public final ArraySet<String> supportedLocales; + public final ArraySet<Locale> supportedLocales; public final int recognitionModeFlags; - public KeyphraseMetadata(int id, String keyphrase, String[] supportedLocales, + public KeyphraseMetadata(int id, String keyphrase, ArraySet<Locale> supportedLocales, int recognitionModeFlags) { this.id = id; this.keyphrase = keyphrase; - this.supportedLocales = new ArraySet<String>(supportedLocales.length); - for (String locale : supportedLocales) { - this.supportedLocales.add(locale); - } + this.supportedLocales = supportedLocales; this.recognitionModeFlags = recognitionModeFlags; } @@ -56,7 +55,7 @@ public class KeyphraseMetadata { /** * @return Indicates if we support the given locale. */ - public boolean supportsLocale(String locale) { + public boolean supportsLocale(Locale locale) { return supportedLocales.isEmpty() || supportedLocales.contains(locale); } } diff --git a/core/java/android/service/voice/AlwaysOnHotwordDetector.java b/core/java/android/service/voice/AlwaysOnHotwordDetector.java index 15e66a0..2095773 100644 --- a/core/java/android/service/voice/AlwaysOnHotwordDetector.java +++ b/core/java/android/service/voice/AlwaysOnHotwordDetector.java @@ -39,10 +39,10 @@ import android.util.Slog; import com.android.internal.app.IVoiceInteractionManagerService; -import java.io.FileDescriptor; import java.io.PrintWriter; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; +import java.util.Locale; /** * A class that lets a VoiceInteractionService implementation interact with @@ -167,7 +167,7 @@ public class AlwaysOnHotwordDetector { private static final int MSG_DETECTION_RESUME = 5; private final String mText; - private final String mLocale; + private final Locale mLocale; /** * The metadata of the Keyphrase, derived from the enrollment application. * This may be null if this keyphrase isn't supported by the enrollment application. @@ -317,7 +317,7 @@ public class AlwaysOnHotwordDetector { * * @hide */ - public AlwaysOnHotwordDetector(String text, String locale, Callback callback, + public AlwaysOnHotwordDetector(String text, Locale locale, Callback callback, KeyphraseEnrollmentInfo keyphraseEnrollmentInfo, IVoiceInteractionService voiceInteractionService, IVoiceInteractionManagerService modelManagementService) { @@ -491,8 +491,6 @@ public class AlwaysOnHotwordDetector { */ void onSoundModelsChanged() { synchronized (mLock) { - // FIXME: This should stop the recognition if it was using an enrolled sound model - // that's no longer available. if (mAvailability == STATE_INVALID || mAvailability == STATE_HARDWARE_UNAVAILABLE || mAvailability == STATE_KEYPHRASE_UNSUPPORTED) { @@ -500,6 +498,13 @@ public class AlwaysOnHotwordDetector { return; } + // Stop the recognition before proceeding. + // This is done because we want to stop the recognition on an older model if it changed + // or was deleted. + // The availability change callback should ensure that the client starts recognition + // again if needed. + stopRecognitionLocked(); + // Execute a refresh availability task - which should then notify of a change. new RefreshAvailabiltyTask().execute(); } diff --git a/core/java/android/service/voice/VoiceInteractionService.java b/core/java/android/service/voice/VoiceInteractionService.java index b200356..884fa9f 100644 --- a/core/java/android/service/voice/VoiceInteractionService.java +++ b/core/java/android/service/voice/VoiceInteractionService.java @@ -35,6 +35,7 @@ import com.android.internal.app.IVoiceInteractionManagerService; import java.io.FileDescriptor; import java.io.PrintWriter; +import java.util.Locale; /** @@ -163,7 +164,7 @@ 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 #startSession(Bundle)} and - * {@link #createAlwaysOnHotwordDetector(String, String, android.service.voice.AlwaysOnHotwordDetector.Callback)} + * {@link #createAlwaysOnHotwordDetector(String, Locale, android.service.voice.AlwaysOnHotwordDetector.Callback)} * will not be operational until this point. */ public void onReady() { @@ -200,6 +201,17 @@ public class VoiceInteractionService extends Service { } /** + * FIXME: Remove once the prebuilts are updated. + * + * @hide + */ + @Deprecated + public final AlwaysOnHotwordDetector createAlwaysOnHotwordDetector( + String keyphrase, String locale, AlwaysOnHotwordDetector.Callback callback) { + return createAlwaysOnHotwordDetector(keyphrase, new Locale(locale), callback); + } + + /** * 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 @@ -207,12 +219,11 @@ public class VoiceInteractionService extends Service { * * @param keyphrase The keyphrase that's being used, for example "Hello Android". * @param locale The locale for which the enrollment needs to be performed. - * This is a Java locale, for example "en_US". * @param callback The callback to notify of detection events. * @return An always-on hotword detector for the given keyphrase and locale. */ public final AlwaysOnHotwordDetector createAlwaysOnHotwordDetector( - String keyphrase, String locale, AlwaysOnHotwordDetector.Callback callback) { + String keyphrase, Locale locale, AlwaysOnHotwordDetector.Callback callback) { if (mSystemService == null) { throw new IllegalStateException("Not available until onReady() is called"); } diff --git a/core/res/res/values/attrs.xml b/core/res/res/values/attrs.xml index 3524636..cc8d7cf 100644 --- a/core/res/res/values/attrs.xml +++ b/core/res/res/values/attrs.xml @@ -6930,8 +6930,8 @@ <attr name="searchKeyphraseId" format="integer" /> <!-- The actual keyphrase/hint text, or empty if not keyphrase dependent. @hide @SystemApi --> <attr name="searchKeyphrase" format="string" /> - <!-- A comma separated list of java locales that are supported for this keyphrase, - or empty if not locale dependent. @hide @SystemApi --> + <!-- A comma separated list of BCP-47 language tag for locales that are supported + for this keyphrase, or empty if not locale dependent. @hide @SystemApi --> <attr name="searchKeyphraseSupportedLocales" format="string" /> <!-- Flags for supported recognition modes. @hide @SystemApi --> <attr name="searchKeyphraseRecognitionFlags"> diff --git a/tests/VoiceInteraction/src/com/android/test/voiceinteraction/MainInteractionService.java b/tests/VoiceInteraction/src/com/android/test/voiceinteraction/MainInteractionService.java index 77c0c32..1233c0c 100644 --- a/tests/VoiceInteraction/src/com/android/test/voiceinteraction/MainInteractionService.java +++ b/tests/VoiceInteraction/src/com/android/test/voiceinteraction/MainInteractionService.java @@ -25,6 +25,7 @@ import android.service.voice.VoiceInteractionService; import android.util.Log; import java.util.Arrays; +import java.util.Locale; public class MainInteractionService extends VoiceInteractionService { static final String TAG = "MainInteractionService"; @@ -67,7 +68,8 @@ public class MainInteractionService extends VoiceInteractionService { Log.i(TAG, "Keyphrase enrollment meta-data: " + Arrays.toString(getKeyphraseEnrollmentInfo().listKeyphraseMetadata())); - mHotwordDetector = createAlwaysOnHotwordDetector("Hello There", "en-US", mHotwordCallback); + mHotwordDetector = createAlwaysOnHotwordDetector( + "Hello There", new Locale("en-US"), mHotwordCallback); } @Override |