Merge "Fix issues with multiple languages and multi-users" into lmp-dev

This commit is contained in:
Sandeep Siddhartha
2014-09-17 20:06:42 +00:00
committed by Android (Google) Code Review
5 changed files with 114 additions and 64 deletions

View File

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

View File

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

View File

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

View File

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

View File

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