Commit a049ad82 authored by Becky Zhou's avatar Becky Zhou Committed by Commit Bot

[Feed] Handle supervised user/enterprise policy on Feed NTP

Use a ScrollView instead of the Stream for the NTP if supervised
user or enterprise policy is enabled.

Bug: 862832
Change-Id: I8987db14dac17ef525860817fcc341b254fea358
Reviewed-on: https://chromium-review.googlesource.com/1208814
Commit-Queue: Becky Zhou <huayinz@chromium.org>
Reviewed-by: default avatarTheresa <twellington@chromium.org>
Cr-Commit-Position: refs/heads/master@{#589609}
parent 5b59b7dd
......@@ -4,16 +4,19 @@
package org.chromium.chrome.browser.feed;
import android.app.Activity;
import android.content.Context;
import android.content.res.Configuration;
import android.content.res.Resources;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.drawable.Drawable;
import android.support.annotation.Nullable;
import android.view.LayoutInflater;
import android.view.MotionEvent;
import android.view.View;
import android.widget.FrameLayout;
import android.widget.ScrollView;
import com.google.android.libraries.feed.api.scope.FeedProcessScope;
import com.google.android.libraries.feed.api.scope.FeedStreamScope;
......@@ -39,13 +42,12 @@ import org.chromium.chrome.browser.profiles.Profile;
import org.chromium.chrome.browser.search_engines.TemplateUrlService;
import org.chromium.chrome.browser.snackbar.Snackbar;
import org.chromium.chrome.browser.snackbar.SnackbarManager;
import org.chromium.chrome.browser.suggestions.SuggestionsNavigationDelegateImpl;
import org.chromium.chrome.browser.tab.Tab;
import org.chromium.chrome.browser.tabmodel.TabModelSelector;
import org.chromium.chrome.browser.util.ViewUtils;
import org.chromium.chrome.browser.widget.displaystyle.HorizontalDisplayStyle;
import org.chromium.chrome.browser.widget.displaystyle.MarginResizer;
import org.chromium.chrome.browser.widget.displaystyle.UiConfig;
import org.chromium.ui.UiUtils;
import java.util.Arrays;
......@@ -54,13 +56,18 @@ import java.util.Arrays;
*/
public class FeedNewTabPage extends NewTabPage {
private final FeedNewTabPageMediator mMediator;
private final StreamLifecycleManager mStreamLifecycleManager;
private final Stream mStream;
private UiConfig mUiConfig;
private FrameLayout mRootView;
private SectionHeaderView mSectionHeaderView;
private FeedImageLoader mImageLoader;
// Used when Feed is enabled.
private @Nullable Stream mStream;
private @Nullable FeedImageLoader mImageLoader;
private @Nullable StreamLifecycleManager mStreamLifecycleManager;
private @Nullable SectionHeaderView mSectionHeaderView;
// Used when Feed is disabled by policy.
private @Nullable ScrollView mScrollViewForPolicy;
private class BasicSnackbarApi implements SnackbarApi {
@Override
......@@ -175,42 +182,12 @@ public class FeedNewTabPage extends NewTabPage {
TabModelSelector tabModelSelector) {
super(activity, nativePageHost, tabModelSelector);
FeedProcessScope feedProcessScope = FeedProcessScopeFactory.getFeedProcessScope();
Tab tab = nativePageHost.getActiveTab();
Profile profile = tab.getProfile();
mImageLoader = new FeedImageLoader(profile, activity);
SuggestionsNavigationDelegateImpl navigationDelegate =
new SuggestionsNavigationDelegateImpl(
activity, profile, nativePageHost, tabModelSelector);
ActionApi actionApi = new FeedActionHandler(navigationDelegate,
() -> FeedProcessScopeFactory.getFeedScheduler().onSuggestionConsumed());
FeedOfflineIndicator offlineIndicator = FeedProcessScopeFactory.getFeedOfflineIndicator();
FeedStreamScope streamScope =
feedProcessScope
.createFeedStreamScopeBuilder(activity, mImageLoader, actionApi,
new BasicStreamConfiguration(),
new BasicCardConfiguration(activity.getResources(), mUiConfig),
new BasicSnackbarApi(), new FeedBasicLogging(), offlineIndicator)
.build();
mStream = streamScope.getStream();
mStreamLifecycleManager = new StreamLifecycleManager(mStream, activity, tab);
LayoutInflater inflater = LayoutInflater.from(activity);
mNewTabPageLayout = (NewTabPageLayout) inflater.inflate(R.layout.new_tab_page_layout, null);
mSectionHeaderView = (SectionHeaderView) inflater.inflate(
R.layout.new_tab_page_snippets_expandable_header, null);
// TODO(huayinz): Add MarginResizer for sign-in promo under issue 860051 or 860043,
// depending on which one lands first.
Resources resources = mSectionHeaderView.getResources();
int defaultMargin =
resources.getDimensionPixelSize(R.dimen.content_suggestions_card_modern_margin);
int wideMargin = resources.getDimensionPixelSize(R.dimen.ntp_wide_card_lateral_margins);
MarginResizer.createAndAttach(mSectionHeaderView, mUiConfig, defaultMargin, wideMargin);
mMediator = new FeedNewTabPageMediator(this,
new SnapScrollHelper(mNewTabPageManager, mNewTabPageLayout, mStream.getView()));
// Mediator should be created before any Stream changes.
mMediator = new FeedNewTabPageMediator(
this, new SnapScrollHelper(mNewTabPageManager, mNewTabPageLayout));
// Don't store a direct reference to the activity, because it might change later if the tab
// is reparented.
......@@ -225,12 +202,6 @@ public class FeedNewTabPage extends NewTabPage {
mSearchProviderHasLogo,
TemplateUrlService.getInstance().isDefaultSearchEngineGoogle(), mMediator,
contextMenuManager, mUiConfig);
mStream.getView().setBackgroundColor(Color.WHITE);
mRootView.addView(mStream.getView());
mStream.setHeaderViews(Arrays.asList(new NonDismissibleHeader(mNewTabPageLayout),
new NonDismissibleHeader(mSectionHeaderView)));
}
@Override
......@@ -247,8 +218,8 @@ public class FeedNewTabPage extends NewTabPage {
public void destroy() {
super.destroy();
mMediator.destroy();
mImageLoader.destroy();
mStreamLifecycleManager.destroy();
if (mImageLoader != null) mImageLoader.destroy();
if (mStreamLifecycleManager != null) mStreamLifecycleManager.destroy();
}
@Override
......@@ -283,6 +254,94 @@ public class FeedNewTabPage extends NewTabPage {
return mStream;
}
/**
* Create a {@link Stream} for this class.
*/
void createStream() {
if (mScrollViewForPolicy != null) {
mRootView.removeView(mScrollViewForPolicy);
mScrollViewForPolicy = null;
}
FeedProcessScope feedProcessScope = FeedProcessScopeFactory.getFeedProcessScope();
Activity activity = mTab.getActivity();
Profile profile = mTab.getProfile();
mImageLoader = new FeedImageLoader(profile, activity);
ActionApi actionApi = new FeedActionHandler(mNewTabPageManager.getNavigationDelegate(),
() -> FeedProcessScopeFactory.getFeedScheduler().onSuggestionConsumed());
FeedOfflineIndicator offlineIndicator = FeedProcessScopeFactory.getFeedOfflineIndicator();
FeedStreamScope streamScope =
feedProcessScope
.createFeedStreamScopeBuilder(activity, mImageLoader, actionApi,
new BasicStreamConfiguration(),
new BasicCardConfiguration(activity.getResources(), mUiConfig),
new BasicSnackbarApi(), new FeedBasicLogging(), offlineIndicator)
.build();
mStream = streamScope.getStream();
mStreamLifecycleManager = new StreamLifecycleManager(mStream, activity, mTab);
LayoutInflater inflater = LayoutInflater.from(activity);
mSectionHeaderView = (SectionHeaderView) inflater.inflate(
R.layout.new_tab_page_snippets_expandable_header, null);
// TODO(huayinz): Add MarginResizer for sign-in promo under issue 860051 or 860043,
// depending on which one lands first.
Resources resources = mSectionHeaderView.getResources();
int defaultMargin =
resources.getDimensionPixelSize(R.dimen.content_suggestions_card_modern_margin);
int wideMargin = resources.getDimensionPixelSize(R.dimen.ntp_wide_card_lateral_margins);
MarginResizer.createAndAttach(mSectionHeaderView, mUiConfig, defaultMargin, wideMargin);
View view = mStream.getView();
view.setBackgroundColor(Color.WHITE);
mRootView.addView(view);
UiUtils.removeViewFromParent(mNewTabPageLayout);
UiUtils.removeViewFromParent(mSectionHeaderView);
mStream.setHeaderViews(Arrays.asList(new NonDismissibleHeader(mNewTabPageLayout),
new NonDismissibleHeader(mSectionHeaderView)));
// Explicitly request focus on the scroll container to avoid UrlBar being focused after
// the scroll container for policy is removed.
view.requestFocus();
}
/**
* @return The {@link ScrollView} for displaying content for supervised user or enterprise
* policy.
*/
ScrollView getScrollViewForPolicy() {
return mScrollViewForPolicy;
}
/**
* Create a {@link ScrollView} for displaying content for supervised user or enterprise policy.
*/
void createScrollViewForPolicy() {
if (mStream != null) {
mRootView.removeView(mStream.getView());
mStreamLifecycleManager.destroy();
mStream = null;
mSectionHeaderView = null;
}
mScrollViewForPolicy = new ScrollView(mTab.getActivity());
mScrollViewForPolicy.setBackgroundColor(Color.WHITE);
// Make scroll view focusable so that it is the next focusable view when the url bar clears
// focus.
mScrollViewForPolicy.setFocusable(true);
mScrollViewForPolicy.setFocusableInTouchMode(true);
mScrollViewForPolicy.setContentDescription(
mScrollViewForPolicy.getResources().getString(R.string.accessibility_new_tab_page));
UiUtils.removeViewFromParent(mNewTabPageLayout);
mScrollViewForPolicy.addView(mNewTabPageLayout);
mRootView.addView(mScrollViewForPolicy);
mScrollViewForPolicy.requestFocus();
}
/** @return The {@link SectionHeaderView} for the Feed section header. */
SectionHeaderView getSectionHeaderView() {
return mSectionHeaderView;
......
......@@ -5,6 +5,8 @@
package org.chromium.chrome.browser.feed;
import android.content.res.Resources;
import android.graphics.Rect;
import android.widget.ScrollView;
import com.google.android.libraries.feed.api.stream.ContentChangedListener;
import com.google.android.libraries.feed.api.stream.ScrollListener;
......@@ -36,6 +38,7 @@ class FeedNewTabPageMediator
private SectionHeader mSectionHeader;
private MemoryPressureCallback mMemoryPressureCallback;
private boolean mFeedEnabled;
private boolean mTouchEnabled = true;
private boolean mStreamContentChanged;
private int mThumbnailWidth;
......@@ -44,31 +47,26 @@ class FeedNewTabPageMediator
/**
* @param feedNewTabPage The {@link FeedNewTabPage} that interacts with this class.
* @param snapScrollHelper The {@link SnapScrollHelper} that handles snap scrolling.
*/
FeedNewTabPageMediator(FeedNewTabPage feedNewTabPage, SnapScrollHelper snapScrollHelper) {
mCoordinator = feedNewTabPage;
mSnapScrollHelper = snapScrollHelper;
initializeProperties();
mPrefChangeRegistrar = new PrefChangeRegistrar();
mPrefChangeRegistrar.addObserver(
Pref.NTP_ARTICLES_LIST_VISIBLE, this::updateSectionHeader);
mPrefChangeRegistrar.addObserver(Pref.NTP_ARTICLES_SECTION_ENABLED, this::updateContent);
initialize();
// Create the content.
updateContent();
}
/** Clears any dependencies. */
void destroy() {
Stream stream = mCoordinator.getStream();
stream.removeScrollListener(mStreamScrollListener);
stream.removeOnContentChangedListener(mStreamContentChangedListener);
destroyPropertiesForStream();
mPrefChangeRegistrar.destroy();
MemoryPressureListener.removeCallback(mMemoryPressureCallback);
}
/**
* Initialize properties for UI components in the {@link FeedNewTabPage}.
* TODO(huayinz): Introduce a Model for these properties.
*/
private void initializeProperties() {
private void initialize() {
// Listen for layout changes on the NewTabPageView itself to catch changes in scroll
// position that are due to layout changes after e.g. device rotation. This contrasts with
// regular scrolling, which is observed through an OnScrollListener.
......@@ -76,7 +74,33 @@ class FeedNewTabPageMediator
(v, left, top, right, bottom, oldLeft, oldTop, oldRight, oldBottom) -> {
mSnapScrollHelper.handleScroll();
});
}
/** Update the content based on supervised user or enterprise policy. */
private void updateContent() {
mFeedEnabled =
PrefServiceBridge.getInstance().getBoolean(Pref.NTP_ARTICLES_SECTION_ENABLED);
if ((mFeedEnabled && mCoordinator.getStream() != null)
|| (!mFeedEnabled && mCoordinator.getScrollViewForPolicy() != null))
return;
if (mFeedEnabled) {
mCoordinator.createStream();
mSnapScrollHelper.setView(mCoordinator.getStream().getView());
initializePropertiesForStream();
} else {
destroyPropertiesForStream();
mCoordinator.createScrollViewForPolicy();
mSnapScrollHelper.setView(mCoordinator.getScrollViewForPolicy());
initializePropertiesForPolicy();
}
}
/**
* Initialize properties for UI components in the {@link FeedNewTabPage}.
* TODO(huayinz): Introduce a Model for these properties.
*/
private void initializePropertiesForStream() {
Stream stream = mCoordinator.getStream();
mStreamScrollListener = new ScrollListener() {
@Override
......@@ -97,6 +121,8 @@ class FeedNewTabPageMediator
new SectionHeader(res.getString(R.string.ntp_article_suggestions_section_header),
PrefServiceBridge.getInstance().getBoolean(Pref.NTP_ARTICLES_LIST_VISIBLE),
this::onSectionHeaderToggled);
mPrefChangeRegistrar.addObserver(
Pref.NTP_ARTICLES_LIST_VISIBLE, this::updateSectionHeader);
mCoordinator.getSectionHeaderView().setHeader(mSectionHeader);
stream.setStreamContentVisibility(mSectionHeader.isExpanded());
......@@ -104,6 +130,28 @@ class FeedNewTabPageMediator
MemoryPressureListener.addCallback(mMemoryPressureCallback);
}
/** Clear any dependencies related to the {@link Stream}. */
private void destroyPropertiesForStream() {
Stream stream = mCoordinator.getStream();
if (stream == null) return;
stream.removeScrollListener(mStreamScrollListener);
stream.removeOnContentChangedListener(mStreamContentChangedListener);
MemoryPressureListener.removeCallback(mMemoryPressureCallback);
mPrefChangeRegistrar.removeObserver(Pref.NTP_ARTICLES_LIST_VISIBLE);
mStreamScrollListener = null;
mStreamContentChangedListener = null;
mMemoryPressureCallback = null;
}
/**
* Initialize properties for the scroll view shown under supervised user or enterprise policy.
*/
private void initializePropertiesForPolicy() {
ScrollView view = mCoordinator.getScrollViewForPolicy();
view.getViewTreeObserver().addOnScrollChangedListener(mSnapScrollHelper::handleScroll);
}
/** Update whether the section header should be expanded. */
private void updateSectionHeader() {
if (mSectionHeader.isExpanded()
......@@ -158,26 +206,42 @@ class FeedNewTabPageMediator
@Override
public boolean isScrollViewInitialized() {
if (mFeedEnabled) {
Stream stream = mCoordinator.getStream();
// During startup the view may not be fully initialized, so we check to see if some basic
// view properties (height of the RecyclerView) are sane.
// During startup the view may not be fully initialized, so we check to see if some
// basic view properties (height of the RecyclerView) are sane.
return stream != null && stream.getView().getHeight() > 0;
} else {
ScrollView scrollView = mCoordinator.getScrollViewForPolicy();
return scrollView != null && scrollView.getHeight() > 0;
}
}
@Override
public int getVerticalScrollOffset() {
// This method returns a valid vertical scroll offset only when the first header view in the
// Stream is visible. In all other cases, this returns 0.
// Stream is visible.
if (!isScrollViewInitialized()) return 0;
if (mFeedEnabled) {
int firstChildTop = mCoordinator.getStream().getChildTopAt(0);
return firstChildTop != Stream.POSITION_NOT_KNOWN ? -firstChildTop : Integer.MIN_VALUE;
} else {
return mCoordinator.getScrollViewForPolicy().getScrollY();
}
}
@Override
public boolean isChildVisibleAtPosition(int position) {
if (mFeedEnabled) {
return isScrollViewInitialized()
&& mCoordinator.getStream().isChildAtPositionVisible(position);
} else {
ScrollView scrollView = mCoordinator.getScrollViewForPolicy();
Rect rect = new Rect();
scrollView.getHitRect(rect);
return scrollView.getChildAt(position).getLocalVisibleRect(rect);
}
}
@Override
......@@ -189,6 +253,11 @@ class FeedNewTabPageMediator
// Calculating the snap position should be idempotent.
assert scrollTo == mSnapScrollHelper.calculateSnapPosition(scrollTo);
if (mFeedEnabled) {
mCoordinator.getStream().smoothScrollBy(0, scrollTo - initialScroll);
} else {
mCoordinator.getScrollViewForPolicy().smoothScrollBy(0, scrollTo - initialScroll);
}
}
}
......@@ -133,7 +133,8 @@ public class NewTabPageView extends FrameLayout {
mNewTabPageLayout.initialize(manager, tab, tileGroupDelegate, searchProviderHasLogo,
searchProviderIsGoogle, mRecyclerView, mContextMenuManager, mUiConfig);
mSnapScrollHelper = new SnapScrollHelper(mManager, mNewTabPageLayout, mRecyclerView);
mSnapScrollHelper = new SnapScrollHelper(mManager, mNewTabPageLayout);
mSnapScrollHelper.setView(mRecyclerView);
mRecyclerView.setSnapScrollHelper(mSnapScrollHelper);
addView(mRecyclerView);
......
......@@ -6,6 +6,7 @@ package org.chromium.chrome.browser.ntp;
import android.annotation.SuppressLint;
import android.content.res.Resources;
import android.support.annotation.NonNull;
import android.view.MotionEvent;
import android.view.View;
......@@ -21,11 +22,11 @@ public class SnapScrollHelper {
private final NewTabPageManager mManager;
private final NewTabPageLayout mNewTabPageLayout;
private final View mView;
private final Runnable mSnapScrollRunnable;
private final int mToolbarHeight;
private final int mSearchBoxTransitionLength;
private View mView;
private boolean mPendingSnapScroll;
private int mLastScrollY = -1;
......@@ -33,20 +34,30 @@ public class SnapScrollHelper {
* @param manager The {@link NewTabPageManager} to get information about user interactions on
* the {@link NewTabPage}.
* @param newTabPageLayout The {@link NewTabPageLayout} associated with the {@link NewTabPage}.
* @param view The view on which this class needs to handle snap scroll.
*/
public SnapScrollHelper(NewTabPageView.NewTabPageManager manager,
NewTabPageLayout newTabPageLayout, View view) {
public SnapScrollHelper(
NewTabPageView.NewTabPageManager manager, NewTabPageLayout newTabPageLayout) {
mManager = manager;
mNewTabPageLayout = newTabPageLayout;
mView = view;
mSnapScrollRunnable = new SnapScrollRunnable();
Resources res = view.getResources();
Resources res = newTabPageLayout.getResources();
mToolbarHeight = res.getDimensionPixelSize(R.dimen.toolbar_height_no_shadow)
+ res.getDimensionPixelSize(R.dimen.toolbar_progress_bar_height);
mSearchBoxTransitionLength =
res.getDimensionPixelSize(R.dimen.ntp_search_box_transition_length);
}
/** @param view The view on which this class needs to handle snap scroll. */
public void setView(@NonNull View view) {
if (mView != null) {
mPendingSnapScroll = false;
mLastScrollY = -1;
mView.removeCallbacks(mSnapScrollRunnable);
mView.setOnTouchListener(null);
}
mView = view;
@SuppressLint("ClickableViewAccessibility")
View.OnTouchListener onTouchListener = (v, event) -> {
......
......@@ -45,6 +45,17 @@ public class PrefChangeRegistrar {
nativeAdd(mNativeRegistrar, preference);
}
/**
* Remove an observer for the specified preference if it has previously been added.
* @param preference The specified preference.
*/
public void removeObserver(@Pref int preference) {
PrefObserver observer = mObservers.get(preference);
if (observer == null) return;
mObservers.remove(preference);
nativeRemove(mNativeRegistrar, preference);
}
/**
* Destroy native PrefChangeRegistrar.
*/
......@@ -64,5 +75,6 @@ public class PrefChangeRegistrar {
private native long nativeInit();
private native void nativeAdd(long nativePrefChangeRegistrarAndroid, int preference);
private native void nativeRemove(long nativePrefChangeRegistrarAndroid, int preference);
private native void nativeDestroy(long nativePrefChangeRegistrarAndroid);
}
......@@ -40,6 +40,13 @@ void PrefChangeRegistrarAndroid::Add(JNIEnv* env,
base::Unretained(this), j_pref_index));
}
void PrefChangeRegistrarAndroid::Remove(JNIEnv* env,
const JavaParamRef<jobject>& obj,
const jint j_pref_index) {
pref_change_registrar_.Remove(
PrefServiceBridge::GetPrefNameExposedToJava(j_pref_index));
}
void PrefChangeRegistrarAndroid::OnPreferenceChange(const int pref_index) {
JNIEnv* env = AttachCurrentThread();
Java_PrefChangeRegistrar_onPreferenceChange(
......
......@@ -23,6 +23,9 @@ class PrefChangeRegistrarAndroid {
void Add(JNIEnv* env,
const JavaParamRef<jobject>& obj,
const jint j_pref_index);
void Remove(JNIEnv* env,
const JavaParamRef<jobject>& obj,
const jint j_pref_index);
private:
~PrefChangeRegistrarAndroid();
......
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