summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authornyquist@chromium.org <nyquist@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2013-02-22 02:03:54 +0000
committernyquist@chromium.org <nyquist@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2013-02-22 02:03:54 +0000
commite7c4dc79da6f08f11811b258dee190fda23fb2a4 (patch)
treec4c67446bb87affe91256705bc7d1559724e4c34
parentc68c8a5791da6c91ee15ba55668ffca18a0c4367 (diff)
downloadchromium_src-e7c4dc79da6f08f11811b258dee190fda23fb2a4.zip
chromium_src-e7c4dc79da6f08f11811b258dee190fda23fb2a4.tar.gz
chromium_src-e7c4dc79da6f08f11811b258dee190fda23fb2a4.tar.bz2
[sync] Upstream the code that triggers the InvalidationController
The InvalidationController needs to be started and stopped based on when the main activity is resumed and paused. This upstreams the listener part of that. It also adds a test for the new functionality and a mock implementation of the SyncContentResolverDelegate. BUG=159203 Review URL: https://chromiumcodereview.appspot.com/12310008 git-svn-id: svn://svn.chromium.org/chrome/trunk/src@183975 0039d316-1c4b-4281-b951-d872f2087c98
-rw-r--r--sync/android/DEPS1
-rw-r--r--sync/android/java/src/org/chromium/sync/notifier/InvalidationController.java45
-rw-r--r--sync/android/java/src/org/chromium/sync/notifier/InvalidationService.java2
-rw-r--r--sync/android/java/src/org/chromium/sync/notifier/SyncStatusHelper.java13
-rw-r--r--sync/android/javatests/src/org/chromium/sync/notifier/InvalidationControllerTest.java86
-rw-r--r--sync/sync_tests.gypi13
-rw-r--r--sync/test/android/OWNERS3
-rw-r--r--sync/test/android/javatests/src/org/chromium/sync/test/util/MockSyncContentResolverDelegate.java154
8 files changed, 305 insertions, 12 deletions
diff --git a/sync/android/DEPS b/sync/android/DEPS
index 338ad7b..d83a77e 100644
--- a/sync/android/DEPS
+++ b/sync/android/DEPS
@@ -1,4 +1,5 @@
include_rules = [
"+third_party/cacheinvalidation", # For imports in sync/notifier.
"+net", # For imports in sync/signin.
+ "+sync/test/android", # For sync test tools
]
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 9b91c98..cd98293 100644
--- a/sync/android/java/src/org/chromium/sync/notifier/InvalidationController.java
+++ b/sync/android/java/src/org/chromium/sync/notifier/InvalidationController.java
@@ -16,6 +16,7 @@ import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Preconditions;
import com.google.common.collect.Lists;
+import org.chromium.base.ActivityStatus;
import org.chromium.sync.internal_api.pub.base.ModelType;
import java.util.HashSet;
@@ -27,7 +28,7 @@ import javax.annotation.Nullable;
* Controller used to send start, stop, and registration-change commands to the invalidation
* client library used by Sync.
*/
-public class InvalidationController {
+public class InvalidationController implements ActivityStatus.StateListener {
/**
* Constants and utility methods to create the intents used to communicate between the
* controller and the invalidation client library.
@@ -104,6 +105,10 @@ public class InvalidationController {
*/
private static final String TAG = InvalidationController.class.getSimpleName();
+ private static final Object LOCK = new Object();
+
+ private static InvalidationController sInstance;
+
private final Context mContext;
/**
@@ -163,10 +168,30 @@ public class InvalidationController {
}
/**
- * Returns a new instance that will use {@code context} to issue intents.
+ * Returns the instance that will use {@code context} to issue intents.
+ *
+ * Calling this method will create the instance if it does not yet exist.
*/
+ public static InvalidationController get(Context context) {
+ synchronized (LOCK) {
+ if (sInstance == null) {
+ sInstance = new InvalidationController(context);
+ }
+ return sInstance;
+ }
+ }
+
+ /**
+ * Returns the singleton instance that will use {@code context} to issue intents.
+ *
+ * This method is only kept until the downstream callers of this method have been changed to use
+ * {@link InvalidationController#get(android.content.Context)}.
+ *
+ * TODO(nyquist) Remove this method.
+ */
+ @Deprecated
public static InvalidationController newInstance(Context context) {
- return new InvalidationController(context);
+ return get(context);
}
/**
@@ -174,7 +199,8 @@ public class InvalidationController {
*/
@VisibleForTesting
InvalidationController(Context context) {
- this.mContext = Preconditions.checkNotNull(context.getApplicationContext());
+ mContext = Preconditions.checkNotNull(context.getApplicationContext());
+ ActivityStatus.registerStateListener(this);
}
/**
@@ -218,4 +244,15 @@ public class InvalidationController {
ModelTypeResolver getModelTypeResolver() {
return new ModelTypeResolverImpl();
}
+
+ @Override
+ public void onActivityStateChange(int newState) {
+ if (SyncStatusHelper.get(mContext).isSyncEnabled()) {
+ if (newState == ActivityStatus.PAUSED) {
+ stop();
+ } else if (newState == ActivityStatus.RESUMED) {
+ start();
+ }
+ }
+ }
}
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 b867e80..5786e6f 100644
--- a/sync/android/java/src/org/chromium/sync/notifier/InvalidationService.java
+++ b/sync/android/java/src/org/chromium/sync/notifier/InvalidationService.java
@@ -388,7 +388,7 @@ public class InvalidationService extends AndroidListener {
bundle.putString("payload", (payload == null) ? "" : payload);
}
Account account = SyncStatusHelper.get(this).getSignedInUser();
- String contractAuthority = InvalidationController.newInstance(this).getContractAuthority();
+ String contractAuthority = InvalidationController.get(this).getContractAuthority();
requestSyncFromContentResolver(bundle, account, contractAuthority);
}
diff --git a/sync/android/java/src/org/chromium/sync/notifier/SyncStatusHelper.java b/sync/android/java/src/org/chromium/sync/notifier/SyncStatusHelper.java
index 4fc63581..4b42eb8 100644
--- a/sync/android/java/src/org/chromium/sync/notifier/SyncStatusHelper.java
+++ b/sync/android/java/src/org/chromium/sync/notifier/SyncStatusHelper.java
@@ -131,7 +131,7 @@ public class SyncStatusHelper {
public boolean isSyncEnabled(Account account) {
StrictMode.ThreadPolicy oldPolicy = temporarilyAllowDiskWritesAndDiskReads();
String contractAuthority =
- InvalidationController.newInstance(mApplicationContext).getContractAuthority();
+ InvalidationController.get(mApplicationContext).getContractAuthority();
boolean enabled = account != null &&
mSyncContentResolverWrapper.getMasterSyncAutomatically() &&
mSyncContentResolverWrapper.getSyncAutomatically(account, contractAuthority);
@@ -163,7 +163,7 @@ public class SyncStatusHelper {
public boolean isSyncEnabledForChrome(Account account) {
StrictMode.ThreadPolicy oldPolicy = temporarilyAllowDiskWritesAndDiskReads();
String contractAuthority =
- InvalidationController.newInstance(mApplicationContext).getContractAuthority();
+ InvalidationController.get(mApplicationContext).getContractAuthority();
boolean enabled = account != null &&
mSyncContentResolverWrapper.getSyncAutomatically(account, contractAuthority);
StrictMode.setThreadPolicy(oldPolicy);
@@ -191,7 +191,7 @@ public class SyncStatusHelper {
StrictMode.ThreadPolicy oldPolicy = temporarilyAllowDiskWritesAndDiskReads();
makeSyncable(account);
String contractAuthority =
- InvalidationController.newInstance(mApplicationContext).getContractAuthority();
+ InvalidationController.get(mApplicationContext).getContractAuthority();
if (!mSyncContentResolverWrapper.getSyncAutomatically(account, contractAuthority)) {
mSyncContentResolverWrapper.setSyncAutomatically(account, contractAuthority, true);
}
@@ -206,13 +206,14 @@ public class SyncStatusHelper {
public void disableAndroidSync(Account account) {
StrictMode.ThreadPolicy oldPolicy = temporarilyAllowDiskWritesAndDiskReads();
String contractAuthority =
- InvalidationController.newInstance(mApplicationContext).getContractAuthority();
+ InvalidationController.get(mApplicationContext).getContractAuthority();
if (mSyncContentResolverWrapper.getSyncAutomatically(account, contractAuthority)) {
mSyncContentResolverWrapper.setSyncAutomatically(account, contractAuthority, false);
}
StrictMode.setThreadPolicy(oldPolicy);
}
+ // TODO(nyquist) Move all these methods about signed in user to GoogleServicesManager.
public Account getSignedInUser() {
String syncAccountName = getSignedInAccountName();
if (syncAccountName == null) {
@@ -252,7 +253,7 @@ public class SyncStatusHelper {
*/
private void makeSyncable(Account account) {
String contractAuthority =
- InvalidationController.newInstance(mApplicationContext).getContractAuthority();
+ InvalidationController.get(mApplicationContext).getContractAuthority();
if (hasFinishedFirstSync(account)) {
mSyncContentResolverWrapper.setIsSyncable(account, contractAuthority, 1);
}
@@ -274,7 +275,7 @@ public class SyncStatusHelper {
*/
boolean hasFinishedFirstSync(Account account) {
String contractAuthority =
- InvalidationController.newInstance(mApplicationContext).getContractAuthority();
+ InvalidationController.get(mApplicationContext).getContractAuthority();
return mSyncContentResolverWrapper.getIsSyncable(account, contractAuthority) <= 0;
}
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 b7f96e3..6006ef3 100644
--- a/sync/android/javatests/src/org/chromium/sync/notifier/InvalidationControllerTest.java
+++ b/sync/android/javatests/src/org/chromium/sync/notifier/InvalidationControllerTest.java
@@ -5,6 +5,7 @@
package org.chromium.sync.notifier;
import android.accounts.Account;
+import android.app.Activity;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
@@ -15,11 +16,13 @@ import android.test.suitebuilder.annotation.SmallTest;
import com.google.common.collect.Lists;
import com.google.common.collect.Sets;
+import org.chromium.base.ActivityStatus;
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 org.chromium.sync.signin.AccountManagerHelper;
+import org.chromium.sync.test.util.MockSyncContentResolverDelegate;
import java.util.ArrayList;
import java.util.HashSet;
@@ -38,7 +41,7 @@ public class InvalidationControllerTest extends InstrumentationTestCase {
@Override
protected void setUp() throws Exception {
mContext = new IntentSavingContext(getInstrumentation().getTargetContext());
- mController = InvalidationController.newInstance(mContext);
+ mController = InvalidationController.get(mContext);
}
@SmallTest
@@ -65,6 +68,87 @@ public class InvalidationControllerTest extends InstrumentationTestCase {
@SmallTest
@Feature({"Sync"})
+ public void testResumingMainActivity() throws Exception {
+ // Resuming main activity should trigger a start if sync is enabled.
+ setupSync(true);
+ mController.onActivityStateChange(ActivityStatus.RESUMED);
+ assertEquals(1, mContext.getNumStartedIntents());
+ Intent intent = mContext.getStartedIntent(0);
+ validateIntentComponent(intent);
+ assertNull(intent.getExtras());
+ }
+
+ @SmallTest
+ @Feature({"Sync"})
+ public void testResumingMainActivityWithSyncDisabled() throws Exception {
+ // Resuming main activity should NOT trigger a start if sync is disabled.
+ setupSync(false);
+ mController.onActivityStateChange(ActivityStatus.RESUMED);
+ assertEquals(0, mContext.getNumStartedIntents());
+ }
+
+ @SmallTest
+ @Feature({"Sync"})
+ public void testPausingMainActivity() throws Exception {
+ // Resuming main activity should trigger a stop if sync is enabled.
+ setupSync(true);
+ mController.onActivityStateChange(ActivityStatus.PAUSED);
+ 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 testPausingMainActivityWithSyncDisabled() throws Exception {
+ // Resuming main activity should NOT trigger a stop if sync is disabled.
+ setupSync(false);
+ mController.onActivityStateChange(ActivityStatus.PAUSED);
+ assertEquals(0, mContext.getNumStartedIntents());
+ }
+
+ private void setupSync(boolean syncEnabled) {
+ MockSyncContentResolverDelegate contentResolver = new MockSyncContentResolverDelegate();
+ // Android master sync can safely always be on.
+ contentResolver.setMasterSyncAutomatically(true);
+ // We don't want to use the system content resolver, so we override it.
+ SyncStatusHelper.overrideSyncStatusHelperForTests(mContext, contentResolver);
+ Account account = AccountManagerHelper.createAccountFromName("test@gmail.com");
+ SyncStatusHelper syncStatusHelper = SyncStatusHelper.get(mContext);
+ syncStatusHelper.setSignedInAccountName(account.name);
+ if (syncEnabled) {
+ syncStatusHelper.enableAndroidSync(account);
+ } else {
+ syncStatusHelper.disableAndroidSync(account);
+ }
+ }
+
+ @SmallTest
+ @Feature({"Sync"})
+ public void testEnsureConstructorRegistersListener() throws Exception {
+ final AtomicBoolean listenerCallbackCalled = new AtomicBoolean();
+
+ // Create instance.
+ new InvalidationController(mContext) {
+ @Override
+ public void onActivityStateChange(int newState) {
+ listenerCallbackCalled.set(true);
+ }
+ };
+
+ // Ensure initial state is correct.
+ assertFalse(listenerCallbackCalled.get());
+
+ // Ensure we get a callback, which means we have registered for them.
+ ActivityStatus.onStateChange(new Activity(), ActivityStatus.RESUMED);
+ assertTrue(listenerCallbackCalled.get());
+ }
+
+ @SmallTest
+ @Feature({"Sync"})
public void testRegisterForSpecificTypes() {
final String controllerFlag = "resolveModelTypes";
final ModelTypeResolver resolver = new ModelTypeResolver() {
diff --git a/sync/sync_tests.gypi b/sync/sync_tests.gypi
index 8b399da..059c930 100644
--- a/sync/sync_tests.gypi
+++ b/sync/sync_tests.gypi
@@ -572,10 +572,23 @@
},
'dependencies': [
'sync_java',
+ 'sync_java_test_support',
'../base/base.gyp:base_java_test_support',
],
'includes': [ '../build/java.gypi' ],
},
+ {
+ 'target_name': 'sync_java_test_support',
+ 'type': 'none',
+ 'variables': {
+ 'package_name': 'sync_java_test_support',
+ 'java_in_dir': '../sync/test/android/javatests',
+ },
+ 'dependencies': [
+ 'sync_java',
+ ],
+ 'includes': [ '../build/java.gypi' ],
+ },
],
}],
# Special target to wrap a gtest_target_type==shared_library
diff --git a/sync/test/android/OWNERS b/sync/test/android/OWNERS
new file mode 100644
index 0000000..72643ec
--- /dev/null
+++ b/sync/test/android/OWNERS
@@ -0,0 +1,3 @@
+nileshagrawal@chromium.org
+nyquist@chromium.org
+yfriedman@chromium.org
diff --git a/sync/test/android/javatests/src/org/chromium/sync/test/util/MockSyncContentResolverDelegate.java b/sync/test/android/javatests/src/org/chromium/sync/test/util/MockSyncContentResolverDelegate.java
new file mode 100644
index 0000000..f253d8c
--- /dev/null
+++ b/sync/test/android/javatests/src/org/chromium/sync/test/util/MockSyncContentResolverDelegate.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.test.util;
+
+
+import android.accounts.Account;
+import android.content.ContentResolver;
+import android.content.SyncStatusObserver;
+import android.os.AsyncTask;
+
+import org.chromium.sync.notifier.SyncContentResolverDelegate;
+
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+
+
+/**
+ * Mock implementation of the SyncContentResolverWrapper.
+ *
+ * This implementation only supports status change listeners for the type
+ * SYNC_OBSERVER_TYPE_SETTINGS.
+ */
+public class MockSyncContentResolverDelegate implements SyncContentResolverDelegate {
+
+ private final Map<String, Boolean> mSyncAutomaticallyMap;
+
+ private final Set<AsyncSyncStatusObserver> mObservers;
+
+ private boolean mMasterSyncAutomatically;
+
+ public MockSyncContentResolverDelegate() {
+ mSyncAutomaticallyMap = new HashMap<String, Boolean>();
+ mObservers = new HashSet<AsyncSyncStatusObserver>();
+ }
+
+ @Override
+ public Object addStatusChangeListener(int mask, SyncStatusObserver callback) {
+ if (mask != ContentResolver.SYNC_OBSERVER_TYPE_SETTINGS) {
+ throw new IllegalArgumentException("This implementation only supports "
+ + "ContentResolver.SYNC_OBSERVER_TYPE_SETTINGS as the mask");
+ }
+ AsyncSyncStatusObserver asyncSyncStatusObserver = new AsyncSyncStatusObserver(callback);
+ synchronized (mObservers) {
+ mObservers.add(asyncSyncStatusObserver);
+ }
+ return asyncSyncStatusObserver;
+ }
+
+ @Override
+ public void removeStatusChangeListener(Object handle) {
+ synchronized (mObservers) {
+ mObservers.remove(handle);
+ }
+ }
+
+ @Override
+ public void setMasterSyncAutomatically(boolean sync) {
+ mMasterSyncAutomatically = sync;
+ notifyObservers();
+ }
+
+ @Override
+ public boolean getMasterSyncAutomatically() {
+ return mMasterSyncAutomatically;
+ }
+
+ @Override
+ public boolean getSyncAutomatically(Account account, String authority) {
+ String key = createKey(account, authority);
+ synchronized (mSyncAutomaticallyMap) {
+ return mSyncAutomaticallyMap.containsKey(key) && mSyncAutomaticallyMap.get(key);
+ }
+ }
+
+ @Override
+ public void setSyncAutomatically(Account account, String authority, boolean sync) {
+ String key = createKey(account, authority);
+ synchronized (mSyncAutomaticallyMap) {
+ if (!mSyncAutomaticallyMap.containsKey(key)) {
+ throw new IllegalArgumentException("Account " + account +
+ " is not syncable for authority " + authority +
+ ". Can not set sync state to " + sync);
+ }
+ mSyncAutomaticallyMap.put(key, sync);
+ }
+ notifyObservers();
+ }
+
+ @Override
+ public void setIsSyncable(Account account, String authority, int syncable) {
+ synchronized (mSyncAutomaticallyMap) {
+ switch (syncable) {
+ case 0:
+ mSyncAutomaticallyMap.remove(createKey(account, authority));
+ break;
+ case 1:
+ mSyncAutomaticallyMap.put(createKey(account, authority), false);
+ break;
+ default:
+ throw new IllegalArgumentException("Unable to understand syncable argument: " +
+ syncable);
+ }
+ }
+ notifyObservers();
+ }
+
+ @Override
+ public int getIsSyncable(Account account, String authority) {
+ synchronized (mSyncAutomaticallyMap) {
+ final Boolean isSyncable = mSyncAutomaticallyMap.get(createKey(account, authority));
+ if (isSyncable == null) {
+ return -1;
+ }
+ return isSyncable ? 1 : 0;
+ }
+ }
+
+ private static String createKey(Account account, String authority) {
+ return account.name + "@@@" + account.type + "@@@" + authority;
+ }
+
+ private void notifyObservers() {
+ synchronized (mObservers) {
+ for (AsyncSyncStatusObserver observer : mObservers) {
+ observer.notifyObserverAsync();
+ }
+ }
+ }
+
+ private static class AsyncSyncStatusObserver {
+
+ private final SyncStatusObserver mSyncStatusObserver;
+
+ private AsyncSyncStatusObserver(SyncStatusObserver syncStatusObserver) {
+ mSyncStatusObserver = syncStatusObserver;
+ }
+
+ private void notifyObserverAsync() {
+ new AsyncTask<Void, Void, Void>() {
+
+ @Override
+ protected Void doInBackground(Void... params) {
+ mSyncStatusObserver.onStatusChanged(
+ ContentResolver.SYNC_OBSERVER_TYPE_SETTINGS);
+ return null;
+ }
+ }.execute();
+ }
+ }
+}