| // 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.contextual_suggestions; |
| |
| import static org.chromium.chrome.browser.dependency_injection.ChromeCommonQualifiers.LAST_USED_PROFILE; |
| |
| import android.content.Context; |
| import android.os.Handler; |
| import android.os.SystemClock; |
| import android.support.annotation.Nullable; |
| import android.text.TextUtils; |
| import android.view.View; |
| |
| import org.chromium.base.ContextUtils; |
| import org.chromium.base.VisibleForTesting; |
| import org.chromium.base.metrics.RecordHistogram; |
| import org.chromium.base.metrics.RecordUserAction; |
| import org.chromium.chrome.R; |
| import org.chromium.chrome.browser.ChromeFeatureList; |
| import org.chromium.chrome.browser.compositor.layouts.EmptyOverviewModeObserver; |
| import org.chromium.chrome.browser.compositor.layouts.LayoutManager; |
| import org.chromium.chrome.browser.compositor.layouts.LayoutManagerChrome; |
| import org.chromium.chrome.browser.compositor.layouts.OverviewModeBehavior; |
| import org.chromium.chrome.browser.contextual_suggestions.ContextualSuggestionsBridge.ContextualSuggestionsResult; |
| import org.chromium.chrome.browser.dependency_injection.ActivityScope; |
| import org.chromium.chrome.browser.feature_engagement.TrackerFactory; |
| import org.chromium.chrome.browser.fullscreen.ChromeFullscreenManager; |
| import org.chromium.chrome.browser.fullscreen.ChromeFullscreenManager.FullscreenListener; |
| import org.chromium.chrome.browser.fullscreen.FullscreenManager; |
| import org.chromium.chrome.browser.profiles.Profile; |
| import org.chromium.chrome.browser.tabmodel.TabModelSelector; |
| import org.chromium.chrome.browser.toolbar.ToolbarManager; |
| import org.chromium.chrome.browser.toolbar.top.ToolbarPhone; |
| import org.chromium.chrome.browser.util.AccessibilityUtil; |
| import org.chromium.chrome.browser.widget.ListMenuButton; |
| import org.chromium.chrome.browser.widget.bottomsheet.BottomSheet; |
| import org.chromium.chrome.browser.widget.bottomsheet.BottomSheet.StateChangeReason; |
| import org.chromium.chrome.browser.widget.bottomsheet.BottomSheetObserver; |
| import org.chromium.chrome.browser.widget.bottomsheet.EmptyBottomSheetObserver; |
| import org.chromium.chrome.browser.widget.textbubble.ImageTextBubble; |
| import org.chromium.chrome.browser.widget.textbubble.TextBubble; |
| import org.chromium.components.feature_engagement.EventConstants; |
| import org.chromium.components.feature_engagement.FeatureConstants; |
| import org.chromium.components.feature_engagement.Tracker; |
| import org.chromium.content_public.browser.WebContents; |
| import org.chromium.ui.widget.ViewRectProvider; |
| |
| import java.util.Collections; |
| import java.util.List; |
| |
| import javax.inject.Inject; |
| import javax.inject.Named; |
| import javax.inject.Provider; |
| |
| /** |
| * A mediator for the contextual suggestions UI component responsible for interacting with |
| * the contextual suggestions C++ components (via a bridge), updating the model, and communicating |
| * with the component coordinator(s). |
| */ |
| @ActivityScope |
| class ContextualSuggestionsMediator |
| implements EnabledStateMonitor.Observer, FetchHelper.Delegate, ListMenuButton.Delegate { |
| @VisibleForTesting |
| static final String IPH_CONFIDENCE_THRESHOLD_PARAM = "iph_confidence_threshold"; |
| private static final int IPH_AUTO_DISMISS_TIMEOUT_MS = 6000; |
| private static final int IPH_AUTO_DISMISS_ACCESSIBILITY_TIMEOUT_MS = 10000; |
| private static boolean sOverrideIPHTimeoutForTesting; |
| |
| private final Profile mProfile; |
| private final TabModelSelector mTabModelSelector; |
| private final ContextualSuggestionsModel mModel; |
| private final ChromeFullscreenManager mFullscreenManager; |
| private final ToolbarManager mToolbarManager; |
| private final EnabledStateMonitor mEnabledStateMonitor; |
| private final Handler mHandler = new Handler(); |
| private final Provider<ContextualSuggestionsSource> mSuggestionSourceProvider; |
| private @Nullable final OverviewModeBehavior mOverviewModeBehavior; |
| private final boolean mRequireReverseScrollForIPH; |
| |
| private ContextualSuggestionsCoordinator mCoordinator; |
| |
| private @Nullable ContextualSuggestionsSource mSuggestionsSource; |
| private @Nullable FetchHelper mFetchHelper; |
| private @Nullable String mCurrentRequestUrl; |
| private @Nullable BottomSheetObserver mSheetObserver; |
| private @Nullable TextBubble mHelpBubble; |
| |
| private boolean mModelPreparedForCurrentTab; |
| private boolean mSuggestionsSetOnBottomSheet; |
| private boolean mHasRecordedButtonShownForTab; |
| |
| /** |
| * Whether the browser controls have fully hidden at least once since the last time |
| * #clearSuggestions() was called. This is used as a proxy for whether the user has scrolled |
| * down on the current page. |
| */ |
| private boolean mHaveBrowserControlsFullyHidden; |
| private int mFullscreenToken = FullscreenManager.INVALID_TOKEN; |
| |
| private boolean mHasPeekDelayPassed; |
| |
| /** Whether the content sheet is observed to be opened for the first time. */ |
| private boolean mHasSheetBeenOpened; |
| |
| /** |
| * Whether in-product help may be shown. This is set to false if the IPH system indicates that |
| * it wouldn't trigger our IPH if requested and when we attempt to show the IPH bubble. |
| */ |
| private boolean mCanShowIph; |
| |
| /** |
| * Whether the current results are eligible for IPH based on the confidence of the results and |
| * the confidence threshold for showing IPH. Should be used in combination with other criteria |
| * e.g. {@link #mCanShowIph} to determine whether to request to show IPH. |
| */ |
| private boolean mCanShowIphForCurrentResults; |
| |
| /** |
| * Construct a new {@link ContextualSuggestionsMediator}. |
| * @param profile The last used {@link Profile}. |
| * @param tabModelSelector The {@link TabModelSelector} for the containing activity. |
| * @param fullscreenManager The {@link ChromeFullscreenManager} to listen for browser controls |
| * events. |
| * @param model The {@link ContextualSuggestionsModel} for the component. |
| * @param toolbarManager The {@link ToolbarManager} for the containing activity. |
| * @param layoutManager The {@link LayoutManager} used to retrieve the |
| * {@link OverviewModeBehavior} if it exists. |
| * @param enabledStateMonitor The state monitor that will alert the mediator if the enabled |
| * state for contextual suggestions changes. |
| * @param suggestionSourceProvider The provider of {@link ContextualSuggestionsSource} |
| * instances. |
| */ |
| @Inject |
| ContextualSuggestionsMediator(@Named(LAST_USED_PROFILE) Profile profile, |
| TabModelSelector tabModelSelector, ChromeFullscreenManager fullscreenManager, |
| ContextualSuggestionsModel model, ToolbarManager toolbarManager, |
| LayoutManager layoutManager, EnabledStateMonitor enabledStateMonitor, |
| Provider<ContextualSuggestionsSource> suggestionSourceProvider) { |
| mProfile = profile.getOriginalProfile(); |
| mTabModelSelector = tabModelSelector; |
| mModel = model; |
| mFullscreenManager = fullscreenManager; |
| |
| mToolbarManager = toolbarManager; |
| mSuggestionSourceProvider = suggestionSourceProvider; |
| |
| mEnabledStateMonitor = enabledStateMonitor; |
| mEnabledStateMonitor.addObserver(this); |
| if (mEnabledStateMonitor.getEnabledState()) { |
| enable(); |
| } |
| |
| mFullscreenManager.addListener(new FullscreenListener() { |
| @Override |
| public void onContentOffsetChanged(int offset) {} |
| |
| @Override |
| public void onControlsOffsetChanged( |
| int topOffset, int bottomOffset, boolean needsAnimate) { |
| if (!mHaveBrowserControlsFullyHidden) { |
| mHaveBrowserControlsFullyHidden = |
| mFullscreenManager.areBrowserControlsOffScreen(); |
| } else if (mCanShowIph && mCanShowIphForCurrentResults |
| && mRequireReverseScrollForIPH |
| && mFullscreenManager.areBrowserControlsFullyVisible()) { |
| mHandler.postDelayed(() -> maybeShowHelpBubble(), |
| ToolbarPhone.LOC_BAR_WIDTH_CHANGE_ANIMATION_DURATION_MS); |
| } |
| reportToolbarButtonShown(); |
| } |
| |
| @Override |
| public void onToggleOverlayVideoMode(boolean enabled) {} |
| |
| @Override |
| public void onBottomControlsHeightChanged(int bottomControlsHeight) {} |
| }); |
| |
| if (layoutManager instanceof LayoutManagerChrome) { |
| mOverviewModeBehavior = (LayoutManagerChrome) layoutManager; |
| mOverviewModeBehavior.addOverviewModeObserver(new EmptyOverviewModeObserver() { |
| @Override |
| public void onOverviewModeFinishedHiding() { |
| reportToolbarButtonShown(); |
| } |
| }); |
| } else { |
| mOverviewModeBehavior = null; |
| } |
| |
| mRequireReverseScrollForIPH = ChromeFeatureList.isEnabled( |
| ChromeFeatureList.CONTEXTUAL_SUGGESTIONS_IPH_REVERSE_SCROLL); |
| } |
| |
| /** |
| * Sets the {@link ContextualSuggestionsCoordinator} for bidirectional communication. |
| */ |
| void initialize(ContextualSuggestionsCoordinator coordinator) { |
| // TODO(pshmakov): get rid of this circular dependency by establishing an observer-observable |
| // relationship between Mediator and Coordinator; |
| mCoordinator = coordinator; |
| } |
| |
| /** Destroys the mediator. */ |
| void destroy() { |
| if (mFetchHelper != null) { |
| mFetchHelper.destroy(); |
| mFetchHelper = null; |
| } |
| |
| if (mSuggestionsSource != null) { |
| mSuggestionsSource.destroy(); |
| mSuggestionsSource = null; |
| } |
| |
| if (mHelpBubble != null) mHelpBubble.dismiss(); |
| |
| mEnabledStateMonitor.removeObserver(this); |
| } |
| |
| @Override |
| public void onEnabledStateChanged(boolean enabled) { |
| if (enabled) { |
| enable(); |
| } else { |
| disable(); |
| } |
| } |
| |
| private void enable() { |
| mSuggestionsSource = mSuggestionSourceProvider.get(); |
| mFetchHelper = new FetchHelper(this, mTabModelSelector); |
| |
| Tracker tracker = TrackerFactory.getTrackerForProfile(mProfile); |
| tracker.addOnInitializedCallback(success -> { |
| if (!success) return; |
| mCanShowIph = |
| tracker.wouldTriggerHelpUI(FeatureConstants.CONTEXTUAL_SUGGESTIONS_FEATURE); |
| }); |
| } |
| |
| private void disable() { |
| clearSuggestions(); |
| |
| if (mFetchHelper != null) { |
| mFetchHelper.destroy(); |
| mFetchHelper = null; |
| } |
| |
| if (mSuggestionsSource != null) { |
| mSuggestionsSource.destroy(); |
| mSuggestionsSource = null; |
| } |
| } |
| |
| @Override |
| public void onSettingsStateChanged(boolean enabled) {} |
| |
| @Override |
| public void requestSuggestions(String url) { |
| // Guard against null tabs when requesting suggestions. https://crbug.com/836672. |
| if (mTabModelSelector.getCurrentTab() == null |
| || mTabModelSelector.getCurrentTab().getWebContents() == null) { |
| assert false; |
| return; |
| } |
| |
| reportEvent(ContextualSuggestionsEvent.FETCH_REQUESTED); |
| mCurrentRequestUrl = url; |
| mSuggestionsSource.fetchSuggestions(url, (suggestionsResult) -> { |
| if (mTabModelSelector.getCurrentTab() == null |
| || mTabModelSelector.getCurrentTab().getWebContents() == null |
| || mSuggestionsSource == null) { |
| return; |
| } |
| assert mFetchHelper != null; |
| |
| // Avoiding double fetches causing suggestions for incorrect context. |
| if (!TextUtils.equals(url, mCurrentRequestUrl)) return; |
| |
| List<ContextualSuggestionsCluster> clusters = suggestionsResult.getClusters(); |
| |
| if (clusters.isEmpty() || clusters.get(0).getSuggestions().isEmpty()) return; |
| |
| int globalSuggestionsCount = 0; |
| for (ContextualSuggestionsCluster cluster : clusters) { |
| cluster.buildChildren(globalSuggestionsCount); |
| globalSuggestionsCount += cluster.getSuggestions().size(); |
| } |
| |
| prepareModel(clusters, suggestionsResult.getPeekText()); |
| |
| // By default, we will show IPH if results have been sent to the UI layer unless |
| // otherwise configured via a field trial. |
| mCanShowIphForCurrentResults = suggestionsResult.getPeekConditions().getConfidence() |
| >= (float) ChromeFeatureList.getFieldTrialParamByFeatureAsDouble( |
| ChromeFeatureList.CONTEXTUAL_SUGGESTIONS_BUTTON, |
| IPH_CONFIDENCE_THRESHOLD_PARAM, 0.d); |
| |
| mToolbarManager.enableExperimentalButton( |
| view -> onToolbarButtonClicked(), |
| R.drawable.contextual_suggestions, |
| R.string.contextual_suggestions_button_description); |
| RecordHistogram.recordBooleanHistogram( |
| "ContextualSuggestions.ResultsReturnedInOverviewMode", isOverviewModeVisible()); |
| reportToolbarButtonShown(); |
| }); |
| } |
| |
| private void onToolbarButtonClicked() { |
| if (mSuggestionsSetOnBottomSheet || !mModelPreparedForCurrentTab) return; |
| |
| maybeShowContentInSheet(); |
| RecordUserAction.record("ContextualSuggestions.ToolbarButtonClicked"); |
| mCoordinator.showSuggestions(mSuggestionsSource); |
| mCoordinator.expandBottomSheet(); |
| } |
| |
| // TODO(twellington): Use peek criteria to determine when to show toolbar button or remove |
| // entirely. |
| private void setPeekConditions(ContextualSuggestionsResult suggestionsResult) { |
| PeekConditions peekConditions = suggestionsResult.getPeekConditions(); |
| long remainingDelay = |
| mFetchHelper.getFetchTimeBaselineMillis(mTabModelSelector.getCurrentTab()) |
| + Math.round(peekConditions.getMinimumSecondsOnPage() * 1000) |
| - SystemClock.uptimeMillis(); |
| |
| if (remainingDelay <= 0) { |
| // Don't postDelayed if the minimum delay has passed so that the suggestions may |
| // be shown through the following call to show contents in the bottom sheet. |
| mHasPeekDelayPassed = true; |
| } else { |
| // Once delay expires, the bottom sheet can be peeked if the browser controls are |
| // already hidden, or the next time the browser controls are fully hidden and |
| // reshown. Note that this triggering on the latter case is handled by |
| // FullscreenListener#onControlsOffsetChanged() in this class. |
| mHandler.postDelayed(() -> { |
| mHasPeekDelayPassed = true; |
| maybeShowContentInSheet(); |
| }, remainingDelay); |
| } |
| } |
| |
| private void reportToolbarButtonShown() { |
| if (mHasRecordedButtonShownForTab || !mFullscreenManager.areBrowserControlsFullyVisible() |
| || isOverviewModeVisible() || mSuggestionsSource == null |
| || !mModel.hasSuggestions()) { |
| return; |
| } |
| |
| mHasRecordedButtonShownForTab = true; |
| reportEvent(ContextualSuggestionsEvent.UI_BUTTON_SHOWN); |
| TrackerFactory.getTrackerForProfile(mProfile).notifyEvent( |
| EventConstants.CONTEXTUAL_SUGGESTIONS_BUTTON_SHOWN); |
| if (mCanShowIph && mCanShowIphForCurrentResults && !mRequireReverseScrollForIPH) { |
| mHandler.postDelayed(() -> maybeShowHelpBubble(), |
| ToolbarPhone.LOC_BAR_WIDTH_CHANGE_ANIMATION_DURATION_MS); |
| } |
| } |
| |
| @Override |
| public void clearState() { |
| clearSuggestions(); |
| } |
| |
| @Override |
| public void reportFetchDelayed(WebContents webContents) { |
| if (mTabModelSelector.getCurrentTab() != null |
| && mTabModelSelector.getCurrentTab().getWebContents() == webContents) { |
| reportEvent(ContextualSuggestionsEvent.FETCH_DELAYED); |
| } |
| } |
| |
| // ListMenuButton.Delegate implementation. |
| @Override |
| public ListMenuButton.Item[] getItems() { |
| final Context context = ContextUtils.getApplicationContext(); |
| if (ChromeFeatureList.isEnabled(ChromeFeatureList.CONTEXTUAL_SUGGESTIONS_OPT_OUT)) { |
| return new ListMenuButton.Item[] { |
| new ListMenuButton.Item(context, R.string.menu_preferences, true), |
| new ListMenuButton.Item(context, R.string.menu_send_feedback, true)}; |
| } else { |
| return new ListMenuButton.Item[] { |
| new ListMenuButton.Item(context, R.string.menu_send_feedback, true)}; |
| } |
| } |
| |
| @Override |
| public void onItemSelected(ListMenuButton.Item item) { |
| if (item.getTextId() == R.string.menu_preferences) { |
| mCoordinator.showSettings(); |
| } else if (item.getTextId() == R.string.menu_send_feedback) { |
| mCoordinator.showFeedback(); |
| } else { |
| assert false : "Unhandled item detected."; |
| } |
| } |
| |
| private void removeSuggestionsFromSheet() { |
| if (mSheetObserver != null) { |
| mCoordinator.removeBottomSheetObserver(mSheetObserver); |
| mSheetObserver = null; |
| } |
| mCoordinator.removeSuggestions(); |
| |
| // Wait until suggestions are fully removed to reset {@code mSuggestionsSetOnBottomSheet}. |
| mCoordinator.addBottomSheetObserver(new EmptyBottomSheetObserver() { |
| @Override |
| public void onSheetContentChanged(@Nullable BottomSheet.BottomSheetContent newContent) { |
| if (!(newContent instanceof ContextualSuggestionsBottomSheetContent)) { |
| mSuggestionsSetOnBottomSheet = false; |
| mCoordinator.removeBottomSheetObserver(this); |
| } |
| } |
| }); |
| } |
| |
| /** |
| * Called when suggestions are cleared either due to the user explicitly dismissing |
| * suggestions via the close button or due to the FetchHelper signaling state should |
| * be cleared. |
| */ |
| private void clearSuggestions() { |
| mModelPreparedForCurrentTab = false; |
| |
| // Remove suggestions before clearing model state so that views don't respond to model |
| // changes while suggestions are hiding. See https://crbug.com/840579. |
| removeSuggestionsFromSheet(); |
| mToolbarManager.disableExperimentalButton(); |
| |
| mHasRecordedButtonShownForTab = false; |
| mHasSheetBeenOpened = false; |
| mHandler.removeCallbacksAndMessages(null); |
| mHasPeekDelayPassed = false; |
| mHaveBrowserControlsFullyHidden = false; |
| mModel.setClusterList(Collections.emptyList()); |
| mModel.setCloseButtonOnClickListener(null); |
| mModel.setMenuButtonDelegate(null); |
| mModel.setTitle(null); |
| mCurrentRequestUrl = ""; |
| |
| if (mSuggestionsSource != null) mSuggestionsSource.clearState(); |
| |
| if (mHelpBubble != null) mHelpBubble.dismiss(); |
| } |
| |
| private void prepareModel(List<ContextualSuggestionsCluster> clusters, String title) { |
| if (mSuggestionsSource == null) return; |
| |
| mModel.setClusterList(clusters); |
| mModel.setCloseButtonOnClickListener(view -> { |
| TrackerFactory.getTrackerForProfile(mProfile).notifyEvent( |
| EventConstants.CONTEXTUAL_SUGGESTIONS_DISMISSED); |
| @ContextualSuggestionsEvent |
| int openedEvent = |
| mHasSheetBeenOpened ? ContextualSuggestionsEvent.UI_DISMISSED_AFTER_OPEN |
| : ContextualSuggestionsEvent.UI_DISMISSED_WITHOUT_OPEN; |
| reportEvent(openedEvent); |
| removeSuggestionsFromSheet(); |
| |
| }); |
| mModel.setMenuButtonDelegate(this); |
| mModel.setTitle(!TextUtils.isEmpty(title) |
| ? title |
| : ContextUtils.getApplicationContext().getResources().getString( |
| R.string.contextual_suggestions_button_description)); |
| |
| mModelPreparedForCurrentTab = true; |
| } |
| |
| private void maybeShowContentInSheet() { |
| if (!mModel.hasSuggestions() || mSuggestionsSource == null) return; |
| |
| mSuggestionsSetOnBottomSheet = true; |
| |
| mSheetObserver = new EmptyBottomSheetObserver() { |
| @Override |
| public void onSheetOffsetChanged(float heightFraction, float offsetPx) { |
| if (mHelpBubble != null) mHelpBubble.dismiss(); |
| } |
| |
| @Override |
| public void onSheetOpened(@StateChangeReason int reason) { |
| if (!mHasSheetBeenOpened) { |
| mHasSheetBeenOpened = true; |
| TrackerFactory.getTrackerForProfile(mProfile).notifyEvent( |
| EventConstants.CONTEXTUAL_SUGGESTIONS_OPENED); |
| reportEvent(ContextualSuggestionsEvent.UI_OPENED); |
| } |
| } |
| |
| @Override |
| public void onSheetClosed(@StateChangeReason int reason) { |
| removeSuggestionsFromSheet(); |
| } |
| }; |
| |
| mCoordinator.addBottomSheetObserver(mSheetObserver); |
| mCoordinator.showContentInSheet(); |
| } |
| |
| private void maybeShowHelpBubble() { |
| View anchorView = mToolbarManager.getExperimentalButtonView(); |
| if (!mCanShowIph || !mCanShowIphForCurrentResults || mToolbarManager.isUrlBarFocused() |
| || anchorView == null || anchorView.getVisibility() != View.VISIBLE |
| || !mFullscreenManager.areBrowserControlsFullyVisible() |
| || mSuggestionsSource == null || !mModel.hasSuggestions()) { |
| return; |
| } |
| |
| // Either we'll fail to show or we'll successfully show. Either way, we can't show IPH |
| // after this attempt. |
| mCanShowIph = false; |
| Tracker tracker = TrackerFactory.getTrackerForProfile(mProfile); |
| if (!tracker.shouldTriggerHelpUI(FeatureConstants.CONTEXTUAL_SUGGESTIONS_FEATURE)) { |
| return; |
| } |
| |
| ViewRectProvider rectProvider = new ViewRectProvider(anchorView); |
| rectProvider.setInsetPx(0, 0, 0, |
| anchorView.getResources().getDimensionPixelOffset( |
| R.dimen.text_bubble_menu_anchor_y_inset)); |
| |
| mHelpBubble = new ImageTextBubble(anchorView.getContext(), anchorView, |
| R.string.contextual_suggestions_in_product_help, |
| R.string.contextual_suggestions_in_product_help_accessibility, true, rectProvider, |
| R.drawable.ic_logo_googleg_24dp); |
| |
| mHelpBubble.setDismissOnTouchInteraction(false); |
| if (!sOverrideIPHTimeoutForTesting) { |
| mHelpBubble.setAutoDismissTimeout(AccessibilityUtil.isAccessibilityEnabled() |
| ? IPH_AUTO_DISMISS_ACCESSIBILITY_TIMEOUT_MS |
| : IPH_AUTO_DISMISS_TIMEOUT_MS); |
| } |
| mHelpBubble.addOnDismissListener(() -> { |
| tracker.dismissed(FeatureConstants.CONTEXTUAL_SUGGESTIONS_FEATURE); |
| mFullscreenManager.getBrowserVisibilityDelegate().releasePersistentShowingToken( |
| mFullscreenToken); |
| mHelpBubble = null; |
| }); |
| mFullscreenToken = |
| mFullscreenManager.getBrowserVisibilityDelegate().showControlsPersistent(); |
| mHelpBubble.show(); |
| } |
| |
| private void reportEvent(@ContextualSuggestionsEvent int event) { |
| if (mTabModelSelector.getCurrentTab() == null |
| || mTabModelSelector.getCurrentTab().getWebContents() == null) { |
| // This method is not expected to be called if the current tab or webcontents are null. |
| // If this assert is hit, please alert someone on the Chrome Explore on Content team. |
| // See https://crbug.com/836672. |
| assert false; |
| return; |
| } |
| |
| mSuggestionsSource.reportEvent(mTabModelSelector.getCurrentTab().getWebContents(), event); |
| } |
| |
| private boolean isOverviewModeVisible() { |
| return mOverviewModeBehavior != null && mOverviewModeBehavior.overviewVisible(); |
| } |
| |
| @VisibleForTesting |
| void showContentInSheetForTesting(boolean disablePeekDelay) { |
| if (disablePeekDelay) mHasPeekDelayPassed = true; |
| maybeShowContentInSheet(); |
| } |
| |
| @VisibleForTesting |
| TextBubble getHelpBubbleForTesting() { |
| return mHelpBubble; |
| } |
| |
| @VisibleForTesting |
| static void setOverrideIPHTimeoutForTesting(boolean override) { |
| sOverrideIPHTimeoutForTesting = override; |
| } |
| |
| @VisibleForTesting |
| boolean getCanShowIphForCurrentResults() { |
| return mCanShowIphForCurrentResults; |
| } |
| } |