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