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 17 package android.accounts; 18 19 import android.Manifest; 20 import android.app.ActivityManager; 21 import android.app.AppGlobals; 22 import android.app.Notification; 23 import android.app.NotificationManager; 24 import android.app.PendingIntent; 25 import android.content.BroadcastReceiver; 26 import android.content.ComponentName; 27 import android.content.ContentValues; 28 import android.content.Context; 29 import android.content.Intent; 30 import android.content.IntentFilter; 31 import android.content.ServiceConnection; 32 import android.content.pm.ApplicationInfo; 33 import android.content.pm.PackageInfo; 34 import android.content.pm.PackageManager; 35 import android.content.pm.RegisteredServicesCache; 36 import android.content.pm.RegisteredServicesCacheListener; 37 import android.content.pm.UserInfo; 38 import android.content.res.Resources; 39 import android.database.Cursor; 40 import android.database.DatabaseUtils; 41 import android.database.sqlite.SQLiteDatabase; 42 import android.database.sqlite.SQLiteOpenHelper; 43 import android.os.Binder; 44 import android.os.Bundle; 45 import android.os.Environment; 46 import android.os.Handler; 47 import android.os.HandlerThread; 48 import android.os.IBinder; 49 import android.os.Looper; 50 import android.os.Message; 51 import android.os.RemoteException; 52 import android.os.SystemClock; 53 import android.os.UserId; 54 import android.text.TextUtils; 55 import android.util.Log; 56 import android.util.Pair; 57 import android.util.SparseArray; 58 59 import com.android.internal.R; 60 import com.android.internal.util.IndentingPrintWriter; 61 62 import java.io.File; 63 import java.io.FileDescriptor; 64 import java.io.PrintWriter; 65 import java.util.ArrayList; 66 import java.util.Arrays; 67 import java.util.Collection; 68 import java.util.HashMap; 69 import java.util.LinkedHashMap; 70 import java.util.List; 71 import java.util.Map; 72 import java.util.concurrent.atomic.AtomicInteger; 73 import java.util.concurrent.atomic.AtomicReference; 74 75 /** 76 * A system service that provides account, password, and authtoken management for all 77 * accounts on the device. Some of these calls are implemented with the help of the corresponding 78 * {@link IAccountAuthenticator} services. This service is not accessed by users directly, 79 * instead one uses an instance of {@link AccountManager}, which can be accessed as follows: 80 * AccountManager accountManager = AccountManager.get(context); 81 * @hide 82 */ 83 public class AccountManagerService 84 extends IAccountManager.Stub 85 implements RegisteredServicesCacheListener<AuthenticatorDescription> { 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 final PackageManager mPackageManager; 95 96 private HandlerThread mMessageThread; 97 private final MessageHandler mMessageHandler; 98 99 // Messages that can be sent on mHandler 100 private static final int MESSAGE_TIMED_OUT = 3; 101 102 private final IAccountAuthenticatorCache mAuthenticatorCache; 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_TYPE_COUNT_PROJECTION = 133 new String[] { ACCOUNTS_TYPE, ACCOUNTS_TYPE_COUNT}; 134 private static final Intent ACCOUNTS_CHANGED_INTENT; 135 136 private static final String COUNT_OF_MATCHING_GRANTS = "" 137 + "SELECT COUNT(*) FROM " + TABLE_GRANTS + ", " + TABLE_ACCOUNTS 138 + " WHERE " + GRANTS_ACCOUNTS_ID + "=" + ACCOUNTS_ID 139 + " AND " + GRANTS_GRANTEE_UID + "=?" 140 + " AND " + GRANTS_AUTH_TOKEN_TYPE + "=?" 141 + " AND " + ACCOUNTS_NAME + "=?" 142 + " AND " + ACCOUNTS_TYPE + "=?"; 143 144 private static final String SELECTION_AUTHTOKENS_BY_ACCOUNT = 145 AUTHTOKENS_ACCOUNTS_ID + "=(select _id FROM accounts WHERE name=? AND type=?)"; 146 private static final String[] COLUMNS_AUTHTOKENS_TYPE_AND_AUTHTOKEN = {AUTHTOKENS_TYPE, 147 AUTHTOKENS_AUTHTOKEN}; 148 149 private static final String SELECTION_USERDATA_BY_ACCOUNT = 150 EXTRAS_ACCOUNTS_ID + "=(select _id FROM accounts WHERE name=? AND type=?)"; 151 private static final String[] COLUMNS_EXTRAS_KEY_AND_VALUE = {EXTRAS_KEY, EXTRAS_VALUE}; 152 153 private final LinkedHashMap<String, Session> mSessions = new LinkedHashMap<String, Session>(); 154 private final AtomicInteger mNotificationIds = new AtomicInteger(1); 155 156 static class UserAccounts { 157 private final int userId; 158 private final DatabaseHelper openHelper; 159 private final HashMap<Pair<Pair<Account, String>, Integer>, Integer> 160 credentialsPermissionNotificationIds = 161 new HashMap<Pair<Pair<Account, String>, Integer>, Integer>(); 162 private final HashMap<Account, Integer> signinRequiredNotificationIds = 163 new HashMap<Account, Integer>(); 164 private final Object cacheLock = new Object(); 165 /** protected by the {@link #cacheLock} */ 166 private final HashMap<String, Account[]> accountCache = 167 new LinkedHashMap<String, Account[]>(); 168 /** protected by the {@link #cacheLock} */ 169 private HashMap<Account, HashMap<String, String>> userDataCache = 170 new HashMap<Account, HashMap<String, String>>(); 171 /** protected by the {@link #cacheLock} */ 172 private HashMap<Account, HashMap<String, String>> authTokenCache = 173 new HashMap<Account, HashMap<String, String>>(); 174 175 UserAccounts(Context context, int userId) { 176 this.userId = userId; 177 synchronized (cacheLock) { 178 openHelper = new DatabaseHelper(context, userId); 179 } 180 } 181 } 182 183 private final SparseArray<UserAccounts> mUsers = new SparseArray<UserAccounts>(); 184 185 private static AtomicReference<AccountManagerService> sThis = 186 new AtomicReference<AccountManagerService>(); 187 private static final Account[] EMPTY_ACCOUNT_ARRAY = new Account[]{}; 188 189 static { 190 ACCOUNTS_CHANGED_INTENT = new Intent(AccountManager.LOGIN_ACCOUNTS_CHANGED_ACTION); 191 ACCOUNTS_CHANGED_INTENT.setFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT); 192 } 193 194 195 /** 196 * This should only be called by system code. One should only call this after the service 197 * has started. 198 * @return a reference to the AccountManagerService instance 199 * @hide 200 */ 201 public static AccountManagerService getSingleton() { 202 return sThis.get(); 203 } 204 205 public AccountManagerService(Context context) { 206 this(context, context.getPackageManager(), new AccountAuthenticatorCache(context)); 207 } 208 209 public AccountManagerService(Context context, PackageManager packageManager, 210 IAccountAuthenticatorCache authenticatorCache) { 211 mContext = context; 212 mPackageManager = packageManager; 213 214 mMessageThread = new HandlerThread("AccountManagerService"); 215 mMessageThread.start(); 216 mMessageHandler = new MessageHandler(mMessageThread.getLooper()); 217 218 mAuthenticatorCache = authenticatorCache; 219 mAuthenticatorCache.setListener(this, null /* Handler */); 220 221 sThis.set(this); 222 223 UserAccounts accounts = initUser(0); 224 225 IntentFilter intentFilter = new IntentFilter(); 226 intentFilter.addAction(Intent.ACTION_PACKAGE_REMOVED); 227 intentFilter.addDataScheme("package"); 228 mContext.registerReceiver(new BroadcastReceiver() { 229 @Override 230 public void onReceive(Context context1, Intent intent) { 231 purgeOldGrantsAll(); 232 } 233 }, intentFilter); 234 235 IntentFilter userFilter = new IntentFilter(); 236 userFilter.addAction(Intent.ACTION_USER_REMOVED); 237 mContext.registerReceiver(new BroadcastReceiver() { 238 @Override 239 public void onReceive(Context context, Intent intent) { 240 onUserRemoved(intent); 241 } 242 }, userFilter); 243 } 244 245 private UserAccounts initUser(int userId) { 246 synchronized (mUsers) { 247 UserAccounts accounts = mUsers.get(userId); 248 if (accounts == null) { 249 accounts = new UserAccounts(mContext, userId); 250 mUsers.append(userId, accounts); 251 purgeOldGrants(accounts); 252 validateAccountsAndPopulateCache(accounts); 253 } 254 return accounts; 255 } 256 } 257 258 private void purgeOldGrantsAll() { 259 synchronized (mUsers) { 260 for (int i = 0; i < mUsers.size(); i++) { 261 purgeOldGrants(mUsers.valueAt(i)); 262 } 263 } 264 } 265 266 private void purgeOldGrants(UserAccounts accounts) { 267 synchronized (accounts.cacheLock) { 268 final SQLiteDatabase db = accounts.openHelper.getWritableDatabase(); 269 final Cursor cursor = db.query(TABLE_GRANTS, 270 new String[]{GRANTS_GRANTEE_UID}, 271 null, null, GRANTS_GRANTEE_UID, null, null); 272 try { 273 while (cursor.moveToNext()) { 274 final int uid = cursor.getInt(0); 275 final boolean packageExists = mPackageManager.getPackagesForUid(uid) != null; 276 if (packageExists) { 277 continue; 278 } 279 Log.d(TAG, "deleting grants for UID " + uid 280 + " because its package is no longer installed"); 281 db.delete(TABLE_GRANTS, GRANTS_GRANTEE_UID + "=?", 282 new String[]{Integer.toString(uid)}); 283 } 284 } finally { 285 cursor.close(); 286 } 287 } 288 } 289 290 private void validateAccountsAndPopulateCache(UserAccounts accounts) { 291 synchronized (accounts.cacheLock) { 292 final SQLiteDatabase db = accounts.openHelper.getWritableDatabase(); 293 boolean accountDeleted = false; 294 Cursor cursor = db.query(TABLE_ACCOUNTS, 295 new String[]{ACCOUNTS_ID, ACCOUNTS_TYPE, ACCOUNTS_NAME}, 296 null, null, null, null, null); 297 try { 298 accounts.accountCache.clear(); 299 final HashMap<String, ArrayList<String>> accountNamesByType = 300 new LinkedHashMap<String, ArrayList<String>>(); 301 while (cursor.moveToNext()) { 302 final long accountId = cursor.getLong(0); 303 final String accountType = cursor.getString(1); 304 final String accountName = cursor.getString(2); 305 if (mAuthenticatorCache.getServiceInfo( 306 AuthenticatorDescription.newKey(accountType)) == null) { 307 Log.d(TAG, "deleting account " + accountName + " because type " 308 + accountType + " no longer has a registered authenticator"); 309 db.delete(TABLE_ACCOUNTS, ACCOUNTS_ID + "=" + accountId, null); 310 accountDeleted = true; 311 final Account account = new Account(accountName, accountType); 312 accounts.userDataCache.remove(account); 313 accounts.authTokenCache.remove(account); 314 } else { 315 ArrayList<String> accountNames = accountNamesByType.get(accountType); 316 if (accountNames == null) { 317 accountNames = new ArrayList<String>(); 318 accountNamesByType.put(accountType, accountNames); 319 } 320 accountNames.add(accountName); 321 } 322 } 323 for (Map.Entry<String, ArrayList<String>> cur 324 : accountNamesByType.entrySet()) { 325 final String accountType = cur.getKey(); 326 final ArrayList<String> accountNames = cur.getValue(); 327 final Account[] accountsForType = new Account[accountNames.size()]; 328 int i = 0; 329 for (String accountName : accountNames) { 330 accountsForType[i] = new Account(accountName, accountType); 331 ++i; 332 } 333 accounts.accountCache.put(accountType, accountsForType); 334 } 335 } finally { 336 cursor.close(); 337 if (accountDeleted) { 338 sendAccountsChangedBroadcast(accounts.userId); 339 } 340 } 341 } 342 } 343 344 private UserAccounts getUserAccountsForCaller() { 345 return getUserAccounts(UserId.getCallingUserId()); 346 } 347 348 protected UserAccounts getUserAccounts(int userId) { 349 synchronized (mUsers) { 350 UserAccounts accounts = mUsers.get(userId); 351 if (accounts == null) { 352 accounts = initUser(userId); 353 mUsers.append(userId, accounts); 354 } 355 return accounts; 356 } 357 } 358 359 private void onUserRemoved(Intent intent) { 360 int userId = intent.getIntExtra(Intent.EXTRA_USERID, -1); 361 if (userId < 1) return; 362 363 UserAccounts accounts; 364 synchronized (mUsers) { 365 accounts = mUsers.get(userId); 366 mUsers.remove(userId); 367 } 368 if (accounts == null) { 369 File dbFile = new File(getDatabaseName(userId)); 370 dbFile.delete(); 371 return; 372 } 373 374 synchronized (accounts.cacheLock) { 375 accounts.openHelper.close(); 376 File dbFile = new File(getDatabaseName(userId)); 377 dbFile.delete(); 378 } 379 } 380 381 private List<UserInfo> getAllUsers() { 382 try { 383 return AppGlobals.getPackageManager().getUsers(); 384 } catch (RemoteException re) { 385 // Local to system process, shouldn't happen 386 } 387 return null; 388 } 389 390 public void onServiceChanged(AuthenticatorDescription desc, boolean removed) { 391 // Validate accounts for all users 392 List<UserInfo> users = getAllUsers(); 393 if (users == null) { 394 validateAccountsAndPopulateCache(getUserAccountsForCaller()); 395 } else { 396 for (UserInfo user : users) { 397 validateAccountsAndPopulateCache(getUserAccounts(user.id)); 398 } 399 } 400 } 401 402 public String getPassword(Account account) { 403 if (Log.isLoggable(TAG, Log.VERBOSE)) { 404 Log.v(TAG, "getPassword: " + account 405 + ", caller's uid " + Binder.getCallingUid() 406 + ", pid " + Binder.getCallingPid()); 407 } 408 if (account == null) throw new IllegalArgumentException("account is null"); 409 checkAuthenticateAccountsPermission(account); 410 411 UserAccounts accounts = getUserAccountsForCaller(); 412 long identityToken = clearCallingIdentity(); 413 try { 414 return readPasswordInternal(accounts, account); 415 } finally { 416 restoreCallingIdentity(identityToken); 417 } 418 } 419 420 private String readPasswordInternal(UserAccounts accounts, Account account) { 421 if (account == null) { 422 return null; 423 } 424 425 synchronized (accounts.cacheLock) { 426 final SQLiteDatabase db = accounts.openHelper.getReadableDatabase(); 427 Cursor cursor = db.query(TABLE_ACCOUNTS, new String[]{ACCOUNTS_PASSWORD}, 428 ACCOUNTS_NAME + "=? AND " + ACCOUNTS_TYPE+ "=?", 429 new String[]{account.name, account.type}, null, null, null); 430 try { 431 if (cursor.moveToNext()) { 432 return cursor.getString(0); 433 } 434 return null; 435 } finally { 436 cursor.close(); 437 } 438 } 439 } 440 441 public String getUserData(Account account, String key) { 442 if (Log.isLoggable(TAG, Log.VERBOSE)) { 443 Log.v(TAG, "getUserData: " + account 444 + ", key " + key 445 + ", caller's uid " + Binder.getCallingUid() 446 + ", pid " + Binder.getCallingPid()); 447 } 448 if (account == null) throw new IllegalArgumentException("account is null"); 449 if (key == null) throw new IllegalArgumentException("key is null"); 450 checkAuthenticateAccountsPermission(account); 451 UserAccounts accounts = getUserAccountsForCaller(); 452 long identityToken = clearCallingIdentity(); 453 try { 454 return readUserDataInternal(accounts, account, key); 455 } finally { 456 restoreCallingIdentity(identityToken); 457 } 458 } 459 460 public AuthenticatorDescription[] getAuthenticatorTypes() { 461 if (Log.isLoggable(TAG, Log.VERBOSE)) { 462 Log.v(TAG, "getAuthenticatorTypes: " 463 + "caller's uid " + Binder.getCallingUid() 464 + ", pid " + Binder.getCallingPid()); 465 } 466 long identityToken = clearCallingIdentity(); 467 try { 468 Collection<AccountAuthenticatorCache.ServiceInfo<AuthenticatorDescription>> 469 authenticatorCollection = mAuthenticatorCache.getAllServices(); 470 AuthenticatorDescription[] types = 471 new AuthenticatorDescription[authenticatorCollection.size()]; 472 int i = 0; 473 for (AccountAuthenticatorCache.ServiceInfo<AuthenticatorDescription> authenticator 474 : authenticatorCollection) { 475 types[i] = authenticator.type; 476 i++; 477 } 478 return types; 479 } finally { 480 restoreCallingIdentity(identityToken); 481 } 482 } 483 484 public boolean addAccount(Account account, String password, Bundle extras) { 485 if (Log.isLoggable(TAG, Log.VERBOSE)) { 486 Log.v(TAG, "addAccount: " + account 487 + ", caller's uid " + Binder.getCallingUid() 488 + ", pid " + Binder.getCallingPid()); 489 } 490 if (account == null) throw new IllegalArgumentException("account is null"); 491 checkAuthenticateAccountsPermission(account); 492 493 UserAccounts accounts = getUserAccountsForCaller(); 494 // fails if the account already exists 495 long identityToken = clearCallingIdentity(); 496 try { 497 return addAccountInternal(accounts, account, password, extras); 498 } finally { 499 restoreCallingIdentity(identityToken); 500 } 501 } 502 503 private boolean addAccountInternal(UserAccounts accounts, Account account, String password, 504 Bundle extras) { 505 if (account == null) { 506 return false; 507 } 508 synchronized (accounts.cacheLock) { 509 final SQLiteDatabase db = accounts.openHelper.getWritableDatabase(); 510 db.beginTransaction(); 511 try { 512 long numMatches = DatabaseUtils.longForQuery(db, 513 "select count(*) from " + TABLE_ACCOUNTS 514 + " WHERE " + ACCOUNTS_NAME + "=? AND " + ACCOUNTS_TYPE+ "=?", 515 new String[]{account.name, account.type}); 516 if (numMatches > 0) { 517 Log.w(TAG, "insertAccountIntoDatabase: " + account 518 + ", skipping since the account already exists"); 519 return false; 520 } 521 ContentValues values = new ContentValues(); 522 values.put(ACCOUNTS_NAME, account.name); 523 values.put(ACCOUNTS_TYPE, account.type); 524 values.put(ACCOUNTS_PASSWORD, password); 525 long accountId = db.insert(TABLE_ACCOUNTS, ACCOUNTS_NAME, values); 526 if (accountId < 0) { 527 Log.w(TAG, "insertAccountIntoDatabase: " + account 528 + ", skipping the DB insert failed"); 529 return false; 530 } 531 if (extras != null) { 532 for (String key : extras.keySet()) { 533 final String value = extras.getString(key); 534 if (insertExtraLocked(db, accountId, key, value) < 0) { 535 Log.w(TAG, "insertAccountIntoDatabase: " + account 536 + ", skipping since insertExtra failed for key " + key); 537 return false; 538 } 539 } 540 } 541 db.setTransactionSuccessful(); 542 insertAccountIntoCacheLocked(accounts, account); 543 } finally { 544 db.endTransaction(); 545 } 546 sendAccountsChangedBroadcast(accounts.userId); 547 return true; 548 } 549 } 550 551 private long insertExtraLocked(SQLiteDatabase db, long accountId, String key, String value) { 552 ContentValues values = new ContentValues(); 553 values.put(EXTRAS_KEY, key); 554 values.put(EXTRAS_ACCOUNTS_ID, accountId); 555 values.put(EXTRAS_VALUE, value); 556 return db.insert(TABLE_EXTRAS, EXTRAS_KEY, values); 557 } 558 559 public void hasFeatures(IAccountManagerResponse response, 560 Account account, String[] features) { 561 if (Log.isLoggable(TAG, Log.VERBOSE)) { 562 Log.v(TAG, "hasFeatures: " + account 563 + ", response " + response 564 + ", features " + stringArrayToString(features) 565 + ", caller's uid " + Binder.getCallingUid() 566 + ", pid " + Binder.getCallingPid()); 567 } 568 if (response == null) throw new IllegalArgumentException("response is null"); 569 if (account == null) throw new IllegalArgumentException("account is null"); 570 if (features == null) throw new IllegalArgumentException("features is null"); 571 checkReadAccountsPermission(); 572 UserAccounts accounts = getUserAccountsForCaller(); 573 long identityToken = clearCallingIdentity(); 574 try { 575 new TestFeaturesSession(accounts, response, account, features).bind(); 576 } finally { 577 restoreCallingIdentity(identityToken); 578 } 579 } 580 581 private class TestFeaturesSession extends Session { 582 private final String[] mFeatures; 583 private final Account mAccount; 584 585 public TestFeaturesSession(UserAccounts accounts, IAccountManagerResponse response, 586 Account account, String[] features) { 587 super(accounts, response, account.type, false /* expectActivityLaunch */, 588 true /* stripAuthTokenFromResult */); 589 mFeatures = features; 590 mAccount = account; 591 } 592 593 public void run() throws RemoteException { 594 try { 595 mAuthenticator.hasFeatures(this, mAccount, mFeatures); 596 } catch (RemoteException e) { 597 onError(AccountManager.ERROR_CODE_REMOTE_EXCEPTION, "remote exception"); 598 } 599 } 600 601 public void onResult(Bundle result) { 602 IAccountManagerResponse response = getResponseAndClose(); 603 if (response != null) { 604 try { 605 if (result == null) { 606 response.onError(AccountManager.ERROR_CODE_INVALID_RESPONSE, "null bundle"); 607 return; 608 } 609 if (Log.isLoggable(TAG, Log.VERBOSE)) { 610 Log.v(TAG, getClass().getSimpleName() + " calling onResult() on response " 611 + response); 612 } 613 final Bundle newResult = new Bundle(); 614 newResult.putBoolean(AccountManager.KEY_BOOLEAN_RESULT, 615 result.getBoolean(AccountManager.KEY_BOOLEAN_RESULT, false)); 616 response.onResult(newResult); 617 } catch (RemoteException e) { 618 // if the caller is dead then there is no one to care about remote exceptions 619 if (Log.isLoggable(TAG, Log.VERBOSE)) { 620 Log.v(TAG, "failure while notifying response", e); 621 } 622 } 623 } 624 } 625 626 protected String toDebugString(long now) { 627 return super.toDebugString(now) + ", hasFeatures" 628 + ", " + mAccount 629 + ", " + (mFeatures != null ? TextUtils.join(",", mFeatures) : null); 630 } 631 } 632 633 public void removeAccount(IAccountManagerResponse response, Account account) { 634 if (Log.isLoggable(TAG, Log.VERBOSE)) { 635 Log.v(TAG, "removeAccount: " + account 636 + ", response " + response 637 + ", caller's uid " + Binder.getCallingUid() 638 + ", pid " + Binder.getCallingPid()); 639 } 640 if (response == null) throw new IllegalArgumentException("response is null"); 641 if (account == null) throw new IllegalArgumentException("account is null"); 642 checkManageAccountsPermission(); 643 UserAccounts accounts = getUserAccountsForCaller(); 644 long identityToken = clearCallingIdentity(); 645 646 cancelNotification(getSigninRequiredNotificationId(accounts, account)); 647 synchronized(accounts.credentialsPermissionNotificationIds) { 648 for (Pair<Pair<Account, String>, Integer> pair: 649 accounts.credentialsPermissionNotificationIds.keySet()) { 650 if (account.equals(pair.first.first)) { 651 int id = accounts.credentialsPermissionNotificationIds.get(pair); 652 cancelNotification(id); 653 } 654 } 655 } 656 657 try { 658 new RemoveAccountSession(accounts, response, account).bind(); 659 } finally { 660 restoreCallingIdentity(identityToken); 661 } 662 } 663 664 private class RemoveAccountSession extends Session { 665 final Account mAccount; 666 public RemoveAccountSession(UserAccounts accounts, IAccountManagerResponse response, 667 Account account) { 668 super(accounts, response, account.type, false /* expectActivityLaunch */, 669 true /* stripAuthTokenFromResult */); 670 mAccount = account; 671 } 672 673 protected String toDebugString(long now) { 674 return super.toDebugString(now) + ", removeAccount" 675 + ", account " + mAccount; 676 } 677 678 public void run() throws RemoteException { 679 mAuthenticator.getAccountRemovalAllowed(this, mAccount); 680 } 681 682 public void onResult(Bundle result) { 683 if (result != null && result.containsKey(AccountManager.KEY_BOOLEAN_RESULT) 684 && !result.containsKey(AccountManager.KEY_INTENT)) { 685 final boolean removalAllowed = result.getBoolean(AccountManager.KEY_BOOLEAN_RESULT); 686 if (removalAllowed) { 687 removeAccountInternal(mAccounts, mAccount); 688 } 689 IAccountManagerResponse response = getResponseAndClose(); 690 if (response != null) { 691 if (Log.isLoggable(TAG, Log.VERBOSE)) { 692 Log.v(TAG, getClass().getSimpleName() + " calling onResult() on response " 693 + response); 694 } 695 Bundle result2 = new Bundle(); 696 result2.putBoolean(AccountManager.KEY_BOOLEAN_RESULT, removalAllowed); 697 try { 698 response.onResult(result2); 699 } catch (RemoteException e) { 700 // ignore 701 } 702 } 703 } 704 super.onResult(result); 705 } 706 } 707 708 /* For testing */ 709 protected void removeAccountInternal(Account account) { 710 removeAccountInternal(getUserAccountsForCaller(), account); 711 } 712 713 private void removeAccountInternal(UserAccounts accounts, Account account) { 714 synchronized (accounts.cacheLock) { 715 final SQLiteDatabase db = accounts.openHelper.getWritableDatabase(); 716 db.delete(TABLE_ACCOUNTS, ACCOUNTS_NAME + "=? AND " + ACCOUNTS_TYPE+ "=?", 717 new String[]{account.name, account.type}); 718 removeAccountFromCacheLocked(accounts, account); 719 sendAccountsChangedBroadcast(accounts.userId); 720 } 721 } 722 723 public void invalidateAuthToken(String accountType, String authToken) { 724 if (Log.isLoggable(TAG, Log.VERBOSE)) { 725 Log.v(TAG, "invalidateAuthToken: accountType " + accountType 726 + ", caller's uid " + Binder.getCallingUid() 727 + ", pid " + Binder.getCallingPid()); 728 } 729 if (accountType == null) throw new IllegalArgumentException("accountType is null"); 730 if (authToken == null) throw new IllegalArgumentException("authToken is null"); 731 checkManageAccountsOrUseCredentialsPermissions(); 732 UserAccounts accounts = getUserAccountsForCaller(); 733 long identityToken = clearCallingIdentity(); 734 try { 735 synchronized (accounts.cacheLock) { 736 final SQLiteDatabase db = accounts.openHelper.getWritableDatabase(); 737 db.beginTransaction(); 738 try { 739 invalidateAuthTokenLocked(accounts, db, accountType, authToken); 740 db.setTransactionSuccessful(); 741 } finally { 742 db.endTransaction(); 743 } 744 } 745 } finally { 746 restoreCallingIdentity(identityToken); 747 } 748 } 749 750 private void invalidateAuthTokenLocked(UserAccounts accounts, SQLiteDatabase db, 751 String accountType, String authToken) { 752 if (authToken == null || accountType == null) { 753 return; 754 } 755 Cursor cursor = db.rawQuery( 756 "SELECT " + TABLE_AUTHTOKENS + "." + AUTHTOKENS_ID 757 + ", " + TABLE_ACCOUNTS + "." + ACCOUNTS_NAME 758 + ", " + TABLE_AUTHTOKENS + "." + AUTHTOKENS_TYPE 759 + " FROM " + TABLE_ACCOUNTS 760 + " JOIN " + TABLE_AUTHTOKENS 761 + " ON " + TABLE_ACCOUNTS + "." + ACCOUNTS_ID 762 + " = " + AUTHTOKENS_ACCOUNTS_ID 763 + " WHERE " + AUTHTOKENS_AUTHTOKEN + " = ? AND " 764 + TABLE_ACCOUNTS + "." + ACCOUNTS_TYPE + " = ?", 765 new String[]{authToken, accountType}); 766 try { 767 while (cursor.moveToNext()) { 768 long authTokenId = cursor.getLong(0); 769 String accountName = cursor.getString(1); 770 String authTokenType = cursor.getString(2); 771 db.delete(TABLE_AUTHTOKENS, AUTHTOKENS_ID + "=" + authTokenId, null); 772 writeAuthTokenIntoCacheLocked(accounts, db, new Account(accountName, accountType), 773 authTokenType, null); 774 } 775 } finally { 776 cursor.close(); 777 } 778 } 779 780 private boolean saveAuthTokenToDatabase(UserAccounts accounts, Account account, String type, 781 String authToken) { 782 if (account == null || type == null) { 783 return false; 784 } 785 cancelNotification(getSigninRequiredNotificationId(accounts, account)); 786 synchronized (accounts.cacheLock) { 787 final SQLiteDatabase db = accounts.openHelper.getWritableDatabase(); 788 db.beginTransaction(); 789 try { 790 long accountId = getAccountIdLocked(db, account); 791 if (accountId < 0) { 792 return false; 793 } 794 db.delete(TABLE_AUTHTOKENS, 795 AUTHTOKENS_ACCOUNTS_ID + "=" + accountId + " AND " + AUTHTOKENS_TYPE + "=?", 796 new String[]{type}); 797 ContentValues values = new ContentValues(); 798 values.put(AUTHTOKENS_ACCOUNTS_ID, accountId); 799 values.put(AUTHTOKENS_TYPE, type); 800 values.put(AUTHTOKENS_AUTHTOKEN, authToken); 801 if (db.insert(TABLE_AUTHTOKENS, AUTHTOKENS_AUTHTOKEN, values) >= 0) { 802 db.setTransactionSuccessful(); 803 writeAuthTokenIntoCacheLocked(accounts, db, account, type, authToken); 804 return true; 805 } 806 return false; 807 } finally { 808 db.endTransaction(); 809 } 810 } 811 } 812 813 public String peekAuthToken(Account account, String authTokenType) { 814 if (Log.isLoggable(TAG, Log.VERBOSE)) { 815 Log.v(TAG, "peekAuthToken: " + account 816 + ", authTokenType " + authTokenType 817 + ", caller's uid " + Binder.getCallingUid() 818 + ", pid " + Binder.getCallingPid()); 819 } 820 if (account == null) throw new IllegalArgumentException("account is null"); 821 if (authTokenType == null) throw new IllegalArgumentException("authTokenType is null"); 822 checkAuthenticateAccountsPermission(account); 823 UserAccounts accounts = getUserAccountsForCaller(); 824 long identityToken = clearCallingIdentity(); 825 try { 826 return readAuthTokenInternal(accounts, account, authTokenType); 827 } finally { 828 restoreCallingIdentity(identityToken); 829 } 830 } 831 832 public void setAuthToken(Account account, String authTokenType, String authToken) { 833 if (Log.isLoggable(TAG, Log.VERBOSE)) { 834 Log.v(TAG, "setAuthToken: " + account 835 + ", authTokenType " + authTokenType 836 + ", caller's uid " + Binder.getCallingUid() 837 + ", pid " + Binder.getCallingPid()); 838 } 839 if (account == null) throw new IllegalArgumentException("account is null"); 840 if (authTokenType == null) throw new IllegalArgumentException("authTokenType is null"); 841 checkAuthenticateAccountsPermission(account); 842 UserAccounts accounts = getUserAccountsForCaller(); 843 long identityToken = clearCallingIdentity(); 844 try { 845 saveAuthTokenToDatabase(accounts, account, authTokenType, authToken); 846 } finally { 847 restoreCallingIdentity(identityToken); 848 } 849 } 850 851 public void setPassword(Account account, String password) { 852 if (Log.isLoggable(TAG, Log.VERBOSE)) { 853 Log.v(TAG, "setAuthToken: " + account 854 + ", caller's uid " + Binder.getCallingUid() 855 + ", pid " + Binder.getCallingPid()); 856 } 857 if (account == null) throw new IllegalArgumentException("account is null"); 858 checkAuthenticateAccountsPermission(account); 859 UserAccounts accounts = getUserAccountsForCaller(); 860 long identityToken = clearCallingIdentity(); 861 try { 862 setPasswordInternal(accounts, account, password); 863 } finally { 864 restoreCallingIdentity(identityToken); 865 } 866 } 867 868 private void setPasswordInternal(UserAccounts accounts, Account account, String password) { 869 if (account == null) { 870 return; 871 } 872 synchronized (accounts.cacheLock) { 873 final SQLiteDatabase db = accounts.openHelper.getWritableDatabase(); 874 db.beginTransaction(); 875 try { 876 final ContentValues values = new ContentValues(); 877 values.put(ACCOUNTS_PASSWORD, password); 878 final long accountId = getAccountIdLocked(db, account); 879 if (accountId >= 0) { 880 final String[] argsAccountId = {String.valueOf(accountId)}; 881 db.update(TABLE_ACCOUNTS, values, ACCOUNTS_ID + "=?", argsAccountId); 882 db.delete(TABLE_AUTHTOKENS, AUTHTOKENS_ACCOUNTS_ID + "=?", argsAccountId); 883 accounts.authTokenCache.remove(account); 884 db.setTransactionSuccessful(); 885 } 886 } finally { 887 db.endTransaction(); 888 } 889 sendAccountsChangedBroadcast(accounts.userId); 890 } 891 } 892 893 private void sendAccountsChangedBroadcast(int userId) { 894 Log.i(TAG, "the accounts changed, sending broadcast of " 895 + ACCOUNTS_CHANGED_INTENT.getAction()); 896 mContext.sendBroadcast(ACCOUNTS_CHANGED_INTENT, userId); 897 } 898 899 public void clearPassword(Account account) { 900 if (Log.isLoggable(TAG, Log.VERBOSE)) { 901 Log.v(TAG, "clearPassword: " + account 902 + ", caller's uid " + Binder.getCallingUid() 903 + ", pid " + Binder.getCallingPid()); 904 } 905 if (account == null) throw new IllegalArgumentException("account is null"); 906 checkManageAccountsPermission(); 907 UserAccounts accounts = getUserAccountsForCaller(); 908 long identityToken = clearCallingIdentity(); 909 try { 910 setPasswordInternal(accounts, account, null); 911 } finally { 912 restoreCallingIdentity(identityToken); 913 } 914 } 915 916 public void setUserData(Account account, String key, String value) { 917 if (Log.isLoggable(TAG, Log.VERBOSE)) { 918 Log.v(TAG, "setUserData: " + account 919 + ", key " + key 920 + ", caller's uid " + Binder.getCallingUid() 921 + ", pid " + Binder.getCallingPid()); 922 } 923 if (key == null) throw new IllegalArgumentException("key is null"); 924 if (account == null) throw new IllegalArgumentException("account is null"); 925 checkAuthenticateAccountsPermission(account); 926 UserAccounts accounts = getUserAccountsForCaller(); 927 long identityToken = clearCallingIdentity(); 928 try { 929 setUserdataInternal(accounts, account, key, value); 930 } finally { 931 restoreCallingIdentity(identityToken); 932 } 933 } 934 935 private void setUserdataInternal(UserAccounts accounts, Account account, String key, 936 String value) { 937 if (account == null || key == null) { 938 return; 939 } 940 synchronized (accounts.cacheLock) { 941 final SQLiteDatabase db = accounts.openHelper.getWritableDatabase(); 942 db.beginTransaction(); 943 try { 944 long accountId = getAccountIdLocked(db, account); 945 if (accountId < 0) { 946 return; 947 } 948 long extrasId = getExtrasIdLocked(db, accountId, key); 949 if (extrasId < 0 ) { 950 extrasId = insertExtraLocked(db, accountId, key, value); 951 if (extrasId < 0) { 952 return; 953 } 954 } else { 955 ContentValues values = new ContentValues(); 956 values.put(EXTRAS_VALUE, value); 957 if (1 != db.update(TABLE_EXTRAS, values, EXTRAS_ID + "=" + extrasId, null)) { 958 return; 959 } 960 961 } 962 writeUserDataIntoCacheLocked(accounts, db, account, key, value); 963 db.setTransactionSuccessful(); 964 } finally { 965 db.endTransaction(); 966 } 967 } 968 } 969 970 private void onResult(IAccountManagerResponse response, Bundle result) { 971 if (result == null) { 972 Log.e(TAG, "the result is unexpectedly null", new Exception()); 973 } 974 if (Log.isLoggable(TAG, Log.VERBOSE)) { 975 Log.v(TAG, getClass().getSimpleName() + " calling onResult() on response " 976 + response); 977 } 978 try { 979 response.onResult(result); 980 } catch (RemoteException e) { 981 // if the caller is dead then there is no one to care about remote 982 // exceptions 983 if (Log.isLoggable(TAG, Log.VERBOSE)) { 984 Log.v(TAG, "failure while notifying response", e); 985 } 986 } 987 } 988 989 public void getAuthTokenLabel(IAccountManagerResponse response, final String accountType, 990 final String authTokenType) 991 throws RemoteException { 992 if (accountType == null) throw new IllegalArgumentException("accountType is null"); 993 if (authTokenType == null) throw new IllegalArgumentException("authTokenType is null"); 994 995 final int callingUid = getCallingUid(); 996 clearCallingIdentity(); 997 if (callingUid != android.os.Process.SYSTEM_UID) { 998 throw new SecurityException("can only call from system"); 999 } 1000 UserAccounts accounts = getUserAccounts(UserId.getUserId(callingUid)); 1001 long identityToken = clearCallingIdentity(); 1002 try { 1003 new Session(accounts, response, accountType, false, 1004 false /* stripAuthTokenFromResult */) { 1005 protected String toDebugString(long now) { 1006 return super.toDebugString(now) + ", getAuthTokenLabel" 1007 + ", " + accountType 1008 + ", authTokenType " + authTokenType; 1009 } 1010 1011 public void run() throws RemoteException { 1012 mAuthenticator.getAuthTokenLabel(this, authTokenType); 1013 } 1014 1015 public void onResult(Bundle result) { 1016 if (result != null) { 1017 String label = result.getString(AccountManager.KEY_AUTH_TOKEN_LABEL); 1018 Bundle bundle = new Bundle(); 1019 bundle.putString(AccountManager.KEY_AUTH_TOKEN_LABEL, label); 1020 super.onResult(bundle); 1021 return; 1022 } else { 1023 super.onResult(result); 1024 } 1025 } 1026 }.bind(); 1027 } finally { 1028 restoreCallingIdentity(identityToken); 1029 } 1030 } 1031 1032 public void getAuthToken(IAccountManagerResponse response, final Account account, 1033 final String authTokenType, final boolean notifyOnAuthFailure, 1034 final boolean expectActivityLaunch, Bundle loginOptionsIn) { 1035 if (Log.isLoggable(TAG, Log.VERBOSE)) { 1036 Log.v(TAG, "getAuthToken: " + account 1037 + ", response " + response 1038 + ", authTokenType " + authTokenType 1039 + ", notifyOnAuthFailure " + notifyOnAuthFailure 1040 + ", expectActivityLaunch " + expectActivityLaunch 1041 + ", caller's uid " + Binder.getCallingUid() 1042 + ", pid " + Binder.getCallingPid()); 1043 } 1044 if (response == null) throw new IllegalArgumentException("response is null"); 1045 if (account == null) throw new IllegalArgumentException("account is null"); 1046 if (authTokenType == null) throw new IllegalArgumentException("authTokenType is null"); 1047 checkBinderPermission(Manifest.permission.USE_CREDENTIALS); 1048 UserAccounts accounts = getUserAccountsForCaller(); 1049 AccountAuthenticatorCache.ServiceInfo<AuthenticatorDescription> authenticatorInfo = 1050 mAuthenticatorCache.getServiceInfo( 1051 AuthenticatorDescription.newKey(account.type)); 1052 final boolean customTokens = 1053 authenticatorInfo != null && authenticatorInfo.type.customTokens; 1054 1055 // skip the check if customTokens 1056 final int callerUid = Binder.getCallingUid(); 1057 final boolean permissionGranted = customTokens || 1058 permissionIsGranted(account, authTokenType, callerUid); 1059 1060 final Bundle loginOptions = (loginOptionsIn == null) ? new Bundle() : 1061 loginOptionsIn; 1062 // let authenticator know the identity of the caller 1063 loginOptions.putInt(AccountManager.KEY_CALLER_UID, callerUid); 1064 loginOptions.putInt(AccountManager.KEY_CALLER_PID, Binder.getCallingPid()); 1065 if (notifyOnAuthFailure) { 1066 loginOptions.putBoolean(AccountManager.KEY_NOTIFY_ON_FAILURE, true); 1067 } 1068 1069 long identityToken = clearCallingIdentity(); 1070 try { 1071 // if the caller has permission, do the peek. otherwise go the more expensive 1072 // route of starting a Session 1073 if (!customTokens && permissionGranted) { 1074 String authToken = readAuthTokenInternal(accounts, account, authTokenType); 1075 if (authToken != null) { 1076 Bundle result = new Bundle(); 1077 result.putString(AccountManager.KEY_AUTHTOKEN, authToken); 1078 result.putString(AccountManager.KEY_ACCOUNT_NAME, account.name); 1079 result.putString(AccountManager.KEY_ACCOUNT_TYPE, account.type); 1080 onResult(response, result); 1081 return; 1082 } 1083 } 1084 1085 new Session(accounts, response, account.type, expectActivityLaunch, 1086 false /* stripAuthTokenFromResult */) { 1087 protected String toDebugString(long now) { 1088 if (loginOptions != null) loginOptions.keySet(); 1089 return super.toDebugString(now) + ", getAuthToken" 1090 + ", " + account 1091 + ", authTokenType " + authTokenType 1092 + ", loginOptions " + loginOptions 1093 + ", notifyOnAuthFailure " + notifyOnAuthFailure; 1094 } 1095 1096 public void run() throws RemoteException { 1097 // If the caller doesn't have permission then create and return the 1098 // "grant permission" intent instead of the "getAuthToken" intent. 1099 if (!permissionGranted) { 1100 mAuthenticator.getAuthTokenLabel(this, authTokenType); 1101 } else { 1102 mAuthenticator.getAuthToken(this, account, authTokenType, loginOptions); 1103 } 1104 } 1105 1106 public void onResult(Bundle result) { 1107 if (result != null) { 1108 if (result.containsKey(AccountManager.KEY_AUTH_TOKEN_LABEL)) { 1109 Intent intent = newGrantCredentialsPermissionIntent(account, callerUid, 1110 new AccountAuthenticatorResponse(this), 1111 authTokenType, 1112 result.getString(AccountManager.KEY_AUTH_TOKEN_LABEL)); 1113 Bundle bundle = new Bundle(); 1114 bundle.putParcelable(AccountManager.KEY_INTENT, intent); 1115 onResult(bundle); 1116 return; 1117 } 1118 String authToken = result.getString(AccountManager.KEY_AUTHTOKEN); 1119 if (authToken != null) { 1120 String name = result.getString(AccountManager.KEY_ACCOUNT_NAME); 1121 String type = result.getString(AccountManager.KEY_ACCOUNT_TYPE); 1122 if (TextUtils.isEmpty(type) || TextUtils.isEmpty(name)) { 1123 onError(AccountManager.ERROR_CODE_INVALID_RESPONSE, 1124 "the type and name should not be empty"); 1125 return; 1126 } 1127 if (!customTokens) { 1128 saveAuthTokenToDatabase(mAccounts, new Account(name, type), 1129 authTokenType, authToken); 1130 } 1131 } 1132 1133 Intent intent = result.getParcelable(AccountManager.KEY_INTENT); 1134 if (intent != null && notifyOnAuthFailure && !customTokens) { 1135 doNotification(mAccounts, 1136 account, result.getString(AccountManager.KEY_AUTH_FAILED_MESSAGE), 1137 intent); 1138 } 1139 } 1140 super.onResult(result); 1141 } 1142 }.bind(); 1143 } finally { 1144 restoreCallingIdentity(identityToken); 1145 } 1146 } 1147 1148 private void createNoCredentialsPermissionNotification(Account account, Intent intent) { 1149 int uid = intent.getIntExtra( 1150 GrantCredentialsPermissionActivity.EXTRAS_REQUESTING_UID, -1); 1151 String authTokenType = intent.getStringExtra( 1152 GrantCredentialsPermissionActivity.EXTRAS_AUTH_TOKEN_TYPE); 1153 String authTokenLabel = intent.getStringExtra( 1154 GrantCredentialsPermissionActivity.EXTRAS_AUTH_TOKEN_LABEL); 1155 1156 Notification n = new Notification(android.R.drawable.stat_sys_warning, null, 1157 0 /* when */); 1158 final String titleAndSubtitle = 1159 mContext.getString(R.string.permission_request_notification_with_subtitle, 1160 account.name); 1161 final int index = titleAndSubtitle.indexOf('\n'); 1162 String title = titleAndSubtitle; 1163 String subtitle = ""; 1164 if (index > 0) { 1165 title = titleAndSubtitle.substring(0, index); 1166 subtitle = titleAndSubtitle.substring(index + 1); 1167 } 1168 n.setLatestEventInfo(mContext, 1169 title, subtitle, 1170 PendingIntent.getActivity(mContext, 0, intent, PendingIntent.FLAG_CANCEL_CURRENT)); 1171 installNotification(getCredentialPermissionNotificationId(account, authTokenType, uid), n); 1172 } 1173 1174 String getAccountLabel(String accountType) { 1175 RegisteredServicesCache.ServiceInfo<AuthenticatorDescription> serviceInfo = 1176 mAuthenticatorCache.getServiceInfo( 1177 AuthenticatorDescription.newKey(accountType)); 1178 if (serviceInfo == null) { 1179 throw new IllegalArgumentException("unknown account type: " + accountType); 1180 } 1181 1182 final Context authContext; 1183 try { 1184 authContext = mContext.createPackageContext( 1185 serviceInfo.type.packageName, 0); 1186 } catch (PackageManager.NameNotFoundException e) { 1187 throw new IllegalArgumentException("unknown account type: " + accountType); 1188 } 1189 try { 1190 return authContext.getString(serviceInfo.type.labelId); 1191 } catch (Resources.NotFoundException e) { 1192 throw new IllegalArgumentException("unknown account type: " + accountType); 1193 } 1194 } 1195 1196 private Intent newGrantCredentialsPermissionIntent(Account account, int uid, 1197 AccountAuthenticatorResponse response, String authTokenType, String authTokenLabel) { 1198 1199 Intent intent = new Intent(mContext, GrantCredentialsPermissionActivity.class); 1200 // See FLAG_ACTIVITY_NEW_TASK docs for limitations and benefits of the flag. 1201 // Since it was set in Eclair+ we can't change it without breaking apps using 1202 // the intent from a non-Activity context. 1203 intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); 1204 intent.addCategory( 1205 String.valueOf(getCredentialPermissionNotificationId(account, authTokenType, uid))); 1206 1207 intent.putExtra(GrantCredentialsPermissionActivity.EXTRAS_ACCOUNT, account); 1208 intent.putExtra(GrantCredentialsPermissionActivity.EXTRAS_AUTH_TOKEN_TYPE, authTokenType); 1209 intent.putExtra(GrantCredentialsPermissionActivity.EXTRAS_RESPONSE, response); 1210 intent.putExtra(GrantCredentialsPermissionActivity.EXTRAS_REQUESTING_UID, uid); 1211 1212 return intent; 1213 } 1214 1215 private Integer getCredentialPermissionNotificationId(Account account, String authTokenType, 1216 int uid) { 1217 Integer id; 1218 UserAccounts accounts = getUserAccounts(UserId.getUserId(uid)); 1219 synchronized (accounts.credentialsPermissionNotificationIds) { 1220 final Pair<Pair<Account, String>, Integer> key = 1221 new Pair<Pair<Account, String>, Integer>( 1222 new Pair<Account, String>(account, authTokenType), uid); 1223 id = accounts.credentialsPermissionNotificationIds.get(key); 1224 if (id == null) { 1225 id = mNotificationIds.incrementAndGet(); 1226 accounts.credentialsPermissionNotificationIds.put(key, id); 1227 } 1228 } 1229 return id; 1230 } 1231 1232 private Integer getSigninRequiredNotificationId(UserAccounts accounts, Account account) { 1233 Integer id; 1234 synchronized (accounts.signinRequiredNotificationIds) { 1235 id = accounts.signinRequiredNotificationIds.get(account); 1236 if (id == null) { 1237 id = mNotificationIds.incrementAndGet(); 1238 accounts.signinRequiredNotificationIds.put(account, id); 1239 } 1240 } 1241 return id; 1242 } 1243 1244 public void addAcount(final IAccountManagerResponse response, final String accountType, 1245 final String authTokenType, final String[] requiredFeatures, 1246 final boolean expectActivityLaunch, final Bundle optionsIn) { 1247 if (Log.isLoggable(TAG, Log.VERBOSE)) { 1248 Log.v(TAG, "addAccount: accountType " + accountType 1249 + ", response " + response 1250 + ", authTokenType " + authTokenType 1251 + ", requiredFeatures " + stringArrayToString(requiredFeatures) 1252 + ", expectActivityLaunch " + expectActivityLaunch 1253 + ", caller's uid " + Binder.getCallingUid() 1254 + ", pid " + Binder.getCallingPid()); 1255 } 1256 if (response == null) throw new IllegalArgumentException("response is null"); 1257 if (accountType == null) throw new IllegalArgumentException("accountType is null"); 1258 checkManageAccountsPermission(); 1259 1260 UserAccounts accounts = getUserAccountsForCaller(); 1261 final int pid = Binder.getCallingPid(); 1262 final int uid = Binder.getCallingUid(); 1263 final Bundle options = (optionsIn == null) ? new Bundle() : optionsIn; 1264 options.putInt(AccountManager.KEY_CALLER_UID, uid); 1265 options.putInt(AccountManager.KEY_CALLER_PID, pid); 1266 1267 long identityToken = clearCallingIdentity(); 1268 try { 1269 new Session(accounts, response, accountType, expectActivityLaunch, 1270 true /* stripAuthTokenFromResult */) { 1271 public void run() throws RemoteException { 1272 mAuthenticator.addAccount(this, mAccountType, authTokenType, requiredFeatures, 1273 options); 1274 } 1275 1276 protected String toDebugString(long now) { 1277 return super.toDebugString(now) + ", addAccount" 1278 + ", accountType " + accountType 1279 + ", requiredFeatures " 1280 + (requiredFeatures != null 1281 ? TextUtils.join(",", requiredFeatures) 1282 : null); 1283 } 1284 }.bind(); 1285 } finally { 1286 restoreCallingIdentity(identityToken); 1287 } 1288 } 1289 1290 public void confirmCredentials(IAccountManagerResponse response, 1291 final Account account, final Bundle options, final boolean expectActivityLaunch) { 1292 if (Log.isLoggable(TAG, Log.VERBOSE)) { 1293 Log.v(TAG, "confirmCredentials: " + account 1294 + ", response " + response 1295 + ", expectActivityLaunch " + expectActivityLaunch 1296 + ", caller's uid " + Binder.getCallingUid() 1297 + ", pid " + Binder.getCallingPid()); 1298 } 1299 if (response == null) throw new IllegalArgumentException("response is null"); 1300 if (account == null) throw new IllegalArgumentException("account is null"); 1301 checkManageAccountsPermission(); 1302 UserAccounts accounts = getUserAccountsForCaller(); 1303 long identityToken = clearCallingIdentity(); 1304 try { 1305 new Session(accounts, response, account.type, expectActivityLaunch, 1306 true /* stripAuthTokenFromResult */) { 1307 public void run() throws RemoteException { 1308 mAuthenticator.confirmCredentials(this, account, options); 1309 } 1310 protected String toDebugString(long now) { 1311 return super.toDebugString(now) + ", confirmCredentials" 1312 + ", " + account; 1313 } 1314 }.bind(); 1315 } finally { 1316 restoreCallingIdentity(identityToken); 1317 } 1318 } 1319 1320 public void updateCredentials(IAccountManagerResponse response, final Account account, 1321 final String authTokenType, final boolean expectActivityLaunch, 1322 final Bundle loginOptions) { 1323 if (Log.isLoggable(TAG, Log.VERBOSE)) { 1324 Log.v(TAG, "updateCredentials: " + account 1325 + ", response " + response 1326 + ", authTokenType " + authTokenType 1327 + ", expectActivityLaunch " + expectActivityLaunch 1328 + ", caller's uid " + Binder.getCallingUid() 1329 + ", pid " + Binder.getCallingPid()); 1330 } 1331 if (response == null) throw new IllegalArgumentException("response is null"); 1332 if (account == null) throw new IllegalArgumentException("account is null"); 1333 if (authTokenType == null) throw new IllegalArgumentException("authTokenType is null"); 1334 checkManageAccountsPermission(); 1335 UserAccounts accounts = getUserAccountsForCaller(); 1336 long identityToken = clearCallingIdentity(); 1337 try { 1338 new Session(accounts, response, account.type, expectActivityLaunch, 1339 true /* stripAuthTokenFromResult */) { 1340 public void run() throws RemoteException { 1341 mAuthenticator.updateCredentials(this, account, authTokenType, loginOptions); 1342 } 1343 protected String toDebugString(long now) { 1344 if (loginOptions != null) loginOptions.keySet(); 1345 return super.toDebugString(now) + ", updateCredentials" 1346 + ", " + account 1347 + ", authTokenType " + authTokenType 1348 + ", loginOptions " + loginOptions; 1349 } 1350 }.bind(); 1351 } finally { 1352 restoreCallingIdentity(identityToken); 1353 } 1354 } 1355 1356 public void editProperties(IAccountManagerResponse response, final String accountType, 1357 final boolean expectActivityLaunch) { 1358 if (Log.isLoggable(TAG, Log.VERBOSE)) { 1359 Log.v(TAG, "editProperties: accountType " + accountType 1360 + ", response " + response 1361 + ", expectActivityLaunch " + expectActivityLaunch 1362 + ", caller's uid " + Binder.getCallingUid() 1363 + ", pid " + Binder.getCallingPid()); 1364 } 1365 if (response == null) throw new IllegalArgumentException("response is null"); 1366 if (accountType == null) throw new IllegalArgumentException("accountType is null"); 1367 checkManageAccountsPermission(); 1368 UserAccounts accounts = getUserAccountsForCaller(); 1369 long identityToken = clearCallingIdentity(); 1370 try { 1371 new Session(accounts, response, accountType, expectActivityLaunch, 1372 true /* stripAuthTokenFromResult */) { 1373 public void run() throws RemoteException { 1374 mAuthenticator.editProperties(this, mAccountType); 1375 } 1376 protected String toDebugString(long now) { 1377 return super.toDebugString(now) + ", editProperties" 1378 + ", accountType " + accountType; 1379 } 1380 }.bind(); 1381 } finally { 1382 restoreCallingIdentity(identityToken); 1383 } 1384 } 1385 1386 private class GetAccountsByTypeAndFeatureSession extends Session { 1387 private final String[] mFeatures; 1388 private volatile Account[] mAccountsOfType = null; 1389 private volatile ArrayList<Account> mAccountsWithFeatures = null; 1390 private volatile int mCurrentAccount = 0; 1391 1392 public GetAccountsByTypeAndFeatureSession(UserAccounts accounts, 1393 IAccountManagerResponse response, String type, String[] features) { 1394 super(accounts, response, type, false /* expectActivityLaunch */, 1395 true /* stripAuthTokenFromResult */); 1396 mFeatures = features; 1397 } 1398 1399 public void run() throws RemoteException { 1400 synchronized (mAccounts.cacheLock) { 1401 mAccountsOfType = getAccountsFromCacheLocked(mAccounts, mAccountType); 1402 } 1403 // check whether each account matches the requested features 1404 mAccountsWithFeatures = new ArrayList<Account>(mAccountsOfType.length); 1405 mCurrentAccount = 0; 1406 1407 checkAccount(); 1408 } 1409 1410 public void checkAccount() { 1411 if (mCurrentAccount >= mAccountsOfType.length) { 1412 sendResult(); 1413 return; 1414 } 1415 1416 final IAccountAuthenticator accountAuthenticator = mAuthenticator; 1417 if (accountAuthenticator == null) { 1418 // It is possible that the authenticator has died, which is indicated by 1419 // mAuthenticator being set to null. If this happens then just abort. 1420 // There is no need to send back a result or error in this case since 1421 // that already happened when mAuthenticator was cleared. 1422 if (Log.isLoggable(TAG, Log.VERBOSE)) { 1423 Log.v(TAG, "checkAccount: aborting session since we are no longer" 1424 + " connected to the authenticator, " + toDebugString()); 1425 } 1426 return; 1427 } 1428 try { 1429 accountAuthenticator.hasFeatures(this, mAccountsOfType[mCurrentAccount], mFeatures); 1430 } catch (RemoteException e) { 1431 onError(AccountManager.ERROR_CODE_REMOTE_EXCEPTION, "remote exception"); 1432 } 1433 } 1434 1435 public void onResult(Bundle result) { 1436 mNumResults++; 1437 if (result == null) { 1438 onError(AccountManager.ERROR_CODE_INVALID_RESPONSE, "null bundle"); 1439 return; 1440 } 1441 if (result.getBoolean(AccountManager.KEY_BOOLEAN_RESULT, false)) { 1442 mAccountsWithFeatures.add(mAccountsOfType[mCurrentAccount]); 1443 } 1444 mCurrentAccount++; 1445 checkAccount(); 1446 } 1447 1448 public void sendResult() { 1449 IAccountManagerResponse response = getResponseAndClose(); 1450 if (response != null) { 1451 try { 1452 Account[] accounts = new Account[mAccountsWithFeatures.size()]; 1453 for (int i = 0; i < accounts.length; i++) { 1454 accounts[i] = mAccountsWithFeatures.get(i); 1455 } 1456 if (Log.isLoggable(TAG, Log.VERBOSE)) { 1457 Log.v(TAG, getClass().getSimpleName() + " calling onResult() on response " 1458 + response); 1459 } 1460 Bundle result = new Bundle(); 1461 result.putParcelableArray(AccountManager.KEY_ACCOUNTS, accounts); 1462 response.onResult(result); 1463 } catch (RemoteException e) { 1464 // if the caller is dead then there is no one to care about remote exceptions 1465 if (Log.isLoggable(TAG, Log.VERBOSE)) { 1466 Log.v(TAG, "failure while notifying response", e); 1467 } 1468 } 1469 } 1470 } 1471 1472 1473 protected String toDebugString(long now) { 1474 return super.toDebugString(now) + ", getAccountsByTypeAndFeatures" 1475 + ", " + (mFeatures != null ? TextUtils.join(",", mFeatures) : null); 1476 } 1477 } 1478 1479 /** 1480 * Returns the accounts for a specific user 1481 * @hide 1482 */ 1483 public Account[] getAccounts(int userId) { 1484 checkReadAccountsPermission(); 1485 UserAccounts accounts = getUserAccounts(userId); 1486 long identityToken = clearCallingIdentity(); 1487 try { 1488 synchronized (accounts.cacheLock) { 1489 return getAccountsFromCacheLocked(accounts, null); 1490 } 1491 } finally { 1492 restoreCallingIdentity(identityToken); 1493 } 1494 } 1495 1496 /** 1497 * Returns all the accounts qualified by user. 1498 * @hide 1499 */ 1500 public AccountAndUser[] getAllAccounts() { 1501 ArrayList<AccountAndUser> allAccounts = new ArrayList<AccountAndUser>(); 1502 List<UserInfo> users = getAllUsers(); 1503 if (users == null) return new AccountAndUser[0]; 1504 1505 synchronized(mUsers) { 1506 for (UserInfo user : users) { 1507 UserAccounts userAccounts = getUserAccounts(user.id); 1508 if (userAccounts == null) continue; 1509 synchronized (userAccounts.cacheLock) { 1510 Account[] accounts = getAccountsFromCacheLocked(userAccounts, null); 1511 for (int a = 0; a < accounts.length; a++) { 1512 allAccounts.add(new AccountAndUser(accounts[a], user.id)); 1513 } 1514 } 1515 } 1516 } 1517 AccountAndUser[] accountsArray = new AccountAndUser[allAccounts.size()]; 1518 return allAccounts.toArray(accountsArray); 1519 } 1520 1521 public Account[] getAccounts(String type) { 1522 if (Log.isLoggable(TAG, Log.VERBOSE)) { 1523 Log.v(TAG, "getAccounts: accountType " + type 1524 + ", caller's uid " + Binder.getCallingUid() 1525 + ", pid " + Binder.getCallingPid()); 1526 } 1527 checkReadAccountsPermission(); 1528 UserAccounts accounts = getUserAccountsForCaller(); 1529 long identityToken = clearCallingIdentity(); 1530 try { 1531 synchronized (accounts.cacheLock) { 1532 return getAccountsFromCacheLocked(accounts, type); 1533 } 1534 } finally { 1535 restoreCallingIdentity(identityToken); 1536 } 1537 } 1538 1539 public void getAccountsByFeatures(IAccountManagerResponse response, 1540 String type, String[] features) { 1541 if (Log.isLoggable(TAG, Log.VERBOSE)) { 1542 Log.v(TAG, "getAccounts: accountType " + type 1543 + ", response " + response 1544 + ", features " + stringArrayToString(features) 1545 + ", caller's uid " + Binder.getCallingUid() 1546 + ", pid " + Binder.getCallingPid()); 1547 } 1548 if (response == null) throw new IllegalArgumentException("response is null"); 1549 if (type == null) throw new IllegalArgumentException("accountType is null"); 1550 checkReadAccountsPermission(); 1551 UserAccounts userAccounts = getUserAccountsForCaller(); 1552 long identityToken = clearCallingIdentity(); 1553 try { 1554 if (features == null || features.length == 0) { 1555 Account[] accounts; 1556 synchronized (userAccounts.cacheLock) { 1557 accounts = getAccountsFromCacheLocked(userAccounts, type); 1558 } 1559 Bundle result = new Bundle(); 1560 result.putParcelableArray(AccountManager.KEY_ACCOUNTS, accounts); 1561 onResult(response, result); 1562 return; 1563 } 1564 new GetAccountsByTypeAndFeatureSession(userAccounts, response, type, features).bind(); 1565 } finally { 1566 restoreCallingIdentity(identityToken); 1567 } 1568 } 1569 1570 private long getAccountIdLocked(SQLiteDatabase db, Account account) { 1571 Cursor cursor = db.query(TABLE_ACCOUNTS, new String[]{ACCOUNTS_ID}, 1572 "name=? AND type=?", new String[]{account.name, account.type}, null, null, null); 1573 try { 1574 if (cursor.moveToNext()) { 1575 return cursor.getLong(0); 1576 } 1577 return -1; 1578 } finally { 1579 cursor.close(); 1580 } 1581 } 1582 1583 private long getExtrasIdLocked(SQLiteDatabase db, long accountId, String key) { 1584 Cursor cursor = db.query(TABLE_EXTRAS, new String[]{EXTRAS_ID}, 1585 EXTRAS_ACCOUNTS_ID + "=" + accountId + " AND " + EXTRAS_KEY + "=?", 1586 new String[]{key}, null, null, null); 1587 try { 1588 if (cursor.moveToNext()) { 1589 return cursor.getLong(0); 1590 } 1591 return -1; 1592 } finally { 1593 cursor.close(); 1594 } 1595 } 1596 1597 private abstract class Session extends IAccountAuthenticatorResponse.Stub 1598 implements IBinder.DeathRecipient, ServiceConnection { 1599 IAccountManagerResponse mResponse; 1600 final String mAccountType; 1601 final boolean mExpectActivityLaunch; 1602 final long mCreationTime; 1603 1604 public int mNumResults = 0; 1605 private int mNumRequestContinued = 0; 1606 private int mNumErrors = 0; 1607 1608 1609 IAccountAuthenticator mAuthenticator = null; 1610 1611 private final boolean mStripAuthTokenFromResult; 1612 protected final UserAccounts mAccounts; 1613 1614 public Session(UserAccounts accounts, IAccountManagerResponse response, String accountType, 1615 boolean expectActivityLaunch, boolean stripAuthTokenFromResult) { 1616 super(); 1617 if (response == null) throw new IllegalArgumentException("response is null"); 1618 if (accountType == null) throw new IllegalArgumentException("accountType is null"); 1619 mAccounts = accounts; 1620 mStripAuthTokenFromResult = stripAuthTokenFromResult; 1621 mResponse = response; 1622 mAccountType = accountType; 1623 mExpectActivityLaunch = expectActivityLaunch; 1624 mCreationTime = SystemClock.elapsedRealtime(); 1625 synchronized (mSessions) { 1626 mSessions.put(toString(), this); 1627 } 1628 try { 1629 response.asBinder().linkToDeath(this, 0 /* flags */); 1630 } catch (RemoteException e) { 1631 mResponse = null; 1632 binderDied(); 1633 } 1634 } 1635 1636 IAccountManagerResponse getResponseAndClose() { 1637 if (mResponse == null) { 1638 // this session has already been closed 1639 return null; 1640 } 1641 IAccountManagerResponse response = mResponse; 1642 close(); // this clears mResponse so we need to save the response before this call 1643 return response; 1644 } 1645 1646 private void close() { 1647 synchronized (mSessions) { 1648 if (mSessions.remove(toString()) == null) { 1649 // the session was already closed, so bail out now 1650 return; 1651 } 1652 } 1653 if (mResponse != null) { 1654 // stop listening for response deaths 1655 mResponse.asBinder().unlinkToDeath(this, 0 /* flags */); 1656 1657 // clear this so that we don't accidentally send any further results 1658 mResponse = null; 1659 } 1660 cancelTimeout(); 1661 unbind(); 1662 } 1663 1664 public void binderDied() { 1665 mResponse = null; 1666 close(); 1667 } 1668 1669 protected String toDebugString() { 1670 return toDebugString(SystemClock.elapsedRealtime()); 1671 } 1672 1673 protected String toDebugString(long now) { 1674 return "Session: expectLaunch " + mExpectActivityLaunch 1675 + ", connected " + (mAuthenticator != null) 1676 + ", stats (" + mNumResults + "/" + mNumRequestContinued 1677 + "/" + mNumErrors + ")" 1678 + ", lifetime " + ((now - mCreationTime) / 1000.0); 1679 } 1680 1681 void bind() { 1682 if (Log.isLoggable(TAG, Log.VERBOSE)) { 1683 Log.v(TAG, "initiating bind to authenticator type " + mAccountType); 1684 } 1685 if (!bindToAuthenticator(mAccountType)) { 1686 Log.d(TAG, "bind attempt failed for " + toDebugString()); 1687 onError(AccountManager.ERROR_CODE_REMOTE_EXCEPTION, "bind failure"); 1688 } 1689 } 1690 1691 private void unbind() { 1692 if (mAuthenticator != null) { 1693 mAuthenticator = null; 1694 mContext.unbindService(this); 1695 } 1696 } 1697 1698 public void scheduleTimeout() { 1699 mMessageHandler.sendMessageDelayed( 1700 mMessageHandler.obtainMessage(MESSAGE_TIMED_OUT, this), TIMEOUT_DELAY_MS); 1701 } 1702 1703 public void cancelTimeout() { 1704 mMessageHandler.removeMessages(MESSAGE_TIMED_OUT, this); 1705 } 1706 1707 public void onServiceConnected(ComponentName name, IBinder service) { 1708 mAuthenticator = IAccountAuthenticator.Stub.asInterface(service); 1709 try { 1710 run(); 1711 } catch (RemoteException e) { 1712 onError(AccountManager.ERROR_CODE_REMOTE_EXCEPTION, 1713 "remote exception"); 1714 } 1715 } 1716 1717 public void onServiceDisconnected(ComponentName name) { 1718 mAuthenticator = null; 1719 IAccountManagerResponse response = getResponseAndClose(); 1720 if (response != null) { 1721 try { 1722 response.onError(AccountManager.ERROR_CODE_REMOTE_EXCEPTION, 1723 "disconnected"); 1724 } catch (RemoteException e) { 1725 if (Log.isLoggable(TAG, Log.VERBOSE)) { 1726 Log.v(TAG, "Session.onServiceDisconnected: " 1727 + "caught RemoteException while responding", e); 1728 } 1729 } 1730 } 1731 } 1732 1733 public abstract void run() throws RemoteException; 1734 1735 public void onTimedOut() { 1736 IAccountManagerResponse response = getResponseAndClose(); 1737 if (response != null) { 1738 try { 1739 response.onError(AccountManager.ERROR_CODE_REMOTE_EXCEPTION, 1740 "timeout"); 1741 } catch (RemoteException e) { 1742 if (Log.isLoggable(TAG, Log.VERBOSE)) { 1743 Log.v(TAG, "Session.onTimedOut: caught RemoteException while responding", 1744 e); 1745 } 1746 } 1747 } 1748 } 1749 1750 public void onResult(Bundle result) { 1751 mNumResults++; 1752 if (result != null && !TextUtils.isEmpty(result.getString(AccountManager.KEY_AUTHTOKEN))) { 1753 String accountName = result.getString(AccountManager.KEY_ACCOUNT_NAME); 1754 String accountType = result.getString(AccountManager.KEY_ACCOUNT_TYPE); 1755 if (!TextUtils.isEmpty(accountName) && !TextUtils.isEmpty(accountType)) { 1756 Account account = new Account(accountName, accountType); 1757 cancelNotification(getSigninRequiredNotificationId(mAccounts, account)); 1758 } 1759 } 1760 IAccountManagerResponse response; 1761 if (mExpectActivityLaunch && result != null 1762 && result.containsKey(AccountManager.KEY_INTENT)) { 1763 response = mResponse; 1764 } else { 1765 response = getResponseAndClose(); 1766 } 1767 if (response != null) { 1768 try { 1769 if (result == null) { 1770 if (Log.isLoggable(TAG, Log.VERBOSE)) { 1771 Log.v(TAG, getClass().getSimpleName() 1772 + " calling onError() on response " + response); 1773 } 1774 response.onError(AccountManager.ERROR_CODE_INVALID_RESPONSE, 1775 "null bundle returned"); 1776 } else { 1777 if (mStripAuthTokenFromResult) { 1778 result.remove(AccountManager.KEY_AUTHTOKEN); 1779 } 1780 if (Log.isLoggable(TAG, Log.VERBOSE)) { 1781 Log.v(TAG, getClass().getSimpleName() 1782 + " calling onResult() on response " + response); 1783 } 1784 response.onResult(result); 1785 } 1786 } catch (RemoteException e) { 1787 // if the caller is dead then there is no one to care about remote exceptions 1788 if (Log.isLoggable(TAG, Log.VERBOSE)) { 1789 Log.v(TAG, "failure while notifying response", e); 1790 } 1791 } 1792 } 1793 } 1794 1795 public void onRequestContinued() { 1796 mNumRequestContinued++; 1797 } 1798 1799 public void onError(int errorCode, String errorMessage) { 1800 mNumErrors++; 1801 IAccountManagerResponse response = getResponseAndClose(); 1802 if (response != null) { 1803 if (Log.isLoggable(TAG, Log.VERBOSE)) { 1804 Log.v(TAG, getClass().getSimpleName() 1805 + " calling onError() on response " + response); 1806 } 1807 try { 1808 response.onError(errorCode, errorMessage); 1809 } catch (RemoteException e) { 1810 if (Log.isLoggable(TAG, Log.VERBOSE)) { 1811 Log.v(TAG, "Session.onError: caught RemoteException while responding", e); 1812 } 1813 } 1814 } else { 1815 if (Log.isLoggable(TAG, Log.VERBOSE)) { 1816 Log.v(TAG, "Session.onError: already closed"); 1817 } 1818 } 1819 } 1820 1821 /** 1822 * find the component name for the authenticator and initiate a bind 1823 * if no authenticator or the bind fails then return false, otherwise return true 1824 */ 1825 private boolean bindToAuthenticator(String authenticatorType) { 1826 AccountAuthenticatorCache.ServiceInfo<AuthenticatorDescription> authenticatorInfo = 1827 mAuthenticatorCache.getServiceInfo( 1828 AuthenticatorDescription.newKey(authenticatorType)); 1829 if (authenticatorInfo == null) { 1830 if (Log.isLoggable(TAG, Log.VERBOSE)) { 1831 Log.v(TAG, "there is no authenticator for " + authenticatorType 1832 + ", bailing out"); 1833 } 1834 return false; 1835 } 1836 1837 Intent intent = new Intent(); 1838 intent.setAction(AccountManager.ACTION_AUTHENTICATOR_INTENT); 1839 intent.setComponent(authenticatorInfo.componentName); 1840 if (Log.isLoggable(TAG, Log.VERBOSE)) { 1841 Log.v(TAG, "performing bindService to " + authenticatorInfo.componentName); 1842 } 1843 if (!mContext.bindService(intent, this, Context.BIND_AUTO_CREATE, mAccounts.userId)) { 1844 if (Log.isLoggable(TAG, Log.VERBOSE)) { 1845 Log.v(TAG, "bindService to " + authenticatorInfo.componentName + " failed"); 1846 } 1847 return false; 1848 } 1849 1850 1851 return true; 1852 } 1853 } 1854 1855 private class MessageHandler extends Handler { 1856 MessageHandler(Looper looper) { 1857 super(looper); 1858 } 1859 1860 public void handleMessage(Message msg) { 1861 switch (msg.what) { 1862 case MESSAGE_TIMED_OUT: 1863 Session session = (Session)msg.obj; 1864 session.onTimedOut(); 1865 break; 1866 1867 default: 1868 throw new IllegalStateException("unhandled message: " + msg.what); 1869 } 1870 } 1871 } 1872 1873 private static String getDatabaseName(int userId) { 1874 File systemDir = Environment.getSystemSecureDirectory(); 1875 File databaseFile = new File(systemDir, "users/" + userId + "/" + DATABASE_NAME); 1876 if (userId == 0) { 1877 // Migrate old file, if it exists, to the new location. 1878 // Make sure the new file doesn't already exist. A dummy file could have been 1879 // accidentally created in the old location, causing the new one to become corrupted 1880 // as well. 1881 File oldFile = new File(systemDir, DATABASE_NAME); 1882 if (oldFile.exists() && !databaseFile.exists()) { 1883 // Check for use directory; create if it doesn't exist, else renameTo will fail 1884 File userDir = new File(systemDir, "users/" + userId); 1885 if (!userDir.exists()) { 1886 if (!userDir.mkdirs()) { 1887 throw new IllegalStateException("User dir cannot be created: " + userDir); 1888 } 1889 } 1890 if (!oldFile.renameTo(databaseFile)) { 1891 throw new IllegalStateException("User dir cannot be migrated: " + databaseFile); 1892 } 1893 } 1894 } 1895 return databaseFile.getPath(); 1896 } 1897 1898 static class DatabaseHelper extends SQLiteOpenHelper { 1899 1900 public DatabaseHelper(Context context, int userId) { 1901 super(context, AccountManagerService.getDatabaseName(userId), null, DATABASE_VERSION); 1902 } 1903 1904 /** 1905 * This call needs to be made while the mCacheLock is held. The way to 1906 * ensure this is to get the lock any time a method is called ont the DatabaseHelper 1907 * @param db The database. 1908 */ 1909 @Override 1910 public void onCreate(SQLiteDatabase db) { 1911 db.execSQL("CREATE TABLE " + TABLE_ACCOUNTS + " ( " 1912 + ACCOUNTS_ID + " INTEGER PRIMARY KEY AUTOINCREMENT, " 1913 + ACCOUNTS_NAME + " TEXT NOT NULL, " 1914 + ACCOUNTS_TYPE + " TEXT NOT NULL, " 1915 + ACCOUNTS_PASSWORD + " TEXT, " 1916 + "UNIQUE(" + ACCOUNTS_NAME + "," + ACCOUNTS_TYPE + "))"); 1917 1918 db.execSQL("CREATE TABLE " + TABLE_AUTHTOKENS + " ( " 1919 + AUTHTOKENS_ID + " INTEGER PRIMARY KEY AUTOINCREMENT, " 1920 + AUTHTOKENS_ACCOUNTS_ID + " INTEGER NOT NULL, " 1921 + AUTHTOKENS_TYPE + " TEXT NOT NULL, " 1922 + AUTHTOKENS_AUTHTOKEN + " TEXT, " 1923 + "UNIQUE (" + AUTHTOKENS_ACCOUNTS_ID + "," + AUTHTOKENS_TYPE + "))"); 1924 1925 createGrantsTable(db); 1926 1927 db.execSQL("CREATE TABLE " + TABLE_EXTRAS + " ( " 1928 + EXTRAS_ID + " INTEGER PRIMARY KEY AUTOINCREMENT, " 1929 + EXTRAS_ACCOUNTS_ID + " INTEGER, " 1930 + EXTRAS_KEY + " TEXT NOT NULL, " 1931 + EXTRAS_VALUE + " TEXT, " 1932 + "UNIQUE(" + EXTRAS_ACCOUNTS_ID + "," + EXTRAS_KEY + "))"); 1933 1934 db.execSQL("CREATE TABLE " + TABLE_META + " ( " 1935 + META_KEY + " TEXT PRIMARY KEY NOT NULL, " 1936 + META_VALUE + " TEXT)"); 1937 1938 createAccountsDeletionTrigger(db); 1939 } 1940 1941 private void createAccountsDeletionTrigger(SQLiteDatabase db) { 1942 db.execSQL("" 1943 + " CREATE TRIGGER " + TABLE_ACCOUNTS + "Delete DELETE ON " + TABLE_ACCOUNTS 1944 + " BEGIN" 1945 + " DELETE FROM " + TABLE_AUTHTOKENS 1946 + " WHERE " + AUTHTOKENS_ACCOUNTS_ID + "=OLD." + ACCOUNTS_ID + " ;" 1947 + " DELETE FROM " + TABLE_EXTRAS 1948 + " WHERE " + EXTRAS_ACCOUNTS_ID + "=OLD." + ACCOUNTS_ID + " ;" 1949 + " DELETE FROM " + TABLE_GRANTS 1950 + " WHERE " + GRANTS_ACCOUNTS_ID + "=OLD." + ACCOUNTS_ID + " ;" 1951 + " END"); 1952 } 1953 1954 private void createGrantsTable(SQLiteDatabase db) { 1955 db.execSQL("CREATE TABLE " + TABLE_GRANTS + " ( " 1956 + GRANTS_ACCOUNTS_ID + " INTEGER NOT NULL, " 1957 + GRANTS_AUTH_TOKEN_TYPE + " STRING NOT NULL, " 1958 + GRANTS_GRANTEE_UID + " INTEGER NOT NULL, " 1959 + "UNIQUE (" + GRANTS_ACCOUNTS_ID + "," + GRANTS_AUTH_TOKEN_TYPE 1960 + "," + GRANTS_GRANTEE_UID + "))"); 1961 } 1962 1963 @Override 1964 public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { 1965 Log.e(TAG, "upgrade from version " + oldVersion + " to version " + newVersion); 1966 1967 if (oldVersion == 1) { 1968 // no longer need to do anything since the work is done 1969 // when upgrading from version 2 1970 oldVersion++; 1971 } 1972 1973 if (oldVersion == 2) { 1974 createGrantsTable(db); 1975 db.execSQL("DROP TRIGGER " + TABLE_ACCOUNTS + "Delete"); 1976 createAccountsDeletionTrigger(db); 1977 oldVersion++; 1978 } 1979 1980 if (oldVersion == 3) { 1981 db.execSQL("UPDATE " + TABLE_ACCOUNTS + " SET " + ACCOUNTS_TYPE + 1982 " = 'com.google' WHERE " + ACCOUNTS_TYPE + " == 'com.google.GAIA'"); 1983 oldVersion++; 1984 } 1985 } 1986 1987 @Override 1988 public void onOpen(SQLiteDatabase db) { 1989 if (Log.isLoggable(TAG, Log.VERBOSE)) Log.v(TAG, "opened database " + DATABASE_NAME); 1990 } 1991 } 1992 1993 public IBinder onBind(Intent intent) { 1994 return asBinder(); 1995 } 1996 1997 /** 1998 * Searches array of arguments for the specified string 1999 * @param args array of argument strings 2000 * @param value value to search for 2001 * @return true if the value is contained in the array 2002 */ 2003 private static boolean scanArgs(String[] args, String value) { 2004 if (args != null) { 2005 for (String arg : args) { 2006 if (value.equals(arg)) { 2007 return true; 2008 } 2009 } 2010 } 2011 return false; 2012 } 2013 2014 protected void dump(FileDescriptor fd, PrintWriter fout, String[] args) { 2015 if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.DUMP) 2016 != PackageManager.PERMISSION_GRANTED) { 2017 fout.println("Permission Denial: can't dump AccountsManager from from pid=" 2018 + Binder.getCallingPid() + ", uid=" + Binder.getCallingUid() 2019 + " without permission " + android.Manifest.permission.DUMP); 2020 return; 2021 } 2022 final boolean isCheckinRequest = scanArgs(args, "--checkin") || scanArgs(args, "-c"); 2023 2024 fout = new IndentingPrintWriter(fout, " "); 2025 int size = mUsers.size(); 2026 for (int i = 0; i < size; i++) { 2027 fout.println("User " + mUsers.keyAt(i) + ":"); 2028 ((IndentingPrintWriter) fout).increaseIndent(); 2029 dumpUser(mUsers.valueAt(i), fd, fout, args, isCheckinRequest); 2030 ((IndentingPrintWriter) fout).decreaseIndent(); 2031 if (i < size - 1) { 2032 fout.println(); 2033 } 2034 } 2035 } 2036 2037 private void dumpUser(UserAccounts userAccounts, FileDescriptor fd, PrintWriter fout, 2038 String[] args, boolean isCheckinRequest) { 2039 synchronized (userAccounts.cacheLock) { 2040 final SQLiteDatabase db = userAccounts.openHelper.getReadableDatabase(); 2041 2042 if (isCheckinRequest) { 2043 // This is a checkin request. *Only* upload the account types and the count of each. 2044 Cursor cursor = db.query(TABLE_ACCOUNTS, ACCOUNT_TYPE_COUNT_PROJECTION, 2045 null, null, ACCOUNTS_TYPE, null, null); 2046 try { 2047 while (cursor.moveToNext()) { 2048 // print type,count 2049 fout.println(cursor.getString(0) + "," + cursor.getString(1)); 2050 } 2051 } finally { 2052 if (cursor != null) { 2053 cursor.close(); 2054 } 2055 } 2056 } else { 2057 Account[] accounts = getAccountsFromCacheLocked(userAccounts, null /* type */); 2058 fout.println("Accounts: " + accounts.length); 2059 for (Account account : accounts) { 2060 fout.println(" " + account); 2061 } 2062 2063 fout.println(); 2064 synchronized (mSessions) { 2065 final long now = SystemClock.elapsedRealtime(); 2066 fout.println("Active Sessions: " + mSessions.size()); 2067 for (Session session : mSessions.values()) { 2068 fout.println(" " + session.toDebugString(now)); 2069 } 2070 } 2071 2072 fout.println(); 2073 mAuthenticatorCache.dump(fd, fout, args); 2074 } 2075 } 2076 } 2077 2078 private void doNotification(UserAccounts accounts, Account account, CharSequence message, 2079 Intent intent) { 2080 long identityToken = clearCallingIdentity(); 2081 try { 2082 if (Log.isLoggable(TAG, Log.VERBOSE)) { 2083 Log.v(TAG, "doNotification: " + message + " intent:" + intent); 2084 } 2085 2086 if (intent.getComponent() != null && 2087 GrantCredentialsPermissionActivity.class.getName().equals( 2088 intent.getComponent().getClassName())) { 2089 createNoCredentialsPermissionNotification(account, intent); 2090 } else { 2091 final Integer notificationId = getSigninRequiredNotificationId(accounts, account); 2092 intent.addCategory(String.valueOf(notificationId)); 2093 Notification n = new Notification(android.R.drawable.stat_sys_warning, null, 2094 0 /* when */); 2095 final String notificationTitleFormat = 2096 mContext.getText(R.string.notification_title).toString(); 2097 n.setLatestEventInfo(mContext, 2098 String.format(notificationTitleFormat, account.name), 2099 message, PendingIntent.getActivity( 2100 mContext, 0, intent, PendingIntent.FLAG_CANCEL_CURRENT)); 2101 installNotification(notificationId, n); 2102 } 2103 } finally { 2104 restoreCallingIdentity(identityToken); 2105 } 2106 } 2107 2108 protected void installNotification(final int notificationId, final Notification n) { 2109 ((NotificationManager) mContext.getSystemService(Context.NOTIFICATION_SERVICE)) 2110 .notify(notificationId, n); 2111 } 2112 2113 protected void cancelNotification(int id) { 2114 long identityToken = clearCallingIdentity(); 2115 try { 2116 ((NotificationManager) mContext.getSystemService(Context.NOTIFICATION_SERVICE)) 2117 .cancel(id); 2118 } finally { 2119 restoreCallingIdentity(identityToken); 2120 } 2121 } 2122 2123 /** Succeeds if any of the specified permissions are granted. */ 2124 private void checkBinderPermission(String... permissions) { 2125 final int uid = Binder.getCallingUid(); 2126 2127 for (String perm : permissions) { 2128 if (mContext.checkCallingOrSelfPermission(perm) == PackageManager.PERMISSION_GRANTED) { 2129 if (Log.isLoggable(TAG, Log.VERBOSE)) { 2130 Log.v(TAG, " caller uid " + uid + " has " + perm); 2131 } 2132 return; 2133 } 2134 } 2135 2136 String msg = "caller uid " + uid + " lacks any of " + TextUtils.join(",", permissions); 2137 Log.w(TAG, " " + msg); 2138 throw new SecurityException(msg); 2139 } 2140 2141 private boolean inSystemImage(int callerUid) { 2142 String[] packages = mPackageManager.getPackagesForUid(callerUid); 2143 for (String name : packages) { 2144 try { 2145 PackageInfo packageInfo = mPackageManager.getPackageInfo(name, 0 /* flags */); 2146 if (packageInfo != null 2147 && (packageInfo.applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0) { 2148 return true; 2149 } 2150 } catch (PackageManager.NameNotFoundException e) { 2151 return false; 2152 } 2153 } 2154 return false; 2155 } 2156 2157 private boolean permissionIsGranted(Account account, String authTokenType, int callerUid) { 2158 final boolean inSystemImage = inSystemImage(callerUid); 2159 final boolean fromAuthenticator = account != null 2160 && hasAuthenticatorUid(account.type, callerUid); 2161 final boolean hasExplicitGrants = account != null 2162 && hasExplicitlyGrantedPermission(account, authTokenType, callerUid); 2163 if (Log.isLoggable(TAG, Log.VERBOSE)) { 2164 Log.v(TAG, "checkGrantsOrCallingUidAgainstAuthenticator: caller uid " 2165 + callerUid + ", " + account 2166 + ": is authenticator? " + fromAuthenticator 2167 + ", has explicit permission? " + hasExplicitGrants); 2168 } 2169 return fromAuthenticator || hasExplicitGrants || inSystemImage; 2170 } 2171 2172 private boolean hasAuthenticatorUid(String accountType, int callingUid) { 2173 for (RegisteredServicesCache.ServiceInfo<AuthenticatorDescription> serviceInfo : 2174 mAuthenticatorCache.getAllServices()) { 2175 if (serviceInfo.type.type.equals(accountType)) { 2176 return (serviceInfo.uid == callingUid) || 2177 (mPackageManager.checkSignatures(serviceInfo.uid, callingUid) 2178 == PackageManager.SIGNATURE_MATCH); 2179 } 2180 } 2181 return false; 2182 } 2183 2184 private boolean hasExplicitlyGrantedPermission(Account account, String authTokenType, 2185 int callerUid) { 2186 if (callerUid == android.os.Process.SYSTEM_UID) { 2187 return true; 2188 } 2189 UserAccounts accounts = getUserAccountsForCaller(); 2190 synchronized (accounts.cacheLock) { 2191 final SQLiteDatabase db = accounts.openHelper.getReadableDatabase(); 2192 String[] args = { String.valueOf(callerUid), authTokenType, 2193 account.name, account.type}; 2194 final boolean permissionGranted = 2195 DatabaseUtils.longForQuery(db, COUNT_OF_MATCHING_GRANTS, args) != 0; 2196 if (!permissionGranted && ActivityManager.isRunningInTestHarness()) { 2197 // TODO: Skip this check when running automated tests. Replace this 2198 // with a more general solution. 2199 Log.d(TAG, "no credentials permission for usage of " + account + ", " 2200 + authTokenType + " by uid " + callerUid 2201 + " but ignoring since device is in test harness."); 2202 return true; 2203 } 2204 return permissionGranted; 2205 } 2206 } 2207 2208 private void checkCallingUidAgainstAuthenticator(Account account) { 2209 final int uid = Binder.getCallingUid(); 2210 if (account == null || !hasAuthenticatorUid(account.type, uid)) { 2211 String msg = "caller uid " + uid + " is different than the authenticator's uid"; 2212 Log.w(TAG, msg); 2213 throw new SecurityException(msg); 2214 } 2215 if (Log.isLoggable(TAG, Log.VERBOSE)) { 2216 Log.v(TAG, "caller uid " + uid + " is the same as the authenticator's uid"); 2217 } 2218 } 2219 2220 private void checkAuthenticateAccountsPermission(Account account) { 2221 checkBinderPermission(Manifest.permission.AUTHENTICATE_ACCOUNTS); 2222 checkCallingUidAgainstAuthenticator(account); 2223 } 2224 2225 private void checkReadAccountsPermission() { 2226 checkBinderPermission(Manifest.permission.GET_ACCOUNTS); 2227 } 2228 2229 private void checkManageAccountsPermission() { 2230 checkBinderPermission(Manifest.permission.MANAGE_ACCOUNTS); 2231 } 2232 2233 private void checkManageAccountsOrUseCredentialsPermissions() { 2234 checkBinderPermission(Manifest.permission.MANAGE_ACCOUNTS, 2235 Manifest.permission.USE_CREDENTIALS); 2236 } 2237 2238 public void updateAppPermission(Account account, String authTokenType, int uid, boolean value) 2239 throws RemoteException { 2240 final int callingUid = getCallingUid(); 2241 2242 if (callingUid != android.os.Process.SYSTEM_UID) { 2243 throw new SecurityException(); 2244 } 2245 2246 if (value) { 2247 grantAppPermission(account, authTokenType, uid); 2248 } else { 2249 revokeAppPermission(account, authTokenType, uid); 2250 } 2251 } 2252 2253 /** 2254 * Allow callers with the given uid permission to get credentials for account/authTokenType. 2255 * <p> 2256 * Although this is public it can only be accessed via the AccountManagerService object 2257 * which is in the system. This means we don't need to protect it with permissions. 2258 * @hide 2259 */ 2260 private void grantAppPermission(Account account, String authTokenType, int uid) { 2261 if (account == null || authTokenType == null) { 2262 Log.e(TAG, "grantAppPermission: called with invalid arguments", new Exception()); 2263 return; 2264 } 2265 UserAccounts accounts = getUserAccounts(UserId.getUserId(uid)); 2266 synchronized (accounts.cacheLock) { 2267 final SQLiteDatabase db = accounts.openHelper.getWritableDatabase(); 2268 db.beginTransaction(); 2269 try { 2270 long accountId = getAccountIdLocked(db, account); 2271 if (accountId >= 0) { 2272 ContentValues values = new ContentValues(); 2273 values.put(GRANTS_ACCOUNTS_ID, accountId); 2274 values.put(GRANTS_AUTH_TOKEN_TYPE, authTokenType); 2275 values.put(GRANTS_GRANTEE_UID, uid); 2276 db.insert(TABLE_GRANTS, GRANTS_ACCOUNTS_ID, values); 2277 db.setTransactionSuccessful(); 2278 } 2279 } finally { 2280 db.endTransaction(); 2281 } 2282 cancelNotification(getCredentialPermissionNotificationId(account, authTokenType, uid)); 2283 } 2284 } 2285 2286 /** 2287 * Don't allow callers with the given uid permission to get credentials for 2288 * account/authTokenType. 2289 * <p> 2290 * Although this is public it can only be accessed via the AccountManagerService object 2291 * which is in the system. This means we don't need to protect it with permissions. 2292 * @hide 2293 */ 2294 private void revokeAppPermission(Account account, String authTokenType, int uid) { 2295 if (account == null || authTokenType == null) { 2296 Log.e(TAG, "revokeAppPermission: called with invalid arguments", new Exception()); 2297 return; 2298 } 2299 UserAccounts accounts = getUserAccounts(UserId.getUserId(uid)); 2300 synchronized (accounts.cacheLock) { 2301 final SQLiteDatabase db = accounts.openHelper.getWritableDatabase(); 2302 db.beginTransaction(); 2303 try { 2304 long accountId = getAccountIdLocked(db, account); 2305 if (accountId >= 0) { 2306 db.delete(TABLE_GRANTS, 2307 GRANTS_ACCOUNTS_ID + "=? AND " + GRANTS_AUTH_TOKEN_TYPE + "=? AND " 2308 + GRANTS_GRANTEE_UID + "=?", 2309 new String[]{String.valueOf(accountId), authTokenType, 2310 String.valueOf(uid)}); 2311 db.setTransactionSuccessful(); 2312 } 2313 } finally { 2314 db.endTransaction(); 2315 } 2316 cancelNotification(getCredentialPermissionNotificationId(account, authTokenType, uid)); 2317 } 2318 } 2319 2320 static final private String stringArrayToString(String[] value) { 2321 return value != null ? ("[" + TextUtils.join(",", value) + "]") : null; 2322 } 2323 2324 private void removeAccountFromCacheLocked(UserAccounts accounts, Account account) { 2325 final Account[] oldAccountsForType = accounts.accountCache.get(account.type); 2326 if (oldAccountsForType != null) { 2327 ArrayList<Account> newAccountsList = new ArrayList<Account>(); 2328 for (Account curAccount : oldAccountsForType) { 2329 if (!curAccount.equals(account)) { 2330 newAccountsList.add(curAccount); 2331 } 2332 } 2333 if (newAccountsList.isEmpty()) { 2334 accounts.accountCache.remove(account.type); 2335 } else { 2336 Account[] newAccountsForType = new Account[newAccountsList.size()]; 2337 newAccountsForType = newAccountsList.toArray(newAccountsForType); 2338 accounts.accountCache.put(account.type, newAccountsForType); 2339 } 2340 } 2341 accounts.userDataCache.remove(account); 2342 accounts.authTokenCache.remove(account); 2343 } 2344 2345 /** 2346 * This assumes that the caller has already checked that the account is not already present. 2347 */ 2348 private void insertAccountIntoCacheLocked(UserAccounts accounts, Account account) { 2349 Account[] accountsForType = accounts.accountCache.get(account.type); 2350 int oldLength = (accountsForType != null) ? accountsForType.length : 0; 2351 Account[] newAccountsForType = new Account[oldLength + 1]; 2352 if (accountsForType != null) { 2353 System.arraycopy(accountsForType, 0, newAccountsForType, 0, oldLength); 2354 } 2355 newAccountsForType[oldLength] = account; 2356 accounts.accountCache.put(account.type, newAccountsForType); 2357 } 2358 2359 protected Account[] getAccountsFromCacheLocked(UserAccounts userAccounts, String accountType) { 2360 if (accountType != null) { 2361 final Account[] accounts = userAccounts.accountCache.get(accountType); 2362 if (accounts == null) { 2363 return EMPTY_ACCOUNT_ARRAY; 2364 } else { 2365 return Arrays.copyOf(accounts, accounts.length); 2366 } 2367 } else { 2368 int totalLength = 0; 2369 for (Account[] accounts : userAccounts.accountCache.values()) { 2370 totalLength += accounts.length; 2371 } 2372 if (totalLength == 0) { 2373 return EMPTY_ACCOUNT_ARRAY; 2374 } 2375 Account[] accounts = new Account[totalLength]; 2376 totalLength = 0; 2377 for (Account[] accountsOfType : userAccounts.accountCache.values()) { 2378 System.arraycopy(accountsOfType, 0, accounts, totalLength, 2379 accountsOfType.length); 2380 totalLength += accountsOfType.length; 2381 } 2382 return accounts; 2383 } 2384 } 2385 2386 protected void writeUserDataIntoCacheLocked(UserAccounts accounts, final SQLiteDatabase db, 2387 Account account, String key, String value) { 2388 HashMap<String, String> userDataForAccount = accounts.userDataCache.get(account); 2389 if (userDataForAccount == null) { 2390 userDataForAccount = readUserDataForAccountFromDatabaseLocked(db, account); 2391 accounts.userDataCache.put(account, userDataForAccount); 2392 } 2393 if (value == null) { 2394 userDataForAccount.remove(key); 2395 } else { 2396 userDataForAccount.put(key, value); 2397 } 2398 } 2399 2400 protected void writeAuthTokenIntoCacheLocked(UserAccounts accounts, final SQLiteDatabase db, 2401 Account account, String key, String value) { 2402 HashMap<String, String> authTokensForAccount = accounts.authTokenCache.get(account); 2403 if (authTokensForAccount == null) { 2404 authTokensForAccount = readAuthTokensForAccountFromDatabaseLocked(db, account); 2405 accounts.authTokenCache.put(account, authTokensForAccount); 2406 } 2407 if (value == null) { 2408 authTokensForAccount.remove(key); 2409 } else { 2410 authTokensForAccount.put(key, value); 2411 } 2412 } 2413 2414 protected String readAuthTokenInternal(UserAccounts accounts, Account account, 2415 String authTokenType) { 2416 synchronized (accounts.cacheLock) { 2417 HashMap<String, String> authTokensForAccount = accounts.authTokenCache.get(account); 2418 if (authTokensForAccount == null) { 2419 // need to populate the cache for this account 2420 final SQLiteDatabase db = accounts.openHelper.getReadableDatabase(); 2421 authTokensForAccount = readAuthTokensForAccountFromDatabaseLocked(db, account); 2422 accounts.authTokenCache.put(account, authTokensForAccount); 2423 } 2424 return authTokensForAccount.get(authTokenType); 2425 } 2426 } 2427 2428 protected String readUserDataInternal(UserAccounts accounts, Account account, String key) { 2429 synchronized (accounts.cacheLock) { 2430 HashMap<String, String> userDataForAccount = accounts.userDataCache.get(account); 2431 if (userDataForAccount == null) { 2432 // need to populate the cache for this account 2433 final SQLiteDatabase db = accounts.openHelper.getReadableDatabase(); 2434 userDataForAccount = readUserDataForAccountFromDatabaseLocked(db, account); 2435 accounts.userDataCache.put(account, userDataForAccount); 2436 } 2437 return userDataForAccount.get(key); 2438 } 2439 } 2440 2441 protected HashMap<String, String> readUserDataForAccountFromDatabaseLocked( 2442 final SQLiteDatabase db, Account account) { 2443 HashMap<String, String> userDataForAccount = new HashMap<String, String>(); 2444 Cursor cursor = db.query(TABLE_EXTRAS, 2445 COLUMNS_EXTRAS_KEY_AND_VALUE, 2446 SELECTION_USERDATA_BY_ACCOUNT, 2447 new String[]{account.name, account.type}, 2448 null, null, null); 2449 try { 2450 while (cursor.moveToNext()) { 2451 final String tmpkey = cursor.getString(0); 2452 final String value = cursor.getString(1); 2453 userDataForAccount.put(tmpkey, value); 2454 } 2455 } finally { 2456 cursor.close(); 2457 } 2458 return userDataForAccount; 2459 } 2460 2461 protected HashMap<String, String> readAuthTokensForAccountFromDatabaseLocked( 2462 final SQLiteDatabase db, Account account) { 2463 HashMap<String, String> authTokensForAccount = new HashMap<String, String>(); 2464 Cursor cursor = db.query(TABLE_AUTHTOKENS, 2465 COLUMNS_AUTHTOKENS_TYPE_AND_AUTHTOKEN, 2466 SELECTION_AUTHTOKENS_BY_ACCOUNT, 2467 new String[]{account.name, account.type}, 2468 null, null, null); 2469 try { 2470 while (cursor.moveToNext()) { 2471 final String type = cursor.getString(0); 2472 final String authToken = cursor.getString(1); 2473 authTokensForAccount.put(type, authToken); 2474 } 2475 } finally { 2476 cursor.close(); 2477 } 2478 return authTokensForAccount; 2479 } 2480 } 2481