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 } 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, may be null 551 * @return a Bundle result or null if the result is to be returned via the response. The result 552 * will contain either: 553 * <ul> 554 * <li> {@link AccountManager#KEY_INTENT}, or 555 * <li> {@link AccountManager#KEY_ACCOUNT_NAME} and {@link AccountManager#KEY_ACCOUNT_TYPE} of 556 * the account that was added, or 557 * <li> {@link AccountManager#KEY_ERROR_CODE} and {@link AccountManager#KEY_ERROR_MESSAGE} to 558 * indicate an error 559 * </ul> 560 * @throws NetworkErrorException if the authenticator could not honor the request due to a 561 * network error 562 */ 563 public abstract Bundle addAccount(AccountAuthenticatorResponse response, String accountType, 564 String authTokenType, String[] requiredFeatures, Bundle options) 565 throws NetworkErrorException; 566 567 /** 568 * Checks that the user knows the credentials of an account. 569 * @param response to send the result back to the AccountManager, will never be null 570 * @param account the account whose credentials are to be checked, will never be null 571 * @param options a Bundle of authenticator-specific options, may be null 572 * @return a Bundle result or null if the result is to be returned via the response. The result 573 * will contain either: 574 * <ul> 575 * <li> {@link AccountManager#KEY_INTENT}, or 576 * <li> {@link AccountManager#KEY_BOOLEAN_RESULT}, true if the check succeeded, false otherwise 577 * <li> {@link AccountManager#KEY_ERROR_CODE} and {@link AccountManager#KEY_ERROR_MESSAGE} to 578 * indicate an error 579 * </ul> 580 * @throws NetworkErrorException if the authenticator could not honor the request due to a 581 * network error 582 */ 583 public abstract Bundle confirmCredentials(AccountAuthenticatorResponse response, 584 Account account, Bundle options) 585 throws NetworkErrorException; 586 587 /** 588 * Gets an authtoken for an account. 589 * 590 * If not {@code null}, the resultant {@link Bundle} will contain different sets of keys 591 * depending on whether a token was successfully issued and, if not, whether one 592 * could be issued via some {@link android.app.Activity}. 593 * <p> 594 * If a token cannot be provided without some additional activity, the Bundle should contain 595 * {@link AccountManager#KEY_INTENT} with an associated {@link Intent}. On the other hand, if 596 * there is no such activity, then a Bundle containing 597 * {@link AccountManager#KEY_ERROR_CODE} and {@link AccountManager#KEY_ERROR_MESSAGE} should be 598 * returned. 599 * <p> 600 * If a token can be successfully issued, the implementation should return the 601 * {@link AccountManager#KEY_ACCOUNT_NAME} and {@link AccountManager#KEY_ACCOUNT_TYPE} of the 602 * account associated with the token as well as the {@link AccountManager#KEY_AUTHTOKEN}. In 603 * addition {@link AbstractAccountAuthenticator} implementations that declare themselves 604 * {@code android:customTokens=true} may also provide a non-negative {@link 605 * #KEY_CUSTOM_TOKEN_EXPIRY} long value containing the expiration timestamp of the expiration 606 * time (in millis since the unix epoch). 607 * <p> 608 * Implementers should assume that tokens will be cached on the basis of account and 609 * authTokenType. The system may ignore the contents of the supplied options Bundle when 610 * determining to re-use a cached token. Furthermore, implementers should assume a supplied 611 * expiration time will be treated as non-binding advice. 612 * <p> 613 * Finally, note that for android:customTokens=false authenticators, tokens are cached 614 * indefinitely until some client calls {@link 615 * AccountManager#invalidateAuthToken(String,String)}. 616 * 617 * @param response to send the result back to the AccountManager, will never be null 618 * @param account the account whose credentials are to be retrieved, will never be null 619 * @param authTokenType the type of auth token to retrieve, will never be null 620 * @param options a Bundle of authenticator-specific options, may be null 621 * @return a Bundle result or null if the result is to be returned via the response. 622 * @throws NetworkErrorException if the authenticator could not honor the request due to a 623 * network error 624 */ 625 public abstract Bundle getAuthToken(AccountAuthenticatorResponse response, 626 Account account, String authTokenType, Bundle options) 627 throws NetworkErrorException; 628 629 /** 630 * Ask the authenticator for a localized label for the given authTokenType. 631 * @param authTokenType the authTokenType whose label is to be returned, will never be null 632 * @return the localized label of the auth token type, may be null if the type isn't known 633 */ 634 public abstract String getAuthTokenLabel(String authTokenType); 635 636 /** 637 * Update the locally stored credentials for an account. 638 * @param response to send the result back to the AccountManager, will never be null 639 * @param account the account whose credentials are to be updated, will never be null 640 * @param authTokenType the type of auth token to retrieve after updating the credentials, 641 * may be null 642 * @param options a Bundle of authenticator-specific options, may be null 643 * @return a Bundle result or null if the result is to be returned via the response. The result 644 * will contain either: 645 * <ul> 646 * <li> {@link AccountManager#KEY_INTENT}, or 647 * <li> {@link AccountManager#KEY_ACCOUNT_NAME} and {@link AccountManager#KEY_ACCOUNT_TYPE} of 648 * the account whose credentials were updated, or 649 * <li> {@link AccountManager#KEY_ERROR_CODE} and {@link AccountManager#KEY_ERROR_MESSAGE} to 650 * indicate an error 651 * </ul> 652 * @throws NetworkErrorException if the authenticator could not honor the request due to a 653 * network error 654 */ 655 public abstract Bundle updateCredentials(AccountAuthenticatorResponse response, 656 Account account, String authTokenType, Bundle options) throws NetworkErrorException; 657 658 /** 659 * Checks if the account supports all the specified authenticator specific features. 660 * @param response to send the result back to the AccountManager, will never be null 661 * @param account the account to check, will never be null 662 * @param features an array of features to check, will never be null 663 * @return a Bundle result or null if the result is to be returned via the response. The result 664 * will contain either: 665 * <ul> 666 * <li> {@link AccountManager#KEY_INTENT}, or 667 * <li> {@link AccountManager#KEY_BOOLEAN_RESULT}, true if the account has all the features, 668 * false otherwise 669 * <li> {@link AccountManager#KEY_ERROR_CODE} and {@link AccountManager#KEY_ERROR_MESSAGE} to 670 * indicate an error 671 * </ul> 672 * @throws NetworkErrorException if the authenticator could not honor the request due to a 673 * network error 674 */ 675 public abstract Bundle hasFeatures(AccountAuthenticatorResponse response, 676 Account account, String[] features) throws NetworkErrorException; 677 678 /** 679 * Checks if the removal of this account is allowed. 680 * @param response to send the result back to the AccountManager, will never be null 681 * @param account the account to check, will never be null 682 * @return a Bundle result or null if the result is to be returned via the response. The result 683 * will contain either: 684 * <ul> 685 * <li> {@link AccountManager#KEY_INTENT}, or 686 * <li> {@link AccountManager#KEY_BOOLEAN_RESULT}, true if the removal of the account is 687 * allowed, false otherwise 688 * <li> {@link AccountManager#KEY_ERROR_CODE} and {@link AccountManager#KEY_ERROR_MESSAGE} to 689 * indicate an error 690 * </ul> 691 * @throws NetworkErrorException if the authenticator could not honor the request due to a 692 * network error 693 */ 694 public Bundle getAccountRemovalAllowed(AccountAuthenticatorResponse response, 695 Account account) throws NetworkErrorException { 696 final Bundle result = new Bundle(); 697 result.putBoolean(AccountManager.KEY_BOOLEAN_RESULT, true); 698 return result; 699 } 700 701 /** 702 * Returns a Bundle that contains whatever is required to clone the account on a different 703 * user. The Bundle is passed to the authenticator instance in the target user via 704 * {@link #addAccountFromCredentials(AccountAuthenticatorResponse, Account, Bundle)}. 705 * The default implementation returns null, indicating that cloning is not supported. 706 * @param response to send the result back to the AccountManager, will never be null 707 * @param account the account to clone, will never be null 708 * @return a Bundle result or null if the result is to be returned via the response. 709 * @throws NetworkErrorException 710 * @see #addAccountFromCredentials(AccountAuthenticatorResponse, Account, Bundle) 711 */ 712 public Bundle getAccountCredentialsForCloning(final AccountAuthenticatorResponse response, 713 final Account account) throws NetworkErrorException { 714 new Thread(new Runnable() { 715 @Override 716 public void run() { 717 Bundle result = new Bundle(); 718 result.putBoolean(AccountManager.KEY_BOOLEAN_RESULT, false); 719 response.onResult(result); 720 } 721 }).start(); 722 return null; 723 } 724 725 /** 726 * Creates an account based on credentials provided by the authenticator instance of another 727 * user on the device, who has chosen to share the account with this user. 728 * @param response to send the result back to the AccountManager, will never be null 729 * @param account the account to clone, will never be null 730 * @param accountCredentials the Bundle containing the required credentials to create the 731 * account. Contents of the Bundle are only meaningful to the authenticator. This Bundle is 732 * provided by {@link #getAccountCredentialsForCloning(AccountAuthenticatorResponse, Account)}. 733 * @return a Bundle result or null if the result is to be returned via the response. 734 * @throws NetworkErrorException 735 * @see #getAccountCredentialsForCloning(AccountAuthenticatorResponse, Account) 736 */ 737 public Bundle addAccountFromCredentials(final AccountAuthenticatorResponse response, 738 Account account, 739 Bundle accountCredentials) throws NetworkErrorException { 740 new Thread(new Runnable() { 741 @Override 742 public void run() { 743 Bundle result = new Bundle(); 744 result.putBoolean(AccountManager.KEY_BOOLEAN_RESULT, false); 745 response.onResult(result); 746 } 747 }).start(); 748 return null; 749 } 750 751 /** 752 * Starts the add account session to authenticate user to an account of the 753 * specified accountType. No file I/O should be performed in this call. 754 * Account should be added to device only when {@link #finishSession} is 755 * called after this. 756 * <p> 757 * Note: when overriding this method, {@link #finishSession} should be 758 * overridden too. 759 * </p> 760 * 761 * @param response to send the result back to the AccountManager, will never 762 * be null 763 * @param accountType the type of account to authenticate with, will never 764 * be null 765 * @param authTokenType the type of auth token to retrieve after 766 * authenticating with the account, may be null 767 * @param requiredFeatures a String array of authenticator-specific features 768 * that the account authenticated with must support, may be null 769 * @param options a Bundle of authenticator-specific options, may be null 770 * @return a Bundle result or null if the result is to be returned via the 771 * response. The result will contain either: 772 * <ul> 773 * <li>{@link AccountManager#KEY_INTENT}, or 774 * <li>{@link AccountManager#KEY_ACCOUNT_SESSION_BUNDLE} for adding 775 * the account to device later, and if account is authenticated, 776 * optional {@link AccountManager#KEY_PASSWORD} and 777 * {@link AccountManager#KEY_ACCOUNT_STATUS_TOKEN} for checking the 778 * status of the account, or 779 * <li>{@link AccountManager#KEY_ERROR_CODE} and 780 * {@link AccountManager#KEY_ERROR_MESSAGE} to indicate an error 781 * </ul> 782 * @throws NetworkErrorException if the authenticator could not honor the 783 * request due to a network error 784 * @see #finishSession(AccountAuthenticatorResponse, String, Bundle) 785 */ 786 public Bundle startAddAccountSession( 787 final AccountAuthenticatorResponse response, 788 final String accountType, 789 final String authTokenType, 790 final String[] requiredFeatures, 791 final Bundle options) 792 throws NetworkErrorException { 793 new Thread(new Runnable() { 794 @Override 795 public void run() { 796 Bundle sessionBundle = new Bundle(); 797 sessionBundle.putString(KEY_AUTH_TOKEN_TYPE, authTokenType); 798 sessionBundle.putStringArray(KEY_REQUIRED_FEATURES, requiredFeatures); 799 sessionBundle.putBundle(KEY_OPTIONS, options); 800 Bundle result = new Bundle(); 801 result.putBundle(AccountManager.KEY_ACCOUNT_SESSION_BUNDLE, sessionBundle); 802 response.onResult(result); 803 } 804 805 }).start(); 806 return null; 807 } 808 809 /** 810 * Asks user to re-authenticate for an account but defers updating the 811 * locally stored credentials. No file I/O should be performed in this call. 812 * Local credentials should be updated only when {@link #finishSession} is 813 * called after this. 814 * <p> 815 * Note: when overriding this method, {@link #finishSession} should be 816 * overridden too. 817 * </p> 818 * 819 * @param response to send the result back to the AccountManager, will never 820 * be null 821 * @param account the account whose credentials are to be updated, will 822 * never be null 823 * @param authTokenType the type of auth token to retrieve after updating 824 * the credentials, may be null 825 * @param options a Bundle of authenticator-specific options, may be null 826 * @return a Bundle result or null if the result is to be returned via the 827 * response. The result will contain either: 828 * <ul> 829 * <li>{@link AccountManager#KEY_INTENT}, or 830 * <li>{@link AccountManager#KEY_ACCOUNT_SESSION_BUNDLE} for 831 * updating the locally stored credentials later, and if account is 832 * re-authenticated, optional {@link AccountManager#KEY_PASSWORD} 833 * and {@link AccountManager#KEY_ACCOUNT_STATUS_TOKEN} for checking 834 * the status of the account later, or 835 * <li>{@link AccountManager#KEY_ERROR_CODE} and 836 * {@link AccountManager#KEY_ERROR_MESSAGE} to indicate an error 837 * </ul> 838 * @throws NetworkErrorException if the authenticator could not honor the 839 * request due to a network error 840 * @see #finishSession(AccountAuthenticatorResponse, String, Bundle) 841 */ 842 public Bundle startUpdateCredentialsSession( 843 final AccountAuthenticatorResponse response, 844 final Account account, 845 final String authTokenType, 846 final Bundle options) throws NetworkErrorException { 847 new Thread(new Runnable() { 848 @Override 849 public void run() { 850 Bundle sessionBundle = new Bundle(); 851 sessionBundle.putString(KEY_AUTH_TOKEN_TYPE, authTokenType); 852 sessionBundle.putParcelable(KEY_ACCOUNT, account); 853 sessionBundle.putBundle(KEY_OPTIONS, options); 854 Bundle result = new Bundle(); 855 result.putBundle(AccountManager.KEY_ACCOUNT_SESSION_BUNDLE, sessionBundle); 856 response.onResult(result); 857 } 858 859 }).start(); 860 return null; 861 } 862 863 /** 864 * Finishes the session started by #startAddAccountSession or 865 * #startUpdateCredentials by installing the account to device with 866 * AccountManager, or updating the local credentials. File I/O may be 867 * performed in this call. 868 * <p> 869 * Note: when overriding this method, {@link #startAddAccountSession} and 870 * {@link #startUpdateCredentialsSession} should be overridden too. 871 * </p> 872 * 873 * @param response to send the result back to the AccountManager, will never 874 * be null 875 * @param accountType the type of account to authenticate with, will never 876 * be null 877 * @param sessionBundle a bundle of session data created by 878 * {@link #startAddAccountSession} used for adding account to 879 * device, or by {@link #startUpdateCredentialsSession} used for 880 * updating local credentials. 881 * @return a Bundle result or null if the result is to be returned via the 882 * response. The result will contain either: 883 * <ul> 884 * <li>{@link AccountManager#KEY_INTENT}, or 885 * <li>{@link AccountManager#KEY_ACCOUNT_NAME} and 886 * {@link AccountManager#KEY_ACCOUNT_TYPE} of the account that was 887 * added or local credentials were updated, and optional 888 * {@link AccountManager#KEY_ACCOUNT_STATUS_TOKEN} for checking 889 * the status of the account later, or 890 * <li>{@link AccountManager#KEY_ERROR_CODE} and 891 * {@link AccountManager#KEY_ERROR_MESSAGE} to indicate an error 892 * </ul> 893 * @throws NetworkErrorException if the authenticator could not honor the request due to a 894 * network error 895 * @see #startAddAccountSession and #startUpdateCredentialsSession 896 */ 897 public Bundle finishSession( 898 final AccountAuthenticatorResponse response, 899 final String accountType, 900 final Bundle sessionBundle) throws NetworkErrorException { 901 if (TextUtils.isEmpty(accountType)) { 902 Log.e(TAG, "Account type cannot be empty."); 903 Bundle result = new Bundle(); 904 result.putInt(AccountManager.KEY_ERROR_CODE, AccountManager.ERROR_CODE_BAD_ARGUMENTS); 905 result.putString(AccountManager.KEY_ERROR_MESSAGE, 906 "accountType cannot be empty."); 907 return result; 908 } 909 910 if (sessionBundle == null) { 911 Log.e(TAG, "Session bundle cannot be null."); 912 Bundle result = new Bundle(); 913 result.putInt(AccountManager.KEY_ERROR_CODE, AccountManager.ERROR_CODE_BAD_ARGUMENTS); 914 result.putString(AccountManager.KEY_ERROR_MESSAGE, 915 "sessionBundle cannot be null."); 916 return result; 917 } 918 919 if (!sessionBundle.containsKey(KEY_AUTH_TOKEN_TYPE)) { 920 // We cannot handle Session bundle not created by default startAddAccountSession(...) 921 // nor startUpdateCredentialsSession(...) implementation. Return error. 922 Bundle result = new Bundle(); 923 result.putInt(AccountManager.KEY_ERROR_CODE, 924 AccountManager.ERROR_CODE_UNSUPPORTED_OPERATION); 925 result.putString(AccountManager.KEY_ERROR_MESSAGE, 926 "Authenticator must override finishSession if startAddAccountSession" 927 + " or startUpdateCredentialsSession is overridden."); 928 response.onResult(result); 929 return result; 930 } 931 String authTokenType = sessionBundle.getString(KEY_AUTH_TOKEN_TYPE); 932 Bundle options = sessionBundle.getBundle(KEY_OPTIONS); 933 String[] requiredFeatures = sessionBundle.getStringArray(KEY_REQUIRED_FEATURES); 934 Account account = sessionBundle.getParcelable(KEY_ACCOUNT); 935 boolean containsKeyAccount = sessionBundle.containsKey(KEY_ACCOUNT); 936 937 // Actual options passed to add account or update credentials flow. 938 Bundle sessionOptions = new Bundle(sessionBundle); 939 // Remove redundant extras in session bundle before passing it to addAccount(...) or 940 // updateCredentials(...). 941 sessionOptions.remove(KEY_AUTH_TOKEN_TYPE); 942 sessionOptions.remove(KEY_REQUIRED_FEATURES); 943 sessionOptions.remove(KEY_OPTIONS); 944 sessionOptions.remove(KEY_ACCOUNT); 945 946 if (options != null) { 947 // options may contains old system info such as 948 // AccountManager.KEY_ANDROID_PACKAGE_NAME required by the add account flow or update 949 // credentials flow, we should replace with the new values of the current call added 950 // to sessionBundle by AccountManager or AccountManagerService. 951 options.putAll(sessionOptions); 952 sessionOptions = options; 953 } 954 955 // Session bundle created by startUpdateCredentialsSession default implementation should 956 // contain KEY_ACCOUNT. 957 if (containsKeyAccount) { 958 return updateCredentials(response, account, authTokenType, options); 959 } 960 // Otherwise, session bundle was created by startAddAccountSession default implementation. 961 return addAccount(response, accountType, authTokenType, requiredFeatures, sessionOptions); 962 } 963 964 /** 965 * Checks if update of the account credentials is suggested. 966 * 967 * @param response to send the result back to the AccountManager, will never be null. 968 * @param account the account to check, will never be null 969 * @param statusToken a String of token to check if update of credentials is suggested. 970 * @return a Bundle result or null if the result is to be returned via the response. The result 971 * will contain either: 972 * <ul> 973 * <li>{@link AccountManager#KEY_BOOLEAN_RESULT}, true if update of account's 974 * credentials is suggested, false otherwise 975 * <li>{@link AccountManager#KEY_ERROR_CODE} and 976 * {@link AccountManager#KEY_ERROR_MESSAGE} to indicate an error 977 * </ul> 978 * @throws NetworkErrorException if the authenticator could not honor the request due to a 979 * network error 980 */ 981 public Bundle isCredentialsUpdateSuggested( 982 final AccountAuthenticatorResponse response, 983 Account account, 984 String statusToken) throws NetworkErrorException { 985 Bundle result = new Bundle(); 986 result.putBoolean(AccountManager.KEY_BOOLEAN_RESULT, false); 987 return result; 988 } 989} 990