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.content.Context;
25 import android.content.Intent;
26 import android.os.Bundle;
27 import android.util.Log;
28 
29 import java.util.ArrayList;
30 import java.util.concurrent.atomic.AtomicBoolean;
31 import java.util.concurrent.atomic.AtomicInteger;
32 
33 /**
34  * A simple Mock Account Authenticator
35  */
36 public class MockAccountAuthenticator extends AbstractAccountAuthenticator {
37     private static String TAG = "AccountManagerTest";
38 
39     public static String KEY_ACCOUNT_INFO = "key_account_info";
40     public static String KEY_ACCOUNT_AUTHENTICATOR_RESPONSE = "key_account_authenticator_response";
41     public static String ACCOUNT_NAME_FOR_NEW_REMOVE_API = "call new removeAccount api";
42     public static String ACCOUNT_NAME_FOR_DEFAULT_IMPL = "call super api";
43     // Key for triggering return intent flow
44     public static String KEY_RETURN_INTENT = "return an intent";
45     public static String ACCOUNT_NAME_FOR_NEW_REMOVE_API1 = "call new removeAccount api";
46 
47     private final Context mContext;
48     private final AtomicInteger mTokenCounter  = new AtomicInteger(0);
49     private final AtomicBoolean mIsRecentlyCalled = new AtomicBoolean(false);
50 
51     AccountAuthenticatorResponse mResponse;
52     String mAccountType;
53     String mAuthTokenType;
54     String[] mRequiredFeatures;
55     public Bundle mOptionsUpdateCredentials;
56     public Bundle mOptionsConfirmCredentials;
57     public Bundle mOptionsAddAccount;
58     public Bundle mOptionsGetAuthToken;
59     Account mAccount;
60     String[] mFeatures;
61 
62     final ArrayList<String> mockFeatureList = new ArrayList<String>();
63     private final long mTokenDurationMillis = 1000; // 1 second
64 
MockAccountAuthenticator(Context context)65     public MockAccountAuthenticator(Context context) {
66         super(context);
67         mContext = context;
68 
69         // Create some mock features
70         mockFeatureList.add(AccountManagerTest.FEATURE_1);
71         mockFeatureList.add(AccountManagerTest.FEATURE_2);
72     }
73 
getTokenDurationMillis()74     public long getTokenDurationMillis() {
75         return mTokenDurationMillis;
76     }
77 
isRecentlyCalled()78     public boolean isRecentlyCalled() {
79         return mIsRecentlyCalled.getAndSet(false);
80     }
81 
getLastTokenServed()82     public String getLastTokenServed() {
83         return Integer.toString(mTokenCounter.get());
84     }
85 
getResponse()86     public AccountAuthenticatorResponse getResponse() {
87         return mResponse;
88     }
89 
getAccountType()90     public String getAccountType() {
91         return mAccountType;
92     }
93 
getAuthTokenType()94     public String getAuthTokenType() {
95         return mAuthTokenType;
96     }
97 
getRequiredFeatures()98     public String[] getRequiredFeatures() {
99         return mRequiredFeatures;
100     }
101 
getAccount()102     public Account getAccount() {
103         return mAccount;
104     }
105 
getFeatures()106     public String[] getFeatures() {
107         return mFeatures;
108     }
109 
clearData()110     public void clearData() {
111         mResponse = null;
112         mAccountType = null;
113         mAuthTokenType = null;
114         mRequiredFeatures = null;
115         mOptionsUpdateCredentials = null;
116         mOptionsAddAccount = null;
117         mOptionsGetAuthToken = null;
118         mOptionsConfirmCredentials = null;
119         mAccount = null;
120         mFeatures = null;
121     }
122 
callAccountAuthenticated()123     public void callAccountAuthenticated() {
124         AccountManager am = AccountManager.get(mContext);
125         am.notifyAccountAuthenticated(mAccount);
126     }
127 
callSetPassword()128     public void callSetPassword() {
129         AccountManager am = AccountManager.get(mContext);
130         am.setPassword(mAccount, "password");
131     }
132 
createResultBundle()133     private Bundle createResultBundle() {
134         Bundle result = new Bundle();
135         result.putString(AccountManager.KEY_ACCOUNT_NAME, AccountManagerTest.ACCOUNT_NAME);
136         result.putString(AccountManager.KEY_ACCOUNT_TYPE, AccountManagerTest.ACCOUNT_TYPE);
137         result.putString(
138                 AccountManager.KEY_AUTHTOKEN,
139                 Integer.toString(mTokenCounter.incrementAndGet()));
140         return result;
141     }
142 
143     /**
144      * Adds an account of the specified accountType.
145      */
146     @Override
addAccount(AccountAuthenticatorResponse response, String accountType, String authTokenType, String[] requiredFeatures, Bundle options)147     public Bundle addAccount(AccountAuthenticatorResponse response, String accountType,
148             String authTokenType, String[] requiredFeatures, Bundle options)
149             throws NetworkErrorException {
150         this.mResponse = response;
151         this.mAccountType = accountType;
152         this.mAuthTokenType = authTokenType;
153         this.mRequiredFeatures = requiredFeatures;
154         this.mOptionsAddAccount = options;
155         AccountManager am = AccountManager.get(mContext);
156         am.addAccountExplicitly(AccountManagerTest.ACCOUNT, "fakePassword", null);
157         return createResultBundle();
158     }
159 
160     /**
161      * Update the locally stored credentials for an account.
162      */
163     @Override
updateCredentials(AccountAuthenticatorResponse response, Account account, String authTokenType, Bundle options)164     public Bundle updateCredentials(AccountAuthenticatorResponse response, Account account,
165             String authTokenType, Bundle options) throws NetworkErrorException {
166         this.mResponse = response;
167         this.mAccount = account;
168         this.mAuthTokenType = authTokenType;
169         this.mOptionsUpdateCredentials = options;
170         return createResultBundle();
171     }
172 
173     /**
174      * Returns a Bundle that contains the Intent of the activity that can be used to edit the
175      * properties. In order to indicate success the activity should call response.setResult()
176      * with a non-null Bundle.
177      */
178     @Override
editProperties(AccountAuthenticatorResponse response, String accountType)179     public Bundle editProperties(AccountAuthenticatorResponse response, String accountType) {
180         this.mResponse = response;
181         this.mAccountType = accountType;
182         return createResultBundle();
183     }
184 
185     /**
186      * Checks that the user knows the credentials of an account.
187      */
188     @Override
confirmCredentials(AccountAuthenticatorResponse response, Account account, Bundle options)189     public Bundle confirmCredentials(AccountAuthenticatorResponse response, Account account,
190             Bundle options) throws NetworkErrorException {
191         this.mResponse = response;
192         this.mAccount = account;
193         this.mOptionsConfirmCredentials = options;
194         Bundle result = new Bundle();
195         if (options.containsKey(KEY_RETURN_INTENT)) {
196             Intent intent = new Intent();
197             intent.setClassName("android.accounts.cts", "android.accounts.cts.AccountDummyActivity");
198             result.putParcelable(AccountManager.KEY_INTENT, intent);
199         } else {
200             result.putBoolean(AccountManager.KEY_BOOLEAN_RESULT, true);
201         }
202 
203         return result;
204     }
205 
206     /**
207      * Gets the authtoken for an account.
208      */
209     @Override
getAuthToken( AccountAuthenticatorResponse response, Account account, String authTokenType, Bundle options)210     public Bundle getAuthToken(
211             AccountAuthenticatorResponse response,
212             Account account,
213             String authTokenType,
214             Bundle options) throws NetworkErrorException {
215         Log.w(TAG, "MockAuth - getAuthToken@" + System.currentTimeMillis());
216         mIsRecentlyCalled.set(true);
217         this.mResponse = response;
218         this.mAccount = account;
219         this.mAuthTokenType = authTokenType;
220         this.mOptionsGetAuthToken = options;
221         Bundle result = new Bundle();
222 
223         result.putString(AccountManager.KEY_ACCOUNT_NAME, account.name);
224         result.putString(AccountManager.KEY_ACCOUNT_TYPE, account.type);
225         String token;
226         if (AccountManagerTest.AUTH_EXPIRING_TOKEN_TYPE.equals(authTokenType)) {
227             /*
228              * The resultant token should simply be the expiration timestamp. E.g. the time after
229              * which getting a new AUTH_EXPIRING_TOKEN_TYPE typed token will return a different
230              * value.
231              */
232             long expiry = System.currentTimeMillis() + mTokenDurationMillis;
233             result.putLong(AbstractAccountAuthenticator.KEY_CUSTOM_TOKEN_EXPIRY, expiry);
234         }
235         result.putString(
236                 AccountManager.KEY_AUTHTOKEN,
237                 Integer.toString(mTokenCounter.incrementAndGet()));
238         return result;
239     }
240 
241     /**
242      * Ask the authenticator for a localized label for the given authTokenType.
243      */
244     @Override
getAuthTokenLabel(String authTokenType)245     public String getAuthTokenLabel(String authTokenType) {
246         this.mAuthTokenType = authTokenType;
247         return AccountManagerTest.AUTH_TOKEN_LABEL;
248     }
249 
250     /**
251      * Checks if the account supports all the specified authenticator specific features.
252      */
253     @Override
hasFeatures(AccountAuthenticatorResponse response, Account account, String[] features)254     public Bundle hasFeatures(AccountAuthenticatorResponse response, Account account,
255             String[] features) throws NetworkErrorException {
256 
257         this.mResponse = response;
258         this.mAccount = account;
259         this.mFeatures = features;
260 
261         Bundle result = new Bundle();
262         if (null == features) {
263             result.putBoolean(AccountManager.KEY_BOOLEAN_RESULT, true);
264         }
265         else {
266             boolean booleanResult = true;
267             for (String feature: features) {
268                 if (!mockFeatureList.contains(feature)) {
269                     booleanResult = false;
270                     break;
271                 }
272             }
273             result.putBoolean(AccountManager.KEY_BOOLEAN_RESULT, booleanResult);
274         }
275         return result;
276     }
277 
278     @Override
getAccountRemovalAllowed(AccountAuthenticatorResponse response, Account account)279     public Bundle getAccountRemovalAllowed(AccountAuthenticatorResponse response,
280             Account account) throws NetworkErrorException {
281         final Bundle result = new Bundle();
282         if (ACCOUNT_NAME_FOR_NEW_REMOVE_API.equals(account.name)) {
283             Intent intent = AccountRemovalDummyActivity.createIntent(mContext);
284             // Pass in the authenticator response, so that account removal can
285             // be
286             // completed
287             intent.putExtra(KEY_ACCOUNT_AUTHENTICATOR_RESPONSE, response);
288             intent.putExtra(KEY_ACCOUNT_INFO, account);
289             result.putParcelable(AccountManager.KEY_INTENT, intent);
290             // Adding this following line to reject account installation
291             // requests
292             // coming from old removeAccount API.
293             result.putBoolean(AccountManager.KEY_BOOLEAN_RESULT, false);
294         } else if (ACCOUNT_NAME_FOR_DEFAULT_IMPL.equals(account.name)) {
295             return super.getAccountRemovalAllowed(response, account);
296         } else {
297             result.putBoolean(AccountManager.KEY_BOOLEAN_RESULT, true);
298         }
299         return result;
300     }
301 
302     @Override
addAccountFromCredentials(final AccountAuthenticatorResponse response, Account account, Bundle accountCredentials)303     public Bundle addAccountFromCredentials(final AccountAuthenticatorResponse response,
304             Account account,
305             Bundle accountCredentials) throws NetworkErrorException {
306         return super.addAccountFromCredentials(response, account, accountCredentials);
307     }
308 
309     @Override
getAccountCredentialsForCloning(final AccountAuthenticatorResponse response, final Account account)310     public Bundle getAccountCredentialsForCloning(final AccountAuthenticatorResponse response,
311             final Account account) throws NetworkErrorException {
312         return super.getAccountCredentialsForCloning(response, account);
313     }
314 
315 }
316