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.annotation.SystemApi; 21 import android.content.Context; 22 import android.content.Intent; 23 import android.content.pm.PackageManager; 24 import android.os.Binder; 25 import android.os.Bundle; 26 import android.os.IBinder; 27 import android.os.RemoteException; 28 import android.text.TextUtils; 29 import android.util.Log; 30 31 import java.util.Arrays; 32 33 /** 34 * Abstract base class for creating AccountAuthenticators. 35 * In order to be an authenticator one must extend this class, provider implementations for the 36 * abstract methods and write a service that returns the result of {@link #getIBinder()} 37 * in the service's {@link android.app.Service#onBind(android.content.Intent)} when invoked 38 * with an intent with action {@link AccountManager#ACTION_AUTHENTICATOR_INTENT}. This service 39 * must specify the following intent filter and metadata tags in its AndroidManifest.xml file 40 * <pre> 41 * <intent-filter> 42 * <action android:name="android.accounts.AccountAuthenticator" /> 43 * </intent-filter> 44 * <meta-data android:name="android.accounts.AccountAuthenticator" 45 * android:resource="@xml/authenticator" /> 46 * </pre> 47 * The <code>android:resource</code> attribute must point to a resource that looks like: 48 * <pre> 49 * <account-authenticator xmlns:android="http://schemas.android.com/apk/res/android" 50 * android:accountType="typeOfAuthenticator" 51 * android:icon="@drawable/icon" 52 * android:smallIcon="@drawable/miniIcon" 53 * android:label="@string/label" 54 * android:accountPreferences="@xml/account_preferences" 55 * /> 56 * </pre> 57 * Replace the icons and labels with your own resources. The <code>android:accountType</code> 58 * attribute must be a string that uniquely identifies your authenticator and will be the same 59 * string that user will use when making calls on the {@link AccountManager} and it also 60 * corresponds to {@link Account#type} for your accounts. One user of the android:icon is the 61 * "Account & Sync" settings page and one user of the android:smallIcon is the Contact Application's 62 * tab panels. 63 * <p> 64 * The preferences attribute points to a PreferenceScreen xml hierarchy that contains 65 * a list of PreferenceScreens that can be invoked to manage the authenticator. An example is: 66 * <pre> 67 * <PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"> 68 * <PreferenceCategory android:title="@string/title_fmt" /> 69 * <PreferenceScreen 70 * android:key="key1" 71 * android:title="@string/key1_action" 72 * android:summary="@string/key1_summary"> 73 * <intent 74 * android:action="key1.ACTION" 75 * android:targetPackage="key1.package" 76 * android:targetClass="key1.class" /> 77 * </PreferenceScreen> 78 * </PreferenceScreen> 79 * </pre> 80 * 81 * <p> 82 * The standard pattern for implementing any of the abstract methods is the following: 83 * <ul> 84 * <li> If the supplied arguments are enough for the authenticator to fully satisfy the request 85 * then it will do so and return a {@link Bundle} that contains the results. 86 * <li> If the authenticator needs information from the user to satisfy the request then it 87 * will create an {@link Intent} to an activity that will prompt the user for the information 88 * and then carry out the request. This intent must be returned in a Bundle as key 89 * {@link AccountManager#KEY_INTENT}. 90 * <p> 91 * The activity needs to return the final result when it is complete so the Intent should contain 92 * the {@link AccountAuthenticatorResponse} as {@link AccountManager#KEY_ACCOUNT_MANAGER_RESPONSE}. 93 * The activity must then call {@link AccountAuthenticatorResponse#onResult} or 94 * {@link AccountAuthenticatorResponse#onError} when it is complete. 95 * <li> If the authenticator cannot synchronously process the request and return a result then it 96 * may choose to return null and then use the AccountManagerResponse to send the result 97 * when it has completed the request. 98 * </ul> 99 * <p> 100 * The following descriptions of each of the abstract authenticator methods will not describe the 101 * possible asynchronous nature of the request handling and will instead just describe the input 102 * parameters and the expected result. 103 * <p> 104 * When writing an activity to satisfy these requests one must pass in the AccountManagerResponse 105 * and return the result via that response when the activity finishes (or whenever else the 106 * activity author deems it is the correct time to respond). 107 * The {@link AccountAuthenticatorActivity} handles this, so one may wish to extend that when 108 * writing activities to handle these requests. 109 */ 110 public abstract class AbstractAccountAuthenticator { 111 private static final String TAG = "AccountAuthenticator"; 112 113 /** 114 * Bundle key used for the {@code long} expiration time (in millis from the unix epoch) of the 115 * associated auth token. 116 * 117 * @see #getAuthToken 118 */ 119 public static final String KEY_CUSTOM_TOKEN_EXPIRY = "android.accounts.expiry"; 120 121 /** 122 * Bundle key used for the {@link String} account type in session bundle. 123 * This is used in the default implementation of 124 * {@link #startAddAccountSession} and {@link startUpdateCredentialsSession}. 125 */ 126 private static final String KEY_AUTH_TOKEN_TYPE = 127 "android.accounts.AbstractAccountAuthenticato.KEY_AUTH_TOKEN_TYPE"; 128 /** 129 * Bundle key used for the {@link String} array of required features in 130 * session bundle. This is used in the default implementation of 131 * {@link #startAddAccountSession} and {@link startUpdateCredentialsSession}. 132 */ 133 private static final String KEY_REQUIRED_FEATURES = 134 "android.accounts.AbstractAccountAuthenticator.KEY_REQUIRED_FEATURES"; 135 /** 136 * Bundle key used for the {@link Bundle} options in session bundle. This is 137 * used in default implementation of {@link #startAddAccountSession} and 138 * {@link startUpdateCredentialsSession}. 139 */ 140 private static final String KEY_OPTIONS = 141 "android.accounts.AbstractAccountAuthenticator.KEY_OPTIONS"; 142 /** 143 * Bundle key used for the {@link Account} account in session bundle. This is used 144 * used in default implementation of {@link startUpdateCredentialsSession}. 145 */ 146 private static final String KEY_ACCOUNT = 147 "android.accounts.AbstractAccountAuthenticator.KEY_ACCOUNT"; 148 149 private final Context mContext; 150 151 public AbstractAccountAuthenticator(Context context) { 152 mContext = context; 153 } 154 155 private class Transport extends IAccountAuthenticator.Stub { 156 @Override 157 public void addAccount(IAccountAuthenticatorResponse response, String accountType, 158 String authTokenType, String[] features, Bundle options) 159 throws RemoteException { 160 if (Log.isLoggable(TAG, Log.VERBOSE)) { 161 Log.v(TAG, "addAccount: accountType " + accountType 162 + ", authTokenType " + authTokenType 163 + ", features " + (features == null ? "[]" : Arrays.toString(features))); 164 } 165 checkBinderPermission(); 166 try { 167 final Bundle result = AbstractAccountAuthenticator.this.addAccount( 168 new AccountAuthenticatorResponse(response), 169 accountType, authTokenType, features, options); 170 if (Log.isLoggable(TAG, Log.VERBOSE)) { 171 if (result != null) { 172 result.keySet(); // force it to be unparcelled 173 } 174 Log.v(TAG, "addAccount: result " + AccountManager.sanitizeResult(result)); 175 } 176 if (result != null) { 177 response.onResult(result); 178 } 179 } catch (Exception e) { 180 handleException(response, "addAccount", accountType, e); 181 } 182 } 183 184 @Override 185 public void confirmCredentials(IAccountAuthenticatorResponse response, 186 Account account, Bundle options) throws RemoteException { 187 if (Log.isLoggable(TAG, Log.VERBOSE)) { 188 Log.v(TAG, "confirmCredentials: " + account); 189 } 190 checkBinderPermission(); 191 try { 192 final Bundle result = AbstractAccountAuthenticator.this.confirmCredentials( 193 new AccountAuthenticatorResponse(response), account, options); 194 if (Log.isLoggable(TAG, Log.VERBOSE)) { 195 if (result != null) { 196 result.keySet(); // force it to be unparcelled 197 } 198 Log.v(TAG, "confirmCredentials: result " 199 + AccountManager.sanitizeResult(result)); 200 } 201 if (result != null) { 202 response.onResult(result); 203 } 204 } catch (Exception e) { 205 handleException(response, "confirmCredentials", account.toString(), e); 206 } 207 } 208 209 @Override 210 public void getAuthTokenLabel(IAccountAuthenticatorResponse response, 211 String authTokenType) 212 throws RemoteException { 213 if (Log.isLoggable(TAG, Log.VERBOSE)) { 214 Log.v(TAG, "getAuthTokenLabel: authTokenType " + authTokenType); 215 } 216 checkBinderPermission(); 217 try { 218 Bundle result = new Bundle(); 219 result.putString(AccountManager.KEY_AUTH_TOKEN_LABEL, 220 AbstractAccountAuthenticator.this.getAuthTokenLabel(authTokenType)); 221 if (Log.isLoggable(TAG, Log.VERBOSE)) { 222 if (result != null) { 223 result.keySet(); // force it to be unparcelled 224 } 225 Log.v(TAG, "getAuthTokenLabel: result " 226 + AccountManager.sanitizeResult(result)); 227 } 228 response.onResult(result); 229 } catch (Exception e) { 230 handleException(response, "getAuthTokenLabel", authTokenType, e); 231 } 232 } 233 234 @Override 235 public void getAuthToken(IAccountAuthenticatorResponse response, 236 Account account, String authTokenType, Bundle loginOptions) 237 throws RemoteException { 238 if (Log.isLoggable(TAG, Log.VERBOSE)) { 239 Log.v(TAG, "getAuthToken: " + account 240 + ", authTokenType " + authTokenType); 241 } 242 checkBinderPermission(); 243 try { 244 final Bundle result = AbstractAccountAuthenticator.this.getAuthToken( 245 new AccountAuthenticatorResponse(response), account, 246 authTokenType, loginOptions); 247 if (Log.isLoggable(TAG, Log.VERBOSE)) { 248 if (result != null) { 249 result.keySet(); // force it to be unparcelled 250 } 251 Log.v(TAG, "getAuthToken: result " + AccountManager.sanitizeResult(result)); 252 } 253 if (result != null) { 254 response.onResult(result); 255 } 256 } catch (Exception e) { 257 handleException(response, "getAuthToken", 258 account.toString() + "," + authTokenType, e); 259 } 260 } 261 262 @Override 263 public void updateCredentials(IAccountAuthenticatorResponse response, Account account, 264 String authTokenType, Bundle loginOptions) throws RemoteException { 265 if (Log.isLoggable(TAG, Log.VERBOSE)) { 266 Log.v(TAG, "updateCredentials: " + account 267 + ", authTokenType " + authTokenType); 268 } 269 checkBinderPermission(); 270 try { 271 final Bundle result = AbstractAccountAuthenticator.this.updateCredentials( 272 new AccountAuthenticatorResponse(response), account, 273 authTokenType, loginOptions); 274 if (Log.isLoggable(TAG, Log.VERBOSE)) { 275 // Result may be null. 276 if (result != null) { 277 result.keySet(); // force it to be unparcelled 278 } 279 Log.v(TAG, "updateCredentials: result " 280 + AccountManager.sanitizeResult(result)); 281 } 282 if (result != null) { 283 response.onResult(result); 284 } 285 } catch (Exception e) { 286 handleException(response, "updateCredentials", 287 account.toString() + "," + authTokenType, e); 288 } 289 } 290 291 @Override 292 public void editProperties(IAccountAuthenticatorResponse response, 293 String accountType) throws RemoteException { 294 checkBinderPermission(); 295 try { 296 final Bundle result = AbstractAccountAuthenticator.this.editProperties( 297 new AccountAuthenticatorResponse(response), accountType); 298 if (result != null) { 299 response.onResult(result); 300 } 301 } catch (Exception e) { 302 handleException(response, "editProperties", accountType, e); 303 } 304 } 305 306 @Override 307 public void hasFeatures(IAccountAuthenticatorResponse response, 308 Account account, String[] features) throws RemoteException { 309 checkBinderPermission(); 310 try { 311 final Bundle result = AbstractAccountAuthenticator.this.hasFeatures( 312 new AccountAuthenticatorResponse(response), account, features); 313 if (result != null) { 314 response.onResult(result); 315 } 316 } catch (Exception e) { 317 handleException(response, "hasFeatures", account.toString(), e); 318 } 319 } 320 321 @Override 322 public void getAccountRemovalAllowed(IAccountAuthenticatorResponse response, 323 Account account) throws RemoteException { 324 checkBinderPermission(); 325 try { 326 final Bundle result = AbstractAccountAuthenticator.this.getAccountRemovalAllowed( 327 new AccountAuthenticatorResponse(response), account); 328 if (result != null) { 329 response.onResult(result); 330 } 331 } catch (Exception e) { 332 handleException(response, "getAccountRemovalAllowed", account.toString(), e); 333 } 334 } 335 336 @Override 337 public void getAccountCredentialsForCloning(IAccountAuthenticatorResponse response, 338 Account account) throws RemoteException { 339 checkBinderPermission(); 340 try { 341 final Bundle result = 342 AbstractAccountAuthenticator.this.getAccountCredentialsForCloning( 343 new AccountAuthenticatorResponse(response), account); 344 if (result != null) { 345 response.onResult(result); 346 } 347 } catch (Exception e) { 348 handleException(response, "getAccountCredentialsForCloning", account.toString(), e); 349 } 350 } 351 352 @Override 353 public void addAccountFromCredentials(IAccountAuthenticatorResponse response, 354 Account account, 355 Bundle accountCredentials) throws RemoteException { 356 checkBinderPermission(); 357 try { 358 final Bundle result = 359 AbstractAccountAuthenticator.this.addAccountFromCredentials( 360 new AccountAuthenticatorResponse(response), account, 361 accountCredentials); 362 if (result != null) { 363 response.onResult(result); 364 } 365 } catch (Exception e) { 366 handleException(response, "addAccountFromCredentials", account.toString(), e); 367 } 368 } 369 370 @Override 371 public void startAddAccountSession(IAccountAuthenticatorResponse response, 372 String accountType, String authTokenType, String[] features, Bundle options) 373 throws RemoteException { 374 if (Log.isLoggable(TAG, Log.VERBOSE)) { 375 Log.v(TAG, 376 "startAddAccountSession: accountType " + accountType 377 + ", authTokenType " + authTokenType 378 + ", features " + (features == null ? "[]" : Arrays.toString(features))); 379 } 380 checkBinderPermission(); 381 try { 382 final Bundle result = AbstractAccountAuthenticator.this.startAddAccountSession( 383 new AccountAuthenticatorResponse(response), accountType, authTokenType, 384 features, options); 385 if (Log.isLoggable(TAG, Log.VERBOSE)) { 386 if (result != null) { 387 result.keySet(); // force it to be unparcelled 388 } 389 Log.v(TAG, "startAddAccountSession: result " 390 + AccountManager.sanitizeResult(result)); 391 } 392 if (result != null) { 393 response.onResult(result); 394 } 395 } catch (Exception e) { 396 handleException(response, "startAddAccountSession", accountType, e); 397 } 398 } 399 400 @Override 401 public void startUpdateCredentialsSession( 402 IAccountAuthenticatorResponse response, 403 Account account, 404 String authTokenType, 405 Bundle loginOptions) throws RemoteException { 406 if (Log.isLoggable(TAG, Log.VERBOSE)) { 407 Log.v(TAG, "startUpdateCredentialsSession: " 408 + account 409 + ", authTokenType " 410 + authTokenType); 411 } 412 checkBinderPermission(); 413 try { 414 final Bundle result = AbstractAccountAuthenticator.this 415 .startUpdateCredentialsSession( 416 new AccountAuthenticatorResponse(response), 417 account, 418 authTokenType, 419 loginOptions); 420 if (Log.isLoggable(TAG, Log.VERBOSE)) { 421 // Result may be null. 422 if (result != null) { 423 result.keySet(); // force it to be unparcelled 424 } 425 Log.v(TAG, "startUpdateCredentialsSession: result " 426 + AccountManager.sanitizeResult(result)); 427 428 } 429 if (result != null) { 430 response.onResult(result); 431 } 432 } catch (Exception e) { 433 handleException(response, "startUpdateCredentialsSession", 434 account.toString() + "," + authTokenType, e); 435 436 } 437 } 438 439 @Override 440 public void finishSession( 441 IAccountAuthenticatorResponse response, 442 String accountType, 443 Bundle sessionBundle) throws RemoteException { 444 if (Log.isLoggable(TAG, Log.VERBOSE)) { 445 Log.v(TAG, "finishSession: accountType " + accountType); 446 } 447 checkBinderPermission(); 448 try { 449 final Bundle result = AbstractAccountAuthenticator.this.finishSession( 450 new AccountAuthenticatorResponse(response), accountType, sessionBundle); 451 if (result != null) { 452 result.keySet(); // force it to be unparcelled 453 } 454 if (Log.isLoggable(TAG, Log.VERBOSE)) { 455 Log.v(TAG, "finishSession: result " + AccountManager.sanitizeResult(result)); 456 } 457 if (result != null) { 458 response.onResult(result); 459 } 460 } catch (Exception e) { 461 handleException(response, "finishSession", accountType, e); 462 463 } 464 } 465 466 @Override 467 public void isCredentialsUpdateSuggested( 468 IAccountAuthenticatorResponse response, 469 Account account, 470 String statusToken) throws RemoteException { 471 checkBinderPermission(); 472 try { 473 final Bundle result = AbstractAccountAuthenticator.this 474 .isCredentialsUpdateSuggested( 475 new AccountAuthenticatorResponse(response), account, statusToken); 476 if (result != null) { 477 response.onResult(result); 478 } 479 } catch (Exception e) { 480 handleException(response, "isCredentialsUpdateSuggested", account.toString(), e); 481 } 482 } 483 } 484 485 private void handleException(IAccountAuthenticatorResponse response, String method, 486 String data, Exception e) throws RemoteException { 487 if (e instanceof NetworkErrorException) { 488 if (Log.isLoggable(TAG, Log.VERBOSE)) { 489 Log.v(TAG, method + "(" + data + ")", e); 490 } 491 response.onError(AccountManager.ERROR_CODE_NETWORK_ERROR, e.getMessage()); 492 } else if (e instanceof UnsupportedOperationException) { 493 if (Log.isLoggable(TAG, Log.VERBOSE)) { 494 Log.v(TAG, method + "(" + data + ")", e); 495 } 496 response.onError(AccountManager.ERROR_CODE_UNSUPPORTED_OPERATION, 497 method + " not supported"); 498 } else if (e instanceof IllegalArgumentException) { 499 if (Log.isLoggable(TAG, Log.VERBOSE)) { 500 Log.v(TAG, method + "(" + data + ")", e); 501 } 502 response.onError(AccountManager.ERROR_CODE_BAD_ARGUMENTS, 503 method + " not supported"); 504 } else { 505 Log.w(TAG, method + "(" + data + ")", e); 506 response.onError(AccountManager.ERROR_CODE_REMOTE_EXCEPTION, 507 method + " failed"); 508 } 509 } 510 511 private void checkBinderPermission() { 512 final int uid = Binder.getCallingUid(); 513 final String perm = Manifest.permission.ACCOUNT_MANAGER; 514 if (mContext.checkCallingOrSelfPermission(perm) != PackageManager.PERMISSION_GRANTED) { 515 throw new SecurityException("caller uid " + uid + " lacks " + perm); 516 } 517 } 518 519 private Transport mTransport = new Transport(); 520 521 /** 522 * @return the IBinder for the AccountAuthenticator 523 */ 524 public final IBinder getIBinder() { 525 return mTransport.asBinder(); 526 } 527 528 /** 529 * Returns a Bundle that contains the Intent of the activity that can be used to edit the 530 * properties. In order to indicate success the activity should call response.setResult() 531 * with a non-null Bundle. 532 * @param response used to set the result for the request. If the Constants.INTENT_KEY 533 * is set in the bundle then this response field is to be used for sending future 534 * results if and when the Intent is started. 535 * @param accountType the AccountType whose properties are to be edited. 536 * @return a Bundle containing the result or the Intent to start to continue the request. 537 * If this is null then the request is considered to still be active and the result should 538 * sent later using response. 539 */ 540 public abstract Bundle editProperties(AccountAuthenticatorResponse response, 541 String accountType); 542 543 /** 544 * Adds an account of the specified accountType. 545 * @param response to send the result back to the AccountManager, will never be null 546 * @param accountType the type of account to add, will never be null 547 * @param authTokenType the type of auth token to retrieve after adding the account, may be null 548 * @param requiredFeatures a String array of authenticator-specific features that the added 549 * account must support, may be null 550 * @param options a Bundle of authenticator-specific options. It always contains 551 * {@link AccountManager#KEY_CALLER_PID} and {@link AccountManager#KEY_CALLER_UID} 552 * fields which will let authenticator know the identity of the caller. 553 * @return a Bundle result or null if the result is to be returned via the response. The result 554 * will contain either: 555 * <ul> 556 * <li> {@link AccountManager#KEY_INTENT}, or 557 * <li> {@link AccountManager#KEY_ACCOUNT_NAME} and {@link AccountManager#KEY_ACCOUNT_TYPE} of 558 * the account that was added, or 559 * <li> {@link AccountManager#KEY_ERROR_CODE} and {@link AccountManager#KEY_ERROR_MESSAGE} to 560 * indicate an error 561 * </ul> 562 * @throws NetworkErrorException if the authenticator could not honor the request due to a 563 * network error 564 */ 565 public abstract Bundle addAccount(AccountAuthenticatorResponse response, String accountType, 566 String authTokenType, String[] requiredFeatures, Bundle options) 567 throws NetworkErrorException; 568 569 /** 570 * Checks that the user knows the credentials of an account. 571 * @param response to send the result back to the AccountManager, will never be null 572 * @param account the account whose credentials are to be checked, will never be null 573 * @param options a Bundle of authenticator-specific options, may be null 574 * @return a Bundle result or null if the result is to be returned via the response. The result 575 * will contain either: 576 * <ul> 577 * <li> {@link AccountManager#KEY_INTENT}, or 578 * <li> {@link AccountManager#KEY_BOOLEAN_RESULT}, true if the check succeeded, false otherwise 579 * <li> {@link AccountManager#KEY_ERROR_CODE} and {@link AccountManager#KEY_ERROR_MESSAGE} to 580 * indicate an error 581 * </ul> 582 * @throws NetworkErrorException if the authenticator could not honor the request due to a 583 * network error 584 */ 585 public abstract Bundle confirmCredentials(AccountAuthenticatorResponse response, 586 Account account, Bundle options) 587 throws NetworkErrorException; 588 589 /** 590 * Gets an authtoken for an account. 591 * 592 * If not {@code null}, the resultant {@link Bundle} will contain different sets of keys 593 * depending on whether a token was successfully issued and, if not, whether one 594 * could be issued via some {@link android.app.Activity}. 595 * <p> 596 * If a token cannot be provided without some additional activity, the Bundle should contain 597 * {@link AccountManager#KEY_INTENT} with an associated {@link Intent}. On the other hand, if 598 * there is no such activity, then a Bundle containing 599 * {@link AccountManager#KEY_ERROR_CODE} and {@link AccountManager#KEY_ERROR_MESSAGE} should be 600 * returned. 601 * <p> 602 * If a token can be successfully issued, the implementation should return the 603 * {@link AccountManager#KEY_ACCOUNT_NAME} and {@link AccountManager#KEY_ACCOUNT_TYPE} of the 604 * account associated with the token as well as the {@link AccountManager#KEY_AUTHTOKEN}. In 605 * addition {@link AbstractAccountAuthenticator} implementations that declare themselves 606 * {@code android:customTokens=true} may also provide a non-negative {@link 607 * #KEY_CUSTOM_TOKEN_EXPIRY} long value containing the expiration timestamp of the expiration 608 * time (in millis since the unix epoch), tokens will be cached in memory based on 609 * application's packageName/signature for however long that was specified. 610 * <p> 611 * Implementers should assume that tokens will be cached on the basis of account and 612 * authTokenType. The system may ignore the contents of the supplied options Bundle when 613 * determining to re-use a cached token. Furthermore, implementers should assume a supplied 614 * expiration time will be treated as non-binding advice. 615 * <p> 616 * Finally, note that for {@code android:customTokens=false} authenticators, tokens are cached 617 * indefinitely until some client calls {@link 618 * AccountManager#invalidateAuthToken(String,String)}. 619 * 620 * @param response to send the result back to the AccountManager, will never be null 621 * @param account the account whose credentials are to be retrieved, will never be null 622 * @param authTokenType the type of auth token to retrieve, will never be null 623 * @param options a Bundle of authenticator-specific options. It always contains 624 * {@link AccountManager#KEY_CALLER_PID} and {@link AccountManager#KEY_CALLER_UID} 625 * fields which will let authenticator know the identity of the caller. 626 * @return a Bundle result or null if the result is to be returned via the response. 627 * @throws NetworkErrorException if the authenticator could not honor the request due to a 628 * network error 629 */ 630 public abstract Bundle getAuthToken(AccountAuthenticatorResponse response, 631 Account account, String authTokenType, Bundle options) 632 throws NetworkErrorException; 633 634 /** 635 * Ask the authenticator for a localized label for the given authTokenType. 636 * @param authTokenType the authTokenType whose label is to be returned, will never be null 637 * @return the localized label of the auth token type, may be null if the type isn't known 638 */ 639 public abstract String getAuthTokenLabel(String authTokenType); 640 641 /** 642 * Update the locally stored credentials for an account. 643 * @param response to send the result back to the AccountManager, will never be null 644 * @param account the account whose credentials are to be updated, will never be null 645 * @param authTokenType the type of auth token to retrieve after updating the credentials, 646 * may be null 647 * @param options a Bundle of authenticator-specific options, may be null 648 * @return a Bundle result or null if the result is to be returned via the response. The result 649 * will contain either: 650 * <ul> 651 * <li> {@link AccountManager#KEY_INTENT}, or 652 * <li> {@link AccountManager#KEY_ACCOUNT_NAME} and {@link AccountManager#KEY_ACCOUNT_TYPE} of 653 * the account whose credentials were updated, or 654 * <li> {@link AccountManager#KEY_ERROR_CODE} and {@link AccountManager#KEY_ERROR_MESSAGE} to 655 * indicate an error 656 * </ul> 657 * @throws NetworkErrorException if the authenticator could not honor the request due to a 658 * network error 659 */ 660 public abstract Bundle updateCredentials(AccountAuthenticatorResponse response, 661 Account account, String authTokenType, Bundle options) throws NetworkErrorException; 662 663 /** 664 * Checks if the account supports all the specified authenticator specific features. 665 * @param response to send the result back to the AccountManager, will never be null 666 * @param account the account to check, will never be null 667 * @param features an array of features to check, will never be null 668 * @return a Bundle result or null if the result is to be returned via the response. The result 669 * will contain either: 670 * <ul> 671 * <li> {@link AccountManager#KEY_INTENT}, or 672 * <li> {@link AccountManager#KEY_BOOLEAN_RESULT}, true if the account has all the features, 673 * false otherwise 674 * <li> {@link AccountManager#KEY_ERROR_CODE} and {@link AccountManager#KEY_ERROR_MESSAGE} to 675 * indicate an error 676 * </ul> 677 * @throws NetworkErrorException if the authenticator could not honor the request due to a 678 * network error 679 */ 680 public abstract Bundle hasFeatures(AccountAuthenticatorResponse response, 681 Account account, String[] features) throws NetworkErrorException; 682 683 /** 684 * Checks if the removal of this account is allowed. 685 * @param response to send the result back to the AccountManager, will never be null 686 * @param account the account to check, will never be null 687 * @return a Bundle result or null if the result is to be returned via the response. The result 688 * will contain either: 689 * <ul> 690 * <li> {@link AccountManager#KEY_INTENT}, or 691 * <li> {@link AccountManager#KEY_BOOLEAN_RESULT}, true if the removal of the account is 692 * allowed, false otherwise 693 * <li> {@link AccountManager#KEY_ERROR_CODE} and {@link AccountManager#KEY_ERROR_MESSAGE} to 694 * indicate an error 695 * </ul> 696 * @throws NetworkErrorException if the authenticator could not honor the request due to a 697 * network error 698 */ 699 public Bundle getAccountRemovalAllowed(AccountAuthenticatorResponse response, 700 Account account) throws NetworkErrorException { 701 final Bundle result = new Bundle(); 702 result.putBoolean(AccountManager.KEY_BOOLEAN_RESULT, true); 703 return result; 704 } 705 706 /** 707 * Returns a Bundle that contains whatever is required to clone the account on a different 708 * user. The Bundle is passed to the authenticator instance in the target user via 709 * {@link #addAccountFromCredentials(AccountAuthenticatorResponse, Account, Bundle)}. 710 * The default implementation returns null, indicating that cloning is not supported. 711 * @param response to send the result back to the AccountManager, will never be null 712 * @param account the account to clone, will never be null 713 * @return a Bundle result or null if the result is to be returned via the response. 714 * @throws NetworkErrorException 715 * @see #addAccountFromCredentials(AccountAuthenticatorResponse, Account, Bundle) 716 */ 717 public Bundle getAccountCredentialsForCloning(final AccountAuthenticatorResponse response, 718 final Account account) throws NetworkErrorException { 719 new Thread(new Runnable() { 720 @Override 721 public void run() { 722 Bundle result = new Bundle(); 723 result.putBoolean(AccountManager.KEY_BOOLEAN_RESULT, false); 724 response.onResult(result); 725 } 726 }).start(); 727 return null; 728 } 729 730 /** 731 * Creates an account based on credentials provided by the authenticator instance of another 732 * user on the device, who has chosen to share the account with this user. 733 * @param response to send the result back to the AccountManager, will never be null 734 * @param account the account to clone, will never be null 735 * @param accountCredentials the Bundle containing the required credentials to create the 736 * account. Contents of the Bundle are only meaningful to the authenticator. This Bundle is 737 * provided by {@link #getAccountCredentialsForCloning(AccountAuthenticatorResponse, Account)}. 738 * @return a Bundle result or null if the result is to be returned via the response. 739 * @throws NetworkErrorException 740 * @see #getAccountCredentialsForCloning(AccountAuthenticatorResponse, Account) 741 */ 742 public Bundle addAccountFromCredentials(final AccountAuthenticatorResponse response, 743 Account account, 744 Bundle accountCredentials) throws NetworkErrorException { 745 new Thread(new Runnable() { 746 @Override 747 public void run() { 748 Bundle result = new Bundle(); 749 result.putBoolean(AccountManager.KEY_BOOLEAN_RESULT, false); 750 response.onResult(result); 751 } 752 }).start(); 753 return null; 754 } 755 756 /** 757 * Starts the add account session to authenticate user to an account of the 758 * specified accountType. No file I/O should be performed in this call. 759 * Account should be added to device only when {@link #finishSession} is 760 * called after this. 761 * <p> 762 * Note: when overriding this method, {@link #finishSession} should be 763 * overridden too. 764 * </p> 765 * 766 * @param response to send the result back to the AccountManager, will never 767 * be null 768 * @param accountType the type of account to authenticate with, will never 769 * be null 770 * @param authTokenType the type of auth token to retrieve after 771 * authenticating with the account, may be null 772 * @param requiredFeatures a String array of authenticator-specific features 773 * that the account authenticated with must support, may be null 774 * @param options a Bundle of authenticator-specific options, may be null 775 * @return a Bundle result or null if the result is to be returned via the 776 * response. The result will contain either: 777 * <ul> 778 * <li>{@link AccountManager#KEY_INTENT}, or 779 * <li>{@link AccountManager#KEY_ACCOUNT_SESSION_BUNDLE} for adding 780 * the account to device later, and if account is authenticated, 781 * optional {@link AccountManager#KEY_PASSWORD} and 782 * {@link AccountManager#KEY_ACCOUNT_STATUS_TOKEN} for checking the 783 * status of the account, or 784 * <li>{@link AccountManager#KEY_ERROR_CODE} and 785 * {@link AccountManager#KEY_ERROR_MESSAGE} to indicate an error 786 * </ul> 787 * @throws NetworkErrorException if the authenticator could not honor the 788 * request due to a network error 789 * @see #finishSession(AccountAuthenticatorResponse, String, Bundle) 790 */ 791 public Bundle startAddAccountSession( 792 final AccountAuthenticatorResponse response, 793 final String accountType, 794 final String authTokenType, 795 final String[] requiredFeatures, 796 final Bundle options) 797 throws NetworkErrorException { 798 new Thread(new Runnable() { 799 @Override 800 public void run() { 801 Bundle sessionBundle = new Bundle(); 802 sessionBundle.putString(KEY_AUTH_TOKEN_TYPE, authTokenType); 803 sessionBundle.putStringArray(KEY_REQUIRED_FEATURES, requiredFeatures); 804 sessionBundle.putBundle(KEY_OPTIONS, options); 805 Bundle result = new Bundle(); 806 result.putBundle(AccountManager.KEY_ACCOUNT_SESSION_BUNDLE, sessionBundle); 807 response.onResult(result); 808 } 809 810 }).start(); 811 return null; 812 } 813 814 /** 815 * Asks user to re-authenticate for an account but defers updating the 816 * locally stored credentials. No file I/O should be performed in this call. 817 * Local credentials should be updated only when {@link #finishSession} is 818 * called after this. 819 * <p> 820 * Note: when overriding this method, {@link #finishSession} should be 821 * overridden too. 822 * </p> 823 * 824 * @param response to send the result back to the AccountManager, will never 825 * be null 826 * @param account the account whose credentials are to be updated, will 827 * never be null 828 * @param authTokenType the type of auth token to retrieve after updating 829 * the credentials, may be null 830 * @param options a Bundle of authenticator-specific options, may be null 831 * @return a Bundle result or null if the result is to be returned via the 832 * response. The result will contain either: 833 * <ul> 834 * <li>{@link AccountManager#KEY_INTENT}, or 835 * <li>{@link AccountManager#KEY_ACCOUNT_SESSION_BUNDLE} for 836 * updating the locally stored credentials later, and if account is 837 * re-authenticated, optional {@link AccountManager#KEY_PASSWORD} 838 * and {@link AccountManager#KEY_ACCOUNT_STATUS_TOKEN} for checking 839 * the status of the account later, or 840 * <li>{@link AccountManager#KEY_ERROR_CODE} and 841 * {@link AccountManager#KEY_ERROR_MESSAGE} to indicate an error 842 * </ul> 843 * @throws NetworkErrorException if the authenticator could not honor the 844 * request due to a network error 845 * @see #finishSession(AccountAuthenticatorResponse, String, Bundle) 846 */ 847 public Bundle startUpdateCredentialsSession( 848 final AccountAuthenticatorResponse response, 849 final Account account, 850 final String authTokenType, 851 final Bundle options) throws NetworkErrorException { 852 new Thread(new Runnable() { 853 @Override 854 public void run() { 855 Bundle sessionBundle = new Bundle(); 856 sessionBundle.putString(KEY_AUTH_TOKEN_TYPE, authTokenType); 857 sessionBundle.putParcelable(KEY_ACCOUNT, account); 858 sessionBundle.putBundle(KEY_OPTIONS, options); 859 Bundle result = new Bundle(); 860 result.putBundle(AccountManager.KEY_ACCOUNT_SESSION_BUNDLE, sessionBundle); 861 response.onResult(result); 862 } 863 864 }).start(); 865 return null; 866 } 867 868 /** 869 * Finishes the session started by #startAddAccountSession or 870 * #startUpdateCredentials by installing the account to device with 871 * AccountManager, or updating the local credentials. File I/O may be 872 * performed in this call. 873 * <p> 874 * Note: when overriding this method, {@link #startAddAccountSession} and 875 * {@link #startUpdateCredentialsSession} should be overridden too. 876 * </p> 877 * 878 * @param response to send the result back to the AccountManager, will never 879 * be null 880 * @param accountType the type of account to authenticate with, will never 881 * be null 882 * @param sessionBundle a bundle of session data created by 883 * {@link #startAddAccountSession} used for adding account to 884 * device, or by {@link #startUpdateCredentialsSession} used for 885 * updating local credentials. 886 * @return a Bundle result or null if the result is to be returned via the 887 * response. The result will contain either: 888 * <ul> 889 * <li>{@link AccountManager#KEY_INTENT}, or 890 * <li>{@link AccountManager#KEY_ACCOUNT_NAME} and 891 * {@link AccountManager#KEY_ACCOUNT_TYPE} of the account that was 892 * added or local credentials were updated, and optional 893 * {@link AccountManager#KEY_ACCOUNT_STATUS_TOKEN} for checking 894 * the status of the account later, or 895 * <li>{@link AccountManager#KEY_ERROR_CODE} and 896 * {@link AccountManager#KEY_ERROR_MESSAGE} to indicate an error 897 * </ul> 898 * @throws NetworkErrorException if the authenticator could not honor the request due to a 899 * network error 900 * @see #startAddAccountSession and #startUpdateCredentialsSession 901 */ 902 public Bundle finishSession( 903 final AccountAuthenticatorResponse response, 904 final String accountType, 905 final Bundle sessionBundle) throws NetworkErrorException { 906 if (TextUtils.isEmpty(accountType)) { 907 Log.e(TAG, "Account type cannot be empty."); 908 Bundle result = new Bundle(); 909 result.putInt(AccountManager.KEY_ERROR_CODE, AccountManager.ERROR_CODE_BAD_ARGUMENTS); 910 result.putString(AccountManager.KEY_ERROR_MESSAGE, 911 "accountType cannot be empty."); 912 return result; 913 } 914 915 if (sessionBundle == null) { 916 Log.e(TAG, "Session bundle cannot be null."); 917 Bundle result = new Bundle(); 918 result.putInt(AccountManager.KEY_ERROR_CODE, AccountManager.ERROR_CODE_BAD_ARGUMENTS); 919 result.putString(AccountManager.KEY_ERROR_MESSAGE, 920 "sessionBundle cannot be null."); 921 return result; 922 } 923 924 if (!sessionBundle.containsKey(KEY_AUTH_TOKEN_TYPE)) { 925 // We cannot handle Session bundle not created by default startAddAccountSession(...) 926 // nor startUpdateCredentialsSession(...) implementation. Return error. 927 Bundle result = new Bundle(); 928 result.putInt(AccountManager.KEY_ERROR_CODE, 929 AccountManager.ERROR_CODE_UNSUPPORTED_OPERATION); 930 result.putString(AccountManager.KEY_ERROR_MESSAGE, 931 "Authenticator must override finishSession if startAddAccountSession" 932 + " or startUpdateCredentialsSession is overridden."); 933 response.onResult(result); 934 return result; 935 } 936 String authTokenType = sessionBundle.getString(KEY_AUTH_TOKEN_TYPE); 937 Bundle options = sessionBundle.getBundle(KEY_OPTIONS); 938 String[] requiredFeatures = sessionBundle.getStringArray(KEY_REQUIRED_FEATURES); 939 Account account = sessionBundle.getParcelable(KEY_ACCOUNT); 940 boolean containsKeyAccount = sessionBundle.containsKey(KEY_ACCOUNT); 941 942 // Actual options passed to add account or update credentials flow. 943 Bundle sessionOptions = new Bundle(sessionBundle); 944 // Remove redundant extras in session bundle before passing it to addAccount(...) or 945 // updateCredentials(...). 946 sessionOptions.remove(KEY_AUTH_TOKEN_TYPE); 947 sessionOptions.remove(KEY_REQUIRED_FEATURES); 948 sessionOptions.remove(KEY_OPTIONS); 949 sessionOptions.remove(KEY_ACCOUNT); 950 951 if (options != null) { 952 // options may contains old system info such as 953 // AccountManager.KEY_ANDROID_PACKAGE_NAME required by the add account flow or update 954 // credentials flow, we should replace with the new values of the current call added 955 // to sessionBundle by AccountManager or AccountManagerService. 956 options.putAll(sessionOptions); 957 sessionOptions = options; 958 } 959 960 // Session bundle created by startUpdateCredentialsSession default implementation should 961 // contain KEY_ACCOUNT. 962 if (containsKeyAccount) { 963 return updateCredentials(response, account, authTokenType, options); 964 } 965 // Otherwise, session bundle was created by startAddAccountSession default implementation. 966 return addAccount(response, accountType, authTokenType, requiredFeatures, sessionOptions); 967 } 968 969 /** 970 * Checks if update of the account credentials is suggested. 971 * 972 * @param response to send the result back to the AccountManager, will never be null. 973 * @param account the account to check, will never be null 974 * @param statusToken a String of token to check if update of credentials is suggested. 975 * @return a Bundle result or null if the result is to be returned via the response. The result 976 * will contain either: 977 * <ul> 978 * <li>{@link AccountManager#KEY_BOOLEAN_RESULT}, true if update of account's 979 * credentials is suggested, false otherwise 980 * <li>{@link AccountManager#KEY_ERROR_CODE} and 981 * {@link AccountManager#KEY_ERROR_MESSAGE} to indicate an error 982 * </ul> 983 * @throws NetworkErrorException if the authenticator could not honor the request due to a 984 * network error 985 */ 986 public Bundle isCredentialsUpdateSuggested( 987 final AccountAuthenticatorResponse response, 988 Account account, 989 String statusToken) throws NetworkErrorException { 990 Bundle result = new Bundle(); 991 result.putBoolean(AccountManager.KEY_BOOLEAN_RESULT, false); 992 return result; 993 } 994 } 995