- add sources.
[platform/framework/web/crosswalk.git] / src / sync / android / javatests / src / org / chromium / sync / notifier / InvalidationServiceTest.java
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.InvalidationIntentProtocol;
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(InvalidationIntentProtocol.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         ObjectId objectId = ObjectId.newInstance(1, "obj".getBytes());
145         invPrefs.setObjectIds(editContext, CollectionUtil.newArrayList(objectId));
146         assertTrue(invPrefs.commit(editContext));
147
148         // Issue ready.
149         getService().ready(CLIENT_ID);
150         assertTrue(Arrays.equals(CLIENT_ID, InvalidationService.getClientIdForTest()));
151         byte[] otherCid = "otherCid".getBytes();
152         getService().ready(otherCid);
153         assertTrue(Arrays.equals(otherCid, InvalidationService.getClientIdForTest()));
154
155         // Verify registrations issued.
156         assertEquals(CollectionUtil.newHashSet(
157                 ModelType.BOOKMARK.toObjectId(), ModelType.SESSION.toObjectId(), objectId),
158                 new HashSet<ObjectId>(getService().mRegistrations.get(0)));
159     }
160
161     @SmallTest
162     @Feature({"Sync"})
163     public void testReissueRegistrations() {
164         /*
165          * Test plan: call the reissueRegistrations method of the listener with both empty and
166          * non-empty sets of desired registrations stored in preferences. Verify that no register
167          * intent is set in the first case and that the appropriate register intent is sent in
168          * the second.
169          */
170
171         // No persisted registrations.
172         getService().reissueRegistrations(CLIENT_ID);
173         assertTrue(getService().mRegistrations.isEmpty());
174
175         // Persist some registrations.
176         InvalidationPreferences invPrefs = new InvalidationPreferences(getContext());
177         EditContext editContext = invPrefs.edit();
178         invPrefs.setSyncTypes(editContext, CollectionUtil.newArrayList("BOOKMARK", "SESSION"));
179         ObjectId objectId = ObjectId.newInstance(1, "obj".getBytes());
180         invPrefs.setObjectIds(editContext, CollectionUtil.newArrayList(objectId));
181         assertTrue(invPrefs.commit(editContext));
182
183         // Reissue registrations and verify that the appropriate registrations are issued.
184         getService().reissueRegistrations(CLIENT_ID);
185         assertEquals(1, getService().mRegistrations.size());
186         assertEquals(CollectionUtil.newHashSet(
187                 ModelType.BOOKMARK.toObjectId(), ModelType.SESSION.toObjectId(), objectId),
188                 new HashSet<ObjectId>(getService().mRegistrations.get(0)));
189     }
190
191     @SmallTest
192     @Feature({"Sync"})
193     public void testInformRegistrationStatus() {
194         /*
195          * Test plan: call inform registration status under a variety of circumstances and verify
196          * that the appropriate (un)register calls are issued.
197          *
198          * 1. Registration of desired object. No calls issued.
199          * 2. Unregistration of undesired object. No calls issued.
200          * 3. Registration of undesired object. Unregistration issued.
201          * 4. Unregistration of desired object. Registration issued.
202          */
203         // Initial test setup: persist a single registration into preferences.
204         InvalidationPreferences invPrefs = new InvalidationPreferences(getContext());
205         EditContext editContext = invPrefs.edit();
206         invPrefs.setSyncTypes(editContext, CollectionUtil.newArrayList("SESSION"));
207         ObjectId desiredObjectId = ObjectId.newInstance(1, "obj1".getBytes());
208         ObjectId undesiredObjectId = ObjectId.newInstance(1, "obj2".getBytes());
209         invPrefs.setObjectIds(editContext, CollectionUtil.newArrayList(desiredObjectId));
210         assertTrue(invPrefs.commit(editContext));
211
212         // Cases 1 and 2: calls matching desired state cause no actions.
213         getService().informRegistrationStatus(CLIENT_ID, ModelType.SESSION.toObjectId(),
214                 RegistrationState.REGISTERED);
215         getService().informRegistrationStatus(CLIENT_ID, desiredObjectId,
216                 RegistrationState.REGISTERED);
217         getService().informRegistrationStatus(CLIENT_ID, ModelType.BOOKMARK.toObjectId(),
218                 RegistrationState.UNREGISTERED);
219         getService().informRegistrationStatus(CLIENT_ID, undesiredObjectId,
220                 RegistrationState.UNREGISTERED);
221         assertTrue(getService().mRegistrations.isEmpty());
222         assertTrue(getService().mUnregistrations.isEmpty());
223
224         // Case 3: registration of undesired object triggers an unregistration.
225         getService().informRegistrationStatus(CLIENT_ID, ModelType.BOOKMARK.toObjectId(),
226                 RegistrationState.REGISTERED);
227         getService().informRegistrationStatus(CLIENT_ID, undesiredObjectId,
228                 RegistrationState.REGISTERED);
229         assertEquals(2, getService().mUnregistrations.size());
230         assertEquals(0, getService().mRegistrations.size());
231         assertEquals(CollectionUtil.newArrayList(ModelType.BOOKMARK.toObjectId()),
232                 getService().mUnregistrations.get(0));
233         assertEquals(CollectionUtil.newArrayList(undesiredObjectId),
234                 getService().mUnregistrations.get(1));
235
236         // Case 4: unregistration of a desired object triggers a registration.
237         getService().informRegistrationStatus(CLIENT_ID, ModelType.SESSION.toObjectId(),
238                 RegistrationState.UNREGISTERED);
239         getService().informRegistrationStatus(CLIENT_ID, desiredObjectId,
240                 RegistrationState.UNREGISTERED);
241         assertEquals(2, getService().mUnregistrations.size());
242         assertEquals(2, getService().mRegistrations.size());
243         assertEquals(CollectionUtil.newArrayList(ModelType.SESSION.toObjectId()),
244                 getService().mRegistrations.get(0));
245         assertEquals(CollectionUtil.newArrayList(desiredObjectId),
246                 getService().mRegistrations.get(1));
247     }
248
249     @SmallTest
250     @Feature({"Sync"})
251     public void testInformRegistrationFailure() {
252         /*
253          * Test plan: call inform registration failure under a variety of circumstances and verify
254          * that the appropriate (un)register calls are issued.
255          *
256          * 1. Transient registration failure for an object that should be registered. Register
257          *    should be called.
258          * 2. Permanent registration failure for an object that should be registered. No calls.
259          * 3. Transient registration failure for an object that should not be registered. Unregister
260          *    should be called.
261          * 4. Permanent registration failure for an object should not be registered. No calls.
262          */
263
264         // Initial test setup: persist a single registration into preferences.
265         InvalidationPreferences invPrefs = new InvalidationPreferences(getContext());
266         EditContext editContext = invPrefs.edit();
267         invPrefs.setSyncTypes(editContext, CollectionUtil.newArrayList("SESSION"));
268         ObjectId desiredObjectId = ObjectId.newInstance(1, "obj1".getBytes());
269         ObjectId undesiredObjectId = ObjectId.newInstance(1, "obj2".getBytes());
270         invPrefs.setObjectIds(editContext, CollectionUtil.newArrayList(desiredObjectId));
271         assertTrue(invPrefs.commit(editContext));
272
273         // Cases 2 and 4: permanent registration failures never cause calls to be made.
274         getService().informRegistrationFailure(CLIENT_ID, ModelType.SESSION.toObjectId(), false,
275                 "");
276         getService().informRegistrationFailure(CLIENT_ID, ModelType.BOOKMARK.toObjectId(), false,
277                 "");
278         getService().informRegistrationFailure(CLIENT_ID, desiredObjectId, false, "");
279         getService().informRegistrationFailure(CLIENT_ID, undesiredObjectId, false, "");
280         assertTrue(getService().mRegistrations.isEmpty());
281         assertTrue(getService().mUnregistrations.isEmpty());
282
283         // Case 1: transient failure of a desired registration results in re-registration.
284         getService().informRegistrationFailure(CLIENT_ID, ModelType.SESSION.toObjectId(), true, "");
285         getService().informRegistrationFailure(CLIENT_ID, desiredObjectId, true, "");
286         assertEquals(2, getService().mRegistrations.size());
287         assertTrue(getService().mUnregistrations.isEmpty());
288         assertEquals(CollectionUtil.newArrayList(ModelType.SESSION.toObjectId()),
289                 getService().mRegistrations.get(0));
290         assertEquals(CollectionUtil.newArrayList(desiredObjectId),
291                 getService().mRegistrations.get(1));
292
293         // Case 3: transient failure of an undesired registration results in unregistration.
294         getService().informRegistrationFailure(CLIENT_ID, ModelType.BOOKMARK.toObjectId(), true,
295                 "");
296         getService().informRegistrationFailure(CLIENT_ID, undesiredObjectId, true, "");
297         assertEquals(2, getService().mRegistrations.size());
298         assertEquals(2, getService().mUnregistrations.size());
299         assertEquals(CollectionUtil.newArrayList(ModelType.BOOKMARK.toObjectId()),
300                 getService().mUnregistrations.get(0));
301         assertEquals(CollectionUtil.newArrayList(undesiredObjectId),
302                 getService().mUnregistrations.get(1));
303     }
304
305     @SmallTest
306     @Feature({"Sync"})
307     public void testInformError() {
308         /*
309          * Test plan: call informError with both permanent and transient errors. Verify that
310          * the transient error causes no action to be taken and that the permanent error causes
311          * the client to be stopped.
312          */
313
314         // Client needs to be started for the permament error to trigger and stop.
315         getService().setShouldRunStates(true, true);
316         getService().onCreate();
317         getService().onHandleIntent(new Intent());
318         getService().mStartedServices.clear();  // Discard start intent.
319
320         // Transient error.
321         getService().informError(ErrorInfo.newInstance(0, true, "transient", null));
322         assertTrue(getService().mStartedServices.isEmpty());
323
324         // Permanent error.
325         getService().informError(ErrorInfo.newInstance(0, false, "permanent", null));
326         assertEquals(1, getService().mStartedServices.size());
327         Intent sentIntent = getService().mStartedServices.get(0);
328         Intent stopIntent = AndroidListener.createStopIntent(getContext());
329         assertTrue(stopIntent.filterEquals(sentIntent));
330         assertEquals(stopIntent.getExtras().keySet(), sentIntent.getExtras().keySet());
331     }
332
333     @SmallTest
334     @Feature({"Sync"})
335     public void testReadWriteState() {
336         /*
337          * Test plan: read, write, and read the internal notification client persistent state.
338          * Verify appropriate return values.
339          */
340         assertNull(getService().readState());
341         byte[] writtenState = new byte[]{7,4,0};
342         getService().writeState(writtenState);
343         assertTrue(Arrays.equals(writtenState, getService().readState()));
344     }
345
346     @SmallTest
347     @Feature({"Sync"})
348     public void testInvalidateWithPayload() {
349         doTestInvalidate(true);
350     }
351
352     @SmallTest
353     @Feature({"Sync"})
354     public void testInvalidateWithoutPayload() {
355         doTestInvalidate(false);
356     }
357
358     private void doTestInvalidate(boolean hasPayload) {
359         /*
360          * Test plan: call invalidate() with an invalidation that may or may not have a payload.
361          * Verify the produced bundle has the correct fields.
362          */
363         // Call invalidate.
364         int version = 4747;
365         ObjectId objectId = ObjectId.newInstance(55, "BOOKMARK".getBytes());
366         final String payload = "testInvalidate-" + hasPayload;
367         Invalidation invalidation = hasPayload ?
368                 Invalidation.newInstance(objectId, version, payload.getBytes()) :
369                 Invalidation.newInstance(objectId, version);
370         byte[] ackHandle = ("testInvalidate-" + hasPayload).getBytes();
371         getService().invalidate(invalidation, ackHandle);
372
373         // Validate bundle.
374         assertEquals(1, getService().mRequestedSyncs.size());
375         Bundle syncBundle = getService().mRequestedSyncs.get(0);
376         assertEquals(55, syncBundle.getInt("objectSource"));
377         assertEquals("BOOKMARK", syncBundle.getString("objectId"));
378         assertEquals(version, syncBundle.getLong("version"));
379         assertEquals(hasPayload ? payload : "", syncBundle.getString("payload"));
380
381         // Ensure acknowledged.
382         assertSingleAcknowledgement(ackHandle);
383     }
384
385     @SmallTest
386     @Feature({"Sync"})
387     public void testInvalidateUnknownVersion() {
388         /*
389          * Test plan: call invalidateUnknownVersion(). Verify the produced bundle has the correct
390          * fields.
391          */
392         ObjectId objectId = ObjectId.newInstance(55, "BOOKMARK".getBytes());
393         byte[] ackHandle = "testInvalidateUV".getBytes();
394         getService().invalidateUnknownVersion(objectId, ackHandle);
395
396         // Validate bundle.
397         assertEquals(1, getService().mRequestedSyncs.size());
398         Bundle syncBundle = getService().mRequestedSyncs.get(0);
399         assertEquals(55, syncBundle.getInt("objectSource"));
400         assertEquals("BOOKMARK", syncBundle.getString("objectId"));
401         assertEquals(0, syncBundle.getLong("version"));
402         assertEquals("", syncBundle.getString("payload"));
403
404         // Ensure acknowledged.
405         assertSingleAcknowledgement(ackHandle);
406     }
407
408     @SmallTest
409     @Feature({"Sync"})
410     public void testInvalidateAll() {
411         /*
412          * Test plan: call invalidateAll(). Verify the produced bundle has the correct fields.
413          */
414         byte[] ackHandle = "testInvalidateAll".getBytes();
415         getService().invalidateAll(ackHandle);
416
417         // Validate bundle.
418         assertEquals(1, getService().mRequestedSyncs.size());
419         Bundle syncBundle = getService().mRequestedSyncs.get(0);
420         assertEquals(0, syncBundle.keySet().size());
421
422         // Ensure acknowledged.
423         assertSingleAcknowledgement(ackHandle);
424     }
425
426     /** Asserts that the service received a single acknowledgement with handle {@code ackHandle}. */
427     private void assertSingleAcknowledgement(byte[] ackHandle) {
428         assertEquals(1, getService().mAcknowledgements.size());
429         assertTrue(Arrays.equals(ackHandle, getService().mAcknowledgements.get(0)));
430     }
431
432     @SmallTest
433     @Feature({"Sync"})
434     public void testShouldClientBeRunning() {
435         /*
436          * Test plan: call shouldClientBeRunning with various combinations of
437          * in-foreground/sync-enabled. Verify appropriate return values.
438          */
439         getService().setShouldRunStates(false, false);
440         assertFalse(getService().shouldClientBeRunning());
441
442         getService().setShouldRunStates(false, true);
443         assertFalse(getService().shouldClientBeRunning());
444
445         getService().setShouldRunStates(true, false);
446         assertFalse(getService().shouldClientBeRunning());
447
448         // Should only be running if both in the foreground and sync is enabled.
449         getService().setShouldRunStates(true, true);
450         assertTrue(getService().shouldClientBeRunning());
451     }
452
453     @SmallTest
454     @Feature({"Sync"})
455     public void testStartAndStopClient() {
456         /*
457          * Test plan: with Chrome configured so that the client should run, send it an empty
458          * intent. Even though no owning account is known, the client should still start. Send
459          * it a stop intent and verify that it stops.
460          */
461
462         // Note: we are manipulating the service object directly, rather than through startService,
463         // because otherwise we would need to handle the asynchronous execution model of the
464         // underlying IntentService.
465         getService().setShouldRunStates(true, true);
466         getService().onCreate();
467
468         Intent startIntent = new Intent();
469         getService().onHandleIntent(startIntent);
470         assertTrue(InvalidationService.getIsClientStartedForTest());
471
472         Intent stopIntent = new Intent().putExtra(InvalidationIntentProtocol.EXTRA_STOP, true);
473         getService().onHandleIntent(stopIntent);
474         assertFalse(InvalidationService.getIsClientStartedForTest());
475
476         // The issued intents should have been an AndroidListener start intent followed by an
477         // AndroidListener stop intent.
478         assertEquals(2, mStartServiceIntents.size());
479         assertTrue(isAndroidListenerStartIntent(mStartServiceIntents.get(0)));
480         assertTrue(isAndroidListenerStopIntent(mStartServiceIntents.get(1)));
481     }
482
483     @SmallTest
484     @Feature({"Sync"})
485     public void testClientStopsWhenShouldNotBeRunning() {
486         /*
487          * Test plan: start the client. Then, change the configuration so that Chrome should not
488          * be running. Send an intent to the service and verify that it stops.
489          */
490         getService().setShouldRunStates(true, true);
491         getService().onCreate();
492
493         // Start the service.
494         Intent startIntent = new Intent();
495         getService().onHandleIntent(startIntent);
496         assertTrue(InvalidationService.getIsClientStartedForTest());
497
498         // Change configuration.
499         getService().setShouldRunStates(false, false);
500
501         // Send an Intent and verify that the service stops.
502         getService().onHandleIntent(startIntent);
503         assertFalse(InvalidationService.getIsClientStartedForTest());
504
505         // The issued intents should have been an AndroidListener start intent followed by an
506         // AndroidListener stop intent.
507         assertEquals(2, mStartServiceIntents.size());
508         assertTrue(isAndroidListenerStartIntent(mStartServiceIntents.get(0)));
509         assertTrue(isAndroidListenerStopIntent(mStartServiceIntents.get(1)));
510     }
511
512     @SmallTest
513     @Feature({"Sync"})
514     public void testRegistrationIntent() {
515         /*
516          * Test plan: send a registration-change intent. Verify that it starts the client and
517          * sets both the account and registrations in shared preferences.
518          */
519         getService().setShouldRunStates(true, true);
520         getService().onCreate();
521
522         // Send register Intent.
523         Set<ModelType> desiredRegistrations = CollectionUtil.newHashSet(
524                 ModelType.BOOKMARK, ModelType.SESSION);
525         Account account = AccountManagerHelper.createAccountFromName("test@example.com");
526         Intent registrationIntent = InvalidationIntentProtocol.createRegisterIntent(account, false,
527                 desiredRegistrations);
528         getService().onHandleIntent(registrationIntent);
529
530         // Verify client started and state written.
531         assertTrue(InvalidationService.getIsClientStartedForTest());
532         InvalidationPreferences invPrefs = new InvalidationPreferences(getContext());
533         assertEquals(account, invPrefs.getSavedSyncedAccount());
534         assertEquals(ModelType.modelTypesToSyncTypes(desiredRegistrations),
535                 invPrefs.getSavedSyncedTypes());
536         assertNull(invPrefs.getSavedObjectIds());
537         assertEquals(1, mStartServiceIntents.size());
538         assertTrue(isAndroidListenerStartIntent(mStartServiceIntents.get(0)));
539
540         // Send another registration-change intent, this type with all-types set to true, and
541         // verify that the on-disk state is updated and that no addition Intents are issued.
542         getService().onHandleIntent(
543                 InvalidationIntentProtocol.createRegisterIntent(account, true, null));
544         assertEquals(account, invPrefs.getSavedSyncedAccount());
545         assertEquals(CollectionUtil.newHashSet(ModelType.ALL_TYPES_TYPE),
546                 invPrefs.getSavedSyncedTypes());
547         assertEquals(1, mStartServiceIntents.size());
548
549         // Finally, send one more registration-change intent, this time with a different account,
550         // and verify that it both updates the account, stops thye existing client, and
551         // starts a new client.
552         Account account2 = AccountManagerHelper.createAccountFromName("test2@example.com");
553         getService().onHandleIntent(
554                 InvalidationIntentProtocol.createRegisterIntent(account2, true, null));
555         assertEquals(account2, invPrefs.getSavedSyncedAccount());
556         assertEquals(3, mStartServiceIntents.size());
557         assertTrue(isAndroidListenerStartIntent(mStartServiceIntents.get(0)));
558         assertTrue(isAndroidListenerStopIntent(mStartServiceIntents.get(1)));
559         assertTrue(isAndroidListenerStartIntent(mStartServiceIntents.get(2)));
560     }
561
562     /**
563      * Determines if the correct object ids have been written to preferences and registered with the
564      * invalidation client.
565      *
566      * @param expectedTypes The Sync types expected to be registered.
567      * @param expectedObjectIds The additional object ids expected to be registered.
568      * @param isReady Whether the client is ready to register/unregister.
569      */
570     private boolean expectedObjectIdsRegistered(Set<ModelType> expectedTypes,
571             Set<ObjectId> expectedObjectIds, boolean isReady) {
572         // Get synced types saved to preferences.
573         Set<String> expectedSyncTypes = ModelType.modelTypesToSyncTypes(expectedTypes);
574         InvalidationPreferences invPrefs = new InvalidationPreferences(getContext());
575         Set<String> actualSyncTypes = invPrefs.getSavedSyncedTypes();
576         if (actualSyncTypes == null) {
577             actualSyncTypes = new HashSet<String>();
578         }
579
580         // Get object ids saved to preferences.
581         Set<ObjectId> actualObjectIds = invPrefs.getSavedObjectIds();
582         if (actualObjectIds == null) {
583             actualObjectIds = new HashSet<ObjectId>();
584         }
585
586         // Get expected registered object ids.
587         Set<ObjectId> expectedRegisteredIds = new HashSet<ObjectId>();
588         if (isReady) {
589             expectedRegisteredIds.addAll(ModelType.modelTypesToObjectIds(expectedTypes));
590             expectedRegisteredIds.addAll(expectedObjectIds);
591         }
592
593         return actualSyncTypes.equals(expectedSyncTypes) &&
594                 actualObjectIds.equals(expectedObjectIds) &&
595                 getService().mCurrentRegistrations.equals(expectedRegisteredIds);
596     }
597
598     @SmallTest
599     @Feature({"Sync"})
600     public void testRegistrationIntentWithTypesAndObjectIds() {
601         /*
602          * Test plan: send a mix of registration-change intents: some for Sync types and some for
603          * object ids. Verify that registering for Sync types does not interfere with object id
604          * registration and vice-versa.
605          */
606         getService().setShouldRunStates(true, true);
607         getService().onCreate();
608
609         Account account = AccountManagerHelper.createAccountFromName("test@example.com");
610         Set<ObjectId> objectIds = new HashSet<ObjectId>();
611         Set<ModelType> types = new HashSet<ModelType>();
612
613         // Register for some object ids.
614         objectIds.add(ObjectId.newInstance(1, "obj1".getBytes()));
615         objectIds.add(ObjectId.newInstance(2, "obj2".getBytes()));
616         Intent registrationIntent =
617                 InvalidationIntentProtocol.createRegisterIntent(account, new int[] {1, 2},
618                         new String[] {"obj1", "obj2"});
619         getService().onHandleIntent(registrationIntent);
620         assertTrue(expectedObjectIdsRegistered(types, objectIds, false /* isReady */));
621
622         // Register for some types.
623         types.add(ModelType.BOOKMARK);
624         types.add(ModelType.SESSION);
625         registrationIntent = InvalidationIntentProtocol.createRegisterIntent(account, false, types);
626         getService().onHandleIntent(registrationIntent);
627         assertTrue(expectedObjectIdsRegistered(types, objectIds, false /* isReady */));
628
629         // Set client to be ready and verify registrations.
630         getService().ready(CLIENT_ID);
631         assertTrue(expectedObjectIdsRegistered(types, objectIds, true /* isReady */));
632
633         // Change object id registration with types registered.
634         objectIds.add(ObjectId.newInstance(3, "obj3".getBytes()));
635         registrationIntent =
636                 InvalidationIntentProtocol.createRegisterIntent(account, new int[] {1, 2, 3},
637                         new String[] {"obj1", "obj2", "obj3"});
638         getService().onHandleIntent(registrationIntent);
639         assertTrue(expectedObjectIdsRegistered(types, objectIds, true /* isReady */));
640
641         // Change type registration with object ids registered.
642         types.remove(ModelType.BOOKMARK);
643         registrationIntent = InvalidationIntentProtocol.createRegisterIntent(account, false, types);
644         getService().onHandleIntent(registrationIntent);
645         assertTrue(expectedObjectIdsRegistered(types, objectIds, true /* isReady */));
646
647         // Unregister all types.
648         types.clear();
649         registrationIntent = InvalidationIntentProtocol.createRegisterIntent(account, false, types);
650         getService().onHandleIntent(registrationIntent);
651         assertTrue(expectedObjectIdsRegistered(types, objectIds, true /* isReady */));
652
653         // Change object id registration with no types registered.
654         objectIds.remove(ObjectId.newInstance(2, "obj2".getBytes()));
655         registrationIntent =
656                 InvalidationIntentProtocol.createRegisterIntent(account, new int[] {1, 3},
657                         new String[] {"obj1", "obj3"});
658         getService().onHandleIntent(registrationIntent);
659         assertTrue(expectedObjectIdsRegistered(types, objectIds, true /* isReady */));
660
661         // Unregister all object ids.
662         objectIds.clear();
663         registrationIntent = InvalidationIntentProtocol.createRegisterIntent(account, new int[0],
664                 new String[0]);
665         getService().onHandleIntent(registrationIntent);
666         assertTrue(expectedObjectIdsRegistered(types, objectIds, true /* isReady */));
667
668         // Change type registration with no object ids registered.
669         types.add(ModelType.BOOKMARK);
670         types.add(ModelType.PASSWORD);
671         registrationIntent = InvalidationIntentProtocol.createRegisterIntent(account, false, types);
672         getService().onHandleIntent(registrationIntent);
673         assertTrue(expectedObjectIdsRegistered(types, objectIds, true /* isReady */));
674     }
675
676     @SmallTest
677     @Feature({"Sync"})
678     public void testRegistrationIntentNoProxyTabsUsingReady() {
679         getService().setShouldRunStates(true, true);
680         getService().onCreate();
681
682         // Send register Intent.
683         Account account = AccountManagerHelper.createAccountFromName("test@example.com");
684         Intent registrationIntent =
685                 InvalidationIntentProtocol.createRegisterIntent(account, true, null);
686         getService().onHandleIntent(registrationIntent);
687
688         // Verify client started and state written.
689         assertTrue(InvalidationService.getIsClientStartedForTest());
690         InvalidationPreferences invPrefs = new InvalidationPreferences(getContext());
691         assertEquals(account, invPrefs.getSavedSyncedAccount());
692         assertEquals(CollectionUtil.newHashSet(ModelType.ALL_TYPES_TYPE),
693                 invPrefs.getSavedSyncedTypes());
694         assertEquals(1, mStartServiceIntents.size());
695         assertTrue(isAndroidListenerStartIntent(mStartServiceIntents.get(0)));
696
697         // Set client to be ready. This triggers registrations.
698         getService().ready(CLIENT_ID);
699         assertTrue(Arrays.equals(CLIENT_ID, InvalidationService.getClientIdForTest()));
700
701         // Ensure registrations are correct.
702         Set<ObjectId> expectedTypes =
703                 ModelType.modelTypesToObjectIds(EnumSet.allOf(ModelType.class));
704         assertEquals(expectedTypes, new HashSet<ObjectId>(getService().mRegistrations.get(0)));
705     }
706
707     @SmallTest
708     @Feature({"Sync"})
709     public void testRegistrationIntentNoProxyTabsAlreadyWithClientId() {
710         getService().setShouldRunStates(true, true);
711         getService().onCreate();
712
713         // Send register Intent with no desired types.
714         Account account = AccountManagerHelper.createAccountFromName("test@example.com");
715         Intent registrationIntent = InvalidationIntentProtocol.createRegisterIntent(
716                 account, false, new HashSet<ModelType>());
717         getService().onHandleIntent(registrationIntent);
718
719         // Verify client started and state written.
720         assertTrue(InvalidationService.getIsClientStartedForTest());
721         InvalidationPreferences invPrefs = new InvalidationPreferences(getContext());
722         assertEquals(account, invPrefs.getSavedSyncedAccount());
723         assertEquals(new HashSet<String>(), invPrefs.getSavedSyncedTypes());
724         assertEquals(1, mStartServiceIntents.size());
725         assertTrue(isAndroidListenerStartIntent(mStartServiceIntents.get(0)));
726
727         // Make sure client is ready.
728         getService().ready(CLIENT_ID);
729         assertTrue(Arrays.equals(CLIENT_ID, InvalidationService.getClientIdForTest()));
730
731         // Choose to register for all types in an already ready client.
732         registrationIntent = InvalidationIntentProtocol.createRegisterIntent(account, true, null);
733         getService().onHandleIntent(registrationIntent);
734
735         // Ensure registrations are correct.
736         assertEquals(1, getService().mRegistrations.size());
737         Set<ObjectId> expectedTypes =
738                 ModelType.modelTypesToObjectIds(EnumSet.allOf(ModelType.class));
739         assertEquals(expectedTypes, new HashSet<ObjectId>(getService().mRegistrations.get(0)));
740     }
741
742     @SmallTest
743     @Feature({"Sync"})
744     public void testRegistrationIntentWhenClientShouldNotBeRunning() {
745         /*
746          * Test plan: send a registration change event when the client should not be running.
747          * Verify that the service updates the on-disk state but does not start the client.
748          */
749         getService().onCreate();
750
751         // Send register Intent.
752         Account account = AccountManagerHelper.createAccountFromName("test@example.com");
753         Set<ModelType> desiredRegistrations = CollectionUtil.newHashSet(
754                 ModelType.BOOKMARK, ModelType.SESSION);
755         Intent registrationIntent = InvalidationIntentProtocol.createRegisterIntent(account, false,
756                 desiredRegistrations);
757         getService().onHandleIntent(registrationIntent);
758
759         // Verify state written but client not started.
760         assertFalse(InvalidationService.getIsClientStartedForTest());
761         InvalidationPreferences invPrefs = new InvalidationPreferences(getContext());
762         assertEquals(account, invPrefs.getSavedSyncedAccount());
763         assertEquals(ModelType.modelTypesToSyncTypes(desiredRegistrations),
764                 invPrefs.getSavedSyncedTypes());
765         assertEquals(0, mStartServiceIntents.size());
766     }
767
768     @SmallTest
769     @Feature({"Sync"})
770     public void testDeferredRegistrationsIssued() {
771         /*
772          * Test plan: send a registration-change intent. Verify that the client issues a start
773          * intent but makes no registration calls. Issue a reissueRegistrations call and verify
774          * that the client does issue the appropriate registrations.
775          */
776         getService().setShouldRunStates(true, true);
777         getService().onCreate();
778
779         // Send register Intent. Verify client started but no registrations issued.
780         Account account = AccountManagerHelper.createAccountFromName("test@example.com");
781         Set<ModelType> desiredRegistrations = CollectionUtil.newHashSet(
782                 ModelType.BOOKMARK, ModelType.SESSION);
783         Set<ObjectId> desiredObjectIds = ModelType.modelTypesToObjectIds(desiredRegistrations);
784
785         Intent registrationIntent = InvalidationIntentProtocol.createRegisterIntent(account, false,
786                 desiredRegistrations);
787         getService().onHandleIntent(registrationIntent);
788         assertTrue(InvalidationService.getIsClientStartedForTest());
789         assertEquals(1, mStartServiceIntents.size());
790         assertTrue(isAndroidListenerStartIntent(mStartServiceIntents.get(0)));
791         InvalidationPreferences invPrefs = new InvalidationPreferences(getContext());
792         assertEquals(ModelType.modelTypesToSyncTypes(desiredRegistrations),
793                 invPrefs.getSavedSyncedTypes());
794         assertEquals(desiredObjectIds, getService().readRegistrationsFromPrefs());
795
796         // Issue reissueRegistrations; verify registration intent issues.
797         getService().reissueRegistrations(CLIENT_ID);
798         assertEquals(2, mStartServiceIntents.size());
799         Intent expectedRegisterIntent = AndroidListener.createRegisterIntent(
800                 getContext(),
801                 CLIENT_ID,
802                 desiredObjectIds);
803         Intent actualRegisterIntent = mStartServiceIntents.get(1);
804         assertTrue(expectedRegisterIntent.filterEquals(actualRegisterIntent));
805         assertEquals(expectedRegisterIntent.getExtras().keySet(),
806                 actualRegisterIntent.getExtras().keySet());
807         assertEquals(
808                 desiredObjectIds,
809                 new HashSet<ObjectId>(getService().mRegistrations.get(0)));
810     }
811
812     @SmallTest
813     @Feature({"Sync"})
814     public void testRegistrationRetries() {
815         /*
816          * Test plan: validate that the alarm receiver used by the AndroidListener underlying
817          * InvalidationService is correctly configured in the manifest and retries registrations
818          * with exponential backoff. May need to be implemented as a downstream Chrome for Android
819          * test.
820          */
821         // TODO(dsmyers): implement.
822         // Bug: https://code.google.com/p/chromium/issues/detail?id=172398
823     }
824
825     /** Returns whether {@code intent} is an {@link AndroidListener} start intent. */
826     private boolean isAndroidListenerStartIntent(Intent intent) {
827         Intent startIntent = AndroidListener.createStartIntent(getContext(),
828                 InvalidationService.CLIENT_TYPE, "unused".getBytes());
829         return intent.getExtras().keySet().equals(startIntent.getExtras().keySet());
830     }
831
832     /** Returns whether {@code intent} is an {@link AndroidListener} stop intent. */
833     private boolean isAndroidListenerStopIntent(Intent intent) {
834         Intent stopIntent = AndroidListener.createStopIntent(getContext());
835         return intent.getExtras().keySet().equals(stopIntent.getExtras().keySet());
836     }
837 }