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