Commit a35cf8d1 authored by siashah's avatar siashah Committed by Chromium LUCI CQ

Show IPH bubble for chip with item tag set

When an ItemTag  is set on the AutofillBarItem, show an IPH Bubble
displaying the ItemTag text with an arrow pointing to the startIcon.

The item tag IPH bubble is an educational bubble that explains what
the offer icon in a payments chip means.
We'd like to show it to the user once(or maybe 3 times at max) and
then never show it again.
When multiple chips have the item tag set, we want to show the
IPH bubble only for the first one.

Implementation: https://screenshot.googleplex.com/BhxGYNvfWHBckfV
(Note: the Visa icon will be replaced by the offer icon)

Bug: 1156916
Change-Id: Ieaa441b1f5474d11b41004b6326e0144dcd272a6
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2600266
Commit-Queue: Siddharth Shah <siashah@chromium.org>
Reviewed-by: default avatarTheresa  <twellington@chromium.org>
Reviewed-by: default avatarFriedrich [CET] <fhorschig@chromium.org>
Cr-Commit-Position: refs/heads/master@{#842218}
parent 4450fe11
...@@ -6,6 +6,7 @@ package org.chromium.chrome.browser.keyboard_accessory.bar_component; ...@@ -6,6 +6,7 @@ package org.chromium.chrome.browser.keyboard_accessory.bar_component;
import android.view.View; import android.view.View;
import androidx.annotation.Nullable;
import androidx.annotation.StringRes; import androidx.annotation.StringRes;
import org.chromium.chrome.browser.feature_engagement.TrackerFactory; import org.chromium.chrome.browser.feature_engagement.TrackerFactory;
...@@ -41,6 +42,7 @@ class KeyboardAccessoryIPHUtils { ...@@ -41,6 +42,7 @@ class KeyboardAccessoryIPHUtils {
tracker.notifyEvent(EventConstants.KEYBOARD_ACCESSORY_PASSWORD_AUTOFILLED); tracker.notifyEvent(EventConstants.KEYBOARD_ACCESSORY_PASSWORD_AUTOFILLED);
return; return;
case FeatureConstants.KEYBOARD_ACCESSORY_PAYMENT_FILLING_FEATURE: case FeatureConstants.KEYBOARD_ACCESSORY_PAYMENT_FILLING_FEATURE:
case FeatureConstants.KEYBOARD_ACCESSORY_PAYMENT_OFFER_FEATURE:
tracker.notifyEvent(EventConstants.KEYBOARD_ACCESSORY_PAYMENT_AUTOFILLED); tracker.notifyEvent(EventConstants.KEYBOARD_ACCESSORY_PAYMENT_AUTOFILLED);
return; return;
} }
...@@ -74,47 +76,75 @@ class KeyboardAccessoryIPHUtils { ...@@ -74,47 +76,75 @@ class KeyboardAccessoryIPHUtils {
} }
/** /**
* Shows a help bubble pointing to the given view. It contains an appropriate text for the given * Shows a help bubble pointing to the given rect. It contains an appropriate text for the given
* feature. The help bubble will not be shown if the {@link Tracker} doesn't allow it anymore. * feature. The help bubble will not be shown if the {@link Tracker} doesn't allow it anymore.
* This may happen for example: if it was shown too often, too many IPH were triggered this * This may happen for example: if it was shown too often, too many IPH were triggered this
* session or other config restrictions apply. * session or other config restrictions apply.
* @param feature A String identifying the IPH feature and its appropriate help text. * @param feature A String identifying the IPH feature and its appropriate help text.
* @param view The {@link View} providing context and the Rect to which the bubble will point. * @param rectProvider The {@link RectProvider} providing bounds to which the bubble will point.
* @param rootView The {@link View} used to determine the maximal dimensions for the bubble. * @param rootView The {@link View} used to determine the maximal dimensions for the bubble.
*/ */
static void showHelpBubble(String feature, View view, View rootView) { static void showHelpBubble(String feature, RectProvider rectProvider, View rootView) {
TextBubble helpBubble = createBubble(feature, new ViewRectProvider(view), rootView); TextBubble helpBubble = createBubble(feature, rectProvider, rootView, null);
if (helpBubble == null) return; if (helpBubble != null) helpBubble.show();
// To emphasize which chip is pointed to, set selected to true for the built-in highlight.
// Prefer ViewHighlighter for views without a LayerDrawable background.
view.setSelected(true);
helpBubble.addOnDismissListener(() -> { view.setSelected(false); });
helpBubble.show();
} }
/** /**
* Shows a help bubble pointing to the given rect. It contains an appropriate text for the given * Shows a help bubble pointing to the given view. It contains an appropriate text for the given
* feature. The help bubble will not be shown if the {@link Tracker} doesn't allow it anymore. * feature. The help bubble will not be shown if the {@link Tracker} doesn't allow it anymore.
* This may happen for example: if it was shown too often, too many IPH were triggered this * This may happen for example: if it was shown too often, too many IPH were triggered this
* session or other config restrictions apply. * session or other config restrictions apply.
* @param feature A String identifying the IPH feature and its appropriate help text. * @param feature A String identifying the IPH feature and its appropriate help text.
* @param helpText String that should be displayed within the IPH bubble.
* @param rectProvider The {@link RectProvider} providing bounds to which the bubble will point. * @param rectProvider The {@link RectProvider} providing bounds to which the bubble will point.
* @param rootView The {@link View} used to determine the maximal dimensions for the bubble. * @param rootView The {@link View} used to determine the maximal dimensions for the bubble.
*/ */
static void showHelpBubble(String feature, RectProvider rectProvider, View rootView) { static void showHelpBubble(
TextBubble helpBubble = createBubble(feature, rectProvider, rootView); String feature, RectProvider rectProvider, View rootView, @Nullable String helpText) {
TextBubble helpBubble = createBubble(feature, rectProvider, rootView, helpText);
if (helpBubble != null) helpBubble.show(); if (helpBubble != null) helpBubble.show();
} }
/**
* Shows a help bubble pointing to the given view. It contains an appropriate text for the given
* feature. The help bubble will not be shown if the {@link Tracker} doesn't allow it anymore.
* This may happen for example: if it was shown too often, too many IPH were triggered this
* session or other config restrictions apply.
* @param feature A String identifying the IPH feature and its appropriate help text.
* @param helpText String that should be displayed within the IPH bubble.
* @param view The {@link View} providing context and the Rect to which the bubble will point.
* @param rootView The {@link View} used to determine the maximal dimensions for the bubble.
*/
static void showHelpBubble(
String feature, View view, View rootView, @Nullable String helpText) {
TextBubble helpBubble =
createBubble(feature, new ViewRectProvider(view), rootView, helpText);
if (helpBubble == null) return;
// To emphasize which chip is pointed to, set selected to true for the built-in highlight.
// Prefer ViewHighlighter for views without a LayerDrawable background.
view.setSelected(true);
helpBubble.addOnDismissListener(() -> { view.setSelected(false); });
helpBubble.show();
}
private static TextBubble createBubble( private static TextBubble createBubble(
String feature, RectProvider rectProvider, View rootView) { String feature, RectProvider rectProvider, View rootView, @Nullable String helpText) {
final Tracker tracker = getTrackerFromProfile(); final Tracker tracker = getTrackerFromProfile();
if (tracker == null) return null; if (tracker == null) return null;
if (!tracker.shouldTriggerHelpUI(feature)) return null; // This call records the IPH intent. if (!tracker.shouldTriggerHelpUI(feature)) return null; // This call records the IPH intent.
@StringRes TextBubble helpBubble;
int helpText = getHelpTextForFeature(feature); // If the help text is provided, then use it directly to generate the text bubble.
TextBubble helpBubble = new TextBubble(rootView.getContext(), rootView, helpText, helpText, if (helpText != null && !helpText.isEmpty()) {
rectProvider, ChromeAccessibilityUtil.get().isAccessibilityEnabled()); helpBubble = new TextBubble(rootView.getContext(), rootView, helpText, helpText,
/* showArrow= */ true, rectProvider,
ChromeAccessibilityUtil.get().isAccessibilityEnabled());
} else {
@StringRes
int helpTextResourceId = getHelpTextForFeature(feature);
helpBubble = new TextBubble(rootView.getContext(), rootView, helpTextResourceId,
helpTextResourceId, rectProvider,
ChromeAccessibilityUtil.get().isAccessibilityEnabled());
}
helpBubble.setDismissOnTouchInteraction(true); helpBubble.setDismissOnTouchInteraction(true);
helpBubble.addOnDismissListener(() -> { tracker.dismissed(feature); }); helpBubble.addOnDismissListener(() -> { tracker.dismissed(feature); });
return helpBubble; return helpBubble;
......
...@@ -329,6 +329,10 @@ class KeyboardAccessoryMediator ...@@ -329,6 +329,10 @@ class KeyboardAccessoryMediator
return FeatureConstants.KEYBOARD_ACCESSORY_PASSWORD_FILLING_FEATURE; return FeatureConstants.KEYBOARD_ACCESSORY_PASSWORD_FILLING_FEATURE;
} }
if (containsCreditCardInfo(suggestion)) { if (containsCreditCardInfo(suggestion)) {
if (!suggestion.getItemTag().isEmpty()) {
// Prefer showing a linked cashback over the general IPH.
return FeatureConstants.KEYBOARD_ACCESSORY_PAYMENT_OFFER_FEATURE;
}
return FeatureConstants.KEYBOARD_ACCESSORY_PAYMENT_FILLING_FEATURE; return FeatureConstants.KEYBOARD_ACCESSORY_PAYMENT_FILLING_FEATURE;
} }
if (containsAddressInfo(suggestion)) { if (containsAddressInfo(suggestion)) {
......
...@@ -57,14 +57,26 @@ class KeyboardAccessoryModernViewBinder { ...@@ -57,14 +57,26 @@ class KeyboardAccessoryModernViewBinder {
@Override @Override
protected void bind(AutofillBarItem item, ChipView chipView) { protected void bind(AutofillBarItem item, ChipView chipView) {
int iconId = item.getSuggestion().getIconId();
if (item.getFeatureForIPH() != null) { if (item.getFeatureForIPH() != null) {
showHelpBubble(item.getFeatureForIPH(), chipView, mRootViewForIPH); if (item.getFeatureForIPH().equals(
FeatureConstants.KEYBOARD_ACCESSORY_PAYMENT_OFFER_FEATURE)
&& iconId != 0) {
if (iconId != 0) {
showHelpBubble(item.getFeatureForIPH(), chipView.getStartIconViewRect(),
mRootViewForIPH, item.getSuggestion().getItemTag());
} else {
showHelpBubble(item.getFeatureForIPH(), chipView, mRootViewForIPH,
item.getSuggestion().getItemTag());
}
} else {
showHelpBubble(item.getFeatureForIPH(), chipView, mRootViewForIPH, null);
}
} }
chipView.getPrimaryTextView().setText(item.getSuggestion().getLabel()); chipView.getPrimaryTextView().setText(item.getSuggestion().getLabel());
chipView.getSecondaryTextView().setText(item.getSuggestion().getSublabel()); chipView.getSecondaryTextView().setText(item.getSuggestion().getSublabel());
chipView.getSecondaryTextView().setVisibility( chipView.getSecondaryTextView().setVisibility(
item.getSuggestion().getSublabel().isEmpty() ? View.GONE : View.VISIBLE); item.getSuggestion().getSublabel().isEmpty() ? View.GONE : View.VISIBLE);
int iconId = item.getSuggestion().getIconId();
chipView.setIcon(iconId != 0 ? iconId : ChipView.INVALID_ICON_ID, false); chipView.setIcon(iconId != 0 ? iconId : ChipView.INVALID_ICON_ID, false);
KeyboardAccessoryData.Action action = item.getAction(); KeyboardAccessoryData.Action action = item.getAction();
assert action != null : "Tried to bind item without action. Chose a wrong ViewHolder?"; assert action != null : "Tried to bind item without action. Chose a wrong ViewHolder?";
......
...@@ -360,6 +360,33 @@ public class KeyboardAccessoryModernViewTest { ...@@ -360,6 +360,33 @@ public class KeyboardAccessoryModernViewTest {
assertThat(tracker.wasDismissed(), is(true)); assertThat(tracker.wasDismissed(), is(true));
} }
@Test
@MediumTest
public void testDismissesPaymentOfferEducationBubbleOnFilling() {
String itemTag = "Cashback linked";
AutofillBarItem itemWithIPH = new AutofillBarItem(
new AutofillSuggestion("Johnathan", "Smith", itemTag, R.drawable.ic_offer_tag_green,
false, 70000, false, false, false),
new KeyboardAccessoryData.Action("", AUTOFILL_SUGGESTION, unused -> {}));
itemWithIPH.setFeatureForIPH(FeatureConstants.KEYBOARD_ACCESSORY_PAYMENT_OFFER_FEATURE);
TestTracker tracker = new TestTracker();
TrackerFactory.setTrackerForTests(tracker);
TestThreadUtils.runOnUiThreadBlocking(() -> {
mModel.set(VISIBLE, true);
mModel.get(BAR_ITEMS).set(new BarItem[] {itemWithIPH, createTabs()});
});
onViewWaiting(withText("Johnathan"));
waitForHelpBubble(withText(itemTag));
onView(withText("Johnathan")).perform(click());
assertThat(tracker.wasDismissed(), is(true));
assertThat(tracker.getLastEmittedEvent(),
is(EventConstants.KEYBOARD_ACCESSORY_PAYMENT_AUTOFILLED));
}
@Test @Test
@MediumTest @MediumTest
public void testNotifiesAboutPartiallyVisibleSuggestions() throws InterruptedException { public void testNotifiesAboutPartiallyVisibleSuggestions() throws InterruptedException {
......
...@@ -30,6 +30,7 @@ import java.lang.annotation.RetentionPolicy; ...@@ -30,6 +30,7 @@ import java.lang.annotation.RetentionPolicy;
FeatureConstants.KEYBOARD_ACCESSORY_BAR_SWIPING_FEATURE, FeatureConstants.KEYBOARD_ACCESSORY_BAR_SWIPING_FEATURE,
FeatureConstants.KEYBOARD_ACCESSORY_PASSWORD_FILLING_FEATURE, FeatureConstants.KEYBOARD_ACCESSORY_PASSWORD_FILLING_FEATURE,
FeatureConstants.KEYBOARD_ACCESSORY_PAYMENT_FILLING_FEATURE, FeatureConstants.KEYBOARD_ACCESSORY_PAYMENT_FILLING_FEATURE,
FeatureConstants.KEYBOARD_ACCESSORY_PAYMENT_OFFER_FEATURE,
FeatureConstants.DOWNLOAD_SETTINGS_FEATURE, FeatureConstants.DOWNLOAD_SETTINGS_FEATURE,
FeatureConstants.DOWNLOAD_INFOBAR_DOWNLOAD_CONTINUING_FEATURE, FeatureConstants.DOWNLOAD_INFOBAR_DOWNLOAD_CONTINUING_FEATURE,
FeatureConstants.DOWNLOAD_INFOBAR_DOWNLOADS_ARE_FASTER_FEATURE, FeatureConstants.DOWNLOAD_INFOBAR_DOWNLOADS_ARE_FASTER_FEATURE,
...@@ -60,6 +61,7 @@ public @interface FeatureConstants { ...@@ -60,6 +61,7 @@ public @interface FeatureConstants {
String KEYBOARD_ACCESSORY_ADDRESS_FILL_FEATURE = "IPH_KeyboardAccessoryAddressFilling"; String KEYBOARD_ACCESSORY_ADDRESS_FILL_FEATURE = "IPH_KeyboardAccessoryAddressFilling";
String KEYBOARD_ACCESSORY_PASSWORD_FILLING_FEATURE = "IPH_KeyboardAccessoryPasswordFilling"; String KEYBOARD_ACCESSORY_PASSWORD_FILLING_FEATURE = "IPH_KeyboardAccessoryPasswordFilling";
String KEYBOARD_ACCESSORY_PAYMENT_FILLING_FEATURE = "IPH_KeyboardAccessoryPaymentFilling"; String KEYBOARD_ACCESSORY_PAYMENT_FILLING_FEATURE = "IPH_KeyboardAccessoryPaymentFilling";
String KEYBOARD_ACCESSORY_PAYMENT_OFFER_FEATURE = "IPH_KeyboardAccessoryPaymentOffer";
String KEYBOARD_ACCESSORY_BAR_SWIPING_FEATURE = "IPH_KeyboardAccessoryBarSwiping"; String KEYBOARD_ACCESSORY_BAR_SWIPING_FEATURE = "IPH_KeyboardAccessoryBarSwiping";
String PREVIEWS_OMNIBOX_UI_FEATURE = "IPH_PreviewsOmniboxUI"; String PREVIEWS_OMNIBOX_UI_FEATURE = "IPH_PreviewsOmniboxUI";
String TRANSLATE_MENU_BUTTON_FEATURE = "IPH_TranslateMenuButton"; String TRANSLATE_MENU_BUTTON_FEATURE = "IPH_TranslateMenuButton";
......
...@@ -99,6 +99,8 @@ const base::Feature kIPHKeyboardAccessoryPasswordFillingFeature{ ...@@ -99,6 +99,8 @@ const base::Feature kIPHKeyboardAccessoryPasswordFillingFeature{
"IPH_KeyboardAccessoryPasswordFilling", base::FEATURE_DISABLED_BY_DEFAULT}; "IPH_KeyboardAccessoryPasswordFilling", base::FEATURE_DISABLED_BY_DEFAULT};
const base::Feature kIPHKeyboardAccessoryPaymentFillingFeature{ const base::Feature kIPHKeyboardAccessoryPaymentFillingFeature{
"IPH_KeyboardAccessoryPaymentFilling", base::FEATURE_DISABLED_BY_DEFAULT}; "IPH_KeyboardAccessoryPaymentFilling", base::FEATURE_DISABLED_BY_DEFAULT};
const base::Feature kIPHKeyboardAccessoryPaymentOfferFeature{
"IPH_KeyboardAccessoryPaymentOffer", base::FEATURE_DISABLED_BY_DEFAULT};
const base::Feature kIPHNewTabPageHomeButtonFeature{ const base::Feature kIPHNewTabPageHomeButtonFeature{
"IPH_NewTabPageHomeButton", base::FEATURE_DISABLED_BY_DEFAULT}; "IPH_NewTabPageHomeButton", base::FEATURE_DISABLED_BY_DEFAULT};
const base::Feature kIPHPreviewsOmniboxUIFeature{ const base::Feature kIPHPreviewsOmniboxUIFeature{
......
...@@ -62,6 +62,7 @@ extern const base::Feature kIPHKeyboardAccessoryAddressFillingFeature; ...@@ -62,6 +62,7 @@ extern const base::Feature kIPHKeyboardAccessoryAddressFillingFeature;
extern const base::Feature kIPHKeyboardAccessoryBarSwipingFeature; extern const base::Feature kIPHKeyboardAccessoryBarSwipingFeature;
extern const base::Feature kIPHKeyboardAccessoryPasswordFillingFeature; extern const base::Feature kIPHKeyboardAccessoryPasswordFillingFeature;
extern const base::Feature kIPHKeyboardAccessoryPaymentFillingFeature; extern const base::Feature kIPHKeyboardAccessoryPaymentFillingFeature;
extern const base::Feature kIPHKeyboardAccessoryPaymentOfferFeature;
extern const base::Feature kIPHNewTabPageHomeButtonFeature; extern const base::Feature kIPHNewTabPageHomeButtonFeature;
extern const base::Feature kIPHPreviewsOmniboxUIFeature; extern const base::Feature kIPHPreviewsOmniboxUIFeature;
extern const base::Feature kIPHQuietNotificationPromptsFeature; extern const base::Feature kIPHQuietNotificationPromptsFeature;
......
...@@ -45,6 +45,7 @@ const base::Feature* const kAllFeatures[] = { ...@@ -45,6 +45,7 @@ const base::Feature* const kAllFeatures[] = {
&kIPHKeyboardAccessoryBarSwipingFeature, &kIPHKeyboardAccessoryBarSwipingFeature,
&kIPHKeyboardAccessoryPasswordFillingFeature, &kIPHKeyboardAccessoryPasswordFillingFeature,
&kIPHKeyboardAccessoryPaymentFillingFeature, &kIPHKeyboardAccessoryPaymentFillingFeature,
&kIPHKeyboardAccessoryPaymentOfferFeature,
&kIPHNewTabPageHomeButtonFeature, &kIPHNewTabPageHomeButtonFeature,
&kIPHPreviewsOmniboxUIFeature, &kIPHPreviewsOmniboxUIFeature,
&kIPHPwaInstallAvailableFeature, &kIPHPwaInstallAvailableFeature,
......
...@@ -93,6 +93,8 @@ DEFINE_VARIATION_PARAM(kIPHKeyboardAccessoryPasswordFillingFeature, ...@@ -93,6 +93,8 @@ DEFINE_VARIATION_PARAM(kIPHKeyboardAccessoryPasswordFillingFeature,
"IPH_KeyboardAccessoryPasswordFilling"); "IPH_KeyboardAccessoryPasswordFilling");
DEFINE_VARIATION_PARAM(kIPHKeyboardAccessoryPaymentFillingFeature, DEFINE_VARIATION_PARAM(kIPHKeyboardAccessoryPaymentFillingFeature,
"IPH_KeyboardAccessoryPaymentFilling"); "IPH_KeyboardAccessoryPaymentFilling");
DEFINE_VARIATION_PARAM(kIPHKeyboardAccessoryPaymentOfferFeature,
"IPH_KeyboardAccessoryPaymentOffer");
DEFINE_VARIATION_PARAM(kIPHNewTabPageButtonFeature, "IPH_NewTabPageHomeButton"); DEFINE_VARIATION_PARAM(kIPHNewTabPageButtonFeature, "IPH_NewTabPageHomeButton");
DEFINE_VARIATION_PARAM(kIPHPreviewsOmniboxUIFeature, "IPH_PreviewsOmniboxUI"); DEFINE_VARIATION_PARAM(kIPHPreviewsOmniboxUIFeature, "IPH_PreviewsOmniboxUI");
DEFINE_VARIATION_PARAM(kIPHPwaInstallAvailableFeature, DEFINE_VARIATION_PARAM(kIPHPwaInstallAvailableFeature,
...@@ -196,6 +198,7 @@ constexpr flags_ui::FeatureEntry::FeatureVariation ...@@ -196,6 +198,7 @@ constexpr flags_ui::FeatureEntry::FeatureVariation
VARIATION_ENTRY(kIPHKeyboardAccessoryBarSwipingFeature), VARIATION_ENTRY(kIPHKeyboardAccessoryBarSwipingFeature),
VARIATION_ENTRY(kIPHKeyboardAccessoryPasswordFillingFeature), VARIATION_ENTRY(kIPHKeyboardAccessoryPasswordFillingFeature),
VARIATION_ENTRY(kIPHKeyboardAccessoryPaymentFillingFeature), VARIATION_ENTRY(kIPHKeyboardAccessoryPaymentFillingFeature),
VARIATION_ENTRY(kIPHKeyboardAccessoryPaymentOfferFeature),
VARIATION_ENTRY(kIPHNewTabPageButtonFeature), VARIATION_ENTRY(kIPHNewTabPageButtonFeature),
VARIATION_ENTRY(kIPHPreviewsOmniboxUIFeature), VARIATION_ENTRY(kIPHPreviewsOmniboxUIFeature),
VARIATION_ENTRY(kIPHPwaInstallAvailableFeature), VARIATION_ENTRY(kIPHPwaInstallAvailableFeature),
......
...@@ -274,6 +274,14 @@ public class ChipView extends LinearLayout { ...@@ -274,6 +274,14 @@ public class ChipView extends LinearLayout {
return mSecondaryText; return mSecondaryText;
} }
/**
* Returns the {@link RectProvider} that contains the start icon for the chip view.
* @return A {@link RectProvider}
*/
public RectProvider getStartIconViewRect() {
return new ViewRectProvider(mStartIcon);
}
/** /**
* Sets the correct tinting on the Chip's image view. * Sets the correct tinting on the Chip's image view.
* @param tintWithTextColor If true then the image view will be tinted with the primary text * @param tintWithTextColor If true then the image view will be tinted with the primary text
......
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