add version entry to soundtrigger model database

Bug:147159435
Test: manual hotword trigger with upgraded database
&& dumpsys voiceinteraction

Change-Id: I45497c1159fe879e1de119a18aac8f7ecc2b0686
diff --git a/api/system-current.txt b/api/system-current.txt
index e0f6422..909729d 100755
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -4386,10 +4386,12 @@
   }
 
   public static class SoundTriggerManager.Model {
-    method public static android.media.soundtrigger.SoundTriggerManager.Model create(java.util.UUID, java.util.UUID, byte[]);
-    method public byte[] getModelData();
-    method public java.util.UUID getModelUuid();
-    method public java.util.UUID getVendorUuid();
+    method @NonNull public static android.media.soundtrigger.SoundTriggerManager.Model create(@NonNull java.util.UUID, @NonNull java.util.UUID, @Nullable byte[], int);
+    method @NonNull public static android.media.soundtrigger.SoundTriggerManager.Model create(@NonNull java.util.UUID, @NonNull java.util.UUID, @Nullable byte[]);
+    method @Nullable public byte[] getModelData();
+    method @NonNull public java.util.UUID getModelUuid();
+    method @NonNull public java.util.UUID getVendorUuid();
+    method public int getVersion();
   }
 
 }
diff --git a/core/java/android/hardware/soundtrigger/SoundTrigger.java b/core/java/android/hardware/soundtrigger/SoundTrigger.java
index d872009..6507ecd 100644
--- a/core/java/android/hardware/soundtrigger/SoundTrigger.java
+++ b/core/java/android/hardware/soundtrigger/SoundTrigger.java
@@ -265,16 +265,20 @@
         @NonNull
         public final UUID vendorUuid;
 
+        /** vendor specific version number of the model */
+        public final int version;
+
         /** Opaque data. For use by vendor implementation and enrollment application */
         @UnsupportedAppUsage
         @NonNull
         public final byte[] data;
 
         public SoundModel(@NonNull UUID uuid, @Nullable UUID vendorUuid, int type,
-                @Nullable byte[] data) {
+                @Nullable byte[] data, int version) {
             this.uuid = requireNonNull(uuid);
             this.vendorUuid = vendorUuid != null ? vendorUuid : new UUID(0, 0);
             this.type = type;
+            this.version = version;
             this.data = data != null ? data : new byte[0];
         }
 
@@ -282,6 +286,7 @@
         public int hashCode() {
             final int prime = 31;
             int result = 1;
+            result = prime * result + version;
             result = prime * result + Arrays.hashCode(data);
             result = prime * result + type;
             result = prime * result + ((uuid == null) ? 0 : uuid.hashCode());
@@ -312,6 +317,8 @@
                 return false;
             if (!Arrays.equals(data, other.data))
                 return false;
+            if (version != other.version)
+                return false;
             return true;
         }
     }
@@ -461,14 +468,19 @@
         @NonNull
         public final Keyphrase[] keyphrases; // keyword phrases in model
 
-        @UnsupportedAppUsage
         public KeyphraseSoundModel(
                 @NonNull UUID uuid, @NonNull UUID vendorUuid, @Nullable byte[] data,
-                @Nullable Keyphrase[] keyphrases) {
-            super(uuid, vendorUuid, TYPE_KEYPHRASE, data);
+                @Nullable Keyphrase[] keyphrases, int version) {
+            super(uuid, vendorUuid, TYPE_KEYPHRASE, data, version);
             this.keyphrases = keyphrases != null ? keyphrases : new Keyphrase[0];
         }
 
