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