Commit e5d7d651 authored by aurimas@chromium.org's avatar aurimas@chromium.org

Upstreaming Select Action

Upstreaming the select action functionality for Android.

BUG=139111


Review URL: https://chromiumcodereview.appspot.com/10828098

git-svn-id: svn://svn.chromium.org/chrome/trunk/src@149329 0039d316-1c4b-4281-b951-d872f2087c98
parent 15c0ba42
// Copyright (c) 2012 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.
#include "content/browser/renderer_host/ime_adapter_android.h"
#include "base/android/jni_android.h"
#include "base/android/jni_string.h"
#include "content/browser/renderer_host/render_widget_host_impl.h"
#include "content/browser/renderer_host/render_widget_host_view_android.h"
#include "content/common/view_messages.h"
#include "jni/ImeAdapter_jni.h"
namespace content {
bool RegisterImeAdapter(JNIEnv* env) {
if (!RegisterNativesImpl(env))
return false;
return true;
}
ImeAdapterAndroid::ImeAdapterAndroid(RenderWidgetHostViewAndroid* rwhva)
: rwhva_(rwhva),
java_ime_adapter_(NULL) {
}
ImeAdapterAndroid::~ImeAdapterAndroid() {
if (java_ime_adapter_) {
JNIEnv* env = base::android::AttachCurrentThread();
Java_ImeAdapter_detach(env, java_ime_adapter_);
env->DeleteGlobalRef(java_ime_adapter_);
}
}
void ImeAdapterAndroid::Unselect(JNIEnv* env, jobject) {
RenderWidgetHostImpl* rwhi = RenderWidgetHostImpl::From(
rwhva_->GetRenderWidgetHost());
if (!rwhi)
return;
rwhi->Send(new ViewMsg_Unselect(rwhi->GetRoutingID()));
}
void ImeAdapterAndroid::SelectAll(JNIEnv* env, jobject) {
RenderWidgetHostImpl* rwhi = RenderWidgetHostImpl::From(
rwhva_->GetRenderWidgetHost());
if (!rwhi)
return;
rwhi->Send(new ViewMsg_SelectAll(rwhi->GetRoutingID()));
}
void ImeAdapterAndroid::Cut(JNIEnv* env, jobject) {
RenderWidgetHostImpl* rwhi = RenderWidgetHostImpl::From(
rwhva_->GetRenderWidgetHost());
if (!rwhi)
return;
rwhi->Send(new ViewMsg_Cut(rwhi->GetRoutingID()));
}
void ImeAdapterAndroid::Copy(JNIEnv* env, jobject) {
RenderWidgetHostImpl* rwhi = RenderWidgetHostImpl::From(
rwhva_->GetRenderWidgetHost());
if (!rwhi)
return;
rwhi->Send(new ViewMsg_Copy(rwhi->GetRoutingID()));
}
void ImeAdapterAndroid::Paste(JNIEnv* env, jobject) {
RenderWidgetHostImpl* rwhi = RenderWidgetHostImpl::From(
rwhva_->GetRenderWidgetHost());
if (!rwhi)
return;
rwhi->Send(new ViewMsg_Paste(rwhi->GetRoutingID()));
}
} // namespace content
\ No newline at end of file
// Copyright (c) 2012 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.
#ifndef CONTENT_BROWSER_RENDERER_HOST_IME_ADAPTER_ANDROID_H_
#define CONTENT_BROWSER_RENDERER_HOST_IME_ADAPTER_ANDROID_H_
#include <jni.h>
namespace content {
class RenderWidgetHostViewAndroid;
struct NativeWebKeyboardEvent;
// This class is in charge of dispatching key events from the java side
// and forward to renderer along with input method results via
// corresponding host view.
class ImeAdapterAndroid {
public:
explicit ImeAdapterAndroid(RenderWidgetHostViewAndroid* rwhva);
~ImeAdapterAndroid();
static bool RegisterImeAdapter(JNIEnv* env);
// Called from java -> native
// The java side is responsible to translate android KeyEvent various enums
// and values into the corresponding WebKit::WebInputEvent.
void Unselect(JNIEnv*, jobject);
void SelectAll(JNIEnv*, jobject);
void Cut(JNIEnv*, jobject);
void Copy(JNIEnv*, jobject);
void Paste(JNIEnv*, jobject);
private:
RenderWidgetHostViewAndroid* rwhva_;
jobject java_ime_adapter_;
};
} // namespace content
#endif // CONTENT_BROWSER_RENDERER_HOST_IME_ADAPTER_ANDROID_H_
......@@ -981,6 +981,9 @@ IPC_MESSAGE_ROUTED1(ViewMsg_Replace,
IPC_MESSAGE_ROUTED0(ViewMsg_Delete)
IPC_MESSAGE_ROUTED0(ViewMsg_SelectAll)
// Replaces all text in the current input field with the specified string.
IPC_MESSAGE_ROUTED0(ViewMsg_Unselect)
// Requests the renderer to select the region between two points.
IPC_MESSAGE_ROUTED2(ViewMsg_SelectRange,
gfx::Point /* start */,
......
......@@ -544,6 +544,8 @@
'browser/renderer_host/gtk_window_utils.h',
'browser/renderer_host/image_transport_factory.cc',
'browser/renderer_host/image_transport_factory.h',
'browser/renderer_host/ime_adapter_android.cc',
'browser/renderer_host/ime_adapter_android.h',
'browser/renderer_host/java/java_bound_object.cc',
'browser/renderer_host/java/java_bound_object.h',
'browser/renderer_host/java/java_bridge_channel_host.cc',
......
......@@ -20,6 +20,7 @@
'public/android/java/src/org/chromium/content/browser/ContentViewStatics.java',
'public/android/java/src/org/chromium/content/browser/DeviceOrientation.java',
'public/android/java/src/org/chromium/content/browser/DownloadController.java',
'public/android/java/src/org/chromium/content/browser/ImeAdapter.java',
'public/android/java/src/org/chromium/content/browser/LocationProvider.java',
'public/android/java/src/org/chromium/content/browser/RemoteDebuggingController.java',
'public/android/java/src/org/chromium/content/browser/SandboxedProcessLauncher.java',
......
......@@ -18,6 +18,12 @@ public class AppResource {
/** Dimension of the radius used in the link preview overlay. */
public static int DIMENSION_LINK_PREVIEW_OVERLAY_RADIUS;
/** Drawable icon resource for the Share button in the action bar. */
public static int DRAWABLE_ICON_ACTION_BAR_SHARE;
/** Drawable icon resource for the Web Search button in the action bar. */
public static int DRAWABLE_ICON_ACTION_BAR_WEB_SEARCH;
/** Drawable resource for the link preview popup overlay. */
public static int DRAWABLE_LINK_PREVIEW_POPUP_OVERLAY;
......@@ -45,6 +51,12 @@ public class AppResource {
/** Layout of the month picker dialog. */
public static int LAYOUT_MONTH_PICKER_DIALOG;
/** String for the Share button in the action bar. */
public static int STRING_ACTION_BAR_SHARE;
/** String for the Web Search button in the action bar. */
public static int STRING_ACTION_BAR_WEB_SEARCH;
/** String for the Clear button in the date picker dialog. */
public static int STRING_DATE_PICKER_DIALOG_CLEAR;
......
......@@ -10,12 +10,14 @@ import android.content.Intent;
import android.graphics.Rect;
import android.graphics.RectF;
import android.util.Log;
import android.view.ActionMode;
import android.view.KeyEvent;
import org.chromium.base.AccessedByNative;
import org.chromium.base.CalledByNative;
import org.chromium.base.JNINamespace;
import org.chromium.content.browser.ContentHttpAuthHandler;
import org.chromium.content.browser.SelectActionModeCallback.ActionHandler;
import java.net.URISyntaxException;
......@@ -264,6 +266,14 @@ public class ContentViewClient {
return false;
}
/**
* Returns an ActionMode.Callback for in-page selection.
*/
public ActionMode.Callback getSelectActionModeCallback(
Context context, ActionHandler actionHandler, boolean incognito) {
return new SelectActionModeCallback(context, actionHandler, incognito);
}
/**
* Called when the contextual ActionBar is shown.
*/
......
......@@ -9,6 +9,7 @@ import android.content.res.Configuration;
import android.graphics.Canvas;
import android.os.Bundle;
import android.util.Log;
import android.view.ActionMode;
import android.view.KeyEvent;
import android.view.MotionEvent;
import android.view.View;
......@@ -146,6 +147,16 @@ public class ContentViewCore implements MotionEventDelegate {
// will be mistakenly fired.
private boolean mIgnoreSingleTap;
// Only valid when focused on a text / password field.
private ImeAdapter mImeAdapter;
// Tracks whether a selection is currently active. When applied to selected text, indicates
// whether the last selected text is still highlighted.
private boolean mHasSelection;
private String mLastSelectedText;
private boolean mSelectionEditable;
private ActionMode mActionMode;
// The legacy webview DownloadListener.
private DownloadListener mDownloadListener;
// ContentViewDownloadDelegate adds support for authenticated downloads
......@@ -472,6 +483,10 @@ public class ContentViewCore implements MotionEventDelegate {
if (mNativeContentViewCore != 0) nativeClearHistory(mNativeContentViewCore);
}
String getSelectedText() {
return mHasSelection ? mLastSelectedText : "";
}
// End FrameLayout overrides.
......@@ -714,6 +729,63 @@ public class ContentViewCore implements MotionEventDelegate {
return mDownloadDelegate;
}
private void showSelectActionBar() {
if (mActionMode != null) {
mActionMode.invalidate();
return;
}
// Start a new action mode with a SelectActionModeCallback.
SelectActionModeCallback.ActionHandler actionHandler =
new SelectActionModeCallback.ActionHandler() {
@Override
public boolean selectAll() {
return mImeAdapter.selectAll();
}
@Override
public boolean cut() {
return mImeAdapter.cut();
}
@Override
public boolean copy() {
return mImeAdapter.copy();
}
@Override
public boolean paste() {
return mImeAdapter.paste();
}
@Override
public boolean isSelectionEditable() {
return mSelectionEditable;
}
@Override
public String getSelectedText() {
return ContentViewCore.this.getSelectedText();
}
@Override
public void onDestroyActionMode() {
mActionMode = null;
mImeAdapter.unselect();
getContentViewClient().onContextualActionBarHidden();
}
};
mActionMode = mContainerView.startActionMode(
getContentViewClient().getSelectActionModeCallback(getContext(), actionHandler,
nativeIsIncognito(mNativeContentViewCore)));
if (mActionMode == null) {
// There is no ActionMode, so remove the selection.
mImeAdapter.unselect();
} else {
getContentViewClient().onContextualActionBarShown();
}
}
/**
* @return Whether the native ContentView has crashed.
*/
......
// Copyright (c) 2012 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.content.browser;
import org.chromium.base.CalledByNative;
import org.chromium.base.JNINamespace;
import org.chromium.content.app.AppResource;
@JNINamespace("content")
class ImeAdapter {
private int mNativeImeAdapterAndroid;
private int mTextInputType;
@CalledByNative
void detach() {
mNativeImeAdapterAndroid = 0;
mTextInputType = 0;
}
boolean unselect() {
if (mNativeImeAdapterAndroid == 0) {
return false;
}
nativeUnselect(mNativeImeAdapterAndroid);
return true;
}
boolean selectAll() {
if (mNativeImeAdapterAndroid == 0) {
return false;
}
nativeSelectAll(mNativeImeAdapterAndroid);
return true;
}
boolean cut() {
if (mNativeImeAdapterAndroid == 0) {
return false;
}
nativeCut(mNativeImeAdapterAndroid);
return true;
}
boolean copy() {
if (mNativeImeAdapterAndroid == 0) {
return false;
}
nativeCopy(mNativeImeAdapterAndroid);
return true;
}
boolean paste() {
if (mNativeImeAdapterAndroid == 0) {
return false;
}
nativePaste(mNativeImeAdapterAndroid);
return true;
}
private native void nativeUnselect(int nativeImeAdapterAndroid);
private native void nativeSelectAll(int nativeImeAdapterAndroid);
private native void nativeCut(int nativeImeAdapterAndroid);
private native void nativeCopy(int nativeImeAdapterAndroid);
private native void nativePaste(int nativeImeAdapterAndroid);
}
\ No newline at end of file
// Copyright (c) 2012 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.content.browser;
import android.app.SearchManager;
import android.content.ClipboardManager;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.content.res.TypedArray;
import android.provider.Browser;
import android.text.TextUtils;
import android.view.ActionMode;
import android.view.Menu;
import android.view.MenuItem;
import org.chromium.content.app.AppResource;
/**
* An ActionMode.Callback for in-page selection. This class handles both the editable and
* non-editable cases.
*/
public class SelectActionModeCallback implements ActionMode.Callback {
private static final int SELECT_ALL_ATTR_INDEX = 0;
private static final int CUT_ATTR_INDEX = 1;
private static final int COPY_ATTR_INDEX = 2;
private static final int PASTE_ATTR_INDEX = 3;
private static final int[] ACTION_MODE_ATTRS = {
android.R.attr.actionModeSelectAllDrawable,
android.R.attr.actionModeCutDrawable,
android.R.attr.actionModeCopyDrawable,
android.R.attr.actionModePasteDrawable,
};
private static final int ID_SELECTALL = 0;
private static final int ID_COPY = 1;
private static final int ID_SHARE = 2;
private static final int ID_SEARCH = 3;
private static final int ID_CUT = 4;
private static final int ID_PASTE = 5;
/**
* An interface to retrieve information about the current selection, and also to perform
* actions based on the selection or when the action bar is dismissed.
*/
public interface ActionHandler {
/**
* Perform a select all action.
* @return true iff the action was successful.
*/
boolean selectAll();
/**
* Perform a copy (to clipboard) action.
* @return true iff the action was successful.
*/
boolean copy();
/**
* Perform a cut (to clipboard) action.
* @return true iff the action was successful.
*/
boolean cut();
/**
* Perform a paste action.
* @return true iff the action was successful.
*/
boolean paste();
/**
* @return true iff the current selection is editable (e.g. text within an input field).
*/
boolean isSelectionEditable();
/**
* @return the currently selected text String.
*/
String getSelectedText();
/**
* Called when the onDestroyActionMode of the SelectActionmodeCallback is called.
*/
void onDestroyActionMode();
}
private Context mContext;
private ActionHandler mActionHandler;
private final boolean mIncognito;
private boolean mEditable;
protected SelectActionModeCallback(
Context context, ActionHandler actionHandler, boolean incognito) {
mContext = context;
mActionHandler = actionHandler;
mIncognito = incognito;
}
protected Context getContext() {
return mContext;
}
@Override
public boolean onCreateActionMode(ActionMode mode, Menu menu) {
mode.setSubtitle(null);
mEditable = mActionHandler.isSelectionEditable();
createActionMenu(mode, menu);
return true;
}
@Override
public boolean onPrepareActionMode(ActionMode mode, Menu menu) {
boolean isEditableNow = mActionHandler.isSelectionEditable();
if (mEditable != isEditableNow) {
mEditable = isEditableNow;
menu.clear();
createActionMenu(mode, menu);
return true;
}
return false;
}
private void createActionMenu(ActionMode mode, Menu menu) {
TypedArray styledAttributes = getContext().obtainStyledAttributes(ACTION_MODE_ATTRS);
menu.add(Menu.NONE, ID_SELECTALL, Menu.NONE, android.R.string.selectAll).
setAlphabeticShortcut('a').
setIcon(styledAttributes.getResourceId(SELECT_ALL_ATTR_INDEX, 0)).
setShowAsAction(
MenuItem.SHOW_AS_ACTION_ALWAYS | MenuItem.SHOW_AS_ACTION_WITH_TEXT);
if (mEditable) {
menu.add(Menu.NONE, ID_CUT, Menu.NONE, android.R.string.cut).
setIcon(styledAttributes.getResourceId(CUT_ATTR_INDEX, 0)).
setAlphabeticShortcut('x').
setShowAsAction(
MenuItem.SHOW_AS_ACTION_ALWAYS | MenuItem.SHOW_AS_ACTION_WITH_TEXT);
}
menu.add(Menu.NONE, ID_COPY, Menu.NONE, android.R.string.copy).
setIcon(styledAttributes.getResourceId(COPY_ATTR_INDEX, 0)).
setAlphabeticShortcut('c').
setShowAsAction(
MenuItem.SHOW_AS_ACTION_ALWAYS | MenuItem.SHOW_AS_ACTION_WITH_TEXT);
if (mEditable && canPaste()) {
menu.add(Menu.NONE, ID_PASTE, Menu.NONE, android.R.string.paste).
setIcon(styledAttributes.getResourceId(PASTE_ATTR_INDEX, 0)).
setAlphabeticShortcut('v').
setShowAsAction(
MenuItem.SHOW_AS_ACTION_ALWAYS | MenuItem.SHOW_AS_ACTION_WITH_TEXT);
}
if (!mEditable) {
if (isShareHandlerAvailable()) {
assert AppResource.STRING_ACTION_BAR_SHARE != 0;
assert AppResource.DRAWABLE_ICON_ACTION_BAR_SHARE != 0;
menu.add(Menu.NONE, ID_SHARE, Menu.NONE, AppResource.STRING_ACTION_BAR_SHARE).
setIcon(AppResource.DRAWABLE_ICON_ACTION_BAR_SHARE).
setShowAsAction(
MenuItem.SHOW_AS_ACTION_ALWAYS | MenuItem.SHOW_AS_ACTION_WITH_TEXT);
}
if (!mIncognito && isWebSearchAvailable()) {
assert AppResource.STRING_ACTION_BAR_WEB_SEARCH != 0;
assert AppResource.DRAWABLE_ICON_ACTION_BAR_WEB_SEARCH != 0;
menu.add(Menu.NONE, ID_SEARCH, Menu.NONE,
AppResource.STRING_ACTION_BAR_WEB_SEARCH).
setIcon(AppResource.DRAWABLE_ICON_ACTION_BAR_WEB_SEARCH).
setShowAsAction(
MenuItem.SHOW_AS_ACTION_ALWAYS | MenuItem.SHOW_AS_ACTION_WITH_TEXT);
}
}
styledAttributes.recycle();
}
@Override
public boolean onActionItemClicked(ActionMode mode, MenuItem item) {
String selection = mActionHandler.getSelectedText();
switch(item.getItemId()) {
case ID_SELECTALL:
mActionHandler.selectAll();
break;
case ID_CUT:
mActionHandler.cut();
break;
case ID_COPY:
mActionHandler.copy();
mode.finish();
break;
case ID_PASTE:
mActionHandler.paste();
break;
case ID_SHARE:
if (!TextUtils.isEmpty(selection)) {
Intent send = new Intent(Intent.ACTION_SEND);
send.setType("text/plain");
send.putExtra(Intent.EXTRA_TEXT, selection);
try {
assert AppResource.STRING_ACTION_BAR_SHARE != 0;
Intent i = Intent.createChooser(send, getContext().getString(
AppResource.STRING_ACTION_BAR_SHARE));
i.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
getContext().startActivity(i);
} catch (android.content.ActivityNotFoundException ex) {
// If no app handles it, do nothing.
}
}
mode.finish();
break;
case ID_SEARCH:
if (!TextUtils.isEmpty(selection)) {
Intent i = new Intent(Intent.ACTION_WEB_SEARCH);
i.putExtra(SearchManager.EXTRA_NEW_SEARCH, true);
i.putExtra(SearchManager.QUERY, selection);
i.putExtra(Browser.EXTRA_APPLICATION_ID, getContext().getPackageName());
try {
getContext().startActivity(i);
} catch (android.content.ActivityNotFoundException ex) {
// If no app handles it, do nothing.
}
}
mode.finish();
break;
default:
return false;
}
return true;
}
@Override
public void onDestroyActionMode(ActionMode mode) {
mActionHandler.onDestroyActionMode();
}
private boolean canPaste() {
ClipboardManager clipMgr = (ClipboardManager)
getContext().getSystemService(Context.CLIPBOARD_SERVICE);
return clipMgr.hasPrimaryClip();
}
private boolean isShareHandlerAvailable() {
Intent intent = new Intent(Intent.ACTION_SEND);
intent.setType("text/plain");
return getContext().getPackageManager()
.queryIntentActivities(intent, PackageManager.MATCH_DEFAULT_ONLY).size() > 0;
}
private boolean isWebSearchAvailable() {
Intent intent = new Intent(Intent.ACTION_WEB_SEARCH);
intent.putExtra(SearchManager.EXTRA_NEW_SEARCH, true);
return getContext().getPackageManager()
.queryIntentActivities(intent, PackageManager.MATCH_DEFAULT_ONLY).size() > 0;
}
}
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