DatabaseHelper.java revision 110f569b47bc21fb38ec25b6110ee302ce137e06
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 SQLiteDatabase db = getWritableDatabase(); 104 ContentValues values = new ContentValues(); 105 // Generate a random ID for the model. 106 values.put(SoundModelContract.KEY_ID, soundModel.uuid.toString()); 107 values.put(SoundModelContract.KEY_DATA, soundModel.data); 108 values.put(SoundModelContract.KEY_TYPE, SoundTrigger.SoundModel.TYPE_KEYPHRASE); 109 110 boolean status = true; 111 if (db.insertWithOnConflict( 112 SoundModelContract.TABLE, null, values, SQLiteDatabase.CONFLICT_REPLACE) != -1) { 113 for (Keyphrase keyphrase : soundModel.keyphrases) { 114 status &= addOrUpdateKeyphrase(db, soundModel.uuid, keyphrase); 115 } 116 db.close(); 117 return status; 118 } else { 119 Slog.w(TAG, "Failed to persist sound model to database"); 120 db.close(); 121 return false; 122 } 123 } 124 125 private boolean addOrUpdateKeyphrase(SQLiteDatabase db, UUID modelId, Keyphrase keyphrase) { 126 ContentValues values = new ContentValues(); 127 values.put(KeyphraseContract.KEY_ID, keyphrase.id); 128 values.put(KeyphraseContract.KEY_RECOGNITION_MODES, keyphrase.recognitionModes); 129 values.put(KeyphraseContract.KEY_SOUND_MODEL_ID, modelId.toString()); 130 values.put(KeyphraseContract.KEY_HINT_TEXT, keyphrase.text); 131 values.put(KeyphraseContract.KEY_LOCALE, keyphrase.locale); 132 values.put(KeyphraseContract.KEY_USERS, getCommaSeparatedString(keyphrase.users)); 133 if (db.insertWithOnConflict( 134 KeyphraseContract.TABLE, null, values, SQLiteDatabase.CONFLICT_REPLACE) != -1) { 135 return true; 136 } else { 137 Slog.w(TAG, "Failed to persist keyphrase to database"); 138 return false; 139 } 140 } 141 142 /** 143 * Deletes the sound model and associated keyphrases. 144 */ 145 public boolean deleteKeyphraseSoundModel(UUID uuid) { 146 SQLiteDatabase db = getWritableDatabase(); 147 String modelId = uuid.toString(); 148 String soundModelClause = SoundModelContract.KEY_ID + "=" + modelId; 149 boolean status = true; 150 if (db.delete(SoundModelContract.TABLE, soundModelClause, null) == 0) { 151 Slog.w(TAG, "No sound models deleted from the database"); 152 status = false; 153 } 154 String keyphraseClause = KeyphraseContract.KEY_SOUND_MODEL_ID + "=" + modelId; 155 if (db.delete(KeyphraseContract.TABLE, keyphraseClause, null) == 0) { 156 Slog.w(TAG, "No keyphrases deleted from the database"); 157 status = false; 158 } 159 db.close(); 160 return status; 161 } 162 163 /** 164 * Lists all the keyphrase sound models currently registered with the system. 165 */ 166 public List<KeyphraseSoundModel> getKephraseSoundModels() { 167 List<KeyphraseSoundModel> models = new ArrayList<>(); 168 String selectQuery = "SELECT * FROM " + SoundModelContract.TABLE; 169 SQLiteDatabase db = getReadableDatabase(); 170 Cursor c = db.rawQuery(selectQuery, null); 171 172 // looping through all rows and adding to list 173 if (c.moveToFirst()) { 174 do { 175 int type = c.getInt(c.getColumnIndex(SoundModelContract.KEY_TYPE)); 176 if (type != SoundTrigger.SoundModel.TYPE_KEYPHRASE) { 177 // Ignore non-keyphrase sound models. 178 continue; 179 } 180 String id = c.getString(c.getColumnIndex(SoundModelContract.KEY_ID)); 181 byte[] data = c.getBlob(c.getColumnIndex(SoundModelContract.KEY_DATA)); 182 // Get all the keyphrases for this this sound model. 183 // Validate the sound model. 184 if (id == null) { 185 Slog.w(TAG, "Ignoring sound model since it doesn't specify an ID"); 186 continue; 187 } 188 KeyphraseSoundModel model = new KeyphraseSoundModel( 189 UUID.fromString(id), data, getKeyphrasesForSoundModel(db, id)); 190 if (DBG) { 191 Slog.d(TAG, "Adding model: " + model); 192 } 193 models.add(model); 194 } while (c.moveToNext()); 195 } 196 c.close(); 197 db.close(); 198 return models; 199 } 200 201 private Keyphrase[] getKeyphrasesForSoundModel(SQLiteDatabase db, String modelId) { 202 List<Keyphrase> keyphrases = new ArrayList<>(); 203 String selectQuery = "SELECT * FROM " + KeyphraseContract.TABLE 204 + " WHERE " + KeyphraseContract.KEY_SOUND_MODEL_ID + " = '" + modelId + "'"; 205 Cursor c = db.rawQuery(selectQuery, null); 206 207 // looping through all rows and adding to list 208 if (c.moveToFirst()) { 209 do { 210 int id = c.getInt(c.getColumnIndex(KeyphraseContract.KEY_ID)); 211 int modes = c.getInt(c.getColumnIndex(KeyphraseContract.KEY_RECOGNITION_MODES)); 212 int[] users = getArrayForCommaSeparatedString( 213 c.getString(c.getColumnIndex(KeyphraseContract.KEY_USERS))); 214 String locale = c.getString(c.getColumnIndex(KeyphraseContract.KEY_LOCALE)); 215 String hintText = c.getString(c.getColumnIndex(KeyphraseContract.KEY_HINT_TEXT)); 216 217 // Only add keyphrases meant for the current user. 218 if (users == null) { 219 // No users present in the keyphrase. 220 Slog.w(TAG, "Ignoring keyphrase since it doesn't specify users"); 221 continue; 222 } 223 boolean isAvailableForCurrentUser = false; 224 int currentUser = mUserManager.getUserHandle(); 225 for (int user : users) { 226 if (currentUser == user) { 227 isAvailableForCurrentUser = true; 228 break; 229 } 230 } 231 if (!isAvailableForCurrentUser) { 232 Slog.w(TAG, "Ignoring keyphrase since it's not for the current user"); 233 continue; 234 } 235 236 keyphrases.add(new Keyphrase(id, modes, locale, hintText, users)); 237 } while (c.moveToNext()); 238 } 239 Keyphrase[] keyphraseArr = new Keyphrase[keyphrases.size()]; 240 keyphrases.toArray(keyphraseArr); 241 c.close(); 242 return keyphraseArr; 243 } 244 245 246 private String getCommaSeparatedString(int[] users) { 247 if (users == null || users.length == 0) { 248 return ""; 249 } 250 String csv = ""; 251 for (int user : users) { 252 csv += String.valueOf(user); 253 csv += ","; 254 } 255 return csv.substring(0, csv.length() - 1); 256 } 257 258 private int[] getArrayForCommaSeparatedString(String text) { 259 if (TextUtils.isEmpty(text)) { 260 return null; 261 } 262 String[] usersStr = text.split(","); 263 int[] users = new int[usersStr.length]; 264 for (int i = 0; i < usersStr.length; i++) { 265 users[i] = Integer.valueOf(usersStr[i]); 266 } 267 return users; 268 } 269} 270