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.content.ComponentName; |
9 | import android.content.Intent; |
10 | import android.os.Bundle; |
11 | import android.test.ServiceTestCase; |
12 | import android.test.suitebuilder.annotation.SmallTest; |
13 | |
14 | import com.google.ipc.invalidation.external.client.InvalidationListener.RegistrationState; |
15 | import com.google.ipc.invalidation.external.client.contrib.AndroidListener; |
16 | import com.google.ipc.invalidation.external.client.types.ErrorInfo; |
17 | import com.google.ipc.invalidation.external.client.types.Invalidation; |
18 | import com.google.ipc.invalidation.external.client.types.ObjectId; |
19 | |
20 | import org.chromium.base.CollectionUtil; |
21 | import org.chromium.base.test.util.AdvancedMockContext; |
22 | import org.chromium.base.test.util.Feature; |
23 | import org.chromium.sync.internal_api.pub.base.ModelType; |
24 | import org.chromium.sync.notifier.InvalidationController.IntentProtocol; |
25 | import org.chromium.sync.notifier.InvalidationPreferences.EditContext; |
26 | import org.chromium.sync.signin.AccountManagerHelper; |
27 | |
28 | import java.util.ArrayList; |
29 | import java.util.Arrays; |
30 | import java.util.EnumSet; |
31 | import java.util.HashSet; |
32 | import java.util.List; |
33 | import java.util.Set; |
34 | |
35 | /** |
36 | * Tests for the {@link InvalidationService}. |
37 | * |
38 | * @author dsmyers@google.com (Daniel Myers) |
39 | */ |
40 | public class InvalidationServiceTest extends ServiceTestCase<TestableInvalidationService> { |
41 | /** Id used when creating clients. */ |
42 | private static final byte[] CLIENT_ID = new byte[]{0, 4, 7}; |
43 | |
44 | /** Intents provided to {@link #startService}. */ |
45 | private List<Intent> mStartServiceIntents; |
46 | |
47 | public InvalidationServiceTest() { |
48 | super(TestableInvalidationService.class); |
49 | } |
50 | |
51 | @Override |
52 | public void setUp() throws Exception { |
53 | super.setUp(); |
54 | mStartServiceIntents = new ArrayList<Intent>(); |
55 | setContext(new AdvancedMockContext(getContext()) { |
56 | @Override |
57 | public ComponentName startService(Intent intent) { |
58 | mStartServiceIntents.add(intent); |
59 | return new ComponentName(this, InvalidationServiceTest.class); |
60 | } |
61 | }); |
62 | setupService(); |
63 | } |
64 | |
65 | @Override |
66 | public void tearDown() throws Exception { |
67 | if (InvalidationService.getIsClientStartedForTest()) { |
68 | Intent stopIntent = new Intent().putExtra(IntentProtocol.EXTRA_STOP, true); |
69 | getService().onHandleIntent(stopIntent); |
70 | } |
71 | assertFalse(InvalidationService.getIsClientStartedForTest()); |
72 | super.tearDown(); |
73 | } |
74 | |
75 | @SmallTest |
76 | @Feature({"Sync"}) |
77 | public void testComputeRegistrationOps() { |
78 | /* |
79 | * Test plan: compute the set of registration operations resulting from various combinations |
80 | * of existing and desired registrations. Verifying that they are correct. |
81 | */ |
82 | Set<ObjectId> regAccumulator = new HashSet<ObjectId>(); |
83 | Set<ObjectId> unregAccumulator = new HashSet<ObjectId>(); |
84 | |
85 | // Empty existing and desired registrations should yield empty operation sets. |
86 | InvalidationService.computeRegistrationOps( |
87 | ModelType.modelTypesToObjectIds( |
88 | CollectionUtil.newHashSet(ModelType.BOOKMARK, ModelType.SESSION)), |
89 | ModelType.modelTypesToObjectIds( |
90 | CollectionUtil.newHashSet(ModelType.BOOKMARK, ModelType.SESSION)), |
91 | regAccumulator, unregAccumulator); |
92 | assertEquals(0, regAccumulator.size()); |
93 | assertEquals(0, unregAccumulator.size()); |
94 | |
95 | // Equal existing and desired registrations should yield empty operation sets. |
96 | InvalidationService.computeRegistrationOps(new HashSet<ObjectId>(), |
97 | new HashSet<ObjectId>(), regAccumulator, unregAccumulator); |
98 | assertEquals(0, regAccumulator.size()); |
99 | assertEquals(0, unregAccumulator.size()); |
100 | |
101 | // Empty existing and non-empty desired registrations should yield desired registrations |
102 | // as the registration operations to do and no unregistrations. |
103 | Set<ObjectId> desiredTypes = |
104 | CollectionUtil.newHashSet( |
105 | ModelType.BOOKMARK.toObjectId(), ModelType.SESSION.toObjectId()); |
106 | InvalidationService.computeRegistrationOps( |
107 | new HashSet<ObjectId>(), |
108 | desiredTypes, |
109 | regAccumulator, unregAccumulator); |
110 | assertEquals( |
111 | CollectionUtil.newHashSet( |
112 | ModelType.BOOKMARK.toObjectId(), ModelType.SESSION.toObjectId()), |
113 | new HashSet<ObjectId>(regAccumulator)); |
114 | assertEquals(0, unregAccumulator.size()); |
115 | regAccumulator.clear(); |
116 | |
117 | // Unequal existing and desired registrations should yield both registrations and |
118 | // unregistrations. We should unregister TYPED_URL and register BOOKMARK, keeping SESSION. |
119 | InvalidationService.computeRegistrationOps( |
120 | CollectionUtil.newHashSet( |
121 | ModelType.SESSION.toObjectId(), ModelType.TYPED_URL.toObjectId()), |
122 | CollectionUtil.newHashSet( |
123 | ModelType.BOOKMARK.toObjectId(), ModelType.SESSION.toObjectId()), |
124 | regAccumulator, unregAccumulator); |
125 | assertEquals(CollectionUtil.newHashSet(ModelType.BOOKMARK.toObjectId()), regAccumulator); |
126 | assertEquals(CollectionUtil.newHashSet(ModelType.TYPED_URL.toObjectId()), |
127 | unregAccumulator); |
128 | regAccumulator.clear(); |
129 | unregAccumulator.clear(); |
130 | } |
131 | |
132 | @SmallTest |
133 | @Feature({"Sync"}) |
134 | public void testReady() { |
135 | /** |
136 | * Test plan: call ready. Verify that the service sets the client id correctly and reissues |
137 | * pending registrations. |
138 | */ |
139 | |
140 | // Persist some registrations. |
141 | InvalidationPreferences invPrefs = new InvalidationPreferences(getContext()); |
142 | EditContext editContext = invPrefs.edit(); |
143 | invPrefs.setSyncTypes(editContext, CollectionUtil.newArrayList("BOOKMARK", "SESSION")); |
144 | assertTrue(invPrefs.commit(editContext)); |
145 | |
146 | // Issue ready. |
147 | getService().ready(CLIENT_ID); |
148 | assertTrue(Arrays.equals(CLIENT_ID, InvalidationService.getClientIdForTest())); |
149 | byte[] otherCid = "otherCid".getBytes(); |
150 | getService().ready(otherCid); |
151 | assertTrue(Arrays.equals(otherCid, InvalidationService.getClientIdForTest())); |
152 | |
153 | // Verify registrations issued. |
154 | assertEquals(CollectionUtil.newHashSet( |
155 | ModelType.BOOKMARK.toObjectId(), ModelType.SESSION.toObjectId()), |
156 | new HashSet<ObjectId>(getService().mRegistrations.get(0))); |
157 | } |
158 | |
159 | @SmallTest |
160 | @Feature({"Sync"}) |
161 | public void testReissueRegistrations() { |
162 | /* |
163 | * Test plan: call the reissueRegistrations method of the listener with both empty and |
164 | * non-empty sets of desired registrations stored in preferences. Verify that no register |
165 | * intent is set in the first case and that the appropriate register intent is sent in |
166 | * the second. |
167 | */ |
168 | |
169 | // No persisted registrations. |
170 | getService().reissueRegistrations(CLIENT_ID); |
171 | assertTrue(getService().mRegistrations.isEmpty()); |
172 | |
173 | // Persist some registrations. |
174 | InvalidationPreferences invPrefs = new InvalidationPreferences(getContext()); |
175 | EditContext editContext = invPrefs.edit(); |
176 | invPrefs.setSyncTypes(editContext, CollectionUtil.newArrayList("BOOKMARK", "SESSION")); |
177 | assertTrue(invPrefs.commit(editContext)); |
178 | |
179 | // Reissue registrations and verify that the appropriate registrations are issued. |
180 | getService().reissueRegistrations(CLIENT_ID); |
181 | assertEquals(1, getService().mRegistrations.size()); |
182 | assertEquals(CollectionUtil.newHashSet( |
183 | ModelType.BOOKMARK.toObjectId(), ModelType.SESSION.toObjectId()), |
184 | new HashSet<ObjectId>(getService().mRegistrations.get(0))); |
185 | } |
186 | |
187 | @SmallTest |
188 | @Feature({"Sync"}) |
189 | public void testInformRegistrationStatus() { |
190 | /* |
191 | * Test plan: call inform registration status under a variety of circumstances and verify |
192 | * that the appropriate (un)register calls are issued. |
193 | * |
194 | * 1. Registration of desired object. No calls issued. |
195 | * 2. Unregistration of undesired object. No calls issued. |
196 | * 3. Registration of undesired object. Unregistration issued. |
197 | * 4. Unregistration of desired object. Registration issued. |
198 | */ |
199 | // Initial test setup: persist a single registration into preferences. |
200 | InvalidationPreferences invPrefs = new InvalidationPreferences(getContext()); |
201 | EditContext editContext = invPrefs.edit(); |
202 | invPrefs.setSyncTypes(editContext, CollectionUtil.newArrayList("SESSION")); |
203 | assertTrue(invPrefs.commit(editContext)); |
204 | |
205 | // Cases 1 and 2: calls matching desired state cause no actions. |
206 | getService().informRegistrationStatus(CLIENT_ID, ModelType.SESSION.toObjectId(), |
207 | RegistrationState.REGISTERED); |
208 | getService().informRegistrationStatus(CLIENT_ID, ModelType.BOOKMARK.toObjectId(), |
209 | RegistrationState.UNREGISTERED); |
210 | assertTrue(getService().mRegistrations.isEmpty()); |
211 | assertTrue(getService().mUnregistrations.isEmpty()); |
212 | |
213 | // Case 3: registration of undesired object triggers an unregistration. |
214 | getService().informRegistrationStatus(CLIENT_ID, ModelType.BOOKMARK.toObjectId(), |
215 | RegistrationState.REGISTERED); |
216 | assertEquals(1, getService().mUnregistrations.size()); |
217 | assertEquals(0, getService().mRegistrations.size()); |
218 | assertEquals(CollectionUtil.newArrayList(ModelType.BOOKMARK.toObjectId()), |
219 | getService().mUnregistrations.get(0)); |
220 | |
221 | // Case 4: unregistration of a desired object triggers a registration. |
222 | getService().informRegistrationStatus(CLIENT_ID, ModelType.SESSION.toObjectId(), |
223 | RegistrationState.UNREGISTERED); |
224 | assertEquals(1, getService().mUnregistrations.size()); |
225 | assertEquals(1, getService().mRegistrations.size()); |
226 | assertEquals(CollectionUtil.newArrayList(ModelType.SESSION.toObjectId()), |
227 | getService().mRegistrations.get(0)); |
228 | } |
229 | |
230 | @SmallTest |
231 | @Feature({"Sync"}) |
232 | public void testInformRegistrationFailure() { |
233 | /* |
234 | * Test plan: call inform registration failure under a variety of circumstances and verify |
235 | * that the appropriate (un)register calls are issued. |
236 | * |
237 | * 1. Transient registration failure for an object that should be registered. Register |
238 | * should be called. |
239 | * 2. Permanent registration failure for an object that should be registered. No calls. |
240 | * 3. Transient registration failure for an object that should not be registered. Unregister |
241 | * should be called. |
242 | * 4. Permanent registration failure for an object should not be registered. No calls. |
243 | */ |
244 | |
245 | // Initial test setup: persist a single registration into preferences. |
246 | InvalidationPreferences invPrefs = new InvalidationPreferences(getContext()); |
247 | EditContext editContext = invPrefs.edit(); |
248 | invPrefs.setSyncTypes(editContext, CollectionUtil.newArrayList("SESSION")); |
249 | assertTrue(invPrefs.commit(editContext)); |
250 | |
251 | // Cases 2 and 4: permanent registration failures never cause calls to be made. |
252 | getService().informRegistrationFailure(CLIENT_ID, ModelType.SESSION.toObjectId(), false, |
253 | ""); |
254 | getService().informRegistrationFailure(CLIENT_ID, ModelType.BOOKMARK.toObjectId(), false, |
255 | ""); |
256 | assertTrue(getService().mRegistrations.isEmpty()); |
257 | assertTrue(getService().mUnregistrations.isEmpty()); |
258 | |
259 | // Case 1: transient failure of a desired registration results in re-registration. |
260 | getService().informRegistrationFailure(CLIENT_ID, ModelType.SESSION.toObjectId(), true, ""); |
261 | assertEquals(1, getService().mRegistrations.size()); |
262 | assertTrue(getService().mUnregistrations.isEmpty()); |
263 | assertEquals(CollectionUtil.newArrayList(ModelType.SESSION.toObjectId()), |
264 | getService().mRegistrations.get(0)); |
265 | |
266 | // Case 3: transient failure of an undesired registration results in unregistration. |
267 | getService().informRegistrationFailure(CLIENT_ID, ModelType.BOOKMARK.toObjectId(), true, |
268 | ""); |
269 | assertEquals(1, getService().mRegistrations.size()); |
270 | assertEquals(1, getService().mUnregistrations.size()); |
271 | assertEquals(CollectionUtil.newArrayList(ModelType.BOOKMARK.toObjectId()), |
272 | getService().mUnregistrations.get(0)); |
273 | } |
274 | |
275 | @SmallTest |
276 | @Feature({"Sync"}) |
277 | public void testInformError() { |
278 | /* |
279 | * Test plan: call informError with both permanent and transient errors. Verify that |
280 | * the transient error causes no action to be taken and that the permanent error causes |
281 | * the client to be stopped. |
282 | */ |
283 | |
284 | // Client needs to be started for the permament error to trigger and stop. |
285 | getService().setShouldRunStates(true, true); |
286 | getService().onCreate(); |
287 | getService().onHandleIntent(new Intent()); |
288 | getService().mStartedServices.clear(); // Discard start intent. |
289 | |
290 | // Transient error. |
291 | getService().informError(ErrorInfo.newInstance(0, true, "transient", null)); |
292 | assertTrue(getService().mStartedServices.isEmpty()); |
293 | |
294 | // Permanent error. |
295 | getService().informError(ErrorInfo.newInstance(0, false, "permanent", null)); |
296 | assertEquals(1, getService().mStartedServices.size()); |
297 | Intent sentIntent = getService().mStartedServices.get(0); |
298 | Intent stopIntent = AndroidListener.createStopIntent(getContext()); |
299 | assertTrue(stopIntent.filterEquals(sentIntent)); |
300 | assertEquals(stopIntent.getExtras().keySet(), sentIntent.getExtras().keySet()); |
301 | } |
302 | |
303 | @SmallTest |
304 | @Feature({"Sync"}) |
305 | public void testReadWriteState() { |
306 | /* |
307 | * Test plan: read, write, and read the internal notification client persistent state. |
308 | * Verify appropriate return values. |
309 | */ |
310 | assertNull(getService().readState()); |
311 | byte[] writtenState = new byte[]{7,4,0}; |
312 | getService().writeState(writtenState); |
313 | assertTrue(Arrays.equals(writtenState, getService().readState())); |
314 | } |
315 | |
316 | @SmallTest |
317 | @Feature({"Sync"}) |
318 | public void testInvalidateWithPayload() { |
319 | doTestInvalidate(true); |
320 | } |
321 | |
322 | @SmallTest |
323 | @Feature({"Sync"}) |
324 | public void testInvalidateWithoutPayload() { |
325 | doTestInvalidate(false); |
326 | } |
327 | |
328 | private void doTestInvalidate(boolean hasPayload) { |
329 | /* |
330 | * Test plan: call invalidate() with an invalidation that may or may not have a payload. |
331 | * Verify the produced bundle has the correct fields. |
332 | */ |
333 | // Call invalidate. |
334 | int version = 4747; |
335 | ObjectId objectId = ModelType.BOOKMARK.toObjectId(); |
336 | final String payload = "testInvalidate-" + hasPayload; |
337 | Invalidation invalidation = hasPayload ? |
338 | Invalidation.newInstance(objectId, version, payload.getBytes()) : |
339 | Invalidation.newInstance(objectId, version); |
340 | byte[] ackHandle = ("testInvalidate-" + hasPayload).getBytes(); |
341 | getService().invalidate(invalidation, ackHandle); |
342 | |
343 | // Validate bundle. |
344 | assertEquals(1, getService().mRequestedSyncs.size()); |
345 | Bundle syncBundle = getService().mRequestedSyncs.get(0); |
346 | assertEquals("BOOKMARK", syncBundle.getString("objectId")); |
347 | assertEquals(version, syncBundle.getLong("version")); |
348 | assertEquals(hasPayload ? payload : "", syncBundle.getString("payload")); |
349 | |
350 | // Ensure acknowledged. |
351 | assertSingleAcknowledgement(ackHandle); |
352 | } |
353 | |
354 | @SmallTest |
355 | @Feature({"Sync"}) |
356 | public void testInvalidateUnknownVersion() { |
357 | /* |
358 | * Test plan: call invalidateUnknownVersion(). Verify the produced bundle has the correct |
359 | * fields. |
360 | */ |
361 | ObjectId objectId = ModelType.BOOKMARK.toObjectId(); |
362 | byte[] ackHandle = "testInvalidateUV".getBytes(); |
363 | getService().invalidateUnknownVersion(objectId, ackHandle); |
364 | |
365 | // Validate bundle. |
366 | assertEquals(1, getService().mRequestedSyncs.size()); |
367 | Bundle syncBundle = getService().mRequestedSyncs.get(0); |
368 | assertEquals("BOOKMARK", syncBundle.getString("objectId")); |
369 | assertEquals(0, syncBundle.getLong("version")); |
370 | assertEquals("", syncBundle.getString("payload")); |
371 | |
372 | // Ensure acknowledged. |
373 | assertSingleAcknowledgement(ackHandle); |
374 | } |
375 | |
376 | @SmallTest |
377 | @Feature({"Sync"}) |
378 | public void testInvalidateAll() { |
379 | /* |
380 | * Test plan: call invalidateAll(). Verify the produced bundle has the correct fields. |
381 | */ |
382 | byte[] ackHandle = "testInvalidateAll".getBytes(); |
383 | getService().invalidateAll(ackHandle); |
384 | |
385 | // Validate bundle. |
386 | assertEquals(1, getService().mRequestedSyncs.size()); |
387 | Bundle syncBundle = getService().mRequestedSyncs.get(0); |
388 | assertEquals(0, syncBundle.keySet().size()); |
389 | |
390 | // Ensure acknowledged. |
391 | assertSingleAcknowledgement(ackHandle); |
392 | } |
393 | |
394 | /** Asserts that the service received a single acknowledgement with handle {@code ackHandle}. */ |
395 | private void assertSingleAcknowledgement(byte[] ackHandle) { |
396 | assertEquals(1, getService().mAcknowledgements.size()); |
397 | assertTrue(Arrays.equals(ackHandle, getService().mAcknowledgements.get(0))); |
398 | } |
399 | |
400 | @SmallTest |
401 | @Feature({"Sync"}) |
402 | public void testShouldClientBeRunning() { |
403 | /* |
404 | * Test plan: call shouldClientBeRunning with various combinations of |
405 | * in-foreground/sync-enabled. Verify appropriate return values. |
406 | */ |
407 | getService().setShouldRunStates(false, false); |
408 | assertFalse(getService().shouldClientBeRunning()); |
409 | |
410 | getService().setShouldRunStates(false, true); |
411 | assertFalse(getService().shouldClientBeRunning()); |
412 | |
413 | getService().setShouldRunStates(true, false); |
414 | assertFalse(getService().shouldClientBeRunning()); |
415 | |
416 | // Should only be running if both in the foreground and sync is enabled. |
417 | getService().setShouldRunStates(true, true); |
418 | assertTrue(getService().shouldClientBeRunning()); |
419 | } |
420 | |
421 | @SmallTest |
422 | @Feature({"Sync"}) |
423 | public void testStartAndStopClient() { |
424 | /* |
425 | * Test plan: with Chrome configured so that the client should run, send it an empty |
426 | * intent. Even though no owning account is known, the client should still start. Send |
427 | * it a stop intent and verify that it stops. |
428 | */ |
429 | |
430 | // Note: we are manipulating the service object directly, rather than through startService, |
431 | // because otherwise we would need to handle the asynchronous execution model of the |
432 | // underlying IntentService. |
433 | getService().setShouldRunStates(true, true); |
434 | getService().onCreate(); |
435 | |
436 | Intent startIntent = new Intent(); |
437 | getService().onHandleIntent(startIntent); |
438 | assertTrue(InvalidationService.getIsClientStartedForTest()); |
439 | |
440 | Intent stopIntent = new Intent().putExtra(IntentProtocol.EXTRA_STOP, true); |
441 | getService().onHandleIntent(stopIntent); |
442 | assertFalse(InvalidationService.getIsClientStartedForTest()); |
443 | |
444 | // The issued intents should have been an AndroidListener start intent followed by an |
445 | // AndroidListener stop intent. |
446 | assertEquals(2, mStartServiceIntents.size()); |
447 | assertTrue(isAndroidListenerStartIntent(mStartServiceIntents.get(0))); |
448 | assertTrue(isAndroidListenerStopIntent(mStartServiceIntents.get(1))); |
449 | } |
450 | |
451 | @SmallTest |
452 | @Feature({"Sync"}) |
453 | public void testClientStopsWhenShouldNotBeRunning() { |
454 | /* |
455 | * Test plan: start the client. Then, change the configuration so that Chrome should not |
456 | * be running. Send an intent to the service and verify that it stops. |
457 | */ |
458 | getService().setShouldRunStates(true, true); |
459 | getService().onCreate(); |
460 | |
461 | // Start the service. |
462 | Intent startIntent = new Intent(); |
463 | getService().onHandleIntent(startIntent); |
464 | assertTrue(InvalidationService.getIsClientStartedForTest()); |
465 | |
466 | // Change configuration. |
467 | getService().setShouldRunStates(false, false); |
468 | |
469 | // Send an Intent and verify that the service stops. |
470 | getService().onHandleIntent(startIntent); |
471 | assertFalse(InvalidationService.getIsClientStartedForTest()); |
472 | |
473 | // The issued intents should have been an AndroidListener start intent followed by an |
474 | // AndroidListener stop intent. |
475 | assertEquals(2, mStartServiceIntents.size()); |
476 | assertTrue(isAndroidListenerStartIntent(mStartServiceIntents.get(0))); |
477 | assertTrue(isAndroidListenerStopIntent(mStartServiceIntents.get(1))); |
478 | } |
479 | |
480 | @SmallTest |
481 | @Feature({"Sync"}) |
482 | public void testRegistrationIntent() { |
483 | /* |
484 | * Test plan: send a registration-change intent. Verify that it starts the client and |
485 | * sets both the account and registrations in shared preferences. |
486 | */ |
487 | getService().setShouldRunStates(true, true); |
488 | getService().onCreate(); |
489 | |
490 | // Send register Intent. |
491 | Set<ModelType> desiredRegistrations = CollectionUtil.newHashSet( |
492 | ModelType.BOOKMARK, ModelType.SESSION); |
493 | Account account = AccountManagerHelper.createAccountFromName("test@example.com"); |
494 | Intent registrationIntent = IntentProtocol.createRegisterIntent(account, false, |
495 | desiredRegistrations); |
496 | getService().onHandleIntent(registrationIntent); |
497 | |
498 | // Verify client started and state written. |
499 | assertTrue(InvalidationService.getIsClientStartedForTest()); |
500 | InvalidationPreferences invPrefs = new InvalidationPreferences(getContext()); |
501 | assertEquals(account, invPrefs.getSavedSyncedAccount()); |
502 | assertEquals(ModelType.modelTypesToSyncTypes(desiredRegistrations), |
503 | invPrefs.getSavedSyncedTypes()); |
504 | assertEquals(1, mStartServiceIntents.size()); |
505 | assertTrue(isAndroidListenerStartIntent(mStartServiceIntents.get(0))); |
506 | |
507 | // Send another registration-change intent, this type with all-types set to true, and |
508 | // verify that the on-disk state is updated and that no addition Intents are issued. |
509 | getService().onHandleIntent(IntentProtocol.createRegisterIntent(account, true, null)); |
510 | assertEquals(account, invPrefs.getSavedSyncedAccount()); |
511 | assertEquals(CollectionUtil.newHashSet(ModelType.ALL_TYPES_TYPE), |
512 | invPrefs.getSavedSyncedTypes()); |
513 | assertEquals(1, mStartServiceIntents.size()); |
514 | |
515 | // Finally, send one more registration-change intent, this time with a different account, |
516 | // and verify that it both updates the account, stops thye existing client, and |
517 | // starts a new client. |
518 | Account account2 = AccountManagerHelper.createAccountFromName("test2@example.com"); |
519 | getService().onHandleIntent(IntentProtocol.createRegisterIntent(account2, true, null)); |
520 | assertEquals(account2, invPrefs.getSavedSyncedAccount()); |
521 | assertEquals(3, mStartServiceIntents.size()); |
522 | assertTrue(isAndroidListenerStartIntent(mStartServiceIntents.get(0))); |
523 | assertTrue(isAndroidListenerStopIntent(mStartServiceIntents.get(1))); |
524 | assertTrue(isAndroidListenerStartIntent(mStartServiceIntents.get(2))); |
525 | } |
526 | |
527 | @SmallTest |
528 | @Feature({"Sync"}) |
529 | public void testRegistrationIntentNoProxyTabsUsingReady() { |
530 | getService().setShouldRunStates(true, true); |
531 | getService().onCreate(); |
532 | |
533 | // Send register Intent. |
534 | Account account = AccountManagerHelper.createAccountFromName("test@example.com"); |
535 | Intent registrationIntent = IntentProtocol.createRegisterIntent(account, true, null); |
536 | getService().onHandleIntent(registrationIntent); |
537 | |
538 | // Verify client started and state written. |
539 | assertTrue(InvalidationService.getIsClientStartedForTest()); |
540 | InvalidationPreferences invPrefs = new InvalidationPreferences(getContext()); |
541 | assertEquals(account, invPrefs.getSavedSyncedAccount()); |
542 | assertEquals(CollectionUtil.newHashSet(ModelType.ALL_TYPES_TYPE), |
543 | invPrefs.getSavedSyncedTypes()); |
544 | assertEquals(1, mStartServiceIntents.size()); |
545 | assertTrue(isAndroidListenerStartIntent(mStartServiceIntents.get(0))); |
546 | |
547 | // Set client to be ready. This triggers registrations. |
548 | getService().ready(CLIENT_ID); |
549 | assertTrue(Arrays.equals(CLIENT_ID, InvalidationService.getClientIdForTest())); |
550 | |
551 | // Ensure registrations are correct. |
552 | Set<ObjectId> expectedTypes = |
553 | ModelType.modelTypesToObjectIds(EnumSet.allOf(ModelType.class)); |
554 | assertEquals(expectedTypes, new HashSet<ObjectId>(getService().mRegistrations.get(0))); |
555 | } |
556 | |
557 | @SmallTest |
558 | @Feature({"Sync"}) |
559 | public void testRegistrationIntentNoProxyTabsAlreadyWithClientId() { |
560 | getService().setShouldRunStates(true, true); |
561 | getService().onCreate(); |
562 | |
563 | // Send register Intent with no desired types. |
564 | Account account = AccountManagerHelper.createAccountFromName("test@example.com"); |
565 | Intent registrationIntent = |
566 | IntentProtocol.createRegisterIntent(account, false, new HashSet<ModelType>()); |
567 | getService().onHandleIntent(registrationIntent); |
568 | |
569 | // Verify client started and state written. |
570 | assertTrue(InvalidationService.getIsClientStartedForTest()); |
571 | InvalidationPreferences invPrefs = new InvalidationPreferences(getContext()); |
572 | assertEquals(account, invPrefs.getSavedSyncedAccount()); |
573 | assertEquals(new HashSet<String>(), invPrefs.getSavedSyncedTypes()); |
574 | assertEquals(1, mStartServiceIntents.size()); |
575 | assertTrue(isAndroidListenerStartIntent(mStartServiceIntents.get(0))); |
576 | |
577 | // Make sure client is ready. |
578 | getService().ready(CLIENT_ID); |
579 | assertTrue(Arrays.equals(CLIENT_ID, InvalidationService.getClientIdForTest())); |
580 | |
581 | // Choose to register for all types in an already ready client. |
582 | registrationIntent = IntentProtocol.createRegisterIntent(account, true, null); |
583 | getService().onHandleIntent(registrationIntent); |
584 | |
585 | // Ensure registrations are correct. |
586 | assertEquals(1, getService().mRegistrations.size()); |
587 | Set<ObjectId> expectedTypes = |
588 | ModelType.modelTypesToObjectIds(EnumSet.allOf(ModelType.class)); |
589 | assertEquals(expectedTypes, new HashSet<ObjectId>(getService().mRegistrations.get(0))); |
590 | } |
591 | |
592 | @SmallTest |
593 | @Feature({"Sync"}) |
594 | public void testRegistrationIntentWhenClientShouldNotBeRunning() { |
595 | /* |
596 | * Test plan: send a registration change event when the client should not be running. |
597 | * Verify that the service updates the on-disk state but does not start the client. |
598 | */ |
599 | getService().onCreate(); |
600 | |
601 | // Send register Intent. |
602 | Account account = AccountManagerHelper.createAccountFromName("test@example.com"); |
603 | Set<ModelType> desiredRegistrations = CollectionUtil.newHashSet( |
604 | ModelType.BOOKMARK, ModelType.SESSION); |
605 | Intent registrationIntent = IntentProtocol.createRegisterIntent(account, false, |
606 | desiredRegistrations); |
607 | getService().onHandleIntent(registrationIntent); |
608 | |
609 | // Verify state written but client not started. |
610 | assertFalse(InvalidationService.getIsClientStartedForTest()); |
611 | InvalidationPreferences invPrefs = new InvalidationPreferences(getContext()); |
612 | assertEquals(account, invPrefs.getSavedSyncedAccount()); |
613 | assertEquals(ModelType.modelTypesToSyncTypes(desiredRegistrations), |
614 | invPrefs.getSavedSyncedTypes()); |
615 | assertEquals(0, mStartServiceIntents.size()); |
616 | } |
617 | |
618 | @SmallTest |
619 | @Feature({"Sync"}) |
620 | public void testDeferredRegistrationsIssued() { |
621 | /* |
622 | * Test plan: send a registration-change intent. Verify that the client issues a start |
623 | * intent but makes no registration calls. Issue a reissueRegistrations call and verify |
624 | * that the client does issue the appropriate registrations. |
625 | */ |
626 | getService().setShouldRunStates(true, true); |
627 | getService().onCreate(); |
628 | |
629 | // Send register Intent. Verify client started but no registrations issued. |
630 | Account account = AccountManagerHelper.createAccountFromName("test@example.com"); |
631 | Set<ModelType> desiredRegistrations = CollectionUtil.newHashSet( |
632 | ModelType.BOOKMARK, ModelType.SESSION); |
633 | Set<ObjectId> desiredObjectIds = ModelType.modelTypesToObjectIds(desiredRegistrations); |
634 | |
635 | Intent registrationIntent = IntentProtocol.createRegisterIntent(account, false, |
636 | desiredRegistrations); |
637 | getService().onHandleIntent(registrationIntent); |
638 | assertTrue(InvalidationService.getIsClientStartedForTest()); |
639 | assertEquals(1, mStartServiceIntents.size()); |
640 | assertTrue(isAndroidListenerStartIntent(mStartServiceIntents.get(0))); |
641 | InvalidationPreferences invPrefs = new InvalidationPreferences(getContext()); |
642 | assertEquals(ModelType.modelTypesToSyncTypes(desiredRegistrations), |
643 | invPrefs.getSavedSyncedTypes()); |
644 | assertEquals(desiredObjectIds, getService().readRegistrationsFromPrefs()); |
645 | |
646 | // Issue reissueRegistrations; verify registration intent issues. |
647 | getService().reissueRegistrations(CLIENT_ID); |
648 | assertEquals(2, mStartServiceIntents.size()); |
649 | Intent expectedRegisterIntent = AndroidListener.createRegisterIntent( |
650 | getContext(), |
651 | CLIENT_ID, |
652 | desiredObjectIds); |
653 | Intent actualRegisterIntent = mStartServiceIntents.get(1); |
654 | assertTrue(expectedRegisterIntent.filterEquals(actualRegisterIntent)); |
655 | assertEquals(expectedRegisterIntent.getExtras().keySet(), |
656 | actualRegisterIntent.getExtras().keySet()); |
657 | assertEquals( |
658 | desiredObjectIds, |
659 | new HashSet<ObjectId>(getService().mRegistrations.get(0))); |
660 | } |
661 | |
662 | @SmallTest |
663 | @Feature({"Sync"}) |
664 | public void testRegistrationRetries() { |
665 | /* |
666 | * Test plan: validate that the alarm receiver used by the AndroidListener underlying |
667 | * InvalidationService is correctly configured in the manifest and retries registrations |
668 | * with exponential backoff. May need to be implemented as a downstream Chrome for Android |
669 | * test. |
670 | */ |
671 | // TODO(dsmyers): implement. |
672 | // Bug: https://code.google.com/p/chromium/issues/detail?id=172398 |
673 | } |
674 | |
675 | /** Returns whether {@code intent} is an {@link AndroidListener} start intent. */ |
676 | private boolean isAndroidListenerStartIntent(Intent intent) { |
677 | Intent startIntent = AndroidListener.createStartIntent(getContext(), |
678 | InvalidationService.CLIENT_TYPE, "unused".getBytes()); |
679 | return intent.getExtras().keySet().equals(startIntent.getExtras().keySet()); |
680 | } |
681 | |
682 | /** Returns whether {@code intent} is an {@link AndroidListener} stop intent. */ |
683 | private boolean isAndroidListenerStopIntent(Intent intent) { |
684 | Intent stopIntent = AndroidListener.createStopIntent(getContext()); |
685 | return intent.getExtras().keySet().equals(stopIntent.getExtras().keySet()); |
686 | } |
687 | } |