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