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