Commit 13977c83 authored by Theresa Wellington's avatar Theresa Wellington Committed by Commit Bot

[Home] Add flag to show pull-to-refresh IPH at top

When the flag is enabled, the pull-to-refresh in-product help bubble is
shown at the top of the screen rather than anchored to the bottom
toolbar.

ArrowBubbleDrawable was modified to support not drawing an arrow.

BUG=793403

Change-Id: I86d753a1eb7a193576f4523de6dd5f01a1a4c145
Reviewed-on: https://chromium-review.googlesource.com/817882
Commit-Queue: Theresa <twellington@chromium.org>
Reviewed-by: default avatarDavid Trainor <dtrainor@chromium.org>
Reviewed-by: default avatarMatthew Jones <mdjones@chromium.org>
Cr-Commit-Position: refs/heads/master@{#523791}
parent 63f4e4ef
......@@ -173,6 +173,8 @@ public abstract class ChromeFeatureList {
public static final String CHROME_HOME_PROMO = "ChromeHomePromo";
public static final String CHROME_HOME_PROMO_INFO_ONLY = "ChromeHomePromoInfoOnly";
public static final String CHROME_HOME_PROMO_ON_STARTUP = "ChromeHomePromoOnStartup";
public static final String CHROME_HOME_PULL_TO_REFRESH_IPH_AT_TOP =
"ChromeHomePullToRefreshIphAtTop";
public static final String CHROME_HOME_SHOW_GOOGLE_G_WHEN_URL_CLEARED =
"ChromeHomeShowGoogleGWhenUrlCleared";
public static final String CHROME_HOME_SURVEY = "ChromeHomeSurvey";
......
......@@ -55,7 +55,7 @@ import org.chromium.chrome.browser.util.FeatureUtilities;
import org.chromium.chrome.browser.util.MathUtils;
import org.chromium.chrome.browser.widget.FadingBackgroundView;
import org.chromium.chrome.browser.widget.bottomsheet.BottomSheetContentController.ContentType;
import org.chromium.chrome.browser.widget.textbubble.ViewAnchoredTextBubble;
import org.chromium.chrome.browser.widget.textbubble.TextBubble;
import org.chromium.content.browser.BrowserStartupController;
import org.chromium.content.browser.ContentViewCore;
import org.chromium.content_public.browser.LoadUrlParams;
......@@ -1702,7 +1702,7 @@ public class BottomSheet
* @return The bottom sheet's help bubble if it exists.
*/
@VisibleForTesting
public @Nullable ViewAnchoredTextBubble getHelpBubbleForTests() {
public @Nullable TextBubble getHelpBubbleForTests() {
return getIphBubbleController().getHelpBubbleForTests();
}
}
......@@ -5,8 +5,10 @@
package org.chromium.chrome.browser.widget.bottomsheet;
import android.content.Context;
import android.graphics.Rect;
import android.support.annotation.Nullable;
import android.view.View;
import android.view.View.OnLayoutChangeListener;
import android.widget.PopupWindow.OnDismissListener;
import org.chromium.base.Callback;
......@@ -22,6 +24,7 @@ import org.chromium.chrome.browser.profiles.Profile;
import org.chromium.chrome.browser.toolbar.BottomToolbarPhone;
import org.chromium.chrome.browser.widget.ViewHighlighter;
import org.chromium.chrome.browser.widget.bottomsheet.BottomSheet.StateChangeReason;
import org.chromium.chrome.browser.widget.textbubble.TextBubble;
import org.chromium.chrome.browser.widget.textbubble.ViewAnchoredTextBubble;
import org.chromium.components.feature_engagement.EventConstants;
import org.chromium.components.feature_engagement.FeatureConstants;
......@@ -41,7 +44,7 @@ public class ChromeHomeIphBubbleController {
*/
private static final String HELP_BUBBLE_TIMEOUT_PARAM_NAME = "x_iph-timeout-duration-ms";
private ViewAnchoredTextBubble mHelpBubble;
private TextBubble mHelpBubble;
private LayoutManagerChrome mLayoutManager;
private BottomToolbarPhone mToolbar;
private View mControlContainer;
......@@ -67,7 +70,7 @@ public class ChromeHomeIphBubbleController {
mBottomSheet.addObserver(new EmptyBottomSheetObserver() {
@Override
public void onSheetOpened(@StateChangeReason int reason) {
if (mHelpBubble != null) mHelpBubble.dismiss();
dismissHelpBubble();
Tracker tracker = TrackerFactory.getTrackerForProfile(Profile.getLastUsedProfile());
tracker.notifyEvent(EventConstants.BOTTOM_SHEET_EXPANDED);
......@@ -149,14 +152,23 @@ public class ChromeHomeIphBubbleController {
if (!fromMenu && !showRefreshIph && !showColdStartIph) return;
// Determine which strings to use.
boolean showAtTopOfScreen = showRefreshIph
&& ChromeFeatureList.isEnabled(
ChromeFeatureList.CHROME_HOME_PULL_TO_REFRESH_IPH_AT_TOP);
boolean showExpandButtonHelpBubble = !showRefreshIph && mToolbar.isUsingExpandButton();
View anchorView = showExpandButtonHelpBubble
? mControlContainer.findViewById(R.id.expand_sheet_button)
: mControlContainer;
int stringId = showRefreshIph ? R.string.bottom_sheet_pull_to_refresh_help_bubble_message
: showExpandButtonHelpBubble
? R.string.bottom_sheet_accessibility_expand_button_help_bubble_message
: R.string.bottom_sheet_help_bubble_message;
int stringId = 0;
if (showRefreshIph) {
stringId = showAtTopOfScreen
? R.string.bottom_sheet_pull_to_refresh_help_bubble_accessibility_message
: R.string.bottom_sheet_pull_to_refresh_help_bubble_message;
} else if (showExpandButtonHelpBubble) {
stringId = R.string.bottom_sheet_accessibility_expand_button_help_bubble_message;
} else {
stringId = R.string.bottom_sheet_help_bubble_message;
}
int accessibilityStringId = showRefreshIph
? R.string.bottom_sheet_pull_to_refresh_help_bubble_accessibility_message
: stringId;
......@@ -166,7 +178,7 @@ public class ChromeHomeIphBubbleController {
EmptyOverviewModeObserver overviewModeObserver = new EmptyOverviewModeObserver() {
@Override
public void onOverviewModeStartedShowing(boolean showToolbar) {
mHelpBubble.dismiss();
dismissHelpBubble();
}
};
mLayoutManager.addOverviewModeObserver(overviewModeObserver);
......@@ -176,8 +188,29 @@ public class ChromeHomeIphBubbleController {
mFullscreenManager.getBrowserVisibilityDelegate().showControlsPersistent();
// Create the help bubble and setup dismissal behavior.
mHelpBubble =
new ViewAnchoredTextBubble(mContext, anchorView, stringId, accessibilityStringId);
View topAnchorView = (View) mBottomSheet.getParent();
OnLayoutChangeListener topAnchorLayoutChangeListener = new OnLayoutChangeListener() {
@Override
public void onLayoutChange(View v, int left, int top, int right, int bottom,
int oldLeft, int oldTop, int oldRight, int oldBottom) {
if (left != oldLeft || right != oldRight || top != oldTop || bottom != oldBottom) {
dismissHelpBubble();
}
}
};
if (showAtTopOfScreen) {
mHelpBubble =
new TextBubble(mContext, topAnchorView, stringId, accessibilityStringId, false);
mHelpBubble.setAnchorRect(getTopAnchorRect(topAnchorView));
topAnchorView.addOnLayoutChangeListener(topAnchorLayoutChangeListener);
} else {
mHelpBubble = new ViewAnchoredTextBubble(
mContext, anchorView, stringId, accessibilityStringId);
int inset = mContext.getResources().getDimensionPixelSize(
R.dimen.bottom_sheet_help_bubble_inset);
((ViewAnchoredTextBubble) mHelpBubble).setInsetPx(0, inset, 0, inset);
}
if (ChromeFeatureList.isEnabled(ChromeFeatureList.CHROME_HOME_PERSISTENT_IPH)) {
int dismissTimeout = ChromeFeatureList.getFieldTrialParamByFeatureAsInt(
......@@ -205,6 +238,10 @@ public class ChromeHomeIphBubbleController {
ViewHighlighter.turnOffHighlight(anchorView);
if (showAtTopOfScreen) {
topAnchorView.removeOnLayoutChangeListener(topAnchorLayoutChangeListener);
}
mHelpBubble = null;
}
});
......@@ -215,9 +252,6 @@ public class ChromeHomeIphBubbleController {
}
// Show the bubble.
int inset = mContext.getResources().getDimensionPixelSize(
R.dimen.bottom_sheet_help_bubble_inset);
mHelpBubble.setInsetPx(0, inset, 0, inset);
mHelpBubble.show();
}
......@@ -225,7 +259,24 @@ public class ChromeHomeIphBubbleController {
* @return The bottom sheet's help bubble if it exists.
*/
@VisibleForTesting
public @Nullable ViewAnchoredTextBubble getHelpBubbleForTests() {
public @Nullable TextBubble getHelpBubbleForTests() {
return mHelpBubble;
}
/** Dismiss the help bubble if it is not null. */
private void dismissHelpBubble() {
if (mHelpBubble != null) mHelpBubble.dismiss();
}
/**
* @param topAnchorView The view used display the IPH bubble when it is shown at the top of the
* screen.
* @return A {@link Rect} used to anchor the IPH bubble.
*/
private Rect getTopAnchorRect(View topAnchorView) {
int[] locationInWindow = new int[2];
topAnchorView.getLocationInWindow(locationInWindow);
int centerPoint = locationInWindow[0] + topAnchorView.getWidth() / 2;
return new Rect(centerPoint, locationInWindow[1], centerPoint, locationInWindow[1]);
}
}
......@@ -37,6 +37,7 @@ class ArrowBubbleDrawable extends Drawable implements Drawable.Callback {
private int mArrowXOffsetPx;
private boolean mArrowOnTop;
private boolean mShowArrow;
public ArrowBubbleDrawable(Context context) {
mRadiusPx = context.getResources().getDimensionPixelSize(R.dimen.text_bubble_corner_radius);
......@@ -102,6 +103,21 @@ class ArrowBubbleDrawable extends Drawable implements Drawable.Callback {
return mArrowOnTop;
}
/**
* @return Whether or not an arrow is currently shown.
*/
public boolean isShowingArrow() {
return mShowArrow;
}
/**
* @param showArrow Whether the bubble should have an arrow.
*/
public void setShowArrow(boolean showArrow) {
mShowArrow = showArrow;
invalidateSelf();
}
/**
* @param color The color to make the bubble and arrow.
*/
......@@ -132,16 +148,19 @@ class ArrowBubbleDrawable extends Drawable implements Drawable.Callback {
public void draw(Canvas canvas) {
mBubbleDrawable.draw(canvas);
canvas.save();
// If the arrow is on the bottom, flip the arrow before drawing.
if (!mArrowOnTop) {
int arrowCenterYPx = getBounds().height() - mArrowHeightPx / 2;
canvas.scale(1, -1, mArrowXOffsetPx, arrowCenterYPx);
canvas.translate(0, arrowCenterYPx - mArrowHeightPx / 2);
if (mShowArrow) {
canvas.save();
// If the arrow is on the bottom, flip the arrow before drawing.
if (!mArrowOnTop) {
int arrowCenterYPx = getBounds().height() - mArrowHeightPx / 2;
canvas.scale(1, -1, mArrowXOffsetPx, arrowCenterYPx);
canvas.translate(0, arrowCenterYPx - mArrowHeightPx / 2);
}
canvas.translate(mArrowXOffsetPx, 0);
canvas.drawPath(mArrowPath, mArrowPaint);
canvas.restore();
}
canvas.translate(mArrowXOffsetPx, 0);
canvas.drawPath(mArrowPath, mArrowPaint);
canvas.restore();
}
@Override
......
......@@ -129,7 +129,7 @@ public class TextBubble implements OnTouchListener {
private final int mAccessibilityStringId;
/**
* Constructs a {@link TextBubble} instance.
* Constructs a {@link TextBubble} instance using the default arrow drawable background.
* @param context Context to draw resources from.
* @param rootView The {@link View} to use for size calculations and for display.
* @param stringId The id of the string resource for the text that should be shown.
......@@ -137,12 +137,28 @@ public class TextBubble implements OnTouchListener {
*/
public TextBubble(Context context, View rootView, @StringRes int stringId,
@StringRes int accessibilityStringId) {
this(context, rootView, stringId, accessibilityStringId, true);
}
/**
* Constructs a {@link TextBubble} instance.
* @param context Context to draw resources from.
* @param rootView The {@link View} to use for size calculations and for display.
* @param stringId The id of the string resource for the text that should be shown.
* @param accessibilityStringId The id of the string resource of the accessibility text.
* @param showArrow Whether the bubble should have an arrow.
*/
public TextBubble(Context context, View rootView, @StringRes int stringId,
@StringRes int accessibilityStringId, boolean showArrow) {
mContext = context;
mRootView = rootView.getRootView();
mStringId = stringId;
mAccessibilityStringId = accessibilityStringId;
mPopupWindow = new PopupWindow(mContext);
mDrawable = new ArrowBubbleDrawable(context);
mDrawable.setShowArrow(showArrow);
mHandler = new Handler();
mPopupWindow.setWidth(ViewGroup.LayoutParams.WRAP_CONTENT);
......@@ -355,14 +371,19 @@ public class TextBubble implements OnTouchListener {
// In landscape mode, root view includes the decorations in some devices. So we guard the
// window dimensions against |mCachedWindowRect.right| instead.
mX = MathUtils.clamp(mX, mMarginPx, mCachedWindowRect.right - mWidth - mMarginPx);
int arrowXOffset = mAnchorRect.centerX() - mX;
// Force the anchor to be in a reasonable spot w.r.t. the bubble (not over the corners).
int minArrowOffset = mDrawable.getArrowLeftSpacing();
int maxArrowOffset = mWidth - mDrawable.getArrowRightSpacing();
arrowXOffset = MathUtils.clamp(arrowXOffset, minArrowOffset, maxArrowOffset);
int arrowXOffset = 0;
if (mDrawable.isShowingArrow()) {
arrowXOffset = mAnchorRect.centerX() - mX;
// Force the anchor to be in a reasonable spot w.r.t. the bubble (not over the corners).
int minArrowOffset = mDrawable.getArrowLeftSpacing();
int maxArrowOffset = mWidth - mDrawable.getArrowRightSpacing();
arrowXOffset = MathUtils.clamp(arrowXOffset, minArrowOffset, maxArrowOffset);
}
// TODO(dtrainor): Figure out how to move the arrow and bubble to make things look better.
// TODO(dtrainor): Figure out how to move the arrow and bubble to make things look
// better.
mDrawable.setPositionProperties(arrowXOffset, positionBelow);
......
......@@ -1907,6 +1907,10 @@ const FeatureEntry kFeatureEntries[] = {
flag_descriptions::kChromeHomePersistentIphName,
flag_descriptions::kChromeHomePersistentIphDescription, kOsAndroid,
FEATURE_VALUE_TYPE(chrome::android::kChromeHomePersistentIph)},
{"enable-chrome-home-pull-to-refresh-iph-at-top",
flag_descriptions::kChromeHomePullToRefreshIphAtTopName,
flag_descriptions::kChromeHomePullToRefreshIphAtTopDescription, kOsAndroid,
FEATURE_VALUE_TYPE(chrome::android::kChromeHomePullToRefreshIphAtTop)},
{"enable-chrome-memex", flag_descriptions::kChromeMemexName,
flag_descriptions::kChromeMemexDescription, kOsAndroid,
FEATURE_VALUE_TYPE(chrome::android::kChromeMemexFeature)},
......
......@@ -73,6 +73,7 @@ const base::Feature* kFeaturesExposedToJava[] = {
&kChromeHomePromo,
&kChromeHomePromoInfoOnly,
&kChromeHomePromoOnStartup,
&kChromeHomePullToRefreshIphAtTop,
&kChromeHomeOptOutSnackbar,
&kChromeHomeShowGoogleGWhenUrlCleared,
&kChromeHomeSurvey,
......@@ -213,6 +214,9 @@ const base::Feature kChromeHomePromoInfoOnly{"ChromeHomePromoInfoOnly",
const base::Feature kChromeHomePromoOnStartup{"ChromeHomePromoOnStartup",
base::FEATURE_ENABLED_BY_DEFAULT};
const base::Feature kChromeHomePullToRefreshIphAtTop{
"ChromeHomePullToRefreshIphAtTop", base::FEATURE_DISABLED_BY_DEFAULT};
const base::Feature kChromeHomeOptOutSnackbar{
"ChromeHomeOptOutSnackbar", base::FEATURE_DISABLED_BY_DEFAULT};
......
......@@ -32,6 +32,7 @@ extern const base::Feature kChromeHomePersistentIph;
extern const base::Feature kChromeHomePromo;
extern const base::Feature kChromeHomePromoInfoOnly;
extern const base::Feature kChromeHomePromoOnStartup;
extern const base::Feature kChromeHomePullToRefreshIphAtTop;
extern const base::Feature kChromeHomeOptOutSnackbar;
extern const base::Feature kChromeHomeShowGoogleGWhenUrlCleared;
extern const base::Feature kChromeHomeSurvey;
......
......@@ -1715,6 +1715,11 @@ const char kChromeHomePromoName[] = "Chrome Home Promo";
const char kChromeHomePromoDescription[] =
"Enable showing the opt-in/out Chrome Home promo.";
const char kChromeHomePullToRefreshIphAtTopName[] =
"Chrome Home Pull-To-Refresh Iph At Top";
const char kChromeHomePullToRefreshIphAtTopDescription[] =
"Show the Chrome Home pull-to-refresh help bubble at the top of the screen";
const char kChromeHomeShowGoogleGName[] = "Chrome Home Show Google G";
const char kChromeHomeShowGoogleGDescription[] =
"Show the Google G when the url is cleared. The flag to clear the url "
......
......@@ -1052,6 +1052,9 @@ extern const char kChromeHomePersonalizedOmniboxSuggestionsDescription[];
extern const char kChromeHomePromoName[];
extern const char kChromeHomePromoDescription[];
extern const char kChromeHomePullToRefreshIphAtTopName[];
extern const char kChromeHomePullToRefreshIphAtTopDescription[];
extern const char kChromeHomeShowGoogleGName[];
extern const char kChromeHomeShowGoogleGDescription[];
......
......@@ -24710,6 +24710,7 @@ from previous Chrome versions.
<int value="-1861814223" label="MidiManagerDynamicInstantiation:enabled"/>
<int value="-1860481724" label="ChromeHomeExpandButton:enabled"/>
<int value="-1856902397" label="LoadingWithMojo:enabled"/>
<int value="-1854432127" label="ChromeHomePullToRefreshIphAtTop:disabled"/>
<int value="-1854372227" label="VrBrowsingExperimentalFeatures:enabled"/>
<int value="-1849706663" label="enable-password-force-saving:disabled"/>
<int value="-1847888049" label="AutofillSendBillingCustomerNumber:enabled"/>
......@@ -24913,6 +24914,7 @@ from previous Chrome versions.
<int value="-1322882747" label="disable-datasaver-prompt"/>
<int value="-1319688939" label="ignore-gpu-blacklist"/>
<int value="-1318914924" label="OverflowIconsForMediaControls:enabled"/>
<int value="-1314603238" label="ChromeHomePullToRefreshIphAtTop:enabled"/>
<int value="-1310737697" label="MaterialDesignSettings:enabled"/>
<int value="-1302904242" label="enable-navigation-tracing"/>
<int value="-1294050129" label="ContentFullscreen:disabled"/>
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment