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