summaryrefslogtreecommitdiffstats
path: root/sync
diff options
context:
space:
mode:
authorstepco@chromium.org <stepco@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2013-09-11 00:20:25 +0000
committerstepco@chromium.org <stepco@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2013-09-11 00:20:25 +0000
commit2fd6b703eec82f0989d401d9c2f0b7e659d70329 (patch)
tree3412f7afad9f544a9fcbe8840617058e07e73138 /sync
parent7f4fe4e003d6d72893d96d8516557e5655e6cedb (diff)
downloadchromium_src-2fd6b703eec82f0989d401d9c2f0b7e659d70329.zip
chromium_src-2fd6b703eec82f0989d401d9c2f0b7e659d70329.tar.gz
chromium_src-2fd6b703eec82f0989d401d9c2f0b7e659d70329.tar.bz2
Enable invalidations for arbitrary objects on Android.
Enable the native InvalidationServiceAndroid to call into the Java-side InvalidationController in order to register for invalidations for arbitrary objects. Currently only Sync objects can be registered for. This change is motivated by the need to receive invalidations for enterprise user policy on Android. BUG=263287 Review URL: https://chromiumcodereview.appspot.com/23643002 git-svn-id: svn://svn.chromium.org/chrome/trunk/src@222417 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'sync')
-rw-r--r--sync/android/java/src/org/chromium/sync/notifier/InvalidationController.java84
-rw-r--r--sync/android/java/src/org/chromium/sync/notifier/InvalidationPreferences.java61
-rw-r--r--sync/android/java/src/org/chromium/sync/notifier/InvalidationService.java86
-rw-r--r--sync/android/javatests/src/org/chromium/sync/notifier/InvalidationControllerTest.java27
-rw-r--r--sync/android/javatests/src/org/chromium/sync/notifier/InvalidationPreferencesTest.java9
-rw-r--r--sync/android/javatests/src/org/chromium/sync/notifier/InvalidationServiceTest.java164
-rw-r--r--sync/android/javatests/src/org/chromium/sync/notifier/TestableInvalidationService.java16
-rw-r--r--sync/sync_android.gypi11
8 files changed, 432 insertions, 26 deletions
diff --git a/sync/android/java/src/org/chromium/sync/notifier/InvalidationController.java b/sync/android/java/src/org/chromium/sync/notifier/InvalidationController.java
index 663ca00..93685db 100644
--- a/sync/android/java/src/org/chromium/sync/notifier/InvalidationController.java
+++ b/sync/android/java/src/org/chromium/sync/notifier/InvalidationController.java
@@ -10,11 +10,16 @@ import android.content.Intent;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Preconditions;
+import com.google.ipc.invalidation.external.client.types.ObjectId;
+import com.google.protos.ipc.invalidation.Types;
import org.chromium.base.ActivityStatus;
+import org.chromium.base.CalledByNative;
import org.chromium.base.CollectionUtil;
import org.chromium.sync.internal_api.pub.base.ModelType;
+import java.util.ArrayList;
+import java.util.HashSet;
import java.util.Set;
/**
@@ -44,6 +49,18 @@ public class InvalidationController implements ActivityStatus.StateListener {
public static final String EXTRA_REGISTERED_TYPES = "registered_types";
/**
+ * Int-array-valued intent extra containing sources of objects to register for.
+ * The array is parallel to EXTRA_REGISTERED_OBJECT_NAMES.
+ */
+ public static final String EXTRA_REGISTERED_OBJECT_SOURCES = "registered_object_sources";
+
+ /**
+ * String-array-valued intent extra containing names of objects to register for.
+ * The array is parallel to EXTRA_REGISTERED_OBJECT_SOURCES.
+ */
+ public static final String EXTRA_REGISTERED_OBJECT_NAMES = "registered_object_names";
+
+ /**
* Boolean-valued intent extra indicating that the service should be stopped.
*/
public static final String EXTRA_STOP = "stop";
@@ -71,6 +88,34 @@ public class InvalidationController implements ActivityStatus.StateListener {
return registerIntent;
}
+ /**
+ * Create an Intent that will start the invalidation listener service and
+ * register for the object ids with the specified sources and names.
+ * Sync-specific objects are filtered out of the request since Sync types
+ * are registered using the other version of createRegisterIntent.
+ */
+ public static Intent createRegisterIntent(Account account, int[] objectSources,
+ String[] objectNames) {
+ Preconditions.checkArgument(objectSources.length == objectNames.length,
+ "objectSources and objectNames must have the same length");
+
+ // Add all non-Sync objects to new lists.
+ ArrayList<Integer> sources = new ArrayList<Integer>();
+ ArrayList<String> names = new ArrayList<String>();
+ for (int i = 0; i < objectSources.length; i++) {
+ if (objectSources[i] != Types.ObjectSource.Type.CHROME_SYNC.getNumber()) {
+ sources.add(objectSources[i]);
+ names.add(objectNames[i]);
+ }
+ }
+
+ Intent registerIntent = new Intent(ACTION_REGISTER);
+ registerIntent.putIntegerArrayListExtra(EXTRA_REGISTERED_OBJECT_SOURCES, sources);
+ registerIntent.putStringArrayListExtra(EXTRA_REGISTERED_OBJECT_NAMES, names);
+ registerIntent.putExtra(EXTRA_ACCOUNT, account);
+ return registerIntent;
+ }
+
/** Returns whether {@code intent} is a stop intent. */
public static boolean isStop(Intent intent) {
return intent.getBooleanExtra(EXTRA_STOP, false);
@@ -78,7 +123,26 @@ public class InvalidationController implements ActivityStatus.StateListener {
/** Returns whether {@code intent} is a registered types change intent. */
public static boolean isRegisteredTypesChange(Intent intent) {
- return intent.hasExtra(EXTRA_REGISTERED_TYPES);
+ return intent.hasExtra(EXTRA_REGISTERED_TYPES) ||
+ intent.hasExtra(EXTRA_REGISTERED_OBJECT_SOURCES);
+ }
+
+ /** Returns the object ids for which to register contained in the intent. */
+ public static Set<ObjectId> getRegisteredObjectIds(Intent intent) {
+ ArrayList<Integer> objectSources =
+ intent.getIntegerArrayListExtra(EXTRA_REGISTERED_OBJECT_SOURCES);
+ ArrayList<String> objectNames =
+ intent.getStringArrayListExtra(EXTRA_REGISTERED_OBJECT_NAMES);
+ if (objectSources == null || objectNames == null ||
+ objectSources.size() != objectNames.size()) {
+ return null;
+ }
+ Set<ObjectId> objectIds = new HashSet<ObjectId>(objectSources.size());
+ for (int i = 0; i < objectSources.size(); i++) {
+ objectIds.add(ObjectId.newInstance(
+ objectSources.get(i), objectNames.get(i).getBytes()));
+ }
+ return objectIds;
}
private IntentProtocol() {
@@ -122,6 +186,23 @@ public class InvalidationController implements ActivityStatus.StateListener {
}
/**
+ * Sets object ids for which the client should register for notification. This is intended for
+ * registering non-Sync types; Sync types are registered with {@code setRegisteredTypes}.
+ *
+ * @param objectSources The sources of the objects.
+ * @param objectNames The names of the objects.
+ */
+ @CalledByNative
+ public void setRegisteredObjectIds(int[] objectSources, String[] objectNames) {
+ InvalidationPreferences invalidationPreferences = new InvalidationPreferences(mContext);
+ Account account = invalidationPreferences.getSavedSyncedAccount();
+ Intent registerIntent = IntentProtocol.createRegisterIntent(account, objectSources,
+ objectNames);
+ registerIntent.setClass(mContext, InvalidationService.class);
+ mContext.startService(registerIntent);
+ }
+
+ /**
* Starts the invalidation client.
*/
public void start() {
@@ -143,6 +224,7 @@ public class InvalidationController implements ActivityStatus.StateListener {
*
* Calling this method will create the instance if it does not yet exist.
*/
+ @CalledByNative
public static InvalidationController get(Context context) {
synchronized (LOCK) {
if (sInstance == null) {
diff --git a/sync/android/java/src/org/chromium/sync/notifier/InvalidationPreferences.java b/sync/android/java/src/org/chromium/sync/notifier/InvalidationPreferences.java
index 2b21206..612a470 100644
--- a/sync/android/java/src/org/chromium/sync/notifier/InvalidationPreferences.java
+++ b/sync/android/java/src/org/chromium/sync/notifier/InvalidationPreferences.java
@@ -13,6 +13,7 @@ import android.util.Log;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Preconditions;
+import com.google.ipc.invalidation.external.client.types.ObjectId;
import java.util.Collection;
import java.util.HashSet;
@@ -55,6 +56,13 @@ public class InvalidationPreferences {
@VisibleForTesting
public static final String SYNC_TANGO_TYPES = "sync_tango_types";
+ /**
+ * Shared preference key to store tango object ids for additional objects that we want to
+ * register for.
+ */
+ @VisibleForTesting
+ public static final String TANGO_OBJECT_IDS = "tango_object_ids";
+
/** Shared preference key to store the name of the account in use. */
@VisibleForTesting
public static final String SYNC_ACCT_NAME = "sync_acct_name";
@@ -106,6 +114,34 @@ public class InvalidationPreferences {
editContext.editor.putStringSet(PrefKeys.SYNC_TANGO_TYPES, selectedTypesSet);
}
+ /** Returns the saved non-sync object ids, or {@code null} if none exist. */
+ @Nullable
+ public Set<ObjectId> getSavedObjectIds() {
+ SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(mContext);
+ Set<String> objectIdStrings = preferences.getStringSet(PrefKeys.TANGO_OBJECT_IDS, null);
+ if (objectIdStrings == null) {
+ return null;
+ }
+ Set<ObjectId> objectIds = new HashSet<ObjectId>(objectIdStrings.size());
+ for (String objectIdString : objectIdStrings) {
+ ObjectId objectId = getObjectId(objectIdString);
+ if (objectId != null) {
+ objectIds.add(objectId);
+ }
+ }
+ return objectIds;
+ }
+
+ /** Sets the saved non-sync object ids */
+ public void setObjectIds(EditContext editContext, Collection<ObjectId> objectIds) {
+ Preconditions.checkNotNull(objectIds);
+ Set<String> objectIdStrings = new HashSet<String>(objectIds.size());
+ for (ObjectId objectId : objectIds) {
+ objectIdStrings.add(getObjectIdString(objectId));
+ }
+ editContext.editor.putStringSet(PrefKeys.TANGO_OBJECT_IDS, objectIdStrings);
+ }
+
/** Returns the saved account, or {@code null} if none exists. */
@Nullable public Account getSavedSyncedAccount() {
SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(mContext);
@@ -138,4 +174,29 @@ public class InvalidationPreferences {
editContext.editor.putString(PrefKeys.SYNC_TANGO_INTERNAL_STATE,
Base64.encodeToString(state, Base64.DEFAULT));
}
+
+ /** Converts the given object id to a string for storage in preferences. */
+ private String getObjectIdString(ObjectId objectId) {
+ return objectId.getSource() + ":" + new String(objectId.getName());
+ }
+
+ /**
+ * Converts the given object id string stored in preferences to an object id.
+ * Returns null if the string does not represent a valid object id.
+ */
+ private ObjectId getObjectId(String objectIdString) {
+ int separatorPos = objectIdString.indexOf(':');
+ // Ensure that the separator is surrounded by at least one character on each side.
+ if (separatorPos < 1 || separatorPos == objectIdString.length() - 1) {
+ return null;
+ }
+ int objectSource;
+ try {
+ objectSource = Integer.parseInt(objectIdString.substring(0, separatorPos));
+ } catch (NumberFormatException e) {
+ return null;
+ }
+ byte[] objectName = objectIdString.substring(separatorPos + 1).getBytes();
+ return ObjectId.newInstance(objectSource, objectName);
+ }
}
diff --git a/sync/android/java/src/org/chromium/sync/notifier/InvalidationService.java b/sync/android/java/src/org/chromium/sync/notifier/InvalidationService.java
index c834085..2d63802 100644
--- a/sync/android/java/src/org/chromium/sync/notifier/InvalidationService.java
+++ b/sync/android/java/src/org/chromium/sync/notifier/InvalidationService.java
@@ -95,7 +95,8 @@ public class InvalidationService extends AndroidListener {
// If the intent requests a change in registrations, change them.
List<String> regTypes =
intent.getStringArrayListExtra(IntentProtocol.EXTRA_REGISTERED_TYPES);
- setRegisteredTypes(new HashSet<String>(regTypes));
+ setRegisteredTypes(regTypes != null ? new HashSet<String>(regTypes) : null,
+ IntentProtocol.getRegisteredObjectIds(intent));
} else {
// Otherwise, we don't recognize the intent. Pass it to the notification client service.
super.onHandleIntent(intent);
@@ -287,33 +288,81 @@ public class InvalidationService extends AndroidListener {
* Reads the saved sync types from storage (if any) and returns a set containing the
* corresponding object ids.
*/
- @VisibleForTesting
- Set<ObjectId> readRegistrationsFromPrefs() {
+ private Set<ObjectId> readSyncRegistrationsFromPrefs() {
Set<String> savedTypes = new InvalidationPreferences(this).getSavedSyncedTypes();
if (savedTypes == null) return Collections.emptySet();
else return ModelType.syncTypesToObjectIds(savedTypes);
}
/**
+ * Reads the saved non-sync object ids from storage (if any) and returns a set containing the
+ * corresponding object ids.
+ */
+ private Set<ObjectId> readNonSyncRegistrationsFromPrefs() {
+ Set<ObjectId> objectIds = new InvalidationPreferences(this).getSavedObjectIds();
+ if (objectIds == null) return Collections.emptySet();
+ else return objectIds;
+ }
+
+ /**
+ * Reads the object registrations from storage (if any) and returns a set containing the
+ * corresponding object ids.
+ */
+ @VisibleForTesting
+ Set<ObjectId> readRegistrationsFromPrefs() {
+ return joinRegistrations(readSyncRegistrationsFromPrefs(),
+ readNonSyncRegistrationsFromPrefs());
+ }
+
+ /**
+ * Join Sync object registrations with non-Sync object registrations to get the full set of
+ * desired object registrations.
+ */
+ private static Set<ObjectId> joinRegistrations(Set<ObjectId> syncRegistrations,
+ Set<ObjectId> nonSyncRegistrations) {
+ if (nonSyncRegistrations.isEmpty()) {
+ return syncRegistrations;
+ }
+ if (syncRegistrations.isEmpty()) {
+ return nonSyncRegistrations;
+ }
+ Set<ObjectId> registrations = new HashSet<ObjectId>(
+ syncRegistrations.size() + nonSyncRegistrations.size());
+ registrations.addAll(syncRegistrations);
+ registrations.addAll(nonSyncRegistrations);
+ return registrations;
+ }
+
+ /**
* Sets the types for which notifications are required to {@code syncTypes}. {@code syncTypes}
* is either a list of specific types or the special wildcard type
- * {@link ModelType#ALL_TYPES_TYPE}.
+ * {@link ModelType#ALL_TYPES_TYPE}. Also registers for additional objects specified by
+ * {@code objectIds}. Either parameter may be null if the corresponding registrations are not
+ * changing.
* <p>
* @param syncTypes
*/
- private void setRegisteredTypes(Set<String> syncTypes) {
+ private void setRegisteredTypes(Set<String> syncTypes, Set<ObjectId> objectIds) {
// If we have a ready client and will be making registration change calls on it, then
// read the current registrations from preferences before we write the new values, so that
// we can take the diff of the two registration sets and determine which registration change
// calls to make.
- Set<ObjectId> existingRegistrations = (sClientId == null) ?
- null : readRegistrationsFromPrefs();
-
- // Write the new sync types to preferences. We do not expand the syncTypes to take into
- // account the ALL_TYPES_TYPE at this point; we want to persist the wildcard unexpanded.
+ Set<ObjectId> existingSyncRegistrations = (sClientId == null) ?
+ null : readSyncRegistrationsFromPrefs();
+ Set<ObjectId> existingNonSyncRegistrations = (sClientId == null) ?
+ null : readNonSyncRegistrationsFromPrefs();
+
+ // Write the new sync types/object ids to preferences. We do not expand the syncTypes to
+ // take into account the ALL_TYPES_TYPE at this point; we want to persist the wildcard
+ // unexpanded.
InvalidationPreferences prefs = new InvalidationPreferences(this);
EditContext editContext = prefs.edit();
- prefs.setSyncTypes(editContext, syncTypes);
+ if (syncTypes != null) {
+ prefs.setSyncTypes(editContext, syncTypes);
+ }
+ if (objectIds != null) {
+ prefs.setObjectIds(editContext, objectIds);
+ }
prefs.commit(editContext);
// If we do not have a ready invalidation client, we cannot change its registrations, so
@@ -327,10 +376,20 @@ public class InvalidationService extends AndroidListener {
// expansion of the ALL_TYPES_TYPE wildcard.
// NOTE: syncTypes MUST NOT be used below this line, since it contains an unexpanded
// wildcard.
+ // When computing the desired set of object ids, if only sync types were provided, then
+ // keep the existing non-sync types, and vice-versa.
+ Set<ObjectId> desiredSyncRegistrations = syncTypes != null ?
+ ModelType.syncTypesToObjectIds(syncTypes) : existingSyncRegistrations;
+ Set<ObjectId> desiredNonSyncRegistrations = objectIds != null ?
+ objectIds : existingNonSyncRegistrations;
+ Set<ObjectId> desiredRegistrations = joinRegistrations(desiredNonSyncRegistrations,
+ desiredSyncRegistrations);
+ Set<ObjectId> existingRegistrations = joinRegistrations(existingNonSyncRegistrations,
+ existingSyncRegistrations);
+
Set<ObjectId> unregistrations = new HashSet<ObjectId>();
Set<ObjectId> registrations = new HashSet<ObjectId>();
- computeRegistrationOps(existingRegistrations,
- ModelType.syncTypesToObjectIds(syncTypes),
+ computeRegistrationOps(existingRegistrations, desiredRegistrations,
registrations, unregistrations);
unregister(sClientId, unregistrations);
register(sClientId, registrations);
@@ -371,6 +430,7 @@ public class InvalidationService extends AndroidListener {
// Use an empty bundle in this case for compatibility with the v1 implementation.
} else {
if (objectId != null) {
+ bundle.putInt("objectSource", objectId.getSource());
bundle.putString("objectId", new String(objectId.getName()));
}
// We use "0" as the version if we have an unknown-version invalidation. This is OK
diff --git a/sync/android/javatests/src/org/chromium/sync/notifier/InvalidationControllerTest.java b/sync/android/javatests/src/org/chromium/sync/notifier/InvalidationControllerTest.java
index 215430b..fe7eb09 100644
--- a/sync/android/javatests/src/org/chromium/sync/notifier/InvalidationControllerTest.java
+++ b/sync/android/javatests/src/org/chromium/sync/notifier/InvalidationControllerTest.java
@@ -13,6 +13,8 @@ import android.content.pm.PackageManager;
import android.test.InstrumentationTestCase;
import android.test.suitebuilder.annotation.SmallTest;
+import com.google.ipc.invalidation.external.client.types.ObjectId;
+
import org.chromium.base.ActivityStatus;
import org.chromium.base.CollectionUtil;
import org.chromium.base.test.util.AdvancedMockContext;
@@ -171,6 +173,7 @@ public class InvalidationControllerTest extends InstrumentationTestCase {
Set<String> actualTypes = new HashSet<String>();
actualTypes.addAll(intent.getStringArrayListExtra(IntentProtocol.EXTRA_REGISTERED_TYPES));
assertEquals(expectedTypes, actualTypes);
+ assertNull(IntentProtocol.getRegisteredObjectIds(intent));
}
@SmallTest
@@ -195,6 +198,7 @@ public class InvalidationControllerTest extends InstrumentationTestCase {
Set<String> actualTypes = new HashSet<String>();
actualTypes.addAll(intent.getStringArrayListExtra(IntentProtocol.EXTRA_REGISTERED_TYPES));
assertEquals(expectedTypes, actualTypes);
+ assertNull(IntentProtocol.getRegisteredObjectIds(intent));
}
@SmallTest
@@ -277,6 +281,29 @@ public class InvalidationControllerTest extends InstrumentationTestCase {
assertEquals(true, resultAllTypes.get());
}
+ @SmallTest
+ @Feature({"Sync"})
+ public void testSetRegisteredObjectIds() {
+ InvalidationController controller = new InvalidationController(mContext);
+ ObjectId bookmark = ModelType.BOOKMARK.toObjectId();
+ controller.setRegisteredObjectIds(new int[] {1, 2, bookmark.getSource()},
+ new String[] {"a", "b", new String(bookmark.getName())});
+ assertEquals(1, mContext.getNumStartedIntents());
+
+ // Validate destination.
+ Intent intent = mContext.getStartedIntent(0);
+ validateIntentComponent(intent);
+ assertEquals(IntentProtocol.ACTION_REGISTER, intent.getAction());
+
+ // Validate registered object ids. The bookmark object should not be registered since it is
+ // a Sync type.
+ assertNull(intent.getStringArrayListExtra(IntentProtocol.EXTRA_REGISTERED_TYPES));
+ Set<ObjectId> objectIds = IntentProtocol.getRegisteredObjectIds(intent);
+ assertEquals(2, objectIds.size());
+ assertTrue(objectIds.contains(ObjectId.newInstance(1, "a".getBytes())));
+ assertTrue(objectIds.contains(ObjectId.newInstance(2, "b".getBytes())));
+ }
+
/**
* Asserts that {@code intent} is destined for the correct component.
*/
diff --git a/sync/android/javatests/src/org/chromium/sync/notifier/InvalidationPreferencesTest.java b/sync/android/javatests/src/org/chromium/sync/notifier/InvalidationPreferencesTest.java
index 2c5e841..b58bda6 100644
--- a/sync/android/javatests/src/org/chromium/sync/notifier/InvalidationPreferencesTest.java
+++ b/sync/android/javatests/src/org/chromium/sync/notifier/InvalidationPreferencesTest.java
@@ -9,6 +9,8 @@ import android.content.Context;
import android.test.InstrumentationTestCase;
import android.test.suitebuilder.annotation.SmallTest;
+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;
@@ -69,6 +71,7 @@ public class InvalidationPreferencesTest extends InstrumentationTestCase {
InvalidationPreferences invPreferences = new InvalidationPreferences(mContext);
assertNull(invPreferences.getSavedSyncedAccount());
assertNull(invPreferences.getSavedSyncedTypes());
+ assertNull(invPreferences.getSavedObjectIds());
assertNull(invPreferences.getInternalNotificationClientState());
}
@@ -85,20 +88,26 @@ public class InvalidationPreferencesTest extends InstrumentationTestCase {
// We should never write both a real type and the all-types type in practice, but we test
// with them here to ensure that preferences are not interpreting the written data.
Set<String> syncTypes = CollectionUtil.newHashSet("BOOKMARK", ModelType.ALL_TYPES_TYPE);
+ Set<ObjectId> objectIds = CollectionUtil.newHashSet(
+ ObjectId.newInstance(1, "obj1".getBytes()),
+ ObjectId.newInstance(2, "obj2".getBytes()));
Account account = new Account("test@example.com", "bogus");
byte[] internalClientState = new byte[]{100,101,102};
invPreferences.setSyncTypes(editContext, syncTypes);
+ invPreferences.setObjectIds(editContext, objectIds);
invPreferences.setAccount(editContext, account);
invPreferences.setInternalNotificationClientState(editContext, internalClientState);
// Nothing should yet have been written.
assertNull(invPreferences.getSavedSyncedAccount());
assertNull(invPreferences.getSavedSyncedTypes());
+ assertNull(invPreferences.getSavedObjectIds());
// Write the new data and verify that they are correctly read back.
invPreferences.commit(editContext);
assertEquals(account, invPreferences.getSavedSyncedAccount());
assertEquals(syncTypes, invPreferences.getSavedSyncedTypes());
+ assertEquals(objectIds, invPreferences.getSavedObjectIds());
assertTrue(Arrays.equals(
internalClientState, invPreferences.getInternalNotificationClientState()));
}
diff --git a/sync/android/javatests/src/org/chromium/sync/notifier/InvalidationServiceTest.java b/sync/android/javatests/src/org/chromium/sync/notifier/InvalidationServiceTest.java
index 18d6507..fd8e070 100644
--- a/sync/android/javatests/src/org/chromium/sync/notifier/InvalidationServiceTest.java
+++ b/sync/android/javatests/src/org/chromium/sync/notifier/InvalidationServiceTest.java
@@ -141,6 +141,8 @@ public class InvalidationServiceTest extends ServiceTestCase<TestableInvalidatio
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.
@@ -152,7 +154,7 @@ public class InvalidationServiceTest extends ServiceTestCase<TestableInvalidatio
// Verify registrations issued.
assertEquals(CollectionUtil.newHashSet(
- ModelType.BOOKMARK.toObjectId(), ModelType.SESSION.toObjectId()),
+ ModelType.BOOKMARK.toObjectId(), ModelType.SESSION.toObjectId(), objectId),
new HashSet<ObjectId>(getService().mRegistrations.get(0)));
}
@@ -174,13 +176,15 @@ public class InvalidationServiceTest extends ServiceTestCase<TestableInvalidatio
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()),
+ ModelType.BOOKMARK.toObjectId(), ModelType.SESSION.toObjectId(), objectId),
new HashSet<ObjectId>(getService().mRegistrations.get(0)));
}
@@ -200,31 +204,46 @@ public class InvalidationServiceTest extends ServiceTestCase<TestableInvalidatio
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);
- assertEquals(1, getService().mUnregistrations.size());
+ 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);
- assertEquals(1, getService().mUnregistrations.size());
- assertEquals(1, getService().mRegistrations.size());
+ 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
@@ -246,6 +265,9 @@ public class InvalidationServiceTest extends ServiceTestCase<TestableInvalidatio
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.
@@ -253,23 +275,31 @@ public class InvalidationServiceTest extends ServiceTestCase<TestableInvalidatio
"");
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, "");
- assertEquals(1, getService().mRegistrations.size());
+ 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,
"");
- assertEquals(1, getService().mRegistrations.size());
- assertEquals(1, getService().mUnregistrations.size());
+ 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
@@ -332,7 +362,7 @@ public class InvalidationServiceTest extends ServiceTestCase<TestableInvalidatio
*/
// Call invalidate.
int version = 4747;
- ObjectId objectId = ModelType.BOOKMARK.toObjectId();
+ ObjectId objectId = ObjectId.newInstance(55, "BOOKMARK".getBytes());
final String payload = "testInvalidate-" + hasPayload;
Invalidation invalidation = hasPayload ?
Invalidation.newInstance(objectId, version, payload.getBytes()) :
@@ -343,6 +373,7 @@ public class InvalidationServiceTest extends ServiceTestCase<TestableInvalidatio
// 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"));
@@ -358,13 +389,14 @@ public class InvalidationServiceTest extends ServiceTestCase<TestableInvalidatio
* Test plan: call invalidateUnknownVersion(). Verify the produced bundle has the correct
* fields.
*/
- ObjectId objectId = ModelType.BOOKMARK.toObjectId();
+ 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"));
@@ -501,6 +533,7 @@ public class InvalidationServiceTest extends ServiceTestCase<TestableInvalidatio
assertEquals(account, invPrefs.getSavedSyncedAccount());
assertEquals(ModelType.modelTypesToSyncTypes(desiredRegistrations),
invPrefs.getSavedSyncedTypes());
+ assertNull(invPrefs.getSavedObjectIds());
assertEquals(1, mStartServiceIntents.size());
assertTrue(isAndroidListenerStartIntent(mStartServiceIntents.get(0)));
@@ -524,6 +557,117 @@ public class InvalidationServiceTest extends ServiceTestCase<TestableInvalidatio
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 = IntentProtocol.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 = IntentProtocol.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 = IntentProtocol.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 = IntentProtocol.createRegisterIntent(account, false, types);
+ getService().onHandleIntent(registrationIntent);
+ assertTrue(expectedObjectIdsRegistered(types, objectIds, true /* isReady */));
+
+ // Unregister all types.
+ types.clear();
+ registrationIntent = IntentProtocol.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 = IntentProtocol.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 = IntentProtocol.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 = IntentProtocol.createRegisterIntent(account, false, types);
+ getService().onHandleIntent(registrationIntent);
+ assertTrue(expectedObjectIdsRegistered(types, objectIds, true /* isReady */));
+ }
+
@SmallTest
@Feature({"Sync"})
public void testRegistrationIntentNoProxyTabsUsingReady() {
diff --git a/sync/android/javatests/src/org/chromium/sync/notifier/TestableInvalidationService.java b/sync/android/javatests/src/org/chromium/sync/notifier/TestableInvalidationService.java
index 04eb6ba..68bdace 100644
--- a/sync/android/javatests/src/org/chromium/sync/notifier/TestableInvalidationService.java
+++ b/sync/android/javatests/src/org/chromium/sync/notifier/TestableInvalidationService.java
@@ -14,7 +14,9 @@ import com.google.ipc.invalidation.external.client.types.ObjectId;
import org.chromium.base.CollectionUtil;
import java.util.ArrayList;
+import java.util.HashSet;
import java.util.List;
+import java.util.Set;
/**
* Subclass of {@link InvalidationService} that captures events and allows controlling
@@ -29,6 +31,12 @@ public class TestableInvalidationService extends InvalidationService {
/** Object ids given to {@link #unregister}, one list element per call. */
final List<List<ObjectId>> mUnregistrations = new ArrayList<List<ObjectId>>();
+ /**
+ * Current registered based on the cumulative calls to {@link #register} and
+ * {@link #unregister}.
+ */
+ final Set<ObjectId> mCurrentRegistrations = new HashSet<ObjectId>();
+
/** Intents given to {@link #startService}. */
final List<Intent> mStartedServices = new ArrayList<Intent>();
@@ -53,13 +61,17 @@ public class TestableInvalidationService extends InvalidationService {
@Override
public void register(byte[] clientId, Iterable<ObjectId> objectIds) {
- mRegistrations.add(CollectionUtil.newArrayList(objectIds));
+ List<ObjectId> objectIdList = CollectionUtil.newArrayList(objectIds);
+ mRegistrations.add(objectIdList);
+ mCurrentRegistrations.addAll(objectIdList);
super.register(clientId, objectIds);
}
@Override
public void unregister(byte[] clientId, Iterable<ObjectId> objectIds) {
- mUnregistrations.add(CollectionUtil.newArrayList(objectIds));
+ List<ObjectId> objectIdList = CollectionUtil.newArrayList(objectIds);
+ mUnregistrations.add(objectIdList);
+ mCurrentRegistrations.removeAll(objectIdList);
super.unregister(clientId, objectIds);
}
diff --git a/sync/sync_android.gypi b/sync/sync_android.gypi
index 1964fbd..b951b07 100644
--- a/sync/sync_android.gypi
+++ b/sync/sync_android.gypi
@@ -21,6 +21,17 @@
],
'includes': [ '../build/java.gypi' ],
},
+ {
+ 'target_name': 'sync_jni_headers',
+ 'type': 'none',
+ 'sources': [
+ 'android/java/src/org/chromium/sync/notifier/InvalidationController.java',
+ ],
+ 'variables': {
+ 'jni_gen_package': 'sync',
+ },
+ 'includes': [ '../build/jni_generator.gypi' ],
+ },
],
}],
],