[clank-q4-fixit] ContextMenuManager use ListMenu
Use ListMenu to show the context menu for suggestion tiles. After this
change:
* The background of the menu can pickup dynamic colors expectedly.
* The anchor of the menu will always be the center of the tile view. Previously, the menu anchor will be the touch point of the long press.
Before: https://screenshot.googleplex.com/45whEEZzncZvjKh
After: https://screenshot.googleplex.com/7csmBSr4H5NUGLb
Bug: 339864862
Change-Id: I84e8e886598ad1f11d127713a0c1bc1b88f913ba
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/6120095
Auto-Submit: Wenyu Fu <wenyufu@chromium.org>
Code-Coverage: findit-for-me@appspot.gserviceaccount.com <findit-for-me@appspot.gserviceaccount.com>
Reviewed-by: Lijin Shen <lazzzis@google.com>
Commit-Queue: Lijin Shen <lazzzis@google.com>
Cr-Commit-Position: refs/heads/main@{#1400586}
diff --git a/chrome/android/chrome_junit_test_java_sources.gni b/chrome/android/chrome_junit_test_java_sources.gni
index 834eb48..aecb2ee 100644
--- a/chrome/android/chrome_junit_test_java_sources.gni
+++ b/chrome/android/chrome_junit_test_java_sources.gni
@@ -281,6 +281,7 @@
"junit/src/org/chromium/chrome/browser/multiwindow/MultiInstanceManagerApi31UnitTest.java",
"junit/src/org/chromium/chrome/browser/multiwindow/MultiWindowTestUtils.java",
"junit/src/org/chromium/chrome/browser/multiwindow/MultiWindowUtilsUnitTest.java",
+ "junit/src/org/chromium/chrome/browser/native_page/ContextMenuManagerUnitTest.java",
"junit/src/org/chromium/chrome/browser/native_page/NativePageFactoryTest.java",
"junit/src/org/chromium/chrome/browser/new_tab_url/DseNewTabUrlManagerUnitTest.java",
"junit/src/org/chromium/chrome/browser/night_mode/GlobalNightModeStateControllerTest.java",
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/native_page/ContextMenuManager.java b/chrome/android/java/src/org/chromium/chrome/browser/native_page/ContextMenuManager.java
index de6eebf..a016e99d 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/native_page/ContextMenuManager.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/native_page/ContextMenuManager.java
@@ -4,7 +4,6 @@
package org.chromium.chrome.browser.native_page;
-import android.content.Context;
import android.view.ContextMenu;
import android.view.Menu;
import android.view.MenuItem;
@@ -12,14 +11,23 @@
import android.view.View;
import androidx.annotation.IntDef;
+import androidx.annotation.Nullable;
import androidx.annotation.StringRes;
import org.chromium.base.metrics.RecordUserAction;
import org.chromium.chrome.R;
import org.chromium.chrome.browser.offlinepages.OfflinePageBridge;
import org.chromium.chrome.browser.ui.native_page.TouchEnabledDelegate;
+import org.chromium.components.browser_ui.widget.BrowserUiListMenuUtils;
import org.chromium.ui.base.WindowAndroid.OnCloseContextMenuListener;
+import org.chromium.ui.listmenu.ListMenu;
+import org.chromium.ui.listmenu.ListMenuButtonDelegate;
+import org.chromium.ui.listmenu.ListMenuHost;
+import org.chromium.ui.listmenu.ListMenuItemProperties;
+import org.chromium.ui.modelutil.MVCListAdapter;
import org.chromium.ui.mojom.WindowOpenDisposition;
+import org.chromium.ui.widget.RectProvider;
+import org.chromium.ui.widget.ViewRectProvider;
import org.chromium.url.GURL;
import java.lang.annotation.Retention;
@@ -66,6 +74,9 @@
private final String mUserActionPrefix;
private View mAnchorView;
+ // Not null after showListContextMenu.
+ private @Nullable ListMenuHost mListContextMenu;
+
/** Defines callback to configure the context menu and respond to user interaction. */
public interface Delegate {
/** Opens the current item the way specified by {@code windowDisposition}. */
@@ -163,11 +174,7 @@
for (@ContextMenuItemId int itemId = 0; itemId < ContextMenuItemId.NUM_ENTRIES; itemId++) {
if (!shouldShowItem(itemId, delegate)) continue;
- menu.add(
- Menu.NONE,
- itemId,
- Menu.NONE,
- getResourceIdForMenuItem(associatedView.getContext(), itemId))
+ menu.add(Menu.NONE, itemId, Menu.NONE, getResourceIdForMenuItem(itemId))
.setOnMenuItemClickListener(listener);
hasItems = true;
}
@@ -198,6 +205,79 @@
notifyContextMenuShown(delegate);
}
+ /**
+ * Show the context menu using a {@link ListMenu}.
+ *
+ * @param associatedView The tile view associated with context menu.
+ * @param delegate Delegate that defines the configuration of the menu and what to do when items
+ * are tapped.
+ * @return Whether menu is shown.
+ */
+ public boolean showListContextMenu(View associatedView, Delegate delegate) {
+ MVCListAdapter.ModelList menuModel = new MVCListAdapter.ModelList();
+ for (@ContextMenuItemId int itemId = 0; itemId < ContextMenuItemId.NUM_ENTRIES; itemId++) {
+ if (!shouldShowItem(itemId, delegate)) continue;
+
+ menuModel.add(
+ BrowserUiListMenuUtils.buildMenuListItem(
+ /* titleId= */ getResourceIdForMenuItem(itemId),
+ /* menuId= */ itemId,
+ /* startIconId= */ 0));
+ }
+
+ if (menuModel.isEmpty()) {
+ return false;
+ }
+
+ // Now show the anchored popup window representing the list menu.
+ mTouchEnabledDelegate.setTouchEnabled(false);
+ mAnchorView = associatedView;
+
+ ListMenu menu =
+ BrowserUiListMenuUtils.getBasicListMenu(
+ mAnchorView.getContext(),
+ menuModel,
+ model ->
+ handleMenuItemClick(
+ model.get(ListMenuItemProperties.MENU_ITEM_ID), delegate));
+ mListContextMenu = new ListMenuHost(mAnchorView, null);
+ mListContextMenu.setMenuMaxWidth(
+ mAnchorView.getResources().getDimensionPixelSize(R.dimen.menu_width));
+ mListContextMenu.tryToFitLargestItem(true);
+ mListContextMenu.setDelegate(
+ new ListMenuButtonDelegate() {
+ @Override
+ public ListMenu getListMenu() {
+ return menu;
+ }
+
+ @Override
+ public RectProvider getRectProvider(View view) {
+ ViewRectProvider rectProvider = new ViewRectProvider(view);
+ rectProvider.setUseCenter(true);
+ return rectProvider;
+ }
+ },
+ /* overrideOnClickListener= */ false);
+ mListContextMenu.addPopupListener(
+ new ListMenuHost.PopupMenuShownListener() {
+ @Override
+ public void onPopupMenuShown() {}
+
+ @Override
+ public void onPopupMenuDismissed() {
+ mAnchorView = null;
+ mListContextMenu = null;
+ mTouchEnabledDelegate.setTouchEnabled(true);
+ mCloseContextMenuCallback.run();
+ }
+ });
+ mListContextMenu.showMenu();
+ notifyContextMenuShown(delegate);
+
+ return true;
+ }
+
@Override
public void onContextMenuClosed() {
if (mAnchorView == null) return;
@@ -257,9 +337,8 @@
/**
* Returns resource id of a string that should be displayed for menu item with given item id.
- * @param context The activity context.
*/
- protected @StringRes int getResourceIdForMenuItem(Context context, @ContextMenuItemId int id) {
+ protected @StringRes int getResourceIdForMenuItem(@ContextMenuItemId int id) {
switch (id) {
case ContextMenuItemId.OPEN_IN_NEW_TAB:
return R.string.contextmenu_open_in_new_tab;
@@ -315,6 +394,10 @@
}
}
+ public ListMenuHost getListMenuForTesting() {
+ return mListContextMenu;
+ }
+
private class ItemClickListener implements OnMenuItemClickListener {
private final Delegate mDelegate;
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/suggestions/tile/TileGroup.java b/chrome/android/java/src/org/chromium/chrome/browser/suggestions/tile/TileGroup.java
index cb3b216f7..bb02dfb 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/suggestions/tile/TileGroup.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/suggestions/tile/TileGroup.java
@@ -128,10 +128,12 @@
}
/** Delegate for handling interactions with tiles. */
- public interface TileInteractionDelegate extends OnClickListener, OnCreateContextMenuListener {
+ public interface TileInteractionDelegate
+ extends OnClickListener, OnCreateContextMenuListener, View.OnLongClickListener {
/**
* Set a runnable for click events on the tile. This is primarily used to track interaction
* with the tile used by feature engagement purposes.
+ *
* @param clickRunnable The {@link Runnable} to be executed when tile is clicked.
*/
void setOnClickRunnable(Runnable clickRunnable);
@@ -700,10 +702,20 @@
@Override
public void onCreateContextMenu(
ContextMenu contextMenu, View view, ContextMenuInfo contextMenuInfo) {
+ if (ChromeFeatureList.isEnabled(ChromeFeatureList.TILE_CONTEXT_MENU_REFACTOR)) return;
+
mContextMenuManager.createContextMenu(contextMenu, view, this);
}
@Override
+ public boolean onLongClick(View view) {
+ if (!ChromeFeatureList.isEnabled(ChromeFeatureList.TILE_CONTEXT_MENU_REFACTOR)) {
+ return false;
+ }
+ return mContextMenuManager.showListContextMenu(view, this);
+ }
+
+ @Override
public void setOnClickRunnable(Runnable clickRunnable) {
mOnClickRunnable = clickRunnable;
}
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/suggestions/tile/TileRenderer.java b/chrome/android/java/src/org/chromium/chrome/browser/suggestions/tile/TileRenderer.java
index 106ae0a..af879c1 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/suggestions/tile/TileRenderer.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/suggestions/tile/TileRenderer.java
@@ -24,6 +24,7 @@
import org.chromium.base.task.TaskTraits;
import org.chromium.chrome.R;
import org.chromium.chrome.browser.feature_engagement.TrackerFactory;
+import org.chromium.chrome.browser.flags.ChromeFeatureList;
import org.chromium.chrome.browser.omnibox.suggestions.mostvisited.SuggestTileType;
import org.chromium.chrome.browser.profiles.Profile;
import org.chromium.chrome.browser.suggestions.ImageFetcher;
@@ -241,7 +242,11 @@
}
tileView.setOnClickListener(delegate);
- tileView.setOnCreateContextMenuListener(delegate);
+ if (ChromeFeatureList.isEnabled(ChromeFeatureList.TILE_CONTEXT_MENU_REFACTOR)) {
+ tileView.setOnLongClickListener(delegate);
+ } else {
+ tileView.setOnCreateContextMenuListener(delegate);
+ }
return tileView;
}
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/ntp/NewTabPageTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/ntp/NewTabPageTest.java
index ab7749e..2a23ca19 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/ntp/NewTabPageTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/ntp/NewTabPageTest.java
@@ -329,6 +329,7 @@
@Test
@SmallTest
@Feature({"NewTabPage", "FeedNewTabPage"})
+ @DisableFeatures(ChromeFeatureList.TILE_CONTEXT_MENU_REFACTOR)
public void testOpenMostVisitedItemInIncognitoTab() throws ExecutionException {
Assert.assertNotNull(mMvTilesLayout);
HistogramWatcher histogramWatcher = expectMostVisitedTilesRecordForNtpModuleClick();
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/suggestions/tile/TileGroupTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/suggestions/tile/TileGroupTest.java
index 81dcc7537..e30b968 100644
--- a/chrome/android/javatests/src/org/chromium/chrome/browser/suggestions/tile/TileGroupTest.java
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/suggestions/tile/TileGroupTest.java
@@ -28,8 +28,10 @@
import org.chromium.base.test.util.Criteria;
import org.chromium.base.test.util.CriteriaHelper;
import org.chromium.base.test.util.Feature;
+import org.chromium.base.test.util.Features;
import org.chromium.base.test.util.Restriction;
import org.chromium.chrome.browser.app.ChromeActivity;
+import org.chromium.chrome.browser.flags.ChromeFeatureList;
import org.chromium.chrome.browser.flags.ChromeSwitches;
import org.chromium.chrome.browser.native_page.ContextMenuManager;
import org.chromium.chrome.browser.ntp.NewTabPage;
@@ -109,6 +111,8 @@
@MediumTest
@Feature({"NewTabPage"})
@Restriction({DeviceFormFactor.PHONE})
+ // Disable the feature due to lack of good list menu testing support.
+ @Features.DisableFeatures(ChromeFeatureList.TILE_CONTEXT_MENU_REFACTOR)
public void testDismissTileWithContextMenu_Phones() throws Exception {
testDismissTileWithContextMenuImpl();
}
@@ -117,6 +121,8 @@
@MediumTest
@Feature({"NewTabPage"})
@Restriction({DeviceFormFactor.TABLET})
+ // Disable the feature due to lack of good list menu testing support.
+ @Features.DisableFeatures(ChromeFeatureList.TILE_CONTEXT_MENU_REFACTOR)
public void testDismissTileWithContextMenu_Tablets() throws Exception {
testDismissTileWithContextMenuImpl();
}
@@ -145,6 +151,8 @@
@MediumTest
@Feature({"NewTabPage"})
@Restriction({DeviceFormFactor.PHONE})
+ // Disable the feature due to lack of good list menu testing support.
+ @Features.DisableFeatures(ChromeFeatureList.TILE_CONTEXT_MENU_REFACTOR)
public void testDismissTileUndo_Phones() throws Exception {
testDismissTileUndoImpl();
}
@@ -153,6 +161,8 @@
@MediumTest
@Feature({"NewTabPage"})
@Restriction({DeviceFormFactor.TABLET})
+ // Disable the feature due to lack of good list menu testing support.
+ @Features.DisableFeatures(ChromeFeatureList.TILE_CONTEXT_MENU_REFACTOR)
public void testDismissTileUndo_Tablets() throws Exception {
testDismissTileUndoImpl();
}
diff --git a/chrome/android/junit/src/org/chromium/chrome/browser/native_page/ContextMenuManagerUnitTest.java b/chrome/android/junit/src/org/chromium/chrome/browser/native_page/ContextMenuManagerUnitTest.java
new file mode 100644
index 0000000..e0ffb56
--- /dev/null
+++ b/chrome/android/junit/src/org/chromium/chrome/browser/native_page/ContextMenuManagerUnitTest.java
@@ -0,0 +1,81 @@
+// Copyright 2024 The Chromium Authors
+// 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.native_page;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.verify;
+
+import android.view.View;
+
+import androidx.test.ext.junit.rules.ActivityScenarioRule;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.junit.MockitoJUnit;
+import org.mockito.junit.MockitoRule;
+import org.robolectric.annotation.Config;
+import org.robolectric.shadows.ShadowPopupWindow;
+
+import org.chromium.base.test.BaseRobolectricTestRunner;
+import org.chromium.chrome.browser.ui.native_page.TouchEnabledDelegate;
+import org.chromium.ui.base.TestActivity;
+
+/** Unit test for {@link ContextMenuManager} */
+@RunWith(BaseRobolectricTestRunner.class)
+@Config(shadows = ShadowPopupWindow.class)
+public class ContextMenuManagerUnitTest {
+
+ @Rule public MockitoRule mMockitoRule = MockitoJUnit.rule();
+
+ @Rule
+ public ActivityScenarioRule<TestActivity> mActivityScenario =
+ new ActivityScenarioRule<>(TestActivity.class);
+
+ private TestActivity mActivity;
+ private ContextMenuManager mManager;
+ private View mAnchorView;
+
+ @Mock NativePageNavigationDelegate mNavigationDelegate;
+ @Mock TouchEnabledDelegate mTouchEnabledDelegate;
+ @Mock ContextMenuManager.Delegate mDelegate;
+
+ @Before
+ public void setup() {
+ mActivityScenario.getScenario().onActivity(activity -> mActivity = activity);
+ mAnchorView = spy(new View(mActivity, null));
+ mManager = new ContextMenuManager(mNavigationDelegate, mTouchEnabledDelegate, () -> {}, "");
+ }
+
+ @Test
+ public void emptyListContextMenu() {
+ assertFalse(
+ "showContextMenu failed since list is empty.",
+ mManager.showListContextMenu(mAnchorView, mDelegate));
+ }
+
+ @Test
+ public void showListContextMenu() {
+ doReturn(true).when(mDelegate).isItemSupported(anyInt());
+ doReturn(false).when(mNavigationDelegate).isOpenInNewTabInGroupEnabled();
+ doReturn(false).when(mNavigationDelegate).isOpenInNewWindowEnabled();
+ doReturn(false).when(mNavigationDelegate).isOpenInIncognitoEnabled();
+ doReturn(null).when(mDelegate).getUrl();
+ doReturn(true).when(mAnchorView).isAttachedToWindow();
+
+ assertTrue(
+ "showContextMenu failed since list is empty.",
+ mManager.showListContextMenu(mAnchorView, mDelegate));
+ assertNotNull("List context menu is null.", mManager.getListMenuForTesting());
+ verify(mDelegate).onContextMenuCreated();
+ }
+}
diff --git a/chrome/android/junit/src/org/chromium/chrome/browser/suggestions/tile/TileRendererTest.java b/chrome/android/junit/src/org/chromium/chrome/browser/suggestions/tile/TileRendererTest.java
index 9329ac3..d42df44 100644
--- a/chrome/android/junit/src/org/chromium/chrome/browser/suggestions/tile/TileRendererTest.java
+++ b/chrome/android/junit/src/org/chromium/chrome/browser/suggestions/tile/TileRendererTest.java
@@ -34,7 +34,9 @@
import org.chromium.base.task.TaskTraits;
import org.chromium.base.task.test.ShadowPostTask;
import org.chromium.base.test.BaseRobolectricTestRunner;
+import org.chromium.base.test.util.Features;
import org.chromium.chrome.R;
+import org.chromium.chrome.browser.flags.ChromeFeatureList;
import org.chromium.chrome.browser.profiles.Profile;
import org.chromium.chrome.browser.search_engines.TemplateUrlServiceFactory;
import org.chromium.chrome.browser.suggestions.ImageFetcher;
@@ -56,6 +58,7 @@
@Config(
manifest = Config.NONE,
shadows = {ShadowPostTask.class})
+@Features.EnableFeatures(ChromeFeatureList.TILE_CONTEXT_MENU_REFACTOR)
public class TileRendererTest {
/**
* Backend that substitutes normal PostTask operations. Allow us to coordinate task execution
diff --git a/chrome/browser/flags/android/chrome_feature_list.cc b/chrome/browser/flags/android/chrome_feature_list.cc
index 4be2c87..d6253f3 100644
--- a/chrome/browser/flags/android/chrome_feature_list.cc
+++ b/chrome/browser/flags/android/chrome_feature_list.cc
@@ -343,6 +343,7 @@
&kTabWindowManagerReportIndicesMismatch,
&kTestDefaultDisabled,
&kTestDefaultEnabled,
+ &kTileContextMenuRefactor,
&kTraceBinderIpc,
&kStartSurfaceReturnTime,
&kUmaBackgroundSessions,
@@ -1076,6 +1077,10 @@
"TestDefaultEnabled",
base::FEATURE_ENABLED_BY_DEFAULT);
+BASE_FEATURE(kTileContextMenuRefactor,
+ "TileContextMenuRefactor",
+ base::FEATURE_ENABLED_BY_DEFAULT);
+
BASE_FEATURE(kTraceBinderIpc,
"TraceBinderIpc",
base::FEATURE_DISABLED_BY_DEFAULT);
diff --git a/chrome/browser/flags/android/chrome_feature_list.h b/chrome/browser/flags/android/chrome_feature_list.h
index 0a2cf7b..ada91151a 100644
--- a/chrome/browser/flags/android/chrome_feature_list.h
+++ b/chrome/browser/flags/android/chrome_feature_list.h
@@ -188,6 +188,7 @@
BASE_DECLARE_FEATURE(kHideTabletToolbarDownloadButton);
BASE_DECLARE_FEATURE(kTestDefaultDisabled);
BASE_DECLARE_FEATURE(kTestDefaultEnabled);
+BASE_DECLARE_FEATURE(kTileContextMenuRefactor);
BASE_DECLARE_FEATURE(kTraceBinderIpc);
BASE_DECLARE_FEATURE(kStartSurfaceReturnTime);
BASE_DECLARE_FEATURE(kTabResumptionModuleAndroid);
diff --git a/chrome/browser/flags/android/java/src/org/chromium/chrome/browser/flags/ChromeFeatureList.java b/chrome/browser/flags/android/java/src/org/chromium/chrome/browser/flags/ChromeFeatureList.java
index 58a30dd2..12e0376 100644
--- a/chrome/browser/flags/android/java/src/org/chromium/chrome/browser/flags/ChromeFeatureList.java
+++ b/chrome/browser/flags/android/java/src/org/chromium/chrome/browser/flags/ChromeFeatureList.java
@@ -548,6 +548,7 @@
public static final String TASK_MANAGER_CLANK = "TaskManagerClank";
public static final String TEST_DEFAULT_DISABLED = "TestDefaultDisabled";
public static final String TEST_DEFAULT_ENABLED = "TestDefaultEnabled";
+ public static final String TILE_CONTEXT_MENU_REFACTOR = "TileContextMenuRefactor";
public static final String TINKER_TANK_BOTTOM_SHEET = "TinkerTankBottomSheet";
public static final String TOOLBAR_PHONE_CLEANUP = "ToolbarPhoneCleanup";
public static final String TOOLBAR_SCROLL_ABLATION = "AndroidToolbarScrollAblation";
diff --git a/ui/android/java/src/org/chromium/ui/widget/ViewRectProvider.java b/ui/android/java/src/org/chromium/ui/widget/ViewRectProvider.java
index 3ae4a0e..e73d415 100644
--- a/ui/android/java/src/org/chromium/ui/widget/ViewRectProvider.java
+++ b/ui/android/java/src/org/chromium/ui/widget/ViewRectProvider.java
@@ -35,9 +35,11 @@
private @Nullable ViewTreeObserver mViewTreeObserver;
private boolean mIncludePadding;
+ private boolean mUseCenterPoint;
/**
* Creates an instance of a {@link ViewRectProvider}.
+ *
* @param view The {@link View} used to generate a {@link Rect}.
*/
public ViewRectProvider(View view) {
@@ -98,6 +100,16 @@
refreshRectBounds(/* forceRefresh= */ true);
}
+ /**
+ * Whether use the center of the view after all the adjustment applied (insets, margins). The
+ * Rect being provided will be a single point.
+ *
+ * @param useCenterPoint Whether the rect represents the center of the view after adjustments.
+ */
+ public void setUseCenter(boolean useCenterPoint) {
+ mUseCenterPoint = useCenterPoint;
+ }
+
@Override
public void startObserving(Observer observer) {
mView.addOnAttachStateChangeListener(this);
@@ -208,6 +220,12 @@
mRect.right = Math.min(mRect.right, mView.getRootView().getWidth());
mRect.bottom = Math.min(mRect.bottom, mView.getRootView().getHeight());
+ if (mUseCenterPoint) {
+ int centerX = mRect.left + mRect.width() / 2;
+ int centerY = mRect.top + mRect.height() / 2;
+ mRect.set(centerX, centerY, centerX, centerY);
+ }
+
notifyRectChanged();
}
diff --git a/ui/android/junit/src/org/chromium/ui/widget/ViewRectProviderTest.java b/ui/android/junit/src/org/chromium/ui/widget/ViewRectProviderTest.java
index 2fe94dc..de7b421 100644
--- a/ui/android/junit/src/org/chromium/ui/widget/ViewRectProviderTest.java
+++ b/ui/android/junit/src/org/chromium/ui/widget/ViewRectProviderTest.java
@@ -213,6 +213,33 @@
assertRectMatch(9, 18, 103, 204);
}
+ @Test
+ public void testUseCenterPoint() {
+ mViewRectProvider.setUseCenter(true);
+
+ int expectedCounts = 0;
+ mView.layout(10, 20, 100, 200);
+ mView.getViewTreeObserver().dispatchOnPreDraw();
+ Assert.assertEquals(
+ "View changing its position on screen should trigger #onRectChanged.",
+ ++expectedCounts,
+ mOnRectChangeCallback.getCallCount());
+ // The rect represents the center of the rect.
+ assertRectMatch(55, 110, 55, 110);
+
+ // Use center works with margin.
+ mViewRectProvider.setMarginPx(1, 2, 3, 4);
+ mView.getViewTreeObserver().dispatchOnPreDraw();
+
+ Assert.assertEquals(
+ "View changing its position on screen should trigger #onRectChanged.",
+ ++expectedCounts,
+ mOnRectChangeCallback.getCallCount());
+ // New left: 9, new right: 103 => centerX = 56
+ // New top: 18, new bottom: 204 => centerY = 111
+ assertRectMatch(56, 111, 56, 111);
+ }
+
private void assertRectMatch(int left, int top, int right, int bottom) {
final Rect expectedRect = new Rect(left, top, right, bottom);
Assert.assertEquals("Rect does not match.", expectedRect, mViewRectProvider.getRect());