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