Merge "CamcorderProfiles: add high speed profile constants" into lmp-dev
diff --git a/core/java/android/os/Parcel.java b/core/java/android/os/Parcel.java
index 95cb9f3..59cd97c 100644
--- a/core/java/android/os/Parcel.java
+++ b/core/java/android/os/Parcel.java
@@ -246,6 +246,7 @@
     private static native void nativeRestoreAllowFds(long nativePtr, boolean lastValue);
 
     private static native void nativeWriteByteArray(long nativePtr, byte[] b, int offset, int len);
+    private static native void nativeWriteBlob(long nativePtr, byte[] b, int offset, int len);
     private static native void nativeWriteInt(long nativePtr, int val);
     private static native void nativeWriteLong(long nativePtr, long val);
     private static native void nativeWriteFloat(long nativePtr, float val);
@@ -255,6 +256,7 @@
     private static native void nativeWriteFileDescriptor(long nativePtr, FileDescriptor val);
 
     private static native byte[] nativeCreateByteArray(long nativePtr);
+    private static native byte[] nativeReadBlob(long nativePtr);
     private static native int nativeReadInt(long nativePtr);
     private static native long nativeReadLong(long nativePtr);
     private static native float nativeReadFloat(long nativePtr);
@@ -479,6 +481,16 @@
     }
 
     /**
+     * Write a blob of data into the parcel at the current {@link #dataPosition},
+     * growing {@link #dataCapacity} if needed.
+     * @param b Bytes to place into the parcel.
+     * {@hide}
+     */
+    public final void writeBlob(byte[] b) {
+        nativeWriteBlob(mNativePtr, b, 0, (b != null) ? b.length : 0);
+    }
+
+    /**
      * Write an integer value into the parcel at the current dataPosition(),
      * growing dataCapacity() if needed.
      */
@@ -1700,6 +1712,14 @@
     }
 
     /**
+     * Read a blob of data from the parcel and return it as a byte array.
+     * {@hide}
+     */
+    public final byte[] readBlob() {
+        return nativeReadBlob(mNativePtr);
+    }
+
+    /**
      * Read and return a String[] object from the parcel.
      * {@hide}
      */
diff --git a/core/java/android/service/voice/AlwaysOnHotwordDetector.java b/core/java/android/service/voice/AlwaysOnHotwordDetector.java
index d685cc5..d077a17 100644
--- a/core/java/android/service/voice/AlwaysOnHotwordDetector.java
+++ b/core/java/android/service/voice/AlwaysOnHotwordDetector.java
@@ -133,10 +133,10 @@
     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 @@
         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 @@
         @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 @@
                     Slog.d(TAG, "Hotword availability changed from " + mAvailability
                             + " -> " + availability);
                 }
+                mIsEnrolledForDetection = enrolled;
                 mAvailability = availability;
-                mEnrolledSoundModel = soundModel;
                 notifyStateChangedLocked();
             }
             return null;
