Commit dcca910d authored by Bo Liu's avatar Bo Liu Committed by Commit Bot

weblayer: Add API to override action mode onclick

Add an API that allows client to declare the action mode items it wants
to override. When overridden, the selected text is passed to the client
and whatever the default action in weblayer does not happen.

Currently only support search and share since they are always enabled.

Bug: b/159070012
Change-Id: Ibabc253a9d990fbf4c1746fd402068df4aeafff2
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2463989Reviewed-by: default avatarScott Violet <sky@chromium.org>
Commit-Queue: Bo <boliu@chromium.org>
Cr-Commit-Position: refs/heads/master@{#815872}
parent 5f364bfb
......@@ -379,6 +379,22 @@ public class SelectionPopupControllerImpl extends ActionModeCallbackHelper
mAllowedMenuItems = allowedMenuItems;
}
@Override
public int getAllowedMenuItemIfAny(ActionMode mode, MenuItem item) {
if (!isActionModeValid()) return 0;
int id = item.getItemId();
int groupId = item.getGroupId();
if (id == R.id.select_action_menu_share) {
return MENU_ITEM_SHARE;
} else if (id == R.id.select_action_menu_web_search) {
return MENU_ITEM_WEB_SEARCH;
} else if (groupId == R.id.select_action_menu_text_processing_menus) {
return MENU_ITEM_PROCESS_TEXT;
}
return 0;
}
@VisibleForTesting
@CalledByNative
public void showSelectionMenu(int left, int top, int right, int bottom, int handleHeight,
......
......@@ -100,7 +100,14 @@ public abstract class ActionModeCallbackHelper {
* Set the action mode menu items allowed on the content.
* @param allowedMenuItems bit field of item-flag mapping.
*/
public abstract void setAllowedMenuItems(int menItems);
public abstract void setAllowedMenuItems(int allowedMenuItems);
/**
* If the passed in mode and menu matches one of the MENU_ITEM_* items, return it.
* Otherwise, return 0. Only call from inside the implementation of
* ActionMode.Callback#onActionItemClicked.
*/
public abstract int getAllowedMenuItemIfAny(ActionMode mode, MenuItem item);
/**
* @see {@link ActionMode.Callback#onCreateActionMode(ActionMode, Menu)}
......
......@@ -20,6 +20,8 @@ import org.junit.runner.RunWith;
import org.chromium.base.test.util.CallbackHelper;
import org.chromium.content_public.browser.test.util.CriteriaHelper;
import org.chromium.content_public.browser.test.util.TestThreadUtils;
import org.chromium.weblayer.ActionModeCallback;
import org.chromium.weblayer.ActionModeItemType;
import org.chromium.weblayer.Browser;
import org.chromium.weblayer.Tab;
import org.chromium.weblayer.TabListCallback;
......@@ -287,4 +289,21 @@ public class TabTest {
// The WebContents should not have been hidden as a result of the rotation.
Assert.assertFalse(mActivityTestRule.executeScriptAndExtractBoolean("gotHide", false));
}
@Test
@SmallTest
@MinWebLayerVersion(88)
public void setFloatingActionModeOverride() throws Exception {
mActivity = mActivityTestRule.launchShellWithUrl("about:blank");
TestThreadUtils.runOnUiThreadBlocking(() -> {
mActivity.getBrowser().getActiveTab().setFloatingActionModeOverride(
ActionModeItemType.SHARE, new ActionModeCallback() {
@Override
public void onActionItemClicked(
@ActionModeItemType int item, String selectedText) {}
});
});
// Smoke test. It's not possible to trigger an action mode click in a test.
}
}
......@@ -340,6 +340,7 @@ generate_jni("jni") {
android_library("interfaces_java") {
sources = [
"org/chromium/weblayer_private/interfaces/APICallException.java",
"org/chromium/weblayer_private/interfaces/ActionModeItemType.java",
"org/chromium/weblayer_private/interfaces/BrowserFragmentArgs.java",
"org/chromium/weblayer_private/interfaces/BrowsingDataType.java",
"org/chromium/weblayer_private/interfaces/CookieChangeCause.java",
......
......@@ -7,31 +7,68 @@ package org.chromium.weblayer_private;
import android.app.SearchManager;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.os.RemoteException;
import android.view.ActionMode;
import android.view.Menu;
import android.view.MenuItem;
import androidx.annotation.Nullable;
import org.chromium.base.PackageManagerUtils;
import org.chromium.content_public.browser.ActionModeCallbackHelper;
import org.chromium.content_public.browser.SelectionPopupController;
import org.chromium.content_public.browser.WebContents;
import org.chromium.weblayer_private.interfaces.APICallException;
import org.chromium.weblayer_private.interfaces.ActionModeItemType;
import org.chromium.weblayer_private.interfaces.ITabClient;
import org.chromium.weblayer_private.interfaces.ObjectWrapper;
/**
* A class that handles selection action mode for WebLayer.
*/
public final class ActionModeCallback implements ActionMode.Callback {
private final ActionModeCallbackHelper mHelper;
// Can be null during init.
private @Nullable ITabClient mTabClient;
// Bitfield of @ActionModeItemType values.
private int mActionModeOverride;
// Convert from content ActionModeCallbackHelper.MENU_ITEM_* values to
// @ActionModeItemType values.
private static int contentToWebLayerType(int contentType) {
switch (contentType) {
case ActionModeCallbackHelper.MENU_ITEM_SHARE:
return ActionModeItemType.SHARE;
case ActionModeCallbackHelper.MENU_ITEM_WEB_SEARCH:
return ActionModeItemType.WEB_SEARCH;
case ActionModeCallbackHelper.MENU_ITEM_PROCESS_TEXT:
case 0:
return 0;
default:
assert false;
return 0;
}
}
public ActionModeCallback(WebContents webContents) {
mHelper =
SelectionPopupController.fromWebContents(webContents).getActionModeCallbackHelper();
}
public void setTabClient(ITabClient tabClient) {
mTabClient = tabClient;
}
public void setOverride(int actionModeItemTypes) {
mActionModeOverride = actionModeItemTypes;
}
@Override
public final boolean onCreateActionMode(ActionMode mode, Menu menu) {
int allowedActionModes = ActionModeCallbackHelper.MENU_ITEM_PROCESS_TEXT
| ActionModeCallbackHelper.MENU_ITEM_SHARE;
if (isWebSearchAvailable()) {
if ((mActionModeOverride & ActionModeItemType.WEB_SEARCH) != 0 || isWebSearchAvailable()) {
allowedActionModes |= ActionModeCallbackHelper.MENU_ITEM_WEB_SEARCH;
}
mHelper.setAllowedMenuItems(allowedActionModes);
......@@ -53,7 +90,19 @@ public final class ActionModeCallback implements ActionMode.Callback {
@Override
public final boolean onActionItemClicked(ActionMode mode, MenuItem item) {
return mHelper.onActionItemClicked(mode, item);
int menuItemType = contentToWebLayerType(mHelper.getAllowedMenuItemIfAny(mode, item));
if ((menuItemType & mActionModeOverride) == 0) {
return mHelper.onActionItemClicked(mode, item);
}
assert WebLayerFactoryImpl.getClientMajorVersion() >= 88;
try {
mTabClient.onActionItemClicked(
menuItemType, ObjectWrapper.wrap(mHelper.getSelectedText()));
} catch (RemoteException e) {
throw new APICallException(e);
}
mode.finish();
return true;
}
@Override
......
......@@ -140,6 +140,7 @@ public final class TabImpl extends ITab.Stub implements LoginPrompt.Observer {
private DisplayCutoutController mDisplayCutoutController;
private boolean mPostContainerViewInitDone;
private ActionModeCallback mActionModeCallback;
private WebLayerAccessibilityUtil.Observer mAccessibilityObserver;
......@@ -323,7 +324,8 @@ public final class TabImpl extends ITab.Stub implements LoginPrompt.Observer {
mPostContainerViewInitDone = true;
SelectionPopupController controller =
SelectionPopupController.fromWebContents(mWebContents);
controller.setActionModeCallback(new ActionModeCallback(mWebContents));
mActionModeCallback = new ActionModeCallback(mWebContents);
controller.setActionModeCallback(mActionModeCallback);
controller.setSelectionClient(SelectionClient.createSmartSelectionClient(mWebContents));
}
......@@ -524,6 +526,7 @@ public final class TabImpl extends ITab.Stub implements LoginPrompt.Observer {
StrictModeWorkaround.apply();
mClient = client;
mTabCallbackProxy = new TabCallbackProxy(mNativeTab, client);
mActionModeCallback.setTabClient(mClient);
}
@Override
......@@ -591,11 +594,13 @@ public final class TabImpl extends ITab.Stub implements LoginPrompt.Observer {
@Override
public void setTranslateTargetLanguage(String targetLanguage) {
StrictModeWorkaround.apply();
TabImplJni.get().setTranslateTargetLanguage(mNativeTab, targetLanguage);
}
@Override
public void setScrollOffsetsEnabled(boolean enabled) {
StrictModeWorkaround.apply();
if (enabled) {
if (mGestureStateListenerWithScroll == null) {
mGestureStateListenerWithScroll = new GestureStateListenerWithScroll() {
......@@ -619,6 +624,12 @@ public final class TabImpl extends ITab.Stub implements LoginPrompt.Observer {
}
}
@Override
public void setFloatingActionModeOverride(int actionModeItemTypes) {
StrictModeWorkaround.apply();
mActionModeCallback.setOverride(actionModeItemTypes);
}
public void removeFaviconCallbackProxy(FaviconCallbackProxy proxy) {
mFaviconCallbackProxies.remove(proxy);
}
......
// Copyright 2020 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
package org.chromium.weblayer_private.interfaces;
import androidx.annotation.IntDef;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@IntDef({ActionModeItemType.SHARE, ActionModeItemType.WEB_SEARCH})
@Retention(RetentionPolicy.SOURCE)
public @interface ActionModeItemType {
int SHARE = 1 << 0;
int WEB_SEARCH = 1 << 1;
}
......@@ -75,4 +75,7 @@ interface ITab {
// Added in 87
void setScrollOffsetsEnabled(in boolean enabled) = 26;
// Added in 88
void setFloatingActionModeOverride(in int actionModeItemTypes) = 27;
}
......@@ -46,4 +46,8 @@ interface ITabClient {
// Added in M87
void onVerticalScrollOffsetChanged(in int offset) = 11;
// Added in M88
void onActionItemClicked(
in int actionModeItemType, in IObjectWrapper selectedString) = 12;
}
......@@ -35,6 +35,8 @@ android_resources("client_resources") {
android_library("java") {
sources = [
"org/chromium/weblayer/ActionModeCallback.java",
"org/chromium/weblayer/ActionModeItemType.java",
"org/chromium/weblayer/BroadcastReceiver.java",
"org/chromium/weblayer/Browser.java",
"org/chromium/weblayer/BrowserControlsOffsetCallback.java",
......
// Copyright 2020 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
package org.chromium.weblayer;
/**
* Used to override floating some action mode menu items.
*
* @since 88
*/
public abstract class ActionModeCallback {
/**
* Called when an overridden item type is clicked. The action mode is closed after this returns.
* @param selectedText the raw selected text. Client is responsible for trimming it to fit into
* some use cases as the text can be very large.
*/
public void onActionItemClicked(@ActionModeItemType int item, String selectedText) {}
}
// Copyright 2020 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
package org.chromium.weblayer;
import androidx.annotation.IntDef;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@IntDef({ActionModeItemType.SHARE, ActionModeItemType.WEB_SEARCH})
@Retention(RetentionPolicy.SOURCE)
public @interface ActionModeItemType {
int SHARE = org.chromium.weblayer_private.interfaces.ActionModeItemType.SHARE;
int WEB_SEARCH = org.chromium.weblayer_private.interfaces.ActionModeItemType.WEB_SEARCH;
}
......@@ -56,6 +56,7 @@ public class Tab {
private FullscreenCallbackClientImpl mFullscreenCallbackClient;
private NewTabCallback mNewTabCallback;
private final ObserverList<ScrollOffsetCallback> mScrollOffsetCallbacks;
private @Nullable ActionModeCallback mActionModeCallback;
// Id from the remote side.
private final int mId;
......@@ -740,6 +741,29 @@ public class Tab {
}
}
/**
* Allow controlling and overriding custom items in the floating seleciton menu.
* Note floating action mode is available on M and up.
* @param actionModeItemTypes a bit field of values in ActionModeItemType.
* @param callback can be null if actionModeItemTypes is 0.
*
* @since 88
*/
public void setFloatingActionModeOverride(
int actionModeItemTypes, @Nullable ActionModeCallback callback) {
ThreadCheck.ensureOnUiThread();
throwIfDestroyed();
if (WebLayer.getSupportedMajorVersionInternal() < 88) {
throw new UnsupportedOperationException();
}
mActionModeCallback = callback;
try {
mImpl.setFloatingActionModeOverride(actionModeItemTypes);
} catch (RemoteException e) {
throw new APICallException(e);
}
}
// Called by Browser when removed.
void onRemovedFromBrowser() {
if (mDestroyOnRemove) {
......@@ -904,6 +928,16 @@ public class Tab {
callback.onVerticalScrollOffsetChanged(value);
}
}
@Override
public void onActionItemClicked(
int actionModeItemType, IObjectWrapper selectedStringWrapper) {
StrictModeWorkaround.apply();
String selectedString = ObjectWrapper.unwrap(selectedStringWrapper, String.class);
if (mActionModeCallback != null) {
mActionModeCallback.onActionItemClicked(actionModeItemType, selectedString);
}
}
}
private static final class ErrorPageCallbackClientImpl extends IErrorPageCallbackClient.Stub {
......
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