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