DatabaseHelper.java revision 174c58187746293144eaa27cf5fc5079db949439
1/** 2 * Copyright (C) 2014 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17package com.android.server.voiceinteraction; 18 19import android.content.ContentValues; 20import android.content.Context; 21import android.database.Cursor; 22import android.database.sqlite.SQLiteDatabase; 23import android.database.sqlite.SQLiteOpenHelper; 24import android.hardware.soundtrigger.SoundTrigger; 25import android.hardware.soundtrigger.SoundTrigger.Keyphrase; 26import android.hardware.soundtrigger.SoundTrigger.KeyphraseSoundModel; 27import android.text.TextUtils; 28import android.util.Slog; 29 30import java.util.Locale; 31import java.util.UUID; 32 33/** 34 * Helper to manage the database of the sound models that have been registered on the device. 35 * 36 * @hide 37 */ 38public class DatabaseHelper extends SQLiteOpenHelper { 39 static final String TAG = "SoundModelDBHelper"; 40 static final boolean DBG = false; 41 42 private static final String NAME = "sound_model.db"; 43 private static final int VERSION = 5; 44 45 public static interface SoundModelContract { 46 public static final String TABLE = "sound_model"; 47 public static final String KEY_MODEL_UUID = "model_uuid"; 48 public static final String KEY_VENDOR_UUID = "vendor_uuid"; 49 public static final String KEY_KEYPHRASE_ID = "keyphrase_id"; 50 public static final String KEY_TYPE = "type"; 51 public static final String KEY_DATA = "data"; 52 public static final String KEY_RECOGNITION_MODES = "recognition_modes"; 53 public static final String KEY_LOCALE = "locale"; 54 public static final String KEY_HINT_TEXT = "hint_text"; 55 public static final String KEY_USERS = "users"; 56 } 57 58 // Table Create Statement 59 private static final String CREATE_TABLE_SOUND_MODEL = "CREATE TABLE " 60 + SoundModelContract.TABLE + "(" 61 + SoundModelContract.KEY_MODEL_UUID + " TEXT PRIMARY KEY," 62 + SoundModelContract.KEY_VENDOR_UUID + " TEXT, " 63 + SoundModelContract.KEY_KEYPHRASE_ID + " INTEGER," 64 + SoundModelContract.KEY_TYPE + " INTEGER," 65 + SoundModelContract.KEY_DATA + " BLOB," 66 + SoundModelContract.KEY_RECOGNITION_MODES + " INTEGER," 67 + SoundModelContract.KEY_LOCALE + " TEXT," 68 + SoundModelContract.KEY_HINT_TEXT + " TEXT," 69 + SoundModelContract.KEY_USERS + " TEXT" + ")"; 70 71 public DatabaseHelper(Context context) { 72 super(context, NAME, null, VERSION); 73 } 74 75 @Override 76 public void onCreate(SQLiteDatabase db) { 77 // creating required tables 78 db.execSQL(CREATE_TABLE_SOUND_MODEL); 79 } 80 81 @Override 82 public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { 83 if (oldVersion < 4) { 84 // For old versions just drop the tables and recreate new ones. 85 db.execSQL("DROP TABLE IF EXISTS " + SoundModelContract.TABLE); 86 onCreate(db); 87 } else { 88 // In the jump to version 5, we added support for the vendor UUID. 89 if (oldVersion == 4) { 90 Slog.d(TAG, "Adding vendor UUID column"); 91 db.execSQL("ALTER TABLE " + SoundModelContract.TABLE + " ADD COLUMN " 92 + SoundModelContract.KEY_VENDOR_UUID + " TEXT"); 93 oldVersion++; 94 } 95 } 96 } 97 98 /** 99 * Updates the given keyphrase model, adds it, if it doesn't already exist. 100 * 101 * TODO: We only support one keyphrase currently. 102 */ 103 public boolean updateKeyphraseSoundModel(KeyphraseSoundModel soundModel) { 104 synchronized(this) { 105 SQLiteDatabase db = getWritableDatabase(); 106 ContentValues values = new ContentValues(); 107 values.put(SoundModelContract.KEY_MODEL_UUID, soundModel.uuid.toString()); 108 if (soundModel.vendorUuid != null) { 109 values.put(SoundModelContract.KEY_VENDOR_UUID, soundModel.vendorUuid.toString()); 110 } 111 values.put(SoundModelContract.KEY_TYPE, SoundTrigger.SoundModel.TYPE_KEYPHRASE); 112 values.put(SoundModelContract.KEY_DATA, soundModel.data); 113 114 if (soundModel.keyphrases != null && soundModel.keyphrases.length == 1) { 115 values.put(SoundModelContract.KEY_KEYPHRASE_ID, soundModel.keyphrases[0].id); 116 values.put(SoundModelContract.KEY_RECOGNITION_MODES, 117 soundModel.keyphrases[0].recognitionModes); 118 values.put(SoundModelContract.KEY_USERS, 119 getCommaSeparatedString(soundModel.keyphrases[0].users)); 120 values.put(SoundModelContract.KEY_LOCALE, soundModel.keyphrases[0].locale); 121 values.put(SoundModelContract.KEY_HINT_TEXT, soundModel.keyphrases[0].text); 122 try { 123 return db.insertWithOnConflict(SoundModelContract.TABLE, null, values, 124 SQLiteDatabase.CONFLICT_REPLACE) != -1; 125 } finally { 126 db.close(); 127 } 128 } 129 return false; 130 } 131 } 132 133 /** 134 * Deletes the sound model and associated keyphrases. 135 */ 136 public boolean deleteKeyphraseSoundModel(int keyphraseId, int userHandle, String bcp47Locale) { 137 // Sanitize the locale to guard against SQL injection. 138 bcp47Locale = Locale.forLanguageTag(bcp47Locale).toLanguageTag(); 139 synchronized(this) { 140 KeyphraseSoundModel soundModel = getKeyphraseSoundModel(keyphraseId, userHandle, 141 bcp47Locale); 142 if (soundModel == null) { 143 return false; 144 } 145 146 // Delete all sound models for the given keyphrase and specified user. 147 SQLiteDatabase db = getWritableDatabase(); 148 String soundModelClause = SoundModelContract.KEY_MODEL_UUID 149 + "='" + soundModel.uuid.toString() + "'"; 150 try { 151 return db.delete(SoundModelContract.TABLE, soundModelClause, null) != 0; 152 } finally { 153 db.close(); 154 } 155 } 156 } 157 158 /** 159 * Returns a matching {@link KeyphraseSoundModel} for the keyphrase ID. 160 * Returns null if a match isn't found. 161 * 162 * TODO: We only support one keyphrase currently. 163 */ 164 public KeyphraseSoundModel getKeyphraseSoundModel(int keyphraseId, int userHandle, 165 String bcp47Locale) { 166 // Sanitize the locale to guard against SQL injection. 167 bcp47Locale = Locale.forLanguageTag(bcp47Locale).toLanguageTag(); 168 synchronized(this) { 169 // Find the corresponding sound model ID for the keyphrase. 170 String selectQuery = "SELECT * FROM " + SoundModelContract.TABLE 171 + " WHERE " + SoundModelContract.KEY_KEYPHRASE_ID + "= '" + keyphraseId 172 + "' AND " + SoundModelContract.KEY_LOCALE + "='" + bcp47Locale + "'"; 173 SQLiteDatabase db = getReadableDatabase(); 174 Cursor c = db.rawQuery(selectQuery, null); 175 176 try { 177 if (c.moveToFirst()) { 178 do { 179 int type = c.getInt(c.getColumnIndex(SoundModelContract.KEY_TYPE)); 180 if (type != SoundTrigger.SoundModel.TYPE_KEYPHRASE) { 181 if (DBG) { 182 Slog.w(TAG, "Ignoring SoundModel since it's type is incorrect"); 183 } 184 continue; 185 } 186 187 String modelUuid = c.getString( 188 c.getColumnIndex(SoundModelContract.KEY_MODEL_UUID)); 189 if (modelUuid == null) { 190 Slog.w(TAG, "Ignoring SoundModel since it doesn't specify an ID"); 191 continue; 192 } 193 194 String vendorUuidString = null; 195 int vendorUuidColumn = c.getColumnIndex(SoundModelContract.KEY_VENDOR_UUID); 196 if (vendorUuidColumn != -1) { 197 vendorUuidString = c.getString(vendorUuidColumn); 198 } 199 byte[] data = c.getBlob(c.getColumnIndex(SoundModelContract.KEY_DATA)); 200 int recognitionModes = c.getInt( 201 c.getColumnIndex(SoundModelContract.KEY_RECOGNITION_MODES)); 202 int[] users = getArrayForCommaSeparatedString( 203 c.getString(c.getColumnIndex(SoundModelContract.KEY_USERS))); 204 String modelLocale = c.getString( 205 c.getColumnIndex(SoundModelContract.KEY_LOCALE)); 206 String text = c.getString( 207 c.getColumnIndex(SoundModelContract.KEY_HINT_TEXT)); 208 209 // Only add keyphrases meant for the current user. 210 if (users == null) { 211 // No users present in the keyphrase. 212 Slog.w(TAG, "Ignoring SoundModel since it doesn't specify users"); 213 continue; 214 } 215 216 boolean isAvailableForCurrentUser = false; 217 for (int user : users) { 218 if (userHandle == user) { 219 isAvailableForCurrentUser = true; 220 break; 221 } 222 } 223 if (!isAvailableForCurrentUser) { 224 if (DBG) { 225 Slog.w(TAG, "Ignoring SoundModel since user handles don't match"); 226 } 227 continue; 228 } else { 229 if (DBG) Slog.d(TAG, "Found a SoundModel for user: " + userHandle); 230 } 231 232 Keyphrase[] keyphrases = new Keyphrase[1]; 233 keyphrases[0] = new Keyphrase( 234 keyphraseId, recognitionModes, modelLocale, text, users); 235 UUID vendorUuid = null; 236 if (vendorUuidString != null) { 237 vendorUuid = UUID.fromString(vendorUuidString); 238 } 239 KeyphraseSoundModel model = new KeyphraseSoundModel( 240 UUID.fromString(modelUuid), vendorUuid, data, keyphrases); 241 if (DBG) { 242 Slog.d(TAG, "Found SoundModel for the given keyphrase/locale/user: " 243 + model); 244 } 245 return model; 246 } while (c.moveToNext()); 247 } 248 Slog.w(TAG, "No SoundModel available for the given keyphrase"); 249 } finally { 250 c.close(); 251 db.close(); 252 } 253 return null; 254 } 255 } 256 257 private static String getCommaSeparatedString(int[] users) { 258 if (users == null) { 259 return ""; 260 } 261 StringBuilder sb = new StringBuilder(); 262 for (int i = 0; i < users.length; i++) { 263 if (i != 0) { 264 sb.append(','); 265 } 266 sb.append(users[i]); 267 } 268 return sb.toString(); 269 } 270 271 private static int[] getArrayForCommaSeparatedString(String text) { 272 if (TextUtils.isEmpty(text)) { 273 return null; 274 } 275 String[] usersStr = text.split(","); 276 int[] users = new int[usersStr.length]; 277 for (int i = 0; i < usersStr.length; i++) { 278 users[i] = Integer.parseInt(usersStr[i]); 279 } 280 return users; 281 } 282} 283