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 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