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