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