1 /* 2 * Copyright (C) 2016 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License 15 */ 16 17 package com.android.server.accounts; 18 19 import android.accounts.Account; 20 import android.content.ContentValues; 21 import android.content.Context; 22 import android.database.Cursor; 23 import android.database.DatabaseUtils; 24 import android.database.sqlite.SQLiteDatabase; 25 import android.database.sqlite.SQLiteOpenHelper; 26 import android.database.sqlite.SQLiteStatement; 27 import android.os.FileUtils; 28 import android.text.TextUtils; 29 import android.util.Log; 30 import android.util.Pair; 31 import android.util.Slog; 32 33 import java.io.File; 34 import java.io.IOException; 35 import java.io.PrintWriter; 36 import java.util.ArrayList; 37 import java.util.Collections; 38 import java.util.HashMap; 39 import java.util.LinkedHashMap; 40 import java.util.List; 41 import java.util.Map; 42 43 /** 44 * Persistence layer abstraction for accessing accounts_ce/accounts_de databases. 45 * 46 * <p>At first, CE database needs to be {@link #attachCeDatabase(File) attached to DE}, 47 * in order for the tables to be available. All operations with CE database are done through the 48 * connection to the DE database, to which it is attached. This approach allows atomic 49 * transactions across two databases</p> 50 */ 51 class AccountsDb implements AutoCloseable { 52 private static final String TAG = "AccountsDb"; 53 54 private static final String DATABASE_NAME = "accounts.db"; 55 private static final int PRE_N_DATABASE_VERSION = 9; 56 private static final int CE_DATABASE_VERSION = 10; 57 private static final int DE_DATABASE_VERSION = 3; // Added visibility support in O 58 59 60 static final String TABLE_ACCOUNTS = "accounts"; 61 private static final String ACCOUNTS_ID = "_id"; 62 private static final String ACCOUNTS_NAME = "name"; 63 private static final String ACCOUNTS_TYPE = "type"; 64 private static final String ACCOUNTS_TYPE_COUNT = "count(type)"; 65 private static final String ACCOUNTS_PASSWORD = "password"; 66 private static final String ACCOUNTS_PREVIOUS_NAME = "previous_name"; 67 private static final String ACCOUNTS_LAST_AUTHENTICATE_TIME_EPOCH_MILLIS = 68 "last_password_entry_time_millis_epoch"; 69 70 private static final String TABLE_AUTHTOKENS = "authtokens"; 71 private static final String AUTHTOKENS_ID = "_id"; 72 private static final String AUTHTOKENS_ACCOUNTS_ID = "accounts_id"; 73 private static final String AUTHTOKENS_TYPE = "type"; 74 private static final String AUTHTOKENS_AUTHTOKEN = "authtoken"; 75 76 private static final String TABLE_VISIBILITY = "visibility"; 77 private static final String VISIBILITY_ACCOUNTS_ID = "accounts_id"; 78 private static final String VISIBILITY_PACKAGE = "_package"; 79 private static final String VISIBILITY_VALUE = "value"; 80 81 private static final String TABLE_GRANTS = "grants"; 82 private static final String GRANTS_ACCOUNTS_ID = "accounts_id"; 83 private static final String GRANTS_AUTH_TOKEN_TYPE = "auth_token_type"; 84 private static final String GRANTS_GRANTEE_UID = "uid"; 85 86 private static final String TABLE_EXTRAS = "extras"; 87 private static final String EXTRAS_ID = "_id"; 88 private static final String EXTRAS_ACCOUNTS_ID = "accounts_id"; 89 private static final String EXTRAS_KEY = "key"; 90 private static final String EXTRAS_VALUE = "value"; 91 92 private static final String TABLE_META = "meta"; 93 private static final String META_KEY = "key"; 94 private static final String META_VALUE = "value"; 95 96 static final String TABLE_SHARED_ACCOUNTS = "shared_accounts"; 97 private static final String SHARED_ACCOUNTS_ID = "_id"; 98 99 private static String TABLE_DEBUG = "debug_table"; 100 101 // Columns for debug_table table 102 private static String DEBUG_TABLE_ACTION_TYPE = "action_type"; 103 private static String DEBUG_TABLE_TIMESTAMP = "time"; 104 private static String DEBUG_TABLE_CALLER_UID = "caller_uid"; 105 private static String DEBUG_TABLE_TABLE_NAME = "table_name"; 106 private static String DEBUG_TABLE_KEY = "primary_key"; 107 108 // These actions correspond to the occurrence of real actions. Since 109 // these are called by the authenticators, the uid associated will be 110 // of the authenticator. 111 static String DEBUG_ACTION_SET_PASSWORD = "action_set_password"; 112 static String DEBUG_ACTION_CLEAR_PASSWORD = "action_clear_password"; 113 static String DEBUG_ACTION_ACCOUNT_ADD = "action_account_add"; 114 static String DEBUG_ACTION_ACCOUNT_REMOVE = "action_account_remove"; 115 static String DEBUG_ACTION_ACCOUNT_REMOVE_DE = "action_account_remove_de"; 116 static String DEBUG_ACTION_AUTHENTICATOR_REMOVE = "action_authenticator_remove"; 117 static String DEBUG_ACTION_ACCOUNT_RENAME = "action_account_rename"; 118 119 // These actions don't necessarily correspond to any action on 120 // accountDb taking place. As an example, there might be a request for 121 // addingAccount, which might not lead to addition of account on grounds 122 // of bad authentication. We will still be logging it to keep track of 123 // who called. 124 static String DEBUG_ACTION_CALLED_ACCOUNT_ADD = "action_called_account_add"; 125 static String DEBUG_ACTION_CALLED_ACCOUNT_REMOVE = "action_called_account_remove"; 126 static String DEBUG_ACTION_SYNC_DE_CE_ACCOUNTS = "action_sync_de_ce_accounts"; 127 128 //This action doesn't add account to accountdb. Account is only 129 // added in finishSession which may be in a different user profile. 130 static String DEBUG_ACTION_CALLED_START_ACCOUNT_ADD = "action_called_start_account_add"; 131 static String DEBUG_ACTION_CALLED_ACCOUNT_SESSION_FINISH = 132 "action_called_account_session_finish"; 133 134 static final String CE_DATABASE_NAME = "accounts_ce.db"; 135 static final String DE_DATABASE_NAME = "accounts_de.db"; 136 private static final String CE_DB_PREFIX = "ceDb."; 137 private static final String CE_TABLE_ACCOUNTS = CE_DB_PREFIX + TABLE_ACCOUNTS; 138 private static final String CE_TABLE_AUTHTOKENS = CE_DB_PREFIX + TABLE_AUTHTOKENS; 139 private static final String CE_TABLE_EXTRAS = CE_DB_PREFIX + TABLE_EXTRAS; 140 141 static final int MAX_DEBUG_DB_SIZE = 64; 142 143 private static final String[] ACCOUNT_TYPE_COUNT_PROJECTION = 144 new String[] { ACCOUNTS_TYPE, ACCOUNTS_TYPE_COUNT}; 145 146 private static final String COUNT_OF_MATCHING_GRANTS = "" 147 + "SELECT COUNT(*) FROM " + TABLE_GRANTS + ", " + TABLE_ACCOUNTS 148 + " WHERE " + GRANTS_ACCOUNTS_ID + "=" + ACCOUNTS_ID 149 + " AND " + GRANTS_GRANTEE_UID + "=?" 150 + " AND " + GRANTS_AUTH_TOKEN_TYPE + "=?" 151 + " AND " + ACCOUNTS_NAME + "=?" 152 + " AND " + ACCOUNTS_TYPE + "=?"; 153 154 private static final String COUNT_OF_MATCHING_GRANTS_ANY_TOKEN = "" 155 + "SELECT COUNT(*) FROM " + TABLE_GRANTS + ", " + TABLE_ACCOUNTS 156 + " WHERE " + GRANTS_ACCOUNTS_ID + "=" + ACCOUNTS_ID 157 + " AND " + GRANTS_GRANTEE_UID + "=?" 158 + " AND " + ACCOUNTS_NAME + "=?" 159 + " AND " + ACCOUNTS_TYPE + "=?"; 160 161 private static final String SELECTION_ACCOUNTS_ID_BY_ACCOUNT = 162 "accounts_id=(select _id FROM accounts WHERE name=? AND type=?)"; 163 164 private static final String[] COLUMNS_AUTHTOKENS_TYPE_AND_AUTHTOKEN = 165 {AUTHTOKENS_TYPE, AUTHTOKENS_AUTHTOKEN}; 166 167 private static final String[] COLUMNS_EXTRAS_KEY_AND_VALUE = {EXTRAS_KEY, EXTRAS_VALUE}; 168 169 private static final String ACCOUNT_ACCESS_GRANTS = "" 170 + "SELECT " + AccountsDb.ACCOUNTS_NAME + ", " 171 + AccountsDb.GRANTS_GRANTEE_UID 172 + " FROM " + AccountsDb.TABLE_ACCOUNTS 173 + ", " + AccountsDb.TABLE_GRANTS 174 + " WHERE " + AccountsDb.GRANTS_ACCOUNTS_ID 175 + "=" + AccountsDb.ACCOUNTS_ID; 176 177 private static final String META_KEY_FOR_AUTHENTICATOR_UID_FOR_TYPE_PREFIX = 178 "auth_uid_for_type:"; 179 private static final String META_KEY_DELIMITER = ":"; 180 private static final String SELECTION_META_BY_AUTHENTICATOR_TYPE = META_KEY + " LIKE ?"; 181 182 private final DeDatabaseHelper mDeDatabase; 183 private final Context mContext; 184 private final File mPreNDatabaseFile; 185 AccountsDb(DeDatabaseHelper deDatabase, Context context, File preNDatabaseFile)186 AccountsDb(DeDatabaseHelper deDatabase, Context context, File preNDatabaseFile) { 187 mDeDatabase = deDatabase; 188 mContext = context; 189 mPreNDatabaseFile = preNDatabaseFile; 190 } 191 192 private static class CeDatabaseHelper extends SQLiteOpenHelper { 193 CeDatabaseHelper(Context context, String ceDatabaseName)194 CeDatabaseHelper(Context context, String ceDatabaseName) { 195 super(context, ceDatabaseName, null, CE_DATABASE_VERSION); 196 } 197 198 /** 199 * This call needs to be made while the mCacheLock is held. 200 * @param db The database. 201 */ 202 @Override onCreate(SQLiteDatabase db)203 public void onCreate(SQLiteDatabase db) { 204 Log.i(TAG, "Creating CE database " + getDatabaseName()); 205 db.execSQL("CREATE TABLE " + TABLE_ACCOUNTS + " ( " 206 + ACCOUNTS_ID + " INTEGER PRIMARY KEY AUTOINCREMENT, " 207 + ACCOUNTS_NAME + " TEXT NOT NULL, " 208 + ACCOUNTS_TYPE + " TEXT NOT NULL, " 209 + ACCOUNTS_PASSWORD + " TEXT, " 210 + "UNIQUE(" + ACCOUNTS_NAME + "," + ACCOUNTS_TYPE + "))"); 211 212 db.execSQL("CREATE TABLE " + TABLE_AUTHTOKENS + " ( " 213 + AUTHTOKENS_ID + " INTEGER PRIMARY KEY AUTOINCREMENT, " 214 + AUTHTOKENS_ACCOUNTS_ID + " INTEGER NOT NULL, " 215 + AUTHTOKENS_TYPE + " TEXT NOT NULL, " 216 + AUTHTOKENS_AUTHTOKEN + " TEXT, " 217 + "UNIQUE (" + AUTHTOKENS_ACCOUNTS_ID + "," + AUTHTOKENS_TYPE + "))"); 218 219 db.execSQL("CREATE TABLE " + TABLE_EXTRAS + " ( " 220 + EXTRAS_ID + " INTEGER PRIMARY KEY AUTOINCREMENT, " 221 + EXTRAS_ACCOUNTS_ID + " INTEGER, " 222 + EXTRAS_KEY + " TEXT NOT NULL, " 223 + EXTRAS_VALUE + " TEXT, " 224 + "UNIQUE(" + EXTRAS_ACCOUNTS_ID + "," + EXTRAS_KEY + "))"); 225 226 createAccountsDeletionTrigger(db); 227 } 228 createAccountsDeletionTrigger(SQLiteDatabase db)229 private void createAccountsDeletionTrigger(SQLiteDatabase db) { 230 db.execSQL("" 231 + " CREATE TRIGGER " + TABLE_ACCOUNTS + "Delete DELETE ON " + TABLE_ACCOUNTS 232 + " BEGIN" 233 + " DELETE FROM " + TABLE_AUTHTOKENS 234 + " WHERE " + AUTHTOKENS_ACCOUNTS_ID + "=OLD." + ACCOUNTS_ID + " ;" 235 + " DELETE FROM " + TABLE_EXTRAS 236 + " WHERE " + EXTRAS_ACCOUNTS_ID + "=OLD." + ACCOUNTS_ID + " ;" 237 + " END"); 238 } 239 240 @Override onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion)241 public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { 242 Log.i(TAG, "Upgrade CE from version " + oldVersion + " to version " + newVersion); 243 244 if (oldVersion == 9) { 245 if (Log.isLoggable(TAG, Log.VERBOSE)) { 246 Log.v(TAG, "onUpgrade upgrading to v10"); 247 } 248 db.execSQL("DROP TABLE IF EXISTS " + TABLE_META); 249 db.execSQL("DROP TABLE IF EXISTS " + TABLE_SHARED_ACCOUNTS); 250 // Recreate the trigger, since the old one references the table to be removed 251 db.execSQL("DROP TRIGGER IF EXISTS " + TABLE_ACCOUNTS + "Delete"); 252 createAccountsDeletionTrigger(db); 253 db.execSQL("DROP TABLE IF EXISTS " + TABLE_GRANTS); 254 db.execSQL("DROP TABLE IF EXISTS " + TABLE_DEBUG); 255 oldVersion++; 256 } 257 258 if (oldVersion != newVersion) { 259 Log.e(TAG, "failed to upgrade version " + oldVersion + " to version " + newVersion); 260 } 261 } 262 263 @Override onOpen(SQLiteDatabase db)264 public void onOpen(SQLiteDatabase db) { 265 if (Log.isLoggable(TAG, Log.VERBOSE)) Log.v(TAG, "opened database " + CE_DATABASE_NAME); 266 } 267 268 269 /** 270 * Creates a new {@code CeDatabaseHelper}. If pre-N db file is present at the old location, 271 * it also performs migration to the new CE database. 272 */ create( Context context, File preNDatabaseFile, File ceDatabaseFile)273 static CeDatabaseHelper create( 274 Context context, 275 File preNDatabaseFile, 276 File ceDatabaseFile) { 277 boolean newDbExists = ceDatabaseFile.exists(); 278 if (Log.isLoggable(TAG, Log.VERBOSE)) { 279 Log.v(TAG, "CeDatabaseHelper.create ceDatabaseFile=" + ceDatabaseFile 280 + " oldDbExists=" + preNDatabaseFile.exists() 281 + " newDbExists=" + newDbExists); 282 } 283 boolean removeOldDb = false; 284 if (!newDbExists && preNDatabaseFile.exists()) { 285 removeOldDb = migratePreNDbToCe(preNDatabaseFile, ceDatabaseFile); 286 } 287 // Try to open and upgrade if necessary 288 CeDatabaseHelper ceHelper = new CeDatabaseHelper(context, ceDatabaseFile.getPath()); 289 ceHelper.getWritableDatabase(); 290 ceHelper.close(); 291 if (removeOldDb) { 292 Slog.i(TAG, "Migration complete - removing pre-N db " + preNDatabaseFile); 293 if (!SQLiteDatabase.deleteDatabase(preNDatabaseFile)) { 294 Slog.e(TAG, "Cannot remove pre-N db " + preNDatabaseFile); 295 } 296 } 297 return ceHelper; 298 } 299 migratePreNDbToCe(File oldDbFile, File ceDbFile)300 private static boolean migratePreNDbToCe(File oldDbFile, File ceDbFile) { 301 Slog.i(TAG, "Moving pre-N DB " + oldDbFile + " to CE " + ceDbFile); 302 try { 303 FileUtils.copyFileOrThrow(oldDbFile, ceDbFile); 304 } catch (IOException e) { 305 Slog.e(TAG, "Cannot copy file to " + ceDbFile + " from " + oldDbFile, e); 306 // Try to remove potentially damaged file if I/O error occurred 307 deleteDbFileWarnIfFailed(ceDbFile); 308 return false; 309 } 310 return true; 311 } 312 } 313 314 /** 315 * Returns information about auth tokens and their account for the specified query 316 * parameters. 317 * Output is in the format: 318 * <pre><code> | AUTHTOKEN_ID | ACCOUNT_NAME | AUTH_TOKEN_TYPE |</code></pre> 319 */ findAuthtokenForAllAccounts(String accountType, String authToken)320 Cursor findAuthtokenForAllAccounts(String accountType, String authToken) { 321 SQLiteDatabase db = mDeDatabase.getReadableDatabaseUserIsUnlocked(); 322 return db.rawQuery( 323 "SELECT " + CE_TABLE_AUTHTOKENS + "." + AUTHTOKENS_ID 324 + ", " + CE_TABLE_ACCOUNTS + "." + ACCOUNTS_NAME 325 + ", " + CE_TABLE_AUTHTOKENS + "." + AUTHTOKENS_TYPE 326 + " FROM " + CE_TABLE_ACCOUNTS 327 + " JOIN " + CE_TABLE_AUTHTOKENS 328 + " ON " + CE_TABLE_ACCOUNTS + "." + ACCOUNTS_ID 329 + " = " + CE_TABLE_AUTHTOKENS + "." + AUTHTOKENS_ACCOUNTS_ID 330 + " WHERE " + CE_TABLE_AUTHTOKENS + "." + AUTHTOKENS_AUTHTOKEN 331 + " = ? AND " + CE_TABLE_ACCOUNTS + "." + ACCOUNTS_TYPE + " = ?", 332 new String[]{authToken, accountType}); 333 } 334 findAuthTokensByAccount(Account account)335 Map<String, String> findAuthTokensByAccount(Account account) { 336 SQLiteDatabase db = mDeDatabase.getReadableDatabaseUserIsUnlocked(); 337 HashMap<String, String> authTokensForAccount = new HashMap<>(); 338 Cursor cursor = db.query(CE_TABLE_AUTHTOKENS, 339 COLUMNS_AUTHTOKENS_TYPE_AND_AUTHTOKEN, 340 SELECTION_ACCOUNTS_ID_BY_ACCOUNT, 341 new String[] {account.name, account.type}, 342 null, null, null); 343 try { 344 while (cursor.moveToNext()) { 345 final String type = cursor.getString(0); 346 final String authToken = cursor.getString(1); 347 authTokensForAccount.put(type, authToken); 348 } 349 } finally { 350 cursor.close(); 351 } 352 return authTokensForAccount; 353 } 354 deleteAuthtokensByAccountIdAndType(long accountId, String authtokenType)355 boolean deleteAuthtokensByAccountIdAndType(long accountId, String authtokenType) { 356 SQLiteDatabase db = mDeDatabase.getWritableDatabaseUserIsUnlocked(); 357 return db.delete(CE_TABLE_AUTHTOKENS, 358 AUTHTOKENS_ACCOUNTS_ID + "=?" + " AND " + AUTHTOKENS_TYPE + "=?", 359 new String[]{String.valueOf(accountId), authtokenType}) > 0; 360 } 361 deleteAuthToken(String authTokenId)362 boolean deleteAuthToken(String authTokenId) { 363 SQLiteDatabase db = mDeDatabase.getWritableDatabaseUserIsUnlocked(); 364 return db.delete( 365 CE_TABLE_AUTHTOKENS, AUTHTOKENS_ID + "= ?", 366 new String[]{authTokenId}) > 0; 367 } 368 insertAuthToken(long accountId, String authTokenType, String authToken)369 long insertAuthToken(long accountId, String authTokenType, String authToken) { 370 SQLiteDatabase db = mDeDatabase.getWritableDatabaseUserIsUnlocked(); 371 ContentValues values = new ContentValues(); 372 values.put(AUTHTOKENS_ACCOUNTS_ID, accountId); 373 values.put(AUTHTOKENS_TYPE, authTokenType); 374 values.put(AUTHTOKENS_AUTHTOKEN, authToken); 375 return db.insert( 376 CE_TABLE_AUTHTOKENS, AUTHTOKENS_AUTHTOKEN, values); 377 } 378 updateCeAccountPassword(long accountId, String password)379 int updateCeAccountPassword(long accountId, String password) { 380 SQLiteDatabase db = mDeDatabase.getWritableDatabaseUserIsUnlocked(); 381 final ContentValues values = new ContentValues(); 382 values.put(ACCOUNTS_PASSWORD, password); 383 return db.update( 384 CE_TABLE_ACCOUNTS, values, ACCOUNTS_ID + "=?", 385 new String[] {String.valueOf(accountId)}); 386 } 387 renameCeAccount(long accountId, String newName)388 boolean renameCeAccount(long accountId, String newName) { 389 SQLiteDatabase db = mDeDatabase.getWritableDatabaseUserIsUnlocked(); 390 final ContentValues values = new ContentValues(); 391 values.put(ACCOUNTS_NAME, newName); 392 final String[] argsAccountId = {String.valueOf(accountId)}; 393 return db.update( 394 CE_TABLE_ACCOUNTS, values, ACCOUNTS_ID + "=?", argsAccountId) > 0; 395 } 396 deleteAuthTokensByAccountId(long accountId)397 boolean deleteAuthTokensByAccountId(long accountId) { 398 SQLiteDatabase db = mDeDatabase.getWritableDatabaseUserIsUnlocked(); 399 return db.delete(CE_TABLE_AUTHTOKENS, AUTHTOKENS_ACCOUNTS_ID + "=?", 400 new String[] {String.valueOf(accountId)}) > 0; 401 } 402 findExtrasIdByAccountId(long accountId, String key)403 long findExtrasIdByAccountId(long accountId, String key) { 404 SQLiteDatabase db = mDeDatabase.getReadableDatabaseUserIsUnlocked(); 405 Cursor cursor = db.query( 406 CE_TABLE_EXTRAS, new String[]{EXTRAS_ID}, 407 EXTRAS_ACCOUNTS_ID + "=" + accountId + " AND " + EXTRAS_KEY + "=?", 408 new String[]{key}, null, null, null); 409 try { 410 if (cursor.moveToNext()) { 411 return cursor.getLong(0); 412 } 413 return -1; 414 } finally { 415 cursor.close(); 416 } 417 } 418 updateExtra(long extrasId, String value)419 boolean updateExtra(long extrasId, String value) { 420 SQLiteDatabase db = mDeDatabase.getWritableDatabaseUserIsUnlocked(); 421 ContentValues values = new ContentValues(); 422 values.put(EXTRAS_VALUE, value); 423 int rows = db.update( 424 TABLE_EXTRAS, values, EXTRAS_ID + "=?", 425 new String[]{String.valueOf(extrasId)}); 426 return rows == 1; 427 } 428 insertExtra(long accountId, String key, String value)429 long insertExtra(long accountId, String key, String value) { 430 SQLiteDatabase db = mDeDatabase.getWritableDatabaseUserIsUnlocked(); 431 ContentValues values = new ContentValues(); 432 values.put(EXTRAS_KEY, key); 433 values.put(EXTRAS_ACCOUNTS_ID, accountId); 434 values.put(EXTRAS_VALUE, value); 435 return db.insert(CE_TABLE_EXTRAS, EXTRAS_KEY, values); 436 } 437 findUserExtrasForAccount(Account account)438 Map<String, String> findUserExtrasForAccount(Account account) { 439 SQLiteDatabase db = mDeDatabase.getReadableDatabaseUserIsUnlocked(); 440 Map<String, String> userExtrasForAccount = new HashMap<>(); 441 String[] selectionArgs = {account.name, account.type}; 442 try (Cursor cursor = db.query(CE_TABLE_EXTRAS, 443 COLUMNS_EXTRAS_KEY_AND_VALUE, 444 SELECTION_ACCOUNTS_ID_BY_ACCOUNT, 445 selectionArgs, 446 null, null, null)) { 447 while (cursor.moveToNext()) { 448 final String tmpkey = cursor.getString(0); 449 final String value = cursor.getString(1); 450 userExtrasForAccount.put(tmpkey, value); 451 } 452 } 453 return userExtrasForAccount; 454 } 455 findCeAccountId(Account account)456 long findCeAccountId(Account account) { 457 SQLiteDatabase db = mDeDatabase.getReadableDatabaseUserIsUnlocked(); 458 String[] columns = { ACCOUNTS_ID }; 459 String selection = "name=? AND type=?"; 460 String[] selectionArgs = {account.name, account.type}; 461 try (Cursor cursor = db.query(CE_TABLE_ACCOUNTS, columns, selection, selectionArgs, 462 null, null, null)) { 463 if (cursor.moveToNext()) { 464 return cursor.getLong(0); 465 } 466 return -1; 467 } 468 } 469 findAccountPasswordByNameAndType(String name, String type)470 String findAccountPasswordByNameAndType(String name, String type) { 471 SQLiteDatabase db = mDeDatabase.getReadableDatabaseUserIsUnlocked(); 472 String selection = ACCOUNTS_NAME + "=? AND " + ACCOUNTS_TYPE + "=?"; 473 String[] selectionArgs = {name, type}; 474 String[] columns = {ACCOUNTS_PASSWORD}; 475 try (Cursor cursor = db.query(CE_TABLE_ACCOUNTS, columns, selection, selectionArgs, 476 null, null, null)) { 477 if (cursor.moveToNext()) { 478 return cursor.getString(0); 479 } 480 return null; 481 } 482 } 483 insertCeAccount(Account account, String password)484 long insertCeAccount(Account account, String password) { 485 SQLiteDatabase db = mDeDatabase.getWritableDatabaseUserIsUnlocked(); 486 ContentValues values = new ContentValues(); 487 values.put(ACCOUNTS_NAME, account.name); 488 values.put(ACCOUNTS_TYPE, account.type); 489 values.put(ACCOUNTS_PASSWORD, password); 490 return db.insert( 491 CE_TABLE_ACCOUNTS, ACCOUNTS_NAME, values); 492 } 493 494 495 static class DeDatabaseHelper extends SQLiteOpenHelper { 496 497 private final int mUserId; 498 private volatile boolean mCeAttached; 499 DeDatabaseHelper(Context context, int userId, String deDatabaseName)500 private DeDatabaseHelper(Context context, int userId, String deDatabaseName) { 501 super(context, deDatabaseName, null, DE_DATABASE_VERSION); 502 mUserId = userId; 503 } 504 505 /** 506 * This call needs to be made while the mCacheLock is held. The way to 507 * ensure this is to get the lock any time a method is called ont the DatabaseHelper 508 * @param db The database. 509 */ 510 @Override onCreate(SQLiteDatabase db)511 public void onCreate(SQLiteDatabase db) { 512 Log.i(TAG, "Creating DE database for user " + mUserId); 513 db.execSQL("CREATE TABLE " + TABLE_ACCOUNTS + " ( " 514 + ACCOUNTS_ID + " INTEGER PRIMARY KEY, " 515 + ACCOUNTS_NAME + " TEXT NOT NULL, " 516 + ACCOUNTS_TYPE + " TEXT NOT NULL, " 517 + ACCOUNTS_PREVIOUS_NAME + " TEXT, " 518 + ACCOUNTS_LAST_AUTHENTICATE_TIME_EPOCH_MILLIS + " INTEGER DEFAULT 0, " 519 + "UNIQUE(" + ACCOUNTS_NAME + "," + ACCOUNTS_TYPE + "))"); 520 521 db.execSQL("CREATE TABLE " + TABLE_META + " ( " 522 + META_KEY + " TEXT PRIMARY KEY NOT NULL, " 523 + META_VALUE + " TEXT)"); 524 525 createGrantsTable(db); 526 createSharedAccountsTable(db); 527 createAccountsDeletionTrigger(db); 528 createDebugTable(db); 529 createAccountsVisibilityTable(db); 530 createAccountsDeletionVisibilityCleanupTrigger(db); 531 } 532 createSharedAccountsTable(SQLiteDatabase db)533 private void createSharedAccountsTable(SQLiteDatabase db) { 534 db.execSQL("CREATE TABLE " + TABLE_SHARED_ACCOUNTS + " ( " 535 + ACCOUNTS_ID + " INTEGER PRIMARY KEY AUTOINCREMENT, " 536 + ACCOUNTS_NAME + " TEXT NOT NULL, " 537 + ACCOUNTS_TYPE + " TEXT NOT NULL, " 538 + "UNIQUE(" + ACCOUNTS_NAME + "," + ACCOUNTS_TYPE + "))"); 539 } 540 createAccountsDeletionTrigger(SQLiteDatabase db)541 private void createAccountsDeletionTrigger(SQLiteDatabase db) { 542 db.execSQL("" 543 + " CREATE TRIGGER " + TABLE_ACCOUNTS + "Delete DELETE ON " + TABLE_ACCOUNTS 544 + " BEGIN" 545 + " DELETE FROM " + TABLE_GRANTS 546 + " WHERE " + GRANTS_ACCOUNTS_ID + "=OLD." + ACCOUNTS_ID + " ;" 547 + " END"); 548 } 549 createGrantsTable(SQLiteDatabase db)550 private void createGrantsTable(SQLiteDatabase db) { 551 db.execSQL("CREATE TABLE " + TABLE_GRANTS + " ( " 552 + GRANTS_ACCOUNTS_ID + " INTEGER NOT NULL, " 553 + GRANTS_AUTH_TOKEN_TYPE + " STRING NOT NULL, " 554 + GRANTS_GRANTEE_UID + " INTEGER NOT NULL, " 555 + "UNIQUE (" + GRANTS_ACCOUNTS_ID + "," + GRANTS_AUTH_TOKEN_TYPE 556 + "," + GRANTS_GRANTEE_UID + "))"); 557 } 558 createAccountsVisibilityTable(SQLiteDatabase db)559 private void createAccountsVisibilityTable(SQLiteDatabase db) { 560 db.execSQL("CREATE TABLE " + TABLE_VISIBILITY + " ( " 561 + VISIBILITY_ACCOUNTS_ID + " INTEGER NOT NULL, " 562 + VISIBILITY_PACKAGE + " TEXT NOT NULL, " 563 + VISIBILITY_VALUE + " INTEGER, " 564 + "PRIMARY KEY(" + VISIBILITY_ACCOUNTS_ID + "," + VISIBILITY_PACKAGE + "))"); 565 } 566 createDebugTable(SQLiteDatabase db)567 static void createDebugTable(SQLiteDatabase db) { 568 db.execSQL("CREATE TABLE " + TABLE_DEBUG + " ( " 569 + ACCOUNTS_ID + " INTEGER," 570 + DEBUG_TABLE_ACTION_TYPE + " TEXT NOT NULL, " 571 + DEBUG_TABLE_TIMESTAMP + " DATETIME," 572 + DEBUG_TABLE_CALLER_UID + " INTEGER NOT NULL," 573 + DEBUG_TABLE_TABLE_NAME + " TEXT NOT NULL," 574 + DEBUG_TABLE_KEY + " INTEGER PRIMARY KEY)"); 575 db.execSQL("CREATE INDEX timestamp_index ON " + TABLE_DEBUG + " (" 576 + DEBUG_TABLE_TIMESTAMP + ")"); 577 } 578 createAccountsDeletionVisibilityCleanupTrigger(SQLiteDatabase db)579 private void createAccountsDeletionVisibilityCleanupTrigger(SQLiteDatabase db) { 580 db.execSQL("" 581 + " CREATE TRIGGER " 582 + TABLE_ACCOUNTS + "DeleteVisibility DELETE ON " + TABLE_ACCOUNTS 583 + " BEGIN" 584 + " DELETE FROM " + TABLE_VISIBILITY 585 + " WHERE " + VISIBILITY_ACCOUNTS_ID + "=OLD." + ACCOUNTS_ID + " ;" 586 + " END"); 587 } 588 589 @Override onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion)590 public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { 591 Log.i(TAG, "upgrade from version " + oldVersion + " to version " + newVersion); 592 593 if (oldVersion == 1) { 594 createAccountsVisibilityTable(db); 595 createAccountsDeletionVisibilityCleanupTrigger(db); 596 oldVersion = 3; // skip version 2 which had uid based table 597 } 598 599 if (oldVersion == 2) { 600 // Remove uid based table and replace it with packageName based 601 db.execSQL("DROP TRIGGER IF EXISTS " + TABLE_ACCOUNTS + "DeleteVisibility"); 602 db.execSQL("DROP TABLE IF EXISTS " + TABLE_VISIBILITY); 603 createAccountsVisibilityTable(db); 604 createAccountsDeletionVisibilityCleanupTrigger(db); 605 oldVersion++; 606 } 607 608 if (oldVersion != newVersion) { 609 Log.e(TAG, "failed to upgrade version " + oldVersion + " to version " + newVersion); 610 } 611 } 612 getReadableDatabaseUserIsUnlocked()613 public SQLiteDatabase getReadableDatabaseUserIsUnlocked() { 614 if(!mCeAttached) { 615 Log.wtf(TAG, "getReadableDatabaseUserIsUnlocked called while user " + mUserId 616 + " is still locked. CE database is not yet available.", new Throwable()); 617 } 618 return super.getReadableDatabase(); 619 } 620 getWritableDatabaseUserIsUnlocked()621 public SQLiteDatabase getWritableDatabaseUserIsUnlocked() { 622 if(!mCeAttached) { 623 Log.wtf(TAG, "getWritableDatabaseUserIsUnlocked called while user " + mUserId 624 + " is still locked. CE database is not yet available.", new Throwable()); 625 } 626 return super.getWritableDatabase(); 627 } 628 629 @Override onOpen(SQLiteDatabase db)630 public void onOpen(SQLiteDatabase db) { 631 if (Log.isLoggable(TAG, Log.VERBOSE)) Log.v(TAG, "opened database " + DE_DATABASE_NAME); 632 } 633 migratePreNDbToDe(File preNDbFile)634 private void migratePreNDbToDe(File preNDbFile) { 635 Log.i(TAG, "Migrate pre-N database to DE preNDbFile=" + preNDbFile); 636 SQLiteDatabase db = getWritableDatabase(); 637 db.execSQL("ATTACH DATABASE '" + preNDbFile.getPath() + "' AS preNDb"); 638 db.beginTransaction(); 639 // Copy accounts fields 640 db.execSQL("INSERT INTO " + TABLE_ACCOUNTS 641 + "(" + ACCOUNTS_ID + "," + ACCOUNTS_NAME + "," + ACCOUNTS_TYPE + ", " 642 + ACCOUNTS_PREVIOUS_NAME + ", " + ACCOUNTS_LAST_AUTHENTICATE_TIME_EPOCH_MILLIS 643 + ") " 644 + "SELECT " + ACCOUNTS_ID + "," + ACCOUNTS_NAME + "," + ACCOUNTS_TYPE + ", " 645 + ACCOUNTS_PREVIOUS_NAME + ", " + ACCOUNTS_LAST_AUTHENTICATE_TIME_EPOCH_MILLIS 646 + " FROM preNDb." + TABLE_ACCOUNTS); 647 // Copy SHARED_ACCOUNTS 648 db.execSQL("INSERT INTO " + TABLE_SHARED_ACCOUNTS 649 + "(" + SHARED_ACCOUNTS_ID + "," + ACCOUNTS_NAME + "," + ACCOUNTS_TYPE + ") " + 650 "SELECT " + SHARED_ACCOUNTS_ID + "," + ACCOUNTS_NAME + "," + ACCOUNTS_TYPE 651 + " FROM preNDb." + TABLE_SHARED_ACCOUNTS); 652 // Copy DEBUG_TABLE 653 db.execSQL("INSERT INTO " + TABLE_DEBUG 654 + "(" + ACCOUNTS_ID + "," + DEBUG_TABLE_ACTION_TYPE + "," 655 + DEBUG_TABLE_TIMESTAMP + "," + DEBUG_TABLE_CALLER_UID + "," 656 + DEBUG_TABLE_TABLE_NAME + "," + DEBUG_TABLE_KEY + ") " + 657 "SELECT " + ACCOUNTS_ID + "," + DEBUG_TABLE_ACTION_TYPE + "," 658 + DEBUG_TABLE_TIMESTAMP + "," + DEBUG_TABLE_CALLER_UID + "," 659 + DEBUG_TABLE_TABLE_NAME + "," + DEBUG_TABLE_KEY 660 + " FROM preNDb." + TABLE_DEBUG); 661 // Copy GRANTS 662 db.execSQL("INSERT INTO " + TABLE_GRANTS 663 + "(" + GRANTS_ACCOUNTS_ID + "," + GRANTS_AUTH_TOKEN_TYPE + "," 664 + GRANTS_GRANTEE_UID + ") " + 665 "SELECT " + GRANTS_ACCOUNTS_ID + "," + GRANTS_AUTH_TOKEN_TYPE + "," 666 + GRANTS_GRANTEE_UID + " FROM preNDb." + TABLE_GRANTS); 667 // Copy META 668 db.execSQL("INSERT INTO " + TABLE_META 669 + "(" + META_KEY + "," + META_VALUE + ") " 670 + "SELECT " + META_KEY + "," + META_VALUE + " FROM preNDb." + TABLE_META); 671 db.setTransactionSuccessful(); 672 db.endTransaction(); 673 674 db.execSQL("DETACH DATABASE preNDb"); 675 } 676 } 677 deleteDeAccount(long accountId)678 boolean deleteDeAccount(long accountId) { 679 SQLiteDatabase db = mDeDatabase.getWritableDatabase(); 680 return db.delete(TABLE_ACCOUNTS, ACCOUNTS_ID + "=" + accountId, null) > 0; 681 } 682 insertSharedAccount(Account account)683 long insertSharedAccount(Account account) { 684 SQLiteDatabase db = mDeDatabase.getWritableDatabase(); 685 ContentValues values = new ContentValues(); 686 values.put(ACCOUNTS_NAME, account.name); 687 values.put(ACCOUNTS_TYPE, account.type); 688 return db.insert( 689 TABLE_SHARED_ACCOUNTS, ACCOUNTS_NAME, values); 690 } 691 deleteSharedAccount(Account account)692 boolean deleteSharedAccount(Account account) { 693 SQLiteDatabase db = mDeDatabase.getWritableDatabase(); 694 return db.delete(TABLE_SHARED_ACCOUNTS, ACCOUNTS_NAME + "=? AND " + ACCOUNTS_TYPE + "=?", 695 new String[]{account.name, account.type}) > 0; 696 } 697 renameSharedAccount(Account account, String newName)698 int renameSharedAccount(Account account, String newName) { 699 SQLiteDatabase db = mDeDatabase.getWritableDatabase(); 700 final ContentValues values = new ContentValues(); 701 values.put(ACCOUNTS_NAME, newName); 702 return db.update(TABLE_SHARED_ACCOUNTS, 703 values, 704 ACCOUNTS_NAME + "=? AND " + ACCOUNTS_TYPE + "=?", 705 new String[] {account.name, account.type}); 706 } 707 getSharedAccounts()708 List<Account> getSharedAccounts() { 709 SQLiteDatabase db = mDeDatabase.getReadableDatabase(); 710 ArrayList<Account> accountList = new ArrayList<>(); 711 Cursor cursor = null; 712 try { 713 cursor = db.query(TABLE_SHARED_ACCOUNTS, new String[] {ACCOUNTS_NAME, ACCOUNTS_TYPE}, 714 null, null, null, null, null); 715 if (cursor != null && cursor.moveToFirst()) { 716 int nameIndex = cursor.getColumnIndex(ACCOUNTS_NAME); 717 int typeIndex = cursor.getColumnIndex(ACCOUNTS_TYPE); 718 do { 719 accountList.add(new Account(cursor.getString(nameIndex), 720 cursor.getString(typeIndex))); 721 } while (cursor.moveToNext()); 722 } 723 } finally { 724 if (cursor != null) { 725 cursor.close(); 726 } 727 } 728 return accountList; 729 } 730 findSharedAccountId(Account account)731 long findSharedAccountId(Account account) { 732 SQLiteDatabase db = mDeDatabase.getReadableDatabase(); 733 Cursor cursor = db.query(TABLE_SHARED_ACCOUNTS, new String[]{ 734 ACCOUNTS_ID}, 735 "name=? AND type=?", new String[]{account.name, account.type}, null, null, 736 null); 737 try { 738 if (cursor.moveToNext()) { 739 return cursor.getLong(0); 740 } 741 return -1; 742 } finally { 743 cursor.close(); 744 } 745 } 746 findAccountLastAuthenticatedTime(Account account)747 long findAccountLastAuthenticatedTime(Account account) { 748 SQLiteDatabase db = mDeDatabase.getReadableDatabase(); 749 return DatabaseUtils.longForQuery(db, 750 "SELECT " + AccountsDb.ACCOUNTS_LAST_AUTHENTICATE_TIME_EPOCH_MILLIS 751 + " FROM " + TABLE_ACCOUNTS + " WHERE " + ACCOUNTS_NAME + "=? AND " 752 + ACCOUNTS_TYPE + "=?", 753 new String[] {account.name, account.type}); 754 } 755 updateAccountLastAuthenticatedTime(Account account)756 boolean updateAccountLastAuthenticatedTime(Account account) { 757 SQLiteDatabase db = mDeDatabase.getWritableDatabase(); 758 final ContentValues values = new ContentValues(); 759 values.put(ACCOUNTS_LAST_AUTHENTICATE_TIME_EPOCH_MILLIS, System.currentTimeMillis()); 760 int rowCount = db.update(TABLE_ACCOUNTS, 761 values, 762 ACCOUNTS_NAME + "=? AND " + ACCOUNTS_TYPE + "=?", 763 new String[] { account.name, account.type }); 764 return rowCount > 0; 765 } 766 dumpDeAccountsTable(PrintWriter pw)767 void dumpDeAccountsTable(PrintWriter pw) { 768 SQLiteDatabase db = mDeDatabase.getReadableDatabase(); 769 Cursor cursor = db.query( 770 TABLE_ACCOUNTS, ACCOUNT_TYPE_COUNT_PROJECTION, 771 null, null, ACCOUNTS_TYPE, null, null); 772 try { 773 while (cursor.moveToNext()) { 774 // print type,count 775 pw.println(cursor.getString(0) + "," + cursor.getString(1)); 776 } 777 } finally { 778 if (cursor != null) { 779 cursor.close(); 780 } 781 } 782 } 783 findDeAccountId(Account account)784 long findDeAccountId(Account account) { 785 SQLiteDatabase db = mDeDatabase.getReadableDatabase(); 786 String[] columns = {ACCOUNTS_ID}; 787 String selection = "name=? AND type=?"; 788 String[] selectionArgs = {account.name, account.type}; 789 try (Cursor cursor = db.query(TABLE_ACCOUNTS, columns, selection, selectionArgs, 790 null, null, null)) { 791 if (cursor.moveToNext()) { 792 return cursor.getLong(0); 793 } 794 return -1; 795 } 796 } 797 findAllDeAccounts()798 Map<Long, Account> findAllDeAccounts() { 799 SQLiteDatabase db = mDeDatabase.getReadableDatabase(); 800 LinkedHashMap<Long, Account> map = new LinkedHashMap<>(); 801 String[] columns = {ACCOUNTS_ID, ACCOUNTS_TYPE, ACCOUNTS_NAME}; 802 try (Cursor cursor = db.query(TABLE_ACCOUNTS, columns, 803 null, null, null, null, ACCOUNTS_ID)) { 804 while (cursor.moveToNext()) { 805 final long accountId = cursor.getLong(0); 806 final String accountType = cursor.getString(1); 807 final String accountName = cursor.getString(2); 808 809 final Account account = new Account(accountName, accountType); 810 map.put(accountId, account); 811 } 812 } 813 return map; 814 } 815 findDeAccountPreviousName(Account account)816 String findDeAccountPreviousName(Account account) { 817 SQLiteDatabase db = mDeDatabase.getReadableDatabase(); 818 String[] columns = {ACCOUNTS_PREVIOUS_NAME}; 819 String selection = ACCOUNTS_NAME + "=? AND " + ACCOUNTS_TYPE + "=?"; 820 String[] selectionArgs = {account.name, account.type}; 821 try (Cursor cursor = db.query(TABLE_ACCOUNTS, columns, selection, selectionArgs, 822 null, null, null)) { 823 if (cursor.moveToNext()) { 824 return cursor.getString(0); 825 } 826 } 827 return null; 828 } 829 insertDeAccount(Account account, long accountId)830 long insertDeAccount(Account account, long accountId) { 831 SQLiteDatabase db = mDeDatabase.getWritableDatabase(); 832 ContentValues values = new ContentValues(); 833 values.put(ACCOUNTS_ID, accountId); 834 values.put(ACCOUNTS_NAME, account.name); 835 values.put(ACCOUNTS_TYPE, account.type); 836 values.put(ACCOUNTS_LAST_AUTHENTICATE_TIME_EPOCH_MILLIS, System.currentTimeMillis()); 837 return db.insert(TABLE_ACCOUNTS, ACCOUNTS_NAME, values); 838 } 839 renameDeAccount(long accountId, String newName, String previousName)840 boolean renameDeAccount(long accountId, String newName, String previousName) { 841 SQLiteDatabase db = mDeDatabase.getWritableDatabase(); 842 final ContentValues values = new ContentValues(); 843 values.put(ACCOUNTS_NAME, newName); 844 values.put(ACCOUNTS_PREVIOUS_NAME, previousName); 845 final String[] argsAccountId = {String.valueOf(accountId)}; 846 return db.update(TABLE_ACCOUNTS, values, ACCOUNTS_ID + "=?", argsAccountId) > 0; 847 } 848 deleteGrantsByAccountIdAuthTokenTypeAndUid(long accountId, String authTokenType, long uid)849 boolean deleteGrantsByAccountIdAuthTokenTypeAndUid(long accountId, 850 String authTokenType, long uid) { 851 SQLiteDatabase db = mDeDatabase.getWritableDatabase(); 852 return db.delete(TABLE_GRANTS, 853 GRANTS_ACCOUNTS_ID + "=? AND " + GRANTS_AUTH_TOKEN_TYPE + "=? AND " 854 + GRANTS_GRANTEE_UID + "=?", 855 new String[] {String.valueOf(accountId), authTokenType, String.valueOf(uid)}) > 0; 856 } 857 findAllUidGrants()858 List<Integer> findAllUidGrants() { 859 SQLiteDatabase db = mDeDatabase.getReadableDatabase(); 860 List<Integer> result = new ArrayList<>(); 861 final Cursor cursor = db.query(TABLE_GRANTS, 862 new String[]{GRANTS_GRANTEE_UID}, 863 null, null, GRANTS_GRANTEE_UID, null, null); 864 try { 865 while (cursor.moveToNext()) { 866 final int uid = cursor.getInt(0); 867 result.add(uid); 868 } 869 } finally { 870 cursor.close(); 871 } 872 return result; 873 } 874 findMatchingGrantsCount(int uid, String authTokenType, Account account)875 long findMatchingGrantsCount(int uid, String authTokenType, Account account) { 876 SQLiteDatabase db = mDeDatabase.getReadableDatabase(); 877 String[] args = {String.valueOf(uid), authTokenType, account.name, account.type}; 878 return DatabaseUtils.longForQuery(db, COUNT_OF_MATCHING_GRANTS, args); 879 } 880 findMatchingGrantsCountAnyToken(int uid, Account account)881 long findMatchingGrantsCountAnyToken(int uid, Account account) { 882 SQLiteDatabase db = mDeDatabase.getReadableDatabase(); 883 String[] args = {String.valueOf(uid), account.name, account.type}; 884 return DatabaseUtils.longForQuery(db, COUNT_OF_MATCHING_GRANTS_ANY_TOKEN, args); 885 } 886 insertGrant(long accountId, String authTokenType, int uid)887 long insertGrant(long accountId, String authTokenType, int uid) { 888 SQLiteDatabase db = mDeDatabase.getWritableDatabase(); 889 ContentValues values = new ContentValues(); 890 values.put(GRANTS_ACCOUNTS_ID, accountId); 891 values.put(GRANTS_AUTH_TOKEN_TYPE, authTokenType); 892 values.put(GRANTS_GRANTEE_UID, uid); 893 return db.insert(TABLE_GRANTS, GRANTS_ACCOUNTS_ID, values); 894 } 895 deleteGrantsByUid(int uid)896 boolean deleteGrantsByUid(int uid) { 897 SQLiteDatabase db = mDeDatabase.getWritableDatabase(); 898 return db.delete(TABLE_GRANTS, GRANTS_GRANTEE_UID + "=?", 899 new String[] {Integer.toString(uid)}) > 0; 900 } 901 setAccountVisibility(long accountId, String packageName, int visibility)902 boolean setAccountVisibility(long accountId, String packageName, int visibility) { 903 SQLiteDatabase db = mDeDatabase.getWritableDatabase(); 904 ContentValues values = new ContentValues(); 905 values.put(VISIBILITY_ACCOUNTS_ID, String.valueOf(accountId)); 906 values.put(VISIBILITY_PACKAGE, packageName); 907 values.put(VISIBILITY_VALUE, String.valueOf(visibility)); 908 return (db.replace(TABLE_VISIBILITY, VISIBILITY_VALUE, values) != -1); 909 } 910 findAccountVisibility(Account account, String packageName)911 Integer findAccountVisibility(Account account, String packageName) { 912 SQLiteDatabase db = mDeDatabase.getReadableDatabase(); 913 final Cursor cursor = db.query(TABLE_VISIBILITY, new String[] {VISIBILITY_VALUE}, 914 SELECTION_ACCOUNTS_ID_BY_ACCOUNT + " AND " + VISIBILITY_PACKAGE + "=? ", 915 new String[] {account.name, account.type, packageName}, null, null, null); 916 try { 917 while (cursor.moveToNext()) { 918 return cursor.getInt(0); 919 } 920 } finally { 921 cursor.close(); 922 } 923 return null; 924 } 925 findAccountVisibility(long accountId, String packageName)926 Integer findAccountVisibility(long accountId, String packageName) { 927 SQLiteDatabase db = mDeDatabase.getReadableDatabase(); 928 final Cursor cursor = db.query(TABLE_VISIBILITY, new String[] {VISIBILITY_VALUE}, 929 VISIBILITY_ACCOUNTS_ID + "=? AND " + VISIBILITY_PACKAGE + "=? ", 930 new String[] {String.valueOf(accountId), packageName}, null, null, null); 931 try { 932 while (cursor.moveToNext()) { 933 return cursor.getInt(0); 934 } 935 } finally { 936 cursor.close(); 937 } 938 return null; 939 } 940 findDeAccountByAccountId(long accountId)941 Account findDeAccountByAccountId(long accountId) { 942 SQLiteDatabase db = mDeDatabase.getReadableDatabase(); 943 final Cursor cursor = db.query(TABLE_ACCOUNTS, new String[] {ACCOUNTS_NAME, ACCOUNTS_TYPE}, 944 ACCOUNTS_ID + "=? ", new String[] {String.valueOf(accountId)}, null, null, null); 945 try { 946 while (cursor.moveToNext()) { 947 return new Account(cursor.getString(0), cursor.getString(1)); 948 } 949 } finally { 950 cursor.close(); 951 } 952 return null; 953 } 954 955 /** 956 * Returns a map from packageNames to visibility. 957 */ findAllVisibilityValuesForAccount(Account account)958 Map<String, Integer> findAllVisibilityValuesForAccount(Account account) { 959 SQLiteDatabase db = mDeDatabase.getReadableDatabase(); 960 Map<String, Integer> result = new HashMap<>(); 961 final Cursor cursor = 962 db.query(TABLE_VISIBILITY, new String[] {VISIBILITY_PACKAGE, VISIBILITY_VALUE}, 963 SELECTION_ACCOUNTS_ID_BY_ACCOUNT, new String[] {account.name, account.type}, 964 null, null, null); 965 try { 966 while (cursor.moveToNext()) { 967 result.put(cursor.getString(0), cursor.getInt(1)); 968 } 969 } finally { 970 cursor.close(); 971 } 972 return result; 973 } 974 975 /** 976 * Returns a map account -> (package -> visibility) 977 */ findAllVisibilityValues()978 Map <Account, Map<String, Integer>> findAllVisibilityValues() { 979 SQLiteDatabase db = mDeDatabase.getReadableDatabase(); 980 Map<Account, Map<String, Integer>> result = new HashMap<>(); 981 Cursor cursor = db.rawQuery( 982 "SELECT " + TABLE_VISIBILITY + "." + VISIBILITY_PACKAGE 983 + ", " + TABLE_VISIBILITY + "." + VISIBILITY_VALUE 984 + ", " + TABLE_ACCOUNTS + "." + ACCOUNTS_NAME 985 + ", " + TABLE_ACCOUNTS + "." + ACCOUNTS_TYPE 986 + " FROM " + TABLE_VISIBILITY 987 + " JOIN " + TABLE_ACCOUNTS 988 + " ON " + TABLE_ACCOUNTS + "." + ACCOUNTS_ID 989 + " = " + TABLE_VISIBILITY + "." + VISIBILITY_ACCOUNTS_ID, null); 990 try { 991 while (cursor.moveToNext()) { 992 String packageName = cursor.getString(0); 993 Integer visibility = cursor.getInt(1); 994 String accountName = cursor.getString(2); 995 String accountType = cursor.getString(3); 996 Account account = new Account(accountName, accountType); 997 Map <String, Integer> accountVisibility = result.get(account); 998 if (accountVisibility == null) { 999 accountVisibility = new HashMap<>(); 1000 result.put(account, accountVisibility); 1001 } 1002 accountVisibility.put(packageName, visibility); 1003 } 1004 } finally { 1005 cursor.close(); 1006 } 1007 return result; 1008 } 1009 deleteAccountVisibilityForPackage(String packageName)1010 boolean deleteAccountVisibilityForPackage(String packageName) { 1011 SQLiteDatabase db = mDeDatabase.getWritableDatabase(); 1012 return db.delete(TABLE_VISIBILITY, VISIBILITY_PACKAGE + "=? ", 1013 new String[] {packageName}) > 0; 1014 } 1015 insertOrReplaceMetaAuthTypeAndUid(String authenticatorType, int uid)1016 long insertOrReplaceMetaAuthTypeAndUid(String authenticatorType, int uid) { 1017 SQLiteDatabase db = mDeDatabase.getWritableDatabase(); 1018 ContentValues values = new ContentValues(); 1019 values.put(META_KEY, 1020 META_KEY_FOR_AUTHENTICATOR_UID_FOR_TYPE_PREFIX + authenticatorType); 1021 values.put(META_VALUE, uid); 1022 return db.insertWithOnConflict(TABLE_META, null, values, 1023 SQLiteDatabase.CONFLICT_REPLACE); 1024 } 1025 findMetaAuthUid()1026 Map<String, Integer> findMetaAuthUid() { 1027 SQLiteDatabase db = mDeDatabase.getReadableDatabase(); 1028 Cursor metaCursor = db.query( 1029 TABLE_META, 1030 new String[]{META_KEY, META_VALUE}, 1031 SELECTION_META_BY_AUTHENTICATOR_TYPE, 1032 new String[]{META_KEY_FOR_AUTHENTICATOR_UID_FOR_TYPE_PREFIX + "%"}, 1033 null /* groupBy */, 1034 null /* having */, 1035 META_KEY); 1036 Map<String, Integer> map = new LinkedHashMap<>(); 1037 try { 1038 while (metaCursor.moveToNext()) { 1039 String type = TextUtils 1040 .split(metaCursor.getString(0), META_KEY_DELIMITER)[1]; 1041 String uidStr = metaCursor.getString(1); 1042 if (TextUtils.isEmpty(type) || TextUtils.isEmpty(uidStr)) { 1043 // Should never happen. 1044 Slog.e(TAG, "Auth type empty: " + TextUtils.isEmpty(type) 1045 + ", uid empty: " + TextUtils.isEmpty(uidStr)); 1046 continue; 1047 } 1048 int uid = Integer.parseInt(metaCursor.getString(1)); 1049 map.put(type, uid); 1050 } 1051 } finally { 1052 metaCursor.close(); 1053 } 1054 return map; 1055 } 1056 deleteMetaByAuthTypeAndUid(String type, int uid)1057 boolean deleteMetaByAuthTypeAndUid(String type, int uid) { 1058 SQLiteDatabase db = mDeDatabase.getWritableDatabase(); 1059 return db.delete( 1060 TABLE_META, 1061 META_KEY + "=? AND " + META_VALUE + "=?", 1062 new String[]{ 1063 META_KEY_FOR_AUTHENTICATOR_UID_FOR_TYPE_PREFIX + type, 1064 String.valueOf(uid)} 1065 ) > 0; 1066 } 1067 1068 /** 1069 * Returns list of all grants as {@link Pair pairs} of account name and UID. 1070 */ findAllAccountGrants()1071 List<Pair<String, Integer>> findAllAccountGrants() { 1072 SQLiteDatabase db = mDeDatabase.getReadableDatabase(); 1073 try (Cursor cursor = db.rawQuery(ACCOUNT_ACCESS_GRANTS, null)) { 1074 if (cursor == null || !cursor.moveToFirst()) { 1075 return Collections.emptyList(); 1076 } 1077 List<Pair<String, Integer>> results = new ArrayList<>(); 1078 do { 1079 final String accountName = cursor.getString(0); 1080 final int uid = cursor.getInt(1); 1081 results.add(Pair.create(accountName, uid)); 1082 } while (cursor.moveToNext()); 1083 return results; 1084 } 1085 } 1086 1087 private static class PreNDatabaseHelper extends SQLiteOpenHelper { 1088 private final Context mContext; 1089 private final int mUserId; 1090 PreNDatabaseHelper(Context context, int userId, String preNDatabaseName)1091 PreNDatabaseHelper(Context context, int userId, String preNDatabaseName) { 1092 super(context, preNDatabaseName, null, PRE_N_DATABASE_VERSION); 1093 mContext = context; 1094 mUserId = userId; 1095 } 1096 1097 @Override onCreate(SQLiteDatabase db)1098 public void onCreate(SQLiteDatabase db) { 1099 // We use PreNDatabaseHelper only if pre-N db exists 1100 throw new IllegalStateException("Legacy database cannot be created - only upgraded!"); 1101 } 1102 createSharedAccountsTable(SQLiteDatabase db)1103 private void createSharedAccountsTable(SQLiteDatabase db) { 1104 db.execSQL("CREATE TABLE " + TABLE_SHARED_ACCOUNTS + " ( " 1105 + ACCOUNTS_ID + " INTEGER PRIMARY KEY AUTOINCREMENT, " 1106 + ACCOUNTS_NAME + " TEXT NOT NULL, " 1107 + ACCOUNTS_TYPE + " TEXT NOT NULL, " 1108 + "UNIQUE(" + ACCOUNTS_NAME + "," + ACCOUNTS_TYPE + "))"); 1109 } 1110 addLastSuccessfullAuthenticatedTimeColumn(SQLiteDatabase db)1111 private void addLastSuccessfullAuthenticatedTimeColumn(SQLiteDatabase db) { 1112 db.execSQL("ALTER TABLE " + TABLE_ACCOUNTS + " ADD COLUMN " 1113 + ACCOUNTS_LAST_AUTHENTICATE_TIME_EPOCH_MILLIS + " DEFAULT 0"); 1114 } 1115 addOldAccountNameColumn(SQLiteDatabase db)1116 private void addOldAccountNameColumn(SQLiteDatabase db) { 1117 db.execSQL("ALTER TABLE " + TABLE_ACCOUNTS + " ADD COLUMN " + ACCOUNTS_PREVIOUS_NAME); 1118 } 1119 addDebugTable(SQLiteDatabase db)1120 private void addDebugTable(SQLiteDatabase db) { 1121 DeDatabaseHelper.createDebugTable(db); 1122 } 1123 createAccountsDeletionTrigger(SQLiteDatabase db)1124 private void createAccountsDeletionTrigger(SQLiteDatabase db) { 1125 db.execSQL("" 1126 + " CREATE TRIGGER " + TABLE_ACCOUNTS + "Delete DELETE ON " + TABLE_ACCOUNTS 1127 + " BEGIN" 1128 + " DELETE FROM " + TABLE_AUTHTOKENS 1129 + " WHERE " + AUTHTOKENS_ACCOUNTS_ID + "=OLD." + ACCOUNTS_ID + " ;" 1130 + " DELETE FROM " + TABLE_EXTRAS 1131 + " WHERE " + EXTRAS_ACCOUNTS_ID + "=OLD." + ACCOUNTS_ID + " ;" 1132 + " DELETE FROM " + TABLE_GRANTS 1133 + " WHERE " + GRANTS_ACCOUNTS_ID + "=OLD." + ACCOUNTS_ID + " ;" 1134 + " END"); 1135 } 1136 createGrantsTable(SQLiteDatabase db)1137 private void createGrantsTable(SQLiteDatabase db) { 1138 db.execSQL("CREATE TABLE " + TABLE_GRANTS + " ( " 1139 + GRANTS_ACCOUNTS_ID + " INTEGER NOT NULL, " 1140 + GRANTS_AUTH_TOKEN_TYPE + " STRING NOT NULL, " 1141 + GRANTS_GRANTEE_UID + " INTEGER NOT NULL, " 1142 + "UNIQUE (" + GRANTS_ACCOUNTS_ID + "," + GRANTS_AUTH_TOKEN_TYPE 1143 + "," + GRANTS_GRANTEE_UID + "))"); 1144 } 1145 insertMetaAuthTypeAndUid(SQLiteDatabase db, String authenticatorType, int uid)1146 static long insertMetaAuthTypeAndUid(SQLiteDatabase db, String authenticatorType, int uid) { 1147 ContentValues values = new ContentValues(); 1148 values.put(META_KEY, 1149 META_KEY_FOR_AUTHENTICATOR_UID_FOR_TYPE_PREFIX + authenticatorType); 1150 values.put(META_VALUE, uid); 1151 return db.insert(TABLE_META, null, values); 1152 } 1153 populateMetaTableWithAuthTypeAndUID(SQLiteDatabase db, Map<String, Integer> authTypeAndUIDMap)1154 private void populateMetaTableWithAuthTypeAndUID(SQLiteDatabase db, 1155 Map<String, Integer> authTypeAndUIDMap) { 1156 for (Map.Entry<String, Integer> entry : authTypeAndUIDMap.entrySet()) { 1157 insertMetaAuthTypeAndUid(db, entry.getKey(), entry.getValue()); 1158 } 1159 } 1160 1161 /** 1162 * Pre-N database may need an upgrade before splitting 1163 */ 1164 @Override onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion)1165 public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { 1166 Log.e(TAG, "upgrade from version " + oldVersion + " to version " + newVersion); 1167 1168 if (oldVersion == 1) { 1169 // no longer need to do anything since the work is done 1170 // when upgrading from version 2 1171 oldVersion++; 1172 } 1173 1174 if (oldVersion == 2) { 1175 createGrantsTable(db); 1176 db.execSQL("DROP TRIGGER " + TABLE_ACCOUNTS + "Delete"); 1177 createAccountsDeletionTrigger(db); 1178 oldVersion++; 1179 } 1180 1181 if (oldVersion == 3) { 1182 db.execSQL("UPDATE " + TABLE_ACCOUNTS + " SET " + ACCOUNTS_TYPE + 1183 " = 'com.google' WHERE " + ACCOUNTS_TYPE + " == 'com.google.GAIA'"); 1184 oldVersion++; 1185 } 1186 1187 if (oldVersion == 4) { 1188 createSharedAccountsTable(db); 1189 oldVersion++; 1190 } 1191 1192 if (oldVersion == 5) { 1193 addOldAccountNameColumn(db); 1194 oldVersion++; 1195 } 1196 1197 if (oldVersion == 6) { 1198 addLastSuccessfullAuthenticatedTimeColumn(db); 1199 oldVersion++; 1200 } 1201 1202 if (oldVersion == 7) { 1203 addDebugTable(db); 1204 oldVersion++; 1205 } 1206 1207 if (oldVersion == 8) { 1208 populateMetaTableWithAuthTypeAndUID( 1209 db, 1210 AccountManagerService.getAuthenticatorTypeAndUIDForUser(mContext, mUserId)); 1211 oldVersion++; 1212 } 1213 1214 if (oldVersion != newVersion) { 1215 Log.e(TAG, "failed to upgrade version " + oldVersion + " to version " + newVersion); 1216 } 1217 } 1218 1219 @Override onOpen(SQLiteDatabase db)1220 public void onOpen(SQLiteDatabase db) { 1221 if (Log.isLoggable(TAG, Log.VERBOSE)) Log.v(TAG, "opened database " + DATABASE_NAME); 1222 } 1223 } 1224 findCeAccountsNotInDe()1225 List<Account> findCeAccountsNotInDe() { 1226 SQLiteDatabase db = mDeDatabase.getReadableDatabaseUserIsUnlocked(); 1227 // Select accounts from CE that do not exist in DE 1228 Cursor cursor = db.rawQuery( 1229 "SELECT " + ACCOUNTS_NAME + "," + ACCOUNTS_TYPE 1230 + " FROM " + CE_TABLE_ACCOUNTS 1231 + " WHERE NOT EXISTS " 1232 + " (SELECT " + ACCOUNTS_ID + " FROM " + TABLE_ACCOUNTS 1233 + " WHERE " + ACCOUNTS_ID + "=" + CE_TABLE_ACCOUNTS + "." + ACCOUNTS_ID 1234 + " )", null); 1235 try { 1236 List<Account> accounts = new ArrayList<>(cursor.getCount()); 1237 while (cursor.moveToNext()) { 1238 String accountName = cursor.getString(0); 1239 String accountType = cursor.getString(1); 1240 accounts.add(new Account(accountName, accountType)); 1241 } 1242 return accounts; 1243 } finally { 1244 cursor.close(); 1245 } 1246 } 1247 deleteCeAccount(long accountId)1248 boolean deleteCeAccount(long accountId) { 1249 SQLiteDatabase db = mDeDatabase.getWritableDatabaseUserIsUnlocked(); 1250 return db.delete( 1251 CE_TABLE_ACCOUNTS, ACCOUNTS_ID + "=" + accountId, null) > 0; 1252 } 1253 isCeDatabaseAttached()1254 boolean isCeDatabaseAttached() { 1255 return mDeDatabase.mCeAttached; 1256 } 1257 beginTransaction()1258 void beginTransaction() { 1259 mDeDatabase.getWritableDatabase().beginTransaction(); 1260 } 1261 setTransactionSuccessful()1262 void setTransactionSuccessful() { 1263 mDeDatabase.getWritableDatabase().setTransactionSuccessful(); 1264 } 1265 endTransaction()1266 void endTransaction() { 1267 mDeDatabase.getWritableDatabase().endTransaction(); 1268 } 1269 attachCeDatabase(File ceDbFile)1270 void attachCeDatabase(File ceDbFile) { 1271 CeDatabaseHelper.create(mContext, mPreNDatabaseFile, ceDbFile); 1272 SQLiteDatabase db = mDeDatabase.getWritableDatabase(); 1273 db.execSQL("ATTACH DATABASE '" + ceDbFile.getPath()+ "' AS ceDb"); 1274 mDeDatabase.mCeAttached = true; 1275 } 1276 1277 /* 1278 * Finds the row key where the next insertion should take place. Returns number of rows 1279 * if it is less {@link #MAX_DEBUG_DB_SIZE}, otherwise finds the lowest number available. 1280 */ calculateDebugTableInsertionPoint()1281 int calculateDebugTableInsertionPoint() { 1282 SQLiteDatabase db = mDeDatabase.getReadableDatabase(); 1283 String queryCountDebugDbRows = "SELECT COUNT(*) FROM " + TABLE_DEBUG; 1284 int size = (int) DatabaseUtils.longForQuery(db, queryCountDebugDbRows, null); 1285 if (size < MAX_DEBUG_DB_SIZE) { 1286 return size; 1287 } 1288 1289 // This query finds the smallest timestamp value (and if 2 records have 1290 // same timestamp, the choose the lower id). 1291 queryCountDebugDbRows = "SELECT " + DEBUG_TABLE_KEY + 1292 " FROM " + TABLE_DEBUG + 1293 " ORDER BY " + DEBUG_TABLE_TIMESTAMP + "," + DEBUG_TABLE_KEY + 1294 " LIMIT 1"; 1295 return (int) DatabaseUtils.longForQuery(db, queryCountDebugDbRows, null); 1296 } 1297 compileSqlStatementForLogging()1298 SQLiteStatement compileSqlStatementForLogging() { 1299 // TODO b/31708085 Fix debug logging - it eagerly opens database for write without a need 1300 SQLiteDatabase db = mDeDatabase.getWritableDatabase(); 1301 String sql = "INSERT OR REPLACE INTO " + AccountsDb.TABLE_DEBUG 1302 + " VALUES (?,?,?,?,?,?)"; 1303 return db.compileStatement(sql); 1304 } 1305 dumpDebugTable(PrintWriter pw)1306 void dumpDebugTable(PrintWriter pw) { 1307 SQLiteDatabase db = mDeDatabase.getReadableDatabase(); 1308 Cursor cursor = db.query(TABLE_DEBUG, null, 1309 null, null, null, null, DEBUG_TABLE_TIMESTAMP); 1310 pw.println("AccountId, Action_Type, timestamp, UID, TableName, Key"); 1311 pw.println("Accounts History"); 1312 try { 1313 while (cursor.moveToNext()) { 1314 // print type,count 1315 pw.println(cursor.getString(0) + "," + cursor.getString(1) + "," + 1316 cursor.getString(2) + "," + cursor.getString(3) + "," 1317 + cursor.getString(4) + "," + cursor.getString(5)); 1318 } 1319 } finally { 1320 cursor.close(); 1321 } 1322 } 1323 1324 @Override close()1325 public void close() { 1326 mDeDatabase.close(); 1327 } 1328 deleteDbFileWarnIfFailed(File dbFile)1329 static void deleteDbFileWarnIfFailed(File dbFile) { 1330 if (!SQLiteDatabase.deleteDatabase(dbFile)) { 1331 Log.w(TAG, "Database at " + dbFile + " was not deleted successfully"); 1332 } 1333 } 1334 create(Context context, int userId, File preNDatabaseFile, File deDatabaseFile)1335 public static AccountsDb create(Context context, int userId, File preNDatabaseFile, 1336 File deDatabaseFile) { 1337 boolean newDbExists = deDatabaseFile.exists(); 1338 DeDatabaseHelper deDatabaseHelper = new DeDatabaseHelper(context, userId, 1339 deDatabaseFile.getPath()); 1340 // If the db just created, and there is a legacy db, migrate it 1341 if (!newDbExists && preNDatabaseFile.exists()) { 1342 // Migrate legacy db to the latest version - PRE_N_DATABASE_VERSION 1343 PreNDatabaseHelper 1344 preNDatabaseHelper = new PreNDatabaseHelper(context, userId, 1345 preNDatabaseFile.getPath()); 1346 // Open the database to force upgrade if required 1347 preNDatabaseHelper.getWritableDatabase(); 1348 preNDatabaseHelper.close(); 1349 // Move data without SPII to DE 1350 deDatabaseHelper.migratePreNDbToDe(preNDatabaseFile); 1351 } 1352 return new AccountsDb(deDatabaseHelper, context, preNDatabaseFile); 1353 } 1354 1355 } 1356