Home | History | Annotate | Download | only in accounts
      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 
    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