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