Home | History | Annotate | Download | only in accounts
      1 /*
      2  * Copyright (C) 2009 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 android.accounts;
     18 
     19 import android.Manifest;
     20 import android.app.ActivityManager;
     21 import android.app.Notification;
     22 import android.app.NotificationManager;
     23 import android.app.PendingIntent;
     24 import android.content.BroadcastReceiver;
     25 import android.content.ComponentName;
     26 import android.content.ContentValues;
     27 import android.content.Context;
     28 import android.content.Intent;
     29 import android.content.IntentFilter;
     30 import android.content.ServiceConnection;
     31 import android.content.pm.ApplicationInfo;
     32 import android.content.pm.PackageInfo;
     33 import android.content.pm.PackageManager;
     34 import android.content.pm.RegisteredServicesCache;
     35 import android.content.pm.RegisteredServicesCacheListener;
     36 import android.content.res.Resources;
     37 import android.database.Cursor;
     38 import android.database.DatabaseUtils;
     39 import android.database.sqlite.SQLiteDatabase;
     40 import android.database.sqlite.SQLiteOpenHelper;
     41 import android.os.Binder;
     42 import android.os.Bundle;
     43 import android.os.Environment;
     44 import android.os.Handler;
     45 import android.os.HandlerThread;
     46 import android.os.IBinder;
     47 import android.os.Looper;
     48 import android.os.Message;
     49 import android.os.RemoteException;
     50 import android.os.SystemClock;
     51 import android.text.TextUtils;
     52 import android.util.Log;
     53 import android.util.Pair;
     54 
     55 import com.android.internal.R;
     56 
     57 import java.io.File;
     58 import java.io.FileDescriptor;
     59 import java.io.PrintWriter;
     60 import java.util.ArrayList;
     61 import java.util.Arrays;
     62 import java.util.Collection;
     63 import java.util.HashMap;
     64 import java.util.LinkedHashMap;
     65 import java.util.concurrent.atomic.AtomicInteger;
     66 import java.util.concurrent.atomic.AtomicReference;
     67 
     68 /**
     69  * A system service that provides  account, password, and authtoken management for all
     70  * accounts on the device. Some of these calls are implemented with the help of the corresponding
     71  * {@link IAccountAuthenticator} services. This service is not accessed by users directly,
     72  * instead one uses an instance of {@link AccountManager}, which can be accessed as follows:
     73  *    AccountManager accountManager = AccountManager.get(context);
     74  * @hide
     75  */
     76 public class AccountManagerService
     77         extends IAccountManager.Stub
     78         implements RegisteredServicesCacheListener<AuthenticatorDescription> {
     79     private static final String TAG = "AccountManagerService";
     80 
     81     private static final int TIMEOUT_DELAY_MS = 1000 * 60;
     82     private static final String DATABASE_NAME = "accounts.db";
     83     private static final int DATABASE_VERSION = 4;
     84 
     85     private final Context mContext;
     86 
     87     private final PackageManager mPackageManager;
     88 
     89     private HandlerThread mMessageThread;
     90     private final MessageHandler mMessageHandler;
     91 
     92     // Messages that can be sent on mHandler
     93     private static final int MESSAGE_TIMED_OUT = 3;
     94 
     95     private final IAccountAuthenticatorCache mAuthenticatorCache;
     96     private final DatabaseHelper mOpenHelper;
     97 
     98     private static final String TABLE_ACCOUNTS = "accounts";
     99     private static final String ACCOUNTS_ID = "_id";
    100     private static final String ACCOUNTS_NAME = "name";
    101     private static final String ACCOUNTS_TYPE = "type";
    102     private static final String ACCOUNTS_TYPE_COUNT = "count(type)";
    103     private static final String ACCOUNTS_PASSWORD = "password";
    104 
    105     private static final String TABLE_AUTHTOKENS = "authtokens";
    106     private static final String AUTHTOKENS_ID = "_id";
    107     private static final String AUTHTOKENS_ACCOUNTS_ID = "accounts_id";
    108     private static final String AUTHTOKENS_TYPE = "type";
    109     private static final String AUTHTOKENS_AUTHTOKEN = "authtoken";
    110 
    111     private static final String TABLE_GRANTS = "grants";
    112     private static final String GRANTS_ACCOUNTS_ID = "accounts_id";
    113     private static final String GRANTS_AUTH_TOKEN_TYPE = "auth_token_type";
    114     private static final String GRANTS_GRANTEE_UID = "uid";
    115 
    116     private static final String TABLE_EXTRAS = "extras";
    117     private static final String EXTRAS_ID = "_id";
    118     private static final String EXTRAS_ACCOUNTS_ID = "accounts_id";
    119     private static final String EXTRAS_KEY = "key";
    120     private static final String EXTRAS_VALUE = "value";
    121 
    122     private static final String TABLE_META = "meta";
    123     private static final String META_KEY = "key";
    124     private static final String META_VALUE = "value";
    125 
    126     private static final String[] ACCOUNT_TYPE_COUNT_PROJECTION =
    127             new String[] { ACCOUNTS_TYPE, ACCOUNTS_TYPE_COUNT};
    128     private static final Intent ACCOUNTS_CHANGED_INTENT;
    129 
    130     private static final String COUNT_OF_MATCHING_GRANTS = ""
    131             + "SELECT COUNT(*) FROM " + TABLE_GRANTS + ", " + TABLE_ACCOUNTS
    132             + " WHERE " + GRANTS_ACCOUNTS_ID + "=" + ACCOUNTS_ID
    133             + " AND " + GRANTS_GRANTEE_UID + "=?"
    134             + " AND " + GRANTS_AUTH_TOKEN_TYPE + "=?"
    135             + " AND " + ACCOUNTS_NAME + "=?"
    136             + " AND " + ACCOUNTS_TYPE + "=?";
    137 
    138     private static final String SELECTION_AUTHTOKENS_BY_ACCOUNT =
    139             AUTHTOKENS_ACCOUNTS_ID + "=(select _id FROM accounts WHERE name=? AND type=?)";
    140     private static final String[] COLUMNS_AUTHTOKENS_TYPE_AND_AUTHTOKEN = {AUTHTOKENS_TYPE,
    141             AUTHTOKENS_AUTHTOKEN};
    142 
    143     private static final String SELECTION_USERDATA_BY_ACCOUNT =
    144             EXTRAS_ACCOUNTS_ID + "=(select _id FROM accounts WHERE name=? AND type=?)";
    145     private static final String[] COLUMNS_EXTRAS_KEY_AND_VALUE = {EXTRAS_KEY, EXTRAS_VALUE};
    146 
    147     private final LinkedHashMap<String, Session> mSessions = new LinkedHashMap<String, Session>();
    148     private final AtomicInteger mNotificationIds = new AtomicInteger(1);
    149 
    150     private final HashMap<Pair<Pair<Account, String>, Integer>, Integer>
    151             mCredentialsPermissionNotificationIds =
    152             new HashMap<Pair<Pair<Account, String>, Integer>, Integer>();
    153     private final HashMap<Account, Integer> mSigninRequiredNotificationIds =
    154             new HashMap<Account, Integer>();
    155     private static AtomicReference<AccountManagerService> sThis =
    156             new AtomicReference<AccountManagerService>();
    157 
    158     private static final Account[] EMPTY_ACCOUNT_ARRAY = new Account[]{};
    159 
    160     static {
    161         ACCOUNTS_CHANGED_INTENT = new Intent(AccountManager.LOGIN_ACCOUNTS_CHANGED_ACTION);
    162         ACCOUNTS_CHANGED_INTENT.setFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
    163     }
    164 
    165     private final Object mCacheLock = new Object();
    166     /** protected by the {@link #mCacheLock} */
    167     private final HashMap<String, Account[]> mAccountCache = new HashMap<String, Account[]>();
    168     /** protected by the {@link #mCacheLock} */
    169     private HashMap<Account, HashMap<String, String>> mUserDataCache =
    170             new HashMap<Account, HashMap<String, String>>();
    171     /** protected by the {@link #mCacheLock} */
    172     private HashMap<Account, HashMap<String, String>> mAuthTokenCache =
    173             new HashMap<Account, HashMap<String, String>>();
    174 
    175     /**
    176      * This should only be called by system code. One should only call this after the service
    177      * has started.
    178      * @return a reference to the AccountManagerService instance
    179      * @hide
    180      */
    181     public static AccountManagerService getSingleton() {
    182         return sThis.get();
    183     }
    184 
    185     public AccountManagerService(Context context) {
    186         this(context, context.getPackageManager(), new AccountAuthenticatorCache(context));
    187     }
    188 
    189     public AccountManagerService(Context context, PackageManager packageManager,
    190             IAccountAuthenticatorCache authenticatorCache) {
    191         mContext = context;
    192         mPackageManager = packageManager;
    193 
    194         synchronized (mCacheLock) {
    195             mOpenHelper = new DatabaseHelper(mContext);
    196         }
    197 
    198         mMessageThread = new HandlerThread("AccountManagerService");
    199         mMessageThread.start();
    200         mMessageHandler = new MessageHandler(mMessageThread.getLooper());
    201 
    202         mAuthenticatorCache = authenticatorCache;
    203         mAuthenticatorCache.setListener(this, null /* Handler */);
    204 
    205         sThis.set(this);
    206 
    207         IntentFilter intentFilter = new IntentFilter();
    208         intentFilter.addAction(Intent.ACTION_PACKAGE_REMOVED);
    209         intentFilter.addDataScheme("package");
    210         mContext.registerReceiver(new BroadcastReceiver() {
    211             @Override
    212             public void onReceive(Context context1, Intent intent) {
    213                 purgeOldGrants();
    214             }
    215         }, intentFilter);
    216         purgeOldGrants();
    217 
    218         validateAccountsAndPopulateCache();
    219     }
    220 
    221     private void purgeOldGrants() {
    222         synchronized (mCacheLock) {
    223             final SQLiteDatabase db = mOpenHelper.getWritableDatabase();
    224             final Cursor cursor = db.query(TABLE_GRANTS,
    225                     new String[]{GRANTS_GRANTEE_UID},
    226                     null, null, GRANTS_GRANTEE_UID, null, null);
    227             try {
    228                 while (cursor.moveToNext()) {
    229                     final int uid = cursor.getInt(0);
    230                     final boolean packageExists = mPackageManager.getPackagesForUid(uid) != null;
    231                     if (packageExists) {
    232                         continue;
    233                     }
    234                     Log.d(TAG, "deleting grants for UID " + uid
    235                             + " because its package is no longer installed");
    236                     db.delete(TABLE_GRANTS, GRANTS_GRANTEE_UID + "=?",
    237                             new String[]{Integer.toString(uid)});
    238                 }
    239             } finally {
    240                 cursor.close();
    241             }
    242         }
    243     }
    244 
    245     private void validateAccountsAndPopulateCache() {
    246         synchronized (mCacheLock) {
    247             final SQLiteDatabase db = mOpenHelper.getWritableDatabase();
    248             boolean accountDeleted = false;
    249             Cursor cursor = db.query(TABLE_ACCOUNTS,
    250                     new String[]{ACCOUNTS_ID, ACCOUNTS_TYPE, ACCOUNTS_NAME},
    251                     null, null, null, null, null);
    252             try {
    253                 mAccountCache.clear();
    254                 final HashMap<String, ArrayList<String>> accountNamesByType =
    255                         new HashMap<String, ArrayList<String>>();
    256                 while (cursor.moveToNext()) {
    257                     final long accountId = cursor.getLong(0);
    258                     final String accountType = cursor.getString(1);
    259                     final String accountName = cursor.getString(2);
    260                     if (mAuthenticatorCache.getServiceInfo(
    261                             AuthenticatorDescription.newKey(accountType)) == null) {
    262                         Log.d(TAG, "deleting account " + accountName + " because type "
    263                                 + accountType + " no longer has a registered authenticator");
    264                         db.delete(TABLE_ACCOUNTS, ACCOUNTS_ID + "=" + accountId, null);
    265                         accountDeleted = true;
    266                         final Account account = new Account(accountName, accountType);
    267                         mUserDataCache.remove(account);
    268                         mAuthTokenCache.remove(account);
    269                     } else {
    270                         ArrayList<String> accountNames = accountNamesByType.get(accountType);
    271                         if (accountNames == null) {
    272                             accountNames = new ArrayList<String>();
    273                             accountNamesByType.put(accountType, accountNames);
    274                         }
    275                         accountNames.add(accountName);
    276                     }
    277                 }
    278                 for (HashMap.Entry<String, ArrayList<String>> cur
    279                         : accountNamesByType.entrySet()) {
    280                     final String accountType = cur.getKey();
    281                     final ArrayList<String> accountNames = cur.getValue();
    282                     final Account[] accountsForType = new Account[accountNames.size()];
    283                     int i = 0;
    284                     for (String accountName : accountNames) {
    285                         accountsForType[i] = new Account(accountName, accountType);
    286                         ++i;
    287                     }
    288                     mAccountCache.put(accountType, accountsForType);
    289                 }
    290             } finally {
    291                 cursor.close();
    292                 if (accountDeleted) {
    293                     sendAccountsChangedBroadcast();
    294                 }
    295             }
    296         }
    297     }
    298 
    299     public void onServiceChanged(AuthenticatorDescription desc, boolean removed) {
    300         validateAccountsAndPopulateCache();
    301     }
    302 
    303     public String getPassword(Account account) {
    304         if (Log.isLoggable(TAG, Log.VERBOSE)) {
    305             Log.v(TAG, "getPassword: " + account
    306                     + ", caller's uid " + Binder.getCallingUid()
    307                     + ", pid " + Binder.getCallingPid());
    308         }
    309         if (account == null) throw new IllegalArgumentException("account is null");
    310         checkAuthenticateAccountsPermission(account);
    311 
    312         long identityToken = clearCallingIdentity();
    313         try {
    314             return readPasswordInternal(account);
    315         } finally {
    316             restoreCallingIdentity(identityToken);
    317         }
    318     }
    319 
    320     private String readPasswordInternal(Account account) {
    321         if (account == null) {
    322             return null;
    323         }
    324 
    325         synchronized (mCacheLock) {
    326             final SQLiteDatabase db = mOpenHelper.getReadableDatabase();
    327             Cursor cursor = db.query(TABLE_ACCOUNTS, new String[]{ACCOUNTS_PASSWORD},
    328                     ACCOUNTS_NAME + "=? AND " + ACCOUNTS_TYPE+ "=?",
    329                     new String[]{account.name, account.type}, null, null, null);
    330             try {
    331                 if (cursor.moveToNext()) {
    332                     return cursor.getString(0);
    333                 }
    334                 return null;
    335             } finally {
    336                 cursor.close();
    337             }
    338         }
    339     }
    340 
    341     public String getUserData(Account account, String key) {
    342         if (Log.isLoggable(TAG, Log.VERBOSE)) {
    343             Log.v(TAG, "getUserData: " + account
    344                     + ", key " + key
    345                     + ", caller's uid " + Binder.getCallingUid()
    346                     + ", pid " + Binder.getCallingPid());
    347         }
    348         if (account == null) throw new IllegalArgumentException("account is null");
    349         if (key == null) throw new IllegalArgumentException("key is null");
    350         checkAuthenticateAccountsPermission(account);
    351         long identityToken = clearCallingIdentity();
    352         try {
    353             return readUserDataInternal(account, key);
    354         } finally {
    355             restoreCallingIdentity(identityToken);
    356         }
    357     }
    358 
    359     public AuthenticatorDescription[] getAuthenticatorTypes() {
    360         if (Log.isLoggable(TAG, Log.VERBOSE)) {
    361             Log.v(TAG, "getAuthenticatorTypes: "
    362                     + "caller's uid " + Binder.getCallingUid()
    363                     + ", pid " + Binder.getCallingPid());
    364         }
    365         long identityToken = clearCallingIdentity();
    366         try {
    367             Collection<AccountAuthenticatorCache.ServiceInfo<AuthenticatorDescription>>
    368                     authenticatorCollection = mAuthenticatorCache.getAllServices();
    369             AuthenticatorDescription[] types =
    370                     new AuthenticatorDescription[authenticatorCollection.size()];
    371             int i = 0;
    372             for (AccountAuthenticatorCache.ServiceInfo<AuthenticatorDescription> authenticator
    373                     : authenticatorCollection) {
    374                 types[i] = authenticator.type;
    375                 i++;
    376             }
    377             return types;
    378         } finally {
    379             restoreCallingIdentity(identityToken);
    380         }
    381     }
    382 
    383     public boolean addAccount(Account account, String password, Bundle extras) {
    384         if (Log.isLoggable(TAG, Log.VERBOSE)) {
    385             Log.v(TAG, "addAccount: " + account
    386                     + ", caller's uid " + Binder.getCallingUid()
    387                     + ", pid " + Binder.getCallingPid());
    388         }
    389         if (account == null) throw new IllegalArgumentException("account is null");
    390         checkAuthenticateAccountsPermission(account);
    391 
    392         // fails if the account already exists
    393         long identityToken = clearCallingIdentity();
    394         try {
    395             return addAccountInternal(account, password, extras);
    396         } finally {
    397             restoreCallingIdentity(identityToken);
    398         }
    399     }
    400 
    401     private boolean addAccountInternal(Account account, String password, Bundle extras) {
    402         if (account == null) {
    403             return false;
    404         }
    405         synchronized (mCacheLock) {
    406             final SQLiteDatabase db = mOpenHelper.getWritableDatabase();
    407             db.beginTransaction();
    408             try {
    409                 long numMatches = DatabaseUtils.longForQuery(db,
    410                         "select count(*) from " + TABLE_ACCOUNTS
    411                                 + " WHERE " + ACCOUNTS_NAME + "=? AND " + ACCOUNTS_TYPE+ "=?",
    412                         new String[]{account.name, account.type});
    413                 if (numMatches > 0) {
    414                     Log.w(TAG, "insertAccountIntoDatabase: " + account
    415                             + ", skipping since the account already exists");
    416                     return false;
    417                 }
    418                 ContentValues values = new ContentValues();
    419                 values.put(ACCOUNTS_NAME, account.name);
    420                 values.put(ACCOUNTS_TYPE, account.type);
    421                 values.put(ACCOUNTS_PASSWORD, password);
    422                 long accountId = db.insert(TABLE_ACCOUNTS, ACCOUNTS_NAME, values);
    423                 if (accountId < 0) {
    424                     Log.w(TAG, "insertAccountIntoDatabase: " + account
    425                             + ", skipping the DB insert failed");
    426                     return false;
    427                 }
    428                 if (extras != null) {
    429                     for (String key : extras.keySet()) {
    430                         final String value = extras.getString(key);
    431                         if (insertExtraLocked(db, accountId, key, value) < 0) {
    432                             Log.w(TAG, "insertAccountIntoDatabase: " + account
    433                                     + ", skipping since insertExtra failed for key " + key);
    434                             return false;
    435                         }
    436                     }
    437                 }
    438                 db.setTransactionSuccessful();
    439                 insertAccountIntoCacheLocked(account);
    440             } finally {
    441                 db.endTransaction();
    442             }
    443             sendAccountsChangedBroadcast();
    444             return true;
    445         }
    446     }
    447 
    448     private long insertExtraLocked(SQLiteDatabase db, long accountId, String key, String value) {
    449         ContentValues values = new ContentValues();
    450         values.put(EXTRAS_KEY, key);
    451         values.put(EXTRAS_ACCOUNTS_ID, accountId);
    452         values.put(EXTRAS_VALUE, value);
    453         return db.insert(TABLE_EXTRAS, EXTRAS_KEY, values);
    454     }
    455 
    456     public void hasFeatures(IAccountManagerResponse response,
    457             Account account, String[] features) {
    458         if (Log.isLoggable(TAG, Log.VERBOSE)) {
    459             Log.v(TAG, "hasFeatures: " + account
    460                     + ", response " + response
    461                     + ", features " + stringArrayToString(features)
    462                     + ", caller's uid " + Binder.getCallingUid()
    463                     + ", pid " + Binder.getCallingPid());
    464         }
    465         if (response == null) throw new IllegalArgumentException("response is null");
    466         if (account == null) throw new IllegalArgumentException("account is null");
    467         if (features == null) throw new IllegalArgumentException("features is null");
    468         checkReadAccountsPermission();
    469         long identityToken = clearCallingIdentity();
    470         try {
    471             new TestFeaturesSession(response, account, features).bind();
    472         } finally {
    473             restoreCallingIdentity(identityToken);
    474         }
    475     }
    476 
    477     private class TestFeaturesSession extends Session {
    478         private final String[] mFeatures;
    479         private final Account mAccount;
    480 
    481         public TestFeaturesSession(IAccountManagerResponse response,
    482                 Account account, String[] features) {
    483             super(response, account.type, false /* expectActivityLaunch */,
    484                     true /* stripAuthTokenFromResult */);
    485             mFeatures = features;
    486             mAccount = account;
    487         }
    488 
    489         public void run() throws RemoteException {
    490             try {
    491                 mAuthenticator.hasFeatures(this, mAccount, mFeatures);
    492             } catch (RemoteException e) {
    493                 onError(AccountManager.ERROR_CODE_REMOTE_EXCEPTION, "remote exception");
    494             }
    495         }
    496 
    497         public void onResult(Bundle result) {
    498             IAccountManagerResponse response = getResponseAndClose();
    499             if (response != null) {
    500                 try {
    501                     if (result == null) {
    502                         response.onError(AccountManager.ERROR_CODE_INVALID_RESPONSE, "null bundle");
    503                         return;
    504                     }
    505                     if (Log.isLoggable(TAG, Log.VERBOSE)) {
    506                         Log.v(TAG, getClass().getSimpleName() + " calling onResult() on response "
    507                                 + response);
    508                     }
    509                     final Bundle newResult = new Bundle();
    510                     newResult.putBoolean(AccountManager.KEY_BOOLEAN_RESULT,
    511                             result.getBoolean(AccountManager.KEY_BOOLEAN_RESULT, false));
    512                     response.onResult(newResult);
    513                 } catch (RemoteException e) {
    514                     // if the caller is dead then there is no one to care about remote exceptions
    515                     if (Log.isLoggable(TAG, Log.VERBOSE)) {
    516                         Log.v(TAG, "failure while notifying response", e);
    517                     }
    518                 }
    519             }
    520         }
    521 
    522         protected String toDebugString(long now) {
    523             return super.toDebugString(now) + ", hasFeatures"
    524                     + ", " + mAccount
    525                     + ", " + (mFeatures != null ? TextUtils.join(",", mFeatures) : null);
    526         }
    527     }
    528 
    529     public void removeAccount(IAccountManagerResponse response, Account account) {
    530         if (Log.isLoggable(TAG, Log.VERBOSE)) {
    531             Log.v(TAG, "removeAccount: " + account
    532                     + ", response " + response
    533                     + ", caller's uid " + Binder.getCallingUid()
    534                     + ", pid " + Binder.getCallingPid());
    535         }
    536         if (response == null) throw new IllegalArgumentException("response is null");
    537         if (account == null) throw new IllegalArgumentException("account is null");
    538         checkManageAccountsPermission();
    539         long identityToken = clearCallingIdentity();
    540 
    541         cancelNotification(getSigninRequiredNotificationId(account));
    542         synchronized(mCredentialsPermissionNotificationIds) {
    543             for (Pair<Pair<Account, String>, Integer> pair:
    544                 mCredentialsPermissionNotificationIds.keySet()) {
    545                 if (account.equals(pair.first.first)) {
    546                     int id = mCredentialsPermissionNotificationIds.get(pair);
    547                     cancelNotification(id);
    548                 }
    549             }
    550         }
    551 
    552         try {
    553             new RemoveAccountSession(response, account).bind();
    554         } finally {
    555             restoreCallingIdentity(identityToken);
    556         }
    557     }
    558 
    559     private class RemoveAccountSession extends Session {
    560         final Account mAccount;
    561         public RemoveAccountSession(IAccountManagerResponse response, Account account) {
    562             super(response, account.type, false /* expectActivityLaunch */,
    563                     true /* stripAuthTokenFromResult */);
    564             mAccount = account;
    565         }
    566 
    567         protected String toDebugString(long now) {
    568             return super.toDebugString(now) + ", removeAccount"
    569                     + ", account " + mAccount;
    570         }
    571 
    572         public void run() throws RemoteException {
    573             mAuthenticator.getAccountRemovalAllowed(this, mAccount);
    574         }
    575 
    576         public void onResult(Bundle result) {
    577             if (result != null && result.containsKey(AccountManager.KEY_BOOLEAN_RESULT)
    578                     && !result.containsKey(AccountManager.KEY_INTENT)) {
    579                 final boolean removalAllowed = result.getBoolean(AccountManager.KEY_BOOLEAN_RESULT);
    580                 if (removalAllowed) {
    581                     removeAccountInternal(mAccount);
    582                 }
    583                 IAccountManagerResponse response = getResponseAndClose();
    584                 if (response != null) {
    585                     if (Log.isLoggable(TAG, Log.VERBOSE)) {
    586                         Log.v(TAG, getClass().getSimpleName() + " calling onResult() on response "
    587                                 + response);
    588                     }
    589                     Bundle result2 = new Bundle();
    590                     result2.putBoolean(AccountManager.KEY_BOOLEAN_RESULT, removalAllowed);
    591                     try {
    592                         response.onResult(result2);
    593                     } catch (RemoteException e) {
    594                         // ignore
    595                     }
    596                 }
    597             }
    598             super.onResult(result);
    599         }
    600     }
    601 
    602     protected void removeAccountInternal(Account account) {
    603         synchronized (mCacheLock) {
    604             final SQLiteDatabase db = mOpenHelper.getWritableDatabase();
    605             db.delete(TABLE_ACCOUNTS, ACCOUNTS_NAME + "=? AND " + ACCOUNTS_TYPE+ "=?",
    606                     new String[]{account.name, account.type});
    607             removeAccountFromCacheLocked(account);
    608             sendAccountsChangedBroadcast();
    609         }
    610     }
    611 
    612     public void invalidateAuthToken(String accountType, String authToken) {
    613         if (Log.isLoggable(TAG, Log.VERBOSE)) {
    614             Log.v(TAG, "invalidateAuthToken: accountType " + accountType
    615                     + ", caller's uid " + Binder.getCallingUid()
    616                     + ", pid " + Binder.getCallingPid());
    617         }
    618         if (accountType == null) throw new IllegalArgumentException("accountType is null");
    619         if (authToken == null) throw new IllegalArgumentException("authToken is null");
    620         checkManageAccountsOrUseCredentialsPermissions();
    621         long identityToken = clearCallingIdentity();
    622         try {
    623             synchronized (mCacheLock) {
    624                 final SQLiteDatabase db = mOpenHelper.getWritableDatabase();
    625                 db.beginTransaction();
    626                 try {
    627                     invalidateAuthTokenLocked(db, accountType, authToken);
    628                     db.setTransactionSuccessful();
    629                 } finally {
    630                     db.endTransaction();
    631                 }
    632             }
    633         } finally {
    634             restoreCallingIdentity(identityToken);
    635         }
    636     }
    637 
    638     private void invalidateAuthTokenLocked(SQLiteDatabase db, String accountType, String authToken) {
    639         if (authToken == null || accountType == null) {
    640             return;
    641         }
    642         Cursor cursor = db.rawQuery(
    643                 "SELECT " + TABLE_AUTHTOKENS + "." + AUTHTOKENS_ID
    644                         + ", " + TABLE_ACCOUNTS + "." + ACCOUNTS_NAME
    645                         + ", " + TABLE_AUTHTOKENS + "." + AUTHTOKENS_TYPE
    646                         + " FROM " + TABLE_ACCOUNTS
    647                         + " JOIN " + TABLE_AUTHTOKENS
    648                         + " ON " + TABLE_ACCOUNTS + "." + ACCOUNTS_ID
    649                         + " = " + AUTHTOKENS_ACCOUNTS_ID
    650                         + " WHERE " + AUTHTOKENS_AUTHTOKEN + " = ? AND "
    651                         + TABLE_ACCOUNTS + "." + ACCOUNTS_TYPE + " = ?",
    652                 new String[]{authToken, accountType});
    653         try {
    654             while (cursor.moveToNext()) {
    655                 long authTokenId = cursor.getLong(0);
    656                 String accountName = cursor.getString(1);
    657                 String authTokenType = cursor.getString(2);
    658                 db.delete(TABLE_AUTHTOKENS, AUTHTOKENS_ID + "=" + authTokenId, null);
    659                 writeAuthTokenIntoCacheLocked(db, new Account(accountName, accountType),
    660                         authTokenType, null);
    661             }
    662         } finally {
    663             cursor.close();
    664         }
    665     }
    666 
    667     private boolean saveAuthTokenToDatabase(Account account, String type, String authToken) {
    668         if (account == null || type == null) {
    669             return false;
    670         }
    671         cancelNotification(getSigninRequiredNotificationId(account));
    672         synchronized (mCacheLock) {
    673             final SQLiteDatabase db = mOpenHelper.getWritableDatabase();
    674             db.beginTransaction();
    675             try {
    676                 long accountId = getAccountIdLocked(db, account);
    677                 if (accountId < 0) {
    678                     return false;
    679                 }
    680                 db.delete(TABLE_AUTHTOKENS,
    681                         AUTHTOKENS_ACCOUNTS_ID + "=" + accountId + " AND " + AUTHTOKENS_TYPE + "=?",
    682                         new String[]{type});
    683                 ContentValues values = new ContentValues();
    684                 values.put(AUTHTOKENS_ACCOUNTS_ID, accountId);
    685                 values.put(AUTHTOKENS_TYPE, type);
    686                 values.put(AUTHTOKENS_AUTHTOKEN, authToken);
    687                 if (db.insert(TABLE_AUTHTOKENS, AUTHTOKENS_AUTHTOKEN, values) >= 0) {
    688                     db.setTransactionSuccessful();
    689                     writeAuthTokenIntoCacheLocked(db, account, type, authToken);
    690                     return true;
    691                 }
    692                 return false;
    693             } finally {
    694                 db.endTransaction();
    695             }
    696         }
    697     }
    698 
    699     public String peekAuthToken(Account account, String authTokenType) {
    700         if (Log.isLoggable(TAG, Log.VERBOSE)) {
    701             Log.v(TAG, "peekAuthToken: " + account
    702                     + ", authTokenType " + authTokenType
    703                     + ", caller's uid " + Binder.getCallingUid()
    704                     + ", pid " + Binder.getCallingPid());
    705         }
    706         if (account == null) throw new IllegalArgumentException("account is null");
    707         if (authTokenType == null) throw new IllegalArgumentException("authTokenType is null");
    708         checkAuthenticateAccountsPermission(account);
    709         long identityToken = clearCallingIdentity();
    710         try {
    711             return readAuthTokenInternal(account, authTokenType);
    712         } finally {
    713             restoreCallingIdentity(identityToken);
    714         }
    715     }
    716 
    717     public void setAuthToken(Account account, String authTokenType, String authToken) {
    718         if (Log.isLoggable(TAG, Log.VERBOSE)) {
    719             Log.v(TAG, "setAuthToken: " + account
    720                     + ", authTokenType " + authTokenType
    721                     + ", caller's uid " + Binder.getCallingUid()
    722                     + ", pid " + Binder.getCallingPid());
    723         }
    724         if (account == null) throw new IllegalArgumentException("account is null");
    725         if (authTokenType == null) throw new IllegalArgumentException("authTokenType is null");
    726         checkAuthenticateAccountsPermission(account);
    727         long identityToken = clearCallingIdentity();
    728         try {
    729             saveAuthTokenToDatabase(account, authTokenType, authToken);
    730         } finally {
    731             restoreCallingIdentity(identityToken);
    732         }
    733     }
    734 
    735     public void setPassword(Account account, String password) {
    736         if (Log.isLoggable(TAG, Log.VERBOSE)) {
    737             Log.v(TAG, "setAuthToken: " + account
    738                     + ", caller's uid " + Binder.getCallingUid()
    739                     + ", pid " + Binder.getCallingPid());
    740         }
    741         if (account == null) throw new IllegalArgumentException("account is null");
    742         checkAuthenticateAccountsPermission(account);
    743         long identityToken = clearCallingIdentity();
    744         try {
    745             setPasswordInternal(account, password);
    746         } finally {
    747             restoreCallingIdentity(identityToken);
    748         }
    749     }
    750 
    751     private void setPasswordInternal(Account account, String password) {
    752         if (account == null) {
    753             return;
    754         }
    755         synchronized (mCacheLock) {
    756             final SQLiteDatabase db = mOpenHelper.getWritableDatabase();
    757             db.beginTransaction();
    758             try {
    759                 final ContentValues values = new ContentValues();
    760                 values.put(ACCOUNTS_PASSWORD, password);
    761                 final long accountId = getAccountIdLocked(db, account);
    762                 if (accountId >= 0) {
    763                     final String[] argsAccountId = {String.valueOf(accountId)};
    764                     db.update(TABLE_ACCOUNTS, values, ACCOUNTS_ID + "=?", argsAccountId);
    765                     db.delete(TABLE_AUTHTOKENS, AUTHTOKENS_ACCOUNTS_ID + "=?", argsAccountId);
    766                     mAuthTokenCache.remove(account);
    767                     db.setTransactionSuccessful();
    768                 }
    769             } finally {
    770                 db.endTransaction();
    771             }
    772             sendAccountsChangedBroadcast();
    773         }
    774     }
    775 
    776     private void sendAccountsChangedBroadcast() {
    777         Log.i(TAG, "the accounts changed, sending broadcast of "
    778                 + ACCOUNTS_CHANGED_INTENT.getAction());
    779         mContext.sendBroadcast(ACCOUNTS_CHANGED_INTENT);
    780     }
    781 
    782     public void clearPassword(Account account) {
    783         if (Log.isLoggable(TAG, Log.VERBOSE)) {
    784             Log.v(TAG, "clearPassword: " + account
    785                     + ", caller's uid " + Binder.getCallingUid()
    786                     + ", pid " + Binder.getCallingPid());
    787         }
    788         if (account == null) throw new IllegalArgumentException("account is null");
    789         checkManageAccountsPermission();
    790         long identityToken = clearCallingIdentity();
    791         try {
    792             setPasswordInternal(account, null);
    793         } finally {
    794             restoreCallingIdentity(identityToken);
    795         }
    796     }
    797 
    798     public void setUserData(Account account, String key, String value) {
    799         if (Log.isLoggable(TAG, Log.VERBOSE)) {
    800             Log.v(TAG, "setUserData: " + account
    801                     + ", key " + key
    802                     + ", caller's uid " + Binder.getCallingUid()
    803                     + ", pid " + Binder.getCallingPid());
    804         }
    805         if (key == null) throw new IllegalArgumentException("key is null");
    806         if (account == null) throw new IllegalArgumentException("account is null");
    807         checkAuthenticateAccountsPermission(account);
    808         long identityToken = clearCallingIdentity();
    809         try {
    810             setUserdataInternal(account, key, value);
    811         } finally {
    812             restoreCallingIdentity(identityToken);
    813         }
    814     }
    815 
    816     private void setUserdataInternal(Account account, String key, String value) {
    817         if (account == null || key == null) {
    818             return;
    819         }
    820         synchronized (mCacheLock) {
    821             final SQLiteDatabase db = mOpenHelper.getWritableDatabase();
    822             db.beginTransaction();
    823             try {
    824                 long accountId = getAccountIdLocked(db, account);
    825                 if (accountId < 0) {
    826                     return;
    827                 }
    828                 long extrasId = getExtrasIdLocked(db, accountId, key);
    829                 if (extrasId < 0 ) {
    830                     extrasId = insertExtraLocked(db, accountId, key, value);
    831                     if (extrasId < 0) {
    832                         return;
    833                     }
    834                 } else {
    835                     ContentValues values = new ContentValues();
    836                     values.put(EXTRAS_VALUE, value);
    837                     if (1 != db.update(TABLE_EXTRAS, values, EXTRAS_ID + "=" + extrasId, null)) {
    838                         return;
    839                     }
    840 
    841                 }
    842                 writeUserDataIntoCacheLocked(db, account, key, value);
    843                 db.setTransactionSuccessful();
    844             } finally {
    845                 db.endTransaction();
    846             }
    847         }
    848     }
    849 
    850     private void onResult(IAccountManagerResponse response, Bundle result) {
    851         if (result == null) {
    852             Log.e(TAG, "the result is unexpectedly null", new Exception());
    853         }
    854         if (Log.isLoggable(TAG, Log.VERBOSE)) {
    855             Log.v(TAG, getClass().getSimpleName() + " calling onResult() on response "
    856                     + response);
    857         }
    858         try {
    859             response.onResult(result);
    860         } catch (RemoteException e) {
    861             // if the caller is dead then there is no one to care about remote
    862             // exceptions
    863             if (Log.isLoggable(TAG, Log.VERBOSE)) {
    864                 Log.v(TAG, "failure while notifying response", e);
    865             }
    866         }
    867     }
    868 
    869     void getAuthTokenLabel(final IAccountManagerResponse response,
    870             final Account account, final String authTokenType) {
    871         if (account == null) throw new IllegalArgumentException("account is null");
    872         if (authTokenType == null) throw new IllegalArgumentException("authTokenType is null");
    873 
    874         checkBinderPermission(Manifest.permission.USE_CREDENTIALS);
    875 
    876         long identityToken = clearCallingIdentity();
    877         try {
    878             new Session(response, account.type, false,
    879                     false /* stripAuthTokenFromResult */) {
    880                 protected String toDebugString(long now) {
    881                     return super.toDebugString(now) + ", getAuthTokenLabel"
    882                             + ", " + account
    883                             + ", authTokenType " + authTokenType;
    884                 }
    885 
    886                 public void run() throws RemoteException {
    887                     mAuthenticator.getAuthTokenLabel(this, authTokenType);
    888                 }
    889 
    890                 public void onResult(Bundle result) {
    891                     if (result != null) {
    892                         String label = result.getString(AccountManager.KEY_AUTH_TOKEN_LABEL);
    893                         Bundle bundle = new Bundle();
    894                         bundle.putString(AccountManager.KEY_AUTH_TOKEN_LABEL, label);
    895                         super.onResult(bundle);
    896                         return;
    897                     } else {
    898                         super.onResult(result);
    899                     }
    900                 }
    901             }.bind();
    902         } finally {
    903             restoreCallingIdentity(identityToken);
    904         }
    905     }
    906 
    907     public void getAuthToken(IAccountManagerResponse response, final Account account,
    908             final String authTokenType, final boolean notifyOnAuthFailure,
    909             final boolean expectActivityLaunch, Bundle loginOptionsIn) {
    910         if (Log.isLoggable(TAG, Log.VERBOSE)) {
    911             Log.v(TAG, "getAuthToken: " + account
    912                     + ", response " + response
    913                     + ", authTokenType " + authTokenType
    914                     + ", notifyOnAuthFailure " + notifyOnAuthFailure
    915                     + ", expectActivityLaunch " + expectActivityLaunch
    916                     + ", caller's uid " + Binder.getCallingUid()
    917                     + ", pid " + Binder.getCallingPid());
    918         }
    919         if (response == null) throw new IllegalArgumentException("response is null");
    920         if (account == null) throw new IllegalArgumentException("account is null");
    921         if (authTokenType == null) throw new IllegalArgumentException("authTokenType is null");
    922         checkBinderPermission(Manifest.permission.USE_CREDENTIALS);
    923         AccountAuthenticatorCache.ServiceInfo<AuthenticatorDescription> authenticatorInfo =
    924             mAuthenticatorCache.getServiceInfo(
    925                     AuthenticatorDescription.newKey(account.type));
    926         final boolean customTokens =
    927             authenticatorInfo != null && authenticatorInfo.type.customTokens;
    928 
    929         // skip the check if customTokens
    930         final int callerUid = Binder.getCallingUid();
    931         final boolean permissionGranted = customTokens ||
    932             permissionIsGranted(account, authTokenType, callerUid);
    933 
    934         final Bundle loginOptions = (loginOptionsIn == null) ? new Bundle() :
    935             loginOptionsIn;
    936         // let authenticator know the identity of the caller
    937         loginOptions.putInt(AccountManager.KEY_CALLER_UID, callerUid);
    938         loginOptions.putInt(AccountManager.KEY_CALLER_PID, Binder.getCallingPid());
    939         if (notifyOnAuthFailure) {
    940             loginOptions.putBoolean(AccountManager.KEY_NOTIFY_ON_FAILURE, true);
    941         }
    942 
    943         long identityToken = clearCallingIdentity();
    944         try {
    945             // if the caller has permission, do the peek. otherwise go the more expensive
    946             // route of starting a Session
    947             if (!customTokens && permissionGranted) {
    948                 String authToken = readAuthTokenInternal(account, authTokenType);
    949                 if (authToken != null) {
    950                     Bundle result = new Bundle();
    951                     result.putString(AccountManager.KEY_AUTHTOKEN, authToken);
    952                     result.putString(AccountManager.KEY_ACCOUNT_NAME, account.name);
    953                     result.putString(AccountManager.KEY_ACCOUNT_TYPE, account.type);
    954                     onResult(response, result);
    955                     return;
    956                 }
    957             }
    958 
    959             new Session(response, account.type, expectActivityLaunch,
    960                     false /* stripAuthTokenFromResult */) {
    961                 protected String toDebugString(long now) {
    962                     if (loginOptions != null) loginOptions.keySet();
    963                     return super.toDebugString(now) + ", getAuthToken"
    964                             + ", " + account
    965                             + ", authTokenType " + authTokenType
    966                             + ", loginOptions " + loginOptions
    967                             + ", notifyOnAuthFailure " + notifyOnAuthFailure;
    968                 }
    969 
    970                 public void run() throws RemoteException {
    971                     // If the caller doesn't have permission then create and return the
    972                     // "grant permission" intent instead of the "getAuthToken" intent.
    973                     if (!permissionGranted) {
    974                         mAuthenticator.getAuthTokenLabel(this, authTokenType);
    975                     } else {
    976                         mAuthenticator.getAuthToken(this, account, authTokenType, loginOptions);
    977                     }
    978                 }
    979 
    980                 public void onResult(Bundle result) {
    981                     if (result != null) {
    982                         if (result.containsKey(AccountManager.KEY_AUTH_TOKEN_LABEL)) {
    983                             Intent intent = newGrantCredentialsPermissionIntent(account, callerUid,
    984                                     new AccountAuthenticatorResponse(this),
    985                                     authTokenType,
    986                                     result.getString(AccountManager.KEY_AUTH_TOKEN_LABEL));
    987                             Bundle bundle = new Bundle();
    988                             bundle.putParcelable(AccountManager.KEY_INTENT, intent);
    989                             onResult(bundle);
    990                             return;
    991                         }
    992                         String authToken = result.getString(AccountManager.KEY_AUTHTOKEN);
    993                         if (authToken != null) {
    994                             String name = result.getString(AccountManager.KEY_ACCOUNT_NAME);
    995                             String type = result.getString(AccountManager.KEY_ACCOUNT_TYPE);
    996                             if (TextUtils.isEmpty(type) || TextUtils.isEmpty(name)) {
    997                                 onError(AccountManager.ERROR_CODE_INVALID_RESPONSE,
    998                                         "the type and name should not be empty");
    999                                 return;
   1000                             }
   1001                             if (!customTokens) {
   1002                                 saveAuthTokenToDatabase(new Account(name, type),
   1003                                         authTokenType, authToken);
   1004                             }
   1005                         }
   1006 
   1007                         Intent intent = result.getParcelable(AccountManager.KEY_INTENT);
   1008                         if (intent != null && notifyOnAuthFailure && !customTokens) {
   1009                             doNotification(
   1010                                     account, result.getString(AccountManager.KEY_AUTH_FAILED_MESSAGE),
   1011                                     intent);
   1012                         }
   1013                     }
   1014                     super.onResult(result);
   1015                 }
   1016             }.bind();
   1017         } finally {
   1018             restoreCallingIdentity(identityToken);
   1019         }
   1020     }
   1021 
   1022     private void createNoCredentialsPermissionNotification(Account account, Intent intent) {
   1023         int uid = intent.getIntExtra(
   1024                 GrantCredentialsPermissionActivity.EXTRAS_REQUESTING_UID, -1);
   1025         String authTokenType = intent.getStringExtra(
   1026                 GrantCredentialsPermissionActivity.EXTRAS_AUTH_TOKEN_TYPE);
   1027         String authTokenLabel = intent.getStringExtra(
   1028                 GrantCredentialsPermissionActivity.EXTRAS_AUTH_TOKEN_LABEL);
   1029 
   1030         Notification n = new Notification(android.R.drawable.stat_sys_warning, null,
   1031                 0 /* when */);
   1032         final String titleAndSubtitle =
   1033                 mContext.getString(R.string.permission_request_notification_with_subtitle,
   1034                 account.name);
   1035         final int index = titleAndSubtitle.indexOf('\n');
   1036         String title = titleAndSubtitle;
   1037         String subtitle = "";
   1038         if (index > 0) {
   1039             title = titleAndSubtitle.substring(0, index);
   1040             subtitle = titleAndSubtitle.substring(index + 1);
   1041         }
   1042         n.setLatestEventInfo(mContext,
   1043                 title, subtitle,
   1044                 PendingIntent.getActivity(mContext, 0, intent, PendingIntent.FLAG_CANCEL_CURRENT));
   1045         installNotification(getCredentialPermissionNotificationId(account, authTokenType, uid), n);
   1046     }
   1047 
   1048     String getAccountLabel(String accountType) {
   1049         RegisteredServicesCache.ServiceInfo<AuthenticatorDescription> serviceInfo =
   1050             mAuthenticatorCache.getServiceInfo(
   1051                     AuthenticatorDescription.newKey(accountType));
   1052         if (serviceInfo == null) {
   1053             throw new IllegalArgumentException("unknown account type: " + accountType);
   1054         }
   1055 
   1056         final Context authContext;
   1057         try {
   1058             authContext = mContext.createPackageContext(
   1059                     serviceInfo.type.packageName, 0);
   1060         } catch (PackageManager.NameNotFoundException e) {
   1061             throw new IllegalArgumentException("unknown account type: " + accountType);
   1062         }
   1063         try {
   1064             return authContext.getString(serviceInfo.type.labelId);
   1065         } catch (Resources.NotFoundException e) {
   1066             throw new IllegalArgumentException("unknown account type: " + accountType);
   1067         }
   1068     }
   1069 
   1070     private Intent newGrantCredentialsPermissionIntent(Account account, int uid,
   1071             AccountAuthenticatorResponse response, String authTokenType, String authTokenLabel) {
   1072 
   1073         Intent intent = new Intent(mContext, GrantCredentialsPermissionActivity.class);
   1074         // See FLAG_ACTIVITY_NEW_TASK docs for limitations and benefits of the flag.
   1075         // Since it was set in Eclair+ we can't change it without breaking apps using
   1076         // the intent from a non-Activity context.
   1077         intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
   1078         intent.addCategory(
   1079                 String.valueOf(getCredentialPermissionNotificationId(account, authTokenType, uid)));
   1080 
   1081         intent.putExtra(GrantCredentialsPermissionActivity.EXTRAS_ACCOUNT, account);
   1082         intent.putExtra(GrantCredentialsPermissionActivity.EXTRAS_AUTH_TOKEN_TYPE, authTokenType);
   1083         intent.putExtra(GrantCredentialsPermissionActivity.EXTRAS_RESPONSE, response);
   1084         intent.putExtra(GrantCredentialsPermissionActivity.EXTRAS_REQUESTING_UID, uid);
   1085 
   1086         return intent;
   1087     }
   1088 
   1089     private Integer getCredentialPermissionNotificationId(Account account, String authTokenType,
   1090             int uid) {
   1091         Integer id;
   1092         synchronized(mCredentialsPermissionNotificationIds) {
   1093             final Pair<Pair<Account, String>, Integer> key =
   1094                     new Pair<Pair<Account, String>, Integer>(
   1095                             new Pair<Account, String>(account, authTokenType), uid);
   1096             id = mCredentialsPermissionNotificationIds.get(key);
   1097             if (id == null) {
   1098                 id = mNotificationIds.incrementAndGet();
   1099                 mCredentialsPermissionNotificationIds.put(key, id);
   1100             }
   1101         }
   1102         return id;
   1103     }
   1104 
   1105     private Integer getSigninRequiredNotificationId(Account account) {
   1106         Integer id;
   1107         synchronized(mSigninRequiredNotificationIds) {
   1108             id = mSigninRequiredNotificationIds.get(account);
   1109             if (id == null) {
   1110                 id = mNotificationIds.incrementAndGet();
   1111                 mSigninRequiredNotificationIds.put(account, id);
   1112             }
   1113         }
   1114         return id;
   1115     }
   1116 
   1117     public void addAcount(final IAccountManagerResponse response, final String accountType,
   1118             final String authTokenType, final String[] requiredFeatures,
   1119             final boolean expectActivityLaunch, final Bundle optionsIn) {
   1120         if (Log.isLoggable(TAG, Log.VERBOSE)) {
   1121             Log.v(TAG, "addAccount: accountType " + accountType
   1122                     + ", response " + response
   1123                     + ", authTokenType " + authTokenType
   1124                     + ", requiredFeatures " + stringArrayToString(requiredFeatures)
   1125                     + ", expectActivityLaunch " + expectActivityLaunch
   1126                     + ", caller's uid " + Binder.getCallingUid()
   1127                     + ", pid " + Binder.getCallingPid());
   1128         }
   1129         if (response == null) throw new IllegalArgumentException("response is null");
   1130         if (accountType == null) throw new IllegalArgumentException("accountType is null");
   1131         checkManageAccountsPermission();
   1132 
   1133         final int pid = Binder.getCallingPid();
   1134         final int uid = Binder.getCallingUid();
   1135         final Bundle options = (optionsIn == null) ? new Bundle() : optionsIn;
   1136         options.putInt(AccountManager.KEY_CALLER_UID, uid);
   1137         options.putInt(AccountManager.KEY_CALLER_PID, pid);
   1138 
   1139         long identityToken = clearCallingIdentity();
   1140         try {
   1141             new Session(response, accountType, expectActivityLaunch,
   1142                     true /* stripAuthTokenFromResult */) {
   1143                 public void run() throws RemoteException {
   1144                     mAuthenticator.addAccount(this, mAccountType, authTokenType, requiredFeatures,
   1145                             options);
   1146                 }
   1147 
   1148                 protected String toDebugString(long now) {
   1149                     return super.toDebugString(now) + ", addAccount"
   1150                             + ", accountType " + accountType
   1151                             + ", requiredFeatures "
   1152                             + (requiredFeatures != null
   1153                               ? TextUtils.join(",", requiredFeatures)
   1154                               : null);
   1155                 }
   1156             }.bind();
   1157         } finally {
   1158             restoreCallingIdentity(identityToken);
   1159         }
   1160     }
   1161 
   1162     public void confirmCredentials(IAccountManagerResponse response,
   1163             final Account account, final Bundle options, final boolean expectActivityLaunch) {
   1164         if (Log.isLoggable(TAG, Log.VERBOSE)) {
   1165             Log.v(TAG, "confirmCredentials: " + account
   1166                     + ", response " + response
   1167                     + ", expectActivityLaunch " + expectActivityLaunch
   1168                     + ", caller's uid " + Binder.getCallingUid()
   1169                     + ", pid " + Binder.getCallingPid());
   1170         }
   1171         if (response == null) throw new IllegalArgumentException("response is null");
   1172         if (account == null) throw new IllegalArgumentException("account is null");
   1173         checkManageAccountsPermission();
   1174         long identityToken = clearCallingIdentity();
   1175         try {
   1176             new Session(response, account.type, expectActivityLaunch,
   1177                     true /* stripAuthTokenFromResult */) {
   1178                 public void run() throws RemoteException {
   1179                     mAuthenticator.confirmCredentials(this, account, options);
   1180                 }
   1181                 protected String toDebugString(long now) {
   1182                     return super.toDebugString(now) + ", confirmCredentials"
   1183                             + ", " + account;
   1184                 }
   1185             }.bind();
   1186         } finally {
   1187             restoreCallingIdentity(identityToken);
   1188         }
   1189     }
   1190 
   1191     public void updateCredentials(IAccountManagerResponse response, final Account account,
   1192             final String authTokenType, final boolean expectActivityLaunch,
   1193             final Bundle loginOptions) {
   1194         if (Log.isLoggable(TAG, Log.VERBOSE)) {
   1195             Log.v(TAG, "updateCredentials: " + account
   1196                     + ", response " + response
   1197                     + ", authTokenType " + authTokenType
   1198                     + ", expectActivityLaunch " + expectActivityLaunch
   1199                     + ", caller's uid " + Binder.getCallingUid()
   1200                     + ", pid " + Binder.getCallingPid());
   1201         }
   1202         if (response == null) throw new IllegalArgumentException("response is null");
   1203         if (account == null) throw new IllegalArgumentException("account is null");
   1204         if (authTokenType == null) throw new IllegalArgumentException("authTokenType is null");
   1205         checkManageAccountsPermission();
   1206         long identityToken = clearCallingIdentity();
   1207         try {
   1208             new Session(response, account.type, expectActivityLaunch,
   1209                     true /* stripAuthTokenFromResult */) {
   1210                 public void run() throws RemoteException {
   1211                     mAuthenticator.updateCredentials(this, account, authTokenType, loginOptions);
   1212                 }
   1213                 protected String toDebugString(long now) {
   1214                     if (loginOptions != null) loginOptions.keySet();
   1215                     return super.toDebugString(now) + ", updateCredentials"
   1216                             + ", " + account
   1217                             + ", authTokenType " + authTokenType
   1218                             + ", loginOptions " + loginOptions;
   1219                 }
   1220             }.bind();
   1221         } finally {
   1222             restoreCallingIdentity(identityToken);
   1223         }
   1224     }
   1225 
   1226     public void editProperties(IAccountManagerResponse response, final String accountType,
   1227             final boolean expectActivityLaunch) {
   1228         if (Log.isLoggable(TAG, Log.VERBOSE)) {
   1229             Log.v(TAG, "editProperties: accountType " + accountType
   1230                     + ", response " + response
   1231                     + ", expectActivityLaunch " + expectActivityLaunch
   1232                     + ", caller's uid " + Binder.getCallingUid()
   1233                     + ", pid " + Binder.getCallingPid());
   1234         }
   1235         if (response == null) throw new IllegalArgumentException("response is null");
   1236         if (accountType == null) throw new IllegalArgumentException("accountType is null");
   1237         checkManageAccountsPermission();
   1238         long identityToken = clearCallingIdentity();
   1239         try {
   1240             new Session(response, accountType, expectActivityLaunch,
   1241                     true /* stripAuthTokenFromResult */) {
   1242                 public void run() throws RemoteException {
   1243                     mAuthenticator.editProperties(this, mAccountType);
   1244                 }
   1245                 protected String toDebugString(long now) {
   1246                     return super.toDebugString(now) + ", editProperties"
   1247                             + ", accountType " + accountType;
   1248                 }
   1249             }.bind();
   1250         } finally {
   1251             restoreCallingIdentity(identityToken);
   1252         }
   1253     }
   1254 
   1255     private class GetAccountsByTypeAndFeatureSession extends Session {
   1256         private final String[] mFeatures;
   1257         private volatile Account[] mAccountsOfType = null;
   1258         private volatile ArrayList<Account> mAccountsWithFeatures = null;
   1259         private volatile int mCurrentAccount = 0;
   1260 
   1261         public GetAccountsByTypeAndFeatureSession(IAccountManagerResponse response,
   1262             String type, String[] features) {
   1263             super(response, type, false /* expectActivityLaunch */,
   1264                     true /* stripAuthTokenFromResult */);
   1265             mFeatures = features;
   1266         }
   1267 
   1268         public void run() throws RemoteException {
   1269             synchronized (mCacheLock) {
   1270                 mAccountsOfType = getAccountsFromCacheLocked(mAccountType);
   1271             }
   1272             // check whether each account matches the requested features
   1273             mAccountsWithFeatures = new ArrayList<Account>(mAccountsOfType.length);
   1274             mCurrentAccount = 0;
   1275 
   1276             checkAccount();
   1277         }
   1278 
   1279         public void checkAccount() {
   1280             if (mCurrentAccount >= mAccountsOfType.length) {
   1281                 sendResult();
   1282                 return;
   1283             }
   1284 
   1285             final IAccountAuthenticator accountAuthenticator = mAuthenticator;
   1286             if (accountAuthenticator == null) {
   1287                 // It is possible that the authenticator has died, which is indicated by
   1288                 // mAuthenticator being set to null. If this happens then just abort.
   1289                 // There is no need to send back a result or error in this case since
   1290                 // that already happened when mAuthenticator was cleared.
   1291                 if (Log.isLoggable(TAG, Log.VERBOSE)) {
   1292                     Log.v(TAG, "checkAccount: aborting session since we are no longer"
   1293                             + " connected to the authenticator, " + toDebugString());
   1294                 }
   1295                 return;
   1296             }
   1297             try {
   1298                 accountAuthenticator.hasFeatures(this, mAccountsOfType[mCurrentAccount], mFeatures);
   1299             } catch (RemoteException e) {
   1300                 onError(AccountManager.ERROR_CODE_REMOTE_EXCEPTION, "remote exception");
   1301             }
   1302         }
   1303 
   1304         public void onResult(Bundle result) {
   1305             mNumResults++;
   1306             if (result == null) {
   1307                 onError(AccountManager.ERROR_CODE_INVALID_RESPONSE, "null bundle");
   1308                 return;
   1309             }
   1310             if (result.getBoolean(AccountManager.KEY_BOOLEAN_RESULT, false)) {
   1311                 mAccountsWithFeatures.add(mAccountsOfType[mCurrentAccount]);
   1312             }
   1313             mCurrentAccount++;
   1314             checkAccount();
   1315         }
   1316 
   1317         public void sendResult() {
   1318             IAccountManagerResponse response = getResponseAndClose();
   1319             if (response != null) {
   1320                 try {
   1321                     Account[] accounts = new Account[mAccountsWithFeatures.size()];
   1322                     for (int i = 0; i < accounts.length; i++) {
   1323                         accounts[i] = mAccountsWithFeatures.get(i);
   1324                     }
   1325                     if (Log.isLoggable(TAG, Log.VERBOSE)) {
   1326                         Log.v(TAG, getClass().getSimpleName() + " calling onResult() on response "
   1327                                 + response);
   1328                     }
   1329                     Bundle result = new Bundle();
   1330                     result.putParcelableArray(AccountManager.KEY_ACCOUNTS, accounts);
   1331                     response.onResult(result);
   1332                 } catch (RemoteException e) {
   1333                     // if the caller is dead then there is no one to care about remote exceptions
   1334                     if (Log.isLoggable(TAG, Log.VERBOSE)) {
   1335                         Log.v(TAG, "failure while notifying response", e);
   1336                     }
   1337                 }
   1338             }
   1339         }
   1340 
   1341 
   1342         protected String toDebugString(long now) {
   1343             return super.toDebugString(now) + ", getAccountsByTypeAndFeatures"
   1344                     + ", " + (mFeatures != null ? TextUtils.join(",", mFeatures) : null);
   1345         }
   1346     }
   1347 
   1348     public Account[] getAccounts(String type) {
   1349         if (Log.isLoggable(TAG, Log.VERBOSE)) {
   1350             Log.v(TAG, "getAccounts: accountType " + type
   1351                     + ", caller's uid " + Binder.getCallingUid()
   1352                     + ", pid " + Binder.getCallingPid());
   1353         }
   1354         checkReadAccountsPermission();
   1355         long identityToken = clearCallingIdentity();
   1356         try {
   1357             synchronized (mCacheLock) {
   1358                 return getAccountsFromCacheLocked(type);
   1359             }
   1360         } finally {
   1361             restoreCallingIdentity(identityToken);
   1362         }
   1363     }
   1364 
   1365     public void getAccountsByFeatures(IAccountManagerResponse response,
   1366             String type, String[] features) {
   1367         if (Log.isLoggable(TAG, Log.VERBOSE)) {
   1368             Log.v(TAG, "getAccounts: accountType " + type
   1369                     + ", response " + response
   1370                     + ", features " + stringArrayToString(features)
   1371                     + ", caller's uid " + Binder.getCallingUid()
   1372                     + ", pid " + Binder.getCallingPid());
   1373         }
   1374         if (response == null) throw new IllegalArgumentException("response is null");
   1375         if (type == null) throw new IllegalArgumentException("accountType is null");
   1376         checkReadAccountsPermission();
   1377         long identityToken = clearCallingIdentity();
   1378         try {
   1379             if (features == null || features.length == 0) {
   1380                 Account[] accounts;
   1381                 synchronized (mCacheLock) {
   1382                     accounts = getAccountsFromCacheLocked(type);
   1383                 }
   1384                 Bundle result = new Bundle();
   1385                 result.putParcelableArray(AccountManager.KEY_ACCOUNTS, accounts);
   1386                 onResult(response, result);
   1387                 return;
   1388             }
   1389             new GetAccountsByTypeAndFeatureSession(response, type, features).bind();
   1390         } finally {
   1391             restoreCallingIdentity(identityToken);
   1392         }
   1393     }
   1394 
   1395     private long getAccountIdLocked(SQLiteDatabase db, Account account) {
   1396         Cursor cursor = db.query(TABLE_ACCOUNTS, new String[]{ACCOUNTS_ID},
   1397                 "name=? AND type=?", new String[]{account.name, account.type}, null, null, null);
   1398         try {
   1399             if (cursor.moveToNext()) {
   1400                 return cursor.getLong(0);
   1401             }
   1402             return -1;
   1403         } finally {
   1404             cursor.close();
   1405         }
   1406     }
   1407 
   1408     private long getExtrasIdLocked(SQLiteDatabase db, long accountId, String key) {
   1409         Cursor cursor = db.query(TABLE_EXTRAS, new String[]{EXTRAS_ID},
   1410                 EXTRAS_ACCOUNTS_ID + "=" + accountId + " AND " + EXTRAS_KEY + "=?",
   1411                 new String[]{key}, null, null, null);
   1412         try {
   1413             if (cursor.moveToNext()) {
   1414                 return cursor.getLong(0);
   1415             }
   1416             return -1;
   1417         } finally {
   1418             cursor.close();
   1419         }
   1420     }
   1421 
   1422     private abstract class Session extends IAccountAuthenticatorResponse.Stub
   1423             implements IBinder.DeathRecipient, ServiceConnection {
   1424         IAccountManagerResponse mResponse;
   1425         final String mAccountType;
   1426         final boolean mExpectActivityLaunch;
   1427         final long mCreationTime;
   1428 
   1429         public int mNumResults = 0;
   1430         private int mNumRequestContinued = 0;
   1431         private int mNumErrors = 0;
   1432 
   1433 
   1434         IAccountAuthenticator mAuthenticator = null;
   1435 
   1436         private final boolean mStripAuthTokenFromResult;
   1437 
   1438         public Session(IAccountManagerResponse response, String accountType,
   1439                 boolean expectActivityLaunch, boolean stripAuthTokenFromResult) {
   1440             super();
   1441             if (response == null) throw new IllegalArgumentException("response is null");
   1442             if (accountType == null) throw new IllegalArgumentException("accountType is null");
   1443             mStripAuthTokenFromResult = stripAuthTokenFromResult;
   1444             mResponse = response;
   1445             mAccountType = accountType;
   1446             mExpectActivityLaunch = expectActivityLaunch;
   1447             mCreationTime = SystemClock.elapsedRealtime();
   1448             synchronized (mSessions) {
   1449                 mSessions.put(toString(), this);
   1450             }
   1451             try {
   1452                 response.asBinder().linkToDeath(this, 0 /* flags */);
   1453             } catch (RemoteException e) {
   1454                 mResponse = null;
   1455                 binderDied();
   1456             }
   1457         }
   1458 
   1459         IAccountManagerResponse getResponseAndClose() {
   1460             if (mResponse == null) {
   1461                 // this session has already been closed
   1462                 return null;
   1463             }
   1464             IAccountManagerResponse response = mResponse;
   1465             close(); // this clears mResponse so we need to save the response before this call
   1466             return response;
   1467         }
   1468 
   1469         private void close() {
   1470             synchronized (mSessions) {
   1471                 if (mSessions.remove(toString()) == null) {
   1472                     // the session was already closed, so bail out now
   1473                     return;
   1474                 }
   1475             }
   1476             if (mResponse != null) {
   1477                 // stop listening for response deaths
   1478                 mResponse.asBinder().unlinkToDeath(this, 0 /* flags */);
   1479 
   1480                 // clear this so that we don't accidentally send any further results
   1481                 mResponse = null;
   1482             }
   1483             cancelTimeout();
   1484             unbind();
   1485         }
   1486 
   1487         public void binderDied() {
   1488             mResponse = null;
   1489             close();
   1490         }
   1491 
   1492         protected String toDebugString() {
   1493             return toDebugString(SystemClock.elapsedRealtime());
   1494         }
   1495 
   1496         protected String toDebugString(long now) {
   1497             return "Session: expectLaunch " + mExpectActivityLaunch
   1498                     + ", connected " + (mAuthenticator != null)
   1499                     + ", stats (" + mNumResults + "/" + mNumRequestContinued
   1500                     + "/" + mNumErrors + ")"
   1501                     + ", lifetime " + ((now - mCreationTime) / 1000.0);
   1502         }
   1503 
   1504         void bind() {
   1505             if (Log.isLoggable(TAG, Log.VERBOSE)) {
   1506                 Log.v(TAG, "initiating bind to authenticator type " + mAccountType);
   1507             }
   1508             if (!bindToAuthenticator(mAccountType)) {
   1509                 Log.d(TAG, "bind attempt failed for " + toDebugString());
   1510                 onError(AccountManager.ERROR_CODE_REMOTE_EXCEPTION, "bind failure");
   1511             }
   1512         }
   1513 
   1514         private void unbind() {
   1515             if (mAuthenticator != null) {
   1516                 mAuthenticator = null;
   1517                 mContext.unbindService(this);
   1518             }
   1519         }
   1520 
   1521         public void scheduleTimeout() {
   1522             mMessageHandler.sendMessageDelayed(
   1523                     mMessageHandler.obtainMessage(MESSAGE_TIMED_OUT, this), TIMEOUT_DELAY_MS);
   1524         }
   1525 
   1526         public void cancelTimeout() {
   1527             mMessageHandler.removeMessages(MESSAGE_TIMED_OUT, this);
   1528         }
   1529 
   1530         public void onServiceConnected(ComponentName name, IBinder service) {
   1531             mAuthenticator = IAccountAuthenticator.Stub.asInterface(service);
   1532             try {
   1533                 run();
   1534             } catch (RemoteException e) {
   1535                 onError(AccountManager.ERROR_CODE_REMOTE_EXCEPTION,
   1536                         "remote exception");
   1537             }
   1538         }
   1539 
   1540         public void onServiceDisconnected(ComponentName name) {
   1541             mAuthenticator = null;
   1542             IAccountManagerResponse response = getResponseAndClose();
   1543             if (response != null) {
   1544                 try {
   1545                     response.onError(AccountManager.ERROR_CODE_REMOTE_EXCEPTION,
   1546                             "disconnected");
   1547                 } catch (RemoteException e) {
   1548                     if (Log.isLoggable(TAG, Log.VERBOSE)) {
   1549                         Log.v(TAG, "Session.onServiceDisconnected: "
   1550                                 + "caught RemoteException while responding", e);
   1551                     }
   1552                 }
   1553             }
   1554         }
   1555 
   1556         public abstract void run() throws RemoteException;
   1557 
   1558         public void onTimedOut() {
   1559             IAccountManagerResponse response = getResponseAndClose();
   1560             if (response != null) {
   1561                 try {
   1562                     response.onError(AccountManager.ERROR_CODE_REMOTE_EXCEPTION,
   1563                             "timeout");
   1564                 } catch (RemoteException e) {
   1565                     if (Log.isLoggable(TAG, Log.VERBOSE)) {
   1566                         Log.v(TAG, "Session.onTimedOut: caught RemoteException while responding",
   1567                                 e);
   1568                     }
   1569                 }
   1570             }
   1571         }
   1572 
   1573         public void onResult(Bundle result) {
   1574             mNumResults++;
   1575             if (result != null && !TextUtils.isEmpty(result.getString(AccountManager.KEY_AUTHTOKEN))) {
   1576                 String accountName = result.getString(AccountManager.KEY_ACCOUNT_NAME);
   1577                 String accountType = result.getString(AccountManager.KEY_ACCOUNT_TYPE);
   1578                 if (!TextUtils.isEmpty(accountName) && !TextUtils.isEmpty(accountType)) {
   1579                     Account account = new Account(accountName, accountType);
   1580                     cancelNotification(getSigninRequiredNotificationId(account));
   1581                 }
   1582             }
   1583             IAccountManagerResponse response;
   1584             if (mExpectActivityLaunch && result != null
   1585                     && result.containsKey(AccountManager.KEY_INTENT)) {
   1586                 response = mResponse;
   1587             } else {
   1588                 response = getResponseAndClose();
   1589             }
   1590             if (response != null) {
   1591                 try {
   1592                     if (result == null) {
   1593                         if (Log.isLoggable(TAG, Log.VERBOSE)) {
   1594                             Log.v(TAG, getClass().getSimpleName()
   1595                                     + " calling onError() on response " + response);
   1596                         }
   1597                         response.onError(AccountManager.ERROR_CODE_INVALID_RESPONSE,
   1598                                 "null bundle returned");
   1599                     } else {
   1600                         if (mStripAuthTokenFromResult) {
   1601                             result.remove(AccountManager.KEY_AUTHTOKEN);
   1602                         }
   1603                         if (Log.isLoggable(TAG, Log.VERBOSE)) {
   1604                             Log.v(TAG, getClass().getSimpleName()
   1605                                     + " calling onResult() on response " + response);
   1606                         }
   1607                         response.onResult(result);
   1608                     }
   1609                 } catch (RemoteException e) {
   1610                     // if the caller is dead then there is no one to care about remote exceptions
   1611                     if (Log.isLoggable(TAG, Log.VERBOSE)) {
   1612                         Log.v(TAG, "failure while notifying response", e);
   1613                     }
   1614                 }
   1615             }
   1616         }
   1617 
   1618         public void onRequestContinued() {
   1619             mNumRequestContinued++;
   1620         }
   1621 
   1622         public void onError(int errorCode, String errorMessage) {
   1623             mNumErrors++;
   1624             IAccountManagerResponse response = getResponseAndClose();
   1625             if (response != null) {
   1626                 if (Log.isLoggable(TAG, Log.VERBOSE)) {
   1627                     Log.v(TAG, getClass().getSimpleName()
   1628                             + " calling onError() on response " + response);
   1629                 }
   1630                 try {
   1631                     response.onError(errorCode, errorMessage);
   1632                 } catch (RemoteException e) {
   1633                     if (Log.isLoggable(TAG, Log.VERBOSE)) {
   1634                         Log.v(TAG, "Session.onError: caught RemoteException while responding", e);
   1635                     }
   1636                 }
   1637             } else {
   1638                 if (Log.isLoggable(TAG, Log.VERBOSE)) {
   1639                     Log.v(TAG, "Session.onError: already closed");
   1640                 }
   1641             }
   1642         }
   1643 
   1644         /**
   1645          * find the component name for the authenticator and initiate a bind
   1646          * if no authenticator or the bind fails then return false, otherwise return true
   1647          */
   1648         private boolean bindToAuthenticator(String authenticatorType) {
   1649             AccountAuthenticatorCache.ServiceInfo<AuthenticatorDescription> authenticatorInfo =
   1650                     mAuthenticatorCache.getServiceInfo(
   1651                             AuthenticatorDescription.newKey(authenticatorType));
   1652             if (authenticatorInfo == null) {
   1653                 if (Log.isLoggable(TAG, Log.VERBOSE)) {
   1654                     Log.v(TAG, "there is no authenticator for " + authenticatorType
   1655                             + ", bailing out");
   1656                 }
   1657                 return false;
   1658             }
   1659 
   1660             Intent intent = new Intent();
   1661             intent.setAction(AccountManager.ACTION_AUTHENTICATOR_INTENT);
   1662             intent.setComponent(authenticatorInfo.componentName);
   1663             if (Log.isLoggable(TAG, Log.VERBOSE)) {
   1664                 Log.v(TAG, "performing bindService to " + authenticatorInfo.componentName);
   1665             }
   1666             if (!mContext.bindService(intent, this, Context.BIND_AUTO_CREATE)) {
   1667                 if (Log.isLoggable(TAG, Log.VERBOSE)) {
   1668                     Log.v(TAG, "bindService to " + authenticatorInfo.componentName + " failed");
   1669                 }
   1670                 return false;
   1671             }
   1672 
   1673 
   1674             return true;
   1675         }
   1676     }
   1677 
   1678     private class MessageHandler extends Handler {
   1679         MessageHandler(Looper looper) {
   1680             super(looper);
   1681         }
   1682 
   1683         public void handleMessage(Message msg) {
   1684             switch (msg.what) {
   1685                 case MESSAGE_TIMED_OUT:
   1686                     Session session = (Session)msg.obj;
   1687                     session.onTimedOut();
   1688                     break;
   1689 
   1690                 default:
   1691                     throw new IllegalStateException("unhandled message: " + msg.what);
   1692             }
   1693         }
   1694     }
   1695 
   1696     private static String getDatabaseName() {
   1697         if(Environment.isEncryptedFilesystemEnabled()) {
   1698             // Hard-coded path in case of encrypted file system
   1699             return Environment.getSystemSecureDirectory().getPath() + File.separator + DATABASE_NAME;
   1700         } else {
   1701             // Regular path in case of non-encrypted file system
   1702             return DATABASE_NAME;
   1703         }
   1704     }
   1705 
   1706     private class DatabaseHelper extends SQLiteOpenHelper {
   1707 
   1708         public DatabaseHelper(Context context) {
   1709             super(context, AccountManagerService.getDatabaseName(), null, DATABASE_VERSION);
   1710         }
   1711 
   1712         /**
   1713          * This call needs to be made while the mCacheLock is held. The way to
   1714          * ensure this is to get the lock any time a method is called ont the DatabaseHelper
   1715          * @param db The database.
   1716          */
   1717         @Override
   1718         public void onCreate(SQLiteDatabase db) {
   1719             db.execSQL("CREATE TABLE " + TABLE_ACCOUNTS + " ( "
   1720                     + ACCOUNTS_ID + " INTEGER PRIMARY KEY AUTOINCREMENT, "
   1721                     + ACCOUNTS_NAME + " TEXT NOT NULL, "
   1722                     + ACCOUNTS_TYPE + " TEXT NOT NULL, "
   1723                     + ACCOUNTS_PASSWORD + " TEXT, "
   1724                     + "UNIQUE(" + ACCOUNTS_NAME + "," + ACCOUNTS_TYPE + "))");
   1725 
   1726             db.execSQL("CREATE TABLE " + TABLE_AUTHTOKENS + " (  "
   1727                     + AUTHTOKENS_ID + " INTEGER PRIMARY KEY AUTOINCREMENT,  "
   1728                     + AUTHTOKENS_ACCOUNTS_ID + " INTEGER NOT NULL, "
   1729                     + AUTHTOKENS_TYPE + " TEXT NOT NULL,  "
   1730                     + AUTHTOKENS_AUTHTOKEN + " TEXT,  "
   1731                     + "UNIQUE (" + AUTHTOKENS_ACCOUNTS_ID + "," + AUTHTOKENS_TYPE + "))");
   1732 
   1733             createGrantsTable(db);
   1734 
   1735             db.execSQL("CREATE TABLE " + TABLE_EXTRAS + " ( "
   1736                     + EXTRAS_ID + " INTEGER PRIMARY KEY AUTOINCREMENT, "
   1737                     + EXTRAS_ACCOUNTS_ID + " INTEGER, "
   1738                     + EXTRAS_KEY + " TEXT NOT NULL, "
   1739                     + EXTRAS_VALUE + " TEXT, "
   1740                     + "UNIQUE(" + EXTRAS_ACCOUNTS_ID + "," + EXTRAS_KEY + "))");
   1741 
   1742             db.execSQL("CREATE TABLE " + TABLE_META + " ( "
   1743                     + META_KEY + " TEXT PRIMARY KEY NOT NULL, "
   1744                     + META_VALUE + " TEXT)");
   1745 
   1746             createAccountsDeletionTrigger(db);
   1747         }
   1748 
   1749         private void createAccountsDeletionTrigger(SQLiteDatabase db) {
   1750             db.execSQL(""
   1751                     + " CREATE TRIGGER " + TABLE_ACCOUNTS + "Delete DELETE ON " + TABLE_ACCOUNTS
   1752                     + " BEGIN"
   1753                     + "   DELETE FROM " + TABLE_AUTHTOKENS
   1754                     + "     WHERE " + AUTHTOKENS_ACCOUNTS_ID + "=OLD." + ACCOUNTS_ID + " ;"
   1755                     + "   DELETE FROM " + TABLE_EXTRAS
   1756                     + "     WHERE " + EXTRAS_ACCOUNTS_ID + "=OLD." + ACCOUNTS_ID + " ;"
   1757                     + "   DELETE FROM " + TABLE_GRANTS
   1758                     + "     WHERE " + GRANTS_ACCOUNTS_ID + "=OLD." + ACCOUNTS_ID + " ;"
   1759                     + " END");
   1760         }
   1761 
   1762         private void createGrantsTable(SQLiteDatabase db) {
   1763             db.execSQL("CREATE TABLE " + TABLE_GRANTS + " (  "
   1764                     + GRANTS_ACCOUNTS_ID + " INTEGER NOT NULL, "
   1765                     + GRANTS_AUTH_TOKEN_TYPE + " STRING NOT NULL,  "
   1766                     + GRANTS_GRANTEE_UID + " INTEGER NOT NULL,  "
   1767                     + "UNIQUE (" + GRANTS_ACCOUNTS_ID + "," + GRANTS_AUTH_TOKEN_TYPE
   1768                     +   "," + GRANTS_GRANTEE_UID + "))");
   1769         }
   1770 
   1771         @Override
   1772         public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
   1773             Log.e(TAG, "upgrade from version " + oldVersion + " to version " + newVersion);
   1774 
   1775             if (oldVersion == 1) {
   1776                 // no longer need to do anything since the work is done
   1777                 // when upgrading from version 2
   1778                 oldVersion++;
   1779             }
   1780 
   1781             if (oldVersion == 2) {
   1782                 createGrantsTable(db);
   1783                 db.execSQL("DROP TRIGGER " + TABLE_ACCOUNTS + "Delete");
   1784                 createAccountsDeletionTrigger(db);
   1785                 oldVersion++;
   1786             }
   1787 
   1788             if (oldVersion == 3) {
   1789                 db.execSQL("UPDATE " + TABLE_ACCOUNTS + " SET " + ACCOUNTS_TYPE +
   1790                         " = 'com.google' WHERE " + ACCOUNTS_TYPE + " == 'com.google.GAIA'");
   1791                 oldVersion++;
   1792             }
   1793         }
   1794 
   1795         @Override
   1796         public void onOpen(SQLiteDatabase db) {
   1797             if (Log.isLoggable(TAG, Log.VERBOSE)) Log.v(TAG, "opened database " + DATABASE_NAME);
   1798         }
   1799     }
   1800 
   1801     private void setMetaValue(String key, String value) {
   1802         ContentValues values = new ContentValues();
   1803         values.put(META_KEY, key);
   1804         values.put(META_VALUE, value);
   1805         synchronized (mCacheLock) {
   1806             mOpenHelper.getWritableDatabase().replace(TABLE_META, META_KEY, values);
   1807         }
   1808     }
   1809 
   1810     public IBinder onBind(Intent intent) {
   1811         return asBinder();
   1812     }
   1813 
   1814     /**
   1815      * Searches array of arguments for the specified string
   1816      * @param args array of argument strings
   1817      * @param value value to search for
   1818      * @return true if the value is contained in the array
   1819      */
   1820     private static boolean scanArgs(String[] args, String value) {
   1821         if (args != null) {
   1822             for (String arg : args) {
   1823                 if (value.equals(arg)) {
   1824                     return true;
   1825                 }
   1826             }
   1827         }
   1828         return false;
   1829     }
   1830 
   1831     protected void dump(FileDescriptor fd, PrintWriter fout, String[] args) {
   1832         if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.DUMP)
   1833                 != PackageManager.PERMISSION_GRANTED) {
   1834             fout.println("Permission Denial: can't dump AccountsManager from from pid="
   1835                     + Binder.getCallingPid() + ", uid=" + Binder.getCallingUid()
   1836                     + " without permission " + android.Manifest.permission.DUMP);
   1837             return;
   1838         }
   1839 
   1840         synchronized (mCacheLock) {
   1841             final SQLiteDatabase db = mOpenHelper.getReadableDatabase();
   1842 
   1843             final boolean isCheckinRequest = scanArgs(args, "--checkin") || scanArgs(args, "-c");
   1844 
   1845             if (isCheckinRequest) {
   1846                 // This is a checkin request. *Only* upload the account types and the count of each.
   1847                 Cursor cursor = db.query(TABLE_ACCOUNTS, ACCOUNT_TYPE_COUNT_PROJECTION,
   1848                         null, null, ACCOUNTS_TYPE, null, null);
   1849                 try {
   1850                     while (cursor.moveToNext()) {
   1851                         // print type,count
   1852                         fout.println(cursor.getString(0) + "," + cursor.getString(1));
   1853                     }
   1854                 } finally {
   1855                     if (cursor != null) {
   1856                         cursor.close();
   1857                     }
   1858                 }
   1859             } else {
   1860                 Account[] accounts = getAccountsFromCacheLocked(null /* type */);
   1861                 fout.println("Accounts: " + accounts.length);
   1862                 for (Account account : accounts) {
   1863                     fout.println("  " + account);
   1864                 }
   1865 
   1866                 fout.println();
   1867                 synchronized (mSessions) {
   1868                     final long now = SystemClock.elapsedRealtime();
   1869                     fout.println("Active Sessions: " + mSessions.size());
   1870                     for (Session session : mSessions.values()) {
   1871                         fout.println("  " + session.toDebugString(now));
   1872                     }
   1873                 }
   1874 
   1875                 fout.println();
   1876                 mAuthenticatorCache.dump(fd, fout, args);
   1877             }
   1878         }
   1879     }
   1880 
   1881     private void doNotification(Account account, CharSequence message, Intent intent) {
   1882         long identityToken = clearCallingIdentity();
   1883         try {
   1884             if (Log.isLoggable(TAG, Log.VERBOSE)) {
   1885                 Log.v(TAG, "doNotification: " + message + " intent:" + intent);
   1886             }
   1887 
   1888             if (intent.getComponent() != null &&
   1889                     GrantCredentialsPermissionActivity.class.getName().equals(
   1890                             intent.getComponent().getClassName())) {
   1891                 createNoCredentialsPermissionNotification(account, intent);
   1892             } else {
   1893                 final Integer notificationId = getSigninRequiredNotificationId(account);
   1894                 intent.addCategory(String.valueOf(notificationId));
   1895                 Notification n = new Notification(android.R.drawable.stat_sys_warning, null,
   1896                         0 /* when */);
   1897                 final String notificationTitleFormat =
   1898                         mContext.getText(R.string.notification_title).toString();
   1899                 n.setLatestEventInfo(mContext,
   1900                         String.format(notificationTitleFormat, account.name),
   1901                         message, PendingIntent.getActivity(
   1902                         mContext, 0, intent, PendingIntent.FLAG_CANCEL_CURRENT));
   1903                 installNotification(notificationId, n);
   1904             }
   1905         } finally {
   1906             restoreCallingIdentity(identityToken);
   1907         }
   1908     }
   1909 
   1910     protected void installNotification(final int notificationId, final Notification n) {
   1911         ((NotificationManager) mContext.getSystemService(Context.NOTIFICATION_SERVICE))
   1912                 .notify(notificationId, n);
   1913     }
   1914 
   1915     protected void cancelNotification(int id) {
   1916         long identityToken = clearCallingIdentity();
   1917         try {
   1918             ((NotificationManager) mContext.getSystemService(Context.NOTIFICATION_SERVICE))
   1919                 .cancel(id);
   1920         } finally {
   1921             restoreCallingIdentity(identityToken);
   1922         }
   1923     }
   1924 
   1925     /** Succeeds if any of the specified permissions are granted. */
   1926     private void checkBinderPermission(String... permissions) {
   1927         final int uid = Binder.getCallingUid();
   1928 
   1929         for (String perm : permissions) {
   1930             if (mContext.checkCallingOrSelfPermission(perm) == PackageManager.PERMISSION_GRANTED) {
   1931                 if (Log.isLoggable(TAG, Log.VERBOSE)) {
   1932                     Log.v(TAG, "  caller uid " + uid + " has " + perm);
   1933                 }
   1934                 return;
   1935             }
   1936         }
   1937 
   1938         String msg = "caller uid " + uid + " lacks any of " + TextUtils.join(",", permissions);
   1939         Log.w(TAG, "  " + msg);
   1940         throw new SecurityException(msg);
   1941     }
   1942 
   1943     private boolean inSystemImage(int callerUid) {
   1944         String[] packages = mPackageManager.getPackagesForUid(callerUid);
   1945         for (String name : packages) {
   1946             try {
   1947                 PackageInfo packageInfo = mPackageManager.getPackageInfo(name, 0 /* flags */);
   1948                 if (packageInfo != null
   1949                         && (packageInfo.applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0) {
   1950                     return true;
   1951                 }
   1952             } catch (PackageManager.NameNotFoundException e) {
   1953                 return false;
   1954             }
   1955         }
   1956         return false;
   1957     }
   1958 
   1959     private boolean permissionIsGranted(Account account, String authTokenType, int callerUid) {
   1960         final boolean inSystemImage = inSystemImage(callerUid);
   1961         final boolean fromAuthenticator = account != null
   1962                 && hasAuthenticatorUid(account.type, callerUid);
   1963         final boolean hasExplicitGrants = account != null
   1964                 && hasExplicitlyGrantedPermission(account, authTokenType);
   1965         if (Log.isLoggable(TAG, Log.VERBOSE)) {
   1966             Log.v(TAG, "checkGrantsOrCallingUidAgainstAuthenticator: caller uid "
   1967                     + callerUid + ", " + account
   1968                     + ": is authenticator? " + fromAuthenticator
   1969                     + ", has explicit permission? " + hasExplicitGrants);
   1970         }
   1971         return fromAuthenticator || hasExplicitGrants || inSystemImage;
   1972     }
   1973 
   1974     private boolean hasAuthenticatorUid(String accountType, int callingUid) {
   1975         for (RegisteredServicesCache.ServiceInfo<AuthenticatorDescription> serviceInfo :
   1976                 mAuthenticatorCache.getAllServices()) {
   1977             if (serviceInfo.type.type.equals(accountType)) {
   1978                 return (serviceInfo.uid == callingUid) ||
   1979                         (mPackageManager.checkSignatures(serviceInfo.uid, callingUid)
   1980                                 == PackageManager.SIGNATURE_MATCH);
   1981             }
   1982         }
   1983         return false;
   1984     }
   1985 
   1986     private boolean hasExplicitlyGrantedPermission(Account account, String authTokenType) {
   1987         if (Binder.getCallingUid() == android.os.Process.SYSTEM_UID) {
   1988             return true;
   1989         }
   1990         synchronized (mCacheLock) {
   1991             final SQLiteDatabase db = mOpenHelper.getReadableDatabase();
   1992             String[] args = {String.valueOf(Binder.getCallingUid()), authTokenType,
   1993                     account.name, account.type};
   1994             final boolean permissionGranted =
   1995                     DatabaseUtils.longForQuery(db, COUNT_OF_MATCHING_GRANTS, args) != 0;
   1996             if (!permissionGranted && ActivityManager.isRunningInTestHarness()) {
   1997                 // TODO: Skip this check when running automated tests. Replace this
   1998                 // with a more general solution.
   1999                 Log.d(TAG, "no credentials permission for usage of " + account + ", "
   2000                         + authTokenType + " by uid " + Binder.getCallingUid()
   2001                         + " but ignoring since device is in test harness.");
   2002                 return true;
   2003             }
   2004             return permissionGranted;
   2005         }
   2006     }
   2007 
   2008     private void checkCallingUidAgainstAuthenticator(Account account) {
   2009         final int uid = Binder.getCallingUid();
   2010         if (account == null || !hasAuthenticatorUid(account.type, uid)) {
   2011             String msg = "caller uid " + uid + " is different than the authenticator's uid";
   2012             Log.w(TAG, msg);
   2013             throw new SecurityException(msg);
   2014         }
   2015         if (Log.isLoggable(TAG, Log.VERBOSE)) {
   2016             Log.v(TAG, "caller uid " + uid + " is the same as the authenticator's uid");
   2017         }
   2018     }
   2019 
   2020     private void checkAuthenticateAccountsPermission(Account account) {
   2021         checkBinderPermission(Manifest.permission.AUTHENTICATE_ACCOUNTS);
   2022         checkCallingUidAgainstAuthenticator(account);
   2023     }
   2024 
   2025     private void checkReadAccountsPermission() {
   2026         checkBinderPermission(Manifest.permission.GET_ACCOUNTS);
   2027     }
   2028 
   2029     private void checkManageAccountsPermission() {
   2030         checkBinderPermission(Manifest.permission.MANAGE_ACCOUNTS);
   2031     }
   2032 
   2033     private void checkManageAccountsOrUseCredentialsPermissions() {
   2034         checkBinderPermission(Manifest.permission.MANAGE_ACCOUNTS,
   2035                 Manifest.permission.USE_CREDENTIALS);
   2036     }
   2037 
   2038     /**
   2039      * Allow callers with the given uid permission to get credentials for account/authTokenType.
   2040      * <p>
   2041      * Although this is public it can only be accessed via the AccountManagerService object
   2042      * which is in the system. This means we don't need to protect it with permissions.
   2043      * @hide
   2044      */
   2045     public void grantAppPermission(Account account, String authTokenType, int uid) {
   2046         if (account == null || authTokenType == null) {
   2047             Log.e(TAG, "grantAppPermission: called with invalid arguments", new Exception());
   2048             return;
   2049         }
   2050         synchronized (mCacheLock) {
   2051             final SQLiteDatabase db = mOpenHelper.getWritableDatabase();
   2052             db.beginTransaction();
   2053             try {
   2054                 long accountId = getAccountIdLocked(db, account);
   2055                 if (accountId >= 0) {
   2056                     ContentValues values = new ContentValues();
   2057                     values.put(GRANTS_ACCOUNTS_ID, accountId);
   2058                     values.put(GRANTS_AUTH_TOKEN_TYPE, authTokenType);
   2059                     values.put(GRANTS_GRANTEE_UID, uid);
   2060                     db.insert(TABLE_GRANTS, GRANTS_ACCOUNTS_ID, values);
   2061                     db.setTransactionSuccessful();
   2062                 }
   2063             } finally {
   2064                 db.endTransaction();
   2065             }
   2066             cancelNotification(getCredentialPermissionNotificationId(account, authTokenType, uid));
   2067         }
   2068     }
   2069 
   2070     /**
   2071      * Don't allow callers with the given uid permission to get credentials for
   2072      * account/authTokenType.
   2073      * <p>
   2074      * Although this is public it can only be accessed via the AccountManagerService object
   2075      * which is in the system. This means we don't need to protect it with permissions.
   2076      * @hide
   2077      */
   2078     public void revokeAppPermission(Account account, String authTokenType, int uid) {
   2079         if (account == null || authTokenType == null) {
   2080             Log.e(TAG, "revokeAppPermission: called with invalid arguments", new Exception());
   2081             return;
   2082         }
   2083         synchronized (mCacheLock) {
   2084             final SQLiteDatabase db = mOpenHelper.getWritableDatabase();
   2085             db.beginTransaction();
   2086             try {
   2087                 long accountId = getAccountIdLocked(db, account);
   2088                 if (accountId >= 0) {
   2089                     db.delete(TABLE_GRANTS,
   2090                             GRANTS_ACCOUNTS_ID + "=? AND " + GRANTS_AUTH_TOKEN_TYPE + "=? AND "
   2091                                     + GRANTS_GRANTEE_UID + "=?",
   2092                             new String[]{String.valueOf(accountId), authTokenType,
   2093                                     String.valueOf(uid)});
   2094                     db.setTransactionSuccessful();
   2095                 }
   2096             } finally {
   2097                 db.endTransaction();
   2098             }
   2099             cancelNotification(getCredentialPermissionNotificationId(account, authTokenType, uid));
   2100         }
   2101     }
   2102 
   2103     static final private String stringArrayToString(String[] value) {
   2104         return value != null ? ("[" + TextUtils.join(",", value) + "]") : null;
   2105     }
   2106 
   2107     private void removeAccountFromCacheLocked(Account account) {
   2108         final Account[] oldAccountsForType = mAccountCache.get(account.type);
   2109         if (oldAccountsForType != null) {
   2110             ArrayList<Account> newAccountsList = new ArrayList<Account>();
   2111             for (Account curAccount : oldAccountsForType) {
   2112                 if (!curAccount.equals(account)) {
   2113                     newAccountsList.add(curAccount);
   2114                 }
   2115             }
   2116             if (newAccountsList.isEmpty()) {
   2117                 mAccountCache.remove(account.type);
   2118             } else {
   2119                 Account[] newAccountsForType = new Account[newAccountsList.size()];
   2120                 newAccountsForType = newAccountsList.toArray(newAccountsForType);
   2121                 mAccountCache.put(account.type, newAccountsForType);
   2122             }
   2123         }
   2124         mUserDataCache.remove(account);
   2125         mAuthTokenCache.remove(account);
   2126     }
   2127 
   2128     /**
   2129      * This assumes that the caller has already checked that the account is not already present.
   2130      */
   2131     private void insertAccountIntoCacheLocked(Account account) {
   2132         Account[] accountsForType = mAccountCache.get(account.type);
   2133         int oldLength = (accountsForType != null) ? accountsForType.length : 0;
   2134         Account[] newAccountsForType = new Account[oldLength + 1];
   2135         if (accountsForType != null) {
   2136             System.arraycopy(accountsForType, 0, newAccountsForType, 0, oldLength);
   2137         }
   2138         newAccountsForType[oldLength] = account;
   2139         mAccountCache.put(account.type, newAccountsForType);
   2140     }
   2141 
   2142     protected Account[] getAccountsFromCacheLocked(String accountType) {
   2143         if (accountType != null) {
   2144             final Account[] accounts = mAccountCache.get(accountType);
   2145             if (accounts == null) {
   2146                 return EMPTY_ACCOUNT_ARRAY;
   2147             } else {
   2148                 return Arrays.copyOf(accounts, accounts.length);
   2149             }
   2150         } else {
   2151             int totalLength = 0;
   2152             for (Account[] accounts : mAccountCache.values()) {
   2153                 totalLength += accounts.length;
   2154             }
   2155             if (totalLength == 0) {
   2156                 return EMPTY_ACCOUNT_ARRAY;
   2157             }
   2158             Account[] accounts = new Account[totalLength];
   2159             totalLength = 0;
   2160             for (Account[] accountsOfType : mAccountCache.values()) {
   2161                 System.arraycopy(accountsOfType, 0, accounts, totalLength,
   2162                         accountsOfType.length);
   2163                 totalLength += accountsOfType.length;
   2164             }
   2165             return accounts;
   2166         }
   2167     }
   2168 
   2169     protected void writeUserDataIntoCacheLocked(final SQLiteDatabase db, Account account,
   2170             String key, String value) {
   2171         HashMap<String, String> userDataForAccount = mUserDataCache.get(account);
   2172         if (userDataForAccount == null) {
   2173             userDataForAccount = readUserDataForAccountFromDatabaseLocked(db, account);
   2174             mUserDataCache.put(account, userDataForAccount);
   2175         }
   2176         if (value == null) {
   2177             userDataForAccount.remove(key);
   2178         } else {
   2179             userDataForAccount.put(key, value);
   2180         }
   2181     }
   2182 
   2183     protected void writeAuthTokenIntoCacheLocked(final SQLiteDatabase db, Account account,
   2184             String key, String value) {
   2185         HashMap<String, String> authTokensForAccount = mAuthTokenCache.get(account);
   2186         if (authTokensForAccount == null) {
   2187             authTokensForAccount = readAuthTokensForAccountFromDatabaseLocked(db, account);
   2188             mAuthTokenCache.put(account, authTokensForAccount);
   2189         }
   2190         if (value == null) {
   2191             authTokensForAccount.remove(key);
   2192         } else {
   2193             authTokensForAccount.put(key, value);
   2194         }
   2195     }
   2196 
   2197     protected String readAuthTokenInternal(Account account, String authTokenType) {
   2198         synchronized (mCacheLock) {
   2199             HashMap<String, String> authTokensForAccount = mAuthTokenCache.get(account);
   2200             if (authTokensForAccount == null) {
   2201                 // need to populate the cache for this account
   2202                 final SQLiteDatabase db = mOpenHelper.getReadableDatabase();
   2203                 authTokensForAccount = readAuthTokensForAccountFromDatabaseLocked(db, account);
   2204                 mAuthTokenCache.put(account, authTokensForAccount);
   2205             }
   2206             return authTokensForAccount.get(authTokenType);
   2207         }
   2208     }
   2209 
   2210     protected String readUserDataInternal(Account account, String key) {
   2211         synchronized (mCacheLock) {
   2212             HashMap<String, String> userDataForAccount = mUserDataCache.get(account);
   2213             if (userDataForAccount == null) {
   2214                 // need to populate the cache for this account
   2215                 final SQLiteDatabase db = mOpenHelper.getReadableDatabase();
   2216                 userDataForAccount = readUserDataForAccountFromDatabaseLocked(db, account);
   2217                 mUserDataCache.put(account, userDataForAccount);
   2218             }
   2219             return userDataForAccount.get(key);
   2220         }
   2221     }
   2222 
   2223     protected HashMap<String, String> readUserDataForAccountFromDatabaseLocked(
   2224             final SQLiteDatabase db, Account account) {
   2225         HashMap<String, String> userDataForAccount = new HashMap<String, String>();
   2226         Cursor cursor = db.query(TABLE_EXTRAS,
   2227                 COLUMNS_EXTRAS_KEY_AND_VALUE,
   2228                 SELECTION_USERDATA_BY_ACCOUNT,
   2229                 new String[]{account.name, account.type},
   2230                 null, null, null);
   2231         try {
   2232             while (cursor.moveToNext()) {
   2233                 final String tmpkey = cursor.getString(0);
   2234                 final String value = cursor.getString(1);
   2235                 userDataForAccount.put(tmpkey, value);
   2236             }
   2237         } finally {
   2238             cursor.close();
   2239         }
   2240         return userDataForAccount;
   2241     }
   2242 
   2243     protected HashMap<String, String> readAuthTokensForAccountFromDatabaseLocked(
   2244             final SQLiteDatabase db, Account account) {
   2245         HashMap<String, String> authTokensForAccount = new HashMap<String, String>();
   2246         Cursor cursor = db.query(TABLE_AUTHTOKENS,
   2247                 COLUMNS_AUTHTOKENS_TYPE_AND_AUTHTOKEN,
   2248                 SELECTION_AUTHTOKENS_BY_ACCOUNT,
   2249                 new String[]{account.name, account.type},
   2250                 null, null, null);
   2251         try {
   2252             while (cursor.moveToNext()) {
   2253                 final String type = cursor.getString(0);
   2254                 final String authToken = cursor.getString(1);
   2255                 authTokensForAccount.put(type, authToken);
   2256             }
   2257         } finally {
   2258             cursor.close();
   2259         }
   2260         return authTokensForAccount;
   2261     }
   2262 }
   2263