| // Copyright 2013 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| package org.chromium.sync.notifier; |
| |
| import android.accounts.Account; |
| import android.content.ComponentName; |
| import android.content.Intent; |
| import android.os.Bundle; |
| import android.test.ServiceTestCase; |
| import android.test.suitebuilder.annotation.SmallTest; |
| |
| import com.google.ipc.invalidation.external.client.InvalidationListener.RegistrationState; |
| import com.google.ipc.invalidation.external.client.contrib.AndroidListener; |
| import com.google.ipc.invalidation.external.client.types.ErrorInfo; |
| import com.google.ipc.invalidation.external.client.types.Invalidation; |
| import com.google.ipc.invalidation.external.client.types.ObjectId; |
| |
| import org.chromium.base.CollectionUtil; |
| import org.chromium.base.test.util.AdvancedMockContext; |
| import org.chromium.base.test.util.Feature; |
| import org.chromium.sync.internal_api.pub.base.ModelType; |
| import org.chromium.sync.notifier.InvalidationPreferences.EditContext; |
| import org.chromium.sync.signin.AccountManagerHelper; |
| |
| import java.util.ArrayList; |
| import java.util.Arrays; |
| import java.util.EnumSet; |
| import java.util.HashSet; |
| import java.util.List; |
| import java.util.Set; |
| |
| /** |
| * Tests for the {@link InvalidationService}. |
| * |
| * @author dsmyers@google.com (Daniel Myers) |
| */ |
| public class InvalidationServiceTest extends ServiceTestCase<TestableInvalidationService> { |
| /** Id used when creating clients. */ |
| private static final byte[] CLIENT_ID = new byte[]{0, 4, 7}; |
| |
| /** Intents provided to {@link #startService}. */ |
| private List<Intent> mStartServiceIntents; |
| |
| public InvalidationServiceTest() { |
| super(TestableInvalidationService.class); |
| } |
| |
| @Override |
| public void setUp() throws Exception { |
| super.setUp(); |
| mStartServiceIntents = new ArrayList<Intent>(); |
| setContext(new AdvancedMockContext(getContext()) { |
| @Override |
| public ComponentName startService(Intent intent) { |
| mStartServiceIntents.add(intent); |
| return new ComponentName(this, InvalidationServiceTest.class); |
| } |
| }); |
| setupService(); |
| } |
| |
| @Override |
| public void tearDown() throws Exception { |
| if (InvalidationService.getIsClientStartedForTest()) { |
| Intent stopIntent = createStopIntent(); |
| getService().onHandleIntent(stopIntent); |
| } |
| assertFalse(InvalidationService.getIsClientStartedForTest()); |
| super.tearDown(); |
| } |
| |
| @SmallTest |
| @Feature({"Sync"}) |
| public void testComputeRegistrationOps() { |
| /* |
| * Test plan: compute the set of registration operations resulting from various combinations |
| * of existing and desired registrations. Verifying that they are correct. |
| */ |
| Set<ObjectId> regAccumulator = new HashSet<ObjectId>(); |
| Set<ObjectId> unregAccumulator = new HashSet<ObjectId>(); |
| |
| // Empty existing and desired registrations should yield empty operation sets. |
| InvalidationService.computeRegistrationOps( |
| ModelType.modelTypesToObjectIds( |
| CollectionUtil.newHashSet(ModelType.BOOKMARK, ModelType.SESSION)), |
| ModelType.modelTypesToObjectIds( |
| CollectionUtil.newHashSet(ModelType.BOOKMARK, ModelType.SESSION)), |
| regAccumulator, unregAccumulator); |
| assertEquals(0, regAccumulator.size()); |
| assertEquals(0, unregAccumulator.size()); |
| |
| // Equal existing and desired registrations should yield empty operation sets. |
| InvalidationService.computeRegistrationOps(new HashSet<ObjectId>(), |
| new HashSet<ObjectId>(), regAccumulator, unregAccumulator); |
| assertEquals(0, regAccumulator.size()); |
| assertEquals(0, unregAccumulator.size()); |
| |
| // Empty existing and non-empty desired registrations should yield desired registrations |
| // as the registration operations to do and no unregistrations. |
| Set<ObjectId> desiredTypes = |
| CollectionUtil.newHashSet( |
| ModelType.BOOKMARK.toObjectId(), ModelType.SESSION.toObjectId()); |
| InvalidationService.computeRegistrationOps( |
| new HashSet<ObjectId>(), |
| desiredTypes, |
| regAccumulator, unregAccumulator); |
| assertEquals( |
| CollectionUtil.newHashSet( |
| ModelType.BOOKMARK.toObjectId(), ModelType.SESSION.toObjectId()), |
| new HashSet<ObjectId>(regAccumulator)); |
| assertEquals(0, unregAccumulator.size()); |
| regAccumulator.clear(); |
| |
| // Unequal existing and desired registrations should yield both registrations and |
| // unregistrations. We should unregister TYPED_URL and register BOOKMARK, keeping SESSION. |
| InvalidationService.computeRegistrationOps( |
| CollectionUtil.newHashSet( |
| ModelType.SESSION.toObjectId(), ModelType.TYPED_URL.toObjectId()), |
| CollectionUtil.newHashSet( |
| ModelType.BOOKMARK.toObjectId(), ModelType.SESSION.toObjectId()), |
| regAccumulator, unregAccumulator); |
| assertEquals(CollectionUtil.newHashSet(ModelType.BOOKMARK.toObjectId()), regAccumulator); |
| assertEquals(CollectionUtil.newHashSet(ModelType.TYPED_URL.toObjectId()), |
| unregAccumulator); |
| regAccumulator.clear(); |
| unregAccumulator.clear(); |
| } |
| |
| @SmallTest |
| @Feature({"Sync"}) |
| public void testReady() { |
| /** |
| * Test plan: call ready. Verify that the service sets the client id correctly and reissues |
| * pending registrations. |
| */ |
| |
| // Persist some registrations. |
| InvalidationPreferences invPrefs = new InvalidationPreferences(getContext()); |
| EditContext editContext = invPrefs.edit(); |
| invPrefs.setSyncTypes(editContext, CollectionUtil.newArrayList("BOOKMARK", "SESSION")); |
| ObjectId objectId = ObjectId.newInstance(1, "obj".getBytes()); |
| invPrefs.setObjectIds(editContext, CollectionUtil.newArrayList(objectId)); |
| assertTrue(invPrefs.commit(editContext)); |
| |
| // Issue ready. |
| getService().ready(CLIENT_ID); |
| assertTrue(Arrays.equals(CLIENT_ID, InvalidationService.getClientIdForTest())); |
| byte[] otherCid = "otherCid".getBytes(); |
| getService().ready(otherCid); |
| assertTrue(Arrays.equals(otherCid, InvalidationService.getClientIdForTest())); |
| |
| // Verify registrations issued. |
| assertEquals(CollectionUtil.newHashSet( |
| ModelType.BOOKMARK.toObjectId(), ModelType.SESSION.toObjectId(), objectId), |
| new HashSet<ObjectId>(getService().mRegistrations.get(0))); |
| } |
| |
| @SmallTest |
| @Feature({"Sync"}) |
| public void testReissueRegistrations() { |
| /* |
| * Test plan: call the reissueRegistrations method of the listener with both empty and |
| * non-empty sets of desired registrations stored in preferences. Verify that no register |
| * intent is set in the first case and that the appropriate register intent is sent in |
| * the second. |
| */ |
| |
| // No persisted registrations. |
| getService().reissueRegistrations(CLIENT_ID); |
| assertTrue(getService().mRegistrations.isEmpty()); |
| |
| // Persist some registrations. |
| InvalidationPreferences invPrefs = new InvalidationPreferences(getContext()); |
| EditContext editContext = invPrefs.edit(); |
| invPrefs.setSyncTypes(editContext, CollectionUtil.newArrayList("BOOKMARK", "SESSION")); |
| ObjectId objectId = ObjectId.newInstance(1, "obj".getBytes()); |
| invPrefs.setObjectIds(editContext, CollectionUtil.newArrayList(objectId)); |
| assertTrue(invPrefs.commit(editContext)); |
| |
| // Reissue registrations and verify that the appropriate registrations are issued. |
| getService().reissueRegistrations(CLIENT_ID); |
| assertEquals(1, getService().mRegistrations.size()); |
| assertEquals(CollectionUtil.newHashSet( |
| ModelType.BOOKMARK.toObjectId(), ModelType.SESSION.toObjectId(), objectId), |
| new HashSet<ObjectId>(getService().mRegistrations.get(0))); |
| } |
| |
| @SmallTest |
| @Feature({"Sync"}) |
| public void testInformRegistrationStatus() { |
| /* |
| * Test plan: call inform registration status under a variety of circumstances and verify |
| * that the appropriate (un)register calls are issued. |
| * |
| * 1. Registration of desired object. No calls issued. |
| * 2. Unregistration of undesired object. No calls issued. |
| * 3. Registration of undesired object. Unregistration issued. |
| * 4. Unregistration of desired object. Registration issued. |
| */ |
| // Initial test setup: persist a single registration into preferences. |
| InvalidationPreferences invPrefs = new InvalidationPreferences(getContext()); |
| EditContext editContext = invPrefs.edit(); |
| invPrefs.setSyncTypes(editContext, CollectionUtil.newArrayList("SESSION")); |
| ObjectId desiredObjectId = ObjectId.newInstance(1, "obj1".getBytes()); |
| ObjectId undesiredObjectId = ObjectId.newInstance(1, "obj2".getBytes()); |
| invPrefs.setObjectIds(editContext, CollectionUtil.newArrayList(desiredObjectId)); |
| assertTrue(invPrefs.commit(editContext)); |
| |
| // Cases 1 and 2: calls matching desired state cause no actions. |
| getService().informRegistrationStatus(CLIENT_ID, ModelType.SESSION.toObjectId(), |
| RegistrationState.REGISTERED); |
| getService().informRegistrationStatus(CLIENT_ID, desiredObjectId, |
| RegistrationState.REGISTERED); |
| getService().informRegistrationStatus(CLIENT_ID, ModelType.BOOKMARK.toObjectId(), |
| RegistrationState.UNREGISTERED); |
| getService().informRegistrationStatus(CLIENT_ID, undesiredObjectId, |
| RegistrationState.UNREGISTERED); |
| assertTrue(getService().mRegistrations.isEmpty()); |
| assertTrue(getService().mUnregistrations.isEmpty()); |
| |
| // Case 3: registration of undesired object triggers an unregistration. |
| getService().informRegistrationStatus(CLIENT_ID, ModelType.BOOKMARK.toObjectId(), |
| RegistrationState.REGISTERED); |
| getService().informRegistrationStatus(CLIENT_ID, undesiredObjectId, |
| RegistrationState.REGISTERED); |
| assertEquals(2, getService().mUnregistrations.size()); |
| assertEquals(0, getService().mRegistrations.size()); |
| assertEquals(CollectionUtil.newArrayList(ModelType.BOOKMARK.toObjectId()), |
| getService().mUnregistrations.get(0)); |
| assertEquals(CollectionUtil.newArrayList(undesiredObjectId), |
| getService().mUnregistrations.get(1)); |
| |
| // Case 4: unregistration of a desired object triggers a registration. |
| getService().informRegistrationStatus(CLIENT_ID, ModelType.SESSION.toObjectId(), |
| RegistrationState.UNREGISTERED); |
| getService().informRegistrationStatus(CLIENT_ID, desiredObjectId, |
| RegistrationState.UNREGISTERED); |
| assertEquals(2, getService().mUnregistrations.size()); |
| assertEquals(2, getService().mRegistrations.size()); |
| assertEquals(CollectionUtil.newArrayList(ModelType.SESSION.toObjectId()), |
| getService().mRegistrations.get(0)); |
| assertEquals(CollectionUtil.newArrayList(desiredObjectId), |
| getService().mRegistrations.get(1)); |
| } |
| |
| @SmallTest |
| @Feature({"Sync"}) |
| public void testInformRegistrationFailure() { |
| /* |
| * Test plan: call inform registration failure under a variety of circumstances and verify |
| * that the appropriate (un)register calls are issued. |
| * |
| * 1. Transient registration failure for an object that should be registered. Register |
| * should be called. |
| * 2. Permanent registration failure for an object that should be registered. No calls. |
| * 3. Transient registration failure for an object that should not be registered. Unregister |
| * should be called. |
| * 4. Permanent registration failure for an object should not be registered. No calls. |
| */ |
| |
| // Initial test setup: persist a single registration into preferences. |
| InvalidationPreferences invPrefs = new InvalidationPreferences(getContext()); |
| EditContext editContext = invPrefs.edit(); |
| invPrefs.setSyncTypes(editContext, CollectionUtil.newArrayList("SESSION")); |
| ObjectId desiredObjectId = ObjectId.newInstance(1, "obj1".getBytes()); |
| ObjectId undesiredObjectId = ObjectId.newInstance(1, "obj2".getBytes()); |
| invPrefs.setObjectIds(editContext, CollectionUtil.newArrayList(desiredObjectId)); |
| assertTrue(invPrefs.commit(editContext)); |
| |
| // Cases 2 and 4: permanent registration failures never cause calls to be made. |
| getService().informRegistrationFailure(CLIENT_ID, ModelType.SESSION.toObjectId(), false, |
| ""); |
| getService().informRegistrationFailure(CLIENT_ID, ModelType.BOOKMARK.toObjectId(), false, |
| ""); |
| getService().informRegistrationFailure(CLIENT_ID, desiredObjectId, false, ""); |
| getService().informRegistrationFailure(CLIENT_ID, undesiredObjectId, false, ""); |
| assertTrue(getService().mRegistrations.isEmpty()); |
| assertTrue(getService().mUnregistrations.isEmpty()); |
| |
| // Case 1: transient failure of a desired registration results in re-registration. |
| getService().informRegistrationFailure(CLIENT_ID, ModelType.SESSION.toObjectId(), true, ""); |
| getService().informRegistrationFailure(CLIENT_ID, desiredObjectId, true, ""); |
| assertEquals(2, getService().mRegistrations.size()); |
| assertTrue(getService().mUnregistrations.isEmpty()); |
| assertEquals(CollectionUtil.newArrayList(ModelType.SESSION.toObjectId()), |
| getService().mRegistrations.get(0)); |
| assertEquals(CollectionUtil.newArrayList(desiredObjectId), |
| getService().mRegistrations.get(1)); |
| |
| // Case 3: transient failure of an undesired registration results in unregistration. |
| getService().informRegistrationFailure(CLIENT_ID, ModelType.BOOKMARK.toObjectId(), true, |
| ""); |
| getService().informRegistrationFailure(CLIENT_ID, undesiredObjectId, true, ""); |
| assertEquals(2, getService().mRegistrations.size()); |
| assertEquals(2, getService().mUnregistrations.size()); |
| assertEquals(CollectionUtil.newArrayList(ModelType.BOOKMARK.toObjectId()), |
| getService().mUnregistrations.get(0)); |
| assertEquals(CollectionUtil.newArrayList(undesiredObjectId), |
| getService().mUnregistrations.get(1)); |
| } |
| |
| @SmallTest |
| @Feature({"Sync"}) |
| public void testInformError() { |
| /* |
| * Test plan: call informError with both permanent and transient errors. Verify that |
| * the transient error causes no action to be taken and that the permanent error causes |
| * the client to be stopped. |
| */ |
| |
| // Client needs to be started for the permament error to trigger and stop. |
| getService().setShouldRunStates(true, true); |
| getService().onCreate(); |
| getService().onHandleIntent(createStartIntent()); |
| getService().mStartedServices.clear(); // Discard start intent. |
| |
| // Transient error. |
| getService().informError(ErrorInfo.newInstance(0, true, "transient", null)); |
| assertTrue(getService().mStartedServices.isEmpty()); |
| |
| // Permanent error. |
| getService().informError(ErrorInfo.newInstance(0, false, "permanent", null)); |
| assertEquals(1, getService().mStartedServices.size()); |
| Intent sentIntent = getService().mStartedServices.get(0); |
| Intent stopIntent = AndroidListener.createStopIntent(getContext()); |
| assertTrue(stopIntent.filterEquals(sentIntent)); |
| assertEquals(stopIntent.getExtras().keySet(), sentIntent.getExtras().keySet()); |
| } |
| |
| @SmallTest |
| @Feature({"Sync"}) |
| public void testReadWriteState() { |
| /* |
| * Test plan: read, write, and read the internal notification client persistent state. |
| * Verify appropriate return values. |
| */ |
| assertNull(getService().readState()); |
| byte[] writtenState = new byte[]{7, 4, 0}; |
| getService().writeState(writtenState); |
| assertTrue(Arrays.equals(writtenState, getService().readState())); |
| } |
| |
| @SmallTest |
| @Feature({"Sync"}) |
| public void testInvalidateWithPayload() { |
| doTestInvalidate(true); |
| } |
| |
| @SmallTest |
| @Feature({"Sync"}) |
| public void testInvalidateWithoutPayload() { |
| doTestInvalidate(false); |
| } |
| |
| private void doTestInvalidate(boolean hasPayload) { |
| /* |
| * Test plan: call invalidate() with an invalidation that may or may not have a payload. |
| * Verify the produced bundle has the correct fields. |
| */ |
| // Call invalidate. |
| int version = 4747; |
| ObjectId objectId = ObjectId.newInstance(55, "BOOKMARK".getBytes()); |
| final String payload = "testInvalidate-" + hasPayload; |
| Invalidation invalidation = hasPayload ? |
| Invalidation.newInstance(objectId, version, payload.getBytes()) : |
| Invalidation.newInstance(objectId, version); |
| byte[] ackHandle = ("testInvalidate-" + hasPayload).getBytes(); |
| getService().invalidate(invalidation, ackHandle); |
| |
| // Validate bundle. |
| assertEquals(1, getService().mRequestedSyncs.size()); |
| Bundle syncBundle = getService().mRequestedSyncs.get(0); |
| assertEquals(55, syncBundle.getInt("objectSource")); |
| assertEquals("BOOKMARK", syncBundle.getString("objectId")); |
| assertEquals(version, syncBundle.getLong("version")); |
| assertEquals(hasPayload ? payload : "", syncBundle.getString("payload")); |
| |
| // Ensure acknowledged. |
| assertSingleAcknowledgement(ackHandle); |
| } |
| |
| @SmallTest |
| @Feature({"Sync"}) |
| public void testInvalidateUnknownVersion() { |
| /* |
| * Test plan: call invalidateUnknownVersion(). Verify the produced bundle has the correct |
| * fields. |
| */ |
| ObjectId objectId = ObjectId.newInstance(55, "BOOKMARK".getBytes()); |
| byte[] ackHandle = "testInvalidateUV".getBytes(); |
| getService().invalidateUnknownVersion(objectId, ackHandle); |
| |
| // Validate bundle. |
| assertEquals(1, getService().mRequestedSyncs.size()); |
| Bundle syncBundle = getService().mRequestedSyncs.get(0); |
| assertEquals(55, syncBundle.getInt("objectSource")); |
| assertEquals("BOOKMARK", syncBundle.getString("objectId")); |
| assertEquals(0, syncBundle.getLong("version")); |
| assertEquals("", syncBundle.getString("payload")); |
| |
| // Ensure acknowledged. |
| assertSingleAcknowledgement(ackHandle); |
| } |
| |
| @SmallTest |
| @Feature({"Sync"}) |
| public void testInvalidateAll() { |
| /* |
| * Test plan: call invalidateAll(). Verify the produced bundle has the correct fields. |
| */ |
| byte[] ackHandle = "testInvalidateAll".getBytes(); |
| getService().invalidateAll(ackHandle); |
| |
| // Validate bundle. |
| assertEquals(1, getService().mRequestedSyncs.size()); |
| Bundle syncBundle = getService().mRequestedSyncs.get(0); |
| assertEquals(0, syncBundle.keySet().size()); |
| |
| // Ensure acknowledged. |
| assertSingleAcknowledgement(ackHandle); |
| } |
| |
| /** Asserts that the service received a single acknowledgement with handle {@code ackHandle}. */ |
| private void assertSingleAcknowledgement(byte[] ackHandle) { |
| assertEquals(1, getService().mAcknowledgements.size()); |
| assertTrue(Arrays.equals(ackHandle, getService().mAcknowledgements.get(0))); |
| } |
| |
| @SmallTest |
| @Feature({"Sync"}) |
| public void testShouldClientBeRunning() { |
| /* |
| * Test plan: call shouldClientBeRunning with various combinations of |
| * in-foreground/sync-enabled. Verify appropriate return values. |
| */ |
| getService().setShouldRunStates(false, false); |
| assertFalse(getService().shouldClientBeRunning()); |
| |
| getService().setShouldRunStates(false, true); |
| assertFalse(getService().shouldClientBeRunning()); |
| |
| getService().setShouldRunStates(true, false); |
| assertFalse(getService().shouldClientBeRunning()); |
| |
| // Should only be running if both in the foreground and sync is enabled. |
| getService().setShouldRunStates(true, true); |
| assertTrue(getService().shouldClientBeRunning()); |
| } |
| |
| @SmallTest |
| @Feature({"Sync"}) |
| public void testStartAndStopClient() { |
| /* |
| * Test plan: with Chrome configured so that the client should run, send it an empty |
| * intent. Even though no owning account is known, the client should still start. Send |
| * it a stop intent and verify that it stops. |
| */ |
| |
| // Note: we are manipulating the service object directly, rather than through startService, |
| // because otherwise we would need to handle the asynchronous execution model of the |
| // underlying IntentService. |
| getService().setShouldRunStates(true, true); |
| getService().onCreate(); |
| |
| Intent startIntent = createStartIntent(); |
| getService().onHandleIntent(startIntent); |
| assertTrue(InvalidationService.getIsClientStartedForTest()); |
| |
| Intent stopIntent = createStopIntent(); |
| getService().onHandleIntent(stopIntent); |
| assertFalse(InvalidationService.getIsClientStartedForTest()); |
| |
| // The issued intents should have been an AndroidListener start intent followed by an |
| // AndroidListener stop intent. |
| assertEquals(2, mStartServiceIntents.size()); |
| assertTrue(isAndroidListenerStartIntent(mStartServiceIntents.get(0))); |
| assertTrue(isAndroidListenerStopIntent(mStartServiceIntents.get(1))); |
| } |
| |
| @SmallTest |
| @Feature({"Sync"}) |
| public void testClientStopsWhenShouldNotBeRunning() { |
| /* |
| * Test plan: start the client. Then, change the configuration so that Chrome should not |
| * be running. Send an intent to the service and verify that it stops. |
| */ |
| getService().setShouldRunStates(true, true); |
| getService().onCreate(); |
| |
| // Start the service. |
| Intent startIntent = createStartIntent(); |
| getService().onHandleIntent(startIntent); |
| assertTrue(InvalidationService.getIsClientStartedForTest()); |
| |
| // Change configuration. |
| getService().setShouldRunStates(false, false); |
| |
| // Send an Intent and verify that the service stops. |
| getService().onHandleIntent(startIntent); |
| assertFalse(InvalidationService.getIsClientStartedForTest()); |
| |
| // The issued intents should have been an AndroidListener start intent followed by an |
| // AndroidListener stop intent. |
| assertEquals(2, mStartServiceIntents.size()); |
| assertTrue(isAndroidListenerStartIntent(mStartServiceIntents.get(0))); |
| assertTrue(isAndroidListenerStopIntent(mStartServiceIntents.get(1))); |
| } |
| |
| @SmallTest |
| @Feature({"Sync"}) |
| public void testRegistrationIntent() { |
| /* |
| * Test plan: send a registration-change intent. Verify that it starts the client and |
| * sets both the account and registrations in shared preferences. |
| */ |
| getService().setShouldRunStates(true, true); |
| getService().onCreate(); |
| |
| // Send register Intent. |
| Set<ModelType> desiredRegistrations = CollectionUtil.newHashSet( |
| ModelType.BOOKMARK, ModelType.SESSION); |
| Account account = AccountManagerHelper.createAccountFromName("test@example.com"); |
| Intent registrationIntent = createRegisterIntent(account, false, desiredRegistrations); |
| getService().onHandleIntent(registrationIntent); |
| |
| // Verify client started and state written. |
| assertTrue(InvalidationService.getIsClientStartedForTest()); |
| InvalidationPreferences invPrefs = new InvalidationPreferences(getContext()); |
| assertEquals(account, invPrefs.getSavedSyncedAccount()); |
| assertEquals(ModelType.modelTypesToSyncTypes(desiredRegistrations), |
| invPrefs.getSavedSyncedTypes()); |
| assertNull(invPrefs.getSavedObjectIds()); |
| assertEquals(1, mStartServiceIntents.size()); |
| assertTrue(isAndroidListenerStartIntent(mStartServiceIntents.get(0))); |
| |
| // Send another registration-change intent, this type with all-types set to true, and |
| // verify that the on-disk state is updated and that no addition Intents are issued. |
| getService().onHandleIntent(createRegisterIntent(account, true, null)); |
| assertEquals(account, invPrefs.getSavedSyncedAccount()); |
| assertEquals(CollectionUtil.newHashSet(ModelType.ALL_TYPES_TYPE), |
| invPrefs.getSavedSyncedTypes()); |
| assertEquals(1, mStartServiceIntents.size()); |
| |
| // Finally, send one more registration-change intent, this time with a different account, |
| // and verify that it both updates the account, stops thye existing client, and |
| // starts a new client. |
| Account account2 = AccountManagerHelper.createAccountFromName("test2@example.com"); |
| getService().onHandleIntent(createRegisterIntent(account2, true, null)); |
| assertEquals(account2, invPrefs.getSavedSyncedAccount()); |
| assertEquals(3, mStartServiceIntents.size()); |
| assertTrue(isAndroidListenerStartIntent(mStartServiceIntents.get(0))); |
| assertTrue(isAndroidListenerStopIntent(mStartServiceIntents.get(1))); |
| assertTrue(isAndroidListenerStartIntent(mStartServiceIntents.get(2))); |
| } |
| |
| /** |
| * Determines if the correct object ids have been written to preferences and registered with the |
| * invalidation client. |
| * |
| * @param expectedTypes The Sync types expected to be registered. |
| * @param expectedObjectIds The additional object ids expected to be registered. |
| * @param isReady Whether the client is ready to register/unregister. |
| */ |
| private boolean expectedObjectIdsRegistered(Set<ModelType> expectedTypes, |
| Set<ObjectId> expectedObjectIds, boolean isReady) { |
| // Get synced types saved to preferences. |
| Set<String> expectedSyncTypes = ModelType.modelTypesToSyncTypes(expectedTypes); |
| InvalidationPreferences invPrefs = new InvalidationPreferences(getContext()); |
| Set<String> actualSyncTypes = invPrefs.getSavedSyncedTypes(); |
| if (actualSyncTypes == null) { |
| actualSyncTypes = new HashSet<String>(); |
| } |
| |
| // Get object ids saved to preferences. |
| Set<ObjectId> actualObjectIds = invPrefs.getSavedObjectIds(); |
| if (actualObjectIds == null) { |
| actualObjectIds = new HashSet<ObjectId>(); |
| } |
| |
| // Get expected registered object ids. |
| Set<ObjectId> expectedRegisteredIds = new HashSet<ObjectId>(); |
| if (isReady) { |
| expectedRegisteredIds.addAll(ModelType.modelTypesToObjectIds(expectedTypes)); |
| expectedRegisteredIds.addAll(expectedObjectIds); |
| } |
| |
| return actualSyncTypes.equals(expectedSyncTypes) && |
| actualObjectIds.equals(expectedObjectIds) && |
| getService().mCurrentRegistrations.equals(expectedRegisteredIds); |
| } |
| |
| @SmallTest |
| @Feature({"Sync"}) |
| public void testRegistrationIntentWithTypesAndObjectIds() { |
| /* |
| * Test plan: send a mix of registration-change intents: some for Sync types and some for |
| * object ids. Verify that registering for Sync types does not interfere with object id |
| * registration and vice-versa. |
| */ |
| getService().setShouldRunStates(true, true); |
| getService().onCreate(); |
| |
| Account account = AccountManagerHelper.createAccountFromName("test@example.com"); |
| Set<ObjectId> objectIds = new HashSet<ObjectId>(); |
| Set<ModelType> types = new HashSet<ModelType>(); |
| |
| // Register for some object ids. |
| objectIds.add(ObjectId.newInstance(1, "obj1".getBytes())); |
| objectIds.add(ObjectId.newInstance(2, "obj2".getBytes())); |
| Intent registrationIntent = |
| createRegisterIntent(account, new int[] {1, 2}, new String[] {"obj1", "obj2"}); |
| getService().onHandleIntent(registrationIntent); |
| assertTrue(expectedObjectIdsRegistered(types, objectIds, false /* isReady */)); |
| |
| // Register for some types. |
| types.add(ModelType.BOOKMARK); |
| types.add(ModelType.SESSION); |
| registrationIntent = createRegisterIntent(account, false, types); |
| getService().onHandleIntent(registrationIntent); |
| assertTrue(expectedObjectIdsRegistered(types, objectIds, false /* isReady */)); |
| |
| // Set client to be ready and verify registrations. |
| getService().ready(CLIENT_ID); |
| assertTrue(expectedObjectIdsRegistered(types, objectIds, true /* isReady */)); |
| |
| // Change object id registration with types registered. |
| objectIds.add(ObjectId.newInstance(3, "obj3".getBytes())); |
| registrationIntent = createRegisterIntent( |
| account, new int[] {1, 2, 3}, new String[] {"obj1", "obj2", "obj3"}); |
| getService().onHandleIntent(registrationIntent); |
| assertTrue(expectedObjectIdsRegistered(types, objectIds, true /* isReady */)); |
| |
| // Change type registration with object ids registered. |
| types.remove(ModelType.BOOKMARK); |
| registrationIntent = createRegisterIntent(account, false, types); |
| getService().onHandleIntent(registrationIntent); |
| assertTrue(expectedObjectIdsRegistered(types, objectIds, true /* isReady */)); |
| |
| // Unregister all types. |
| types.clear(); |
| registrationIntent = createRegisterIntent(account, false, types); |
| getService().onHandleIntent(registrationIntent); |
| assertTrue(expectedObjectIdsRegistered(types, objectIds, true /* isReady */)); |
| |
| // Change object id registration with no types registered. |
| objectIds.remove(ObjectId.newInstance(2, "obj2".getBytes())); |
| registrationIntent = createRegisterIntent( |
| account, new int[] {1, 3}, new String[] {"obj1", "obj3"}); |
| getService().onHandleIntent(registrationIntent); |
| assertTrue(expectedObjectIdsRegistered(types, objectIds, true /* isReady */)); |
| |
| // Unregister all object ids. |
| objectIds.clear(); |
| registrationIntent = createRegisterIntent(account, new int[0], new String[0]); |
| getService().onHandleIntent(registrationIntent); |
| assertTrue(expectedObjectIdsRegistered(types, objectIds, true /* isReady */)); |
| |
| // Change type registration with no object ids registered. |
| types.add(ModelType.BOOKMARK); |
| types.add(ModelType.PASSWORD); |
| registrationIntent = createRegisterIntent(account, false, types); |
| getService().onHandleIntent(registrationIntent); |
| assertTrue(expectedObjectIdsRegistered(types, objectIds, true /* isReady */)); |
| } |
| |
| @SmallTest |
| @Feature({"Sync"}) |
| public void testRegistrationIntentNoProxyTabsUsingReady() { |
| getService().setShouldRunStates(true, true); |
| getService().onCreate(); |
| |
| // Send register Intent. |
| Account account = AccountManagerHelper.createAccountFromName("test@example.com"); |
| Intent registrationIntent = createRegisterIntent(account, true, null); |
| getService().onHandleIntent(registrationIntent); |
| |
| // Verify client started and state written. |
| assertTrue(InvalidationService.getIsClientStartedForTest()); |
| InvalidationPreferences invPrefs = new InvalidationPreferences(getContext()); |
| assertEquals(account, invPrefs.getSavedSyncedAccount()); |
| assertEquals(CollectionUtil.newHashSet(ModelType.ALL_TYPES_TYPE), |
| invPrefs.getSavedSyncedTypes()); |
| assertEquals(1, mStartServiceIntents.size()); |
| assertTrue(isAndroidListenerStartIntent(mStartServiceIntents.get(0))); |
| |
| // Set client to be ready. This triggers registrations. |
| getService().ready(CLIENT_ID); |
| assertTrue(Arrays.equals(CLIENT_ID, InvalidationService.getClientIdForTest())); |
| |
| // Ensure registrations are correct. |
| Set<ObjectId> expectedTypes = |
| ModelType.modelTypesToObjectIds(EnumSet.allOf(ModelType.class)); |
| assertEquals(expectedTypes, new HashSet<ObjectId>(getService().mRegistrations.get(0))); |
| } |
| |
| @SmallTest |
| @Feature({"Sync"}) |
| public void testRegistrationIntentNoProxyTabsAlreadyWithClientId() { |
| getService().setShouldRunStates(true, true); |
| getService().onCreate(); |
| |
| // Send register Intent with no desired types. |
| Account account = AccountManagerHelper.createAccountFromName("test@example.com"); |
| Intent registrationIntent = createRegisterIntent(account, false, new HashSet<ModelType>()); |
| getService().onHandleIntent(registrationIntent); |
| |
| // Verify client started and state written. |
| assertTrue(InvalidationService.getIsClientStartedForTest()); |
| InvalidationPreferences invPrefs = new InvalidationPreferences(getContext()); |
| assertEquals(account, invPrefs.getSavedSyncedAccount()); |
| assertEquals(new HashSet<String>(), invPrefs.getSavedSyncedTypes()); |
| assertEquals(1, mStartServiceIntents.size()); |
| assertTrue(isAndroidListenerStartIntent(mStartServiceIntents.get(0))); |
| |
| // Make sure client is ready. |
| getService().ready(CLIENT_ID); |
| assertTrue(Arrays.equals(CLIENT_ID, InvalidationService.getClientIdForTest())); |
| |
| // Choose to register for all types in an already ready client. |
| registrationIntent = createRegisterIntent(account, true, null); |
| getService().onHandleIntent(registrationIntent); |
| |
| // Ensure registrations are correct. |
| assertEquals(1, getService().mRegistrations.size()); |
| Set<ObjectId> expectedTypes = |
| ModelType.modelTypesToObjectIds(EnumSet.allOf(ModelType.class)); |
| assertEquals(expectedTypes, new HashSet<ObjectId>(getService().mRegistrations.get(0))); |
| } |
| |
| @SmallTest |
| @Feature({"Sync"}) |
| public void testRegistrationIntentWhenClientShouldNotBeRunning() { |
| /* |
| * Test plan: send a registration change event when the client should not be running. |
| * Verify that the service updates the on-disk state but does not start the client. |
| */ |
| getService().onCreate(); |
| |
| // Send register Intent. |
| Account account = AccountManagerHelper.createAccountFromName("test@example.com"); |
| Set<ModelType> desiredRegistrations = CollectionUtil.newHashSet( |
| ModelType.BOOKMARK, ModelType.SESSION); |
| Intent registrationIntent = createRegisterIntent(account, false, desiredRegistrations); |
| getService().onHandleIntent(registrationIntent); |
| |
| // Verify state written but client not started. |
| assertFalse(InvalidationService.getIsClientStartedForTest()); |
| InvalidationPreferences invPrefs = new InvalidationPreferences(getContext()); |
| assertEquals(account, invPrefs.getSavedSyncedAccount()); |
| assertEquals(ModelType.modelTypesToSyncTypes(desiredRegistrations), |
| invPrefs.getSavedSyncedTypes()); |
| assertEquals(0, mStartServiceIntents.size()); |
| } |
| |
| @SmallTest |
| @Feature({"Sync"}) |
| public void testDeferredRegistrationsIssued() { |
| /* |
| * Test plan: send a registration-change intent. Verify that the client issues a start |
| * intent but makes no registration calls. Issue a reissueRegistrations call and verify |
| * that the client does issue the appropriate registrations. |
| */ |
| getService().setShouldRunStates(true, true); |
| getService().onCreate(); |
| |
| // Send register Intent. Verify client started but no registrations issued. |
| Account account = AccountManagerHelper.createAccountFromName("test@example.com"); |
| Set<ModelType> desiredRegistrations = CollectionUtil.newHashSet( |
| ModelType.BOOKMARK, ModelType.SESSION); |
| Set<ObjectId> desiredObjectIds = ModelType.modelTypesToObjectIds(desiredRegistrations); |
| |
| Intent registrationIntent = createRegisterIntent(account, false, desiredRegistrations); |
| getService().onHandleIntent(registrationIntent); |
| assertTrue(InvalidationService.getIsClientStartedForTest()); |
| assertEquals(1, mStartServiceIntents.size()); |
| assertTrue(isAndroidListenerStartIntent(mStartServiceIntents.get(0))); |
| InvalidationPreferences invPrefs = new InvalidationPreferences(getContext()); |
| assertEquals(ModelType.modelTypesToSyncTypes(desiredRegistrations), |
| invPrefs.getSavedSyncedTypes()); |
| assertEquals(desiredObjectIds, getService().readRegistrationsFromPrefs()); |
| |
| // Issue reissueRegistrations; verify registration intent issues. |
| getService().reissueRegistrations(CLIENT_ID); |
| assertEquals(2, mStartServiceIntents.size()); |
| Intent expectedRegisterIntent = AndroidListener.createRegisterIntent( |
| getContext(), |
| CLIENT_ID, |
| desiredObjectIds); |
| Intent actualRegisterIntent = mStartServiceIntents.get(1); |
| assertTrue(expectedRegisterIntent.filterEquals(actualRegisterIntent)); |
| assertEquals(expectedRegisterIntent.getExtras().keySet(), |
| actualRegisterIntent.getExtras().keySet()); |
| assertEquals( |
| desiredObjectIds, |
| new HashSet<ObjectId>(getService().mRegistrations.get(0))); |
| } |
| |
| @SmallTest |
| @Feature({"Sync"}) |
| public void testRegistrationRetries() { |
| /* |
| * Test plan: validate that the alarm receiver used by the AndroidListener underlying |
| * InvalidationService is correctly configured in the manifest and retries registrations |
| * with exponential backoff. May need to be implemented as a downstream Chrome for Android |
| * test. |
| */ |
| // TODO(dsmyers): implement. |
| // Bug: https://code.google.com/p/chromium/issues/detail?id=172398 |
| } |
| |
| /** Creates an intent to start the InvalidationService. */ |
| private Intent createStartIntent() { |
| Intent intent = new Intent(); |
| return intent; |
| } |
| |
| /** Creates an intent to stop the InvalidationService. */ |
| private Intent createStopIntent() { |
| Intent intent = new Intent(); |
| intent.putExtra(InvalidationIntentProtocol.EXTRA_STOP, true); |
| return intent; |
| } |
| |
| /** Creates an intent to register some types with the InvalidationService. */ |
| private Intent createRegisterIntent(Account account, boolean allTypes, Set<ModelType> types) { |
| Intent intent = InvalidationIntentProtocol.createRegisterIntent(account, allTypes, types); |
| return intent; |
| } |
| |
| /** Creates an intent to register some types with the InvalidationService. */ |
| private Intent createRegisterIntent( |
| Account account, int[] objectSources, String[] objectNames) { |
| Intent intent = InvalidationIntentProtocol.createRegisterIntent( |
| account, objectSources, objectNames); |
| return intent; |
| } |
| |
| /** Returns whether {@code intent} is an {@link AndroidListener} start intent. */ |
| private boolean isAndroidListenerStartIntent(Intent intent) { |
| Intent startIntent = AndroidListener.createStartIntent(getContext(), |
| InvalidationService.CLIENT_TYPE, "unused".getBytes()); |
| return intent.getExtras().keySet().equals(startIntent.getExtras().keySet()); |
| } |
| |
| /** Returns whether {@code intent} is an {@link AndroidListener} stop intent. */ |
| private boolean isAndroidListenerStopIntent(Intent intent) { |
| Intent stopIntent = AndroidListener.createStopIntent(getContext()); |
| return intent.getExtras().keySet().equals(stopIntent.getExtras().keySet()); |
| } |
| } |