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 = [ ...@@ -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/UsageStatsMetricsReporter.java",
"java/src/org/chromium/chrome/browser/usage_stats/UsageStatsService.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/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/ArDelegate.java",
"java/src/org/chromium/chrome/browser/vr/ArDelegateProvider.java", "java/src/org/chromium/chrome/browser/vr/ArDelegateProvider.java",
"java/src/org/chromium/chrome/browser/webapps/ActivateWebApkActivity.java", "java/src/org/chromium/chrome/browser/webapps/ActivateWebApkActivity.java",
......
...@@ -73,9 +73,6 @@ import org.chromium.chrome.browser.dom_distiller.ReaderModeManager; ...@@ -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.DownloadOpenSource;
import org.chromium.chrome.browser.download.DownloadUtils; import org.chromium.chrome.browser.download.DownloadUtils;
import org.chromium.chrome.browser.feature_engagement.ScreenshotMonitor; 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.feed.FeedProcessScopeFactory;
import org.chromium.chrome.browser.firstrun.FirstRunSignInProcessor; import org.chromium.chrome.browser.firstrun.FirstRunSignInProcessor;
import org.chromium.chrome.browser.flags.ActivityType; import org.chromium.chrome.browser.flags.ActivityType;
...@@ -153,9 +150,6 @@ import org.chromium.chrome.browser.util.UrlConstants; ...@@ -153,9 +150,6 @@ import org.chromium.chrome.browser.util.UrlConstants;
import org.chromium.chrome.browser.vr.VrModuleProvider; import org.chromium.chrome.browser.vr.VrModuleProvider;
import org.chromium.chrome.browser.widget.bottomsheet.EmptyBottomSheetObserver; import org.chromium.chrome.browser.widget.bottomsheet.EmptyBottomSheetObserver;
import org.chromium.chrome.features.start_surface.StartSurface; 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.LoadUrlParams;
import org.chromium.content_public.browser.NavigationHandle; import org.chromium.content_public.browser.NavigationHandle;
import org.chromium.content_public.browser.UiThreadTaskTraits; import org.chromium.content_public.browser.UiThreadTaskTraits;
...@@ -175,7 +169,7 @@ import java.util.Locale; ...@@ -175,7 +169,7 @@ import java.util.Locale;
* This is the main activity for ChromeMobile when not running in document mode. All the tabs * 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. * 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 TAG = "ChromeTabbedActivity";
private static final String HELP_URL_PREFIX = "https://support.google.com/chrome/"; private static final String HELP_URL_PREFIX = "https://support.google.com/chrome/";
...@@ -718,7 +712,7 @@ public class ChromeTabbedActivity extends ChromeActivity implements ScreenshotMo ...@@ -718,7 +712,7 @@ public class ChromeTabbedActivity extends ChromeActivity implements ScreenshotMo
mOverviewModeController.hideOverview(false); mOverviewModeController.hideOverview(false);
mScreenshotMonitor = new ScreenshotMonitor(ChromeTabbedActivity.this); mScreenshotMonitor = new ScreenshotMonitor(getToolbarButtonInProductHelpController());
} }
} }
...@@ -2353,22 +2347,6 @@ public class ChromeTabbedActivity extends ChromeActivity implements ScreenshotMo ...@@ -2353,22 +2347,6 @@ public class ChromeTabbedActivity extends ChromeActivity implements ScreenshotMo
isIncognito ? "new-incognito-tab-shortcut" : "new-tab-shortcut"); 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 @VisibleForTesting
public MultiInstanceManager getMultiInstanceMangerForTesting() { public MultiInstanceManager getMultiInstanceMangerForTesting() {
return mMultiInstanceManager; return mMultiInstanceManager;
......
...@@ -30,7 +30,6 @@ import org.chromium.chrome.browser.infobar.IPHInfoBarSupport; ...@@ -30,7 +30,6 @@ import org.chromium.chrome.browser.infobar.IPHInfoBarSupport;
import org.chromium.chrome.browser.infobar.InfoBar; import org.chromium.chrome.browser.infobar.InfoBar;
import org.chromium.chrome.browser.infobar.InfoBarContainer; import org.chromium.chrome.browser.infobar.InfoBarContainer;
import org.chromium.chrome.browser.infobar.InfoBarIdentifier; 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.Tab;
import org.chromium.chrome.browser.tab.TabImpl; import org.chromium.chrome.browser.tab.TabImpl;
import org.chromium.components.download.DownloadState; import org.chromium.components.download.DownloadState;
...@@ -851,9 +850,7 @@ public class DownloadInfoBarController implements OfflineContentProvider.Observe ...@@ -851,9 +850,7 @@ public class DownloadInfoBarController implements OfflineContentProvider.Observe
} }
ChromeTabbedActivity activity = (ChromeTabbedActivity) getActivity(); ChromeTabbedActivity activity = (ChromeTabbedActivity) getActivity();
Profile profile = mIsIncognito ? Profile.getLastUsedProfile().getOffTheRecordProfile() activity.getToolbarButtonInProductHelpController().showDownloadContinuingIPH();
: Profile.getLastUsedProfile().getOriginalProfile();
activity.getToolbarButtonInProductHelpController().maybeShowDownloadContinuingIPH(profile);
} }
@Nullable @Nullable
......
...@@ -153,7 +153,7 @@ public class TabbedRootUiCoordinator extends RootUiCoordinator implements Native ...@@ -153,7 +153,7 @@ public class TabbedRootUiCoordinator extends RootUiCoordinator implements Native
mToolbarButtonInProductHelpController = mToolbarButtonInProductHelpController =
new ToolbarButtonInProductHelpController(mActivity, mAppMenuCoordinator); new ToolbarButtonInProductHelpController(mActivity, mAppMenuCoordinator);
if (!triggerPromo()) { if (!triggerPromo()) {
mToolbarButtonInProductHelpController.maybeShowColdStartIPH(); mToolbarButtonInProductHelpController.showColdStartIPH();
} }
} }
......
// Copyright 2018 The Chromium Authors. All rights reserved. // Copyright 2018 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be // Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file. // found in the LICENSE file.
package org.chromium.chrome.browser.toolbar; 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.base.task.PostTask;
import org.chromium.chrome.R; import org.chromium.chrome.R;
import org.chromium.chrome.browser.ActivityTabProvider.ActivityTabTabObserver; import org.chromium.chrome.browser.ActivityTabProvider.ActivityTabTabObserver;
import org.chromium.chrome.browser.ChromeActivity; import org.chromium.chrome.browser.ChromeActivity;
import org.chromium.chrome.browser.ChromeTabbedActivity;
import org.chromium.chrome.browser.datareduction.DataReductionSavingsMilestonePromo; 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.feature_engagement.TrackerFactory;
import org.chromium.chrome.browser.flags.FeatureUtilities; import org.chromium.chrome.browser.flags.FeatureUtilities;
import org.chromium.chrome.browser.net.spdyproxy.DataReductionProxySettings; 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.previews.Previews;
import org.chromium.chrome.browser.profiles.Profile; import org.chromium.chrome.browser.profiles.Profile;
import org.chromium.chrome.browser.tab.Tab; 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.AppMenuCoordinator;
import org.chromium.chrome.browser.ui.appmenu.AppMenuHandler; import org.chromium.chrome.browser.ui.appmenu.AppMenuHandler;
import org.chromium.chrome.browser.ui.widget.highlight.ViewHighlighter; import org.chromium.chrome.browser.ui.appmenu.AppMenuPropertiesDelegate;
import org.chromium.chrome.browser.ui.widget.textbubble.TextBubble; 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.EventConstants;
import org.chromium.components.feature_engagement.FeatureConstants; import org.chromium.components.feature_engagement.FeatureConstants;
import org.chromium.components.feature_engagement.Tracker; import org.chromium.components.feature_engagement.Tracker;
import org.chromium.content_public.browser.UiThreadTaskTraits; import org.chromium.content_public.browser.UiThreadTaskTraits;
import org.chromium.ui.widget.ViewRectProvider;
/** /**
* A helper class for IPH shown on the toolbar. * 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 ActivityTabTabObserver mPageLoadObserver;
private final ChromeActivity mActivity; private final ChromeActivity mActivity;
private final AppMenuPropertiesDelegate mAppMenuPropertiesDelegate;
private AppMenuHandler mAppMenuHandler; private AppMenuHandler mAppMenuHandler;
private UserEducationHelper mUserEducationHelper;
public ToolbarButtonInProductHelpController( public ToolbarButtonInProductHelpController(
final ChromeActivity activity, AppMenuCoordinator appMenuCoordinator) { final ChromeActivity activity, AppMenuCoordinator appMenuCoordinator) {
mActivity = activity; mActivity = activity;
mUserEducationHelper = new UserEducationHelper(mActivity);
mPageLoadObserver = new ActivityTabTabObserver(activity.getActivityTabProvider()) { mPageLoadObserver = new ActivityTabTabObserver(activity.getActivityTabProvider()) {
/** /**
* Stores total data saved at the start of a page load. Used to calculate delta at the * Stores total data saved at the start of a page load. Used to calculate delta at the
...@@ -61,6 +65,15 @@ public class ToolbarButtonInProductHelpController { ...@@ -61,6 +65,15 @@ public class ToolbarButtonInProductHelpController {
@Override @Override
public void onPageLoadFinished(Tab tab, String url) { 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() long dataSaved = DataReductionProxySettings.getInstance()
.getContentLengthSavedInHistorySummary() .getContentLengthSavedInHistorySummary()
- mDataSavedOnStartPageLoad; - mDataSavedOnStartPageLoad;
...@@ -69,15 +82,36 @@ public class ToolbarButtonInProductHelpController { ...@@ -69,15 +82,36 @@ public class ToolbarButtonInProductHelpController {
if (Previews.isPreview(tab)) { if (Previews.isPreview(tab)) {
tracker.notifyEvent(EventConstants.PREVIEWS_PAGE_LOADED); tracker.notifyEvent(EventConstants.PREVIEWS_PAGE_LOADED);
} }
if (tab.isUserInteractable()) { if (tab.isUserInteractable()) {
maybeShowDataSaverDetail(); showDataSaverDetail();
if (dataSaved > 0L) maybeShowDataSaverMilestonePromo(); if (dataSaved > 0L) showDataSaverMilestonePromo();
if (Previews.isPreview(tab)) maybeShowPreviewVerboseStatus(); 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(); mAppMenuHandler = appMenuCoordinator.getAppMenuHandler();
mAppMenuPropertiesDelegate = appMenuCoordinator.getAppMenuPropertiesDelegate();
} }
public void destroy() { public void destroy() {
...@@ -86,27 +120,68 @@ public class ToolbarButtonInProductHelpController { ...@@ -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() { private static int getDataReductionMenuItemHighlight() {
return FeatureUtilities.isBottomToolbarEnabled() ? R.id.data_reduction_menu_item return FeatureUtilities.isBottomToolbarEnabled() ? R.id.data_reduction_menu_item
: R.id.app_menu_footer; : R.id.app_menu_footer;
} }
// Attempts to show an IPH text bubble for data saver detail. // Attempts to show an IPH text bubble for data saver detail.
private void maybeShowDataSaverDetail() { private void showDataSaverDetail() {
View anchorView = mActivity.getToolbarManager().getMenuButtonView(); mUserEducationHelper.requestShowIPH(
if (anchorView == null) return; new IPHCommandBuilder(mActivity.getResources(),
FeatureConstants.DATA_SAVER_DETAIL_FEATURE,
setupAndMaybeShowIPHForFeature(FeatureConstants.DATA_SAVER_DETAIL_FEATURE, R.string.iph_data_saver_detail_text,
getDataReductionMenuItemHighlight(), false, R.string.iph_data_saver_detail_text, R.string.iph_data_saver_detail_accessibility_text)
R.string.iph_data_saver_detail_accessibility_text, anchorView, .setAnchorView(mActivity.getToolbarManager().getMenuButtonView())
Profile.getLastUsedProfile(), null); .setOnShowCallback(
()
-> turnOnHighlightForMenuItem(
getDataReductionMenuItemHighlight(), false))
.setOnDismissCallback(this::turnOffHighlightForMenuItem)
.build());
} }
// Attempts to show an IPH text bubble for data saver milestone promo. // Attempts to show an IPH text bubble for data saver milestone promo.
private void maybeShowDataSaverMilestonePromo() { private void showDataSaverMilestonePromo() {
View anchorView = mActivity.getToolbarManager().getMenuButtonView();
if (anchorView == null) return;
final DataReductionSavingsMilestonePromo promo = final DataReductionSavingsMilestonePromo promo =
new DataReductionSavingsMilestonePromo(mActivity, new DataReductionSavingsMilestonePromo(mActivity,
DataReductionProxySettings.getInstance().getTotalHttpContentLengthSaved()); DataReductionProxySettings.getInstance().getTotalHttpContentLengthSaved());
...@@ -114,137 +189,109 @@ public class ToolbarButtonInProductHelpController { ...@@ -114,137 +189,109 @@ public class ToolbarButtonInProductHelpController {
final Runnable dismissCallback = () -> { final Runnable dismissCallback = () -> {
promo.onPromoTextSeen(); promo.onPromoTextSeen();
turnOffHighlightForMenuItem();
}; };
setupAndMaybeShowIPHForFeature(FeatureConstants.DATA_SAVER_MILESTONE_PROMO_FEATURE,
getDataReductionMenuItemHighlight(), false, promo.getPromoText(), mUserEducationHelper.requestShowIPH(
promo.getPromoText(), anchorView, Profile.getLastUsedProfile(), dismissCallback); 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 page in preview mode. // Attempts to show an IPH text bubble for page in preview mode.
private void maybeShowPreviewVerboseStatus() { private void showPreviewVerboseStatus() {
final View anchorView = mActivity.getToolbarManager().getSecurityIconView(); mUserEducationHelper.requestShowIPH(
if (anchorView == null) return; new IPHCommandBuilder(mActivity.getResources(),
FeatureConstants.PREVIEWS_OMNIBOX_UI_FEATURE,
setupAndMaybeShowIPHForFeature(FeatureConstants.PREVIEWS_OMNIBOX_UI_FEATURE, null, true,
R.string.iph_previews_omnibox_ui_text, R.string.iph_previews_omnibox_ui_text,
R.string.iph_previews_omnibox_ui_accessibility_text, anchorView, R.string.iph_previews_omnibox_ui_accessibility_text)
Profile.getLastUsedProfile(), null); .setAnchorView(mActivity.getToolbarManager().getSecurityIconView())
} .setShouldHighlight(false)
.build());
/**
* Attempts to show an IPH text bubble for those that trigger on a cold start.
*/
public void maybeShowColdStartIPH() {
maybeShowDownloadHomeIPH();
} }
private void maybeShowDownloadHomeIPH() { private void showDownloadHomeIPH() {
setupAndMaybeShowIPHForFeature(FeatureConstants.DOWNLOAD_HOME_FEATURE, mUserEducationHelper.requestShowIPH(
R.id.downloads_menu_id, true, R.string.iph_download_home_text, new IPHCommandBuilder(mActivity.getResources(),
R.string.iph_download_home_accessibility_text, FeatureConstants.DOWNLOAD_HOME_FEATURE, R.string.iph_download_home_text,
mActivity.getToolbarManager().getMenuButtonView(), Profile.getLastUsedProfile(), R.string.iph_download_home_accessibility_text)
null); .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. * Show the download page in-product-help bubble. Also used by download page screenshot IPH.
* @param window The window to use for the IPH. * @param tab The current tab.
* @param profile The profile to use for the tracker.
*/ */
public void maybeShowDownloadContinuingIPH(Profile profile) { private void showDownloadPageTextBubble(final Tab tab, String featureName) {
setupAndMaybeShowIPHForFeature( if (tab == null) return;
FeatureConstants.DOWNLOAD_INFOBAR_DOWNLOAD_CONTINUING_FEATURE, ChromeActivity activity = ((TabImpl) tab).getActivity();
R.id.downloads_menu_id, true, if (!(activity instanceof ChromeTabbedActivity) || activity.isTablet()
R.string.iph_download_infobar_download_continuing_text, || activity.isInOverviewMode() || !DownloadUtils.isAllowedToDownloadPage(tab)) {
R.string.iph_download_infobar_download_continuing_text, return;
mActivity.getToolbarManager().getMenuButtonView(), profile, null);
} }
private void setupAndMaybeShowIPHForFeature(String featureName, Integer highlightMenuItemId, mUserEducationHelper.requestShowIPH(
boolean circleHighlight, @StringRes int stringId, @StringRes int accessibilityStringId, new IPHCommandBuilder(mActivity.getResources(), featureName,
View anchorView, Profile profile, @Nullable Runnable onDismissCallback) { R.string.iph_download_page_for_offline_usage_text,
final String contentString = mActivity.getString(stringId); R.string.iph_download_page_for_offline_usage_accessibility_text)
final String accessibilityString = mActivity.getString(accessibilityStringId); .setOnShowCallback(
final Tracker tracker = TrackerFactory.getTrackerForProfile(profile); () -> turnOnHighlightForMenuItem(R.id.offline_page_id, true))
tracker.addOnInitializedCallback((Callback<Boolean>) success .setOnDismissCallback(this::turnOffHighlightForMenuItem)
-> maybeShowIPH(tracker, featureName, highlightMenuItemId, circleHighlight, .setAnchorView(mActivity.getToolbarManager().getMenuButtonView())
contentString, accessibilityString, anchorView, onDismissCallback)); .build());
} // Record metrics if we show Download IPH after a screenshot of the page.
ChromeTabbedActivity chromeActivity = ((ChromeTabbedActivity) activity);
private void setupAndMaybeShowIPHForFeature(String featureName, Integer highlightMenuItemId, ScreenshotTabObserver tabObserver =
boolean circleHighlight, String contentString, String accessibilityString, ScreenshotTabObserver.from(chromeActivity.getActivityTab());
View anchorView, Profile profile, @Nullable Runnable onDismissCallback) { if (tabObserver != null) {
final Tracker tracker = TrackerFactory.getTrackerForProfile(profile); tabObserver.onActionPerformedAfterScreenshot(
tracker.addOnInitializedCallback((Callback<Boolean>) success ScreenshotTabObserver.SCREENSHOT_ACTION_DOWNLOAD_IPH);
-> maybeShowIPH(tracker, featureName, highlightMenuItemId, circleHighlight,
contentString, accessibilityString, anchorView, onDismissCallback));
}
private static boolean shouldHighlightForIPH(String featureName) {
switch (featureName) {
case FeatureConstants.PREVIEWS_OMNIBOX_UI_FEATURE:
return false;
default:
return true;
} }
} }
private void maybeShowIPH(Tracker tracker, String featureName, Integer highlightMenuItemId, /**
boolean circleHighlight, String contentString, String accessibilityString, * Show the translate manual trigger in-product-help bubble.
View anchorView, @Nullable Runnable onDismissCallback) { * @param tab The current tab.
// Activity was destroyed; don't show IPH. */
if (mActivity.isActivityFinishingOrDestroyed() || anchorView == null) return; private void showTranslateMenuButtonTextBubble(final Tab tab) {
if (tab == null) return;
assert (contentString.length() > 0); if (!TranslateUtils.canTranslateCurrentTab(tab)
assert (accessibilityString.length() > 0); || !TranslateBridge.shouldShowManualTranslateIPH(tab)) {
return;
// 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);
} }
int yInsetPx = mActivity.getResources().getDimensionPixelOffset( mUserEducationHelper.requestShowIPH(
R.dimen.text_bubble_menu_anchor_y_inset); new IPHCommandBuilder(mActivity.getResources(),
rectProvider.setInsetPx(0, 0, 0, yInsetPx); FeatureConstants.TRANSLATE_MENU_BUTTON_FEATURE,
textBubble.show(); 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( private void turnOnHighlightForMenuItem(Integer highlightMenuItemId, boolean circleHighlight) {
Integer highlightMenuItemId, boolean circleHighlight, View anchorView) {
if (mAppMenuHandler != null) { if (mAppMenuHandler != null) {
mAppMenuHandler.setMenuHighlight(highlightMenuItemId, circleHighlight); mAppMenuHandler.setMenuHighlight(highlightMenuItemId, circleHighlight);
} else {
ViewHighlighter.turnOnHighlight(anchorView, circleHighlight);
} }
} }
private void turnOffHighlightForTextBubble(View anchorView) { private void turnOffHighlightForMenuItem() {
if (mAppMenuHandler != null) { if (mAppMenuHandler != null) {
mAppMenuHandler.clearMenuHighlight(); mAppMenuHandler.clearMenuHighlight();
} else {
ViewHighlighter.turnOffHighlight(anchorView);
} }
} }
} }
...@@ -36,7 +36,6 @@ import org.chromium.base.supplier.ObservableSupplierImpl; ...@@ -36,7 +36,6 @@ import org.chromium.base.supplier.ObservableSupplierImpl;
import org.chromium.base.supplier.Supplier; import org.chromium.base.supplier.Supplier;
import org.chromium.chrome.R; import org.chromium.chrome.R;
import org.chromium.chrome.browser.ChromeActivity; import org.chromium.chrome.browser.ChromeActivity;
import org.chromium.chrome.browser.ChromeTabbedActivity;
import org.chromium.chrome.browser.TabLoadStatus; import org.chromium.chrome.browser.TabLoadStatus;
import org.chromium.chrome.browser.ThemeColorProvider; import org.chromium.chrome.browser.ThemeColorProvider;
import org.chromium.chrome.browser.ThemeColorProvider.ThemeColorObserver; import org.chromium.chrome.browser.ThemeColorProvider.ThemeColorObserver;
...@@ -50,8 +49,6 @@ import org.chromium.chrome.browser.compositor.layouts.OverviewModeBehavior; ...@@ -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.OverviewModeBehavior.OverviewModeObserver;
import org.chromium.chrome.browser.compositor.layouts.OverviewModeState; import org.chromium.chrome.browser.compositor.layouts.OverviewModeState;
import org.chromium.chrome.browser.compositor.layouts.SceneChangeObserver; 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.feature_engagement.TrackerFactory;
import org.chromium.chrome.browser.findinpage.FindToolbarManager; import org.chromium.chrome.browser.findinpage.FindToolbarManager;
import org.chromium.chrome.browser.findinpage.FindToolbarObserver; import org.chromium.chrome.browser.findinpage.FindToolbarObserver;
...@@ -67,7 +64,6 @@ import org.chromium.chrome.browser.net.spdyproxy.DataReductionProxySettings; ...@@ -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.FakeboxDelegate;
import org.chromium.chrome.browser.ntp.IncognitoNewTabPage; import org.chromium.chrome.browser.ntp.IncognitoNewTabPage;
import org.chromium.chrome.browser.ntp.NewTabPage; 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.omaha.UpdateMenuItemHelper;
import org.chromium.chrome.browser.omnibox.LocationBar; import org.chromium.chrome.browser.omnibox.LocationBar;
import org.chromium.chrome.browser.omnibox.SearchEngineLogoUtils; import org.chromium.chrome.browser.omnibox.SearchEngineLogoUtils;
...@@ -106,8 +102,6 @@ import org.chromium.chrome.browser.toolbar.top.ToolbarControlContainer; ...@@ -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.ToolbarLayout;
import org.chromium.chrome.browser.toolbar.top.TopToolbarCoordinator; import org.chromium.chrome.browser.toolbar.top.TopToolbarCoordinator;
import org.chromium.chrome.browser.toolbar.top.ViewShiftingActionBarDelegate; 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.ImmersiveModeManager;
import org.chromium.chrome.browser.ui.appmenu.AppMenuButtonHelper; import org.chromium.chrome.browser.ui.appmenu.AppMenuButtonHelper;
import org.chromium.chrome.browser.ui.appmenu.AppMenuCoordinator; import org.chromium.chrome.browser.ui.appmenu.AppMenuCoordinator;
...@@ -115,7 +109,6 @@ import org.chromium.chrome.browser.ui.appmenu.AppMenuHandler; ...@@ -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.AppMenuObserver;
import org.chromium.chrome.browser.ui.appmenu.AppMenuPropertiesDelegate; import org.chromium.chrome.browser.ui.appmenu.AppMenuPropertiesDelegate;
import org.chromium.chrome.browser.ui.appmenu.MenuButtonDelegate; 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.ui.widget.textbubble.TextBubble;
import org.chromium.chrome.browser.util.UrlConstants; import org.chromium.chrome.browser.util.UrlConstants;
import org.chromium.chrome.browser.widget.ScrimView; import org.chromium.chrome.browser.widget.ScrimView;
...@@ -123,7 +116,6 @@ import org.chromium.chrome.browser.widget.ScrimView.ScrimObserver; ...@@ -123,7 +116,6 @@ import org.chromium.chrome.browser.widget.ScrimView.ScrimObserver;
import org.chromium.chrome.browser.widget.ScrimView.ScrimParams; import org.chromium.chrome.browser.widget.ScrimView.ScrimParams;
import org.chromium.components.browser_ui.styles.ChromeColors; import org.chromium.components.browser_ui.styles.ChromeColors;
import org.chromium.components.feature_engagement.EventConstants; 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.feature_engagement.Tracker;
import org.chromium.components.search_engines.TemplateUrl; import org.chromium.components.search_engines.TemplateUrl;
import org.chromium.components.search_engines.TemplateUrlService; import org.chromium.components.search_engines.TemplateUrlService;
...@@ -138,7 +130,6 @@ import org.chromium.ui.base.DeviceFormFactor; ...@@ -138,7 +130,6 @@ import org.chromium.ui.base.DeviceFormFactor;
import org.chromium.ui.base.PageTransition; import org.chromium.ui.base.PageTransition;
import org.chromium.ui.util.TokenHolder; import org.chromium.ui.util.TokenHolder;
import org.chromium.ui.widget.Toast; import org.chromium.ui.widget.Toast;
import org.chromium.ui.widget.ViewRectProvider;
import java.util.List; import java.util.List;
...@@ -468,11 +459,6 @@ public class ToolbarManager implements ScrimObserver, ToolbarTabController, UrlF ...@@ -468,11 +459,6 @@ public class ToolbarManager implements ScrimObserver, ToolbarTabController, UrlF
@Override @Override
public void onPageLoadFinished(Tab tab, String url) { public void onPageLoadFinished(Tab tab, String url) {
if (tab.isShowingErrorPage()) {
handleIPHForErrorPageShown(tab);
return;
}
// TODO(crbug.com/896476): Remove this. // TODO(crbug.com/896476): Remove this.
if (Previews.isPreview(tab)) { if (Previews.isPreview(tab)) {
// Some previews (like Client LoFi) are not fully decided until the page // Some previews (like Client LoFi) are not fully decided until the page
...@@ -484,8 +470,6 @@ public class ToolbarManager implements ScrimObserver, ToolbarTabController, UrlF ...@@ -484,8 +470,6 @@ public class ToolbarManager implements ScrimObserver, ToolbarTabController, UrlF
PreviewsAndroidBridge.getInstance().getPreviewsType( PreviewsAndroidBridge.getInstance().getPreviewsType(
tab.getWebContents())); tab.getWebContents()));
} }
handleIPHForSuccessfulPageLoad(tab);
} }
@Override @Override
...@@ -652,34 +636,6 @@ public class ToolbarManager implements ScrimObserver, ToolbarTabController, UrlF ...@@ -652,34 +636,6 @@ public class ToolbarManager implements ScrimObserver, ToolbarTabController, UrlF
// anchored at the bottom of the controls container. // anchored at the bottom of the controls container.
setControlContainerTopMargin(getToolbarExtraYOffset()); 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() { mBookmarksObserver = new BookmarkBridge.BookmarkModelObserver() {
...@@ -946,84 +902,6 @@ public class ToolbarManager implements ScrimObserver, ToolbarTabController, UrlF ...@@ -946,84 +902,6 @@ public class ToolbarManager implements ScrimObserver, ToolbarTabController, UrlF
return mBottomControlsCoordinator; 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. * Initialize the manager with the components that had native initialization dependencies.
* <p> * <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 ...@@ -44,6 +44,15 @@ public class ViewRectProvider extends RectProvider
refreshRectBounds(); 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}. * Whether padding should be included in the {@link Rect} for the {@link View}.
* @param includePadding Whether padding should be included. Defaults to false. * @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