blob: 485a94ad65ed69810fece0073dc0819c3746102e [file] [log] [blame]
// Copyright 2015 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.chrome.browser.invalidation;
import android.accounts.Account;
import android.app.Activity;
import android.content.Intent;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageInfo;
import android.os.Bundle;
import org.junit.After;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.robolectric.Robolectric;
import org.robolectric.Shadows;
import org.robolectric.annotation.Config;
import org.robolectric.shadows.ShadowActivity;
import org.robolectric.shadows.ShadowLooper;
import org.robolectric.shadows.ShadowPackageManager;
import org.chromium.base.ActivityState;
import org.chromium.base.ApplicationState;
import org.chromium.base.ApplicationStatus;
import org.chromium.base.CollectionUtil;
import org.chromium.base.test.BaseRobolectricTestRunner;
import org.chromium.base.test.asynctask.CustomShadowAsyncTask;
import org.chromium.base.test.util.Feature;
import org.chromium.chrome.browser.sync.ProfileSyncService;
import org.chromium.components.signin.AccountManagerFacade;
import org.chromium.components.signin.ChromeSigninController;
import org.chromium.components.signin.SystemAccountManagerDelegate;
import org.chromium.components.sync.AndroidSyncSettings;
import org.chromium.components.sync.ModelType;
import org.chromium.components.sync.ModelTypeHelper;
import org.chromium.components.sync.notifier.InvalidationIntentProtocol;
import org.chromium.components.sync.test.util.MockSyncContentResolverDelegate;
import java.util.HashSet;
import java.util.Set;
import java.util.concurrent.atomic.AtomicBoolean;
/**
* Tests for the {@link InvalidationController}.
*/
@RunWith(BaseRobolectricTestRunner.class)
@Config(manifest = Config.NONE, sdk = 21, shadows = {CustomShadowAsyncTask.class})
public class InvalidationControllerTest {
/**
* Stubbed out ProfileSyncService with a setter to control return value of
* {@link ProfileSyncService#getPreferredDataTypes()}
*/
private static class ProfileSyncServiceStub extends ProfileSyncService {
private Set<Integer> mPreferredDataTypes;
public ProfileSyncServiceStub() {
super();
}
public void setPreferredDataTypes(Set<Integer> types) {
mPreferredDataTypes = types;
}
@Override
protected void init() {
// Skip native initialization.
}
@Override
public Set<Integer> getPreferredDataTypes() {
return mPreferredDataTypes;
}
}
private enum IntentType { START, START_AND_REGISTER, STOP };
private ShadowActivity mShadowActivity;
private Activity mActivity;
/**
* The names of the preferred ModelTypes.
*/
private Set<String> mAllTypes;
/**
* The names of the non-session preferred ModelTypes.
*/
private Set<String> mNonSessionTypes;
@Before
public void setUp() throws Exception {
mActivity = Robolectric.buildActivity(Activity.class).setup().get();
mShadowActivity = Shadows.shadowOf(mActivity);
ShadowPackageManager packageManager = Shadows.shadowOf(mActivity.getPackageManager());
Bundle metaData = new Bundle();
metaData.putString(
"ipc.invalidation.ticl.listener_service_class",
ChromeInvalidationClientService.class.getName());
ApplicationInfo applicationInfo = new ApplicationInfo();
applicationInfo.metaData = metaData;
PackageInfo packageInfo = new PackageInfo();
packageInfo.packageName = mActivity.getPackageName();
packageInfo.applicationInfo = applicationInfo;
packageManager.addPackage(packageInfo);
AccountManagerFacade.overrideAccountManagerFacadeForTests(
new SystemAccountManagerDelegate());
ModelTypeHelper.setTestDelegate(new ModelTypeHelper.TestDelegate() {
@Override
public String toNotificationType(int modelType) {
return Integer.toString(modelType);
}
});
ProfileSyncServiceStub profileSyncServiceStub = new ProfileSyncServiceStub();
ProfileSyncService.overrideForTests(profileSyncServiceStub);
profileSyncServiceStub.setPreferredDataTypes(
CollectionUtil.newHashSet(ModelType.BOOKMARKS, ModelType.SESSIONS));
mAllTypes = CollectionUtil.newHashSet(
ModelTypeHelper.toNotificationType(ModelType.BOOKMARKS),
ModelTypeHelper.toNotificationType(ModelType.SESSIONS));
mNonSessionTypes = CollectionUtil.newHashSet(ModelTypeHelper.toNotificationType(
ModelType.BOOKMARKS));
// We don't want to use the system content resolver, so we override it.
MockSyncContentResolverDelegate delegate = new MockSyncContentResolverDelegate();
// Android master sync can safely always be on.
delegate.setMasterSyncAutomatically(true);
AndroidSyncSettings.overrideForTests(delegate, null);
ChromeSigninController.get().setSignedInAccountName("test@example.com");
AndroidSyncSettings.updateAccount(ChromeSigninController.get().getSignedInUser());
AndroidSyncSettings.enableChromeSync();
}
@After
public void tearDown() {
AccountManagerFacade.resetAccountManagerFacadeForTests();
}
/**
* Verify the intent sent by InvalidationController#stop().
*/
@Test
@Feature({"Sync"})
public void testStop() throws Exception {
InvalidationController controller = new InvalidationController(true);
controller.stop();
Intent intent = getOnlyIntent();
validateIntentComponent(intent);
Assert.assertEquals(1, intent.getExtras().size());
Assert.assertTrue(intent.hasExtra(InvalidationIntentProtocol.EXTRA_STOP));
Assert.assertTrue(intent.getBooleanExtra(InvalidationIntentProtocol.EXTRA_STOP, false));
}
/**
* Verify the intent sent by InvalidationController#ensureStartedAndUpdateRegisteredTypes().
*/
@Test
@Feature({"Sync"})
public void testEnsureStartedAndUpdateRegisteredTypes() {
InvalidationController controller = new InvalidationController(false);
controller.ensureStartedAndUpdateRegisteredTypes();
Intent intent = getOnlyIntent();
// Ensure GCM is initialized. This is a regression test for http://crbug.com/475299.
Assert.assertTrue(controller.isGcmInitialized());
// Validate destination.
validateIntentComponent(intent);
Assert.assertEquals(InvalidationIntentProtocol.ACTION_REGISTER, intent.getAction());
// Validate account.
Account intentAccount =
intent.getParcelableExtra(InvalidationIntentProtocol.EXTRA_ACCOUNT);
Assert.assertEquals("test@example.com", intentAccount.name);
// Validate registered types.
Assert.assertEquals(mAllTypes, getRegisterIntentRegisterTypes(intent));
Assert.assertNull(InvalidationIntentProtocol.getRegisteredObjectIds(intent));
}
/**
* Test that pausing and resuming Chrome's activities does not send any intents if sync is
* disabled.
*/
@Test
@Feature({"Sync"})
public void testPauseAndResumeMainActivityWithSyncDisabled() throws Exception {
AndroidSyncSettings.disableChromeSync();
InvalidationController controller = new InvalidationController(false);
controller.onApplicationStateChange(ApplicationState.HAS_PAUSED_ACTIVITIES);
controller.onApplicationStateChange(ApplicationState.HAS_RUNNING_ACTIVITIES);
assertNoNewIntents();
}
/**
* Test that ensureStartedAndUpdateRegisteredTypes() does not send any intents if
* ProfileSyncService is null.
*/
@Test
@Feature({"Sync"})
public void testNullProfileSyncService() throws Exception {
ProfileSyncService.overrideForTests(null);
InvalidationController controller = new InvalidationController(false);
controller.ensureStartedAndUpdateRegisteredTypes();
assertNoNewIntents();
}
/**
* Test that creating an InvalidationController object registers an ApplicationStateListener.
*/
@Test
@Feature({"Sync"})
public void testEnsureConstructorRegistersListener() throws Exception {
final AtomicBoolean listenerCallbackCalled = new AtomicBoolean();
// Create instance.
new InvalidationController(true) {
@Override
public void onApplicationStateChange(int newState) {
listenerCallbackCalled.set(true);
}
};
// Ensure initial state is correct.
Assert.assertFalse(listenerCallbackCalled.get());
// Ensure we get a callback, which means we have registered for them.
ApplicationStatus.onStateChangeForTesting(mActivity, ActivityState.DESTROYED);
Assert.assertTrue(listenerCallbackCalled.get());
}
/**
* Test that the controller registers for session invalidations and stays registered when
* disabling session invalidations is prohibited.
*/
@Test
@Feature({"Sync"})
public void testCannotToggleSessionInvalidations() {
InvalidationController controller = new InvalidationController(false);
controller.ensureStartedAndUpdateRegisteredTypes();
Assert.assertEquals(mAllTypes, getRegisterIntentRegisterTypes(getOnlyIntent()));
controller.onRecentTabsPageOpened();
ShadowLooper.runUiThreadTasksIncludingDelayedTasks();
assertNoNewIntents();
controller.onRecentTabsPageClosed();
ShadowLooper.runUiThreadTasksIncludingDelayedTasks();
assertNoNewIntents();
}
/**********************************************************************************************
* Tests for when session invalidations can be disabled.
*/
/**
* Test that an intent is sent to register for session invalidations after the RecentTabsPage is
* opened.
*/
@Test
@Feature({"Sync"})
public void testRecentTabsPageShown() {
InvalidationController controller = new InvalidationController(true);
controller.ensureStartedAndUpdateRegisteredTypes();
Assert.assertEquals(mNonSessionTypes, getRegisterIntentRegisterTypes(getOnlyIntent()));
controller.onRecentTabsPageOpened();
ShadowLooper.runUiThreadTasksIncludingDelayedTasks();
Assert.assertEquals(mAllTypes, getRegisterIntentRegisterTypes(getOnlyIntent()));
controller.onRecentTabsPageClosed();
ShadowLooper.runUiThreadTasksIncludingDelayedTasks();
Assert.assertEquals(mNonSessionTypes, getRegisterIntentRegisterTypes(getOnlyIntent()));
}
/**
* Test that if the InvalidationController is started while the RecentTabsPage is shown that
* an intent is sent to register for session invalidations only once the InvalidationController
* is started.
*/
@Test
@Feature({"Sync"})
public void testStartWhileRecentTabsPageShown() {
InvalidationController controller = new InvalidationController(true);
controller.onRecentTabsPageOpened();
ShadowLooper.runUiThreadTasksIncludingDelayedTasks();
assertNoNewIntents();
controller.ensureStartedAndUpdateRegisteredTypes();
Assert.assertEquals(IntentType.START_AND_REGISTER, getIntentType(getOnlyIntent()));
ShadowLooper.runUiThreadTasksIncludingDelayedTasks();
Assert.assertEquals(mAllTypes, getRegisterIntentRegisterTypes(getOnlyIntent()));
controller.onRecentTabsPageClosed();
ShadowLooper.runUiThreadTasksIncludingDelayedTasks();
Assert.assertEquals(mNonSessionTypes, getRegisterIntentRegisterTypes(getOnlyIntent()));
}
/**
* Test the handling for multiple open RecentTabsPages.
*/
@Test
@Feature({"Sync"})
public void testMultipleRecentTabsPages() {
InvalidationController controller = new InvalidationController(true);
controller.ensureStartedAndUpdateRegisteredTypes();
Assert.assertEquals(mNonSessionTypes, getRegisterIntentRegisterTypes(getOnlyIntent()));
controller.onRecentTabsPageOpened();
ShadowLooper.runUiThreadTasksIncludingDelayedTasks();
Assert.assertEquals(mAllTypes, getRegisterIntentRegisterTypes(getOnlyIntent()));
controller.onRecentTabsPageOpened();
ShadowLooper.runUiThreadTasksIncludingDelayedTasks();
assertNoNewIntents();
controller.onRecentTabsPageClosed();
ShadowLooper.runUiThreadTasksIncludingDelayedTasks();
assertNoNewIntents();
controller.onRecentTabsPageClosed();
ShadowLooper.runUiThreadTasksIncludingDelayedTasks();
Assert.assertEquals(mNonSessionTypes, getRegisterIntentRegisterTypes(getOnlyIntent()));
}
/**
* Test the handling for when the RecentTabsPage is opened/closed quickly.
*/
@Test
@Feature({"Sync"})
public void testOpenCloseRecentTabsPageQuickly() {
InvalidationController controller = new InvalidationController(true);
controller.ensureStartedAndUpdateRegisteredTypes();
Assert.assertEquals(mNonSessionTypes, getRegisterIntentRegisterTypes(getOnlyIntent()));
controller.onRecentTabsPageOpened();
controller.onRecentTabsPageClosed();
controller.onRecentTabsPageOpened();
ShadowLooper.runUiThreadTasksIncludingDelayedTasks();
Assert.assertEquals(mAllTypes, getRegisterIntentRegisterTypes(getOnlyIntent()));
controller.onRecentTabsPageClosed();
ShadowLooper.runUiThreadTasksIncludingDelayedTasks();
Assert.assertEquals(mNonSessionTypes, getRegisterIntentRegisterTypes(getOnlyIntent()));
controller.onRecentTabsPageOpened();
controller.onRecentTabsPageClosed();
controller.onRecentTabsPageOpened();
controller.onRecentTabsPageClosed();
ShadowLooper.runUiThreadTasksIncludingDelayedTasks();
assertNoNewIntents();
}
/**
* Test that if the InvalidationController is stopped prior to session sync invalidations being
* disabled as a result of the RecentTabsPage being closed, that session sync invalidations are
* disabled when the InvalidationController is restarted.
*/
@Test
@Feature({"Sync"})
public void testDisableSessionInvalidationsOnStart() {
InvalidationController controller = new InvalidationController(true);
controller.ensureStartedAndUpdateRegisteredTypes();
Assert.assertEquals(mNonSessionTypes, getRegisterIntentRegisterTypes(getOnlyIntent()));
controller.onRecentTabsPageOpened();
ShadowLooper.runUiThreadTasksIncludingDelayedTasks();
Assert.assertEquals(mAllTypes, getRegisterIntentRegisterTypes(getOnlyIntent()));
controller.onRecentTabsPageClosed();
controller.stop();
ShadowLooper.runUiThreadTasksIncludingDelayedTasks();
Assert.assertEquals(IntentType.STOP, getIntentType(getOnlyIntent()));
controller.ensureStartedAndUpdateRegisteredTypes();
Assert.assertEquals(mAllTypes, getRegisterIntentRegisterTypes(getOnlyIntent()));
ShadowLooper.runUiThreadTasksIncludingDelayedTasks();
Assert.assertEquals(mNonSessionTypes, getRegisterIntentRegisterTypes(getOnlyIntent()));
}
/**
* Test that if the activity is paused prior to session sync invalidations being disabled as a
* result of the RecentTabsPage being closed, that session sync invalidations are disabled when
* the activity is resumed.
*/
@Test
@Feature({"Sync"})
public void testDisableSessionInvalidationsOnResume() {
InvalidationController controller = new InvalidationController(true);
controller.ensureStartedAndUpdateRegisteredTypes();
Assert.assertEquals(mNonSessionTypes, getRegisterIntentRegisterTypes(getOnlyIntent()));
controller.onRecentTabsPageOpened();
ShadowLooper.runUiThreadTasksIncludingDelayedTasks();
Assert.assertEquals(mAllTypes, getRegisterIntentRegisterTypes(getOnlyIntent()));
controller.onRecentTabsPageClosed();
controller.onApplicationStateChange(ApplicationState.HAS_PAUSED_ACTIVITIES);
ShadowLooper.runUiThreadTasksIncludingDelayedTasks();
Assert.assertEquals(IntentType.STOP, getIntentType(getOnlyIntent()));
controller.onApplicationStateChange(ApplicationState.HAS_RUNNING_ACTIVITIES);
Assert.assertEquals(IntentType.START, getIntentType(getOnlyIntent()));
ShadowLooper.runUiThreadTasksIncludingDelayedTasks();
Assert.assertEquals(mNonSessionTypes, getRegisterIntentRegisterTypes(getOnlyIntent()));
}
/**
* Test that pausing all of Chrome's activities and resuming one of them keeps session
* invalidations enabled if they were enabled prior to Chrome's activities getting paused.
*/
@Test
@Feature({"Sync"})
public void testPauseAndResumeMainActivity() throws Exception {
InvalidationController controller = new InvalidationController(true);
controller.ensureStartedAndUpdateRegisteredTypes();
Assert.assertEquals(mNonSessionTypes, getRegisterIntentRegisterTypes(getOnlyIntent()));
controller.onRecentTabsPageOpened();
ShadowLooper.runUiThreadTasksIncludingDelayedTasks();
Assert.assertEquals(mAllTypes, getRegisterIntentRegisterTypes(getOnlyIntent()));
controller.onApplicationStateChange(ApplicationState.HAS_PAUSED_ACTIVITIES);
ShadowLooper.runUiThreadTasksIncludingDelayedTasks();
Assert.assertEquals(IntentType.STOP, getIntentType(getOnlyIntent()));
// Resuming the activity should not send a START_AND_REGISTER intent.
controller.onApplicationStateChange(ApplicationState.HAS_RUNNING_ACTIVITIES);
ShadowLooper.runUiThreadTasksIncludingDelayedTasks();
Assert.assertEquals(IntentType.START, getIntentType(getOnlyIntent()));
}
/**
* Test that opening the RecentTabsPage has no effect after the InvalidationController is
* stopped.
*/
@Test
@Feature({"Sync"})
public void testPauseAndResumeMainActivityAfterStop() throws Exception {
InvalidationController controller = new InvalidationController(true);
controller.ensureStartedAndUpdateRegisteredTypes();
Assert.assertEquals(mNonSessionTypes, getRegisterIntentRegisterTypes(getOnlyIntent()));
controller.stop();
ShadowLooper.runUiThreadTasksIncludingDelayedTasks();
Assert.assertEquals(IntentType.STOP, getIntentType(getOnlyIntent()));
controller.onRecentTabsPageOpened();
ShadowLooper.runUiThreadTasksIncludingDelayedTasks();
assertNoNewIntents();
}
/**
* Asserts that {@code intent} is destined for the correct component.
*/
private static void validateIntentComponent(Intent intent) {
Assert.assertNotNull(intent.getComponent());
Assert.assertEquals(
ChromeInvalidationClientService.class.getName(),
intent.getComponent().getClassName());
}
/**
* Assert that there are no new intents.
*/
private void assertNoNewIntents() {
Assert.assertNull(mShadowActivity.getNextStartedService());
}
/**
* Assert that there is only one new intent. Returns the intent.
*/
private Intent getOnlyIntent() {
Intent intent = mShadowActivity.getNextStartedService();
Assert.assertNotNull(intent);
Assert.assertNull(mShadowActivity.getNextStartedService());
return intent;
}
/**
* Returns the type of the intent.
*/
private static IntentType getIntentType(Intent intent) {
if (intent.hasExtra(InvalidationIntentProtocol.EXTRA_STOP)) {
return IntentType.STOP;
} else if (intent.hasExtra(InvalidationIntentProtocol.EXTRA_REGISTERED_TYPES)) {
return IntentType.START_AND_REGISTER;
} else {
return IntentType.START;
}
}
/**
* Returns the names of the ModelTypes to be registered by the passed in START_AND_REGISTER
* intent.
*/
private static Set<String> getRegisterIntentRegisterTypes(Intent intent) {
Set<String> registeredTypes = new HashSet<String>();
registeredTypes.addAll(
intent.getStringArrayListExtra(InvalidationIntentProtocol.EXTRA_REGISTERED_TYPES));
return registeredTypes;
}
}