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