OSDN Git Service

Fix various bugs with model management
authorSandeep Siddhartha <sansid@google.com>
Fri, 25 Jul 2014 17:49:34 +0000 (10:49 -0700)
committerSandeep Siddhartha <sansid@google.com>
Sat, 26 Jul 2014 00:11:34 +0000 (17:11 -0700)
- 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

core/java/android/service/voice/AlwaysOnHotwordDetector.java
core/java/com/android/internal/app/IVoiceInteractionManagerService.aidl
services/voiceinteraction/java/com/android/server/voiceinteraction/DatabaseHelper.java
services/voiceinteraction/java/com/android/server/voiceinteraction/SoundTriggerHelper.java
services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java

index d685cc5..d077a17 100644 (file)
@@ -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<KeyphraseSoundModel> 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;
         }
     }
 }
index 7d5abd2..22ec4be 100644 (file)
@@ -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<SoundTrigger.KeyphraseSoundModel> 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.
index 1e0d6de..8913eb9 100644 (file)
@@ -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<KeyphraseSoundModel> getKephraseSoundModels() {
+    public KeyphraseSoundModel getKeyphraseSoundModel(int keyphraseId) {
         synchronized(this) {
-            List<KeyphraseSoundModel> 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<Keyphrase> 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 "";
index 4430586..86dca79 100644 (file)
@@ -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;
 
index 5d9e107..a3d578a 100644 (file)
@@ -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<KeyphraseSoundModel> 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);
             }
         }