summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-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();
+ }
+ }
+}