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