+        @UnsupportedAppUsage
+        public KeyphraseSoundModel(@NonNull UUID uuid, @NonNull UUID vendorUuid,
+                @Nullable byte[] data, @Nullable Keyphrase[] keyphrases) {
+            this(uuid, vendorUuid, data, keyphrases, -1);
+        }
+
         public static final @android.annotation.NonNull Parcelable.Creator<KeyphraseSoundModel> CREATOR
                 = new Parcelable.Creator<KeyphraseSoundModel>() {
             public KeyphraseSoundModel createFromParcel(Parcel in) {
@@ -487,9 +499,10 @@
             if (length >= 0) {
                 vendorUuid = UUID.fromString(in.readString());
             }
+            int version = in.readInt();
             byte[] data = in.readBlob();
             Keyphrase[] keyphrases = in.createTypedArray(Keyphrase.CREATOR);
-            return new KeyphraseSoundModel(uuid, vendorUuid, data, keyphrases);
+            return new KeyphraseSoundModel(uuid, vendorUuid, data, keyphrases, version);
         }
 
         @Override
@@ -508,13 +521,16 @@
             }
             dest.writeBlob(data);
             dest.writeTypedArray(keyphrases, flags);
+            dest.writeInt(version);
         }
 
         @Override
         public String toString() {
             return "KeyphraseSoundModel [keyphrases=" + Arrays.toString(keyphrases)
                     + ", uuid=" + uuid + ", vendorUuid=" + vendorUuid
-                    + ", type=" + type + ", data=" + (data == null ? 0 : data.length) + "]";
+                    + ", type=" + type
+                    + ", data=" + (data == null ? 0 : data.length)
+                    + ", version=" + version + "]";
         }
 
         @Override
@@ -560,10 +576,15 @@
             }
         };
 
+        public GenericSoundModel(@NonNull UUID uuid, @NonNull UUID vendorUuid,
+                @Nullable byte[] data, int version) {
+            super(uuid, vendorUuid, TYPE_GENERIC_SOUND, data, version);
+        }
+
         @UnsupportedAppUsage
         public GenericSoundModel(@NonNull UUID uuid, @NonNull UUID vendorUuid,
                 @Nullable byte[] data) {
-            super(uuid, vendorUuid, TYPE_GENERIC_SOUND, data);
+            this(uuid, vendorUuid, data, -1);
         }
 
         @Override
@@ -579,7 +600,8 @@
                 vendorUuid = UUID.fromString(in.readString());
             }
             byte[] data = in.readBlob();
-            return new GenericSoundModel(uuid, vendorUuid, data);
+            int version = in.readInt();
+            return new GenericSoundModel(uuid, vendorUuid, data, version);
         }
 
         @Override
@@ -592,12 +614,15 @@
                 dest.writeString(vendorUuid.toString());
             }
             dest.writeBlob(data);
+            dest.writeInt(version);
         }
 
         @Override
         public String toString() {
             return "GenericSoundModel [uuid=" + uuid + ", vendorUuid=" + vendorUuid
-                    + ", type=" + type + ", data=" + (data == null ? 0 : data.length) + "]";
+                    + ", type=" + type
+                    + ", data=" + (data == null ? 0 : data.length)
+                    + ", version=" + version + "]";
         }
     }
 
diff --git a/media/java/android/media/soundtrigger/SoundTriggerManager.java b/media/java/android/media/soundtrigger/SoundTriggerManager.java
index 938ffcd..dd4dac2 100644
--- a/media/java/android/media/soundtrigger/SoundTriggerManager.java
+++ b/media/java/android/media/soundtrigger/SoundTriggerManager.java
@@ -44,6 +44,7 @@
 import com.android.internal.util.Preconditions;
 
 import java.util.HashMap;
+import java.util.Objects;
 import java.util.UUID;
 
 /**
@@ -175,19 +176,40 @@
          * Factory constructor to create a SoundModel instance for use with methods in this
          * class.
          */
