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 17package android.accounts; 18 19import android.os.Bundle; 20import android.os.RemoteException; 21import android.os.Binder; 22import android.os.IBinder; 23import android.content.pm.PackageManager; 24import android.content.Context; 25import android.content.Intent; 26import android.Manifest; 27import android.util.Log; 28 29import java.util.Arrays; 30 31/** 32 * Abstract base class for creating AccountAuthenticators. 33 * In order to be an authenticator one must extend this class, provider implementations for the 34 * abstract methods and write a service that returns the result of {@link #getIBinder()} 35 * in the service's {@link android.app.Service#onBind(android.content.Intent)} when invoked 36 * with an intent with action {@link AccountManager#ACTION_AUTHENTICATOR_INTENT}. This service 37 * must specify the following intent filter and metadata tags in its AndroidManifest.xml file 38 * <pre> 39 * <intent-filter> 40 * <action android:name="android.accounts.AccountAuthenticator" /> 41 * </intent-filter> 42 * <meta-data android:name="android.accounts.AccountAuthenticator" 43 * android:resource="@xml/authenticator" /> 44 * </pre> 45 * The <code>android:resource</code> attribute must point to a resource that looks like: 46 * <pre> 47 * <account-authenticator xmlns:android="http://schemas.android.com/apk/res/android" 48 * android:accountType="typeOfAuthenticator" 49 * android:icon="@drawable/icon" 50 * android:smallIcon="@drawable/miniIcon" 51 * android:label="@string/label" 52 * android:accountPreferences="@xml/account_preferences" 53 * /> 54 * </pre> 55 * Replace the icons and labels with your own resources. The <code>android:accountType</code> 56 * attribute must be a string that uniquely identifies your authenticator and will be the same 57 * string that user will use when making calls on the {@link AccountManager} and it also 58 * corresponds to {@link Account#type} for your accounts. One user of the android:icon is the 59 * "Account & Sync" settings page and one user of the android:smallIcon is the Contact Application's 60 * tab panels. 61 * <p> 62 * The preferences attribute points to a PreferenceScreen xml hierarchy that contains 63 * a list of PreferenceScreens that can be invoked to manage the authenticator. An example is: 64 * <pre> 65 * <PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"> 66 * <PreferenceCategory android:title="@string/title_fmt" /> 67 * <PreferenceScreen 68 * android:key="key1" 69 * android:title="@string/key1_action" 70 * android:summary="@string/key1_summary"> 71 * <intent 72 * android:action="key1.ACTION" 73 * android:targetPackage="key1.package" 74 * android:targetClass="key1.class" /> 75 * </PreferenceScreen> 76 * </PreferenceScreen> 77 * </pre> 78 * 79 * <p> 80 * The standard pattern for implementing any of the abstract methods is the following: 81 * <ul> 82 * <li> If the supplied arguments are enough for the authenticator to fully satisfy the request 83 * then it will do so and return a {@link Bundle} that contains the results. 84 * <li> If the authenticator needs information from the user to satisfy the request then it 85 * will create an {@link Intent} to an activity that will prompt the user for the information 86 * and then carry out the request. This intent must be returned in a Bundle as key 87 * {@link AccountManager#KEY_INTENT}. 88 * <p> 89 * The activity needs to return the final result when it is complete so the Intent should contain 90 * the {@link AccountAuthenticatorResponse} as {@link AccountManager#KEY_ACCOUNT_MANAGER_RESPONSE}. 91 * The activity must then call {@link AccountAuthenticatorResponse#onResult} or 92 * {@link AccountAuthenticatorResponse#onError} when it is complete. 93 * <li> If the authenticator cannot synchronously process the request and return a result then it 94 * may choose to return null and then use the AccountManagerResponse to send the result 95 * when it has completed the request. 96 * </ul> 97 * <p> 98 * The following descriptions of each of the abstract authenticator methods will not describe the 99 * possible asynchronous nature of the request handling and will instead just describe the input 100 * parameters and the expected result. 101 * <p> 102 * When writing an activity to satisfy these requests one must pass in the AccountManagerResponse 103 * and return the result via that response when the activity finishes (or whenever else the 104 * activity author deems it is the correct time to respond). 105 * The {@link AccountAuthenticatorActivity} handles this, so one may wish to extend that when 106 * writing activities to handle these requests. 107 */ 108public abstract class AbstractAccountAuthenticator { 109 private static final String TAG = "AccountAuthenticator"; 110 111 private final Context mContext; 112 113 public AbstractAccountAuthenticator(Context context) { 114 mContext = context; 115 } 116 117 private class Transport extends IAccountAuthenticator.Stub { 118 public void addAccount(IAccountAuthenticatorResponse response, String accountType, 119 String authTokenType, String[] features, Bundle options) 120 throws RemoteException { 121 if (Log.isLoggable(TAG, Log.VERBOSE)) { 122 Log.v(TAG, "addAccount: accountType " + accountType 123 + ", authTokenType " + authTokenType 124 + ", features " + (features == null ? "[]" : Arrays.toString(features))); 125 } 126 checkBinderPermission(); 127 try { 128 final Bundle result = AbstractAccountAuthenticator.this.addAccount( 129 new AccountAuthenticatorResponse(response), 130 accountType, authTokenType, features, options); 131 if (Log.isLoggable(TAG, Log.VERBOSE)) { 132 result.keySet(); // force it to be unparcelled 133 Log.v(TAG, "addAccount: result " + AccountManager.sanitizeResult(result)); 134 } 135 if (result != null) { 136 response.onResult(result); 137 } 138 } catch (Exception e) { 139 handleException(response, "addAccount", accountType, e); 140 } 141 } 142 143 public void confirmCredentials(IAccountAuthenticatorResponse response, 144 Account account, Bundle options) throws RemoteException { 145 if (Log.isLoggable(TAG, Log.VERBOSE)) { 146 Log.v(TAG, "confirmCredentials: " + account); 147 } 148 checkBinderPermission(); 149 try { 150 final Bundle result = AbstractAccountAuthenticator.this.confirmCredentials( 151 new AccountAuthenticatorResponse(response), account, options); 152 if (Log.isLoggable(TAG, Log.VERBOSE)) { 153 result.keySet(); // force it to be unparcelled 154 Log.v(TAG, "confirmCredentials: result " 155 + AccountManager.sanitizeResult(result)); 156 } 157 if (result != null) { 158 response.onResult(result); 159 } 160 } catch (Exception e) { 161 handleException(response, "confirmCredentials", account.toString(), e); 162 } 163 } 164 165 public void getAuthTokenLabel(IAccountAuthenticatorResponse response, 166 String authTokenType) 167 throws RemoteException { 168 if (Log.isLoggable(TAG, Log.VERBOSE)) { 169 Log.v(TAG, "getAuthTokenLabel: authTokenType " + authTokenType); 170 } 171 checkBinderPermission(); 172 try { 173 Bundle result = new Bundle(); 174 result.putString(AccountManager.KEY_AUTH_TOKEN_LABEL, 175 AbstractAccountAuthenticator.this.getAuthTokenLabel(authTokenType)); 176 if (Log.isLoggable(TAG, Log.VERBOSE)) { 177 result.keySet(); // force it to be unparcelled 178 Log.v(TAG, "getAuthTokenLabel: result " 179 + AccountManager.sanitizeResult(result)); 180 } 181 response.onResult(result); 182 } catch (Exception e) { 183 handleException(response, "getAuthTokenLabel", authTokenType, e); 184 } 185 } 186 187 public void getAuthToken(IAccountAuthenticatorResponse response, 188 Account account, String authTokenType, Bundle loginOptions) 189 throws RemoteException { 190 if (Log.isLoggable(TAG, Log.VERBOSE)) { 191 Log.v(TAG, "getAuthToken: " + account 192 + ", authTokenType " + authTokenType); 193 } 194 checkBinderPermission(); 195 try { 196 final Bundle result = AbstractAccountAuthenticator.this.getAuthToken( 197 new AccountAuthenticatorResponse(response), account, 198 authTokenType, loginOptions); 199 if (Log.isLoggable(TAG, Log.VERBOSE)) { 200 result.keySet(); // force it to be unparcelled 201 Log.v(TAG, "getAuthToken: result " + AccountManager.sanitizeResult(result)); 202 } 203 if (result != null) { 204 response.onResult(result); 205 } 206 } catch (Exception e) { 207 handleException(response, "getAuthToken", 208 account.toString() + "," + authTokenType, e); 209 } 210 } 211 212 public void updateCredentials(IAccountAuthenticatorResponse response, Account account, 213 String authTokenType, Bundle loginOptions) throws RemoteException { 214 if (Log.isLoggable(TAG, Log.VERBOSE)) { 215 Log.v(TAG, "updateCredentials: " + account 216 + ", authTokenType " + authTokenType); 217 } 218 checkBinderPermission(); 219 try { 220 final Bundle result = AbstractAccountAuthenticator.this.updateCredentials( 221 new AccountAuthenticatorResponse(response), account, 222 authTokenType, loginOptions); 223 if (Log.isLoggable(TAG, Log.VERBOSE)) { 224 result.keySet(); // force it to be unparcelled 225 Log.v(TAG, "updateCredentials: result " 226 + AccountManager.sanitizeResult(result)); 227 } 228 if (result != null) { 229 response.onResult(result); 230 } 231 } catch (Exception e) { 232 handleException(response, "updateCredentials", 233 account.toString() + "," + authTokenType, e); 234 } 235 } 236 237 public void editProperties(IAccountAuthenticatorResponse response, 238 String accountType) throws RemoteException { 239 checkBinderPermission(); 240 try { 241 final Bundle result = AbstractAccountAuthenticator.this.editProperties( 242 new AccountAuthenticatorResponse(response), accountType); 243 if (result != null) { 244 response.onResult(result); 245 } 246 } catch (Exception e) { 247 handleException(response, "editProperties", accountType, e); 248 } 249 } 250 251 public void hasFeatures(IAccountAuthenticatorResponse response, 252 Account account, String[] features) throws RemoteException { 253 checkBinderPermission(); 254 try { 255 final Bundle result = AbstractAccountAuthenticator.this.hasFeatures( 256 new AccountAuthenticatorResponse(response), account, features); 257 if (result != null) { 258 response.onResult(result); 259 } 260 } catch (Exception e) { 261 handleException(response, "hasFeatures", account.toString(), e); 262 } 263 } 264 265 public void getAccountRemovalAllowed(IAccountAuthenticatorResponse response, 266 Account account) throws RemoteException { 267 checkBinderPermission(); 268 try { 269 final Bundle result = AbstractAccountAuthenticator.this.getAccountRemovalAllowed( 270 new AccountAuthenticatorResponse(response), account); 271 if (result != null) { 272 response.onResult(result); 273 } 274 } catch (Exception e) { 275 handleException(response, "getAccountRemovalAllowed", account.toString(), e); 276 } 277 } 278 279 public void getAccountCredentialsForCloning(IAccountAuthenticatorResponse response, 280 Account account) throws RemoteException { 281 checkBinderPermission(); 282 try { 283 final Bundle result = 284 AbstractAccountAuthenticator.this.getAccountCredentialsForCloning( 285 new AccountAuthenticatorResponse(response), account); 286 if (result != null) { 287 response.onResult(result); 288 } 289 } catch (Exception e) { 290 handleException(response, "getAccountCredentialsForCloning", account.toString(), e); 291 } 292 } 293 294 public void addAccountFromCredentials(IAccountAuthenticatorResponse response, 295 Account account, 296 Bundle accountCredentials) throws RemoteException { 297 checkBinderPermission(); 298 try { 299 final Bundle result = 300 AbstractAccountAuthenticator.this.addAccountFromCredentials( 301 new AccountAuthenticatorResponse(response), account, 302 accountCredentials); 303 if (result != null) { 304 response.onResult(result); 305 } 306 } catch (Exception e) { 307 handleException(response, "addAccountFromCredentials", account.toString(), e); 308 } 309 } 310 } 311 312 private void handleException(IAccountAuthenticatorResponse response, String method, 313 String data, Exception e) throws RemoteException { 314 if (e instanceof NetworkErrorException) { 315 if (Log.isLoggable(TAG, Log.VERBOSE)) { 316 Log.v(TAG, method + "(" + data + ")", e); 317 } 318 response.onError(AccountManager.ERROR_CODE_NETWORK_ERROR, e.getMessage()); 319 } else if (e instanceof UnsupportedOperationException) { 320 if (Log.isLoggable(TAG, Log.VERBOSE)) { 321 Log.v(TAG, method + "(" + data + ")", e); 322 } 323 response.onError(AccountManager.ERROR_CODE_UNSUPPORTED_OPERATION, 324 method + " not supported"); 325 } else if (e instanceof IllegalArgumentException) { 326 if (Log.isLoggable(TAG, Log.VERBOSE)) { 327 Log.v(TAG, method + "(" + data + ")", e); 328 } 329 response.onError(AccountManager.ERROR_CODE_BAD_ARGUMENTS, 330 method + " not supported"); 331 } else { 332 Log.w(TAG, method + "(" + data + ")", e); 333 response.onError(AccountManager.ERROR_CODE_REMOTE_EXCEPTION, 334 method + " failed"); 335 } 336 } 337 338 private void checkBinderPermission() { 339 final int uid = Binder.getCallingUid(); 340 final String perm = Manifest.permission.ACCOUNT_MANAGER; 341 if (mContext.checkCallingOrSelfPermission(perm) != PackageManager.PERMISSION_GRANTED) { 342 throw new SecurityException("caller uid " + uid + " lacks " + perm); 343 } 344 } 345 346 private Transport mTransport = new Transport(); 347 348 /** 349 * @return the IBinder for the AccountAuthenticator 350 */ 351 public final IBinder getIBinder() { 352 return mTransport.asBinder(); 353 } 354 355 /** 356 * Returns a Bundle that contains the Intent of the activity that can be used to edit the 357 * properties. In order to indicate success the activity should call response.setResult() 358 * with a non-null Bundle. 359 * @param response used to set the result for the request. If the Constants.INTENT_KEY 360 * is set in the bundle then this response field is to be used for sending future 361 * results if and when the Intent is started. 362 * @param accountType the AccountType whose properties are to be edited. 363 * @return a Bundle containing the result or the Intent to start to continue the request. 364 * If this is null then the request is considered to still be active and the result should 365 * sent later using response. 366 */ 367 public abstract Bundle editProperties(AccountAuthenticatorResponse response, 368 String accountType); 369 370 /** 371 * Adds an account of the specified accountType. 372 * @param response to send the result back to the AccountManager, will never be null 373 * @param accountType the type of account to add, will never be null 374 * @param authTokenType the type of auth token to retrieve after adding the account, may be null 375 * @param requiredFeatures a String array of authenticator-specific features that the added 376 * account must support, may be null 377 * @param options a Bundle of authenticator-specific options, may be null 378 * @return a Bundle result or null if the result is to be returned via the response. The result 379 * will contain either: 380 * <ul> 381 * <li> {@link AccountManager#KEY_INTENT}, or 382 * <li> {@link AccountManager#KEY_ACCOUNT_NAME} and {@link AccountManager#KEY_ACCOUNT_TYPE} of 383 * the account that was added, or 384 * <li> {@link AccountManager#KEY_ERROR_CODE} and {@link AccountManager#KEY_ERROR_MESSAGE} to 385 * indicate an error 386 * </ul> 387 * @throws NetworkErrorException if the authenticator could not honor the request due to a 388 * network error 389 */ 390 public abstract Bundle addAccount(AccountAuthenticatorResponse response, String accountType, 391 String authTokenType, String[] requiredFeatures, Bundle options) 392 throws NetworkErrorException; 393 394 /** 395 * Checks that the user knows the credentials of an account. 396 * @param response to send the result back to the AccountManager, will never be null 397 * @param account the account whose credentials are to be checked, will never be null 398 * @param options a Bundle of authenticator-specific options, may be null 399 * @return a Bundle result or null if the result is to be returned via the response. The result 400 * will contain either: 401 * <ul> 402 * <li> {@link AccountManager#KEY_INTENT}, or 403 * <li> {@link AccountManager#KEY_BOOLEAN_RESULT}, true if the check succeeded, false otherwise 404 * <li> {@link AccountManager#KEY_ERROR_CODE} and {@link AccountManager#KEY_ERROR_MESSAGE} to 405 * indicate an error 406 * </ul> 407 * @throws NetworkErrorException if the authenticator could not honor the request due to a 408 * network error 409 */ 410 public abstract Bundle confirmCredentials(AccountAuthenticatorResponse response, 411 Account account, Bundle options) 412 throws NetworkErrorException; 413 /** 414 * Gets the authtoken for an account. 415 * @param response to send the result back to the AccountManager, will never be null 416 * @param account the account whose credentials are to be retrieved, will never be null 417 * @param authTokenType the type of auth token to retrieve, will never be null 418 * @param options a Bundle of authenticator-specific options, may be null 419 * @return a Bundle result or null if the result is to be returned via the response. The result 420 * will contain either: 421 * <ul> 422 * <li> {@link AccountManager#KEY_INTENT}, or 423 * <li> {@link AccountManager#KEY_ACCOUNT_NAME}, {@link AccountManager#KEY_ACCOUNT_TYPE}, 424 * and {@link AccountManager#KEY_AUTHTOKEN}, or 425 * <li> {@link AccountManager#KEY_ERROR_CODE} and {@link AccountManager#KEY_ERROR_MESSAGE} to 426 * indicate an error 427 * </ul> 428 * @throws NetworkErrorException if the authenticator could not honor the request due to a 429 * network error 430 */ 431 public abstract Bundle getAuthToken(AccountAuthenticatorResponse response, 432 Account account, String authTokenType, Bundle options) 433 throws NetworkErrorException; 434 435 /** 436 * Ask the authenticator for a localized label for the given authTokenType. 437 * @param authTokenType the authTokenType whose label is to be returned, will never be null 438 * @return the localized label of the auth token type, may be null if the type isn't known 439 */ 440 public abstract String getAuthTokenLabel(String authTokenType); 441 442 /** 443 * Update the locally stored credentials for an account. 444 * @param response to send the result back to the AccountManager, will never be null 445 * @param account the account whose credentials are to be updated, will never be null 446 * @param authTokenType the type of auth token to retrieve after updating the credentials, 447 * may be null 448 * @param options a Bundle of authenticator-specific options, may be null 449 * @return a Bundle result or null if the result is to be returned via the response. The result 450 * will contain either: 451 * <ul> 452 * <li> {@link AccountManager#KEY_INTENT}, or 453 * <li> {@link AccountManager#KEY_ACCOUNT_NAME} and {@link AccountManager#KEY_ACCOUNT_TYPE} of 454 * the account that was added, or 455 * <li> {@link AccountManager#KEY_ERROR_CODE} and {@link AccountManager#KEY_ERROR_MESSAGE} to 456 * indicate an error 457 * </ul> 458 * @throws NetworkErrorException if the authenticator could not honor the request due to a 459 * network error 460 */ 461 public abstract Bundle updateCredentials(AccountAuthenticatorResponse response, 462 Account account, String authTokenType, Bundle options) throws NetworkErrorException; 463 464 /** 465 * Checks if the account supports all the specified authenticator specific features. 466 * @param response to send the result back to the AccountManager, will never be null 467 * @param account the account to check, will never be null 468 * @param features an array of features to check, will never be null 469 * @return a Bundle result or null if the result is to be returned via the response. The result 470 * will contain either: 471 * <ul> 472 * <li> {@link AccountManager#KEY_INTENT}, or 473 * <li> {@link AccountManager#KEY_BOOLEAN_RESULT}, true if the account has all the features, 474 * false otherwise 475 * <li> {@link AccountManager#KEY_ERROR_CODE} and {@link AccountManager#KEY_ERROR_MESSAGE} to 476 * indicate an error 477 * </ul> 478 * @throws NetworkErrorException if the authenticator could not honor the request due to a 479 * network error 480 */ 481 public abstract Bundle hasFeatures(AccountAuthenticatorResponse response, 482 Account account, String[] features) throws NetworkErrorException; 483 484 /** 485 * Checks if the removal of this account is allowed. 486 * @param response to send the result back to the AccountManager, will never be null 487 * @param account the account to check, will never be null 488 * @return a Bundle result or null if the result is to be returned via the response. The result 489 * will contain either: 490 * <ul> 491 * <li> {@link AccountManager#KEY_INTENT}, or 492 * <li> {@link AccountManager#KEY_BOOLEAN_RESULT}, true if the removal of the account is 493 * allowed, false otherwise 494 * <li> {@link AccountManager#KEY_ERROR_CODE} and {@link AccountManager#KEY_ERROR_MESSAGE} to 495 * indicate an error 496 * </ul> 497 * @throws NetworkErrorException if the authenticator could not honor the request due to a 498 * network error 499 */ 500 public Bundle getAccountRemovalAllowed(AccountAuthenticatorResponse response, 501 Account account) throws NetworkErrorException { 502 final Bundle result = new Bundle(); 503 result.putBoolean(AccountManager.KEY_BOOLEAN_RESULT, true); 504 return result; 505 } 506 507 /** 508 * Returns a Bundle that contains whatever is required to clone the account on a different 509 * user. The Bundle is passed to the authenticator instance in the target user via 510 * {@link #addAccountFromCredentials(AccountAuthenticatorResponse, Account, Bundle)}. 511 * The default implementation returns null, indicating that cloning is not supported. 512 * @param response to send the result back to the AccountManager, will never be null 513 * @param account the account to clone, will never be null 514 * @return a Bundle result or null if the result is to be returned via the response. 515 * @throws NetworkErrorException 516 * @see {@link #addAccountFromCredentials(AccountAuthenticatorResponse, Account, Bundle)} 517 */ 518 public Bundle getAccountCredentialsForCloning(final AccountAuthenticatorResponse response, 519 final Account account) throws NetworkErrorException { 520 new Thread(new Runnable() { 521 public void run() { 522 Bundle result = new Bundle(); 523 result.putBoolean(AccountManager.KEY_BOOLEAN_RESULT, false); 524 response.onResult(result); 525 } 526 }).start(); 527 return null; 528 } 529 530 /** 531 * Creates an account based on credentials provided by the authenticator instance of another 532 * user on the device, who has chosen to share the account with this user. 533 * @param response to send the result back to the AccountManager, will never be null 534 * @param account the account to clone, will never be null 535 * @param accountCredentials the Bundle containing the required credentials to create the 536 * account. Contents of the Bundle are only meaningful to the authenticator. This Bundle is 537 * provided by {@link #getAccountCredentialsForCloning(AccountAuthenticatorResponse, Account)}. 538 * @return a Bundle result or null if the result is to be returned via the response. 539 * @throws NetworkErrorException 540 * @see {@link #getAccountCredentialsForCloning(AccountAuthenticatorResponse, Account)} 541 */ 542 public Bundle addAccountFromCredentials(final AccountAuthenticatorResponse response, 543 Account account, 544 Bundle accountCredentials) throws NetworkErrorException { 545 new Thread(new Runnable() { 546 public void run() { 547 Bundle result = new Bundle(); 548 result.putBoolean(AccountManager.KEY_BOOLEAN_RESULT, false); 549 response.onResult(result); 550 } 551 }).start(); 552 return null; 553 } 554} 555