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
This commit is contained in:
Nicholas Ambur
2020-01-14 20:35:04 -08:00
parent ef84fc4843
commit f771e6c9f6
7 changed files with 358 additions and 130 deletions

View File

@@ -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 {

View File

@@ -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;

View File

@@ -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
}

View File

@@ -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;
}
}

View File

@@ -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.
@@ -81,6 +82,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.
*/

View File

@@ -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,92 +230,119 @@ 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);
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;
}
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);
}
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;
}
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);
}
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());
}
Slog.w(TAG, "No SoundModel available for the given keyphrase");
} finally {
c.close();
db.close();
}
return null;
return getValidKeyphraseSoundModelForUser(selectQuery, userHandle);
}
}
/**
* 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);
}
}
private KeyphraseSoundModel getValidKeyphraseSoundModelForUser(String selectQuery,
int userHandle) {
SQLiteDatabase db = getReadableDatabase();
Cursor c = db.rawQuery(selectQuery, null);
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;
}
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;
}
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);
}
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());
}
Slog.w(TAG, "No SoundModel available for the given keyphrase");
} finally {
c.close();
db.close();
}
return null;
}
private static String getCommaSeparatedString(int[] users) {
if (users == null) {
return "";

View File

@@ -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.