OLD | NEW |
| (Empty) |
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.AuthenticatorDescription; | |
10 import android.content.Context; | |
11 import android.content.Intent; | |
12 import android.os.AsyncTask; | |
13 | |
14 import org.chromium.base.Callback; | |
15 import org.chromium.base.Log; | |
16 import org.chromium.base.VisibleForTesting; | |
17 import org.chromium.sync.signin.AccountManagerDelegate; | |
18 import org.chromium.sync.signin.AccountManagerHelper; | |
19 | |
20 import java.util.ArrayList; | |
21 import java.util.HashSet; | |
22 import java.util.Set; | |
23 import java.util.UUID; | |
24 import java.util.concurrent.LinkedBlockingDeque; | |
25 import java.util.concurrent.ThreadPoolExecutor; | |
26 import java.util.concurrent.TimeUnit; | |
27 | |
28 /** | |
29 * The MockAccountManager helps out if you want to mock out all calls to the And
roid AccountManager. | |
30 * | |
31 * You should provide a set of accounts as a constructor argument, or use the mo
re direct approach | |
32 * and provide an array of AccountHolder objects. | |
33 * | |
34 * Currently, this implementation supports adding and removing accounts, handlin
g credentials | |
35 * (including confirming them), and handling of dummy auth tokens. | |
36 * | |
37 * If you want to auto-approve a given authtokentype, use addAccountHolderExplic
itly(...) with | |
38 * an AccountHolder you have built with hasBeenAccepted("yourAuthTokenType", tru
e). | |
39 * | |
40 * If you want to auto-approve all auth token types for a given account, use the
{@link | |
41 * AccountHolder} builder method alwaysAccept(true). | |
42 */ | |
43 public class MockAccountManager implements AccountManagerDelegate { | |
44 | |
45 private static final String TAG = "MockAccountManager"; | |
46 | |
47 protected final Context mContext; | |
48 | |
49 private final Set<AccountHolder> mAccounts; | |
50 | |
51 // Tracks the number of in-progress getAccountsByType() tasks so that tests
can wait for | |
52 // their completion. | |
53 private final ZeroCounter mGetAccountsTaskCounter; | |
54 | |
55 @VisibleForTesting | |
56 public MockAccountManager(Context context, Context testContext, Account... a
ccounts) { | |
57 mContext = context; | |
58 mGetAccountsTaskCounter = new ZeroCounter(); | |
59 mAccounts = new HashSet<AccountHolder>(); | |
60 if (accounts != null) { | |
61 for (Account account : accounts) { | |
62 mAccounts.add(AccountHolder.create().account(account).alwaysAcce
pt(true).build()); | |
63 } | |
64 } | |
65 } | |
66 | |
67 private static class SingleThreadedExecutor extends ThreadPoolExecutor { | |
68 public SingleThreadedExecutor() { | |
69 super(1, 1, 1, TimeUnit.SECONDS, new LinkedBlockingDeque<Runnable>()
); | |
70 } | |
71 } | |
72 | |
73 @Override | |
74 public Account[] getAccountsByType(String type) { | |
75 if (!AccountManagerHelper.GOOGLE_ACCOUNT_TYPE.equals(type)) { | |
76 throw new IllegalArgumentException("Invalid account type: " + type); | |
77 } | |
78 if (mAccounts == null) { | |
79 return new Account[0]; | |
80 } else { | |
81 ArrayList<Account> validAccounts = new ArrayList<Account>(); | |
82 for (AccountHolder ah : mAccounts) { | |
83 if (type.equals(ah.getAccount().type)) { | |
84 validAccounts.add(ah.getAccount()); | |
85 } | |
86 } | |
87 | |
88 Account[] accounts = new Account[validAccounts.size()]; | |
89 int i = 0; | |
90 for (Account account : validAccounts) { | |
91 accounts[i++] = account; | |
92 } | |
93 return accounts; | |
94 } | |
95 } | |
96 | |
97 @Override | |
98 public void getAccountsByType(final String type, final Callback<Account[]> c
allback) { | |
99 mGetAccountsTaskCounter.increment(); | |
100 new AsyncTask<Void, Void, Account[]>() { | |
101 @Override | |
102 protected Account[] doInBackground(Void... params) { | |
103 return getAccountsByType(type); | |
104 } | |
105 | |
106 @Override | |
107 protected void onPostExecute(Account[] accounts) { | |
108 callback.onResult(accounts); | |
109 mGetAccountsTaskCounter.decrement(); | |
110 } | |
111 }.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); | |
112 } | |
113 | |
114 @VisibleForTesting | |
115 public void waitForGetAccountsTask() throws InterruptedException { | |
116 // Wait until all tasks are done because we don't know which is being wa
ited for. | |
117 mGetAccountsTaskCounter.waitUntilZero(); | |
118 } | |
119 | |
120 @VisibleForTesting | |
121 public boolean addAccountHolderExplicitly(AccountHolder accountHolder) { | |
122 return addAccountHolderExplicitly(accountHolder, false); | |
123 } | |
124 | |
125 /** | |
126 * Add an AccountHolder directly. | |
127 * | |
128 * @param accountHolder the account holder to add | |
129 * @param broadcastEvent whether to broadcast an AccountChangedEvent | |
130 * @return whether the account holder was added successfully | |
131 */ | |
132 public boolean addAccountHolderExplicitly(AccountHolder accountHolder, | |
133 boolean broadcastEvent) { | |
134 boolean result = mAccounts.add(accountHolder); | |
135 if (broadcastEvent) { | |
136 postAsyncAccountChangedEvent(); | |
137 } | |
138 return result; | |
139 } | |
140 | |
141 @VisibleForTesting | |
142 public boolean removeAccountHolderExplicitly(AccountHolder accountHolder) { | |
143 return removeAccountHolderExplicitly(accountHolder, false); | |
144 } | |
145 | |
146 /** | |
147 * Remove an AccountHolder directly. | |
148 * | |
149 * @param accountHolder the account holder to remove | |
150 * @param broadcastEvent whether to broadcast an AccountChangedEvent | |
151 * @return whether the account holder was removed successfully | |
152 */ | |
153 public boolean removeAccountHolderExplicitly(AccountHolder accountHolder, | |
154 boolean broadcastEvent) { | |
155 boolean result = mAccounts.remove(accountHolder); | |
156 if (broadcastEvent) { | |
157 postAsyncAccountChangedEvent(); | |
158 } | |
159 return result; | |
160 } | |
161 | |
162 @Override | |
163 public String getAuthToken(Account account, String authTokenScope) { | |
164 AccountHolder ah = getAccountHolder(account); | |
165 assert ah.hasBeenAccepted(authTokenScope); | |
166 synchronized (mAccounts) { | |
167 // Some tests register auth tokens with value null, and those should
be preserved. | |
168 if (!ah.hasAuthTokenRegistered(authTokenScope) | |
169 && ah.getAuthToken(authTokenScope) == null) { | |
170 // No authtoken registered. Need to create one. | |
171 String authToken = UUID.randomUUID().toString(); | |
172 Log.d(TAG, "Created new auth token for " + ah.getAccount() + ":
authTokenScope = " | |
173 + authTokenScope + ", authToken = " + authToken)
; | |
174 ah = ah.withAuthToken(authTokenScope, authToken); | |
175 mAccounts.add(ah); | |
176 } | |
177 } | |
178 return ah.getAuthToken(authTokenScope); | |
179 } | |
180 | |
181 @Override | |
182 public void invalidateAuthToken(String authToken) { | |
183 if (authToken == null) { | |
184 throw new IllegalArgumentException("AuthToken can not be null"); | |
185 } | |
186 for (AccountHolder ah : mAccounts) { | |
187 if (ah.removeAuthToken(authToken)) { | |
188 break; | |
189 } | |
190 } | |
191 } | |
192 | |
193 @Override | |
194 public AuthenticatorDescription[] getAuthenticatorTypes() { | |
195 AuthenticatorDescription googleAuthenticator = new AuthenticatorDescript
ion( | |
196 AccountManagerHelper.GOOGLE_ACCOUNT_TYPE, "p1", 0, 0, 0, 0); | |
197 | |
198 return new AuthenticatorDescription[] { googleAuthenticator }; | |
199 } | |
200 | |
201 @Override | |
202 public void hasFeatures( | |
203 Account account, final String[] features, final Callback<Boolean> ca
llback) { | |
204 final AccountHolder accountHolder = getAccountHolder(account); | |
205 accountHolder.addFeaturesCallback(new Runnable() { | |
206 @Override | |
207 public void run() { | |
208 Set<String> accountFeatures = accountHolder.getFeatures(); | |
209 boolean hasAllFeatures = true; | |
210 for (String feature : features) { | |
211 if (!accountFeatures.contains(feature)) { | |
212 Log.d(TAG, accountFeatures + " does not contain " + feat
ure); | |
213 hasAllFeatures = false; | |
214 } | |
215 } | |
216 callback.onResult(hasAllFeatures); | |
217 } | |
218 }); | |
219 } | |
220 | |
221 private AccountHolder getAccountHolder(Account account) { | |
222 if (account == null) { | |
223 throw new IllegalArgumentException("Account can not be null"); | |
224 } | |
225 for (AccountHolder accountHolder : mAccounts) { | |
226 if (account.equals(accountHolder.getAccount())) { | |
227 return accountHolder; | |
228 } | |
229 } | |
230 throw new IllegalArgumentException("Can not find AccountHolder for accou
nt " + account); | |
231 } | |
232 | |
233 private void postAsyncAccountChangedEvent() { | |
234 // Mimic that this does not happen on the main thread. | |
235 new AsyncTask<Void, Void, Void>() { | |
236 @Override | |
237 protected Void doInBackground(Void... params) { | |
238 mContext.sendBroadcast(new Intent(AccountManager.LOGIN_ACCOUNTS_
CHANGED_ACTION)); | |
239 return null; | |
240 } | |
241 }.execute(); | |
242 } | |
243 | |
244 /** | |
245 * Simple concurrency helper class for waiting until a resource count become
s zero. | |
246 */ | |
247 private static class ZeroCounter { | |
248 private static final int WAIT_TIMEOUT_MS = 10000; | |
249 | |
250 private final Object mLock = new Object(); | |
251 private int mCount = 0; | |
252 | |
253 public void increment() { | |
254 synchronized (mLock) { | |
255 mCount++; | |
256 } | |
257 } | |
258 | |
259 public void decrement() { | |
260 synchronized (mLock) { | |
261 if (--mCount == 0) { | |
262 mLock.notifyAll(); | |
263 } | |
264 } | |
265 } | |
266 | |
267 public void waitUntilZero() throws InterruptedException { | |
268 synchronized (mLock) { | |
269 while (mCount != 0) { | |
270 mLock.wait(WAIT_TIMEOUT_MS); | |
271 } | |
272 } | |
273 } | |
274 } | |
275 } | |
OLD | NEW |