diff options
author | Nicholas Ambur <nambur@google.com> | 2020-01-14 20:35:04 -0800 |
---|---|---|
committer | Nicholas Ambur <nambur@google.com> | 2020-01-22 16:40:18 -0800 |
commit | f771e6c9f694c772dd261aacbc0c566e587c345f (patch) | |
tree | 49c85da089db57e2b0aa547d1f3326a4fcbbfb5d | |
parent | ef84fc48433d47ea9c91dcb3273ae3d74ca6d32a (diff) |
async enrollment support AlwaysOnHotwordDetector
Added ability for AlwaysOnHotwordDetector to support async enrollment
performed outside of support detected through KeyphraseEnrollmentInfo.
Bug: 147159435
Test: tested enrollment and availability is updated when enrolling
outside of KeyphraseEnrollmentInfo
Change-Id: Ia5d71e90c062ac100d4c6df760acf0d41920853e
7 files changed, 348 insertions, 120 deletions
diff --git a/api/current.txt b/api/current.txt index ab1918fcb3ad..e8a766c67b5f 100644 --- a/api/current.txt +++ b/api/current.txt @@ -42845,7 +42845,7 @@ package android.service.voice { field public static final int STATE_HARDWARE_UNAVAILABLE = -2; // 0xfffffffe field public static final int STATE_KEYPHRASE_ENROLLED = 2; // 0x2 field public static final int STATE_KEYPHRASE_UNENROLLED = 1; // 0x1 - field public static final int STATE_KEYPHRASE_UNSUPPORTED = -1; // 0xffffffff + field @Deprecated public static final int STATE_KEYPHRASE_UNSUPPORTED = -1; // 0xffffffff } public abstract static class AlwaysOnHotwordDetector.Callback { diff --git a/core/java/android/hardware/soundtrigger/KeyphraseMetadata.aidl b/core/java/android/hardware/soundtrigger/KeyphraseMetadata.aidl new file mode 100644 index 000000000000..7a5e932bb7a0 --- /dev/null +++ b/core/java/android/hardware/soundtrigger/KeyphraseMetadata.aidl @@ -0,0 +1,19 @@ +/** + * Copyright (C) 2014 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.hardware.soundtrigger; + +parcelable KeyphraseMetadata; diff --git a/core/java/android/hardware/soundtrigger/KeyphraseMetadata.java b/core/java/android/hardware/soundtrigger/KeyphraseMetadata.java index ed8c296e572f..15462deea158 100644 --- a/core/java/android/hardware/soundtrigger/KeyphraseMetadata.java +++ b/core/java/android/hardware/soundtrigger/KeyphraseMetadata.java @@ -16,8 +16,13 @@ package android.hardware.soundtrigger; +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.os.Parcelable; import android.util.ArraySet; +import com.android.internal.util.DataClass; + import java.util.Locale; /** @@ -25,37 +30,168 @@ import java.util.Locale; * * @hide */ -public class KeyphraseMetadata { +@DataClass( + genEqualsHashCode = true, + genToString = true, + genConstructor = false, + genHiddenConstDefs = true) +public final class KeyphraseMetadata implements Parcelable { public final int id; + @NonNull public final String keyphrase; + @NonNull public final ArraySet<Locale> supportedLocales; public final int recognitionModeFlags; - public KeyphraseMetadata(int id, String keyphrase, ArraySet<Locale> supportedLocales, - int recognitionModeFlags) { + public KeyphraseMetadata(int id, @NonNull String keyphrase, + @NonNull ArraySet<Locale> supportedLocales, int recognitionModeFlags) { this.id = id; this.keyphrase = keyphrase; this.supportedLocales = supportedLocales; this.recognitionModeFlags = recognitionModeFlags; } - @Override - public String toString() { - return "id=" + id + ", keyphrase=" + keyphrase + ", supported-locales=" + supportedLocales - + ", recognition-modes=" + recognitionModeFlags; - } - /** * @return Indicates if we support the given phrase. */ - public boolean supportsPhrase(String phrase) { + public boolean supportsPhrase(@Nullable String phrase) { return keyphrase.isEmpty() || keyphrase.equalsIgnoreCase(phrase); } /** * @return Indicates if we support the given locale. */ - public boolean supportsLocale(Locale locale) { + public boolean supportsLocale(@Nullable Locale locale) { return supportedLocales.isEmpty() || supportedLocales.contains(locale); } + + + + + // Code below generated by codegen v1.0.14. + // + // DO NOT MODIFY! + // CHECKSTYLE:OFF Generated code + // + // To regenerate run: + // $ codegen $ANDROID_BUILD_TOP/frameworks/base/core/java/android/hardware/soundtrigger/KeyphraseMetadata.java + // + // To exclude the generated code from IntelliJ auto-formatting enable (one-time): + // Settings > Editor > Code Style > Formatter Control + //@formatter:off + + + @Override + @DataClass.Generated.Member + public String toString() { + // You can override field toString logic by defining methods like: + // String fieldNameToString() { ... } + + return "KeyphraseMetadata { " + + "id = " + id + ", " + + "keyphrase = " + keyphrase + ", " + + "supportedLocales = " + supportedLocales + ", " + + "recognitionModeFlags = " + recognitionModeFlags + + " }"; + } + + @Override + @DataClass.Generated.Member + public boolean equals(@Nullable Object o) { + // You can override field equality logic by defining either of the methods like: + // boolean fieldNameEquals(KeyphraseMetadata other) { ... } + // boolean fieldNameEquals(FieldType otherValue) { ... } + + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + @SuppressWarnings("unchecked") + KeyphraseMetadata that = (KeyphraseMetadata) o; + //noinspection PointlessBooleanExpression + return true + && id == that.id + && java.util.Objects.equals(keyphrase, that.keyphrase) + && java.util.Objects.equals(supportedLocales, that.supportedLocales) + && recognitionModeFlags == that.recognitionModeFlags; + } + + @Override + @DataClass.Generated.Member + public int hashCode() { + // You can override field hashCode logic by defining methods like: + // int fieldNameHashCode() { ... } + + int _hash = 1; + _hash = 31 * _hash + id; + _hash = 31 * _hash + java.util.Objects.hashCode(keyphrase); + _hash = 31 * _hash + java.util.Objects.hashCode(supportedLocales); + _hash = 31 * _hash + recognitionModeFlags; + return _hash; + } + + @Override + @DataClass.Generated.Member + public void writeToParcel(@NonNull android.os.Parcel dest, int flags) { + // You can override field parcelling by defining methods like: + // void parcelFieldName(Parcel dest, int flags) { ... } + + dest.writeInt(id); + dest.writeString(keyphrase); + dest.writeArraySet(supportedLocales); + dest.writeInt(recognitionModeFlags); + } + + @Override + @DataClass.Generated.Member + public int describeContents() { return 0; } + + /** @hide */ + @SuppressWarnings({"unchecked", "RedundantCast"}) + @DataClass.Generated.Member + /* package-private */ KeyphraseMetadata(@NonNull android.os.Parcel in) { + // You can override field unparcelling by defining methods like: + // static FieldType unparcelFieldName(Parcel in) { ... } + + int _id = in.readInt(); + String _keyphrase = in.readString(); + ArraySet<Locale> _supportedLocales = (ArraySet) in.readArraySet(null); + int _recognitionModeFlags = in.readInt(); + + this.id = _id; + this.keyphrase = _keyphrase; + com.android.internal.util.AnnotationValidations.validate( + NonNull.class, null, keyphrase); + this.supportedLocales = _supportedLocales; + com.android.internal.util.AnnotationValidations.validate( + NonNull.class, null, supportedLocales); + this.recognitionModeFlags = _recognitionModeFlags; + + // onConstructed(); // You can define this method to get a callback + } + + @DataClass.Generated.Member + public static final @NonNull Parcelable.Creator<KeyphraseMetadata> CREATOR + = new Parcelable.Creator<KeyphraseMetadata>() { + @Override + public KeyphraseMetadata[] newArray(int size) { + return new KeyphraseMetadata[size]; + } + + @Override + public KeyphraseMetadata createFromParcel(@NonNull android.os.Parcel in) { + return new KeyphraseMetadata(in); + } + }; + + @DataClass.Generated( + time = 1579290593964L, + codegenVersion = "1.0.14", + sourceFile = "frameworks/base/core/java/android/hardware/soundtrigger/KeyphraseMetadata.java", + inputSignatures = "public final int id\npublic final @android.annotation.NonNull java.lang.String keyphrase\npublic final @android.annotation.NonNull android.util.ArraySet<java.util.Locale> supportedLocales\npublic final int recognitionModeFlags\npublic boolean supportsPhrase(java.lang.String)\npublic boolean supportsLocale(java.util.Locale)\nclass KeyphraseMetadata extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genEqualsHashCode=true, genToString=true, genConstructor=false, genHiddenConstDefs=true)") + @Deprecated + private void __metadata() {} + + + //@formatter:on + // End of generated code + } diff --git a/core/java/android/service/voice/AlwaysOnHotwordDetector.java b/core/java/android/service/voice/AlwaysOnHotwordDetector.java index d7b81c38f2c1..1966f17aaf35 100644 --- a/core/java/android/service/voice/AlwaysOnHotwordDetector.java +++ b/core/java/android/service/voice/AlwaysOnHotwordDetector.java @@ -29,7 +29,6 @@ import android.hardware.soundtrigger.SoundTrigger; import android.hardware.soundtrigger.SoundTrigger.ConfidenceLevel; import android.hardware.soundtrigger.SoundTrigger.KeyphraseRecognitionEvent; import android.hardware.soundtrigger.SoundTrigger.KeyphraseRecognitionExtra; -import android.hardware.soundtrigger.SoundTrigger.KeyphraseSoundModel; import android.hardware.soundtrigger.SoundTrigger.ModuleProperties; import android.hardware.soundtrigger.SoundTrigger.RecognitionConfig; import android.media.AudioFormat; @@ -67,7 +66,12 @@ public class AlwaysOnHotwordDetector { /** * Indicates that recognition for the given keyphrase is not supported. * No further interaction should be performed with the detector that returns this availability. + * + * @deprecated This is no longer a valid state. Enrollment can occur outside of + * {@link KeyphraseEnrollmentInfo} through another privileged application. We can no longer + * determine ahead of time if the keyphrase and locale are unsupported by the system. */ + @Deprecated public static final int STATE_KEYPHRASE_UNSUPPORTED = -1; /** * Indicates that the given keyphrase is not enrolled. @@ -220,7 +224,8 @@ public class AlwaysOnHotwordDetector { * The metadata of the Keyphrase, derived from the enrollment application. * This may be null if this keyphrase isn't supported by the enrollment application. */ - private final KeyphraseMetadata mKeyphraseMetadata; + @Nullable + private KeyphraseMetadata mKeyphraseMetadata; private final KeyphraseEnrollmentInfo mKeyphraseEnrollmentInfo; private final IVoiceInteractionService mVoiceInteractionService; private final IVoiceInteractionManagerService mModelManagementService; @@ -420,7 +425,6 @@ public class AlwaysOnHotwordDetector { mText = text; mLocale = locale; mKeyphraseEnrollmentInfo = keyphraseEnrollmentInfo; - mKeyphraseMetadata = mKeyphraseEnrollmentInfo.getKeyphraseMetadata(text, locale); mExternalCallback = callback; mHandler = new MyHandler(); mInternalCallback = new SoundTriggerListener(mHandler); @@ -456,8 +460,7 @@ public class AlwaysOnHotwordDetector { } // This method only makes sense if we can actually support a recognition. - if (mAvailability != STATE_KEYPHRASE_ENROLLED - && mAvailability != STATE_KEYPHRASE_UNENROLLED) { + if (mAvailability != STATE_KEYPHRASE_ENROLLED || mKeyphraseMetadata == null) { throw new UnsupportedOperationException( "Getting supported recognition modes for the keyphrase is not supported"); } @@ -733,8 +736,7 @@ public class AlwaysOnHotwordDetector { void onSoundModelsChanged() { synchronized (mLock) { if (mAvailability == STATE_INVALID - || mAvailability == STATE_HARDWARE_UNAVAILABLE - || mAvailability == STATE_KEYPHRASE_UNSUPPORTED) { + || mAvailability == STATE_HARDWARE_UNAVAILABLE) { Slog.w(TAG, "Received onSoundModelsChanged for an unsupported keyphrase/config"); return; } @@ -744,7 +746,9 @@ public class AlwaysOnHotwordDetector { // or was deleted. // The availability change callback should ensure that the client starts recognition // again if needed. - stopRecognitionLocked(); + if (mAvailability == STATE_KEYPHRASE_ENROLLED) { + stopRecognitionLocked(); + } // Execute a refresh availability task - which should then notify of a change. new RefreshAvailabiltyTask().execute(); @@ -927,20 +931,17 @@ public class AlwaysOnHotwordDetector { @Override public Void doInBackground(Void... params) { int availability = internalGetInitialAvailability(); - boolean enrolled = false; - // Fetch the sound model if the availability is one of the supported ones. - if (availability == STATE_NOT_READY - || availability == STATE_KEYPHRASE_UNENROLLED - || availability == STATE_KEYPHRASE_ENROLLED) { - enrolled = internalGetIsEnrolled(mKeyphraseMetadata.id, mLocale); - if (!enrolled) { - availability = STATE_KEYPHRASE_UNENROLLED; - } else { - availability = STATE_KEYPHRASE_ENROLLED; - } - } synchronized (mLock) { + if (availability == STATE_NOT_READY) { + internalUpdateEnrolledKeyphraseMetadata(); + if (mKeyphraseMetadata != null) { + availability = STATE_KEYPHRASE_ENROLLED; + } else { + availability = STATE_KEYPHRASE_UNENROLLED; + } + } + if (DBG) { Slog.d(TAG, "Hotword availability changed from " + mAvailability + " -> " + availability); @@ -969,28 +970,22 @@ public class AlwaysOnHotwordDetector { } catch (RemoteException e) { Slog.w(TAG, "RemoteException in getDspProperties!", e); } + // No DSP available if (dspModuleProperties == null) { return STATE_HARDWARE_UNAVAILABLE; } - // No enrollment application supports this keyphrase/locale - if (mKeyphraseMetadata == null) { - return STATE_KEYPHRASE_UNSUPPORTED; - } + return STATE_NOT_READY; } - /** - * @return The corresponding {@link KeyphraseSoundModel} or null if none is found. - */ - private boolean internalGetIsEnrolled(int keyphraseId, Locale locale) { + private void internalUpdateEnrolledKeyphraseMetadata() { try { - return mModelManagementService.isEnrolledForKeyphrase( - mVoiceInteractionService, keyphraseId, locale.toLanguageTag()); + mKeyphraseMetadata = mModelManagementService.getEnrolledKeyphraseMetadata( + mVoiceInteractionService, mText, mLocale.toLanguageTag()); } catch (RemoteException e) { - Slog.w(TAG, "RemoteException in listRegisteredKeyphraseSoundModels!", e); + Slog.w(TAG, "RemoteException in internalUpdateEnrolledKeyphraseMetadata", e); } - return false; } } diff --git a/core/java/com/android/internal/app/IVoiceInteractionManagerService.aidl b/core/java/com/android/internal/app/IVoiceInteractionManagerService.aidl index be2d1d60e9a2..f3b6d292623d 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.KeyphraseMetadata; import android.hardware.soundtrigger.ModelParams; import android.hardware.soundtrigger.SoundTrigger; import android.service.voice.IVoiceInteractionService; @@ -72,8 +73,8 @@ interface IVoiceInteractionManagerService { */ SoundTrigger.ModuleProperties getDspModuleProperties(in IVoiceInteractionService service); /** - * Indicates if there's a keyphrase sound model available for the given keyphrase ID. - * This performs the check for the current user. + * Indicates if there's a keyphrase sound model available for the given keyphrase ID and the + * user ID of the caller. * * @param service The current VoiceInteractionService. * @param keyphraseId The unique identifier for the keyphrase. @@ -82,6 +83,18 @@ interface IVoiceInteractionManagerService { boolean isEnrolledForKeyphrase(IVoiceInteractionService service, int keyphraseId, String bcp47Locale); /** + * Generates KeyphraseMetadata for an enrolled sound model based on keyphrase string, locale, + * and the user ID of the caller. + * + * @param service The current VoiceInteractionService + * @param keyphrase Keyphrase text associated with the enrolled model + * @param bcp47Locale The BCP47 language tag for the keyphrase's locale. + * @return The metadata for the enrolled voice model bassed on the passed in parameters. Null if + * no matching voice model exists. + */ + KeyphraseMetadata getEnrolledKeyphraseMetadata(IVoiceInteractionService service, + String keyphrase, String bcp47Locale); + /** * Starts a recognition for the given keyphrase. */ int startRecognition(in IVoiceInteractionService service, int keyphraseId, diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/DatabaseHelper.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/DatabaseHelper.java index 195a9e49d70d..af81ab6339f3 100644 --- a/services/voiceinteraction/java/com/android/server/voiceinteraction/DatabaseHelper.java +++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/DatabaseHelper.java @@ -194,7 +194,7 @@ public class DatabaseHelper extends SQLiteOpenHelper { * Deletes the sound model and associated keyphrases. */ public boolean deleteKeyphraseSoundModel(int keyphraseId, int userHandle, String bcp47Locale) { - // Sanitize the locale to guard against SQL injection. + // Normalize the locale to guard against SQL injection. bcp47Locale = Locale.forLanguageTag(bcp47Locale).toLanguageTag(); synchronized(this) { KeyphraseSoundModel soundModel = getKeyphraseSoundModel(keyphraseId, userHandle, @@ -230,90 +230,117 @@ public class DatabaseHelper extends SQLiteOpenHelper { String selectQuery = "SELECT * FROM " + SoundModelContract.TABLE + " WHERE " + SoundModelContract.KEY_KEYPHRASE_ID + "= '" + keyphraseId + "' AND " + SoundModelContract.KEY_LOCALE + "='" + bcp47Locale + "'"; - SQLiteDatabase db = getReadableDatabase(); - Cursor c = db.rawQuery(selectQuery, null); + return getValidKeyphraseSoundModelForUser(selectQuery, userHandle); + } + } - try { - if (c.moveToFirst()) { - do { - int type = c.getInt(c.getColumnIndex(SoundModelContract.KEY_TYPE)); - if (type != SoundTrigger.SoundModel.TYPE_KEYPHRASE) { - if (DBG) { - Slog.w(TAG, "Ignoring SoundModel since it's type is incorrect"); - } - continue; - } + /** + * Returns a matching {@link KeyphraseSoundModel} for the keyphrase string. + * Returns null if a match isn't found. + * + * TODO: We only support one keyphrase currently. + */ + public KeyphraseSoundModel getKeyphraseSoundModel(String keyphrase, int userHandle, + String bcp47Locale) { + // Sanitize the locale to guard against SQL injection. + bcp47Locale = Locale.forLanguageTag(bcp47Locale).toLanguageTag(); + synchronized (this) { + // Find the corresponding sound model ID for the keyphrase. + String selectQuery = "SELECT * FROM " + SoundModelContract.TABLE + + " WHERE " + SoundModelContract.KEY_HINT_TEXT + "= '" + keyphrase + + "' AND " + SoundModelContract.KEY_LOCALE + "='" + bcp47Locale + "'"; + return getValidKeyphraseSoundModelForUser(selectQuery, userHandle); + } + } - String modelUuid = c.getString( - c.getColumnIndex(SoundModelContract.KEY_MODEL_UUID)); - if (modelUuid == null) { - Slog.w(TAG, "Ignoring SoundModel since it doesn't specify an ID"); - continue; - } + private KeyphraseSoundModel getValidKeyphraseSoundModelForUser(String selectQuery, + int userHandle) { + SQLiteDatabase db = getReadableDatabase(); + Cursor c = db.rawQuery(selectQuery, null); - String vendorUuidString = null; - int vendorUuidColumn = c.getColumnIndex(SoundModelContract.KEY_VENDOR_UUID); - if (vendorUuidColumn != -1) { - vendorUuidString = c.getString(vendorUuidColumn); - } - byte[] data = c.getBlob(c.getColumnIndex(SoundModelContract.KEY_DATA)); - int recognitionModes = c.getInt( - c.getColumnIndex(SoundModelContract.KEY_RECOGNITION_MODES)); - int[] users = getArrayForCommaSeparatedString( - c.getString(c.getColumnIndex(SoundModelContract.KEY_USERS))); - Locale modelLocale = Locale.forLanguageTag(c.getString( - c.getColumnIndex(SoundModelContract.KEY_LOCALE))); - String text = c.getString( - c.getColumnIndex(SoundModelContract.KEY_HINT_TEXT)); - int version = c.getInt( - c.getColumnIndex(SoundModelContract.KEY_MODEL_VERSION)); - - // Only add keyphrases meant for the current user. - if (users == null) { - // No users present in the keyphrase. - Slog.w(TAG, "Ignoring SoundModel since it doesn't specify users"); - continue; + try { + if (c.moveToFirst()) { + do { + int type = c.getInt(c.getColumnIndex(SoundModelContract.KEY_TYPE)); + if (type != SoundTrigger.SoundModel.TYPE_KEYPHRASE) { + if (DBG) { + Slog.w(TAG, "Ignoring SoundModel since its type is incorrect"); } + continue; + } - boolean isAvailableForCurrentUser = false; - for (int user : users) { - if (userHandle == user) { - isAvailableForCurrentUser = true; - break; - } - } - if (!isAvailableForCurrentUser) { - if (DBG) { - Slog.w(TAG, "Ignoring SoundModel since user handles don't match"); - } - continue; - } else { - if (DBG) Slog.d(TAG, "Found a SoundModel for user: " + userHandle); - } + String modelUuid = c.getString( + c.getColumnIndex(SoundModelContract.KEY_MODEL_UUID)); + if (modelUuid == null) { + Slog.w(TAG, "Ignoring SoundModel since it doesn't specify an ID"); + continue; + } + + String vendorUuidString = null; + int vendorUuidColumn = c.getColumnIndex(SoundModelContract.KEY_VENDOR_UUID); + if (vendorUuidColumn != -1) { + vendorUuidString = c.getString(vendorUuidColumn); + } + int keyphraseId = c.getInt( + c.getColumnIndex(SoundModelContract.KEY_KEYPHRASE_ID)); + byte[] data = c.getBlob(c.getColumnIndex(SoundModelContract.KEY_DATA)); + int recognitionModes = c.getInt( + c.getColumnIndex(SoundModelContract.KEY_RECOGNITION_MODES)); + int[] users = getArrayForCommaSeparatedString( + c.getString(c.getColumnIndex(SoundModelContract.KEY_USERS))); + Locale modelLocale = Locale.forLanguageTag(c.getString( + c.getColumnIndex(SoundModelContract.KEY_LOCALE))); + String text = c.getString( + c.getColumnIndex(SoundModelContract.KEY_HINT_TEXT)); + int version = c.getInt( + c.getColumnIndex(SoundModelContract.KEY_MODEL_VERSION)); + + // Only add keyphrases meant for the current user. + if (users == null) { + // No users present in the keyphrase. + Slog.w(TAG, "Ignoring SoundModel since it doesn't specify users"); + continue; + } - Keyphrase[] keyphrases = new Keyphrase[1]; - keyphrases[0] = new Keyphrase( - keyphraseId, recognitionModes, modelLocale, text, users); - UUID vendorUuid = null; - if (vendorUuidString != null) { - vendorUuid = UUID.fromString(vendorUuidString); + boolean isAvailableForCurrentUser = false; + for (int user : users) { + if (userHandle == user) { + isAvailableForCurrentUser = true; + break; } - KeyphraseSoundModel model = new KeyphraseSoundModel( - UUID.fromString(modelUuid), vendorUuid, data, keyphrases, version); + } + if (!isAvailableForCurrentUser) { if (DBG) { - Slog.d(TAG, "Found SoundModel for the given keyphrase/locale/user: " - + model); + Slog.w(TAG, "Ignoring SoundModel since user handles don't match"); } - return model; - } while (c.moveToNext()); - } - Slog.w(TAG, "No SoundModel available for the given keyphrase"); - } finally { - c.close(); - db.close(); + continue; + } else { + if (DBG) Slog.d(TAG, "Found a SoundModel for user: " + userHandle); + } + + Keyphrase[] keyphrases = new Keyphrase[1]; + keyphrases[0] = new Keyphrase( + keyphraseId, recognitionModes, modelLocale, text, users); + UUID vendorUuid = null; + if (vendorUuidString != null) { + vendorUuid = UUID.fromString(vendorUuidString); + } + KeyphraseSoundModel model = new KeyphraseSoundModel( + UUID.fromString(modelUuid), vendorUuid, data, keyphrases, version); + if (DBG) { + Slog.d(TAG, "Found SoundModel for the given keyphrase/locale/user: " + + model); + } + return model; + } while (c.moveToNext()); } - return null; + Slog.w(TAG, "No SoundModel available for the given keyphrase"); + } finally { + c.close(); + db.close(); } + + return null; } private static String getCommaSeparatedString(int[] users) { diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java index ec0a1bacf094..d5eec332cda0 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.KeyphraseMetadata; import android.hardware.soundtrigger.ModelParams; +import android.hardware.soundtrigger.SoundTrigger; import android.hardware.soundtrigger.SoundTrigger.KeyphraseSoundModel; import android.hardware.soundtrigger.SoundTrigger.ModelParamRange; import android.hardware.soundtrigger.SoundTrigger.ModuleProperties; @@ -90,6 +92,7 @@ import com.android.server.wm.ActivityTaskManagerInternal; import java.io.FileDescriptor; import java.io.PrintWriter; import java.util.List; +import java.util.Locale; import java.util.Objects; import java.util.concurrent.Executor; @@ -1024,6 +1027,41 @@ public class VoiceInteractionManagerService extends SystemService { } } + @Nullable + public KeyphraseMetadata getEnrolledKeyphraseMetadata(IVoiceInteractionService service, + String keyphrase, String bcp47Locale) { + synchronized (this) { + enforceIsCurrentVoiceInteractionService(service); + } + + if (bcp47Locale == null) { + throw new IllegalArgumentException("Illegal argument(s) in isEnrolledForKeyphrase"); + } + + final int callingUid = UserHandle.getCallingUserId(); + final long caller = Binder.clearCallingIdentity(); + try { + KeyphraseSoundModel model = + mDbHelper.getKeyphraseSoundModel(keyphrase, callingUid, bcp47Locale); + if (model == null) { + return null; + } + + for (SoundTrigger.Keyphrase phrase : model.keyphrases) { + if (keyphrase.equals(phrase.text)) { + ArraySet<Locale> locales = new ArraySet<>(); + locales.add(phrase.locale); + return new KeyphraseMetadata(phrase.id, phrase.text, locales, + phrase.recognitionModes); + } + } + } finally { + Binder.restoreCallingIdentity(caller); + } + + return null; + } + @Override public ModuleProperties getDspModuleProperties(IVoiceInteractionService service) { // Allow the call if this is the current voice interaction service. |