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