Home | History | Annotate | Download | only in accounts
      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  *   &lt;intent-filter&gt;
     41  *     &lt;action android:name="android.accounts.AccountAuthenticator" /&gt;
     42  *   &lt;/intent-filter&gt;
     43  *   &lt;meta-data android:name="android.accounts.AccountAuthenticator"
     44  *             android:resource="@xml/authenticator" /&gt;
     45  * </pre>
     46  * The <code>android:resource</code> attribute must point to a resource that looks like:
     47  * <pre>
     48  * &lt;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  * /&gt;
     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  * &lt;PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"&gt;
     67  *    &lt;PreferenceCategory android:title="@string/title_fmt" /&gt;
     68  *    &lt;PreferenceScreen
     69  *         android:key="key1"
     70  *         android:title="@string/key1_action"
     71  *         android:summary="@string/key1_summary"&gt;
     72  *         &lt;intent
     73  *             android:action="key1.ACTION"
     74  *             android:targetPackage="key1.package"
     75  *             android:targetClass="key1.class" /&gt;
     76  *     &lt;/PreferenceScreen&gt;
     77  * &lt;/PreferenceScreen&gt;
     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