Commit fff95d0a authored by Patrick Noland's avatar Patrick Noland Committed by Commit Bot

[ToolbarMVC] Move generic IPH code to UserEducationHelper

This CL also moves some custom IPH code from ToolbarManager to
ToolbarButtonInProductHelpController.

Bug: 865801
Change-Id: I49ea9a8954c17be39d99e598208a5b4e78f9fa31
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/1995466
Commit-Queue: Patrick Noland <pnoland@chromium.org>
Reviewed-by: default avatarTheresa  <twellington@chromium.org>
Reviewed-by: default avatarDavid Trainor <dtrainor@chromium.org>
Cr-Commit-Position: refs/heads/master@{#734048}
parent c6f2eadb
......@@ -1778,6 +1778,9 @@ chrome_java_sources = [
"java/src/org/chromium/chrome/browser/usage_stats/UsageStatsMetricsReporter.java",
"java/src/org/chromium/chrome/browser/usage_stats/UsageStatsService.java",
"java/src/org/chromium/chrome/browser/usage_stats/WebsiteEvent.java",
"java/src/org/chromium/chrome/browser/user_education/IPHCommand.java",
"java/src/org/chromium/chrome/browser/user_education/IPHCommandBuilder.java",
"java/src/org/chromium/chrome/browser/user_education/UserEducationHelper.java",
"java/src/org/chromium/chrome/browser/vr/ArDelegate.java",
"java/src/org/chromium/chrome/browser/vr/ArDelegateProvider.java",
"java/src/org/chromium/chrome/browser/webapps/ActivateWebApkActivity.java",
......
......@@ -73,9 +73,6 @@ import org.chromium.chrome.browser.dom_distiller.ReaderModeManager;
import org.chromium.chrome.browser.download.DownloadOpenSource;
import org.chromium.chrome.browser.download.DownloadUtils;
import org.chromium.chrome.browser.feature_engagement.ScreenshotMonitor;
import org.chromium.chrome.browser.feature_engagement.ScreenshotMonitorDelegate;
import org.chromium.chrome.browser.feature_engagement.ScreenshotTabObserver;
import org.chromium.chrome.browser.feature_engagement.TrackerFactory;
import org.chromium.chrome.browser.feed.FeedProcessScopeFactory;
import org.chromium.chrome.browser.firstrun.FirstRunSignInProcessor;
import org.chromium.chrome.browser.flags.ActivityType;
......@@ -153,9 +150,6 @@ import org.chromium.chrome.browser.util.UrlConstants;
import org.chromium.chrome.browser.vr.VrModuleProvider;
import org.chromium.chrome.browser.widget.bottomsheet.EmptyBottomSheetObserver;
import org.chromium.chrome.features.start_surface.StartSurface;
import org.chromium.components.feature_engagement.EventConstants;
import org.chromium.components.feature_engagement.FeatureConstants;
import org.chromium.components.feature_engagement.Tracker;
import org.chromium.content_public.browser.LoadUrlParams;
import org.chromium.content_public.browser.NavigationHandle;
import org.chromium.content_public.browser.UiThreadTaskTraits;
......@@ -175,7 +169,7 @@ import java.util.Locale;
* This is the main activity for ChromeMobile when not running in document mode. All the tabs
* are accessible via a chrome specific tab switching UI.
*/
public class ChromeTabbedActivity extends ChromeActivity implements ScreenshotMonitorDelegate {
public class ChromeTabbedActivity extends ChromeActivity {
private static final String TAG = "ChromeTabbedActivity";
private static final String HELP_URL_PREFIX = "https://support.google.com/chrome/";
......@@ -718,7 +712,7 @@ public class ChromeTabbedActivity extends ChromeActivity implements ScreenshotMo
mOverviewModeController.hideOverview(false);
mScreenshotMonitor = new ScreenshotMonitor(ChromeTabbedActivity.this);
mScreenshotMonitor = new ScreenshotMonitor(getToolbarButtonInProductHelpController());
}
}
......@@ -2353,22 +2347,6 @@ public class ChromeTabbedActivity extends ChromeActivity implements ScreenshotMo
isIncognito ? "new-incognito-tab-shortcut" : "new-tab-shortcut");
}
@Override
public void onScreenshotTaken() {
Tracker tracker = TrackerFactory.getTrackerForProfile(Profile.getLastUsedProfile());
tracker.notifyEvent(EventConstants.SCREENSHOT_TAKEN_CHROME_IN_FOREGROUND);
PostTask.postTask(UiThreadTaskTraits.DEFAULT, new Runnable() {
@Override
public void run() {
getToolbarManager().showDownloadPageTextBubble(
getActivityTab(), FeatureConstants.DOWNLOAD_PAGE_SCREENSHOT_FEATURE);
ScreenshotTabObserver tabObserver = ScreenshotTabObserver.from(getActivityTab());
if (tabObserver != null) tabObserver.onScreenshotTaken();
}
});
}
@VisibleForTesting
public MultiInstanceManager getMultiInstanceMangerForTesting() {
return mMultiInstanceManager;
......
......@@ -30,7 +30,6 @@ import org.chromium.chrome.browser.infobar.IPHInfoBarSupport;
import org.chromium.chrome.browser.infobar.InfoBar;
import org.chromium.chrome.browser.infobar.InfoBarContainer;
import org.chromium.chrome.browser.infobar.InfoBarIdentifier;
import org.chromium.chrome.browser.profiles.Profile;
import org.chromium.chrome.browser.tab.Tab;
import org.chromium.chrome.browser.tab.TabImpl;
import org.chromium.components.download.DownloadState;
......@@ -851,9 +850,7 @@ public class DownloadInfoBarController implements OfflineContentProvider.Observe
}
ChromeTabbedActivity activity = (ChromeTabbedActivity) getActivity();
Profile profile = mIsIncognito ? Profile.getLastUsedProfile().getOffTheRecordProfile()
: Profile.getLastUsedProfile().getOriginalProfile();
activity.getToolbarButtonInProductHelpController().maybeShowDownloadContinuingIPH(profile);
activity.getToolbarButtonInProductHelpController().showDownloadContinuingIPH();
}
@Nullable
......
......@@ -153,7 +153,7 @@ public class TabbedRootUiCoordinator extends RootUiCoordinator implements Native
mToolbarButtonInProductHelpController =
new ToolbarButtonInProductHelpController(mActivity, mAppMenuCoordinator);
if (!triggerPromo()) {
mToolbarButtonInProductHelpController.maybeShowColdStartIPH();
mToolbarButtonInProductHelpController.showColdStartIPH();
}
}
......
// Copyright 2018 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
package org.chromium.chrome.browser.toolbar;
import android.view.View;
import androidx.annotation.Nullable;
import androidx.annotation.StringRes;
import org.chromium.base.Callback;
import org.chromium.base.task.PostTask;
import org.chromium.chrome.R;
import org.chromium.chrome.browser.ActivityTabProvider.ActivityTabTabObserver;
import org.chromium.chrome.browser.ChromeActivity;
import org.chromium.chrome.browser.ChromeTabbedActivity;
import org.chromium.chrome.browser.datareduction.DataReductionSavingsMilestonePromo;
import org.chromium.chrome.browser.download.DownloadUtils;
import org.chromium.chrome.browser.feature_engagement.ScreenshotMonitorDelegate;
import org.chromium.chrome.browser.feature_engagement.ScreenshotTabObserver;
import org.chromium.chrome.browser.feature_engagement.TrackerFactory;
import org.chromium.chrome.browser.flags.FeatureUtilities;
import org.chromium.chrome.browser.net.spdyproxy.DataReductionProxySettings;
import org.chromium.chrome.browser.offlinepages.OfflinePageBridge;
import org.chromium.chrome.browser.previews.Previews;
import org.chromium.chrome.browser.profiles.Profile;
import org.chromium.chrome.browser.tab.Tab;
import org.chromium.chrome.browser.tab.TabImpl;
import org.chromium.chrome.browser.translate.TranslateBridge;
import org.chromium.chrome.browser.translate.TranslateUtils;
import org.chromium.chrome.browser.ui.appmenu.AppMenuCoordinator;
import org.chromium.chrome.browser.ui.appmenu.AppMenuHandler;
import org.chromium.chrome.browser.ui.widget.highlight.ViewHighlighter;
import org.chromium.chrome.browser.ui.widget.textbubble.TextBubble;
import org.chromium.chrome.browser.ui.appmenu.AppMenuPropertiesDelegate;
import org.chromium.chrome.browser.user_education.IPHCommandBuilder;
import org.chromium.chrome.browser.user_education.UserEducationHelper;
import org.chromium.components.feature_engagement.EventConstants;
import org.chromium.components.feature_engagement.FeatureConstants;
import org.chromium.components.feature_engagement.Tracker;
import org.chromium.content_public.browser.UiThreadTaskTraits;
import org.chromium.ui.widget.ViewRectProvider;
/**
* A helper class for IPH shown on the toolbar.
* TODO(https://crbug.com/865801): Remove feature-specific IPH from here.
*/
public class ToolbarButtonInProductHelpController {
public class ToolbarButtonInProductHelpController implements ScreenshotMonitorDelegate {
private final ActivityTabTabObserver mPageLoadObserver;
private final ChromeActivity mActivity;
private final AppMenuPropertiesDelegate mAppMenuPropertiesDelegate;
private AppMenuHandler mAppMenuHandler;
private UserEducationHelper mUserEducationHelper;
public ToolbarButtonInProductHelpController(
final ChromeActivity activity, AppMenuCoordinator appMenuCoordinator) {
mActivity = activity;
mUserEducationHelper = new UserEducationHelper(mActivity);
mPageLoadObserver = new ActivityTabTabObserver(activity.getActivityTabProvider()) {
/**
* Stores total data saved at the start of a page load. Used to calculate delta at the
......@@ -61,6 +65,15 @@ public class ToolbarButtonInProductHelpController {
@Override
public void onPageLoadFinished(Tab tab, String url) {
if (tab.isShowingErrorPage()) {
handleIPHForErrorPageShown(tab);
return;
}
handleIPHForSuccessfulPageLoad(tab);
}
private void handleIPHForSuccessfulPageLoad(final Tab tab) {
long dataSaved = DataReductionProxySettings.getInstance()
.getContentLengthSavedInHistorySummary()
- mDataSavedOnStartPageLoad;
......@@ -69,15 +82,36 @@ public class ToolbarButtonInProductHelpController {
if (Previews.isPreview(tab)) {
tracker.notifyEvent(EventConstants.PREVIEWS_PAGE_LOADED);
}
if (tab.isUserInteractable()) {
maybeShowDataSaverDetail();
if (dataSaved > 0L) maybeShowDataSaverMilestonePromo();
if (Previews.isPreview(tab)) maybeShowPreviewVerboseStatus();
showDataSaverDetail();
if (dataSaved > 0L) showDataSaverMilestonePromo();
if (Previews.isPreview(tab)) showPreviewVerboseStatus();
}
showDownloadPageTextBubble(tab, FeatureConstants.DOWNLOAD_PAGE_FEATURE);
showTranslateMenuButtonTextBubble(tab);
}
private void handleIPHForErrorPageShown(Tab tab) {
if (!(mActivity instanceof ChromeTabbedActivity) || mActivity.isTablet()) {
return;
}
OfflinePageBridge bridge =
OfflinePageBridge.getForProfile(((TabImpl) tab).getProfile());
if (bridge == null
|| !bridge.isShowingDownloadButtonInErrorPage(tab.getWebContents())) {
return;
}
Tracker tracker = TrackerFactory.getTrackerForProfile(((TabImpl) tab).getProfile());
tracker.notifyEvent(EventConstants.USER_HAS_SEEN_DINO);
}
};
mAppMenuHandler = appMenuCoordinator.getAppMenuHandler();
mAppMenuPropertiesDelegate = appMenuCoordinator.getAppMenuPropertiesDelegate();
}
public void destroy() {
......@@ -86,27 +120,68 @@ public class ToolbarButtonInProductHelpController {
}
}
/**
* Attempts to show an IPH text bubble for download continuing.
*/
public void showDownloadContinuingIPH() {
mUserEducationHelper.requestShowIPH(
new IPHCommandBuilder(mActivity.getResources(),
FeatureConstants.DOWNLOAD_INFOBAR_DOWNLOAD_CONTINUING_FEATURE,
R.string.iph_download_infobar_download_continuing_text,
R.string.iph_download_infobar_download_continuing_text)
.setAnchorView(mActivity.getToolbarManager().getMenuButtonView())
.setOnShowCallback(
() -> turnOnHighlightForMenuItem(R.id.downloads_menu_id, true))
.setOnDismissCallback(this::turnOffHighlightForMenuItem)
.build());
}
/**
* Attempts to show an IPH text bubble for those that trigger on a cold start.
*/
public void showColdStartIPH() {
showDownloadHomeIPH();
}
// Overridden public methods.
@Override
public void onScreenshotTaken() {
Tracker tracker = TrackerFactory.getTrackerForProfile(Profile.getLastUsedProfile());
tracker.notifyEvent(EventConstants.SCREENSHOT_TAKEN_CHROME_IN_FOREGROUND);
PostTask.postTask(UiThreadTaskTraits.DEFAULT, () -> {
showDownloadPageTextBubble(
mActivity.getActivityTab(), FeatureConstants.DOWNLOAD_PAGE_SCREENSHOT_FEATURE);
ScreenshotTabObserver tabObserver =
ScreenshotTabObserver.from(mActivity.getActivityTab());
if (tabObserver != null) tabObserver.onScreenshotTaken();
});
}
// Private methods.
private static int getDataReductionMenuItemHighlight() {
return FeatureUtilities.isBottomToolbarEnabled() ? R.id.data_reduction_menu_item
: R.id.app_menu_footer;
}
// Attempts to show an IPH text bubble for data saver detail.
private void maybeShowDataSaverDetail() {
View anchorView = mActivity.getToolbarManager().getMenuButtonView();
if (anchorView == null) return;
setupAndMaybeShowIPHForFeature(FeatureConstants.DATA_SAVER_DETAIL_FEATURE,
getDataReductionMenuItemHighlight(), false, R.string.iph_data_saver_detail_text,
R.string.iph_data_saver_detail_accessibility_text, anchorView,
Profile.getLastUsedProfile(), null);
private void showDataSaverDetail() {
mUserEducationHelper.requestShowIPH(
new IPHCommandBuilder(mActivity.getResources(),
FeatureConstants.DATA_SAVER_DETAIL_FEATURE,
R.string.iph_data_saver_detail_text,
R.string.iph_data_saver_detail_accessibility_text)
.setAnchorView(mActivity.getToolbarManager().getMenuButtonView())
.setOnShowCallback(
()
-> turnOnHighlightForMenuItem(
getDataReductionMenuItemHighlight(), false))
.setOnDismissCallback(this::turnOffHighlightForMenuItem)
.build());
}
// Attempts to show an IPH text bubble for data saver milestone promo.
private void maybeShowDataSaverMilestonePromo() {
View anchorView = mActivity.getToolbarManager().getMenuButtonView();
if (anchorView == null) return;
private void showDataSaverMilestonePromo() {
final DataReductionSavingsMilestonePromo promo =
new DataReductionSavingsMilestonePromo(mActivity,
DataReductionProxySettings.getInstance().getTotalHttpContentLengthSaved());
......@@ -114,137 +189,109 @@ public class ToolbarButtonInProductHelpController {
final Runnable dismissCallback = () -> {
promo.onPromoTextSeen();
turnOffHighlightForMenuItem();
};
setupAndMaybeShowIPHForFeature(FeatureConstants.DATA_SAVER_MILESTONE_PROMO_FEATURE,
getDataReductionMenuItemHighlight(), false, promo.getPromoText(),
promo.getPromoText(), anchorView, Profile.getLastUsedProfile(), dismissCallback);
}
// Attempts to show an IPH text bubble for page in preview mode.
private void maybeShowPreviewVerboseStatus() {
final View anchorView = mActivity.getToolbarManager().getSecurityIconView();
if (anchorView == null) return;
setupAndMaybeShowIPHForFeature(FeatureConstants.PREVIEWS_OMNIBOX_UI_FEATURE, null, true,
R.string.iph_previews_omnibox_ui_text,
R.string.iph_previews_omnibox_ui_accessibility_text, anchorView,
Profile.getLastUsedProfile(), null);
mUserEducationHelper.requestShowIPH(
new IPHCommandBuilder(mActivity.getResources(),
FeatureConstants.DATA_SAVER_MILESTONE_PROMO_FEATURE, promo.getPromoText(),
promo.getPromoText())
.setAnchorView(mActivity.getToolbarManager().getMenuButtonView())
.setOnShowCallback(
()
-> turnOnHighlightForMenuItem(
getDataReductionMenuItemHighlight(), false))
.setOnDismissCallback(dismissCallback)
.build());
}
/**
* Attempts to show an IPH text bubble for those that trigger on a cold start.
*/
public void maybeShowColdStartIPH() {
maybeShowDownloadHomeIPH();
// Attempts to show an IPH text bubble for page in preview mode.
private void showPreviewVerboseStatus() {
mUserEducationHelper.requestShowIPH(
new IPHCommandBuilder(mActivity.getResources(),
FeatureConstants.PREVIEWS_OMNIBOX_UI_FEATURE,
R.string.iph_previews_omnibox_ui_text,
R.string.iph_previews_omnibox_ui_accessibility_text)
.setAnchorView(mActivity.getToolbarManager().getSecurityIconView())
.setShouldHighlight(false)
.build());
}
private void maybeShowDownloadHomeIPH() {
setupAndMaybeShowIPHForFeature(FeatureConstants.DOWNLOAD_HOME_FEATURE,
R.id.downloads_menu_id, true, R.string.iph_download_home_text,
R.string.iph_download_home_accessibility_text,
mActivity.getToolbarManager().getMenuButtonView(), Profile.getLastUsedProfile(),
null);
private void showDownloadHomeIPH() {
mUserEducationHelper.requestShowIPH(
new IPHCommandBuilder(mActivity.getResources(),
FeatureConstants.DOWNLOAD_HOME_FEATURE, R.string.iph_download_home_text,
R.string.iph_download_home_accessibility_text)
.setAnchorView(mActivity.getToolbarManager().getMenuButtonView())
.setOnShowCallback(
() -> turnOnHighlightForMenuItem(R.id.downloads_menu_id, true))
.setOnDismissCallback(this::turnOffHighlightForMenuItem)
.build());
}
/**
* Attempts to show an IPH text bubble for download continuing.
* @param window The window to use for the IPH.
* @param profile The profile to use for the tracker.
* Show the download page in-product-help bubble. Also used by download page screenshot IPH.
* @param tab The current tab.
*/
public void maybeShowDownloadContinuingIPH(Profile profile) {
setupAndMaybeShowIPHForFeature(
FeatureConstants.DOWNLOAD_INFOBAR_DOWNLOAD_CONTINUING_FEATURE,
R.id.downloads_menu_id, true,
R.string.iph_download_infobar_download_continuing_text,
R.string.iph_download_infobar_download_continuing_text,
mActivity.getToolbarManager().getMenuButtonView(), profile, null);
}
private void setupAndMaybeShowIPHForFeature(String featureName, Integer highlightMenuItemId,
boolean circleHighlight, @StringRes int stringId, @StringRes int accessibilityStringId,
View anchorView, Profile profile, @Nullable Runnable onDismissCallback) {
final String contentString = mActivity.getString(stringId);
final String accessibilityString = mActivity.getString(accessibilityStringId);
final Tracker tracker = TrackerFactory.getTrackerForProfile(profile);
tracker.addOnInitializedCallback((Callback<Boolean>) success
-> maybeShowIPH(tracker, featureName, highlightMenuItemId, circleHighlight,
contentString, accessibilityString, anchorView, onDismissCallback));
}
private void setupAndMaybeShowIPHForFeature(String featureName, Integer highlightMenuItemId,
boolean circleHighlight, String contentString, String accessibilityString,
View anchorView, Profile profile, @Nullable Runnable onDismissCallback) {
final Tracker tracker = TrackerFactory.getTrackerForProfile(profile);
tracker.addOnInitializedCallback((Callback<Boolean>) success
-> maybeShowIPH(tracker, featureName, highlightMenuItemId, circleHighlight,
contentString, accessibilityString, anchorView, onDismissCallback));
}
private void showDownloadPageTextBubble(final Tab tab, String featureName) {
if (tab == null) return;
ChromeActivity activity = ((TabImpl) tab).getActivity();
if (!(activity instanceof ChromeTabbedActivity) || activity.isTablet()
|| activity.isInOverviewMode() || !DownloadUtils.isAllowedToDownloadPage(tab)) {
return;
}
private static boolean shouldHighlightForIPH(String featureName) {
switch (featureName) {
case FeatureConstants.PREVIEWS_OMNIBOX_UI_FEATURE:
return false;
default:
return true;
mUserEducationHelper.requestShowIPH(
new IPHCommandBuilder(mActivity.getResources(), featureName,
R.string.iph_download_page_for_offline_usage_text,
R.string.iph_download_page_for_offline_usage_accessibility_text)
.setOnShowCallback(
() -> turnOnHighlightForMenuItem(R.id.offline_page_id, true))
.setOnDismissCallback(this::turnOffHighlightForMenuItem)
.setAnchorView(mActivity.getToolbarManager().getMenuButtonView())
.build());
// Record metrics if we show Download IPH after a screenshot of the page.
ChromeTabbedActivity chromeActivity = ((ChromeTabbedActivity) activity);
ScreenshotTabObserver tabObserver =
ScreenshotTabObserver.from(chromeActivity.getActivityTab());
if (tabObserver != null) {
tabObserver.onActionPerformedAfterScreenshot(
ScreenshotTabObserver.SCREENSHOT_ACTION_DOWNLOAD_IPH);
}
}
private void maybeShowIPH(Tracker tracker, String featureName, Integer highlightMenuItemId,
boolean circleHighlight, String contentString, String accessibilityString,
View anchorView, @Nullable Runnable onDismissCallback) {
// Activity was destroyed; don't show IPH.
if (mActivity.isActivityFinishingOrDestroyed() || anchorView == null) return;
assert (contentString.length() > 0);
assert (accessibilityString.length() > 0);
// Post a request to show the IPH bubble to allow time for a layout pass. Since the bubble
// is shown on startup, the anchor view may not have a height initially see
// https://crbug.com/871537.
PostTask.postTask(UiThreadTaskTraits.DEFAULT, () -> {
if (mActivity.isActivityFinishingOrDestroyed()) return;
if (!tracker.shouldTriggerHelpUI(featureName)) return;
ViewRectProvider rectProvider = new ViewRectProvider(anchorView);
TextBubble textBubble = new TextBubble(
mActivity, anchorView, contentString, accessibilityString, true, rectProvider);
textBubble.setDismissOnTouchInteraction(true);
textBubble.addOnDismissListener(() -> anchorView.getHandler().postDelayed(() -> {
tracker.dismissed(featureName);
if (onDismissCallback != null) {
onDismissCallback.run();
}
if (shouldHighlightForIPH(featureName)) {
turnOffHighlightForTextBubble(anchorView);
}
}, ViewHighlighter.IPH_MIN_DELAY_BETWEEN_TWO_HIGHLIGHTS));
if (shouldHighlightForIPH(featureName)) {
turnOnHighlightForTextBubble(highlightMenuItemId, circleHighlight, anchorView);
}
/**
* Show the translate manual trigger in-product-help bubble.
* @param tab The current tab.
*/
private void showTranslateMenuButtonTextBubble(final Tab tab) {
if (tab == null) return;
if (!TranslateUtils.canTranslateCurrentTab(tab)
|| !TranslateBridge.shouldShowManualTranslateIPH(tab)) {
return;
}
int yInsetPx = mActivity.getResources().getDimensionPixelOffset(
R.dimen.text_bubble_menu_anchor_y_inset);
rectProvider.setInsetPx(0, 0, 0, yInsetPx);
textBubble.show();
});
mUserEducationHelper.requestShowIPH(
new IPHCommandBuilder(mActivity.getResources(),
FeatureConstants.TRANSLATE_MENU_BUTTON_FEATURE,
R.string.iph_translate_menu_button_text,
R.string.iph_translate_menu_button_accessibility_text)
.setOnShowCallback(
() -> turnOnHighlightForMenuItem(R.id.translate_id, false))
.setOnDismissCallback(this::turnOffHighlightForMenuItem)
.setAnchorView(mActivity.getToolbarManager().getMenuButtonView())
.build());
}
private void turnOnHighlightForTextBubble(
Integer highlightMenuItemId, boolean circleHighlight, View anchorView) {
private void turnOnHighlightForMenuItem(Integer highlightMenuItemId, boolean circleHighlight) {
if (mAppMenuHandler != null) {
mAppMenuHandler.setMenuHighlight(highlightMenuItemId, circleHighlight);
} else {
ViewHighlighter.turnOnHighlight(anchorView, circleHighlight);
}
}
private void turnOffHighlightForTextBubble(View anchorView) {
private void turnOffHighlightForMenuItem() {
if (mAppMenuHandler != null) {
mAppMenuHandler.clearMenuHighlight();
} else {
ViewHighlighter.turnOffHighlight(anchorView);
}
}
}
......@@ -36,7 +36,6 @@ import org.chromium.base.supplier.ObservableSupplierImpl;
import org.chromium.base.supplier.Supplier;
import org.chromium.chrome.R;
import org.chromium.chrome.browser.ChromeActivity;
import org.chromium.chrome.browser.ChromeTabbedActivity;
import org.chromium.chrome.browser.TabLoadStatus;
import org.chromium.chrome.browser.ThemeColorProvider;
import org.chromium.chrome.browser.ThemeColorProvider.ThemeColorObserver;
......@@ -50,8 +49,6 @@ import org.chromium.chrome.browser.compositor.layouts.OverviewModeBehavior;
import org.chromium.chrome.browser.compositor.layouts.OverviewModeBehavior.OverviewModeObserver;
import org.chromium.chrome.browser.compositor.layouts.OverviewModeState;
import org.chromium.chrome.browser.compositor.layouts.SceneChangeObserver;
import org.chromium.chrome.browser.download.DownloadUtils;
import org.chromium.chrome.browser.feature_engagement.ScreenshotTabObserver;
import org.chromium.chrome.browser.feature_engagement.TrackerFactory;
import org.chromium.chrome.browser.findinpage.FindToolbarManager;
import org.chromium.chrome.browser.findinpage.FindToolbarObserver;
......@@ -67,7 +64,6 @@ import org.chromium.chrome.browser.net.spdyproxy.DataReductionProxySettings;
import org.chromium.chrome.browser.ntp.FakeboxDelegate;
import org.chromium.chrome.browser.ntp.IncognitoNewTabPage;
import org.chromium.chrome.browser.ntp.NewTabPage;
import org.chromium.chrome.browser.offlinepages.OfflinePageBridge;
import org.chromium.chrome.browser.omaha.UpdateMenuItemHelper;
import org.chromium.chrome.browser.omnibox.LocationBar;
import org.chromium.chrome.browser.omnibox.SearchEngineLogoUtils;
......@@ -106,8 +102,6 @@ import org.chromium.chrome.browser.toolbar.top.ToolbarControlContainer;
import org.chromium.chrome.browser.toolbar.top.ToolbarLayout;
import org.chromium.chrome.browser.toolbar.top.TopToolbarCoordinator;
import org.chromium.chrome.browser.toolbar.top.ViewShiftingActionBarDelegate;
import org.chromium.chrome.browser.translate.TranslateBridge;
import org.chromium.chrome.browser.translate.TranslateUtils;
import org.chromium.chrome.browser.ui.ImmersiveModeManager;
import org.chromium.chrome.browser.ui.appmenu.AppMenuButtonHelper;
import org.chromium.chrome.browser.ui.appmenu.AppMenuCoordinator;
......@@ -115,7 +109,6 @@ import org.chromium.chrome.browser.ui.appmenu.AppMenuHandler;
import org.chromium.chrome.browser.ui.appmenu.AppMenuObserver;
import org.chromium.chrome.browser.ui.appmenu.AppMenuPropertiesDelegate;
import org.chromium.chrome.browser.ui.appmenu.MenuButtonDelegate;
import org.chromium.chrome.browser.ui.widget.highlight.ViewHighlighter;
import org.chromium.chrome.browser.ui.widget.textbubble.TextBubble;
import org.chromium.chrome.browser.util.UrlConstants;
import org.chromium.chrome.browser.widget.ScrimView;
......@@ -123,7 +116,6 @@ import org.chromium.chrome.browser.widget.ScrimView.ScrimObserver;
import org.chromium.chrome.browser.widget.ScrimView.ScrimParams;
import org.chromium.components.browser_ui.styles.ChromeColors;
import org.chromium.components.feature_engagement.EventConstants;
import org.chromium.components.feature_engagement.FeatureConstants;
import org.chromium.components.feature_engagement.Tracker;
import org.chromium.components.search_engines.TemplateUrl;
import org.chromium.components.search_engines.TemplateUrlService;
......@@ -138,7 +130,6 @@ import org.chromium.ui.base.DeviceFormFactor;
import org.chromium.ui.base.PageTransition;
import org.chromium.ui.util.TokenHolder;
import org.chromium.ui.widget.Toast;
import org.chromium.ui.widget.ViewRectProvider;
import java.util.List;
......@@ -468,11 +459,6 @@ public class ToolbarManager implements ScrimObserver, ToolbarTabController, UrlF
@Override
public void onPageLoadFinished(Tab tab, String url) {
if (tab.isShowingErrorPage()) {
handleIPHForErrorPageShown(tab);
return;
}
// TODO(crbug.com/896476): Remove this.
if (Previews.isPreview(tab)) {
// Some previews (like Client LoFi) are not fully decided until the page
......@@ -484,8 +470,6 @@ public class ToolbarManager implements ScrimObserver, ToolbarTabController, UrlF
PreviewsAndroidBridge.getInstance().getPreviewsType(
tab.getWebContents()));
}
handleIPHForSuccessfulPageLoad(tab);
}
@Override
......@@ -652,34 +636,6 @@ public class ToolbarManager implements ScrimObserver, ToolbarTabController, UrlF
// anchored at the bottom of the controls container.
setControlContainerTopMargin(getToolbarExtraYOffset());
}
private void handleIPHForSuccessfulPageLoad(final Tab tab) {
if (mTextBubble != null) {
mTextBubble.dismiss();
mTextBubble = null;
return;
}
showDownloadPageTextBubble(tab, FeatureConstants.DOWNLOAD_PAGE_FEATURE);
showTranslateMenuButtonTextBubble(
tab, FeatureConstants.TRANSLATE_MENU_BUTTON_FEATURE);
}
private void handleIPHForErrorPageShown(Tab tab) {
if (!(mActivity instanceof ChromeTabbedActivity) || mActivity.isTablet()) {
return;
}
OfflinePageBridge bridge =
OfflinePageBridge.getForProfile(((TabImpl) tab).getProfile());
if (bridge == null
|| !bridge.isShowingDownloadButtonInErrorPage(tab.getWebContents())) {
return;
}
Tracker tracker = TrackerFactory.getTrackerForProfile(((TabImpl) tab).getProfile());
tracker.notifyEvent(EventConstants.USER_HAS_SEEN_DINO);
}
};
mBookmarksObserver = new BookmarkBridge.BookmarkModelObserver() {
......@@ -946,84 +902,6 @@ public class ToolbarManager implements ScrimObserver, ToolbarTabController, UrlF
return mBottomControlsCoordinator;
}
// TODO(https://crbug.com/865801): Remove this IPH code from toolbar manager.
private void showMenuIPHTextBubble(ChromeActivity activity, Tracker tracker, String featureName,
@StringRes int stringId, @StringRes int accessibilityStringId, Integer highlightItemId,
boolean circleHighlight) {
ViewRectProvider rectProvider = new ViewRectProvider(getMenuButtonView());
int yInsetPx = mActivity.getResources().getDimensionPixelOffset(
R.dimen.text_bubble_menu_anchor_y_inset);
rectProvider.setInsetPx(0, 0, 0, yInsetPx);
mTextBubble = new TextBubble(
mActivity, getMenuButtonView(), stringId, accessibilityStringId, rectProvider);
mTextBubble.setDismissOnTouchInteraction(true);
mTextBubble.addOnDismissListener(() -> {
mHandler.postDelayed(() -> {
tracker.dismissed(featureName);
mAppMenuHandler.clearMenuHighlight();
}, ViewHighlighter.IPH_MIN_DELAY_BETWEEN_TWO_HIGHLIGHTS);
});
mAppMenuHandler.setMenuHighlight(highlightItemId, circleHighlight);
mTextBubble.show();
}
/**
* Show the download page in-product-help bubble. Also used by download page screenshot IPH.
* @param tab The current tab.
* @param featureName The associated feature name.
*/
// TODO(https://crbug.com/865801): Remove feature specific IPH from toolbar manager.
public void showDownloadPageTextBubble(final Tab tab, String featureName) {
if (tab == null) return;
// TODO(shaktisahu): Find out if the download menu button is enabled (crbug/712438).
ChromeActivity activity = ((TabImpl) tab).getActivity();
if (!(activity instanceof ChromeTabbedActivity) || activity.isTablet()
|| activity.isInOverviewMode() || !DownloadUtils.isAllowedToDownloadPage(tab)) {
return;
}
final Tracker tracker = TrackerFactory.getTrackerForProfile(((TabImpl) tab).getProfile());
if (!tracker.shouldTriggerHelpUI(featureName)) return;
showMenuIPHTextBubble(activity, tracker, featureName,
R.string.iph_download_page_for_offline_usage_text,
R.string.iph_download_page_for_offline_usage_accessibility_text,
R.id.offline_page_id, true);
// Record metrics if we show Download IPH after a screenshot of the page.
ChromeTabbedActivity chromeActivity = ((ChromeTabbedActivity) activity);
ScreenshotTabObserver tabObserver =
ScreenshotTabObserver.from(chromeActivity.getActivityTab());
if (tabObserver != null) {
tabObserver.onActionPerformedAfterScreenshot(
ScreenshotTabObserver.SCREENSHOT_ACTION_DOWNLOAD_IPH);
}
}
/**
* Show the translate manual trigger in-product-help bubble.
* @param tab The current tab.
* @param featureName The associated feature name.
*/
// TODO(https://crbug.com/865801): Remove feature specific IPH from toolbar manager.
public void showTranslateMenuButtonTextBubble(final Tab tab, String featureName) {
if (tab == null) return;
ChromeActivity activity = ((TabImpl) tab).getActivity();
if (mAppMenuPropertiesDelegate == null || !TranslateUtils.canTranslateCurrentTab(tab)
|| !TranslateBridge.shouldShowManualTranslateIPH(tab)) {
return;
}
// Find out if the help UI should appear.
final Tracker tracker = TrackerFactory.getTrackerForProfile(((TabImpl) tab).getProfile());
if (!tracker.shouldTriggerHelpUI(featureName)) return;
showMenuIPHTextBubble(activity, tracker, featureName,
R.string.iph_translate_menu_button_text,
R.string.iph_translate_menu_button_accessibility_text, R.id.translate_id, false);
}
/**
* Initialize the manager with the components that had native initialization dependencies.
* <p>
......
// Copyright 2020 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.user_education;
import android.graphics.Rect;
import android.view.View;
import androidx.annotation.Nullable;
/**
* Class encapsulating the data needed to show in-product help (IPH).
*/
public class IPHCommand {
public final String featureName;
public final String contentString;
public final String accessibilityText;
public final boolean circleHighlight;
public final boolean shouldHighlight;
public final boolean dismissOnTouch;
public final View anchorView;
@Nullable
public final Runnable onDismissCallback;
@Nullable
public final Runnable onShowCallback;
public final Rect insetRect;
IPHCommand(String featureName, String contentString, String accessibilityText,
boolean circleHighlight, boolean shouldHighlight, boolean dismissOnTouch,
View anchorView, Runnable onDismissCallback, Runnable onShowCallback, Rect insetRect) {
this.featureName = featureName;
this.contentString = contentString;
this.accessibilityText = accessibilityText;
this.circleHighlight = circleHighlight;
this.shouldHighlight = shouldHighlight;
this.dismissOnTouch = dismissOnTouch;
this.anchorView = anchorView;
this.onDismissCallback = onDismissCallback;
this.onShowCallback = onShowCallback;
this.insetRect = insetRect;
}
}
\ No newline at end of file
// Copyright 2020 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.user_education;
import android.content.res.Resources;
import android.graphics.Rect;
import android.view.View;
import androidx.annotation.StringRes;
import org.chromium.chrome.R;
/**
* Builder for (@see IPHCommand.java). Use this instead of constructing an IPHCommand directly.
*/
public class IPHCommandBuilder {
private static final Runnable NO_OP_RUNNABLE = () -> {};
private Resources mResources;
private final String mFeatureName;
private String mContentString;
private String mAccessibilityText;
private boolean mShouldHighlight;
private boolean mCircleHighlight = true;
private boolean mDismissOnTouch = true;
@StringRes
private int mStringId;
@StringRes
private int mAccessibilityStringId;
private View mAnchorView;
private Runnable mOnShowCallback;
private Runnable mOnDismissCallback;
private Rect mInsetRect;
/**
* Constructor for IPHCommandBuilder when you would like your strings to be resolved for you.
* @param resources Resources object used to resolve strings and dimensions.
* @param featureName String identifier for the feature from FeatureConstants.
* @param stringId Resource id of the string displayed to the use.
* @param accessibilityStringId resource id of the string to use for accessibility.
*/
public IPHCommandBuilder(Resources resources, String featureName, @StringRes int stringId,
@StringRes int accessibilityStringId) {
mResources = resources;
mFeatureName = featureName;
mStringId = stringId;
mAccessibilityStringId = accessibilityStringId;
}
/**
* Constructor for IPHCommandBuilder when you have your strings pre-resolved.
* @param resources Resources object used to resolve strings and dimensions.
* @param featureName String identifier for the feature from FeatureConstants.
* @param contentString String displayed to the user.
* @param accessibilityText String to use for accessibility.
*/
public IPHCommandBuilder(Resources resources, String featureName, String contentString,
String accessibilityText) {
mResources = resources;
mFeatureName = featureName;
mContentString = contentString;
mAccessibilityText = accessibilityText;
}
/**
*
* @param circleHighlight whether the highlight should be circular.
*/
public IPHCommandBuilder setCircleHighlight(boolean circleHighlight) {
mCircleHighlight = circleHighlight;
return this;
}
/**
*
* @param shouldHighlight whether the anchor view should be highlighted.
*/
public IPHCommandBuilder setShouldHighlight(boolean shouldHighlight) {
mShouldHighlight = shouldHighlight;
return this;
}
/**
*
* @param anchorView the view that the IPH bubble should be anchored to.
*/
public IPHCommandBuilder setAnchorView(View anchorView) {
mAnchorView = anchorView;
return this;
}
/**
*
* @param onShowCallback callback to invoke when the IPH bubble is first shown.
*/
public IPHCommandBuilder setOnShowCallback(Runnable onShowCallback) {
mOnShowCallback = onShowCallback;
return this;
}
/**
*
* @param onDismissCallback callback to invoke when the IPH bubble is dismissed.
*/
public IPHCommandBuilder setOnDismissCallback(Runnable onDismissCallback) {
mOnDismissCallback = onDismissCallback;
return this;
}
/**
*
* @param insetRect The inset rectangle to use when shrinking the anchor view to show the IPH
* bubble.
*/
public IPHCommandBuilder setInsetRect(Rect insetRect) {
mInsetRect = insetRect;
return this;
}
/**
*
* @param dismissOnTouch Whether the IPH bubble should be dismissed when the user performs a
* touch interaction.
*/
public IPHCommandBuilder setDismissOnTouch(boolean dismissOnTouch) {
mDismissOnTouch = dismissOnTouch;
return this;
}
/**
*
* @return an (@see IPHCommand) containing the accumulated state of this builder.
*/
public IPHCommand build() {
if (mOnDismissCallback == null) {
mOnDismissCallback = NO_OP_RUNNABLE;
}
if (mOnShowCallback == null) {
mOnShowCallback = NO_OP_RUNNABLE;
}
if (mContentString == null) {
assert mResources != null;
mContentString = mResources.getString(mStringId);
}
if (mAccessibilityText == null) {
assert mResources != null;
mAccessibilityText = mResources.getString(mAccessibilityStringId);
}
if (mInsetRect == null) {
int yInsetPx =
mResources.getDimensionPixelOffset(R.dimen.text_bubble_menu_anchor_y_inset);
mInsetRect = new Rect(0, 0, 0, yInsetPx);
}
return new IPHCommand(mFeatureName, mContentString, mAccessibilityText, mCircleHighlight,
mShouldHighlight, mDismissOnTouch, mAnchorView, mOnDismissCallback, mOnShowCallback,
mInsetRect);
}
}
\ No newline at end of file
mdjones@chromium.org
dtrainor@chromium.org
twellington@chromium.org
pnoland@chromium.org
# COMPONENT: UI>Browser>Mobile
# OS: Android
// Copyright 2020 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.user_education;
import android.app.Activity;
import android.view.View;
import org.chromium.chrome.browser.feature_engagement.TrackerFactory;
import org.chromium.chrome.browser.profiles.Profile;
import org.chromium.chrome.browser.ui.widget.highlight.ViewHighlighter;
import org.chromium.chrome.browser.ui.widget.textbubble.TextBubble;
import org.chromium.components.feature_engagement.Tracker;
import org.chromium.ui.widget.ViewRectProvider;
/**
* Class that shows and hides in-product help message bubbles.
* Recipes for use:
* 1. Create an IPH bubble anchored to a view:
* mUserEducationHelper.requestShowIPH(new IPHCommandBuilder(myContext.getResources(),
* FeatureConstants.MY_FEATURE_NAME, R.string.my_feature_iph_text,
* R.string.my_feature_iph_accessibility_text)
* .setAnchorView(myAnchorView)
* .setCircleHighlight(true)
* .build());
* 2. Create an IPH bubble that does custom logic when shown and hidden
* mUserEducationHelper.requestShowIPH(new IPHCommandBuilder(myContext.getResources(),
* FeatureConstants.MY_FEATURE_NAME, R.string.my_feature_iph_text,
* R.string.my_feature_iph_accessibility_text)
* .setAnchorView(myAnchorView)
* .setCircleHighlight(true)
* .setOnShowCallback( ()-> doCustomShowLogic())
* .setOnDismissCallback(() ->
* doCustomDismissLogic())
* .build());
*/
public class UserEducationHelper {
private final Activity mActivity;
public UserEducationHelper(Activity activity) {
mActivity = activity;
}
/**
* Requests display of the in-product help (IPH) data in @param iphCommand.
* @see IPHCommand for a breakdown of this data.
* Display will only occur if the feature engagement tracker for the current profile says it
* should.
*/
public void requestShowIPH(IPHCommand iphCommand) {
Profile profile = Profile.getLastUsedProfile();
final Tracker tracker = TrackerFactory.getTrackerForProfile(profile);
tracker.addOnInitializedCallback(success -> showIPH(tracker, iphCommand));
}
private void showIPH(Tracker tracker, IPHCommand iphCommand) {
// Activity was destroyed; don't show IPH.
View anchorView = iphCommand.anchorView;
if (mActivity.isFinishing() || mActivity.isDestroyed() || anchorView == null) return;
if (mActivity.isFinishing() || mActivity.isDestroyed()) return;
String featureName = iphCommand.featureName;
if (!tracker.shouldTriggerHelpUI(featureName)) return;
String contentString = iphCommand.contentString;
String accessibilityString = iphCommand.accessibilityText;
assert (!contentString.isEmpty());
assert (!accessibilityString.isEmpty());
ViewRectProvider rectProvider = new ViewRectProvider(anchorView);
TextBubble textBubble = new TextBubble(
mActivity, anchorView, contentString, accessibilityString, true, rectProvider);
textBubble.setDismissOnTouchInteraction(iphCommand.dismissOnTouch);
textBubble.addOnDismissListener(() -> anchorView.getHandler().postDelayed(() -> {
tracker.dismissed(featureName);
iphCommand.onDismissCallback.run();
if (iphCommand.shouldHighlight) {
ViewHighlighter.turnOffHighlight(anchorView);
}
}, ViewHighlighter.IPH_MIN_DELAY_BETWEEN_TWO_HIGHLIGHTS));
if (iphCommand.shouldHighlight) {
ViewHighlighter.turnOnHighlight(anchorView, iphCommand.circleHighlight);
}
rectProvider.setInsetPx(iphCommand.insetRect);
textBubble.show();
iphCommand.onShowCallback.run();
}
}
\ No newline at end of file
......@@ -44,6 +44,15 @@ public class ViewRectProvider extends RectProvider
refreshRectBounds();
}
/**
* Specifies the inset values in pixels that determine how to shrink the {@link View} bounds
* when creating the {@link Rect}.
*/
public void setInsetPx(Rect insetRect) {
mInsetRect.set(insetRect);
refreshRectBounds();
}
/**
* Whether padding should be included in the {@link Rect} for the {@link View}.
* @param includePadding Whether padding should be included. Defaults to false.
......
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