@@ -475,31 +474,14 @@
         /**
          * @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;
         }
     }
 }
diff --git a/core/java/com/android/internal/app/IVoiceInteractionManagerService.aidl b/core/java/com/android/internal/app/IVoiceInteractionManagerService.aidl
index 7d5abd2..22ec4be 100644
--- a/core/java/com/android/internal/app/IVoiceInteractionManagerService.aidl
+++ b/core/java/com/android/internal/app/IVoiceInteractionManagerService.aidl
@@ -33,19 +33,24 @@
     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 @@
      * 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/core/jni/android_os_Parcel.cpp b/core/jni/android_os_Parcel.cpp
index 50f6c73..3ba481e 100644
--- a/core/jni/android_os_Parcel.cpp
+++ b/core/jni/android_os_Parcel.cpp
@@ -187,6 +187,37 @@
     }
 }
 
+static void android_os_Parcel_writeBlob(JNIEnv* env, jclass clazz, jlong nativePtr, jobject data,
+                                        jint offset, jint length) {
+    Parcel* parcel = reinterpret_cast<Parcel*>(nativePtr);
+    if (parcel == NULL) {
+        return;
+    }
+
+    const status_t err = parcel->writeInt32(length);
+    if (err != NO_ERROR) {
+        signalExceptionForError(env, clazz, err);
+        return;
+    }
+
+    android::Parcel::WritableBlob blob;
+    android::status_t err2 = parcel->writeBlob(length, &blob);
+    if (err2 != NO_ERROR) {
+        signalExceptionForError(env, clazz, err2);
+        return;
+    }
+
+    jbyte* ar = (jbyte*)env->GetPrimitiveArrayCritical((jarray)data, 0);
+    if (ar == NULL) {
+        memset(blob.data(), 0, length);
+    } else {
+        memcpy(blob.data(), ar + offset, length);
+        env->ReleasePrimitiveArrayCritical((jarray)data, ar, 0);
+    }
+
+    blob.release();
+}
+
 static void android_os_Parcel_writeInt(JNIEnv* env, jclass clazz, jlong nativePtr, jint val) {
     Parcel* parcel = reinterpret_cast<Parcel*>(nativePtr);
     const status_t err = parcel->writeInt32(val);
@@ -297,6 +328,36 @@
     return ret;
 }
 
+static jbyteArray android_os_Parcel_readBlob(JNIEnv* env, jclass clazz, jlong nativePtr)
+{
+    jbyteArray ret = NULL;
+
+    Parcel* parcel = reinterpret_cast<Parcel*>(nativePtr);
+    if (parcel != NULL) {
+        int32_t len = parcel->readInt32();
+        if (len >= 0) {
+            android::Parcel::ReadableBlob blob;
+            android::status_t err = parcel->readBlob(len, &blob);
+            if (err != NO_ERROR) {
+                signalExceptionForError(env, clazz, err);
+                return NULL;
+            }
+
+            ret = env->NewByteArray(len);
+            if (ret != NULL) {
+                jbyte* a2 = (jbyte*)env->GetPrimitiveArrayCritical(ret, 0);
+                if (a2) {
+                    memcpy(a2, blob.data(), len);
+                    env->ReleasePrimitiveArrayCritical(ret, a2, 0);
+                }
+            }
+            blob.release();
+        }
+    }
+
+    return ret;
+}
+
 static jint android_os_Parcel_readInt(JNIEnv* env, jclass clazz, jlong nativePtr)
 {
     Parcel* parcel = reinterpret_cast<Parcel*>(nativePtr);
@@ -634,6 +695,7 @@
     {"nativeRestoreAllowFds",     "(JZ)V", (void*)android_os_Parcel_restoreAllowFds},
 
     {"nativeWriteByteArray",      "(J[BII)V", (void*)android_os_Parcel_writeNative},
+    {"nativeWriteBlob",           "(J[BII)V", (void*)android_os_Parcel_writeBlob},
     {"nativeWriteInt",            "(JI)V", (void*)android_os_Parcel_writeInt},
     {"nativeWriteLong",           "(JJ)V", (void*)android_os_Parcel_writeLong},
     {"nativeWriteFloat",          "(JF)V", (void*)android_os_Parcel_writeFloat},
@@ -643,6 +705,7 @@
     {"nativeWriteFileDescriptor", "(JLjava/io/FileDescriptor;)V", (void*)android_os_Parcel_writeFileDescriptor},
 
     {"nativeCreateByteArray",     "(J)[B", (void*)android_os_Parcel_createByteArray},
+    {"nativeReadBlob",            "(J)[B", (void*)android_os_Parcel_readBlob},
     {"nativeReadInt",             "(J)I", (void*)android_os_Parcel_readInt},
     {"nativeReadLong",            "(J)J", (void*)android_os_Parcel_readLong},
     {"nativeReadFloat",           "(J)F", (void*)android_os_Parcel_readFloat},
diff --git a/core/res/res/layout/app_permission_item.xml b/core/res/res/layout/app_permission_item.xml
index e2ffffb..1eff3dc 100644
--- a/core/res/res/layout/app_permission_item.xml
+++ b/core/res/res/layout/app_permission_item.xml
@@ -27,8 +27,8 @@
 
     <ImageView
         android:id="@+id/perm_icon"
-        android:layout_width="32dp"
-        android:layout_height="32dp"
+        android:layout_width="24dp"
+        android:layout_height="24dp"
         android:layout_marginStart="16dp"
         android:layout_marginEnd="8dp"
         android:scaleType="fitCenter" />
diff --git a/core/res/res/layout/app_permission_item_money.xml b/core/res/res/layout/app_permission_item_money.xml
index 3fa4653..7e1aca1 100644
--- a/core/res/res/layout/app_permission_item_money.xml
+++ b/core/res/res/layout/app_permission_item_money.xml
@@ -27,8 +27,8 @@
 
     <ImageView
         android:id="@+id/perm_icon"
-        android:layout_width="32dp"
-        android:layout_height="32dp"
+        android:layout_width="24dp"
+        android:layout_height="24dp"
         android:layout_marginStart="16dp"
         android:layout_marginEnd="8dp"
         android:scaleType="fitCenter" />
diff --git a/core/res/res/layout/app_permission_item_old.xml b/core/res/res/layout/app_permission_item_old.xml
index ce0cd42..de6fc4f 100644
--- a/core/res/res/layout/app_permission_item_old.xml
+++ b/core/res/res/layout/app_permission_item_old.xml
@@ -26,8 +26,8 @@
 
     <ImageView
         android:id="@+id/perm_icon"
-        android:layout_width="30dip"
-        android:layout_height="30dip"
+        android:layout_width="24dip"
+        android:layout_height="24dip"
         android:layout_alignParentStart="true"
         android:scaleType="fitCenter" />
 
diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/DatabaseHelper.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/DatabaseHelper.java
index 1e0d6de..8913eb9 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.text.TextUtils;
 import android.util.Slog;
 
-import java.util.ArrayList;
-import java.util.List;
 import java.util.UUID;
 
 /**
@@ -39,43 +37,35 @@
  */
 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 @@
     @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);
