1/* 2 * Copyright (C) 2009 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17package android.accounts; 18 19import android.Manifest; 20import android.annotation.SystemApi; 21import android.content.Context; 22import android.content.Intent; 23import android.content.pm.PackageManager; 24import android.os.Binder; 25import android.os.Bundle; 26import android.os.IBinder; 27import android.os.RemoteException; 28import android.text.TextUtils; 29import android.util.Log; 30 31import 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 */ 110public 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 } else { 179 response.onError(AccountManager.ERROR_CODE_INVALID_RESPONSE, 180 "null bundle returned"); 181 } 182 } catch (Exception e) { 183 handleException(response, "addAccount", accountType, e); 184 } 185 } 186 187 @Override 188 public void confirmCredentials(IAccountAuthenticatorResponse response, 189 Account account, Bundle options) throws RemoteException { 190 if (Log.isLoggable(TAG, Log.VERBOSE)) { 191 Log.v(TAG, "confirmCredentials: " + account); 192 } 193 checkBinderPermission(); 194 try { 195 final Bundle result = AbstractAccountAuthenticator.this.confirmCredentials( 196 new AccountAuthenticatorResponse(response), account, options); 197 if (Log.isLoggable(TAG, Log.VERBOSE)) { 198 if (result != null) { 199 result.keySet(); // force it to be unparcelled 200 } 201 Log.v(TAG, "confirmCredentials: result " 202 + AccountManager.sanitizeResult(result)); 203 } 204 if (result != null) { 205 response.onResult(result); 206 } 207 } catch (Exception e) { 208 handleException(response, "confirmCredentials", account.toString(), e); 209 } 210 } 211 212 @Override 213 public void getAuthTokenLabel(IAccountAuthenticatorResponse response, 214 String authTokenType) 215 throws RemoteException { 216 if (Log.isLoggable(TAG, Log.VERBOSE)) { 217 Log.v(TAG, "getAuthTokenLabel: authTokenType " + authTokenType); 218 } 219 checkBinderPermission(); 220 try { 221 Bundle result = new Bundle(); 222 result.putString(AccountManager.KEY_AUTH_TOKEN_LABEL, 223 AbstractAccountAuthenticator.this.getAuthTokenLabel(authTokenType)); 224 if (Log.isLoggable(TAG, Log.VERBOSE)) { 225 if (result != null) { 226 result.keySet(); // force it to be unparcelled 227 } 228 Log.v(TAG, "getAuthTokenLabel: result " 229 + AccountManager.sanitizeResult(result)); 230 } 231 response.onResult(result); 232 } catch (Exception e) { 233 handleException(response, "getAuthTokenLabel", authTokenType, e); 234 } 235 } 236 237 @Override 238 public void getAuthToken(IAccountAuthenticatorResponse response, 239 Account account, String authTokenType, Bundle loginOptions) 240 throws RemoteException { 241 if (Log.isLoggable(TAG, Log.VERBOSE)) { 242 Log.v(TAG, "getAuthToken: " + account 243 + ", authTokenType " + authTokenType); 244 } 245 checkBinderPermission(); 246 try { 247 final Bundle result = AbstractAccountAuthenticator.this.getAuthToken( 248 new AccountAuthenticatorResponse(response), account, 249 authTokenType, loginOptions); 250 if (Log.isLoggable(TAG, Log.VERBOSE)) { 251 if (result != null) { 252 result.keySet(); // force it to be unparcelled 253 } 254 Log.v(TAG, "getAuthToken: result " + AccountManager.sanitizeResult(result)); 255 } 256 if (result != null) { 257 response.onResult(result); 258 } 259 } catch (Exception e) { 260 handleException(response, "getAuthToken", 261 account.toString() + "," + authTokenType, e); 262 } 263 } 264 265 @Override 266 public void updateCredentials(IAccountAuthenticatorResponse response, Account account, 267 String authTokenType, Bundle loginOptions) throws RemoteException { 268 if (Log.isLoggable(TAG, Log.VERBOSE)) { 269 Log.v(TAG, "updateCredentials: " + account 270 + ", authTokenType " + authTokenType); 271 } 272 checkBinderPermission(); 273 try { 274 final Bundle result = AbstractAccountAuthenticator.this.updateCredentials( 275 new AccountAuthenticatorResponse(response), account, 276 authTokenType, loginOptions); 277 if (Log.isLoggable(TAG, Log.VERBOSE)) { 278 // Result may be null. 279 if (result != null) { 280 result.keySet(); // force it to be unparcelled 281 } 282 Log.v(TAG, "updateCredentials: result " 283 + AccountManager.sanitizeResult(result)); 284 } 285 if (result != null) { 286 response.onResult(result); 287 } 288 } catch (Exception e) { 289 handleException(response, "updateCredentials", 290 account.toString() + "," + authTokenType, e); 291 } 292 } 293 294 @Override 295 public void editProperties(IAccountAuthenticatorResponse response, 296 String accountType) throws RemoteException { 297 checkBinderPermission(); 298 try { 299 final Bundle result = AbstractAccountAuthenticator.this.editProperties( 300 new AccountAuthenticatorResponse(response), accountType); 301 if (result != null) { 302 response.onResult(result); 303 } 304 } catch (Exception e) { 305 handleException(response, "editProperties", accountType, e); 306 } 307 } 308 309 @Override 310 public void hasFeatures(IAccountAuthenticatorResponse response, 311 Account account, String[] features) throws RemoteException { 312 checkBinderPermission(); 313 try { 314 final Bundle result = AbstractAccountAuthenticator.this.hasFeatures( 315 new AccountAuthenticatorResponse(response), account, features); 316 if (result != null) { 317 response.onResult(result); 318 } 319 } catch (Exception e) { 320 handleException(response, "hasFeatures", account.toString(), e); 321 } 322 } 323 324 @Override 325 public void getAccountRemovalAllowed(IAccountAuthenticatorResponse response, 326 Account account) throws RemoteException { 327 checkBinderPermission(); 328 try { 329 final Bundle result = AbstractAccountAuthenticator.this.getAccountRemovalAllowed( 330 new AccountAuthenticatorResponse(response), account); 331 if (result != null) { 332 response.onResult(result); 333 } 334 } catch (Exception e) { 335 handleException(response, "getAccountRemovalAllowed", account.toString(), e); 336 } 337 } 338 339 @Override 340 public void getAccountCredentialsForCloning(IAccountAuthenticatorResponse response, 341 Account account) throws RemoteException { 342 checkBinderPermission(); 343 try { 344 final Bundle result = 345 AbstractAccountAuthenticator.this.getAccountCredentialsForCloning( 346 new AccountAuthenticatorResponse(response), account); 347 if (result != null) { 348 response.onResult(result); 349 } 350 } catch (Exception e) { 351 handleException(response, "getAccountCredentialsForCloning", account.toString(), e); 352 } 353 } 354 355 @Override 356 public void addAccountFromCredentials(IAccountAuthenticatorResponse response, 357 Account account, 358 Bundle accountCredentials) throws RemoteException { 359 checkBinderPermission(); 360 try { 361 final Bundle result = 362 AbstractAccountAuthenticator.this.addAccountFromCredentials( 363 new AccountAuthenticatorResponse(response), account, 364 accountCredentials); 365 if (result != null) { 366 response.onResult(result); 367 } 368 } catch (Exception e) { 369 handleException(response, "addAccountFromCredentials", account.toString(), e); 370 } 371 } 372 373 @Override 374 public void startAddAccountSession(IAccountAuthenticatorResponse response, 375 String accountType, String authTokenType, String[] features, Bundle options) 376 throws RemoteException { 377 if (Log.isLoggable(TAG, Log.VERBOSE)) { 378 Log.v(TAG, 379 "startAddAccountSession: accountType " + accountType 380 + ", authTokenType " + authTokenType 381 + ", features " + (features == null ? "[]" : Arrays.toString(features))); 382 } 383 checkBinderPermission(); 384 try { 385 final Bundle result = AbstractAccountAuthenticator.this.startAddAccountSession( 386 new AccountAuthenticatorResponse(response), accountType, authTokenType, 387 features, options); 388 if (Log.isLoggable(TAG, Log.VERBOSE)) { 389 if (result != null) { 390 result.keySet(); // force it to be unparcelled 391 } 392 Log.v(TAG, "startAddAccountSession: result " 393 + AccountManager.sanitizeResult(result)); 394 } 395 if (result != null) { 396 response.onResult(result); 397 } 398 } catch (Exception e) { 399 handleException(response, "startAddAccountSession", accountType, e); 400 } 401 } 402 403 @Override 404 public void startUpdateCredentialsSession( 405 IAccountAuthenticatorResponse response, 406 Account account, 407 String authTokenType, 408 Bundle loginOptions) throws RemoteException { 409 if (Log.isLoggable(TAG, Log.VERBOSE)) { 410 Log.v(TAG, "startUpdateCredentialsSession: " 411 + account 412 + ", authTokenType " 413 + authTokenType); 414 } 415 checkBinderPermission(); 416 try { 417 final Bundle result = AbstractAccountAuthenticator.this 418 .startUpdateCredentialsSession( 419 new AccountAuthenticatorResponse(response), 420 account, 421 authTokenType, 422 loginOptions); 423 if (Log.isLoggable(TAG, Log.VERBOSE)) { 424 // Result may be null. 425 if (result != null) { 426 result.keySet(); // force it to be unparcelled 427 } 428 Log.v(TAG, "startUpdateCredentialsSession: result " 429 + AccountManager.sanitizeResult(result)); 430 431 } 432 if (result != null) { 433 response.onResult(result); 434 } 435 } catch (Exception e) { 436 handleException(response, "startUpdateCredentialsSession", 437 account.toString() + "," + authTokenType, e); 438 439 } 440 } 441 442 @Override 443 public void finishSession( 444 IAccountAuthenticatorResponse response, 445 String accountType, 446 Bundle sessionBundle) throws RemoteException { 447 if (Log.isLoggable(TAG, Log.VERBOSE)) { 448 Log.v(TAG, "finishSession: accountType " + accountType); 449 } 450 checkBinderPermission(); 451 try { 452 final Bundle result = AbstractAccountAuthenticator.this.finishSession( 453 new AccountAuthenticatorResponse(response), accountType, sessionBundle); 454 if (result != null) { 455 result.keySet(); // force it to be unparcelled 456 } 457 if (Log.isLoggable(TAG, Log.VERBOSE)) { 458 Log.v(TAG, "finishSession: result " + AccountManager.sanitizeResult(result)); 459 } 460 if (result != null) { 461 response.onResult(result); 462 } 463 } catch (Exception e) { 464 handleException(response, "finishSession", accountType, e); 465 466 } 467 } 468 469 @Override 470 public void isCredentialsUpdateSuggested( 471 IAccountAuthenticatorResponse response, 472 Account account, 473 String statusToken) throws RemoteException { 474 checkBinderPermission(); 475 try { 476 final Bundle result = AbstractAccountAuthenticator.this 477 .isCredentialsUpdateSuggested( 478 new AccountAuthenticatorResponse(response), account, statusToken); 479 if (result != null) { 480 response.onResult(result); 481 } 482 } catch (Exception e) { 483 handleException(response, "isCredentialsUpdateSuggested", account.toString(), e); 484 } 485 } 486 } 487 488 private void handleException(IAccountAuthenticatorResponse response, String method, 489 String data, Exception e) throws RemoteException { 490 if (e instanceof NetworkErrorException) { 491 if (Log.isLoggable(TAG, Log.VERBOSE)) { 492 Log.v(TAG, method + "(" + data + ")", e); 493 } 494 response.onError(AccountManager.ERROR_CODE_NETWORK_ERROR, e.getMessage()); 495 } else if (e instanceof UnsupportedOperationException) { 496 if (Log.isLoggable(TAG, Log.VERBOSE)) { 497 Log.v(TAG, method + "(" + data + ")", e); 498 } 499 response.onError(AccountManager.ERROR_CODE_UNSUPPORTED_OPERATION, 500 method + " not supported"); 501 } else if (e instanceof IllegalArgumentException) { 502 if (Log.isLoggable(TAG, Log.VERBOSE)) { 503 Log.v(TAG, method + "(" + data + ")", e); 504 } 505 response.onError(AccountManager.ERROR_CODE_BAD_ARGUMENTS, 506 method + " not supported"); 507 } else { 508 Log.w(TAG, method + "(" + data + ")", e); 509 response.onError(AccountManager.ERROR_CODE_REMOTE_EXCEPTION, 510 method + " failed"); 511 } 512 } 513 514 private void checkBinderPermission() { 515 final int uid = Binder.getCallingUid(); 516 final String perm = Manifest.permission.ACCOUNT_MANAGER; 517 if (mContext.checkCallingOrSelfPermission(perm) != PackageManager.PERMISSION_GRANTED) { 518 throw new SecurityException("caller uid " + uid + " lacks " + perm); 519 } 520 } 521 522 private Transport mTransport = new Transport(); 523 524 /** 525 * @return the IBinder for the AccountAuthenticator 526 */ 527 public final IBinder getIBinder() { 528 return mTransport.asBinder(); 529 } 530 531 /** 532 * Returns a Bundle that contains the Intent of the activity that can be used to edit the 533 * properties. In order to indicate success the activity should call response.setResult() 534 * with a non-null Bundle. 535 * @param response used to set the result for the request. If the Constants.INTENT_KEY 536 * is set in the bundle then this response field is to be used for sending future 537 * results if and when the Intent is started. 538 * @param accountType the AccountType whose properties are to be edited. 539 * @return a Bundle containing the result or the Intent to start to continue the request. 540 * If this is null then the request is considered to still be active and the result should 541 * sent later using response. 542 */ 543 public abstract Bundle editProperties(AccountAuthenticatorResponse response, 544 String accountType); 545 546 /** 547 * Adds an account of the specified accountType. 548 * @param response to send the result back to the AccountManager, will never be null 549 * @param accountType the type of account to add, will never be null 550 * @param authTokenType the type of auth token to retrieve after adding the account, may be null 551 * @param requiredFeatures a String array of authenticator-specific features that the added 552 * account must support, may be null 553 * @param options a Bundle of authenticator-specific options. It always contains 554 * {@link AccountManager#KEY_CALLER_PID} and {@link AccountManager#KEY_CALLER_UID} 555 * fields which will let authenticator know the identity of the caller. 556 * @return a Bundle result or null if the result is to be returned via the response. The result 557 * will contain either: 558 * <ul> 559 * <li> {@link AccountManager#KEY_INTENT}, or 560 * <li> {@link AccountManager#KEY_ACCOUNT_NAME} and {@link AccountManager#KEY_ACCOUNT_TYPE} of 561 * the account that was added, or 562 * <li> {@link AccountManager#KEY_ERROR_CODE} and {@link AccountManager#KEY_ERROR_MESSAGE} to 563 * indicate an error 564 * </ul> 565 * @throws NetworkErrorException if the authenticator could not honor the request due to a 566 * network error 567 */ 568 public abstract Bundle addAccount(AccountAuthenticatorResponse response, String accountType, 569 String authTokenType, String[] requiredFeatures, Bundle options) 570 throws NetworkErrorException; 571 572 /** 573 * Checks that the user knows the credentials of an account. 574 * @param response to send the result back to the AccountManager, will never be null 575 * @param account the account whose credentials are to be checked, will never be null 576 * @param options a Bundle of authenticator-specific options, may be null 577 * @return a Bundle result or null if the result is to be returned via the response. The result 578 * will contain either: 579 * <ul> 580 * <li> {@link AccountManager#KEY_INTENT}, or 581 * <li> {@link AccountManager#KEY_BOOLEAN_RESULT}, true if the check succeeded, false otherwise 582 * <li> {@link AccountManager#KEY_ERROR_CODE} and {@link AccountManager#KEY_ERROR_MESSAGE} to 583 * indicate an error 584 * </ul> 585 * @throws NetworkErrorException if the authenticator could not honor the request due to a 586 * network error 587 */ 588 public abstract Bundle confirmCredentials(AccountAuthenticatorResponse response, 589 Account account, Bundle options) 590 throws NetworkErrorException; 591 592 /** 593 * Gets an authtoken for an account. 594 * 595 * If not {@code null}, the resultant {@link Bundle} will contain different sets of keys 596 * depending on whether a token was successfully issued and, if not, whether one 597 * could be issued via some {@link android.app.Activity}. 598 * <p> 599 * If a token cannot be provided without some additional activity, the Bundle should contain 600 * {@link AccountManager#KEY_INTENT} with an associated {@link Intent}. On the other hand, if 601 * there is no such activity, then a Bundle containing 602 * {@link AccountManager#KEY_ERROR_CODE} and {@link AccountManager#KEY_ERROR_MESSAGE} should be 603 * returned. 604 * <p> 605 * If a token can be successfully issued, the implementation should return the 606 * {@link AccountManager#KEY_ACCOUNT_NAME} and {@link AccountManager#KEY_ACCOUNT_TYPE} of the 607 * account associated with the token as well as the {@link AccountManager#KEY_AUTHTOKEN}. In 608 * addition {@link AbstractAccountAuthenticator} implementations that declare themselves 609 * {@code android:customTokens=true} may also provide a non-negative {@link 610 * #KEY_CUSTOM_TOKEN_EXPIRY} long value containing the expiration timestamp of the expiration 611 * time (in millis since the unix epoch), tokens will be cached in memory based on 612 * application's packageName/signature for however long that was specified. 613 * <p> 614 * Implementers should assume that tokens will be cached on the basis of account and 615 * authTokenType. The system may ignore the contents of the supplied options Bundle when 616 * determining to re-use a cached token. Furthermore, implementers should assume a supplied 617 * expiration time will be treated as non-binding advice. 618 * <p> 619 * Finally, note that for {@code android:customTokens=false} authenticators, tokens are cached 620 * indefinitely until some client calls {@link 621 * AccountManager#invalidateAuthToken(String,String)}. 622 * 623 * @param response to send the result back to the AccountManager, will never be null 624 * @param account the account whose credentials are to be retrieved, will never be null 625 * @param authTokenType the type of auth token to retrieve, will never be null 626 * @param options a Bundle of authenticator-specific options. It always contains 627 * {@link AccountManager#KEY_CALLER_PID} and {@link AccountManager#KEY_CALLER_UID} 628 * fields which will let authenticator know the identity of the caller. 629 * @return a Bundle result or null if the result is to be returned via the response. 630 * @throws NetworkErrorException if the authenticator could not honor the request due to a 631 * network error 632 */ 633 public abstract Bundle getAuthToken(AccountAuthenticatorResponse response, 634 Account account, String authTokenType, Bundle options) 635 throws NetworkErrorException; 636 637 /** 638 * Ask the authenticator for a localized label for the given authTokenType. 639 * @param authTokenType the authTokenType whose label is to be returned, will never be null 640 * @return the localized label of the auth token type, may be null if the type isn't known 641 */ 642 public abstract String getAuthTokenLabel(String authTokenType); 643 644 /** 645 * Update the locally stored credentials for an account. 646 * @param response to send the result back to the AccountManager, will never be null 647 * @param account the account whose credentials are to be updated, will never be null 648 * @param authTokenType the type of auth token to retrieve after updating the credentials, 649 * may be null 650 * @param options a Bundle of authenticator-specific options, may be null 651 * @return a Bundle result or null if the result is to be returned via the response. The result 652 * will contain either: 653 * <ul> 654 * <li> {@link AccountManager#KEY_INTENT}, or 655 * <li> {@link AccountManager#KEY_ACCOUNT_NAME} and {@link AccountManager#KEY_ACCOUNT_TYPE} of 656 * the account whose credentials were updated, or 657 * <li> {@link AccountManager#KEY_ERROR_CODE} and {@link AccountManager#KEY_ERROR_MESSAGE} to 658 * indicate an error 659 * </ul> 660 * @throws NetworkErrorException if the authenticator could not honor the request due to a 661 * network error 662 */ 663 public abstract Bundle updateCredentials(AccountAuthenticatorResponse response, 664 Account account, String authTokenType, Bundle options) throws NetworkErrorException; 665 666 /** 667 * Checks if the account supports all the specified authenticator specific features. 668 * @param response to send the result back to the AccountManager, will never be null 669 * @param account the account to check, will never be null 670 * @param features an array of features to check, will never be null 671 * @return a Bundle result or null if the result is to be returned via the response. The result 672 * will contain either: 673 * <ul> 674 * <li> {@link AccountManager#KEY_INTENT}, or 675 * <li> {@link AccountManager#KEY_BOOLEAN_RESULT}, true if the account has all the features, 676 * false otherwise 677 * <li> {@link AccountManager#KEY_ERROR_CODE} and {@link AccountManager#KEY_ERROR_MESSAGE} to 678 * indicate an error 679 * </ul> 680 * @throws NetworkErrorException if the authenticator could not honor the request due to a 681 * network error 682 */ 683 public abstract Bundle hasFeatures(AccountAuthenticatorResponse response, 684 Account account, String[] features) throws NetworkErrorException; 685 686 /** 687 * Checks if the removal of this account is allowed. 688 * @param response to send the result back to the AccountManager, will never be null 689 * @param account the account to check, will never be null 690 * @return a Bundle result or null if the result is to be returned via the response. The result 691 * will contain either: 692 * <ul> 693 * <li> {@link AccountManager#KEY_INTENT}, or 694 * <li> {@link AccountManager#KEY_BOOLEAN_RESULT}, true if the removal of the account is 695 * allowed, false otherwise 696 * <li> {@link AccountManager#KEY_ERROR_CODE} and {@link AccountManager#KEY_ERROR_MESSAGE} to 697 * indicate an error 698 * </ul> 699 * @throws NetworkErrorException if the authenticator could not honor the request due to a 700 * network error 701 */ 702 public Bundle getAccountRemovalAllowed(AccountAuthenticatorResponse response, 703 Account account) throws NetworkErrorException { 704 final Bundle result = new Bundle(); 705 result.putBoolean(AccountManager.KEY_BOOLEAN_RESULT, true); 706 return result; 707 } 708 709 /** 710 * Returns a Bundle that contains whatever is required to clone the account on a different 711 * user. The Bundle is passed to the authenticator instance in the target user via 712 * {@link #addAccountFromCredentials(AccountAuthenticatorResponse, Account, Bundle)}. 713 * The default implementation returns null, indicating that cloning is not supported. 714 * @param response to send the result back to the AccountManager, will never be null 715 * @param account the account to clone, will never be null 716 * @return a Bundle result or null if the result is to be returned via the response. 717 * @throws NetworkErrorException 718 * @see #addAccountFromCredentials(AccountAuthenticatorResponse, Account, Bundle) 719 */ 720 public Bundle getAccountCredentialsForCloning(final AccountAuthenticatorResponse response, 721 final Account account) throws NetworkErrorException { 722 new Thread(new Runnable() { 723 @Override 724 public void run() { 725 Bundle result = new Bundle(); 726 result.putBoolean(AccountManager.KEY_BOOLEAN_RESULT, false); 727 response.onResult(result); 728 } 729 }).start(); 730 return null; 731 } 732 733 /** 734 * Creates an account based on credentials provided by the authenticator instance of another 735 * user on the device, who has chosen to share the account with this user. 736 * @param response to send the result back to the AccountManager, will never be null 737 * @param account the account to clone, will never be null 738 * @param accountCredentials the Bundle containing the required credentials to create the 739 * account. Contents of the Bundle are only meaningful to the authenticator. This Bundle is 740 * provided by {@link #getAccountCredentialsForCloning(AccountAuthenticatorResponse, Account)}. 741 * @return a Bundle result or null if the result is to be returned via the response. 742 * @throws NetworkErrorException 743 * @see #getAccountCredentialsForCloning(AccountAuthenticatorResponse, Account) 744 */ 745 public Bundle addAccountFromCredentials(final AccountAuthenticatorResponse response, 746 Account account, 747 Bundle accountCredentials) throws NetworkErrorException { 748 new Thread(new Runnable() { 749 @Override 750 public void run() { 751 Bundle result = new Bundle(); 752 result.putBoolean(AccountManager.KEY_BOOLEAN_RESULT, false); 753 response.onResult(result); 754 } 755 }).start(); 756 return null; 757 } 758 759 /** 760 * Starts the add account session to authenticate user to an account of the 761 * specified accountType. No file I/O should be performed in this call. 762 * Account should be added to device only when {@link #finishSession} is 763 * called after this. 764 * <p> 765 * Note: when overriding this method, {@link #finishSession} should be 766 * overridden too. 767 * </p> 768 * 769 * @param response to send the result back to the AccountManager, will never 770 * be null 771 * @param accountType the type of account to authenticate with, will never 772 * be null 773 * @param authTokenType the type of auth token to retrieve after 774 * authenticating with the account, may be null 775 * @param requiredFeatures a String array of authenticator-specific features 776 * that the account authenticated with must support, may be null 777 * @param options a Bundle of authenticator-specific options, may be null 778 * @return a Bundle result or null if the result is to be returned via the 779 * response. The result will contain either: 780 * <ul> 781 * <li>{@link AccountManager#KEY_INTENT}, or 782 * <li>{@link AccountManager#KEY_ACCOUNT_SESSION_BUNDLE} for adding 783 * the account to device later, and if account is authenticated, 784 * optional {@link AccountManager#KEY_PASSWORD} and 785 * {@link AccountManager#KEY_ACCOUNT_STATUS_TOKEN} for checking the 786 * status of the account, or 787 * <li>{@link AccountManager#KEY_ERROR_CODE} and 788 * {@link AccountManager#KEY_ERROR_MESSAGE} to indicate an error 789 * </ul> 790 * @throws NetworkErrorException if the authenticator could not honor the 791 * request due to a network error 792 * @see #finishSession(AccountAuthenticatorResponse, String, Bundle) 793 */ 794 public Bundle startAddAccountSession( 795 final AccountAuthenticatorResponse response, 796 final String accountType, 797 final String authTokenType, 798 final String[] requiredFeatures, 799 final Bundle options) 800 throws NetworkErrorException { 801 new Thread(new Runnable() { 802 @Override 803 public void run() { 804 Bundle sessionBundle = new Bundle(); 805 sessionBundle.putString(KEY_AUTH_TOKEN_TYPE, authTokenType); 806 sessionBundle.putStringArray(KEY_REQUIRED_FEATURES, requiredFeatures); 807 sessionBundle.putBundle(KEY_OPTIONS, options); 808 Bundle result = new Bundle(); 809 result.putBundle(AccountManager.KEY_ACCOUNT_SESSION_BUNDLE, sessionBundle); 810 response.onResult(result); 811 } 812 813 }).start(); 814 return null; 815 } 816 817 /** 818 * Asks user to re-authenticate for an account but defers updating the 819 * locally stored credentials. No file I/O should be performed in this call. 820 * Local credentials should be updated only when {@link #finishSession} is 821 * called after this. 822 * <p> 823 * Note: when overriding this method, {@link #finishSession} should be 824 * overridden too. 825 * </p> 826 * 827 * @param response to send the result back to the AccountManager, will never 828 * be null 829 * @param account the account whose credentials are to be updated, will 830 * never be null 831 * @param authTokenType the type of auth token to retrieve after updating 832 * the credentials, may be null 833 * @param options a Bundle of authenticator-specific options, may be null 834 * @return a Bundle result or null if the result is to be returned via the 835 * response. The result will contain either: 836 * <ul> 837 * <li>{@link AccountManager#KEY_INTENT}, or 838 * <li>{@link AccountManager#KEY_ACCOUNT_SESSION_BUNDLE} for 839 * updating the locally stored credentials later, and if account is 840 * re-authenticated, optional {@link AccountManager#KEY_PASSWORD} 841 * and {@link AccountManager#KEY_ACCOUNT_STATUS_TOKEN} for checking 842 * the status of the account later, or 843 * <li>{@link AccountManager#KEY_ERROR_CODE} and 844 * {@link AccountManager#KEY_ERROR_MESSAGE} to indicate an error 845 * </ul> 846 * @throws NetworkErrorException if the authenticator could not honor the 847 * request due to a network error 848 * @see #finishSession(AccountAuthenticatorResponse, String, Bundle) 849 */ 850 public Bundle startUpdateCredentialsSession( 851 final AccountAuthenticatorResponse response, 852 final Account account, 853 final String authTokenType, 854 final Bundle options) throws NetworkErrorException { 855 new Thread(new Runnable() { 856 @Override 857 public void run() { 858 Bundle sessionBundle = new Bundle(); 859 sessionBundle.putString(KEY_AUTH_TOKEN_TYPE, authTokenType); 860 sessionBundle.putParcelable(KEY_ACCOUNT, account); 861 sessionBundle.putBundle(KEY_OPTIONS, options); 862 Bundle result = new Bundle(); 863 result.putBundle(AccountManager.KEY_ACCOUNT_SESSION_BUNDLE, sessionBundle); 864 response.onResult(result); 865 } 866 867 }).start(); 868 return null; 869 } 870 871 /** 872 * Finishes the session started by #startAddAccountSession or 873 * #startUpdateCredentials by installing the account to device with 874 * AccountManager, or updating the local credentials. File I/O may be 875 * performed in this call. 876 * <p> 877 * Note: when overriding this method, {@link #startAddAccountSession} and 878 * {@link #startUpdateCredentialsSession} should be overridden too. 879 * </p> 880 * 881 * @param response to send the result back to the AccountManager, will never 882 * be null 883 * @param accountType the type of account to authenticate with, will never 884 * be null 885 * @param sessionBundle a bundle of session data created by 886 * {@link #startAddAccountSession} used for adding account to 887 * device, or by {@link #startUpdateCredentialsSession} used for 888 * updating local credentials. 889 * @return a Bundle result or null if the result is to be returned via the 890 * response. The result will contain either: 891 * <ul> 892 * <li>{@link AccountManager#KEY_INTENT}, or 893 * <li>{@link AccountManager#KEY_ACCOUNT_NAME} and 894 * {@link AccountManager#KEY_ACCOUNT_TYPE} of the account that was 895 * added or local credentials were updated, and optional 896 * {@link AccountManager#KEY_ACCOUNT_STATUS_TOKEN} for checking 897 * the status of the account later, or 898 * <li>{@link AccountManager#KEY_ERROR_CODE} and 899 * {@link AccountManager#KEY_ERROR_MESSAGE} to indicate an error 900 * </ul> 901 * @throws NetworkErrorException if the authenticator could not honor the request due to a 902 * network error 903 * @see #startAddAccountSession and #startUpdateCredentialsSession 904 */ 905 public Bundle finishSession( 906 final AccountAuthenticatorResponse response, 907 final String accountType, 908 final Bundle sessionBundle) throws NetworkErrorException { 909 if (TextUtils.isEmpty(accountType)) { 910 Log.e(TAG, "Account type cannot be empty."); 911 Bundle result = new Bundle(); 912 result.putInt(AccountManager.KEY_ERROR_CODE, AccountManager.ERROR_CODE_BAD_ARGUMENTS); 913 result.putString(AccountManager.KEY_ERROR_MESSAGE, 914 "accountType cannot be empty."); 915 return result; 916 } 917 918 if (sessionBundle == null) { 919 Log.e(TAG, "Session bundle cannot be null."); 920 Bundle result = new Bundle(); 921 result.putInt(AccountManager.KEY_ERROR_CODE, AccountManager.ERROR_CODE_BAD_ARGUMENTS); 922 result.putString(AccountManager.KEY_ERROR_MESSAGE, 923 "sessionBundle cannot be null."); 924 return result; 925 } 926 927 if (!sessionBundle.containsKey(KEY_AUTH_TOKEN_TYPE)) { 928 // We cannot handle Session bundle not created by default startAddAccountSession(...) 929 // nor startUpdateCredentialsSession(...) implementation. Return error. 930 Bundle result = new Bundle(); 931 result.putInt(AccountManager.KEY_ERROR_CODE, 932 AccountManager.ERROR_CODE_UNSUPPORTED_OPERATION); 933 result.putString(AccountManager.KEY_ERROR_MESSAGE, 934 "Authenticator must override finishSession if startAddAccountSession" 935 + " or startUpdateCredentialsSession is overridden."); 936 response.onResult(result); 937 return result; 938 } 939 String authTokenType = sessionBundle.getString(KEY_AUTH_TOKEN_TYPE); 940 Bundle options = sessionBundle.getBundle(KEY_OPTIONS); 941 String[] requiredFeatures = sessionBundle.getStringArray(KEY_REQUIRED_FEATURES); 942 Account account = sessionBundle.getParcelable(KEY_ACCOUNT); 943 boolean containsKeyAccount = sessionBundle.containsKey(KEY_ACCOUNT); 944 945 // Actual options passed to add account or update credentials flow. 946 Bundle sessionOptions = new Bundle(sessionBundle); 947 // Remove redundant extras in session bundle before passing it to addAccount(...) or 948 // updateCredentials(...). 949 sessionOptions.remove(KEY_AUTH_TOKEN_TYPE); 950 sessionOptions.remove(KEY_REQUIRED_FEATURES); 951 sessionOptions.remove(KEY_OPTIONS); 952 sessionOptions.remove(KEY_ACCOUNT); 953 954 if (options != null) { 955 // options may contains old system info such as 956 // AccountManager.KEY_ANDROID_PACKAGE_NAME required by the add account flow or update 957 // credentials flow, we should replace with the new values of the current call added 958 // to sessionBundle by AccountManager or AccountManagerService. 959 options.putAll(sessionOptions); 960 sessionOptions = options; 961 } 962 963 // Session bundle created by startUpdateCredentialsSession default implementation should 964 // contain KEY_ACCOUNT. 965 if (containsKeyAccount) { 966 return updateCredentials(response, account, authTokenType, options); 967 } 968 // Otherwise, session bundle was created by startAddAccountSession default implementation. 969 return addAccount(response, accountType, authTokenType, requiredFeatures, sessionOptions); 970 } 971 972 /** 973 * Checks if update of the account credentials is suggested. 974 * 975 * @param response to send the result back to the AccountManager, will never be null. 976 * @param account the account to check, will never be null 977 * @param statusToken a String of token to check if update of credentials is suggested. 978 * @return a Bundle result or null if the result is to be returned via the response. The result 979 * will contain either: 980 * <ul> 981 * <li>{@link AccountManager#KEY_BOOLEAN_RESULT}, true if update of account's 982 * credentials is suggested, false otherwise 983 * <li>{@link AccountManager#KEY_ERROR_CODE} and 984 * {@link AccountManager#KEY_ERROR_MESSAGE} to indicate an error 985 * </ul> 986 * @throws NetworkErrorException if the authenticator could not honor the request due to a 987 * network error 988 */ 989 public Bundle isCredentialsUpdateSuggested( 990 final AccountAuthenticatorResponse response, 991 Account account, 992 String statusToken) throws NetworkErrorException { 993 Bundle result = new Bundle(); 994 result.putBoolean(AccountManager.KEY_BOOLEAN_RESULT, false); 995 return result; 996 } 997} 998