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