1 /*
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 
17 package com.android.server.accounts;
18 
19 import android.accounts.Account;
20 import android.annotation.Nullable;
21 import android.content.ContentValues;
22 import android.content.Context;
23 import android.database.Cursor;
24 import android.database.DatabaseUtils;
25 import android.database.sqlite.SQLiteDatabase;
26 import android.database.sqlite.SQLiteException;
27 import android.database.sqlite.SQLiteOpenHelper;
28 import android.database.sqlite.SQLiteStatement;
29 import android.os.FileUtils;
30 import android.text.TextUtils;
31 import android.util.Log;
32 import android.util.Pair;
33 import android.util.Slog;
34 
35 import java.io.File;
36 import java.io.IOException;
37 import java.io.PrintWriter;
38 import java.util.ArrayList;
39 import java.util.Collections;
40 import java.util.HashMap;
41 import java.util.LinkedHashMap;
42 import java.util.List;
43 import java.util.Map;
44 
45 /**
46  * Persistence layer abstraction for accessing accounts_ce/accounts_de databases.
47  *
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>
52  */
53 class AccountsDb implements AutoCloseable {
54     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;
59     private static final int DE_DATABASE_VERSION = 3; // Added visibility support in O
60 
61     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 
77     private static final String TABLE_VISIBILITY = "visibility";
78     private static final String VISIBILITY_ACCOUNTS_ID = "accounts_id";
79     private static final String VISIBILITY_PACKAGE = "_package";
80     private static final String VISIBILITY_VALUE = "value";
81 
82     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 
142     static final int MAX_DEBUG_DB_SIZE = 64;
143 
144     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 
162     private static final String SELECTION_ACCOUNTS_ID_BY_ACCOUNT =
163         "accounts_id=(select _id FROM accounts WHERE name=? AND type=?)";
164 
165     private static final String[] COLUMNS_AUTHTOKENS_TYPE_AND_AUTHTOKEN =
166             {AUTHTOKENS_TYPE, AUTHTOKENS_AUTHTOKEN};
167 
168     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 
183     private final DeDatabaseHelper mDeDatabase;
184     private final Context mContext;
185     private final File mPreNDatabaseFile;
186 
187     final Object mDebugStatementLock = new Object();
188     private volatile long mDebugDbInsertionPoint = -1;
189     private volatile SQLiteStatement mDebugStatementForLogging; // not thread safe.
190 
AccountsDb(DeDatabaseHelper deDatabase, Context context, File preNDatabaseFile)191     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 {
198 
CeDatabaseHelper(Context context, String ceDatabaseName)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
onCreate(SQLiteDatabase db)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 
createAccountsDeletionTrigger(SQLiteDatabase db)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
onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion)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);
260                 oldVersion++;
261             }
262 
263             if (oldVersion != newVersion) {
264                 Log.e(TAG, "failed to upgrade version " + oldVersion + " to version " + newVersion);
265             }
266         }
267 
268         @Override
onDowngrade(SQLiteDatabase db, int oldVersion, int newVersion)269         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
onOpen(SQLiteDatabase db)276         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.
284          */
create( Context context, File preNDatabaseFile, File ceDatabaseFile)285         static CeDatabaseHelper create(
286                 Context context,
287                 File preNDatabaseFile,
288                 File ceDatabaseFile) {
289             boolean newDbExists = ceDatabaseFile.exists();
290             if (Log.isLoggable(TAG, Log.VERBOSE)) {
291                 Log.v(TAG, "CeDatabaseHelper.create ceDatabaseFile=" + ceDatabaseFile
292                         + " oldDbExists=" + preNDatabaseFile.exists()
293                         + " newDbExists=" + newDbExists);
294             }
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 
migratePreNDbToCe(File oldDbFile, File ceDbFile)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         }
324     }
325 
326     /**
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      */
findAuthtokenForAllAccounts(String accountType, String authToken)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 
findAuthTokensByAccount(Account account)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,
352                 SELECTION_ACCOUNTS_ID_BY_ACCOUNT,
353                 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 
deleteAuthtokensByAccountIdAndType(long accountId, String authtokenType)367     boolean deleteAuthtokensByAccountIdAndType(long accountId, String authtokenType) {
368         SQLiteDatabase db = mDeDatabase.getWritableDatabaseUserIsUnlocked();
369         return db.delete(CE_TABLE_AUTHTOKENS,
370                 AUTHTOKENS_ACCOUNTS_ID + "=?" + " AND " + AUTHTOKENS_TYPE + "=?",
371                 new String[]{String.valueOf(accountId), authtokenType}) > 0;
372     }
373 
deleteAuthToken(String authTokenId)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 
insertAuthToken(long accountId, String authTokenType, String authToken)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 
updateCeAccountPassword(long accountId, String password)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 
renameCeAccount(long accountId, String newName)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 
deleteAuthTokensByAccountId(long accountId)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 
findExtrasIdByAccountId(long accountId, String key)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 
updateExtra(long extrasId, String value)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 
insertExtra(long accountId, String key, String value)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 
findUserExtrasForAccount(Account account)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,
456                 SELECTION_ACCOUNTS_ID_BY_ACCOUNT,
457                 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 
findCeAccountId(Account account)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 
findAccountPasswordByNameAndType(String name, String type)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 
insertCeAccount(Account account, String password)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 
507     static class DeDatabaseHelper extends SQLiteOpenHelper {
508 
509         private final int mUserId;
510         private volatile boolean mCeAttached;
511 
DeDatabaseHelper(Context context, int userId, String deDatabaseName)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
onCreate(SQLiteDatabase db)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);
540             createDebugTable(db);
541             createAccountsVisibilityTable(db);
542             createAccountsDeletionVisibilityCleanupTrigger(db);
543         }
544 
createSharedAccountsTable(SQLiteDatabase db)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 
createAccountsDeletionTrigger(SQLiteDatabase db)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 
createGrantsTable(SQLiteDatabase db)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 
createAccountsVisibilityTable(SQLiteDatabase db)571         private void createAccountsVisibilityTable(SQLiteDatabase db) {
572             db.execSQL("CREATE TABLE " + TABLE_VISIBILITY + " ( "
573                   + VISIBILITY_ACCOUNTS_ID + " INTEGER NOT NULL, "
574                   + VISIBILITY_PACKAGE + " TEXT NOT NULL, "
575                   + VISIBILITY_VALUE + " INTEGER, "
576                   + "PRIMARY KEY(" + VISIBILITY_ACCOUNTS_ID + "," + VISIBILITY_PACKAGE + "))");
577         }
578 
createDebugTable(SQLiteDatabase db)579         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 
createAccountsDeletionVisibilityCleanupTrigger(SQLiteDatabase db)591         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 
601         @Override
onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion)602         public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
603             Log.i(TAG, "upgrade from version " + oldVersion + " to version " + newVersion);
604 
605             if (oldVersion == 1) {
606                 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");
614                 db.execSQL("DROP TABLE IF EXISTS " + TABLE_VISIBILITY);
615                 createAccountsVisibilityTable(db);
616                 createAccountsDeletionVisibilityCleanupTrigger(db);
617                 oldVersion++;
618             }
619 
620             if (oldVersion != newVersion) {
621                 Log.e(TAG, "failed to upgrade version " + oldVersion + " to version " + newVersion);
622             }
623         }
624 
625         @Override
onDowngrade(SQLiteDatabase db, int oldVersion, int newVersion)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 
getReadableDatabaseUserIsUnlocked()632         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 
getWritableDatabaseUserIsUnlocked()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
onOpen(SQLiteDatabase db)649         public void onOpen(SQLiteDatabase db) {
650             if (Log.isLoggable(TAG, Log.VERBOSE)) Log.v(TAG, "opened database " + DE_DATABASE_NAME);
651         }
652 
migratePreNDbToDe(File preNDbFile)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         }
695     }
696 
deleteDeAccount(long accountId)697     boolean deleteDeAccount(long accountId) {
698         SQLiteDatabase db = mDeDatabase.getWritableDatabase();
699         return db.delete(TABLE_ACCOUNTS, ACCOUNTS_ID + "=" + accountId, null) > 0;
700     }
701 
insertSharedAccount(Account account)702     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     }
710 
deleteSharedAccount(Account account)711     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     }
716 
renameSharedAccount(Account account, String newName)717     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     }
726 
getSharedAccounts()727     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);
737                 do {
738                     accountList.add(new Account(cursor.getString(nameIndex),
739                             cursor.getString(typeIndex)));
740                 } while (cursor.moveToNext());
741             }
742         } finally {
743             if (cursor != null) {
744                 cursor.close();
745             }
746         }
747         return accountList;
748     }
749 
findSharedAccountId(Account account)750     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);
759             }
760             return -1;
761         } finally {
762             cursor.close();
763         }
764     }
765 
findAccountLastAuthenticatedTime(Account account)766     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 
updateAccountLastAuthenticatedTime(Account account)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 
dumpDeAccountsTable(PrintWriter pw)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 
findDeAccountId(Account account)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 
findAllDeAccounts()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 
findDeAccountPreviousName(Account account)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 
insertDeAccount(Account account, long accountId)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 
renameDeAccount(long accountId, String newName, String previousName)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 
deleteGrantsByAccountIdAuthTokenTypeAndUid(long accountId, String authTokenType, long uid)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 
findAllUidGrants()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 
findMatchingGrantsCount(int uid, String authTokenType, Account account)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 
findMatchingGrantsCountAnyToken(int uid, Account account)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 
insertGrant(long accountId, String authTokenType, int uid)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 
deleteGrantsByUid(int uid)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 
setAccountVisibility(long accountId, String packageName, int visibility)921     boolean setAccountVisibility(long accountId, String packageName, int visibility) {
922         SQLiteDatabase db = mDeDatabase.getWritableDatabase();
923         ContentValues values = new ContentValues();
924         values.put(VISIBILITY_ACCOUNTS_ID, String.valueOf(accountId));
925         values.put(VISIBILITY_PACKAGE, packageName);
926         values.put(VISIBILITY_VALUE, String.valueOf(visibility));
927         return (db.replace(TABLE_VISIBILITY, VISIBILITY_VALUE, values) != -1);
928     }
929 
findAccountVisibility(Account account, String packageName)930     Integer findAccountVisibility(Account account, String packageName) {
931         SQLiteDatabase db = mDeDatabase.getReadableDatabase();
932         final Cursor cursor = db.query(TABLE_VISIBILITY, new String[] {VISIBILITY_VALUE},
933                 SELECTION_ACCOUNTS_ID_BY_ACCOUNT + " AND " + VISIBILITY_PACKAGE + "=? ",
934                 new String[] {account.name, account.type, packageName}, null, null, null);
935         try {
936             while (cursor.moveToNext()) {
937                 return cursor.getInt(0);
938             }
939         } finally {
940             cursor.close();
941         }
942         return null;
943     }
944 
findAccountVisibility(long accountId, String packageName)945     Integer findAccountVisibility(long accountId, String packageName) {
946         SQLiteDatabase db = mDeDatabase.getReadableDatabase();
947         final Cursor cursor = db.query(TABLE_VISIBILITY, new String[] {VISIBILITY_VALUE},
948                 VISIBILITY_ACCOUNTS_ID + "=? AND " + VISIBILITY_PACKAGE + "=? ",
949                 new String[] {String.valueOf(accountId), packageName}, null, null, null);
950         try {
951             while (cursor.moveToNext()) {
952                 return cursor.getInt(0);
953             }
954         } finally {
955             cursor.close();
956         }
957         return null;
958     }
959 
findDeAccountByAccountId(long accountId)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     /**
975      * Returns a map from packageNames to visibility.
976      */
findAllVisibilityValuesForAccount(Account account)977     Map<String, Integer> findAllVisibilityValuesForAccount(Account account) {
978         SQLiteDatabase db = mDeDatabase.getReadableDatabase();
979         Map<String, Integer> result = new HashMap<>();
980         final Cursor cursor =
981                 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);
984         try {
985             while (cursor.moveToNext()) {
986                 result.put(cursor.getString(0), cursor.getInt(1));
987             }
988         } finally {
989             cursor.close();
990         }
991         return result;
992     }
993 
994     /**
995      * Returns a map account -> (package -> visibility)
996      */
findAllVisibilityValues()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 
deleteAccountVisibilityForPackage(String packageName)1029     boolean deleteAccountVisibilityForPackage(String packageName) {
1030         SQLiteDatabase db = mDeDatabase.getWritableDatabase();
1031         return db.delete(TABLE_VISIBILITY, VISIBILITY_PACKAGE + "=? ",
1032                 new String[] {packageName}) > 0;
1033     }
1034 
insertOrReplaceMetaAuthTypeAndUid(String authenticatorType, int uid)1035     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 
findMetaAuthUid()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 
deleteMetaByAuthTypeAndUid(String type, int uid)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 
1087     /**
1088      * Returns list of all grants as {@link Pair pairs} of account name and UID.
1089      */
findAllAccountGrants()1090     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 {
1107         private final Context mContext;
1108         private final int mUserId;
1109 
PreNDatabaseHelper(Context context, int userId, String preNDatabaseName)1110         PreNDatabaseHelper(Context context, int userId, String preNDatabaseName) {
1111             super(context, preNDatabaseName, null, PRE_N_DATABASE_VERSION);
1112             mContext = context;
1113             mUserId = userId;
1114         }
1115 
1116         @Override
onCreate(SQLiteDatabase db)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 
createSharedAccountsTable(SQLiteDatabase db)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 
addLastSuccessfullAuthenticatedTimeColumn(SQLiteDatabase db)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 
addOldAccountNameColumn(SQLiteDatabase db)1135         private void addOldAccountNameColumn(SQLiteDatabase db) {
1136             db.execSQL("ALTER TABLE " + TABLE_ACCOUNTS + " ADD COLUMN " + ACCOUNTS_PREVIOUS_NAME);
1137         }
1138 
addDebugTable(SQLiteDatabase db)1139         private void addDebugTable(SQLiteDatabase db) {
1140             DeDatabaseHelper.createDebugTable(db);
1141         }
1142 
createAccountsDeletionTrigger(SQLiteDatabase db)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 
createGrantsTable(SQLiteDatabase db)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 
insertMetaAuthTypeAndUid(SQLiteDatabase db, String authenticatorType, int uid)1165         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 
populateMetaTableWithAuthTypeAndUID(SQLiteDatabase db, Map<String, Integer> authTypeAndUIDMap)1173         private void populateMetaTableWithAuthTypeAndUID(SQLiteDatabase db,
1174                 Map<String, Integer> authTypeAndUIDMap) {
1175             for (Map.Entry<String, Integer> entry : authTypeAndUIDMap.entrySet()) {
1176                 insertMetaAuthTypeAndUid(db, entry.getKey(), entry.getValue());
1177             }
1178         }
1179 
1180         /**
1181          * Pre-N database may need an upgrade before splitting
1182          */
1183         @Override
onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion)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
onOpen(SQLiteDatabase db)1239         public void onOpen(SQLiteDatabase db) {
1240             if (Log.isLoggable(TAG, Log.VERBOSE)) Log.v(TAG, "opened database " + DATABASE_NAME);
1241         }
1242     }
1243 
findCeAccountsNotInDe()1244     List<Account> findCeAccountsNotInDe() {
1245         SQLiteDatabase db = mDeDatabase.getReadableDatabaseUserIsUnlocked();
1246         // 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 
deleteCeAccount(long accountId)1267     boolean deleteCeAccount(long accountId) {
1268         SQLiteDatabase db = mDeDatabase.getWritableDatabaseUserIsUnlocked();
1269         return db.delete(
1270                 CE_TABLE_ACCOUNTS, ACCOUNTS_ID + "=" + accountId, null) > 0;
1271     }
1272 
isCeDatabaseAttached()1273     boolean isCeDatabaseAttached() {
1274         return mDeDatabase.mCeAttached;
1275     }
1276 
beginTransaction()1277     void beginTransaction() {
1278         mDeDatabase.getWritableDatabase().beginTransaction();
1279     }
1280 
setTransactionSuccessful()1281     void setTransactionSuccessful() {
1282         mDeDatabase.getWritableDatabase().setTransactionSuccessful();
1283     }
1284 
endTransaction()1285     void endTransaction() {
1286         mDeDatabase.getWritableDatabase().endTransaction();
1287     }
1288 
attachCeDatabase(File ceDbFile)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      */
calculateDebugTableInsertionPoint()1300     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             }
1308 
1309             // 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         }
1322     }
1323 
compileSqlStatementForLogging()1324     SQLiteStatement compileSqlStatementForLogging() {
1325         SQLiteDatabase db = mDeDatabase.getWritableDatabase();
1326         String sql = "INSERT OR REPLACE INTO " + AccountsDb.TABLE_DEBUG
1327                 + " VALUES (?,?,?,?,?,?)";
1328         return db.compileStatement(sql);
1329     }
1330 
1331     /**
1332      * Returns statement for logging or {@code null} on database open failure.
1333      * Returned value must be guarded by {link #debugStatementLock}
1334      */
getStatementForLogging()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 
closeDebugStatement()1348     void closeDebugStatement() {
1349         synchronized (mDebugStatementLock) {
1350             if (mDebugStatementForLogging != null) {
1351                 mDebugStatementForLogging.close();
1352                 mDebugStatementForLogging = null;
1353             }
1354         }
1355     }
1356 
reserveDebugDbInsertionPoint()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 
dumpDebugTable(PrintWriter pw)1366     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 
1384     @Override
close()1385     public void close() {
1386         mDeDatabase.close();
1387     }
1388 
deleteDbFileWarnIfFailed(File dbFile)1389     static void deleteDbFileWarnIfFailed(File dbFile) {
1390         if (!SQLiteDatabase.deleteDatabase(dbFile)) {
1391             Log.w(TAG, "Database at " + dbFile + " was not deleted successfully");
1392         }
1393     }
1394 
create(Context context, int userId, File preNDatabaseFile, File deDatabaseFile)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 
1415     /**
1416      * Removes all tables and triggers created by AccountManager.
1417      */
resetDatabase(SQLiteDatabase db)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     }
1437 }
1438