DatabaseHelper.java revision f63bc523eadbe01ce0a5ad52868a5dccb3d5f6dd
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.os.UserManager; 28import android.text.TextUtils; 29import android.util.Slog; 30 31import java.util.ArrayList; 32import java.util.List; 33import java.util.UUID; 34 35/** 36 * Helper to manage the database of the sound models that have been registered on the device. 37 * 38 * @hide 39 */ 40public class DatabaseHelper extends SQLiteOpenHelper { 41 static final String TAG = "SoundModelDBHelper"; 42 static final boolean DBG = false; 43 44 private static final String NAME = "sound_model.db"; 45 private static final int VERSION = 2; 46 47 public static interface KeyphraseContract { 48 public static final String TABLE = "keyphrase"; 49 public static final String KEY_ID = "_id"; 50 public static final String KEY_RECOGNITION_MODES = "modes"; 51 public static final String KEY_LOCALE = "locale"; 52 public static final String KEY_HINT_TEXT = "hint_text"; 53 public static final String KEY_USERS = "users"; 54 public static final String KEY_SOUND_MODEL_ID = "sound_model_id"; 55 } 56 57 public static interface SoundModelContract { 58 public static final String TABLE = "sound_model"; 59 public static final String KEY_ID = "_id"; 60 public static final String KEY_TYPE = "type"; 61 public static final String KEY_DATA = "data"; 62 } 63 64 // Table Create Statements 65 private static final String CREATE_TABLE_KEYPRHASES = "CREATE TABLE " 66 + KeyphraseContract.TABLE + "(" 67 + KeyphraseContract.KEY_ID + " INTEGER PRIMARY KEY," 68 + KeyphraseContract.KEY_RECOGNITION_MODES + " INTEGER," 69 + KeyphraseContract.KEY_USERS + " TEXT," 70 + KeyphraseContract.KEY_SOUND_MODEL_ID + " TEXT," 71 + KeyphraseContract.KEY_LOCALE + " TEXT," 72 + KeyphraseContract.KEY_HINT_TEXT + " TEXT" + ")"; 73 74 private static final String CREATE_TABLE_SOUND_MODEL = "CREATE TABLE " 75 + SoundModelContract.TABLE + "(" 76 + SoundModelContract.KEY_ID + " TEXT PRIMARY KEY," 77 + SoundModelContract.KEY_TYPE + " INTEGER," 78 + SoundModelContract.KEY_DATA + " BLOB" + ")"; 79 80 private final UserManager mUserManager; 81 82 public DatabaseHelper(Context context) { 83 super(context, NAME, null, VERSION); 84 mUserManager = (UserManager) context.getSystemService(Context.USER_SERVICE); 85 } 86 87 @Override 88 public void onCreate(SQLiteDatabase db) { 89 // creating required tables 90 db.execSQL(CREATE_TABLE_KEYPRHASES); 91 db.execSQL(CREATE_TABLE_SOUND_MODEL); 92 } 93 94 @Override 95 public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { 96 // TODO: For now, drop older tables and recreate new ones. 97 db.execSQL("DROP TABLE IF EXISTS " + KeyphraseContract.TABLE); 98 db.execSQL("DROP TABLE IF EXISTS " + SoundModelContract.TABLE); 99 onCreate(db); 100 } 101 102 public boolean addOrUpdateKeyphraseSoundModel(KeyphraseSoundModel soundModel) { 103 synchronized(this) { 104 SQLiteDatabase db = getWritableDatabase(); 105 ContentValues values = new ContentValues(); 106 // Generate a random ID for the model. 107 values.put(SoundModelContract.KEY_ID, soundModel.uuid.toString()); 108 values.put(SoundModelContract.KEY_DATA, soundModel.data); 109 values.put(SoundModelContract.KEY_TYPE, SoundTrigger.SoundModel.TYPE_KEYPHRASE); 110 111 boolean status = true; 112 if (db.insertWithOnConflict(SoundModelContract.TABLE, null, values, 113 SQLiteDatabase.CONFLICT_REPLACE) != -1) { 114 for (Keyphrase keyphrase : soundModel.keyphrases) { 115 status &= addOrUpdateKeyphraseLocked(db, soundModel.uuid, keyphrase); 116 } 117 db.close(); 118 return status; 119 } else { 120 Slog.w(TAG, "Failed to persist sound model to database"); 121 db.close(); 122 return false; 123 } 124 } 125 } 126 127 private boolean addOrUpdateKeyphraseLocked( 128 SQLiteDatabase db, UUID modelId, Keyphrase keyphrase) { 129 ContentValues values = new ContentValues(); 130 values.put(KeyphraseContract.KEY_ID, keyphrase.id); 131 values.put(KeyphraseContract.KEY_RECOGNITION_MODES, keyphrase.recognitionModes); 132 values.put(KeyphraseContract.KEY_SOUND_MODEL_ID, modelId.toString()); 133 values.put(KeyphraseContract.KEY_HINT_TEXT, keyphrase.text); 134 values.put(KeyphraseContract.KEY_LOCALE, keyphrase.locale); 135 values.put(KeyphraseContract.KEY_USERS, getCommaSeparatedString(keyphrase.users)); 136 if (db.insertWithOnConflict( 137 KeyphraseContract.TABLE, null, values, SQLiteDatabase.CONFLICT_REPLACE) != -1) { 138 return true; 139 } else { 140 Slog.w(TAG, "Failed to persist keyphrase to database"); 141 return false; 142 } 143 } 144 145 /** 146 * Deletes the sound model and associated keyphrases. 147 */ 148 public boolean deleteKeyphraseSoundModel(UUID uuid) { 149 synchronized(this) { 150 SQLiteDatabase db = getWritableDatabase(); 151 String modelId = uuid.toString(); 152 String soundModelClause = SoundModelContract.KEY_ID + "=" + modelId; 153 boolean status = true; 154 if (db.delete(SoundModelContract.TABLE, soundModelClause, null) == 0) { 155 Slog.w(TAG, "No sound models deleted from the database"); 156 status = false; 157 } 158 String keyphraseClause = KeyphraseContract.KEY_SOUND_MODEL_ID + "=" + modelId; 159 if (db.delete(KeyphraseContract.TABLE, keyphraseClause, null) == 0) { 160 Slog.w(TAG, "No keyphrases deleted from the database"); 161 status = false; 162 } 163 db.close(); 164 return status; 165 } 166 } 167 168 /** 169 * Lists all the keyphrase sound models currently registered with the system. 170 */ 171 public List<KeyphraseSoundModel> getKephraseSoundModels() { 172 synchronized(this) { 173 List<KeyphraseSoundModel> models = new ArrayList<>(); 174 String selectQuery = "SELECT * FROM " + SoundModelContract.TABLE; 175 SQLiteDatabase db = getReadableDatabase(); 176 Cursor c = db.rawQuery(selectQuery, null); 177 178 // looping through all rows and adding to list 179 if (c.moveToFirst()) { 180 do { 181 int type = c.getInt(c.getColumnIndex(SoundModelContract.KEY_TYPE)); 182 if (type != SoundTrigger.SoundModel.TYPE_KEYPHRASE) { 183 // Ignore non-keyphrase sound models. 184 continue; 185 } 186 String id = c.getString(c.getColumnIndex(SoundModelContract.KEY_ID)); 187 byte[] data = c.getBlob(c.getColumnIndex(SoundModelContract.KEY_DATA)); 188 // Get all the keyphrases for this this sound model. 189 // Validate the sound model. 190 if (id == null) { 191 Slog.w(TAG, "Ignoring sound model since it doesn't specify an ID"); 192 continue; 193 } 194 KeyphraseSoundModel model = new KeyphraseSoundModel( 195 UUID.fromString(id), data, getKeyphrasesForSoundModelLocked(db, id)); 196 if (DBG) { 197 Slog.d(TAG, "Adding model: " + model); 198 } 199 models.add(model); 200 } while (c.moveToNext()); 201 } 202 c.close(); 203 db.close(); 204 return models; 205 } 206 } 207 208 private Keyphrase[] getKeyphrasesForSoundModelLocked(SQLiteDatabase db, String modelId) { 209 List<Keyphrase> keyphrases = new ArrayList<>(); 210 String selectQuery = "SELECT * FROM " + KeyphraseContract.TABLE 211 + " WHERE " + KeyphraseContract.KEY_SOUND_MODEL_ID + " = '" + modelId + "'"; 212 Cursor c = db.rawQuery(selectQuery, null); 213 214 // looping through all rows and adding to list 215 if (c.moveToFirst()) { 216 do { 217 int id = c.getInt(c.getColumnIndex(KeyphraseContract.KEY_ID)); 218 int modes = c.getInt(c.getColumnIndex(KeyphraseContract.KEY_RECOGNITION_MODES)); 219 int[] users = getArrayForCommaSeparatedString( 220 c.getString(c.getColumnIndex(KeyphraseContract.KEY_USERS))); 221 String locale = c.getString(c.getColumnIndex(KeyphraseContract.KEY_LOCALE)); 222 String hintText = c.getString(c.getColumnIndex(KeyphraseContract.KEY_HINT_TEXT)); 223 224 // Only add keyphrases meant for the current user. 225 if (users == null) { 226 // No users present in the keyphrase. 227 Slog.w(TAG, "Ignoring keyphrase since it doesn't specify users"); 228 continue; 229 } 230 boolean isAvailableForCurrentUser = false; 231 int currentUser = mUserManager.getUserHandle(); 232 for (int user : users) { 233 if (currentUser == user) { 234 isAvailableForCurrentUser = true; 235 break; 236 } 237 } 238 if (!isAvailableForCurrentUser) { 239 Slog.w(TAG, "Ignoring keyphrase since it's not for the current user"); 240 continue; 241 } 242 243 keyphrases.add(new Keyphrase(id, modes, locale, hintText, users)); 244 } while (c.moveToNext()); 245 } 246 Keyphrase[] keyphraseArr = new Keyphrase[keyphrases.size()]; 247 keyphrases.toArray(keyphraseArr); 248 c.close(); 249 return keyphraseArr; 250 } 251 252 253 private static String getCommaSeparatedString(int[] users) { 254 if (users == null || users.length == 0) { 255 return ""; 256 } 257 String csv = ""; 258 for (int user : users) { 259 csv += String.valueOf(user); 260 csv += ","; 261 } 262 return csv.substring(0, csv.length() - 1); 263 } 264 265 private static int[] getArrayForCommaSeparatedString(String text) { 266 if (TextUtils.isEmpty(text)) { 267 return null; 268 } 269 String[] usersStr = text.split(","); 270 int[] users = new int[usersStr.length]; 271 for (int i = 0; i < usersStr.length; i++) { 272 users[i] = Integer.valueOf(usersStr[i]); 273 } 274 return users; 275 } 276} 277