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