Merge "Fix issues with multiple languages and multi-users" into lmp-dev
This commit is contained in:
committed by
Android (Google) Code Review
commit
80ea66b102
@@ -170,8 +170,7 @@ public class AlwaysOnHotwordDetector {
|
||||
= SoundTrigger.RECOGNITION_MODE_USER_IDENTIFICATION;
|
||||
|
||||
static final String TAG = "AlwaysOnHotwordDetector";
|
||||
// TODO: Set to false.
|
||||
static final boolean DBG = true;
|
||||
static final boolean DBG = false;
|
||||
|
||||
private static final int STATUS_ERROR = SoundTrigger.STATUS_ERROR;
|
||||
private static final int STATUS_OK = SoundTrigger.STATUS_OK;
|
||||
@@ -575,7 +574,7 @@ public class AlwaysOnHotwordDetector {
|
||||
int code = STATUS_ERROR;
|
||||
try {
|
||||
code = mModelManagementService.startRecognition(mVoiceInteractionService,
|
||||
mKeyphraseMetadata.id, mInternalCallback,
|
||||
mKeyphraseMetadata.id, mLocale.toLanguageTag(), mInternalCallback,
|
||||
new RecognitionConfig(captureTriggerAudio, allowMultipleTriggers,
|
||||
recognitionExtra, null /* additional data */));
|
||||
} catch (RemoteException e) {
|
||||
@@ -690,7 +689,7 @@ public class AlwaysOnHotwordDetector {
|
||||
if (availability == STATE_NOT_READY
|
||||
|| availability == STATE_KEYPHRASE_UNENROLLED
|
||||
|| availability == STATE_KEYPHRASE_ENROLLED) {
|
||||
enrolled = internalGetIsEnrolled(mKeyphraseMetadata.id);
|
||||
enrolled = internalGetIsEnrolled(mKeyphraseMetadata.id, mLocale);
|
||||
if (!enrolled) {
|
||||
availability = STATE_KEYPHRASE_UNENROLLED;
|
||||
} else {
|
||||
@@ -741,10 +740,10 @@ public class AlwaysOnHotwordDetector {
|
||||
/**
|
||||
* @return The corresponding {@link KeyphraseSoundModel} or null if none is found.
|
||||
*/
|
||||
private boolean internalGetIsEnrolled(int keyphraseId) {
|
||||
private boolean internalGetIsEnrolled(int keyphraseId, Locale locale) {
|
||||
try {
|
||||
return mModelManagementService.isEnrolledForKeyphrase(
|
||||
mVoiceInteractionService, keyphraseId);
|
||||
mVoiceInteractionService, keyphraseId, locale.toLanguageTag());
|
||||
} catch (RemoteException e) {
|
||||
Slog.w(TAG, "RemoteException in listRegisteredKeyphraseSoundModels!", e);
|
||||
}
|
||||
|
||||
@@ -33,32 +33,44 @@ interface IVoiceInteractionManagerService {
|
||||
void finish(IBinder token);
|
||||
|
||||
/**
|
||||
* Lists the registered Sound model for keyphrase detection.
|
||||
* May be null if no matching sound models exist.
|
||||
* Gets the registered Sound model for keyphrase detection for the current user.
|
||||
* May be null if no matching sound model exists.
|
||||
*
|
||||
* @param keyphraseId The unique identifier for the keyphrase.
|
||||
* @param bcp47Locale The BCP47 language tag for the keyphrase's locale.
|
||||
*/
|
||||
SoundTrigger.KeyphraseSoundModel getKeyphraseSoundModel(int keyphraseId);
|
||||
SoundTrigger.KeyphraseSoundModel getKeyphraseSoundModel(int keyphraseId, in String bcp47Locale);
|
||||
/**
|
||||
* Updates the given keyphrase sound model. Adds the model if it doesn't exist currently.
|
||||
* Add/Update the given keyphrase sound model.
|
||||
*/
|
||||
int updateKeyphraseSoundModel(in SoundTrigger.KeyphraseSoundModel model);
|
||||
/**
|
||||
* Deletes the given keyphrase sound model.
|
||||
* Deletes the given keyphrase sound model for the current user.
|
||||
*
|
||||
* @param keyphraseId The unique identifier for the keyphrase.
|
||||
* @param bcp47Locale The BCP47 language tag for the keyphrase's locale.
|
||||
*/
|
||||
int deleteKeyphraseSoundModel(int keyphraseId);
|
||||
int deleteKeyphraseSoundModel(int keyphraseId, in String bcp47Locale);
|
||||
|
||||
/**
|
||||
* Indicates if there's a keyphrase sound model available for the given keyphrase ID.
|
||||
*/
|
||||
boolean isEnrolledForKeyphrase(IVoiceInteractionService service, int keyphraseId);
|
||||
/**
|
||||
* Gets the properties of the DSP hardware on this device, null if not present.
|
||||
*/
|
||||
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.
|
||||
*
|
||||
* @param service The current VoiceInteractionService.
|
||||
* @param keyphraseId The unique identifier for the keyphrase.
|
||||
* @param bcp47Locale The BCP47 language tag for the keyphrase's locale.
|
||||
*/
|
||||
boolean isEnrolledForKeyphrase(IVoiceInteractionService service, int keyphraseId,
|
||||
String bcp47Locale);
|
||||
/**
|
||||
* Starts a recognition for the given keyphrase.
|
||||
*/
|
||||
int startRecognition(in IVoiceInteractionService service, int keyphraseId,
|
||||
in IRecognitionStatusCallback callback,
|
||||
in String bcp47Locale, in IRecognitionStatusCallback callback,
|
||||
in SoundTrigger.RecognitionConfig recognitionConfig);
|
||||
/**
|
||||
* Stops a recognition for the given keyphrase.
|
||||
|
||||
@@ -24,10 +24,10 @@ import android.database.sqlite.SQLiteOpenHelper;
|
||||
import android.hardware.soundtrigger.SoundTrigger;
|
||||
import android.hardware.soundtrigger.SoundTrigger.Keyphrase;
|
||||
import android.hardware.soundtrigger.SoundTrigger.KeyphraseSoundModel;
|
||||
import android.os.UserManager;
|
||||
import android.text.TextUtils;
|
||||
import android.util.Slog;
|
||||
|
||||
import java.util.Locale;
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
@@ -37,8 +37,7 @@ import java.util.UUID;
|
||||
*/
|
||||
public class DatabaseHelper extends SQLiteOpenHelper {
|
||||
static final String TAG = "SoundModelDBHelper";
|
||||
// TODO: Set to false.
|
||||
static final boolean DBG = true;
|
||||
static final boolean DBG = false;
|
||||
|
||||
private static final String NAME = "sound_model.db";
|
||||
private static final int VERSION = 4;
|
||||
@@ -67,11 +66,8 @@ public class DatabaseHelper extends SQLiteOpenHelper {
|
||||
+ SoundModelContract.KEY_HINT_TEXT + " TEXT,"
|
||||
+ SoundModelContract.KEY_USERS + " TEXT" + ")";
|
||||
|
||||
private final UserManager mUserManager;
|
||||
|
||||
public DatabaseHelper(Context context) {
|
||||
super(context, NAME, null, VERSION);
|
||||
mUserManager = (UserManager) context.getSystemService(Context.USER_SERVICE);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -122,17 +118,20 @@ public class DatabaseHelper extends SQLiteOpenHelper {
|
||||
/**
|
||||
* Deletes the sound model and associated keyphrases.
|
||||
*/
|
||||
public boolean deleteKeyphraseSoundModel(UUID modelUuid) {
|
||||
if (modelUuid == null) {
|
||||
Slog.w(TAG, "Model UUID must be specified for deletion");
|
||||
return false;
|
||||
}
|
||||
|
||||
public boolean deleteKeyphraseSoundModel(int keyphraseId, int userHandle, String bcp47Locale) {
|
||||
// Sanitize the locale to guard against SQL injection.
|
||||
bcp47Locale = Locale.forLanguageTag(bcp47Locale).toLanguageTag();
|
||||
synchronized(this) {
|
||||
SQLiteDatabase db = getWritableDatabase();
|
||||
String soundModelClause = SoundModelContract.KEY_MODEL_UUID + "='"
|
||||
+ modelUuid.toString() + "'";
|
||||
KeyphraseSoundModel soundModel = getKeyphraseSoundModel(keyphraseId, userHandle,
|
||||
bcp47Locale);
|
||||
if (soundModel == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Delete all sound models for the given keyphrase and specified user.
|
||||
SQLiteDatabase db = getWritableDatabase();
|
||||
String soundModelClause = SoundModelContract.KEY_MODEL_UUID
|
||||
+ "='" + soundModel.uuid.toString() + "'";
|
||||
try {
|
||||
return db.delete(SoundModelContract.TABLE, soundModelClause, null) != 0;
|
||||
} finally {
|
||||
@@ -147,11 +146,15 @@ public class DatabaseHelper extends SQLiteOpenHelper {
|
||||
*
|
||||
* TODO: We only support one keyphrase currently.
|
||||
*/
|
||||
public KeyphraseSoundModel getKeyphraseSoundModel(int keyphraseId) {
|
||||
public KeyphraseSoundModel getKeyphraseSoundModel(int keyphraseId, 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_KEYPHRASE_ID + " = '" + keyphraseId + "'";
|
||||
+ " WHERE " + SoundModelContract.KEY_KEYPHRASE_ID + "= '" + keyphraseId
|
||||
+ "' AND " + SoundModelContract.KEY_LOCALE + "='" + bcp47Locale + "'";
|
||||
SQLiteDatabase db = getReadableDatabase();
|
||||
Cursor c = db.rawQuery(selectQuery, null);
|
||||
|
||||
@@ -160,14 +163,16 @@ public class DatabaseHelper extends SQLiteOpenHelper {
|
||||
do {
|
||||
int type = c.getInt(c.getColumnIndex(SoundModelContract.KEY_TYPE));
|
||||
if (type != SoundTrigger.SoundModel.TYPE_KEYPHRASE) {
|
||||
Slog.w(TAG, "Ignoring sound model since it's type is incorrect");
|
||||
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 sound model since it doesn't specify an ID");
|
||||
Slog.w(TAG, "Ignoring SoundModel since it doesn't specify an ID");
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -176,7 +181,7 @@ public class DatabaseHelper extends SQLiteOpenHelper {
|
||||
c.getColumnIndex(SoundModelContract.KEY_RECOGNITION_MODES));
|
||||
int[] users = getArrayForCommaSeparatedString(
|
||||
c.getString(c.getColumnIndex(SoundModelContract.KEY_USERS)));
|
||||
String locale = c.getString(
|
||||
String modelLocale = c.getString(
|
||||
c.getColumnIndex(SoundModelContract.KEY_LOCALE));
|
||||
String text = c.getString(
|
||||
c.getColumnIndex(SoundModelContract.KEY_HINT_TEXT));
|
||||
@@ -184,28 +189,37 @@ public class DatabaseHelper extends SQLiteOpenHelper {
|
||||
// Only add keyphrases meant for the current user.
|
||||
if (users == null) {
|
||||
// No users present in the keyphrase.
|
||||
Slog.w(TAG, "Ignoring keyphrase since it doesn't specify users");
|
||||
Slog.w(TAG, "Ignoring SoundModel since it doesn't specify users");
|
||||
continue;
|
||||
}
|
||||
|
||||
boolean isAvailableForCurrentUser = false;
|
||||
int currentUser = mUserManager.getUserHandle();
|
||||
for (int user : users) {
|
||||
if (currentUser == user) {
|
||||
if (userHandle == user) {
|
||||
isAvailableForCurrentUser = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!isAvailableForCurrentUser) {
|
||||
Slog.w(TAG, "Ignoring keyphrase since it's not for the current user");
|
||||
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, locale, text, users);
|
||||
return new KeyphraseSoundModel(UUID.fromString(modelUuid),
|
||||
keyphraseId, recognitionModes, modelLocale, text, users);
|
||||
KeyphraseSoundModel model = new KeyphraseSoundModel(
|
||||
UUID.fromString(modelUuid),
|
||||
null /* FIXME use vendor UUID */, data, keyphrases);
|
||||
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");
|
||||
@@ -218,15 +232,17 @@ public class DatabaseHelper extends SQLiteOpenHelper {
|
||||
}
|
||||
|
||||
private static String getCommaSeparatedString(int[] users) {
|
||||
if (users == null || users.length == 0) {
|
||||
if (users == null) {
|
||||
return "";
|
||||
}
|
||||
String csv = "";
|
||||
for (int user : users) {
|
||||
csv += String.valueOf(user);
|
||||
csv += ",";
|
||||
StringBuilder sb = new StringBuilder();
|
||||
for (int i = 0; i < users.length; i++) {
|
||||
if (i != 0) {
|
||||
sb.append(',');
|
||||
}
|
||||
sb.append(users[i]);
|
||||
}
|
||||
return csv.substring(0, csv.length() - 1);
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
private static int[] getArrayForCommaSeparatedString(String text) {
|
||||
|
||||
@@ -50,8 +50,7 @@ import java.util.UUID;
|
||||
*/
|
||||
public class SoundTriggerHelper implements SoundTrigger.StatusListener {
|
||||
static final String TAG = "SoundTriggerHelper";
|
||||
// TODO: Set to false.
|
||||
static final boolean DBG = true;
|
||||
static final boolean DBG = false;
|
||||
|
||||
/**
|
||||
* Return codes for {@link #startRecognition(int, KeyphraseSoundModel,
|
||||
@@ -166,8 +165,14 @@ public class SoundTriggerHelper implements SoundTrigger.StatusListener {
|
||||
}
|
||||
}
|
||||
|
||||
// Unload the previous model if the current one isn't invalid
|
||||
// and, it's not the same as the new one, or we are already started
|
||||
// if we are already started, we can get multiple calls to start
|
||||
// if the underlying sound model changes, in which case we should unload and reload.
|
||||
// The model reuse helps only in cases when we trigger and stop internally
|
||||
// without a start recognition call.
|
||||
if (mCurrentSoundModelHandle != INVALID_VALUE
|
||||
&& !soundModel.uuid.equals(mCurrentSoundModelUuid)) {
|
||||
&& (!soundModel.uuid.equals(mCurrentSoundModelUuid) || mStarted)) {
|
||||
Slog.w(TAG, "Unloading previous sound model");
|
||||
int status = mModule.unloadSoundModel(mCurrentSoundModelHandle);
|
||||
if (status != SoundTrigger.STATUS_OK) {
|
||||
|
||||
@@ -446,7 +446,7 @@ public class VoiceInteractionManagerService extends SystemService {
|
||||
//----------------- Model management APIs --------------------------------//
|
||||
|
||||
@Override
|
||||
public KeyphraseSoundModel getKeyphraseSoundModel(int keyphraseId) {
|
||||
public KeyphraseSoundModel getKeyphraseSoundModel(int keyphraseId, String bcp47Locale) {
|
||||
synchronized (this) {
|
||||
if (mContext.checkCallingPermission(Manifest.permission.MANAGE_VOICE_KEYPHRASES)
|
||||
!= PackageManager.PERMISSION_GRANTED) {
|
||||
@@ -455,9 +455,14 @@ public class VoiceInteractionManagerService extends SystemService {
|
||||
}
|
||||
}
|
||||
|
||||
if (bcp47Locale == null) {
|
||||
throw new IllegalArgumentException("Illegal argument(s) in getKeyphraseSoundModel");
|
||||
}
|
||||
|
||||
final int callingUid = UserHandle.getCallingUserId();
|
||||
final long caller = Binder.clearCallingIdentity();
|
||||
try {
|
||||
return mDbHelper.getKeyphraseSoundModel(keyphraseId);
|
||||
return mDbHelper.getKeyphraseSoundModel(keyphraseId, callingUid, bcp47Locale);
|
||||
} finally {
|
||||
Binder.restoreCallingIdentity(caller);
|
||||
}
|
||||
@@ -495,7 +500,7 @@ public class VoiceInteractionManagerService extends SystemService {
|
||||
}
|
||||
|
||||
@Override
|
||||
public int deleteKeyphraseSoundModel(int keyphraseId) {
|
||||
public int deleteKeyphraseSoundModel(int keyphraseId, String bcp47Locale) {
|
||||
synchronized (this) {
|
||||
if (mContext.checkCallingPermission(Manifest.permission.MANAGE_VOICE_KEYPHRASES)
|
||||
!= PackageManager.PERMISSION_GRANTED) {
|
||||
@@ -504,13 +509,16 @@ public class VoiceInteractionManagerService extends SystemService {
|
||||
}
|
||||
}
|
||||
|
||||
if (bcp47Locale == null) {
|
||||
throw new IllegalArgumentException(
|
||||
"Illegal argument(s) in deleteKeyphraseSoundModel");
|
||||
}
|
||||
|
||||
final int callingUid = UserHandle.getCallingUserId();
|
||||
final long caller = Binder.clearCallingIdentity();
|
||||
boolean deleted = false;
|
||||
try {
|
||||
KeyphraseSoundModel soundModel = mDbHelper.getKeyphraseSoundModel(keyphraseId);
|
||||
if (soundModel != null) {
|
||||
deleted = mDbHelper.deleteKeyphraseSoundModel(soundModel.uuid);
|
||||
}
|
||||
deleted = mDbHelper.deleteKeyphraseSoundModel(keyphraseId, callingUid, bcp47Locale);
|
||||
return deleted ? SoundTriggerHelper.STATUS_OK : SoundTriggerHelper.STATUS_ERROR;
|
||||
} finally {
|
||||
if (deleted) {
|
||||
@@ -527,7 +535,8 @@ public class VoiceInteractionManagerService extends SystemService {
|
||||
|
||||
//----------------- SoundTrigger APIs --------------------------------//
|
||||
@Override
|
||||
public boolean isEnrolledForKeyphrase(IVoiceInteractionService service, int keyphraseId) {
|
||||
public boolean isEnrolledForKeyphrase(IVoiceInteractionService service, int keyphraseId,
|
||||
String bcp47Locale) {
|
||||
synchronized (this) {
|
||||
if (mImpl == null || mImpl.mService == null
|
||||
|| service.asBinder() != mImpl.mService.asBinder()) {
|
||||
@@ -536,9 +545,15 @@ public class VoiceInteractionManagerService extends SystemService {
|
||||
}
|
||||
}
|
||||
|
||||
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(keyphraseId);
|
||||
KeyphraseSoundModel model =
|
||||
mDbHelper.getKeyphraseSoundModel(keyphraseId, callingUid, bcp47Locale);
|
||||
return model != null;
|
||||
} finally {
|
||||
Binder.restoreCallingIdentity(caller);
|
||||
@@ -566,7 +581,8 @@ public class VoiceInteractionManagerService extends SystemService {
|
||||
|
||||
@Override
|
||||
public int startRecognition(IVoiceInteractionService service, int keyphraseId,
|
||||
IRecognitionStatusCallback callback, RecognitionConfig recognitionConfig) {
|
||||
String bcp47Locale, IRecognitionStatusCallback callback,
|
||||
RecognitionConfig recognitionConfig) {
|
||||
// Allow the call if this is the current voice interaction service.
|
||||
synchronized (this) {
|
||||
if (mImpl == null || mImpl.mService == null
|
||||
@@ -575,14 +591,16 @@ public class VoiceInteractionManagerService extends SystemService {
|
||||
"Caller is not the current voice interaction service");
|
||||
}
|
||||
|
||||
if (callback == null || recognitionConfig == null) {
|
||||
if (callback == null || recognitionConfig == null || bcp47Locale == null) {
|
||||
throw new IllegalArgumentException("Illegal argument(s) in startRecognition");
|
||||
}
|
||||
}
|
||||
|
||||
int callingUid = UserHandle.getCallingUserId();
|
||||
final long caller = Binder.clearCallingIdentity();
|
||||
try {
|
||||
KeyphraseSoundModel soundModel = mDbHelper.getKeyphraseSoundModel(keyphraseId);
|
||||
KeyphraseSoundModel soundModel =
|
||||
mDbHelper.getKeyphraseSoundModel(keyphraseId, callingUid, bcp47Locale);
|
||||
if (soundModel == null
|
||||
|| soundModel.uuid == null
|
||||
|| soundModel.keyphrases == null) {
|
||||
|
||||
Reference in New Issue
Block a user