blob: 78d4535f6bbacfbc992540ee9ae7941db13b407f [file] [log] [blame]
Fred Quintana60307342009-03-24 22:48:12 -07001/*
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.os.*;
20import android.content.*;
21import android.database.sqlite.*;
22import android.database.Cursor;
23import android.database.DatabaseUtils;
24import android.util.Log;
25import android.text.TextUtils;
26import android.telephony.TelephonyManager;
27
28import java.util.HashMap;
29
30import com.google.android.collect.Maps;
31import com.android.internal.telephony.TelephonyIntents;
32
33/**
34 * A system service that provides account, password, and authtoken management for all
35 * accounts on the device. Some of these calls are implemented with the help of the corresponding
36 * {@link IAccountAuthenticator} services. This service is not accessed by users directly,
37 * instead one uses an instance of {@link AccountManager}, which can be accessed as follows:
38 * AccountManager accountManager =
39 * (AccountManager)context.getSystemService(Context.ACCOUNT_SERVICE)
40 */
41public class AccountManagerService extends IAccountManager.Stub {
42 private static final String TAG = "AccountManagerService";
43
44 private static final int TIMEOUT_DELAY_MS = 1000 * 60;
45 private static final String DATABASE_NAME = "accounts.db";
46 private static final int DATABASE_VERSION = 1;
47
48 private final Context mContext;
49
50 private HandlerThread mMessageThread;
51 private final MessageHandler mMessageHandler;
52
53 // Messages that can be sent on mHandler
54 private static final int MESSAGE_TIMED_OUT = 3;
55 private static final int MESSAGE_CONNECTED = 7;
56 private static final int MESSAGE_DISCONNECTED = 8;
57
58 private final AccountAuthenticatorCache mAuthenticatorCache;
59 private final AuthenticatorBindHelper mBindHelper;
60 public final HashMap<AuthTokenKey, String> mAuthTokenCache = Maps.newHashMap();
61 private final DatabaseHelper mOpenHelper;
62 private final SimWatcher mSimWatcher;
63
64 private static final String TABLE_ACCOUNTS = "accounts";
65 private static final String ACCOUNTS_ID = "_id";
66 private static final String ACCOUNTS_NAME = "name";
67 private static final String ACCOUNTS_TYPE = "type";
68 private static final String ACCOUNTS_PASSWORD = "password";
69
70 private static final String TABLE_AUTHTOKENS = "authtokens";
71 private static final String AUTHTOKENS_ID = "_id";
72 private static final String AUTHTOKENS_ACCOUNTS_ID = "accounts_id";
73 private static final String AUTHTOKENS_TYPE = "type";
74 private static final String AUTHTOKENS_AUTHTOKEN = "authtoken";
75
76 private static final String TABLE_EXTRAS = "extras";
77 private static final String EXTRAS_ID = "_id";
78 private static final String EXTRAS_ACCOUNTS_ID = "accounts_id";
79 private static final String EXTRAS_KEY = "key";
80 private static final String EXTRAS_VALUE = "value";
81
82 private static final String TABLE_META = "meta";
83 private static final String META_KEY = "key";
84 private static final String META_VALUE = "value";
85
86 private static final String[] ACCOUNT_NAME_TYPE_PROJECTION =
87 new String[]{ACCOUNTS_ID, ACCOUNTS_NAME, ACCOUNTS_TYPE};
88 private static final Intent ACCOUNTS_CHANGED_INTENT =
89 new Intent(AccountsServiceConstants.LOGIN_ACCOUNTS_CHANGED_ACTION);
90
91 public class AuthTokenKey {
92 public final Account mAccount;
93 public final String mAuthTokenType;
94 private final int mHashCode;
95
96 public AuthTokenKey(Account account, String authTokenType) {
97 mAccount = account;
98 mAuthTokenType = authTokenType;
99 mHashCode = computeHashCode();
100 }
101
102 public boolean equals(Object o) {
103 if (o == this) {
104 return true;
105 }
106 if (!(o instanceof AuthTokenKey)) {
107 return false;
108 }
109 AuthTokenKey other = (AuthTokenKey)o;
110 if (!mAccount.equals(other.mAccount)) {
111 return false;
112 }
113 return (mAuthTokenType == null)
114 ? other.mAuthTokenType == null
115 : mAuthTokenType.equals(other.mAuthTokenType);
116 }
117
118 private int computeHashCode() {
119 int result = 17;
120 result = 31 * result + mAccount.hashCode();
121 result = 31 * result + ((mAuthTokenType == null) ? 0 : mAuthTokenType.hashCode());
122 return result;
123 }
124
125 public int hashCode() {
126 return mHashCode;
127 }
128 }
129
130 public AccountManagerService(Context context) {
131 mContext = context;
132
133 mOpenHelper = new DatabaseHelper(mContext);
134
135 mMessageThread = new HandlerThread("AccountManagerService");
136 mMessageThread.start();
137 mMessageHandler = new MessageHandler(mMessageThread.getLooper());
138
139 mAuthenticatorCache = new AccountAuthenticatorCache(mContext);
140 mBindHelper = new AuthenticatorBindHelper(mContext, mAuthenticatorCache, mMessageHandler,
141 MESSAGE_CONNECTED, MESSAGE_DISCONNECTED);
142
143 mSimWatcher = new SimWatcher(mContext);
144 }
145
146 public String getPassword(Account account) throws RemoteException {
147 SQLiteDatabase db = mOpenHelper.getReadableDatabase();
148 Cursor cursor = db.query(TABLE_ACCOUNTS, new String[]{ACCOUNTS_PASSWORD},
149 ACCOUNTS_NAME + "=? AND " + ACCOUNTS_TYPE+ "=?",
150 new String[]{account.mName, account.mType}, null, null, null);
151 try {
152 if (cursor.moveToNext()) {
153 return cursor.getString(0);
154 }
155 return null;
156 } finally {
157 cursor.close();
158 }
159 }
160
161 public String getUserData(Account account, String key) throws RemoteException {
162 SQLiteDatabase db = mOpenHelper.getReadableDatabase();
163 db.beginTransaction();
164 try {
165 long accountId = getAccountId(db, account);
166 if (accountId < 0) {
167 return null;
168 }
169 Cursor cursor = db.query(TABLE_EXTRAS, new String[]{EXTRAS_VALUE},
170 EXTRAS_ACCOUNTS_ID + "=" + accountId + " AND " + EXTRAS_KEY + "=?",
171 new String[]{key}, null, null, null);
172 try {
173 if (cursor.moveToNext()) {
174 return cursor.getString(0);
175 }
176 return null;
177 } finally {
178 cursor.close();
179 }
180 } finally {
181 db.setTransactionSuccessful();
182 db.endTransaction();
183 }
184 }
185
186 public Account[] getAccounts() throws RemoteException {
187 return getAccountsByType(null);
188 }
189
190 public Account[] getAccountsByType(String accountType) throws RemoteException {
191 SQLiteDatabase db = mOpenHelper.getReadableDatabase();
192
193 final String selection = accountType == null ? null : (ACCOUNTS_TYPE + "=?");
194 final String[] selectionArgs = accountType == null ? null : new String[]{accountType};
195 Cursor cursor = db.query(TABLE_ACCOUNTS, ACCOUNT_NAME_TYPE_PROJECTION,
196 selection, selectionArgs, null, null, null);
197 try {
198 int i = 0;
199 Account[] accounts = new Account[cursor.getCount()];
200 while (cursor.moveToNext()) {
201 accounts[i] = new Account(cursor.getString(1), cursor.getString(2));
202 i++;
203 }
204 return accounts;
205 } finally {
206 cursor.close();
207 }
208 }
209
210 public boolean addAccount(Account account, String password, Bundle extras)
211 throws RemoteException {
212 // fails if the account already exists
213 SQLiteDatabase db = mOpenHelper.getWritableDatabase();
214 db.beginTransaction();
215 try {
216 long numMatches = DatabaseUtils.longForQuery(db,
217 "select count(*) from " + TABLE_ACCOUNTS
218 + " WHERE " + ACCOUNTS_NAME + "=? AND " + ACCOUNTS_TYPE+ "=?",
219 new String[]{account.mName, account.mType});
220 if (numMatches > 0) {
221 return false;
222 }
223 ContentValues values = new ContentValues();
224 values.put(ACCOUNTS_NAME, account.mName);
225 values.put(ACCOUNTS_TYPE, account.mType);
226 values.put(ACCOUNTS_PASSWORD, password);
227 long accountId = db.insert(TABLE_ACCOUNTS, ACCOUNTS_NAME, values);
228 if (accountId < 0) {
229 return false;
230 }
231 if (extras != null) {
232 for (String key : extras.keySet()) {
233 final String value = extras.getString(key);
234 if (insertExtra(db, accountId, key, value) < 0) {
235 return false;
236 }
237 }
238 }
239 db.setTransactionSuccessful();
240 mContext.sendBroadcast(ACCOUNTS_CHANGED_INTENT);
241 return true;
242 } finally {
243 db.endTransaction();
244 }
245 }
246
247 private long insertExtra(SQLiteDatabase db, long accountId, String key, String value) {
248 ContentValues values = new ContentValues();
249 values.put(EXTRAS_KEY, key);
250 values.put(EXTRAS_ACCOUNTS_ID, accountId);
251 values.put(EXTRAS_VALUE, value);
252 return db.insert(TABLE_EXTRAS, EXTRAS_KEY, values);
253 }
254
255 public void removeAccount(Account account) throws RemoteException {
256 // clear out matching authtokens from the cache
257 final SQLiteDatabase db = mOpenHelper.getWritableDatabase();
258 db.delete(TABLE_ACCOUNTS, ACCOUNTS_NAME + "=? AND " + ACCOUNTS_TYPE+ "=?",
259 new String[]{account.mName, account.mType});
260 mContext.sendBroadcast(ACCOUNTS_CHANGED_INTENT);
261 }
262
263 public void invalidateAuthToken(String accountType, String authToken) throws RemoteException {
264 SQLiteDatabase db = mOpenHelper.getWritableDatabase();
265 db.beginTransaction();
266 try {
267 invalidateAuthToken(db, accountType, authToken);
268 db.setTransactionSuccessful();
269 } finally {
270 db.endTransaction();
271 }
272 }
273
274 private void invalidateAuthToken(SQLiteDatabase db, String accountType, String authToken) {
275 Cursor cursor = db.rawQuery(
276 "SELECT " + TABLE_AUTHTOKENS + "." + AUTHTOKENS_ID
277 + ", " + TABLE_ACCOUNTS + "." + ACCOUNTS_NAME
278 + ", " + TABLE_AUTHTOKENS + "." + AUTHTOKENS_TYPE
279 + " FROM " + TABLE_ACCOUNTS
280 + " JOIN " + TABLE_AUTHTOKENS
281 + " ON " + TABLE_ACCOUNTS + "." + ACCOUNTS_ID
282 + " = " + AUTHTOKENS_ACCOUNTS_ID
283 + " WHERE " + AUTHTOKENS_AUTHTOKEN + " = ? AND "
284 + TABLE_ACCOUNTS + "." + ACCOUNTS_TYPE + " = ?",
285 new String[]{authToken, accountType});
286 try {
287 while (cursor.moveToNext()) {
288 long authTokenId = cursor.getLong(0);
289 String accountName = cursor.getString(1);
290 String authTokenType = cursor.getString(2);
291 AuthTokenKey key = new AuthTokenKey(new Account(accountName, accountType),
292 authTokenType);
293 mAuthTokenCache.remove(key);
294 db.delete(TABLE_AUTHTOKENS, AUTHTOKENS_ID + "=" + authTokenId, null);
295 }
296 } finally {
297 cursor.close();
298 }
299 }
300
301 private boolean saveAuthTokenToDatabase(Account account, String type, String authToken) {
302 SQLiteDatabase db = mOpenHelper.getWritableDatabase();
303 db.beginTransaction();
304 try {
305 if (saveAuthTokenToDatabase(db, account, type, authToken)) {
306 mContext.sendBroadcast(ACCOUNTS_CHANGED_INTENT);
307 db.setTransactionSuccessful();
308 return true;
309 }
310 return false;
311 } finally {
312 db.endTransaction();
313 }
314 }
315
316 private boolean saveAuthTokenToDatabase(SQLiteDatabase db, Account account,
317 String type, String authToken) {
318 long accountId = getAccountId(db, account);
319 if (accountId < 0) {
320 return false;
321 }
322 db.delete(TABLE_AUTHTOKENS,
323 AUTHTOKENS_ACCOUNTS_ID + "=" + accountId + " AND " + AUTHTOKENS_TYPE + "=?",
324 new String[]{type});
325 ContentValues values = new ContentValues();
326 values.put(AUTHTOKENS_ACCOUNTS_ID, accountId);
327 values.put(AUTHTOKENS_TYPE, type);
328 values.put(AUTHTOKENS_AUTHTOKEN, authToken);
329 return db.insert(TABLE_AUTHTOKENS, AUTHTOKENS_AUTHTOKEN, values) >= 0;
330 }
331
332 public String readAuthTokenFromDatabase(Account account, String authTokenType) {
333 SQLiteDatabase db = mOpenHelper.getReadableDatabase();
334 db.beginTransaction();
335 try {
336 long accountId = getAccountId(db, account);
337 if (accountId < 0) {
338 return null;
339 }
340 return getAuthToken(db, accountId, authTokenType);
341 } finally {
342 db.setTransactionSuccessful();
343 db.endTransaction();
344 }
345 }
346
347 public String peekAuthToken(Account account, String authTokenType) throws RemoteException {
348 AuthTokenKey key = new AuthTokenKey(account, authTokenType);
349 if (mAuthTokenCache.containsKey(key)) {
350 return mAuthTokenCache.get(key);
351 }
352 return readAuthTokenFromDatabase(account, authTokenType);
353 }
354
355 public void setAuthToken(Account account, String authTokenType, String authToken)
356 throws RemoteException {
357 cacheAuthToken(account, authTokenType, authToken);
358 }
359
360 public void setPassword(Account account, String password) throws RemoteException {
361 ContentValues values = new ContentValues();
362 values.put(ACCOUNTS_PASSWORD, password);
363 mOpenHelper.getWritableDatabase().update(TABLE_ACCOUNTS, values,
364 ACCOUNTS_NAME + "=? AND " + ACCOUNTS_TYPE+ "=?",
365 new String[]{account.mName, account.mType});
366 mContext.sendBroadcast(ACCOUNTS_CHANGED_INTENT);
367 }
368
369 public void clearPassword(Account account) throws RemoteException {
370 setPassword(account, null);
371 }
372
373 public void setUserData(Account account, String key, String value) throws RemoteException {
374 SQLiteDatabase db = mOpenHelper.getWritableDatabase();
375 db.beginTransaction();
376 try {
377 long accountId = getAccountId(db, account);
378 if (accountId < 0) {
379 return;
380 }
381 long extrasId = getExtrasId(db, accountId, key);
382 if (extrasId < 0 ) {
383 extrasId = insertExtra(db, accountId, key, value);
384 if (extrasId < 0) {
385 return;
386 }
387 } else {
388 ContentValues values = new ContentValues();
389 values.put(EXTRAS_VALUE, value);
390 if (1 != db.update(TABLE_EXTRAS, values, EXTRAS_ID + "=" + extrasId, null)) {
391 return;
392 }
393
394 }
395 db.setTransactionSuccessful();
396 } finally {
397 db.endTransaction();
398 }
399 }
400
401 public void getAuthToken(IAccountManagerResponse response, Account account,
402 String authTokenType, boolean notifyOnAuthFailure) throws RemoteException {
403 // create a new Session
404 Session session = new GetAuthTokenSession(response, account, authTokenType,
405 notifyOnAuthFailure);
406
407 String authToken = getCachedAuthToken(account, authTokenType);
408 if (authToken != null) {
409 session.onStringResult(authToken);
410 return;
411 }
412
413 session.bind();
414 }
415
416 public void addAccountInteractively(IAccountManagerResponse response, String accountType)
417 throws RemoteException {
418 new AddAccountInteractivelySession(response, accountType).bind();
419 }
420
421 public void authenticateAccount(IAccountManagerResponse response, Account account,
422 String password)
423 throws RemoteException {
424 new AuthenticateAccountSession(response, account, password).bind();
425 }
426
427 public void updatePassword(IAccountManagerResponse response, Account account)
428 throws RemoteException {
429 new UpdatePasswordSession(response, account).bind();
430 }
431
432 public void editProperties(IAccountManagerResponse response, String accountType)
433 throws RemoteException {
434 new EditPropertiesSession(response, accountType).bind();
435 }
436
437 public void getPasswordStrength(IAccountManagerResponse response,
438 String accountType, String password) throws RemoteException {
439 new GetPasswordStrengthSession(response, accountType, password).bind();
440 }
441
442 public void checkUsernameExistence(IAccountManagerResponse response,
443 String accountType, String username) throws RemoteException {
444 new CheckUsernameExistenceSession(response, username, accountType).bind();
445 }
446
447 private boolean cacheAuthToken(Account account, String authTokenType, String authToken) {
448 if (saveAuthTokenToDatabase(account, authTokenType, authToken)) {
449 final AuthTokenKey key = new AuthTokenKey(account, authTokenType);
450 mAuthTokenCache.put(key, authToken);
451 return true;
452 } else {
453 return false;
454 }
455 }
456
457 private String getCachedAuthToken(Account account, String authTokenType) {
458 final AuthTokenKey key = new AuthTokenKey(account, authTokenType);
459 if (!mAuthTokenCache.containsKey(key)) return null;
460 return mAuthTokenCache.get(key);
461 }
462
463 private long getAccountId(SQLiteDatabase db, Account account) {
464 Cursor cursor = db.query(TABLE_ACCOUNTS, new String[]{ACCOUNTS_ID},
465 "name=? AND type=?", new String[]{account.mName, account.mType}, null, null, null);
466 try {
467 if (cursor.moveToNext()) {
468 return cursor.getLong(0);
469 }
470 return -1;
471 } finally {
472 cursor.close();
473 }
474 }
475
476 private long getExtrasId(SQLiteDatabase db, long accountId, String key) {
477 Cursor cursor = db.query(TABLE_EXTRAS, new String[]{EXTRAS_ID},
478 EXTRAS_ACCOUNTS_ID + "=" + accountId + " AND " + EXTRAS_KEY + "=?",
479 new String[]{key}, null, null, null);
480 try {
481 if (cursor.moveToNext()) {
482 return cursor.getLong(0);
483 }
484 return -1;
485 } finally {
486 cursor.close();
487 }
488 }
489
490 private String getAuthToken(SQLiteDatabase db, long accountId, String authTokenType) {
491 Cursor cursor = db.query(TABLE_AUTHTOKENS, new String[]{AUTHTOKENS_AUTHTOKEN},
492 AUTHTOKENS_ACCOUNTS_ID + "=" + accountId + " AND " + AUTHTOKENS_TYPE + "=?",
493 new String[]{authTokenType},
494 null, null, null);
495 try {
496 if (cursor.moveToNext()) {
497 return cursor.getString(0);
498 }
499 return null;
500 } finally {
501 cursor.close();
502 }
503 }
504
505 private class Session extends IAccountAuthenticatorResponse.Stub
506 implements AuthenticatorBindHelper.Callback {
507 IAccountManagerResponse mResponse;
508 final String mAccountType;
509
510 IAccountAuthenticator mAuthenticator = null;
511
512 public Session(IAccountManagerResponse response, String accountType) {
513 super();
514 mResponse = response;
515 mAccountType = accountType;
516 }
517
518 IAccountManagerResponse close() {
519 if (mResponse == null) {
520 // this session has already been closed
521 return null;
522 }
523 cancelTimeout();
524 unbind();
525 IAccountManagerResponse response = mResponse;
526 mResponse = null;
527 return response;
528 }
529
530 void bind() {
531 if (!mBindHelper.bind(mAccountType, this)) {
532 onError(6, "bind failure");
533 }
534 }
535
536 private void unbind() {
537 if (mAuthenticator != null) {
538 mAuthenticator = null;
539 mBindHelper.unbind(this);
540 }
541 }
542
543 public void scheduleTimeout() {
544 mMessageHandler.sendMessageDelayed(
545 mMessageHandler.obtainMessage(MESSAGE_TIMED_OUT, this), TIMEOUT_DELAY_MS);
546 }
547
548 public void cancelTimeout() {
549 mMessageHandler.removeMessages(MESSAGE_TIMED_OUT, this);
550 }
551
552 public void onConnected(IBinder service) {
553 mAuthenticator = IAccountAuthenticator.Stub.asInterface(service);
554 // do the next step
555 }
556
557 public void onDisconnected() {
558 IAccountManagerResponse response = close();
559 if (response != null) {
560 onError(3, "disconnected");
561 }
562 }
563
564 public void onTimedOut() {
565 IAccountManagerResponse response = close();
566 if (response != null) {
567 onError(4, "timeout");
568 }
569 }
570
571 public void onIntResult(int result) throws RemoteException {
572 IAccountManagerResponse response = close();
573 if (response != null) {
574 response.onIntResult(result);
575 }
576 }
577
578 public void onBooleanResult(boolean result) throws RemoteException {
579 IAccountManagerResponse response = close();
580 if (response != null) {
581 response.onBooleanResult(result);
582 }
583 }
584
585 public void onStringResult(String result) throws RemoteException {
586 IAccountManagerResponse response = close();
587 if (response != null) {
588 response.onStringResult(result);
589 }
590 }
591
592 public void onError(int errorCode, String errorMessage) {
593 IAccountManagerResponse response = close();
594 if (response != null) {
595 try {
596 response.onError(errorCode, errorMessage);
597 } catch (RemoteException e) {
598 // error while trying to notify user of an error
599 }
600 }
601 }
602 }
603
604 private class GetAuthTokenSession extends Session {
605 final Account mAccount;
606 final String mAuthTokenType;
607 final boolean mNotifyOnAuthFailure;
608
609 public GetAuthTokenSession(IAccountManagerResponse response,
610 Account account, String authTokenType, boolean interactive) {
611 super(response, account.mType);
612 mAccount = account;
613 mAuthTokenType = authTokenType;
614 mNotifyOnAuthFailure = interactive;
615 }
616
617 public void onConnected(IBinder service) {
618 super.onConnected(service);
619
620 try {
621 mAuthenticator.getAuthToken(this, mAccount.mName, mAccount.mType, mAuthTokenType);
622 } catch (RemoteException e) {
623 onError(4, "remote exception");
624 }
625 }
626
627 public void onStringResult(String result) throws RemoteException {
628 IAccountManagerResponse response = close();
629 if (response != null) {
630 cacheAuthToken(mAccount, mAccountType, result);
631 response.onStringResult(result);
632 }
633 }
634
635 public void onError(int errorCode, String errorMessage) {
636 if (mNotifyOnAuthFailure && errorCode == 0 /* TODO: put the real value here */) {
637 // TODO: authentication failed, pop up the notification
638 }
639 super.onError(errorCode, errorMessage);
640 }
641 }
642
643 private class CheckUsernameExistenceSession extends Session {
644 final String mUsername;
645
646 public CheckUsernameExistenceSession(IAccountManagerResponse response,
647 String username, String accountType) {
648 super(response, accountType);
649 mUsername = username;
650 }
651
652 public void onConnected(IBinder service) {
653 super.onConnected(service);
654
655 try {
656 mAuthenticator.checkUsernameExistence(this, mAccountType, mUsername);
657 } catch (RemoteException e) {
658 onError(4, "remote exception");
659 }
660 }
661 }
662
663 private class AddAccountInteractivelySession extends Session {
664
665 public AddAccountInteractivelySession(IAccountManagerResponse response,
666 String accountType) {
667 super(response, accountType);
668 }
669
670 public void onConnected(IBinder service) {
671 super.onConnected(service);
672
673 try {
674 mAuthenticator.addAccount(this, mAccountType);
675 } catch (RemoteException e) {
676 onError(4, "remote exception");
677 }
678 }
679 }
680
681 private class AuthenticateAccountSession extends Session {
682 final String mUsername;
683 final String mPassword;
684
685 public AuthenticateAccountSession(IAccountManagerResponse response, Account account,
686 String password) {
687 super(response, account.mType);
688 mUsername = account.mName;
689 mPassword = password;
690 }
691
692 public void onConnected(IBinder service) {
693 super.onConnected(service);
694
695 try {
696 mAuthenticator.authenticateAccount(this, mUsername, mAccountType, mPassword);
697 } catch (RemoteException e) {
698 onError(4, "remote exception");
699 }
700 }
701 }
702
703 private class UpdatePasswordSession extends Session {
704 final String mUsername;
705
706 public UpdatePasswordSession(IAccountManagerResponse response, Account account) {
707 super(response, account.mType);
708 mUsername = account.mName;
709 }
710
711 public void onConnected(IBinder service) {
712 super.onConnected(service);
713
714 try {
715 mAuthenticator.updatePassword(this, mUsername, mAccountType);
716 } catch (RemoteException e) {
717 onError(4, "remote exception");
718 }
719 }
720 }
721
722 private class EditPropertiesSession extends Session {
723 public EditPropertiesSession(IAccountManagerResponse response, String accountType) {
724 super(response, accountType);
725 }
726
727 public void onConnected(IBinder service) {
728 super.onConnected(service);
729
730 try {
731 mAuthenticator.editProperties(this, mAccountType);
732 } catch (RemoteException e) {
733 onError(4, "remote exception");
734 }
735 }
736 }
737
738 private class GetPasswordStrengthSession extends Session {
739 final String mPassword;
740
741 public GetPasswordStrengthSession(IAccountManagerResponse response,
742 String accountType, String password) {
743 super(response, accountType);
744 mPassword = password;
745 }
746
747 public void onConnected(IBinder service) {
748 super.onConnected(service);
749
750 try {
751 mAuthenticator.getPasswordStrength(this, mAccountType, mPassword);
752 } catch (RemoteException e) {
753 onError(4, "remote exception");
754 }
755 }
756 }
757
758 private class MessageHandler extends Handler {
759 MessageHandler(Looper looper) {
760 super(looper);
761 }
762
763 public void handleMessage(Message msg) {
764 if (mBindHelper.handleMessage(msg)) {
765 return;
766 }
767 switch (msg.what) {
768 case MESSAGE_TIMED_OUT:
769 Session session = (Session)msg.obj;
770 session.onTimedOut();
771 break;
772
773 default:
774 throw new IllegalStateException("unhandled message: " + msg.what);
775 }
776 }
777 }
778
779 private class DatabaseHelper extends SQLiteOpenHelper {
780 public DatabaseHelper(Context context) {
781 super(context, DATABASE_NAME, null, DATABASE_VERSION);
782 }
783
784 @Override
785 public void onCreate(SQLiteDatabase db) {
786 db.execSQL("CREATE TABLE " + TABLE_ACCOUNTS + " ( "
787 + ACCOUNTS_ID + " INTEGER PRIMARY KEY AUTOINCREMENT, "
788 + ACCOUNTS_NAME + " TEXT NOT NULL, "
789 + ACCOUNTS_TYPE + " TEXT NOT NULL, "
790 + ACCOUNTS_PASSWORD + " TEXT, "
791 + "UNIQUE(" + ACCOUNTS_NAME + "," + ACCOUNTS_TYPE + "))");
792
793 db.execSQL("CREATE TABLE " + TABLE_AUTHTOKENS + " ( "
794 + AUTHTOKENS_ID + " INTEGER PRIMARY KEY AUTOINCREMENT, "
795 + AUTHTOKENS_ACCOUNTS_ID + " INTEGER NOT NULL, "
796 + AUTHTOKENS_TYPE + " TEXT NOT NULL, "
797 + AUTHTOKENS_AUTHTOKEN + " TEXT, "
798 + "UNIQUE (" + AUTHTOKENS_ACCOUNTS_ID + "," + AUTHTOKENS_TYPE + "))");
799
800 db.execSQL("CREATE TABLE " + TABLE_EXTRAS + " ( "
801 + EXTRAS_ID + " INTEGER PRIMARY KEY AUTOINCREMENT, "
802 + EXTRAS_ACCOUNTS_ID + " INTEGER, "
803 + EXTRAS_KEY + " TEXT NOT NULL, "
804 + EXTRAS_VALUE + " TEXT, "
805 + "UNIQUE(" + EXTRAS_ACCOUNTS_ID + "," + EXTRAS_KEY + "))");
806
807 db.execSQL("CREATE TABLE " + TABLE_META + " ( "
808 + META_KEY + " TEXT PRIMARY KEY NOT NULL, "
809 + META_VALUE + " TEXT)");
810 }
811
812 @Override
813 public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
814 Log.e(TAG, "GLS upgrade from version " + oldVersion + " to version " +
815 newVersion + " not supported");
816
817 db.execSQL("DROP TABLE " + TABLE_ACCOUNTS);
818 db.execSQL("DROP TABLE " + TABLE_AUTHTOKENS);
819 db.execSQL("DROP TABLE " + TABLE_EXTRAS);
820 db.execSQL("DROP TABLE " + TABLE_META);
821 onCreate(db);
822 }
823
824 @Override
825 public void onOpen(SQLiteDatabase db) {
826 if (Log.isLoggable(TAG, Log.VERBOSE)) Log.v(TAG, "opened database " + DATABASE_NAME);
827 }
828 }
829
830 private void setMetaValue(String key, String value) {
831 ContentValues values = new ContentValues();
832 values.put(META_KEY, key);
833 values.put(META_VALUE, value);
834 mOpenHelper.getWritableDatabase().replace(TABLE_META, META_KEY, values);
835 }
836
837 private String getMetaValue(String key) {
838 Cursor c = mOpenHelper.getReadableDatabase().query(TABLE_META,
839 new String[]{META_VALUE}, META_KEY + "=?", new String[]{key}, null, null, null);
840 try {
841 if (c.moveToNext()) {
842 return c.getString(0);
843 }
844 return null;
845 } finally {
846 c.close();
847 }
848 }
849
850 private class SimWatcher extends BroadcastReceiver {
851 public SimWatcher(Context context) {
852 // Re-scan the SIM card when the SIM state changes, and also if
853 // the disk recovers from a full state (we may have failed to handle
854 // things properly while the disk was full).
855 final IntentFilter filter = new IntentFilter();
856 filter.addAction(TelephonyIntents.ACTION_SIM_STATE_CHANGED);
857 filter.addAction(Intent.ACTION_DEVICE_STORAGE_OK);
858 context.registerReceiver(this, filter);
859 }
860
861 /**
862 * Compare the IMSI to the one stored in the login service's
863 * database. If they differ, erase all passwords and
864 * authtokens (and store the new IMSI).
865 */
866 @Override
867 public void onReceive(Context context, Intent intent) {
868 // Check IMSI on every update; nothing happens if the IMSI is missing or unchanged.
869 String imsi = ((TelephonyManager) context.getSystemService(
870 Context.TELEPHONY_SERVICE)).getSubscriberId();
871 if (TextUtils.isEmpty(imsi)) return;
872
873 String storedImsi = getMetaValue("imsi");
874
875 if (Log.isLoggable(TAG, Log.VERBOSE)) {
876 Log.v(TAG, "current IMSI=" + imsi + "; stored IMSI=" + storedImsi);
877 }
878
879 if (!imsi.equals(storedImsi) && !"initial".equals(storedImsi)) {
880 if (Log.isLoggable(TAG, Log.VERBOSE)) {
881 Log.v(TAG, "wiping all passwords and authtokens");
882 }
883 SQLiteDatabase db = mOpenHelper.getWritableDatabase();
884 db.beginTransaction();
885 try {
886 db.execSQL("DELETE from " + TABLE_AUTHTOKENS);
887 db.execSQL("UPDATE " + TABLE_ACCOUNTS + " SET " + ACCOUNTS_PASSWORD + " = ''");
888 mContext.sendBroadcast(ACCOUNTS_CHANGED_INTENT);
889 db.setTransactionSuccessful();
890 } finally {
891 db.endTransaction();
892 }
893 }
894 setMetaValue("imsi", imsi);
895 }
896 }
897
898 public IBinder onBind(Intent intent) {
899 return asBinder();
900 }
901}