blob: 0c3d268f4b0f243facf8446166aee57de3cb049d [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 Dementyeve366f822017-01-31 10:25:10 -080057 private static final int DE_DATABASE_VERSION = 3; // 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";
Dmitry Dementyeve366f822017-01-31 10:25:10 -080078 private static final String VISIBILITY_PACKAGE = "_package";
Dmitry Dementyeve5768622016-11-21 13:51:27 -080079 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);
Dmitry Dementyeve366f822017-01-31 10:25:10 -0800255 oldVersion++;
Fyodor Kupolov1ce01612016-08-26 11:39:07 -0700256 }
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 Dementyev01985ff2017-01-19 16:03:39 -0800358 AUTHTOKENS_ACCOUNTS_ID + "=?" + " 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, "
Dmitry Dementyeve366f822017-01-31 10:25:10 -0800562 + VISIBILITY_PACKAGE + " TEXT NOT NULL, "
Dmitry Dementyeve5768622016-11-21 13:51:27 -0800563 + VISIBILITY_VALUE + " INTEGER, "
Dmitry Dementyeve366f822017-01-31 10:25:10 -0800564 + "PRIMARY KEY(" + VISIBILITY_ACCOUNTS_ID + "," + VISIBILITY_PACKAGE + "))");
Dmitry Dementyeve5768622016-11-21 13:51:27 -0800565 }
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) {
Dmitry Dementyeve366f822017-01-31 10:25:10 -0800594 createAccountsVisibilityTable(db);
595 createAccountsDeletionVisibilityCleanupTrigger(db);
596 oldVersion = 3; // skip version 2 which had uid based table
597 }
598
599 if (oldVersion == 2) {
600 // Remove uid based table and replace it with packageName based
601 db.execSQL("DROP TRIGGER IF EXISTS " + TABLE_ACCOUNTS + "DeleteVisibility");
Dmitry Dementyev6fe34fcb2017-02-03 17:46:10 -0800602 db.execSQL("DROP TABLE IF EXISTS " + TABLE_VISIBILITY);
Dmitry Dementyeve366f822017-01-31 10:25:10 -0800603 createAccountsVisibilityTable(db);
604 createAccountsDeletionVisibilityCleanupTrigger(db);
605 oldVersion++;
Dmitry Dementyeve5768622016-11-21 13:51:27 -0800606 }
607
Fyodor Kupolov1ce01612016-08-26 11:39:07 -0700608 if (oldVersion != newVersion) {
609 Log.e(TAG, "failed to upgrade version " + oldVersion + " to version " + newVersion);
610 }
611 }
612
Fyodor Kupolov1ce01612016-08-26 11:39:07 -0700613 public SQLiteDatabase getReadableDatabaseUserIsUnlocked() {
614 if(!mCeAttached) {
615 Log.wtf(TAG, "getReadableDatabaseUserIsUnlocked called while user " + mUserId
616 + " is still locked. CE database is not yet available.", new Throwable());
617 }
618 return super.getReadableDatabase();
619 }
620
621 public SQLiteDatabase getWritableDatabaseUserIsUnlocked() {
622 if(!mCeAttached) {
623 Log.wtf(TAG, "getWritableDatabaseUserIsUnlocked called while user " + mUserId
624 + " is still locked. CE database is not yet available.", new Throwable());
625 }
626 return super.getWritableDatabase();
627 }
628
629 @Override
630 public void onOpen(SQLiteDatabase db) {
631 if (Log.isLoggable(TAG, Log.VERBOSE)) Log.v(TAG, "opened database " + DE_DATABASE_NAME);
632 }
633
634 private void migratePreNDbToDe(File preNDbFile) {
635 Log.i(TAG, "Migrate pre-N database to DE preNDbFile=" + preNDbFile);
636 SQLiteDatabase db = getWritableDatabase();
637 db.execSQL("ATTACH DATABASE '" + preNDbFile.getPath() + "' AS preNDb");
638 db.beginTransaction();
639 // Copy accounts fields
640 db.execSQL("INSERT INTO " + TABLE_ACCOUNTS
641 + "(" + ACCOUNTS_ID + "," + ACCOUNTS_NAME + "," + ACCOUNTS_TYPE + ", "
642 + ACCOUNTS_PREVIOUS_NAME + ", " + ACCOUNTS_LAST_AUTHENTICATE_TIME_EPOCH_MILLIS
643 + ") "
644 + "SELECT " + ACCOUNTS_ID + "," + ACCOUNTS_NAME + "," + ACCOUNTS_TYPE + ", "
645 + ACCOUNTS_PREVIOUS_NAME + ", " + ACCOUNTS_LAST_AUTHENTICATE_TIME_EPOCH_MILLIS
646 + " FROM preNDb." + TABLE_ACCOUNTS);
647 // Copy SHARED_ACCOUNTS
648 db.execSQL("INSERT INTO " + TABLE_SHARED_ACCOUNTS
649 + "(" + SHARED_ACCOUNTS_ID + "," + ACCOUNTS_NAME + "," + ACCOUNTS_TYPE + ") " +
650 "SELECT " + SHARED_ACCOUNTS_ID + "," + ACCOUNTS_NAME + "," + ACCOUNTS_TYPE
651 + " FROM preNDb." + TABLE_SHARED_ACCOUNTS);
652 // Copy DEBUG_TABLE
653 db.execSQL("INSERT INTO " + TABLE_DEBUG
654 + "(" + ACCOUNTS_ID + "," + DEBUG_TABLE_ACTION_TYPE + ","
655 + DEBUG_TABLE_TIMESTAMP + "," + DEBUG_TABLE_CALLER_UID + ","
656 + DEBUG_TABLE_TABLE_NAME + "," + DEBUG_TABLE_KEY + ") " +
657 "SELECT " + ACCOUNTS_ID + "," + DEBUG_TABLE_ACTION_TYPE + ","
658 + DEBUG_TABLE_TIMESTAMP + "," + DEBUG_TABLE_CALLER_UID + ","
659 + DEBUG_TABLE_TABLE_NAME + "," + DEBUG_TABLE_KEY
660 + " FROM preNDb." + TABLE_DEBUG);
661 // Copy GRANTS
662 db.execSQL("INSERT INTO " + TABLE_GRANTS
663 + "(" + GRANTS_ACCOUNTS_ID + "," + GRANTS_AUTH_TOKEN_TYPE + ","
664 + GRANTS_GRANTEE_UID + ") " +
665 "SELECT " + GRANTS_ACCOUNTS_ID + "," + GRANTS_AUTH_TOKEN_TYPE + ","
666 + GRANTS_GRANTEE_UID + " FROM preNDb." + TABLE_GRANTS);
667 // Copy META
668 db.execSQL("INSERT INTO " + TABLE_META
669 + "(" + META_KEY + "," + META_VALUE + ") "
670 + "SELECT " + META_KEY + "," + META_VALUE + " FROM preNDb." + TABLE_META);
671 db.setTransactionSuccessful();
672 db.endTransaction();
673
674 db.execSQL("DETACH DATABASE preNDb");
675 }
Fyodor Kupolov00de49e2016-09-23 13:10:27 -0700676 }
Fyodor Kupolov1ce01612016-08-26 11:39:07 -0700677
Fyodor Kupolov00de49e2016-09-23 13:10:27 -0700678 boolean deleteDeAccount(long accountId) {
679 SQLiteDatabase db = mDeDatabase.getWritableDatabase();
680 return db.delete(TABLE_ACCOUNTS, ACCOUNTS_ID + "=" + accountId, null) > 0;
681 }
Fyodor Kupolov1ce01612016-08-26 11:39:07 -0700682
Fyodor Kupolov00de49e2016-09-23 13:10:27 -0700683 long insertSharedAccount(Account account) {
684 SQLiteDatabase db = mDeDatabase.getWritableDatabase();
685 ContentValues values = new ContentValues();
686 values.put(ACCOUNTS_NAME, account.name);
687 values.put(ACCOUNTS_TYPE, account.type);
688 return db.insert(
689 TABLE_SHARED_ACCOUNTS, ACCOUNTS_NAME, values);
690 }
Fyodor Kupolov1ce01612016-08-26 11:39:07 -0700691
Fyodor Kupolov00de49e2016-09-23 13:10:27 -0700692 boolean deleteSharedAccount(Account account) {
693 SQLiteDatabase db = mDeDatabase.getWritableDatabase();
694 return db.delete(TABLE_SHARED_ACCOUNTS, ACCOUNTS_NAME + "=? AND " + ACCOUNTS_TYPE + "=?",
695 new String[]{account.name, account.type}) > 0;
696 }
Fyodor Kupolov1ce01612016-08-26 11:39:07 -0700697
Fyodor Kupolov00de49e2016-09-23 13:10:27 -0700698 int renameSharedAccount(Account account, String newName) {
699 SQLiteDatabase db = mDeDatabase.getWritableDatabase();
700 final ContentValues values = new ContentValues();
701 values.put(ACCOUNTS_NAME, newName);
702 return db.update(TABLE_SHARED_ACCOUNTS,
703 values,
704 ACCOUNTS_NAME + "=? AND " + ACCOUNTS_TYPE + "=?",
705 new String[] {account.name, account.type});
706 }
Fyodor Kupolov1ce01612016-08-26 11:39:07 -0700707
Fyodor Kupolov00de49e2016-09-23 13:10:27 -0700708 List<Account> getSharedAccounts() {
709 SQLiteDatabase db = mDeDatabase.getReadableDatabase();
710 ArrayList<Account> accountList = new ArrayList<>();
711 Cursor cursor = null;
712 try {
713 cursor = db.query(TABLE_SHARED_ACCOUNTS, new String[] {ACCOUNTS_NAME, ACCOUNTS_TYPE},
714 null, null, null, null, null);
715 if (cursor != null && cursor.moveToFirst()) {
716 int nameIndex = cursor.getColumnIndex(ACCOUNTS_NAME);
717 int typeIndex = cursor.getColumnIndex(ACCOUNTS_TYPE);
Fyodor Kupolov1ce01612016-08-26 11:39:07 -0700718 do {
Fyodor Kupolov00de49e2016-09-23 13:10:27 -0700719 accountList.add(new Account(cursor.getString(nameIndex),
720 cursor.getString(typeIndex)));
Fyodor Kupolov1ce01612016-08-26 11:39:07 -0700721 } while (cursor.moveToNext());
Fyodor Kupolov00de49e2016-09-23 13:10:27 -0700722 }
723 } finally {
724 if (cursor != null) {
725 cursor.close();
Fyodor Kupolov1ce01612016-08-26 11:39:07 -0700726 }
727 }
Fyodor Kupolov00de49e2016-09-23 13:10:27 -0700728 return accountList;
729 }
Fyodor Kupolov1ce01612016-08-26 11:39:07 -0700730
Fyodor Kupolov00de49e2016-09-23 13:10:27 -0700731 long findSharedAccountId(Account account) {
732 SQLiteDatabase db = mDeDatabase.getReadableDatabase();
733 Cursor cursor = db.query(TABLE_SHARED_ACCOUNTS, new String[]{
734 ACCOUNTS_ID},
735 "name=? AND type=?", new String[]{account.name, account.type}, null, null,
736 null);
737 try {
738 if (cursor.moveToNext()) {
739 return cursor.getLong(0);
Fyodor Kupolov1ce01612016-08-26 11:39:07 -0700740 }
Fyodor Kupolov00de49e2016-09-23 13:10:27 -0700741 return -1;
742 } finally {
743 cursor.close();
Fyodor Kupolov1ce01612016-08-26 11:39:07 -0700744 }
745 }
746
Fyodor Kupolov00de49e2016-09-23 13:10:27 -0700747 long findAccountLastAuthenticatedTime(Account account) {
748 SQLiteDatabase db = mDeDatabase.getReadableDatabase();
749 return DatabaseUtils.longForQuery(db,
750 "SELECT " + AccountsDb.ACCOUNTS_LAST_AUTHENTICATE_TIME_EPOCH_MILLIS
751 + " FROM " + TABLE_ACCOUNTS + " WHERE " + ACCOUNTS_NAME + "=? AND "
752 + ACCOUNTS_TYPE + "=?",
753 new String[] {account.name, account.type});
754 }
755
756 boolean updateAccountLastAuthenticatedTime(Account account) {
757 SQLiteDatabase db = mDeDatabase.getWritableDatabase();
758 final ContentValues values = new ContentValues();
759 values.put(ACCOUNTS_LAST_AUTHENTICATE_TIME_EPOCH_MILLIS, System.currentTimeMillis());
760 int rowCount = db.update(TABLE_ACCOUNTS,
761 values,
762 ACCOUNTS_NAME + "=? AND " + ACCOUNTS_TYPE + "=?",
763 new String[] { account.name, account.type });
764 return rowCount > 0;
765 }
766
767 void dumpDeAccountsTable(PrintWriter pw) {
768 SQLiteDatabase db = mDeDatabase.getReadableDatabase();
769 Cursor cursor = db.query(
770 TABLE_ACCOUNTS, ACCOUNT_TYPE_COUNT_PROJECTION,
771 null, null, ACCOUNTS_TYPE, null, null);
772 try {
773 while (cursor.moveToNext()) {
774 // print type,count
775 pw.println(cursor.getString(0) + "," + cursor.getString(1));
776 }
777 } finally {
778 if (cursor != null) {
779 cursor.close();
780 }
781 }
782 }
783
784 long findDeAccountId(Account account) {
785 SQLiteDatabase db = mDeDatabase.getReadableDatabase();
786 String[] columns = {ACCOUNTS_ID};
787 String selection = "name=? AND type=?";
788 String[] selectionArgs = {account.name, account.type};
789 try (Cursor cursor = db.query(TABLE_ACCOUNTS, columns, selection, selectionArgs,
790 null, null, null)) {
791 if (cursor.moveToNext()) {
792 return cursor.getLong(0);
793 }
794 return -1;
795 }
796 }
797
798 Map<Long, Account> findAllDeAccounts() {
799 SQLiteDatabase db = mDeDatabase.getReadableDatabase();
800 LinkedHashMap<Long, Account> map = new LinkedHashMap<>();
801 String[] columns = {ACCOUNTS_ID, ACCOUNTS_TYPE, ACCOUNTS_NAME};
802 try (Cursor cursor = db.query(TABLE_ACCOUNTS, columns,
803 null, null, null, null, ACCOUNTS_ID)) {
804 while (cursor.moveToNext()) {
805 final long accountId = cursor.getLong(0);
806 final String accountType = cursor.getString(1);
807 final String accountName = cursor.getString(2);
808
809 final Account account = new Account(accountName, accountType);
810 map.put(accountId, account);
811 }
812 }
813 return map;
814 }
815
816 String findDeAccountPreviousName(Account account) {
817 SQLiteDatabase db = mDeDatabase.getReadableDatabase();
818 String[] columns = {ACCOUNTS_PREVIOUS_NAME};
819 String selection = ACCOUNTS_NAME + "=? AND " + ACCOUNTS_TYPE + "=?";
820 String[] selectionArgs = {account.name, account.type};
821 try (Cursor cursor = db.query(TABLE_ACCOUNTS, columns, selection, selectionArgs,
822 null, null, null)) {
823 if (cursor.moveToNext()) {
824 return cursor.getString(0);
825 }
826 }
827 return null;
828 }
829
830 long insertDeAccount(Account account, long accountId) {
831 SQLiteDatabase db = mDeDatabase.getWritableDatabase();
832 ContentValues values = new ContentValues();
833 values.put(ACCOUNTS_ID, accountId);
834 values.put(ACCOUNTS_NAME, account.name);
835 values.put(ACCOUNTS_TYPE, account.type);
836 values.put(ACCOUNTS_LAST_AUTHENTICATE_TIME_EPOCH_MILLIS, System.currentTimeMillis());
837 return db.insert(TABLE_ACCOUNTS, ACCOUNTS_NAME, values);
838 }
839
840 boolean renameDeAccount(long accountId, String newName, String previousName) {
841 SQLiteDatabase db = mDeDatabase.getWritableDatabase();
842 final ContentValues values = new ContentValues();
843 values.put(ACCOUNTS_NAME, newName);
844 values.put(ACCOUNTS_PREVIOUS_NAME, previousName);
845 final String[] argsAccountId = {String.valueOf(accountId)};
846 return db.update(TABLE_ACCOUNTS, values, ACCOUNTS_ID + "=?", argsAccountId) > 0;
847 }
848
849 boolean deleteGrantsByAccountIdAuthTokenTypeAndUid(long accountId,
850 String authTokenType, long uid) {
851 SQLiteDatabase db = mDeDatabase.getWritableDatabase();
852 return db.delete(TABLE_GRANTS,
853 GRANTS_ACCOUNTS_ID + "=? AND " + GRANTS_AUTH_TOKEN_TYPE + "=? AND "
854 + GRANTS_GRANTEE_UID + "=?",
855 new String[] {String.valueOf(accountId), authTokenType, String.valueOf(uid)}) > 0;
856 }
857
858 List<Integer> findAllUidGrants() {
859 SQLiteDatabase db = mDeDatabase.getReadableDatabase();
860 List<Integer> result = new ArrayList<>();
861 final Cursor cursor = db.query(TABLE_GRANTS,
862 new String[]{GRANTS_GRANTEE_UID},
863 null, null, GRANTS_GRANTEE_UID, null, null);
864 try {
865 while (cursor.moveToNext()) {
866 final int uid = cursor.getInt(0);
867 result.add(uid);
868 }
869 } finally {
870 cursor.close();
871 }
872 return result;
873 }
874
875 long findMatchingGrantsCount(int uid, String authTokenType, Account account) {
876 SQLiteDatabase db = mDeDatabase.getReadableDatabase();
877 String[] args = {String.valueOf(uid), authTokenType, account.name, account.type};
878 return DatabaseUtils.longForQuery(db, COUNT_OF_MATCHING_GRANTS, args);
879 }
880
881 long findMatchingGrantsCountAnyToken(int uid, Account account) {
882 SQLiteDatabase db = mDeDatabase.getReadableDatabase();
883 String[] args = {String.valueOf(uid), account.name, account.type};
884 return DatabaseUtils.longForQuery(db, COUNT_OF_MATCHING_GRANTS_ANY_TOKEN, args);
885 }
886
887 long insertGrant(long accountId, String authTokenType, int uid) {
888 SQLiteDatabase db = mDeDatabase.getWritableDatabase();
889 ContentValues values = new ContentValues();
890 values.put(GRANTS_ACCOUNTS_ID, accountId);
891 values.put(GRANTS_AUTH_TOKEN_TYPE, authTokenType);
892 values.put(GRANTS_GRANTEE_UID, uid);
893 return db.insert(TABLE_GRANTS, GRANTS_ACCOUNTS_ID, values);
894 }
895
896 boolean deleteGrantsByUid(int uid) {
897 SQLiteDatabase db = mDeDatabase.getWritableDatabase();
898 return db.delete(TABLE_GRANTS, GRANTS_GRANTEE_UID + "=?",
899 new String[] {Integer.toString(uid)}) > 0;
900 }
901
Dmitry Dementyeve366f822017-01-31 10:25:10 -0800902 boolean setAccountVisibility(long accountId, String packageName, int visibility) {
Dmitry Dementyeve5768622016-11-21 13:51:27 -0800903 SQLiteDatabase db = mDeDatabase.getWritableDatabase();
904 ContentValues values = new ContentValues();
905 values.put(VISIBILITY_ACCOUNTS_ID, String.valueOf(accountId));
Dmitry Dementyeve366f822017-01-31 10:25:10 -0800906 values.put(VISIBILITY_PACKAGE, packageName);
Dmitry Dementyeve5768622016-11-21 13:51:27 -0800907 values.put(VISIBILITY_VALUE, String.valueOf(visibility));
908 return (db.replace(TABLE_VISIBILITY, VISIBILITY_VALUE, values) != -1);
909 }
910
Dmitry Dementyeve366f822017-01-31 10:25:10 -0800911 Integer findAccountVisibility(Account account, String packageName) {
Dmitry Dementyev71fa5262017-03-23 12:29:17 -0700912 SQLiteDatabase db = mDeDatabase.getReadableDatabase();
Dmitry Dementyeve5768622016-11-21 13:51:27 -0800913 final Cursor cursor = db.query(TABLE_VISIBILITY, new String[] {VISIBILITY_VALUE},
Dmitry Dementyeve366f822017-01-31 10:25:10 -0800914 SELECTION_ACCOUNTS_ID_BY_ACCOUNT + " AND " + VISIBILITY_PACKAGE + "=? ",
915 new String[] {account.name, account.type, packageName}, null, null, null);
Dmitry Dementyeve5768622016-11-21 13:51:27 -0800916 try {
917 while (cursor.moveToNext()) {
918 return cursor.getInt(0);
919 }
920 } finally {
921 cursor.close();
922 }
923 return null;
924 }
925
Dmitry Dementyeve366f822017-01-31 10:25:10 -0800926 Integer findAccountVisibility(long accountId, String packageName) {
Dmitry Dementyev71fa5262017-03-23 12:29:17 -0700927 SQLiteDatabase db = mDeDatabase.getReadableDatabase();
Dmitry Dementyeve5768622016-11-21 13:51:27 -0800928 final Cursor cursor = db.query(TABLE_VISIBILITY, new String[] {VISIBILITY_VALUE},
Dmitry Dementyeve366f822017-01-31 10:25:10 -0800929 VISIBILITY_ACCOUNTS_ID + "=? AND " + VISIBILITY_PACKAGE + "=? ",
930 new String[] {String.valueOf(accountId), packageName}, null, null, null);
Dmitry Dementyeve5768622016-11-21 13:51:27 -0800931 try {
932 while (cursor.moveToNext()) {
933 return cursor.getInt(0);
934 }
935 } finally {
936 cursor.close();
937 }
938 return null;
939 }
940
941 Account findDeAccountByAccountId(long accountId) {
942 SQLiteDatabase db = mDeDatabase.getReadableDatabase();
943 final Cursor cursor = db.query(TABLE_ACCOUNTS, new String[] {ACCOUNTS_NAME, ACCOUNTS_TYPE},
944 ACCOUNTS_ID + "=? ", new String[] {String.valueOf(accountId)}, null, null, null);
945 try {
946 while (cursor.moveToNext()) {
947 return new Account(cursor.getString(0), cursor.getString(1));
948 }
949 } finally {
950 cursor.close();
951 }
952 return null;
953 }
954
955 /**
Dmitry Dementyeve366f822017-01-31 10:25:10 -0800956 * Returns a map from packageNames to visibility.
Dmitry Dementyeve5768622016-11-21 13:51:27 -0800957 */
Dmitry Dementyeve366f822017-01-31 10:25:10 -0800958 Map<String, Integer> findAllVisibilityValuesForAccount(Account account) {
Dmitry Dementyeve5768622016-11-21 13:51:27 -0800959 SQLiteDatabase db = mDeDatabase.getReadableDatabase();
Dmitry Dementyeve366f822017-01-31 10:25:10 -0800960 Map<String, Integer> result = new HashMap<>();
Dmitry Dementyev01985ff2017-01-19 16:03:39 -0800961 final Cursor cursor =
Dmitry Dementyeve366f822017-01-31 10:25:10 -0800962 db.query(TABLE_VISIBILITY, new String[] {VISIBILITY_PACKAGE, VISIBILITY_VALUE},
963 SELECTION_ACCOUNTS_ID_BY_ACCOUNT, new String[] {account.name, account.type},
964 null, null, null);
Dmitry Dementyeve5768622016-11-21 13:51:27 -0800965 try {
966 while (cursor.moveToNext()) {
Dmitry Dementyeve366f822017-01-31 10:25:10 -0800967 result.put(cursor.getString(0), cursor.getInt(1));
Dmitry Dementyeve5768622016-11-21 13:51:27 -0800968 }
969 } finally {
970 cursor.close();
971 }
972 return result;
973 }
974
Dmitry Dementyev71fa5262017-03-23 12:29:17 -0700975 /**
976 * Returns a map account -> (package -> visibility)
977 */
978 Map <Account, Map<String, Integer>> findAllVisibilityValues() {
979 SQLiteDatabase db = mDeDatabase.getReadableDatabase();
980 Map<Account, Map<String, Integer>> result = new HashMap<>();
981 Cursor cursor = db.rawQuery(
982 "SELECT " + TABLE_VISIBILITY + "." + VISIBILITY_PACKAGE
983 + ", " + TABLE_VISIBILITY + "." + VISIBILITY_VALUE
984 + ", " + TABLE_ACCOUNTS + "." + ACCOUNTS_NAME
985 + ", " + TABLE_ACCOUNTS + "." + ACCOUNTS_TYPE
986 + " FROM " + TABLE_VISIBILITY
987 + " JOIN " + TABLE_ACCOUNTS
988 + " ON " + TABLE_ACCOUNTS + "." + ACCOUNTS_ID
989 + " = " + TABLE_VISIBILITY + "." + VISIBILITY_ACCOUNTS_ID, null);
990 try {
991 while (cursor.moveToNext()) {
992 String packageName = cursor.getString(0);
993 Integer visibility = cursor.getInt(1);
994 String accountName = cursor.getString(2);
995 String accountType = cursor.getString(3);
996 Account account = new Account(accountName, accountType);
997 Map <String, Integer> accountVisibility = result.get(account);
998 if (accountVisibility == null) {
999 accountVisibility = new HashMap<>();
1000 result.put(account, accountVisibility);
1001 }
1002 accountVisibility.put(packageName, visibility);
1003 }
1004 } finally {
1005 cursor.close();
1006 }
1007 return result;
1008 }
1009
Dmitry Dementyeve366f822017-01-31 10:25:10 -08001010 boolean deleteAccountVisibilityForPackage(String packageName) {
Dmitry Dementyeve5768622016-11-21 13:51:27 -08001011 SQLiteDatabase db = mDeDatabase.getWritableDatabase();
Dmitry Dementyeve366f822017-01-31 10:25:10 -08001012 return db.delete(TABLE_VISIBILITY, VISIBILITY_PACKAGE + "=? ",
1013 new String[] {packageName}) > 0;
Dmitry Dementyeve5768622016-11-21 13:51:27 -08001014 }
1015
Fyodor Kupolov00de49e2016-09-23 13:10:27 -07001016 long insertOrReplaceMetaAuthTypeAndUid(String authenticatorType, int uid) {
1017 SQLiteDatabase db = mDeDatabase.getWritableDatabase();
1018 ContentValues values = new ContentValues();
1019 values.put(META_KEY,
1020 META_KEY_FOR_AUTHENTICATOR_UID_FOR_TYPE_PREFIX + authenticatorType);
1021 values.put(META_VALUE, uid);
1022 return db.insertWithOnConflict(TABLE_META, null, values,
1023 SQLiteDatabase.CONFLICT_REPLACE);
1024 }
1025
1026 Map<String, Integer> findMetaAuthUid() {
1027 SQLiteDatabase db = mDeDatabase.getReadableDatabase();
1028 Cursor metaCursor = db.query(
1029 TABLE_META,
1030 new String[]{META_KEY, META_VALUE},
1031 SELECTION_META_BY_AUTHENTICATOR_TYPE,
1032 new String[]{META_KEY_FOR_AUTHENTICATOR_UID_FOR_TYPE_PREFIX + "%"},
1033 null /* groupBy */,
1034 null /* having */,
1035 META_KEY);
1036 Map<String, Integer> map = new LinkedHashMap<>();
1037 try {
1038 while (metaCursor.moveToNext()) {
1039 String type = TextUtils
1040 .split(metaCursor.getString(0), META_KEY_DELIMITER)[1];
1041 String uidStr = metaCursor.getString(1);
1042 if (TextUtils.isEmpty(type) || TextUtils.isEmpty(uidStr)) {
1043 // Should never happen.
1044 Slog.e(TAG, "Auth type empty: " + TextUtils.isEmpty(type)
1045 + ", uid empty: " + TextUtils.isEmpty(uidStr));
1046 continue;
1047 }
1048 int uid = Integer.parseInt(metaCursor.getString(1));
1049 map.put(type, uid);
1050 }
1051 } finally {
1052 metaCursor.close();
1053 }
1054 return map;
1055 }
1056
1057 boolean deleteMetaByAuthTypeAndUid(String type, int uid) {
1058 SQLiteDatabase db = mDeDatabase.getWritableDatabase();
1059 return db.delete(
1060 TABLE_META,
1061 META_KEY + "=? AND " + META_VALUE + "=?",
1062 new String[]{
1063 META_KEY_FOR_AUTHENTICATOR_UID_FOR_TYPE_PREFIX + type,
1064 String.valueOf(uid)}
1065 ) > 0;
1066 }
1067
Fyodor Kupolov79d138e2016-10-04 12:09:05 -07001068 /**
1069 * Returns list of all grants as {@link Pair pairs} of account name and UID.
1070 */
Fyodor Kupolov00de49e2016-09-23 13:10:27 -07001071 List<Pair<String, Integer>> findAllAccountGrants() {
1072 SQLiteDatabase db = mDeDatabase.getReadableDatabase();
1073 try (Cursor cursor = db.rawQuery(ACCOUNT_ACCESS_GRANTS, null)) {
1074 if (cursor == null || !cursor.moveToFirst()) {
1075 return Collections.emptyList();
1076 }
1077 List<Pair<String, Integer>> results = new ArrayList<>();
1078 do {
1079 final String accountName = cursor.getString(0);
1080 final int uid = cursor.getInt(1);
1081 results.add(Pair.create(accountName, uid));
1082 } while (cursor.moveToNext());
1083 return results;
1084 }
1085 }
1086
1087 private static class PreNDatabaseHelper extends SQLiteOpenHelper {
Fyodor Kupolov1ce01612016-08-26 11:39:07 -07001088 private final Context mContext;
1089 private final int mUserId;
1090
Fyodor Kupolov00de49e2016-09-23 13:10:27 -07001091 PreNDatabaseHelper(Context context, int userId, String preNDatabaseName) {
Fyodor Kupolov1ce01612016-08-26 11:39:07 -07001092 super(context, preNDatabaseName, null, PRE_N_DATABASE_VERSION);
1093 mContext = context;
1094 mUserId = userId;
1095 }
1096
1097 @Override
1098 public void onCreate(SQLiteDatabase db) {
1099 // We use PreNDatabaseHelper only if pre-N db exists
1100 throw new IllegalStateException("Legacy database cannot be created - only upgraded!");
1101 }
1102
1103 private void createSharedAccountsTable(SQLiteDatabase db) {
1104 db.execSQL("CREATE TABLE " + TABLE_SHARED_ACCOUNTS + " ( "
1105 + ACCOUNTS_ID + " INTEGER PRIMARY KEY AUTOINCREMENT, "
1106 + ACCOUNTS_NAME + " TEXT NOT NULL, "
1107 + ACCOUNTS_TYPE + " TEXT NOT NULL, "
1108 + "UNIQUE(" + ACCOUNTS_NAME + "," + ACCOUNTS_TYPE + "))");
1109 }
1110
1111 private void addLastSuccessfullAuthenticatedTimeColumn(SQLiteDatabase db) {
1112 db.execSQL("ALTER TABLE " + TABLE_ACCOUNTS + " ADD COLUMN "
1113 + ACCOUNTS_LAST_AUTHENTICATE_TIME_EPOCH_MILLIS + " DEFAULT 0");
1114 }
1115
1116 private void addOldAccountNameColumn(SQLiteDatabase db) {
1117 db.execSQL("ALTER TABLE " + TABLE_ACCOUNTS + " ADD COLUMN " + ACCOUNTS_PREVIOUS_NAME);
1118 }
1119
1120 private void addDebugTable(SQLiteDatabase db) {
Fyodor Kupolov00de49e2016-09-23 13:10:27 -07001121 DeDatabaseHelper.createDebugTable(db);
Fyodor Kupolov1ce01612016-08-26 11:39:07 -07001122 }
1123
1124 private void createAccountsDeletionTrigger(SQLiteDatabase db) {
1125 db.execSQL(""
1126 + " CREATE TRIGGER " + TABLE_ACCOUNTS + "Delete DELETE ON " + TABLE_ACCOUNTS
1127 + " BEGIN"
1128 + " DELETE FROM " + TABLE_AUTHTOKENS
1129 + " WHERE " + AUTHTOKENS_ACCOUNTS_ID + "=OLD." + ACCOUNTS_ID + " ;"
1130 + " DELETE FROM " + TABLE_EXTRAS
1131 + " WHERE " + EXTRAS_ACCOUNTS_ID + "=OLD." + ACCOUNTS_ID + " ;"
1132 + " DELETE FROM " + TABLE_GRANTS
1133 + " WHERE " + GRANTS_ACCOUNTS_ID + "=OLD." + ACCOUNTS_ID + " ;"
1134 + " END");
1135 }
1136
1137 private void createGrantsTable(SQLiteDatabase db) {
1138 db.execSQL("CREATE TABLE " + TABLE_GRANTS + " ( "
1139 + GRANTS_ACCOUNTS_ID + " INTEGER NOT NULL, "
1140 + GRANTS_AUTH_TOKEN_TYPE + " STRING NOT NULL, "
1141 + GRANTS_GRANTEE_UID + " INTEGER NOT NULL, "
1142 + "UNIQUE (" + GRANTS_ACCOUNTS_ID + "," + GRANTS_AUTH_TOKEN_TYPE
1143 + "," + GRANTS_GRANTEE_UID + "))");
1144 }
1145
Fyodor Kupolov00de49e2016-09-23 13:10:27 -07001146 static long insertMetaAuthTypeAndUid(SQLiteDatabase db, String authenticatorType, int uid) {
1147 ContentValues values = new ContentValues();
1148 values.put(META_KEY,
1149 META_KEY_FOR_AUTHENTICATOR_UID_FOR_TYPE_PREFIX + authenticatorType);
1150 values.put(META_VALUE, uid);
1151 return db.insert(TABLE_META, null, values);
1152 }
1153
Fyodor Kupolov1ce01612016-08-26 11:39:07 -07001154 private void populateMetaTableWithAuthTypeAndUID(SQLiteDatabase db,
1155 Map<String, Integer> authTypeAndUIDMap) {
1156 for (Map.Entry<String, Integer> entry : authTypeAndUIDMap.entrySet()) {
Fyodor Kupolov00de49e2016-09-23 13:10:27 -07001157 insertMetaAuthTypeAndUid(db, entry.getKey(), entry.getValue());
Fyodor Kupolov1ce01612016-08-26 11:39:07 -07001158 }
1159 }
1160
1161 /**
1162 * Pre-N database may need an upgrade before splitting
1163 */
1164 @Override
1165 public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
1166 Log.e(TAG, "upgrade from version " + oldVersion + " to version " + newVersion);
1167
1168 if (oldVersion == 1) {
1169 // no longer need to do anything since the work is done
1170 // when upgrading from version 2
1171 oldVersion++;
1172 }
1173
1174 if (oldVersion == 2) {
1175 createGrantsTable(db);
1176 db.execSQL("DROP TRIGGER " + TABLE_ACCOUNTS + "Delete");
1177 createAccountsDeletionTrigger(db);
1178 oldVersion++;
1179 }
1180
1181 if (oldVersion == 3) {
1182 db.execSQL("UPDATE " + TABLE_ACCOUNTS + " SET " + ACCOUNTS_TYPE +
1183 " = 'com.google' WHERE " + ACCOUNTS_TYPE + " == 'com.google.GAIA'");
1184 oldVersion++;
1185 }
1186
1187 if (oldVersion == 4) {
1188 createSharedAccountsTable(db);
1189 oldVersion++;
1190 }
1191
1192 if (oldVersion == 5) {
1193 addOldAccountNameColumn(db);
1194 oldVersion++;
1195 }
1196
1197 if (oldVersion == 6) {
1198 addLastSuccessfullAuthenticatedTimeColumn(db);
1199 oldVersion++;
1200 }
1201
1202 if (oldVersion == 7) {
1203 addDebugTable(db);
1204 oldVersion++;
1205 }
1206
1207 if (oldVersion == 8) {
1208 populateMetaTableWithAuthTypeAndUID(
1209 db,
1210 AccountManagerService.getAuthenticatorTypeAndUIDForUser(mContext, mUserId));
1211 oldVersion++;
1212 }
1213
1214 if (oldVersion != newVersion) {
1215 Log.e(TAG, "failed to upgrade version " + oldVersion + " to version " + newVersion);
1216 }
1217 }
1218
1219 @Override
1220 public void onOpen(SQLiteDatabase db) {
1221 if (Log.isLoggable(TAG, Log.VERBOSE)) Log.v(TAG, "opened database " + DATABASE_NAME);
1222 }
1223 }
1224
Fyodor Kupolov00de49e2016-09-23 13:10:27 -07001225 List<Account> findCeAccountsNotInDe() {
1226 SQLiteDatabase db = mDeDatabase.getReadableDatabaseUserIsUnlocked();
Fyodor Kupolov1ce01612016-08-26 11:39:07 -07001227 // Select accounts from CE that do not exist in DE
1228 Cursor cursor = db.rawQuery(
1229 "SELECT " + ACCOUNTS_NAME + "," + ACCOUNTS_TYPE
1230 + " FROM " + CE_TABLE_ACCOUNTS
1231 + " WHERE NOT EXISTS "
1232 + " (SELECT " + ACCOUNTS_ID + " FROM " + TABLE_ACCOUNTS
1233 + " WHERE " + ACCOUNTS_ID + "=" + CE_TABLE_ACCOUNTS + "." + ACCOUNTS_ID
1234 + " )", null);
1235 try {
1236 List<Account> accounts = new ArrayList<>(cursor.getCount());
1237 while (cursor.moveToNext()) {
1238 String accountName = cursor.getString(0);
1239 String accountType = cursor.getString(1);
1240 accounts.add(new Account(accountName, accountType));
1241 }
1242 return accounts;
1243 } finally {
1244 cursor.close();
1245 }
1246 }
1247
Fyodor Kupolov00de49e2016-09-23 13:10:27 -07001248 boolean deleteCeAccount(long accountId) {
Fyodor Kupolov98e9e852016-12-09 14:58:05 -08001249 SQLiteDatabase db = mDeDatabase.getWritableDatabaseUserIsUnlocked();
Fyodor Kupolov1ce01612016-08-26 11:39:07 -07001250 return db.delete(
1251 CE_TABLE_ACCOUNTS, ACCOUNTS_ID + "=" + accountId, null) > 0;
1252 }
1253
Fyodor Kupolov00de49e2016-09-23 13:10:27 -07001254 boolean isCeDatabaseAttached() {
1255 return mDeDatabase.mCeAttached;
1256 }
1257
1258 void beginTransaction() {
1259 mDeDatabase.getWritableDatabase().beginTransaction();
1260 }
1261
1262 void setTransactionSuccessful() {
1263 mDeDatabase.getWritableDatabase().setTransactionSuccessful();
1264 }
1265
1266 void endTransaction() {
1267 mDeDatabase.getWritableDatabase().endTransaction();
1268 }
1269
1270 void attachCeDatabase(File ceDbFile) {
1271 CeDatabaseHelper.create(mContext, mPreNDatabaseFile, ceDbFile);
1272 SQLiteDatabase db = mDeDatabase.getWritableDatabase();
1273 db.execSQL("ATTACH DATABASE '" + ceDbFile.getPath()+ "' AS ceDb");
1274 mDeDatabase.mCeAttached = true;
1275 }
1276
1277 /*
1278 * Finds the row key where the next insertion should take place. Returns number of rows
1279 * if it is less {@link #MAX_DEBUG_DB_SIZE}, otherwise finds the lowest number available.
1280 */
1281 int calculateDebugTableInsertionPoint() {
1282 SQLiteDatabase db = mDeDatabase.getReadableDatabase();
1283 String queryCountDebugDbRows = "SELECT COUNT(*) FROM " + TABLE_DEBUG;
1284 int size = (int) DatabaseUtils.longForQuery(db, queryCountDebugDbRows, null);
1285 if (size < MAX_DEBUG_DB_SIZE) {
1286 return size;
1287 }
1288
1289 // This query finds the smallest timestamp value (and if 2 records have
1290 // same timestamp, the choose the lower id).
1291 queryCountDebugDbRows = "SELECT " + DEBUG_TABLE_KEY +
1292 " FROM " + TABLE_DEBUG +
1293 " ORDER BY " + DEBUG_TABLE_TIMESTAMP + "," + DEBUG_TABLE_KEY +
1294 " LIMIT 1";
1295 return (int) DatabaseUtils.longForQuery(db, queryCountDebugDbRows, null);
1296 }
1297
1298 SQLiteStatement compileSqlStatementForLogging() {
1299 // TODO b/31708085 Fix debug logging - it eagerly opens database for write without a need
1300 SQLiteDatabase db = mDeDatabase.getWritableDatabase();
1301 String sql = "INSERT OR REPLACE INTO " + AccountsDb.TABLE_DEBUG
1302 + " VALUES (?,?,?,?,?,?)";
1303 return db.compileStatement(sql);
1304 }
1305
1306 void dumpDebugTable(PrintWriter pw) {
1307 SQLiteDatabase db = mDeDatabase.getReadableDatabase();
1308 Cursor cursor = db.query(TABLE_DEBUG, null,
1309 null, null, null, null, DEBUG_TABLE_TIMESTAMP);
1310 pw.println("AccountId, Action_Type, timestamp, UID, TableName, Key");
1311 pw.println("Accounts History");
1312 try {
1313 while (cursor.moveToNext()) {
1314 // print type,count
1315 pw.println(cursor.getString(0) + "," + cursor.getString(1) + "," +
1316 cursor.getString(2) + "," + cursor.getString(3) + ","
1317 + cursor.getString(4) + "," + cursor.getString(5));
1318 }
1319 } finally {
1320 cursor.close();
1321 }
1322 }
1323
Andrew Scullc7770d62017-05-22 17:49:58 +01001324 @Override
Fyodor Kupolov00de49e2016-09-23 13:10:27 -07001325 public void close() {
1326 mDeDatabase.close();
1327 }
1328
Fyodor Kupolov1ce01612016-08-26 11:39:07 -07001329 static void deleteDbFileWarnIfFailed(File dbFile) {
1330 if (!SQLiteDatabase.deleteDatabase(dbFile)) {
1331 Log.w(TAG, "Database at " + dbFile + " was not deleted successfully");
1332 }
1333 }
Fyodor Kupolov00de49e2016-09-23 13:10:27 -07001334
1335 public static AccountsDb create(Context context, int userId, File preNDatabaseFile,
1336 File deDatabaseFile) {
1337 boolean newDbExists = deDatabaseFile.exists();
1338 DeDatabaseHelper deDatabaseHelper = new DeDatabaseHelper(context, userId,
1339 deDatabaseFile.getPath());
1340 // If the db just created, and there is a legacy db, migrate it
1341 if (!newDbExists && preNDatabaseFile.exists()) {
1342 // Migrate legacy db to the latest version - PRE_N_DATABASE_VERSION
1343 PreNDatabaseHelper
1344 preNDatabaseHelper = new PreNDatabaseHelper(context, userId,
1345 preNDatabaseFile.getPath());
1346 // Open the database to force upgrade if required
1347 preNDatabaseHelper.getWritableDatabase();
1348 preNDatabaseHelper.close();
1349 // Move data without SPII to DE
1350 deDatabaseHelper.migratePreNDbToDe(preNDatabaseFile);
1351 }
1352 return new AccountsDb(deDatabaseHelper, context, preNDatabaseFile);
1353 }
1354
Dmitry Dementyev01985ff2017-01-19 16:03:39 -08001355}