Merge "[FBE] Split accounts.db into two DE/CE databases" into nyc-dev

This commit is contained in:
Fyodor Kupolov
2016-03-31 16:16:47 +00:00
committed by Android (Google) Code Review

View File

@@ -65,6 +65,7 @@ import android.database.sqlite.SQLiteStatement;
import android.os.Binder;
import android.os.Bundle;
import android.os.Environment;
import android.os.FileUtils;
import android.os.Handler;
import android.os.IBinder;
import android.os.Looper;
@@ -75,11 +76,13 @@ import android.os.RemoteException;
import android.os.SystemClock;
import android.os.UserHandle;
import android.os.UserManager;
import android.os.storage.StorageManager;
import android.text.TextUtils;
import android.util.Log;
import android.util.Pair;
import android.util.Slog;
import android.util.SparseArray;
import android.util.SparseBooleanArray;
import com.android.internal.R;
import com.android.internal.util.ArrayUtils;
@@ -91,6 +94,7 @@ import com.google.android.collect.Sets;
import java.io.File;
import java.io.FileDescriptor;
import java.io.IOException;
import java.io.PrintWriter;
import java.security.GeneralSecurityException;
import java.security.MessageDigest;
@@ -107,7 +111,6 @@ import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicReference;
@@ -126,7 +129,9 @@ public class AccountManagerService
private static final String TAG = "AccountManagerService";
private static final String DATABASE_NAME = "accounts.db";
private static final int DATABASE_VERSION = 9;
private static final int PRE_N_DATABASE_VERSION = 9;
private static final int CE_DATABASE_VERSION = 10;
private static final int DE_DATABASE_VERSION = 1;
private static final int MAX_DEBUG_DB_SIZE = 64;
@@ -176,6 +181,15 @@ public class AccountManagerService
private static final String META_VALUE = "value";
private static final String TABLE_SHARED_ACCOUNTS = "shared_accounts";
private static final String SHARED_ACCOUNTS_ID = "_id";
private static final String PRE_N_DATABASE_NAME = "accounts.db";
private static final String CE_DATABASE_NAME = "accounts_ce.db";
private static final String DE_DATABASE_NAME = "accounts_de.db";
private static final String CE_DB_PREFIX = "ceDb.";
private static final String CE_TABLE_ACCOUNTS = CE_DB_PREFIX + TABLE_ACCOUNTS;
private static final String CE_TABLE_AUTHTOKENS = CE_DB_PREFIX + TABLE_AUTHTOKENS;
private static final String CE_TABLE_EXTRAS = CE_DB_PREFIX + TABLE_EXTRAS;
private static final String[] ACCOUNT_TYPE_COUNT_PROJECTION =
new String[] { ACCOUNTS_TYPE, ACCOUNTS_TYPE_COUNT};
@@ -214,7 +228,7 @@ public class AccountManagerService
static class UserAccounts {
private final int userId;
private final DatabaseHelper openHelper;
private final DeDatabaseHelper openHelper;
private final HashMap<Pair<Pair<Account, String>, Integer>, Integer>
credentialsPermissionNotificationIds =
new HashMap<Pair<Pair<Account, String>, Integer>, Integer>();
@@ -255,15 +269,15 @@ public class AccountManagerService
UserAccounts(Context context, int userId) {
this.userId = userId;
synchronized (cacheLock) {
openHelper = new DatabaseHelper(context, userId);
openHelper = DeDatabaseHelper.create(context, userId);
}
}
}
private final SparseArray<UserAccounts> mUsers = new SparseArray<UserAccounts>();
private final SparseArray<UserAccounts> mUsers = new SparseArray<>();
private final SparseBooleanArray mUnlockedUsers = new SparseBooleanArray();
private static AtomicReference<AccountManagerService> sThis =
new AtomicReference<AccountManagerService>();
private static AtomicReference<AccountManagerService> sThis = new AtomicReference<>();
private static final Account[] EMPTY_ACCOUNT_ARRAY = new Account[]{};
/**
@@ -381,6 +395,11 @@ public class AccountManagerService
*/
private void validateAccountsInternal(
UserAccounts accounts, boolean invalidateAuthenticatorCache) {
if (Log.isLoggable(TAG, Log.DEBUG)) {
Log.d(TAG, "validateAccountsInternal " + accounts.userId
+ " isCeDatabaseAttached=" + accounts.openHelper.isCeDatabaseAttached()
+ " userLocked=" + mUnlockedUsers.get(accounts.userId));
}
if (invalidateAuthenticatorCache) {
mAuthenticatorCache.invalidateCache(accounts.userId);
}
@@ -453,8 +472,7 @@ public class AccountManagerService
null, null, null, null, ACCOUNTS_ID);
try {
accounts.accountCache.clear();
final HashMap<String, ArrayList<String>> accountNamesByType =
new LinkedHashMap<String, ArrayList<String>>();
final HashMap<String, ArrayList<String>> accountNamesByType = new LinkedHashMap<>();
while (cursor.moveToNext()) {
final long accountId = cursor.getLong(0);
final String accountType = cursor.getString(1);
@@ -482,15 +500,12 @@ public class AccountManagerService
accountNames.add(accountName);
}
}
for (Map.Entry<String, ArrayList<String>> cur
: accountNamesByType.entrySet()) {
for (Map.Entry<String, ArrayList<String>> cur : accountNamesByType.entrySet()) {
final String accountType = cur.getKey();
final ArrayList<String> accountNames = cur.getValue();
final Account[] accountsForType = new Account[accountNames.size()];
int i = 0;
for (String accountName : accountNames) {
accountsForType[i] = new Account(accountName, accountType);
++i;
for (int i = 0; i < accountsForType.length; i++) {
accountsForType[i] = new Account(accountNames.get(i), accountType);
}
accounts.accountCache.put(accountType, accountsForType);
}
@@ -522,12 +537,25 @@ public class AccountManagerService
protected UserAccounts getUserAccounts(int userId) {
synchronized (mUsers) {
UserAccounts accounts = mUsers.get(userId);
boolean validateAccounts = false;
if (accounts == null) {
accounts = new UserAccounts(mContext, userId);
initializeDebugDbSizeAndCompileSqlStatementForLogging(
accounts.openHelper.getWritableDatabase(), accounts);
mUsers.append(userId, accounts);
purgeOldGrants(accounts);
validateAccounts = true;
}
// open CE database if necessary
if (!accounts.openHelper.isCeDatabaseAttached() && mUnlockedUsers.get(userId)) {
Log.i(TAG, "User " + userId + " is unlocked - opening CE database");
synchronized (accounts.cacheLock) {
CeDatabaseHelper.create(mContext, userId);
accounts.openHelper.attachCeDatabase();
}
// TODO Synchronize accounts by removing CE account not available in DE
}
if (validateAccounts) {
validateAccountsInternal(accounts, true /* invalidateAuthenticatorCache */);
}
return accounts;
@@ -571,27 +599,51 @@ public class AccountManagerService
if (userId < 1) return;
UserAccounts accounts;
boolean userUnlocked;
synchronized (mUsers) {
accounts = mUsers.get(userId);
mUsers.remove(userId);
userUnlocked = mUnlockedUsers.get(userId);
mUnlockedUsers.delete(userId);
}
if (accounts == null) {
File dbFile = new File(getDatabaseName(userId));
dbFile.delete();
return;
if (accounts != null) {
synchronized (accounts.cacheLock) {
accounts.openHelper.close();
}
}
Log.i(TAG, "Removing database files for user " + userId);
File dbFile = new File(getDeDatabaseName(userId));
synchronized (accounts.cacheLock) {
accounts.openHelper.close();
File dbFile = new File(getDatabaseName(userId));
dbFile.delete();
deleteDbFileWarnIfFailed(dbFile);
// Remove CE file if user is unlocked, or FBE is not enabled
boolean fbeEnabled = StorageManager.isFileEncryptedNativeOrEmulated();
if (!fbeEnabled || userUnlocked) {
File ceDb = new File(getCeDatabaseName(userId));
if (ceDb.exists()) {
deleteDbFileWarnIfFailed(ceDb);
}
}
}
private static void deleteDbFileWarnIfFailed(File dbFile) {
if (!SQLiteDatabase.deleteDatabase(dbFile)) {
Log.w(TAG, "Database at " + dbFile + " was not deleted successfully");
}
}
private void onUserUnlocked(Intent intent) {
int userId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, -1);
if (Log.isLoggable(TAG, Log.VERBOSE)) {
Log.v(TAG, "onUserUnlocked " + userId);
}
synchronized (mUsers) {
mUnlockedUsers.put(userId, true);
}
if (userId < 1) return;
syncSharedAccounts(userId);
}
private void syncSharedAccounts(int userId) {
// Check if there's a shared account that needs to be created as an account
Account[] sharedAccounts = getSharedAccountsAsUser(userId);
if (sharedAccounts == null || sharedAccounts.length == 0) return;
@@ -645,20 +697,15 @@ public class AccountManagerService
if (account == null) {
return null;
}
if (!isUserUnlocked(accounts.userId)) {
Log.w(TAG, "Password is not available - user " + accounts.userId + " data is locked");
return null;
}
synchronized (accounts.cacheLock) {
final SQLiteDatabase db = accounts.openHelper.getReadableDatabase();
Cursor cursor = db.query(TABLE_ACCOUNTS, new String[]{ACCOUNTS_PASSWORD},
ACCOUNTS_NAME + "=? AND " + ACCOUNTS_TYPE+ "=?",
new String[]{account.name, account.type}, null, null, null);
try {
if (cursor.moveToNext()) {
return cursor.getString(0);
}
return null;
} finally {
cursor.close();
}
final SQLiteDatabase db = accounts.openHelper.getReadableDatabaseUserIsUnlocked();
return CeDatabaseHelper.findAccountPasswordByNameAndType(db, account.name,
account.type);
}
}
@@ -699,7 +746,7 @@ public class AccountManagerService
try {
if (cursor.moveToNext()) {
String previousName = cursor.getString(0);
previousNameRef = new AtomicReference<String>(previousName);
previousNameRef = new AtomicReference<>(previousName);
accounts.previousNameCache.put(account, previousNameRef);
return previousName;
} else {
@@ -825,7 +872,7 @@ public class AccountManagerService
long identityToken = clearCallingIdentity();
try {
UserAccounts accounts = getUserAccounts(userId);
return addAccountInternal(accounts, account, password, extras, false, callingUid);
return addAccountInternal(accounts, account, password, extras, callingUid);
} finally {
restoreCallingIdentity(identityToken);
}
@@ -999,17 +1046,22 @@ public class AccountManagerService
}
private boolean addAccountInternal(UserAccounts accounts, Account account, String password,
Bundle extras, boolean restricted, int callingUid) {
Bundle extras, int callingUid) {
Bundle.setDefusable(extras, true);
if (account == null) {
return false;
}
if (!isUserUnlocked(accounts.userId)) {
Log.w(TAG, "Account " + account + " cannot be added - user " + accounts.userId
+ " is locked. callingUid=" + callingUid);
return false;
}
synchronized (accounts.cacheLock) {
final SQLiteDatabase db = accounts.openHelper.getWritableDatabase();
final SQLiteDatabase db = accounts.openHelper.getWritableDatabaseUserIsUnlocked();
db.beginTransaction();
try {
long numMatches = DatabaseUtils.longForQuery(db,
"select count(*) from " + TABLE_ACCOUNTS
"select count(*) from " + CE_TABLE_ACCOUNTS
+ " WHERE " + ACCOUNTS_NAME + "=? AND " + ACCOUNTS_TYPE+ "=?",
new String[]{account.name, account.type});
if (numMatches > 0) {
@@ -1021,13 +1073,24 @@ public class AccountManagerService
values.put(ACCOUNTS_NAME, account.name);
values.put(ACCOUNTS_TYPE, account.type);
values.put(ACCOUNTS_PASSWORD, password);
values.put(ACCOUNTS_LAST_AUTHENTICATE_TIME_EPOCH_MILLIS, System.currentTimeMillis());
long accountId = db.insert(TABLE_ACCOUNTS, ACCOUNTS_NAME, values);
long accountId = db.insert(CE_TABLE_ACCOUNTS, ACCOUNTS_NAME, values);
if (accountId < 0) {
Log.w(TAG, "insertAccountIntoDatabase: " + account
+ ", skipping the DB insert failed");
return false;
}
// Insert into DE table
values = new ContentValues();
values.put(ACCOUNTS_ID, accountId);
values.put(ACCOUNTS_NAME, account.name);
values.put(ACCOUNTS_TYPE, account.type);
values.put(ACCOUNTS_LAST_AUTHENTICATE_TIME_EPOCH_MILLIS,
System.currentTimeMillis());
if (db.insert(TABLE_ACCOUNTS, ACCOUNTS_NAME, values) < 0) {
Log.w(TAG, "insertAccountIntoDatabase: " + account
+ ", skipping the DB insert failed");
return false;
}
if (extras != null) {
for (String key : extras.keySet()) {
final String value = extras.getString(key);
@@ -1055,6 +1118,12 @@ public class AccountManagerService
return true;
}
private boolean isUserUnlocked(int userId) {
synchronized (mUsers) {
return mUnlockedUsers.get(userId);
}
}
/**
* Adds the account to all linked restricted users as shared accounts. If the user is currently
* running, then clone the account too.
@@ -1079,7 +1148,7 @@ public class AccountManagerService
values.put(EXTRAS_KEY, key);
values.put(EXTRAS_ACCOUNTS_ID, accountId);
values.put(EXTRAS_VALUE, value);
return db.insert(TABLE_EXTRAS, EXTRAS_KEY, values);
return db.insert(CE_TABLE_EXTRAS, EXTRAS_KEY, values);
}
@Override
@@ -1226,17 +1295,19 @@ public class AccountManagerService
}
}
synchronized (accounts.cacheLock) {
final SQLiteDatabase db = accounts.openHelper.getWritableDatabase();
final SQLiteDatabase db = accounts.openHelper.getWritableDatabaseUserIsUnlocked();
db.beginTransaction();
boolean isSuccessful = false;
Account renamedAccount = new Account(newName, accountToRename.type);
try {
final ContentValues values = new ContentValues();
values.put(ACCOUNTS_NAME, newName);
values.put(ACCOUNTS_PREVIOUS_NAME, accountToRename.name);
final long accountId = getAccountIdLocked(db, accountToRename);
if (accountId >= 0) {
final ContentValues values = new ContentValues();
values.put(ACCOUNTS_NAME, newName);
final String[] argsAccountId = { String.valueOf(accountId) };
db.update(CE_TABLE_ACCOUNTS, values, ACCOUNTS_ID + "=?", argsAccountId);
// Update NAME/PREVIOUS_NAME in DE accounts table
values.put(ACCOUNTS_PREVIOUS_NAME, accountToRename.name);
db.update(TABLE_ACCOUNTS, values, ACCOUNTS_ID + "=?", argsAccountId);
db.setTransactionSuccessful();
isSuccessful = true;
@@ -1332,7 +1403,7 @@ public class AccountManagerService
* authenticator. This will let users remove accounts (via Settings in the system) but not
* arbitrary applications (like competing authenticators).
*/
UserHandle user = new UserHandle(userId);
UserHandle user = UserHandle.of(userId);
if (!isAccountManagedByCaller(account.type, callingUid, user.getIdentifier())
&& !isSystemUid(callingUid)) {
String msg = String.format(
@@ -1467,13 +1538,17 @@ public class AccountManagerService
}
private boolean removeAccountInternal(UserAccounts accounts, Account account, int callingUid) {
// For now user is required to be unlocked. TODO: Handle both cases in the future
int deleted;
synchronized (accounts.cacheLock) {
final SQLiteDatabase db = accounts.openHelper.getWritableDatabase();
final SQLiteDatabase db = accounts.openHelper.getWritableDatabaseUserIsUnlocked();
final long accountId = getAccountIdLocked(db, account);
deleted = db.delete(TABLE_ACCOUNTS, ACCOUNTS_NAME + "=? AND " + ACCOUNTS_TYPE
+ "=?",
new String[]{account.name, account.type});
// Delete from CE table
db.delete(CE_TABLE_ACCOUNTS, ACCOUNTS_NAME + "=? AND " + ACCOUNTS_TYPE + "=?",
new String[]{account.name, account.type});
removeAccountFromCacheLocked(accounts, account);
sendAccountsChangedBroadcast(accounts.userId);
@@ -1512,7 +1587,7 @@ public class AccountManagerService
try {
UserAccounts accounts = getUserAccounts(userId);
synchronized (accounts.cacheLock) {
final SQLiteDatabase db = accounts.openHelper.getWritableDatabase();
final SQLiteDatabase db = accounts.openHelper.getWritableDatabaseUserIsUnlocked();
db.beginTransaction();
try {
invalidateAuthTokenLocked(accounts, db, accountType, authToken);
@@ -1544,22 +1619,22 @@ public class AccountManagerService
return;
}
Cursor cursor = db.rawQuery(
"SELECT " + TABLE_AUTHTOKENS + "." + AUTHTOKENS_ID
+ ", " + TABLE_ACCOUNTS + "." + ACCOUNTS_NAME
+ ", " + TABLE_AUTHTOKENS + "." + AUTHTOKENS_TYPE
+ " FROM " + TABLE_ACCOUNTS
+ " JOIN " + TABLE_AUTHTOKENS
+ " ON " + TABLE_ACCOUNTS + "." + ACCOUNTS_ID
+ " = " + AUTHTOKENS_ACCOUNTS_ID
+ " WHERE " + AUTHTOKENS_AUTHTOKEN + " = ? AND "
+ TABLE_ACCOUNTS + "." + ACCOUNTS_TYPE + " = ?",
"SELECT " + CE_TABLE_AUTHTOKENS + "." + AUTHTOKENS_ID
+ ", " + CE_TABLE_ACCOUNTS + "." + ACCOUNTS_NAME
+ ", " + CE_TABLE_AUTHTOKENS + "." + AUTHTOKENS_TYPE
+ " FROM " + CE_TABLE_ACCOUNTS
+ " JOIN " + CE_TABLE_AUTHTOKENS
+ " ON " + CE_TABLE_ACCOUNTS + "." + ACCOUNTS_ID
+ " = " + CE_TABLE_AUTHTOKENS + "." + AUTHTOKENS_ACCOUNTS_ID
+ " WHERE " + CE_TABLE_AUTHTOKENS + "." + AUTHTOKENS_AUTHTOKEN
+ " = ? AND " + CE_TABLE_ACCOUNTS + "." + ACCOUNTS_TYPE + " = ?",
new String[]{authToken, accountType});
try {
while (cursor.moveToNext()) {
long authTokenId = cursor.getLong(0);
String accountName = cursor.getString(1);
String authTokenType = cursor.getString(2);
db.delete(TABLE_AUTHTOKENS, AUTHTOKENS_ID + "=" + authTokenId, null);
db.delete(CE_TABLE_AUTHTOKENS, AUTHTOKENS_ID + "=" + authTokenId, null);
writeAuthTokenIntoCacheLocked(
accounts,
db,
@@ -1585,7 +1660,7 @@ public class AccountManagerService
return;
}
cancelNotification(getSigninRequiredNotificationId(accounts, account),
new UserHandle(accounts.userId));
UserHandle.of(accounts.userId));
synchronized (accounts.cacheLock) {
accounts.accountTokenCaches.put(
account, token, tokenType, callerPkg, callerSigDigest, expiryMillis);
@@ -1598,23 +1673,23 @@ public class AccountManagerService
return false;
}
cancelNotification(getSigninRequiredNotificationId(accounts, account),
new UserHandle(accounts.userId));
UserHandle.of(accounts.userId));
synchronized (accounts.cacheLock) {
final SQLiteDatabase db = accounts.openHelper.getWritableDatabase();
final SQLiteDatabase db = accounts.openHelper.getWritableDatabaseUserIsUnlocked();
db.beginTransaction();
try {
long accountId = getAccountIdLocked(db, account);
if (accountId < 0) {
return false;
}
db.delete(TABLE_AUTHTOKENS,
db.delete(CE_TABLE_AUTHTOKENS,
AUTHTOKENS_ACCOUNTS_ID + "=" + accountId + " AND " + AUTHTOKENS_TYPE + "=?",
new String[]{type});
ContentValues values = new ContentValues();
values.put(AUTHTOKENS_ACCOUNTS_ID, accountId);
values.put(AUTHTOKENS_TYPE, type);
values.put(AUTHTOKENS_AUTHTOKEN, authToken);
if (db.insert(TABLE_AUTHTOKENS, AUTHTOKENS_AUTHTOKEN, values) >= 0) {
if (db.insert(CE_TABLE_AUTHTOKENS, AUTHTOKENS_AUTHTOKEN, values) >= 0) {
db.setTransactionSuccessful();
writeAuthTokenIntoCacheLocked(accounts, db, account, type, authToken);
return true;
@@ -1714,7 +1789,7 @@ public class AccountManagerService
return;
}
synchronized (accounts.cacheLock) {
final SQLiteDatabase db = accounts.openHelper.getWritableDatabase();
final SQLiteDatabase db = accounts.openHelper.getWritableDatabaseUserIsUnlocked();
db.beginTransaction();
try {
final ContentValues values = new ContentValues();
@@ -1722,8 +1797,8 @@ public class AccountManagerService
final long accountId = getAccountIdLocked(db, account);
if (accountId >= 0) {
final String[] argsAccountId = {String.valueOf(accountId)};
db.update(TABLE_ACCOUNTS, values, ACCOUNTS_ID + "=?", argsAccountId);
db.delete(TABLE_AUTHTOKENS, AUTHTOKENS_ACCOUNTS_ID + "=?", argsAccountId);
db.update(CE_TABLE_ACCOUNTS, values, ACCOUNTS_ID + "=?", argsAccountId);
db.delete(CE_TABLE_AUTHTOKENS, AUTHTOKENS_ACCOUNTS_ID + "=?", argsAccountId);
accounts.authTokenCache.remove(account);
accounts.accountTokenCaches.remove(account);
db.setTransactionSuccessful();
@@ -1806,7 +1881,7 @@ public class AccountManagerService
return;
}
synchronized (accounts.cacheLock) {
final SQLiteDatabase db = accounts.openHelper.getWritableDatabase();
final SQLiteDatabase db = accounts.openHelper.getWritableDatabaseUserIsUnlocked();
db.beginTransaction();
try {
long accountId = getAccountIdLocked(db, account);
@@ -1822,7 +1897,7 @@ public class AccountManagerService
} else {
ContentValues values = new ContentValues();
values.put(EXTRAS_VALUE, value);
if (1 != db.update(TABLE_EXTRAS, values, EXTRAS_ID + "=" + extrasId, null)) {
if (1 != db.update(CE_TABLE_EXTRAS, values, EXTRAS_ID + "=" + extrasId, null)) {
return;
}
@@ -3307,7 +3382,6 @@ public class AccountManagerService
long sharedTableAccountId = getAccountIdFromSharedTable(db, account);
final ContentValues values = new ContentValues();
values.put(ACCOUNTS_NAME, newName);
values.put(ACCOUNTS_PREVIOUS_NAME, account.name);
int r = db.update(
TABLE_SHARED_ACCOUNTS,
values,
@@ -3347,7 +3421,7 @@ public class AccountManagerService
public Account[] getSharedAccountsAsUser(int userId) {
userId = handleIncomingUser(userId);
UserAccounts accounts = getUserAccounts(userId);
ArrayList<Account> accountList = new ArrayList<Account>();
ArrayList<Account> accountList = new ArrayList<>();
Cursor cursor = null;
try {
cursor = accounts.openHelper.getReadableDatabase()
@@ -3488,7 +3562,7 @@ public class AccountManagerService
}
private long getExtrasIdLocked(SQLiteDatabase db, long accountId, String key) {
Cursor cursor = db.query(TABLE_EXTRAS, new String[]{EXTRAS_ID},
Cursor cursor = db.query(CE_TABLE_EXTRAS, new String[]{EXTRAS_ID},
EXTRAS_ACCOUNTS_ID + "=" + accountId + " AND " + EXTRAS_KEY + "=?",
new String[]{key}, null, null, null);
try {
@@ -3729,8 +3803,8 @@ public class AccountManagerService
if (accountPresent) {
lastAuthenticatedTime = DatabaseUtils.longForQuery(
mAccounts.openHelper.getReadableDatabase(),
"select " + ACCOUNTS_LAST_AUTHENTICATE_TIME_EPOCH_MILLIS
+ " from " +
"SELECT " + ACCOUNTS_LAST_AUTHENTICATE_TIME_EPOCH_MILLIS
+ " FROM " +
TABLE_ACCOUNTS + " WHERE " + ACCOUNTS_NAME + "=? AND "
+ ACCOUNTS_TYPE + "=?",
new String[] {
@@ -3859,7 +3933,7 @@ public class AccountManagerService
Log.v(TAG, "performing bindService to " + authenticatorInfo.componentName);
}
if (!mContext.bindServiceAsUser(intent, this, Context.BIND_AUTO_CREATE,
new UserHandle(mAccounts.userId))) {
UserHandle.of(mAccounts.userId))) {
if (Log.isLoggable(TAG, Log.VERBOSE)) {
Log.v(TAG, "bindService to " + authenticatorInfo.componentName + " failed");
}
@@ -3893,15 +3967,16 @@ public class AccountManagerService
}
}
private static String getDatabaseName(int userId) {
static String getPreNDatabaseName(int userId) {
File systemDir = Environment.getDataSystemDirectory();
File databaseFile = new File(Environment.getUserSystemDirectory(userId), DATABASE_NAME);
File databaseFile = new File(Environment.getUserSystemDirectory(userId),
PRE_N_DATABASE_NAME);
if (userId == 0) {
// Migrate old file, if it exists, to the new location.
// Make sure the new file doesn't already exist. A dummy file could have been
// accidentally created in the old location, causing the new one to become corrupted
// as well.
File oldFile = new File(systemDir, DATABASE_NAME);
File oldFile = new File(systemDir, PRE_N_DATABASE_NAME);
if (oldFile.exists() && !databaseFile.exists()) {
// Check for use directory; create if it doesn't exist, else renameTo will fail
File userDir = Environment.getUserSystemDirectory(userId);
@@ -3918,6 +3993,18 @@ public class AccountManagerService
return databaseFile.getPath();
}
static String getDeDatabaseName(int userId) {
File databaseFile = new File(Environment.getDataSystemDeDirectory(userId),
DE_DATABASE_NAME);
return databaseFile.getPath();
}
static String getCeDatabaseName(int userId) {
File databaseFile = new File(Environment.getDataSystemCeDirectory(userId),
CE_DATABASE_NAME);
return databaseFile.getPath();
}
private static class DebugDbHelper{
private DebugDbHelper() {
}
@@ -4050,58 +4137,22 @@ public class AccountManagerService
return DatabaseUtils.longForQuery(db, queryCountDebugDbRows, null);
}
static class DatabaseHelper extends SQLiteOpenHelper {
static class PreNDatabaseHelper extends SQLiteOpenHelper {
private final Context mContext;
private final int mUserId;
public DatabaseHelper(Context context, int userId) {
super(context, AccountManagerService.getDatabaseName(userId), null, DATABASE_VERSION);
public PreNDatabaseHelper(Context context, int userId) {
super(context, AccountManagerService.getPreNDatabaseName(userId), null,
PRE_N_DATABASE_VERSION);
mContext = context;
mUserId = userId;
}
/**
* This call needs to be made while the mCacheLock is held. The way to
* ensure this is to get the lock any time a method is called ont the DatabaseHelper
* @param db The database.
*/
@Override
public void onCreate(SQLiteDatabase db) {
db.execSQL("CREATE TABLE " + TABLE_ACCOUNTS + " ( "
+ ACCOUNTS_ID + " INTEGER PRIMARY KEY AUTOINCREMENT, "
+ ACCOUNTS_NAME + " TEXT NOT NULL, "
+ ACCOUNTS_TYPE + " TEXT NOT NULL, "
+ ACCOUNTS_PASSWORD + " TEXT, "
+ ACCOUNTS_PREVIOUS_NAME + " TEXT, "
+ ACCOUNTS_LAST_AUTHENTICATE_TIME_EPOCH_MILLIS + " INTEGER DEFAULT 0, "
+ "UNIQUE(" + ACCOUNTS_NAME + "," + ACCOUNTS_TYPE + "))");
db.execSQL("CREATE TABLE " + TABLE_AUTHTOKENS + " ( "
+ AUTHTOKENS_ID + " INTEGER PRIMARY KEY AUTOINCREMENT, "
+ AUTHTOKENS_ACCOUNTS_ID + " INTEGER NOT NULL, "
+ AUTHTOKENS_TYPE + " TEXT NOT NULL, "
+ AUTHTOKENS_AUTHTOKEN + " TEXT, "
+ "UNIQUE (" + AUTHTOKENS_ACCOUNTS_ID + "," + AUTHTOKENS_TYPE + "))");
createGrantsTable(db);
db.execSQL("CREATE TABLE " + TABLE_EXTRAS + " ( "
+ EXTRAS_ID + " INTEGER PRIMARY KEY AUTOINCREMENT, "
+ EXTRAS_ACCOUNTS_ID + " INTEGER, "
+ EXTRAS_KEY + " TEXT NOT NULL, "
+ EXTRAS_VALUE + " TEXT, "
+ "UNIQUE(" + EXTRAS_ACCOUNTS_ID + "," + EXTRAS_KEY + "))");
db.execSQL("CREATE TABLE " + TABLE_META + " ( "
+ META_KEY + " TEXT PRIMARY KEY NOT NULL, "
+ META_VALUE + " TEXT)");
createSharedAccountsTable(db);
createAccountsDeletionTrigger(db);
DebugDbHelper.createDebugTable(db);
// We use PreNDatabaseHelper only if pre-N db exists
throw new IllegalStateException("Legacy database cannot be created - only upgraded!");
}
private void createSharedAccountsTable(SQLiteDatabase db) {
@@ -4161,6 +4212,9 @@ public class AccountManagerService
}
}
/**
* Pre-N database may need an upgrade before splitting
*/
@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
Log.e(TAG, "upgrade from version " + oldVersion + " to version " + newVersion);
@@ -4222,6 +4276,315 @@ public class AccountManagerService
}
}
static class DeDatabaseHelper extends SQLiteOpenHelper {
private final int mUserId;
private volatile boolean mCeAttached;
private DeDatabaseHelper(Context context, int userId) {
super(context, getDeDatabaseName(userId), null, DE_DATABASE_VERSION);
mUserId = userId;
}
/**
* This call needs to be made while the mCacheLock is held. The way to
* ensure this is to get the lock any time a method is called ont the DatabaseHelper
* @param db The database.
*/
@Override
public void onCreate(SQLiteDatabase db) {
Log.i(TAG, "Creating DE database for user " + mUserId);
db.execSQL("CREATE TABLE " + TABLE_ACCOUNTS + " ( "
+ ACCOUNTS_ID + " INTEGER PRIMARY KEY, "
+ ACCOUNTS_NAME + " TEXT NOT NULL, "
+ ACCOUNTS_TYPE + " TEXT NOT NULL, "
+ ACCOUNTS_PREVIOUS_NAME + " TEXT, "
+ ACCOUNTS_LAST_AUTHENTICATE_TIME_EPOCH_MILLIS + " INTEGER DEFAULT 0, "
+ "UNIQUE(" + ACCOUNTS_NAME + "," + ACCOUNTS_TYPE + "))");
db.execSQL("CREATE TABLE " + TABLE_META + " ( "
+ META_KEY + " TEXT PRIMARY KEY NOT NULL, "
+ META_VALUE + " TEXT)");
createGrantsTable(db);
createSharedAccountsTable(db);
createAccountsDeletionTrigger(db);
DebugDbHelper.createDebugTable(db);
}
private void createSharedAccountsTable(SQLiteDatabase db) {
db.execSQL("CREATE TABLE " + TABLE_SHARED_ACCOUNTS + " ( "
+ ACCOUNTS_ID + " INTEGER PRIMARY KEY AUTOINCREMENT, "
+ ACCOUNTS_NAME + " TEXT NOT NULL, "
+ ACCOUNTS_TYPE + " TEXT NOT NULL, "
+ "UNIQUE(" + ACCOUNTS_NAME + "," + ACCOUNTS_TYPE + "))");
}
private void createAccountsDeletionTrigger(SQLiteDatabase db) {
db.execSQL(""
+ " CREATE TRIGGER " + TABLE_ACCOUNTS + "Delete DELETE ON " + TABLE_ACCOUNTS
+ " BEGIN"
+ " DELETE FROM " + TABLE_GRANTS
+ " WHERE " + GRANTS_ACCOUNTS_ID + "=OLD." + ACCOUNTS_ID + " ;"
+ " END");
}
private void createGrantsTable(SQLiteDatabase db) {
db.execSQL("CREATE TABLE " + TABLE_GRANTS + " ( "
+ GRANTS_ACCOUNTS_ID + " INTEGER NOT NULL, "
+ GRANTS_AUTH_TOKEN_TYPE + " STRING NOT NULL, "
+ GRANTS_GRANTEE_UID + " INTEGER NOT NULL, "
+ "UNIQUE (" + GRANTS_ACCOUNTS_ID + "," + GRANTS_AUTH_TOKEN_TYPE
+ "," + GRANTS_GRANTEE_UID + "))");
}
@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
Log.i(TAG, "upgrade from version " + oldVersion + " to version " + newVersion);
if (oldVersion != newVersion) {
Log.e(TAG, "failed to upgrade version " + oldVersion + " to version " + newVersion);
}
}
public void attachCeDatabase() {
File ceDbFile = new File(getCeDatabaseName(mUserId));
SQLiteDatabase db = getWritableDatabase();
db.execSQL("ATTACH DATABASE '" + ceDbFile.getPath()+ "' AS ceDb");
mCeAttached = true;
}
public boolean isCeDatabaseAttached() {
return mCeAttached;
}
public SQLiteDatabase getReadableDatabaseUserIsUnlocked() {
if(!mCeAttached) {
Log.wtf(TAG, "getReadableDatabaseUserIsUnlocked called while user "
+ mUserId + " is still locked ", new Throwable());
}
return super.getReadableDatabase();
}
public SQLiteDatabase getWritableDatabaseUserIsUnlocked() {
if(!mCeAttached) {
Log.wtf(TAG, "getWritableDatabaseUserIsUnlocked called while user " + mUserId
+ " is still locked ", new Throwable());
}
return super.getWritableDatabase();
}
@Override
public void onOpen(SQLiteDatabase db) {
if (Log.isLoggable(TAG, Log.VERBOSE)) Log.v(TAG, "opened database " + DE_DATABASE_NAME);
}
private void migratePreNDbToDe(File preNDbFile) {
Log.i(TAG, "Migrate pre-N database to DE preNDbFile=" + preNDbFile);
SQLiteDatabase db = getWritableDatabase();
db.execSQL("ATTACH DATABASE '" + preNDbFile.getPath() + "' AS preNDb");
db.beginTransaction();
// Copy accounts fields
db.execSQL("INSERT INTO " + TABLE_ACCOUNTS
+ "(" + ACCOUNTS_ID + "," + ACCOUNTS_NAME + "," + ACCOUNTS_TYPE + ", "
+ ACCOUNTS_PREVIOUS_NAME + ", " + ACCOUNTS_LAST_AUTHENTICATE_TIME_EPOCH_MILLIS
+ ") "
+ "SELECT " + ACCOUNTS_ID + "," + ACCOUNTS_NAME + "," + ACCOUNTS_TYPE + ", "
+ ACCOUNTS_PREVIOUS_NAME + ", " + ACCOUNTS_LAST_AUTHENTICATE_TIME_EPOCH_MILLIS
+ " FROM preNDb." + TABLE_ACCOUNTS);
// Copy SHARED_ACCOUNTS
db.execSQL("INSERT INTO " + TABLE_SHARED_ACCOUNTS
+ "(" + SHARED_ACCOUNTS_ID + "," + ACCOUNTS_NAME + "," + ACCOUNTS_TYPE + ") " +
"SELECT " + SHARED_ACCOUNTS_ID + "," + ACCOUNTS_NAME + "," + ACCOUNTS_TYPE
+ " FROM preNDb." + TABLE_SHARED_ACCOUNTS);
// Copy DEBUG_TABLE
db.execSQL("INSERT INTO " + DebugDbHelper.TABLE_DEBUG
+ "(" + ACCOUNTS_ID + "," + DebugDbHelper.ACTION_TYPE + ","
+ DebugDbHelper.TIMESTAMP + "," + DebugDbHelper.CALLER_UID + ","
+ DebugDbHelper.TABLE_NAME + "," + DebugDbHelper.KEY + ") " +
"SELECT " + ACCOUNTS_ID + "," + DebugDbHelper.ACTION_TYPE + ","
+ DebugDbHelper.TIMESTAMP + "," + DebugDbHelper.CALLER_UID + ","
+ DebugDbHelper.TABLE_NAME + "," + DebugDbHelper.KEY
+ " FROM preNDb." + DebugDbHelper.TABLE_DEBUG);
// Copy GRANTS
db.execSQL("INSERT INTO " + TABLE_GRANTS
+ "(" + GRANTS_ACCOUNTS_ID + "," + GRANTS_AUTH_TOKEN_TYPE + ","
+ GRANTS_GRANTEE_UID + ") " +
"SELECT " + GRANTS_ACCOUNTS_ID + "," + GRANTS_AUTH_TOKEN_TYPE + ","
+ GRANTS_GRANTEE_UID + " FROM preNDb." + TABLE_GRANTS);
// Copy META
db.execSQL("INSERT INTO " + TABLE_META
+ "(" + META_KEY + "," + META_VALUE + ") "
+ "SELECT " + META_KEY + "," + META_VALUE + " FROM preNDb." + TABLE_META);
db.setTransactionSuccessful();
db.endTransaction();
db.execSQL("DETACH DATABASE preNDb");
}
static DeDatabaseHelper create(Context context, int userId) {
File oldDb = new File(getPreNDatabaseName(userId));
File newDb = new File(getDeDatabaseName(userId));
boolean newDbExists = newDb.exists();
DeDatabaseHelper deDatabaseHelper = new DeDatabaseHelper(context, userId);
// If the db just created, and there is a legacy db, migrate it
if (!newDbExists && oldDb.exists()) {
// Migrate legacy db to the latest version - PRE_N_DATABASE_VERSION
PreNDatabaseHelper preNDatabaseHelper = new PreNDatabaseHelper(context, userId);
// Open the database to force upgrade if required
preNDatabaseHelper.getWritableDatabase();
preNDatabaseHelper.close();
// Move data without SPII to DE
deDatabaseHelper.migratePreNDbToDe(oldDb);
}
return deDatabaseHelper;
}
}
static class CeDatabaseHelper extends SQLiteOpenHelper {
public CeDatabaseHelper(Context context, int userId) {
super(context, getCeDatabaseName(userId), null, CE_DATABASE_VERSION);
}
/**
* This call needs to be made while the mCacheLock is held.
* @param db The database.
*/
@Override
public void onCreate(SQLiteDatabase db) {
Log.i(TAG, "Creating CE database " + getDatabaseName());
db.execSQL("CREATE TABLE " + TABLE_ACCOUNTS + " ( "
+ ACCOUNTS_ID + " INTEGER PRIMARY KEY AUTOINCREMENT, "
+ ACCOUNTS_NAME + " TEXT NOT NULL, "
+ ACCOUNTS_TYPE + " TEXT NOT NULL, "
+ ACCOUNTS_PASSWORD + " TEXT, "
+ "UNIQUE(" + ACCOUNTS_NAME + "," + ACCOUNTS_TYPE + "))");
db.execSQL("CREATE TABLE " + TABLE_AUTHTOKENS + " ( "
+ AUTHTOKENS_ID + " INTEGER PRIMARY KEY AUTOINCREMENT, "
+ AUTHTOKENS_ACCOUNTS_ID + " INTEGER NOT NULL, "
+ AUTHTOKENS_TYPE + " TEXT NOT NULL, "
+ AUTHTOKENS_AUTHTOKEN + " TEXT, "
+ "UNIQUE (" + AUTHTOKENS_ACCOUNTS_ID + "," + AUTHTOKENS_TYPE + "))");
db.execSQL("CREATE TABLE " + TABLE_EXTRAS + " ( "
+ EXTRAS_ID + " INTEGER PRIMARY KEY AUTOINCREMENT, "
+ EXTRAS_ACCOUNTS_ID + " INTEGER, "
+ EXTRAS_KEY + " TEXT NOT NULL, "
+ EXTRAS_VALUE + " TEXT, "
+ "UNIQUE(" + EXTRAS_ACCOUNTS_ID + "," + EXTRAS_KEY + "))");
createAccountsDeletionTrigger(db);
}
private void createAccountsDeletionTrigger(SQLiteDatabase db) {
db.execSQL(""
+ " CREATE TRIGGER " + TABLE_ACCOUNTS + "Delete DELETE ON " + TABLE_ACCOUNTS
+ " BEGIN"
+ " DELETE FROM " + TABLE_AUTHTOKENS
+ " WHERE " + AUTHTOKENS_ACCOUNTS_ID + "=OLD." + ACCOUNTS_ID + " ;"
+ " DELETE FROM " + TABLE_EXTRAS
+ " WHERE " + EXTRAS_ACCOUNTS_ID + "=OLD." + ACCOUNTS_ID + " ;"
+ " END");
}
@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
Log.i(TAG, "Upgrade CE from version " + oldVersion + " to version " + newVersion);
if (oldVersion == 9) {
if (Log.isLoggable(TAG, Log.VERBOSE)) {
Log.v(TAG, "onUpgrade upgrading to v10");
}
db.execSQL("DROP TABLE IF EXISTS " + TABLE_META);
db.execSQL("DROP TABLE IF EXISTS " + TABLE_SHARED_ACCOUNTS);
// Recreate the trigger, since the old one references the table to be removed
db.execSQL("DROP TRIGGER IF EXISTS " + TABLE_ACCOUNTS + "Delete");
createAccountsDeletionTrigger(db);
db.execSQL("DROP TABLE IF EXISTS " + TABLE_GRANTS);
db.execSQL("DROP TABLE IF EXISTS " + DebugDbHelper.TABLE_DEBUG);
oldVersion ++;
}
if (oldVersion != newVersion) {
Log.e(TAG, "failed to upgrade version " + oldVersion + " to version " + newVersion);
}
}
@Override
public void onOpen(SQLiteDatabase db) {
if (Log.isLoggable(TAG, Log.VERBOSE)) Log.v(TAG, "opened database " + CE_DATABASE_NAME);
}
static String findAccountPasswordByNameAndType(SQLiteDatabase db, String name,
String type) {
Cursor cursor = db.query(CE_TABLE_ACCOUNTS, new String[]{ACCOUNTS_PASSWORD},
ACCOUNTS_NAME + "=? AND " + ACCOUNTS_TYPE + "=?",
new String[]{name, type}, null, null, null);
try {
if (cursor.moveToNext()) {
return cursor.getString(0);
}
return null;
} finally {
cursor.close();
}
}
/**
* Creates a new {@code CeDatabaseHelper}. If pre-N db file is present at the old location,
* it also performs migration to the new CE database.
* @param context
* @param userId id of the user where the database is located
*/
static CeDatabaseHelper create(Context context, int userId) {
File oldDatabaseFile = new File(getPreNDatabaseName(userId));
File ceDatabaseFile = new File(getCeDatabaseName(userId));
boolean newDbExists = ceDatabaseFile.exists();
if (Log.isLoggable(TAG, Log.VERBOSE)) {
Log.v(TAG, "CeDatabaseHelper.create userId=" + userId + " oldDbExists="
+ oldDatabaseFile.exists() + " newDbExists=" + newDbExists);
}
boolean removeOldDb = false;
if (!newDbExists && oldDatabaseFile.exists()) {
removeOldDb = migratePreNDbToCe(oldDatabaseFile, ceDatabaseFile);
}
// Try to open and upgrade if necessary
CeDatabaseHelper ceHelper = new CeDatabaseHelper(context, userId);
ceHelper.getWritableDatabase();
ceHelper.close();
if (removeOldDb) {
// TODO STOPSHIP - backup file during testing. Remove file before the release
Log.i(TAG, "Migration complete - creating backup of old db " + oldDatabaseFile);
renameToBakFile(oldDatabaseFile);
}
return ceHelper;
}
private static void renameToBakFile(File file) {
File bakFile = new File(file.getPath() + ".bak");
if (!file.renameTo(bakFile)) {
Log.e(TAG, "Cannot move file to " + bakFile);
}
}
private static boolean migratePreNDbToCe(File oldDbFile, File ceDbFile) {
Log.i(TAG, "Moving pre-N DB " + oldDbFile + " to CE " + ceDbFile);
try {
FileUtils.copyFileOrThrow(oldDbFile, ceDbFile);
} catch (IOException e) {
Log.e(TAG, "Cannot copy file to " + ceDbFile + " from " + oldDbFile, e);
// Try to remove potentially damaged file if I/O error occurred
deleteDbFileWarnIfFailed(ceDbFile);
return false;
}
return true;
}
}
public IBinder onBind(@SuppressWarnings("unused") Intent intent) {
return asBinder();
}
@@ -4666,7 +5029,7 @@ public class AccountManagerService
db.endTransaction();
}
cancelNotification(getCredentialPermissionNotificationId(account, authTokenType, uid),
new UserHandle(accounts.userId));
UserHandle.of(accounts.userId));
}
}
@@ -4891,7 +5254,7 @@ public class AccountManagerService
HashMap<String, String> authTokensForAccount = accounts.authTokenCache.get(account);
if (authTokensForAccount == null) {
// need to populate the cache for this account
final SQLiteDatabase db = accounts.openHelper.getReadableDatabase();
final SQLiteDatabase db = accounts.openHelper.getReadableDatabaseUserIsUnlocked();
authTokensForAccount = readAuthTokensForAccountFromDatabaseLocked(db, account);
accounts.authTokenCache.put(account, authTokensForAccount);
}
@@ -4904,7 +5267,7 @@ public class AccountManagerService
HashMap<String, String> userDataForAccount = accounts.userDataCache.get(account);
if (userDataForAccount == null) {
// need to populate the cache for this account
final SQLiteDatabase db = accounts.openHelper.getReadableDatabase();
final SQLiteDatabase db = accounts.openHelper.getReadableDatabaseUserIsUnlocked();
userDataForAccount = readUserDataForAccountFromDatabaseLocked(db, account);
accounts.userDataCache.put(account, userDataForAccount);
}
@@ -4914,8 +5277,8 @@ public class AccountManagerService
protected HashMap<String, String> readUserDataForAccountFromDatabaseLocked(
final SQLiteDatabase db, Account account) {
HashMap<String, String> userDataForAccount = new HashMap<String, String>();
Cursor cursor = db.query(TABLE_EXTRAS,
HashMap<String, String> userDataForAccount = new HashMap<>();
Cursor cursor = db.query(CE_TABLE_EXTRAS,
COLUMNS_EXTRAS_KEY_AND_VALUE,
SELECTION_USERDATA_BY_ACCOUNT,
new String[]{account.name, account.type},
@@ -4934,8 +5297,8 @@ public class AccountManagerService
protected HashMap<String, String> readAuthTokensForAccountFromDatabaseLocked(
final SQLiteDatabase db, Account account) {
HashMap<String, String> authTokensForAccount = new HashMap<String, String>();
Cursor cursor = db.query(TABLE_AUTHTOKENS,
HashMap<String, String> authTokensForAccount = new HashMap<>();
Cursor cursor = db.query(CE_TABLE_AUTHTOKENS,
COLUMNS_AUTHTOKENS_TYPE_AND_AUTHTOKEN,
SELECTION_AUTHTOKENS_BY_ACCOUNT,
new String[]{account.name, account.type},