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