AccountManagerService.java revision 3326920329cecb57c7ff1fc5c6add5c98aab9ed9
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.database.Cursor;
25import android.database.DatabaseUtils;
26import android.database.sqlite.SQLiteDatabase;
27import android.database.sqlite.SQLiteOpenHelper;
28import android.os.Bundle;
29import android.os.Handler;
30import android.os.HandlerThread;
31import android.os.IBinder;
32import android.os.Looper;
33import android.os.Message;
34import android.os.RemoteException;
35import android.os.SystemClock;
36import android.telephony.TelephonyManager;
37import android.text.TextUtils;
38import android.util.Log;
39import android.app.PendingIntent;
40import android.app.NotificationManager;
41import android.app.Notification;
42
43import java.io.FileDescriptor;
44import java.io.PrintWriter;
45import java.util.ArrayList;
46import java.util.Collection;
47import java.util.HashMap;
48import java.util.LinkedHashMap;
49
50import com.android.internal.telephony.TelephonyIntents;
51import com.android.internal.R;
52import com.google.android.collect.Lists;
53import com.google.android.collect.Maps;
54
55/**
56 * A system service that provides  account, password, and authtoken management for all
57 * accounts on the device. Some of these calls are implemented with the help of the corresponding
58 * {@link IAccountAuthenticator} services. This service is not accessed by users directly,
59 * instead one uses an instance of {@link AccountManager}, which can be accessed as follows:
60 *    AccountManager accountManager =
61 *      (AccountManager)context.getSystemService(Context.ACCOUNT_SERVICE)
62 * @hide
63 */
64public class AccountManagerService extends IAccountManager.Stub {
65    private static final String TAG = "AccountManagerService";
66
67    private static final int TIMEOUT_DELAY_MS = 1000 * 60;
68    private static final String DATABASE_NAME = "accounts.db";
69    private static final int DATABASE_VERSION = 2;
70
71    private final Context mContext;
72
73    private HandlerThread mMessageThread;
74    private final MessageHandler mMessageHandler;
75
76    // Messages that can be sent on mHandler
77    private static final int MESSAGE_TIMED_OUT = 3;
78    private static final int MESSAGE_CONNECTED = 7;
79    private static final int MESSAGE_DISCONNECTED = 8;
80
81    private final AccountAuthenticatorCache mAuthenticatorCache;
82    private final AuthenticatorBindHelper mBindHelper;
83    private final DatabaseHelper mOpenHelper;
84    private final SimWatcher mSimWatcher;
85
86    private static final String TABLE_ACCOUNTS = "accounts";
87    private static final String ACCOUNTS_ID = "_id";
88    private static final String ACCOUNTS_NAME = "name";
89    private static final String ACCOUNTS_TYPE = "type";
90    private static final String ACCOUNTS_PASSWORD = "password";
91
92    private static final String TABLE_AUTHTOKENS = "authtokens";
93    private static final String AUTHTOKENS_ID = "_id";
94    private static final String AUTHTOKENS_ACCOUNTS_ID = "accounts_id";
95    private static final String AUTHTOKENS_TYPE = "type";
96    private static final String AUTHTOKENS_AUTHTOKEN = "authtoken";
97
98    private static final String TABLE_EXTRAS = "extras";
99    private static final String EXTRAS_ID = "_id";
100    private static final String EXTRAS_ACCOUNTS_ID = "accounts_id";
101    private static final String EXTRAS_KEY = "key";
102    private static final String EXTRAS_VALUE = "value";
103
104    private static final String TABLE_META = "meta";
105    private static final String META_KEY = "key";
106    private static final String META_VALUE = "value";
107
108    private static final String[] ACCOUNT_NAME_TYPE_PROJECTION =
109            new String[]{ACCOUNTS_ID, ACCOUNTS_NAME, ACCOUNTS_TYPE};
110    private static final Intent ACCOUNTS_CHANGED_INTENT =
111            new Intent(Constants.LOGIN_ACCOUNTS_CHANGED_ACTION);
112
113    private final LinkedHashMap<String, Session> mSessions = new LinkedHashMap<String, Session>();
114    private static final int NOTIFICATION_ID = 234;
115
116    public class AuthTokenKey {
117        public final Account mAccount;
118        public final String mAuthTokenType;
119        private final int mHashCode;
120
121        public AuthTokenKey(Account account, String authTokenType) {
122            mAccount = account;
123            mAuthTokenType = authTokenType;
124            mHashCode = computeHashCode();
125        }
126
127        public boolean equals(Object o) {
128            if (o == this) {
129                return true;
130            }
131            if (!(o instanceof AuthTokenKey)) {
132                return false;
133            }
134            AuthTokenKey other = (AuthTokenKey)o;
135            if (!mAccount.equals(other.mAccount)) {
136                return false;
137            }
138            return (mAuthTokenType == null)
139                    ? other.mAuthTokenType == null
140                    : mAuthTokenType.equals(other.mAuthTokenType);
141        }
142
143        private int computeHashCode() {
144            int result = 17;
145            result = 31 * result + mAccount.hashCode();
146            result = 31 * result + ((mAuthTokenType == null) ? 0 : mAuthTokenType.hashCode());
147            return result;
148        }
149
150        public int hashCode() {
151            return mHashCode;
152        }
153    }
154
155    public AccountManagerService(Context context) {
156        mContext = context;
157
158        mOpenHelper = new DatabaseHelper(mContext);
159
160        mMessageThread = new HandlerThread("AccountManagerService");
161        mMessageThread.start();
162        mMessageHandler = new MessageHandler(mMessageThread.getLooper());
163
164        mAuthenticatorCache = new AccountAuthenticatorCache(mContext);
165        mBindHelper = new AuthenticatorBindHelper(mContext, mAuthenticatorCache, mMessageHandler,
166                MESSAGE_CONNECTED, MESSAGE_DISCONNECTED);
167
168        mSimWatcher = new SimWatcher(mContext);
169    }
170
171    public String getPassword(Account account) {
172        long identityToken = clearCallingIdentity();
173        try {
174            SQLiteDatabase db = mOpenHelper.getReadableDatabase();
175            Cursor cursor = db.query(TABLE_ACCOUNTS, new String[]{ACCOUNTS_PASSWORD},
176                    ACCOUNTS_NAME + "=? AND " + ACCOUNTS_TYPE+ "=?",
177                    new String[]{account.mName, account.mType}, null, null, null);
178            try {
179                if (cursor.moveToNext()) {
180                    return cursor.getString(0);
181                }
182                return null;
183            } finally {
184                cursor.close();
185            }
186        } finally {
187            restoreCallingIdentity(identityToken);
188        }
189    }
190
191    public String getUserData(Account account, String key) {
192        long identityToken = clearCallingIdentity();
193        try {
194            SQLiteDatabase db = mOpenHelper.getReadableDatabase();
195            db.beginTransaction();
196            try {
197                long accountId = getAccountId(db, account);
198                if (accountId < 0) {
199                    return null;
200                }
201                Cursor cursor = db.query(TABLE_EXTRAS, new String[]{EXTRAS_VALUE},
202                        EXTRAS_ACCOUNTS_ID + "=" + accountId + " AND " + EXTRAS_KEY + "=?",
203                        new String[]{key}, null, null, null);
204                try {
205                    if (cursor.moveToNext()) {
206                        return cursor.getString(0);
207                    }
208                    return null;
209                } finally {
210                    cursor.close();
211                }
212            } finally {
213                db.setTransactionSuccessful();
214                db.endTransaction();
215            }
216        } finally {
217            restoreCallingIdentity(identityToken);
218        }
219    }
220
221    public String[] getAuthenticatorTypes() {
222        long identityToken = clearCallingIdentity();
223        try {
224            Collection<AccountAuthenticatorCache.AuthenticatorInfo> authenticatorCollection =
225                    mAuthenticatorCache.getAllAuthenticators();
226            String[] types = new String[authenticatorCollection.size()];
227            int i = 0;
228            for (AccountAuthenticatorCache.AuthenticatorInfo authenticator : authenticatorCollection) {
229                types[i] = authenticator.mType;
230                i++;
231            }
232            return types;
233        } finally {
234            restoreCallingIdentity(identityToken);
235        }
236    }
237
238    public Account[] getAccounts() {
239        long identityToken = clearCallingIdentity();
240        try {
241            return getAccountsByType(null);
242        } finally {
243            restoreCallingIdentity(identityToken);
244        }
245    }
246
247    public Account[] getAccountsByType(String accountType) {
248        long identityToken = clearCallingIdentity();
249        try {
250            SQLiteDatabase db = mOpenHelper.getReadableDatabase();
251
252            final String selection = accountType == null ? null : (ACCOUNTS_TYPE + "=?");
253            final String[] selectionArgs = accountType == null ? null : new String[]{accountType};
254            Cursor cursor = db.query(TABLE_ACCOUNTS, ACCOUNT_NAME_TYPE_PROJECTION,
255                    selection, selectionArgs, null, null, null);
256            try {
257                int i = 0;
258                Account[] accounts = new Account[cursor.getCount()];
259                while (cursor.moveToNext()) {
260                    accounts[i] = new Account(cursor.getString(1), cursor.getString(2));
261                    i++;
262                }
263                return accounts;
264            } finally {
265                cursor.close();
266            }
267        } finally {
268            restoreCallingIdentity(identityToken);
269        }
270    }
271
272    public boolean addAccount(Account account, String password, Bundle extras) {
273        // fails if the account already exists
274        long identityToken = clearCallingIdentity();
275        try {
276            SQLiteDatabase db = mOpenHelper.getWritableDatabase();
277            db.beginTransaction();
278            try {
279                long numMatches = DatabaseUtils.longForQuery(db,
280                        "select count(*) from " + TABLE_ACCOUNTS
281                                + " WHERE " + ACCOUNTS_NAME + "=? AND " + ACCOUNTS_TYPE+ "=?",
282                        new String[]{account.mName, account.mType});
283                if (numMatches > 0) {
284                    return false;
285                }
286                ContentValues values = new ContentValues();
287                values.put(ACCOUNTS_NAME, account.mName);
288                values.put(ACCOUNTS_TYPE, account.mType);
289                values.put(ACCOUNTS_PASSWORD, password);
290                long accountId = db.insert(TABLE_ACCOUNTS, ACCOUNTS_NAME, values);
291                if (accountId < 0) {
292                    return false;
293                }
294                if (extras != null) {
295                    for (String key : extras.keySet()) {
296                        final String value = extras.getString(key);
297                        if (insertExtra(db, accountId, key, value) < 0) {
298                            return false;
299                        }
300                    }
301                }
302                db.setTransactionSuccessful();
303                sendAccountsChangedBroadcast();
304                return true;
305            } finally {
306                db.endTransaction();
307            }
308        } finally {
309            restoreCallingIdentity(identityToken);
310        }
311    }
312
313    private long insertExtra(SQLiteDatabase db, long accountId, String key, String value) {
314        ContentValues values = new ContentValues();
315        values.put(EXTRAS_KEY, key);
316        values.put(EXTRAS_ACCOUNTS_ID, accountId);
317        values.put(EXTRAS_VALUE, value);
318        return db.insert(TABLE_EXTRAS, EXTRAS_KEY, values);
319    }
320
321    public void removeAccount(Account account) {
322        long identityToken = clearCallingIdentity();
323        try {
324            final SQLiteDatabase db = mOpenHelper.getWritableDatabase();
325            db.delete(TABLE_ACCOUNTS, ACCOUNTS_NAME + "=? AND " + ACCOUNTS_TYPE+ "=?",
326                    new String[]{account.mName, account.mType});
327            sendAccountsChangedBroadcast();
328        } finally {
329            restoreCallingIdentity(identityToken);
330        }
331    }
332
333    public void invalidateAuthToken(String accountType, String authToken) {
334        long identityToken = clearCallingIdentity();
335        try {
336            SQLiteDatabase db = mOpenHelper.getWritableDatabase();
337            db.beginTransaction();
338            try {
339                invalidateAuthToken(db, accountType, authToken);
340                db.setTransactionSuccessful();
341            } finally {
342                db.endTransaction();
343            }
344        } finally {
345            restoreCallingIdentity(identityToken);
346        }
347    }
348
349    private void invalidateAuthToken(SQLiteDatabase db, String accountType, String authToken) {
350        Cursor cursor = db.rawQuery(
351                "SELECT " + TABLE_AUTHTOKENS + "." + AUTHTOKENS_ID
352                        + ", " + TABLE_ACCOUNTS + "." + ACCOUNTS_NAME
353                        + ", " + TABLE_AUTHTOKENS + "." + AUTHTOKENS_TYPE
354                        + " FROM " + TABLE_ACCOUNTS
355                        + " JOIN " + TABLE_AUTHTOKENS
356                        + " ON " + TABLE_ACCOUNTS + "." + ACCOUNTS_ID
357                        + " = " + AUTHTOKENS_ACCOUNTS_ID
358                        + " WHERE " + AUTHTOKENS_AUTHTOKEN + " = ? AND "
359                        + TABLE_ACCOUNTS + "." + ACCOUNTS_TYPE + " = ?",
360                new String[]{authToken, accountType});
361        try {
362            while (cursor.moveToNext()) {
363                long authTokenId = cursor.getLong(0);
364                String accountName = cursor.getString(1);
365                String authTokenType = cursor.getString(2);
366                db.delete(TABLE_AUTHTOKENS, AUTHTOKENS_ID + "=" + authTokenId, null);
367            }
368        } finally {
369            cursor.close();
370        }
371    }
372
373    private boolean saveAuthTokenToDatabase(Account account, String type, String authToken) {
374        SQLiteDatabase db = mOpenHelper.getWritableDatabase();
375        db.beginTransaction();
376        try {
377            long accountId = getAccountId(db, account);
378            if (accountId < 0) {
379                return false;
380            }
381            db.delete(TABLE_AUTHTOKENS,
382                    AUTHTOKENS_ACCOUNTS_ID + "=" + accountId + " AND " + AUTHTOKENS_TYPE + "=?",
383                    new String[]{type});
384            ContentValues values = new ContentValues();
385            values.put(AUTHTOKENS_ACCOUNTS_ID, accountId);
386            values.put(AUTHTOKENS_TYPE, type);
387            values.put(AUTHTOKENS_AUTHTOKEN, authToken);
388            if (db.insert(TABLE_AUTHTOKENS, AUTHTOKENS_AUTHTOKEN, values) >= 0) {
389                db.setTransactionSuccessful();
390                return true;
391            }
392            return false;
393        } finally {
394            db.endTransaction();
395        }
396    }
397
398    public String readAuthTokenFromDatabase(Account account, String authTokenType) {
399        SQLiteDatabase db = mOpenHelper.getReadableDatabase();
400        db.beginTransaction();
401        try {
402            long accountId = getAccountId(db, account);
403            if (accountId < 0) {
404                return null;
405            }
406            return getAuthToken(db, accountId, authTokenType);
407        } finally {
408            db.setTransactionSuccessful();
409            db.endTransaction();
410        }
411    }
412
413    public String peekAuthToken(Account account, String authTokenType) {
414        long identityToken = clearCallingIdentity();
415        try {
416            return readAuthTokenFromDatabase(account, authTokenType);
417        } finally {
418            restoreCallingIdentity(identityToken);
419        }
420    }
421
422    public void setAuthToken(Account account, String authTokenType, String authToken) {
423        long identityToken = clearCallingIdentity();
424        try {
425            cacheAuthToken(account, authTokenType, authToken);
426        } finally {
427            restoreCallingIdentity(identityToken);
428        }
429    }
430
431    public void setPassword(Account account, String password) {
432        long identityToken = clearCallingIdentity();
433        try {
434            ContentValues values = new ContentValues();
435            values.put(ACCOUNTS_PASSWORD, password);
436            mOpenHelper.getWritableDatabase().update(TABLE_ACCOUNTS, values,
437                    ACCOUNTS_NAME + "=? AND " + ACCOUNTS_TYPE+ "=?",
438                    new String[]{account.mName, account.mType});
439            sendAccountsChangedBroadcast();
440        } finally {
441            restoreCallingIdentity(identityToken);
442        }
443    }
444
445    private void sendAccountsChangedBroadcast() {
446        mContext.sendBroadcast(ACCOUNTS_CHANGED_INTENT);
447    }
448
449    public void clearPassword(Account account) {
450        long identityToken = clearCallingIdentity();
451        try {
452            setPassword(account, null);
453        } finally {
454            restoreCallingIdentity(identityToken);
455        }
456    }
457
458    public void setUserData(Account account, String key, String value) {
459        long identityToken = clearCallingIdentity();
460        try {
461            SQLiteDatabase db = mOpenHelper.getWritableDatabase();
462            db.beginTransaction();
463            try {
464                long accountId = getAccountId(db, account);
465                if (accountId < 0) {
466                    return;
467                }
468                long extrasId = getExtrasId(db, accountId, key);
469                if (extrasId < 0 ) {
470                    extrasId = insertExtra(db, accountId, key, value);
471                    if (extrasId < 0) {
472                        return;
473                    }
474                } else {
475                    ContentValues values = new ContentValues();
476                    values.put(EXTRAS_VALUE, value);
477                    if (1 != db.update(TABLE_EXTRAS, values, EXTRAS_ID + "=" + extrasId, null)) {
478                        return;
479                    }
480
481                }
482                db.setTransactionSuccessful();
483            } finally {
484                db.endTransaction();
485            }
486        } finally {
487            restoreCallingIdentity(identityToken);
488        }
489    }
490
491    public void getAuthToken(IAccountManagerResponse response, final Account account,
492            final String authTokenType, final boolean notifyOnAuthFailure,
493            final boolean expectActivityLaunch, final Bundle loginOptions) {
494        long identityToken = clearCallingIdentity();
495        try {
496            String authToken = readAuthTokenFromDatabase(account, authTokenType);
497            if (authToken != null) {
498                try {
499                    Bundle result = new Bundle();
500                    result.putString(Constants.AUTHTOKEN_KEY, authToken);
501                    result.putString(Constants.ACCOUNT_NAME_KEY, account.mName);
502                    result.putString(Constants.ACCOUNT_TYPE_KEY, account.mType);
503                    response.onResult(result);
504                } catch (RemoteException e) {
505                    // if the caller is dead then there is no one to care about remote exceptions
506                    if (Log.isLoggable(TAG, Log.VERBOSE)) {
507                        Log.v(TAG, "failure while notifying response", e);
508                    }
509                }
510                return;
511            }
512
513            new Session(response, account.mType, expectActivityLaunch) {
514                protected String toDebugString(long now) {
515                    if (loginOptions != null) loginOptions.keySet();
516                    return super.toDebugString(now) + ", getAuthToken"
517                            + ", " + account
518                            + ", authTokenType " + authTokenType
519                            + ", loginOptions " + loginOptions
520                            + ", notifyOnAuthFailure " + notifyOnAuthFailure;
521                }
522
523                public void run() throws RemoteException {
524                    mAuthenticator.getAuthToken(this, account, authTokenType, loginOptions);
525                }
526
527                public void onResult(Bundle result) {
528                    if (result != null) {
529                        String authToken = result.getString(Constants.AUTHTOKEN_KEY);
530                        if (authToken != null) {
531                            String name = result.getString(Constants.ACCOUNT_NAME_KEY);
532                            String type = result.getString(Constants.ACCOUNT_TYPE_KEY);
533                            if (TextUtils.isEmpty(type) || TextUtils.isEmpty(name)) {
534                                onError(Constants.ERROR_CODE_INVALID_RESPONSE,
535                                        "the type and name should not be empty");
536                                return;
537                            }
538                            cacheAuthToken(new Account(name, type), authTokenType, authToken);
539                        }
540
541                        Intent intent = result.getParcelable(Constants.INTENT_KEY);
542                        if (intent != null && notifyOnAuthFailure) {
543                            doNotification(result.getString(Constants.AUTH_FAILED_MESSAGE_KEY),
544                                    intent);
545                        }
546                    }
547                    super.onResult(result);
548                }
549            }.bind();
550        } finally {
551            restoreCallingIdentity(identityToken);
552        }
553    }
554
555
556    public void addAcount(final IAccountManagerResponse response, final String accountType,
557            final String authTokenType, final String[] requiredFeatures,
558            final boolean expectActivityLaunch, final Bundle options) {
559        long identityToken = clearCallingIdentity();
560        try {
561            new Session(response, accountType, expectActivityLaunch) {
562                public void run() throws RemoteException {
563                    mAuthenticator.addAccount(this, mAccountType, authTokenType, requiredFeatures,
564                            options);
565                }
566
567                protected String toDebugString(long now) {
568                    return super.toDebugString(now) + ", addAccount"
569                            + ", accountType " + accountType
570                            + ", requiredFeatures "
571                            + (requiredFeatures != null
572                              ? TextUtils.join(",", requiredFeatures)
573                              : null);
574                }
575            }.bind();
576        } finally {
577            restoreCallingIdentity(identityToken);
578        }
579    }
580
581    public void confirmCredentials(IAccountManagerResponse response,
582            final Account account, final boolean expectActivityLaunch) {
583        long identityToken = clearCallingIdentity();
584        try {
585            new Session(response, account.mType, expectActivityLaunch) {
586                public void run() throws RemoteException {
587                    mAuthenticator.confirmCredentials(this, account);
588                }
589                protected String toDebugString(long now) {
590                    return super.toDebugString(now) + ", confirmCredentials"
591                            + ", " + account;
592                }
593            }.bind();
594        } finally {
595            restoreCallingIdentity(identityToken);
596        }
597    }
598
599    public void confirmPassword(IAccountManagerResponse response, final Account account,
600            final String password) {
601        long identityToken = clearCallingIdentity();
602        try {
603            new Session(response, account.mType, false /* expectActivityLaunch */) {
604                public void run() throws RemoteException {
605                    mAuthenticator.confirmPassword(this, account, password);
606                }
607                protected String toDebugString(long now) {
608                    return super.toDebugString(now) + ", confirmPassword"
609                            + ", " + account;
610                }
611            }.bind();
612        } finally {
613            restoreCallingIdentity(identityToken);
614        }
615    }
616
617    public void updateCredentials(IAccountManagerResponse response, final Account account,
618            final String authTokenType, final boolean expectActivityLaunch,
619            final Bundle loginOptions) {
620        long identityToken = clearCallingIdentity();
621        try {
622            new Session(response, account.mType, expectActivityLaunch) {
623                public void run() throws RemoteException {
624                    mAuthenticator.updateCredentials(this, account, authTokenType, loginOptions);
625                }
626                protected String toDebugString(long now) {
627                    if (loginOptions != null) loginOptions.keySet();
628                    return super.toDebugString(now) + ", updateCredentials"
629                            + ", " + account
630                            + ", authTokenType " + authTokenType
631                            + ", loginOptions " + loginOptions;
632                }
633            }.bind();
634        } finally {
635            restoreCallingIdentity(identityToken);
636        }
637    }
638
639    public void editProperties(IAccountManagerResponse response, final String accountType,
640            final boolean expectActivityLaunch) {
641        long identityToken = clearCallingIdentity();
642        try {
643            new Session(response, accountType, expectActivityLaunch) {
644                public void run() throws RemoteException {
645                    mAuthenticator.editProperties(this, mAccountType);
646                }
647                protected String toDebugString(long now) {
648                    return super.toDebugString(now) + ", editProperties"
649                            + ", accountType " + accountType;
650                }
651            }.bind();
652        } finally {
653            restoreCallingIdentity(identityToken);
654        }
655    }
656
657    private class GetAccountsByTypeAndFeatureSession extends Session {
658        private final String[] mFeatures;
659        private volatile Account[] mAccountsOfType = null;
660        private volatile ArrayList<Account> mAccountsWithFeatures = null;
661        private volatile int mCurrentAccount = 0;
662
663        public GetAccountsByTypeAndFeatureSession(IAccountManagerResponse response,
664            String type, String[] features) {
665            super(response, type, false /* expectActivityLaunch */);
666            mFeatures = features;
667        }
668
669        public void run() throws RemoteException {
670            mAccountsOfType = getAccountsByType(mAccountType);
671            // check whether each account matches the requested features
672            mAccountsWithFeatures = new ArrayList<Account>(mAccountsOfType.length);
673            mCurrentAccount = 0;
674
675            checkAccount();
676        }
677
678        public void checkAccount() {
679            if (mCurrentAccount >= mAccountsOfType.length) {
680                sendResult();
681                return;
682            }
683
684            try {
685                mAuthenticator.hasFeatures(this, mAccountsOfType[mCurrentAccount], mFeatures);
686            } catch (RemoteException e) {
687                onError(Constants.ERROR_CODE_REMOTE_EXCEPTION, "remote exception");
688            }
689        }
690
691        public void onResult(Bundle result) {
692            mNumResults++;
693            if (result == null) {
694                onError(Constants.ERROR_CODE_INVALID_RESPONSE, "null bundle");
695                return;
696            }
697            if (result.getBoolean(Constants.BOOLEAN_RESULT_KEY, false)) {
698                mAccountsWithFeatures.add(mAccountsOfType[mCurrentAccount]);
699            }
700            mCurrentAccount++;
701            checkAccount();
702        }
703
704        public void sendResult() {
705            IAccountManagerResponse response = getResponseAndClose();
706            if (response != null) {
707                try {
708                    Account[] accounts = new Account[mAccountsWithFeatures.size()];
709                    for (int i = 0; i < accounts.length; i++) {
710                        accounts[i] = mAccountsWithFeatures.get(i);
711                    }
712                    Bundle result = new Bundle();
713                    result.putParcelableArray(Constants.ACCOUNTS_KEY, accounts);
714                    response.onResult(result);
715                } catch (RemoteException e) {
716                    // if the caller is dead then there is no one to care about remote exceptions
717                    if (Log.isLoggable(TAG, Log.VERBOSE)) {
718                        Log.v(TAG, "failure while notifying response", e);
719                    }
720                }
721            }
722        }
723
724
725        protected String toDebugString(long now) {
726            return super.toDebugString(now) + ", getAccountsByTypeAndFeatures"
727                    + ", " + (mFeatures != null ? TextUtils.join(",", mFeatures) : null);
728        }
729    }
730    public void getAccountsByTypeAndFeatures(IAccountManagerResponse response,
731            String type, String[] features) {
732        if (type == null) {
733            if (response != null) {
734                try {
735                    response.onError(Constants.ERROR_CODE_BAD_ARGUMENTS, "type is null");
736                } catch (RemoteException e) {
737                    // ignore this
738                }
739            }
740            return;
741        }
742        long identityToken = clearCallingIdentity();
743        try {
744            new GetAccountsByTypeAndFeatureSession(response, type, features).bind();
745        } finally {
746            restoreCallingIdentity(identityToken);
747        }
748    }
749
750    private boolean cacheAuthToken(Account account, String authTokenType, String authToken) {
751        return saveAuthTokenToDatabase(account, authTokenType, authToken);
752    }
753
754    private long getAccountId(SQLiteDatabase db, Account account) {
755        Cursor cursor = db.query(TABLE_ACCOUNTS, new String[]{ACCOUNTS_ID},
756                "name=? AND type=?", new String[]{account.mName, account.mType}, null, null, null);
757        try {
758            if (cursor.moveToNext()) {
759                return cursor.getLong(0);
760            }
761            return -1;
762        } finally {
763            cursor.close();
764        }
765    }
766
767    private long getExtrasId(SQLiteDatabase db, long accountId, String key) {
768        Cursor cursor = db.query(TABLE_EXTRAS, new String[]{EXTRAS_ID},
769                EXTRAS_ACCOUNTS_ID + "=" + accountId + " AND " + EXTRAS_KEY + "=?",
770                new String[]{key}, null, null, null);
771        try {
772            if (cursor.moveToNext()) {
773                return cursor.getLong(0);
774            }
775            return -1;
776        } finally {
777            cursor.close();
778        }
779    }
780
781    private String getAuthToken(SQLiteDatabase db, long accountId, String authTokenType) {
782        Cursor cursor = db.query(TABLE_AUTHTOKENS, new String[]{AUTHTOKENS_AUTHTOKEN},
783                AUTHTOKENS_ACCOUNTS_ID + "=" + accountId + " AND " + AUTHTOKENS_TYPE + "=?",
784                new String[]{authTokenType},
785                null, null, null);
786        try {
787            if (cursor.moveToNext()) {
788                return cursor.getString(0);
789            }
790            return null;
791        } finally {
792            cursor.close();
793        }
794    }
795
796    private abstract class Session extends IAccountAuthenticatorResponse.Stub
797            implements AuthenticatorBindHelper.Callback, IBinder.DeathRecipient {
798        IAccountManagerResponse mResponse;
799        final String mAccountType;
800        final boolean mExpectActivityLaunch;
801        final long mCreationTime;
802
803        public int mNumResults = 0;
804        private int mNumRequestContinued = 0;
805        private int mNumErrors = 0;
806
807
808        IAccountAuthenticator mAuthenticator = null;
809
810        public Session(IAccountManagerResponse response, String accountType,
811                boolean expectActivityLaunch) {
812            super();
813            if (response == null) throw new IllegalArgumentException("response is null");
814            if (accountType == null) throw new IllegalArgumentException("accountType is null");
815            mResponse = response;
816            mAccountType = accountType;
817            mExpectActivityLaunch = expectActivityLaunch;
818            mCreationTime = SystemClock.elapsedRealtime();
819            synchronized (mSessions) {
820                mSessions.put(toString(), this);
821            }
822            try {
823                response.asBinder().linkToDeath(this, 0 /* flags */);
824            } catch (RemoteException e) {
825                mResponse = null;
826                binderDied();
827            }
828        }
829
830        IAccountManagerResponse getResponseAndClose() {
831            if (mResponse == null) {
832                // this session has already been closed
833                return null;
834            }
835            IAccountManagerResponse response = mResponse;
836            close(); // this clears mResponse so we need to save the response before this call
837            return response;
838        }
839
840        private void close() {
841            synchronized (mSessions) {
842                if (mSessions.remove(toString()) == null) {
843                    // the session was already closed, so bail out now
844                    return;
845                }
846            }
847            if (mResponse != null) {
848                // stop listening for response deaths
849                mResponse.asBinder().unlinkToDeath(this, 0 /* flags */);
850
851                // clear this so that we don't accidentally send any further results
852                mResponse = null;
853            }
854            cancelTimeout();
855            unbind();
856        }
857
858        public void binderDied() {
859            mResponse = null;
860            close();
861        }
862
863        protected String toDebugString() {
864            return toDebugString(SystemClock.elapsedRealtime());
865        }
866
867        protected String toDebugString(long now) {
868            return "Session: expectLaunch " + mExpectActivityLaunch
869                    + ", connected " + (mAuthenticator != null)
870                    + ", stats (" + mNumResults + "/" + mNumRequestContinued
871                    + "/" + mNumErrors + ")"
872                    + ", lifetime " + ((now - mCreationTime) / 1000.0);
873        }
874
875        void bind() {
876            if (Log.isLoggable(TAG, Log.VERBOSE)) {
877                Log.v(TAG, "initiating bind to authenticator type " + mAccountType);
878            }
879            if (!mBindHelper.bind(mAccountType, this)) {
880                Log.d(TAG, "bind attempt failed for " + toDebugString());
881                onError(Constants.ERROR_CODE_REMOTE_EXCEPTION, "bind failure");
882            }
883        }
884
885        private void unbind() {
886            if (mAuthenticator != null) {
887                mAuthenticator = null;
888                mBindHelper.unbind(this);
889            }
890        }
891
892        public void scheduleTimeout() {
893            mMessageHandler.sendMessageDelayed(
894                    mMessageHandler.obtainMessage(MESSAGE_TIMED_OUT, this), TIMEOUT_DELAY_MS);
895        }
896
897        public void cancelTimeout() {
898            mMessageHandler.removeMessages(MESSAGE_TIMED_OUT, this);
899        }
900
901        public void onConnected(IBinder service) {
902            mAuthenticator = IAccountAuthenticator.Stub.asInterface(service);
903            try {
904                run();
905            } catch (RemoteException e) {
906                onError(Constants.ERROR_CODE_REMOTE_EXCEPTION,
907                        "remote exception");
908            }
909        }
910
911        public abstract void run() throws RemoteException;
912
913        public void onDisconnected() {
914            mAuthenticator = null;
915            IAccountManagerResponse response = getResponseAndClose();
916            if (response != null) {
917                onError(Constants.ERROR_CODE_REMOTE_EXCEPTION,
918                        "disconnected");
919            }
920        }
921
922        public void onTimedOut() {
923            IAccountManagerResponse response = getResponseAndClose();
924            if (response != null) {
925                onError(Constants.ERROR_CODE_REMOTE_EXCEPTION,
926                        "timeout");
927            }
928        }
929
930        public void onResult(Bundle result) {
931            mNumResults++;
932            if (result != null && !TextUtils.isEmpty(result.getString(Constants.AUTHTOKEN_KEY))) {
933                cancelNotification();
934            }
935            IAccountManagerResponse response;
936            if (mExpectActivityLaunch && result != null
937                    && result.containsKey(Constants.INTENT_KEY)) {
938                response = mResponse;
939            } else {
940                response = getResponseAndClose();
941            }
942            if (response != null) {
943                try {
944                    if (result == null) {
945                        response.onError(Constants.ERROR_CODE_INVALID_RESPONSE,
946                                "null bundle returned");
947                    } else {
948                        response.onResult(result);
949                    }
950                } catch (RemoteException e) {
951                    // if the caller is dead then there is no one to care about remote exceptions
952                    if (Log.isLoggable(TAG, Log.VERBOSE)) {
953                        Log.v(TAG, "failure while notifying response", e);
954                    }
955                }
956            }
957        }
958
959        public void onRequestContinued() {
960            mNumRequestContinued++;
961        }
962
963        public void onError(int errorCode, String errorMessage) {
964            mNumErrors++;
965            if (Log.isLoggable(TAG, Log.VERBOSE)) {
966                Log.v(TAG, "Session.onError: " + errorCode + ", " + errorMessage);
967            }
968            IAccountManagerResponse response = getResponseAndClose();
969            if (response != null) {
970                if (Log.isLoggable(TAG, Log.VERBOSE)) {
971                    Log.v(TAG, "Session.onError: responding");
972                }
973                try {
974                    response.onError(errorCode, errorMessage);
975                } catch (RemoteException e) {
976                    if (Log.isLoggable(TAG, Log.VERBOSE)) {
977                        Log.v(TAG, "Session.onError: caught RemoteException while responding", e);
978                    }
979                }
980            } else {
981                if (Log.isLoggable(TAG, Log.VERBOSE)) {
982                    Log.v(TAG, "Session.onError: already closed");
983                }
984            }
985        }
986    }
987
988    private class MessageHandler extends Handler {
989        MessageHandler(Looper looper) {
990            super(looper);
991        }
992
993        public void handleMessage(Message msg) {
994            if (mBindHelper.handleMessage(msg)) {
995                return;
996            }
997            switch (msg.what) {
998                case MESSAGE_TIMED_OUT:
999                    Session session = (Session)msg.obj;
1000                    session.onTimedOut();
1001                    break;
1002
1003                default:
1004                    throw new IllegalStateException("unhandled message: " + msg.what);
1005            }
1006        }
1007    }
1008
1009    private class DatabaseHelper extends SQLiteOpenHelper {
1010        public DatabaseHelper(Context context) {
1011            super(context, DATABASE_NAME, null, DATABASE_VERSION);
1012        }
1013
1014        @Override
1015        public void onCreate(SQLiteDatabase db) {
1016            db.execSQL("CREATE TABLE " + TABLE_ACCOUNTS + " ( "
1017                    + ACCOUNTS_ID + " INTEGER PRIMARY KEY AUTOINCREMENT, "
1018                    + ACCOUNTS_NAME + " TEXT NOT NULL, "
1019                    + ACCOUNTS_TYPE + " TEXT NOT NULL, "
1020                    + ACCOUNTS_PASSWORD + " TEXT, "
1021                    + "UNIQUE(" + ACCOUNTS_NAME + "," + ACCOUNTS_TYPE + "))");
1022
1023            db.execSQL("CREATE TABLE " + TABLE_AUTHTOKENS + " (  "
1024                    + AUTHTOKENS_ID + " INTEGER PRIMARY KEY AUTOINCREMENT,  "
1025                    + AUTHTOKENS_ACCOUNTS_ID + " INTEGER NOT NULL, "
1026                    + AUTHTOKENS_TYPE + " TEXT NOT NULL,  "
1027                    + AUTHTOKENS_AUTHTOKEN + " TEXT,  "
1028                    + "UNIQUE (" + AUTHTOKENS_ACCOUNTS_ID + "," + AUTHTOKENS_TYPE + "))");
1029
1030            db.execSQL("CREATE TABLE " + TABLE_EXTRAS + " ( "
1031                    + EXTRAS_ID + " INTEGER PRIMARY KEY AUTOINCREMENT, "
1032                    + EXTRAS_ACCOUNTS_ID + " INTEGER, "
1033                    + EXTRAS_KEY + " TEXT NOT NULL, "
1034                    + EXTRAS_VALUE + " TEXT, "
1035                    + "UNIQUE(" + EXTRAS_ACCOUNTS_ID + "," + EXTRAS_KEY + "))");
1036
1037            db.execSQL("CREATE TABLE " + TABLE_META + " ( "
1038                    + META_KEY + " TEXT PRIMARY KEY NOT NULL, "
1039                    + META_VALUE + " TEXT)");
1040
1041            db.execSQL(""
1042                    + " CREATE TRIGGER " + TABLE_ACCOUNTS + "Delete DELETE ON " + TABLE_ACCOUNTS
1043                    + " BEGIN"
1044                    + "   DELETE FROM " + TABLE_AUTHTOKENS
1045                    + "     WHERE " + AUTHTOKENS_ACCOUNTS_ID + "=OLD." + ACCOUNTS_ID + " ;"
1046                    + "   DELETE FROM " + TABLE_EXTRAS
1047                    + "     WHERE " + EXTRAS_ACCOUNTS_ID + "=OLD." + ACCOUNTS_ID + " ;"
1048                    + " END");
1049        }
1050
1051        @Override
1052        public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
1053            Log.e(TAG, "upgrade from version " + oldVersion + " to version " + newVersion);
1054
1055            if (oldVersion == 1) {
1056                db.execSQL(""
1057                        + " CREATE TRIGGER " + TABLE_ACCOUNTS + "Delete DELETE ON " + TABLE_ACCOUNTS
1058                        + " BEGIN"
1059                        + "   DELETE FROM " + TABLE_AUTHTOKENS
1060                        + "     WHERE " + AUTHTOKENS_ACCOUNTS_ID + " =OLD." + ACCOUNTS_ID + " ;"
1061                        + "   DELETE FROM " + TABLE_EXTRAS
1062                        + "     WHERE " + EXTRAS_ACCOUNTS_ID + " =OLD." + ACCOUNTS_ID + " ;"
1063                        + " END");
1064                oldVersion++;
1065            }
1066        }
1067
1068        @Override
1069        public void onOpen(SQLiteDatabase db) {
1070            if (Log.isLoggable(TAG, Log.VERBOSE)) Log.v(TAG, "opened database " + DATABASE_NAME);
1071        }
1072    }
1073
1074    private void setMetaValue(String key, String value) {
1075        ContentValues values = new ContentValues();
1076        values.put(META_KEY, key);
1077        values.put(META_VALUE, value);
1078        mOpenHelper.getWritableDatabase().replace(TABLE_META, META_KEY, values);
1079    }
1080
1081    private String getMetaValue(String key) {
1082        Cursor c = mOpenHelper.getReadableDatabase().query(TABLE_META,
1083                new String[]{META_VALUE}, META_KEY + "=?", new String[]{key}, null, null, null);
1084        try {
1085            if (c.moveToNext()) {
1086                return c.getString(0);
1087            }
1088            return null;
1089        } finally {
1090            c.close();
1091        }
1092    }
1093
1094    private class SimWatcher extends BroadcastReceiver {
1095        public SimWatcher(Context context) {
1096            // Re-scan the SIM card when the SIM state changes, and also if
1097            // the disk recovers from a full state (we may have failed to handle
1098            // things properly while the disk was full).
1099            final IntentFilter filter = new IntentFilter();
1100            filter.addAction(TelephonyIntents.ACTION_SIM_STATE_CHANGED);
1101            filter.addAction(Intent.ACTION_DEVICE_STORAGE_OK);
1102            context.registerReceiver(this, filter);
1103        }
1104
1105        /**
1106         * Compare the IMSI to the one stored in the login service's
1107         * database.  If they differ, erase all passwords and
1108         * authtokens (and store the new IMSI).
1109         */
1110        @Override
1111        public void onReceive(Context context, Intent intent) {
1112            // Check IMSI on every update; nothing happens if the IMSI is missing or unchanged.
1113            String imsi = ((TelephonyManager) context.getSystemService(
1114                    Context.TELEPHONY_SERVICE)).getSubscriberId();
1115            if (TextUtils.isEmpty(imsi)) return;
1116
1117            String storedImsi = getMetaValue("imsi");
1118
1119            if (Log.isLoggable(TAG, Log.VERBOSE)) {
1120                Log.v(TAG, "current IMSI=" + imsi + "; stored IMSI=" + storedImsi);
1121            }
1122
1123            if (!imsi.equals(storedImsi) && !"initial".equals(storedImsi)) {
1124                if (Log.isLoggable(TAG, Log.VERBOSE)) {
1125                    Log.v(TAG, "wiping all passwords and authtokens");
1126                }
1127                SQLiteDatabase db = mOpenHelper.getWritableDatabase();
1128                db.beginTransaction();
1129                try {
1130                    db.execSQL("DELETE from " + TABLE_AUTHTOKENS);
1131                    db.execSQL("UPDATE " + TABLE_ACCOUNTS + " SET " + ACCOUNTS_PASSWORD + " = ''");
1132                    sendAccountsChangedBroadcast();
1133                    db.setTransactionSuccessful();
1134                } finally {
1135                    db.endTransaction();
1136                }
1137            }
1138            setMetaValue("imsi", imsi);
1139        }
1140    }
1141
1142    public IBinder onBind(Intent intent) {
1143        return asBinder();
1144    }
1145
1146    protected void dump(FileDescriptor fd, PrintWriter fout, String[] args) {
1147        synchronized (mSessions) {
1148            final long now = SystemClock.elapsedRealtime();
1149            fout.println("AccountManagerService: " + mSessions.size() + " sessions");
1150            for (Session session : mSessions.values()) {
1151                fout.println("  " + session.toDebugString(now));
1152            }
1153        }
1154
1155        fout.println();
1156
1157        mAuthenticatorCache.dump(fd, fout, args);
1158    }
1159
1160    private void doNotification(CharSequence message, Intent intent) {
1161        long identityToken = clearCallingIdentity();
1162        try {
1163            if (Log.isLoggable(TAG, Log.VERBOSE)) {
1164                Log.v(TAG, "doNotification: " + message + " intent:" + intent);
1165            }
1166
1167            Notification n = new Notification(android.R.drawable.stat_sys_warning, null,
1168                    0 /* when */);
1169            n.setLatestEventInfo(mContext, mContext.getText(R.string.notification_title), message,
1170                PendingIntent.getActivity(mContext, 0, intent, PendingIntent.FLAG_CANCEL_CURRENT));
1171            ((NotificationManager) mContext.getSystemService(Context.NOTIFICATION_SERVICE))
1172                    .notify(NOTIFICATION_ID, n);
1173        } finally {
1174            restoreCallingIdentity(identityToken);
1175        }
1176    }
1177
1178    private void cancelNotification() {
1179        long identityToken = clearCallingIdentity();
1180        try {
1181            ((NotificationManager) mContext.getSystemService(Context.NOTIFICATION_SERVICE))
1182                .cancel(NOTIFICATION_ID);
1183        } finally {
1184            restoreCallingIdentity(identityToken);
1185        }
1186    }
1187}
1188