Commit 99a151ab authored by Theresa's avatar Theresa Committed by Commit Bot

Introduce AnchoredPopupWindow and refactor TextBubble

Introduce a new AnchoredPopupWindow class, containing most of the logic
previously in TextBubble, and refactor TextBubble to use this new class.
Also introduces a ViewRectProvider that contains most of the logic
previously in ViewAnchoredTextBubble.

This lays the ground work for using AnchoredPopupWindow (with extra
positioning customization/logic) for ListMenuButton to work around a bug
in Android ListPopupWindow positioning introduced in Android N.

BUG=709522

Change-Id: Ie6364ddcbd7b84736650ad3df8d433e1a75be6b6
Reviewed-on: https://chromium-review.googlesource.com/884328
Commit-Queue: Theresa <twellington@chromium.org>
Reviewed-by: default avatarMatthew Jones <mdjones@chromium.org>
Reviewed-by: default avatarDavid Trainor <dtrainor@chromium.org>
Cr-Commit-Position: refs/heads/master@{#532012}
parent e0f2031d
...@@ -127,11 +127,12 @@ import org.chromium.chrome.browser.util.IntentUtils; ...@@ -127,11 +127,12 @@ import org.chromium.chrome.browser.util.IntentUtils;
import org.chromium.chrome.browser.vr_shell.VrShellDelegate; import org.chromium.chrome.browser.vr_shell.VrShellDelegate;
import org.chromium.chrome.browser.widget.OverviewListLayout; import org.chromium.chrome.browser.widget.OverviewListLayout;
import org.chromium.chrome.browser.widget.ViewHighlighter; import org.chromium.chrome.browser.widget.ViewHighlighter;
import org.chromium.chrome.browser.widget.ViewRectProvider;
import org.chromium.chrome.browser.widget.bottomsheet.BottomSheet; import org.chromium.chrome.browser.widget.bottomsheet.BottomSheet;
import org.chromium.chrome.browser.widget.bottomsheet.BottomSheet.StateChangeReason; import org.chromium.chrome.browser.widget.bottomsheet.BottomSheet.StateChangeReason;
import org.chromium.chrome.browser.widget.bottomsheet.ChromeHomeIphMenuHeader; import org.chromium.chrome.browser.widget.bottomsheet.ChromeHomeIphMenuHeader;
import org.chromium.chrome.browser.widget.emptybackground.EmptyBackgroundViewWrapper; import org.chromium.chrome.browser.widget.emptybackground.EmptyBackgroundViewWrapper;
import org.chromium.chrome.browser.widget.textbubble.ViewAnchoredTextBubble; import org.chromium.chrome.browser.widget.textbubble.TextBubble;
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;
...@@ -928,9 +929,10 @@ public class ChromeTabbedActivity ...@@ -928,9 +929,10 @@ public class ChromeTabbedActivity
} }
} }
ViewAnchoredTextBubble textBubble = View anchorView = getToolbarAnchorViewForDownloadHomeTextBubble();
new ViewAnchoredTextBubble(this, getToolbarAnchorViewForDownloadHomeTextBubble(), ViewRectProvider rectProvider = new ViewRectProvider(anchorView);
R.string.iph_download_home_text, accessibilityStringId); TextBubble textBubble = new TextBubble(this, anchorView, R.string.iph_download_home_text,
accessibilityStringId, rectProvider);
textBubble.setDismissOnTouchInteraction(true); textBubble.setDismissOnTouchInteraction(true);
textBubble.addOnDismissListener(() -> mHandler.postDelayed(() -> { textBubble.addOnDismissListener(() -> mHandler.postDelayed(() -> {
tracker.dismissed(FeatureConstants.DOWNLOAD_HOME_FEATURE); tracker.dismissed(FeatureConstants.DOWNLOAD_HOME_FEATURE);
...@@ -944,7 +946,7 @@ public class ChromeTabbedActivity ...@@ -944,7 +946,7 @@ public class ChromeTabbedActivity
int yInsetPx = int yInsetPx =
getResources().getDimensionPixelOffset(R.dimen.text_bubble_menu_anchor_y_inset); getResources().getDimensionPixelOffset(R.dimen.text_bubble_menu_anchor_y_inset);
textBubble.setInsetPx(0, isChromeHomeExpandButtonEnabled ? yInsetPx : 0, 0, rectProvider.setInsetPx(0, isChromeHomeExpandButtonEnabled ? yInsetPx : 0, 0,
FeatureUtilities.isChromeHomeEnabled() ? 0 : yInsetPx); FeatureUtilities.isChromeHomeEnabled() ? 0 : yInsetPx);
textBubble.show(); textBubble.show();
} }
......
...@@ -12,6 +12,7 @@ import org.chromium.chrome.R; ...@@ -12,6 +12,7 @@ import org.chromium.chrome.R;
import org.chromium.chrome.browser.compositor.bottombar.contextualsearch.ContextualSearchPanel; import org.chromium.chrome.browser.compositor.bottombar.contextualsearch.ContextualSearchPanel;
import org.chromium.chrome.browser.feature_engagement.TrackerFactory; import org.chromium.chrome.browser.feature_engagement.TrackerFactory;
import org.chromium.chrome.browser.profiles.Profile; import org.chromium.chrome.browser.profiles.Profile;
import org.chromium.chrome.browser.widget.RectProvider;
import org.chromium.chrome.browser.widget.textbubble.TextBubble; import org.chromium.chrome.browser.widget.textbubble.TextBubble;
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;
...@@ -25,6 +26,7 @@ public class ContextualSearchIPH { ...@@ -25,6 +26,7 @@ public class ContextualSearchIPH {
private View mParentView; private View mParentView;
private ContextualSearchPanel mSearchPanel; private ContextualSearchPanel mSearchPanel;
private TextBubble mHelpBubble; private TextBubble mHelpBubble;
private RectProvider mRectProvider;
private String mFeatureName; private String mFeatureName;
private boolean mIsShowing; private boolean mIsShowing;
...@@ -109,8 +111,9 @@ public class ContextualSearchIPH { ...@@ -109,8 +111,9 @@ public class ContextualSearchIPH {
assert stringId != 0; assert stringId != 0;
assert mHelpBubble == null; assert mHelpBubble == null;
mHelpBubble = new TextBubble(mParentView.getContext(), mParentView, stringId, stringId); mRectProvider = new RectProvider(getHelpBubbleAnchorRect());
mHelpBubble.setAnchorRect(getHelpBubbleAnchorRect()); mHelpBubble = new TextBubble(
mParentView.getContext(), mParentView, stringId, stringId, mRectProvider);
mHelpBubble.setDismissOnTouchInteraction(true); mHelpBubble.setDismissOnTouchInteraction(true);
mHelpBubble.addOnDismissListener(() -> { mHelpBubble.addOnDismissListener(() -> {
...@@ -129,7 +132,7 @@ public class ContextualSearchIPH { ...@@ -129,7 +132,7 @@ public class ContextualSearchIPH {
void updateBubblePosition() { void updateBubblePosition() {
if (!mIsShowing || mHelpBubble == null || !mHelpBubble.isShowing()) return; if (!mIsShowing || mHelpBubble == null || !mHelpBubble.isShowing()) return;
mHelpBubble.setAnchorRect(getHelpBubbleAnchorRect()); mRectProvider.setRect(getHelpBubbleAnchorRect());
} }
/** /**
......
...@@ -12,14 +12,14 @@ import org.chromium.chrome.browser.feature_engagement.TrackerFactory; ...@@ -12,14 +12,14 @@ import org.chromium.chrome.browser.feature_engagement.TrackerFactory;
import org.chromium.chrome.browser.infobar.IPHInfoBarSupport.PopupState; import org.chromium.chrome.browser.infobar.IPHInfoBarSupport.PopupState;
import org.chromium.chrome.browser.infobar.IPHInfoBarSupport.TrackerParameters; import org.chromium.chrome.browser.infobar.IPHInfoBarSupport.TrackerParameters;
import org.chromium.chrome.browser.profiles.Profile; import org.chromium.chrome.browser.profiles.Profile;
import org.chromium.chrome.browser.widget.textbubble.ViewAnchoredTextBubble; import org.chromium.chrome.browser.widget.textbubble.TextBubble;
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;
/** /**
* Default implementation of {@link IPHInfoBarSupport.IPHBubbleDelegate} that handles interacting * Default implementation of {@link IPHInfoBarSupport.IPHBubbleDelegate} that handles interacting
* with {@link Tracker} and creating a {@link ViewAnchoredTextBubble} based on the type of infobar * with {@link Tracker} and creating a {@link TextBubble} based on the type of infobar
* shown. * shown.
*/ */
class IPHBubbleDelegateImpl implements IPHInfoBarSupport.IPHBubbleDelegate { class IPHBubbleDelegateImpl implements IPHInfoBarSupport.IPHBubbleDelegate {
...@@ -42,8 +42,8 @@ class IPHBubbleDelegateImpl implements IPHInfoBarSupport.IPHBubbleDelegate { ...@@ -42,8 +42,8 @@ class IPHBubbleDelegateImpl implements IPHInfoBarSupport.IPHBubbleDelegate {
PopupState state = new PopupState(); PopupState state = new PopupState();
state.view = anchorView; state.view = anchorView;
state.feature = params.feature; state.feature = params.feature;
state.bubble = new ViewAnchoredTextBubble( state.bubble = new TextBubble(
mContext, anchorView, params.textId, params.accessibilityTextId); mContext, anchorView, params.textId, params.accessibilityTextId, anchorView);
state.bubble.setDismissOnTouchInteraction(true); state.bubble.setDismissOnTouchInteraction(true);
return state; return state;
......
...@@ -103,7 +103,7 @@ class IPHInfoBarSupport implements OnDismissListener, InfoBarContainer.InfoBarAn ...@@ -103,7 +103,7 @@ class IPHInfoBarSupport implements OnDismissListener, InfoBarContainer.InfoBarAn
@Override @Override
public void notifyAnimationFinished(int animationType) {} public void notifyAnimationFinished(int animationType) {}
// Calling {@link ViewAnchoredTextBubble#dismiss()} will invoke {@link #onDismiss} which will // Calling {@link TextBubble#dismiss()} will invoke {@link #onDismiss} which will
// set the value of {@link #mCurrentState} to null, which is what the assert checks. Since this // set the value of {@link #mCurrentState} to null, which is what the assert checks. Since this
// goes through the Android SDK, FindBugs does not see this as happening, so the FindBugs // goes through the Android SDK, FindBugs does not see this as happening, so the FindBugs
// warning for a field guaranteed to be non-null being checked for null equality needs to be // warning for a field guaranteed to be non-null being checked for null equality needs to be
...@@ -133,7 +133,7 @@ class IPHInfoBarSupport implements OnDismissListener, InfoBarContainer.InfoBarAn ...@@ -133,7 +133,7 @@ class IPHInfoBarSupport implements OnDismissListener, InfoBarContainer.InfoBarAn
@Override @Override
public void onAddInfoBar(InfoBarContainer container, InfoBar infoBar, boolean isFirst) {} public void onAddInfoBar(InfoBarContainer container, InfoBar infoBar, boolean isFirst) {}
// Calling {@link ViewAnchoredTextBubble#dismiss()} will invoke {@link #onDismiss} which will // Calling {@link TextBubble#dismiss()} will invoke {@link #onDismiss} which will
// set the value of {@link #mCurrentState} to null, which is what the assert checks. Since this // set the value of {@link #mCurrentState} to null, which is what the assert checks. Since this
// goes through the Android SDK, FindBugs does not see this as happening, so the FindBugs // goes through the Android SDK, FindBugs does not see this as happening, so the FindBugs
// warning for a field guaranteed to be non-null being checked for null equality needs to be // warning for a field guaranteed to be non-null being checked for null equality needs to be
......
...@@ -94,9 +94,10 @@ import org.chromium.chrome.browser.tabmodel.TabReparentingParams; ...@@ -94,9 +94,10 @@ import org.chromium.chrome.browser.tabmodel.TabReparentingParams;
import org.chromium.chrome.browser.util.ColorUtils; import org.chromium.chrome.browser.util.ColorUtils;
import org.chromium.chrome.browser.util.FeatureUtilities; import org.chromium.chrome.browser.util.FeatureUtilities;
import org.chromium.chrome.browser.vr_shell.VrShellDelegate; import org.chromium.chrome.browser.vr_shell.VrShellDelegate;
import org.chromium.chrome.browser.widget.AnchoredPopupWindow;
import org.chromium.chrome.browser.widget.PulseDrawable; import org.chromium.chrome.browser.widget.PulseDrawable;
import org.chromium.chrome.browser.widget.ViewRectProvider;
import org.chromium.chrome.browser.widget.textbubble.TextBubble; import org.chromium.chrome.browser.widget.textbubble.TextBubble;
import org.chromium.chrome.browser.widget.textbubble.ViewAnchoredTextBubble;
import org.chromium.components.dom_distiller.core.DomDistillerUrlUtils; import org.chromium.components.dom_distiller.core.DomDistillerUrlUtils;
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;
...@@ -1731,10 +1732,11 @@ public class Tab ...@@ -1731,10 +1732,11 @@ public class Tab
if (!(getActivity() instanceof ChromeTabbedActivity)) return; if (!(getActivity() instanceof ChromeTabbedActivity)) return;
ViewAnchoredTextBubble textBubble = new ViewAnchoredTextBubble(getActivity(), View anchorView = getActivity().getToolbarManager().getMenuButton();
getActivity().getToolbarManager().getMenuButton(), ViewRectProvider rectProvider = new ViewRectProvider(anchorView);
R.string.iph_data_saver_detail_text, TextBubble textBubble =
R.string.iph_data_saver_detail_accessibility_text); new TextBubble(getActivity(), anchorView, R.string.iph_data_saver_detail_text,
R.string.iph_data_saver_detail_accessibility_text, rectProvider);
textBubble.setDismissOnTouchInteraction(true); textBubble.setDismissOnTouchInteraction(true);
getActivity().getAppMenuHandler().setMenuHighlight(R.id.app_menu_footer); getActivity().getAppMenuHandler().setMenuHighlight(R.id.app_menu_footer);
textBubble.addOnDismissListener(new OnDismissListener() { textBubble.addOnDismissListener(new OnDismissListener() {
...@@ -1751,7 +1753,7 @@ public class Tab ...@@ -1751,7 +1753,7 @@ public class Tab
}); });
int yInsetPx = mThemedApplicationContext.getResources().getDimensionPixelOffset( int yInsetPx = mThemedApplicationContext.getResources().getDimensionPixelOffset(
R.dimen.text_bubble_menu_anchor_y_inset); R.dimen.text_bubble_menu_anchor_y_inset);
textBubble.setInsetPx(0, FeatureUtilities.isChromeHomeEnabled() ? yInsetPx : 0, 0, rectProvider.setInsetPx(0, FeatureUtilities.isChromeHomeEnabled() ? yInsetPx : 0, 0,
FeatureUtilities.isChromeHomeEnabled() ? 0 : yInsetPx); FeatureUtilities.isChromeHomeEnabled() ? 0 : yInsetPx);
textBubble.show(); textBubble.show();
} }
...@@ -3382,6 +3384,8 @@ public class Tab ...@@ -3382,6 +3384,8 @@ public class Tab
@CalledByNative @CalledByNative
private void showMediaDownloadInProductHelp(int x, int y, int width, int height) { private void showMediaDownloadInProductHelp(int x, int y, int width, int height) {
Rect rect = new Rect(x, y, x + width, y + height);
// If we are not currently showing the widget, ask the tracker if we can show it. // If we are not currently showing the widget, ask the tracker if we can show it.
if (mDownloadIPHBubble == null) { if (mDownloadIPHBubble == null) {
Tracker tracker = TrackerFactory.getTrackerForProfile(Profile.getLastUsedProfile()); Tracker tracker = TrackerFactory.getTrackerForProfile(Profile.getLastUsedProfile());
...@@ -3395,7 +3399,7 @@ public class Tab ...@@ -3395,7 +3399,7 @@ public class Tab
mDownloadIPHBubble = new TextBubble(getApplicationContext(), mDownloadIPHBubble = new TextBubble(getApplicationContext(),
mContentViewCore.getContainerView(), R.string.iph_media_download_text, mContentViewCore.getContainerView(), R.string.iph_media_download_text,
R.string.iph_media_download_accessibility_text); R.string.iph_media_download_accessibility_text, rect);
mDownloadIPHBubble.setDismissOnTouchInteraction(true); mDownloadIPHBubble.setDismissOnTouchInteraction(true);
mDownloadIPHBubble.addOnDismissListener(new OnDismissListener() { mDownloadIPHBubble.addOnDismissListener(new OnDismissListener() {
@Override @Override
...@@ -3410,9 +3414,7 @@ public class Tab ...@@ -3410,9 +3414,7 @@ public class Tab
}); });
} }
Rect rect = new Rect(x, y, x + width, y + height); mDownloadIPHBubble.setPreferredOrientation(AnchoredPopupWindow.Orientation.BELOW);
mDownloadIPHBubble.setAnchorRect(rect);
mDownloadIPHBubble.setPreferredOrientation(TextBubble.Orientation.BELOW);
mDownloadIPHBubble.show(); mDownloadIPHBubble.show();
createPulse(rect); createPulse(rect);
} }
......
...@@ -12,8 +12,8 @@ import android.view.View; ...@@ -12,8 +12,8 @@ import android.view.View;
import org.chromium.base.ContextUtils; import org.chromium.base.ContextUtils;
import org.chromium.chrome.R; import org.chromium.chrome.R;
import org.chromium.chrome.browser.widget.ViewRectProvider;
import org.chromium.chrome.browser.widget.textbubble.TextBubble; import org.chromium.chrome.browser.widget.textbubble.TextBubble;
import org.chromium.chrome.browser.widget.textbubble.ViewAnchoredTextBubble;
/** /**
* Draws a bubble pointing upward at the tab switcher button. * Draws a bubble pointing upward at the tab switcher button.
...@@ -37,12 +37,15 @@ public class TabSwitcherCallout { ...@@ -37,12 +37,15 @@ public class TabSwitcherCallout {
if (!isTabSwitcherCalloutNecessary()) return null; if (!isTabSwitcherCalloutNecessary()) return null;
setIsTabSwitcherCalloutNecessary(false); setIsTabSwitcherCalloutNecessary(false);
ViewAnchoredTextBubble bubble = new ViewAnchoredTextBubble(context, tabSwitcherButton, ViewRectProvider rectProvider = new ViewRectProvider(tabSwitcherButton);
R.string.tab_switcher_callout_body, R.string.tab_switcher_callout_body); int yInsetPx = (int) (Y_OVERLAP_DP * context.getResources().getDisplayMetrics().density);
rectProvider.setInsetPx(0, yInsetPx, 0, yInsetPx);
TextBubble bubble =
new TextBubble(context, tabSwitcherButton, R.string.tab_switcher_callout_body,
R.string.tab_switcher_callout_body, rectProvider);
bubble.setDismissOnTouchInteraction(true); bubble.setDismissOnTouchInteraction(true);
bubble.setAutoDismissTimeout(TAB_SWITCHER_CALLOUT_DISMISS_MS); bubble.setAutoDismissTimeout(TAB_SWITCHER_CALLOUT_DISMISS_MS);
int yInsetPx = (int) (Y_OVERLAP_DP * context.getResources().getDisplayMetrics().density);
bubble.setInsetPx(0, yInsetPx, 0, yInsetPx);
bubble.show(); bubble.show();
return bubble; return bubble;
} }
......
...@@ -74,9 +74,10 @@ import org.chromium.chrome.browser.tabmodel.TabModelSelectorObserver; ...@@ -74,9 +74,10 @@ import org.chromium.chrome.browser.tabmodel.TabModelSelectorObserver;
import org.chromium.chrome.browser.toolbar.ActionModeController.ActionBarDelegate; import org.chromium.chrome.browser.toolbar.ActionModeController.ActionBarDelegate;
import org.chromium.chrome.browser.util.FeatureUtilities; import org.chromium.chrome.browser.util.FeatureUtilities;
import org.chromium.chrome.browser.widget.ViewHighlighter; import org.chromium.chrome.browser.widget.ViewHighlighter;
import org.chromium.chrome.browser.widget.ViewRectProvider;
import org.chromium.chrome.browser.widget.findinpage.FindToolbarManager; import org.chromium.chrome.browser.widget.findinpage.FindToolbarManager;
import org.chromium.chrome.browser.widget.findinpage.FindToolbarObserver; import org.chromium.chrome.browser.widget.findinpage.FindToolbarObserver;
import org.chromium.chrome.browser.widget.textbubble.ViewAnchoredTextBubble; import org.chromium.chrome.browser.widget.textbubble.TextBubble;
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;
...@@ -167,7 +168,7 @@ public class ToolbarManager implements ToolbarTabController, UrlFocusChangeListe ...@@ -167,7 +168,7 @@ public class ToolbarManager implements ToolbarTabController, UrlFocusChangeListe
private AppMenuButtonHelper mAppMenuButtonHelper; private AppMenuButtonHelper mAppMenuButtonHelper;
private ViewAnchoredTextBubble mTextBubble; private TextBubble mTextBubble;
private HomepageStateListener mHomepageStateListener; private HomepageStateListener mHomepageStateListener;
...@@ -611,9 +612,14 @@ public class ToolbarManager implements ToolbarTabController, UrlFocusChangeListe ...@@ -611,9 +612,14 @@ public class ToolbarManager implements ToolbarTabController, UrlFocusChangeListe
if (!tracker.shouldTriggerHelpUI(featureName)) return; if (!tracker.shouldTriggerHelpUI(featureName)) return;
mTextBubble = new ViewAnchoredTextBubble(mToolbar.getContext(), getMenuButton(), ViewRectProvider rectProvider = new ViewRectProvider(getMenuButton());
int yInsetPx = mToolbar.getContext().getResources().getDimensionPixelOffset(
R.dimen.text_bubble_menu_anchor_y_inset);
rectProvider.setInsetPx(0, FeatureUtilities.isChromeHomeEnabled() ? yInsetPx : 0, 0,
FeatureUtilities.isChromeHomeEnabled() ? 0 : yInsetPx);
mTextBubble = new TextBubble(mToolbar.getContext(), getMenuButton(),
R.string.iph_download_page_for_offline_usage_text, R.string.iph_download_page_for_offline_usage_text,
R.string.iph_download_page_for_offline_usage_accessibility_text); R.string.iph_download_page_for_offline_usage_accessibility_text, rectProvider);
mTextBubble.setDismissOnTouchInteraction(true); mTextBubble.setDismissOnTouchInteraction(true);
mTextBubble.addOnDismissListener(new OnDismissListener() { mTextBubble.addOnDismissListener(new OnDismissListener() {
@Override @Override
...@@ -628,10 +634,6 @@ public class ToolbarManager implements ToolbarTabController, UrlFocusChangeListe ...@@ -628,10 +634,6 @@ public class ToolbarManager implements ToolbarTabController, UrlFocusChangeListe
} }
}); });
activity.getAppMenuHandler().setMenuHighlight(R.id.offline_page_id); activity.getAppMenuHandler().setMenuHighlight(R.id.offline_page_id);
int yInsetPx = mToolbar.getContext().getResources().getDimensionPixelOffset(
R.dimen.text_bubble_menu_anchor_y_inset);
mTextBubble.setInsetPx(0, FeatureUtilities.isChromeHomeEnabled() ? yInsetPx : 0, 0,
FeatureUtilities.isChromeHomeEnabled() ? 0 : yInsetPx);
mTextBubble.show(); mTextBubble.show();
} }
......
// 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.widget;
import android.graphics.Rect;
/**
* Provides a {@link Rect} object that represents a position in screen space.
*/
public class RectProvider {
/** An observer to be notified of changes to the {@Rect} position. */
public interface Observer {
/**
* Called when the {@link Rect} location has changed. The new {@link Rect} can be retrieved
* using {@link #getRect()}.
*/
void onRectChanged();
/** Called when the {@link Rect} is no longer visible. */
void onRectHidden();
}
/**
* The {@link Rect} provided by this provider. This is the Rect that will be passed to
* observers and returned from {@link #getRect()}.
*/
protected final Rect mRect = new Rect();
private Observer mObserver;
/** Creates an instance of a {@link RectProvider}. */
public RectProvider() {}
/**
* Creates an instance of a {@link RectProvider}.
* @param rect The {@link Rect} to provide.
*/
public RectProvider(Rect rect) {
mRect.set(rect);
}
/**
* Sets the {@link Rect} provided by this provider.
* @param rect The {@link Rect} to provide.
*/
public void setRect(Rect rect) {
mRect.set(rect);
notifyRectChanged();
}
/**
* Start observing changes to the {@link Rect}'s position in the window. This does not guarantee
* an immediate call to observer methods. Use {@link #getRect()} to retrieve the {@link Rect}.
* @param observer The {@link Observer} to be notified of changes.
*/
public void startObserving(Observer observer) {
assert mObserver == null || mObserver == observer;
mObserver = observer;
}
/**
* Stop observing changes to the {@link Rect}'s position in the window.
*/
public void stopObserving() {
mObserver = null;
}
/** @return The {@link Rect} that this provider represents. */
public Rect getRect() {
return mRect;
}
/** Notify the observer that the {@link Rect} changed. */
protected void notifyRectChanged() {
if (mObserver != null) mObserver.onRectChanged();
}
/** Notify the observer that the {@link Rect} is hidden. */
protected void notifyRectHidden() {
if (mObserver != null) mObserver.onRectHidden();
}
}
// Copyright 2017 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.widget.textbubble; package org.chromium.chrome.browser.widget;
import android.content.Context;
import android.graphics.Rect; import android.graphics.Rect;
import android.support.annotation.StringRes;
import android.view.View; import android.view.View;
import android.view.ViewTreeObserver; import android.view.ViewTreeObserver;
import android.view.ViewTreeObserver.OnPreDrawListener;
import android.widget.PopupWindow.OnDismissListener;
import org.chromium.base.ApiCompatibilityUtils; import org.chromium.base.ApiCompatibilityUtils;
import org.chromium.content.browser.PositionObserver;
import org.chromium.content.browser.ViewPositionObserver;
/** /**
* A helper class that anchors a {@link TextBubble} to a particular {@link View}. The bubble will * Provides a {@Rect} for the location of a {@View} in its window, see
* listen to layout events on the {@link View} and update accordingly. * {@link View#getLocationOnScreen(int[])}.
*/ */
public class ViewAnchoredTextBubble extends TextBubble public class ViewRectProvider extends RectProvider
implements PositionObserver.Listener, ViewTreeObserver.OnGlobalLayoutListener, implements ViewTreeObserver.OnGlobalLayoutListener, View.OnAttachStateChangeListener,
View.OnAttachStateChangeListener, OnPreDrawListener, OnDismissListener { ViewTreeObserver.OnPreDrawListener {
private final int[] mCachedWindowCoordinates = new int[2]; private final int[] mCachedWindowCoordinates = new int[2];
private final Rect mAnchorRect = new Rect();
private final Rect mInsetRect = new Rect(); private final Rect mInsetRect = new Rect();
private final View mAnchorView; private final View mView;
private final ViewPositionObserver mViewPositionObserver;
/** If not {@code null}, the {@link ViewTreeObserver} that we are registered to. */ /** If not {@code null}, the {@link ViewTreeObserver} that we are registered to. */
private ViewTreeObserver mViewTreeObserver; private ViewTreeObserver mViewTreeObserver;
/** /**
* Creates an instance of a {@link ViewAnchoredTextBubble}. * Creates an instance of a {@link ViewRectProvider}.
* @param context Context to draw resources from. * @param view The {@link View} used to generate a {@link Rect}.
* @param anchorView The {@link View} to anchor to.
* @param stringId The id of the string resource for the text that should be shown.
* @param accessibilityStringId The id of the string resource of the accessibility text.
*/ */
public ViewAnchoredTextBubble(Context context, View anchorView, @StringRes int stringId, public ViewRectProvider(View view) {
@StringRes int accessibilityStringId) { mView = view;
super(context, anchorView.getRootView(), stringId, accessibilityStringId); mCachedWindowCoordinates[0] = -1;
mAnchorView = anchorView; mCachedWindowCoordinates[1] = -1;
mViewPositionObserver = new ViewPositionObserver(mAnchorView);
} }
/** /**
* Specifies the inset values in pixels that determine how to shrink the {@link View} bounds * Specifies the inset values in pixels that determine how to shrink the {@link View} bounds
* when creating the anchor {@link Rect}. * when creating the {@link Rect}.
*/ */
public void setInsetPx(int left, int top, int right, int bottom) { public void setInsetPx(int left, int top, int right, int bottom) {
mInsetRect.set(left, top, right, bottom); mInsetRect.set(left, top, right, bottom);
refreshAnchorBounds(); refreshRectBounds();
} }
// TextBubble implementation.
@Override @Override
public void show() { public void startObserving(Observer observer) {
mViewPositionObserver.addListener(this); mView.addOnAttachStateChangeListener(this);
mAnchorView.addOnAttachStateChangeListener(this); mViewTreeObserver = mView.getViewTreeObserver();
mViewTreeObserver = mAnchorView.getViewTreeObserver();
mViewTreeObserver.addOnGlobalLayoutListener(this); mViewTreeObserver.addOnGlobalLayoutListener(this);
mViewTreeObserver.addOnPreDrawListener(this); mViewTreeObserver.addOnPreDrawListener(this);
refreshAnchorBounds(); refreshRectBounds();
super.show();
super.startObserving(observer);
} }
@Override @Override
public void onDismiss() { public void stopObserving() {
mViewPositionObserver.removeListener(this); mView.removeOnAttachStateChangeListener(this);
mAnchorView.removeOnAttachStateChangeListener(this);
if (mViewTreeObserver != null && mViewTreeObserver.isAlive()) { if (mViewTreeObserver != null && mViewTreeObserver.isAlive()) {
mViewTreeObserver.removeOnGlobalLayoutListener(this); mViewTreeObserver.removeOnGlobalLayoutListener(this);
mViewTreeObserver.removeOnPreDrawListener(this); mViewTreeObserver.removeOnPreDrawListener(this);
} }
mViewTreeObserver = null; mViewTreeObserver = null;
super.stopObserving();
} }
// ViewTreeObserver.OnGlobalLayoutListener implementation. // ViewTreeObserver.OnGlobalLayoutListener implementation.
@Override @Override
public void onGlobalLayout() { public void onGlobalLayout() {
if (!mAnchorView.isShown()) dismiss(); if (!mView.isShown()) notifyRectHidden();
} }
// ViewTreeObserver.OnPreDrawListener implementation. // ViewTreeObserver.OnPreDrawListener implementation.
@Override @Override
public boolean onPreDraw() { public boolean onPreDraw() {
if (!mAnchorView.isShown()) dismiss(); if (!mView.isShown()) {
notifyRectHidden();
} else {
refreshRectBounds();
}
return true; return true;
} }
...@@ -101,42 +92,46 @@ public class ViewAnchoredTextBubble extends TextBubble ...@@ -101,42 +92,46 @@ public class ViewAnchoredTextBubble extends TextBubble
@Override @Override
public void onViewDetachedFromWindow(View v) { public void onViewDetachedFromWindow(View v) {
dismiss(); notifyRectHidden();
} }
// PositionObserver.Listener implementation. private void refreshRectBounds() {
@Override int previousPositionX = mCachedWindowCoordinates[0];
public void onPositionChanged(int positionX, int positionY) { int previousPositionY = mCachedWindowCoordinates[1];
refreshAnchorBounds(); mView.getLocationInWindow(mCachedWindowCoordinates);
}
private void refreshAnchorBounds() { // Return if the window position is invalid.
mAnchorView.getLocationInWindow(mCachedWindowCoordinates);
if (mCachedWindowCoordinates[0] < 0 || mCachedWindowCoordinates[1] < 0) return; if (mCachedWindowCoordinates[0] < 0 || mCachedWindowCoordinates[1] < 0) return;
mAnchorRect.left = mCachedWindowCoordinates[0]; // Return if the window coordinates haven't changed.
mAnchorRect.top = mCachedWindowCoordinates[1]; if (mCachedWindowCoordinates[0] == previousPositionX
mAnchorRect.right = mAnchorRect.left + mAnchorView.getWidth(); && mCachedWindowCoordinates[1] == previousPositionY) {
mAnchorRect.bottom = mAnchorRect.top + mAnchorView.getHeight(); return;
}
mRect.left = mCachedWindowCoordinates[0];
mRect.top = mCachedWindowCoordinates[1];
mRect.right = mRect.left + mView.getWidth();
mRect.bottom = mRect.top + mView.getHeight();
mAnchorRect.left += mInsetRect.left; mRect.left += mInsetRect.left;
mAnchorRect.top += mInsetRect.top; mRect.top += mInsetRect.top;
mAnchorRect.right -= mInsetRect.right; mRect.right -= mInsetRect.right;
mAnchorRect.bottom -= mInsetRect.bottom; mRect.bottom -= mInsetRect.bottom;
// Account for the padding. // Account for the padding.
boolean isRtl = ApiCompatibilityUtils.isLayoutRtl(mAnchorView); boolean isRtl = ApiCompatibilityUtils.isLayoutRtl(mView);
mAnchorRect.left += isRtl ? ApiCompatibilityUtils.getPaddingEnd(mAnchorView) mRect.left += isRtl ? ApiCompatibilityUtils.getPaddingEnd(mView)
: ApiCompatibilityUtils.getPaddingStart(mAnchorView); : ApiCompatibilityUtils.getPaddingStart(mView);
mAnchorRect.right -= isRtl ? ApiCompatibilityUtils.getPaddingStart(mAnchorView) mRect.right -= isRtl ? ApiCompatibilityUtils.getPaddingStart(mView)
: ApiCompatibilityUtils.getPaddingEnd(mAnchorView); : ApiCompatibilityUtils.getPaddingEnd(mView);
mAnchorRect.top += mAnchorView.getPaddingTop(); mRect.top += mView.getPaddingTop();
mAnchorRect.bottom -= mAnchorView.getPaddingBottom(); mRect.bottom -= mView.getPaddingBottom();
// Make sure we still have a valid Rect after applying the inset. // Make sure we still have a valid Rect after applying the inset.
mAnchorRect.right = Math.max(mAnchorRect.left, mAnchorRect.right); mRect.right = Math.max(mRect.left, mRect.right);
mAnchorRect.bottom = Math.max(mAnchorRect.top, mAnchorRect.bottom); mRect.bottom = Math.max(mRect.top, mRect.bottom);
setAnchorRect(mAnchorRect); notifyRectChanged();
} }
} }
\ No newline at end of file
...@@ -50,11 +50,12 @@ import org.chromium.chrome.browser.tabmodel.TabModelSelectorObserver; ...@@ -50,11 +50,12 @@ import org.chromium.chrome.browser.tabmodel.TabModelSelectorObserver;
import org.chromium.chrome.browser.util.MathUtils; import org.chromium.chrome.browser.util.MathUtils;
import org.chromium.chrome.browser.util.ViewUtils; import org.chromium.chrome.browser.util.ViewUtils;
import org.chromium.chrome.browser.widget.ViewHighlighter; import org.chromium.chrome.browser.widget.ViewHighlighter;
import org.chromium.chrome.browser.widget.ViewRectProvider;
import org.chromium.chrome.browser.widget.bottomsheet.BottomSheet.BottomSheetContent; import org.chromium.chrome.browser.widget.bottomsheet.BottomSheet.BottomSheetContent;
import org.chromium.chrome.browser.widget.bottomsheet.BottomSheet.StateChangeReason; import org.chromium.chrome.browser.widget.bottomsheet.BottomSheet.StateChangeReason;
import org.chromium.chrome.browser.widget.bottomsheet.base.BottomNavigationView; import org.chromium.chrome.browser.widget.bottomsheet.base.BottomNavigationView;
import org.chromium.chrome.browser.widget.bottomsheet.base.BottomNavigationView.OnNavigationItemSelectedListener; import org.chromium.chrome.browser.widget.bottomsheet.base.BottomNavigationView.OnNavigationItemSelectedListener;
import org.chromium.chrome.browser.widget.textbubble.ViewAnchoredTextBubble; import org.chromium.chrome.browser.widget.textbubble.TextBubble;
import org.chromium.content.browser.BrowserStartupController; import org.chromium.content.browser.BrowserStartupController;
import org.chromium.ui.UiUtils; import org.chromium.ui.UiUtils;
...@@ -801,8 +802,9 @@ public class BottomSheetContentController ...@@ -801,8 +802,9 @@ public class BottomSheetContentController
throw new RuntimeException("Attempting to show invalid content in bottom sheet."); throw new RuntimeException("Attempting to show invalid content in bottom sheet.");
} }
final ViewAnchoredTextBubble helpBubble = ViewRectProvider rectProvider = new ViewRectProvider(mBottomSheet);
new ViewAnchoredTextBubble(getContext(), mBottomSheet, stringId, stringId); final TextBubble helpBubble =
new TextBubble(getContext(), mBottomSheet, stringId, stringId, rectProvider);
helpBubble.setDismissOnTouchInteraction(true); helpBubble.setDismissOnTouchInteraction(true);
mBottomSheet.addObserver(new EmptyBottomSheetObserver() { mBottomSheet.addObserver(new EmptyBottomSheetObserver() {
...@@ -829,7 +831,7 @@ public class BottomSheetContentController ...@@ -829,7 +831,7 @@ public class BottomSheetContentController
if (state != BottomSheet.SHEET_STATE_HALF || mHelpBubbleShown) return; if (state != BottomSheet.SHEET_STATE_HALF || mHelpBubbleShown) return;
int inset = getContext().getResources().getDimensionPixelSize( int inset = getContext().getResources().getDimensionPixelSize(
R.dimen.bottom_sheet_help_bubble_inset); R.dimen.bottom_sheet_help_bubble_inset);
helpBubble.setInsetPx(0, inset, 0, inset); rectProvider.setInsetPx(0, inset, 0, inset);
helpBubble.show(); helpBubble.show();
mHelpBubbleShown = true; mHelpBubbleShown = true;
} }
......
...@@ -24,9 +24,9 @@ import org.chromium.chrome.browser.fullscreen.ChromeFullscreenManager; ...@@ -24,9 +24,9 @@ import org.chromium.chrome.browser.fullscreen.ChromeFullscreenManager;
import org.chromium.chrome.browser.profiles.Profile; import org.chromium.chrome.browser.profiles.Profile;
import org.chromium.chrome.browser.toolbar.BottomToolbarPhone; import org.chromium.chrome.browser.toolbar.BottomToolbarPhone;
import org.chromium.chrome.browser.widget.ViewHighlighter; import org.chromium.chrome.browser.widget.ViewHighlighter;
import org.chromium.chrome.browser.widget.ViewRectProvider;
import org.chromium.chrome.browser.widget.bottomsheet.BottomSheet.StateChangeReason; import org.chromium.chrome.browser.widget.bottomsheet.BottomSheet.StateChangeReason;
import org.chromium.chrome.browser.widget.textbubble.TextBubble; import org.chromium.chrome.browser.widget.textbubble.TextBubble;
import org.chromium.chrome.browser.widget.textbubble.ViewAnchoredTextBubble;
import org.chromium.components.feature_engagement.EventConstants; import org.chromium.components.feature_engagement.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;
...@@ -218,16 +218,16 @@ public class ChromeHomeIphBubbleController { ...@@ -218,16 +218,16 @@ public class ChromeHomeIphBubbleController {
}; };
if (showAtTopOfScreen) { if (showAtTopOfScreen) {
mHelpBubble = mHelpBubble = new TextBubble(mContext, topAnchorView, stringId, accessibilityStringId,
new TextBubble(mContext, topAnchorView, stringId, accessibilityStringId, false); false, getTopAnchorRect(topAnchorView));
mHelpBubble.setAnchorRect(getTopAnchorRect(topAnchorView));
topAnchorView.addOnLayoutChangeListener(topAnchorLayoutChangeListener); topAnchorView.addOnLayoutChangeListener(topAnchorLayoutChangeListener);
} else { } else {
mHelpBubble = new ViewAnchoredTextBubble( ViewRectProvider rectProvider = new ViewRectProvider(anchorView);
mContext, anchorView, stringId, accessibilityStringId);
int inset = mContext.getResources().getDimensionPixelSize( int inset = mContext.getResources().getDimensionPixelSize(
R.dimen.bottom_sheet_help_bubble_inset); R.dimen.bottom_sheet_help_bubble_inset);
((ViewAnchoredTextBubble) mHelpBubble).setInsetPx(0, inset, 0, inset); rectProvider.setInsetPx(0, inset, 0, inset);
mHelpBubble = new TextBubble(
mContext, anchorView, stringId, accessibilityStringId, rectProvider);
} }
if (ChromeFeatureList.isEnabled(ChromeFeatureList.CHROME_HOME_PERSISTENT_IPH)) { if (ChromeFeatureList.isEnabled(ChromeFeatureList.CHROME_HOME_PERSISTENT_IPH)) {
......
...@@ -1333,6 +1333,7 @@ chrome_java_sources = [ ...@@ -1333,6 +1333,7 @@ chrome_java_sources = [
"java/src/org/chromium/chrome/browser/webshare/ShareServiceImplementationFactory.java", "java/src/org/chromium/chrome/browser/webshare/ShareServiceImplementationFactory.java",
"java/src/org/chromium/chrome/browser/widget/AlertDialogEditText.java", "java/src/org/chromium/chrome/browser/widget/AlertDialogEditText.java",
"java/src/org/chromium/chrome/browser/widget/AlwaysDismissedDialog.java", "java/src/org/chromium/chrome/browser/widget/AlwaysDismissedDialog.java",
"java/src/org/chromium/chrome/browser/widget/AnchoredPopupWindow.java",
"java/src/org/chromium/chrome/browser/widget/BoundedLinearLayout.java", "java/src/org/chromium/chrome/browser/widget/BoundedLinearLayout.java",
"java/src/org/chromium/chrome/browser/widget/ClipDrawableProgressBar.java", "java/src/org/chromium/chrome/browser/widget/ClipDrawableProgressBar.java",
"java/src/org/chromium/chrome/browser/widget/CompatibilityTextInputLayout.java", "java/src/org/chromium/chrome/browser/widget/CompatibilityTextInputLayout.java",
...@@ -1360,6 +1361,7 @@ chrome_java_sources = [ ...@@ -1360,6 +1361,7 @@ chrome_java_sources = [
"java/src/org/chromium/chrome/browser/widget/PulseInterpolator.java", "java/src/org/chromium/chrome/browser/widget/PulseInterpolator.java",
"java/src/org/chromium/chrome/browser/widget/RadioButtonLayout.java", "java/src/org/chromium/chrome/browser/widget/RadioButtonLayout.java",
"java/src/org/chromium/chrome/browser/widget/RadioButtonWithDescription.java", "java/src/org/chromium/chrome/browser/widget/RadioButtonWithDescription.java",
"java/src/org/chromium/chrome/browser/widget/RectProvider.java",
"java/src/org/chromium/chrome/browser/widget/RoundedIconGenerator.java", "java/src/org/chromium/chrome/browser/widget/RoundedIconGenerator.java",
"java/src/org/chromium/chrome/browser/widget/ThumbnailDiskStorage.java", "java/src/org/chromium/chrome/browser/widget/ThumbnailDiskStorage.java",
"java/src/org/chromium/chrome/browser/widget/ThumbnailGenerator.java", "java/src/org/chromium/chrome/browser/widget/ThumbnailGenerator.java",
...@@ -1374,6 +1376,7 @@ chrome_java_sources = [ ...@@ -1374,6 +1376,7 @@ chrome_java_sources = [
"java/src/org/chromium/chrome/browser/widget/ToolbarProgressBarAnimatingView.java", "java/src/org/chromium/chrome/browser/widget/ToolbarProgressBarAnimatingView.java",
"java/src/org/chromium/chrome/browser/widget/VerticallyFixedEditText.java", "java/src/org/chromium/chrome/browser/widget/VerticallyFixedEditText.java",
"java/src/org/chromium/chrome/browser/widget/ViewHighlighter.java", "java/src/org/chromium/chrome/browser/widget/ViewHighlighter.java",
"java/src/org/chromium/chrome/browser/widget/ViewRectProvider.java",
"java/src/org/chromium/chrome/browser/widget/ViewResourceFrameLayout.java", "java/src/org/chromium/chrome/browser/widget/ViewResourceFrameLayout.java",
"java/src/org/chromium/chrome/browser/widget/accessibility/AccessibilityTabModelAdapter.java", "java/src/org/chromium/chrome/browser/widget/accessibility/AccessibilityTabModelAdapter.java",
"java/src/org/chromium/chrome/browser/widget/accessibility/AccessibilityTabModelListItem.java", "java/src/org/chromium/chrome/browser/widget/accessibility/AccessibilityTabModelListItem.java",
...@@ -1420,7 +1423,6 @@ chrome_java_sources = [ ...@@ -1420,7 +1423,6 @@ chrome_java_sources = [
"java/src/org/chromium/chrome/browser/widget/selection/SelectableListToolbar.java", "java/src/org/chromium/chrome/browser/widget/selection/SelectableListToolbar.java",
"java/src/org/chromium/chrome/browser/widget/textbubble/ArrowBubbleDrawable.java", "java/src/org/chromium/chrome/browser/widget/textbubble/ArrowBubbleDrawable.java",
"java/src/org/chromium/chrome/browser/widget/textbubble/TextBubble.java", "java/src/org/chromium/chrome/browser/widget/textbubble/TextBubble.java",
"java/src/org/chromium/chrome/browser/widget/textbubble/ViewAnchoredTextBubble.java",
] ]
chrome_vr_java_sources = [ chrome_vr_java_sources = [
......
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