blob: 23e33039bd38df262c87a8c91cdf16acebfdaa93 [file] [log] [blame]
// Copyright 2018 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.feed;
import static org.chromium.components.browser_ui.widget.listmenu.BasicListMenu.buildMenuListItem;
import android.content.res.Resources;
import android.graphics.Rect;
import android.os.SystemClock;
import android.view.View;
import android.widget.ScrollView;
import androidx.annotation.Nullable;
import androidx.annotation.VisibleForTesting;
import org.chromium.base.MemoryPressureListener;
import org.chromium.base.memory.MemoryPressureCallback;
import org.chromium.chrome.R;
import org.chromium.chrome.browser.feed.shared.FeedFeatures;
import org.chromium.chrome.browser.feed.shared.stream.Stream;
import org.chromium.chrome.browser.feed.shared.stream.Stream.ContentChangedListener;
import org.chromium.chrome.browser.feed.shared.stream.Stream.ScrollListener;
import org.chromium.chrome.browser.native_page.ContextMenuManager;
import org.chromium.chrome.browser.native_page.NativePageNavigationDelegate;
import org.chromium.chrome.browser.ntp.NewTabPageLayout;
import org.chromium.chrome.browser.ntp.SnapScrollHelper;
import org.chromium.chrome.browser.ntp.cards.SignInPromo;
import org.chromium.chrome.browser.ntp.cards.promo.HomepagePromoController.HomepagePromoStateListener;
import org.chromium.chrome.browser.ntp.cards.promo.HomepagePromoVariationManager;
import org.chromium.chrome.browser.ntp.cards.promo.enhanced_protection.EnhancedProtectionPromoController.EnhancedProtectionPromoStateListener;
import org.chromium.chrome.browser.ntp.snippets.SectionHeader;
import org.chromium.chrome.browser.preferences.Pref;
import org.chromium.chrome.browser.preferences.PrefChangeRegistrar;
import org.chromium.chrome.browser.profiles.Profile;
import org.chromium.chrome.browser.search_engines.TemplateUrlServiceFactory;
import org.chromium.chrome.browser.signin.SigninPromoController;
import org.chromium.chrome.browser.signin.SigninPromoUtil;
import org.chromium.chrome.browser.signin.services.IdentityServicesProvider;
import org.chromium.chrome.browser.signin.services.SigninManager;
import org.chromium.chrome.browser.signin.ui.PersonalizedSigninPromoView;
import org.chromium.chrome.browser.suggestions.SuggestionsMetrics;
import org.chromium.chrome.features.start_surface.StartSurfaceConfiguration;
import org.chromium.components.browser_ui.widget.listmenu.ListMenu;
import org.chromium.components.browser_ui.widget.listmenu.ListMenuItemProperties;
import org.chromium.components.feature_engagement.Tracker;
import org.chromium.components.prefs.PrefService;
import org.chromium.components.search_engines.TemplateUrlService.TemplateUrlServiceObserver;
import org.chromium.components.signin.base.CoreAccountInfo;
import org.chromium.components.signin.identitymanager.IdentityManager;
import org.chromium.components.signin.metrics.SigninAccessPoint;
import org.chromium.components.user_prefs.UserPrefs;
import org.chromium.content_public.browser.LoadUrlParams;
import org.chromium.ui.modelutil.MVCListAdapter.ModelList;
import org.chromium.ui.modelutil.PropertyModel;
import org.chromium.ui.mojom.WindowOpenDisposition;
import java.util.Locale;
/**
* A mediator for the {@link FeedSurfaceCoordinator} responsible for interacting with the
* native library and handling business logic.
*/
@VisibleForTesting(otherwise = VisibleForTesting.PACKAGE_PRIVATE)
public class FeedSurfaceMediator
implements NewTabPageLayout.ScrollDelegate, ContextMenuManager.TouchEnabledDelegate,
TemplateUrlServiceObserver, ListMenu.Delegate, HomepagePromoStateListener,
EnhancedProtectionPromoStateListener, IdentityManager.Observer {
@VisibleForTesting
public static final String FEED_CONTENT_FIRST_LOADED_TIME_MS_UMA = "FeedContentFirstLoadedTime";
private static final float IPH_TRIGGER_BAR_TRANSITION_FRACTION = 1.0f;
private static final float IPH_STREAM_MIN_SCROLL_FRACTION = 0.10f;
private static final float IPH_FEED_HEADER_MAX_POS_FRACTION = 0.35f;
private final FeedSurfaceCoordinator mCoordinator;
private final @Nullable SnapScrollHelper mSnapScrollHelper;
private final PrefChangeRegistrar mPrefChangeRegistrar;
private final SigninManager mSigninManager;
private final NativePageNavigationDelegate mPageNavigationDelegate;
private @Nullable ScrollListener mStreamScrollListener;
private ContentChangedListener mStreamContentChangedListener;
private SectionHeader mSectionHeader;
private MemoryPressureCallback mMemoryPressureCallback;
private @Nullable SignInPromo mSignInPromo;
private boolean mFeedEnabled;
private boolean mHasHeader;
private boolean mTouchEnabled = true;
private boolean mStreamContentChanged;
private boolean mHasHeaderMenu;
private int mThumbnailWidth;
private int mThumbnailHeight;
private int mThumbnailScrollY;
/** Whether the Feed content is loading. */
private boolean mIsLoadingFeed;
/** Cached parameters for recording the histogram of "FeedContentFirstLoadedTime". */
private boolean mIsInstantStart;
private long mActivityCreationTimeMs;
private long mContentFirstAvailableTimeMs;
// Whether missing a histogram record when onOverviewShownAtLaunch() is called. It is possible
// that Feed content is still loading at that time and the {@link mContentFirstAvailableTimeMs}
// hasn't been set yet.
private boolean mHasPendingUmaRecording;
/**
* @param coordinator The {@link FeedSurfaceCoordinator} that interacts with this class.
* @param snapScrollHelper The {@link SnapScrollHelper} that handles snap scrolling.
* @param pageNavigationDelegate The {@link NativePageNavigationDelegate} that handles page
* navigation.
*/
FeedSurfaceMediator(FeedSurfaceCoordinator coordinator,
@Nullable SnapScrollHelper snapScrollHelper,
@Nullable NativePageNavigationDelegate pageNavigationDelegate) {
mCoordinator = coordinator;
mSnapScrollHelper = snapScrollHelper;
mSigninManager = IdentityServicesProvider.get().getSigninManager(
Profile.getLastUsedRegularProfile());
mPageNavigationDelegate = pageNavigationDelegate;
mPrefChangeRegistrar = new PrefChangeRegistrar();
mHasHeader = mCoordinator.getSectionHeaderView() != null;
mPrefChangeRegistrar.addObserver(Pref.ENABLE_SNIPPETS, this::updateContent);
mHasHeaderMenu = FeedFeatures.isReportingUserActions();
// Check that there is a navigation delegate when using the feed header menu.
if (mPageNavigationDelegate == null && mHasHeaderMenu) {
assert false : "Need navigation delegate for header menu";
}
initialize();
// Create the content.
updateContent();
}
/** Clears any dependencies. */
void destroy() {
destroyPropertiesForStream();
mPrefChangeRegistrar.destroy();
TemplateUrlServiceFactory.get().removeObserver(this);
}
@VisibleForTesting
public void destroyForTesting() {
destroy();
}
private void initialize() {
if (mSnapScrollHelper == null) return;
// 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.
mCoordinator.getView().addOnLayoutChangeListener(
(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 = FeedFeatures.isFeedEnabled();
if ((mFeedEnabled && mCoordinator.getStream() != null)
|| (!mFeedEnabled && mCoordinator.getScrollViewForPolicy() != null)) {
return;
}
if (mFeedEnabled) {
mIsLoadingFeed = true;
mCoordinator.createStream();
if (mSnapScrollHelper != null) {
mSnapScrollHelper.setView(mCoordinator.getStream().getView());
}
initializePropertiesForStream();
} else {
destroyPropertiesForStream();
mCoordinator.createScrollViewForPolicy();
if (mSnapScrollHelper != null) {
mSnapScrollHelper.setView(mCoordinator.getScrollViewForPolicy());
}
initializePropertiesForPolicy();
}
}
/**
* Initialize properties for UI components in the {@link NewTabPage}.
* TODO(huayinz): Introduce a Model for these properties.
*/
private void initializePropertiesForStream() {
Stream stream = mCoordinator.getStream();
if (mSnapScrollHelper != null) {
mStreamScrollListener = new ScrollListener() {
@Override
public void onScrollStateChanged(int state) {}
@Override
public void onScrolled(int dx, int dy) {
mSnapScrollHelper.handleScroll();
}
};
stream.addScrollListener(mStreamScrollListener);
}
mStreamContentChangedListener = new ContentChangedListener() {
@Override
public void onContentChanged() {
mStreamContentChanged = true;
if (mSnapScrollHelper != null) mSnapScrollHelper.resetSearchBoxOnScroll(true);
// Feed v2's background is set to be transparent in {@link
// FeedSurfaceCoordinator#createStream} to show the Feed placeholder. When first
// batch of articles are about to show, set recyclerView back to non-transparent.
// Feed v2 doesn't call onAddFinished(), so we hide placeholder here.
if (FeedFeatures.isV2Enabled() && mCoordinator.isPlaceholderShown()) {
stream.hidePlaceholder();
}
}
@Override
public void onAddFinished() {
// Feed v1's background is set to be transparent in {@link
// FeedSurfaceCoordinator#createStream} to show the Feed placeholder. After first
// batch of articles finish fade-in animation, set recyclerView back to
// non-transparent.
if (!FeedFeatures.isV2Enabled() && mCoordinator.isPlaceholderShown()) {
stream.hidePlaceholder();
}
if (mContentFirstAvailableTimeMs == 0) {
mContentFirstAvailableTimeMs = SystemClock.elapsedRealtime();
if (mHasPendingUmaRecording) {
maybeRecordContentLoadingTime();
mHasPendingUmaRecording = false;
}
}
mIsLoadingFeed = false;
}
@Override
public void onAddStarting() {
// Feed v1's sign-in view is set to be invisible in {@link
// FeedSurfaceCoordinator#getSigninPromoView} if the Feed placeholder is shown. Set
// sign-in box visible back when Feed articles are about to show.
if (!FeedFeatures.isV2Enabled() && mCoordinator.isPlaceholderShown()) {
mCoordinator.fadeInSigninView();
}
}
};
stream.addOnContentChangedListener(mStreamContentChangedListener);
boolean suggestionsVisible = getPrefService().getBoolean(Pref.ARTICLES_LIST_VISIBLE);
if (mHasHeader) {
mSectionHeader = new SectionHeader(getSectionHeaderText(suggestionsVisible),
suggestionsVisible, this::onSectionHeaderToggled);
mPrefChangeRegistrar.addObserver(Pref.ARTICLES_LIST_VISIBLE, this::updateSectionHeader);
TemplateUrlServiceFactory.get().addObserver(this);
mCoordinator.getSectionHeaderView().setHeader(mSectionHeader);
if (mHasHeaderMenu) {
mSectionHeader.setMenuModelList(buildMenuItems());
mSectionHeader.setListMenuDelegate(this::onItemSelected);
FeedSurfaceMediator mediator = this;
HeaderIphScrollListener.Delegate delegate = new HeaderIphScrollListener.Delegate() {
@Override
public Tracker getFeatureEngagementTracker() {
return mCoordinator.getFeatureEngagementTracker();
}
@Override
public Stream getStream() {
return mCoordinator.getStream();
}
@Override
public boolean isFeedHeaderPositionInRecyclerViewSuitableForIPH(
float headerMaxPosFraction) {
return mCoordinator.isFeedHeaderPositionInRecyclerViewSuitableForIPH(
headerMaxPosFraction);
}
@Override
public void showMenuIph() {
mCoordinator.getSectionHeaderView().showMenuIph(
mCoordinator.getUserEducationHelper());
}
@Override
public int getVerticalScrollOffset() {
return mediator.getVerticalScrollOffset();
}
@Override
public boolean isFeedExpanded() {
return mSectionHeader.isExpanded();
}
@Override
public int getRootViewHeight() {
return mCoordinator.getView().getHeight();
}
@Override
public boolean isSignedIn() {
return mSigninManager.getIdentityManager().hasPrimaryAccount();
}
};
mCoordinator.getStream().addScrollListener(new HeaderIphScrollListener(delegate));
mSigninManager.getIdentityManager().addObserver(this);
}
}
// Show feed if there is no header that would allow user to hide feed.
// This is currently only relevant for the two panes start surface.
stream.setStreamContentVisibility(mHasHeader ? mSectionHeader.isExpanded() : true);
initStreamHeaderViews();
mMemoryPressureCallback = pressure -> mCoordinator.getStream().trim();
MemoryPressureListener.addCallback(mMemoryPressureCallback);
}
private void initStreamHeaderViews() {
View homepagePromoView = null;
boolean signInPromoVisible = false;
if (!HomepagePromoVariationManager.getInstance().isSuppressingSignInPromo()) {
signInPromoVisible = createSignInPromoIfNeeded();
if (!signInPromoVisible) homepagePromoView = createHomepagePromoIfNeeded();
} else {
homepagePromoView = createHomepagePromoIfNeeded();
if (homepagePromoView == null) signInPromoVisible = createSignInPromoIfNeeded();
}
// Post processing - if HomepagePromo is showing, then we set the SignInPromo to null.
if (homepagePromoView != null && mSignInPromo != null) {
mSignInPromo.destroy();
mSignInPromo = null;
}
View enhancedProtectionPromoView = null;
if (homepagePromoView == null && !signInPromoVisible) {
enhancedProtectionPromoView = createEnhancedProtectionPromoIfNeeded();
}
// We are not going to show two promos at the same time.
mCoordinator.updateHeaderViews(
signInPromoVisible, homepagePromoView, enhancedProtectionPromoView);
}
/**
* Create and setup the SignInPromo if necessary.
* @return Whether the SignPromo is visible.
*/
private boolean createSignInPromoIfNeeded() {
if (!SignInPromo.shouldCreatePromo()
|| !SigninPromoController.hasNotReachedImpressionLimit(
SigninAccessPoint.NTP_CONTENT_SUGGESTIONS)) {
return false;
}
if (mSignInPromo == null) {
boolean suggestionsVisible = getPrefService().getBoolean(Pref.ARTICLES_LIST_VISIBLE);
mSignInPromo = new FeedSignInPromo(mSigninManager);
mSignInPromo.setCanShowPersonalizedSuggestions(suggestionsVisible);
}
return mSignInPromo.isVisible();
}
private View createHomepagePromoIfNeeded() {
if (mCoordinator.getHomepagePromoController() == null) return null;
View homepagePromoView = mCoordinator.getHomepagePromoController().getPromoView();
if (homepagePromoView != null) {
mCoordinator.getHomepagePromoController().setHomepagePromoStateListener(this);
}
return homepagePromoView;
}
private View createEnhancedProtectionPromoIfNeeded() {
if (mCoordinator.getEnhancedProtectionPromoController() == null) return null;
View enhancedProtectionPromoView =
mCoordinator.getEnhancedProtectionPromoController().getPromoView();
if (enhancedProtectionPromoView != null) {
mCoordinator.getEnhancedProtectionPromoController()
.setEnhancedProtectionPromoStateListener(this);
}
return enhancedProtectionPromoView;
}
/** Clear any dependencies related to the {@link Stream}. */
private void destroyPropertiesForStream() {
Stream stream = mCoordinator.getStream();
if (stream == null) return;
if (mStreamScrollListener != null) {
stream.removeScrollListener(mStreamScrollListener);
mStreamScrollListener = null;
}
stream.removeOnContentChangedListener(mStreamContentChangedListener);
mStreamContentChangedListener = null;
MemoryPressureListener.removeCallback(mMemoryPressureCallback);
mMemoryPressureCallback = null;
if (mSignInPromo != null) {
mSignInPromo.destroy();
mSignInPromo = null;
}
mPrefChangeRegistrar.removeObserver(Pref.ARTICLES_LIST_VISIBLE);
TemplateUrlServiceFactory.get().removeObserver(this);
mSigninManager.getIdentityManager().removeObserver(this);
}
/**
* Initialize properties for the scroll view shown under supervised user or enterprise policy.
*/
private void initializePropertiesForPolicy() {
ScrollView view = mCoordinator.getScrollViewForPolicy();
if (mSnapScrollHelper != null) {
view.getViewTreeObserver().addOnScrollChangedListener(mSnapScrollHelper::handleScroll);
}
}
/** Update whether the section header should be expanded and its text contents. */
private void updateSectionHeader() {
boolean suggestionsVisible = getPrefService().getBoolean(Pref.ARTICLES_LIST_VISIBLE);
if (mSectionHeader.isExpanded() != suggestionsVisible) mSectionHeader.toggleHeader();
mSectionHeader.setHeaderText(getSectionHeaderText(mSectionHeader.isExpanded()));
if (mHasHeaderMenu) {
mSectionHeader.setMenuModelList(buildMenuItems());
}
if (mSignInPromo != null) {
mSignInPromo.setCanShowPersonalizedSuggestions(suggestionsVisible);
}
if (suggestionsVisible) mCoordinator.getStreamLifecycleManager().activate();
mStreamContentChanged = true;
mCoordinator.getSectionHeaderView().updateVisuals();
}
/**
* Callback on section header toggled. This will update the visibility of the Feed and the
* expand icon on the section header view.
*/
private void onSectionHeaderToggled() {
getPrefService().setBoolean(Pref.ARTICLES_LIST_VISIBLE, mSectionHeader.isExpanded());
mCoordinator.getStream().setStreamContentVisibility(mSectionHeader.isExpanded());
// TODO(huayinz): Update the section header view through a ModelChangeProcessor.
mCoordinator.getSectionHeaderView().updateVisuals();
}
/** Returns the section header text based on the selected default search engine */
private String getSectionHeaderText(boolean isExpanded) {
Resources res = mCoordinator.getSectionHeaderView().getResources();
final boolean isDefaultSearchEngineGoogle =
TemplateUrlServiceFactory.get().isDefaultSearchEngineGoogle();
final int sectionHeaderStringId;
if (mHasHeaderMenu) {
if (isDefaultSearchEngineGoogle) {
sectionHeaderStringId =
isExpanded ? R.string.ntp_discover_on : R.string.ntp_discover_off;
} else {
sectionHeaderStringId = isExpanded ? R.string.ntp_discover_on_branded
: R.string.ntp_discover_off_branded;
}
} else {
sectionHeaderStringId = isDefaultSearchEngineGoogle
? R.string.ntp_article_suggestions_section_header
: R.string.ntp_article_suggestions_section_header_branded;
}
return res.getString(sectionHeaderStringId);
}
private ModelList buildMenuItems() {
ModelList itemList = new ModelList();
int icon_id = 0;
if (mSigninManager.getIdentityManager().hasPrimaryAccount()) {
itemList.add(buildMenuListItem(R.string.ntp_manage_my_activity,
R.id.ntp_feed_header_menu_item_activity, icon_id));
itemList.add(buildMenuListItem(R.string.ntp_manage_interests,
R.id.ntp_feed_header_menu_item_interest, icon_id));
}
itemList.add(buildMenuListItem(
R.string.learn_more, R.id.ntp_feed_header_menu_item_learn, icon_id));
if (mSectionHeader.isExpanded()) {
itemList.add(buildMenuListItem(R.string.ntp_turn_off_feed,
R.id.ntp_feed_header_menu_item_toggle_switch, icon_id));
} else {
itemList.add(buildMenuListItem(R.string.ntp_turn_on_feed,
R.id.ntp_feed_header_menu_item_toggle_switch, icon_id));
}
return itemList;
}
/**
* Callback on sign-in promo is dismissed.
*/
void onSignInPromoDismissed() {
View view = mCoordinator.getSigninPromoView();
mSignInPromo.dismiss(removedItemTitle
-> view.announceForAccessibility(view.getResources().getString(
R.string.ntp_accessibility_item_removed, removedItemTitle)));
}
/** Whether a new thumbnail should be captured. */
boolean shouldCaptureThumbnail() {
return mStreamContentChanged || mCoordinator.getView().getWidth() != mThumbnailWidth
|| mCoordinator.getView().getHeight() != mThumbnailHeight
|| getVerticalScrollOffset() != mThumbnailScrollY;
}
/** Reset all the properties for thumbnail capturing after a new thumbnail is captured. */
void onThumbnailCaptured() {
mThumbnailWidth = mCoordinator.getView().getWidth();
mThumbnailHeight = mCoordinator.getView().getHeight();
mThumbnailScrollY = getVerticalScrollOffset();
mStreamContentChanged = false;
}
/**
* @return Whether the touch events are enabled on the {@link FeedNewTabPage}.
* TODO(huayinz): Move this method to a Model once a Model is introduced.
*/
boolean getTouchEnabled() {
return mTouchEnabled;
}
private PrefService getPrefService() {
return UserPrefs.get(Profile.getLastUsedRegularProfile());
}
// TouchEnabledDelegate interface.
@Override
public void setTouchEnabled(boolean enabled) {
mTouchEnabled = enabled;
}
// ScrollDelegate interface.
@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.
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.
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 (!isScrollViewInitialized()) return false;
if (mFeedEnabled) {
return mCoordinator.getStream().isChildAtPositionVisible(position);
} else {
ScrollView scrollView = mCoordinator.getScrollViewForPolicy();
Rect rect = new Rect();
scrollView.getHitRect(rect);
return scrollView.getChildAt(position).getLocalVisibleRect(rect);
}
}
@Override
public void snapScroll() {
if (mSnapScrollHelper == null) return;
if (!isScrollViewInitialized()) return;
int initialScroll = getVerticalScrollOffset();
int scrollTo = mSnapScrollHelper.calculateSnapPosition(initialScroll);
// 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);
}
}
@Override
public void onTemplateURLServiceChanged() {
updateSectionHeader();
}
@Override
public void onItemSelected(PropertyModel item) {
int itemId = item.get(ListMenuItemProperties.MENU_ITEM_ID);
if (itemId == R.id.ntp_feed_header_menu_item_activity) {
mPageNavigationDelegate.openUrl(WindowOpenDisposition.CURRENT_TAB,
new LoadUrlParams("https://myactivity.google.com/myactivity?product=50"));
FeedUma.recordFeedControlsAction(FeedUma.CONTROLS_ACTION_CLICKED_MY_ACTIVITY);
} else if (itemId == R.id.ntp_feed_header_menu_item_interest) {
mPageNavigationDelegate.openUrl(WindowOpenDisposition.CURRENT_TAB,
new LoadUrlParams("https://www.google.com/preferences/interests"));
FeedUma.recordFeedControlsAction(FeedUma.CONTROLS_ACTION_CLICKED_MANAGE_INTERESTS);
} else if (itemId == R.id.ntp_feed_header_menu_item_learn) {
mPageNavigationDelegate.navigateToHelpPage();
FeedUma.recordFeedControlsAction(FeedUma.CONTROLS_ACTION_CLICKED_LEARN_MORE);
} else if (itemId == R.id.ntp_feed_header_menu_item_toggle_switch) {
mSectionHeader.toggleHeader();
FeedUma.recordFeedControlsAction(FeedUma.CONTROLS_ACTION_TOGGLED_FEED);
SuggestionsMetrics.recordArticlesListVisible();
} else {
assert false : String.format(Locale.ENGLISH,
"Cannot handle action for item in the menu with id %d", itemId);
}
}
@Override
public void onHomepagePromoStateChange() {
// If the homepage has status update, we'll not show the HomepagePromo again.
// There are cases where the user has their homepage reset to default. This is an edge case
// and we don't have to reflect that change immediately.
mCoordinator.updateHeaderViews(false, null, null);
}
@Override
public void onEnhancedProtectionPromoStateChange() {
// If the enhanced protection promo has been dismissed, delete it.
mCoordinator.updateHeaderViews(false, null, null);
}
// IdentityManager.Delegate interface.
@Override
public void onPrimaryAccountSet(CoreAccountInfo account) {
updateSectionHeader();
}
@Override
public void onPrimaryAccountCleared(CoreAccountInfo account) {
updateSectionHeader();
}
/**
* The {@link SignInPromo} for the Feed.
* TODO(huayinz): Update content and visibility through a ModelChangeProcessor.
*/
private class FeedSignInPromo extends SignInPromo {
FeedSignInPromo(SigninManager signinManager) {
super(signinManager);
maybeUpdateSignInPromo();
}
@Override
protected void setVisibilityInternal(boolean visible) {
if (isVisible() == visible) return;
super.setVisibilityInternal(visible);
mCoordinator.updateHeaderViews(visible, null, null);
maybeUpdateSignInPromo();
}
@Override
protected void notifyDataChanged() {
maybeUpdateSignInPromo();
}
/** Update the content displayed in {@link PersonalizedSigninPromoView}. */
private void maybeUpdateSignInPromo() {
// Only call #setupPromoViewFromCache() if SignInPromo is visible to avoid potentially
// blocking the UI thread for several seconds if the accounts cache is not populated
// yet.
if (!isVisible()) return;
if (isUserSignedInButNotSyncing()) {
SigninPromoUtil.setupSyncPromoViewFromCache(mSigninPromoController,
mProfileDataCache, mCoordinator.getSigninPromoView(), null);
} else {
SigninPromoUtil.setupSigninPromoViewFromCache(mSigninPromoController,
mProfileDataCache, mCoordinator.getSigninPromoView(), null);
}
}
}
// TODO(huayinz): Return the Model for testing in Coordinator instead once a Model is created.
@VisibleForTesting
public SectionHeader getSectionHeaderForTesting() {
return mSectionHeader;
}
@VisibleForTesting
public SignInPromo getSignInPromoForTesting() {
return mSignInPromo;
}
void onOverviewShownAtLaunch(long activityCreationTimeMs, boolean isInstantStart) {
assert mActivityCreationTimeMs == 0;
mActivityCreationTimeMs = activityCreationTimeMs;
mIsInstantStart = isInstantStart;
if (!maybeRecordContentLoadingTime() && mIsLoadingFeed) {
mHasPendingUmaRecording = true;
}
}
private boolean maybeRecordContentLoadingTime() {
if (mActivityCreationTimeMs == 0 || mContentFirstAvailableTimeMs == 0) return false;
StartSurfaceConfiguration.recordHistogram(FEED_CONTENT_FIRST_LOADED_TIME_MS_UMA,
mContentFirstAvailableTimeMs - mActivityCreationTimeMs, mIsInstantStart);
return true;
}
}