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