AbstractAccountAuthenticator.java revision 8570f7440780db5c9b410e033e843b0e80e2fd27
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, or 300 * <li> {@link AccountManager#KEY_ERROR_CODE} and {@link AccountManager#KEY_ERROR_MESSAGE} to 301 * indicate an error 302 * </ul> 303 * @throws NetworkErrorException if the authenticator could not honor the request due to a 304 * network error 305 */ 306 public abstract Bundle addAccount(AccountAuthenticatorResponse response, String accountType, 307 String authTokenType, String[] requiredFeatures, Bundle options) 308 throws NetworkErrorException; 309 310 /** 311 * Checks that the user knows the credentials of an account. 312 * @param response to send the result back to the AccountManager, will never be null 313 * @param account the account whose credentials are to be checked, will never be null 314 * @param options a Bundle of authenticator-specific options, may be null 315 * @return a Bundle result or null if the result is to be returned via the response. The result 316 * will contain either: 317 * <ul> 318 * <li> {@link AccountManager#KEY_INTENT}, or 319 * <li> {@link AccountManager#KEY_BOOLEAN_RESULT}, true if the check succeeded, false otherwise 320 * <li> {@link AccountManager#KEY_ERROR_CODE} and {@link AccountManager#KEY_ERROR_MESSAGE} to 321 * indicate an error 322 * </ul> 323 * @throws NetworkErrorException if the authenticator could not honor the request due to a 324 * network error 325 */ 326 public abstract Bundle confirmCredentials(AccountAuthenticatorResponse response, 327 Account account, Bundle options) 328 throws NetworkErrorException; 329 /** 330 * Gets the authtoken for an account. 331 * @param response to send the result back to the AccountManager, will never be null 332 * @param account the account whose credentials are to be retrieved, will never be null 333 * @param authTokenType the type of auth token to retrieve, will never be null 334 * @param options a Bundle of authenticator-specific options, may be null 335 * @return a Bundle result or null if the result is to be returned via the response. The result 336 * will contain either: 337 * <ul> 338 * <li> {@link AccountManager#KEY_INTENT}, or 339 * <li> {@link AccountManager#KEY_ACCOUNT_NAME}, {@link AccountManager#KEY_ACCOUNT_TYPE}, 340 * and {@link AccountManager#KEY_AUTHTOKEN}, or 341 * <li> {@link AccountManager#KEY_ERROR_CODE} and {@link AccountManager#KEY_ERROR_MESSAGE} to 342 * indicate an error 343 * </ul> 344 * @throws NetworkErrorException if the authenticator could not honor the request due to a 345 * network error 346 */ 347 public abstract Bundle getAuthToken(AccountAuthenticatorResponse response, 348 Account account, String authTokenType, Bundle options) 349 throws NetworkErrorException; 350 351 /** 352 * Ask the authenticator for a localized label for the given authTokenType. 353 * @param authTokenType the authTokenType whose label is to be returned, will never be null 354 * @return the localized label of the auth token type, may be null if the type isn't known 355 */ 356 public abstract String getAuthTokenLabel(String authTokenType); 357 358 /** 359 * Update the locally stored credentials for an account. 360 * @param response to send the result back to the AccountManager, will never be null 361 * @param account the account whose credentials are to be updated, will never be null 362 * @param authTokenType the type of auth token to retrieve after updating the credentials, 363 * may be null 364 * @param options a Bundle of authenticator-specific options, may be null 365 * @return a Bundle result or null if the result is to be returned via the response. The result 366 * will contain either: 367 * <ul> 368 * <li> {@link AccountManager#KEY_INTENT}, or 369 * <li> {@link AccountManager#KEY_ACCOUNT_NAME} and {@link AccountManager#KEY_ACCOUNT_TYPE} of 370 * the account that was added, or 371 * <li> {@link AccountManager#KEY_ERROR_CODE} and {@link AccountManager#KEY_ERROR_MESSAGE} to 372 * indicate an error 373 * </ul> 374 * @throws NetworkErrorException if the authenticator could not honor the request due to a 375 * network error 376 */ 377 public abstract Bundle updateCredentials(AccountAuthenticatorResponse response, 378 Account account, String authTokenType, Bundle options) throws NetworkErrorException; 379 380 /** 381 * Checks if the account supports all the specified authenticator specific features. 382 * @param response to send the result back to the AccountManager, will never be null 383 * @param account the account to check, will never be null 384 * @param features an array of features to check, will never be null 385 * @return a Bundle result or null if the result is to be returned via the response. The result 386 * will contain either: 387 * <ul> 388 * <li> {@link AccountManager#KEY_INTENT}, or 389 * <li> {@link AccountManager#KEY_BOOLEAN_RESULT}, true if the account has all the features, 390 * false otherwise 391 * <li> {@link AccountManager#KEY_ERROR_CODE} and {@link AccountManager#KEY_ERROR_MESSAGE} to 392 * indicate an error 393 * </ul> 394 * @throws NetworkErrorException if the authenticator could not honor the request due to a 395 * network error 396 */ 397 public abstract Bundle hasFeatures(AccountAuthenticatorResponse response, 398 Account account, String[] features) throws NetworkErrorException; 399 400 /** 401 * Checks if the removal of this account is allowed. 402 * @param response to send the result back to the AccountManager, will never be null 403 * @param account the account to check, will never be null 404 * @return a Bundle result or null if the result is to be returned via the response. The result 405 * will contain either: 406 * <ul> 407 * <li> {@link AccountManager#KEY_INTENT}, or 408 * <li> {@link AccountManager#KEY_BOOLEAN_RESULT}, true if the removal of the account is 409 * allowed, false otherwise 410 * <li> {@link AccountManager#KEY_ERROR_CODE} and {@link AccountManager#KEY_ERROR_MESSAGE} to 411 * indicate an error 412 * </ul> 413 * @throws NetworkErrorException if the authenticator could not honor the request due to a 414 * network error 415 */ 416 public Bundle getAccountRemovalAllowed(AccountAuthenticatorResponse response, 417 Account account) throws NetworkErrorException { 418 final Bundle result = new Bundle(); 419 result.putBoolean(AccountManager.KEY_BOOLEAN_RESULT, true); 420 return result; 421 } 422} 423