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