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