From 93c2ea2cf63fdcff8c85c3ca2e68373be17c1bbb Mon Sep 17 00:00:00 2001 From: Aurimas Liutikas Date: Mon, 18 May 2015 13:01:12 -0700 Subject: [PATCH] Revert of [Contextual Search] Remove opt-in code. (patchset #6 id:100001 of https://codereview.chromium.org/1103163004/) Reason for revert: It broke Clank bots. Original issue's description: > [Contextual Search] Remove opt-in code. > > This CL requires a downstream change: > https://chrome-internal-review.googlesource.com/#/c/214963/ > > BUG=482618 > R=donnd@chromium.org, dtrainor@chromium.org, thakis@chromium.org > > Committed: https://chromium.googlesource.com/chromium/src/+/277f043bb56fb1e4bb93d9d17fc3de4ada0eb81b TBR=donnd@chromium.org,mathp@chromium.org,dtrainor@chromium.org,thakis@chromium.org NOPRESUBMIT=true NOTREECHECKS=true NOTRY=true BUG=482618 Review URL: https://codereview.chromium.org/1123313006 Cr-Commit-Position: refs/heads/master@{#330248} Review URL: https://codereview.chromium.org/1133403004 Cr-Commit-Position: refs/branch-heads/2403@{#9} Cr-Branched-From: f54b8097a9c45ed4ad308133d49f05325d6c5070-refs/heads/master@{#330231} --- .../chrome/browser/ChromeSwitches.java | 4 + .../ContextualSearchControl.java | 17 ++ .../ContextualSearchPanel.java | 36 ++- .../ContextualSearchPanelAnimation.java | 22 +- .../ContextualSearchPanelBase.java | 245 ++++++++++++++++-- .../ContextualSearchPanelDelegate.java | 17 ++ .../ContextualSearchPanelStateHandler.java | 45 +++- .../ContextualSearchManagementDelegate.java | 1 - .../contextualsearch/ContextualSearchUma.java | 21 ++ chrome/browser/browser_resources.grd | 8 + .../resources/contextual_search/OWNERS | 1 + .../resources/contextual_search/header.svg | 25 ++ .../resources/contextual_search/promo.css | 162 ++++++++++++ .../resources/contextual_search/promo.html | 37 +++ .../resources/contextual_search/promo.js | 58 +++++ .../contextual_search_promo_source_android.cc | 149 +++++++++++ .../contextual_search_promo_source_android.h | 47 ++++ chrome/chrome_browser.gypi | 2 + 18 files changed, 856 insertions(+), 41 deletions(-) create mode 100644 chrome/browser/resources/contextual_search/OWNERS create mode 100644 chrome/browser/resources/contextual_search/header.svg create mode 100644 chrome/browser/resources/contextual_search/promo.css create mode 100644 chrome/browser/resources/contextual_search/promo.html create mode 100644 chrome/browser/resources/contextual_search/promo.js create mode 100644 chrome/browser/search/contextual_search_promo_source_android.cc create mode 100644 chrome/browser/search/contextual_search_promo_source_android.h diff --git a/chrome/android/java/src/org/chromium/chrome/browser/ChromeSwitches.java b/chrome/android/java/src/org/chromium/chrome/browser/ChromeSwitches.java index b25d35d786d63..6a24cf2fc8218 100644 --- a/chrome/android/java/src/org/chromium/chrome/browser/ChromeSwitches.java +++ b/chrome/android/java/src/org/chromium/chrome/browser/ChromeSwitches.java @@ -69,6 +69,10 @@ public abstract class ChromeSwitches { /** Enable Contextual Search. */ public static final String ENABLE_CONTEXTUAL_SEARCH = "enable-contextual-search"; + /** Disable Contextual Search first-run flow, for testing. Not exposed to user. */ + public static final String DISABLE_CONTEXTUAL_SEARCH_PROMO_FOR_TESTING = + "disable-contextual-search-promo-for-testing"; + /** Enable Contextual Search for instrumentation testing. Not exposed to user. */ public static final String ENABLE_CONTEXTUAL_SEARCH_FOR_TESTING = "enable-contextual-search-for-testing"; diff --git a/chrome/android/java/src/org/chromium/chrome/browser/compositor/bottombar/contextualsearch/ContextualSearchControl.java b/chrome/android/java/src/org/chromium/chrome/browser/compositor/bottombar/contextualsearch/ContextualSearchControl.java index f6206a891c45b..17853b0110ca6 100644 --- a/chrome/android/java/src/org/chromium/chrome/browser/compositor/bottombar/contextualsearch/ContextualSearchControl.java +++ b/chrome/android/java/src/org/chromium/chrome/browser/compositor/bottombar/contextualsearch/ContextualSearchControl.java @@ -81,6 +81,23 @@ public ViewParent invalidateChildInParent(int[] location, Rect dirty) { return parent; } + /** + * Sets the text to display on top of the first-run promo. + * @param selection The portion of the text that represents the user's selection. + */ + public void setFirstRunText(String selection) { + // TODO(pedrosimonetti): confirm that is okay to remove the experimental text +// String firstRunText = ContextualSearchFieldTrial.getEnglishExperimentFirstRunText( +// selection); +// if (firstRunText == null) { +// firstRunText = +// getContext().getString(R.string.contextual_search_action_bar, selection); +// } + String firstRunText = + getContext().getString(R.string.contextual_search_action_bar, selection); + setCentralText(firstRunText); + } + /** * Sets the search context to display in the control. * @param selection The portion of the context that represents the user's selection. diff --git a/chrome/android/java/src/org/chromium/chrome/browser/compositor/bottombar/contextualsearch/ContextualSearchPanel.java b/chrome/android/java/src/org/chromium/chrome/browser/compositor/bottombar/contextualsearch/ContextualSearchPanel.java index eab9307b34348..d6b0b12befc22 100644 --- a/chrome/android/java/src/org/chromium/chrome/browser/compositor/bottombar/contextualsearch/ContextualSearchPanel.java +++ b/chrome/android/java/src/org/chromium/chrome/browser/compositor/bottombar/contextualsearch/ContextualSearchPanel.java @@ -23,6 +23,7 @@ public static enum PanelState { UNDEFINED, CLOSED, PEEKED, + PROMO, EXPANDED, MAXIMIZED; } @@ -145,7 +146,7 @@ public void setPreferenceState(boolean enabled) { } @Override - protected boolean isPromoAvailable() { + protected boolean isPanelPromoAvailable() { return mManagementDelegate != null && mManagementDelegate.isOptOutPromoAvailable(); } @@ -153,7 +154,6 @@ protected boolean isPromoAvailable() { public void onPromoButtonClick(boolean accepted) { super.onPromoButtonClick(accepted); mManagementDelegate.logPromoOutcome(); - setIsPromoActive(false); } @Override @@ -236,7 +236,19 @@ public void handleClick(long time, float x, float y) { if (mManagementDelegate.isRunningInCompatibilityMode()) { mManagementDelegate.openResolvedSearchUrlInNewTab(); } else { - expandPanel(StateChangeReason.SEARCH_BAR_TAP); + // NOTE(pedrosimonetti): If the promo is active and getPromoContentHeight() + // returns -1 that means that the promo page hasn't finished loading, and + // therefore it wasn't possible to calculate the height of the promo contents. + // This will only happen if the user taps on a word that will trigger the + // promo, and then quickly taps on the peeking bar, before the promo page + // (which is local) finishes loading. + // + // TODO(pedrosimonetti): For now, we're simply ignoring the tap action in + // that case. Consider implementing a better approach, where the Panel + // would auto-expand once the height is calculated. + if (!getIsPromoActive() || getPromoContentHeight() != -1) { + expandPanel(StateChangeReason.SEARCH_BAR_TAP); + } } } else if (isExpanded()) { peekPanel(StateChangeReason.SEARCH_BAR_TAP); @@ -385,6 +397,24 @@ public void updateBasePageSelectionYPx(float y) { super.updateBasePageSelectionYPx(y); } + @Override + public void setPromoContentHeight(float height) { + // NOTE(pedrosimonetti): exposing superclass method to the interface. + super.setPromoContentHeight(height); + } + + @Override + public void setShouldHidePromoHeader(boolean shouldHidePromoHeader) { + // NOTE(pedrosimonetti): exposing superclass method to the interface. + super.setShouldHidePromoHeader(shouldHidePromoHeader); + } + + @Override + public void animateAfterFirstRunSuccess() { + // NOTE(pedrosimonetti): exposing superclass method to the interface. + super.animateAfterFirstRunSuccess(); + } + @Override public void onLoadStarted() { setProgressBarCompletion(0); diff --git a/chrome/android/java/src/org/chromium/chrome/browser/compositor/bottombar/contextualsearch/ContextualSearchPanelAnimation.java b/chrome/android/java/src/org/chromium/chrome/browser/compositor/bottombar/contextualsearch/ContextualSearchPanelAnimation.java index 27fb02d8864b9..9172d758c9faa 100644 --- a/chrome/android/java/src/org/chromium/chrome/browser/compositor/bottombar/contextualsearch/ContextualSearchPanelAnimation.java +++ b/chrome/android/java/src/org/chromium/chrome/browser/compositor/bottombar/contextualsearch/ContextualSearchPanelAnimation.java @@ -28,7 +28,8 @@ abstract class ContextualSearchPanelAnimation extends ContextualSearchPanelBase */ protected enum Property { PANEL_HEIGHT, - PROMO_VISIBILITY + PROMO_VISIBILITY, + FIRST_RUN_PANEL_HEIGHT, } /** @@ -109,7 +110,7 @@ protected void maximizePanel(StateChangeReason reason) { * @param reason The reason for the change of panel state. */ protected void expandPanel(StateChangeReason reason) { - animatePanelToState(PanelState.EXPANDED, reason); + animatePanelToState(getIntermediaryState(), reason); } /** @@ -192,6 +193,19 @@ protected void animatePromoAcceptance() { animateProperty(Property.PROMO_VISIBILITY, 1.f, 0.f, BASE_ANIMATION_DURATION_MS); } + /** + * Animates the Contextual Search panel after first-run success. + */ + protected void animateAfterFirstRunSuccess() { + final PanelState desiredState = PanelState.EXPANDED; + mAnimatingState = desiredState; + mAnimatingStateReason = StateChangeReason.OPTIN; + + final float desiredHeight = getPanelHeightFromState(desiredState); + animateProperty(Property.FIRST_RUN_PANEL_HEIGHT, getHeight(), desiredHeight, + BASE_ANIMATION_DURATION_MS); + } + /** * Animates the Panel to its nearest state. */ @@ -225,7 +239,7 @@ protected void animateToProjectedState(float velocity) { // the EXPANDED state is the only one that will show the Promo. if (projectedState == PanelState.MAXIMIZED && getPanelState() == PanelState.PEEKED - && isPromoAvailable()) { + && isPanelPromoAvailable()) { projectedState = PanelState.EXPANDED; } @@ -341,6 +355,8 @@ public void setProperty(Property prop, float value) { setPanelHeight(value); } else if (prop == Property.PROMO_VISIBILITY) { setPromoVisibilityForOptInAnimation(value); + } else if (prop == Property.FIRST_RUN_PANEL_HEIGHT) { + setPanelHeightForPromoOptInAnimation(value); } } diff --git a/chrome/android/java/src/org/chromium/chrome/browser/compositor/bottombar/contextualsearch/ContextualSearchPanelBase.java b/chrome/android/java/src/org/chromium/chrome/browser/compositor/bottombar/contextualsearch/ContextualSearchPanelBase.java index 93a70054e7c78..db12da124e2c9 100644 --- a/chrome/android/java/src/org/chromium/chrome/browser/compositor/bottombar/contextualsearch/ContextualSearchPanelBase.java +++ b/chrome/android/java/src/org/chromium/chrome/browser/compositor/bottombar/contextualsearch/ContextualSearchPanelBase.java @@ -97,6 +97,17 @@ abstract class ContextualSearchPanelBase extends ContextualSearchPanelStateHandl */ private static final float SEARCH_ICON_PADDING_LEFT_DP = 16.f; + /** + * The height of the promo state Contextual Search Panel search bar, in dps. + */ + private static final float SEARCH_BAR_HEIGHT_STATE_PROMO = 24.f; + + /** + * How much the Promo Panel should displace in order to make some elements + * invisible (like the Search Provider Icon and the Search Bar Text). + */ + private static final float DISAPPEARING_ELEMENT_THRESHOLD_DP = 10.f; + /** * The height of the Search Bar's border in dps. */ @@ -121,6 +132,11 @@ abstract class ContextualSearchPanelBase extends ContextualSearchPanelStateHandl */ private final float mSearchBarHeightPeeking; + /** + * The height of the Search Bar when the Panel is displaying the Promo, in dps. + */ + private final float mSearchBarHeightPromo; + /** * The height of the Search Bar when the Panel is expanded, in dps. */ @@ -147,6 +163,12 @@ abstract class ContextualSearchPanelBase extends ContextualSearchPanelStateHandl */ private float mBasePageTargetY = 0.f; + /** + * The Y coordinate to apply to the Base Page in order to keep the selection + * in view when the Search Panel is in its PROMO state. + */ + private float mPromoBasePageTargetY = 0.f; + /** * Whether the Panel is showing. */ @@ -174,6 +196,7 @@ public ContextualSearchPanelBase(Context context) { mSearchBarHeightMaximized = TOOLBAR_HEIGHT_DP + PANEL_SHADOW_HEIGHT_DP; mSearchBarHeightExpanded = Math.round((mSearchBarHeightPeeking + mSearchBarHeightMaximized) / 2.f); + mSearchBarHeightPromo = SEARCH_BAR_HEIGHT_STATE_PROMO + PANEL_SHADOW_HEIGHT_DP; initializeUiState(); } @@ -199,7 +222,7 @@ public ContextualSearchPanelBase(Context context) { /** * @return Whether the Panel Promo is available. */ - protected abstract boolean isPromoAvailable(); + protected abstract boolean isPanelPromoAvailable(); /** * Animates the acceptance of the Promo. @@ -595,6 +618,42 @@ protected float getPromoYPx() { return Math.round((getOffsetY() + getSearchBarHeight()) / mPxToDp); } + // -------------------------------------------------------------------------------------------- + // Opt In Promo states + // -------------------------------------------------------------------------------------------- + + private float mPromoContentHeight; + private boolean mShouldHidePromoHeader; + + /** + * Sets the height of the promo content. + */ + protected void setPromoContentHeight(float height) { + mPromoContentHeight = height; + updateBasePageTargetY(); + } + + /** + * Gets the height of the promo content, or -1 if height has not been calculated. + */ + public float getPromoContentHeight() { + return mPromoContentHeight > 0.0f ? mPromoContentHeight : -1.f; + } + + /** + * @param shouldHidePromoHeader Sets whether the promo's header should be hidden. + */ + protected void setShouldHidePromoHeader(boolean shouldHidePromoHeader) { + mShouldHidePromoHeader = shouldHidePromoHeader; + } + + /** + * @return Gets whether the promo's header should be hidden. + */ + public boolean shouldHidePromoHeader() { + return mShouldHidePromoHeader; + } + // ============================================================================================ // Helpers // ============================================================================================ @@ -633,6 +692,9 @@ protected float getPanelHeightFromState(PanelState state) { panelHeight = 0; } else if (state == PanelState.PEEKED) { panelHeight = mSearchBarHeightPeeking; + } else if (state == PanelState.PROMO) { + panelHeight = getPromoContentHeight() + + mSearchBarHeightPromo; } else if (state == PanelState.EXPANDED) { panelHeight = fullscreenHeight * EXPANDED_PANEL_HEIGHT_PERCENTAGE + PANEL_SHADOW_HEIGHT_DP; @@ -679,7 +741,7 @@ protected PanelState findNearestPanelStateFromHeight(float desiredPanelHeight) { */ protected void setClampedPanelHeight(float height) { final float clampedHeight = MathUtils.clamp(height, - getPanelHeightFromState(PanelState.MAXIMIZED), + getPanelHeightFromState(getMaximumState()), getPanelHeightFromState(PanelState.PEEKED)); setPanelHeight(clampedHeight); } @@ -690,7 +752,7 @@ protected void setPanelState(PanelState state, StateChangeReason reason) { if (state == PanelState.CLOSED) { mIsShowing = false; - destroyPromoView(); + destroySearchPromo(); destroyContextualSearchControl(); onClose(); } else if (state == PanelState.EXPANDED) { @@ -743,6 +805,8 @@ private void updatePanelForHeight(float height) { updatePanelForExpansion(percentage); } else if (endState == PanelState.MAXIMIZED) { updatePanelForMaximization(percentage); + } else if (endState == PanelState.PROMO) { + updatePanelForOpeningPromo(percentage); } } @@ -960,6 +1024,57 @@ private void updatePanelForMaximization(float percentage) { updateSearchBarShadow(); } + /** + * Updates the UI state for the peeked to promo transition (and vice versa), + * according to a completion |percentage|. + * + * @param percentage The completion percentage. + */ + private void updatePanelForOpeningPromo(float percentage) { + // Base page offset. + float baseBaseY = MathUtils.interpolate( + 0, + getPromoBasePageOffsetY(), + percentage); + mBasePageY = baseBaseY; + + // Base page brightness. + float brightness = MathUtils.interpolate( + BASE_PAGE_BRIGHTNESS_STATE_PEEKED, + BASE_PAGE_BRIGHTNESS_STATE_EXPANDED, + percentage); + mBasePageBrightness = brightness; + + // Search Bar height. + float searchBarHeight = Math.round(MathUtils.interpolate( + mSearchBarHeightPeeking, + mSearchBarHeightPromo, + percentage)); + mSearchBarHeight = searchBarHeight; + + // Search Bar border. + mIsSearchBarBorderVisible = false; + + // Search Bar text opacity. + float peekedHeight = getPanelHeightFromState(PanelState.PEEKED); + float threshold = DISAPPEARING_ELEMENT_THRESHOLD_DP / mPxToDp; + float diff = Math.min(mHeight - peekedHeight, threshold); + float disappearingElementOpacity = MathUtils.interpolate(1.f, 0.f, diff / threshold); + mSearchBarTextOpacity = disappearingElementOpacity; + + // Search provider icon opacity. + boolean shouldDisplaySearchProviderIcon = shouldHidePromoHeader(); + mSearchProviderIconOpacity = + shouldDisplaySearchProviderIcon ? 1.f : disappearingElementOpacity; + + // Search icon opacity. + mSearchIconOpacity = SEARCH_ICON_OPACITY_PEEKED; + + // Progress Bar. + mProgressBarOpacity = 0.f; + mProgressBarY = searchBarHeight - PROGRESS_BAR_HEIGHT_DP + 1; + } + /** * Updates the UI state for Opt Out Promo. * @@ -968,7 +1083,7 @@ private void updatePanelForMaximization(float percentage) { * visibility between 0 and 1 means the Promo is partially visible. */ private void updatePromoVisibility(float percentage) { - if (isPromoAvailable()) { + if (isPanelPromoAvailable()) { mPromoVisible = true; mPromoHeightPx = Math.round(MathUtils.clamp(percentage * mPromoContentHeightPx, @@ -1016,6 +1131,7 @@ protected void updateBasePageSelectionYPx(float y) { */ private void updateBasePageTargetY() { mBasePageTargetY = calculateBasePageTargetY(PanelState.EXPANDED); + mPromoBasePageTargetY = calculateBasePageTargetY(PanelState.PROMO); } /** @@ -1062,6 +1178,14 @@ public float getBasePageTargetY() { return mBasePageTargetY; } + /** + * @return The Y coordinate to apply to the Base Page in order to keep the + * selection in view when the Search Panel is in PROMO state. + */ + private float getPromoBasePageOffsetY() { + return mPromoBasePageTargetY; + } + // ============================================================================================ // Resource Loader // ============================================================================================ @@ -1080,9 +1204,9 @@ public void setDynamicResourceLoader(DynamicResourceLoader resourceLoader) { mControl.getResourceAdapter()); } - if (mPromoView != null) { + if (mSearchPromoView != null) { mResourceLoader.registerResource(R.id.contextual_search_opt_out_promo, - mPromoView.getResourceAdapter()); + mSearchPromoView.getResourceAdapter()); } } @@ -1149,7 +1273,6 @@ public void onPromoPreferenceClick() { new Handler().post(new Runnable() { @Override public void run() { - setIsPromoActive(false); PreferencesLauncher.launchSettingsPage(mContext, ContextualSearchPreferenceFragment.class.getName()); } @@ -1177,7 +1300,7 @@ public void onPromoButtonClick(boolean accepted) { /** * The {@link ContextualSearchOptOutPromo} instance. */ - private ContextualSearchOptOutPromo mPromoView; + private ContextualSearchOptOutPromo mSearchPromoView; /** * Whether the Search Promo View is visible. @@ -1187,37 +1310,38 @@ public void onPromoButtonClick(boolean accepted) { /** * Creates the Search Promo View. */ - public void createPromoView() { - if (!isPromoAvailable()) return; + public void createSearchPromo() { + if (!isPanelPromoAvailable()) return; assert mContainerView != null; - if (mPromoView == null) { + if (mSearchPromoView == null) { LayoutInflater.from(mContext).inflate( R.layout.contextual_search_opt_out_promo, mContainerView); - mPromoView = (ContextualSearchOptOutPromo) + mSearchPromoView = (ContextualSearchOptOutPromo) mContainerView.findViewById(R.id.contextual_search_opt_out_promo); if (mResourceLoader != null) { mResourceLoader.registerResource(R.id.contextual_search_opt_out_promo, - mPromoView.getResourceAdapter()); + mSearchPromoView.getResourceAdapter()); } - mPromoView.setPromoHost(this); - setPromoContentHeightPx(mPromoView.getHeightForGivenWidth(mContainerView.getWidth())); + mSearchPromoView.setPromoHost(this); + setPromoContentHeightPx( + mSearchPromoView.getHeightForGivenWidth(mContainerView.getWidth())); } - assert mPromoView != null; + assert mSearchPromoView != null; } /** * Destroys the Search Promo View. */ - protected void destroyPromoView() { - if (!isPromoAvailable()) return; + protected void destroySearchPromo() { + if (!isPanelPromoAvailable()) return; - if (mPromoView != null) { - mContainerView.removeView(mPromoView); - mPromoView = null; + if (mSearchPromoView != null) { + mContainerView.removeView(mSearchPromoView); + mSearchPromoView = null; if (mResourceLoader != null) { mResourceLoader.unregisterResource(R.id.contextual_search_opt_out_promo); } @@ -1230,14 +1354,14 @@ protected void destroyPromoView() { * @param y The Y position. */ public void showPromoViewAtYPosition(float y) { - if (mPromoView == null || !isPromoAvailable()) return; + if (mSearchPromoView == null || !isPanelPromoAvailable()) return; - mPromoView.setTranslationY(y); - mPromoView.setVisibility(View.VISIBLE); + mSearchPromoView.setTranslationY(y); + mSearchPromoView.setVisibility(View.VISIBLE); // NOTE(pedrosimonetti): We need to call requestLayout, otherwise // the Promo View will not become visible. - mPromoView.requestLayout(); + mSearchPromoView.requestLayout(); mIsSearchPromoViewVisible = true; } @@ -1246,13 +1370,13 @@ public void showPromoViewAtYPosition(float y) { * Hides the Search Promo View. */ public void hidePromoView() { - if (mPromoView == null + if (mSearchPromoView == null || !mIsSearchPromoViewVisible - || !isPromoAvailable()) { + || !isPanelPromoAvailable()) { return; } - mPromoView.setVisibility(View.INVISIBLE); + mSearchPromoView.setVisibility(View.INVISIBLE); mIsSearchPromoViewVisible = false; } @@ -1266,4 +1390,69 @@ protected void setPromoVisibilityForOptInAnimation(float percentage) { updatePromoVisibility(percentage); updateSearchBarShadow(); } + + // ============================================================================================ + // Opt In Promo + // ============================================================================================ + + /** + * Updates the UI state for a given |height| when performing the opt-in animation. + * + * @param height The Contextual Search Panel height. + */ + protected void setPanelHeightForPromoOptInAnimation(float height) { + PanelState endState = PanelState.EXPANDED; + PanelState startState = PanelState.PROMO; + float percentage = getStateCompletion(height, startState, endState); + + updatePanelSize(height, endState, percentage); + + updatePanelForPromoOptInAnimation(percentage); + } + + /** + * Updates the UI state for the PROMO to EXPANDED transition (and vice versa), + * according to a completion |percentage|. + * + * @param percentage The completion percentage. + */ + private void updatePanelForPromoOptInAnimation(float percentage) { + // Base page offset. + float baseBaseY = MathUtils.interpolate( + getPromoBasePageOffsetY(), + getBasePageTargetY(), + percentage); + mBasePageY = baseBaseY; + + // Base page brightness. + mBasePageBrightness = BASE_PAGE_BRIGHTNESS_STATE_EXPANDED; + + // Search Bar height. + float searchBarHeight = Math.round(MathUtils.interpolate( + mSearchBarHeightPromo, + mSearchBarHeightExpanded, + percentage)); + mSearchBarHeight = searchBarHeight; + + // Search Bar border. + mIsSearchBarBorderVisible = false; + + // Search Bar text opacity. + final float threshold = 0.5f; + float searchBarTextOpacity = MathUtils.interpolate(0.f, 1.f, + Math.max(percentage - threshold, 0) / threshold); + mSearchBarTextOpacity = searchBarTextOpacity; + + // Search provider icon opacity. + boolean shouldDisplaySearchProviderIcon = shouldHidePromoHeader(); + mSearchProviderIconOpacity = shouldDisplaySearchProviderIcon + ? 1.f : MathUtils.interpolate(0.f, 1.f, percentage); + + // Search icon opacity. + mSearchIconOpacity = SEARCH_ICON_OPACITY_PEEKED; + + // Progress Bar. + mProgressBarOpacity = percentage == 1.f ? 1.f : 0.f; + mProgressBarY = searchBarHeight - PROGRESS_BAR_HEIGHT_DP + 1; + } } diff --git a/chrome/android/java/src/org/chromium/chrome/browser/compositor/bottombar/contextualsearch/ContextualSearchPanelDelegate.java b/chrome/android/java/src/org/chromium/chrome/browser/compositor/bottombar/contextualsearch/ContextualSearchPanelDelegate.java index 581a9a21408f0..55ab8e09467fd 100644 --- a/chrome/android/java/src/org/chromium/chrome/browser/compositor/bottombar/contextualsearch/ContextualSearchPanelDelegate.java +++ b/chrome/android/java/src/org/chromium/chrome/browser/compositor/bottombar/contextualsearch/ContextualSearchPanelDelegate.java @@ -53,6 +53,23 @@ public interface ContextualSearchPanelDelegate { */ void updateBasePageSelectionYPx(float y); + /** + * Sets the content height of the First Run Flow Panel. + * @param height The content height of the First Run Flow Panel. + */ + void setPromoContentHeight(float height); + + /** + * @param shouldHidePromoHeader Sets whether the First Run Flow's header + * should be hidden. + */ + void setShouldHidePromoHeader(boolean shouldHidePromoHeader); + + /** + * Animates the Contextual Search panel after first-run success. + */ + void animateAfterFirstRunSuccess(); + /** * Handles the onLoadStarted event in the WebContents. */ diff --git a/chrome/android/java/src/org/chromium/chrome/browser/compositor/bottombar/contextualsearch/ContextualSearchPanelStateHandler.java b/chrome/android/java/src/org/chromium/chrome/browser/compositor/bottombar/contextualsearch/ContextualSearchPanelStateHandler.java index 6a8815b81a056..55d10ee4e2c37 100644 --- a/chrome/android/java/src/org/chromium/chrome/browser/compositor/bottombar/contextualsearch/ContextualSearchPanelStateHandler.java +++ b/chrome/android/java/src/org/chromium/chrome/browser/compositor/bottombar/contextualsearch/ContextualSearchPanelStateHandler.java @@ -18,7 +18,18 @@ */ abstract class ContextualSearchPanelStateHandler { - // Valid previous states for the Panel. + // Valid previous states for when the promo is active. + private static final Map PREVIOUS_STATES_PROMO; + static { + Map states = new HashMap(); + // Pairs are of the form . + states.put(PanelState.PEEKED, PanelState.CLOSED); + states.put(PanelState.PROMO, PanelState.PEEKED); + states.put(PanelState.EXPANDED, PanelState.PROMO); + PREVIOUS_STATES_PROMO = Collections.unmodifiableMap(states); + } + + // Valid previous states for when the promo is not active (normal flow). private static final Map PREVIOUS_STATES_NORMAL; static { Map states = new HashMap(); @@ -60,10 +71,28 @@ PanelState getPanelState() { * @return The {@code PanelState} that is before the |state| in the order of states. */ PanelState getPreviousPanelState(PanelState state) { - PanelState prevState = PREVIOUS_STATES_NORMAL.get(state); + PanelState prevState = mIsPromoActive + ? PREVIOUS_STATES_PROMO.get(state) + : PREVIOUS_STATES_NORMAL.get(state); return prevState != null ? prevState : PanelState.UNDEFINED; } + /** + * Return the maximum state that the panel can be in, depending on whether the promo is + * active. + */ + PanelState getMaximumState() { + return mIsPromoActive ? PanelState.PROMO : PanelState.MAXIMIZED; + } + + /** + * Return the intermediary state that the panel can be in, depending on whether the promo is + * active. + */ + PanelState getIntermediaryState() { + return mIsPromoActive ? PanelState.PROMO : PanelState.EXPANDED; + } + /** * Sets the panel's state. * @param toState The panel state to transition to. @@ -169,10 +198,14 @@ protected void setPanelState(PanelState toState, StateChangeReason reason) { * @return whether the state is valid. */ boolean isValidState(PanelState state) { - ArrayList validStates = - new ArrayList(PREVIOUS_STATES_NORMAL.values()); - // MAXIMIZED is not the previous state of anything, but it's a valid state. - validStates.add(PanelState.MAXIMIZED); + ArrayList validStates; + if (mIsPromoActive) { + validStates = new ArrayList(PREVIOUS_STATES_PROMO.values()); + } else { + validStates = new ArrayList(PREVIOUS_STATES_NORMAL.values()); + // MAXIMIZED is not the previous state of anything, but it's a valid state. + validStates.add(PanelState.MAXIMIZED); + } return validStates.contains(state); } diff --git a/chrome/android/java/src/org/chromium/chrome/browser/contextualsearch/ContextualSearchManagementDelegate.java b/chrome/android/java/src/org/chromium/chrome/browser/contextualsearch/ContextualSearchManagementDelegate.java index 90e2d5174a0ec..ddfe3335d21c0 100644 --- a/chrome/android/java/src/org/chromium/chrome/browser/contextualsearch/ContextualSearchManagementDelegate.java +++ b/chrome/android/java/src/org/chromium/chrome/browser/contextualsearch/ContextualSearchManagementDelegate.java @@ -21,7 +21,6 @@ public interface ContextualSearchManagementDelegate { public void setPreferenceState(boolean enabled); /** - * TODO(pedrosimonetti): rename to isPromoAvailable (requires upstream and downstream changes). * @return Whether the Opt-out promo is available to be be shown in the panel. */ boolean isOptOutPromoAvailable(); diff --git a/chrome/android/java/src/org/chromium/chrome/browser/contextualsearch/ContextualSearchUma.java b/chrome/android/java/src/org/chromium/chrome/browser/contextualsearch/ContextualSearchUma.java index b944ef9921288..c0b5e1bb095bc 100644 --- a/chrome/android/java/src/org/chromium/chrome/browser/contextualsearch/ContextualSearchUma.java +++ b/chrome/android/java/src/org/chromium/chrome/browser/contextualsearch/ContextualSearchUma.java @@ -408,6 +408,17 @@ public int hashCode() { PROMO_BY_GESTURE_CODES = Collections.unmodifiableMap(codes); } + /** + * Logs the state of the Contextual Search preference. This function should be called if the + * Contextual Search feature is enabled, and will track the different preference settings + * (disabled, enabled or uninitialized). Calling more than once is fine. + * This is deprecated; pass the number of taps remaining to logPreferenceState. + */ + @Deprecated + public static void logPreferenceState() { + logPreferenceState(PROMO_TAPS_REMAINING_INVALID); + } + /** * Logs the state of the Contextual Search preference. This function should be called if the * Contextual Search feature is enabled, and will track the different preference settings @@ -433,6 +444,16 @@ public static void logPreferenceChange(boolean enabled) { enabled ? PREFERENCE_ENABLED : PREFERENCE_DISABLED, PREFERENCE_HISTOGRAM_BOUNDARY); } + /** + * Logs the outcome of a first run flow. + * This is deprecated; call logPromoOutcome instead. + */ + @Deprecated + public static void logFirstRunFlowOutcome() { + RecordHistogram.recordEnumeratedHistogram("Search.ContextualSearchFirstRunFlowOutcome", + getPreferenceValue(), PREFERENCE_HISTOGRAM_BOUNDARY); + } + /** * Logs the outcome of the promo (first run flow). * Logs multiple histograms; with and without the originating gesture. diff --git a/chrome/browser/browser_resources.grd b/chrome/browser/browser_resources.grd index 7051bde80fbc6..ed14ec79441e4 100644 --- a/chrome/browser/browser_resources.grd +++ b/chrome/browser/browser_resources.grd @@ -24,6 +24,10 @@ + + + + @@ -106,6 +110,10 @@ + + + + diff --git a/chrome/browser/resources/contextual_search/OWNERS b/chrome/browser/resources/contextual_search/OWNERS new file mode 100644 index 0000000000000..07a31a62aca51 --- /dev/null +++ b/chrome/browser/resources/contextual_search/OWNERS @@ -0,0 +1 @@ +mathp@chromium.org diff --git a/chrome/browser/resources/contextual_search/header.svg b/chrome/browser/resources/contextual_search/header.svg new file mode 100644 index 0000000000000..3fb9aa3585d0a --- /dev/null +++ b/chrome/browser/resources/contextual_search/header.svg @@ -0,0 +1,25 @@ + + + + + + + + + + + + + + + + + + diff --git a/chrome/browser/resources/contextual_search/promo.css b/chrome/browser/resources/contextual_search/promo.css new file mode 100644 index 0000000000000..63fe3392ee401 --- /dev/null +++ b/chrome/browser/resources/contextual_search/promo.css @@ -0,0 +1,162 @@ +/* Copyright 2014 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. */ + +/* TODO: Need to clean up @font-face after we remove font-family from body. */ +@font-face { + font-family: 'Roboto2'; + font-weight: 400; + src: local('Roboto'), local('Roboto2-Regular'), + url(chrome://resources/roboto/roboto.woff2) format('woff2'), + url(chrome://resources/roboto/roboto.woff) format('woff'); +} + +/* TODO(pedrosimonetti): Find a better way to fix the problem when the + * text scaling preference is set to a high value. + * + * This CSS rule prevents the promo text from scaling as explained here: + * https://code.google.com/p/chromium/issues/detail?id=252828#c10 + * + * For for background about the problem, see: + * https://code.google.com/p/chromium/issues/detail?id=466773 + */ +#heading, +#description { + max-height: 999999px; +} + +body { + font-family: 'Roboto2', sans-serif; + margin: 0; +} + +a { + text-decoration: none; +} + +a.colored-link { + color: rgb(66, 133, 244); +} + +#container { + /* NOTE(pedrosimonetti): There's an implicit extra top margin that is + * rendered natively (currently using 24dp). So, the total padding will + * be 38dp (24dp + 14dp). For more info, see SEARCH_BAR_HEIGHT_STATE_PROMO + * in ContextualSearchPanelBase.java. + * + * We're also setting the side and bottom paddings to ensure to make sure + * that when computing the height of the container all margins/paddings will + * be considered. + */ + padding: 14px 16px 12px; +} + +#button-container { + margin-top: 24px; + text-align: end; + width: 100%; +} + +#container.hide { + -webkit-transform: scale(0.95); + -webkit-transition-duration: 130ms; + -webkit-transition-property: opacity, -webkit-transform; + opacity: 0; +} + +#description { + color: #7E7E7E; + font-size: 16px; + line-height: 1.38em; + margin: 12px 0 24px; +} + +/* Some properties below can be overridden in landscape orientation. */ +#heading { + font-size: 23px; + margin: 20px 0 12px; + text-align: center; +} +.header-image { + background-image: url(header.svg); + background-repeat: no-repeat; + height: 98px; + margin: 0 auto 38px auto; + width: 156px; +} +.portrait { + display: block; +} +.landscape { + display: none; +} + +/* Landscape */ +@media screen and (orientation:landscape) { + #heading { + margin-top: 0; + /* The heading text and description text should be aligned, therefore + * the left margin here will be equal to the header image width (156px) + * plus its right margin (24px). Therefore the total left should be + * 156px + 24px = 180px. + */ + margin-left: 180px; + padding-top: 8px; + text-align: left; + } + .header-image { + /* The header image is supposed to be vertically centered when the promo + * is in landscape mode. For now, we're forcefully moving the image 4 + * pixels up to make it centered. A better approach would be using CSS + * flexbox to properly center it, but this will require changing the + * markup and styling of the whole promo, and it could be tricky coming + * up with a single markup that works in both portrait and lanscape modes. + */ + margin: 0 24px 0 0; + position: relative; + top: -4px; + } + .portrait { + display: none; + } + .landscape { + display: block; + float: left; + } + html[dir='rtl'] .landscape { + float: right; + } +} + +button { + background: none; + border: none; + display: inline-block; + font-family: 'Roboto2', sans-serif; + font-size: 14px; + margin: 6px 0; + /* We use a slightly different top-bottom padding because Roboto has a + * rendering bug which makes an extra padding to be rendered at the bottom of + * text. + */ + padding: 12px 16px 8px; + white-space: nowrap; +} + +button .caption { + text-transform: uppercase; +} + +#optin-button { + background: rgb(66, 133, 244); + background-clip: padding-box; + border-radius: 3px; +} + +#optin-button .caption { + color: white; +} + +#optout-button .caption { + color: rgb(66, 133, 244); +} diff --git a/chrome/browser/resources/contextual_search/promo.html b/chrome/browser/resources/contextual_search/promo.html new file mode 100644 index 0000000000000..b5bbd1bcda2a8 --- /dev/null +++ b/chrome/browser/resources/contextual_search/promo.html @@ -0,0 +1,37 @@ + + + + + + + Contextual Search First-Run + + + + + + +
+
+
+
+
+ + + + +
+
+ + +
+
+ + diff --git a/chrome/browser/resources/contextual_search/promo.js b/chrome/browser/resources/contextual_search/promo.js new file mode 100644 index 0000000000000..8e17d909aeaec --- /dev/null +++ b/chrome/browser/resources/contextual_search/promo.js @@ -0,0 +1,58 @@ +/* Copyright 2014 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. +*/ + + + + +/** + * The amount of delay to use in the opt-in action in order to give time for + * the fade-out animation to execute, before navigating to the opt-in URL, + * in milliseconds. + * @const + */ +var OPT_IN_DELAY_MS = 65; + +/** + * Once the DOM is loaded, determine if the header image is to be kept and + * register a handler to add the 'hide' class to the container element in order + * to hide it. + */ +document.addEventListener('DOMContentLoaded', function(event) { + if (config['hideHeader']) { + removeHeaderImages(); + } + $('optin-button').addEventListener('click', function() { + $('container').classList.add('hide'); + setTimeout(function() { + location.hash = 'optin'; + }, OPT_IN_DELAY_MS); + }); + $('optout-button').addEventListener('click', function() { + location.hash = 'optout'; + }); +}); + +/** + * Returns the height of the content. Method called from Chrome to properly size + * the view embedding it. + * @return {number} The height of the content, in pixels. + */ +function getContentHeight() { + return $('container').clientHeight; +} + +/** + * Removes all header images from the promo. + */ +function removeHeaderImages() { + var images = document.querySelectorAll('.header-image'); + for (var i = 0, length = images.length; i < length; i++) { + var image = images[i]; + var parent = image.parentElement; + if (parent) { + parent.removeChild(image); + } + } +} diff --git a/chrome/browser/search/contextual_search_promo_source_android.cc b/chrome/browser/search/contextual_search_promo_source_android.cc new file mode 100644 index 0000000000000..ea6f8c71740da --- /dev/null +++ b/chrome/browser/search/contextual_search_promo_source_android.cc @@ -0,0 +1,149 @@ +// Copyright 2014 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. + +#include "chrome/browser/search/contextual_search_promo_source_android.h" + +#include + +#include "base/json/json_string_value_serializer.h" +#include "base/memory/ref_counted_memory.h" +#include "base/strings/string_util.h" +#include "base/strings/stringprintf.h" +#include "base/values.h" +#include "chrome/common/url_constants.h" +#include "chrome/grit/chromium_strings.h" +#include "components/variations/variations_associated_data.h" +#include "grit/browser_resources.h" +#include "ui/base/l10n/l10n_util.h" +#include "ui/base/resource/resource_bundle.h" +#include "ui/base/webui/jstemplate_builder.h" +#include "url/gurl.h" + +namespace { + +const char kPromoConfigPath[] = "/config.js"; +const char kPromoHTMLPath[] = "/promo.html"; +const char kPromoCSSPath[] = "/promo.css"; +const char kPromoJSPath[] = "/promo.js"; + +// Field trial related constants. +const char kContextualSearchFieldTrialName[] = "ContextualSearch"; +const char kContextualSearchHidePromoHeaderParam[] = "hide_promo_header"; +const char kContextualSearchEnabledValue[] = "enabled"; + +// Returns whether we should hide the first-run promo header. +bool ShouldHidePromoHeader() { + return variations::GetVariationParamValue( + kContextualSearchFieldTrialName, kContextualSearchHidePromoHeaderParam) == + kContextualSearchEnabledValue; +} + +// Returns a JS dictionary of configuration data for the Contextual Search +// promo. +std::string GetConfigData() { + base::DictionaryValue config_data; + config_data.SetBoolean("hideHeader", ShouldHidePromoHeader()); + + // Serialize the dictionary. + std::string js_text; + JSONStringValueSerializer serializer(&js_text); + serializer.Serialize(config_data); + + std::string config_data_js; + config_data_js.append("var config = "); + config_data_js.append(js_text); + config_data_js.append(";"); + return config_data_js; +} + +} // namespace + +ContextualSearchPromoSourceAndroid::ContextualSearchPromoSourceAndroid() {} + +ContextualSearchPromoSourceAndroid::~ContextualSearchPromoSourceAndroid() {} + +void ContextualSearchPromoSourceAndroid::StartDataRequest( + const std::string& path_and_query, int render_process_id, + int render_frame_id, + const content::URLDataSource::GotDataCallback& callback) { + GURL url(std::string(chrome::kChromeUIContextualSearchPromoURL) + "/" + + path_and_query); + std::string path(url.path()); + if (path == kPromoHTMLPath) { + SendHtmlWithStrings(callback); + } else if (path == kPromoCSSPath) { + SendResource(IDR_CONTEXTUAL_SEARCH_PROMO_CSS, callback); + } else if (path == kPromoJSPath) { + SendResource(IDR_CONTEXTUAL_SEARCH_PROMO_JS, callback); + } else if (path == kPromoConfigPath) { + SendConfigResource(callback); + } else { + callback.Run(NULL); + } +} + +std::string ContextualSearchPromoSourceAndroid::GetSource() const { + return chrome::kChromeUIContextualSearchPromoHost; +} + +std::string ContextualSearchPromoSourceAndroid::GetMimeType( + const std::string& path_and_query) const { + std::string path(GURL("chrome://host/" + path_and_query).path()); + if (EndsWith(path, ".js", false)) return "application/javascript"; + if (EndsWith(path, ".png", false)) return "image/png"; + if (EndsWith(path, ".css", false)) return "text/css"; + if (EndsWith(path, ".html", false)) return "text/html"; + if (EndsWith(path, ".woff", false)) return "font/woff"; + if (EndsWith(path, ".woff2", false)) return "font/woff2"; + return ""; +} + +bool ContextualSearchPromoSourceAndroid::ShouldDenyXFrameOptions() const { + return false; +} + +bool +ContextualSearchPromoSourceAndroid::ShouldAddContentSecurityPolicy() const { + return false; +} + +void ContextualSearchPromoSourceAndroid::SendResource( + int resource_id, const content::URLDataSource::GotDataCallback& callback) { + scoped_refptr response( + ResourceBundle::GetSharedInstance().LoadDataResourceBytes(resource_id)); + callback.Run(response.get()); +} + +void ContextualSearchPromoSourceAndroid::SendConfigResource( + const content::URLDataSource::GotDataCallback& callback) { + std::string response = GetConfigData(); + callback.Run(base::RefCountedString::TakeString(&response)); +} + +void ContextualSearchPromoSourceAndroid::SendHtmlWithStrings( + const content::URLDataSource::GotDataCallback& callback) { + base::DictionaryValue strings_data; + // The three following statements are part of the description paragraph. + strings_data.SetString( + "description-1", + l10n_util::GetStringUTF16(IDS_CONTEXTUAL_SEARCH_PROMO_DESCRIPTION_1)); + strings_data.SetString( + "feature-name", + l10n_util::GetStringUTF16(IDS_CONTEXTUAL_SEARCH_PROMO_FEATURE_NAME)); + strings_data.SetString( + "description-2", + l10n_util::GetStringUTF16(IDS_CONTEXTUAL_SEARCH_PROMO_DESCRIPTION_2)); + + strings_data.SetString( + "heading", l10n_util::GetStringUTF16(IDS_CONTEXTUAL_SEARCH_HEADER)); + strings_data.SetString( + "optIn", l10n_util::GetStringUTF16(IDS_CONTEXTUAL_SEARCH_PROMO_OPTIN)); + strings_data.SetString( + "optOut", l10n_util::GetStringUTF16(IDS_CONTEXTUAL_SEARCH_PROMO_OPTOUT)); + base::StringPiece html( + ResourceBundle::GetSharedInstance().GetRawDataResource( + IDR_CONTEXTUAL_SEARCH_PROMO_HTML)); + std::string response(webui::GetI18nTemplateHtml(html, &strings_data)); + callback.Run(base::RefCountedString::TakeString(&response)); +} diff --git a/chrome/browser/search/contextual_search_promo_source_android.h b/chrome/browser/search/contextual_search_promo_source_android.h new file mode 100644 index 0000000000000..4ed5bbbf9bae8 --- /dev/null +++ b/chrome/browser/search/contextual_search_promo_source_android.h @@ -0,0 +1,47 @@ +// Copyright 2014 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. + +#ifndef CHROME_BROWSER_SEARCH_CONTEXTUAL_SEARCH_PROMO_SOURCE_ANDROID_H_ +#define CHROME_BROWSER_SEARCH_CONTEXTUAL_SEARCH_PROMO_SOURCE_ANDROID_H_ + +#include "base/basictypes.h" +#include "base/compiler_specific.h" +#include "content/public/browser/url_data_source.h" + +// Serves HTML for displaying the contextual search first-run promo. +class ContextualSearchPromoSourceAndroid : public content::URLDataSource { + public: + ContextualSearchPromoSourceAndroid(); + ~ContextualSearchPromoSourceAndroid() override; + + protected: + // Overridden from content::URLDataSource: + void StartDataRequest( + const std::string& path_and_query, + int render_process_id, + int render_frame_id, + const content::URLDataSource::GotDataCallback& callback) override; + std::string GetSource() const override; + std::string GetMimeType(const std::string& path_and_query) const override; + bool ShouldDenyXFrameOptions() const override; + bool ShouldAddContentSecurityPolicy() const override; + + // Sends unmodified resource bytes. + void SendResource( + int resource_id, + const content::URLDataSource::GotDataCallback& callback); + + // Sends the config JS resource. + void SendConfigResource( + const content::URLDataSource::GotDataCallback& callback); + + // Sends HTML with localized strings. + void SendHtmlWithStrings( + const content::URLDataSource::GotDataCallback& callback); + + private: + DISALLOW_COPY_AND_ASSIGN(ContextualSearchPromoSourceAndroid); +}; + +#endif // CHROME_BROWSER_SEARCH_CONTEXTUAL_SEARCH_PROMO_SOURCE_ANDROID_H_ diff --git a/chrome/chrome_browser.gypi b/chrome/chrome_browser.gypi index 80a098b0c1df2..acc96c7c187f1 100644 --- a/chrome/chrome_browser.gypi +++ b/chrome/chrome_browser.gypi @@ -664,6 +664,8 @@ 'browser/safe_json_parser.h', 'browser/search/contextual_search_policy_handler_android.cc', 'browser/search/contextual_search_policy_handler_android.h', + 'browser/search/contextual_search_promo_source_android.cc', + 'browser/search/contextual_search_promo_source_android.h', 'browser/search/iframe_source.cc', 'browser/search/iframe_source.h', 'browser/search/instant_io_context.cc',