summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorNicholas Ambur <nambur@google.com>2020-01-14 20:35:04 -0800
committerNicholas Ambur <nambur@google.com>2020-01-22 16:40:18 -0800
commitf771e6c9f694c772dd261aacbc0c566e587c345f (patch)
tree49c85da089db57e2b0aa547d1f3326a4fcbbfb5d
parentef84fc48433d47ea9c91dcb3273ae3d74ca6d32a (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
-rw-r--r--api/current.txt2
-rw-r--r--core/java/android/hardware/soundtrigger/KeyphraseMetadata.aidl19
-rw-r--r--core/java/android/hardware/soundtrigger/KeyphraseMetadata.java158
-rw-r--r--core/java/android/service/voice/AlwaysOnHotwordDetector.java59
-rw-r--r--core/java/com/android/internal/app/IVoiceInteractionManagerService.aidl17
-rw-r--r--services/voiceinteraction/java/com/android/server/voiceinteraction/DatabaseHelper.java175
-rw-r--r--services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java38
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.