Loading core/java/android/service/voice/AlwaysOnHotwordDetector.java +5 −6 Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -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) { Loading Loading @@ -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 { Loading Loading @@ -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); } Loading core/java/com/android/internal/app/IVoiceInteractionManagerService.aidl +23 −11 Original line number Diff line number Diff line Loading @@ -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. Loading services/voiceinteraction/java/com/android/server/voiceinteraction/DatabaseHelper.java +48 −32 Original line number Diff line number Diff line Loading @@ -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; /** Loading @@ -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; Loading Loading @@ -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 Loading Loading @@ -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"); public boolean deleteKeyphraseSoundModel(int keyphraseId, int userHandle, String bcp47Locale) { // Sanitize the locale to guard against SQL injection. bcp47Locale = Locale.forLanguageTag(bcp47Locale).toLanguageTag(); synchronized(this) { KeyphraseSoundModel soundModel = getKeyphraseSoundModel(keyphraseId, userHandle, bcp47Locale); if (soundModel == null) { return false; } synchronized(this) { // Delete all sound models for the given keyphrase and specified user. SQLiteDatabase db = getWritableDatabase(); String soundModelClause = SoundModelContract.KEY_MODEL_UUID + "='" + modelUuid.toString() + "'"; String soundModelClause = SoundModelContract.KEY_MODEL_UUID + "='" + soundModel.uuid.toString() + "'"; try { return db.delete(SoundModelContract.TABLE, soundModelClause, null) != 0; } finally { Loading @@ -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); Loading @@ -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; } Loading @@ -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)); Loading @@ -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"); Loading @@ -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) { Loading services/voiceinteraction/java/com/android/server/voiceinteraction/SoundTriggerHelper.java +8 −3 Original line number Diff line number Diff line Loading @@ -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, Loading Loading @@ -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) { Loading services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java +30 −12 Original line number Diff line number Diff line Loading @@ -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) { Loading @@ -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); } Loading Loading @@ -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) { Loading @@ -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) { Loading @@ -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()) { Loading @@ -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); Loading Loading @@ -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 Loading @@ -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) { Loading Loading
core/java/android/service/voice/AlwaysOnHotwordDetector.java +5 −6 Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -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) { Loading Loading @@ -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 { Loading Loading @@ -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); } Loading
core/java/com/android/internal/app/IVoiceInteractionManagerService.aidl +23 −11 Original line number Diff line number Diff line Loading @@ -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. Loading
services/voiceinteraction/java/com/android/server/voiceinteraction/DatabaseHelper.java +48 −32 Original line number Diff line number Diff line Loading @@ -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; /** Loading @@ -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; Loading Loading @@ -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 Loading Loading @@ -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"); public boolean deleteKeyphraseSoundModel(int keyphraseId, int userHandle, String bcp47Locale) { // Sanitize the locale to guard against SQL injection. bcp47Locale = Locale.forLanguageTag(bcp47Locale).toLanguageTag(); synchronized(this) { KeyphraseSoundModel soundModel = getKeyphraseSoundModel(keyphraseId, userHandle, bcp47Locale); if (soundModel == null) { return false; } synchronized(this) { // Delete all sound models for the given keyphrase and specified user. SQLiteDatabase db = getWritableDatabase(); String soundModelClause = SoundModelContract.KEY_MODEL_UUID + "='" + modelUuid.toString() + "'"; String soundModelClause = SoundModelContract.KEY_MODEL_UUID + "='" + soundModel.uuid.toString() + "'"; try { return db.delete(SoundModelContract.TABLE, soundModelClause, null) != 0; } finally { Loading @@ -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); Loading @@ -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; } Loading @@ -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)); Loading @@ -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"); Loading @@ -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) { Loading
services/voiceinteraction/java/com/android/server/voiceinteraction/SoundTriggerHelper.java +8 −3 Original line number Diff line number Diff line Loading @@ -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, Loading Loading @@ -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) { Loading
services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java +30 −12 Original line number Diff line number Diff line Loading @@ -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) { Loading @@ -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); } Loading Loading @@ -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) { Loading @@ -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) { Loading @@ -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()) { Loading @@ -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); Loading Loading @@ -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 Loading @@ -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) { Loading