Home | History | Annotate | Download | only in util
      1 // Copyright 2013 The Chromium Authors. All rights reserved.
      2 // Use of this source code is governed by a BSD-style license that can be
      3 // found in the LICENSE file.
      4 
      5 package org.chromium.sync.test.util;
      6 
      7 import android.accounts.Account;
      8 import android.accounts.AccountManager;
      9 import android.accounts.AccountManagerCallback;
     10 import android.accounts.AccountManagerFuture;
     11 import android.accounts.AuthenticatorDescription;
     12 import android.accounts.AuthenticatorException;
     13 import android.accounts.OperationCanceledException;
     14 import android.app.Activity;
     15 import android.content.BroadcastReceiver;
     16 import android.content.ComponentName;
     17 import android.content.Context;
     18 import android.content.Intent;
     19 import android.content.IntentFilter;
     20 import android.os.AsyncTask;
     21 import android.os.Bundle;
     22 import android.os.Handler;
     23 import android.util.Log;
     24 
     25 import org.chromium.base.ThreadUtils;
     26 import org.chromium.sync.signin.AccountManagerDelegate;
     27 import org.chromium.sync.signin.AccountManagerHelper;
     28 
     29 import java.io.IOException;
     30 import java.util.HashSet;
     31 import java.util.LinkedList;
     32 import java.util.List;
     33 import java.util.Set;
     34 import java.util.UUID;
     35 import java.util.concurrent.Callable;
     36 import java.util.concurrent.CancellationException;
     37 import java.util.concurrent.ExecutionException;
     38 import java.util.concurrent.Executor;
     39 import java.util.concurrent.FutureTask;
     40 import java.util.concurrent.LinkedBlockingDeque;
     41 import java.util.concurrent.ThreadPoolExecutor;
     42 import java.util.concurrent.TimeUnit;
     43 import java.util.concurrent.TimeoutException;
     44 
     45 import javax.annotation.Nullable;
     46 
     47 /**
     48  * The MockAccountManager helps out if you want to mock out all calls to the Android AccountManager.
     49  *
     50  * You should provide a set of accounts as a constructor argument, or use the more direct approach
     51  * and provide an array of AccountHolder objects.
     52  *
     53  * Currently, this implementation supports adding and removing accounts, handling credentials
     54  * (including confirming them), and handling of dummy auth tokens.
     55  *
     56  * If you want the MockAccountManager to popup an activity for granting/denying access to an
     57  * authtokentype for a given account, use prepareGrantAppPermission(...).
     58  *
     59  * If you want to auto-approve a given authtokentype, use addAccountHolderExplicitly(...) with
     60  * an AccountHolder you have built with hasBeenAccepted("yourAuthTokenType", true).
     61  *
     62  * If you want to auto-approve all auth token types for a given account, use the {@link
     63  * AccountHolder} builder method alwaysAccept(true).
     64  */
     65 public class MockAccountManager implements AccountManagerDelegate {
     66 
     67     private static final String TAG = "MockAccountManager";
     68 
     69     private static final int WAIT_TIME_FOR_GRANT_BROADCAST_MS = 20000;
     70 
     71     static final String MUTEX_WAIT_ACTION =
     72             "org.chromium.sync.test.util.MockAccountManager.MUTEX_WAIT_ACTION";
     73 
     74     protected final Context mContext;
     75 
     76     private final Context mTestContext;
     77 
     78     private final Set<AccountHolder> mAccounts;
     79 
     80     private final List<AccountAuthTokenPreparation> mAccountPermissionPreparations;
     81 
     82     private final Handler mMainHandler;
     83 
     84     private final SingleThreadedExecutor mExecutor;
     85 
     86     public MockAccountManager(Context context, Context testContext, Account... accounts) {
     87         mContext = context;
     88         // The manifest that is backing testContext needs to provide the
     89         // MockGrantCredentialsPermissionActivity.
     90         mTestContext = testContext;
     91         mMainHandler = new Handler(ThreadUtils.getUiThreadLooper());
     92         mExecutor = new SingleThreadedExecutor();
     93         mAccounts = new HashSet<AccountHolder>();
     94         mAccountPermissionPreparations = new LinkedList<AccountAuthTokenPreparation>();
     95         if (accounts != null) {
     96             for (Account account : accounts) {
     97                 mAccounts.add(AccountHolder.create().account(account).alwaysAccept(true).build());
     98             }
     99         }
    100     }
    101 
    102     private static class SingleThreadedExecutor extends ThreadPoolExecutor {
    103         public SingleThreadedExecutor() {
    104             super(1, 1, 1, TimeUnit.SECONDS, new LinkedBlockingDeque<Runnable>());
    105         }
    106     }
    107 
    108     @Override
    109     public Account[] getAccounts() {
    110         return getAccountsByType(null);
    111     }
    112 
    113     @Override
    114     public Account[] getAccountsByType(@Nullable String type) {
    115         if(!AccountManagerHelper.GOOGLE_ACCOUNT_TYPE.equals(type)) {
    116             throw new IllegalArgumentException("Invalid account type: " + type);
    117         }
    118         if (mAccounts == null) {
    119             return new Account[0];
    120         } else {
    121             Account[] accounts = new Account[mAccounts.size()];
    122             int i = 0;
    123             for (AccountHolder ah : mAccounts) {
    124                 accounts[i++] = ah.getAccount();
    125             }
    126             return accounts;
    127         }
    128     }
    129 
    130     @Override
    131     public boolean addAccountExplicitly(Account account, String password, Bundle userdata) {
    132         AccountHolder accountHolder =
    133                 AccountHolder.create().account(account).password(password).build();
    134         return addAccountHolderExplicitly(accountHolder);
    135     }
    136 
    137     public boolean addAccountHolderExplicitly(AccountHolder accountHolder) {
    138         boolean result = mAccounts.add(accountHolder);
    139         postAsyncAccountChangedEvent();
    140         return result;
    141     }
    142 
    143     @Override
    144     public AccountManagerFuture<Boolean> removeAccount(Account account,
    145             AccountManagerCallback<Boolean> callback, Handler handler) {
    146         mAccounts.remove(getAccountHolder(account));
    147         postAsyncAccountChangedEvent();
    148         return runTask(mExecutor,
    149                 new AccountManagerTask<Boolean>(handler, callback, new Callable<Boolean>() {
    150                     @Override
    151                     public Boolean call() throws Exception {
    152                         // Removal always successful.
    153                         return true;
    154                     }
    155                 }));
    156     }
    157 
    158     @Override
    159     public String getPassword(Account account) {
    160         return getAccountHolder(account).getPassword();
    161     }
    162 
    163     @Override
    164     public void setPassword(Account account, String password) {
    165         mAccounts.add(getAccountHolder(account).withPassword(password));
    166     }
    167 
    168     @Override
    169     public void clearPassword(Account account) {
    170         setPassword(account, null);
    171     }
    172 
    173     @Override
    174     public AccountManagerFuture<Bundle> confirmCredentials(Account account, Bundle bundle,
    175             Activity activity, AccountManagerCallback<Bundle> callback, Handler handler) {
    176         String password = bundle.getString(AccountManager.KEY_PASSWORD);
    177         if (password == null) {
    178             throw new IllegalArgumentException("Password is null");
    179         }
    180         final AccountHolder accountHolder = getAccountHolder(account);
    181         final boolean correctPassword = password.equals(accountHolder.getPassword());
    182         return runTask(mExecutor,
    183                 new AccountManagerTask<Bundle>(handler, callback, new Callable<Bundle>() {
    184             @Override
    185             public Bundle call() throws Exception {
    186                 Bundle result = new Bundle();
    187                 result.putString(AccountManager.KEY_ACCOUNT_NAME, accountHolder.getAccount().name);
    188                 result.putString(
    189                         AccountManager.KEY_ACCOUNT_TYPE, AccountManagerHelper.GOOGLE_ACCOUNT_TYPE);
    190                 result.putBoolean(AccountManager.KEY_BOOLEAN_RESULT, correctPassword);
    191                 return result;
    192             }
    193         }));
    194     }
    195 
    196     @Override
    197     public String blockingGetAuthToken(Account account, String authTokenType,
    198             boolean notifyAuthFailure)
    199             throws OperationCanceledException, IOException, AuthenticatorException {
    200         AccountHolder accountHolder = getAccountHolder(account);
    201         if (accountHolder.hasBeenAccepted(authTokenType)) {
    202             // If account has already been accepted we can just return the auth token.
    203             return internalGenerateAndStoreAuthToken(accountHolder, authTokenType);
    204         }
    205         AccountAuthTokenPreparation prepared = getPreparedPermission(account, authTokenType);
    206         Intent intent = newGrantCredentialsPermissionIntent(false, account, authTokenType);
    207         waitForActivity(mContext, intent);
    208         applyPreparedPermission(prepared);
    209         return internalGenerateAndStoreAuthToken(accountHolder, authTokenType);
    210     }
    211 
    212     @Override
    213     public AccountManagerFuture<Bundle> getAuthToken(Account account, String authTokenType,
    214             Bundle options, Activity activity, AccountManagerCallback<Bundle> callback,
    215             Handler handler) {
    216         return getAuthTokenFuture(account, authTokenType, activity, callback, handler);
    217     }
    218 
    219     @Override
    220     public AccountManagerFuture<Bundle> getAuthToken(Account account, String authTokenType,
    221             boolean notifyAuthFailure, AccountManagerCallback<Bundle> callback, Handler handler) {
    222         return getAuthTokenFuture(account, authTokenType, null, callback, handler);
    223     }
    224 
    225     private AccountManagerFuture<Bundle> getAuthTokenFuture(Account account, String authTokenType,
    226             Activity activity, AccountManagerCallback<Bundle> callback, Handler handler) {
    227         final AccountHolder ah = getAccountHolder(account);
    228         if (ah.hasBeenAccepted(authTokenType)) {
    229             final String authToken = internalGenerateAndStoreAuthToken(ah, authTokenType);
    230             return runTask(mExecutor,
    231                     new AccountManagerAuthTokenTask(activity, handler, callback,
    232                             account, authTokenType,
    233                             new Callable<Bundle>() {
    234                         @Override
    235                         public Bundle call() throws Exception {
    236                             return getAuthTokenBundle(ah.getAccount(), authToken);
    237                         }
    238                     }));
    239         } else {
    240             Log.d(TAG, "getAuthTokenFuture: Account " + ah.getAccount() +
    241                     " is asking for permission for " + authTokenType);
    242             final Intent intent = newGrantCredentialsPermissionIntent(
    243                     activity != null, account, authTokenType);
    244             return runTask(mExecutor,
    245                     new AccountManagerAuthTokenTask(activity, handler, callback,
    246                             account, authTokenType,
    247                             new Callable<Bundle>() {
    248                         @Override
    249                         public Bundle call() throws Exception {
    250                             Bundle result = new Bundle();
    251                             result.putParcelable(AccountManager.KEY_INTENT, intent);
    252                             return result;
    253                         }
    254                     }));
    255         }
    256     }
    257 
    258     private static Bundle getAuthTokenBundle(Account account, String authToken) {
    259         Bundle result = new Bundle();
    260         result.putString(AccountManager.KEY_AUTHTOKEN, authToken);
    261         result.putString(AccountManager.KEY_ACCOUNT_NAME, account.name);
    262         result.putString(AccountManager.KEY_ACCOUNT_TYPE, account.type);
    263         return result;
    264     }
    265 
    266     private String internalGenerateAndStoreAuthToken(AccountHolder ah, String authTokenType) {
    267         synchronized (mAccounts) {
    268             // Some tests register auth tokens with value null, and those should be preserved.
    269             if (!ah.hasAuthTokenRegistered(authTokenType) &&
    270                     ah.getAuthToken(authTokenType) == null) {
    271                 // No authtoken registered. Need to create one.
    272                 String authToken = UUID.randomUUID().toString();
    273                 Log.d(TAG, "Created new auth token for " + ah.getAccount() +
    274                         ": autTokenType = " + authTokenType + ", authToken = " + authToken);
    275                 ah = ah.withAuthToken(authTokenType, authToken);
    276                 mAccounts.add(ah);
    277             }
    278         }
    279         return ah.getAuthToken(authTokenType);
    280     }
    281 
    282     @Override
    283     public String peekAuthToken(Account account, String authTokenType) {
    284         return getAccountHolder(account).getAuthToken(authTokenType);
    285     }
    286 
    287     @Override
    288     public void invalidateAuthToken(String accountType, String authToken) {
    289         if(!AccountManagerHelper.GOOGLE_ACCOUNT_TYPE.equals(accountType)) {
    290             throw new IllegalArgumentException("Invalid account type: " + accountType);
    291         }
    292         if (authToken == null) {
    293             throw new IllegalArgumentException("AuthToken can not be null");
    294         }
    295         for (AccountHolder ah : mAccounts) {
    296             if (ah.removeAuthToken(authToken)) {
    297                 break;
    298             }
    299         }
    300     }
    301 
    302     @Override
    303     public AuthenticatorDescription[] getAuthenticatorTypes() {
    304         AuthenticatorDescription googleAuthenticator = new AuthenticatorDescription(
    305                 AccountManagerHelper.GOOGLE_ACCOUNT_TYPE, "p1", 0, 0, 0, 0);
    306 
    307         return new AuthenticatorDescription[] { googleAuthenticator };
    308     }
    309 
    310     public void prepareAllowAppPermission(Account account, String authTokenType) {
    311         addPreparedAppPermission(new AccountAuthTokenPreparation(account, authTokenType, true));
    312     }
    313 
    314     public void prepareDenyAppPermission(Account account, String authTokenType) {
    315         addPreparedAppPermission(new AccountAuthTokenPreparation(account, authTokenType, false));
    316     }
    317 
    318     private void addPreparedAppPermission(AccountAuthTokenPreparation accountAuthTokenPreparation) {
    319         Log.d(TAG, "Adding " + accountAuthTokenPreparation);
    320         mAccountPermissionPreparations.add(accountAuthTokenPreparation);
    321     }
    322 
    323     private AccountAuthTokenPreparation getPreparedPermission(Account account,
    324             String authTokenType) {
    325         for (AccountAuthTokenPreparation accountPrep : mAccountPermissionPreparations) {
    326             if (accountPrep.getAccount().equals(account) &&
    327                     accountPrep.getAuthTokenType().equals(authTokenType)) {
    328                 return accountPrep;
    329             }
    330         }
    331         return null;
    332     }
    333 
    334     private void applyPreparedPermission(AccountAuthTokenPreparation prep) {
    335         if (prep != null) {
    336             Log.d(TAG, "Applying " + prep);
    337             mAccountPermissionPreparations.remove(prep);
    338             mAccounts.add(getAccountHolder(prep.getAccount()).withHasBeenAccepted(
    339                     prep.getAuthTokenType(), prep.isAllowed()));
    340         }
    341     }
    342 
    343     private Intent newGrantCredentialsPermissionIntent(boolean hasActivity, Account account,
    344             String authTokenType) {
    345         Intent intent = new Intent();
    346         intent.setComponent(new ComponentName(mTestContext,
    347                 MockGrantCredentialsPermissionActivity.class.getCanonicalName()));
    348         intent.putExtra(MockGrantCredentialsPermissionActivity.ACCOUNT, account);
    349         intent.putExtra(MockGrantCredentialsPermissionActivity.AUTH_TOKEN_TYPE, authTokenType);
    350         if (!hasActivity) {
    351             // No activity provided, so we help the caller by adding the new task flag
    352             intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
    353         }
    354         return intent;
    355     }
    356 
    357     private AccountHolder getAccountHolder(Account account) {
    358         if (account == null) {
    359             throw new IllegalArgumentException("Account can not be null");
    360         }
    361         for (AccountHolder accountHolder : mAccounts) {
    362             if (account.equals(accountHolder.getAccount())) {
    363                 return accountHolder;
    364             }
    365         }
    366         throw new IllegalArgumentException("Can not find AccountHolder for account " + account);
    367     }
    368 
    369     private static <T> AccountManagerFuture<T> runTask(Executor executorService,
    370             AccountManagerTask<T> accountManagerBundleTask) {
    371         executorService.execute(accountManagerBundleTask);
    372         return accountManagerBundleTask;
    373     }
    374 
    375     private class AccountManagerTask<T> extends FutureTask<T> implements AccountManagerFuture<T> {
    376 
    377         protected final Handler mHandler;
    378 
    379         protected final AccountManagerCallback<T> mCallback;
    380 
    381         protected final Callable<T> mCallable;
    382 
    383         public AccountManagerTask(Handler handler,
    384                 AccountManagerCallback<T> callback, Callable<T> callable) {
    385             super(new Callable<T>() {
    386                 @Override
    387                 public T call() throws Exception {
    388                     throw new IllegalStateException("this should never be called, "
    389                             + "but call must be overridden.");
    390                 }
    391             });
    392             mHandler = handler;
    393             mCallback = callback;
    394             mCallable = callable;
    395         }
    396 
    397         private T internalGetResult(long timeout, TimeUnit unit)
    398                 throws OperationCanceledException, IOException, AuthenticatorException {
    399             try {
    400                 if (timeout == -1) {
    401                     return get();
    402                 } else {
    403                     return get(timeout, unit);
    404                 }
    405             } catch (CancellationException e) {
    406                 throw new OperationCanceledException();
    407             } catch (TimeoutException e) {
    408                 // Fall through and cancel.
    409             } catch (InterruptedException e) {
    410                 // Fall through and cancel.
    411             } catch (ExecutionException e) {
    412                 final Throwable cause = e.getCause();
    413                 if (cause instanceof IOException) {
    414                     throw (IOException) cause;
    415                 } else if (cause instanceof UnsupportedOperationException) {
    416                     throw new AuthenticatorException(cause);
    417                 } else if (cause instanceof AuthenticatorException) {
    418                     throw (AuthenticatorException) cause;
    419                 } else if (cause instanceof RuntimeException) {
    420                     throw (RuntimeException) cause;
    421                 } else if (cause instanceof Error) {
    422                     throw (Error) cause;
    423                 } else {
    424                     throw new IllegalStateException(cause);
    425                 }
    426             } finally {
    427                 cancel(true /* Interrupt if running. */);
    428             }
    429             throw new OperationCanceledException();
    430         }
    431 
    432         @Override
    433         public T getResult()
    434                 throws OperationCanceledException, IOException, AuthenticatorException {
    435             return internalGetResult(-1, null);
    436         }
    437 
    438         @Override
    439         public T getResult(long timeout, TimeUnit unit)
    440                 throws OperationCanceledException, IOException, AuthenticatorException {
    441             return internalGetResult(timeout, unit);
    442         }
    443 
    444         @Override
    445         public void run() {
    446             try {
    447                 set(mCallable.call());
    448             } catch (Exception e) {
    449                 setException(e);
    450             }
    451         }
    452 
    453         @Override
    454         protected void done() {
    455             if (mCallback != null) {
    456                 postToHandler(getHandler(), mCallback, this);
    457             }
    458         }
    459 
    460         protected Handler getHandler() {
    461             return mHandler == null ? mMainHandler : mHandler;
    462         }
    463 
    464     }
    465 
    466     private static <T> void postToHandler(Handler handler, final AccountManagerCallback<T> callback,
    467             final AccountManagerFuture<T> future) {
    468         handler.post(new Runnable() {
    469             @Override
    470             public void run() {
    471                 callback.run(future);
    472             }
    473         });
    474     }
    475 
    476     private class AccountManagerAuthTokenTask extends AccountManagerTask<Bundle> {
    477 
    478         private final Activity mActivity;
    479 
    480         private final AccountAuthTokenPreparation mAccountAuthTokenPreparation;
    481 
    482         private final Account mAccount;
    483 
    484         private final String mAuthTokenType;
    485 
    486         public AccountManagerAuthTokenTask(Activity activity, Handler handler,
    487                 AccountManagerCallback<Bundle> callback,
    488                 Account account, String authTokenType,
    489                 Callable<Bundle> callable) {
    490             super(handler, callback, callable);
    491             mActivity = activity;
    492             mAccountAuthTokenPreparation = getPreparedPermission(account, authTokenType);
    493             mAccount = account;
    494             mAuthTokenType = authTokenType;
    495         }
    496 
    497         @Override
    498         public void run() {
    499             try {
    500                 Bundle bundle = mCallable.call();
    501                 Intent intent = bundle.getParcelable(AccountManager.KEY_INTENT);
    502                 if (intent != null) {
    503                     // Start the intent activity and wait for it to finish.
    504                     if (mActivity != null) {
    505                         waitForActivity(mActivity, intent);
    506                     } else {
    507                         waitForActivity(mContext, intent);
    508                     }
    509                     if (mAccountAuthTokenPreparation == null) {
    510                         throw new IllegalStateException("No account preparation ready for " +
    511                                 mAccount + ", authTokenType = " + mAuthTokenType +
    512                                 ". Add a call to either prepareGrantAppPermission(...) or " +
    513                                 "prepareRevokeAppPermission(...) in your test before asking for " +
    514                                 "an auth token");
    515                     } else {
    516                         // We have shown the Allow/Deny activity, and it has gone away. We can now
    517                         // apply the pre-stored permission.
    518                         applyPreparedPermission(mAccountAuthTokenPreparation);
    519                         generateResult(getAccountHolder(mAccount), mAuthTokenType);
    520                     }
    521                 } else {
    522                     set(bundle);
    523                 }
    524             } catch (Exception e) {
    525                 setException(e);
    526             }
    527         }
    528 
    529         private void generateResult(AccountHolder accountHolder, String authTokenType)
    530                 throws OperationCanceledException {
    531             if (accountHolder.hasBeenAccepted(authTokenType)) {
    532                 String authToken = internalGenerateAndStoreAuthToken(accountHolder, authTokenType);
    533                 // Return a valid auth token.
    534                 set(getAuthTokenBundle(accountHolder.getAccount(), authToken));
    535             } else {
    536                 // Throw same exception as when user clicks "Deny".
    537                 throw new OperationCanceledException("User denied request");
    538             }
    539         }
    540     }
    541 
    542     /**
    543      * This method starts {@link MockGrantCredentialsPermissionActivity} and waits for it
    544      * to be started before it returns.
    545      *
    546      * @param context the context to start the intent in
    547      * @param intent the intent to use to start MockGrantCredentialsPermissionActivity
    548      */
    549     private void waitForActivity(Context context, Intent intent) {
    550         final Object mutex = new Object();
    551         BroadcastReceiver receiver = new BroadcastReceiver() {
    552             @Override
    553             public void onReceive(Context context, Intent intent) {
    554                 synchronized (mutex) {
    555                     mutex.notifyAll();
    556                 }
    557             }
    558         };
    559         if (!MockGrantCredentialsPermissionActivity.class.getCanonicalName().
    560                 equals(intent.getComponent().getClassName())) {
    561             throw new IllegalArgumentException("Can only wait for "
    562                     + "MockGrantCredentialsPermissionActivity");
    563         }
    564         mContext.registerReceiver(receiver, new IntentFilter(MUTEX_WAIT_ACTION));
    565         context.startActivity(intent);
    566         try {
    567             Log.d(TAG, "Waiting for broadcast of " + MUTEX_WAIT_ACTION);
    568             synchronized (mutex) {
    569                 mutex.wait(WAIT_TIME_FOR_GRANT_BROADCAST_MS);
    570             }
    571         } catch (InterruptedException e) {
    572             throw new IllegalStateException("Got unexpected InterruptedException");
    573         }
    574         Log.d(TAG, "Got broadcast of " + MUTEX_WAIT_ACTION);
    575         mContext.unregisterReceiver(receiver);
    576     }
    577 
    578     private void postAsyncAccountChangedEvent() {
    579         // Mimic that this does not happen on the main thread.
    580         new AsyncTask<Void, Void, Void>() {
    581             @Override
    582             protected Void doInBackground(Void... params) {
    583                 mContext.sendBroadcast(new Intent(AccountManager.LOGIN_ACCOUNTS_CHANGED_ACTION));
    584                 return null;
    585             }
    586         }.execute();
    587     }
    588 
    589     /**
    590      * Internal class for storage of prepared account auth token permissions.
    591      *
    592      * This is used internally by {@link MockAccountManager} to mock the same behavior as clicking
    593      * Allow/Deny in the Android {@link GrantCredentialsPermissionActivity}.
    594      */
    595     private static class AccountAuthTokenPreparation {
    596 
    597         private final Account mAccount;
    598 
    599         private final String mAuthTokenType;
    600 
    601         private final boolean mAllowed;
    602 
    603         private AccountAuthTokenPreparation(Account account, String authTokenType,
    604                 boolean allowed) {
    605             mAccount = account;
    606             mAuthTokenType = authTokenType;
    607             mAllowed = allowed;
    608         }
    609 
    610         public Account getAccount() {
    611             return mAccount;
    612         }
    613 
    614         public String getAuthTokenType() {
    615             return mAuthTokenType;
    616         }
    617 
    618         public boolean isAllowed() {
    619             return mAllowed;
    620         }
    621 
    622         @Override
    623         public String toString() {
    624             return "AccountAuthTokenPreparation{" +
    625                     "mAccount=" + mAccount +
    626                     ", mAuthTokenType='" + mAuthTokenType + '\'' +
    627                     ", mAllowed=" + mAllowed +
    628                     '}';
    629         }
    630     }
    631 }
    632