From 452a642430e3f8abfa053e48893dd0edfb12799b Mon Sep 17 00:00:00 2001 From: Sandeep Siddhartha Date: Fri, 25 Jul 2014 10:49:34 -0700 Subject: [PATCH] Fix various bugs with model management - Tie the sound model and keyphrase for simplicity We won't support multiple keyphrases in a single model out of the box. The db schema will need to be changed by the OEM wishing to add multiple hotwords. This is because we currently have no way to test the flow and make sure that things work well with multiple keyphrases and also the framework only reads the metadata for one keyphrase. - Make the delete/update operations atomic - Make the flow of data from Enrollment -> VIMS; the large sound model doesn't cross the process boundary any other time. This is achieved by passing they key around, instead of the model themselves. - Add a specific delete operation in DatabaseHelper rather than relying on emptying the keyphrases to delete. Bug: 16555803 Bug: 16516353 Change-Id: I1e0cce137517502a669e431ca7e9f9f755598328 --- .../service/voice/AlwaysOnHotwordDetector.java | 42 +--- .../app/IVoiceInteractionManagerService.aidl | 17 +- .../server/voiceinteraction/DatabaseHelper.java | 246 +++++++++------------ .../voiceinteraction/SoundTriggerHelper.java | 3 +- .../VoiceInteractionManagerService.java | 117 ++++++---- 5 files changed, 202 insertions(+), 223 deletions(-) diff --git a/core/java/android/service/voice/AlwaysOnHotwordDetector.java b/core/java/android/service/voice/AlwaysOnHotwordDetector.java index d685cc5357ea..d077a17072e9 100644 --- a/core/java/android/service/voice/AlwaysOnHotwordDetector.java +++ b/core/java/android/service/voice/AlwaysOnHotwordDetector.java @@ -133,10 +133,10 @@ public class AlwaysOnHotwordDetector { private final Handler mHandler; /** - * The sound model for the keyphrase, derived from the model management service - * (IVoiceInteractionManagerService). May be null if the keyphrase isn't enrolled yet. + * Indicates if there is a sound model enrolled for the keyphrase, + * derived from the model management service (IVoiceInteractionManagerService). */ - private KeyphraseSoundModel mEnrolledSoundModel; + private boolean mIsEnrolledForDetection; private int mAvailability = STATE_NOT_READY; /** @@ -257,7 +257,7 @@ public class AlwaysOnHotwordDetector { int code = STATUS_ERROR; try { code = mModelManagementService.startRecognition(mVoiceInteractionService, - mKeyphraseMetadata.id, mEnrolledSoundModel, mInternalCallback, + mKeyphraseMetadata.id, mInternalCallback, new RecognitionConfig( captureTriggerAudio, recognitionExtra, null /* additional data */)); } catch (RemoteException e) { @@ -417,14 +417,13 @@ public class AlwaysOnHotwordDetector { @Override public Void doInBackground(Void... params) { int availability = internalGetInitialAvailability(); - KeyphraseSoundModel soundModel = null; + 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) { - soundModel = - internalGetKeyphraseSoundModel(mKeyphraseMetadata.id); - if (soundModel == null) { + enrolled = internalGetIsEnrolled(mKeyphraseMetadata.id); + if (!enrolled) { availability = STATE_KEYPHRASE_UNENROLLED; } else { availability = STATE_KEYPHRASE_ENROLLED; @@ -436,8 +435,8 @@ public class AlwaysOnHotwordDetector { Slog.d(TAG, "Hotword availability changed from " + mAvailability + " -> " + availability); } + mIsEnrolledForDetection = enrolled; mAvailability = availability; - mEnrolledSoundModel = soundModel; notifyStateChangedLocked(); } return null; @@ -475,31 +474,14 @@ public class AlwaysOnHotwordDetector { /** * @return The corresponding {@link KeyphraseSoundModel} or null if none is found. */ - private KeyphraseSoundModel internalGetKeyphraseSoundModel(int keyphraseId) { - List soundModels; + private boolean internalGetIsEnrolled(int keyphraseId) { try { - soundModels = mModelManagementService - .listRegisteredKeyphraseSoundModels(mVoiceInteractionService); - if (soundModels == null || soundModels.isEmpty()) { - Slog.i(TAG, "No available sound models for keyphrase ID: " + keyphraseId); - return null; - } - for (int i = 0; i < soundModels.size(); i++) { - KeyphraseSoundModel soundModel = soundModels.get(i); - if (soundModel.keyphrases == null || soundModel.keyphrases.length == 0) { - continue; - } - for (int j = 0; i < soundModel.keyphrases.length; j++) { - Keyphrase keyphrase = soundModel.keyphrases[j]; - if (keyphrase.id == keyphraseId) { - return soundModel; - } - } - } + return mModelManagementService.isEnrolledForKeyphrase( + mVoiceInteractionService, keyphraseId); } catch (RemoteException e) { Slog.w(TAG, "RemoteException in listRegisteredKeyphraseSoundModels!"); } - return null; + return false; } } } diff --git a/core/java/com/android/internal/app/IVoiceInteractionManagerService.aidl b/core/java/com/android/internal/app/IVoiceInteractionManagerService.aidl index 7d5abd2cca14..22ec4bead91e 100644 --- a/core/java/com/android/internal/app/IVoiceInteractionManagerService.aidl +++ b/core/java/com/android/internal/app/IVoiceInteractionManagerService.aidl @@ -33,19 +33,24 @@ interface IVoiceInteractionManagerService { void finish(IBinder token); /** - * Lists the registered Sound models for keyphrase detection. + * Lists the registered Sound model for keyphrase detection. * May be null if no matching sound models exist. - * - * @param service The current voice interaction service. */ - List listRegisteredKeyphraseSoundModels( - in IVoiceInteractionService service); + SoundTrigger.KeyphraseSoundModel getKeyphraseSoundModel(int keyphraseId); /** * Updates the given keyphrase sound model. Adds the model if it doesn't exist currently. */ int updateKeyphraseSoundModel(in SoundTrigger.KeyphraseSoundModel model); + /** + * Deletes the given keyphrase sound model. + */ + int deleteKeyphraseSoundModel(int keyphraseId); /** + * 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); @@ -53,7 +58,7 @@ interface IVoiceInteractionManagerService { * Starts a recognition for the given keyphrase. */ int startRecognition(in IVoiceInteractionService service, int keyphraseId, - in SoundTrigger.KeyphraseSoundModel soundModel, in IRecognitionStatusCallback callback, + in IRecognitionStatusCallback callback, in SoundTrigger.RecognitionConfig recognitionConfig); /** * Stops a recognition for the given keyphrase. diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/DatabaseHelper.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/DatabaseHelper.java index 1e0d6de7bc90..8913eb958fa7 100644 --- a/services/voiceinteraction/java/com/android/server/voiceinteraction/DatabaseHelper.java +++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/DatabaseHelper.java @@ -28,8 +28,6 @@ import android.os.UserManager; import android.text.TextUtils; import android.util.Slog; -import java.util.ArrayList; -import java.util.List; import java.util.UUID; /** @@ -39,43 +37,35 @@ import java.util.UUID; */ public class DatabaseHelper extends SQLiteOpenHelper { static final String TAG = "SoundModelDBHelper"; - static final boolean DBG = false; + // TODO: Set to false. + static final boolean DBG = true; private static final String NAME = "sound_model.db"; - private static final int VERSION = 2; - - public static interface KeyphraseContract { - public static final String TABLE = "keyphrase"; - public static final String KEY_ID = "_id"; - public static final String KEY_RECOGNITION_MODES = "modes"; - public static final String KEY_LOCALE = "locale"; - public static final String KEY_HINT_TEXT = "hint_text"; - public static final String KEY_USERS = "users"; - public static final String KEY_SOUND_MODEL_ID = "sound_model_id"; - } + private static final int VERSION = 3; public static interface SoundModelContract { public static final String TABLE = "sound_model"; - public static final String KEY_ID = "_id"; + public static final String KEY_KEYPHRASE_ID = "keyphrase_id"; + public static final String KEY_MODEL_UUID = "model_uuid"; public static final String KEY_TYPE = "type"; public static final String KEY_DATA = "data"; + public static final String KEY_RECOGNITION_MODES = "recognition_modes"; + public static final String KEY_LOCALE = "locale"; + public static final String KEY_HINT_TEXT = "hint_text"; + public static final String KEY_USERS = "users"; } - // Table Create Statements - private static final String CREATE_TABLE_KEYPRHASES = "CREATE TABLE " - + KeyphraseContract.TABLE + "(" - + KeyphraseContract.KEY_ID + " INTEGER PRIMARY KEY," - + KeyphraseContract.KEY_RECOGNITION_MODES + " INTEGER," - + KeyphraseContract.KEY_USERS + " TEXT," - + KeyphraseContract.KEY_SOUND_MODEL_ID + " TEXT," - + KeyphraseContract.KEY_LOCALE + " TEXT," - + KeyphraseContract.KEY_HINT_TEXT + " TEXT" + ")"; - + // Table Create Statement private static final String CREATE_TABLE_SOUND_MODEL = "CREATE TABLE " + SoundModelContract.TABLE + "(" - + SoundModelContract.KEY_ID + " TEXT PRIMARY KEY," + + SoundModelContract.KEY_KEYPHRASE_ID + " INTEGER PRIMARY KEY," + + SoundModelContract.KEY_MODEL_UUID + " TEXT," + SoundModelContract.KEY_TYPE + " INTEGER," - + SoundModelContract.KEY_DATA + " BLOB" + ")"; + + SoundModelContract.KEY_DATA + " BLOB," + + SoundModelContract.KEY_RECOGNITION_MODES + " INTEGER," + + SoundModelContract.KEY_LOCALE + " TEXT," + + SoundModelContract.KEY_HINT_TEXT + " TEXT," + + SoundModelContract.KEY_USERS + " TEXT" + ")"; private final UserManager mUserManager; @@ -87,57 +77,44 @@ public class DatabaseHelper extends SQLiteOpenHelper { @Override public void onCreate(SQLiteDatabase db) { // creating required tables - db.execSQL(CREATE_TABLE_KEYPRHASES); db.execSQL(CREATE_TABLE_SOUND_MODEL); } @Override public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { // TODO: For now, drop older tables and recreate new ones. - db.execSQL("DROP TABLE IF EXISTS " + KeyphraseContract.TABLE); db.execSQL("DROP TABLE IF EXISTS " + SoundModelContract.TABLE); onCreate(db); } - public boolean addOrUpdateKeyphraseSoundModel(KeyphraseSoundModel soundModel) { + /** + * Updates the given keyphrase model, adds it, if it doesn't already exist. + * + * TODO: We only support one keyphrase currently. + */ + public boolean updateKeyphraseSoundModel(KeyphraseSoundModel soundModel) { synchronized(this) { SQLiteDatabase db = getWritableDatabase(); ContentValues values = new ContentValues(); - // Generate a random ID for the model. - values.put(SoundModelContract.KEY_ID, soundModel.uuid.toString()); - values.put(SoundModelContract.KEY_DATA, soundModel.data); + values.put(SoundModelContract.KEY_MODEL_UUID, soundModel.uuid.toString()); values.put(SoundModelContract.KEY_TYPE, SoundTrigger.SoundModel.TYPE_KEYPHRASE); - - boolean status = true; - if (db.insertWithOnConflict(SoundModelContract.TABLE, null, values, - SQLiteDatabase.CONFLICT_REPLACE) != -1) { - for (Keyphrase keyphrase : soundModel.keyphrases) { - status &= addOrUpdateKeyphraseLocked(db, soundModel.uuid, keyphrase); + values.put(SoundModelContract.KEY_DATA, soundModel.data); + + if (soundModel.keyphrases != null && soundModel.keyphrases.length == 1) { + values.put(SoundModelContract.KEY_KEYPHRASE_ID, soundModel.keyphrases[0].id); + values.put(SoundModelContract.KEY_RECOGNITION_MODES, + soundModel.keyphrases[0].recognitionModes); + values.put(SoundModelContract.KEY_USERS, + getCommaSeparatedString(soundModel.keyphrases[0].users)); + values.put(SoundModelContract.KEY_LOCALE, soundModel.keyphrases[0].locale); + values.put(SoundModelContract.KEY_HINT_TEXT, soundModel.keyphrases[0].text); + try { + return db.insertWithOnConflict(SoundModelContract.TABLE, null, values, + SQLiteDatabase.CONFLICT_REPLACE) != -1; + } finally { + db.close(); } - db.close(); - return status; - } else { - Slog.w(TAG, "Failed to persist sound model to database"); - db.close(); - return false; } - } - } - - private boolean addOrUpdateKeyphraseLocked( - SQLiteDatabase db, UUID modelId, Keyphrase keyphrase) { - ContentValues values = new ContentValues(); - values.put(KeyphraseContract.KEY_ID, keyphrase.id); - values.put(KeyphraseContract.KEY_RECOGNITION_MODES, keyphrase.recognitionModes); - values.put(KeyphraseContract.KEY_SOUND_MODEL_ID, modelId.toString()); - values.put(KeyphraseContract.KEY_HINT_TEXT, keyphrase.text); - values.put(KeyphraseContract.KEY_LOCALE, keyphrase.locale); - values.put(KeyphraseContract.KEY_USERS, getCommaSeparatedString(keyphrase.users)); - if (db.insertWithOnConflict( - KeyphraseContract.TABLE, null, values, SQLiteDatabase.CONFLICT_REPLACE) != -1) { - return true; - } else { - Slog.w(TAG, "Failed to persist keyphrase to database"); return false; } } @@ -145,111 +122,90 @@ public class DatabaseHelper extends SQLiteOpenHelper { /** * Deletes the sound model and associated keyphrases. */ - public boolean deleteKeyphraseSoundModel(UUID uuid) { + public boolean deleteKeyphraseSoundModel(int keyphraseId) { synchronized(this) { SQLiteDatabase db = getWritableDatabase(); - String modelId = uuid.toString(); - String soundModelClause = SoundModelContract.KEY_ID + "=" + modelId; - boolean status = true; - if (db.delete(SoundModelContract.TABLE, soundModelClause, null) == 0) { - Slog.w(TAG, "No sound models deleted from the database"); - status = false; - } - String keyphraseClause = KeyphraseContract.KEY_SOUND_MODEL_ID + "=" + modelId; - if (db.delete(KeyphraseContract.TABLE, keyphraseClause, null) == 0) { - Slog.w(TAG, "No keyphrases deleted from the database"); - status = false; + String soundModelClause = SoundModelContract.KEY_KEYPHRASE_ID + "=" + keyphraseId; + + try { + return db.delete(SoundModelContract.TABLE, soundModelClause, null) != 0; + } finally { + db.close(); } - db.close(); - return status; } } /** - * Lists all the keyphrase sound models currently registered with the system. + * Returns a matching {@link KeyphraseSoundModel} for the keyphrase ID. + * Returns null if a match isn't found. + * + * TODO: We only support one keyphrase currently. */ - public List getKephraseSoundModels() { + public KeyphraseSoundModel getKeyphraseSoundModel(int keyphraseId) { synchronized(this) { - List models = new ArrayList<>(); - String selectQuery = "SELECT * FROM " + SoundModelContract.TABLE; + // Find the corresponding sound model ID for the keyphrase. + String selectQuery = "SELECT * FROM " + SoundModelContract.TABLE + + " WHERE " + SoundModelContract.KEY_KEYPHRASE_ID + " = '" + keyphraseId + "'"; SQLiteDatabase db = getReadableDatabase(); Cursor c = db.rawQuery(selectQuery, null); - - // looping through all rows and adding to list - if (c.moveToFirst()) { - do { + + try { + if (c.moveToFirst()) { int type = c.getInt(c.getColumnIndex(SoundModelContract.KEY_TYPE)); if (type != SoundTrigger.SoundModel.TYPE_KEYPHRASE) { - // Ignore non-keyphrase sound models. - continue; + Slog.w(TAG, "No KeyphraseSoundModel available for the given keyphrase"); + return null; } - String id = c.getString(c.getColumnIndex(SoundModelContract.KEY_ID)); - byte[] data = c.getBlob(c.getColumnIndex(SoundModelContract.KEY_DATA)); - // Get all the keyphrases for this this sound model. - // Validate the sound model. - if (id == null) { + + 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"); - continue; - } - KeyphraseSoundModel model = new KeyphraseSoundModel( - UUID.fromString(id), data, getKeyphrasesForSoundModelLocked(db, id)); - if (DBG) { - Slog.d(TAG, "Adding model: " + model); + return null; } - models.add(model); - } while (c.moveToNext()); - } - c.close(); - db.close(); - return models; - } - } - - private Keyphrase[] getKeyphrasesForSoundModelLocked(SQLiteDatabase db, String modelId) { - List keyphrases = new ArrayList<>(); - String selectQuery = "SELECT * FROM " + KeyphraseContract.TABLE - + " WHERE " + KeyphraseContract.KEY_SOUND_MODEL_ID + " = '" + modelId + "'"; - Cursor c = db.rawQuery(selectQuery, null); - // looping through all rows and adding to list - if (c.moveToFirst()) { - do { - int id = c.getInt(c.getColumnIndex(KeyphraseContract.KEY_ID)); - int modes = c.getInt(c.getColumnIndex(KeyphraseContract.KEY_RECOGNITION_MODES)); - int[] users = getArrayForCommaSeparatedString( - c.getString(c.getColumnIndex(KeyphraseContract.KEY_USERS))); - String locale = c.getString(c.getColumnIndex(KeyphraseContract.KEY_LOCALE)); - String hintText = c.getString(c.getColumnIndex(KeyphraseContract.KEY_HINT_TEXT)); - - // 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"); - continue; - } - boolean isAvailableForCurrentUser = false; - int currentUser = mUserManager.getUserHandle(); - for (int user : users) { - if (currentUser == user) { - isAvailableForCurrentUser = true; - break; + 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))); + String locale = c.getString(c.getColumnIndex(SoundModelContract.KEY_LOCALE)); + String text = c.getString( + c.getColumnIndex(SoundModelContract.KEY_HINT_TEXT)); + + // 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"); + return null; + } + boolean isAvailableForCurrentUser = false; + int currentUser = mUserManager.getUserHandle(); + for (int user : users) { + if (currentUser == user) { + isAvailableForCurrentUser = true; + break; + } + } + if (!isAvailableForCurrentUser) { + Slog.w(TAG, "Ignoring keyphrase since it's not for the current user"); + return null; } - } - if (!isAvailableForCurrentUser) { - Slog.w(TAG, "Ignoring keyphrase since it's not for the current user"); - continue; - } - keyphrases.add(new Keyphrase(id, modes, locale, hintText, users)); - } while (c.moveToNext()); + Keyphrase[] keyphrases = new Keyphrase[1]; + keyphrases[0] = new Keyphrase( + keyphraseId, recognitionModes, locale, text, users); + return new KeyphraseSoundModel(UUID.fromString(modelUuid), data, keyphrases); + } + Slog.w(TAG, "No SoundModel available for the given keyphrase"); + } finally { + c.close(); + db.close(); + } + return null; } - Keyphrase[] keyphraseArr = new Keyphrase[keyphrases.size()]; - keyphrases.toArray(keyphraseArr); - c.close(); - return keyphraseArr; } - private static String getCommaSeparatedString(int[] users) { if (users == null || users.length == 0) { return ""; diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/SoundTriggerHelper.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/SoundTriggerHelper.java index 4430586e083a..86dca79bd9c1 100644 --- a/services/voiceinteraction/java/com/android/server/voiceinteraction/SoundTriggerHelper.java +++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/SoundTriggerHelper.java @@ -38,7 +38,8 @@ import java.util.ArrayList; */ public class SoundTriggerHelper implements SoundTrigger.StatusListener { static final String TAG = "SoundTriggerHelper"; - static final boolean DBG = false; + // TODO: Set to false. + static final boolean DBG = true; // TODO: Remove this. static final int TEMP_KEYPHRASE_ID = 100; diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java index 5d9e1072f4bc..a3d578a5dbb9 100644 --- a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java +++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java @@ -50,6 +50,7 @@ import com.android.server.UiThread; import java.io.FileDescriptor; import java.io.PrintWriter; import java.util.List; +import java.util.UUID; /** @@ -243,34 +244,18 @@ public class VoiceInteractionManagerService extends SystemService { //----------------- Model management APIs --------------------------------// @Override - public List listRegisteredKeyphraseSoundModels( - IVoiceInteractionService service) { - // Allow the call if this is the current voice interaction service - // or the caller holds the MANAGE_VOICE_KEYPHRASES permission. + public KeyphraseSoundModel getKeyphraseSoundModel(int keyphraseId) { synchronized (this) { - boolean permissionGranted = - mContext.checkCallingPermission(Manifest.permission.MANAGE_VOICE_KEYPHRASES) - == PackageManager.PERMISSION_GRANTED; - boolean currentVoiceInteractionService = service != null - && mImpl != null - && mImpl.mService != null - && service.asBinder() == mImpl.mService.asBinder(); - - if (!permissionGranted && !currentVoiceInteractionService) { - if (!currentVoiceInteractionService) { - throw new SecurityException( - "Caller is not the current voice interaction service"); - } - if (!permissionGranted) { - throw new SecurityException("Caller does not hold the permission " - + Manifest.permission.MANAGE_VOICE_KEYPHRASES); - } + if (mContext.checkCallingPermission(Manifest.permission.MANAGE_VOICE_KEYPHRASES) + != PackageManager.PERMISSION_GRANTED) { + throw new SecurityException("Caller does not hold the permission " + + Manifest.permission.MANAGE_VOICE_KEYPHRASES); } } final long caller = Binder.clearCallingIdentity(); try { - return mDbHelper.getKephraseSoundModels(); + return mDbHelper.getKeyphraseSoundModel(keyphraseId); } finally { Binder.restoreCallingIdentity(caller); } @@ -291,15 +276,35 @@ public class VoiceInteractionManagerService extends SystemService { final long caller = Binder.clearCallingIdentity(); try { - boolean success = false; - if (model.keyphrases == null) { - // If the keyphrases are not present in the model, delete the model. - success = mDbHelper.deleteKeyphraseSoundModel(model.uuid); + if (mDbHelper.updateKeyphraseSoundModel(model)) { + synchronized (this) { + // Notify the voice interaction service of a change in sound models. + if (mImpl != null && mImpl.mService != null) { + mImpl.notifySoundModelsChangedLocked(); + } + } + return SoundTriggerHelper.STATUS_OK; } else { - // Else update the model. - success = mDbHelper.addOrUpdateKeyphraseSoundModel(model); + return SoundTriggerHelper.STATUS_ERROR; } - if (success) { + } finally { + Binder.restoreCallingIdentity(caller); + } + } + + @Override + public int deleteKeyphraseSoundModel(int keyphraseId) { + synchronized (this) { + if (mContext.checkCallingPermission(Manifest.permission.MANAGE_VOICE_KEYPHRASES) + != PackageManager.PERMISSION_GRANTED) { + throw new SecurityException("Caller does not hold the permission " + + Manifest.permission.MANAGE_VOICE_KEYPHRASES); + } + } + + final long caller = Binder.clearCallingIdentity(); + try { + if (mDbHelper.deleteKeyphraseSoundModel(keyphraseId)) { synchronized (this) { // Notify the voice interaction service of a change in sound models. if (mImpl != null && mImpl.mService != null) { @@ -317,6 +322,25 @@ public class VoiceInteractionManagerService extends SystemService { //----------------- SoundTrigger APIs --------------------------------// @Override + public boolean isEnrolledForKeyphrase(IVoiceInteractionService service, int keyphraseId) { + synchronized (this) { + if (mImpl == null || mImpl.mService == null + || service.asBinder() != mImpl.mService.asBinder()) { + throw new SecurityException( + "Caller is not the current voice interaction service"); + } + } + + final long caller = Binder.clearCallingIdentity(); + try { + KeyphraseSoundModel model = mDbHelper.getKeyphraseSoundModel(keyphraseId); + return model != null; + } finally { + Binder.restoreCallingIdentity(caller); + } + } + + @Override public ModuleProperties getDspModuleProperties(IVoiceInteractionService service) { // Allow the call if this is the current voice interaction service. synchronized (this) { @@ -337,8 +361,7 @@ public class VoiceInteractionManagerService extends SystemService { @Override public int startRecognition(IVoiceInteractionService service, int keyphraseId, - KeyphraseSoundModel soundModel, IRecognitionStatusCallback callback, - RecognitionConfig recognitionConfig) { + IRecognitionStatusCallback callback, RecognitionConfig recognitionConfig) { // Allow the call if this is the current voice interaction service. synchronized (this) { if (mImpl == null || mImpl.mService == null @@ -347,13 +370,25 @@ public class VoiceInteractionManagerService extends SystemService { "Caller is not the current voice interaction service"); } - final long caller = Binder.clearCallingIdentity(); - try { + if (callback == null || recognitionConfig == null) { + throw new IllegalArgumentException("Illegal argument(s) in startRecognition"); + } + } + + final long caller = Binder.clearCallingIdentity(); + try { + KeyphraseSoundModel soundModel = mDbHelper.getKeyphraseSoundModel(keyphraseId); + if (soundModel == null + || soundModel.uuid == null + || soundModel.keyphrases == null) { + Slog.w(TAG, "No matching sound model found in startRecognition"); + return SoundTriggerHelper.STATUS_ERROR; + } else { return mSoundTriggerHelper.startRecognition( keyphraseId, soundModel, callback, recognitionConfig); - } finally { - Binder.restoreCallingIdentity(caller); } + } finally { + Binder.restoreCallingIdentity(caller); } } @@ -367,13 +402,13 @@ public class VoiceInteractionManagerService extends SystemService { throw new SecurityException( "Caller is not the current voice interaction service"); } + } - final long caller = Binder.clearCallingIdentity(); - try { - return mSoundTriggerHelper.stopRecognition(keyphraseId, callback); - } finally { - Binder.restoreCallingIdentity(caller); - } + final long caller = Binder.clearCallingIdentity(); + try { + return mSoundTriggerHelper.stopRecognition(keyphraseId, callback); + } finally { + Binder.restoreCallingIdentity(caller); } } -- 2.11.0