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 = 4; 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_KEYPHRASE_ID = "keyphrase_id"; 49 public static final String KEY_TYPE = "type"; 50 public static final String KEY_DATA = "data"; 51 public static final String KEY_RECOGNITION_MODES = "recognition_modes"; 52 public static final String KEY_LOCALE = "locale"; 53 public static final String KEY_HINT_TEXT = "hint_text"; 54 public static final String KEY_USERS = "users"; 55 } 56 57 // Table Create Statement 58 private static final String CREATE_TABLE_SOUND_MODEL = "CREATE TABLE " 59 + SoundModelContract.TABLE + "(" 60 + SoundModelContract.KEY_MODEL_UUID + " TEXT PRIMARY KEY," 61 + SoundModelContract.KEY_KEYPHRASE_ID + " INTEGER," 62 + SoundModelContract.KEY_TYPE + " INTEGER," 63 + SoundModelContract.KEY_DATA + " BLOB," 64 + SoundModelContract.KEY_RECOGNITION_MODES + " INTEGER," 65 + SoundModelContract.KEY_LOCALE + " TEXT," 66 + SoundModelContract.KEY_HINT_TEXT + " TEXT," 67 + SoundModelContract.KEY_USERS + " TEXT" + ")"; 68 69 public DatabaseHelper(Context context) { 70 super(context, NAME, null, VERSION); 71 } 72 73 @Override 74 public void onCreate(SQLiteDatabase db) { 75 // creating required tables 76 db.execSQL(CREATE_TABLE_SOUND_MODEL); 77 } 78 79 @Override 80 public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { 81 // TODO: For now, drop older tables and recreate new ones. 82 db.execSQL("DROP TABLE IF EXISTS " + SoundModelContract.TABLE); 83 onCreate(db); 84 } 85 86 /** 87 * Updates the given keyphrase model, adds it, if it doesn't already exist. 88 * 89 * TODO: We only support one keyphrase currently. 90 */ 91 public boolean updateKeyphraseSoundModel(KeyphraseSoundModel soundModel) { 92 synchronized(this) { 93 SQLiteDatabase db = getWritableDatabase(); 94 ContentValues values = new ContentValues(); 95 values.put(SoundModelContract.KEY_MODEL_UUID, soundModel.uuid.toString()); 96 values.put(SoundModelContract.KEY_TYPE, SoundTrigger.SoundModel.TYPE_KEYPHRASE); 97 values.put(SoundModelContract.KEY_DATA, soundModel.data); 98 99 if (soundModel.keyphrases != null && soundModel.keyphrases.length == 1) { 100 values.put(SoundModelContract.KEY_KEYPHRASE_ID, soundModel.keyphrases[0].id); 101 values.put(SoundModelContract.KEY_RECOGNITION_MODES, 102 soundModel.keyphrases[0].recognitionModes); 103 values.put(SoundModelContract.KEY_USERS, 104 getCommaSeparatedString(soundModel.keyphrases[0].users)); 105 values.put(SoundModelContract.KEY_LOCALE, soundModel.keyphrases[0].locale); 106 values.put(SoundModelContract.KEY_HINT_TEXT, soundModel.keyphrases[0].text); 107 try { 108 return db.insertWithOnConflict(SoundModelContract.TABLE, null, values, 109 SQLiteDatabase.CONFLICT_REPLACE) != -1; 110 } finally { 111 db.close(); 112 } 113 } 114 return false; 115 } 116 } 117 118 /** 119 * Deletes the sound model and associated keyphrases. 120 */ 121 public boolean deleteKeyphraseSoundModel(int keyphraseId, int userHandle, String bcp47Locale) { 122 // Sanitize the locale to guard against SQL injection. 123 bcp47Locale = Locale.forLanguageTag(bcp47Locale).toLanguageTag(); 124 synchronized(this) { 125 KeyphraseSoundModel soundModel = getKeyphraseSoundModel(keyphraseId, userHandle, 126 bcp47Locale); 127 if (soundModel == null) { 128 return false; 129 } 130 131 // Delete all sound models for the given keyphrase and specified user. 132 SQLiteDatabase db = getWritableDatabase(); 133 String soundModelClause = SoundModelContract.KEY_MODEL_UUID 134 + "='" + soundModel.uuid.toString() + "'"; 135 try { 136 return db.delete(SoundModelContract.TABLE, soundModelClause, null) != 0; 137 } finally { 138 db.close(); 139 } 140 } 141 } 142 143 /** 144 * Returns a matching {@link KeyphraseSoundModel} for the keyphrase ID. 145 * Returns null if a match isn't found. 146 * 147 * TODO: We only support one keyphrase currently. 148 */ 149 public KeyphraseSoundModel getKeyphraseSoundModel(int keyphraseId, int userHandle, 150 String bcp47Locale) { 151 // Sanitize the locale to guard against SQL injection. 152 bcp47Locale = Locale.forLanguageTag(bcp47Locale).toLanguageTag(); 153 synchronized(this) { 154 // Find the corresponding sound model ID for the keyphrase. 155 String selectQuery = "SELECT * FROM " + SoundModelContract.TABLE 156 + " WHERE " + SoundModelContract.KEY_KEYPHRASE_ID + "= '" + keyphraseId 157 + "' AND " + SoundModelContract.KEY_LOCALE + "='" + bcp47Locale + "'"; 158 SQLiteDatabase db = getReadableDatabase(); 159 Cursor c = db.rawQuery(selectQuery, null); 160 161 try { 162 if (c.moveToFirst()) { 163 do { 164 int type = c.getInt(c.getColumnIndex(SoundModelContract.KEY_TYPE)); 165 if (type != SoundTrigger.SoundModel.TYPE_KEYPHRASE) { 166 if (DBG) { 167 Slog.w(TAG, "Ignoring SoundModel since it's type is incorrect"); 168 } 169 continue; 170 } 171 172 String modelUuid = c.getString( 173 c.getColumnIndex(SoundModelContract.KEY_MODEL_UUID)); 174 if (modelUuid == null) { 175 Slog.w(TAG, "Ignoring SoundModel since it doesn't specify an ID"); 176 continue; 177 } 178 179 byte[] data = c.getBlob(c.getColumnIndex(SoundModelContract.KEY_DATA)); 180 int recognitionModes = c.getInt( 181 c.getColumnIndex(SoundModelContract.KEY_RECOGNITION_MODES)); 182 int[] users = getArrayForCommaSeparatedString( 183 c.getString(c.getColumnIndex(SoundModelContract.KEY_USERS))); 184 String modelLocale = c.getString( 185 c.getColumnIndex(SoundModelContract.KEY_LOCALE)); 186 String text = c.getString( 187 c.getColumnIndex(SoundModelContract.KEY_HINT_TEXT)); 188 189 // Only add keyphrases meant for the current user. 190 if (users == null) { 191 // No users present in the keyphrase. 192 Slog.w(TAG, "Ignoring SoundModel since it doesn't specify users"); 193 continue; 194 } 195 196 boolean isAvailableForCurrentUser = false; 197 for (int user : users) { 198 if (userHandle == user) { 199 isAvailableForCurrentUser = true; 200 break; 201 } 202 } 203 if (!isAvailableForCurrentUser) { 204 if (DBG) { 205 Slog.w(TAG, "Ignoring SoundModel since user handles don't match"); 206 } 207 continue; 208 } else { 209 if (DBG) Slog.d(TAG, "Found a SoundModel for user: " + userHandle); 210 } 211 212 Keyphrase[] keyphrases = new Keyphrase[1]; 213 keyphrases[0] = new Keyphrase( 214 keyphraseId, recognitionModes, modelLocale, text, users); 215 KeyphraseSoundModel model = new KeyphraseSoundModel( 216 UUID.fromString(modelUuid), 217 null /* FIXME use vendor UUID */, data, keyphrases); 218 if (DBG) { 219 Slog.d(TAG, "Found SoundModel for the given keyphrase/locale/user: " 220 + model); 221 } 222 return model; 223 } while (c.moveToNext()); 224 } 225 Slog.w(TAG, "No SoundModel available for the given keyphrase"); 226 } finally { 227 c.close(); 228 db.close(); 229 } 230 return null; 231 } 232 } 233 234 private static String getCommaSeparatedString(int[] users) { 235 if (users == null) { 236 return ""; 237 } 238 StringBuilder sb = new StringBuilder(); 239 for (int i = 0; i < users.length; i++) { 240 if (i != 0) { 241 sb.append(','); 242 } 243 sb.append(users[i]); 244 } 245 return sb.toString(); 246 } 247 248 private static int[] getArrayForCommaSeparatedString(String text) { 249 if (TextUtils.isEmpty(text)) { 250 return null; 251 } 252 String[] usersStr = text.split(","); 253 int[] users = new int[usersStr.length]; 254 for (int i = 0; i < usersStr.length; i++) { 255 users[i] = Integer.parseInt(usersStr[i]); 256 } 257 return users; 258 } 259} 260