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