diff options
author | nyquist@chromium.org <nyquist@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2013-01-12 16:07:20 +0000 |
---|---|---|
committer | nyquist@chromium.org <nyquist@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2013-01-12 16:07:20 +0000 |
commit | 8d8e18a3ae07fd1dc070b5c65b6f27cccf602998 (patch) | |
tree | 848e7e86ca737ed9279c0c3ca35cd1188b609e6b /sync | |
parent | 636ee43284f75156541540698d89df1227ff3b12 (diff) | |
download | chromium_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')
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. |