-        public static Model create(UUID modelUuid, UUID vendorUuid, byte[] data) {
-            return new Model(new SoundTrigger.GenericSoundModel(modelUuid,
-                        vendorUuid, data));
+        @NonNull
+        public static Model create(@NonNull UUID modelUuid, @NonNull UUID vendorUuid,
+                @Nullable byte[] data, int version) {
+            Objects.requireNonNull(modelUuid);
+            Objects.requireNonNull(vendorUuid);
+            return new Model(new SoundTrigger.GenericSoundModel(modelUuid, vendorUuid, data,
+                    version));
         }
 
+        /**
+         * Factory constructor to create a SoundModel instance for use with methods in this
+         * class.
+         */
+        @NonNull
+        public static Model create(@NonNull UUID modelUuid, @NonNull UUID vendorUuid,
+                @Nullable byte[] data) {
+            return create(modelUuid, vendorUuid, data, -1);
+        }
+
+        @NonNull
         public UUID getModelUuid() {
             return mGenericSoundModel.uuid;
         }
 
+        @NonNull
         public UUID getVendorUuid() {
             return mGenericSoundModel.vendorUuid;
         }
 
+        public int getVersion() {
+            return mGenericSoundModel.version;
+        }
+
+        @Nullable
         public byte[] getModelData() {
             return mGenericSoundModel.data;
         }
diff --git a/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerDbHelper.java b/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerDbHelper.java
index f7cd6a3..9906585 100644
--- a/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerDbHelper.java
+++ b/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerDbHelper.java
@@ -21,12 +21,10 @@
 import android.database.Cursor;
 import android.database.sqlite.SQLiteDatabase;
 import android.database.sqlite.SQLiteOpenHelper;
-import android.hardware.soundtrigger.SoundTrigger;
 import android.hardware.soundtrigger.SoundTrigger.GenericSoundModel;
-import android.text.TextUtils;
 import android.util.Slog;
 
-import java.util.Locale;
+import java.io.PrintWriter;
 import java.util.UUID;
 
 /**
@@ -39,7 +37,7 @@
     static final boolean DBG = false;
 
     private static final String NAME = "st_sound_model.db";
-    private static final int VERSION = 1;
+    private static final int VERSION = 2;
 
     // Sound trigger-based sound models.
     public static interface GenericSoundModelContract {
@@ -47,15 +45,16 @@
         public static final String KEY_MODEL_UUID = "model_uuid";
         public static final String KEY_VENDOR_UUID = "vendor_uuid";
         public static final String KEY_DATA = "data";
+        public static final String KEY_MODEL_VERSION = "model_version";
     }
 
-
     // Table Create Statement for the sound trigger table
     private static final String CREATE_TABLE_ST_SOUND_MODEL = "CREATE TABLE "
             + GenericSoundModelContract.TABLE + "("
             + GenericSoundModelContract.KEY_MODEL_UUID + " TEXT PRIMARY KEY,"
             + GenericSoundModelContract.KEY_VENDOR_UUID + " TEXT,"
-            + GenericSoundModelContract.KEY_DATA + " BLOB" + " )";
+            + GenericSoundModelContract.KEY_DATA + " BLOB,"
+            + GenericSoundModelContract.KEY_MODEL_VERSION + " INTEGER" + " )";
 
 
     public SoundTriggerDbHelper(Context context) {
@@ -70,9 +69,13 @@
 
     @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 " + GenericSoundModelContract.TABLE);
-        onCreate(db);
+        if (oldVersion == 1) {
+            // In version 2, a model version number was added.
+            Slog.d(TAG, "Adding model version column");
+            db.execSQL("ALTER TABLE " + GenericSoundModelContract.TABLE + " ADD COLUMN "
+                    + GenericSoundModelContract.KEY_MODEL_VERSION + " INTEGER DEFAULT -1");
+            oldVersion++;
+        }
     }
 
     /**
@@ -86,6 +89,7 @@
             values.put(GenericSoundModelContract.KEY_MODEL_UUID, soundModel.uuid.toString());
             values.put(GenericSoundModelContract.KEY_VENDOR_UUID, soundModel.vendorUuid.toString());
             values.put(GenericSoundModelContract.KEY_DATA, soundModel.data);
+            values.put(GenericSoundModelContract.KEY_MODEL_VERSION, soundModel.version);
 
             try {
                 return db.insertWithOnConflict(GenericSoundModelContract.TABLE, null, values,
@@ -113,8 +117,10 @@
                                 GenericSoundModelContract.KEY_DATA));
                         String vendor_uuid = c.getString(
                                 c.getColumnIndex(GenericSoundModelContract.KEY_VENDOR_UUID));
+                        int version = c.getInt(
+                                c.getColumnIndex(GenericSoundModelContract.KEY_MODEL_VERSION));
                         return new GenericSoundModel(model_uuid, UUID.fromString(vendor_uuid),
-                                data);
+                                data, version);
                     } while (c.moveToNext());
                 }
             } finally {
@@ -142,4 +148,48 @@
             }
         }
     }
+
+    public void dump(PrintWriter pw) {
+        synchronized(this) {
+            String selectQuery = "SELECT  * FROM " + GenericSoundModelContract.TABLE;
+            SQLiteDatabase db = getReadableDatabase();
+            Cursor c = db.rawQuery(selectQuery, null);
+            try {
+                pw.println("  Enrolled GenericSoundModels:");
+                if (c.moveToFirst()) {
+                    String[] columnNames = c.getColumnNames();
+                    do {
+                        for (String name : columnNames) {
+                            int colNameIndex = c.getColumnIndex(name);
+                            int type = c.getType(colNameIndex);
+                            switch (type) {
+                                case Cursor.FIELD_TYPE_STRING:
+                                    pw.printf("    %s: %s\n", name,
+                                            c.getString(colNameIndex));
+                                    break;
+                                case Cursor.FIELD_TYPE_BLOB:
+                                    pw.printf("    %s: data blob\n", name);
+                                    break;
+                                case Cursor.FIELD_TYPE_INTEGER:
+                                    pw.printf("    %s: %d\n", name,
+                                            c.getInt(colNameIndex));
+                                    break;
+                                case Cursor.FIELD_TYPE_FLOAT:
+                                    pw.printf("    %s: %f\n", name,
+                                            c.getFloat(colNameIndex));
+                                    break;
+                                case Cursor.FIELD_TYPE_NULL:
+                                    pw.printf("    %s: null\n", name);
+                                    break;
+                            }
+                        }
+                        pw.println();
+                    } while (c.moveToNext());
+                }
+            } finally {
+                c.close();
+                db.close();
+            }
+        }
+    }
 }
diff --git a/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerService.java b/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerService.java
index e37755b..767010a 100644
--- a/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerService.java
+++ b/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerService.java
@@ -1495,6 +1495,9 @@
             // log
             sEventLogger.dump(pw);
 
+            // enrolled models
+            mDbHelper.dump(pw);
+
             // stats
             mSoundModelStatTracker.dump(pw);
         }
diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/DatabaseHelper.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/DatabaseHelper.java
index dd7b5a8..c58b6da 100644
--- a/services/voiceinteraction/java/com/android/server/voiceinteraction/DatabaseHelper.java
+++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/DatabaseHelper.java
@@ -27,6 +27,7 @@
 import android.text.TextUtils;
 import android.util.Slog;
 
+import java.io.PrintWriter;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.List;
@@ -43,7 +44,7 @@
     static final boolean DBG = false;
 
     private static final String NAME = "sound_model.db";
-    private static final int VERSION = 6;
+    private static final int VERSION = 7;
 
     public static interface SoundModelContract {
         public static final String TABLE = "sound_model";
@@ -56,6 +57,7 @@
         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_MODEL_VERSION = "model_version";
     }
 
     // Table Create Statement
@@ -70,6 +72,7 @@
             + SoundModelContract.KEY_LOCALE + " TEXT,"
             + SoundModelContract.KEY_HINT_TEXT + " TEXT,"
             + SoundModelContract.KEY_USERS + " TEXT,"
+            + SoundModelContract.KEY_MODEL_VERSION + " INTEGER,"
             + "PRIMARY KEY (" + SoundModelContract.KEY_KEYPHRASE_ID + ","
                               + SoundModelContract.KEY_LOCALE + ","
                               + SoundModelContract.KEY_USERS + ")"
@@ -138,6 +141,13 @@
             }
             oldVersion++;
         }
+        if (oldVersion == 6) {
+            // In version 7, a model version number was added.
+            Slog.d(TAG, "Adding model version column");
+            db.execSQL("ALTER TABLE " + SoundModelContract.TABLE + " ADD COLUMN "
+                    + SoundModelContract.KEY_MODEL_VERSION + " INTEGER DEFAULT -1");
+            oldVersion++;
+        }
     }
 
     /**
@@ -155,6 +165,7 @@
             }
             values.put(SoundModelContract.KEY_TYPE, SoundTrigger.SoundModel.TYPE_KEYPHRASE);
             values.put(SoundModelContract.KEY_DATA, soundModel.data);
+            values.put(SoundModelContract.KEY_MODEL_VERSION, soundModel.version);
 
             if (soundModel.keyphrases != null && soundModel.keyphrases.length == 1) {
                 values.put(SoundModelContract.KEY_KEYPHRASE_ID, soundModel.keyphrases[0].id);
@@ -250,6 +261,8 @@
                                 c.getColumnIndex(SoundModelContract.KEY_LOCALE));
                         String text = c.getString(
                                 c.getColumnIndex(SoundModelContract.KEY_HINT_TEXT));
+                        int version = c.getInt(
+                                c.getColumnIndex(SoundModelContract.KEY_MODEL_VERSION));
 
                         // Only add keyphrases meant for the current user.
                         if (users == null) {
@@ -282,7 +295,7 @@
                             vendorUuid = UUID.fromString(vendorUuidString);
                         }
                         KeyphraseSoundModel model = new KeyphraseSoundModel(
-                                UUID.fromString(modelUuid), vendorUuid, data, keyphrases);
+                                UUID.fromString(modelUuid), vendorUuid, data, keyphrases, version);
                         if (DBG) {
                             Slog.d(TAG, "Found SoundModel for the given keyphrase/locale/user: "
                                     + model);
@@ -325,6 +338,10 @@
         return users;
     }
 
+    /**
+     * SoundModelRecord is no longer used, and it should only be used on database migration.
+     * This class does not need to be modified when modifying the database scheme.
+     */
     private static class SoundModelRecord {
         public final String modelUuid;
         public final String vendorUuid;
@@ -413,4 +430,48 @@
           return a == b;
         }
     }
