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