Commit 0d4e91e5 authored by Becky Zhou's avatar Becky Zhou Committed by Commit Bot

[Feed] StreamLifecycleManager respect prefs::kArticlesListVisible

Bug: 907282
Change-Id: I239dfb4ed667e6584bd598f7221003271ee7185b
Reviewed-on: https://chromium-review.googlesource.com/c/1347270Reviewed-by: default avatarTheresa <twellington@chromium.org>
Reviewed-by: default avatarSky Malice <skym@chromium.org>
Commit-Queue: Becky Zhou <huayinz@chromium.org>
Cr-Commit-Position: refs/heads/master@{#612277}
parent 87380b99
...@@ -282,6 +282,13 @@ public class FeedNewTabPage extends NewTabPage { ...@@ -282,6 +282,13 @@ public class FeedNewTabPage extends NewTabPage {
mMediator.onThumbnailCaptured(); mMediator.onThumbnailCaptured();
} }
/**
* @return The {@link StreamLifecycleManager} that manages the lifecycle of the {@link Stream}.
*/
StreamLifecycleManager getStreamLifecycleManager() {
return mStreamLifecycleManager;
}
/** @return The {@link Stream} that this class holds. */ /** @return The {@link Stream} that this class holds. */
Stream getStream() { Stream getStream() {
return mStream; return mStream;
...@@ -362,6 +369,9 @@ public class FeedNewTabPage extends NewTabPage { ...@@ -362,6 +369,9 @@ public class FeedNewTabPage extends NewTabPage {
void createScrollViewForPolicy() { void createScrollViewForPolicy() {
if (mStream != null) { if (mStream != null) {
mRootView.removeView(mStream.getView()); mRootView.removeView(mStream.getView());
assert mStreamLifecycleManager
!= null
: "StreamLifecycleManager should not be null when the Stream is not null.";
mStreamLifecycleManager.destroy(); mStreamLifecycleManager.destroy();
mStreamLifecycleManager = null; mStreamLifecycleManager = null;
// Do not call mStream.onDestroy(), the mStreamLifecycleManager has done that for us. // Do not call mStream.onDestroy(), the mStreamLifecycleManager has done that for us.
......
...@@ -152,14 +152,20 @@ class FeedNewTabPageMediator ...@@ -152,14 +152,20 @@ class FeedNewTabPageMediator
if (stream == null) return; if (stream == null) return;
stream.removeScrollListener(mStreamScrollListener); stream.removeScrollListener(mStreamScrollListener);
stream.removeOnContentChangedListener(mStreamContentChangedListener);
MemoryPressureListener.removeCallback(mMemoryPressureCallback);
if (mSignInPromo != null) mSignInPromo.destroy();
mPrefChangeRegistrar.removeObserver(Pref.NTP_ARTICLES_LIST_VISIBLE);
mStreamScrollListener = null; mStreamScrollListener = null;
stream.removeOnContentChangedListener(mStreamContentChangedListener);
mStreamContentChangedListener = null; mStreamContentChangedListener = null;
MemoryPressureListener.removeCallback(mMemoryPressureCallback);
mMemoryPressureCallback = null; mMemoryPressureCallback = null;
mSignInPromo = null;
if (mSignInPromo != null) {
mSignInPromo.destroy();
mSignInPromo = null;
}
mPrefChangeRegistrar.removeObserver(Pref.NTP_ARTICLES_LIST_VISIBLE);
} }
/** /**
...@@ -178,6 +184,7 @@ class FeedNewTabPageMediator ...@@ -178,6 +184,7 @@ class FeedNewTabPageMediator
if (mSignInPromo != null) { if (mSignInPromo != null) {
mSignInPromo.setCanShowPersonalizedSuggestions(suggestionsVisible); mSignInPromo.setCanShowPersonalizedSuggestions(suggestionsVisible);
} }
if (suggestionsVisible) mCoordinator.getStreamLifecycleManager().activate();
mStreamContentChanged = true; mStreamContentChanged = true;
} }
......
...@@ -29,9 +29,20 @@ import java.util.concurrent.Executors; ...@@ -29,9 +29,20 @@ import java.util.concurrent.Executors;
public class FeedProcessScopeFactory { public class FeedProcessScopeFactory {
private static final String TAG = "FeedProcessScopeFtry"; private static final String TAG = "FeedProcessScopeFtry";
/** Flag that tracks whether we've ever been disabled via enterprise policy. Should only be /**
* accessed through isFeedProcessScopeEnabled(). */ * Flag that tracks whether we've ever been disabled via enterprise policy. Should only be
* accessed through isFeedProcessScopeEnabled().
*/
private static boolean sEverDisabledForPolicy; private static boolean sEverDisabledForPolicy;
/**
* Tracks whether the article suggestions should be visible to the user during the current
* session. If user opts in to the suggestions during the current session, the suggestions
* services will be immediately warmed up. If user opts out during the current session,
* the suggestions services will not shut down until the next session.
*/
private static boolean sArticlesVisibleDuringSession;
private static PrefChangeRegistrar sPrefChangeRegistrar; private static PrefChangeRegistrar sPrefChangeRegistrar;
private static FeedAppLifecycle sFeedAppLifecycle; private static FeedAppLifecycle sFeedAppLifecycle;
private static FeedProcessScope sFeedProcessScope; private static FeedProcessScope sFeedProcessScope;
...@@ -109,6 +120,8 @@ public class FeedProcessScopeFactory { ...@@ -109,6 +120,8 @@ public class FeedProcessScopeFactory {
&& sFeedAppLifecycle == null && sFeedLoggingBridge == null; && sFeedAppLifecycle == null && sFeedLoggingBridge == null;
if (!isFeedProcessEnabled()) return; if (!isFeedProcessEnabled()) return;
sArticlesVisibleDuringSession =
PrefServiceBridge.getInstance().getBoolean(Pref.NTP_ARTICLES_LIST_VISIBLE);
sPrefChangeRegistrar = new PrefChangeRegistrar(); sPrefChangeRegistrar = new PrefChangeRegistrar();
sPrefChangeRegistrar.addObserver(Pref.NTP_ARTICLES_SECTION_ENABLED, sPrefChangeRegistrar.addObserver(Pref.NTP_ARTICLES_SECTION_ENABLED,
FeedProcessScopeFactory::articlesEnabledPrefChange); FeedProcessScopeFactory::articlesEnabledPrefChange);
...@@ -193,6 +206,21 @@ public class FeedProcessScopeFactory { ...@@ -193,6 +206,21 @@ public class FeedProcessScopeFactory {
destroy(); destroy();
} }
/**
* @return Whether article suggestions are prepared to be shown based on user preference. If
* article suggestions are set hidden within a session, this will still return true
* until the next restart.
*/
static boolean areArticlesVisibleDuringSession() {
// Skip the native call if sArticlesVisibleDuringSession is already true to reduce overhead.
if (!sArticlesVisibleDuringSession
&& PrefServiceBridge.getInstance().getBoolean(Pref.NTP_ARTICLES_LIST_VISIBLE)) {
sArticlesVisibleDuringSession = true;
}
return sArticlesVisibleDuringSession;
}
private static void articlesEnabledPrefChange() { private static void articlesEnabledPrefChange() {
// Should only be subscribed while it was enabled. A change should mean articles are now // Should only be subscribed while it was enabled. A change should mean articles are now
// disabled. // disabled.
......
...@@ -136,8 +136,11 @@ class StreamLifecycleManager implements ApplicationStatus.ActivityStateListener ...@@ -136,8 +136,11 @@ class StreamLifecycleManager implements ApplicationStatus.ActivityStateListener
/** @return Whether the {@link Stream} can be shown. */ /** @return Whether the {@link Stream} can be shown. */
private boolean canShow() { private boolean canShow() {
final int state = ApplicationStatus.getStateForActivity(mActivity); final int state = ApplicationStatus.getStateForActivity(mActivity);
// We don't call Stream#onShow to prevent feed services from being warmed up if the user
// has opted out from article suggestions during the previous session.
return (mStreamState == CREATED || mStreamState == HIDDEN) && !mTab.isHidden() return (mStreamState == CREATED || mStreamState == HIDDEN) && !mTab.isHidden()
&& (state == ActivityState.STARTED || state == ActivityState.RESUMED); && (state == ActivityState.STARTED || state == ActivityState.RESUMED)
&& FeedProcessScopeFactory.areArticlesVisibleDuringSession();
} }
/** Calls {@link Stream#onShow()}. */ /** Calls {@link Stream#onShow()}. */
...@@ -150,15 +153,17 @@ class StreamLifecycleManager implements ApplicationStatus.ActivityStateListener ...@@ -150,15 +153,17 @@ class StreamLifecycleManager implements ApplicationStatus.ActivityStateListener
/** @return Whether the {@link Stream} can be activated. */ /** @return Whether the {@link Stream} can be activated. */
private boolean canActivate() { private boolean canActivate() {
return mStreamState != ACTIVE && mStreamState != DESTROYED && mTab.isUserInteractable() return (mStreamState == SHOWN || mStreamState == INACTIVE) && mTab.isUserInteractable()
&& ApplicationStatus.getStateForActivity(mActivity) == ActivityState.RESUMED; && ApplicationStatus.getStateForActivity(mActivity) == ActivityState.RESUMED
&& FeedProcessScopeFactory.areArticlesVisibleDuringSession();
} }
/** Calls {@link Stream#onActive()}. */ /** Calls {@link Stream#onActive()}. */
private void activate() { void activate() {
// Make sure the Stream can be shown and is set shown before setting it to active state.
show();
if (!canActivate()) return; if (!canActivate()) return;
show();
mStreamState = ACTIVE; mStreamState = ACTIVE;
mStream.onActive(); mStream.onActive();
} }
...@@ -175,6 +180,7 @@ class StreamLifecycleManager implements ApplicationStatus.ActivityStateListener ...@@ -175,6 +180,7 @@ class StreamLifecycleManager implements ApplicationStatus.ActivityStateListener
private void hide() { private void hide() {
if (mStreamState == HIDDEN || mStreamState == CREATED || mStreamState == DESTROYED) return; if (mStreamState == HIDDEN || mStreamState == CREATED || mStreamState == DESTROYED) return;
// Make sure the Stream is inactive before setting it to hidden state.
deactivate(); deactivate();
mStreamState = HIDDEN; mStreamState = HIDDEN;
// Save instance state as the Stream begins to hide. This matches the activity lifecycle // Save instance state as the Stream begins to hide. This matches the activity lifecycle
...@@ -190,6 +196,7 @@ class StreamLifecycleManager implements ApplicationStatus.ActivityStateListener ...@@ -190,6 +196,7 @@ class StreamLifecycleManager implements ApplicationStatus.ActivityStateListener
void destroy() { void destroy() {
if (mStreamState == DESTROYED) return; if (mStreamState == DESTROYED) return;
// Make sure the Stream is hidden before setting it to destroyed state.
hide(); hide();
mStreamState = DESTROYED; mStreamState = DESTROYED;
mTab.removeObserver(mTabObserver); mTab.removeObserver(mTabObserver);
......
...@@ -6,7 +6,10 @@ package org.chromium.chrome.browser.feed; ...@@ -6,7 +6,10 @@ package org.chromium.chrome.browser.feed;
import static org.mockito.AdditionalMatchers.or; import static org.mockito.AdditionalMatchers.or;
import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.isNull; import static org.mockito.ArgumentMatchers.isNull;
import static org.mockito.Mockito.doNothing;
import static org.mockito.Mockito.times; import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when; import static org.mockito.Mockito.when;
...@@ -20,6 +23,7 @@ import android.support.test.filters.SmallTest; ...@@ -20,6 +23,7 @@ import android.support.test.filters.SmallTest;
import com.google.android.libraries.feed.api.stream.Stream; import com.google.android.libraries.feed.api.stream.Stream;
import org.junit.After;
import org.junit.Before; import org.junit.Before;
import org.junit.Test; import org.junit.Test;
import org.junit.runner.RunWith; import org.junit.runner.RunWith;
...@@ -32,6 +36,8 @@ import org.robolectric.annotation.Config; ...@@ -32,6 +36,8 @@ import org.robolectric.annotation.Config;
import org.chromium.base.ActivityState; import org.chromium.base.ActivityState;
import org.chromium.base.ApplicationStatus; import org.chromium.base.ApplicationStatus;
import org.chromium.base.test.BaseRobolectricTestRunner; import org.chromium.base.test.BaseRobolectricTestRunner;
import org.chromium.chrome.browser.preferences.Pref;
import org.chromium.chrome.browser.preferences.PrefServiceBridge;
import org.chromium.chrome.browser.tab.Tab; import org.chromium.chrome.browser.tab.Tab;
import org.chromium.chrome.browser.tab.Tab.TabHidingType; import org.chromium.chrome.browser.tab.Tab.TabHidingType;
...@@ -47,17 +53,30 @@ public class StreamLifecycleManagerTest { ...@@ -47,17 +53,30 @@ public class StreamLifecycleManagerTest {
private Tab mTab; private Tab mTab;
@Mock @Mock
private Stream mStream; private Stream mStream;
@Mock
private PrefServiceBridge mPrefServiceBridge;
private StreamLifecycleManager mStreamLifecycleManager; private StreamLifecycleManager mStreamLifecycleManager;
@Before @Before
public void setUp() throws Exception { public void setUp() throws Exception {
MockitoAnnotations.initMocks(this); MockitoAnnotations.initMocks(this);
// Initialize a test instance for PrefServiceBridge.
when(mPrefServiceBridge.getBoolean(anyInt())).thenReturn(true);
doNothing().when(mPrefServiceBridge).setBoolean(anyInt(), anyBoolean());
PrefServiceBridge.setInstanceForTesting(mPrefServiceBridge);
ApplicationStatus.onStateChangeForTesting(mActivity, ActivityState.CREATED); ApplicationStatus.onStateChangeForTesting(mActivity, ActivityState.CREATED);
mStreamLifecycleManager = new StreamLifecycleManager(mStream, mActivity, mTab); mStreamLifecycleManager = new StreamLifecycleManager(mStream, mActivity, mTab);
verify(mStream, times(1)).onCreate(or(any(String.class), isNull())); verify(mStream, times(1)).onCreate(or(any(String.class), isNull()));
} }
@After
public void tearDown() {
PrefServiceBridge.setInstanceForTesting(null);
}
@Test @Test
@SmallTest @SmallTest
public void testShow() { public void testShow() {
...@@ -82,6 +101,33 @@ public class StreamLifecycleManagerTest { ...@@ -82,6 +101,33 @@ public class StreamLifecycleManagerTest {
verify(mStream, times(1)).onShow(); verify(mStream, times(1)).onShow();
} }
@Test
@SmallTest
public void testShow_ArticlesNotVisible() {
// Verify that onShow is not called when articles are set hidden by the user.
when(mPrefServiceBridge.getBoolean(Pref.NTP_ARTICLES_LIST_VISIBLE)).thenReturn(false);
ApplicationStatus.onStateChangeForTesting(mActivity, ActivityState.STARTED);
when(mTab.isHidden()).thenReturn(false);
when(mTab.isUserInteractable()).thenReturn(true);
mStreamLifecycleManager.getTabObserverForTesting().onShown(mTab, FROM_NEW);
verify(mStream, times(0)).onShow();
// Verify that onShow is called when articles are set shown by the user.
when(mPrefServiceBridge.getBoolean(Pref.NTP_ARTICLES_LIST_VISIBLE)).thenReturn(true);
mStreamLifecycleManager.getTabObserverForTesting().onShown(mTab, FROM_NEW);
verify(mStream, times(1)).onShow();
// Verify that onHide is called after tab is hidden.
mStreamLifecycleManager.getTabObserverForTesting().onHidden(mTab, CHANGED_TABS);
verify(mStream, times(1)).onHide();
// Verify that onShow is called when articles are set hidden by the user within the same
// session.
when(mPrefServiceBridge.getBoolean(Pref.NTP_ARTICLES_LIST_VISIBLE)).thenReturn(false);
mStreamLifecycleManager.getTabObserverForTesting().onShown(mTab, FROM_NEW);
verify(mStream, times(2)).onShow();
}
@Test @Test
@SmallTest @SmallTest
public void testActivate() { public void testActivate() {
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment