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