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 AbstractAccountAuthenticator(Context context)151 public AbstractAccountAuthenticator(Context context) { 152 mContext = context; 153 } 154 155 private class Transport extends IAccountAuthenticator.Stub { 156 @Override addAccount(IAccountAuthenticatorResponse response, String accountType, String authTokenType, String[] features, Bundle options)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 confirmCredentials(IAccountAuthenticatorResponse response, Account account, Bundle options)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 getAuthTokenLabel(IAccountAuthenticatorResponse response, String authTokenType)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 getAuthToken(IAccountAuthenticatorResponse response, Account account, String authTokenType, Bundle loginOptions)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 updateCredentials(IAccountAuthenticatorResponse response, Account account, String authTokenType, Bundle loginOptions)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 editProperties(IAccountAuthenticatorResponse response, String accountType)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 hasFeatures(IAccountAuthenticatorResponse response, Account account, String[] features)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 getAccountRemovalAllowed(IAccountAuthenticatorResponse response, Account account)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 getAccountCredentialsForCloning(IAccountAuthenticatorResponse response, Account account)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 addAccountFromCredentials(IAccountAuthenticatorResponse response, Account account, Bundle accountCredentials)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 startAddAccountSession(IAccountAuthenticatorResponse response, String accountType, String authTokenType, String[] features, Bundle options)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 startUpdateCredentialsSession( IAccountAuthenticatorResponse response, Account account, String authTokenType, Bundle loginOptions)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 finishSession( IAccountAuthenticatorResponse response, String accountType, Bundle sessionBundle)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 isCredentialsUpdateSuggested( IAccountAuthenticatorResponse response, Account account, String statusToken)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 handleException(IAccountAuthenticatorResponse response, String method, String data, Exception e)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 checkBinderPermission()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 */ getIBinder()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 */ editProperties(AccountAuthenticatorResponse response, String accountType)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 */ addAccount(AccountAuthenticatorResponse response, String accountType, String authTokenType, String[] requiredFeatures, Bundle options)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 */ confirmCredentials(AccountAuthenticatorResponse response, Account account, Bundle options)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 */ getAuthToken(AccountAuthenticatorResponse response, Account account, String authTokenType, Bundle options)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 */ getAuthTokenLabel(String authTokenType)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 */ updateCredentials(AccountAuthenticatorResponse response, Account account, String authTokenType, Bundle options)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 */ hasFeatures(AccountAuthenticatorResponse response, Account account, String[] features)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 */ getAccountRemovalAllowed(AccountAuthenticatorResponse response, Account account)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 */ getAccountCredentialsForCloning(final AccountAuthenticatorResponse response, final Account account)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 */ addAccountFromCredentials(final AccountAuthenticatorResponse response, Account account, Bundle accountCredentials)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 */ startAddAccountSession( final AccountAuthenticatorResponse response, final String accountType, final String authTokenType, final String[] requiredFeatures, final Bundle options)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 */ startUpdateCredentialsSession( final AccountAuthenticatorResponse response, final Account account, final String authTokenType, final Bundle options)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 */ finishSession( final AccountAuthenticatorResponse response, final String accountType, final Bundle sessionBundle)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 */ isCredentialsUpdateSuggested( final AccountAuthenticatorResponse response, Account account, String statusToken)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