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