blob: 5ca74711ddbbce5bd32e5bc3ab5fb36db495e5fc [file] [log] [blame]
Fyodor Kupolov1ce01612016-08-26 11:39:07 -07001/*
2 * Copyright (C) 2016 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.accounts;
18
19import android.accounts.Account;
20import android.content.ContentValues;
21import android.content.Context;
22import android.database.Cursor;
23import android.database.DatabaseUtils;
24import android.database.sqlite.SQLiteDatabase;
25import android.database.sqlite.SQLiteOpenHelper;
26import android.database.sqlite.SQLiteStatement;
27import android.os.FileUtils;
28import android.text.TextUtils;
29import android.util.Log;
30import android.util.Pair;
31import android.util.Slog;
32
33import java.io.File;
34import java.io.IOException;
35import java.io.PrintWriter;
36import java.util.ArrayList;
37import java.util.Collections;
38import java.util.HashMap;
39import java.util.LinkedHashMap;
40import java.util.List;
41import java.util.Map;
42
43/**
44 * Persistence layer abstraction for accessing accounts_ce/accounts_de databases.
Fyodor Kupolov00de49e2016-09-23 13:10:27 -070045 *
46 * <p>At first, CE database needs to be {@link #attachCeDatabase(File) attached to DE},
47 * in order for the tables to be available. All operations with CE database are done through the
48 * connection to the DE database, to which it is attached. This approach allows atomic
49 * transactions across two databases</p>
Fyodor Kupolov1ce01612016-08-26 11:39:07 -070050 */
Fyodor Kupolov00de49e2016-09-23 13:10:27 -070051class AccountsDb implements AutoCloseable {
Fyodor Kupolov1ce01612016-08-26 11:39:07 -070052 private static final String TAG = "AccountsDb";
53
54 private static final String DATABASE_NAME = "accounts.db";
55 private static final int PRE_N_DATABASE_VERSION = 9;
56 private static final int CE_DATABASE_VERSION = 10;
Dmitry Dementyeve5768622016-11-21 13:51:27 -080057 private static final int DE_DATABASE_VERSION = 2; // Added visibility support in O.
Fyodor Kupolov1ce01612016-08-26 11:39:07 -070058
59
60 static final String TABLE_ACCOUNTS = "accounts";
61 private static final String ACCOUNTS_ID = "_id";
62 private static final String ACCOUNTS_NAME = "name";
63 private static final String ACCOUNTS_TYPE = "type";
64 private static final String ACCOUNTS_TYPE_COUNT = "count(type)";
65 private static final String ACCOUNTS_PASSWORD = "password";
66 private static final String ACCOUNTS_PREVIOUS_NAME = "previous_name";
67 private static final String ACCOUNTS_LAST_AUTHENTICATE_TIME_EPOCH_MILLIS =
68 "last_password_entry_time_millis_epoch";
69
70 private static final String TABLE_AUTHTOKENS = "authtokens";
71 private static final String AUTHTOKENS_ID = "_id";
72 private static final String AUTHTOKENS_ACCOUNTS_ID = "accounts_id";
73 private static final String AUTHTOKENS_TYPE = "type";
74 private static final String AUTHTOKENS_AUTHTOKEN = "authtoken";
75
Dmitry Dementyeve5768622016-11-21 13:51:27 -080076 private static final String TABLE_VISIBILITY = "visibility";
77 private static final String VISIBILITY_ACCOUNTS_ID = "accounts_id";
78 private static final String VISIBILITY_UID = "_uid";
79 private static final String VISIBILITY_VALUE = "value";
80
Fyodor Kupolov1ce01612016-08-26 11:39:07 -070081 private static final String TABLE_GRANTS = "grants";
82 private static final String GRANTS_ACCOUNTS_ID = "accounts_id";
83 private static final String GRANTS_AUTH_TOKEN_TYPE = "auth_token_type";
84 private static final String GRANTS_GRANTEE_UID = "uid";
85
86 private static final String TABLE_EXTRAS = "extras";
87 private static final String EXTRAS_ID = "_id";
88 private static final String EXTRAS_ACCOUNTS_ID = "accounts_id";
89 private static final String EXTRAS_KEY = "key";
90 private static final String EXTRAS_VALUE = "value";
91
92 private static final String TABLE_META = "meta";
93 private static final String META_KEY = "key";
94 private static final String META_VALUE = "value";
95
96 static final String TABLE_SHARED_ACCOUNTS = "shared_accounts";
97 private static final String SHARED_ACCOUNTS_ID = "_id";
98
99 private static String TABLE_DEBUG = "debug_table";
100
101 // Columns for debug_table table
102 private static String DEBUG_TABLE_ACTION_TYPE = "action_type";
103 private static String DEBUG_TABLE_TIMESTAMP = "time";
104 private static String DEBUG_TABLE_CALLER_UID = "caller_uid";
105 private static String DEBUG_TABLE_TABLE_NAME = "table_name";
106 private static String DEBUG_TABLE_KEY = "primary_key";
107
108 // These actions correspond to the occurrence of real actions. Since
109 // these are called by the authenticators, the uid associated will be
110 // of the authenticator.
111 static String DEBUG_ACTION_SET_PASSWORD = "action_set_password";
112 static String DEBUG_ACTION_CLEAR_PASSWORD = "action_clear_password";
113 static String DEBUG_ACTION_ACCOUNT_ADD = "action_account_add";
114 static String DEBUG_ACTION_ACCOUNT_REMOVE = "action_account_remove";
115 static String DEBUG_ACTION_ACCOUNT_REMOVE_DE = "action_account_remove_de";
116 static String DEBUG_ACTION_AUTHENTICATOR_REMOVE = "action_authenticator_remove";
117 static String DEBUG_ACTION_ACCOUNT_RENAME = "action_account_rename";
118
119 // These actions don't necessarily correspond to any action on
120 // accountDb taking place. As an example, there might be a request for
121 // addingAccount, which might not lead to addition of account on grounds
122 // of bad authentication. We will still be logging it to keep track of
123 // who called.
124 static String DEBUG_ACTION_CALLED_ACCOUNT_ADD = "action_called_account_add";
125 static String DEBUG_ACTION_CALLED_ACCOUNT_REMOVE = "action_called_account_remove";
126 static String DEBUG_ACTION_SYNC_DE_CE_ACCOUNTS = "action_sync_de_ce_accounts";
127
128 //This action doesn't add account to accountdb. Account is only
129 // added in finishSession which may be in a different user profile.
130 static String DEBUG_ACTION_CALLED_START_ACCOUNT_ADD = "action_called_start_account_add";
131 static String DEBUG_ACTION_CALLED_ACCOUNT_SESSION_FINISH =
132 "action_called_account_session_finish";
133
134 static final String CE_DATABASE_NAME = "accounts_ce.db";
135 static final String DE_DATABASE_NAME = "accounts_de.db";
136 private static final String CE_DB_PREFIX = "ceDb.";
137 private static final String CE_TABLE_ACCOUNTS = CE_DB_PREFIX + TABLE_ACCOUNTS;
138 private static final String CE_TABLE_AUTHTOKENS = CE_DB_PREFIX + TABLE_AUTHTOKENS;
139 private static final String CE_TABLE_EXTRAS = CE_DB_PREFIX + TABLE_EXTRAS;
140
Fyodor Kupolov00de49e2016-09-23 13:10:27 -0700141 static final int MAX_DEBUG_DB_SIZE = 64;
142
Fyodor Kupolov1ce01612016-08-26 11:39:07 -0700143 private static final String[] ACCOUNT_TYPE_COUNT_PROJECTION =
144 new String[] { ACCOUNTS_TYPE, ACCOUNTS_TYPE_COUNT};
145
146 private static final String COUNT_OF_MATCHING_GRANTS = ""
147 + "SELECT COUNT(*) FROM " + TABLE_GRANTS + ", " + TABLE_ACCOUNTS
148 + " WHERE " + GRANTS_ACCOUNTS_ID + "=" + ACCOUNTS_ID
149 + " AND " + GRANTS_GRANTEE_UID + "=?"
150 + " AND " + GRANTS_AUTH_TOKEN_TYPE + "=?"
151 + " AND " + ACCOUNTS_NAME + "=?"
152 + " AND " + ACCOUNTS_TYPE + "=?";
153
154 private static final String COUNT_OF_MATCHING_GRANTS_ANY_TOKEN = ""
155 + "SELECT COUNT(*) FROM " + TABLE_GRANTS + ", " + TABLE_ACCOUNTS
156 + " WHERE " + GRANTS_ACCOUNTS_ID + "=" + ACCOUNTS_ID
157 + " AND " + GRANTS_GRANTEE_UID + "=?"
158 + " AND " + ACCOUNTS_NAME + "=?"
159 + " AND " + ACCOUNTS_TYPE + "=?";
160
Dmitry Dementyeve5768622016-11-21 13:51:27 -0800161 private static final String SELECTION_ACCOUNTS_ID_BY_ACCOUNT =
162 "accounts_id=(select _id FROM accounts WHERE name=? AND type=?)";
Fyodor Kupolov1ce01612016-08-26 11:39:07 -0700163
Dmitry Dementyeve5768622016-11-21 13:51:27 -0800164 private static final String[] COLUMNS_AUTHTOKENS_TYPE_AND_AUTHTOKEN =
165 {AUTHTOKENS_TYPE, AUTHTOKENS_AUTHTOKEN};
Fyodor Kupolov1ce01612016-08-26 11:39:07 -0700166
Fyodor Kupolov1ce01612016-08-26 11:39:07 -0700167 private static final String[] COLUMNS_EXTRAS_KEY_AND_VALUE = {EXTRAS_KEY, EXTRAS_VALUE};
168
169 private static final String ACCOUNT_ACCESS_GRANTS = ""
170 + "SELECT " + AccountsDb.ACCOUNTS_NAME + ", "
171 + AccountsDb.GRANTS_GRANTEE_UID
172 + " FROM " + AccountsDb.TABLE_ACCOUNTS
173 + ", " + AccountsDb.TABLE_GRANTS
174 + " WHERE " + AccountsDb.GRANTS_ACCOUNTS_ID
175 + "=" + AccountsDb.ACCOUNTS_ID;
176
177 private static final String META_KEY_FOR_AUTHENTICATOR_UID_FOR_TYPE_PREFIX =
178 "auth_uid_for_type:";
179 private static final String META_KEY_DELIMITER = ":";
180 private static final String SELECTION_META_BY_AUTHENTICATOR_TYPE = META_KEY + " LIKE ?";
181
Fyodor Kupolov00de49e2016-09-23 13:10:27 -0700182 private final DeDatabaseHelper mDeDatabase;
183 private final Context mContext;
184 private final File mPreNDatabaseFile;
185
186 AccountsDb(DeDatabaseHelper deDatabase, Context context, File preNDatabaseFile) {
187 mDeDatabase = deDatabase;
188 mContext = context;
189 mPreNDatabaseFile = preNDatabaseFile;
190 }
191
192 private static class CeDatabaseHelper extends SQLiteOpenHelper {
Fyodor Kupolov1ce01612016-08-26 11:39:07 -0700193
194 CeDatabaseHelper(Context context, String ceDatabaseName) {
195 super(context, ceDatabaseName, null, CE_DATABASE_VERSION);
196 }
197
198 /**
199 * This call needs to be made while the mCacheLock is held.
200 * @param db The database.
201 */
202 @Override
203 public void onCreate(SQLiteDatabase db) {
204 Log.i(TAG, "Creating CE database " + getDatabaseName());
205 db.execSQL("CREATE TABLE " + TABLE_ACCOUNTS + " ( "
206 + ACCOUNTS_ID + " INTEGER PRIMARY KEY AUTOINCREMENT, "
207 + ACCOUNTS_NAME + " TEXT NOT NULL, "
208 + ACCOUNTS_TYPE + " TEXT NOT NULL, "
209 + ACCOUNTS_PASSWORD + " TEXT, "
210 + "UNIQUE(" + ACCOUNTS_NAME + "," + ACCOUNTS_TYPE + "))");
211
212 db.execSQL("CREATE TABLE " + TABLE_AUTHTOKENS + " ( "
213 + AUTHTOKENS_ID + " INTEGER PRIMARY KEY AUTOINCREMENT, "
214 + AUTHTOKENS_ACCOUNTS_ID + " INTEGER NOT NULL, "
215 + AUTHTOKENS_TYPE + " TEXT NOT NULL, "
216 + AUTHTOKENS_AUTHTOKEN + " TEXT, "
217 + "UNIQUE (" + AUTHTOKENS_ACCOUNTS_ID + "," + AUTHTOKENS_TYPE + "))");
218
219 db.execSQL("CREATE TABLE " + TABLE_EXTRAS + " ( "
220 + EXTRAS_ID + " INTEGER PRIMARY KEY AUTOINCREMENT, "
221 + EXTRAS_ACCOUNTS_ID + " INTEGER, "
222 + EXTRAS_KEY + " TEXT NOT NULL, "
223 + EXTRAS_VALUE + " TEXT, "
224 + "UNIQUE(" + EXTRAS_ACCOUNTS_ID + "," + EXTRAS_KEY + "))");
225
226 createAccountsDeletionTrigger(db);
227 }
228
229 private void createAccountsDeletionTrigger(SQLiteDatabase db) {
230 db.execSQL(""
231 + " CREATE TRIGGER " + TABLE_ACCOUNTS + "Delete DELETE ON " + TABLE_ACCOUNTS
232 + " BEGIN"
233 + " DELETE FROM " + TABLE_AUTHTOKENS
234 + " WHERE " + AUTHTOKENS_ACCOUNTS_ID + "=OLD." + ACCOUNTS_ID + " ;"
235 + " DELETE FROM " + TABLE_EXTRAS
236 + " WHERE " + EXTRAS_ACCOUNTS_ID + "=OLD." + ACCOUNTS_ID + " ;"
237 + " END");
238 }
239
240 @Override
241 public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
242 Log.i(TAG, "Upgrade CE from version " + oldVersion + " to version " + newVersion);
243
244 if (oldVersion == 9) {
245 if (Log.isLoggable(TAG, Log.VERBOSE)) {
246 Log.v(TAG, "onUpgrade upgrading to v10");
247 }
248 db.execSQL("DROP TABLE IF EXISTS " + TABLE_META);
249 db.execSQL("DROP TABLE IF EXISTS " + TABLE_SHARED_ACCOUNTS);
250 // Recreate the trigger, since the old one references the table to be removed
251 db.execSQL("DROP TRIGGER IF EXISTS " + TABLE_ACCOUNTS + "Delete");
252 createAccountsDeletionTrigger(db);
253 db.execSQL("DROP TABLE IF EXISTS " + TABLE_GRANTS);
254 db.execSQL("DROP TABLE IF EXISTS " + TABLE_DEBUG);
255 oldVersion ++;
256 }
257
258 if (oldVersion != newVersion) {
259 Log.e(TAG, "failed to upgrade version " + oldVersion + " to version " + newVersion);
260 }
261 }
262
263 @Override
264 public void onOpen(SQLiteDatabase db) {
265 if (Log.isLoggable(TAG, Log.VERBOSE)) Log.v(TAG, "opened database " + CE_DATABASE_NAME);
266 }
267
268
269 /**
270 * Creates a new {@code CeDatabaseHelper}. If pre-N db file is present at the old location,
271 * it also performs migration to the new CE database.
Fyodor Kupolov1ce01612016-08-26 11:39:07 -0700272 */
273 static CeDatabaseHelper create(
274 Context context,
Fyodor Kupolov1ce01612016-08-26 11:39:07 -0700275 File preNDatabaseFile,
276 File ceDatabaseFile) {
277 boolean newDbExists = ceDatabaseFile.exists();
278 if (Log.isLoggable(TAG, Log.VERBOSE)) {
Fyodor Kupolov00de49e2016-09-23 13:10:27 -0700279 Log.v(TAG, "CeDatabaseHelper.create ceDatabaseFile=" + ceDatabaseFile
280 + " oldDbExists=" + preNDatabaseFile.exists()
281 + " newDbExists=" + newDbExists);
Fyodor Kupolov1ce01612016-08-26 11:39:07 -0700282 }
283 boolean removeOldDb = false;
284 if (!newDbExists && preNDatabaseFile.exists()) {
285 removeOldDb = migratePreNDbToCe(preNDatabaseFile, ceDatabaseFile);
286 }
287 // Try to open and upgrade if necessary
288 CeDatabaseHelper ceHelper = new CeDatabaseHelper(context, ceDatabaseFile.getPath());
289 ceHelper.getWritableDatabase();
290 ceHelper.close();
291 if (removeOldDb) {
292 Slog.i(TAG, "Migration complete - removing pre-N db " + preNDatabaseFile);
293 if (!SQLiteDatabase.deleteDatabase(preNDatabaseFile)) {
294 Slog.e(TAG, "Cannot remove pre-N db " + preNDatabaseFile);
295 }
296 }
297 return ceHelper;
298 }
299
300 private static boolean migratePreNDbToCe(File oldDbFile, File ceDbFile) {
301 Slog.i(TAG, "Moving pre-N DB " + oldDbFile + " to CE " + ceDbFile);
302 try {
303 FileUtils.copyFileOrThrow(oldDbFile, ceDbFile);
304 } catch (IOException e) {
305 Slog.e(TAG, "Cannot copy file to " + ceDbFile + " from " + oldDbFile, e);
306 // Try to remove potentially damaged file if I/O error occurred
307 deleteDbFileWarnIfFailed(ceDbFile);
308 return false;
309 }
310 return true;
311 }
Fyodor Kupolov1ce01612016-08-26 11:39:07 -0700312 }
313
Fyodor Kupolov00de49e2016-09-23 13:10:27 -0700314 /**
315 * Returns information about auth tokens and their account for the specified query
316 * parameters.
317 * Output is in the format:
318 * <pre><code> | AUTHTOKEN_ID | ACCOUNT_NAME | AUTH_TOKEN_TYPE |</code></pre>
319 */
320 Cursor findAuthtokenForAllAccounts(String accountType, String authToken) {
321 SQLiteDatabase db = mDeDatabase.getReadableDatabaseUserIsUnlocked();
322 return db.rawQuery(
323 "SELECT " + CE_TABLE_AUTHTOKENS + "." + AUTHTOKENS_ID
324 + ", " + CE_TABLE_ACCOUNTS + "." + ACCOUNTS_NAME
325 + ", " + CE_TABLE_AUTHTOKENS + "." + AUTHTOKENS_TYPE
326 + " FROM " + CE_TABLE_ACCOUNTS
327 + " JOIN " + CE_TABLE_AUTHTOKENS
328 + " ON " + CE_TABLE_ACCOUNTS + "." + ACCOUNTS_ID
329 + " = " + CE_TABLE_AUTHTOKENS + "." + AUTHTOKENS_ACCOUNTS_ID
330 + " WHERE " + CE_TABLE_AUTHTOKENS + "." + AUTHTOKENS_AUTHTOKEN
331 + " = ? AND " + CE_TABLE_ACCOUNTS + "." + ACCOUNTS_TYPE + " = ?",
332 new String[]{authToken, accountType});
333 }
334
335 Map<String, String> findAuthTokensByAccount(Account account) {
336 SQLiteDatabase db = mDeDatabase.getReadableDatabaseUserIsUnlocked();
337 HashMap<String, String> authTokensForAccount = new HashMap<>();
338 Cursor cursor = db.query(CE_TABLE_AUTHTOKENS,
339 COLUMNS_AUTHTOKENS_TYPE_AND_AUTHTOKEN,
Dmitry Dementyeve5768622016-11-21 13:51:27 -0800340 SELECTION_ACCOUNTS_ID_BY_ACCOUNT,
Fyodor Kupolov00de49e2016-09-23 13:10:27 -0700341 new String[] {account.name, account.type},
342 null, null, null);
343 try {
344 while (cursor.moveToNext()) {
345 final String type = cursor.getString(0);
346 final String authToken = cursor.getString(1);
347 authTokensForAccount.put(type, authToken);
348 }
349 } finally {
350 cursor.close();
351 }
352 return authTokensForAccount;
353 }
354
355 boolean deleteAuthtokensByAccountIdAndType(long accountId, String authtokenType) {
356 SQLiteDatabase db = mDeDatabase.getWritableDatabaseUserIsUnlocked();
357 return db.delete(CE_TABLE_AUTHTOKENS,
Dmitry Dementyev2e22cfb2017-01-09 18:42:14 +0000358 AUTHTOKENS_ACCOUNTS_ID + "=?" + accountId + " AND " + AUTHTOKENS_TYPE + "=?",
Fyodor Kupolov00de49e2016-09-23 13:10:27 -0700359 new String[]{String.valueOf(accountId), authtokenType}) > 0;
360 }
361
362 boolean deleteAuthToken(String authTokenId) {
363 SQLiteDatabase db = mDeDatabase.getWritableDatabaseUserIsUnlocked();
364 return db.delete(
365 CE_TABLE_AUTHTOKENS, AUTHTOKENS_ID + "= ?",
366 new String[]{authTokenId}) > 0;
367 }
368
369 long insertAuthToken(long accountId, String authTokenType, String authToken) {
370 SQLiteDatabase db = mDeDatabase.getWritableDatabaseUserIsUnlocked();
371 ContentValues values = new ContentValues();
372 values.put(AUTHTOKENS_ACCOUNTS_ID, accountId);
373 values.put(AUTHTOKENS_TYPE, authTokenType);
374 values.put(AUTHTOKENS_AUTHTOKEN, authToken);
375 return db.insert(
376 CE_TABLE_AUTHTOKENS, AUTHTOKENS_AUTHTOKEN, values);
377 }
378
379 int updateCeAccountPassword(long accountId, String password) {
380 SQLiteDatabase db = mDeDatabase.getWritableDatabaseUserIsUnlocked();
381 final ContentValues values = new ContentValues();
382 values.put(ACCOUNTS_PASSWORD, password);
383 return db.update(
384 CE_TABLE_ACCOUNTS, values, ACCOUNTS_ID + "=?",
385 new String[] {String.valueOf(accountId)});
386 }
387
388 boolean renameCeAccount(long accountId, String newName) {
389 SQLiteDatabase db = mDeDatabase.getWritableDatabaseUserIsUnlocked();
390 final ContentValues values = new ContentValues();
391 values.put(ACCOUNTS_NAME, newName);
392 final String[] argsAccountId = {String.valueOf(accountId)};
393 return db.update(
394 CE_TABLE_ACCOUNTS, values, ACCOUNTS_ID + "=?", argsAccountId) > 0;
395 }
396
397 boolean deleteAuthTokensByAccountId(long accountId) {
398 SQLiteDatabase db = mDeDatabase.getWritableDatabaseUserIsUnlocked();
399 return db.delete(CE_TABLE_AUTHTOKENS, AUTHTOKENS_ACCOUNTS_ID + "=?",
400 new String[] {String.valueOf(accountId)}) > 0;
401 }
402
403 long findExtrasIdByAccountId(long accountId, String key) {
404 SQLiteDatabase db = mDeDatabase.getReadableDatabaseUserIsUnlocked();
405 Cursor cursor = db.query(
406 CE_TABLE_EXTRAS, new String[]{EXTRAS_ID},
407 EXTRAS_ACCOUNTS_ID + "=" + accountId + " AND " + EXTRAS_KEY + "=?",
408 new String[]{key}, null, null, null);
409 try {
410 if (cursor.moveToNext()) {
411 return cursor.getLong(0);
412 }
413 return -1;
414 } finally {
415 cursor.close();
416 }
417 }
418
419 boolean updateExtra(long extrasId, String value) {
420 SQLiteDatabase db = mDeDatabase.getWritableDatabaseUserIsUnlocked();
421 ContentValues values = new ContentValues();
422 values.put(EXTRAS_VALUE, value);
423 int rows = db.update(
424 TABLE_EXTRAS, values, EXTRAS_ID + "=?",
425 new String[]{String.valueOf(extrasId)});
426 return rows == 1;
427 }
428
429 long insertExtra(long accountId, String key, String value) {
430 SQLiteDatabase db = mDeDatabase.getWritableDatabaseUserIsUnlocked();
431 ContentValues values = new ContentValues();
432 values.put(EXTRAS_KEY, key);
433 values.put(EXTRAS_ACCOUNTS_ID, accountId);
434 values.put(EXTRAS_VALUE, value);
435 return db.insert(CE_TABLE_EXTRAS, EXTRAS_KEY, values);
436 }
437
438 Map<String, String> findUserExtrasForAccount(Account account) {
439 SQLiteDatabase db = mDeDatabase.getReadableDatabaseUserIsUnlocked();
440 Map<String, String> userExtrasForAccount = new HashMap<>();
441 String[] selectionArgs = {account.name, account.type};
442 try (Cursor cursor = db.query(CE_TABLE_EXTRAS,
443 COLUMNS_EXTRAS_KEY_AND_VALUE,
Dmitry Dementyeve5768622016-11-21 13:51:27 -0800444 SELECTION_ACCOUNTS_ID_BY_ACCOUNT,
Fyodor Kupolov00de49e2016-09-23 13:10:27 -0700445 selectionArgs,
446 null, null, null)) {
447 while (cursor.moveToNext()) {
448 final String tmpkey = cursor.getString(0);
449 final String value = cursor.getString(1);
450 userExtrasForAccount.put(tmpkey, value);
451 }
452 }
453 return userExtrasForAccount;
454 }
455
456 long findCeAccountId(Account account) {
457 SQLiteDatabase db = mDeDatabase.getReadableDatabaseUserIsUnlocked();
458 String[] columns = { ACCOUNTS_ID };
459 String selection = "name=? AND type=?";
460 String[] selectionArgs = {account.name, account.type};
461 try (Cursor cursor = db.query(CE_TABLE_ACCOUNTS, columns, selection, selectionArgs,
462 null, null, null)) {
463 if (cursor.moveToNext()) {
464 return cursor.getLong(0);
465 }
466 return -1;
467 }
468 }
469
470 String findAccountPasswordByNameAndType(String name, String type) {
471 SQLiteDatabase db = mDeDatabase.getReadableDatabaseUserIsUnlocked();
472 String selection = ACCOUNTS_NAME + "=? AND " + ACCOUNTS_TYPE + "=?";
473 String[] selectionArgs = {name, type};
474 String[] columns = {ACCOUNTS_PASSWORD};
475 try (Cursor cursor = db.query(CE_TABLE_ACCOUNTS, columns, selection, selectionArgs,
476 null, null, null)) {
477 if (cursor.moveToNext()) {
478 return cursor.getString(0);
479 }
480 return null;
481 }
482 }
483
484 long insertCeAccount(Account account, String password) {
485 SQLiteDatabase db = mDeDatabase.getWritableDatabaseUserIsUnlocked();
486 ContentValues values = new ContentValues();
487 values.put(ACCOUNTS_NAME, account.name);
488 values.put(ACCOUNTS_TYPE, account.type);
489 values.put(ACCOUNTS_PASSWORD, password);
490 return db.insert(
491 CE_TABLE_ACCOUNTS, ACCOUNTS_NAME, values);
492 }
493
494
Fyodor Kupolov1ce01612016-08-26 11:39:07 -0700495 static class DeDatabaseHelper extends SQLiteOpenHelper {
496
497 private final int mUserId;
498 private volatile boolean mCeAttached;
499
500 private DeDatabaseHelper(Context context, int userId, String deDatabaseName) {
501 super(context, deDatabaseName, null, DE_DATABASE_VERSION);
502 mUserId = userId;
503 }
504
505 /**
506 * This call needs to be made while the mCacheLock is held. The way to
507 * ensure this is to get the lock any time a method is called ont the DatabaseHelper
508 * @param db The database.
509 */
510 @Override
511 public void onCreate(SQLiteDatabase db) {
512 Log.i(TAG, "Creating DE database for user " + mUserId);
513 db.execSQL("CREATE TABLE " + TABLE_ACCOUNTS + " ( "
514 + ACCOUNTS_ID + " INTEGER PRIMARY KEY, "
515 + ACCOUNTS_NAME + " TEXT NOT NULL, "
516 + ACCOUNTS_TYPE + " TEXT NOT NULL, "
517 + ACCOUNTS_PREVIOUS_NAME + " TEXT, "
518 + ACCOUNTS_LAST_AUTHENTICATE_TIME_EPOCH_MILLIS + " INTEGER DEFAULT 0, "
519 + "UNIQUE(" + ACCOUNTS_NAME + "," + ACCOUNTS_TYPE + "))");
520
521 db.execSQL("CREATE TABLE " + TABLE_META + " ( "
522 + META_KEY + " TEXT PRIMARY KEY NOT NULL, "
523 + META_VALUE + " TEXT)");
524
525 createGrantsTable(db);
526 createSharedAccountsTable(db);
527 createAccountsDeletionTrigger(db);
Fyodor Kupolov00de49e2016-09-23 13:10:27 -0700528 createDebugTable(db);
Dmitry Dementyeve5768622016-11-21 13:51:27 -0800529 createAccountsVisibilityTable(db);
530 createAccountsDeletionVisibilityCleanupTrigger(db);
Fyodor Kupolov1ce01612016-08-26 11:39:07 -0700531 }
532
533 private void createSharedAccountsTable(SQLiteDatabase db) {
534 db.execSQL("CREATE TABLE " + TABLE_SHARED_ACCOUNTS + " ( "
535 + ACCOUNTS_ID + " INTEGER PRIMARY KEY AUTOINCREMENT, "
536 + ACCOUNTS_NAME + " TEXT NOT NULL, "
537 + ACCOUNTS_TYPE + " TEXT NOT NULL, "
538 + "UNIQUE(" + ACCOUNTS_NAME + "," + ACCOUNTS_TYPE + "))");
539 }
540
541 private void createAccountsDeletionTrigger(SQLiteDatabase db) {
542 db.execSQL(""
543 + " CREATE TRIGGER " + TABLE_ACCOUNTS + "Delete DELETE ON " + TABLE_ACCOUNTS
544 + " BEGIN"
545 + " DELETE FROM " + TABLE_GRANTS
546 + " WHERE " + GRANTS_ACCOUNTS_ID + "=OLD." + ACCOUNTS_ID + " ;"
547 + " END");
548 }
549
550 private void createGrantsTable(SQLiteDatabase db) {
551 db.execSQL("CREATE TABLE " + TABLE_GRANTS + " ( "
552 + GRANTS_ACCOUNTS_ID + " INTEGER NOT NULL, "
553 + GRANTS_AUTH_TOKEN_TYPE + " STRING NOT NULL, "
554 + GRANTS_GRANTEE_UID + " INTEGER NOT NULL, "
555 + "UNIQUE (" + GRANTS_ACCOUNTS_ID + "," + GRANTS_AUTH_TOKEN_TYPE
556 + "," + GRANTS_GRANTEE_UID + "))");
557 }
558
Dmitry Dementyeve5768622016-11-21 13:51:27 -0800559 private void createAccountsVisibilityTable(SQLiteDatabase db) {
560 db.execSQL("CREATE TABLE " + TABLE_VISIBILITY + " ( "
561 + VISIBILITY_ACCOUNTS_ID + " INTEGER NOT NULL, "
562 + VISIBILITY_UID + " TEXT NOT NULL, "
563 + VISIBILITY_VALUE + " INTEGER, "
564 + "PRIMARY KEY(" + VISIBILITY_ACCOUNTS_ID + "," + VISIBILITY_UID + "))");
565 }
566
Fyodor Kupolov00de49e2016-09-23 13:10:27 -0700567 static void createDebugTable(SQLiteDatabase db) {
568 db.execSQL("CREATE TABLE " + TABLE_DEBUG + " ( "
569 + ACCOUNTS_ID + " INTEGER,"
570 + DEBUG_TABLE_ACTION_TYPE + " TEXT NOT NULL, "
571 + DEBUG_TABLE_TIMESTAMP + " DATETIME,"
572 + DEBUG_TABLE_CALLER_UID + " INTEGER NOT NULL,"
573 + DEBUG_TABLE_TABLE_NAME + " TEXT NOT NULL,"
574 + DEBUG_TABLE_KEY + " INTEGER PRIMARY KEY)");
575 db.execSQL("CREATE INDEX timestamp_index ON " + TABLE_DEBUG + " ("
576 + DEBUG_TABLE_TIMESTAMP + ")");
577 }
578
Dmitry Dementyeve5768622016-11-21 13:51:27 -0800579 private void createAccountsDeletionVisibilityCleanupTrigger(SQLiteDatabase db) {
580 db.execSQL(""
581 + " CREATE TRIGGER "
582 + TABLE_ACCOUNTS + "DeleteVisibility DELETE ON " + TABLE_ACCOUNTS
583 + " BEGIN"
584 + " DELETE FROM " + TABLE_VISIBILITY
585 + " WHERE " + VISIBILITY_ACCOUNTS_ID + "=OLD." + ACCOUNTS_ID + " ;"
586 + " END");
587 }
588
Fyodor Kupolov1ce01612016-08-26 11:39:07 -0700589 @Override
590 public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
591 Log.i(TAG, "upgrade from version " + oldVersion + " to version " + newVersion);
592
Dmitry Dementyeve5768622016-11-21 13:51:27 -0800593 if (oldVersion == 1) {
594 createAccountsVisibilityTable(db);
595 createAccountsDeletionVisibilityCleanupTrigger(db);
596 ++oldVersion;
597 }
598
Fyodor Kupolov1ce01612016-08-26 11:39:07 -0700599 if (oldVersion != newVersion) {
600 Log.e(TAG, "failed to upgrade version " + oldVersion + " to version " + newVersion);
601 }
602 }
603
Fyodor Kupolov1ce01612016-08-26 11:39:07 -0700604 public SQLiteDatabase getReadableDatabaseUserIsUnlocked() {
605 if(!mCeAttached) {
606 Log.wtf(TAG, "getReadableDatabaseUserIsUnlocked called while user " + mUserId
607 + " is still locked. CE database is not yet available.", new Throwable());
608 }
609 return super.getReadableDatabase();
610 }
611
612 public SQLiteDatabase getWritableDatabaseUserIsUnlocked() {
613 if(!mCeAttached) {
614 Log.wtf(TAG, "getWritableDatabaseUserIsUnlocked called while user " + mUserId
615 + " is still locked. CE database is not yet available.", new Throwable());
616 }
617 return super.getWritableDatabase();
618 }
619
620 @Override
621 public void onOpen(SQLiteDatabase db) {
622 if (Log.isLoggable(TAG, Log.VERBOSE)) Log.v(TAG, "opened database " + DE_DATABASE_NAME);
623 }
624
625 private void migratePreNDbToDe(File preNDbFile) {
626 Log.i(TAG, "Migrate pre-N database to DE preNDbFile=" + preNDbFile);
627 SQLiteDatabase db = getWritableDatabase();
628 db.execSQL("ATTACH DATABASE '" + preNDbFile.getPath() + "' AS preNDb");
629 db.beginTransaction();
630 // Copy accounts fields
631 db.execSQL("INSERT INTO " + TABLE_ACCOUNTS
632 + "(" + ACCOUNTS_ID + "," + ACCOUNTS_NAME + "," + ACCOUNTS_TYPE + ", "
633 + ACCOUNTS_PREVIOUS_NAME + ", " + ACCOUNTS_LAST_AUTHENTICATE_TIME_EPOCH_MILLIS
634 + ") "
635 + "SELECT " + ACCOUNTS_ID + "," + ACCOUNTS_NAME + "," + ACCOUNTS_TYPE + ", "
636 + ACCOUNTS_PREVIOUS_NAME + ", " + ACCOUNTS_LAST_AUTHENTICATE_TIME_EPOCH_MILLIS
637 + " FROM preNDb." + TABLE_ACCOUNTS);
638 // Copy SHARED_ACCOUNTS
639 db.execSQL("INSERT INTO " + TABLE_SHARED_ACCOUNTS
640 + "(" + SHARED_ACCOUNTS_ID + "," + ACCOUNTS_NAME + "," + ACCOUNTS_TYPE + ") " +
641 "SELECT " + SHARED_ACCOUNTS_ID + "," + ACCOUNTS_NAME + "," + ACCOUNTS_TYPE
642 + " FROM preNDb." + TABLE_SHARED_ACCOUNTS);
643 // Copy DEBUG_TABLE
644 db.execSQL("INSERT INTO " + TABLE_DEBUG
645 + "(" + ACCOUNTS_ID + "," + DEBUG_TABLE_ACTION_TYPE + ","
646 + DEBUG_TABLE_TIMESTAMP + "," + DEBUG_TABLE_CALLER_UID + ","
647 + DEBUG_TABLE_TABLE_NAME + "," + DEBUG_TABLE_KEY + ") " +
648 "SELECT " + ACCOUNTS_ID + "," + DEBUG_TABLE_ACTION_TYPE + ","
649 + DEBUG_TABLE_TIMESTAMP + "," + DEBUG_TABLE_CALLER_UID + ","
650 + DEBUG_TABLE_TABLE_NAME + "," + DEBUG_TABLE_KEY
651 + " FROM preNDb." + TABLE_DEBUG);
652 // Copy GRANTS
653 db.execSQL("INSERT INTO " + TABLE_GRANTS
654 + "(" + GRANTS_ACCOUNTS_ID + "," + GRANTS_AUTH_TOKEN_TYPE + ","
655 + GRANTS_GRANTEE_UID + ") " +
656 "SELECT " + GRANTS_ACCOUNTS_ID + "," + GRANTS_AUTH_TOKEN_TYPE + ","
657 + GRANTS_GRANTEE_UID + " FROM preNDb." + TABLE_GRANTS);
658 // Copy META
659 db.execSQL("INSERT INTO " + TABLE_META
660 + "(" + META_KEY + "," + META_VALUE + ") "
661 + "SELECT " + META_KEY + "," + META_VALUE + " FROM preNDb." + TABLE_META);
662 db.setTransactionSuccessful();
663 db.endTransaction();
664
665 db.execSQL("DETACH DATABASE preNDb");
666 }
Fyodor Kupolov00de49e2016-09-23 13:10:27 -0700667 }
Fyodor Kupolov1ce01612016-08-26 11:39:07 -0700668
Fyodor Kupolov00de49e2016-09-23 13:10:27 -0700669 boolean deleteDeAccount(long accountId) {
670 SQLiteDatabase db = mDeDatabase.getWritableDatabase();
671 return db.delete(TABLE_ACCOUNTS, ACCOUNTS_ID + "=" + accountId, null) > 0;
672 }
Fyodor Kupolov1ce01612016-08-26 11:39:07 -0700673
Fyodor Kupolov00de49e2016-09-23 13:10:27 -0700674 long insertSharedAccount(Account account) {
675 SQLiteDatabase db = mDeDatabase.getWritableDatabase();
676 ContentValues values = new ContentValues();
677 values.put(ACCOUNTS_NAME, account.name);
678 values.put(ACCOUNTS_TYPE, account.type);
679 return db.insert(
680 TABLE_SHARED_ACCOUNTS, ACCOUNTS_NAME, values);
681 }
Fyodor Kupolov1ce01612016-08-26 11:39:07 -0700682
Fyodor Kupolov00de49e2016-09-23 13:10:27 -0700683 boolean deleteSharedAccount(Account account) {
684 SQLiteDatabase db = mDeDatabase.getWritableDatabase();
685 return db.delete(TABLE_SHARED_ACCOUNTS, ACCOUNTS_NAME + "=? AND " + ACCOUNTS_TYPE + "=?",
686 new String[]{account.name, account.type}) > 0;
687 }
Fyodor Kupolov1ce01612016-08-26 11:39:07 -0700688
Fyodor Kupolov00de49e2016-09-23 13:10:27 -0700689 int renameSharedAccount(Account account, String newName) {
690 SQLiteDatabase db = mDeDatabase.getWritableDatabase();
691 final ContentValues values = new ContentValues();
692 values.put(ACCOUNTS_NAME, newName);
693 return db.update(TABLE_SHARED_ACCOUNTS,
694 values,
695 ACCOUNTS_NAME + "=? AND " + ACCOUNTS_TYPE + "=?",
696 new String[] {account.name, account.type});
697 }
Fyodor Kupolov1ce01612016-08-26 11:39:07 -0700698
Fyodor Kupolov00de49e2016-09-23 13:10:27 -0700699 List<Account> getSharedAccounts() {
700 SQLiteDatabase db = mDeDatabase.getReadableDatabase();
701 ArrayList<Account> accountList = new ArrayList<>();
702 Cursor cursor = null;
703 try {
704 cursor = db.query(TABLE_SHARED_ACCOUNTS, new String[] {ACCOUNTS_NAME, ACCOUNTS_TYPE},
705 null, null, null, null, null);
706 if (cursor != null && cursor.moveToFirst()) {
707 int nameIndex = cursor.getColumnIndex(ACCOUNTS_NAME);
708 int typeIndex = cursor.getColumnIndex(ACCOUNTS_TYPE);
Fyodor Kupolov1ce01612016-08-26 11:39:07 -0700709 do {
Fyodor Kupolov00de49e2016-09-23 13:10:27 -0700710 accountList.add(new Account(cursor.getString(nameIndex),
711 cursor.getString(typeIndex)));
Fyodor Kupolov1ce01612016-08-26 11:39:07 -0700712 } while (cursor.moveToNext());
Fyodor Kupolov00de49e2016-09-23 13:10:27 -0700713 }
714 } finally {
715 if (cursor != null) {
716 cursor.close();
Fyodor Kupolov1ce01612016-08-26 11:39:07 -0700717 }
718 }
Fyodor Kupolov00de49e2016-09-23 13:10:27 -0700719 return accountList;
720 }
Fyodor Kupolov1ce01612016-08-26 11:39:07 -0700721
Fyodor Kupolov00de49e2016-09-23 13:10:27 -0700722 long findSharedAccountId(Account account) {
723 SQLiteDatabase db = mDeDatabase.getReadableDatabase();
724 Cursor cursor = db.query(TABLE_SHARED_ACCOUNTS, new String[]{
725 ACCOUNTS_ID},
726 "name=? AND type=?", new String[]{account.name, account.type}, null, null,
727 null);
728 try {
729 if (cursor.moveToNext()) {
730 return cursor.getLong(0);
Fyodor Kupolov1ce01612016-08-26 11:39:07 -0700731 }
Fyodor Kupolov00de49e2016-09-23 13:10:27 -0700732 return -1;
733 } finally {
734 cursor.close();
Fyodor Kupolov1ce01612016-08-26 11:39:07 -0700735 }
736 }
737
Fyodor Kupolov00de49e2016-09-23 13:10:27 -0700738 long findAccountLastAuthenticatedTime(Account account) {
739 SQLiteDatabase db = mDeDatabase.getReadableDatabase();
740 return DatabaseUtils.longForQuery(db,
741 "SELECT " + AccountsDb.ACCOUNTS_LAST_AUTHENTICATE_TIME_EPOCH_MILLIS
742 + " FROM " + TABLE_ACCOUNTS + " WHERE " + ACCOUNTS_NAME + "=? AND "
743 + ACCOUNTS_TYPE + "=?",
744 new String[] {account.name, account.type});
745 }
746
747 boolean updateAccountLastAuthenticatedTime(Account account) {
748 SQLiteDatabase db = mDeDatabase.getWritableDatabase();
749 final ContentValues values = new ContentValues();
750 values.put(ACCOUNTS_LAST_AUTHENTICATE_TIME_EPOCH_MILLIS, System.currentTimeMillis());
751 int rowCount = db.update(TABLE_ACCOUNTS,
752 values,
753 ACCOUNTS_NAME + "=? AND " + ACCOUNTS_TYPE + "=?",
754 new String[] { account.name, account.type });
755 return rowCount > 0;
756 }
757
758 void dumpDeAccountsTable(PrintWriter pw) {
759 SQLiteDatabase db = mDeDatabase.getReadableDatabase();
760 Cursor cursor = db.query(
761 TABLE_ACCOUNTS, ACCOUNT_TYPE_COUNT_PROJECTION,
762 null, null, ACCOUNTS_TYPE, null, null);
763 try {
764 while (cursor.moveToNext()) {
765 // print type,count
766 pw.println(cursor.getString(0) + "," + cursor.getString(1));
767 }
768 } finally {
769 if (cursor != null) {
770 cursor.close();
771 }
772 }
773 }
774
775 long findDeAccountId(Account account) {
776 SQLiteDatabase db = mDeDatabase.getReadableDatabase();
777 String[] columns = {ACCOUNTS_ID};
778 String selection = "name=? AND type=?";
779 String[] selectionArgs = {account.name, account.type};
780 try (Cursor cursor = db.query(TABLE_ACCOUNTS, columns, selection, selectionArgs,
781 null, null, null)) {
782 if (cursor.moveToNext()) {
783 return cursor.getLong(0);
784 }
785 return -1;
786 }
787 }
788
789 Map<Long, Account> findAllDeAccounts() {
790 SQLiteDatabase db = mDeDatabase.getReadableDatabase();
791 LinkedHashMap<Long, Account> map = new LinkedHashMap<>();
792 String[] columns = {ACCOUNTS_ID, ACCOUNTS_TYPE, ACCOUNTS_NAME};
793 try (Cursor cursor = db.query(TABLE_ACCOUNTS, columns,
794 null, null, null, null, ACCOUNTS_ID)) {
795 while (cursor.moveToNext()) {
796 final long accountId = cursor.getLong(0);
797 final String accountType = cursor.getString(1);
798 final String accountName = cursor.getString(2);
799
800 final Account account = new Account(accountName, accountType);
801 map.put(accountId, account);
802 }
803 }
804 return map;
805 }
806
807 String findDeAccountPreviousName(Account account) {
808 SQLiteDatabase db = mDeDatabase.getReadableDatabase();
809 String[] columns = {ACCOUNTS_PREVIOUS_NAME};
810 String selection = ACCOUNTS_NAME + "=? AND " + ACCOUNTS_TYPE + "=?";
811 String[] selectionArgs = {account.name, account.type};
812 try (Cursor cursor = db.query(TABLE_ACCOUNTS, columns, selection, selectionArgs,
813 null, null, null)) {
814 if (cursor.moveToNext()) {
815 return cursor.getString(0);
816 }
817 }
818 return null;
819 }
820
821 long insertDeAccount(Account account, long accountId) {
822 SQLiteDatabase db = mDeDatabase.getWritableDatabase();
823 ContentValues values = new ContentValues();
824 values.put(ACCOUNTS_ID, accountId);
825 values.put(ACCOUNTS_NAME, account.name);
826 values.put(ACCOUNTS_TYPE, account.type);
827 values.put(ACCOUNTS_LAST_AUTHENTICATE_TIME_EPOCH_MILLIS, System.currentTimeMillis());
828 return db.insert(TABLE_ACCOUNTS, ACCOUNTS_NAME, values);
829 }
830
831 boolean renameDeAccount(long accountId, String newName, String previousName) {
832 SQLiteDatabase db = mDeDatabase.getWritableDatabase();
833 final ContentValues values = new ContentValues();
834 values.put(ACCOUNTS_NAME, newName);
835 values.put(ACCOUNTS_PREVIOUS_NAME, previousName);
836 final String[] argsAccountId = {String.valueOf(accountId)};
837 return db.update(TABLE_ACCOUNTS, values, ACCOUNTS_ID + "=?", argsAccountId) > 0;
838 }
839
840 boolean deleteGrantsByAccountIdAuthTokenTypeAndUid(long accountId,
841 String authTokenType, long uid) {
842 SQLiteDatabase db = mDeDatabase.getWritableDatabase();
843 return db.delete(TABLE_GRANTS,
844 GRANTS_ACCOUNTS_ID + "=? AND " + GRANTS_AUTH_TOKEN_TYPE + "=? AND "
845 + GRANTS_GRANTEE_UID + "=?",
846 new String[] {String.valueOf(accountId), authTokenType, String.valueOf(uid)}) > 0;
847 }
848
849 List<Integer> findAllUidGrants() {
850 SQLiteDatabase db = mDeDatabase.getReadableDatabase();
851 List<Integer> result = new ArrayList<>();
852 final Cursor cursor = db.query(TABLE_GRANTS,
853 new String[]{GRANTS_GRANTEE_UID},
854 null, null, GRANTS_GRANTEE_UID, null, null);
855 try {
856 while (cursor.moveToNext()) {
857 final int uid = cursor.getInt(0);
858 result.add(uid);
859 }
860 } finally {
861 cursor.close();
862 }
863 return result;
864 }
865
866 long findMatchingGrantsCount(int uid, String authTokenType, Account account) {
867 SQLiteDatabase db = mDeDatabase.getReadableDatabase();
868 String[] args = {String.valueOf(uid), authTokenType, account.name, account.type};
869 return DatabaseUtils.longForQuery(db, COUNT_OF_MATCHING_GRANTS, args);
870 }
871
872 long findMatchingGrantsCountAnyToken(int uid, Account account) {
873 SQLiteDatabase db = mDeDatabase.getReadableDatabase();
874 String[] args = {String.valueOf(uid), account.name, account.type};
875 return DatabaseUtils.longForQuery(db, COUNT_OF_MATCHING_GRANTS_ANY_TOKEN, args);
876 }
877
878 long insertGrant(long accountId, String authTokenType, int uid) {
879 SQLiteDatabase db = mDeDatabase.getWritableDatabase();
880 ContentValues values = new ContentValues();
881 values.put(GRANTS_ACCOUNTS_ID, accountId);
882 values.put(GRANTS_AUTH_TOKEN_TYPE, authTokenType);
883 values.put(GRANTS_GRANTEE_UID, uid);
884 return db.insert(TABLE_GRANTS, GRANTS_ACCOUNTS_ID, values);
885 }
886
887 boolean deleteGrantsByUid(int uid) {
888 SQLiteDatabase db = mDeDatabase.getWritableDatabase();
889 return db.delete(TABLE_GRANTS, GRANTS_GRANTEE_UID + "=?",
890 new String[] {Integer.toString(uid)}) > 0;
891 }
892
Dmitry Dementyeve5768622016-11-21 13:51:27 -0800893 boolean setAccountVisibility(long accountId, int uid, int visibility) {
894 SQLiteDatabase db = mDeDatabase.getWritableDatabase();
895 ContentValues values = new ContentValues();
896 values.put(VISIBILITY_ACCOUNTS_ID, String.valueOf(accountId));
897 values.put(VISIBILITY_UID, String.valueOf(uid));
898 values.put(VISIBILITY_VALUE, String.valueOf(visibility));
899 return (db.replace(TABLE_VISIBILITY, VISIBILITY_VALUE, values) != -1);
900 }
901
902 Integer findAccountVisibility(Account account, int uid) {
903 SQLiteDatabase db = mDeDatabase.getWritableDatabase();
904 final Cursor cursor = db.query(TABLE_VISIBILITY, new String[] {VISIBILITY_VALUE},
905 SELECTION_ACCOUNTS_ID_BY_ACCOUNT + " AND " + VISIBILITY_UID + "=? ",
906 new String[] {account.name, account.type, String.valueOf(uid)}, null, null, null);
907 try {
908 while (cursor.moveToNext()) {
909 return cursor.getInt(0);
910 }
911 } finally {
912 cursor.close();
913 }
914 return null;
915 }
916
917 Integer findAccountVisibility(long accountId, int uid) {
918 SQLiteDatabase db = mDeDatabase.getWritableDatabase();
919 final Cursor cursor = db.query(TABLE_VISIBILITY, new String[] {VISIBILITY_VALUE},
920 VISIBILITY_ACCOUNTS_ID + "=? AND " + VISIBILITY_UID + "=? ",
921 new String[] {String.valueOf(accountId), String.valueOf(uid)}, null, null, null);
922 try {
923 while (cursor.moveToNext()) {
924 return cursor.getInt(0);
925 }
926 } finally {
927 cursor.close();
928 }
929 return null;
930 }
931
932 Account findDeAccountByAccountId(long accountId) {
933 SQLiteDatabase db = mDeDatabase.getReadableDatabase();
934 final Cursor cursor = db.query(TABLE_ACCOUNTS, new String[] {ACCOUNTS_NAME, ACCOUNTS_TYPE},
935 ACCOUNTS_ID + "=? ", new String[] {String.valueOf(accountId)}, null, null, null);
936 try {
937 while (cursor.moveToNext()) {
938 return new Account(cursor.getString(0), cursor.getString(1));
939 }
940 } finally {
941 cursor.close();
942 }
943 return null;
944 }
945
946 /**
947 * Returns a map from uid to visibility value.
948 */
949 Map<Integer, Integer> findAccountVisibilityForAccountId(long accountId) {
950 SQLiteDatabase db = mDeDatabase.getReadableDatabase();
951 Map<Integer, Integer> result = new HashMap<>();
952 final Cursor cursor = db.query(TABLE_VISIBILITY,
953 new String[] {VISIBILITY_UID, VISIBILITY_VALUE}, VISIBILITY_ACCOUNTS_ID + "=? ",
954 new String[] {String.valueOf(accountId)}, null, null, null);
955 try {
956 while (cursor.moveToNext()) {
957 result.put(cursor.getInt(0), cursor.getInt(1));
958 }
959 } finally {
960 cursor.close();
961 }
962 return result;
963 }
964
965 boolean deleteAccountVisibilityForUid(int uid) {
966 SQLiteDatabase db = mDeDatabase.getWritableDatabase();
967 return db.delete(TABLE_VISIBILITY, VISIBILITY_UID + "=? ",
968 new String[] {Integer.toString(uid)}) > 0;
969 }
970
Fyodor Kupolov00de49e2016-09-23 13:10:27 -0700971 long insertOrReplaceMetaAuthTypeAndUid(String authenticatorType, int uid) {
972 SQLiteDatabase db = mDeDatabase.getWritableDatabase();
973 ContentValues values = new ContentValues();
974 values.put(META_KEY,
975 META_KEY_FOR_AUTHENTICATOR_UID_FOR_TYPE_PREFIX + authenticatorType);
976 values.put(META_VALUE, uid);
977 return db.insertWithOnConflict(TABLE_META, null, values,
978 SQLiteDatabase.CONFLICT_REPLACE);
979 }
980
981 Map<String, Integer> findMetaAuthUid() {
982 SQLiteDatabase db = mDeDatabase.getReadableDatabase();
983 Cursor metaCursor = db.query(
984 TABLE_META,
985 new String[]{META_KEY, META_VALUE},
986 SELECTION_META_BY_AUTHENTICATOR_TYPE,
987 new String[]{META_KEY_FOR_AUTHENTICATOR_UID_FOR_TYPE_PREFIX + "%"},
988 null /* groupBy */,
989 null /* having */,
990 META_KEY);
991 Map<String, Integer> map = new LinkedHashMap<>();
992 try {
993 while (metaCursor.moveToNext()) {
994 String type = TextUtils
995 .split(metaCursor.getString(0), META_KEY_DELIMITER)[1];
996 String uidStr = metaCursor.getString(1);
997 if (TextUtils.isEmpty(type) || TextUtils.isEmpty(uidStr)) {
998 // Should never happen.
999 Slog.e(TAG, "Auth type empty: " + TextUtils.isEmpty(type)
1000 + ", uid empty: " + TextUtils.isEmpty(uidStr));
1001 continue;
1002 }
1003 int uid = Integer.parseInt(metaCursor.getString(1));
1004 map.put(type, uid);
1005 }
1006 } finally {
1007 metaCursor.close();
1008 }
1009 return map;
1010 }
1011
1012 boolean deleteMetaByAuthTypeAndUid(String type, int uid) {
1013 SQLiteDatabase db = mDeDatabase.getWritableDatabase();
1014 return db.delete(
1015 TABLE_META,
1016 META_KEY + "=? AND " + META_VALUE + "=?",
1017 new String[]{
1018 META_KEY_FOR_AUTHENTICATOR_UID_FOR_TYPE_PREFIX + type,
1019 String.valueOf(uid)}
1020 ) > 0;
1021 }
1022
Fyodor Kupolov79d138e2016-10-04 12:09:05 -07001023 /**
1024 * Returns list of all grants as {@link Pair pairs} of account name and UID.
1025 */
Fyodor Kupolov00de49e2016-09-23 13:10:27 -07001026 List<Pair<String, Integer>> findAllAccountGrants() {
1027 SQLiteDatabase db = mDeDatabase.getReadableDatabase();
1028 try (Cursor cursor = db.rawQuery(ACCOUNT_ACCESS_GRANTS, null)) {
1029 if (cursor == null || !cursor.moveToFirst()) {
1030 return Collections.emptyList();
1031 }
1032 List<Pair<String, Integer>> results = new ArrayList<>();
1033 do {
1034 final String accountName = cursor.getString(0);
1035 final int uid = cursor.getInt(1);
1036 results.add(Pair.create(accountName, uid));
1037 } while (cursor.moveToNext());
1038 return results;
1039 }
1040 }
1041
1042 private static class PreNDatabaseHelper extends SQLiteOpenHelper {
Fyodor Kupolov1ce01612016-08-26 11:39:07 -07001043 private final Context mContext;
1044 private final int mUserId;
1045
Fyodor Kupolov00de49e2016-09-23 13:10:27 -07001046 PreNDatabaseHelper(Context context, int userId, String preNDatabaseName) {
Fyodor Kupolov1ce01612016-08-26 11:39:07 -07001047 super(context, preNDatabaseName, null, PRE_N_DATABASE_VERSION);
1048 mContext = context;
1049 mUserId = userId;
1050 }
1051
1052 @Override
1053 public void onCreate(SQLiteDatabase db) {
1054 // We use PreNDatabaseHelper only if pre-N db exists
1055 throw new IllegalStateException("Legacy database cannot be created - only upgraded!");
1056 }
1057
1058 private void createSharedAccountsTable(SQLiteDatabase db) {
1059 db.execSQL("CREATE TABLE " + TABLE_SHARED_ACCOUNTS + " ( "
1060 + ACCOUNTS_ID + " INTEGER PRIMARY KEY AUTOINCREMENT, "
1061 + ACCOUNTS_NAME + " TEXT NOT NULL, "
1062 + ACCOUNTS_TYPE + " TEXT NOT NULL, "
1063 + "UNIQUE(" + ACCOUNTS_NAME + "," + ACCOUNTS_TYPE + "))");
1064 }
1065
1066 private void addLastSuccessfullAuthenticatedTimeColumn(SQLiteDatabase db) {
1067 db.execSQL("ALTER TABLE " + TABLE_ACCOUNTS + " ADD COLUMN "
1068 + ACCOUNTS_LAST_AUTHENTICATE_TIME_EPOCH_MILLIS + " DEFAULT 0");
1069 }
1070
1071 private void addOldAccountNameColumn(SQLiteDatabase db) {
1072 db.execSQL("ALTER TABLE " + TABLE_ACCOUNTS + " ADD COLUMN " + ACCOUNTS_PREVIOUS_NAME);
1073 }
1074
1075 private void addDebugTable(SQLiteDatabase db) {
Fyodor Kupolov00de49e2016-09-23 13:10:27 -07001076 DeDatabaseHelper.createDebugTable(db);
Fyodor Kupolov1ce01612016-08-26 11:39:07 -07001077 }
1078
1079 private void createAccountsDeletionTrigger(SQLiteDatabase db) {
1080 db.execSQL(""
1081 + " CREATE TRIGGER " + TABLE_ACCOUNTS + "Delete DELETE ON " + TABLE_ACCOUNTS
1082 + " BEGIN"
1083 + " DELETE FROM " + TABLE_AUTHTOKENS
1084 + " WHERE " + AUTHTOKENS_ACCOUNTS_ID + "=OLD." + ACCOUNTS_ID + " ;"
1085 + " DELETE FROM " + TABLE_EXTRAS
1086 + " WHERE " + EXTRAS_ACCOUNTS_ID + "=OLD." + ACCOUNTS_ID + " ;"
1087 + " DELETE FROM " + TABLE_GRANTS
1088 + " WHERE " + GRANTS_ACCOUNTS_ID + "=OLD." + ACCOUNTS_ID + " ;"
1089 + " END");
1090 }
1091
1092 private void createGrantsTable(SQLiteDatabase db) {
1093 db.execSQL("CREATE TABLE " + TABLE_GRANTS + " ( "
1094 + GRANTS_ACCOUNTS_ID + " INTEGER NOT NULL, "
1095 + GRANTS_AUTH_TOKEN_TYPE + " STRING NOT NULL, "
1096 + GRANTS_GRANTEE_UID + " INTEGER NOT NULL, "
1097 + "UNIQUE (" + GRANTS_ACCOUNTS_ID + "," + GRANTS_AUTH_TOKEN_TYPE
1098 + "," + GRANTS_GRANTEE_UID + "))");
1099 }
1100
Fyodor Kupolov00de49e2016-09-23 13:10:27 -07001101 static long insertMetaAuthTypeAndUid(SQLiteDatabase db, String authenticatorType, int uid) {
1102 ContentValues values = new ContentValues();
1103 values.put(META_KEY,
1104 META_KEY_FOR_AUTHENTICATOR_UID_FOR_TYPE_PREFIX + authenticatorType);
1105 values.put(META_VALUE, uid);
1106 return db.insert(TABLE_META, null, values);
1107 }
1108
Fyodor Kupolov1ce01612016-08-26 11:39:07 -07001109 private void populateMetaTableWithAuthTypeAndUID(SQLiteDatabase db,
1110 Map<String, Integer> authTypeAndUIDMap) {
1111 for (Map.Entry<String, Integer> entry : authTypeAndUIDMap.entrySet()) {
Fyodor Kupolov00de49e2016-09-23 13:10:27 -07001112 insertMetaAuthTypeAndUid(db, entry.getKey(), entry.getValue());
Fyodor Kupolov1ce01612016-08-26 11:39:07 -07001113 }
1114 }
1115
1116 /**
1117 * Pre-N database may need an upgrade before splitting
1118 */
1119 @Override
1120 public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
1121 Log.e(TAG, "upgrade from version " + oldVersion + " to version " + newVersion);
1122
1123 if (oldVersion == 1) {
1124 // no longer need to do anything since the work is done
1125 // when upgrading from version 2
1126 oldVersion++;
1127 }
1128
1129 if (oldVersion == 2) {
1130 createGrantsTable(db);
1131 db.execSQL("DROP TRIGGER " + TABLE_ACCOUNTS + "Delete");
1132 createAccountsDeletionTrigger(db);
1133 oldVersion++;
1134 }
1135
1136 if (oldVersion == 3) {
1137 db.execSQL("UPDATE " + TABLE_ACCOUNTS + " SET " + ACCOUNTS_TYPE +
1138 " = 'com.google' WHERE " + ACCOUNTS_TYPE + " == 'com.google.GAIA'");
1139 oldVersion++;
1140 }
1141
1142 if (oldVersion == 4) {
1143 createSharedAccountsTable(db);
1144 oldVersion++;
1145 }
1146
1147 if (oldVersion == 5) {
1148 addOldAccountNameColumn(db);
1149 oldVersion++;
1150 }
1151
1152 if (oldVersion == 6) {
1153 addLastSuccessfullAuthenticatedTimeColumn(db);
1154 oldVersion++;
1155 }
1156
1157 if (oldVersion == 7) {
1158 addDebugTable(db);
1159 oldVersion++;
1160 }
1161
1162 if (oldVersion == 8) {
1163 populateMetaTableWithAuthTypeAndUID(
1164 db,
1165 AccountManagerService.getAuthenticatorTypeAndUIDForUser(mContext, mUserId));
1166 oldVersion++;
1167 }
1168
1169 if (oldVersion != newVersion) {
1170 Log.e(TAG, "failed to upgrade version " + oldVersion + " to version " + newVersion);
1171 }
1172 }
1173
1174 @Override
1175 public void onOpen(SQLiteDatabase db) {
1176 if (Log.isLoggable(TAG, Log.VERBOSE)) Log.v(TAG, "opened database " + DATABASE_NAME);
1177 }
1178 }
1179
Fyodor Kupolov00de49e2016-09-23 13:10:27 -07001180 List<Account> findCeAccountsNotInDe() {
1181 SQLiteDatabase db = mDeDatabase.getReadableDatabaseUserIsUnlocked();
Fyodor Kupolov1ce01612016-08-26 11:39:07 -07001182 // Select accounts from CE that do not exist in DE
1183 Cursor cursor = db.rawQuery(
1184 "SELECT " + ACCOUNTS_NAME + "," + ACCOUNTS_TYPE
1185 + " FROM " + CE_TABLE_ACCOUNTS
1186 + " WHERE NOT EXISTS "
1187 + " (SELECT " + ACCOUNTS_ID + " FROM " + TABLE_ACCOUNTS
1188 + " WHERE " + ACCOUNTS_ID + "=" + CE_TABLE_ACCOUNTS + "." + ACCOUNTS_ID
1189 + " )", null);
1190 try {
1191 List<Account> accounts = new ArrayList<>(cursor.getCount());
1192 while (cursor.moveToNext()) {
1193 String accountName = cursor.getString(0);
1194 String accountType = cursor.getString(1);
1195 accounts.add(new Account(accountName, accountType));
1196 }
1197 return accounts;
1198 } finally {
1199 cursor.close();
1200 }
1201 }
1202
Fyodor Kupolov00de49e2016-09-23 13:10:27 -07001203 boolean deleteCeAccount(long accountId) {
Fyodor Kupolov98e9e852016-12-09 14:58:05 -08001204 SQLiteDatabase db = mDeDatabase.getWritableDatabaseUserIsUnlocked();
Fyodor Kupolov1ce01612016-08-26 11:39:07 -07001205 return db.delete(
1206 CE_TABLE_ACCOUNTS, ACCOUNTS_ID + "=" + accountId, null) > 0;
1207 }
1208
Fyodor Kupolov00de49e2016-09-23 13:10:27 -07001209 boolean isCeDatabaseAttached() {
1210 return mDeDatabase.mCeAttached;
1211 }
1212
1213 void beginTransaction() {
1214 mDeDatabase.getWritableDatabase().beginTransaction();
1215 }
1216
1217 void setTransactionSuccessful() {
1218 mDeDatabase.getWritableDatabase().setTransactionSuccessful();
1219 }
1220
1221 void endTransaction() {
1222 mDeDatabase.getWritableDatabase().endTransaction();
1223 }
1224
1225 void attachCeDatabase(File ceDbFile) {
1226 CeDatabaseHelper.create(mContext, mPreNDatabaseFile, ceDbFile);
1227 SQLiteDatabase db = mDeDatabase.getWritableDatabase();
1228 db.execSQL("ATTACH DATABASE '" + ceDbFile.getPath()+ "' AS ceDb");
1229 mDeDatabase.mCeAttached = true;
1230 }
1231
1232 /*
1233 * Finds the row key where the next insertion should take place. Returns number of rows
1234 * if it is less {@link #MAX_DEBUG_DB_SIZE}, otherwise finds the lowest number available.
1235 */
1236 int calculateDebugTableInsertionPoint() {
1237 SQLiteDatabase db = mDeDatabase.getReadableDatabase();
1238 String queryCountDebugDbRows = "SELECT COUNT(*) FROM " + TABLE_DEBUG;
1239 int size = (int) DatabaseUtils.longForQuery(db, queryCountDebugDbRows, null);
1240 if (size < MAX_DEBUG_DB_SIZE) {
1241 return size;
1242 }
1243
1244 // This query finds the smallest timestamp value (and if 2 records have
1245 // same timestamp, the choose the lower id).
1246 queryCountDebugDbRows = "SELECT " + DEBUG_TABLE_KEY +
1247 " FROM " + TABLE_DEBUG +
1248 " ORDER BY " + DEBUG_TABLE_TIMESTAMP + "," + DEBUG_TABLE_KEY +
1249 " LIMIT 1";
1250 return (int) DatabaseUtils.longForQuery(db, queryCountDebugDbRows, null);
1251 }
1252
1253 SQLiteStatement compileSqlStatementForLogging() {
1254 // TODO b/31708085 Fix debug logging - it eagerly opens database for write without a need
1255 SQLiteDatabase db = mDeDatabase.getWritableDatabase();
1256 String sql = "INSERT OR REPLACE INTO " + AccountsDb.TABLE_DEBUG
1257 + " VALUES (?,?,?,?,?,?)";
1258 return db.compileStatement(sql);
1259 }
1260
1261 void dumpDebugTable(PrintWriter pw) {
1262 SQLiteDatabase db = mDeDatabase.getReadableDatabase();
1263 Cursor cursor = db.query(TABLE_DEBUG, null,
1264 null, null, null, null, DEBUG_TABLE_TIMESTAMP);
1265 pw.println("AccountId, Action_Type, timestamp, UID, TableName, Key");
1266 pw.println("Accounts History");
1267 try {
1268 while (cursor.moveToNext()) {
1269 // print type,count
1270 pw.println(cursor.getString(0) + "," + cursor.getString(1) + "," +
1271 cursor.getString(2) + "," + cursor.getString(3) + ","
1272 + cursor.getString(4) + "," + cursor.getString(5));
1273 }
1274 } finally {
1275 cursor.close();
1276 }
1277 }
1278
1279 public void close() {
1280 mDeDatabase.close();
1281 }
1282
Fyodor Kupolov1ce01612016-08-26 11:39:07 -07001283 static void deleteDbFileWarnIfFailed(File dbFile) {
1284 if (!SQLiteDatabase.deleteDatabase(dbFile)) {
1285 Log.w(TAG, "Database at " + dbFile + " was not deleted successfully");
1286 }
1287 }
Fyodor Kupolov00de49e2016-09-23 13:10:27 -07001288
1289 public static AccountsDb create(Context context, int userId, File preNDatabaseFile,
1290 File deDatabaseFile) {
1291 boolean newDbExists = deDatabaseFile.exists();
1292 DeDatabaseHelper deDatabaseHelper = new DeDatabaseHelper(context, userId,
1293 deDatabaseFile.getPath());
1294 // If the db just created, and there is a legacy db, migrate it
1295 if (!newDbExists && preNDatabaseFile.exists()) {
1296 // Migrate legacy db to the latest version - PRE_N_DATABASE_VERSION
1297 PreNDatabaseHelper
1298 preNDatabaseHelper = new PreNDatabaseHelper(context, userId,
1299 preNDatabaseFile.getPath());
1300 // Open the database to force upgrade if required
1301 preNDatabaseHelper.getWritableDatabase();
1302 preNDatabaseHelper.close();
1303 // Move data without SPII to DE
1304 deDatabaseHelper.migratePreNDbToDe(preNDatabaseFile);
1305 }
1306 return new AccountsDb(deDatabaseHelper, context, preNDatabaseFile);
1307 }
1308
Dmitry Dementyev2e22cfb2017-01-09 18:42:14 +00001309}