-                }
-                db.close();
-                return status;
-            } else {
-                Slog.w(TAG, "Failed to persist sound model to database");
-                db.close();
-                return false;
-            }
-        }
-    }
+            values.put(SoundModelContract.KEY_DATA, soundModel.data);
 
-    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");
+            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();
+                }
+            }
             return false;
         }
     }
@@ -145,111 +122,90 @@
     /**
      * 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 soundModelClause = SoundModelContract.KEY_KEYPHRASE_ID + "=" + keyphraseId;
+
+            try {
+                return db.delete(SoundModelContract.TABLE, soundModelClause, null) != 0;
+            } finally {
+                db.close();
             }
-            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;
-            }
-            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;
+                        return null;
                     }
-                    KeyphraseSoundModel model = new KeyphraseSoundModel(
-                            UUID.fromString(id), data, getKeyphrasesForSoundModelLocked(db, id));
-                    if (DBG) {
-                        Slog.d(TAG, "Adding model: " + model);
+
+                    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;
                     }
-                    models.add(model);
-                } while (c.moveToNext());
+                    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;
+                    }
+
+                    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();
             }
-            c.close();
-            db.close();
-            return models;
+            return null;
         }
     }
 
-    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;
-                    }
-                }
-                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[] 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 4430586..86dca79 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 @@
  */
 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 5d9e107..a3d578a 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 java.io.FileDescriptor;
 import java.io.PrintWriter;
 import java.util.List;
+import java.util.UUID;
 
 
 /**
@@ -243,34 +244,18 @@
         //----------------- 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 @@
 
             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 @@
 
         //----------------- 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 @@
 
         @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 @@
                             "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 @@
                     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);
             }
         }