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