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