Create the base Custom Context Menu Dialog.
This CL creates the new Context Menu. There are a few key decisions I
made during each moment and if the explanation isn't in here, please
take a look at the design doc under caveats or just talk to me! (Usually
we can get things cleared up a little quicker through in person chat
than through conversations).
This change covers the base dialog and the feature flag for it. Please
take a look when you can. Image loading, Share Helper, and contextmenu
-> context_menu will all come in different cls.
----Screenshots----
https://drive.google.com/open?id=0B6D5A57VLDpeSHZfNkgzUlVhYlk
https://drive.google.com/open?id=0B6D5A57VLDpeVDNHUXFOVzVnbFU
Video - https://drive.google.com/open?id=0B6D5A57VLDpeaUNVNXFReVNnYUk
DESIGN_DOC=
https://docs.google.com/document/d/1MvLS1sRJ6CqOdSg8sKzA0j2gbKO2XGRAb02SPlK1cbw/edit#heading=h.xgjl2srtytjt
BUG=613357
Review-Url: https://codereview.chromium.org/2751333006
Cr-Commit-Position: refs/heads/master@{#460664}
diff --git a/chrome/android/java/res/drawable-hdpi/context_menu_add_to_contacts.png b/chrome/android/java/res/drawable-hdpi/context_menu_add_to_contacts.png
new file mode 100644
index 0000000..50f01e6
--- /dev/null
+++ b/chrome/android/java/res/drawable-hdpi/context_menu_add_to_contacts.png
Binary files differ
diff --git a/chrome/android/java/res/drawable-hdpi/context_menu_load_image.png b/chrome/android/java/res/drawable-hdpi/context_menu_load_image.png
new file mode 100644
index 0000000..b67131f
--- /dev/null
+++ b/chrome/android/java/res/drawable-hdpi/context_menu_load_image.png
Binary files differ
diff --git a/chrome/android/java/res/drawable-hdpi/context_menu_new_tab.png b/chrome/android/java/res/drawable-hdpi/context_menu_new_tab.png
new file mode 100644
index 0000000..1d60579
--- /dev/null
+++ b/chrome/android/java/res/drawable-hdpi/context_menu_new_tab.png
Binary files differ
diff --git a/chrome/android/java/res/drawable-mdpi/context_menu_add_to_contacts.png b/chrome/android/java/res/drawable-mdpi/context_menu_add_to_contacts.png
new file mode 100644
index 0000000..7760780
--- /dev/null
+++ b/chrome/android/java/res/drawable-mdpi/context_menu_add_to_contacts.png
Binary files differ
diff --git a/chrome/android/java/res/drawable-mdpi/context_menu_load_image.png b/chrome/android/java/res/drawable-mdpi/context_menu_load_image.png
new file mode 100644
index 0000000..2afed1b
--- /dev/null
+++ b/chrome/android/java/res/drawable-mdpi/context_menu_load_image.png
Binary files differ
diff --git a/chrome/android/java/res/drawable-mdpi/context_menu_new_tab.png b/chrome/android/java/res/drawable-mdpi/context_menu_new_tab.png
new file mode 100644
index 0000000..392b7d4
--- /dev/null
+++ b/chrome/android/java/res/drawable-mdpi/context_menu_new_tab.png
Binary files differ
diff --git a/chrome/android/java/res/drawable-xhdpi/context_menu_add_to_contacts.png b/chrome/android/java/res/drawable-xhdpi/context_menu_add_to_contacts.png
new file mode 100644
index 0000000..8cf90cf
--- /dev/null
+++ b/chrome/android/java/res/drawable-xhdpi/context_menu_add_to_contacts.png
Binary files differ
diff --git a/chrome/android/java/res/drawable-xhdpi/context_menu_load_image.png b/chrome/android/java/res/drawable-xhdpi/context_menu_load_image.png
new file mode 100644
index 0000000..8ea6af2
--- /dev/null
+++ b/chrome/android/java/res/drawable-xhdpi/context_menu_load_image.png
Binary files differ
diff --git a/chrome/android/java/res/drawable-xhdpi/context_menu_new_tab.png b/chrome/android/java/res/drawable-xhdpi/context_menu_new_tab.png
new file mode 100644
index 0000000..fb2a2b4
--- /dev/null
+++ b/chrome/android/java/res/drawable-xhdpi/context_menu_new_tab.png
Binary files differ
diff --git a/chrome/android/java/res/drawable-xxhdpi/context_menu_add_to_contacts.png b/chrome/android/java/res/drawable-xxhdpi/context_menu_add_to_contacts.png
new file mode 100644
index 0000000..de426c6
--- /dev/null
+++ b/chrome/android/java/res/drawable-xxhdpi/context_menu_add_to_contacts.png
Binary files differ
diff --git a/chrome/android/java/res/drawable-xxhdpi/context_menu_load_image.png b/chrome/android/java/res/drawable-xxhdpi/context_menu_load_image.png
new file mode 100644
index 0000000..f589079
--- /dev/null
+++ b/chrome/android/java/res/drawable-xxhdpi/context_menu_load_image.png
Binary files differ
diff --git a/chrome/android/java/res/drawable-xxhdpi/context_menu_new_tab.png b/chrome/android/java/res/drawable-xxhdpi/context_menu_new_tab.png
new file mode 100644
index 0000000..92f601c
--- /dev/null
+++ b/chrome/android/java/res/drawable-xxhdpi/context_menu_new_tab.png
Binary files differ
diff --git a/chrome/android/java/res/drawable-xxxhdpi/context_menu_add_to_contacts.png b/chrome/android/java/res/drawable-xxxhdpi/context_menu_add_to_contacts.png
new file mode 100644
index 0000000..7c31254a
--- /dev/null
+++ b/chrome/android/java/res/drawable-xxxhdpi/context_menu_add_to_contacts.png
Binary files differ
diff --git a/chrome/android/java/res/drawable-xxxhdpi/context_menu_load_image.png b/chrome/android/java/res/drawable-xxxhdpi/context_menu_load_image.png
new file mode 100644
index 0000000..93b7437
--- /dev/null
+++ b/chrome/android/java/res/drawable-xxxhdpi/context_menu_load_image.png
Binary files differ
diff --git a/chrome/android/java/res/drawable-xxxhdpi/context_menu_new_tab.png b/chrome/android/java/res/drawable-xxxhdpi/context_menu_new_tab.png
new file mode 100644
index 0000000..5647efc2
--- /dev/null
+++ b/chrome/android/java/res/drawable-xxxhdpi/context_menu_new_tab.png
Binary files differ
diff --git a/chrome/android/java/res/layout/tabular_context_menu.xml b/chrome/android/java/res/layout/tabular_context_menu.xml
new file mode 100644
index 0000000..11998c6
--- /dev/null
+++ b/chrome/android/java/res/layout/tabular_context_menu.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright 2017 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. -->
+<org.chromium.chrome.browser.contextmenu.TabularContextMenuViewPager
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:app="http://schemas.android.com/apk/res-auto"
+ android:id="@+id/custom_pager"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_marginTop="20dp"
+ android:layout_marginBottom="20dp"
+ android:minWidth="280dp">
+ <android.support.design.widget.TabLayout
+ android:id="@+id/tab_layout"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:background="@color/google_grey_100"
+ app:tabTextColor="#80000000"
+ app:tabSelectedTextColor="@color/light_active_color"
+ app:tabIndicatorColor="@color/light_active_color"
+ app:tabGravity="fill"
+ app:tabMode="fixed" />
+</org.chromium.chrome.browser.contextmenu.TabularContextMenuViewPager>
diff --git a/chrome/android/java/res/layout/tabular_context_menu_page.xml b/chrome/android/java/res/layout/tabular_context_menu_page.xml
new file mode 100644
index 0000000..fcf46296
--- /dev/null
+++ b/chrome/android/java/res/layout/tabular_context_menu_page.xml
@@ -0,0 +1,39 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright 2017 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. -->
+<LinearLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/content_layer"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:minWidth="280dp"
+ android:orientation="vertical">
+ <TextView
+ android:id="@+id/context_header_text"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_marginTop="20dp"
+ android:layout_marginBottom="10dp"
+ android:layout_marginStart="20dp"
+ android:layout_marginEnd="20dp"
+ android:gravity="center"
+ android:maxLines="1"
+ android:textSize="13sp"
+ android:textStyle="bold"
+ android:ellipsize="end"
+ android:visibility="gone"/>
+ <View
+ android:id="@+id/context_divider"
+ android:layout_width="match_parent"
+ android:layout_height="1dp"
+ android:background="#1e000000"/>
+ <ListView
+ android:id="@+id/selectable_items"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:paddingTop="8dp"
+ android:paddingBottom="8dp"
+ android:dividerHeight="0dp"
+ android:divider="@null"/>
+</LinearLayout>
diff --git a/chrome/android/java/res/layout/tabular_context_menu_row.xml b/chrome/android/java/res/layout/tabular_context_menu_row.xml
new file mode 100644
index 0000000..fe1aa32
--- /dev/null
+++ b/chrome/android/java/res/layout/tabular_context_menu_row.xml
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright 2017 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. -->
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:tools="http://schemas.android.com/tools"
+ android:orientation="horizontal"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:paddingStart="15dp"
+ android:paddingEnd="15dp"
+ android:minHeight="40dp"
+ tools:ignore="UseCompoundDrawables">
+ <ImageView
+ android:id="@+id/context_menu_icon"
+ android:layout_width="20dp"
+ android:layout_height="20dp"
+ android:layout_gravity="center_vertical"
+ android:contentDescription="@null" />
+ <TextView
+ android:id="@+id/context_text"
+ android:layout_width="0dp"
+ android:layout_height="wrap_content"
+ android:layout_gravity="center_vertical"
+ android:layout_marginStart="10dp"
+ android:layout_weight="1"
+ android:textSize="15sp"
+ android:textColor="#dd000000" />
+</LinearLayout>
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/ChromeFeatureList.java b/chrome/android/java/src/org/chromium/chrome/browser/ChromeFeatureList.java
index c39d6f6d..5ca6a1e 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/ChromeFeatureList.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/ChromeFeatureList.java
@@ -127,6 +127,7 @@
public static final String CONSISTENT_OMNIBOX_GEOLOCATION = "ConsistentOmniboxGeolocation";
public static final String CONTEXTUAL_SEARCH_SINGLE_ACTIONS = "ContextualSearchSingleActions";
public static final String CONTEXTUAL_SEARCH_URL_ACTIONS = "ContextualSearchUrlActions";
+ public static final String CUSTOM_CONTEXT_MENU = "CustomContextMenu";
public static final String CUSTOM_FEEDBACK_UI = "CustomFeedbackUi";
/** Whether we show an important sites dialog in the "Clear Browsing Data" flow. */
public static final String IMPORTANT_SITES_IN_CBD = "ImportantSitesInCBD";
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/contextmenu/ContextMenuHelper.java b/chrome/android/java/src/org/chromium/chrome/browser/contextmenu/ContextMenuHelper.java
index 615b7c8..dbb62b1 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/contextmenu/ContextMenuHelper.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/contextmenu/ContextMenuHelper.java
@@ -5,6 +5,7 @@
package org.chromium.chrome.browser.contextmenu;
import android.app.Activity;
+import android.content.Context;
import android.util.Pair;
import android.view.ContextMenu;
import android.view.ContextMenu.ContextMenuInfo;
@@ -15,6 +16,7 @@
import org.chromium.base.VisibleForTesting;
import org.chromium.base.annotations.CalledByNative;
import org.chromium.base.metrics.RecordHistogram;
+import org.chromium.chrome.browser.ChromeFeatureList;
import org.chromium.chrome.browser.share.ShareHelper;
import org.chromium.content.browser.ContentViewCore;
import org.chromium.content_public.browser.WebContents;
@@ -31,7 +33,10 @@
private ContextMenuPopulator mPopulator;
private ContextMenuParams mCurrentContextMenuParams;
- private Activity mActivity;
+ private Context mContext;
+ private Callback<Integer> mCallback;
+ private Runnable mOnMenuShown;
+ private Runnable mOnMenuClosed;
private ContextMenuHelper(long nativeContextMenuHelper) {
mNativeContextMenuHelper = nativeContextMenuHelper;
@@ -64,31 +69,60 @@
* @param params The {@link ContextMenuParams} that indicate what menu items to show.
*/
@CalledByNative
- private void showContextMenu(ContentViewCore contentViewCore, ContextMenuParams params) {
+ private void showContextMenu(final ContentViewCore contentViewCore, ContextMenuParams params) {
if (params.isFile()) return;
View view = contentViewCore.getContainerView();
final WindowAndroid windowAndroid = contentViewCore.getWindowAndroid();
if (view == null || view.getVisibility() != View.VISIBLE || view.getParent() == null
- || windowAndroid == null || windowAndroid.getActivity().get() == null) {
+ || windowAndroid == null || windowAndroid.getContext().get() == null
+ || mPopulator == null) {
return;
}
mCurrentContextMenuParams = params;
- mActivity = windowAndroid.getActivity().get();
+ mContext = windowAndroid.getContext().get();
+ mCallback = new Callback<Integer>() {
+ @Override
+ public void onResult(Integer result) {
+ mPopulator.onItemSelected(
+ ContextMenuHelper.this, mCurrentContextMenuParams, result);
+ }
+ };
+ mOnMenuShown = new Runnable() {
+ @Override
+ public void run() {
+ WebContents webContents = contentViewCore.getWebContents();
+ RecordHistogram.recordBooleanHistogram("ContextMenu.Shown", webContents != null);
+ }
+ };
+ mOnMenuClosed = new Runnable() {
+ @Override
+ public void run() {
+ if (mNativeContextMenuHelper == 0) return;
+ nativeOnContextMenuClosed(mNativeContextMenuHelper);
+ }
+ };
+ if (ChromeFeatureList.isEnabled(ChromeFeatureList.CUSTOM_CONTEXT_MENU)) {
+ List<Pair<Integer, List<ContextMenuItem>>> items =
+ mPopulator.buildContextMenu(null, mContext, mCurrentContextMenuParams);
+
+ ContextMenuUi menuUi = new TabularContextMenuUi();
+ menuUi.displayMenu(mContext, mCurrentContextMenuParams, items, mCallback, mOnMenuShown,
+ mOnMenuClosed);
+ return;
+ }
+
+ // The Platform Context Menu requires the listener within this hepler since this helper and
+ // provides context menu for us to show.
view.setOnCreateContextMenuListener(this);
if (view.showContextMenu()) {
- WebContents webContents = contentViewCore.getWebContents();
- RecordHistogram.recordBooleanHistogram(
- "ContextMenu.Shown", webContents != null);
-
+ mOnMenuShown.run();
windowAndroid.addContextMenuCloseListener(new OnCloseContextMenuListener() {
@Override
public void onContextMenuClosed() {
- if (mNativeContextMenuHelper == 0) return;
-
- nativeOnContextMenuClosed(mNativeContextMenuHelper);
+ mOnMenuClosed.run();
windowAndroid.removeContextMenuCloseListener(this);
}
});
@@ -132,18 +166,11 @@
@Override
public void onCreateContextMenu(ContextMenu menu, View v, ContextMenuInfo menuInfo) {
- assert mPopulator != null;
-
List<Pair<Integer, List<ContextMenuItem>>> items =
mPopulator.buildContextMenu(menu, v.getContext(), mCurrentContextMenuParams);
ContextMenuUi menuUi = new PlatformContextMenuUi(menu);
- menuUi.displayMenu(mActivity, mCurrentContextMenuParams, items, new Callback<Integer>() {
- @Override
- public void onResult(Integer result) {
- mPopulator.onItemSelected(
- ContextMenuHelper.this, mCurrentContextMenuParams, result);
- }
- });
+ menuUi.displayMenu(
+ mContext, mCurrentContextMenuParams, items, mCallback, mOnMenuShown, mOnMenuClosed);
}
/**
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/contextmenu/ContextMenuItem.java b/chrome/android/java/src/org/chromium/chrome/browser/contextmenu/ContextMenuItem.java
index 32e7241..1f9763d9 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/contextmenu/ContextMenuItem.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/contextmenu/ContextMenuItem.java
@@ -5,66 +5,85 @@
package org.chromium.chrome.browser.contextmenu;
import android.content.Context;
+import android.graphics.drawable.Drawable;
+import android.support.annotation.DrawableRes;
import android.support.annotation.IdRes;
import android.support.annotation.StringRes;
+import org.chromium.base.ApiCompatibilityUtils;
import org.chromium.chrome.R;
import org.chromium.chrome.browser.DefaultBrowserInfo;
import org.chromium.chrome.browser.search_engines.TemplateUrlService;
+import org.chromium.chrome.browser.widget.TintedDrawable;
/**
* List of all Context Menu Items available in Chrome.
*/
public enum ContextMenuItem {
// Custom Tab Group
- OPEN_IN_NEW_CHROME_TAB(R.string.contextmenu_open_in_new_chrome_tab,
- R.id.contextmenu_open_in_new_chrome_tab),
- OPEN_IN_CHROME_INCOGNITO_TAB(R.string.contextmenu_open_in_chrome_incognito_tab,
+ OPEN_IN_NEW_CHROME_TAB(R.drawable.context_menu_new_tab,
+ R.string.contextmenu_open_in_new_chrome_tab, R.id.contextmenu_open_in_new_chrome_tab),
+ OPEN_IN_CHROME_INCOGNITO_TAB(R.drawable.incognito_statusbar,
+ R.string.contextmenu_open_in_chrome_incognito_tab,
R.id.contextmenu_open_in_chrome_incognito_tab),
- OPEN_IN_BROWSER_ID(0, R.id.contextmenu_open_in_browser_id),
+ OPEN_IN_BROWSER_ID(R.drawable.context_menu_new_tab, 0, R.id.contextmenu_open_in_browser_id),
// Link Group
- OPEN_IN_OTHER_WINDOW(R.string.contextmenu_open_in_other_window,
+ OPEN_IN_OTHER_WINDOW(R.drawable.context_menu_new_tab, R.string.contextmenu_open_in_other_window,
R.id.contextmenu_open_in_other_window),
- OPEN_IN_NEW_TAB(R.string.contextmenu_open_in_new_tab, R.id.contextmenu_open_in_new_tab),
- OPEN_IN_INCOGNITO_TAB(R.string.contextmenu_open_in_incognito_tab,
- R.id.contextmenu_open_in_incognito_tab),
- COPY_LINK_ADDRESS(R.string.contextmenu_copy_link_address, R.id.contextmenu_copy_link_address),
- COPY_LINK_TEXT(R.string.contextmenu_copy_link_text, R.id.contextmenu_copy_link_text),
- SAVE_LINK_AS(R.string.contextmenu_save_link, R.id.contextmenu_save_link_as),
+ OPEN_IN_NEW_TAB(R.drawable.context_menu_new_tab, R.string.contextmenu_open_in_new_tab,
+ R.id.contextmenu_open_in_new_tab),
+ OPEN_IN_INCOGNITO_TAB(R.drawable.incognito_statusbar,
+ R.string.contextmenu_open_in_incognito_tab, R.id.contextmenu_open_in_incognito_tab),
+ COPY_LINK_ADDRESS(R.drawable.ic_content_copy, R.string.contextmenu_copy_link_address,
+ R.id.contextmenu_copy_link_address),
+ COPY_LINK_TEXT(R.drawable.ic_content_copy, R.string.contextmenu_copy_link_text,
+ R.id.contextmenu_copy_link_text),
+ SAVE_LINK_AS(R.drawable.ic_file_download_white_24dp, R.string.contextmenu_save_link,
+ R.id.contextmenu_save_link_as),
// Image Group
- LOAD_ORIGINAL_IMAGE(R.string.contextmenu_load_original_image,
- R.id.contextmenu_load_original_image),
- SAVE_IMAGE(R.string.contextmenu_save_image, R.id.contextmenu_save_image),
- OPEN_IMAGE(R.string.contextmenu_open_image, R.id.contextmenu_open_image),
- OPEN_IMAGE_IN_NEW_TAB(R.string.contextmenu_open_image_in_new_tab,
- R.id.contextmenu_open_image_in_new_tab),
- SEARCH_BY_IMAGE(R.string.contextmenu_search_web_for_image, R.id.contextmenu_search_by_image),
- SHARE_IMAGE(R.string.contextmenu_share_image, R.id.contextmenu_share_image),
+ LOAD_ORIGINAL_IMAGE(R.drawable.context_menu_load_image,
+ R.string.contextmenu_load_original_image, R.id.contextmenu_load_original_image),
+ SAVE_IMAGE(R.drawable.ic_file_download_white_24dp, R.string.contextmenu_save_image,
+ R.id.contextmenu_save_image),
+ OPEN_IMAGE(R.drawable.context_menu_new_tab, R.string.contextmenu_open_image,
+ R.id.contextmenu_open_image),
+ OPEN_IMAGE_IN_NEW_TAB(R.drawable.context_menu_new_tab,
+ R.string.contextmenu_open_image_in_new_tab, R.id.contextmenu_open_image_in_new_tab),
+ SEARCH_BY_IMAGE(R.drawable.googleg, R.string.contextmenu_search_web_for_image,
+ R.id.contextmenu_search_by_image),
+ SHARE_IMAGE(R.drawable.ic_share_white_24dp, R.string.contextmenu_share_image,
+ R.id.contextmenu_share_image),
// Message Group
- CALL(R.string.contextmenu_call, R.id.contextmenu_call),
- SEND_MESSAGE(R.string.contextmenu_send_message, R.id.contextmenu_send_message),
- ADD_TO_CONTACTS(R.string.contextmenu_add_to_contacts, R.id.contextmenu_add_to_contacts),
- COPY(R.string.contextmenu_copy, R.id.contextmenu_copy),
+ CALL(R.drawable.ic_phone_googblue_36dp, R.string.contextmenu_call, R.id.contextmenu_call),
+ SEND_MESSAGE(R.drawable.ic_email_googblue_36dp, R.string.contextmenu_send_message,
+ R.id.contextmenu_send_message),
+ ADD_TO_CONTACTS(R.drawable.context_menu_add_to_contacts, R.string.contextmenu_add_to_contacts,
+ R.id.contextmenu_add_to_contacts),
+ COPY(R.drawable.ic_content_copy, R.string.contextmenu_copy, R.id.contextmenu_copy),
// Video Group
- SAVE_VIDEO(R.string.contextmenu_save_video, R.id.contextmenu_save_video),
-
+ SAVE_VIDEO(R.drawable.ic_file_download_white_24dp, R.string.contextmenu_save_video,
+ R.id.contextmenu_save_video),
// Other
- OPEN_IN_CHROME(R.string.menu_open_in_chrome, R.id.menu_id_open_in_chrome);
+ OPEN_IN_CHROME(R.drawable.context_menu_new_tab, R.string.menu_open_in_chrome,
+ R.id.menu_id_open_in_chrome);
+ @DrawableRes public final int iconId;
@StringRes public final int stringId;
@IdRes public final int menuId;
/**
* A representation of a Context Menu Item. Each item should have a string and an id associated
* with it.
+ * @param iconId The icon that appears in {@link TabularContextMenuUi} to represent each item.
* @param stringId The string that describes the action of the item.
- * @param menuId The id found in ids.xml
+ * @param menuId The id found in ids.xml.
*/
- ContextMenuItem(@StringRes int stringId, @IdRes int menuId) {
+ ContextMenuItem(@DrawableRes int iconId, @StringRes int stringId, @IdRes int menuId) {
+ this.iconId = iconId;
this.stringId = stringId;
this.menuId = menuId;
}
@@ -89,4 +108,22 @@
return context.getString(stringId);
}
+
+ /**
+ * Returns the drawable and the content description associated with the context menu. If no
+ * drawable is associated with the icon, null is returned for the drawable and the
+ * iconDescription.
+ */
+ Drawable getDrawableAndDescription(Context context) {
+ if (iconId == R.drawable.context_menu_new_tab
+ || iconId == R.drawable.context_menu_add_to_contacts
+ || iconId == R.drawable.context_menu_load_image) {
+ return ApiCompatibilityUtils.getDrawable(context.getResources(), iconId);
+ } else if (iconId == 0) {
+ return null;
+ } else {
+ return TintedDrawable.constructTintedDrawable(
+ context.getResources(), iconId, R.color.light_normal_color);
+ }
+ }
}
\ No newline at end of file
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/contextmenu/ContextMenuParams.java b/chrome/android/java/src/org/chromium/chrome/browser/contextmenu/ContextMenuParams.java
index 963e43d..58ae91d 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/contextmenu/ContextMenuParams.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/contextmenu/ContextMenuParams.java
@@ -6,6 +6,7 @@
import android.text.TextUtils;
+import org.chromium.base.VisibleForTesting;
import org.chromium.base.annotations.CalledByNative;
import org.chromium.base.annotations.JNINamespace;
import org.chromium.chrome.browser.UrlConstants;
@@ -133,7 +134,8 @@
return false;
}
- private ContextMenuParams(int mediaType, String pageUrl, String linkUrl, String linkText,
+ @VisibleForTesting
+ ContextMenuParams(int mediaType, String pageUrl, String linkUrl, String linkText,
String unfilteredLinkUrl, String srcUrl, String titleText, boolean imageWasFetchedLoFi,
Referrer referrer, boolean canSavemedia) {
mPageUrl = pageUrl;
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/contextmenu/ContextMenuUi.java b/chrome/android/java/src/org/chromium/chrome/browser/contextmenu/ContextMenuUi.java
index 5982197..8d23366 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/contextmenu/ContextMenuUi.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/contextmenu/ContextMenuUi.java
@@ -4,7 +4,7 @@
package org.chromium.chrome.browser.contextmenu;
-import android.app.Activity;
+import android.content.Context;
import android.util.Pair;
import org.chromium.base.Callback;
@@ -18,7 +18,7 @@
public interface ContextMenuUi {
/**
* Shows the Context Menu in Chrome.
- * @param activity Used to inflate the context menu.
+ * @param context Used to inflate the context menu.
* @param params The current parameters for the the context menu.
* @param items The list of items that need to be displayed in the context menu items. This is
* taken from the return value of {@link ContextMenuPopulator#buildContextMenu(
@@ -26,7 +26,12 @@
* @param onItemClicked When the user has pressed an item the menuId associated with the item
* is sent back through {@link Callback#onResult(Object)}. The ids that
* could be called are in ids.xml.
+ * @param onMenuShown After the menu is displayed this method should be called to present a
+ * full menu.
+ * @param onMenuClosed When the menu is closed, this method is called to do any possible final
+ * clean up.
*/
- void displayMenu(Activity activity, ContextMenuParams params,
- List<Pair<Integer, List<ContextMenuItem>>> items, Callback<Integer> onItemClicked);
+ void displayMenu(Context context, ContextMenuParams params,
+ List<Pair<Integer, List<ContextMenuItem>>> items, Callback<Integer> onItemClicked,
+ Runnable onMenuShown, Runnable onMenuClosed);
}
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/contextmenu/PlatformContextMenuUi.java b/chrome/android/java/src/org/chromium/chrome/browser/contextmenu/PlatformContextMenuUi.java
index 5069c044e..c73f44c 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/contextmenu/PlatformContextMenuUi.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/contextmenu/PlatformContextMenuUi.java
@@ -4,7 +4,6 @@
package org.chromium.chrome.browser.contextmenu;
-import android.app.Activity;
import android.content.Context;
import android.text.TextUtils;
import android.util.Pair;
@@ -27,13 +26,12 @@
}
@Override
- public void displayMenu(Activity activity, ContextMenuParams params,
- List<Pair<Integer, List<ContextMenuItem>>> itemGroups,
- final Callback<Integer> listener) {
-
+ public void displayMenu(Context context, ContextMenuParams params,
+ List<Pair<Integer, List<ContextMenuItem>>> itemGroups, final Callback<Integer> listener,
+ Runnable onMenuShown, Runnable onMenuClosed) {
String headerText = ChromeContextMenuPopulator.createHeaderText(params);
if (!TextUtils.isEmpty(headerText)) {
- setHeaderText(activity, mMenu, headerText);
+ setHeaderText(context, mMenu, headerText);
}
MenuItem.OnMenuItemClickListener menuListener = new MenuItem.OnMenuItemClickListener() {
@@ -47,7 +45,7 @@
List<ContextMenuItem> group = itemGroups.get(groupIndex).second;
for (int itemIndex = 0; itemIndex < group.size(); itemIndex++) {
ContextMenuItem item = group.get(itemIndex);
- MenuItem menuItem = mMenu.add(0, item.menuId, 0, item.getString(activity));
+ MenuItem menuItem = mMenu.add(0, item.menuId, 0, item.getString(context));
menuItem.setOnMenuItemClickListener(menuListener);
}
}
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/contextmenu/TabularContextMenuListAdapter.java b/chrome/android/java/src/org/chromium/chrome/browser/contextmenu/TabularContextMenuListAdapter.java
new file mode 100644
index 0000000..dbe631d
--- /dev/null
+++ b/chrome/android/java/src/org/chromium/chrome/browser/contextmenu/TabularContextMenuListAdapter.java
@@ -0,0 +1,83 @@
+// Copyright 2017 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.contextmenu;
+
+import android.content.Context;
+import android.graphics.drawable.Drawable;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.BaseAdapter;
+import android.widget.ImageView;
+import android.widget.TextView;
+
+import org.chromium.chrome.R;
+
+import java.util.List;
+
+/**
+ * Takes a list of {@link ContextMenuItem} and puts them in an adapter meant to be used within a
+ * list view.
+ */
+class TabularContextMenuListAdapter extends BaseAdapter {
+ private final List<ContextMenuItem> mMenuItems;
+ private final Context mContext;
+
+ /**
+ * Adapter for the tabular context menu UI
+ * @param menuItems The list of items to display in the view.
+ * @param context Used to inflate the layout.
+ */
+ TabularContextMenuListAdapter(List<ContextMenuItem> menuItems, Context context) {
+ mMenuItems = menuItems;
+ mContext = context;
+ }
+
+ @Override
+ public int getCount() {
+ return mMenuItems.size();
+ }
+
+ @Override
+ public Object getItem(int position) {
+ return mMenuItems.get(position);
+ }
+
+ @Override
+ public long getItemId(int position) {
+ return mMenuItems.get(position).menuId;
+ }
+
+ @Override
+ public View getView(int position, View convertView, ViewGroup parent) {
+ ContextMenuItem menuItem = mMenuItems.get(position);
+ ViewHolderItem viewHolder;
+
+ if (convertView == null) {
+ LayoutInflater inflater = LayoutInflater.from(mContext);
+ convertView = inflater.inflate(R.layout.tabular_context_menu_row, null);
+
+ viewHolder = new ViewHolderItem();
+ viewHolder.mIcon = (ImageView) convertView.findViewById(R.id.context_menu_icon);
+ viewHolder.mText = (TextView) convertView.findViewById(R.id.context_text);
+
+ convertView.setTag(viewHolder);
+ } else {
+ viewHolder = (ViewHolderItem) convertView.getTag();
+ }
+
+ viewHolder.mText.setText(menuItem.getString(mContext));
+ Drawable icon = menuItem.getDrawableAndDescription(mContext);
+ viewHolder.mIcon.setImageDrawable(icon);
+ viewHolder.mIcon.setVisibility(icon != null ? View.VISIBLE : View.INVISIBLE);
+
+ return convertView;
+ }
+
+ private static class ViewHolderItem {
+ ImageView mIcon;
+ TextView mText;
+ }
+}
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/contextmenu/TabularContextMenuPagerAdapter.java b/chrome/android/java/src/org/chromium/chrome/browser/contextmenu/TabularContextMenuPagerAdapter.java
new file mode 100644
index 0000000..436eca1
--- /dev/null
+++ b/chrome/android/java/src/org/chromium/chrome/browser/contextmenu/TabularContextMenuPagerAdapter.java
@@ -0,0 +1,71 @@
+// Copyright 2017 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.contextmenu;
+
+import android.support.v4.text.TextUtilsCompat;
+import android.support.v4.view.PagerAdapter;
+import android.support.v4.view.ViewCompat;
+import android.util.Pair;
+import android.view.View;
+import android.view.ViewGroup;
+
+import java.util.List;
+import java.util.Locale;
+
+/**
+ * Takes a list of views and strings and creates a wrapper for the ViewPager and Tab adapter.
+ */
+class TabularContextMenuPagerAdapter extends PagerAdapter {
+ private final List<Pair<String, ViewGroup>> mViewList;
+ private final boolean mIsRightToLeft;
+
+ /**
+ * Used in combination of a TabLayout to create a multi view layout.
+ * @param views Thew views to use in the pager Adapter.
+ */
+ TabularContextMenuPagerAdapter(List<Pair<String, ViewGroup>> views) {
+ mViewList = views;
+ mIsRightToLeft = TextUtilsCompat.getLayoutDirectionFromLocale(Locale.getDefault())
+ == ViewCompat.LAYOUT_DIRECTION_RTL;
+ }
+
+ // Addresses the RTL display bug: https://code.google.com/p/android/issues/detail?id=56831
+ private int adjustIndexForDirectionality(int index, int count) {
+ if (mIsRightToLeft) {
+ return count - 1 - index;
+ }
+ return index;
+ }
+
+ @Override
+ public Object instantiateItem(ViewGroup container, int position) {
+ position = adjustIndexForDirectionality(position, getCount());
+ ViewGroup layout = mViewList.get(position).second;
+ container.addView(layout);
+ return layout;
+ }
+
+ @Override
+ public void destroyItem(ViewGroup container, int position, Object object) {
+ position = adjustIndexForDirectionality(position, getCount());
+ container.removeViewAt(position);
+ }
+
+ @Override
+ public int getCount() {
+ return mViewList.size();
+ }
+
+ @Override
+ public boolean isViewFromObject(View view, Object object) {
+ return view == object;
+ }
+
+ @Override
+ public CharSequence getPageTitle(int position) {
+ position = adjustIndexForDirectionality(position, getCount());
+ return mViewList.get(position).first;
+ }
+}
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/contextmenu/TabularContextMenuUi.java b/chrome/android/java/src/org/chromium/chrome/browser/contextmenu/TabularContextMenuUi.java
new file mode 100644
index 0000000..387ffd6
--- /dev/null
+++ b/chrome/android/java/src/org/chromium/chrome/browser/contextmenu/TabularContextMenuUi.java
@@ -0,0 +1,183 @@
+// Copyright 2017 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.contextmenu;
+
+import android.app.Dialog;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.support.design.widget.TabLayout;
+import android.support.v4.view.ViewPager;
+import android.support.v7.app.AlertDialog;
+import android.text.TextUtils;
+import android.util.Pair;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.AdapterView;
+import android.widget.BaseAdapter;
+import android.widget.ListView;
+import android.widget.TextView;
+
+import org.chromium.base.ApiCompatibilityUtils;
+import org.chromium.base.Callback;
+import org.chromium.base.VisibleForTesting;
+import org.chromium.chrome.R;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * A custom dialog that separates each group into separate tabs. It uses a dialog instead.
+ */
+public class TabularContextMenuUi implements ContextMenuUi, AdapterView.OnItemClickListener {
+ private Dialog mDialog;
+ private Callback<Integer> mCallback;
+ private int mMenuItemHeight;
+
+ @Override
+ public void displayMenu(Context context, ContextMenuParams params,
+ List<Pair<Integer, List<ContextMenuItem>>> items, Callback<Integer> onItemClicked,
+ final Runnable onMenuShown, final Runnable onMenuClosed) {
+ mCallback = onItemClicked;
+ mDialog = createDialog(context, params, items);
+ mDialog.getWindow().setBackgroundDrawable(ApiCompatibilityUtils.getDrawable(
+ context.getResources(), R.drawable.bg_find_toolbar_popup));
+
+ mDialog.setOnShowListener(new DialogInterface.OnShowListener() {
+ @Override
+ public void onShow(DialogInterface dialogInterface) {
+ onMenuShown.run();
+ }
+ });
+
+ mDialog.setOnDismissListener(new DialogInterface.OnDismissListener() {
+ @Override
+ public void onDismiss(DialogInterface dialogInterface) {
+ onMenuClosed.run();
+ }
+ });
+
+ mDialog.show();
+ }
+
+ /**
+ * Returns the fully complete dialog based off the params and the itemGroups.
+ * @param context Used to inflate the dialog.
+ * @param params Used to get the header title.
+ * @param itemGroups If there is more than one group it will create a paged view.
+ * @return Returns a final dialog that does not have a background can be displayed using
+ * {@link AlertDialog#show()}.
+ */
+ private Dialog createDialog(Context context, ContextMenuParams params,
+ List<Pair<Integer, List<ContextMenuItem>>> itemGroups) {
+ Dialog dialog = new Dialog(context);
+ dialog.setContentView(createPagerView(context, params, itemGroups));
+ return dialog;
+ }
+
+ /**
+ * Creates the view of a context menu. Based off the Context Type, it'll adjust the list of
+ * items and display only the ones that'll be on that specific group.
+ * @param context Used to get the resources of an item.
+ * @param params used to create the header text.
+ * @param items A set of Items to display in a context menu. Filtered based off the type.
+ * @return Returns a filled LinearLayout with all the context menu items
+ */
+ @VisibleForTesting
+ ViewGroup createContextMenuPageUi(
+ Context context, ContextMenuParams params, List<ContextMenuItem> items, int maxCount) {
+ ViewGroup baseLayout = (ViewGroup) LayoutInflater.from(context).inflate(
+ R.layout.tabular_context_menu_page, null);
+ ListView listView = (ListView) baseLayout.findViewById(R.id.selectable_items);
+ displayHeaderIfVisibleItems(params, baseLayout);
+
+ // Set the list adapter and get the height to display it appropriately in a dialog.
+ TabularContextMenuListAdapter listAdapter =
+ new TabularContextMenuListAdapter(items, context);
+ ViewGroup.LayoutParams layoutParams = listView.getLayoutParams();
+ layoutParams.height = measureApproximateListViewHeight(listView, listAdapter, maxCount);
+ listView.setLayoutParams(layoutParams);
+ listView.setAdapter(listAdapter);
+ listView.setOnItemClickListener(this);
+
+ return baseLayout;
+ }
+
+ private void displayHeaderIfVisibleItems(ContextMenuParams params, ViewGroup baseLayout) {
+ String headerText = ChromeContextMenuPopulator.createHeaderText(params);
+ TextView headerTextView = (TextView) baseLayout.findViewById(R.id.context_header_text);
+ if (TextUtils.isEmpty(headerText)) {
+ headerTextView.setVisibility(View.GONE);
+ baseLayout.findViewById(R.id.context_divider).setVisibility(View.GONE);
+ return;
+ }
+ headerTextView.setVisibility(View.VISIBLE);
+ headerTextView.setText(headerText);
+ }
+
+ /**
+ * To save time measuring the height, this method gets an item if the height has not been
+ * previous measured and multiplies it by count of the total amount of items. It is fine if the
+ * height too small as the ListView will scroll through the other values.
+ * @param listView The ListView to measure the surrounding padding.
+ * @param listAdapter The adapter which contains the items within the list.
+ * @return Returns the combined height of the padding of the ListView and the approximate height
+ * of the ListView based off the an item.
+ */
+ private int measureApproximateListViewHeight(
+ ListView listView, BaseAdapter listAdapter, int maxCount) {
+ int totalHeight = listView.getPaddingTop() + listView.getPaddingBottom();
+ if (mMenuItemHeight == 0 && !listAdapter.isEmpty()) {
+ View view = listAdapter.getView(0, null, listView);
+ view.measure(View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED),
+ View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED));
+ mMenuItemHeight = view.getMeasuredHeight();
+ }
+ return totalHeight + mMenuItemHeight * maxCount;
+ }
+
+ /**
+ * Creates a ViewPageAdapter based off the given list of views.
+ * @param context Used to inflate the new ViewPager
+ * @param params Used to get the header text.
+ * @param itemGroups The list of views to put into the ViewPager. The string is the title of the
+ * tab
+ * @return Returns a complete tabular context menu view.
+ */
+ @VisibleForTesting
+ View createPagerView(Context context, ContextMenuParams params,
+ List<Pair<Integer, List<ContextMenuItem>>> itemGroups) {
+ View view = LayoutInflater.from(context).inflate(R.layout.tabular_context_menu, null);
+
+ List<Pair<String, ViewGroup>> viewGroups = new ArrayList<>();
+ int maxCount = 0;
+ for (int i = 0; i < itemGroups.size(); i++) {
+ Pair<Integer, List<ContextMenuItem>> itemGroup = itemGroups.get(i);
+ maxCount = Math.max(maxCount, itemGroup.second.size());
+ }
+ for (int i = 0; i < itemGroups.size(); i++) {
+ Pair<Integer, List<ContextMenuItem>> itemGroup = itemGroups.get(i);
+ viewGroups.add(new Pair<>(context.getString(itemGroup.first),
+ createContextMenuPageUi(context, params, itemGroup.second, maxCount)));
+ }
+ TabularContextMenuViewPager pager =
+ (TabularContextMenuViewPager) view.findViewById(R.id.custom_pager);
+ pager.setAdapter(new TabularContextMenuPagerAdapter(viewGroups));
+
+ TabLayout tabLayout = (TabLayout) view.findViewById(R.id.tab_layout);
+ if (itemGroups.size() <= 1) {
+ tabLayout.setVisibility(View.GONE);
+ }
+ tabLayout.setupWithViewPager((ViewPager) view.findViewById(R.id.custom_pager));
+
+ return view;
+ }
+
+ @Override
+ public void onItemClick(AdapterView<?> adapterView, View view, int position, long id) {
+ mDialog.dismiss();
+ mCallback.onResult((int) id);
+ }
+}
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/contextmenu/TabularContextMenuViewPager.java b/chrome/android/java/src/org/chromium/chrome/browser/contextmenu/TabularContextMenuViewPager.java
new file mode 100644
index 0000000..640aabe
--- /dev/null
+++ b/chrome/android/java/src/org/chromium/chrome/browser/contextmenu/TabularContextMenuViewPager.java
@@ -0,0 +1,53 @@
+// Copyright 2017 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.contextmenu;
+
+import android.content.Context;
+import android.support.v4.view.ViewPager;
+import android.util.AttributeSet;
+import android.view.View;
+
+import org.chromium.chrome.R;
+
+/**
+ * When there is more than one view for the context menu to display, it wraps the display in a view
+ * pager.
+ */
+public class TabularContextMenuViewPager extends ViewPager {
+ public TabularContextMenuViewPager(Context context) {
+ super(context);
+ }
+
+ public TabularContextMenuViewPager(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ }
+
+ /**
+ * Used to show the full ViewPager dialog. Without this the dialog would have no height or
+ * width.
+ */
+ @Override
+ protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+ int maxHeight = 0;
+ int tabHeight = 0;
+ for (int i = 0; i < getChildCount(); i++) {
+ View child = getChildAt(i);
+ child.measure(widthMeasureSpec,
+ MeasureSpec.makeMeasureSpec(maxHeight, MeasureSpec.UNSPECIFIED));
+ int measuredHeight = child.getMeasuredHeight();
+
+ // The ViewPager also considers the tab layout one of its children, and needs to be
+ // treated separately from getting the largest height.
+ if (child.getId() == R.id.tab_layout && child.getVisibility() != GONE) {
+ tabHeight = measuredHeight;
+ } else {
+ maxHeight = Math.max(maxHeight, child.getMeasuredHeight());
+ }
+ }
+ maxHeight += tabHeight;
+ heightMeasureSpec = MeasureSpec.makeMeasureSpec(maxHeight, MeasureSpec.EXACTLY);
+ super.onMeasure(widthMeasureSpec, heightMeasureSpec);
+ }
+}
diff --git a/chrome/android/java_sources.gni b/chrome/android/java_sources.gni
index e9567b1..19f0196 100644
--- a/chrome/android/java_sources.gni
+++ b/chrome/android/java_sources.gni
@@ -222,6 +222,10 @@
"java/src/org/chromium/chrome/browser/contextmenu/ContextMenuPopulator.java",
"java/src/org/chromium/chrome/browser/contextmenu/ContextMenuTitleView.java",
"java/src/org/chromium/chrome/browser/contextmenu/ContextMenuUi.java",
+ "java/src/org/chromium/chrome/browser/contextmenu/TabularContextMenuUi.java",
+ "java/src/org/chromium/chrome/browser/contextmenu/TabularContextMenuListAdapter.java",
+ "java/src/org/chromium/chrome/browser/contextmenu/TabularContextMenuPagerAdapter.java",
+ "java/src/org/chromium/chrome/browser/contextmenu/TabularContextMenuViewPager.java",
"java/src/org/chromium/chrome/browser/contextmenu/PlatformContextMenuUi.java",
"java/src/org/chromium/chrome/browser/contextualsearch/BarOverlapTapSuppression.java",
"java/src/org/chromium/chrome/browser/contextualsearch/ContextualSearchBlacklist.java",
@@ -1286,6 +1290,7 @@
"javatests/src/org/chromium/chrome/browser/compositor/layouts/MockResourcesForLayout.java",
"javatests/src/org/chromium/chrome/browser/compositor/overlays/strip/TabStripTest.java",
"javatests/src/org/chromium/chrome/browser/contextmenu/ContextMenuTest.java",
+ "javatests/src/org/chromium/chrome/browser/contextmenu/TabularContextMenuUiTest.java",
"javatests/src/org/chromium/chrome/browser/contextualsearch/ContextualSearchFakeServer.java",
"javatests/src/org/chromium/chrome/browser/contextualsearch/ContextualSearchManagerTest.java",
"javatests/src/org/chromium/chrome/browser/contextualsearch/ContextualSearchPolicyTest.java",
diff --git a/chrome/android/javatests/src/org/chromium/chrome/browser/contextmenu/TabularContextMenuUiTest.java b/chrome/android/javatests/src/org/chromium/chrome/browser/contextmenu/TabularContextMenuUiTest.java
new file mode 100644
index 0000000..9722996
--- /dev/null
+++ b/chrome/android/javatests/src/org/chromium/chrome/browser/contextmenu/TabularContextMenuUiTest.java
@@ -0,0 +1,142 @@
+// Copyright 2017 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.contextmenu;
+
+import android.support.design.widget.TabLayout;
+import android.support.test.filters.SmallTest;
+import android.util.Pair;
+import android.view.View;
+import android.widget.TextView;
+
+import org.chromium.base.CollectionUtil;
+import org.chromium.base.ThreadUtils;
+import org.chromium.base.test.util.Feature;
+import org.chromium.chrome.R;
+import org.chromium.chrome.browser.ChromeActivity;
+import org.chromium.chrome.test.ChromeActivityTestCaseBase;
+import org.chromium.content_public.common.Referrer;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.Callable;
+import java.util.concurrent.ExecutionException;
+
+/**
+ * A class to checkout the TabularContextMenuUi. This confirms the the UI represents items and
+ * groups.
+ */
+public class TabularContextMenuUiTest extends ChromeActivityTestCaseBase<ChromeActivity> {
+ public TabularContextMenuUiTest() {
+ super(ChromeActivity.class);
+ }
+
+ private static class MockMenuParams extends ContextMenuParams {
+ private String mUrl = "";
+
+ private MockMenuParams(int mediaType, String pageUrl, String linkUrl, String linkText,
+ String unfilteredLinkUrl, String srcUrl, String titleText,
+ boolean imageWasFetchedLoFi, Referrer referrer, boolean canSavemedia) {
+ super(mediaType, pageUrl, linkUrl, linkText, unfilteredLinkUrl, srcUrl, titleText,
+ imageWasFetchedLoFi, referrer, canSavemedia);
+ }
+
+ private MockMenuParams(String url) {
+ this(0, "", "", "", "", "", "", false, null, true);
+ mUrl = url;
+ }
+
+ @Override
+ public String getLinkUrl() {
+ return mUrl;
+ }
+ }
+
+ @Override
+ public void startMainActivity() throws InterruptedException {
+ startMainActivityOnBlankPage();
+ }
+
+ @SmallTest
+ @Feature({"CustomContextMenu"})
+ public void testViewDisplaysSingleItemProperly() throws ExecutionException {
+ final TabularContextMenuUi dialog = new TabularContextMenuUi();
+
+ final List<Pair<Integer, List<ContextMenuItem>>> itemGroups = new ArrayList<>();
+ List<ContextMenuItem> item = CollectionUtil.newArrayList(ContextMenuItem.ADD_TO_CONTACTS,
+ ContextMenuItem.CALL, ContextMenuItem.COPY_LINK_ADDRESS);
+ itemGroups.add(new Pair<>(R.string.contextmenu_link_title, item));
+ final String url = "http://google.com";
+ View view = ThreadUtils.runOnUiThreadBlocking(new Callable<View>() {
+ @Override
+ public View call() {
+ return dialog.createPagerView(getActivity(), new MockMenuParams(url), itemGroups);
+ }
+ });
+
+ TabLayout layout = (TabLayout) view.findViewById(R.id.tab_layout);
+ assertEquals(layout.getVisibility(), View.GONE);
+ }
+
+ @SmallTest
+ @Feature({"CustomContextMenu"})
+ public void testViewDisplaysViewPagerForMultipleItems() throws ExecutionException {
+ final TabularContextMenuUi dialog = new TabularContextMenuUi();
+
+ final List<Pair<Integer, List<ContextMenuItem>>> itemGroups = new ArrayList<>();
+ List<ContextMenuItem> item = CollectionUtil.newArrayList(ContextMenuItem.ADD_TO_CONTACTS,
+ ContextMenuItem.CALL, ContextMenuItem.COPY_LINK_ADDRESS);
+ itemGroups.add(new Pair<>(R.string.contextmenu_link_title, item));
+ itemGroups.add(new Pair<>(R.string.contextmenu_link_title, item));
+ final String url = "http://google.com";
+ View view = ThreadUtils.runOnUiThreadBlocking(new Callable<View>() {
+ @Override
+ public View call() {
+ return dialog.createPagerView(getActivity(), new MockMenuParams(url), itemGroups);
+ }
+ });
+
+ TabLayout layout = (TabLayout) view.findViewById(R.id.tab_layout);
+ assertEquals(layout.getVisibility(), View.VISIBLE);
+ }
+
+ @SmallTest
+ @Feature({"CustomContextMenu"})
+ public void testURLIsShownOnContextMenu() throws ExecutionException {
+ final TabularContextMenuUi dialog = new TabularContextMenuUi();
+ final List<ContextMenuItem> item =
+ CollectionUtil.newArrayList(ContextMenuItem.ADD_TO_CONTACTS, ContextMenuItem.CALL,
+ ContextMenuItem.COPY_LINK_ADDRESS);
+ final String expectedUrl = "http://google.com";
+ View view = ThreadUtils.runOnUiThreadBlocking(new Callable<View>() {
+ @Override
+ public View call() {
+ return dialog.createContextMenuPageUi(
+ getActivity(), new MockMenuParams(expectedUrl), item, item.size());
+ }
+ });
+
+ TextView textView = (TextView) view.findViewById(R.id.context_header_text);
+ assertEquals(expectedUrl, String.valueOf(textView.getText()));
+ }
+
+ @SmallTest
+ @Feature({"CustomContextMenu"})
+ public void testHeaderIsNotShownWhenThereIsNoParams() throws ExecutionException {
+ final TabularContextMenuUi dialog = new TabularContextMenuUi();
+ final List<ContextMenuItem> item =
+ CollectionUtil.newArrayList(ContextMenuItem.ADD_TO_CONTACTS, ContextMenuItem.CALL,
+ ContextMenuItem.COPY_LINK_ADDRESS);
+ View view = ThreadUtils.runOnUiThreadBlocking(new Callable<View>() {
+ @Override
+ public View call() {
+ return dialog.createContextMenuPageUi(
+ getActivity(), new MockMenuParams(""), item, item.size());
+ }
+ });
+
+ assertEquals(view.findViewById(R.id.context_header_text).getVisibility(), View.GONE);
+ assertEquals(view.findViewById(R.id.context_divider).getVisibility(), View.GONE);
+ }
+}
diff --git a/chrome/browser/about_flags.cc b/chrome/browser/about_flags.cc
index c33cfb5b..e3d084e 100644
--- a/chrome/browser/about_flags.cc
+++ b/chrome/browser/about_flags.cc
@@ -2485,6 +2485,13 @@
kDelayNavigationFeatureVariations,
"DelayNavigation")},
+#if defined(OS_ANDROID)
+ {"enable-custom-context-menu",
+ flag_descriptions::kEnableCustomContextMenuName,
+ flag_descriptions::kEnableCustomContextMenuDescription, kOsAndroid,
+ FEATURE_VALUE_TYPE(chrome::android::kCustomContextMenu)},
+#endif // OS_ANDROID
+
// NOTE: Adding new command-line switches requires adding corresponding
// entries to enum "LoginCustomFlags" in histograms.xml. See note in
// histograms.xml and don't forget to run AboutFlagsHistogramTest unit test.
diff --git a/chrome/browser/android/chrome_feature_list.cc b/chrome/browser/android/chrome_feature_list.cc
index de980216..2d9479f 100644
--- a/chrome/browser/android/chrome_feature_list.cc
+++ b/chrome/browser/android/chrome_feature_list.cc
@@ -51,6 +51,7 @@
&kChromeHomeFeature,
&kContextualSearchSingleActions,
&kContextualSearchUrlActions,
+ &kCustomContextMenu,
&kCustomFeedbackUi,
&kImportantSitesInCBD,
&kImprovedA2HS,
@@ -121,6 +122,9 @@
const base::Feature kContextualSearchUrlActions{
"ContextualSearchUrlActions", base::FEATURE_DISABLED_BY_DEFAULT};
+const base::Feature kCustomContextMenu{"CustomContextMenu",
+ base::FEATURE_DISABLED_BY_DEFAULT};
+
const base::Feature kCustomFeedbackUi{"CustomFeedbackUi",
base::FEATURE_DISABLED_BY_DEFAULT};
diff --git a/chrome/browser/android/chrome_feature_list.h b/chrome/browser/android/chrome_feature_list.h
index 4027b4f..0f87ecdd 100644
--- a/chrome/browser/android/chrome_feature_list.h
+++ b/chrome/browser/android/chrome_feature_list.h
@@ -22,6 +22,7 @@
extern const base::Feature kChromeHomeFeature;
extern const base::Feature kContextualSearchSingleActions;
extern const base::Feature kContextualSearchUrlActions;
+extern const base::Feature kCustomContextMenu;
extern const base::Feature kCustomFeedbackUi;
extern const base::Feature kDownloadAutoResumptionThrottling;
extern const base::Feature kImportantSitesInCBD;
diff --git a/chrome/browser/flag_descriptions.cc b/chrome/browser/flag_descriptions.cc
index 63877eb..3cd7892c 100644
--- a/chrome/browser/flag_descriptions.cc
+++ b/chrome/browser/flag_descriptions.cc
@@ -2740,6 +2740,16 @@
"Enables additional keyboard shortcuts that are useful for debugging "
"Ash.";
+#if defined(OS_ANDROID)
+
+const char kEnableCustomContextMenuName[] = "Enable custom context menu";
+
+const char kEnableCustomContextMenuDescription[] =
+ "Enables a new context menu when a link, image, or video is pressed within "
+ "Chrome.";
+
+#endif // defined(OS_ANDROID)
+
#if defined(OS_CHROMEOS)
// File Manager
diff --git a/chrome/browser/flag_descriptions.h b/chrome/browser/flag_descriptions.h
index 9e97ec7..08656e2 100644
--- a/chrome/browser/flag_descriptions.h
+++ b/chrome/browser/flag_descriptions.h
@@ -2959,6 +2959,16 @@
// Description of the 'Debugging keyboard shortcuts' lab.
extern const char kDebugShortcutsDescription[];
+#if defined(OS_ANDROID)
+
+// Name for the flag to enable the custom context menu.
+extern const char kEnableCustomContextMenuName[];
+
+// Description for the flag to enable the custom context menu.
+extern const char kEnableCustomContextMenuDescription[];
+
+#endif // defined(OS_ANDROID)
+
#if defined(OS_CHROMEOS)
// File Manager
diff --git a/tools/metrics/histograms/histograms.xml b/tools/metrics/histograms/histograms.xml
index 027255a..a06a3251 100644
--- a/tools/metrics/histograms/histograms.xml
+++ b/tools/metrics/histograms/histograms.xml
@@ -100615,6 +100615,7 @@
<int value="379428799" label="security-chip-animation"/>
<int value="385969127" label="disable-win32k-lockdown"/>
<int value="387178525" label="VideoFullscreenOrientationLock:enabled"/>
+ <int value="388996324" label="CustomContextMenu:disabled"/>
<int value="400322063" label="ash-disable-screen-orientation-lock"/>
<int value="401983950" label="enable-spdy4"/>
<int value="402143634" label="enable-search-button-in-omnibox-always"/>
@@ -100687,6 +100688,7 @@
<int value="646252875" label="ReadItLaterInMenu:enabled"/>
<int value="646738320" label="disable-gesture-editing"/>
<int value="650602639" label="enable-autofill-keyboard-accessory-view"/>
+ <int value="652561231" label="CustomContextMenu:enabled"/>
<int value="683410401"
label="enable-proximity-auth-bluetooth-low-energy-discovery"/>
<int value="684806628" label="TranslateLanguageByULP:disabled"/>