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.content.ContentValues;
21 import android.content.Context;
22 import android.database.Cursor;
23 import android.database.DatabaseUtils;
24 import android.database.sqlite.SQLiteDatabase;
25 import android.database.sqlite.SQLiteOpenHelper;
26 import android.database.sqlite.SQLiteStatement;
27 import android.os.FileUtils;
28 import android.text.TextUtils;
29 import android.util.Log;
30 import android.util.Pair;
31 import android.util.Slog;
32 
33 import java.io.File;
34 import java.io.IOException;
35 import java.io.PrintWriter;
36 import java.util.ArrayList;
37 import java.util.Collections;
38 import java.util.HashMap;
39 import java.util.LinkedHashMap;
40 import java.util.List;
41 import java.util.Map;
42 
43 /**
44  * Persistence layer abstraction for accessing accounts_ce/accounts_de databases.
45  *
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>
50  */
51 class AccountsDb implements AutoCloseable {
52     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;
57     private static final int DE_DATABASE_VERSION = 3; // Added visibility support in O
58 
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 
76     private static final String TABLE_VISIBILITY = "visibility";
77     private static final String VISIBILITY_ACCOUNTS_ID = "accounts_id";
78     private static final String VISIBILITY_PACKAGE = "_package";
79     private static final String VISIBILITY_VALUE = "value";
80 
81     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 
141     static final int MAX_DEBUG_DB_SIZE = 64;
142 
143     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 
161     private static final String SELECTION_ACCOUNTS_ID_BY_ACCOUNT =
162         "accounts_id=(select _id FROM accounts WHERE name=? AND type=?)";
163 
164     private static final String[] COLUMNS_AUTHTOKENS_TYPE_AND_AUTHTOKEN =
165             {AUTHTOKENS_TYPE, AUTHTOKENS_AUTHTOKEN};
166 
167     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 
182     private final DeDatabaseHelper mDeDatabase;
183     private final Context mContext;
184     private final File mPreNDatabaseFile;
185 
AccountsDb(DeDatabaseHelper deDatabase, Context context, File preNDatabaseFile)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 {
193 
CeDatabaseHelper(Context context, String ceDatabaseName)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
onCreate(SQLiteDatabase db)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 
createAccountsDeletionTrigger(SQLiteDatabase db)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
onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion)241         public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
242             Log.i(TAG, "Upgrade CE from version " + oldVersion + " to version " + newVersion);
243 
244             if (oldVersion == 9) {
245                 if (Log.isLoggable(TAG, Log.VERBOSE)) {
246                     Log.v(TAG, "onUpgrade upgrading to v10");
247                 }
248                 db.execSQL("DROP TABLE IF EXISTS " + TABLE_META);
249                 db.execSQL("DROP TABLE IF EXISTS " + TABLE_SHARED_ACCOUNTS);
250                 // Recreate the trigger, since the old one references the table to be removed
251                 db.execSQL("DROP TRIGGER IF EXISTS " + TABLE_ACCOUNTS + "Delete");
252                 createAccountsDeletionTrigger(db);
253                 db.execSQL("DROP TABLE IF EXISTS " + TABLE_GRANTS);
254                 db.execSQL("DROP TABLE IF EXISTS " + TABLE_DEBUG);
255                 oldVersion++;
256             }
257 
258             if (oldVersion != newVersion) {
259                 Log.e(TAG, "failed to upgrade version " + oldVersion + " to version " + newVersion);
260             }
261         }
262 
263         @Override
onOpen(SQLiteDatabase db)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.
272          */
create( Context context, File preNDatabaseFile, File ceDatabaseFile)273         static CeDatabaseHelper create(
274                 Context context,
275                 File preNDatabaseFile,
276                 File ceDatabaseFile) {
277             boolean newDbExists = ceDatabaseFile.exists();
278             if (Log.isLoggable(TAG, Log.VERBOSE)) {
279                 Log.v(TAG, "CeDatabaseHelper.create ceDatabaseFile=" + ceDatabaseFile
280                         + " oldDbExists=" + preNDatabaseFile.exists()
281                         + " newDbExists=" + newDbExists);
282             }
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 
migratePreNDbToCe(File oldDbFile, File ceDbFile)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         }
312     }
313 
314     /**
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      */
findAuthtokenForAllAccounts(String accountType, String authToken)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 
findAuthTokensByAccount(Account account)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,
340                 SELECTION_ACCOUNTS_ID_BY_ACCOUNT,
341                 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 
deleteAuthtokensByAccountIdAndType(long accountId, String authtokenType)355     boolean deleteAuthtokensByAccountIdAndType(long accountId, String authtokenType) {
356         SQLiteDatabase db = mDeDatabase.getWritableDatabaseUserIsUnlocked();
357         return db.delete(CE_TABLE_AUTHTOKENS,
358                 AUTHTOKENS_ACCOUNTS_ID + "=?" + " AND " + AUTHTOKENS_TYPE + "=?",
359                 new String[]{String.valueOf(accountId), authtokenType}) > 0;
360     }
361 
deleteAuthToken(String authTokenId)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 
insertAuthToken(long accountId, String authTokenType, String authToken)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 
updateCeAccountPassword(long accountId, String password)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 
renameCeAccount(long accountId, String newName)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 
deleteAuthTokensByAccountId(long accountId)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 
findExtrasIdByAccountId(long accountId, String key)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 
updateExtra(long extrasId, String value)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 
insertExtra(long accountId, String key, String value)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 
findUserExtrasForAccount(Account account)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,
444                 SELECTION_ACCOUNTS_ID_BY_ACCOUNT,
445                 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 
findCeAccountId(Account account)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 
findAccountPasswordByNameAndType(String name, String type)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 
insertCeAccount(Account account, String password)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 
495     static class DeDatabaseHelper extends SQLiteOpenHelper {
496 
497         private final int mUserId;
498         private volatile boolean mCeAttached;
499 
DeDatabaseHelper(Context context, int userId, String deDatabaseName)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
onCreate(SQLiteDatabase db)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);
528             createDebugTable(db);
529             createAccountsVisibilityTable(db);
530             createAccountsDeletionVisibilityCleanupTrigger(db);
531         }
532 
createSharedAccountsTable(SQLiteDatabase db)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 
createAccountsDeletionTrigger(SQLiteDatabase db)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 
createGrantsTable(SQLiteDatabase db)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 
createAccountsVisibilityTable(SQLiteDatabase db)559         private void createAccountsVisibilityTable(SQLiteDatabase db) {
560             db.execSQL("CREATE TABLE " + TABLE_VISIBILITY + " ( "
561                   + VISIBILITY_ACCOUNTS_ID + " INTEGER NOT NULL, "
562                   + VISIBILITY_PACKAGE + " TEXT NOT NULL, "
563                   + VISIBILITY_VALUE + " INTEGER, "
564                   + "PRIMARY KEY(" + VISIBILITY_ACCOUNTS_ID + "," + VISIBILITY_PACKAGE + "))");
565         }
566 
createDebugTable(SQLiteDatabase db)567         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 
createAccountsDeletionVisibilityCleanupTrigger(SQLiteDatabase db)579         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 
589         @Override
onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion)590         public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
591             Log.i(TAG, "upgrade from version " + oldVersion + " to version " + newVersion);
592 
593             if (oldVersion == 1) {
594                 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");
602                 db.execSQL("DROP TABLE IF EXISTS " + TABLE_VISIBILITY);
603                 createAccountsVisibilityTable(db);
604                 createAccountsDeletionVisibilityCleanupTrigger(db);
605                 oldVersion++;
606             }
607 
608             if (oldVersion != newVersion) {
609                 Log.e(TAG, "failed to upgrade version " + oldVersion + " to version " + newVersion);
610             }
611         }
612 
getReadableDatabaseUserIsUnlocked()613         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 
getWritableDatabaseUserIsUnlocked()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
onOpen(SQLiteDatabase db)630         public void onOpen(SQLiteDatabase db) {
631             if (Log.isLoggable(TAG, Log.VERBOSE)) Log.v(TAG, "opened database " + DE_DATABASE_NAME);
632         }
633 
migratePreNDbToDe(File preNDbFile)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         }
676     }
677 
deleteDeAccount(long accountId)678     boolean deleteDeAccount(long accountId) {
679         SQLiteDatabase db = mDeDatabase.getWritableDatabase();
680         return db.delete(TABLE_ACCOUNTS, ACCOUNTS_ID + "=" + accountId, null) > 0;
681     }
682 
insertSharedAccount(Account account)683     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     }
691 
deleteSharedAccount(Account account)692     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     }
697 
renameSharedAccount(Account account, String newName)698     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     }
707 
getSharedAccounts()708     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);
718                 do {
719                     accountList.add(new Account(cursor.getString(nameIndex),
720                             cursor.getString(typeIndex)));
721                 } while (cursor.moveToNext());
722             }
723         } finally {
724             if (cursor != null) {
725                 cursor.close();
726             }
727         }
728         return accountList;
729     }
730 
findSharedAccountId(Account account)731     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);
740             }
741             return -1;
742         } finally {
743             cursor.close();
744         }
745     }
746 
findAccountLastAuthenticatedTime(Account account)747     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 
updateAccountLastAuthenticatedTime(Account account)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 
dumpDeAccountsTable(PrintWriter pw)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 
findDeAccountId(Account account)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 
findAllDeAccounts()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 
findDeAccountPreviousName(Account account)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 
insertDeAccount(Account account, long accountId)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 
renameDeAccount(long accountId, String newName, String previousName)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 
deleteGrantsByAccountIdAuthTokenTypeAndUid(long accountId, String authTokenType, long uid)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 
findAllUidGrants()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 
findMatchingGrantsCount(int uid, String authTokenType, Account account)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 
findMatchingGrantsCountAnyToken(int uid, Account account)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 
insertGrant(long accountId, String authTokenType, int uid)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 
deleteGrantsByUid(int uid)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 
setAccountVisibility(long accountId, String packageName, int visibility)902     boolean setAccountVisibility(long accountId, String packageName, int visibility) {
903         SQLiteDatabase db = mDeDatabase.getWritableDatabase();
904         ContentValues values = new ContentValues();
905         values.put(VISIBILITY_ACCOUNTS_ID, String.valueOf(accountId));
906         values.put(VISIBILITY_PACKAGE, packageName);
907         values.put(VISIBILITY_VALUE, String.valueOf(visibility));
908         return (db.replace(TABLE_VISIBILITY, VISIBILITY_VALUE, values) != -1);
909     }
910 
findAccountVisibility(Account account, String packageName)911     Integer findAccountVisibility(Account account, String packageName) {
912         SQLiteDatabase db = mDeDatabase.getReadableDatabase();
913         final Cursor cursor = db.query(TABLE_VISIBILITY, new String[] {VISIBILITY_VALUE},
914                 SELECTION_ACCOUNTS_ID_BY_ACCOUNT + " AND " + VISIBILITY_PACKAGE + "=? ",
915                 new String[] {account.name, account.type, packageName}, null, null, null);
916         try {
917             while (cursor.moveToNext()) {
918                 return cursor.getInt(0);
919             }
920         } finally {
921             cursor.close();
922         }
923         return null;
924     }
925 
findAccountVisibility(long accountId, String packageName)926     Integer findAccountVisibility(long accountId, String packageName) {
927         SQLiteDatabase db = mDeDatabase.getReadableDatabase();
928         final Cursor cursor = db.query(TABLE_VISIBILITY, new String[] {VISIBILITY_VALUE},
929                 VISIBILITY_ACCOUNTS_ID + "=? AND " + VISIBILITY_PACKAGE + "=? ",
930                 new String[] {String.valueOf(accountId), packageName}, null, null, null);
931         try {
932             while (cursor.moveToNext()) {
933                 return cursor.getInt(0);
934             }
935         } finally {
936             cursor.close();
937         }
938         return null;
939     }
940 
findDeAccountByAccountId(long accountId)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     /**
956      * Returns a map from packageNames to visibility.
957      */
findAllVisibilityValuesForAccount(Account account)958     Map<String, Integer> findAllVisibilityValuesForAccount(Account account) {
959         SQLiteDatabase db = mDeDatabase.getReadableDatabase();
960         Map<String, Integer> result = new HashMap<>();
961         final Cursor cursor =
962                 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);
965         try {
966             while (cursor.moveToNext()) {
967                 result.put(cursor.getString(0), cursor.getInt(1));
968             }
969         } finally {
970             cursor.close();
971         }
972         return result;
973     }
974 
975     /**
976      * Returns a map account -> (package -> visibility)
977      */
findAllVisibilityValues()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 
deleteAccountVisibilityForPackage(String packageName)1010     boolean deleteAccountVisibilityForPackage(String packageName) {
1011         SQLiteDatabase db = mDeDatabase.getWritableDatabase();
1012         return db.delete(TABLE_VISIBILITY, VISIBILITY_PACKAGE + "=? ",
1013                 new String[] {packageName}) > 0;
1014     }
1015 
insertOrReplaceMetaAuthTypeAndUid(String authenticatorType, int uid)1016     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 
findMetaAuthUid()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 
deleteMetaByAuthTypeAndUid(String type, int uid)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 
1068     /**
1069      * Returns list of all grants as {@link Pair pairs} of account name and UID.
1070      */
findAllAccountGrants()1071     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 {
1088         private final Context mContext;
1089         private final int mUserId;
1090 
PreNDatabaseHelper(Context context, int userId, String preNDatabaseName)1091         PreNDatabaseHelper(Context context, int userId, String preNDatabaseName) {
1092             super(context, preNDatabaseName, null, PRE_N_DATABASE_VERSION);
1093             mContext = context;
1094             mUserId = userId;
1095         }
1096 
1097         @Override
onCreate(SQLiteDatabase db)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 
createSharedAccountsTable(SQLiteDatabase db)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 
addLastSuccessfullAuthenticatedTimeColumn(SQLiteDatabase db)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 
addOldAccountNameColumn(SQLiteDatabase db)1116         private void addOldAccountNameColumn(SQLiteDatabase db) {
1117             db.execSQL("ALTER TABLE " + TABLE_ACCOUNTS + " ADD COLUMN " + ACCOUNTS_PREVIOUS_NAME);
1118         }
1119 
addDebugTable(SQLiteDatabase db)1120         private void addDebugTable(SQLiteDatabase db) {
1121             DeDatabaseHelper.createDebugTable(db);
1122         }
1123 
createAccountsDeletionTrigger(SQLiteDatabase db)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 
createGrantsTable(SQLiteDatabase db)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 
insertMetaAuthTypeAndUid(SQLiteDatabase db, String authenticatorType, int uid)1146         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 
populateMetaTableWithAuthTypeAndUID(SQLiteDatabase db, Map<String, Integer> authTypeAndUIDMap)1154         private void populateMetaTableWithAuthTypeAndUID(SQLiteDatabase db,
1155                 Map<String, Integer> authTypeAndUIDMap) {
1156             for (Map.Entry<String, Integer> entry : authTypeAndUIDMap.entrySet()) {
1157                 insertMetaAuthTypeAndUid(db, entry.getKey(), entry.getValue());
1158             }
1159         }
1160 
1161         /**
1162          * Pre-N database may need an upgrade before splitting
1163          */
1164         @Override
onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion)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
onOpen(SQLiteDatabase db)1220         public void onOpen(SQLiteDatabase db) {
1221             if (Log.isLoggable(TAG, Log.VERBOSE)) Log.v(TAG, "opened database " + DATABASE_NAME);
1222         }
1223     }
1224 
findCeAccountsNotInDe()1225     List<Account> findCeAccountsNotInDe() {
1226         SQLiteDatabase db = mDeDatabase.getReadableDatabaseUserIsUnlocked();
1227         // 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 
deleteCeAccount(long accountId)1248     boolean deleteCeAccount(long accountId) {
1249         SQLiteDatabase db = mDeDatabase.getWritableDatabaseUserIsUnlocked();
1250         return db.delete(
1251                 CE_TABLE_ACCOUNTS, ACCOUNTS_ID + "=" + accountId, null) > 0;
1252     }
1253 
isCeDatabaseAttached()1254     boolean isCeDatabaseAttached() {
1255         return mDeDatabase.mCeAttached;
1256     }
1257 
beginTransaction()1258     void beginTransaction() {
1259         mDeDatabase.getWritableDatabase().beginTransaction();
1260     }
1261 
setTransactionSuccessful()1262     void setTransactionSuccessful() {
1263         mDeDatabase.getWritableDatabase().setTransactionSuccessful();
1264     }
1265 
endTransaction()1266     void endTransaction() {
1267         mDeDatabase.getWritableDatabase().endTransaction();
1268     }
1269 
attachCeDatabase(File ceDbFile)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      */
calculateDebugTableInsertionPoint()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 
compileSqlStatementForLogging()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 
dumpDebugTable(PrintWriter pw)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 
1324     @Override
close()1325     public void close() {
1326         mDeDatabase.close();
1327     }
1328 
deleteDbFileWarnIfFailed(File dbFile)1329     static void deleteDbFileWarnIfFailed(File dbFile) {
1330         if (!SQLiteDatabase.deleteDatabase(dbFile)) {
1331             Log.w(TAG, "Database at " + dbFile + " was not deleted successfully");
1332         }
1333     }
1334 
create(Context context, int userId, File preNDatabaseFile, File deDatabaseFile)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 
1355 }
1356