1 | // Copyright (c) 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.notifier; |
6 | |
7 | import android.accounts.Account; |
8 | import android.app.Activity; |
9 | import android.content.ComponentName; |
10 | import android.content.Context; |
11 | import android.content.Intent; |
12 | import android.content.pm.PackageManager; |
13 | import android.test.InstrumentationTestCase; |
14 | import android.test.suitebuilder.annotation.SmallTest; |
15 | |
16 | import org.chromium.base.ActivityStatus; |
17 | import org.chromium.base.CollectionUtil; |
18 | import org.chromium.base.test.util.AdvancedMockContext; |
19 | import org.chromium.base.test.util.Feature; |
20 | import org.chromium.sync.internal_api.pub.base.ModelType; |
21 | import org.chromium.sync.notifier.InvalidationController.IntentProtocol; |
22 | import org.chromium.sync.signin.AccountManagerHelper; |
23 | import org.chromium.sync.signin.ChromeSigninController; |
24 | import org.chromium.sync.test.util.MockSyncContentResolverDelegate; |
25 | |
26 | import java.util.ArrayList; |
27 | import java.util.HashSet; |
28 | import java.util.List; |
29 | import java.util.Set; |
30 | import java.util.concurrent.atomic.AtomicBoolean; |
31 | import java.util.concurrent.atomic.AtomicReference; |
32 | |
33 | /** |
34 | * Tests for the {@link InvalidationController}. |
35 | */ |
36 | public class InvalidationControllerTest extends InstrumentationTestCase { |
37 | private IntentSavingContext mContext; |
38 | private InvalidationController mController; |
39 | |
40 | @Override |
41 | protected void setUp() throws Exception { |
42 | mContext = new IntentSavingContext(getInstrumentation().getTargetContext()); |
43 | mController = InvalidationController.get(mContext); |
44 | // We don't want to use the system content resolver, so we override it. |
45 | MockSyncContentResolverDelegate delegate = new MockSyncContentResolverDelegate(); |
46 | // Android master sync can safely always be on. |
47 | delegate.setMasterSyncAutomatically(true); |
48 | SyncStatusHelper.overrideSyncStatusHelperForTests(mContext, delegate); |
49 | } |
50 | |
51 | @SmallTest |
52 | @Feature({"Sync"}) |
53 | public void testStart() throws Exception { |
54 | mController.start(); |
55 | assertEquals(1, mContext.getNumStartedIntents()); |
56 | Intent intent = mContext.getStartedIntent(0); |
57 | validateIntentComponent(intent); |
58 | assertNull(intent.getExtras()); |
59 | } |
60 | |
61 | @SmallTest |
62 | @Feature({"Sync"}) |
63 | public void testStop() throws Exception { |
64 | mController.stop(); |
65 | assertEquals(1, mContext.getNumStartedIntents()); |
66 | Intent intent = mContext.getStartedIntent(0); |
67 | validateIntentComponent(intent); |
68 | assertEquals(1, intent.getExtras().size()); |
69 | assertTrue(intent.hasExtra(IntentProtocol.EXTRA_STOP)); |
70 | assertTrue(intent.getBooleanExtra(IntentProtocol.EXTRA_STOP, false)); |
71 | } |
72 | |
73 | @SmallTest |
74 | @Feature({"Sync"}) |
75 | public void testResumingMainActivity() throws Exception { |
76 | // Resuming main activity should trigger a start if sync is enabled. |
77 | setupSync(true); |
78 | mController.onActivityStateChange(ActivityStatus.RESUMED); |
79 | assertEquals(1, mContext.getNumStartedIntents()); |
80 | Intent intent = mContext.getStartedIntent(0); |
81 | validateIntentComponent(intent); |
82 | assertNull(intent.getExtras()); |
83 | } |
84 | |
85 | @SmallTest |
86 | @Feature({"Sync"}) |
87 | public void testResumingMainActivityWithSyncDisabled() throws Exception { |
88 | // Resuming main activity should NOT trigger a start if sync is disabled. |
89 | setupSync(false); |
90 | mController.onActivityStateChange(ActivityStatus.RESUMED); |
91 | assertEquals(0, mContext.getNumStartedIntents()); |
92 | } |
93 | |
94 | @SmallTest |
95 | @Feature({"Sync"}) |
96 | public void testPausingMainActivity() throws Exception { |
97 | // Resuming main activity should trigger a stop if sync is enabled. |
98 | setupSync(true); |
99 | mController.onActivityStateChange(ActivityStatus.PAUSED); |
100 | assertEquals(1, mContext.getNumStartedIntents()); |
101 | Intent intent = mContext.getStartedIntent(0); |
102 | validateIntentComponent(intent); |
103 | assertEquals(1, intent.getExtras().size()); |
104 | assertTrue(intent.hasExtra(IntentProtocol.EXTRA_STOP)); |
105 | assertTrue(intent.getBooleanExtra(IntentProtocol.EXTRA_STOP, false)); |
106 | } |
107 | |
108 | @SmallTest |
109 | @Feature({"Sync"}) |
110 | public void testPausingMainActivityWithSyncDisabled() throws Exception { |
111 | // Resuming main activity should NOT trigger a stop if sync is disabled. |
112 | setupSync(false); |
113 | mController.onActivityStateChange(ActivityStatus.PAUSED); |
114 | assertEquals(0, mContext.getNumStartedIntents()); |
115 | } |
116 | |
117 | private void setupSync(boolean syncEnabled) { |
118 | Account account = AccountManagerHelper.createAccountFromName("test@gmail.com"); |
119 | ChromeSigninController chromeSigninController = ChromeSigninController.get(mContext); |
120 | chromeSigninController.setSignedInAccountName(account.name); |
121 | SyncStatusHelper syncStatusHelper = SyncStatusHelper.get(mContext); |
122 | if (syncEnabled) { |
123 | syncStatusHelper.enableAndroidSync(account); |
124 | } else { |
125 | syncStatusHelper.disableAndroidSync(account); |
126 | } |
127 | } |
128 | |
129 | @SmallTest |
130 | @Feature({"Sync"}) |
131 | public void testEnsureConstructorRegistersListener() throws Exception { |
132 | final AtomicBoolean listenerCallbackCalled = new AtomicBoolean(); |
133 | |
134 | // Create instance. |
135 | new InvalidationController(mContext) { |
136 | @Override |
137 | public void onActivityStateChange(int newState) { |
138 | listenerCallbackCalled.set(true); |
139 | } |
140 | }; |
141 | |
142 | // Ensure initial state is correct. |
143 | assertFalse(listenerCallbackCalled.get()); |
144 | |
145 | // Ensure we get a callback, which means we have registered for them. |
146 | ActivityStatus.onStateChange(new Activity(), ActivityStatus.RESUMED); |
147 | assertTrue(listenerCallbackCalled.get()); |
148 | } |
149 | |
150 | @SmallTest |
151 | @Feature({"Sync"}) |
152 | public void testRegisterForSpecificTypes() { |
153 | InvalidationController controller = new InvalidationController(mContext); |
154 | Account account = new Account("test@example.com", "bogus"); |
155 | controller.setRegisteredTypes(account, false, |
156 | CollectionUtil.newHashSet(ModelType.BOOKMARK, ModelType.SESSION)); |
157 | assertEquals(1, mContext.getNumStartedIntents()); |
158 | |
159 | // Validate destination. |
160 | Intent intent = mContext.getStartedIntent(0); |
161 | validateIntentComponent(intent); |
162 | assertEquals(IntentProtocol.ACTION_REGISTER, intent.getAction()); |
163 | |
164 | // Validate account. |
165 | Account intentAccount = intent.getParcelableExtra(IntentProtocol.EXTRA_ACCOUNT); |
166 | assertEquals(account, intentAccount); |
167 | |
168 | // Validate registered types. |
169 | Set<String> expectedTypes = CollectionUtil.newHashSet(ModelType.BOOKMARK.name(), |
170 | ModelType.SESSION.name()); |
171 | Set<String> actualTypes = new HashSet<String>(); |
172 | actualTypes.addAll(intent.getStringArrayListExtra(IntentProtocol.EXTRA_REGISTERED_TYPES)); |
173 | assertEquals(expectedTypes, actualTypes); |
174 | } |
175 | |
176 | @SmallTest |
177 | @Feature({"Sync"}) |
178 | public void testRegisterForAllTypes() { |
179 | Account account = new Account("test@example.com", "bogus"); |
180 | mController.setRegisteredTypes(account, true, |
181 | CollectionUtil.newHashSet(ModelType.BOOKMARK, ModelType.SESSION)); |
182 | assertEquals(1, mContext.getNumStartedIntents()); |
183 | |
184 | // Validate destination. |
185 | Intent intent = mContext.getStartedIntent(0); |
186 | validateIntentComponent(intent); |
187 | assertEquals(IntentProtocol.ACTION_REGISTER, intent.getAction()); |
188 | |
189 | // Validate account. |
190 | Account intentAccount = intent.getParcelableExtra(IntentProtocol.EXTRA_ACCOUNT); |
191 | assertEquals(account, intentAccount); |
192 | |
193 | // Validate registered types. |
194 | Set<String> expectedTypes = CollectionUtil.newHashSet(ModelType.ALL_TYPES_TYPE); |
195 | Set<String> actualTypes = new HashSet<String>(); |
196 | actualTypes.addAll(intent.getStringArrayListExtra(IntentProtocol.EXTRA_REGISTERED_TYPES)); |
197 | assertEquals(expectedTypes, actualTypes); |
198 | } |
199 | |
200 | @SmallTest |
201 | @Feature({"Sync"}) |
202 | public void testRefreshShouldReadValuesFromDiskWithSpecificTypes() { |
203 | // Store some preferences for ModelTypes and account. We are using the helper class |
204 | // for this, so we don't have to deal with low-level details such as preference keys. |
205 | InvalidationPreferences invalidationPreferences = new InvalidationPreferences(mContext); |
206 | InvalidationPreferences.EditContext edit = invalidationPreferences.edit(); |
207 | Set<String> storedModelTypes = new HashSet<String>(); |
208 | storedModelTypes.add(ModelType.BOOKMARK.name()); |
209 | storedModelTypes.add(ModelType.TYPED_URL.name()); |
210 | Set<ModelType> refreshedTypes = new HashSet<ModelType>(); |
211 | refreshedTypes.add(ModelType.BOOKMARK); |
212 | refreshedTypes.add(ModelType.TYPED_URL); |
213 | invalidationPreferences.setSyncTypes(edit, storedModelTypes); |
214 | Account storedAccount = AccountManagerHelper.createAccountFromName("test@gmail.com"); |
215 | invalidationPreferences.setAccount(edit, storedAccount); |
216 | invalidationPreferences.commit(edit); |
217 | |
218 | // Ensure all calls to {@link InvalidationController#setRegisteredTypes} store values |
219 | // we can inspect in the test. |
220 | final AtomicReference<Account> resultAccount = new AtomicReference<Account>(); |
221 | final AtomicBoolean resultAllTypes = new AtomicBoolean(); |
222 | final AtomicReference<Set<ModelType>> resultTypes = new AtomicReference<Set<ModelType>>(); |
223 | InvalidationController controller = new InvalidationController(mContext) { |
224 | @Override |
225 | public void setRegisteredTypes( |
226 | Account account, boolean allTypes, Set<ModelType> types) { |
227 | resultAccount.set(account); |
228 | resultAllTypes.set(allTypes); |
229 | resultTypes.set(types); |
230 | } |
231 | }; |
232 | |
233 | // Execute the test. |
234 | controller.refreshRegisteredTypes(refreshedTypes); |
235 | |
236 | // Validate the values. |
237 | assertEquals(storedAccount, resultAccount.get()); |
238 | assertEquals(false, resultAllTypes.get()); |
239 | assertEquals(ModelType.syncTypesToModelTypes(storedModelTypes), resultTypes.get()); |
240 | } |
241 | |
242 | @SmallTest |
243 | @Feature({"Sync"}) |
244 | public void testRefreshShouldReadValuesFromDiskWithAllTypes() { |
245 | // Store preferences for the ModelType.ALL_TYPES_TYPE and account. We are using the |
246 | // helper class for this, so we don't have to deal with low-level details such as preference |
247 | // keys. |
248 | InvalidationPreferences invalidationPreferences = new InvalidationPreferences(mContext); |
249 | InvalidationPreferences.EditContext edit = invalidationPreferences.edit(); |
250 | List<String> storedModelTypes = new ArrayList<String>(); |
251 | storedModelTypes.add(ModelType.ALL_TYPES_TYPE); |
252 | invalidationPreferences.setSyncTypes(edit, storedModelTypes); |
253 | Account storedAccount = AccountManagerHelper.createAccountFromName("test@gmail.com"); |
254 | invalidationPreferences.setAccount(edit, storedAccount); |
255 | invalidationPreferences.commit(edit); |
256 | |
257 | // Ensure all calls to {@link InvalidationController#setRegisteredTypes} store values |
258 | // we can inspect in the test. |
259 | final AtomicReference<Account> resultAccount = new AtomicReference<Account>(); |
260 | final AtomicBoolean resultAllTypes = new AtomicBoolean(); |
261 | final AtomicReference<Set<ModelType>> resultTypes = new AtomicReference<Set<ModelType>>(); |
262 | InvalidationController controller = new InvalidationController(mContext) { |
263 | @Override |
264 | public void setRegisteredTypes( |
265 | Account account, boolean allTypes, Set<ModelType> types) { |
266 | resultAccount.set(account); |
267 | resultAllTypes.set(allTypes); |
268 | resultTypes.set(types); |
269 | } |
270 | }; |
271 | |
272 | // Execute the test. |
273 | controller.refreshRegisteredTypes(new HashSet<ModelType>()); |
274 | |
275 | // Validate the values. |
276 | assertEquals(storedAccount, resultAccount.get()); |
277 | assertEquals(true, resultAllTypes.get()); |
278 | } |
279 | |
280 | /** |
281 | * Asserts that {@code intent} is destined for the correct component. |
282 | */ |
283 | private static void validateIntentComponent(Intent intent) { |
284 | assertNotNull(intent.getComponent()); |
285 | assertEquals(InvalidationService.class.getName(), |
286 | intent.getComponent().getClassName()); |
287 | } |
288 | |
289 | /** |
290 | * Mock context that saves all intents given to {@code startService}. |
291 | */ |
292 | private static class IntentSavingContext extends AdvancedMockContext { |
293 | private final List<Intent> startedIntents = new ArrayList<Intent>(); |
294 | |
295 | IntentSavingContext(Context targetContext) { |
296 | super(targetContext); |
297 | } |
298 | |
299 | @Override |
300 | public ComponentName startService(Intent intent) { |
301 | startedIntents.add(intent); |
302 | return new ComponentName(this, getClass()); |
303 | } |
304 | |
305 | int getNumStartedIntents() { |
306 | return startedIntents.size(); |
307 | } |
308 | |
309 | Intent getStartedIntent(int idx) { |
310 | return startedIntents.get(idx); |
311 | } |
312 | |
313 | @Override |
314 | public PackageManager getPackageManager() { |
315 | return getBaseContext().getPackageManager(); |
316 | } |
317 | } |
318 | } |