Merge "[FBE] Split accounts.db into two DE/CE databases" into nyc-dev
diff --git a/services/core/java/com/android/server/accounts/AccountManagerService.java b/services/core/java/com/android/server/accounts/AccountManagerService.java
index 1632f92..bb32303 100644
--- a/services/core/java/com/android/server/accounts/AccountManagerService.java
+++ b/services/core/java/com/android/server/accounts/AccountManagerService.java
@@ -65,6 +65,7 @@
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.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 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.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 @@
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 @@
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 @@
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 @@
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 @@
*/
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
}
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 @@
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 @@
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 @@
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 @@
}
}
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 @@
* 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 @@
}
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
} 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 @@
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 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 @@
}
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 @@
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 @@
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 @@
}
}
- 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 @@
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 @@
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 @@
}
}
+ /**
+ * 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 @@
}
}
+ 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 @@
db.endTransaction();
}
cancelNotification(getCredentialPermissionNotificationId(account, authTokenType, uid),
- new UserHandle(accounts.userId));
+ UserHandle.of(accounts.userId));
}
}
@@ -4891,7 +5254,7 @@
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 @@
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 @@
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 @@
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},