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