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:
@@ -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 {
|
||||
|
||||
@@ -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;
|
||||
@@ -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
|
||||
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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.
|
||||
*/
|
||||
|
||||
@@ -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 "";
|
||||
|
||||
@@ -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.
|
||||
|
||||
Reference in New Issue
Block a user