Home | History | Annotate | Download | only in cts
      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.cts;
     18 
     19 import android.accounts.AbstractAccountAuthenticator;
     20 import android.accounts.Account;
     21 import android.accounts.AccountAuthenticatorResponse;
     22 import android.accounts.AccountManager;
     23 import android.accounts.NetworkErrorException;
     24 import android.accounts.cts.common.Fixtures;
     25 import android.content.Context;
     26 import android.content.Intent;
     27 import android.os.Bundle;
     28 import android.util.Log;
     29 
     30 import java.util.ArrayList;
     31 import java.util.concurrent.atomic.AtomicBoolean;
     32 import java.util.concurrent.atomic.AtomicInteger;
     33 
     34 /**
     35  * A simple Mock Account Authenticator
     36  */
     37 public class MockAccountAuthenticator extends AbstractAccountAuthenticator {
     38     private static String TAG = "AccountManagerTest";
     39 
     40     public static String KEY_ACCOUNT_INFO = "key_account_info";
     41     public static String KEY_ACCOUNT_AUTHENTICATOR_RESPONSE = "key_account_authenticator_response";
     42     public static String ACCOUNT_NAME_FOR_NEW_REMOVE_API = "call new removeAccount api";
     43     public static String ACCOUNT_NAME_FOR_DEFAULT_IMPL = "call super api";
     44     // Key for triggering return intent flow
     45     public static String KEY_RETURN_INTENT = "return an intent";
     46     public static String ACCOUNT_NAME_FOR_NEW_REMOVE_API1 = "call new removeAccount api";
     47 
     48     private final Context mContext;
     49     private final AtomicInteger mTokenCounter  = new AtomicInteger(0);
     50     private final AtomicBoolean mIsRecentlyCalled = new AtomicBoolean(false);
     51 
     52     AccountAuthenticatorResponse mResponse;
     53     String mAccountType;
     54     String mAuthTokenType;
     55     String[] mRequiredFeatures;
     56     public Bundle mOptionsUpdateCredentials;
     57     public Bundle mOptionsConfirmCredentials;
     58     public Bundle mOptionsAddAccount;
     59     public Bundle mOptionsGetAuthToken;
     60     public Bundle mOptionsStartAddAccountSession;
     61     public Bundle mOptionsStartUpdateCredentialsSession;
     62     public Bundle mOptionsFinishSession;
     63     Account mAccount;
     64     String[] mFeatures;
     65     String mStatusToken;
     66 
     67     final ArrayList<String> mockFeatureList = new ArrayList<String>();
     68     private final long mTokenDurationMillis = 1000; // 1 second
     69 
     70     public MockAccountAuthenticator(Context context) {
     71         super(context);
     72         mContext = context;
     73 
     74         // Create some mock features
     75         mockFeatureList.add(AccountManagerTest.FEATURE_1);
     76         mockFeatureList.add(AccountManagerTest.FEATURE_2);
     77     }
     78 
     79     public long getTokenDurationMillis() {
     80         return mTokenDurationMillis;
     81     }
     82 
     83     public boolean isRecentlyCalled() {
     84         return mIsRecentlyCalled.getAndSet(false);
     85     }
     86 
     87     public String getLastTokenServed() {
     88         return Integer.toString(mTokenCounter.get());
     89     }
     90 
     91     public AccountAuthenticatorResponse getResponse() {
     92         return mResponse;
     93     }
     94 
     95     public String getAccountType() {
     96         return mAccountType;
     97     }
     98 
     99     public String getAuthTokenType() {
    100         return mAuthTokenType;
    101     }
    102 
    103     public String[] getRequiredFeatures() {
    104         return mRequiredFeatures;
    105     }
    106 
    107     public Account getAccount() {
    108         return mAccount;
    109     }
    110 
    111     public String[] getFeatures() {
    112         return mFeatures;
    113     }
    114 
    115     public String getStatusToken() {
    116         return mStatusToken;
    117     }
    118 
    119     public void clearData() {
    120         mResponse = null;
    121         mAccountType = null;
    122         mAuthTokenType = null;
    123         mRequiredFeatures = null;
    124         mOptionsUpdateCredentials = null;
    125         mOptionsAddAccount = null;
    126         mOptionsGetAuthToken = null;
    127         mOptionsConfirmCredentials = null;
    128         mOptionsStartAddAccountSession = null;
    129         mOptionsStartUpdateCredentialsSession = null;
    130         mOptionsFinishSession = null;
    131         mAccount = null;
    132         mFeatures = null;
    133         mStatusToken = null;
    134     }
    135 
    136     public void callAccountAuthenticated() {
    137         AccountManager am = AccountManager.get(mContext);
    138         am.notifyAccountAuthenticated(mAccount);
    139     }
    140 
    141     public void callSetPassword() {
    142         AccountManager am = AccountManager.get(mContext);
    143         am.setPassword(mAccount, "password");
    144     }
    145 
    146     private Bundle createResultBundle() {
    147         Bundle result = new Bundle();
    148         result.putString(AccountManager.KEY_ACCOUNT_NAME, AccountManagerTest.ACCOUNT_NAME);
    149         result.putString(AccountManager.KEY_ACCOUNT_TYPE, AccountManagerTest.ACCOUNT_TYPE);
    150         result.putString(
    151                 AccountManager.KEY_AUTHTOKEN,
    152                 Integer.toString(mTokenCounter.incrementAndGet()));
    153         return result;
    154     }
    155 
    156     /**
    157      * Adds an account of the specified accountType.
    158      */
    159     @Override
    160     public Bundle addAccount(AccountAuthenticatorResponse response, String accountType,
    161             String authTokenType, String[] requiredFeatures, Bundle options)
    162             throws NetworkErrorException {
    163         this.mResponse = response;
    164         this.mAccountType = accountType;
    165         this.mAuthTokenType = authTokenType;
    166         this.mRequiredFeatures = requiredFeatures;
    167         this.mOptionsAddAccount = options;
    168         AccountManager am = AccountManager.get(mContext);
    169         am.addAccountExplicitly(AccountManagerTest.ACCOUNT, "fakePassword", null);
    170         return createResultBundle();
    171     }
    172 
    173     /**
    174      * Update the locally stored credentials for an account.
    175      */
    176     @Override
    177     public Bundle updateCredentials(AccountAuthenticatorResponse response, Account account,
    178             String authTokenType, Bundle options) throws NetworkErrorException {
    179         this.mResponse = response;
    180         this.mAccount = account;
    181         this.mAuthTokenType = authTokenType;
    182         this.mOptionsUpdateCredentials = options;
    183         return createResultBundle();
    184     }
    185 
    186     /**
    187      * Returns a Bundle that contains the Intent of the activity that can be used to edit the
    188      * properties. In order to indicate success the activity should call response.setResult()
    189      * with a non-null Bundle.
    190      */
    191     @Override
    192     public Bundle editProperties(AccountAuthenticatorResponse response, String accountType) {
    193         this.mResponse = response;
    194         this.mAccountType = accountType;
    195         return createResultBundle();
    196     }
    197 
    198     /**
    199      * Checks that the user knows the credentials of an account.
    200      */
    201     @Override
    202     public Bundle confirmCredentials(AccountAuthenticatorResponse response, Account account,
    203             Bundle options) throws NetworkErrorException {
    204         this.mResponse = response;
    205         this.mAccount = account;
    206         this.mOptionsConfirmCredentials = options;
    207         Bundle result = new Bundle();
    208         if (options.containsKey(KEY_RETURN_INTENT)) {
    209             Intent intent = new Intent();
    210             intent.setClassName("android.accounts.cts", "android.accounts.cts.AccountDummyActivity");
    211             result.putParcelable(AccountManager.KEY_INTENT, intent);
    212         } else {
    213             result.putBoolean(AccountManager.KEY_BOOLEAN_RESULT, true);
    214         }
    215 
    216         return result;
    217     }
    218 
    219     /**
    220      * Gets the authtoken for an account.
    221      */
    222     @Override
    223     public Bundle getAuthToken(
    224             AccountAuthenticatorResponse response,
    225             Account account,
    226             String authTokenType,
    227             Bundle options) throws NetworkErrorException {
    228         Log.w(TAG, "MockAuth - getAuthToken@" + System.currentTimeMillis());
    229         mIsRecentlyCalled.set(true);
    230         this.mResponse = response;
    231         this.mAccount = account;
    232         this.mAuthTokenType = authTokenType;
    233         this.mOptionsGetAuthToken = options;
    234         Bundle result = new Bundle();
    235 
    236         result.putString(AccountManager.KEY_ACCOUNT_NAME, account.name);
    237         result.putString(AccountManager.KEY_ACCOUNT_TYPE, account.type);
    238         String token;
    239         if (AccountManagerTest.AUTH_EXPIRING_TOKEN_TYPE.equals(authTokenType)) {
    240             /*
    241              * The resultant token should simply be the expiration timestamp. E.g. the time after
    242              * which getting a new AUTH_EXPIRING_TOKEN_TYPE typed token will return a different
    243              * value.
    244              */
    245             long expiry = System.currentTimeMillis() + mTokenDurationMillis;
    246             result.putLong(AbstractAccountAuthenticator.KEY_CUSTOM_TOKEN_EXPIRY, expiry);
    247         }
    248         result.putString(
    249                 AccountManager.KEY_AUTHTOKEN,
    250                 Integer.toString(mTokenCounter.incrementAndGet()));
    251         return result;
    252     }
    253 
    254     /**
    255      * Ask the authenticator for a localized label for the given authTokenType.
    256      */
    257     @Override
    258     public String getAuthTokenLabel(String authTokenType) {
    259         this.mAuthTokenType = authTokenType;
    260         return AccountManagerTest.AUTH_TOKEN_LABEL;
    261     }
    262 
    263     /**
    264      * Checks if the account supports all the specified authenticator specific features.
    265      */
    266     @Override
    267     public Bundle hasFeatures(AccountAuthenticatorResponse response, Account account,
    268             String[] features) throws NetworkErrorException {
    269 
    270         this.mResponse = response;
    271         this.mAccount = account;
    272         this.mFeatures = features;
    273 
    274         Bundle result = new Bundle();
    275         if (null == features) {
    276             result.putBoolean(AccountManager.KEY_BOOLEAN_RESULT, true);
    277         }
    278         else {
    279             boolean booleanResult = true;
    280             for (String feature: features) {
    281                 if (!mockFeatureList.contains(feature)) {
    282                     booleanResult = false;
    283                     break;
    284                 }
    285             }
    286             result.putBoolean(AccountManager.KEY_BOOLEAN_RESULT, booleanResult);
    287         }
    288         return result;
    289     }
    290 
    291     @Override
    292     public Bundle getAccountRemovalAllowed(AccountAuthenticatorResponse response,
    293             Account account) throws NetworkErrorException {
    294         final Bundle result = new Bundle();
    295         if (ACCOUNT_NAME_FOR_NEW_REMOVE_API.equals(account.name)) {
    296             Intent intent = AccountRemovalDummyActivity.createIntent(mContext);
    297             // Pass in the authenticator response, so that account removal can
    298             // be
    299             // completed
    300             intent.putExtra(KEY_ACCOUNT_AUTHENTICATOR_RESPONSE, response);
    301             intent.putExtra(KEY_ACCOUNT_INFO, account);
    302             result.putParcelable(AccountManager.KEY_INTENT, intent);
    303             // Adding this following line to reject account installation
    304             // requests
    305             // coming from old removeAccount API.
    306             result.putBoolean(AccountManager.KEY_BOOLEAN_RESULT, false);
    307         } else if (ACCOUNT_NAME_FOR_DEFAULT_IMPL.equals(account.name)) {
    308             return super.getAccountRemovalAllowed(response, account);
    309         } else {
    310             result.putBoolean(AccountManager.KEY_BOOLEAN_RESULT, true);
    311         }
    312         return result;
    313     }
    314 
    315     @Override
    316     public Bundle addAccountFromCredentials(final AccountAuthenticatorResponse response,
    317             Account account,
    318             Bundle accountCredentials) throws NetworkErrorException {
    319         return super.addAccountFromCredentials(response, account, accountCredentials);
    320     }
    321 
    322     @Override
    323     public Bundle getAccountCredentialsForCloning(final AccountAuthenticatorResponse response,
    324             final Account account) throws NetworkErrorException {
    325         return super.getAccountCredentialsForCloning(response, account);
    326     }
    327 
    328 
    329     /**
    330      * Start add account flow of the specified accountType to authenticate user.
    331      */
    332     @Override
    333     public Bundle startAddAccountSession(AccountAuthenticatorResponse response,
    334             String accountType,
    335             String authTokenType,
    336             String[] requiredFeatures,
    337             Bundle options) throws NetworkErrorException {
    338         this.mResponse = response;
    339         this.mAccountType = accountType;
    340         this.mAuthTokenType = authTokenType;
    341         this.mRequiredFeatures = requiredFeatures;
    342         this.mOptionsStartAddAccountSession = options;
    343 
    344         String accountName = null;
    345         Bundle sessionBundle = null;
    346         if (options != null) {
    347             accountName = options.getString(Fixtures.KEY_ACCOUNT_NAME);
    348             sessionBundle = options.getBundle(Fixtures.KEY_ACCOUNT_SESSION_BUNDLE);
    349         }
    350 
    351         Bundle result = new Bundle();
    352         if (accountName.startsWith(Fixtures.PREFIX_NAME_SUCCESS)) {
    353             // fill bundle with a success result.
    354             result.putBundle(AccountManager.KEY_ACCOUNT_SESSION_BUNDLE, sessionBundle);
    355             result.putString(AccountManager.KEY_ACCOUNT_STATUS_TOKEN,
    356                     AccountManagerTest.ACCOUNT_STATUS_TOKEN);
    357             result.putString(AccountManager.KEY_PASSWORD, AccountManagerTest.ACCOUNT_PASSWORD);
    358             result.putString(AccountManager.KEY_AUTHTOKEN,
    359                     Integer.toString(mTokenCounter.incrementAndGet()));
    360         } else if (accountName.startsWith(Fixtures.PREFIX_NAME_INTERVENE)) {
    361             // Specify data to be returned by the eventual activity.
    362             Intent eventualActivityResultData = new Intent();
    363             eventualActivityResultData.putExtra(AccountManager.KEY_AUTHTOKEN,
    364                     Integer.toString(mTokenCounter.incrementAndGet()));
    365             eventualActivityResultData.putExtra(AccountManager.KEY_ACCOUNT_STATUS_TOKEN,
    366                     AccountManagerTest.ACCOUNT_STATUS_TOKEN);
    367             eventualActivityResultData.putExtra(AccountManager.KEY_PASSWORD,
    368                     AccountManagerTest.ACCOUNT_PASSWORD);
    369             eventualActivityResultData.putExtra(AccountManager.KEY_ACCOUNT_SESSION_BUNDLE,
    370                     sessionBundle);
    371             // Fill result with Intent.
    372             Intent intent = new Intent(mContext, AccountAuthenticatorDummyActivity.class);
    373             intent.putExtra(Fixtures.KEY_RESULT, eventualActivityResultData);
    374             intent.putExtra(Fixtures.KEY_CALLBACK, response);
    375 
    376             result.putParcelable(AccountManager.KEY_INTENT, intent);
    377         } else {
    378             // fill with error
    379             fillResultWithError(result, options);
    380         }
    381         return result;
    382     }
    383 
    384     /**
    385      * Start update credentials flow to re-auth user without updating locally stored credentials
    386      * for an account.
    387      */
    388     @Override
    389     public Bundle startUpdateCredentialsSession(AccountAuthenticatorResponse response,
    390             Account account,
    391             String authTokenType,
    392             Bundle options) throws NetworkErrorException {
    393         mResponse = response;
    394         mAccount = account;
    395         mAuthTokenType = authTokenType;
    396         mOptionsStartUpdateCredentialsSession = options;
    397 
    398         String accountName = null;
    399         Bundle sessionBundle = null;
    400         if (options != null) {
    401             accountName = options.getString(Fixtures.KEY_ACCOUNT_NAME);
    402             sessionBundle = options.getBundle(Fixtures.KEY_ACCOUNT_SESSION_BUNDLE);
    403         }
    404 
    405         Bundle result = new Bundle();
    406         if (accountName.startsWith(Fixtures.PREFIX_NAME_SUCCESS)) {
    407             // fill bundle with a success result.
    408             result.putBundle(AccountManager.KEY_ACCOUNT_SESSION_BUNDLE, sessionBundle);
    409             result.putString(AccountManager.KEY_ACCOUNT_STATUS_TOKEN,
    410                     AccountManagerTest.ACCOUNT_STATUS_TOKEN);
    411             result.putString(AccountManager.KEY_PASSWORD, AccountManagerTest.ACCOUNT_PASSWORD);
    412             result.putString(AccountManager.KEY_AUTHTOKEN,
    413                     Integer.toString(mTokenCounter.incrementAndGet()));
    414         } else if (accountName.startsWith(Fixtures.PREFIX_NAME_INTERVENE)) {
    415             // Specify data to be returned by the eventual activity.
    416             Intent eventualActivityResultData = new Intent();
    417             eventualActivityResultData.putExtra(AccountManager.KEY_AUTHTOKEN,
    418                     Integer.toString(mTokenCounter.incrementAndGet()));
    419             eventualActivityResultData.putExtra(AccountManager.KEY_ACCOUNT_STATUS_TOKEN,
    420                     AccountManagerTest.ACCOUNT_STATUS_TOKEN);
    421             eventualActivityResultData.putExtra(AccountManager.KEY_PASSWORD,
    422                     AccountManagerTest.ACCOUNT_PASSWORD);
    423             eventualActivityResultData.putExtra(AccountManager.KEY_ACCOUNT_SESSION_BUNDLE,
    424                     sessionBundle);
    425             // Fill result with Intent.
    426             Intent intent = new Intent(mContext, AccountAuthenticatorDummyActivity.class);
    427             intent.putExtra(Fixtures.KEY_RESULT, eventualActivityResultData);
    428             intent.putExtra(Fixtures.KEY_CALLBACK, response);
    429 
    430             result.putParcelable(AccountManager.KEY_INTENT, intent);
    431         } else {
    432             // fill with error
    433             fillResultWithError(result, options);
    434         }
    435         return result;
    436     }
    437 
    438     /**
    439      * Finishes account session started by adding the account to device or updating the local
    440      * credentials.
    441      */
    442     @Override
    443     public Bundle finishSession(AccountAuthenticatorResponse response,
    444             String accountType,
    445             Bundle sessionBundle) throws NetworkErrorException {
    446         this.mResponse = response;
    447         this.mAccountType = accountType;
    448         this.mOptionsFinishSession = sessionBundle;
    449 
    450         String accountName = null;
    451         if (sessionBundle != null) {
    452             accountName = sessionBundle.getString(Fixtures.KEY_ACCOUNT_NAME);
    453         }
    454 
    455         Bundle result = new Bundle();
    456         if (accountName.startsWith(Fixtures.PREFIX_NAME_SUCCESS)) {
    457             // fill bundle with a success result.
    458             result.putString(AccountManager.KEY_ACCOUNT_NAME, AccountManagerTest.ACCOUNT_NAME);
    459             result.putString(AccountManager.KEY_ACCOUNT_TYPE, AccountManagerTest.ACCOUNT_TYPE);
    460             result.putString(AccountManager.KEY_AUTHTOKEN,
    461                     Integer.toString(mTokenCounter.incrementAndGet()));
    462         } else if (accountName.startsWith(Fixtures.PREFIX_NAME_INTERVENE)) {
    463             // Specify data to be returned by the eventual activity.
    464             Intent eventualActivityResultData = new Intent();
    465             eventualActivityResultData.putExtra(AccountManager.KEY_ACCOUNT_NAME,
    466                     AccountManagerTest.ACCOUNT_NAME);
    467             eventualActivityResultData.putExtra(AccountManager.KEY_ACCOUNT_TYPE,
    468                     AccountManagerTest.ACCOUNT_TYPE);
    469             eventualActivityResultData.putExtra(AccountManager.KEY_AUTHTOKEN,
    470                     Integer.toString(mTokenCounter.incrementAndGet()));
    471 
    472             // Fill result with Intent.
    473             Intent intent = new Intent(mContext, AccountAuthenticatorDummyActivity.class);
    474             intent.putExtra(Fixtures.KEY_RESULT, eventualActivityResultData);
    475             intent.putExtra(Fixtures.KEY_CALLBACK, response);
    476 
    477             result.putParcelable(AccountManager.KEY_INTENT, intent);
    478         } else {
    479             // fill with error
    480             fillResultWithError(result, sessionBundle);
    481         }
    482         return result;
    483     }
    484 
    485     private void fillResultWithError(Bundle result, Bundle options) {
    486         int errorCode = AccountManager.ERROR_CODE_INVALID_RESPONSE;
    487         String errorMsg = "Default Error Message";
    488         if (options != null) {
    489             errorCode = options.getInt(AccountManager.KEY_ERROR_CODE);
    490             errorMsg = options.getString(AccountManager.KEY_ERROR_MESSAGE);
    491         }
    492         result.putInt(AccountManager.KEY_ERROR_CODE, errorCode);
    493         result.putString(AccountManager.KEY_ERROR_MESSAGE, errorMsg);
    494     }
    495 
    496     /**
    497      * Checks if the credentials of the account should be updated.
    498      */
    499     @Override
    500     public Bundle isCredentialsUpdateSuggested(
    501             final AccountAuthenticatorResponse response,
    502             Account account,
    503             String statusToken) throws NetworkErrorException {
    504         this.mResponse = response;
    505         this.mAccount = account;
    506         this.mStatusToken = statusToken;
    507 
    508         Bundle result = new Bundle();
    509         if (account.name.startsWith(Fixtures.PREFIX_NAME_SUCCESS)) {
    510             // fill bundle with a success result.
    511             result.putBoolean(AccountManager.KEY_BOOLEAN_RESULT, true);
    512         } else {
    513             // fill with error
    514             int errorCode = AccountManager.ERROR_CODE_INVALID_RESPONSE;
    515             String errorMsg = "Default Error Message";
    516             result.putInt(AccountManager.KEY_ERROR_CODE, errorCode);
    517             result.putString(AccountManager.KEY_ERROR_MESSAGE, errorMsg);
    518         }
    519 
    520         response.onResult(result);
    521         return null;
    522     }
    523 }
    524