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