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