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
17package com.android.server.accounts;
18
19import android.accounts.Account;
20import android.content.ContentValues;
21import android.content.Context;
22import android.database.Cursor;
23import android.database.DatabaseUtils;
24import android.database.sqlite.SQLiteDatabase;
25import android.database.sqlite.SQLiteOpenHelper;
26import android.database.sqlite.SQLiteStatement;
27import android.os.FileUtils;
28import android.text.TextUtils;
29import android.util.Log;
30import android.util.Pair;
31import android.util.Slog;
32
33import java.io.File;
34import java.io.IOException;
35import java.io.PrintWriter;
36import java.util.ArrayList;
37import java.util.Collections;
38import java.util.HashMap;
39import java.util.LinkedHashMap;
40import java.util.List;
41import java.util.Map;
42
43/**
44 * Persistence layer abstraction for accessing accounts_ce/accounts_de databases.
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 */
51class 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
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
194        CeDatabaseHelper(Context context, String ceDatabaseName) {
195            super(context, ceDatabaseName, null, CE_DATABASE_VERSION);
196        }
197
198        /**
199         * This call needs to be made while the mCacheLock is held.
200         * @param db The database.
201         */
202        @Override
203        public void onCreate(SQLiteDatabase db) {
204            Log.i(TAG, "Creating CE database " + getDatabaseName());
205            db.execSQL("CREATE TABLE " + TABLE_ACCOUNTS + " ( "
206                    + ACCOUNTS_ID + " INTEGER PRIMARY KEY AUTOINCREMENT, "
207                    + ACCOUNTS_NAME + " TEXT NOT NULL, "
208                    + ACCOUNTS_TYPE + " TEXT NOT NULL, "
209                    + ACCOUNTS_PASSWORD + " TEXT, "
210                    + "UNIQUE(" + ACCOUNTS_NAME + "," + ACCOUNTS_TYPE + "))");
211
212            db.execSQL("CREATE TABLE " + TABLE_AUTHTOKENS + " (  "
213                    + AUTHTOKENS_ID + " INTEGER PRIMARY KEY AUTOINCREMENT,  "
214                    + AUTHTOKENS_ACCOUNTS_ID + " INTEGER NOT NULL, "
215                    + AUTHTOKENS_TYPE + " TEXT NOT NULL,  "
216                    + AUTHTOKENS_AUTHTOKEN + " TEXT,  "
217                    + "UNIQUE (" + AUTHTOKENS_ACCOUNTS_ID + "," + AUTHTOKENS_TYPE + "))");
218
219            db.execSQL("CREATE TABLE " + TABLE_EXTRAS + " ( "
220                    + EXTRAS_ID + " INTEGER PRIMARY KEY AUTOINCREMENT, "
221                    + EXTRAS_ACCOUNTS_ID + " INTEGER, "
222                    + EXTRAS_KEY + " TEXT NOT NULL, "
223                    + EXTRAS_VALUE + " TEXT, "
224                    + "UNIQUE(" + EXTRAS_ACCOUNTS_ID + "," + EXTRAS_KEY + "))");
225
226            createAccountsDeletionTrigger(db);
227        }
228
229        private void createAccountsDeletionTrigger(SQLiteDatabase db) {
230            db.execSQL(""
231                    + " CREATE TRIGGER " + TABLE_ACCOUNTS + "Delete DELETE ON " + TABLE_ACCOUNTS
232                    + " BEGIN"
233                    + "   DELETE FROM " + TABLE_AUTHTOKENS
234                    + "     WHERE " + AUTHTOKENS_ACCOUNTS_ID + "=OLD." + ACCOUNTS_ID + " ;"
235                    + "   DELETE FROM " + TABLE_EXTRAS
236                    + "     WHERE " + EXTRAS_ACCOUNTS_ID + "=OLD." + ACCOUNTS_ID + " ;"
237                    + " END");
238        }
239
240        @Override
241        public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
242            Log.i(TAG, "Upgrade CE from version " + oldVersion + " to version " + newVersion);
243
244            if (oldVersion == 9) {
245                if (Log.isLoggable(TAG, Log.VERBOSE)) {
246                    Log.v(TAG, "onUpgrade upgrading to v10");
247                }
248                db.execSQL("DROP TABLE IF EXISTS " + TABLE_META);
249                db.execSQL("DROP TABLE IF EXISTS " + TABLE_SHARED_ACCOUNTS);
250                // Recreate the trigger, since the old one references the table to be removed
251                db.execSQL("DROP TRIGGER IF EXISTS " + TABLE_ACCOUNTS + "Delete");
252                createAccountsDeletionTrigger(db);
253                db.execSQL("DROP TABLE IF EXISTS " + TABLE_GRANTS);
254                db.execSQL("DROP TABLE IF EXISTS " + TABLE_DEBUG);
255                oldVersion++;
256            }
257
258            if (oldVersion != newVersion) {
259                Log.e(TAG, "failed to upgrade version " + oldVersion + " to version " + newVersion);
260            }
261        }
262
263        @Override
264        public void onOpen(SQLiteDatabase db) {
265            if (Log.isLoggable(TAG, Log.VERBOSE)) Log.v(TAG, "opened database " + CE_DATABASE_NAME);
266        }
267
268
269        /**
270         * Creates a new {@code CeDatabaseHelper}. If pre-N db file is present at the old location,
271         * it also performs migration to the new CE database.
272         */
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
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     */
320    Cursor findAuthtokenForAllAccounts(String accountType, String authToken) {
321        SQLiteDatabase db = mDeDatabase.getReadableDatabaseUserIsUnlocked();
322        return db.rawQuery(
323                "SELECT " + CE_TABLE_AUTHTOKENS + "." + AUTHTOKENS_ID
324                        + ", " + CE_TABLE_ACCOUNTS + "." + ACCOUNTS_NAME
325                        + ", " + CE_TABLE_AUTHTOKENS + "." + AUTHTOKENS_TYPE
326                        + " FROM " + CE_TABLE_ACCOUNTS
327                        + " JOIN " + CE_TABLE_AUTHTOKENS
328                        + " ON " + CE_TABLE_ACCOUNTS + "." + ACCOUNTS_ID
329                        + " = " + CE_TABLE_AUTHTOKENS + "." + AUTHTOKENS_ACCOUNTS_ID
330                        + " WHERE " + CE_TABLE_AUTHTOKENS + "." + AUTHTOKENS_AUTHTOKEN
331                        + " = ? AND " + CE_TABLE_ACCOUNTS + "." + ACCOUNTS_TYPE + " = ?",
332                new String[]{authToken, accountType});
333    }
334
335    Map<String, String> findAuthTokensByAccount(Account account) {
336        SQLiteDatabase db = mDeDatabase.getReadableDatabaseUserIsUnlocked();
337        HashMap<String, String> authTokensForAccount = new HashMap<>();
338        Cursor cursor = db.query(CE_TABLE_AUTHTOKENS,
339                COLUMNS_AUTHTOKENS_TYPE_AND_AUTHTOKEN,
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
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
362    boolean deleteAuthToken(String authTokenId) {
363        SQLiteDatabase db = mDeDatabase.getWritableDatabaseUserIsUnlocked();
364        return db.delete(
365                CE_TABLE_AUTHTOKENS, AUTHTOKENS_ID + "= ?",
366                new String[]{authTokenId}) > 0;
367    }
368
369    long insertAuthToken(long accountId, String authTokenType, String authToken) {
370        SQLiteDatabase db = mDeDatabase.getWritableDatabaseUserIsUnlocked();
371        ContentValues values = new ContentValues();
372        values.put(AUTHTOKENS_ACCOUNTS_ID, accountId);
373        values.put(AUTHTOKENS_TYPE, authTokenType);
374        values.put(AUTHTOKENS_AUTHTOKEN, authToken);
375        return db.insert(
376                CE_TABLE_AUTHTOKENS, AUTHTOKENS_AUTHTOKEN, values);
377    }
378
379    int updateCeAccountPassword(long accountId, String password) {
380        SQLiteDatabase db = mDeDatabase.getWritableDatabaseUserIsUnlocked();
381        final ContentValues values = new ContentValues();
382        values.put(ACCOUNTS_PASSWORD, password);
383        return db.update(
384                CE_TABLE_ACCOUNTS, values, ACCOUNTS_ID + "=?",
385                new String[] {String.valueOf(accountId)});
386    }
387
388    boolean renameCeAccount(long accountId, String newName) {
389        SQLiteDatabase db = mDeDatabase.getWritableDatabaseUserIsUnlocked();
390        final ContentValues values = new ContentValues();
391        values.put(ACCOUNTS_NAME, newName);
392        final String[] argsAccountId = {String.valueOf(accountId)};
393        return db.update(
394                CE_TABLE_ACCOUNTS, values, ACCOUNTS_ID + "=?", argsAccountId) > 0;
395    }
396
397    boolean deleteAuthTokensByAccountId(long accountId) {
398        SQLiteDatabase db = mDeDatabase.getWritableDatabaseUserIsUnlocked();
399        return db.delete(CE_TABLE_AUTHTOKENS, AUTHTOKENS_ACCOUNTS_ID + "=?",
400                new String[] {String.valueOf(accountId)}) > 0;
401    }
402
403    long findExtrasIdByAccountId(long accountId, String key) {
404        SQLiteDatabase db = mDeDatabase.getReadableDatabaseUserIsUnlocked();
405        Cursor cursor = db.query(
406                CE_TABLE_EXTRAS, new String[]{EXTRAS_ID},
407                EXTRAS_ACCOUNTS_ID + "=" + accountId + " AND " + EXTRAS_KEY + "=?",
408                new String[]{key}, null, null, null);
409        try {
410            if (cursor.moveToNext()) {
411                return cursor.getLong(0);
412            }
413            return -1;
414        } finally {
415            cursor.close();
416        }
417    }
418
419    boolean updateExtra(long extrasId, String value) {
420        SQLiteDatabase db = mDeDatabase.getWritableDatabaseUserIsUnlocked();
421        ContentValues values = new ContentValues();
422        values.put(EXTRAS_VALUE, value);
423        int rows = db.update(
424                TABLE_EXTRAS, values, EXTRAS_ID + "=?",
425                new String[]{String.valueOf(extrasId)});
426        return rows == 1;
427    }
428
429    long insertExtra(long accountId, String key, String value) {
430        SQLiteDatabase db = mDeDatabase.getWritableDatabaseUserIsUnlocked();
431        ContentValues values = new ContentValues();
432        values.put(EXTRAS_KEY, key);
433        values.put(EXTRAS_ACCOUNTS_ID, accountId);
434        values.put(EXTRAS_VALUE, value);
435        return db.insert(CE_TABLE_EXTRAS, EXTRAS_KEY, values);
436    }
437
438    Map<String, String> findUserExtrasForAccount(Account account) {
439        SQLiteDatabase db = mDeDatabase.getReadableDatabaseUserIsUnlocked();
440        Map<String, String> userExtrasForAccount = new HashMap<>();
441        String[] selectionArgs = {account.name, account.type};
442        try (Cursor cursor = db.query(CE_TABLE_EXTRAS,
443                COLUMNS_EXTRAS_KEY_AND_VALUE,
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
456    long findCeAccountId(Account account) {
457        SQLiteDatabase db = mDeDatabase.getReadableDatabaseUserIsUnlocked();
458        String[] columns = { ACCOUNTS_ID };
459        String selection = "name=? AND type=?";
460        String[] selectionArgs = {account.name, account.type};
461        try (Cursor cursor = db.query(CE_TABLE_ACCOUNTS, columns, selection, selectionArgs,
462                null, null, null)) {
463            if (cursor.moveToNext()) {
464                return cursor.getLong(0);
465            }
466            return -1;
467        }
468    }
469
470    String findAccountPasswordByNameAndType(String name, String type) {
471        SQLiteDatabase db = mDeDatabase.getReadableDatabaseUserIsUnlocked();
472        String selection = ACCOUNTS_NAME + "=? AND " + ACCOUNTS_TYPE + "=?";
473        String[] selectionArgs = {name, type};
474        String[] columns = {ACCOUNTS_PASSWORD};
475        try (Cursor cursor = db.query(CE_TABLE_ACCOUNTS, columns, selection, selectionArgs,
476                null, null, null)) {
477            if (cursor.moveToNext()) {
478                return cursor.getString(0);
479            }
480            return null;
481        }
482    }
483
484    long insertCeAccount(Account account, String password) {
485        SQLiteDatabase db = mDeDatabase.getWritableDatabaseUserIsUnlocked();
486        ContentValues values = new ContentValues();
487        values.put(ACCOUNTS_NAME, account.name);
488        values.put(ACCOUNTS_TYPE, account.type);
489        values.put(ACCOUNTS_PASSWORD, password);
490        return db.insert(
491                CE_TABLE_ACCOUNTS, ACCOUNTS_NAME, values);
492    }
493
494
495    static class DeDatabaseHelper extends SQLiteOpenHelper {
496
497        private final int mUserId;
498        private volatile boolean mCeAttached;
499
500        private DeDatabaseHelper(Context context, int userId, String deDatabaseName) {
501            super(context, deDatabaseName, null, DE_DATABASE_VERSION);
502            mUserId = userId;
503        }
504
505        /**
506         * This call needs to be made while the mCacheLock is held. The way to
507         * ensure this is to get the lock any time a method is called ont the DatabaseHelper
508         * @param db The database.
509         */
510        @Override
511        public void onCreate(SQLiteDatabase db) {
512            Log.i(TAG, "Creating DE database for user " + mUserId);
513            db.execSQL("CREATE TABLE " + TABLE_ACCOUNTS + " ( "
514                    + ACCOUNTS_ID + " INTEGER PRIMARY KEY, "
515                    + ACCOUNTS_NAME + " TEXT NOT NULL, "
516                    + ACCOUNTS_TYPE + " TEXT NOT NULL, "
517                    + ACCOUNTS_PREVIOUS_NAME + " TEXT, "
518                    + ACCOUNTS_LAST_AUTHENTICATE_TIME_EPOCH_MILLIS + " INTEGER DEFAULT 0, "
519                    + "UNIQUE(" + ACCOUNTS_NAME + "," + ACCOUNTS_TYPE + "))");
520
521            db.execSQL("CREATE TABLE " + TABLE_META + " ( "
522                    + META_KEY + " TEXT PRIMARY KEY NOT NULL, "
523                    + META_VALUE + " TEXT)");
524
525            createGrantsTable(db);
526            createSharedAccountsTable(db);
527            createAccountsDeletionTrigger(db);
528            createDebugTable(db);
529            createAccountsVisibilityTable(db);
530            createAccountsDeletionVisibilityCleanupTrigger(db);
531        }
532
533        private void createSharedAccountsTable(SQLiteDatabase db) {
534            db.execSQL("CREATE TABLE " + TABLE_SHARED_ACCOUNTS + " ( "
535                    + ACCOUNTS_ID + " INTEGER PRIMARY KEY AUTOINCREMENT, "
536                    + ACCOUNTS_NAME + " TEXT NOT NULL, "
537                    + ACCOUNTS_TYPE + " TEXT NOT NULL, "
538                    + "UNIQUE(" + ACCOUNTS_NAME + "," + ACCOUNTS_TYPE + "))");
539        }
540
541        private void createAccountsDeletionTrigger(SQLiteDatabase db) {
542            db.execSQL(""
543                    + " CREATE TRIGGER " + TABLE_ACCOUNTS + "Delete DELETE ON " + TABLE_ACCOUNTS
544                    + " BEGIN"
545                    + "   DELETE FROM " + TABLE_GRANTS
546                    + "     WHERE " + GRANTS_ACCOUNTS_ID + "=OLD." + ACCOUNTS_ID + " ;"
547                    + " END");
548        }
549
550        private void createGrantsTable(SQLiteDatabase db) {
551            db.execSQL("CREATE TABLE " + TABLE_GRANTS + " (  "
552                    + GRANTS_ACCOUNTS_ID + " INTEGER NOT NULL, "
553                    + GRANTS_AUTH_TOKEN_TYPE + " STRING NOT NULL,  "
554                    + GRANTS_GRANTEE_UID + " INTEGER NOT NULL,  "
555                    + "UNIQUE (" + GRANTS_ACCOUNTS_ID + "," + GRANTS_AUTH_TOKEN_TYPE
556                    +   "," + GRANTS_GRANTEE_UID + "))");
557        }
558
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
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
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
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
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
621        public SQLiteDatabase getWritableDatabaseUserIsUnlocked() {
622            if(!mCeAttached) {
623                Log.wtf(TAG, "getWritableDatabaseUserIsUnlocked called while user " + mUserId
624                        + " is still locked. CE database is not yet available.", new Throwable());
625            }
626            return super.getWritableDatabase();
627        }
628
629        @Override
630        public void onOpen(SQLiteDatabase db) {
631            if (Log.isLoggable(TAG, Log.VERBOSE)) Log.v(TAG, "opened database " + DE_DATABASE_NAME);
632        }
633
634        private void migratePreNDbToDe(File preNDbFile) {
635            Log.i(TAG, "Migrate pre-N database to DE preNDbFile=" + preNDbFile);
636            SQLiteDatabase db = getWritableDatabase();
637            db.execSQL("ATTACH DATABASE '" +  preNDbFile.getPath() + "' AS preNDb");
638            db.beginTransaction();
639            // Copy accounts fields
640            db.execSQL("INSERT INTO " + TABLE_ACCOUNTS
641                    + "(" + ACCOUNTS_ID + "," + ACCOUNTS_NAME + "," + ACCOUNTS_TYPE + ", "
642                    + ACCOUNTS_PREVIOUS_NAME + ", " + ACCOUNTS_LAST_AUTHENTICATE_TIME_EPOCH_MILLIS
643                    + ") "
644                    + "SELECT " + ACCOUNTS_ID + "," + ACCOUNTS_NAME + "," + ACCOUNTS_TYPE + ", "
645                    + ACCOUNTS_PREVIOUS_NAME + ", " + ACCOUNTS_LAST_AUTHENTICATE_TIME_EPOCH_MILLIS
646                    + " FROM preNDb." + TABLE_ACCOUNTS);
647            // Copy SHARED_ACCOUNTS
648            db.execSQL("INSERT INTO " + TABLE_SHARED_ACCOUNTS
649                    + "(" + SHARED_ACCOUNTS_ID + "," + ACCOUNTS_NAME + "," + ACCOUNTS_TYPE + ") " +
650                    "SELECT " + SHARED_ACCOUNTS_ID + "," + ACCOUNTS_NAME + "," + ACCOUNTS_TYPE
651                    + " FROM preNDb." + TABLE_SHARED_ACCOUNTS);
652            // Copy DEBUG_TABLE
653            db.execSQL("INSERT INTO " + TABLE_DEBUG
654                    + "(" + ACCOUNTS_ID + "," + DEBUG_TABLE_ACTION_TYPE + ","
655                    + DEBUG_TABLE_TIMESTAMP + "," + DEBUG_TABLE_CALLER_UID + ","
656                    + DEBUG_TABLE_TABLE_NAME + "," + DEBUG_TABLE_KEY + ") " +
657                    "SELECT " + ACCOUNTS_ID + "," + DEBUG_TABLE_ACTION_TYPE + ","
658                    + DEBUG_TABLE_TIMESTAMP + "," + DEBUG_TABLE_CALLER_UID + ","
659                    + DEBUG_TABLE_TABLE_NAME + "," + DEBUG_TABLE_KEY
660                    + " FROM preNDb." + TABLE_DEBUG);
661            // Copy GRANTS
662            db.execSQL("INSERT INTO " + TABLE_GRANTS
663                    + "(" + GRANTS_ACCOUNTS_ID + "," + GRANTS_AUTH_TOKEN_TYPE + ","
664                    + GRANTS_GRANTEE_UID + ") " +
665                    "SELECT " + GRANTS_ACCOUNTS_ID + "," + GRANTS_AUTH_TOKEN_TYPE + ","
666                    + GRANTS_GRANTEE_UID + " FROM preNDb." + TABLE_GRANTS);
667            // Copy META
668            db.execSQL("INSERT INTO " + TABLE_META
669                    + "(" + META_KEY + "," + META_VALUE + ") "
670                    + "SELECT " + META_KEY + "," + META_VALUE + " FROM preNDb." + TABLE_META);
671            db.setTransactionSuccessful();
672            db.endTransaction();
673
674            db.execSQL("DETACH DATABASE preNDb");
675        }
676    }
677
678    boolean deleteDeAccount(long accountId) {
679        SQLiteDatabase db = mDeDatabase.getWritableDatabase();
680        return db.delete(TABLE_ACCOUNTS, ACCOUNTS_ID + "=" + accountId, null) > 0;
681    }
682
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
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
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
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
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
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
756    boolean updateAccountLastAuthenticatedTime(Account account) {
757        SQLiteDatabase db = mDeDatabase.getWritableDatabase();
758        final ContentValues values = new ContentValues();
759        values.put(ACCOUNTS_LAST_AUTHENTICATE_TIME_EPOCH_MILLIS, System.currentTimeMillis());
760        int rowCount = db.update(TABLE_ACCOUNTS,
761                values,
762                ACCOUNTS_NAME + "=? AND " + ACCOUNTS_TYPE + "=?",
763                new String[] { account.name, account.type });
764        return rowCount > 0;
765    }
766
767    void dumpDeAccountsTable(PrintWriter pw) {
768        SQLiteDatabase db = mDeDatabase.getReadableDatabase();
769        Cursor cursor = db.query(
770                TABLE_ACCOUNTS, ACCOUNT_TYPE_COUNT_PROJECTION,
771                null, null, ACCOUNTS_TYPE, null, null);
772        try {
773            while (cursor.moveToNext()) {
774                // print type,count
775                pw.println(cursor.getString(0) + "," + cursor.getString(1));
776            }
777        } finally {
778            if (cursor != null) {
779                cursor.close();
780            }
781        }
782    }
783
784    long findDeAccountId(Account account) {
785        SQLiteDatabase db = mDeDatabase.getReadableDatabase();
786        String[] columns = {ACCOUNTS_ID};
787        String selection = "name=? AND type=?";
788        String[] selectionArgs = {account.name, account.type};
789        try (Cursor cursor = db.query(TABLE_ACCOUNTS, columns, selection, selectionArgs,
790                null, null, null)) {
791            if (cursor.moveToNext()) {
792                return cursor.getLong(0);
793            }
794            return -1;
795        }
796    }
797
798    Map<Long, Account> findAllDeAccounts() {
799        SQLiteDatabase db = mDeDatabase.getReadableDatabase();
800        LinkedHashMap<Long, Account> map = new LinkedHashMap<>();
801        String[] columns = {ACCOUNTS_ID, ACCOUNTS_TYPE, ACCOUNTS_NAME};
802        try (Cursor cursor = db.query(TABLE_ACCOUNTS, columns,
803                null, null, null, null, ACCOUNTS_ID)) {
804            while (cursor.moveToNext()) {
805                final long accountId = cursor.getLong(0);
806                final String accountType = cursor.getString(1);
807                final String accountName = cursor.getString(2);
808
809                final Account account = new Account(accountName, accountType);
810                map.put(accountId, account);
811            }
812        }
813        return map;
814    }
815
816    String findDeAccountPreviousName(Account account) {
817        SQLiteDatabase db = mDeDatabase.getReadableDatabase();
818        String[] columns = {ACCOUNTS_PREVIOUS_NAME};
819        String selection = ACCOUNTS_NAME + "=? AND " + ACCOUNTS_TYPE + "=?";
820        String[] selectionArgs = {account.name, account.type};
821        try (Cursor cursor = db.query(TABLE_ACCOUNTS, columns, selection, selectionArgs,
822                null, null, null)) {
823            if (cursor.moveToNext()) {
824                return cursor.getString(0);
825            }
826        }
827        return null;
828    }
829
830    long insertDeAccount(Account account, long accountId) {
831        SQLiteDatabase db = mDeDatabase.getWritableDatabase();
832        ContentValues values = new ContentValues();
833        values.put(ACCOUNTS_ID, accountId);
834        values.put(ACCOUNTS_NAME, account.name);
835        values.put(ACCOUNTS_TYPE, account.type);
836        values.put(ACCOUNTS_LAST_AUTHENTICATE_TIME_EPOCH_MILLIS, System.currentTimeMillis());
837        return db.insert(TABLE_ACCOUNTS, ACCOUNTS_NAME, values);
838    }
839
840    boolean renameDeAccount(long accountId, String newName, String previousName) {
841        SQLiteDatabase db = mDeDatabase.getWritableDatabase();
842        final ContentValues values = new ContentValues();
843        values.put(ACCOUNTS_NAME, newName);
844        values.put(ACCOUNTS_PREVIOUS_NAME, previousName);
845        final String[] argsAccountId = {String.valueOf(accountId)};
846        return db.update(TABLE_ACCOUNTS, values, ACCOUNTS_ID + "=?", argsAccountId) > 0;
847    }
848
849    boolean deleteGrantsByAccountIdAuthTokenTypeAndUid(long accountId,
850            String authTokenType, long uid) {
851        SQLiteDatabase db = mDeDatabase.getWritableDatabase();
852        return db.delete(TABLE_GRANTS,
853                GRANTS_ACCOUNTS_ID + "=? AND " + GRANTS_AUTH_TOKEN_TYPE + "=? AND "
854                        + GRANTS_GRANTEE_UID + "=?",
855                new String[] {String.valueOf(accountId), authTokenType, String.valueOf(uid)}) > 0;
856    }
857
858    List<Integer> findAllUidGrants() {
859        SQLiteDatabase db = mDeDatabase.getReadableDatabase();
860        List<Integer> result = new ArrayList<>();
861        final Cursor cursor = db.query(TABLE_GRANTS,
862                new String[]{GRANTS_GRANTEE_UID},
863                null, null, GRANTS_GRANTEE_UID, null, null);
864        try {
865            while (cursor.moveToNext()) {
866                final int uid = cursor.getInt(0);
867                result.add(uid);
868            }
869        } finally {
870            cursor.close();
871        }
872        return result;
873    }
874
875    long findMatchingGrantsCount(int uid, String authTokenType, Account account) {
876        SQLiteDatabase db = mDeDatabase.getReadableDatabase();
877        String[] args = {String.valueOf(uid), authTokenType, account.name, account.type};
878        return DatabaseUtils.longForQuery(db, COUNT_OF_MATCHING_GRANTS, args);
879    }
880
881    long findMatchingGrantsCountAnyToken(int uid, Account account) {
882        SQLiteDatabase db = mDeDatabase.getReadableDatabase();
883        String[] args = {String.valueOf(uid), account.name, account.type};
884        return DatabaseUtils.longForQuery(db, COUNT_OF_MATCHING_GRANTS_ANY_TOKEN, args);
885    }
886
887    long insertGrant(long accountId, String authTokenType, int uid) {
888        SQLiteDatabase db = mDeDatabase.getWritableDatabase();
889        ContentValues values = new ContentValues();
890        values.put(GRANTS_ACCOUNTS_ID, accountId);
891        values.put(GRANTS_AUTH_TOKEN_TYPE, authTokenType);
892        values.put(GRANTS_GRANTEE_UID, uid);
893        return db.insert(TABLE_GRANTS, GRANTS_ACCOUNTS_ID, values);
894    }
895
896    boolean deleteGrantsByUid(int uid) {
897        SQLiteDatabase db = mDeDatabase.getWritableDatabase();
898        return db.delete(TABLE_GRANTS, GRANTS_GRANTEE_UID + "=?",
899                new String[] {Integer.toString(uid)}) > 0;
900    }
901
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
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
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
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     */
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     */
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
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
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
1026    Map<String, Integer> findMetaAuthUid() {
1027        SQLiteDatabase db = mDeDatabase.getReadableDatabase();
1028        Cursor metaCursor = db.query(
1029                TABLE_META,
1030                new String[]{META_KEY, META_VALUE},
1031                SELECTION_META_BY_AUTHENTICATOR_TYPE,
1032                new String[]{META_KEY_FOR_AUTHENTICATOR_UID_FOR_TYPE_PREFIX + "%"},
1033                null /* groupBy */,
1034                null /* having */,
1035                META_KEY);
1036        Map<String, Integer> map = new LinkedHashMap<>();
1037        try {
1038            while (metaCursor.moveToNext()) {
1039                String type = TextUtils
1040                        .split(metaCursor.getString(0), META_KEY_DELIMITER)[1];
1041                String uidStr = metaCursor.getString(1);
1042                if (TextUtils.isEmpty(type) || TextUtils.isEmpty(uidStr)) {
1043                    // Should never happen.
1044                    Slog.e(TAG, "Auth type empty: " + TextUtils.isEmpty(type)
1045                            + ", uid empty: " + TextUtils.isEmpty(uidStr));
1046                    continue;
1047                }
1048                int uid = Integer.parseInt(metaCursor.getString(1));
1049                map.put(type, uid);
1050            }
1051        } finally {
1052            metaCursor.close();
1053        }
1054        return map;
1055    }
1056
1057    boolean deleteMetaByAuthTypeAndUid(String type, int uid) {
1058        SQLiteDatabase db = mDeDatabase.getWritableDatabase();
1059        return db.delete(
1060                TABLE_META,
1061                META_KEY + "=? AND " + META_VALUE + "=?",
1062                new String[]{
1063                        META_KEY_FOR_AUTHENTICATOR_UID_FOR_TYPE_PREFIX + type,
1064                        String.valueOf(uid)}
1065        ) > 0;
1066    }
1067
1068    /**
1069     * Returns list of all grants as {@link Pair pairs} of account name and UID.
1070     */
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
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
1098        public void onCreate(SQLiteDatabase db) {
1099            // We use PreNDatabaseHelper only if pre-N db exists
1100            throw new IllegalStateException("Legacy database cannot be created - only upgraded!");
1101        }
1102
1103        private void createSharedAccountsTable(SQLiteDatabase db) {
1104            db.execSQL("CREATE TABLE " + TABLE_SHARED_ACCOUNTS + " ( "
1105                    + ACCOUNTS_ID + " INTEGER PRIMARY KEY AUTOINCREMENT, "
1106                    + ACCOUNTS_NAME + " TEXT NOT NULL, "
1107                    + ACCOUNTS_TYPE + " TEXT NOT NULL, "
1108                    + "UNIQUE(" + ACCOUNTS_NAME + "," + ACCOUNTS_TYPE + "))");
1109        }
1110
1111        private void addLastSuccessfullAuthenticatedTimeColumn(SQLiteDatabase db) {
1112            db.execSQL("ALTER TABLE " + TABLE_ACCOUNTS + " ADD COLUMN "
1113                    + ACCOUNTS_LAST_AUTHENTICATE_TIME_EPOCH_MILLIS + " DEFAULT 0");
1114        }
1115
1116        private void addOldAccountNameColumn(SQLiteDatabase db) {
1117            db.execSQL("ALTER TABLE " + TABLE_ACCOUNTS + " ADD COLUMN " + ACCOUNTS_PREVIOUS_NAME);
1118        }
1119
1120        private void addDebugTable(SQLiteDatabase db) {
1121            DeDatabaseHelper.createDebugTable(db);
1122        }
1123
1124        private void createAccountsDeletionTrigger(SQLiteDatabase db) {
1125            db.execSQL(""
1126                    + " CREATE TRIGGER " + TABLE_ACCOUNTS + "Delete DELETE ON " + TABLE_ACCOUNTS
1127                    + " BEGIN"
1128                    + "   DELETE FROM " + TABLE_AUTHTOKENS
1129                    + "     WHERE " + AUTHTOKENS_ACCOUNTS_ID + "=OLD." + ACCOUNTS_ID + " ;"
1130                    + "   DELETE FROM " + TABLE_EXTRAS
1131                    + "     WHERE " + EXTRAS_ACCOUNTS_ID + "=OLD." + ACCOUNTS_ID + " ;"
1132                    + "   DELETE FROM " + TABLE_GRANTS
1133                    + "     WHERE " + GRANTS_ACCOUNTS_ID + "=OLD." + ACCOUNTS_ID + " ;"
1134                    + " END");
1135        }
1136
1137        private void createGrantsTable(SQLiteDatabase db) {
1138            db.execSQL("CREATE TABLE " + TABLE_GRANTS + " (  "
1139                    + GRANTS_ACCOUNTS_ID + " INTEGER NOT NULL, "
1140                    + GRANTS_AUTH_TOKEN_TYPE + " STRING NOT NULL,  "
1141                    + GRANTS_GRANTEE_UID + " INTEGER NOT NULL,  "
1142                    + "UNIQUE (" + GRANTS_ACCOUNTS_ID + "," + GRANTS_AUTH_TOKEN_TYPE
1143                    +   "," + GRANTS_GRANTEE_UID + "))");
1144        }
1145
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
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
1165        public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
1166            Log.e(TAG, "upgrade from version " + oldVersion + " to version " + newVersion);
1167
1168            if (oldVersion == 1) {
1169                // no longer need to do anything since the work is done
1170                // when upgrading from version 2
1171                oldVersion++;
1172            }
1173
1174            if (oldVersion == 2) {
1175                createGrantsTable(db);
1176                db.execSQL("DROP TRIGGER " + TABLE_ACCOUNTS + "Delete");
1177                createAccountsDeletionTrigger(db);
1178                oldVersion++;
1179            }
1180
1181            if (oldVersion == 3) {
1182                db.execSQL("UPDATE " + TABLE_ACCOUNTS + " SET " + ACCOUNTS_TYPE +
1183                        " = 'com.google' WHERE " + ACCOUNTS_TYPE + " == 'com.google.GAIA'");
1184                oldVersion++;
1185            }
1186
1187            if (oldVersion == 4) {
1188                createSharedAccountsTable(db);
1189                oldVersion++;
1190            }
1191
1192            if (oldVersion == 5) {
1193                addOldAccountNameColumn(db);
1194                oldVersion++;
1195            }
1196
1197            if (oldVersion == 6) {
1198                addLastSuccessfullAuthenticatedTimeColumn(db);
1199                oldVersion++;
1200            }
1201
1202            if (oldVersion == 7) {
1203                addDebugTable(db);
1204                oldVersion++;
1205            }
1206
1207            if (oldVersion == 8) {
1208                populateMetaTableWithAuthTypeAndUID(
1209                        db,
1210                        AccountManagerService.getAuthenticatorTypeAndUIDForUser(mContext, mUserId));
1211                oldVersion++;
1212            }
1213
1214            if (oldVersion != newVersion) {
1215                Log.e(TAG, "failed to upgrade version " + oldVersion + " to version " + newVersion);
1216            }
1217        }
1218
1219        @Override
1220        public void onOpen(SQLiteDatabase db) {
1221            if (Log.isLoggable(TAG, Log.VERBOSE)) Log.v(TAG, "opened database " + DATABASE_NAME);
1222        }
1223    }
1224
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
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
1254    boolean isCeDatabaseAttached() {
1255        return mDeDatabase.mCeAttached;
1256    }
1257
1258    void beginTransaction() {
1259        mDeDatabase.getWritableDatabase().beginTransaction();
1260    }
1261
1262    void setTransactionSuccessful() {
1263        mDeDatabase.getWritableDatabase().setTransactionSuccessful();
1264    }
1265
1266    void endTransaction() {
1267        mDeDatabase.getWritableDatabase().endTransaction();
1268    }
1269
1270    void attachCeDatabase(File ceDbFile) {
1271        CeDatabaseHelper.create(mContext, mPreNDatabaseFile, ceDbFile);
1272        SQLiteDatabase db = mDeDatabase.getWritableDatabase();
1273        db.execSQL("ATTACH DATABASE '" +  ceDbFile.getPath()+ "' AS ceDb");
1274        mDeDatabase.mCeAttached = true;
1275    }
1276
1277    /*
1278     * Finds the row key where the next insertion should take place. Returns number of rows
1279     * if it is less {@link #MAX_DEBUG_DB_SIZE}, otherwise finds the lowest number available.
1280     */
1281    int calculateDebugTableInsertionPoint() {
1282        SQLiteDatabase db = mDeDatabase.getReadableDatabase();
1283        String queryCountDebugDbRows = "SELECT COUNT(*) FROM " + TABLE_DEBUG;
1284        int size = (int) DatabaseUtils.longForQuery(db, queryCountDebugDbRows, null);
1285        if (size < MAX_DEBUG_DB_SIZE) {
1286            return size;
1287        }
1288
1289        // This query finds the smallest timestamp value (and if 2 records have
1290        // same timestamp, the choose the lower id).
1291        queryCountDebugDbRows = "SELECT " + DEBUG_TABLE_KEY +
1292                " FROM " + TABLE_DEBUG +
1293                " ORDER BY "  + DEBUG_TABLE_TIMESTAMP + "," + DEBUG_TABLE_KEY +
1294                " LIMIT 1";
1295        return (int) DatabaseUtils.longForQuery(db, queryCountDebugDbRows, null);
1296    }
1297
1298    SQLiteStatement compileSqlStatementForLogging() {
1299        // TODO b/31708085 Fix debug logging - it eagerly opens database for write without a need
1300        SQLiteDatabase db = mDeDatabase.getWritableDatabase();
1301        String sql = "INSERT OR REPLACE INTO " + AccountsDb.TABLE_DEBUG
1302                + " VALUES (?,?,?,?,?,?)";
1303        return db.compileStatement(sql);
1304    }
1305
1306    void dumpDebugTable(PrintWriter pw) {
1307        SQLiteDatabase db = mDeDatabase.getReadableDatabase();
1308        Cursor cursor = db.query(TABLE_DEBUG, null,
1309                null, null, null, null, DEBUG_TABLE_TIMESTAMP);
1310        pw.println("AccountId, Action_Type, timestamp, UID, TableName, Key");
1311        pw.println("Accounts History");
1312        try {
1313            while (cursor.moveToNext()) {
1314                // print type,count
1315                pw.println(cursor.getString(0) + "," + cursor.getString(1) + "," +
1316                        cursor.getString(2) + "," + cursor.getString(3) + ","
1317                        + cursor.getString(4) + "," + cursor.getString(5));
1318            }
1319        } finally {
1320            cursor.close();
1321        }
1322    }
1323
1324    @Override
1325    public void close() {
1326        mDeDatabase.close();
1327    }
1328
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
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