blob: 53a9dff32883004a80eb8e8f4eeda2b4488ebdad [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.compositor.bottombar.contextualsearch;
import android.app.Activity;
import android.content.Context;
import android.graphics.Rect;
import android.graphics.RectF;
import org.chromium.base.ActivityState;
import org.chromium.base.ApiCompatibilityUtils;
import org.chromium.base.VisibleForTesting;
import org.chromium.chrome.R;
import org.chromium.chrome.browser.compositor.LayerTitleCache;
import org.chromium.chrome.browser.compositor.bottombar.OverlayPanel;
import org.chromium.chrome.browser.compositor.bottombar.OverlayPanel.PanelProgressObserver;
import org.chromium.chrome.browser.compositor.bottombar.OverlayPanelContent;
import org.chromium.chrome.browser.compositor.bottombar.OverlayPanelManager;
import org.chromium.chrome.browser.compositor.bottombar.OverlayPanelManager.PanelPriority;
import org.chromium.chrome.browser.compositor.bottombar.contextualsearch.ContextualSearchPromoControl.ContextualSearchPromoHost;
import org.chromium.chrome.browser.compositor.layouts.LayoutUpdateHost;
import org.chromium.chrome.browser.compositor.scene_layer.ContextualSearchSceneLayer;
import org.chromium.chrome.browser.compositor.scene_layer.SceneOverlayLayer;
import org.chromium.chrome.browser.contextualsearch.ContextualSearchManagementDelegate;
import org.chromium.chrome.browser.profiles.Profile;
import org.chromium.chrome.browser.util.FeatureUtilities;
import org.chromium.chrome.browser.util.MathUtils;
import org.chromium.chrome.browser.widget.ScrimView;
import org.chromium.chrome.browser.widget.ScrimView.ScrimParams;
import org.chromium.ui.base.LocalizationUtils;
import org.chromium.ui.resources.ResourceManager;
/**
* Controls the Contextual Search Panel.
*/
public class ContextualSearchPanel extends OverlayPanel {
/** When using the Generic UX we never show the Arrow Icon */
private static final float ARROW_ICON_OPACITY_GENERIC_UX = 0.f;
/** When using the Generic UX we always show the Close Icon */
private static final float CLOSE_ICON_OPACITY_GENERIC_UX = 1.f;
/** Used for logging state changes. */
private final ContextualSearchPanelMetrics mPanelMetrics;
/** The height of the bar shadow, in pixels. */
private final float mBarShadowHeightPx;
/** The distance of the divider from the end of the bar, in dp. */
private final float mEndButtonWidthDp;
/** Whether the Panel should be promoted to a new tab after being maximized. */
private boolean mShouldPromoteToTabAfterMaximizing;
/** The object for handling global Contextual Search management duties */
private ContextualSearchManagementDelegate mManagementDelegate;
/** Whether the content view has been touched. */
private boolean mHasContentBeenTouched;
/** The compositor layer used for drawing the panel. */
private ContextualSearchSceneLayer mSceneLayer;
/**
* Whether to use the Generic Sheet UX.
* This activates the closebox in the peeking Bar, and may someday do more,
* e.g. swipe-closed behavior. See crbug.com/831783 for details.
*/
private boolean mUseGenericSheetUx;
/**
* A ScrimView for adjusting the Status Bar's brightness when a scrim is present (when the panel
* is open).
*/
private ScrimView mScrimView;
/**
* Params that configure our use of the ScrimView for adjusting the Status Bar's
* brightness when a scrim is present (when the panel is open).
*/
private ScrimParams mScrimParams;
// ============================================================================================
// Constructor
// ============================================================================================
/**
* @param context The current Android {@link Context}.
* @param updateHost The {@link LayoutUpdateHost} used to request updates in the Layout.
* @param panelManager The object managing the how different panels are shown.
*/
public ContextualSearchPanel(
Context context, LayoutUpdateHost updateHost, OverlayPanelManager panelManager) {
super(context, updateHost, panelManager);
mSceneLayer = createNewContextualSearchSceneLayer();
mPanelMetrics = new ContextualSearchPanelMetrics();
mBarShadowHeightPx =
ApiCompatibilityUtils
.getDrawable(mContext.getResources(), R.drawable.modern_toolbar_shadow)
.getIntrinsicHeight();
mEndButtonWidthDp = mPxToDp
* mContext.getResources().getDimensionPixelSize(
R.dimen.contextual_search_end_button_width);
}
@Override
protected void initializeUiState() {
mUseGenericSheetUx = mActivity.supportsContextualSuggestionsBottomSheet()
&& FeatureUtilities.areContextualSuggestionsEnabled(mActivity);
}
@Override
public OverlayPanelContent createNewOverlayPanelContent() {
return new OverlayPanelContent(mManagementDelegate.getOverlayContentDelegate(),
new PanelProgressObserver(), mActivity, getBarHeight());
}
// ============================================================================================
// Scene Overlay
// ============================================================================================
/**
* Create a new scene layer for this panel. This should be overridden by tests as necessary.
*/
protected ContextualSearchSceneLayer createNewContextualSearchSceneLayer() {
return new ContextualSearchSceneLayer(mContext.getResources().getDisplayMetrics().density);
}
@Override
public SceneOverlayLayer getUpdatedSceneOverlayTree(RectF viewport, RectF visibleViewport,
LayerTitleCache layerTitleCache, ResourceManager resourceManager, float yOffset) {
super.getUpdatedSceneOverlayTree(
viewport, visibleViewport, layerTitleCache, resourceManager, yOffset);
mSceneLayer.update(resourceManager, this, getSearchBarControl(), getBarBannerControl(),
getPromoControl(), getImageControl());
return mSceneLayer;
}
// ============================================================================================
// Contextual Search Manager Integration
// ============================================================================================
/**
* Sets the {@code ContextualSearchManagementDelegate} associated with this panel.
* @param delegate The {@code ContextualSearchManagementDelegate}.
*/
public void setManagementDelegate(ContextualSearchManagementDelegate delegate) {
if (mManagementDelegate != delegate) {
mManagementDelegate = delegate;
if (delegate != null) {
setChromeActivity(mManagementDelegate.getChromeActivity());
}
}
}
/**
* Notifies that the preference state has changed.
* @param isEnabled Whether the feature is enabled.
*/
public void onContextualSearchPrefChanged(boolean isEnabled) {
if (!isShowing()) return;
getPromoControl().onContextualSearchPrefChanged(isEnabled);
}
// ============================================================================================
// Panel State
// ============================================================================================
@Override
public void setPanelState(PanelState toState, @StateChangeReason int reason) {
PanelState fromState = getPanelState();
mPanelMetrics.onPanelStateChanged(
fromState, toState, reason, Profile.getLastUsedProfile().getOriginalProfile());
if (toState == PanelState.PEEKED
&& (fromState == PanelState.CLOSED || fromState == PanelState.UNDEFINED)) {
// If the Bar Banner is visible, it should animate when the SearchBar peeks.
if (getBarBannerControl().isVisible()) {
getBarBannerControl().animateAppearance();
}
}
if ((fromState == PanelState.UNDEFINED || fromState == PanelState.CLOSED)
&& toState == PanelState.PEEKED) {
mManagementDelegate.onPanelFinishedShowing();
}
super.setPanelState(toState, reason);
}
@Override
protected boolean isSupportedState(PanelState state) {
return canDisplayContentInPanel() || state != PanelState.MAXIMIZED;
}
@Override
protected float getExpandedHeight() {
if (canDisplayContentInPanel()) {
return super.getExpandedHeight();
} else {
return getBarHeightPeeking() + getPromoHeightPx() * mPxToDp;
}
}
@Override
protected PanelState getProjectedState(float velocity) {
PanelState projectedState = super.getProjectedState(velocity);
// Prevent the fling gesture from moving the Panel from PEEKED to MAXIMIZED. This is to
// make sure the Promo will be visible, considering that the EXPANDED state is the only
// one that will show the Promo.
if (getPromoControl().isVisible()
&& projectedState == PanelState.MAXIMIZED
&& getPanelState() == PanelState.PEEKED) {
projectedState = PanelState.EXPANDED;
}
// If we're swiping the panel down from MAXIMIZED skip the EXPANDED state and go all the
// way to PEEKED.
if (getPanelState() == PanelState.MAXIMIZED && projectedState == PanelState.EXPANDED) {
projectedState = PanelState.PEEKED;
}
return projectedState;
}
@Override
public boolean onBackPressed() {
if (!isShowing()) return false;
mManagementDelegate.hideContextualSearch(StateChangeReason.BACK_PRESS);
return true;
}
// ============================================================================================
// Contextual Search Manager Integration
// ============================================================================================
@Override
protected void onClosed(@StateChangeReason int reason) {
// Must be called before destroying Content because unseen visits should be removed from
// history, and if the Content gets destroyed there won't be a Webcontents to do that.
mManagementDelegate.onCloseContextualSearch(reason);
setProgressBarCompletion(0);
setProgressBarVisible(false);
getImageControl().hideCustomImage(false);
super.onClosed(reason);
if (mSceneLayer != null) mSceneLayer.hideTree();
if (mScrimView != null) mScrimView.hideScrim(false);
}
// ============================================================================================
// Generic Event Handling
// ============================================================================================
private boolean isCoordinateInsideActionTarget(float x) {
if (LocalizationUtils.isLayoutRtl()) {
return x >= getContentX() + mEndButtonWidthDp;
} else {
return x <= getContentX() + getWidth() - mEndButtonWidthDp;
}
}
/**
* Handles a bar click. The position is given in dp.
*/
@Override
public void handleBarClick(float x, float y) {
getSearchBarControl().onSearchBarClick(x);
if (isPeeking()) {
if (useGenericSheetUx() && isCoordinateInsideCloseButton(x)) {
closePanel(StateChangeReason.CLOSE_BUTTON, true);
} else if (getSearchBarControl().getQuickActionControl().hasQuickAction()
&& isCoordinateInsideActionTarget(x)) {
mPanelMetrics.setWasQuickActionClicked();
getSearchBarControl().getQuickActionControl().sendIntent(
mActivity.getActivityTab());
} else {
// super takes care of expanding the Panel when peeking.
super.handleBarClick(x, y);
}
} else if (isExpanded() || isMaximized()) {
if (isCoordinateInsideCloseButton(x)) {
closePanel(StateChangeReason.CLOSE_BUTTON, true);
} else if (canPromoteToNewTab()) {
mManagementDelegate.promoteToTab();
}
}
}
@Override
public boolean onInterceptBarClick() {
return onInterceptOpeningPanel();
}
@Override
public boolean onInterceptBarSwipe() {
return onInterceptOpeningPanel();
}
/**
* @return True if the event on the bar was intercepted.
*/
private boolean onInterceptOpeningPanel() {
if (mManagementDelegate.isRunningInCompatibilityMode()) {
mManagementDelegate.openResolvedSearchUrlInNewTab();
return true;
}
return false;
}
@Override
public void onShowPress(float x, float y) {
if (isCoordinateInsideBar(x, y)) getSearchBarControl().onShowPress(x);
super.onShowPress(x, y);
}
// ============================================================================================
// Panel base methods
// ============================================================================================
@Override
protected void destroyComponents() {
super.destroyComponents();
destroyPromoControl();
destroyBarBannerControl();
destroySearchBarControl();
}
@Override
public void onActivityStateChange(Activity activity, int newState) {
super.onActivityStateChange(activity, newState);
if (newState == ActivityState.PAUSED) {
mManagementDelegate.logCurrentState();
}
}
@Override
public @PanelPriority int getPriority() {
return PanelPriority.HIGH;
}
@Override
public boolean canBeSuppressed() {
// The selected text on the page is lost when the panel is closed, thus, this panel cannot
// be restored if it is suppressed.
return false;
}
@Override
public void notifyBarTouched(float x) {
if (canDisplayContentInPanel()) {
getOverlayPanelContent().showContent();
}
}
@Override
public float getContentY() {
return getOffsetY() + getBarContainerHeight() + getPromoHeightPx() * mPxToDp;
}
@Override
public float getBarContainerHeight() {
return getBarHeight() + getBarBannerControl().getHeightPx();
}
@Override
protected float getPeekedHeight() {
return getBarHeightPeeking() + getBarBannerControl().getHeightPeekingPx() * mPxToDp;
}
@Override
protected float calculateBarShadowOpacity() {
float barShadowOpacity = 0.f;
if (getPromoHeightPx() > 0.f) {
float threshold = 2 * mBarShadowHeightPx;
barShadowOpacity = getPromoHeightPx() > mBarShadowHeightPx ? 1.f
: MathUtils.interpolate(0.f, 1.f, getPromoHeightPx() / threshold);
}
return barShadowOpacity;
}
@Override
protected boolean doesMatchFullWidthCriteria(float containerWidth) {
if (!mOverrideIsFullWidthSizePanelForTesting && mActivity != null
&& mActivity.getBottomSheet() != null) {
return true;
}
return super.doesMatchFullWidthCriteria(containerWidth);
}
// ============================================================================================
// Animation Handling
// ============================================================================================
@Override
protected void onHeightAnimationFinished() {
super.onHeightAnimationFinished();
if (mShouldPromoteToTabAfterMaximizing && getPanelState() == PanelState.MAXIMIZED) {
mShouldPromoteToTabAfterMaximizing = false;
mManagementDelegate.promoteToTab();
}
}
// ============================================================================================
// Contextual Search Panel API
// ============================================================================================
/**
* Notify the panel that the content was seen.
*/
public void setWasSearchContentViewSeen() {
mPanelMetrics.setWasSearchContentViewSeen();
}
/**
* @param isActive Whether the promo is active.
*/
public void setIsPromoActive(boolean isActive, boolean isMandatory) {
if (isActive) {
getPromoControl().show(isMandatory);
} else {
getPromoControl().hide();
}
mPanelMetrics.setIsPromoActive(isActive);
}
/**
* Shows the Bar Banner.
*/
public void showBarBanner() {
getBarBannerControl().show();
}
/**
* Hides the Bar Banner.
*/
public void hideBarBanner() {
getBarBannerControl().hide();
}
/**
* @return Whether the Bar Banner is visible.
*/
@VisibleForTesting
public boolean isBarBannerVisible() {
return getBarBannerControl().isVisible();
}
/**
* Called after the panel has navigated to prefetched Search Results.
* If the user has the panel open then they will see the prefetched result starting to load.
* Currently this just logs the time between the start of the search until the results start to
* render in the Panel.
* @param didResolve Whether the search required the Search Term to be resolved.
*/
public void onPanelNavigatedToPrefetchedSearch(boolean didResolve) {
mPanelMetrics.onPanelNavigatedToPrefetchedSearch(didResolve);
}
/**
* Maximizes the Contextual Search Panel.
* @param reason The {@code StateChangeReason} behind the maximization.
*/
@Override
public void maximizePanel(@StateChangeReason int reason) {
mShouldPromoteToTabAfterMaximizing = false;
super.maximizePanel(reason);
}
/**
* Maximizes the Contextual Search Panel, then promotes it to a regular Tab.
* @param reason The {@code StateChangeReason} behind the maximization and promotion to tab.
*/
public void maximizePanelThenPromoteToTab(@StateChangeReason int reason) {
mShouldPromoteToTabAfterMaximizing = true;
super.maximizePanel(reason);
}
/**
* Maximizes the Contextual Search Panel, then promotes it to a regular Tab.
* @param reason The {@code StateChangeReason} behind the maximization and promotion to tab.
* @param duration The animation duration in milliseconds.
*/
public void maximizePanelThenPromoteToTab(@StateChangeReason int reason, long duration) {
mShouldPromoteToTabAfterMaximizing = true;
animatePanelToState(PanelState.MAXIMIZED, reason, duration);
}
@Override
public void peekPanel(@StateChangeReason int reason) {
super.peekPanel(reason);
if (getPanelState() == PanelState.CLOSED || getPanelState() == PanelState.PEEKED) {
mHasContentBeenTouched = false;
}
if ((getPanelState() == PanelState.UNDEFINED || getPanelState() == PanelState.CLOSED)
&& reason == StateChangeReason.TEXT_SELECT_TAP) {
mPanelMetrics.onPanelTriggeredFromTap();
}
}
@Override
public void closePanel(@StateChangeReason int reason, boolean animate) {
super.closePanel(reason, animate);
mHasContentBeenTouched = false;
}
@Override
public void expandPanel(@StateChangeReason int reason) {
super.expandPanel(reason);
}
@Override
public PanelState getPanelState() {
// NOTE(pedrosimonetti): exposing superclass method to the interface.
return super.getPanelState();
}
@Override
public void requestPanelShow(@StateChangeReason int reason) {
// If a re-tap is causing the panel to show when already shown, the superclass may ignore
// that, but we want to be sure to capture search metrics for each tap.
if (isShowing() && getPanelState() == PanelState.PEEKED) {
peekPanel(reason);
}
super.requestPanelShow(reason);
}
/**
* Gets whether a touch on the content view has been done yet or not.
*/
public boolean didTouchContent() {
return mHasContentBeenTouched;
}
/**
* Sets the search term to display in the SearchBar.
* This should be called when the search term is set without search term resolution, or
* after search term resolution completed.
* @param searchTerm The string that represents the search term.
*/
public void setSearchTerm(String searchTerm) {
getImageControl().hideCustomImage(true);
getSearchBarControl().setSearchTerm(searchTerm);
mPanelMetrics.onSearchRequestStarted();
// Make sure the new Search Term draws.
requestUpdate();
}
/**
* Sets the search context details to display in the SearchBar.
* @param selection The portion of the context that represents the user's selection.
* @param end The portion of the context from the selection to its end.
*/
public void setContextDetails(String selection, String end) {
getImageControl().hideCustomImage(true);
getSearchBarControl().setContextDetails(selection, end);
mPanelMetrics.onSearchRequestStarted();
// Make sure the new Context draws.
requestUpdate();
}
/**
* Sets the caption to display in the SearchBar.
* When the caption is displayed, the Search Term is pushed up and the caption shows below.
* @param caption The string to show in as the caption.
*/
public void setCaption(String caption) {
getSearchBarControl().setCaption(caption);
}
/**
* Handles showing the resolved search term in the SearchBar.
* @param searchTerm The string that represents the search term.
* @param thumbnailUrl The URL of the thumbnail to display.
* @param quickActionUri The URI for the intent associated with the quick action.
* @param quickActionCategory The {@code QuickActionCategory} for the quick action.
*/
public void onSearchTermResolved(String searchTerm, String thumbnailUrl, String quickActionUri,
int quickActionCategory) {
mPanelMetrics.onSearchTermResolved();
getSearchBarControl().setSearchTerm(searchTerm);
getSearchBarControl().animateSearchTermResolution();
if (mActivity == null || mActivity.getToolbarManager() == null) return;
getSearchBarControl().setQuickAction(quickActionUri, quickActionCategory,
mActivity.getToolbarManager().getPrimaryColor());
getImageControl().setThumbnailUrl(thumbnailUrl);
}
/**
* Calculates the position of the Contextual Search panel in the screen.
* @return A {@link Rect} object that represents the Contextual Search panel's position in
* the screen, in pixels.
*/
public Rect getPanelRect() {
int[] contentLocationInWindow = new int[2];
mActivity.findViewById(android.R.id.content).getLocationInWindow(contentLocationInWindow);
int leftPadding = contentLocationInWindow[0];
int topPadding = contentLocationInWindow[1];
// getOffsetX() and getOffsetY() return the position of the panel relative to the activity,
// therefore leftPadding and topPadding are added to get the position in the screen.
int left = (int) (getOffsetX() / mPxToDp) + leftPadding;
int top = (int) (getOffsetY() / mPxToDp) + topPadding;
int bottom = top + (int) (getBarHeight() / mPxToDp);
int right = left + (int) (getWidth() / mPxToDp);
return new Rect(left, top, right, bottom);
}
// ============================================================================================
// Panel Metrics
// ============================================================================================
// TODO(pedrosimonetti): replace proxy methods with direct PanelMetrics usage
/**
* @return The {@link ContextualSearchPanelMetrics}.
*/
public ContextualSearchPanelMetrics getPanelMetrics() {
return mPanelMetrics;
}
/**
* Sets that the contextual search involved the promo.
*/
public void setDidSearchInvolvePromo() {
mPanelMetrics.setDidSearchInvolvePromo();
}
// ============================================================================================
// Panel Rendering
// ============================================================================================
// TODO(pedrosimonetti): generalize the dispatching of panel updates.
@Override
protected void updatePanelForCloseOrPeek(float percentage) {
super.updatePanelForCloseOrPeek(percentage);
getPromoControl().onUpdateFromCloseToPeek(percentage);
getBarBannerControl().onUpdateFromCloseToPeek(percentage);
getSearchBarControl().onUpdateFromCloseToPeek(percentage);
}
@Override
protected void updatePanelForExpansion(float percentage) {
super.updatePanelForExpansion(percentage);
getPromoControl().onUpdateFromPeekToExpand(percentage);
getBarBannerControl().onUpdateFromPeekToExpand(percentage);
getSearchBarControl().onUpdateFromPeekToExpand(percentage);
}
@Override
protected void updatePanelForMaximization(float percentage) {
super.updatePanelForMaximization(percentage);
getPromoControl().onUpdateFromExpandToMaximize(percentage);
getBarBannerControl().onUpdateFromExpandToMaximize(percentage);
}
@Override
protected void updatePanelForSizeChange() {
if (getPromoControl().isVisible()) {
getPromoControl().invalidate(true);
}
if (getBarBannerControl().isVisible()) {
getBarBannerControl().onResized(this);
}
// NOTE(pedrosimonetti): We cannot tell where the selection will be after the
// orientation change, so we are setting the selection position to zero, which
// means the base page will be positioned in its original state and we won't
// try to keep the selection in view.
updateBasePageSelectionYPx(0.f);
updateBasePageTargetY();
super.updatePanelForSizeChange();
mManagementDelegate.onPanelResized();
}
@Override
protected void updateStatusBar() {
float maxBrightness = getMaxBasePageBrightness();
float minBrightness = getMinBasePageBrightness();
float basePageBrightness = getBasePageBrightness();
// Compute Status Bar alpha based on the base-page brightness range applied by the Overlay.
// TODO(donnd): Create a full-screen sized view and apply the black_alpha_65 color to get
// an exact match between the scrim and the status bar colors instead of adjusting the
// status bar alpha to approximate the native overlay brightness filter.
// Details in https://crbug.com/848922.
float statusBarAlpha =
(maxBrightness - basePageBrightness) / (maxBrightness - minBrightness);
if (statusBarAlpha == 0.0) {
if (mScrimView != null) mScrimView.hideScrim(false);
mScrimParams = null;
mScrimView = null;
return;
} else {
mScrimView = mManagementDelegate.getChromeActivity().getScrim();
if (mScrimParams == null) {
mScrimParams = new ScrimParams(null, false, true, 0, null);
mScrimView.showScrim(mScrimParams);
}
mScrimView.setViewAlpha(statusBarAlpha);
}
}
@Override
public float getArrowIconOpacity() {
if (useGenericSheetUx()) {
return ARROW_ICON_OPACITY_GENERIC_UX;
} else {
return super.getArrowIconOpacity();
}
}
@Override
public float getCloseIconOpacity() {
if (useGenericSheetUx()) {
return CLOSE_ICON_OPACITY_GENERIC_UX;
} else {
return super.getCloseIconOpacity();
}
}
/**
* Whether the UX should match the generic sheet UX used by the generic assistive surface.
* TODO(crbug.com/831783) remove when the generic sheet UX is the default.
* @return Whether to apply the generic UX, rather than the legacy Contextual Search UX.
*/
boolean useGenericSheetUx() {
return mUseGenericSheetUx;
}
// ============================================================================================
// Selection position
// ============================================================================================
/** The approximate Y coordinate of the selection in pixels. */
private float mBasePageSelectionYPx = -1.f;
/**
* Updates the coordinate of the existing selection.
* @param y The y coordinate of the selection in pixels.
*/
public void updateBasePageSelectionYPx(float y) {
mBasePageSelectionYPx = y;
}
@Override
protected float calculateBasePageDesiredOffset() {
float offset = 0.f;
if (mBasePageSelectionYPx > 0.f) {
// Convert from px to dp.
final float selectionY = mBasePageSelectionYPx * mPxToDp;
// Calculate the offset to center the selection on the available area.
final float availableHeight = getTabHeight() - getExpandedHeight();
offset = -selectionY + availableHeight / 2;
}
return offset;
}
// ============================================================================================
// ContextualSearchBarControl
// ============================================================================================
private ContextualSearchBarControl mSearchBarControl;
/**
* Creates the ContextualSearchBarControl, if needed. The Views are set to INVISIBLE, because
* they won't actually be displayed on the screen (their snapshots will be displayed instead).
*/
public ContextualSearchBarControl getSearchBarControl() {
if (mSearchBarControl == null) {
mSearchBarControl =
new ContextualSearchBarControl(this, mContext, mContainerView, mResourceLoader);
}
return mSearchBarControl;
}
/**
* Destroys the ContextualSearchBarControl.
*/
protected void destroySearchBarControl() {
if (mSearchBarControl != null) {
mSearchBarControl.destroy();
mSearchBarControl = null;
}
}
// ============================================================================================
// Image Control
// ============================================================================================
/**
* @return The {@link ContextualSearchImageControl} for the panel.
*/
public ContextualSearchImageControl getImageControl() {
return getSearchBarControl().getImageControl();
}
// ============================================================================================
// Bar Banner
// ============================================================================================
private ContextualSearchBarBannerControl mBarBannerControl;
/**
* Creates the ContextualSearchBarBannerControl, if needed.
*/
private ContextualSearchBarBannerControl getBarBannerControl() {
if (mBarBannerControl == null) {
mBarBannerControl = new ContextualSearchBarBannerControl(
this, mContext, mContainerView, mResourceLoader);
}
return mBarBannerControl;
}
/**
* Destroys the ContextualSearchBarBannerControl.
*/
private void destroyBarBannerControl() {
if (mBarBannerControl != null) {
mBarBannerControl.destroy();
mBarBannerControl = null;
}
}
// ============================================================================================
// Promo
// ============================================================================================
private ContextualSearchPromoControl mPromoControl;
private ContextualSearchPromoHost mPromoHost;
/**
* @return Whether the Promo reached a state in which it could be interacted.
*/
public boolean wasPromoInteractive() {
return getPromoControl().wasInteractive();
}
/**
* @return Height of the promo in pixels.
*/
private float getPromoHeightPx() {
return getPromoControl().getHeightPx();
}
/**
* Creates the ContextualSearchPromoControl, if needed.
*/
private ContextualSearchPromoControl getPromoControl() {
if (mPromoControl == null) {
mPromoControl =
new ContextualSearchPromoControl(this, getContextualSearchPromoHost(),
mContext, mContainerView, mResourceLoader);
}
return mPromoControl;
}
/**
* Destroys the ContextualSearchPromoControl.
*/
private void destroyPromoControl() {
if (mPromoControl != null) {
mPromoControl.destroy();
mPromoControl = null;
}
}
/**
* @return An implementation of {@link ContextualSearchPromoHost}.
*/
private ContextualSearchPromoHost getContextualSearchPromoHost() {
if (mPromoHost == null) {
mPromoHost = new ContextualSearchPromoHost() {
@Override
public void onPromoOptIn(boolean wasMandatory) {
if (wasMandatory) {
getOverlayPanelContent().showContent();
expandPanel(StateChangeReason.OPTIN);
}
}
@Override
public void onPromoOptOut() {
closePanel(OverlayPanel.StateChangeReason.OPTOUT, true);
}
@Override
public void onUpdatePromoAppearance() {
ContextualSearchPanel.this.updateBarShadow();
}
};
}
return mPromoHost;
}
// ============================================================================================
// Panel Content
// ============================================================================================
/**
* @return Whether the content can be displayed in the panel.
*/
public boolean canDisplayContentInPanel() {
// TODO(pedrosimonetti): add svelte support.
return !getPromoControl().isMandatory();
}
@Override
public void onTouchSearchContentViewAck() {
mHasContentBeenTouched = true;
}
/**
* Destroy the current content in the panel.
* NOTE(mdjones): This should not be exposed. The only use is in ContextualSearchManager for a
* bug related to loading new panel content.
*/
public void destroyContent() {
super.destroyOverlayPanelContent();
}
/**
* @return Whether the panel content can be displayed in a new tab.
*/
boolean canPromoteToNewTab() {
return !mActivity.isCustomTab() && canDisplayContentInPanel();
}
// ============================================================================================
// Testing Support
// ============================================================================================
/**
* Simulates a tap on the panel's end button.
*/
@VisibleForTesting
public void simulateTapOnEndButton() {
endHeightAnimation();
// Determine the x-position for the simulated tap.
float xPosition;
if (LocalizationUtils.isLayoutRtl()) {
xPosition = getContentX() + (mEndButtonWidthDp / 2);
} else {
xPosition = getContentX() + getWidth() - (mEndButtonWidthDp / 2);
}
// Determine the y-position for the simulated tap.
float yPosition = getOffsetY() + (getHeight() / 2);
// Simulate the tap.
handleClick(xPosition, yPosition);
}
}