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