summaryrefslogtreecommitdiffstats
path: root/sync
diff options
context:
space:
mode:
authornyquist@chromium.org <nyquist@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2013-01-12 16:07:20 +0000
committernyquist@chromium.org <nyquist@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2013-01-12 16:07:20 +0000
commit8d8e18a3ae07fd1dc070b5c65b6f27cccf602998 (patch)
tree848e7e86ca737ed9279c0c3ca35cd1188b609e6b /sync
parent636ee43284f75156541540698d89df1227ff3b12 (diff)
downloadchromium_src-8d8e18a3ae07fd1dc070b5c65b6f27cccf602998.zip
chromium_src-8d8e18a3ae07fd1dc070b5c65b6f27cccf602998.tar.gz
chromium_src-8d8e18a3ae07fd1dc070b5c65b6f27cccf602998.tar.bz2
[sync] Add java InvalidationController for Android.
* Adds InvalidationController to facilitate the move to the new invalidation client, but still supporting the old way. * Sets up the chromium test shell to be able to run the new tests for sync. * Upstreaming a few base test utilities for MockContext and SharedPreferences. InvalidationController and InvalidationControllerTest provided by dsmyers@chromium.org. BUG=159221 Review URL: https://chromiumcodereview.appspot.com/11778084 git-svn-id: svn://svn.chromium.org/chrome/trunk/src@176533 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'sync')
-rw-r--r--sync/android/OWNERS3
-rw-r--r--sync/android/java/src/org/chromium/sync/internal_api/pub/base/ModelType.java46
-rw-r--r--sync/android/java/src/org/chromium/sync/notifier/InvalidationController.java179
-rw-r--r--sync/android/javatests/src/org/chromium/sync/notifier/InvalidationControllerTest.java154
-rw-r--r--sync/sync.gyp31
5 files changed, 413 insertions, 0 deletions
diff --git a/sync/android/OWNERS b/sync/android/OWNERS
new file mode 100644
index 0000000..72643ec
--- /dev/null
+++ b/sync/android/OWNERS
@@ -0,0 +1,3 @@
+nileshagrawal@chromium.org
+nyquist@chromium.org
+yfriedman@chromium.org
diff --git a/sync/android/java/src/org/chromium/sync/internal_api/pub/base/ModelType.java b/sync/android/java/src/org/chromium/sync/internal_api/pub/base/ModelType.java
new file mode 100644
index 0000000..4dbdab0
--- /dev/null
+++ b/sync/android/java/src/org/chromium/sync/internal_api/pub/base/ModelType.java
@@ -0,0 +1,46 @@
+// Copyright (c) 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.internal_api.pub.base;
+
+import com.google.ipc.invalidation.external.client.types.ObjectId;
+import com.google.protos.ipc.invalidation.Types;
+
+/**
+ * The model types that are synced in Chrome for Android.
+ */
+public enum ModelType {
+
+ /**
+ * A bookmark folder or a bookmark URL object.
+ */
+ BOOKMARK("BOOKMARK"),
+ /**
+ * A typed_url folder or a typed_url object.
+ */
+ TYPED_URL("TYPED_URL"),
+ /**
+ * An object representing a browser session or tab.
+ */
+ SESSION("SESSION");
+
+ private final String mModelType;
+
+ ModelType(String modelType) {
+ mModelType = modelType;
+ }
+
+ public ObjectId toObjectId() {
+ return ObjectId.newInstance(Types.ObjectSource.Type.CHROME_SYNC.getNumber(),
+ mModelType.getBytes());
+ }
+
+ public static ModelType fromObjectId(ObjectId objectId) {
+ try {
+ return valueOf(new String(objectId.getName()));
+ } catch (IllegalArgumentException e) {
+ return null;
+ }
+ }
+}
diff --git a/sync/android/java/src/org/chromium/sync/notifier/InvalidationController.java b/sync/android/java/src/org/chromium/sync/notifier/InvalidationController.java
new file mode 100644
index 0000000..71ac7b5
--- /dev/null
+++ b/sync/android/java/src/org/chromium/sync/notifier/InvalidationController.java
@@ -0,0 +1,179 @@
+// Copyright (c) 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.Context;
+import android.content.Intent;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.PackageManager.NameNotFoundException;
+import android.util.Log;
+
+import com.google.common.base.Preconditions;
+import com.google.common.collect.Lists;
+
+import org.chromium.sync.internal_api.pub.base.ModelType;
+
+import java.util.Set;
+
+/**
+ * Controller used to send start, stop, and registration-change commands to the invalidation
+ * client library used by Sync.
+ */
+public class InvalidationController {
+ /**
+ * Constants and utility methods to create the intents used to communicate between the
+ * controller and the invalidation client library.
+ */
+ public static class IntentProtocol {
+ /**
+ * Action set on register intents.
+ */
+ public static final String ACTION_REGISTER =
+ "org.chromium.sync.notifier.ACTION_REGISTER_TYPES";
+
+ /**
+ * Special syncable type that lets us know to sync all types.
+ */
+ public static final String ALL_TYPES_TYPE = "ALL_TYPES";
+
+ /**
+ * Parcelable-valued intent extra containing the account of the user.
+ */
+ public static final String EXTRA_ACCOUNT = "account";
+
+ /**
+ * String-list-valued intent extra of the syncable types to sync.
+ */
+ public static final String EXTRA_REGISTERED_TYPES = "registered_types";
+
+ /**
+ * Boolean-valued intent extra indicating that the service should be stopped.
+ */
+ public static final String EXTRA_STOP = "stop";
+
+ /**
+ * Create an Intent that will start the invalidation listener service and
+ * register for the specified types.
+ */
+ public static Intent createRegisterIntent(Account account,
+ boolean allTypes, Set<ModelType> types) {
+ Intent registerIntent = new Intent(ACTION_REGISTER);
+ String[] selectedTypesArray;
+ if (allTypes) {
+ selectedTypesArray = new String[]{ALL_TYPES_TYPE};
+ } else {
+ selectedTypesArray = new String[types.size()];
+ int pos = 0;
+ for (ModelType type : types) {
+ selectedTypesArray[pos++] = type.name();
+ }
+ }
+ registerIntent.putStringArrayListExtra(EXTRA_REGISTERED_TYPES,
+ Lists.newArrayList(selectedTypesArray));
+ registerIntent.putExtra(EXTRA_ACCOUNT, account);
+ return registerIntent;
+ }
+
+ private IntentProtocol() {
+ // Disallow instantiation.
+ }
+ }
+
+ /**
+ * Name of the manifest application metadata property specifying the name of the class
+ * implementing the invalidation client.
+ */
+ private static final String IMPLEMENTING_CLASS_MANIFEST_PROPERTY =
+ "org.chromium.sync.notifier.IMPLEMENTING_CLASS_NAME";
+
+ /**
+ * Logging tag.
+ */
+ private static final String TAG = InvalidationController.class.getSimpleName();
+
+ private final Context context;
+
+ /**
+ * Sets the types for which the client should register for notifications.
+ *
+ * @param account Account of the user.
+ * @param allTypes If {@code true}, registers for all types, and {@code types} is ignored
+ * @param types Set of types for which to register. Ignored if {@code allTypes == true}.
+ */
+ public void setRegisteredTypes(Account account, boolean allTypes, Set<ModelType> types) {
+ Intent registerIntent = IntentProtocol.createRegisterIntent(account, allTypes, types);
+ setDestinationClassName(registerIntent);
+ context.startService(registerIntent);
+ }
+
+ /**
+ * Starts the invalidation client.
+ */
+ public void start() {
+ Intent intent = setDestinationClassName(new Intent());
+ context.startService(intent);
+ }
+
+ /**
+ * Stops the invalidation client.
+ */
+ public void stop() {
+ Intent intent = setDestinationClassName(new Intent());
+ intent.putExtra(IntentProtocol.EXTRA_STOP, true);
+ context.startService(intent);
+ }
+
+ /**
+ * Returns the contract authority to use when requesting sync.
+ */
+ public String getContractAuthority() {
+ return context.getPackageName();
+ }
+
+ /**
+ * Returns a new instance that will use {@code context} to issue intents.
+ */
+ public static InvalidationController newInstance(Context context) {
+ return new InvalidationController(context);
+ }
+
+ /**
+ * Creates an instance using {@code context} to send intents.
+ */
+ private InvalidationController(Context context) {
+ this.context = Preconditions.checkNotNull(context.getApplicationContext());
+ }
+
+ /**
+ * Sets the destination class name of {@code intent} to the value given by the manifest
+ * property named {@link #IMPLEMENTING_CLASS_MANIFEST_PROPERTY}. If no such property exists or
+ * its value is null, takes no action.
+ *
+ * @return {@code intent}
+ */
+ private Intent setDestinationClassName(Intent intent) {
+ ApplicationInfo appInfo;
+ try {
+ // Fetch application info and read the appropriate metadata element.
+ appInfo = context.getPackageManager().getApplicationInfo(context.getPackageName(),
+ PackageManager.GET_META_DATA);
+ String className = null;
+ if (appInfo.metaData != null) {
+ className = appInfo.metaData.getString(IMPLEMENTING_CLASS_MANIFEST_PROPERTY);
+ }
+ if (className == null) {
+ Log.wtf(TAG, "No value for " + IMPLEMENTING_CLASS_MANIFEST_PROPERTY
+ + " in manifest; sync notifications will not work");
+ } else {
+ intent.setClassName(context, className);
+ }
+ } catch (NameNotFoundException exception) {
+ Log.wtf(TAG, "Cannot read own application info", exception);
+ }
+ return intent;
+ }
+}
diff --git a/sync/android/javatests/src/org/chromium/sync/notifier/InvalidationControllerTest.java b/sync/android/javatests/src/org/chromium/sync/notifier/InvalidationControllerTest.java
new file mode 100644
index 0000000..eee2bdf
--- /dev/null
+++ b/sync/android/javatests/src/org/chromium/sync/notifier/InvalidationControllerTest.java
@@ -0,0 +1,154 @@
+// Copyright (c) 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.Context;
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.test.InstrumentationTestCase;
+import android.test.suitebuilder.annotation.SmallTest;
+
+import com.google.common.collect.Lists;
+import com.google.common.collect.Sets;
+
+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.InvalidationController.IntentProtocol;
+
+import java.util.List;
+import java.util.Set;
+
+/**
+ * Tests for the {@link InvalidationController}.
+ */
+public class InvalidationControllerTest extends InstrumentationTestCase {
+ private IntentSavingContext mContext;
+ private InvalidationController mController;
+
+ @Override
+ protected void setUp() throws Exception {
+ mContext = new IntentSavingContext(getInstrumentation().getTargetContext());
+ mController = InvalidationController.newInstance(mContext);
+ }
+
+ @SmallTest
+ @Feature({"Sync"})
+ public void testStart() throws Exception {
+ mController.start();
+ assertEquals(1, mContext.getNumStartedIntents());
+ Intent intent = mContext.getStartedIntent(0);
+ validateIntentComponent(intent);
+ assertNull(intent.getExtras());
+ }
+
+ @SmallTest
+ @Feature({"Sync"})
+ public void testStop() throws Exception {
+ mController.stop();
+ assertEquals(1, mContext.getNumStartedIntents());
+ Intent intent = mContext.getStartedIntent(0);
+ validateIntentComponent(intent);
+ assertEquals(1, intent.getExtras().size());
+ assertTrue(intent.hasExtra(IntentProtocol.EXTRA_STOP));
+ assertTrue(intent.getBooleanExtra(IntentProtocol.EXTRA_STOP, false));
+ }
+
+ @SmallTest
+ @Feature({"Sync"})
+ public void testRegisterForSpecificTypes() {
+ Account account = new Account("test@example.com", "bogus");
+ mController.setRegisteredTypes(account, false,
+ Sets.newHashSet(ModelType.BOOKMARK, ModelType.SESSION));
+ assertEquals(1, mContext.getNumStartedIntents());
+
+ // Validate destination.
+ Intent intent = mContext.getStartedIntent(0);
+ validateIntentComponent(intent);
+ assertEquals(IntentProtocol.ACTION_REGISTER, intent.getAction());
+
+ // Validate account.
+ Account intentAccount = intent.getParcelableExtra(IntentProtocol.EXTRA_ACCOUNT);
+ assertEquals(account, intentAccount);
+
+ // Validate registered types.
+ Set<String> expectedTypes =
+ Sets.newHashSet(ModelType.BOOKMARK.name(), ModelType.SESSION.name());
+ Set<String> actualTypes = Sets.newHashSet();
+ actualTypes.addAll(intent.getStringArrayListExtra(IntentProtocol.EXTRA_REGISTERED_TYPES));
+ assertEquals(expectedTypes, actualTypes);
+ }
+
+ @SmallTest
+ @Feature({"Sync"})
+ public void testRegisterForAllTypes() {
+ Account account = new Account("test@example.com", "bogus");
+ mController.setRegisteredTypes(account, true,
+ Sets.newHashSet(ModelType.BOOKMARK, ModelType.SESSION));
+ assertEquals(1, mContext.getNumStartedIntents());
+
+ // Validate destination.
+ Intent intent = mContext.getStartedIntent(0);
+ validateIntentComponent(intent);
+ assertEquals(IntentProtocol.ACTION_REGISTER, intent.getAction());
+
+ // Validate account.
+ Account intentAccount = intent.getParcelableExtra(IntentProtocol.EXTRA_ACCOUNT);
+ assertEquals(account, intentAccount);
+
+ // Validate registered types.
+ Set<String> expectedTypes = Sets.newHashSet(IntentProtocol.ALL_TYPES_TYPE);
+ Set<String> actualTypes = Sets.newHashSet();
+ actualTypes.addAll(intent.getStringArrayListExtra(IntentProtocol.EXTRA_REGISTERED_TYPES));
+ assertEquals(expectedTypes, actualTypes);
+ }
+
+ @SmallTest
+ @Feature({"Sync"})
+ public void testGetContractAuthority() throws Exception {
+ assertEquals(mContext.getPackageName(), mController.getContractAuthority());
+ }
+
+ /**
+ * Asserts that {@code intent} is destined for the correct component.
+ */
+ private static void validateIntentComponent(Intent intent) {
+ assertNotNull(intent.getComponent());
+ assertEquals("org.chromium.sync.notifier.TEST_VALUE",
+ intent.getComponent().getClassName());
+ }
+
+ /**
+ * Mock context that saves all intents given to {@code startService}.
+ */
+ private static class IntentSavingContext extends AdvancedMockContext {
+ private final List<Intent> startedIntents = Lists.newArrayList();
+
+ IntentSavingContext(Context targetContext) {
+ super(targetContext);
+ }
+
+ @Override
+ public ComponentName startService(Intent intent) {
+ startedIntents.add(intent);
+ return new ComponentName(this, getClass());
+ }
+
+ int getNumStartedIntents() {
+ return startedIntents.size();
+ }
+
+ Intent getStartedIntent(int idx) {
+ return startedIntents.get(idx);
+ }
+
+ @Override
+ public PackageManager getPackageManager() {
+ return getBaseContext().getPackageManager();
+ }
+ }
+}
diff --git a/sync/sync.gyp b/sync/sync.gyp
index c22fec1..7caf784 100644
--- a/sync/sync.gyp
+++ b/sync/sync.gyp
@@ -986,6 +986,37 @@
},
],
}],
+ ['OS == "android"', {
+ 'targets': [
+ {
+ 'target_name': 'sync_java',
+ 'type': 'none',
+ 'variables': {
+ 'package_name': 'sync',
+ 'java_in_dir': '../sync/android/java',
+ },
+ 'dependencies': [
+ '../third_party/cacheinvalidation/cacheinvalidation.gyp:cacheinvalidation_javalib',
+ '../third_party/guava/guava.gyp:guava_javalib',
+ '../third_party/jsr-305/jsr-305.gyp:jsr_305_javalib',
+ ],
+ 'includes': [ '../build/java.gypi' ],
+ },
+ {
+ 'target_name': 'sync_javatests',
+ 'type': 'none',
+ 'variables': {
+ 'package_name': 'sync_javatests',
+ 'java_in_dir': '../sync/android/javatests',
+ },
+ 'dependencies': [
+ 'sync_java',
+ '../base/base.gyp:base_java_test_support',
+ ],
+ 'includes': [ '../build/java.gypi' ],
+ },
+ ],
+ }],
# Special target to wrap a gtest_target_type==shared_library
# sync_unit_tests into an android apk for execution.