+
+    public void dump(PrintWriter pw) {
+        synchronized(this) {
+            String selectQuery = "SELECT  * FROM " + SoundModelContract.TABLE;
+            SQLiteDatabase db = getReadableDatabase();
+            Cursor c = db.rawQuery(selectQuery, null);
+            try {
+                pw.println("  Enrolled KeyphraseSoundModels:");
+                if (c.moveToFirst()) {
+                    String[] columnNames = c.getColumnNames();
+                    do {
+                        for (String name : columnNames) {
+                            int colNameIndex = c.getColumnIndex(name);
+                            int type = c.getType(colNameIndex);
+                            switch (type) {
+                                case Cursor.FIELD_TYPE_STRING:
+                                    pw.printf("    %s: %s\n", name,
+                                            c.getString(colNameIndex));
+                                    break;
+                                case Cursor.FIELD_TYPE_BLOB:
+                                    pw.printf("    %s: data blob\n", name);
+                                    break;
+                                case Cursor.FIELD_TYPE_INTEGER:
+                                    pw.printf("    %s: %d\n", name,
+                                            c.getInt(colNameIndex));
+                                    break;
+                                case Cursor.FIELD_TYPE_FLOAT:
+                                    pw.printf("    %s: %f\n", name,
+                                            c.getFloat(colNameIndex));
+                                    break;
+                                case Cursor.FIELD_TYPE_NULL:
+                                    pw.printf("    %s: null\n", name);
+                                    break;
+                            }
+                        }
+                        pw.println();
+                    } while (c.moveToNext());
+                }
+            } finally {
+                c.close();
+                db.close();
+            }
+        }
+    }
 }
diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java
index 06c8074..be4e907 100644
--- a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java
+++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java
@@ -1343,6 +1343,7 @@
                 pw.println("  mCurUserUnlocked: " + mCurUserUnlocked);
                 pw.println("  mCurUserSupported: " + mCurUserSupported);
                 dumpSupportedUsers(pw, "  ");
+                mDbHelper.dump(pw);
                 if (mImpl == null) {
                     pw.println("  (No active implementation)");
                     return;