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 */ 181 public static AccountManagerService getSingleton() { 182 return sThis.get(); 183 } 184 185 public AccountManagerService(Context context) { 186 this(context, context.getPackageManager(), new AccountAuthenticatorCache(context)); 187 } 188 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 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 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 299 public void onServiceChanged(AuthenticatorDescription desc, boolean removed) { 300 validateAccountsAndPopulateCache(); 301 } 302 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 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 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 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 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 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 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 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 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 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 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 522 protected String toDebugString(long now) { 523 return super.toDebugString(now) + ", hasFeatures" 524 + ", " + mAccount 525 + ", " + (mFeatures != null ? TextUtils.join(",", mFeatures) : null); 526 } 527 } 528 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; 561 public RemoveAccountSession(IAccountManagerResponse response, Account account) { 562 super(response, account.type, false /* expectActivityLaunch */, 563 true /* stripAuthTokenFromResult */); 564 mAccount = account; 565 } 566 567 protected String toDebugString(long now) { 568 return super.toDebugString(now) + ", removeAccount" 569 + ", account " + mAccount; 570 } 571 572 public void run() throws RemoteException { 573 mAuthenticator.getAccountRemovalAllowed(this, mAccount); 574 } 575 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 1261 public GetAccountsByTypeAndFeatureSession(IAccountManagerResponse response, 1262 String type, String[] features) { 1263 super(response, type, false /* expectActivityLaunch */, 1264 true /* stripAuthTokenFromResult */); 1265 mFeatures = features; 1266 } 1267 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 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 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 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 1342 protected String toDebugString(long now) { 1343 return super.toDebugString(now) + ", getAccountsByTypeAndFeatures" 1344 + ", " + (mFeatures != null ? TextUtils.join(",", mFeatures) : null); 1345 } 1346 } 1347 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 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 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 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 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 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 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 1487 public void binderDied() { 1488 mResponse = null; 1489 close(); 1490 } 1491 1492 protected String toDebugString() { 1493 return toDebugString(SystemClock.elapsedRealtime()); 1494 } 1495 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 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 1514 private void unbind() { 1515 if (mAuthenticator != null) { 1516 mAuthenticator = null; 1517 mContext.unbindService(this); 1518 } 1519 } 1520 1521 public void scheduleTimeout() { 1522 mMessageHandler.sendMessageDelayed( 1523 mMessageHandler.obtainMessage(MESSAGE_TIMED_OUT, this), TIMEOUT_DELAY_MS); 1524 } 1525 1526 public void cancelTimeout() { 1527 mMessageHandler.removeMessages(MESSAGE_TIMED_OUT, this); 1528 } 1529 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 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 1556 public abstract void run() throws RemoteException; 1557 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 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 1618 public void onRequestContinued() { 1619 mNumRequestContinued++; 1620 } 1621 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 */ 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 { 1679 MessageHandler(Looper looper) { 1680 super(looper); 1681 } 1682 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 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 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 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 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 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 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 1796 public void onOpen(SQLiteDatabase db) { 1797 if (Log.isLoggable(TAG, Log.VERBOSE)) Log.v(TAG, "opened database " + DATABASE_NAME); 1798 } 1799 } 1800 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 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 */ 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 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 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 1910 protected void installNotification(final int notificationId, final Notification n) { 1911 ((NotificationManager) mContext.getSystemService(Context.NOTIFICATION_SERVICE)) 1912 .notify(notificationId, n); 1913 } 1914 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. */ 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 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 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 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 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 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 2020 private void checkAuthenticateAccountsPermission(Account account) { 2021 checkBinderPermission(Manifest.permission.AUTHENTICATE_ACCOUNTS); 2022 checkCallingUidAgainstAuthenticator(account); 2023 } 2024 2025 private void checkReadAccountsPermission() { 2026 checkBinderPermission(Manifest.permission.GET_ACCOUNTS); 2027 } 2028 2029 private void checkManageAccountsPermission() { 2030 checkBinderPermission(Manifest.permission.MANAGE_ACCOUNTS); 2031 } 2032 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 */ 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 */ 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 2103 static final private String stringArrayToString(String[] value) { 2104 return value != null ? ("[" + TextUtils.join(",", value) + "]") : null; 2105 } 2106 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 */ 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 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 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 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 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 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 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 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