blob: da665906a70dff8b445b20535d6ac23646702e0a [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;
Dmitry Dementyev47443192018-10-24 13:31:59 -070020import android.annotation.Nullable;
Fyodor Kupolov1ce01612016-08-26 11:39:07 -070021import android.content.ContentValues;
22import android.content.Context;
23import android.database.Cursor;
24import android.database.DatabaseUtils;
25import android.database.sqlite.SQLiteDatabase;
Dmitry Dementyev47443192018-10-24 13:31:59 -070026import android.database.sqlite.SQLiteException;
Fyodor Kupolov1ce01612016-08-26 11:39:07 -070027import android.database.sqlite.SQLiteOpenHelper;
28import android.database.sqlite.SQLiteStatement;
29import android.os.FileUtils;
30import android.text.TextUtils;
31import android.util.Log;
32import android.util.Pair;
33import android.util.Slog;
34
35import java.io.File;
36import java.io.IOException;
37import java.io.PrintWriter;
38import java.util.ArrayList;
39import java.util.Collections;
40import java.util.HashMap;
41import java.util.LinkedHashMap;
42import java.util.List;
43import java.util.Map;
44
45/**
46 * Persistence layer abstraction for accessing accounts_ce/accounts_de databases.
Fyodor Kupolov00de49e2016-09-23 13:10:27 -070047 *
48 * <p>At first, CE database needs to be {@link #attachCeDatabase(File) attached to DE},
49 * in order for the tables to be available. All operations with CE database are done through the
50 * connection to the DE database, to which it is attached. This approach allows atomic
51 * transactions across two databases</p>
Fyodor Kupolov1ce01612016-08-26 11:39:07 -070052 */
Fyodor Kupolov00de49e2016-09-23 13:10:27 -070053class AccountsDb implements AutoCloseable {
Fyodor Kupolov1ce01612016-08-26 11:39:07 -070054 private static final String TAG = "AccountsDb";
55
56 private static final String DATABASE_NAME = "accounts.db";
57 private static final int PRE_N_DATABASE_VERSION = 9;
58 private static final int CE_DATABASE_VERSION = 10;
Dmitry Dementyeve366f822017-01-31 10:25:10 -080059 private static final int DE_DATABASE_VERSION = 3; // Added visibility support in O
Fyodor Kupolov1ce01612016-08-26 11:39:07 -070060
Fyodor Kupolov1ce01612016-08-26 11:39:07 -070061 static final String TABLE_ACCOUNTS = "accounts";
62 private static final String ACCOUNTS_ID = "_id";
63 private static final String ACCOUNTS_NAME = "name";
64 private static final String ACCOUNTS_TYPE = "type";
65 private static final String ACCOUNTS_TYPE_COUNT = "count(type)";
66 private static final String ACCOUNTS_PASSWORD = "password";
67 private static final String ACCOUNTS_PREVIOUS_NAME = "previous_name";
68 private static final String ACCOUNTS_LAST_AUTHENTICATE_TIME_EPOCH_MILLIS =
69 "last_password_entry_time_millis_epoch";
70
71 private static final String TABLE_AUTHTOKENS = "authtokens";
72 private static final String AUTHTOKENS_ID = "_id";
73 private static final String AUTHTOKENS_ACCOUNTS_ID = "accounts_id";
74 private static final String AUTHTOKENS_TYPE = "type";
75 private static final String AUTHTOKENS_AUTHTOKEN = "authtoken";
76
Dmitry Dementyeve5768622016-11-21 13:51:27 -080077 private static final String TABLE_VISIBILITY = "visibility";
78 private static final String VISIBILITY_ACCOUNTS_ID = "accounts_id";
Dmitry Dementyeve366f822017-01-31 10:25:10 -080079 private static final String VISIBILITY_PACKAGE = "_package";
Dmitry Dementyeve5768622016-11-21 13:51:27 -080080 private static final String VISIBILITY_VALUE = "value";
81
Fyodor Kupolov1ce01612016-08-26 11:39:07 -070082 private static final String TABLE_GRANTS = "grants";
83 private static final String GRANTS_ACCOUNTS_ID = "accounts_id";
84 private static final String GRANTS_AUTH_TOKEN_TYPE = "auth_token_type";
85 private static final String GRANTS_GRANTEE_UID = "uid";
86
87 private static final String TABLE_EXTRAS = "extras";
88 private static final String EXTRAS_ID = "_id";
89 private static final String EXTRAS_ACCOUNTS_ID = "accounts_id";
90 private static final String EXTRAS_KEY = "key";
91 private static final String EXTRAS_VALUE = "value";
92
93 private static final String TABLE_META = "meta";
94 private static final String META_KEY = "key";
95 private static final String META_VALUE = "value";
96
97 static final String TABLE_SHARED_ACCOUNTS = "shared_accounts";
98 private static final String SHARED_ACCOUNTS_ID = "_id";
99
100 private static String TABLE_DEBUG = "debug_table";
101
102 // Columns for debug_table table
103 private static String DEBUG_TABLE_ACTION_TYPE = "action_type";
104 private static String DEBUG_TABLE_TIMESTAMP = "time";
105 private static String DEBUG_TABLE_CALLER_UID = "caller_uid";
106 private static String DEBUG_TABLE_TABLE_NAME = "table_name";
107 private static String DEBUG_TABLE_KEY = "primary_key";
108
109 // These actions correspond to the occurrence of real actions. Since
110 // these are called by the authenticators, the uid associated will be
111 // of the authenticator.
112 static String DEBUG_ACTION_SET_PASSWORD = "action_set_password";
113 static String DEBUG_ACTION_CLEAR_PASSWORD = "action_clear_password";
114 static String DEBUG_ACTION_ACCOUNT_ADD = "action_account_add";
115 static String DEBUG_ACTION_ACCOUNT_REMOVE = "action_account_remove";
116 static String DEBUG_ACTION_ACCOUNT_REMOVE_DE = "action_account_remove_de";
117 static String DEBUG_ACTION_AUTHENTICATOR_REMOVE = "action_authenticator_remove";
118 static String DEBUG_ACTION_ACCOUNT_RENAME = "action_account_rename";
119
120 // These actions don't necessarily correspond to any action on
121 // accountDb taking place. As an example, there might be a request for
122 // addingAccount, which might not lead to addition of account on grounds
123 // of bad authentication. We will still be logging it to keep track of
124 // who called.
125 static String DEBUG_ACTION_CALLED_ACCOUNT_ADD = "action_called_account_add";
126 static String DEBUG_ACTION_CALLED_ACCOUNT_REMOVE = "action_called_account_remove";
127 static String DEBUG_ACTION_SYNC_DE_CE_ACCOUNTS = "action_sync_de_ce_accounts";
128
129 //This action doesn't add account to accountdb. Account is only
130 // added in finishSession which may be in a different user profile.
131 static String DEBUG_ACTION_CALLED_START_ACCOUNT_ADD = "action_called_start_account_add";
132 static String DEBUG_ACTION_CALLED_ACCOUNT_SESSION_FINISH =
133 "action_called_account_session_finish";
134
135 static final String CE_DATABASE_NAME = "accounts_ce.db";
136 static final String DE_DATABASE_NAME = "accounts_de.db";
137 private static final String CE_DB_PREFIX = "ceDb.";
138 private static final String CE_TABLE_ACCOUNTS = CE_DB_PREFIX + TABLE_ACCOUNTS;
139 private static final String CE_TABLE_AUTHTOKENS = CE_DB_PREFIX + TABLE_AUTHTOKENS;
140 private static final String CE_TABLE_EXTRAS = CE_DB_PREFIX + TABLE_EXTRAS;
141
Fyodor Kupolov00de49e2016-09-23 13:10:27 -0700142 static final int MAX_DEBUG_DB_SIZE = 64;
143
Fyodor Kupolov1ce01612016-08-26 11:39:07 -0700144 private static final String[] ACCOUNT_TYPE_COUNT_PROJECTION =
145 new String[] { ACCOUNTS_TYPE, ACCOUNTS_TYPE_COUNT};
146
147 private static final String COUNT_OF_MATCHING_GRANTS = ""
148 + "SELECT COUNT(*) FROM " + TABLE_GRANTS + ", " + TABLE_ACCOUNTS
149 + " WHERE " + GRANTS_ACCOUNTS_ID + "=" + ACCOUNTS_ID
150 + " AND " + GRANTS_GRANTEE_UID + "=?"
151 + " AND " + GRANTS_AUTH_TOKEN_TYPE + "=?"
152 + " AND " + ACCOUNTS_NAME + "=?"
153 + " AND " + ACCOUNTS_TYPE + "=?";
154
155 private static final String COUNT_OF_MATCHING_GRANTS_ANY_TOKEN = ""
156 + "SELECT COUNT(*) FROM " + TABLE_GRANTS + ", " + TABLE_ACCOUNTS
157 + " WHERE " + GRANTS_ACCOUNTS_ID + "=" + ACCOUNTS_ID
158 + " AND " + GRANTS_GRANTEE_UID + "=?"
159 + " AND " + ACCOUNTS_NAME + "=?"
160 + " AND " + ACCOUNTS_TYPE + "=?";
161
Dmitry Dementyeve5768622016-11-21 13:51:27 -0800162 private static final String SELECTION_ACCOUNTS_ID_BY_ACCOUNT =
163 "accounts_id=(select _id FROM accounts WHERE name=? AND type=?)";
Fyodor Kupolov1ce01612016-08-26 11:39:07 -0700164
Dmitry Dementyeve5768622016-11-21 13:51:27 -0800165 private static final String[] COLUMNS_AUTHTOKENS_TYPE_AND_AUTHTOKEN =
166 {AUTHTOKENS_TYPE, AUTHTOKENS_AUTHTOKEN};
Fyodor Kupolov1ce01612016-08-26 11:39:07 -0700167
Fyodor Kupolov1ce01612016-08-26 11:39:07 -0700168 private static final String[] COLUMNS_EXTRAS_KEY_AND_VALUE = {EXTRAS_KEY, EXTRAS_VALUE};
169
170 private static final String ACCOUNT_ACCESS_GRANTS = ""
171 + "SELECT " + AccountsDb.ACCOUNTS_NAME + ", "
172 + AccountsDb.GRANTS_GRANTEE_UID
173 + " FROM " + AccountsDb.TABLE_ACCOUNTS
174 + ", " + AccountsDb.TABLE_GRANTS
175 + " WHERE " + AccountsDb.GRANTS_ACCOUNTS_ID
176 + "=" + AccountsDb.ACCOUNTS_ID;
177
178 private static final String META_KEY_FOR_AUTHENTICATOR_UID_FOR_TYPE_PREFIX =
179 "auth_uid_for_type:";
180 private static final String META_KEY_DELIMITER = ":";
181 private static final String SELECTION_META_BY_AUTHENTICATOR_TYPE = META_KEY + " LIKE ?";
182
Fyodor Kupolov00de49e2016-09-23 13:10:27 -0700183 private final DeDatabaseHelper mDeDatabase;
184 private final Context mContext;
185 private final File mPreNDatabaseFile;
186
Dmitry Dementyev47443192018-10-24 13:31:59 -0700187 final Object mDebugStatementLock = new Object();
188 private volatile long mDebugDbInsertionPoint = -1;
189 private volatile SQLiteStatement mDebugStatementForLogging; // not thread safe.
190
Fyodor Kupolov00de49e2016-09-23 13:10:27 -0700191 AccountsDb(DeDatabaseHelper deDatabase, Context context, File preNDatabaseFile) {
192 mDeDatabase = deDatabase;
193 mContext = context;
194 mPreNDatabaseFile = preNDatabaseFile;
195 }
196
197 private static class CeDatabaseHelper extends SQLiteOpenHelper {
Fyodor Kupolov1ce01612016-08-26 11:39:07 -0700198
199 CeDatabaseHelper(Context context, String ceDatabaseName) {
200 super(context, ceDatabaseName, null, CE_DATABASE_VERSION);
201 }
202
203 /**
204 * This call needs to be made while the mCacheLock is held.
205 * @param db The database.
206 */
207 @Override
208 public void onCreate(SQLiteDatabase db) {
209 Log.i(TAG, "Creating CE database " + getDatabaseName());
210 db.execSQL("CREATE TABLE " + TABLE_ACCOUNTS + " ( "
211 + ACCOUNTS_ID + " INTEGER PRIMARY KEY AUTOINCREMENT, "
212 + ACCOUNTS_NAME + " TEXT NOT NULL, "
213 + ACCOUNTS_TYPE + " TEXT NOT NULL, "
214 + ACCOUNTS_PASSWORD + " TEXT, "
215 + "UNIQUE(" + ACCOUNTS_NAME + "," + ACCOUNTS_TYPE + "))");
216
217 db.execSQL("CREATE TABLE " + TABLE_AUTHTOKENS + " ( "
218 + AUTHTOKENS_ID + " INTEGER PRIMARY KEY AUTOINCREMENT, "
219 + AUTHTOKENS_ACCOUNTS_ID + " INTEGER NOT NULL, "
220 + AUTHTOKENS_TYPE + " TEXT NOT NULL, "
221 + AUTHTOKENS_AUTHTOKEN + " TEXT, "
222 + "UNIQUE (" + AUTHTOKENS_ACCOUNTS_ID + "," + AUTHTOKENS_TYPE + "))");
223
224 db.execSQL("CREATE TABLE " + TABLE_EXTRAS + " ( "
225 + EXTRAS_ID + " INTEGER PRIMARY KEY AUTOINCREMENT, "
226 + EXTRAS_ACCOUNTS_ID + " INTEGER, "
227 + EXTRAS_KEY + " TEXT NOT NULL, "
228 + EXTRAS_VALUE + " TEXT, "
229 + "UNIQUE(" + EXTRAS_ACCOUNTS_ID + "," + EXTRAS_KEY + "))");
230
231 createAccountsDeletionTrigger(db);
232 }
233
234 private void createAccountsDeletionTrigger(SQLiteDatabase db) {
235 db.execSQL(""
236 + " CREATE TRIGGER " + TABLE_ACCOUNTS + "Delete DELETE ON " + TABLE_ACCOUNTS
237 + " BEGIN"
238 + " DELETE FROM " + TABLE_AUTHTOKENS
239 + " WHERE " + AUTHTOKENS_ACCOUNTS_ID + "=OLD." + ACCOUNTS_ID + " ;"
240 + " DELETE FROM " + TABLE_EXTRAS
241 + " WHERE " + EXTRAS_ACCOUNTS_ID + "=OLD." + ACCOUNTS_ID + " ;"
242 + " END");
243 }
244
245 @Override
246 public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
247 Log.i(TAG, "Upgrade CE from version " + oldVersion + " to version " + newVersion);
248
249 if (oldVersion == 9) {
250 if (Log.isLoggable(TAG, Log.VERBOSE)) {
251 Log.v(TAG, "onUpgrade upgrading to v10");
252 }
253 db.execSQL("DROP TABLE IF EXISTS " + TABLE_META);
254 db.execSQL("DROP TABLE IF EXISTS " + TABLE_SHARED_ACCOUNTS);
255 // Recreate the trigger, since the old one references the table to be removed
256 db.execSQL("DROP TRIGGER IF EXISTS " + TABLE_ACCOUNTS + "Delete");
257 createAccountsDeletionTrigger(db);
258 db.execSQL("DROP TABLE IF EXISTS " + TABLE_GRANTS);
259 db.execSQL("DROP TABLE IF EXISTS " + TABLE_DEBUG);
Dmitry Dementyeve366f822017-01-31 10:25:10 -0800260 oldVersion++;
Fyodor Kupolov1ce01612016-08-26 11:39:07 -0700261 }
262
263 if (oldVersion != newVersion) {
264 Log.e(TAG, "failed to upgrade version " + oldVersion + " to version " + newVersion);
265 }
266 }
267
268 @Override
Dmitry Dementyev08b5a412019-03-19 16:17:16 -0700269 public void onDowngrade(SQLiteDatabase db, int oldVersion, int newVersion) {
270 Log.e(TAG, "onDowngrade: recreate accounts CE table");
271 resetDatabase(db);
272 onCreate(db);
273 }
274
275 @Override
Fyodor Kupolov1ce01612016-08-26 11:39:07 -0700276 public void onOpen(SQLiteDatabase db) {
277 if (Log.isLoggable(TAG, Log.VERBOSE)) Log.v(TAG, "opened database " + CE_DATABASE_NAME);
278 }
279
280
281 /**
282 * Creates a new {@code CeDatabaseHelper}. If pre-N db file is present at the old location,
283 * it also performs migration to the new CE database.
Fyodor Kupolov1ce01612016-08-26 11:39:07 -0700284 */
285 static CeDatabaseHelper create(
286 Context context,
Fyodor Kupolov1ce01612016-08-26 11:39:07 -0700287 File preNDatabaseFile,
288 File ceDatabaseFile) {
289 boolean newDbExists = ceDatabaseFile.exists();
290 if (Log.isLoggable(TAG, Log.VERBOSE)) {
Fyodor Kupolov00de49e2016-09-23 13:10:27 -0700291 Log.v(TAG, "CeDatabaseHelper.create ceDatabaseFile=" + ceDatabaseFile
292 + " oldDbExists=" + preNDatabaseFile.exists()
293 + " newDbExists=" + newDbExists);
Fyodor Kupolov1ce01612016-08-26 11:39:07 -0700294 }
295 boolean removeOldDb = false;
296 if (!newDbExists && preNDatabaseFile.exists()) {
297 removeOldDb = migratePreNDbToCe(preNDatabaseFile, ceDatabaseFile);
298 }
299 // Try to open and upgrade if necessary
300 CeDatabaseHelper ceHelper = new CeDatabaseHelper(context, ceDatabaseFile.getPath());
301 ceHelper.getWritableDatabase();
302 ceHelper.close();
303 if (removeOldDb) {
304 Slog.i(TAG, "Migration complete - removing pre-N db " + preNDatabaseFile);
305 if (!SQLiteDatabase.deleteDatabase(preNDatabaseFile)) {
306 Slog.e(TAG, "Cannot remove pre-N db " + preNDatabaseFile);
307 }
308 }
309 return ceHelper;
310 }
311
312 private static boolean migratePreNDbToCe(File oldDbFile, File ceDbFile) {
313 Slog.i(TAG, "Moving pre-N DB " + oldDbFile + " to CE " + ceDbFile);
314 try {
315 FileUtils.copyFileOrThrow(oldDbFile, ceDbFile);
316 } catch (IOException e) {
317 Slog.e(TAG, "Cannot copy file to " + ceDbFile + " from " + oldDbFile, e);
318 // Try to remove potentially damaged file if I/O error occurred
319 deleteDbFileWarnIfFailed(ceDbFile);
320 return false;
321 }
322 return true;
323 }
Fyodor Kupolov1ce01612016-08-26 11:39:07 -0700324 }
325
Fyodor Kupolov00de49e2016-09-23 13:10:27 -0700326 /**
327 * Returns information about auth tokens and their account for the specified query
328 * parameters.
329 * Output is in the format:
330 * <pre><code> | AUTHTOKEN_ID | ACCOUNT_NAME | AUTH_TOKEN_TYPE |</code></pre>
331 */
332 Cursor findAuthtokenForAllAccounts(String accountType, String authToken) {
333 SQLiteDatabase db = mDeDatabase.getReadableDatabaseUserIsUnlocked();
334 return db.rawQuery(
335 "SELECT " + CE_TABLE_AUTHTOKENS + "." + AUTHTOKENS_ID
336 + ", " + CE_TABLE_ACCOUNTS + "." + ACCOUNTS_NAME
337 + ", " + CE_TABLE_AUTHTOKENS + "." + AUTHTOKENS_TYPE
338 + " FROM " + CE_TABLE_ACCOUNTS
339 + " JOIN " + CE_TABLE_AUTHTOKENS
340 + " ON " + CE_TABLE_ACCOUNTS + "." + ACCOUNTS_ID
341 + " = " + CE_TABLE_AUTHTOKENS + "." + AUTHTOKENS_ACCOUNTS_ID
342 + " WHERE " + CE_TABLE_AUTHTOKENS + "." + AUTHTOKENS_AUTHTOKEN
343 + " = ? AND " + CE_TABLE_ACCOUNTS + "." + ACCOUNTS_TYPE + " = ?",
344 new String[]{authToken, accountType});
345 }
346
347 Map<String, String> findAuthTokensByAccount(Account account) {
348 SQLiteDatabase db = mDeDatabase.getReadableDatabaseUserIsUnlocked();
349 HashMap<String, String> authTokensForAccount = new HashMap<>();
350 Cursor cursor = db.query(CE_TABLE_AUTHTOKENS,
351 COLUMNS_AUTHTOKENS_TYPE_AND_AUTHTOKEN,
Dmitry Dementyeve5768622016-11-21 13:51:27 -0800352 SELECTION_ACCOUNTS_ID_BY_ACCOUNT,
Fyodor Kupolov00de49e2016-09-23 13:10:27 -0700353 new String[] {account.name, account.type},
354 null, null, null);
355 try {
356 while (cursor.moveToNext()) {
357 final String type = cursor.getString(0);
358 final String authToken = cursor.getString(1);
359 authTokensForAccount.put(type, authToken);
360 }
361 } finally {
362 cursor.close();
363 }
364 return authTokensForAccount;
365 }
366
367 boolean deleteAuthtokensByAccountIdAndType(long accountId, String authtokenType) {
368 SQLiteDatabase db = mDeDatabase.getWritableDatabaseUserIsUnlocked();
369 return db.delete(CE_TABLE_AUTHTOKENS,
Dmitry Dementyev01985ff2017-01-19 16:03:39 -0800370 AUTHTOKENS_ACCOUNTS_ID + "=?" + " AND " + AUTHTOKENS_TYPE + "=?",
Fyodor Kupolov00de49e2016-09-23 13:10:27 -0700371 new String[]{String.valueOf(accountId), authtokenType}) > 0;
372 }
373
374 boolean deleteAuthToken(String authTokenId) {
375 SQLiteDatabase db = mDeDatabase.getWritableDatabaseUserIsUnlocked();
376 return db.delete(
377 CE_TABLE_AUTHTOKENS, AUTHTOKENS_ID + "= ?",
378 new String[]{authTokenId}) > 0;
379 }
380
381 long insertAuthToken(long accountId, String authTokenType, String authToken) {
382 SQLiteDatabase db = mDeDatabase.getWritableDatabaseUserIsUnlocked();
383 ContentValues values = new ContentValues();
384 values.put(AUTHTOKENS_ACCOUNTS_ID, accountId);
385 values.put(AUTHTOKENS_TYPE, authTokenType);
386 values.put(AUTHTOKENS_AUTHTOKEN, authToken);
387 return db.insert(
388 CE_TABLE_AUTHTOKENS, AUTHTOKENS_AUTHTOKEN, values);
389 }
390
391 int updateCeAccountPassword(long accountId, String password) {
392 SQLiteDatabase db = mDeDatabase.getWritableDatabaseUserIsUnlocked();
393 final ContentValues values = new ContentValues();
394 values.put(ACCOUNTS_PASSWORD, password);
395 return db.update(
396 CE_TABLE_ACCOUNTS, values, ACCOUNTS_ID + "=?",
397 new String[] {String.valueOf(accountId)});
398 }
399
400 boolean renameCeAccount(long accountId, String newName) {
401 SQLiteDatabase db = mDeDatabase.getWritableDatabaseUserIsUnlocked();
402 final ContentValues values = new ContentValues();
403 values.put(ACCOUNTS_NAME, newName);
404 final String[] argsAccountId = {String.valueOf(accountId)};
405 return db.update(
406 CE_TABLE_ACCOUNTS, values, ACCOUNTS_ID + "=?", argsAccountId) > 0;
407 }
408
409 boolean deleteAuthTokensByAccountId(long accountId) {
410 SQLiteDatabase db = mDeDatabase.getWritableDatabaseUserIsUnlocked();
411 return db.delete(CE_TABLE_AUTHTOKENS, AUTHTOKENS_ACCOUNTS_ID + "=?",
412 new String[] {String.valueOf(accountId)}) > 0;
413 }
414
415 long findExtrasIdByAccountId(long accountId, String key) {
416 SQLiteDatabase db = mDeDatabase.getReadableDatabaseUserIsUnlocked();
417 Cursor cursor = db.query(
418 CE_TABLE_EXTRAS, new String[]{EXTRAS_ID},
419 EXTRAS_ACCOUNTS_ID + "=" + accountId + " AND " + EXTRAS_KEY + "=?",
420 new String[]{key}, null, null, null);
421 try {
422 if (cursor.moveToNext()) {
423 return cursor.getLong(0);
424 }
425 return -1;
426 } finally {
427 cursor.close();
428 }
429 }
430
431 boolean updateExtra(long extrasId, String value) {
432 SQLiteDatabase db = mDeDatabase.getWritableDatabaseUserIsUnlocked();
433 ContentValues values = new ContentValues();
434 values.put(EXTRAS_VALUE, value);
435 int rows = db.update(
436 TABLE_EXTRAS, values, EXTRAS_ID + "=?",
437 new String[]{String.valueOf(extrasId)});
438 return rows == 1;
439 }
440
441 long insertExtra(long accountId, String key, String value) {
442 SQLiteDatabase db = mDeDatabase.getWritableDatabaseUserIsUnlocked();
443 ContentValues values = new ContentValues();
444 values.put(EXTRAS_KEY, key);
445 values.put(EXTRAS_ACCOUNTS_ID, accountId);
446 values.put(EXTRAS_VALUE, value);
447 return db.insert(CE_TABLE_EXTRAS, EXTRAS_KEY, values);
448 }
449
450 Map<String, String> findUserExtrasForAccount(Account account) {
451 SQLiteDatabase db = mDeDatabase.getReadableDatabaseUserIsUnlocked();
452 Map<String, String> userExtrasForAccount = new HashMap<>();
453 String[] selectionArgs = {account.name, account.type};
454 try (Cursor cursor = db.query(CE_TABLE_EXTRAS,
455 COLUMNS_EXTRAS_KEY_AND_VALUE,
Dmitry Dementyeve5768622016-11-21 13:51:27 -0800456 SELECTION_ACCOUNTS_ID_BY_ACCOUNT,
Fyodor Kupolov00de49e2016-09-23 13:10:27 -0700457 selectionArgs,
458 null, null, null)) {
459 while (cursor.moveToNext()) {
460 final String tmpkey = cursor.getString(0);
461 final String value = cursor.getString(1);
462 userExtrasForAccount.put(tmpkey, value);
463 }
464 }
465 return userExtrasForAccount;
466 }
467
468 long findCeAccountId(Account account) {
469 SQLiteDatabase db = mDeDatabase.getReadableDatabaseUserIsUnlocked();
470 String[] columns = { ACCOUNTS_ID };
471 String selection = "name=? AND type=?";
472 String[] selectionArgs = {account.name, account.type};
473 try (Cursor cursor = db.query(CE_TABLE_ACCOUNTS, columns, selection, selectionArgs,
474 null, null, null)) {
475 if (cursor.moveToNext()) {
476 return cursor.getLong(0);
477 }
478 return -1;
479 }
480 }
481
482 String findAccountPasswordByNameAndType(String name, String type) {
483 SQLiteDatabase db = mDeDatabase.getReadableDatabaseUserIsUnlocked();
484 String selection = ACCOUNTS_NAME + "=? AND " + ACCOUNTS_TYPE + "=?";
485 String[] selectionArgs = {name, type};
486 String[] columns = {ACCOUNTS_PASSWORD};
487 try (Cursor cursor = db.query(CE_TABLE_ACCOUNTS, columns, selection, selectionArgs,
488 null, null, null)) {
489 if (cursor.moveToNext()) {
490 return cursor.getString(0);
491 }
492 return null;
493 }
494 }
495
496 long insertCeAccount(Account account, String password) {
497 SQLiteDatabase db = mDeDatabase.getWritableDatabaseUserIsUnlocked();
498 ContentValues values = new ContentValues();
499 values.put(ACCOUNTS_NAME, account.name);
500 values.put(ACCOUNTS_TYPE, account.type);
501 values.put(ACCOUNTS_PASSWORD, password);
502 return db.insert(
503 CE_TABLE_ACCOUNTS, ACCOUNTS_NAME, values);
504 }
505
506
Fyodor Kupolov1ce01612016-08-26 11:39:07 -0700507 static class DeDatabaseHelper extends SQLiteOpenHelper {
508
509 private final int mUserId;
510 private volatile boolean mCeAttached;
511
512 private DeDatabaseHelper(Context context, int userId, String deDatabaseName) {
513 super(context, deDatabaseName, null, DE_DATABASE_VERSION);
514 mUserId = userId;
515 }
516
517 /**
518 * This call needs to be made while the mCacheLock is held. The way to
519 * ensure this is to get the lock any time a method is called ont the DatabaseHelper
520 * @param db The database.
521 */
522 @Override
523 public void onCreate(SQLiteDatabase db) {
524 Log.i(TAG, "Creating DE database for user " + mUserId);
525 db.execSQL("CREATE TABLE " + TABLE_ACCOUNTS + " ( "
526 + ACCOUNTS_ID + " INTEGER PRIMARY KEY, "
527 + ACCOUNTS_NAME + " TEXT NOT NULL, "
528 + ACCOUNTS_TYPE + " TEXT NOT NULL, "
529 + ACCOUNTS_PREVIOUS_NAME + " TEXT, "
530 + ACCOUNTS_LAST_AUTHENTICATE_TIME_EPOCH_MILLIS + " INTEGER DEFAULT 0, "
531 + "UNIQUE(" + ACCOUNTS_NAME + "," + ACCOUNTS_TYPE + "))");
532
533 db.execSQL("CREATE TABLE " + TABLE_META + " ( "
534 + META_KEY + " TEXT PRIMARY KEY NOT NULL, "
535 + META_VALUE + " TEXT)");
536
537 createGrantsTable(db);
538 createSharedAccountsTable(db);
539 createAccountsDeletionTrigger(db);
Fyodor Kupolov00de49e2016-09-23 13:10:27 -0700540 createDebugTable(db);
Dmitry Dementyeve5768622016-11-21 13:51:27 -0800541 createAccountsVisibilityTable(db);
542 createAccountsDeletionVisibilityCleanupTrigger(db);
Fyodor Kupolov1ce01612016-08-26 11:39:07 -0700543 }
544
545 private void createSharedAccountsTable(SQLiteDatabase db) {
546 db.execSQL("CREATE TABLE " + TABLE_SHARED_ACCOUNTS + " ( "
547 + ACCOUNTS_ID + " INTEGER PRIMARY KEY AUTOINCREMENT, "
548 + ACCOUNTS_NAME + " TEXT NOT NULL, "
549 + ACCOUNTS_TYPE + " TEXT NOT NULL, "
550 + "UNIQUE(" + ACCOUNTS_NAME + "," + ACCOUNTS_TYPE + "))");
551 }
552
553 private void createAccountsDeletionTrigger(SQLiteDatabase db) {
554 db.execSQL(""
555 + " CREATE TRIGGER " + TABLE_ACCOUNTS + "Delete DELETE ON " + TABLE_ACCOUNTS
556 + " BEGIN"
557 + " DELETE FROM " + TABLE_GRANTS
558 + " WHERE " + GRANTS_ACCOUNTS_ID + "=OLD." + ACCOUNTS_ID + " ;"
559 + " END");
560 }
561
562 private void createGrantsTable(SQLiteDatabase db) {
563 db.execSQL("CREATE TABLE " + TABLE_GRANTS + " ( "
564 + GRANTS_ACCOUNTS_ID + " INTEGER NOT NULL, "
565 + GRANTS_AUTH_TOKEN_TYPE + " STRING NOT NULL, "
566 + GRANTS_GRANTEE_UID + " INTEGER NOT NULL, "
567 + "UNIQUE (" + GRANTS_ACCOUNTS_ID + "," + GRANTS_AUTH_TOKEN_TYPE
568 + "," + GRANTS_GRANTEE_UID + "))");
569 }
570
Dmitry Dementyeve5768622016-11-21 13:51:27 -0800571 private void createAccountsVisibilityTable(SQLiteDatabase db) {
572 db.execSQL("CREATE TABLE " + TABLE_VISIBILITY + " ( "
573 + VISIBILITY_ACCOUNTS_ID + " INTEGER NOT NULL, "
Dmitry Dementyeve366f822017-01-31 10:25:10 -0800574 + VISIBILITY_PACKAGE + " TEXT NOT NULL, "
Dmitry Dementyeve5768622016-11-21 13:51:27 -0800575 + VISIBILITY_VALUE + " INTEGER, "
Dmitry Dementyeve366f822017-01-31 10:25:10 -0800576 + "PRIMARY KEY(" + VISIBILITY_ACCOUNTS_ID + "," + VISIBILITY_PACKAGE + "))");
Dmitry Dementyeve5768622016-11-21 13:51:27 -0800577 }
578
Fyodor Kupolov00de49e2016-09-23 13:10:27 -0700579 static void createDebugTable(SQLiteDatabase db) {
580 db.execSQL("CREATE TABLE " + TABLE_DEBUG + " ( "
581 + ACCOUNTS_ID + " INTEGER,"
582 + DEBUG_TABLE_ACTION_TYPE + " TEXT NOT NULL, "
583 + DEBUG_TABLE_TIMESTAMP + " DATETIME,"
584 + DEBUG_TABLE_CALLER_UID + " INTEGER NOT NULL,"
585 + DEBUG_TABLE_TABLE_NAME + " TEXT NOT NULL,"
586 + DEBUG_TABLE_KEY + " INTEGER PRIMARY KEY)");
587 db.execSQL("CREATE INDEX timestamp_index ON " + TABLE_DEBUG + " ("
588 + DEBUG_TABLE_TIMESTAMP + ")");
589 }
590
Dmitry Dementyeve5768622016-11-21 13:51:27 -0800591 private void createAccountsDeletionVisibilityCleanupTrigger(SQLiteDatabase db) {
592 db.execSQL(""
593 + " CREATE TRIGGER "
594 + TABLE_ACCOUNTS + "DeleteVisibility DELETE ON " + TABLE_ACCOUNTS
595 + " BEGIN"
596 + " DELETE FROM " + TABLE_VISIBILITY
597 + " WHERE " + VISIBILITY_ACCOUNTS_ID + "=OLD." + ACCOUNTS_ID + " ;"
598 + " END");
599 }
600
Fyodor Kupolov1ce01612016-08-26 11:39:07 -0700601 @Override
602 public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
603 Log.i(TAG, "upgrade from version " + oldVersion + " to version " + newVersion);
604
Dmitry Dementyeve5768622016-11-21 13:51:27 -0800605 if (oldVersion == 1) {
Dmitry Dementyeve366f822017-01-31 10:25:10 -0800606 createAccountsVisibilityTable(db);
607 createAccountsDeletionVisibilityCleanupTrigger(db);
608 oldVersion = 3; // skip version 2 which had uid based table
609 }
610
611 if (oldVersion == 2) {
612 // Remove uid based table and replace it with packageName based
613 db.execSQL("DROP TRIGGER IF EXISTS " + TABLE_ACCOUNTS + "DeleteVisibility");
Dmitry Dementyev6fe34fcb2017-02-03 17:46:10 -0800614 db.execSQL("DROP TABLE IF EXISTS " + TABLE_VISIBILITY);
Dmitry Dementyeve366f822017-01-31 10:25:10 -0800615 createAccountsVisibilityTable(db);
616 createAccountsDeletionVisibilityCleanupTrigger(db);
617 oldVersion++;
Dmitry Dementyeve5768622016-11-21 13:51:27 -0800618 }
619
Fyodor Kupolov1ce01612016-08-26 11:39:07 -0700620 if (oldVersion != newVersion) {
621 Log.e(TAG, "failed to upgrade version " + oldVersion + " to version " + newVersion);
622 }
623 }
624
Dmitry Dementyev08b5a412019-03-19 16:17:16 -0700625 @Override
626 public void onDowngrade(SQLiteDatabase db, int oldVersion, int newVersion) {
627 Log.e(TAG, "onDowngrade: recreate accounts DE table");
628 resetDatabase(db);
629 onCreate(db);
630 }
631
Fyodor Kupolov1ce01612016-08-26 11:39:07 -0700632 public SQLiteDatabase getReadableDatabaseUserIsUnlocked() {
633 if(!mCeAttached) {
634 Log.wtf(TAG, "getReadableDatabaseUserIsUnlocked called while user " + mUserId
635 + " is still locked. CE database is not yet available.", new Throwable());
636 }
637 return super.getReadableDatabase();
638 }
639
640 public SQLiteDatabase getWritableDatabaseUserIsUnlocked() {
641 if(!mCeAttached) {
642 Log.wtf(TAG, "getWritableDatabaseUserIsUnlocked called while user " + mUserId
643 + " is still locked. CE database is not yet available.", new Throwable());
644 }
645 return super.getWritableDatabase();
646 }
647
648 @Override
649 public void onOpen(SQLiteDatabase db) {
650 if (Log.isLoggable(TAG, Log.VERBOSE)) Log.v(TAG, "opened database " + DE_DATABASE_NAME);
651 }
652
653 private void migratePreNDbToDe(File preNDbFile) {
654 Log.i(TAG, "Migrate pre-N database to DE preNDbFile=" + preNDbFile);
655 SQLiteDatabase db = getWritableDatabase();
656 db.execSQL("ATTACH DATABASE '" + preNDbFile.getPath() + "' AS preNDb");
657 db.beginTransaction();
658 // Copy accounts fields
659 db.execSQL("INSERT INTO " + TABLE_ACCOUNTS
660 + "(" + ACCOUNTS_ID + "," + ACCOUNTS_NAME + "," + ACCOUNTS_TYPE + ", "
661 + ACCOUNTS_PREVIOUS_NAME + ", " + ACCOUNTS_LAST_AUTHENTICATE_TIME_EPOCH_MILLIS
662 + ") "
663 + "SELECT " + ACCOUNTS_ID + "," + ACCOUNTS_NAME + "," + ACCOUNTS_TYPE + ", "
664 + ACCOUNTS_PREVIOUS_NAME + ", " + ACCOUNTS_LAST_AUTHENTICATE_TIME_EPOCH_MILLIS
665 + " FROM preNDb." + TABLE_ACCOUNTS);
666 // Copy SHARED_ACCOUNTS
667 db.execSQL("INSERT INTO " + TABLE_SHARED_ACCOUNTS
668 + "(" + SHARED_ACCOUNTS_ID + "," + ACCOUNTS_NAME + "," + ACCOUNTS_TYPE + ") " +
669 "SELECT " + SHARED_ACCOUNTS_ID + "," + ACCOUNTS_NAME + "," + ACCOUNTS_TYPE
670 + " FROM preNDb." + TABLE_SHARED_ACCOUNTS);
671 // Copy DEBUG_TABLE
672 db.execSQL("INSERT INTO " + TABLE_DEBUG
673 + "(" + ACCOUNTS_ID + "," + DEBUG_TABLE_ACTION_TYPE + ","
674 + DEBUG_TABLE_TIMESTAMP + "," + DEBUG_TABLE_CALLER_UID + ","
675 + DEBUG_TABLE_TABLE_NAME + "," + DEBUG_TABLE_KEY + ") " +
676 "SELECT " + ACCOUNTS_ID + "," + DEBUG_TABLE_ACTION_TYPE + ","
677 + DEBUG_TABLE_TIMESTAMP + "," + DEBUG_TABLE_CALLER_UID + ","
678 + DEBUG_TABLE_TABLE_NAME + "," + DEBUG_TABLE_KEY
679 + " FROM preNDb." + TABLE_DEBUG);
680 // Copy GRANTS
681 db.execSQL("INSERT INTO " + TABLE_GRANTS
682 + "(" + GRANTS_ACCOUNTS_ID + "," + GRANTS_AUTH_TOKEN_TYPE + ","
683 + GRANTS_GRANTEE_UID + ") " +
684 "SELECT " + GRANTS_ACCOUNTS_ID + "," + GRANTS_AUTH_TOKEN_TYPE + ","
685 + GRANTS_GRANTEE_UID + " FROM preNDb." + TABLE_GRANTS);
686 // Copy META
687 db.execSQL("INSERT INTO " + TABLE_META
688 + "(" + META_KEY + "," + META_VALUE + ") "
689 + "SELECT " + META_KEY + "," + META_VALUE + " FROM preNDb." + TABLE_META);
690 db.setTransactionSuccessful();
691 db.endTransaction();
692
693 db.execSQL("DETACH DATABASE preNDb");
694 }
Fyodor Kupolov00de49e2016-09-23 13:10:27 -0700695 }
Fyodor Kupolov1ce01612016-08-26 11:39:07 -0700696
Fyodor Kupolov00de49e2016-09-23 13:10:27 -0700697 boolean deleteDeAccount(long accountId) {
698 SQLiteDatabase db = mDeDatabase.getWritableDatabase();
699 return db.delete(TABLE_ACCOUNTS, ACCOUNTS_ID + "=" + accountId, null) > 0;
700 }
Fyodor Kupolov1ce01612016-08-26 11:39:07 -0700701
Fyodor Kupolov00de49e2016-09-23 13:10:27 -0700702 long insertSharedAccount(Account account) {
703 SQLiteDatabase db = mDeDatabase.getWritableDatabase();
704 ContentValues values = new ContentValues();
705 values.put(ACCOUNTS_NAME, account.name);
706 values.put(ACCOUNTS_TYPE, account.type);
707 return db.insert(
708 TABLE_SHARED_ACCOUNTS, ACCOUNTS_NAME, values);
709 }
Fyodor Kupolov1ce01612016-08-26 11:39:07 -0700710
Fyodor Kupolov00de49e2016-09-23 13:10:27 -0700711 boolean deleteSharedAccount(Account account) {
712 SQLiteDatabase db = mDeDatabase.getWritableDatabase();
713 return db.delete(TABLE_SHARED_ACCOUNTS, ACCOUNTS_NAME + "=? AND " + ACCOUNTS_TYPE + "=?",
714 new String[]{account.name, account.type}) > 0;
715 }
Fyodor Kupolov1ce01612016-08-26 11:39:07 -0700716
Fyodor Kupolov00de49e2016-09-23 13:10:27 -0700717 int renameSharedAccount(Account account, String newName) {
718 SQLiteDatabase db = mDeDatabase.getWritableDatabase();
719 final ContentValues values = new ContentValues();
720 values.put(ACCOUNTS_NAME, newName);
721 return db.update(TABLE_SHARED_ACCOUNTS,
722 values,
723 ACCOUNTS_NAME + "=? AND " + ACCOUNTS_TYPE + "=?",
724 new String[] {account.name, account.type});
725 }
Fyodor Kupolov1ce01612016-08-26 11:39:07 -0700726
Fyodor Kupolov00de49e2016-09-23 13:10:27 -0700727 List<Account> getSharedAccounts() {
728 SQLiteDatabase db = mDeDatabase.getReadableDatabase();
729 ArrayList<Account> accountList = new ArrayList<>();
730 Cursor cursor = null;
731 try {
732 cursor = db.query(TABLE_SHARED_ACCOUNTS, new String[] {ACCOUNTS_NAME, ACCOUNTS_TYPE},
733 null, null, null, null, null);
734 if (cursor != null && cursor.moveToFirst()) {
735 int nameIndex = cursor.getColumnIndex(ACCOUNTS_NAME);
736 int typeIndex = cursor.getColumnIndex(ACCOUNTS_TYPE);
Fyodor Kupolov1ce01612016-08-26 11:39:07 -0700737 do {
Fyodor Kupolov00de49e2016-09-23 13:10:27 -0700738 accountList.add(new Account(cursor.getString(nameIndex),
739 cursor.getString(typeIndex)));
Fyodor Kupolov1ce01612016-08-26 11:39:07 -0700740 } while (cursor.moveToNext());
Fyodor Kupolov00de49e2016-09-23 13:10:27 -0700741 }
742 } finally {
743 if (cursor != null) {
744 cursor.close();
Fyodor Kupolov1ce01612016-08-26 11:39:07 -0700745 }
746 }
Fyodor Kupolov00de49e2016-09-23 13:10:27 -0700747 return accountList;
748 }
Fyodor Kupolov1ce01612016-08-26 11:39:07 -0700749
Fyodor Kupolov00de49e2016-09-23 13:10:27 -0700750 long findSharedAccountId(Account account) {
751 SQLiteDatabase db = mDeDatabase.getReadableDatabase();
752 Cursor cursor = db.query(TABLE_SHARED_ACCOUNTS, new String[]{
753 ACCOUNTS_ID},
754 "name=? AND type=?", new String[]{account.name, account.type}, null, null,
755 null);
756 try {
757 if (cursor.moveToNext()) {
758 return cursor.getLong(0);
Fyodor Kupolov1ce01612016-08-26 11:39:07 -0700759 }
Fyodor Kupolov00de49e2016-09-23 13:10:27 -0700760 return -1;
761 } finally {
762 cursor.close();
Fyodor Kupolov1ce01612016-08-26 11:39:07 -0700763 }
764 }
765
Fyodor Kupolov00de49e2016-09-23 13:10:27 -0700766 long findAccountLastAuthenticatedTime(Account account) {
767 SQLiteDatabase db = mDeDatabase.getReadableDatabase();
768 return DatabaseUtils.longForQuery(db,
769 "SELECT " + AccountsDb.ACCOUNTS_LAST_AUTHENTICATE_TIME_EPOCH_MILLIS
770 + " FROM " + TABLE_ACCOUNTS + " WHERE " + ACCOUNTS_NAME + "=? AND "
771 + ACCOUNTS_TYPE + "=?",
772 new String[] {account.name, account.type});
773 }
774
775 boolean updateAccountLastAuthenticatedTime(Account account) {
776 SQLiteDatabase db = mDeDatabase.getWritableDatabase();
777 final ContentValues values = new ContentValues();
778 values.put(ACCOUNTS_LAST_AUTHENTICATE_TIME_EPOCH_MILLIS, System.currentTimeMillis());
779 int rowCount = db.update(TABLE_ACCOUNTS,
780 values,
781 ACCOUNTS_NAME + "=? AND " + ACCOUNTS_TYPE + "=?",
782 new String[] { account.name, account.type });
783 return rowCount > 0;
784 }
785
786 void dumpDeAccountsTable(PrintWriter pw) {
787 SQLiteDatabase db = mDeDatabase.getReadableDatabase();
788 Cursor cursor = db.query(
789 TABLE_ACCOUNTS, ACCOUNT_TYPE_COUNT_PROJECTION,
790 null, null, ACCOUNTS_TYPE, null, null);
791 try {
792 while (cursor.moveToNext()) {
793 // print type,count
794 pw.println(cursor.getString(0) + "," + cursor.getString(1));
795 }
796 } finally {
797 if (cursor != null) {
798 cursor.close();
799 }
800 }
801 }
802
803 long findDeAccountId(Account account) {
804 SQLiteDatabase db = mDeDatabase.getReadableDatabase();
805 String[] columns = {ACCOUNTS_ID};
806 String selection = "name=? AND type=?";
807 String[] selectionArgs = {account.name, account.type};
808 try (Cursor cursor = db.query(TABLE_ACCOUNTS, columns, selection, selectionArgs,
809 null, null, null)) {
810 if (cursor.moveToNext()) {
811 return cursor.getLong(0);
812 }
813 return -1;
814 }
815 }
816
817 Map<Long, Account> findAllDeAccounts() {
818 SQLiteDatabase db = mDeDatabase.getReadableDatabase();
819 LinkedHashMap<Long, Account> map = new LinkedHashMap<>();
820 String[] columns = {ACCOUNTS_ID, ACCOUNTS_TYPE, ACCOUNTS_NAME};
821 try (Cursor cursor = db.query(TABLE_ACCOUNTS, columns,
822 null, null, null, null, ACCOUNTS_ID)) {
823 while (cursor.moveToNext()) {
824 final long accountId = cursor.getLong(0);
825 final String accountType = cursor.getString(1);
826 final String accountName = cursor.getString(2);
827
828 final Account account = new Account(accountName, accountType);
829 map.put(accountId, account);
830 }
831 }
832 return map;
833 }
834
835 String findDeAccountPreviousName(Account account) {
836 SQLiteDatabase db = mDeDatabase.getReadableDatabase();
837 String[] columns = {ACCOUNTS_PREVIOUS_NAME};
838 String selection = ACCOUNTS_NAME + "=? AND " + ACCOUNTS_TYPE + "=?";
839 String[] selectionArgs = {account.name, account.type};
840 try (Cursor cursor = db.query(TABLE_ACCOUNTS, columns, selection, selectionArgs,
841 null, null, null)) {
842 if (cursor.moveToNext()) {
843 return cursor.getString(0);
844 }
845 }
846 return null;
847 }
848
849 long insertDeAccount(Account account, long accountId) {
850 SQLiteDatabase db = mDeDatabase.getWritableDatabase();
851 ContentValues values = new ContentValues();
852 values.put(ACCOUNTS_ID, accountId);
853 values.put(ACCOUNTS_NAME, account.name);
854 values.put(ACCOUNTS_TYPE, account.type);
855 values.put(ACCOUNTS_LAST_AUTHENTICATE_TIME_EPOCH_MILLIS, System.currentTimeMillis());
856 return db.insert(TABLE_ACCOUNTS, ACCOUNTS_NAME, values);
857 }
858
859 boolean renameDeAccount(long accountId, String newName, String previousName) {
860 SQLiteDatabase db = mDeDatabase.getWritableDatabase();
861 final ContentValues values = new ContentValues();
862 values.put(ACCOUNTS_NAME, newName);
863 values.put(ACCOUNTS_PREVIOUS_NAME, previousName);
864 final String[] argsAccountId = {String.valueOf(accountId)};
865 return db.update(TABLE_ACCOUNTS, values, ACCOUNTS_ID + "=?", argsAccountId) > 0;
866 }
867
868 boolean deleteGrantsByAccountIdAuthTokenTypeAndUid(long accountId,
869 String authTokenType, long uid) {
870 SQLiteDatabase db = mDeDatabase.getWritableDatabase();
871 return db.delete(TABLE_GRANTS,
872 GRANTS_ACCOUNTS_ID + "=? AND " + GRANTS_AUTH_TOKEN_TYPE + "=? AND "
873 + GRANTS_GRANTEE_UID + "=?",
874 new String[] {String.valueOf(accountId), authTokenType, String.valueOf(uid)}) > 0;
875 }
876
877 List<Integer> findAllUidGrants() {
878 SQLiteDatabase db = mDeDatabase.getReadableDatabase();
879 List<Integer> result = new ArrayList<>();
880 final Cursor cursor = db.query(TABLE_GRANTS,
881 new String[]{GRANTS_GRANTEE_UID},
882 null, null, GRANTS_GRANTEE_UID, null, null);
883 try {
884 while (cursor.moveToNext()) {
885 final int uid = cursor.getInt(0);
886 result.add(uid);
887 }
888 } finally {
889 cursor.close();
890 }
891 return result;
892 }
893
894 long findMatchingGrantsCount(int uid, String authTokenType, Account account) {
895 SQLiteDatabase db = mDeDatabase.getReadableDatabase();
896 String[] args = {String.valueOf(uid), authTokenType, account.name, account.type};
897 return DatabaseUtils.longForQuery(db, COUNT_OF_MATCHING_GRANTS, args);
898 }
899
900 long findMatchingGrantsCountAnyToken(int uid, Account account) {
901 SQLiteDatabase db = mDeDatabase.getReadableDatabase();
902 String[] args = {String.valueOf(uid), account.name, account.type};
903 return DatabaseUtils.longForQuery(db, COUNT_OF_MATCHING_GRANTS_ANY_TOKEN, args);
904 }
905
906 long insertGrant(long accountId, String authTokenType, int uid) {
907 SQLiteDatabase db = mDeDatabase.getWritableDatabase();
908 ContentValues values = new ContentValues();
909 values.put(GRANTS_ACCOUNTS_ID, accountId);
910 values.put(GRANTS_AUTH_TOKEN_TYPE, authTokenType);
911 values.put(GRANTS_GRANTEE_UID, uid);
912 return db.insert(TABLE_GRANTS, GRANTS_ACCOUNTS_ID, values);
913 }
914
915 boolean deleteGrantsByUid(int uid) {
916 SQLiteDatabase db = mDeDatabase.getWritableDatabase();
917 return db.delete(TABLE_GRANTS, GRANTS_GRANTEE_UID + "=?",
918 new String[] {Integer.toString(uid)}) > 0;
919 }
920
Dmitry Dementyeve366f822017-01-31 10:25:10 -0800921 boolean setAccountVisibility(long accountId, String packageName, int visibility) {
Dmitry Dementyeve5768622016-11-21 13:51:27 -0800922 SQLiteDatabase db = mDeDatabase.getWritableDatabase();
923 ContentValues values = new ContentValues();
924 values.put(VISIBILITY_ACCOUNTS_ID, String.valueOf(accountId));
Dmitry Dementyeve366f822017-01-31 10:25:10 -0800925 values.put(VISIBILITY_PACKAGE, packageName);
Dmitry Dementyeve5768622016-11-21 13:51:27 -0800926 values.put(VISIBILITY_VALUE, String.valueOf(visibility));
927 return (db.replace(TABLE_VISIBILITY, VISIBILITY_VALUE, values) != -1);
928 }
929
Dmitry Dementyeve366f822017-01-31 10:25:10 -0800930 Integer findAccountVisibility(Account account, String packageName) {
Dmitry Dementyev71fa5262017-03-23 12:29:17 -0700931 SQLiteDatabase db = mDeDatabase.getReadableDatabase();
Dmitry Dementyeve5768622016-11-21 13:51:27 -0800932 final Cursor cursor = db.query(TABLE_VISIBILITY, new String[] {VISIBILITY_VALUE},
Dmitry Dementyeve366f822017-01-31 10:25:10 -0800933 SELECTION_ACCOUNTS_ID_BY_ACCOUNT + " AND " + VISIBILITY_PACKAGE + "=? ",
934 new String[] {account.name, account.type, packageName}, null, null, null);
Dmitry Dementyeve5768622016-11-21 13:51:27 -0800935 try {
936 while (cursor.moveToNext()) {
937 return cursor.getInt(0);
938 }
939 } finally {
940 cursor.close();
941 }
942 return null;
943 }
944
Dmitry Dementyeve366f822017-01-31 10:25:10 -0800945 Integer findAccountVisibility(long accountId, String packageName) {
Dmitry Dementyev71fa5262017-03-23 12:29:17 -0700946 SQLiteDatabase db = mDeDatabase.getReadableDatabase();
Dmitry Dementyeve5768622016-11-21 13:51:27 -0800947 final Cursor cursor = db.query(TABLE_VISIBILITY, new String[] {VISIBILITY_VALUE},
Dmitry Dementyeve366f822017-01-31 10:25:10 -0800948 VISIBILITY_ACCOUNTS_ID + "=? AND " + VISIBILITY_PACKAGE + "=? ",
949 new String[] {String.valueOf(accountId), packageName}, null, null, null);
Dmitry Dementyeve5768622016-11-21 13:51:27 -0800950 try {
951 while (cursor.moveToNext()) {
952 return cursor.getInt(0);
953 }
954 } finally {
955 cursor.close();
956 }
957 return null;
958 }
959
960 Account findDeAccountByAccountId(long accountId) {
961 SQLiteDatabase db = mDeDatabase.getReadableDatabase();
962 final Cursor cursor = db.query(TABLE_ACCOUNTS, new String[] {ACCOUNTS_NAME, ACCOUNTS_TYPE},
963 ACCOUNTS_ID + "=? ", new String[] {String.valueOf(accountId)}, null, null, null);
964 try {
965 while (cursor.moveToNext()) {
966 return new Account(cursor.getString(0), cursor.getString(1));
967 }
968 } finally {
969 cursor.close();
970 }
971 return null;
972 }
973
974 /**
Dmitry Dementyeve366f822017-01-31 10:25:10 -0800975 * Returns a map from packageNames to visibility.
Dmitry Dementyeve5768622016-11-21 13:51:27 -0800976 */
Dmitry Dementyeve366f822017-01-31 10:25:10 -0800977 Map<String, Integer> findAllVisibilityValuesForAccount(Account account) {
Dmitry Dementyeve5768622016-11-21 13:51:27 -0800978 SQLiteDatabase db = mDeDatabase.getReadableDatabase();
Dmitry Dementyeve366f822017-01-31 10:25:10 -0800979 Map<String, Integer> result = new HashMap<>();
Dmitry Dementyev01985ff2017-01-19 16:03:39 -0800980 final Cursor cursor =
Dmitry Dementyeve366f822017-01-31 10:25:10 -0800981 db.query(TABLE_VISIBILITY, new String[] {VISIBILITY_PACKAGE, VISIBILITY_VALUE},
982 SELECTION_ACCOUNTS_ID_BY_ACCOUNT, new String[] {account.name, account.type},
983 null, null, null);
Dmitry Dementyeve5768622016-11-21 13:51:27 -0800984 try {
985 while (cursor.moveToNext()) {
Dmitry Dementyeve366f822017-01-31 10:25:10 -0800986 result.put(cursor.getString(0), cursor.getInt(1));
Dmitry Dementyeve5768622016-11-21 13:51:27 -0800987 }
988 } finally {
989 cursor.close();
990 }
991 return result;
992 }
993
Dmitry Dementyev71fa5262017-03-23 12:29:17 -0700994 /**
995 * Returns a map account -> (package -> visibility)
996 */
997 Map <Account, Map<String, Integer>> findAllVisibilityValues() {
998 SQLiteDatabase db = mDeDatabase.getReadableDatabase();
999 Map<Account, Map<String, Integer>> result = new HashMap<>();
1000 Cursor cursor = db.rawQuery(
1001 "SELECT " + TABLE_VISIBILITY + "." + VISIBILITY_PACKAGE
1002 + ", " + TABLE_VISIBILITY + "." + VISIBILITY_VALUE
1003 + ", " + TABLE_ACCOUNTS + "." + ACCOUNTS_NAME
1004 + ", " + TABLE_ACCOUNTS + "." + ACCOUNTS_TYPE
1005 + " FROM " + TABLE_VISIBILITY
1006 + " JOIN " + TABLE_ACCOUNTS
1007 + " ON " + TABLE_ACCOUNTS + "." + ACCOUNTS_ID
1008 + " = " + TABLE_VISIBILITY + "." + VISIBILITY_ACCOUNTS_ID, null);
1009 try {
1010 while (cursor.moveToNext()) {
1011 String packageName = cursor.getString(0);
1012 Integer visibility = cursor.getInt(1);
1013 String accountName = cursor.getString(2);
1014 String accountType = cursor.getString(3);
1015 Account account = new Account(accountName, accountType);
1016 Map <String, Integer> accountVisibility = result.get(account);
1017 if (accountVisibility == null) {
1018 accountVisibility = new HashMap<>();
1019 result.put(account, accountVisibility);
1020 }
1021 accountVisibility.put(packageName, visibility);
1022 }
1023 } finally {
1024 cursor.close();
1025 }
1026 return result;
1027 }
1028
Dmitry Dementyeve366f822017-01-31 10:25:10 -08001029 boolean deleteAccountVisibilityForPackage(String packageName) {
Dmitry Dementyeve5768622016-11-21 13:51:27 -08001030 SQLiteDatabase db = mDeDatabase.getWritableDatabase();
Dmitry Dementyeve366f822017-01-31 10:25:10 -08001031 return db.delete(TABLE_VISIBILITY, VISIBILITY_PACKAGE + "=? ",
1032 new String[] {packageName}) > 0;
Dmitry Dementyeve5768622016-11-21 13:51:27 -08001033 }
1034
Fyodor Kupolov00de49e2016-09-23 13:10:27 -07001035 long insertOrReplaceMetaAuthTypeAndUid(String authenticatorType, int uid) {
1036 SQLiteDatabase db = mDeDatabase.getWritableDatabase();
1037 ContentValues values = new ContentValues();
1038 values.put(META_KEY,
1039 META_KEY_FOR_AUTHENTICATOR_UID_FOR_TYPE_PREFIX + authenticatorType);
1040 values.put(META_VALUE, uid);
1041 return db.insertWithOnConflict(TABLE_META, null, values,
1042 SQLiteDatabase.CONFLICT_REPLACE);
1043 }
1044
1045 Map<String, Integer> findMetaAuthUid() {
1046 SQLiteDatabase db = mDeDatabase.getReadableDatabase();
1047 Cursor metaCursor = db.query(
1048 TABLE_META,
1049 new String[]{META_KEY, META_VALUE},
1050 SELECTION_META_BY_AUTHENTICATOR_TYPE,
1051 new String[]{META_KEY_FOR_AUTHENTICATOR_UID_FOR_TYPE_PREFIX + "%"},
1052 null /* groupBy */,
1053 null /* having */,
1054 META_KEY);
1055 Map<String, Integer> map = new LinkedHashMap<>();
1056 try {
1057 while (metaCursor.moveToNext()) {
1058 String type = TextUtils
1059 .split(metaCursor.getString(0), META_KEY_DELIMITER)[1];
1060 String uidStr = metaCursor.getString(1);
1061 if (TextUtils.isEmpty(type) || TextUtils.isEmpty(uidStr)) {
1062 // Should never happen.
1063 Slog.e(TAG, "Auth type empty: " + TextUtils.isEmpty(type)
1064 + ", uid empty: " + TextUtils.isEmpty(uidStr));
1065 continue;
1066 }
1067 int uid = Integer.parseInt(metaCursor.getString(1));
1068 map.put(type, uid);
1069 }
1070 } finally {
1071 metaCursor.close();
1072 }
1073 return map;
1074 }
1075
1076 boolean deleteMetaByAuthTypeAndUid(String type, int uid) {
1077 SQLiteDatabase db = mDeDatabase.getWritableDatabase();
1078 return db.delete(
1079 TABLE_META,
1080 META_KEY + "=? AND " + META_VALUE + "=?",
1081 new String[]{
1082 META_KEY_FOR_AUTHENTICATOR_UID_FOR_TYPE_PREFIX + type,
1083 String.valueOf(uid)}
1084 ) > 0;
1085 }
1086
Fyodor Kupolov79d138e2016-10-04 12:09:05 -07001087 /**
1088 * Returns list of all grants as {@link Pair pairs} of account name and UID.
1089 */
Fyodor Kupolov00de49e2016-09-23 13:10:27 -07001090 List<Pair<String, Integer>> findAllAccountGrants() {
1091 SQLiteDatabase db = mDeDatabase.getReadableDatabase();
1092 try (Cursor cursor = db.rawQuery(ACCOUNT_ACCESS_GRANTS, null)) {
1093 if (cursor == null || !cursor.moveToFirst()) {
1094 return Collections.emptyList();
1095 }
1096 List<Pair<String, Integer>> results = new ArrayList<>();
1097 do {
1098 final String accountName = cursor.getString(0);
1099 final int uid = cursor.getInt(1);
1100 results.add(Pair.create(accountName, uid));
1101 } while (cursor.moveToNext());
1102 return results;
1103 }
1104 }
1105
1106 private static class PreNDatabaseHelper extends SQLiteOpenHelper {
Fyodor Kupolov1ce01612016-08-26 11:39:07 -07001107 private final Context mContext;
1108 private final int mUserId;
1109
Fyodor Kupolov00de49e2016-09-23 13:10:27 -07001110 PreNDatabaseHelper(Context context, int userId, String preNDatabaseName) {
Fyodor Kupolov1ce01612016-08-26 11:39:07 -07001111 super(context, preNDatabaseName, null, PRE_N_DATABASE_VERSION);
1112 mContext = context;
1113 mUserId = userId;
1114 }
1115
1116 @Override
1117 public void onCreate(SQLiteDatabase db) {
1118 // We use PreNDatabaseHelper only if pre-N db exists
1119 throw new IllegalStateException("Legacy database cannot be created - only upgraded!");
1120 }
1121
1122 private void createSharedAccountsTable(SQLiteDatabase db) {
1123 db.execSQL("CREATE TABLE " + TABLE_SHARED_ACCOUNTS + " ( "
1124 + ACCOUNTS_ID + " INTEGER PRIMARY KEY AUTOINCREMENT, "
1125 + ACCOUNTS_NAME + " TEXT NOT NULL, "
1126 + ACCOUNTS_TYPE + " TEXT NOT NULL, "
1127 + "UNIQUE(" + ACCOUNTS_NAME + "," + ACCOUNTS_TYPE + "))");
1128 }
1129
1130 private void addLastSuccessfullAuthenticatedTimeColumn(SQLiteDatabase db) {
1131 db.execSQL("ALTER TABLE " + TABLE_ACCOUNTS + " ADD COLUMN "
1132 + ACCOUNTS_LAST_AUTHENTICATE_TIME_EPOCH_MILLIS + " DEFAULT 0");
1133 }
1134
1135 private void addOldAccountNameColumn(SQLiteDatabase db) {
1136 db.execSQL("ALTER TABLE " + TABLE_ACCOUNTS + " ADD COLUMN " + ACCOUNTS_PREVIOUS_NAME);
1137 }
1138
1139 private void addDebugTable(SQLiteDatabase db) {
Fyodor Kupolov00de49e2016-09-23 13:10:27 -07001140 DeDatabaseHelper.createDebugTable(db);
Fyodor Kupolov1ce01612016-08-26 11:39:07 -07001141 }
1142
1143 private void createAccountsDeletionTrigger(SQLiteDatabase db) {
1144 db.execSQL(""
1145 + " CREATE TRIGGER " + TABLE_ACCOUNTS + "Delete DELETE ON " + TABLE_ACCOUNTS
1146 + " BEGIN"
1147 + " DELETE FROM " + TABLE_AUTHTOKENS
1148 + " WHERE " + AUTHTOKENS_ACCOUNTS_ID + "=OLD." + ACCOUNTS_ID + " ;"
1149 + " DELETE FROM " + TABLE_EXTRAS
1150 + " WHERE " + EXTRAS_ACCOUNTS_ID + "=OLD." + ACCOUNTS_ID + " ;"
1151 + " DELETE FROM " + TABLE_GRANTS
1152 + " WHERE " + GRANTS_ACCOUNTS_ID + "=OLD." + ACCOUNTS_ID + " ;"
1153 + " END");
1154 }
1155
1156 private void createGrantsTable(SQLiteDatabase db) {
1157 db.execSQL("CREATE TABLE " + TABLE_GRANTS + " ( "
1158 + GRANTS_ACCOUNTS_ID + " INTEGER NOT NULL, "
1159 + GRANTS_AUTH_TOKEN_TYPE + " STRING NOT NULL, "
1160 + GRANTS_GRANTEE_UID + " INTEGER NOT NULL, "
1161 + "UNIQUE (" + GRANTS_ACCOUNTS_ID + "," + GRANTS_AUTH_TOKEN_TYPE
1162 + "," + GRANTS_GRANTEE_UID + "))");
1163 }
1164
Fyodor Kupolov00de49e2016-09-23 13:10:27 -07001165 static long insertMetaAuthTypeAndUid(SQLiteDatabase db, String authenticatorType, int uid) {
1166 ContentValues values = new ContentValues();
1167 values.put(META_KEY,
1168 META_KEY_FOR_AUTHENTICATOR_UID_FOR_TYPE_PREFIX + authenticatorType);
1169 values.put(META_VALUE, uid);
1170 return db.insert(TABLE_META, null, values);
1171 }
1172
Fyodor Kupolov1ce01612016-08-26 11:39:07 -07001173 private void populateMetaTableWithAuthTypeAndUID(SQLiteDatabase db,
1174 Map<String, Integer> authTypeAndUIDMap) {
1175 for (Map.Entry<String, Integer> entry : authTypeAndUIDMap.entrySet()) {
Fyodor Kupolov00de49e2016-09-23 13:10:27 -07001176 insertMetaAuthTypeAndUid(db, entry.getKey(), entry.getValue());
Fyodor Kupolov1ce01612016-08-26 11:39:07 -07001177 }
1178 }
1179
1180 /**
1181 * Pre-N database may need an upgrade before splitting
1182 */
1183 @Override
1184 public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
1185 Log.e(TAG, "upgrade from version " + oldVersion + " to version " + newVersion);
1186
1187 if (oldVersion == 1) {
1188 // no longer need to do anything since the work is done
1189 // when upgrading from version 2
1190 oldVersion++;
1191 }
1192
1193 if (oldVersion == 2) {
1194 createGrantsTable(db);
1195 db.execSQL("DROP TRIGGER " + TABLE_ACCOUNTS + "Delete");
1196 createAccountsDeletionTrigger(db);
1197 oldVersion++;
1198 }
1199
1200 if (oldVersion == 3) {
1201 db.execSQL("UPDATE " + TABLE_ACCOUNTS + " SET " + ACCOUNTS_TYPE +
1202 " = 'com.google' WHERE " + ACCOUNTS_TYPE + " == 'com.google.GAIA'");
1203 oldVersion++;
1204 }
1205
1206 if (oldVersion == 4) {
1207 createSharedAccountsTable(db);
1208 oldVersion++;
1209 }
1210
1211 if (oldVersion == 5) {
1212 addOldAccountNameColumn(db);
1213 oldVersion++;
1214 }
1215
1216 if (oldVersion == 6) {
1217 addLastSuccessfullAuthenticatedTimeColumn(db);
1218 oldVersion++;
1219 }
1220
1221 if (oldVersion == 7) {
1222 addDebugTable(db);
1223 oldVersion++;
1224 }
1225
1226 if (oldVersion == 8) {
1227 populateMetaTableWithAuthTypeAndUID(
1228 db,
1229 AccountManagerService.getAuthenticatorTypeAndUIDForUser(mContext, mUserId));
1230 oldVersion++;
1231 }
1232
1233 if (oldVersion != newVersion) {
1234 Log.e(TAG, "failed to upgrade version " + oldVersion + " to version " + newVersion);
1235 }
1236 }
1237
1238 @Override
1239 public void onOpen(SQLiteDatabase db) {
1240 if (Log.isLoggable(TAG, Log.VERBOSE)) Log.v(TAG, "opened database " + DATABASE_NAME);
1241 }
1242 }
1243
Fyodor Kupolov00de49e2016-09-23 13:10:27 -07001244 List<Account> findCeAccountsNotInDe() {
1245 SQLiteDatabase db = mDeDatabase.getReadableDatabaseUserIsUnlocked();
Fyodor Kupolov1ce01612016-08-26 11:39:07 -07001246 // Select accounts from CE that do not exist in DE
1247 Cursor cursor = db.rawQuery(
1248 "SELECT " + ACCOUNTS_NAME + "," + ACCOUNTS_TYPE
1249 + " FROM " + CE_TABLE_ACCOUNTS
1250 + " WHERE NOT EXISTS "
1251 + " (SELECT " + ACCOUNTS_ID + " FROM " + TABLE_ACCOUNTS
1252 + " WHERE " + ACCOUNTS_ID + "=" + CE_TABLE_ACCOUNTS + "." + ACCOUNTS_ID
1253 + " )", null);
1254 try {
1255 List<Account> accounts = new ArrayList<>(cursor.getCount());
1256 while (cursor.moveToNext()) {
1257 String accountName = cursor.getString(0);
1258 String accountType = cursor.getString(1);
1259 accounts.add(new Account(accountName, accountType));
1260 }
1261 return accounts;
1262 } finally {
1263 cursor.close();
1264 }
1265 }
1266
Fyodor Kupolov00de49e2016-09-23 13:10:27 -07001267 boolean deleteCeAccount(long accountId) {
Fyodor Kupolov98e9e852016-12-09 14:58:05 -08001268 SQLiteDatabase db = mDeDatabase.getWritableDatabaseUserIsUnlocked();
Fyodor Kupolov1ce01612016-08-26 11:39:07 -07001269 return db.delete(
1270 CE_TABLE_ACCOUNTS, ACCOUNTS_ID + "=" + accountId, null) > 0;
1271 }
1272
Fyodor Kupolov00de49e2016-09-23 13:10:27 -07001273 boolean isCeDatabaseAttached() {
1274 return mDeDatabase.mCeAttached;
1275 }
1276
1277 void beginTransaction() {
1278 mDeDatabase.getWritableDatabase().beginTransaction();
1279 }
1280
1281 void setTransactionSuccessful() {
1282 mDeDatabase.getWritableDatabase().setTransactionSuccessful();
1283 }
1284
1285 void endTransaction() {
1286 mDeDatabase.getWritableDatabase().endTransaction();
1287 }
1288
1289 void attachCeDatabase(File ceDbFile) {
1290 CeDatabaseHelper.create(mContext, mPreNDatabaseFile, ceDbFile);
1291 SQLiteDatabase db = mDeDatabase.getWritableDatabase();
1292 db.execSQL("ATTACH DATABASE '" + ceDbFile.getPath()+ "' AS ceDb");
1293 mDeDatabase.mCeAttached = true;
1294 }
1295
1296 /*
1297 * Finds the row key where the next insertion should take place. Returns number of rows
1298 * if it is less {@link #MAX_DEBUG_DB_SIZE}, otherwise finds the lowest number available.
1299 */
Dmitry Dementyev47443192018-10-24 13:31:59 -07001300 long calculateDebugTableInsertionPoint() {
1301 try {
1302 SQLiteDatabase db = mDeDatabase.getReadableDatabase();
1303 String queryCountDebugDbRows = "SELECT COUNT(*) FROM " + TABLE_DEBUG;
1304 int size = (int) DatabaseUtils.longForQuery(db, queryCountDebugDbRows, null);
1305 if (size < MAX_DEBUG_DB_SIZE) {
1306 return size;
1307 }
Fyodor Kupolov00de49e2016-09-23 13:10:27 -07001308
Dmitry Dementyev47443192018-10-24 13:31:59 -07001309 // This query finds the smallest timestamp value (and if 2 records have
1310 // same timestamp, the choose the lower id).
1311 queryCountDebugDbRows =
1312 "SELECT " + DEBUG_TABLE_KEY
1313 + " FROM " + TABLE_DEBUG
1314 + " ORDER BY " + DEBUG_TABLE_TIMESTAMP + ","
1315 + DEBUG_TABLE_KEY
1316 + " LIMIT 1";
1317 return DatabaseUtils.longForQuery(db, queryCountDebugDbRows, null);
1318 } catch (SQLiteException e) {
1319 Log.e(TAG, "Failed to open debug table" + e);
1320 return -1;
1321 }
Fyodor Kupolov00de49e2016-09-23 13:10:27 -07001322 }
1323
1324 SQLiteStatement compileSqlStatementForLogging() {
Fyodor Kupolov00de49e2016-09-23 13:10:27 -07001325 SQLiteDatabase db = mDeDatabase.getWritableDatabase();
1326 String sql = "INSERT OR REPLACE INTO " + AccountsDb.TABLE_DEBUG
1327 + " VALUES (?,?,?,?,?,?)";
1328 return db.compileStatement(sql);
1329 }
1330
Dmitry Dementyev47443192018-10-24 13:31:59 -07001331 /**
1332 * Returns statement for logging or {@code null} on database open failure.
1333 * Returned value must be guarded by {link #debugStatementLock}
1334 */
1335 @Nullable SQLiteStatement getStatementForLogging() {
1336 if (mDebugStatementForLogging != null) {
1337 return mDebugStatementForLogging;
1338 }
1339 try {
1340 mDebugStatementForLogging = compileSqlStatementForLogging();
1341 return mDebugStatementForLogging;
1342 } catch (SQLiteException e) {
1343 Log.e(TAG, "Failed to open debug table" + e);
1344 return null;
1345 }
1346 }
1347
1348 void closeDebugStatement() {
1349 synchronized (mDebugStatementLock) {
1350 if (mDebugStatementForLogging != null) {
1351 mDebugStatementForLogging.close();
1352 mDebugStatementForLogging = null;
1353 }
1354 }
1355 }
1356
1357 long reserveDebugDbInsertionPoint() {
1358 if (mDebugDbInsertionPoint == -1) {
1359 mDebugDbInsertionPoint = calculateDebugTableInsertionPoint();
1360 return mDebugDbInsertionPoint;
1361 }
1362 mDebugDbInsertionPoint = (mDebugDbInsertionPoint + 1) % MAX_DEBUG_DB_SIZE;
1363 return mDebugDbInsertionPoint;
1364 }
1365
Fyodor Kupolov00de49e2016-09-23 13:10:27 -07001366 void dumpDebugTable(PrintWriter pw) {
1367 SQLiteDatabase db = mDeDatabase.getReadableDatabase();
1368 Cursor cursor = db.query(TABLE_DEBUG, null,
1369 null, null, null, null, DEBUG_TABLE_TIMESTAMP);
1370 pw.println("AccountId, Action_Type, timestamp, UID, TableName, Key");
1371 pw.println("Accounts History");
1372 try {
1373 while (cursor.moveToNext()) {
1374 // print type,count
1375 pw.println(cursor.getString(0) + "," + cursor.getString(1) + "," +
1376 cursor.getString(2) + "," + cursor.getString(3) + ","
1377 + cursor.getString(4) + "," + cursor.getString(5));
1378 }
1379 } finally {
1380 cursor.close();
1381 }
1382 }
1383
Andrew Scullc7770d62017-05-22 17:49:58 +01001384 @Override
Fyodor Kupolov00de49e2016-09-23 13:10:27 -07001385 public void close() {
1386 mDeDatabase.close();
1387 }
1388
Fyodor Kupolov1ce01612016-08-26 11:39:07 -07001389 static void deleteDbFileWarnIfFailed(File dbFile) {
1390 if (!SQLiteDatabase.deleteDatabase(dbFile)) {
1391 Log.w(TAG, "Database at " + dbFile + " was not deleted successfully");
1392 }
1393 }
Fyodor Kupolov00de49e2016-09-23 13:10:27 -07001394
1395 public static AccountsDb create(Context context, int userId, File preNDatabaseFile,
1396 File deDatabaseFile) {
1397 boolean newDbExists = deDatabaseFile.exists();
1398 DeDatabaseHelper deDatabaseHelper = new DeDatabaseHelper(context, userId,
1399 deDatabaseFile.getPath());
1400 // If the db just created, and there is a legacy db, migrate it
1401 if (!newDbExists && preNDatabaseFile.exists()) {
1402 // Migrate legacy db to the latest version - PRE_N_DATABASE_VERSION
1403 PreNDatabaseHelper
1404 preNDatabaseHelper = new PreNDatabaseHelper(context, userId,
1405 preNDatabaseFile.getPath());
1406 // Open the database to force upgrade if required
1407 preNDatabaseHelper.getWritableDatabase();
1408 preNDatabaseHelper.close();
1409 // Move data without SPII to DE
1410 deDatabaseHelper.migratePreNDbToDe(preNDatabaseFile);
1411 }
1412 return new AccountsDb(deDatabaseHelper, context, preNDatabaseFile);
1413 }
1414
Dmitry Dementyev08b5a412019-03-19 16:17:16 -07001415 /**
1416 * Removes all tables and triggers created by AccountManager.
1417 */
1418 private static void resetDatabase(SQLiteDatabase db) {
1419 try (Cursor c = db.rawQuery("SELECT name FROM sqlite_master WHERE type ='table'", null)) {
1420 while (c.moveToNext()) {
1421 String name = c.getString(0);
1422 // Skip tables managed by SQLiteDatabase
1423 if ("android_metadata".equals(name) || "sqlite_sequence".equals(name)) {
1424 continue;
1425 }
1426 db.execSQL("DROP TABLE IF EXISTS " + name);
1427 }
1428 }
1429
1430 try (Cursor c = db.rawQuery("SELECT name FROM sqlite_master WHERE type ='trigger'", null)) {
1431 while (c.moveToNext()) {
1432 String name = c.getString(0);
1433 db.execSQL("DROP TRIGGER IF EXISTS " + name);
1434 }
1435 }
1436 }
Dmitry Dementyev01985ff2017-01-19 16:03:39 -08001437}