AccountManagerService.java revision 08fa1428eed52083789dc3b366aea4873199bd37
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 17package android.accounts; 18 19import android.content.BroadcastReceiver; 20import android.content.ContentValues; 21import android.content.Context; 22import android.content.Intent; 23import android.content.IntentFilter; 24import android.content.pm.PackageManager; 25import android.content.pm.RegisteredServicesCache; 26import android.content.pm.PackageInfo; 27import android.content.pm.ApplicationInfo; 28import android.content.pm.RegisteredServicesCacheListener; 29import android.database.Cursor; 30import android.database.DatabaseUtils; 31import android.database.sqlite.SQLiteDatabase; 32import android.database.sqlite.SQLiteOpenHelper; 33import android.os.Bundle; 34import android.os.Handler; 35import android.os.HandlerThread; 36import android.os.IBinder; 37import android.os.Looper; 38import android.os.Message; 39import android.os.RemoteException; 40import android.os.SystemClock; 41import android.os.Binder; 42import android.os.SystemProperties; 43import android.telephony.TelephonyManager; 44import android.text.TextUtils; 45import android.util.Log; 46import android.util.Pair; 47import android.app.PendingIntent; 48import android.app.NotificationManager; 49import android.app.Notification; 50import android.Manifest; 51 52import java.io.FileDescriptor; 53import java.io.PrintWriter; 54import java.util.ArrayList; 55import java.util.Collection; 56import java.util.LinkedHashMap; 57import java.util.HashMap; 58import java.util.concurrent.atomic.AtomicInteger; 59import java.util.concurrent.atomic.AtomicReference; 60 61import com.android.internal.telephony.TelephonyIntents; 62import 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 */ 73public 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("monkey.running", 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 */ 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 179 public AuthTokenKey(Account account, String authTokenType) { 180 mAccount = account; 181 mAuthTokenType = authTokenType; 182 mHashCode = computeHashCode(); 183 } 184 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 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 208 public int hashCode() { 209 return mHashCode; 210 } 211 } 212 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 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 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 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 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 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 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 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 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 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 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 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; 435 public RemoveAccountSession(IAccountManagerResponse response, Account account) { 436 super(response, account.type, false /* expectActivityLaunch */); 437 mAccount = account; 438 } 439 440 protected String toDebugString(long now) { 441 return super.toDebugString(now) + ", removeAccount" 442 + ", account " + mAccount; 443 } 444 445 public void run() throws RemoteException { 446 mAuthenticator.getAccountRemovalAllowed(this, mAccount); 447 } 448 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 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 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 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 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 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 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 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 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 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 613 private void sendAccountsChangedBroadcast() { 614 mContext.sendBroadcast(ACCOUNTS_CHANGED_INTENT); 615 } 616 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 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 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 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 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 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 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 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 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 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 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 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 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 948 public GetAccountsByTypeAndFeatureSession(IAccountManagerResponse response, 949 String type, String[] features) { 950 super(response, type, false /* expectActivityLaunch */); 951 mFeatures = features; 952 } 953 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 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 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 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 1010 protected String toDebugString(long now) { 1011 return super.toDebugString(now) + ", getAccountsByTypeAndFeatures" 1012 + ", " + (mFeatures != null ? TextUtils.join(",", mFeatures) : null); 1013 } 1014 } 1015 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 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 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 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 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 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 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 1140 public void binderDied() { 1141 mResponse = null; 1142 close(); 1143 } 1144 1145 protected String toDebugString() { 1146 return toDebugString(SystemClock.elapsedRealtime()); 1147 } 1148 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 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 1167 private void unbind() { 1168 if (mAuthenticator != null) { 1169 mAuthenticator = null; 1170 mBindHelper.unbind(this); 1171 } 1172 } 1173 1174 public void scheduleTimeout() { 1175 mMessageHandler.sendMessageDelayed( 1176 mMessageHandler.obtainMessage(MESSAGE_TIMED_OUT, this), TIMEOUT_DELAY_MS); 1177 } 1178 1179 public void cancelTimeout() { 1180 mMessageHandler.removeMessages(MESSAGE_TIMED_OUT, this); 1181 } 1182 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 1193 public abstract void run() throws RemoteException; 1194 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 1204 public void onTimedOut() { 1205 IAccountManagerResponse response = getResponseAndClose(); 1206 if (response != null) { 1207 onError(AccountManager.ERROR_CODE_REMOTE_EXCEPTION, 1208 "timeout"); 1209 } 1210 } 1211 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 1246 public void onRequestContinued() { 1247 mNumRequestContinued++; 1248 } 1249 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 { 1276 MessageHandler(Looper looper) { 1277 super(looper); 1278 } 1279 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 { 1297 public DatabaseHelper(Context context) { 1298 super(context, DATABASE_NAME, null, DATABASE_VERSION); 1299 } 1300 1301 @Override 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 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 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 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 1380 public void onOpen(SQLiteDatabase db) { 1381 if (Log.isLoggable(TAG, Log.VERBOSE)) Log.v(TAG, "opened database " + DATABASE_NAME); 1382 } 1383 } 1384 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 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 { 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 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 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 */ 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 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 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 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 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 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 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 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 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 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 1633 private void checkAuthenticateAccountsPermission(Account account) { 1634 checkBinderPermission(Manifest.permission.AUTHENTICATE_ACCOUNTS); 1635 checkCallingUidAgainstAuthenticator(account); 1636 } 1637 1638 private void checkReadAccountsPermission() { 1639 checkBinderPermission(Manifest.permission.GET_ACCOUNTS); 1640 } 1641 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 */ 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 */ 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