| Index: content/public/android/java/src/org/chromium/content/browser/ContentViewCore.java
|
| diff --git a/content/public/android/java/src/org/chromium/content/browser/ContentViewCore.java b/content/public/android/java/src/org/chromium/content/browser/ContentViewCore.java
|
| index 148eab716fae59ac99cced0a39672d24aff790c6..b2b17029bde41c858f1ab55093b4f16941e35647 100644
|
| --- a/content/public/android/java/src/org/chromium/content/browser/ContentViewCore.java
|
| +++ b/content/public/android/java/src/org/chromium/content/browser/ContentViewCore.java
|
| @@ -6,16 +6,12 @@ package org.chromium.content.browser;
|
|
|
| import android.annotation.SuppressLint;
|
| import android.annotation.TargetApi;
|
| -import android.app.Activity;
|
| -import android.app.SearchManager;
|
| import android.app.assist.AssistStructure.ViewNode;
|
| import android.content.ClipData;
|
| import android.content.ClipDescription;
|
| import android.content.ClipboardManager;
|
| -import android.content.ContentResolver;
|
| import android.content.Context;
|
| import android.content.Intent;
|
| -import android.content.pm.PackageManager;
|
| import android.content.res.Configuration;
|
| import android.graphics.Bitmap;
|
| import android.graphics.Rect;
|
| @@ -24,8 +20,6 @@ import android.os.Bundle;
|
| import android.os.Handler;
|
| import android.os.ResultReceiver;
|
| import android.os.SystemClock;
|
| -import android.provider.Browser;
|
| -import android.text.TextUtils;
|
| import android.util.Pair;
|
| import android.view.ActionMode;
|
| import android.view.DragEvent;
|
| @@ -37,7 +31,6 @@ import android.view.Surface;
|
| import android.view.View;
|
| import android.view.ViewGroup;
|
| import android.view.ViewStructure;
|
| -import android.view.WindowManager;
|
| import android.view.accessibility.AccessibilityEvent;
|
| import android.view.accessibility.AccessibilityManager;
|
| import android.view.accessibility.AccessibilityManager.AccessibilityStateChangeListener;
|
| @@ -48,29 +41,21 @@ import android.view.inputmethod.InputConnection;
|
| import android.view.inputmethod.InputMethodManager;
|
|
|
| import org.chromium.base.CommandLine;
|
| -import org.chromium.base.Log;
|
| import org.chromium.base.ObserverList;
|
| import org.chromium.base.ObserverList.RewindableIterator;
|
| import org.chromium.base.TraceEvent;
|
| import org.chromium.base.VisibleForTesting;
|
| import org.chromium.base.annotations.CalledByNative;
|
| import org.chromium.base.annotations.JNINamespace;
|
| -import org.chromium.base.metrics.RecordUserAction;
|
| -import org.chromium.content.R;
|
| import org.chromium.content.browser.accessibility.BrowserAccessibilityManager;
|
| import org.chromium.content.browser.accessibility.captioning.CaptioningBridgeFactory;
|
| import org.chromium.content.browser.accessibility.captioning.SystemCaptioningBridge;
|
| import org.chromium.content.browser.accessibility.captioning.TextTrackSettings;
|
| import org.chromium.content.browser.input.AnimationIntervalProvider;
|
| -import org.chromium.content.browser.input.FloatingPastePopupMenu;
|
| import org.chromium.content.browser.input.ImeAdapter;
|
| import org.chromium.content.browser.input.InputMethodManagerWrapper;
|
| import org.chromium.content.browser.input.JoystickScrollProvider;
|
| import org.chromium.content.browser.input.JoystickZoomProvider;
|
| -import org.chromium.content.browser.input.LGEmailActionModeWorkaround;
|
| -import org.chromium.content.browser.input.LegacyPastePopupMenu;
|
| -import org.chromium.content.browser.input.PastePopupMenu;
|
| -import org.chromium.content.browser.input.PastePopupMenu.PastePopupMenuDelegate;
|
| import org.chromium.content.browser.input.SelectPopup;
|
| import org.chromium.content.browser.input.SelectPopupDialog;
|
| import org.chromium.content.browser.input.SelectPopupDropdown;
|
| @@ -87,7 +72,6 @@ import org.chromium.ui.base.ViewAndroidDelegate;
|
| import org.chromium.ui.base.WindowAndroid;
|
| import org.chromium.ui.base.ime.TextInputType;
|
| import org.chromium.ui.display.DisplayAndroid.DisplayAndroidObserver;
|
| -import org.chromium.ui.touch_selection.SelectionEventType;
|
|
|
| import java.lang.annotation.Annotation;
|
| import java.lang.ref.WeakReference;
|
| @@ -345,11 +329,6 @@ public class ContentViewCore implements AccessibilityStateChangeListener, Displa
|
| // Only valid when focused on a text / password field.
|
| private ImeAdapter mImeAdapter;
|
|
|
| - // Lazily created paste popup menu, triggered either via long press in an
|
| - // editable region or from tapping the insertion handle.
|
| - private PastePopupMenu mPastePopupMenu;
|
| - private boolean mWasPastePopupShowingOnInsertionDragStart;
|
| -
|
| // Size of the viewport in physical pixels as set from onSizeChanged.
|
| private int mViewportWidthPix;
|
| private int mViewportHeightPix;
|
| @@ -370,22 +349,8 @@ public class ContentViewCore implements AccessibilityStateChangeListener, Displa
|
|
|
| private boolean mIsMobileOptimizedHint;
|
|
|
| - // 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 boolean mHasInsertion;
|
| - private boolean mDraggingSelection;
|
| - private String mLastSelectedText;
|
| - private boolean mFocusedNodeEditable;
|
| - private boolean mFocusedNodeIsPassword;
|
| private WebActionMode mActionMode;
|
| - private boolean mFloatingActionModeCreationFailed;
|
| - private boolean mUnselectAllOnActionModeDismiss;
|
| private boolean mPreserveSelectionOnNextLossOfFocus;
|
| - private WebActionModeCallback.ActionHandler mActionHandler;
|
| -
|
| - // Selection rectangle in DIP.
|
| - private final Rect mSelectionRect = new Rect();
|
|
|
| // Whether native accessibility, i.e. without any script injection, is allowed.
|
| private boolean mNativeAccessibilityAllowed;
|
| @@ -460,9 +425,6 @@ public class ContentViewCore implements AccessibilityStateChangeListener, Displa
|
| // A flag to determine if we enable hover feature or not.
|
| private Boolean mEnableTouchHover;
|
|
|
| - // The client that implements Contextual Search functionality, or null if none exists.
|
| - private ContextualSearchClient mContextualSearchClient;
|
| -
|
| // NOTE: This object will not be released by Android framework until the matching
|
| // ResultReceiver in the InputMethodService (IME app) gets gc'ed.
|
| private ShowKeyboardResultReceiver mShowKeyboardResultReceiver;
|
| @@ -530,6 +492,19 @@ public class ContentViewCore implements AccessibilityStateChangeListener, Displa
|
| return nativeGetJavaWindowAndroid(mNativeContentViewCore);
|
| }
|
|
|
| + /**
|
| + * @return The WebActionMode that handles select action mode on web contents.
|
| + */
|
| + @VisibleForTesting
|
| + public WebActionMode getActionModeForTesting() {
|
| + return mActionMode;
|
| + }
|
| +
|
| + @VisibleForTesting
|
| + public void setActionModeForTesting(WebActionMode actionMode) {
|
| + mActionMode = actionMode;
|
| + }
|
| +
|
| @Override
|
| public void addWindowAndroidChangedObserver(WindowAndroidChangedObserver observer) {
|
| mWindowAndroidChangedObservers.addObserver(observer);
|
| @@ -584,7 +559,7 @@ public class ContentViewCore implements AccessibilityStateChangeListener, Displa
|
| public void onImeEvent() {
|
| mPopupZoomer.hide(true);
|
| getContentViewClient().onImeEvent();
|
| - if (mFocusedNodeEditable) dismissTextHandles();
|
| + if (isFocusedNodeEditable()) mWebContents.dismissTextHandles();
|
| }
|
|
|
| @Override
|
| @@ -633,6 +608,7 @@ public class ContentViewCore implements AccessibilityStateChangeListener, Displa
|
| * containerView.
|
| * @param webContents A WebContents instance to connect to.
|
| * @param windowAndroid An instance of the WindowAndroid.
|
| + * @param actionModeCallback ActionMode.Callback instance.
|
| */
|
| // Perform important post-construction set up of the ContentViewCore.
|
| // We do not require the containing view in the constructor to allow embedders to create a
|
| @@ -645,8 +621,10 @@ public class ContentViewCore implements AccessibilityStateChangeListener, Displa
|
| // deleting it after destroying the ContentViewCore.
|
| public void initialize(ViewAndroidDelegate viewDelegate,
|
| InternalAccessDelegate internalDispatcher, WebContents webContents,
|
| - WindowAndroid windowAndroid) {
|
| + WindowAndroid windowAndroid, ActionMode.Callback actionModeCallback) {
|
| mViewAndroidDelegate = viewDelegate;
|
| + mActionMode = new WebActionMode(mContext, windowAndroid, webContents,
|
| + viewDelegate.getContainerView(), mRenderCoordinates, actionModeCallback);
|
| setContainerView(viewDelegate.getContainerView());
|
| long windowNativePointer = windowAndroid.getNativePointer();
|
| assert windowNativePointer != 0;
|
| @@ -681,7 +659,7 @@ public class ContentViewCore implements AccessibilityStateChangeListener, Displa
|
| // TODO(yusufo): Rename this call to be general for tab reparenting.
|
| // Clean up cached popups that may have been created with an old activity.
|
| mSelectPopup = null;
|
| - mPastePopupMenu = null;
|
| + destroyPastePopup();
|
|
|
| addDisplayAndroidObserverIfNeeded();
|
|
|
| @@ -730,12 +708,13 @@ public class ContentViewCore implements AccessibilityStateChangeListener, Displa
|
| TraceEvent.begin("ContentViewCore.setContainerView");
|
| if (mContainerView != null) {
|
| assert mOverscrollRefreshHandler == null;
|
| - mPastePopupMenu = null;
|
| - hidePopupsAndClearSelection();
|
| + hideSelectPopupWithCancelMessage();
|
| + mPopupZoomer.hide(false);
|
| }
|
|
|
| mContainerView = containerView;
|
| mContainerView.setClickable(true);
|
| + mActionMode.setContainerView(containerView);
|
| } finally {
|
| TraceEvent.end("ContentViewCore.setContainerView");
|
| }
|
| @@ -857,7 +836,7 @@ public class ContentViewCore implements AccessibilityStateChangeListener, Displa
|
| }
|
| mGestureStateListeners.clear();
|
| hidePopupsAndPreserveSelection();
|
| - mPastePopupMenu = null;
|
| + destroyPastePopup();
|
|
|
| // See warning in javadoc before adding more clean up code here.
|
| }
|
| @@ -989,21 +968,14 @@ public class ContentViewCore implements AccessibilityStateChangeListener, Displa
|
| */
|
| @VisibleForTesting
|
| public String getSelectedText() {
|
| - return mHasSelection ? mLastSelectedText : "";
|
| - }
|
| -
|
| - /**
|
| - * @return Whether the current selection is editable (false if no text selected).
|
| - */
|
| - public boolean isSelectionEditable() {
|
| - return mHasSelection ? mFocusedNodeEditable : false;
|
| + return mActionMode.getSelectedText();
|
| }
|
|
|
| /**
|
| * @return Whether the current focused node is editable.
|
| */
|
| public boolean isFocusedNodeEditable() {
|
| - return mFocusedNodeEditable;
|
| + return mActionMode.isSelectionEditable();
|
| }
|
|
|
| /**
|
| @@ -1013,8 +985,6 @@ public class ContentViewCore implements AccessibilityStateChangeListener, Displa
|
| return GamepadList.isGamepadAPIActive();
|
| }
|
|
|
| - // End FrameLayout overrides.
|
| -
|
| /**
|
| * @see View#onTouchEvent(MotionEvent)
|
| */
|
| @@ -1123,7 +1093,7 @@ public class ContentViewCore implements AccessibilityStateChangeListener, Displa
|
| private void setTouchScrollInProgress(boolean inProgress) {
|
| if (mTouchScrollInProgress == inProgress) return;
|
| mTouchScrollInProgress = inProgress;
|
| - updateActionModeVisibility();
|
| + mActionMode.updateActionModeVisibility(inProgress);
|
| }
|
|
|
| @SuppressWarnings("unused")
|
| @@ -1195,9 +1165,7 @@ public class ContentViewCore implements AccessibilityStateChangeListener, Displa
|
| @SuppressWarnings("unused")
|
| @CalledByNative
|
| private void onShowUnhandledTapUIIfNeeded(int x, int y) {
|
| - if (mContextualSearchClient != null) {
|
| - mContextualSearchClient.showUnhandledTapUIIfNeeded(x, y);
|
| - }
|
| + mActionMode.onShowUnhandledTapUIIfNeeded(x, y);
|
| }
|
|
|
| /**
|
| @@ -1317,37 +1285,33 @@ public class ContentViewCore implements AccessibilityStateChangeListener, Displa
|
| }
|
|
|
| private void hidePopupsAndClearSelection() {
|
| - mUnselectAllOnActionModeDismiss = true;
|
| - hidePopups();
|
| + mActionMode.destroyActionModeAndUnselect();
|
| + hidePastePopup();
|
| + hideSelectPopupWithCancelMessage();
|
| + mPopupZoomer.hide(false);
|
| }
|
|
|
| @CalledByNative
|
| private void hidePopupsAndPreserveSelection() {
|
| - mUnselectAllOnActionModeDismiss = false;
|
| - hidePopups();
|
| - }
|
| -
|
| - private void hidePopups() {
|
| - hideSelectActionMode();
|
| + mActionMode.destroyActionModeAndKeepSelection();
|
| hidePastePopup();
|
| - hideSelectPopupWithCancelMesage();
|
| + hideSelectPopupWithCancelMessage();
|
| mPopupZoomer.hide(false);
|
| - if (mUnselectAllOnActionModeDismiss) dismissTextHandles();
|
| }
|
|
|
| private void restoreSelectionPopupsIfNecessary() {
|
| - if (mHasSelection && mActionMode == null) showSelectActionMode(true);
|
| + mActionMode.restoreSelectionPopupsIfNecessary();
|
| }
|
|
|
| - public void hideSelectActionMode() {
|
| - if (mActionMode != null) {
|
| - mActionMode.finish();
|
| - mActionMode = null;
|
| - }
|
| + /**
|
| + * Hide action mode and put into destroyed state.
|
| + */
|
| + public void destroySelectActionMode() {
|
| + mActionMode.finishActionMode();
|
| }
|
|
|
| public boolean isSelectActionBarShowing() {
|
| - return mActionMode != null;
|
| + return mActionMode.isActionModeValid();
|
| }
|
|
|
| private void resetGestureDetection() {
|
| @@ -1511,7 +1475,7 @@ public class ContentViewCore implements AccessibilityStateChangeListener, Displa
|
| public void onWindowFocusChanged(boolean hasWindowFocus) {
|
| mImeAdapter.onWindowFocusChanged(hasWindowFocus);
|
| if (!hasWindowFocus) resetGestureDetection();
|
| - if (mActionMode != null) mActionMode.onWindowFocusChanged(hasWindowFocus);
|
| + mActionMode.onWindowFocusChanged(hasWindowFocus);
|
| for (mGestureStateListenersIterator.rewind(); mGestureStateListenersIterator.hasNext();) {
|
| mGestureStateListenersIterator.next().onWindowFocusChanged(hasWindowFocus);
|
| }
|
| @@ -1519,7 +1483,7 @@ public class ContentViewCore implements AccessibilityStateChangeListener, Displa
|
|
|
| public void onFocusChanged(boolean gainFocus) {
|
| mImeAdapter.onViewFocusChanged(gainFocus);
|
| - mJoystickScrollProvider.setEnabled(gainFocus && !mFocusedNodeEditable);
|
| + mJoystickScrollProvider.setEnabled(gainFocus && !isFocusedNodeEditable());
|
|
|
| if (gainFocus) {
|
| restoreSelectionPopupsIfNecessary();
|
| @@ -1836,279 +1800,24 @@ public class ContentViewCore implements AccessibilityStateChangeListener, Displa
|
| nativeSendOrientationChangeEvent(mNativeContentViewCore, orientation);
|
| }
|
|
|
| - @VisibleForTesting
|
| - public WebActionModeCallback.ActionHandler getSelectActionHandler() {
|
| - return mActionHandler;
|
| + public ActionModeCallbackHelper getActionModeCallbackHelper() {
|
| + return mActionMode;
|
| }
|
|
|
| - private void showSelectActionMode(boolean allowFallbackIfFloatingActionModeCreationFails) {
|
| - if (mActionMode != null) {
|
| - mActionMode.invalidate();
|
| - return;
|
| - }
|
| -
|
| - // Start a new action mode with a WebActionModeCallback.
|
| - if (mActionHandler == null) {
|
| - mActionHandler = new WebActionModeCallback.ActionHandler() {
|
| - /**
|
| - * Android Intent size limitations prevent sending over a megabyte of data. Limit
|
| - * query lengths to 100kB because other things may be added to the Intent.
|
| - */
|
| - private static final int MAX_SHARE_QUERY_LENGTH = 100000;
|
| -
|
| - /** Google search doesn't support requests slightly larger than this. */
|
| - private static final int MAX_SEARCH_QUERY_LENGTH = 1000;
|
| -
|
| - // All WebContents actions record a user action internally.
|
| - @Override
|
| - public void selectAll() {
|
| - mWebContents.selectAll();
|
| - // Even though the above statement logged a SelectAll user action, we want to
|
| - // track whether the focus was in an editable field, so log that too.
|
| - if (isFocusedNodeEditable()) {
|
| - RecordUserAction.record("MobileActionMode.SelectAllWasEditable");
|
| - } else {
|
| - RecordUserAction.record("MobileActionMode.SelectAllWasNonEditable");
|
| - }
|
| - }
|
| -
|
| - @Override
|
| - public void cut() {
|
| - mWebContents.cut();
|
| - }
|
| -
|
| - @Override
|
| - public void copy() {
|
| - mWebContents.copy();
|
| - }
|
| -
|
| - @Override
|
| - public void paste() {
|
| - mWebContents.paste();
|
| - }
|
| -
|
| - @Override
|
| - public void share() {
|
| - RecordUserAction.record("MobileActionMode.Share");
|
| - final String query = sanitizeQuery(getSelectedText(), MAX_SHARE_QUERY_LENGTH);
|
| - if (TextUtils.isEmpty(query)) return;
|
| -
|
| - Intent send = new Intent(Intent.ACTION_SEND);
|
| - send.setType("text/plain");
|
| - send.putExtra(Intent.EXTRA_TEXT, query);
|
| - try {
|
| - Intent i = Intent.createChooser(send, getContext().getString(
|
| - R.string.actionbar_share));
|
| - i.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
|
| - getContext().startActivity(i);
|
| - } catch (android.content.ActivityNotFoundException ex) {
|
| - // If no app handles it, do nothing.
|
| - }
|
| - }
|
| -
|
| - @Override
|
| - public void processText(Intent intent) {
|
| - RecordUserAction.record("MobileActionMode.ProcessTextIntent");
|
| - assert Build.VERSION.SDK_INT >= Build.VERSION_CODES.M;
|
| -
|
| - final String query = sanitizeQuery(getSelectedText(), MAX_SEARCH_QUERY_LENGTH);
|
| - if (TextUtils.isEmpty(query)) return;
|
| -
|
| - intent.putExtra(Intent.EXTRA_PROCESS_TEXT, query);
|
| - try {
|
| - if (getContentViewClient().doesPerformProcessText()) {
|
| - getContentViewClient().startProcessTextIntent(intent);
|
| - } else {
|
| - getWindowAndroid().showIntent(
|
| - intent, new WindowAndroid.IntentCallback() {
|
| - @Override
|
| - public void onIntentCompleted(WindowAndroid window,
|
| - int resultCode, ContentResolver contentResolver,
|
| - Intent data) {
|
| - onReceivedProcessTextResult(resultCode, data);
|
| - }
|
| - }, null);
|
| - }
|
| - } catch (android.content.ActivityNotFoundException ex) {
|
| - // If no app handles it, do nothing.
|
| - }
|
| - }
|
| -
|
| - @Override
|
| - public void search() {
|
| - RecordUserAction.record("MobileActionMode.WebSearch");
|
| - final String query = sanitizeQuery(getSelectedText(), MAX_SEARCH_QUERY_LENGTH);
|
| - if (TextUtils.isEmpty(query)) return;
|
| -
|
| - // See if ContentViewClient wants to override.
|
| - if (getContentViewClient().doesPerformWebSearch()) {
|
| - getContentViewClient().performWebSearch(query);
|
| - return;
|
| - }
|
| -
|
| - Intent i = new Intent(Intent.ACTION_WEB_SEARCH);
|
| - i.putExtra(SearchManager.EXTRA_NEW_SEARCH, true);
|
| - i.putExtra(SearchManager.QUERY, query);
|
| - i.putExtra(Browser.EXTRA_APPLICATION_ID, getContext().getPackageName());
|
| - i.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
|
| - try {
|
| - getContext().startActivity(i);
|
| - } catch (android.content.ActivityNotFoundException ex) {
|
| - // If no app handles it, do nothing.
|
| - }
|
| - }
|
| -
|
| - @Override
|
| - public boolean isSelectionPassword() {
|
| - return mFocusedNodeIsPassword;
|
| - }
|
| -
|
| - @Override
|
| - public boolean isSelectionEditable() {
|
| - return mFocusedNodeEditable;
|
| - }
|
| -
|
| - @Override
|
| - public boolean isInsertion() {
|
| - return mHasInsertion;
|
| - }
|
| -
|
| - @Override
|
| - public void onDestroyActionMode() {
|
| - mActionMode = null;
|
| - if (mUnselectAllOnActionModeDismiss) {
|
| - dismissTextHandles();
|
| - clearSelection();
|
| - }
|
| - if (!supportsFloatingActionMode()) {
|
| - getContentViewClient().onContextualActionBarHidden();
|
| - }
|
| - }
|
| -
|
| - @Override
|
| - public void onGetContentRect(Rect outRectPix) {
|
| - final float deviceScale = mRenderCoordinates.getDeviceScaleFactor();
|
| - outRectPix.set((int) (mSelectionRect.left * deviceScale),
|
| - (int) (mSelectionRect.top * deviceScale),
|
| - (int) (mSelectionRect.right * deviceScale),
|
| - (int) (mSelectionRect.bottom * deviceScale));
|
| -
|
| - // The selection coordinates are relative to the content viewport, but we need
|
| - // coordinates relative to the containing View.
|
| - outRectPix.offset(0, (int) mRenderCoordinates.getContentOffsetYPix());
|
| - }
|
| -
|
| - @Override
|
| - public boolean isIncognito() {
|
| - return mWebContents.isIncognito();
|
| - }
|
| -
|
| - @Override
|
| - public boolean isSelectActionModeAllowed(int actionModeItem) {
|
| - boolean isAllowedByClient =
|
| - getContentViewClient().isSelectActionModeAllowed(actionModeItem);
|
| - if (actionModeItem == WebActionModeCallback.MENU_ITEM_SHARE) {
|
| - return isAllowedByClient && isShareAvailable();
|
| - }
|
| -
|
| - if (actionModeItem == WebActionModeCallback.MENU_ITEM_WEB_SEARCH) {
|
| - return isAllowedByClient && isWebSearchAvailable();
|
| - }
|
| -
|
| - return isAllowedByClient;
|
| - }
|
| -
|
| - private boolean isShareAvailable() {
|
| - 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() {
|
| - if (getContentViewClient().doesPerformWebSearch()) return true;
|
| - 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;
|
| - }
|
| -
|
| - private String sanitizeQuery(String query, int maxLength) {
|
| - if (TextUtils.isEmpty(query) || query.length() < maxLength) return query;
|
| - Log.w(TAG, "Truncating oversized query (" + query.length() + ").");
|
| - return query.substring(0, maxLength) + "…";
|
| - }
|
| - };
|
| - }
|
| - mActionMode = null;
|
| - // On ICS, startActionMode throws an NPE when getParent() is null.
|
| - if (mContainerView.getParent() != null) {
|
| - assert mWebContents != null;
|
| - ActionMode actionMode = startActionMode(allowFallbackIfFloatingActionModeCreationFails);
|
| - if (actionMode != null) {
|
| - // This is to work around an LGE email issue. See crbug.com/651706 for more details.
|
| - LGEmailActionModeWorkaround.runIfNecessary(mContext, actionMode);
|
| - mActionMode = new WebActionMode(actionMode, mContainerView);
|
| - }
|
| - }
|
| - mUnselectAllOnActionModeDismiss = true;
|
| - if (mActionMode == null) {
|
| + private void showSelectActionMode(boolean allowFallback) {
|
| + if (!mActionMode.showActionMode(allowFallback)) return;
|
| + if (!mActionMode.isActionModeValid()) {
|
| // There is no ActionMode, so remove the selection.
|
| clearSelection();
|
| - } else {
|
| - // TODO(jdduke): Refactor ContentViewClient.onContextualActionBarShown to be aware
|
| - // of non-action-bar (i.e., floating) ActionMode instances, crbug.com/524666.
|
| - if (!supportsFloatingActionMode()) {
|
| - getContentViewClient().onContextualActionBarShown();
|
| - }
|
| }
|
| }
|
|
|
| - private boolean supportsFloatingActionMode() {
|
| - if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) return false;
|
| - return !mFloatingActionModeCreationFailed;
|
| - }
|
| -
|
| - private ActionMode startActionMode(boolean allowFallbackIfFloatingActionModeCreationFails) {
|
| - WebActionModeCallback callback =
|
| - new WebActionModeCallback(mContainerView.getContext(), mActionHandler);
|
| - if (supportsFloatingActionMode()) {
|
| - ActionMode actionMode = startFloatingActionMode(callback);
|
| - if (actionMode != null) return actionMode;
|
| - mFloatingActionModeCreationFailed = true;
|
| - if (!allowFallbackIfFloatingActionModeCreationFails) return null;
|
| - }
|
| - return startDefaultActionMode(callback);
|
| - }
|
| -
|
| - private ActionMode startDefaultActionMode(WebActionModeCallback callback) {
|
| - return mContainerView.startActionMode(callback);
|
| - }
|
| -
|
| - @TargetApi(Build.VERSION_CODES.M)
|
| - private ActionMode startFloatingActionMode(WebActionModeCallback callback) {
|
| - ActionMode.Callback2 callback2 = new FloatingWebActionModeCallback(callback);
|
| - return mContainerView.startActionMode(callback2, ActionMode.TYPE_FLOATING);
|
| - }
|
| -
|
| - private void invalidateActionModeContentRect() {
|
| - if (mActionMode != null) mActionMode.invalidateContentRect();
|
| - }
|
| -
|
| - private void updateActionModeVisibility() {
|
| - if (mActionMode == null) return;
|
| - // The active fling count isn't reliable with WebView, so only use the
|
| - // active touch scroll signal for hiding. The fling animation movement
|
| - // will naturally hide the ActionMode by invalidating its content rect.
|
| - mActionMode.hide(mDraggingSelection || mTouchScrollInProgress);
|
| - }
|
| -
|
| /**
|
| * Clears the current text selection. Note that we will try to move cursor to selection
|
| * end if applicable.
|
| */
|
| public void clearSelection() {
|
| - if (mFocusedNodeEditable) {
|
| + if (isFocusedNodeEditable()) {
|
| mImeAdapter.moveCursorToSelectionEnd();
|
| } else {
|
| // This method can be called during shutdown, guard against null accordingly.
|
| @@ -2123,118 +1832,11 @@ public class ContentViewCore implements AccessibilityStateChangeListener, Displa
|
| mPreserveSelectionOnNextLossOfFocus = true;
|
| }
|
|
|
| - /**
|
| - * @return Whether the page has an active, touch-controlled selection region.
|
| - */
|
| - @VisibleForTesting
|
| - public boolean hasSelection() {
|
| - return mHasSelection;
|
| - }
|
| -
|
| - /**
|
| - * @return Whether the page has an active, touch-controlled insertion handle.
|
| - */
|
| - @VisibleForTesting
|
| - public boolean hasInsertion() {
|
| - return mHasInsertion;
|
| - }
|
| -
|
| - // All coordinates are in DIP.
|
| @CalledByNative
|
| private void onSelectionEvent(
|
| int eventType, int xAnchor, int yAnchor, int left, int top, int right, int bottom) {
|
| - // Ensure the provided selection coordinates form a non-empty rect, as required by
|
| - // the selection action mode.
|
| - if (left == right) ++right;
|
| - if (top == bottom) ++bottom;
|
| - switch (eventType) {
|
| - case SelectionEventType.SELECTION_HANDLES_SHOWN:
|
| - mSelectionRect.set(left, top, right, bottom);
|
| - mHasSelection = true;
|
| - mUnselectAllOnActionModeDismiss = true;
|
| - showSelectActionMode(true);
|
| - break;
|
| -
|
| - case SelectionEventType.SELECTION_HANDLES_MOVED:
|
| - mSelectionRect.set(left, top, right, bottom);
|
| - invalidateActionModeContentRect();
|
| - break;
|
| -
|
| - case SelectionEventType.SELECTION_HANDLES_CLEARED:
|
| - mHasSelection = false;
|
| - mDraggingSelection = false;
|
| - mUnselectAllOnActionModeDismiss = false;
|
| - hideSelectActionMode();
|
| - mSelectionRect.setEmpty();
|
| - break;
|
| -
|
| - case SelectionEventType.SELECTION_HANDLE_DRAG_STARTED:
|
| - mDraggingSelection = true;
|
| - updateActionModeVisibility();
|
| - break;
|
| -
|
| - case SelectionEventType.SELECTION_HANDLE_DRAG_STOPPED:
|
| - mDraggingSelection = false;
|
| - updateActionModeVisibility();
|
| - break;
|
| -
|
| - case SelectionEventType.INSERTION_HANDLE_SHOWN:
|
| - mSelectionRect.set(left, top, right, bottom);
|
| - mHasInsertion = true;
|
| - break;
|
| -
|
| - case SelectionEventType.INSERTION_HANDLE_MOVED:
|
| - mSelectionRect.set(left, top, right, bottom);
|
| - if (!isScrollInProgress() && isPastePopupShowing()) {
|
| - showPastePopup(xAnchor, yAnchor);
|
| - } else {
|
| - hidePastePopup();
|
| - }
|
| - break;
|
| -
|
| - case SelectionEventType.INSERTION_HANDLE_TAPPED:
|
| - if (mWasPastePopupShowingOnInsertionDragStart) {
|
| - hidePastePopup();
|
| - } else {
|
| - showPastePopup(xAnchor, yAnchor);
|
| - }
|
| - mWasPastePopupShowingOnInsertionDragStart = false;
|
| - break;
|
| -
|
| - case SelectionEventType.INSERTION_HANDLE_CLEARED:
|
| - hidePastePopup();
|
| - mHasInsertion = false;
|
| - mSelectionRect.setEmpty();
|
| - break;
|
| -
|
| - case SelectionEventType.INSERTION_HANDLE_DRAG_STARTED:
|
| - mWasPastePopupShowingOnInsertionDragStart = isPastePopupShowing();
|
| - hidePastePopup();
|
| - break;
|
| -
|
| - case SelectionEventType.INSERTION_HANDLE_DRAG_STOPPED:
|
| - if (mWasPastePopupShowingOnInsertionDragStart) {
|
| - showPastePopup(xAnchor, yAnchor);
|
| - }
|
| - mWasPastePopupShowingOnInsertionDragStart = false;
|
| - break;
|
| - case SelectionEventType.SELECTION_ESTABLISHED:
|
| - case SelectionEventType.SELECTION_DISSOLVED:
|
| - break;
|
| -
|
| - default:
|
| - assert false : "Invalid selection event type.";
|
| - }
|
| - if (mContextualSearchClient != null) {
|
| - final float deviceScale = mRenderCoordinates.getDeviceScaleFactor();
|
| - int xAnchorPix = (int) (xAnchor * deviceScale);
|
| - int yAnchorPix = (int) (yAnchor * deviceScale);
|
| - mContextualSearchClient.onSelectionEvent(eventType, xAnchorPix, yAnchorPix);
|
| - }
|
| - }
|
| -
|
| - private void dismissTextHandles() {
|
| - if (mNativeContentViewCore != 0) nativeDismissTextHandles(mNativeContentViewCore);
|
| + mActionMode.onSelectionEvent(eventType, xAnchor, yAnchor, left, top, right, bottom,
|
| + isScrollInProgress(), mTouchScrollInProgress, mImeAdapter);
|
| }
|
|
|
| private void setTextHandlesTemporarilyHidden(boolean hide) {
|
| @@ -2345,7 +1947,6 @@ public class ContentViewCore implements AccessibilityStateChangeListener, Displa
|
| TraceEvent.begin("ContentViewCore.updateImeAdapter");
|
| boolean focusedNodeEditable = (textInputType != TextInputType.NONE);
|
| boolean focusedNodeIsPassword = (textInputType == TextInputType.PASSWORD);
|
| - if (!focusedNodeEditable) hidePastePopup();
|
|
|
| mImeAdapter.attach(nativeImeAdapterAndroid);
|
| mImeAdapter.updateKeyboardVisibility(
|
| @@ -2353,18 +1954,11 @@ public class ContentViewCore implements AccessibilityStateChangeListener, Displa
|
| mImeAdapter.updateState(text, selectionStart, selectionEnd, compositionStart,
|
| compositionEnd, isNonImeChange, inBatchEditMode);
|
|
|
| - if (mActionMode != null) {
|
| - final boolean actionModeConfigurationChanged =
|
| - focusedNodeEditable != mFocusedNodeEditable
|
| - || focusedNodeIsPassword != mFocusedNodeIsPassword;
|
| - if (actionModeConfigurationChanged) mActionMode.invalidate();
|
| - }
|
| -
|
| - mFocusedNodeIsPassword = focusedNodeIsPassword;
|
| - if (focusedNodeEditable != mFocusedNodeEditable) {
|
| - mFocusedNodeEditable = focusedNodeEditable;
|
| - mJoystickScrollProvider.setEnabled(!mFocusedNodeEditable);
|
| - getContentViewClient().onFocusedNodeEditabilityChanged(mFocusedNodeEditable);
|
| + boolean editableToggled = (focusedNodeEditable != isFocusedNodeEditable());
|
| + mActionMode.updateSelectionState(focusedNodeEditable, focusedNodeIsPassword);
|
| + if (editableToggled) {
|
| + mJoystickScrollProvider.setEnabled(!focusedNodeEditable);
|
| + getContentViewClient().onFocusedNodeEditabilityChanged(focusedNodeEditable);
|
| }
|
| } finally {
|
| TraceEvent.end("ContentViewCore.updateImeAdapter");
|
| @@ -2438,7 +2032,7 @@ public class ContentViewCore implements AccessibilityStateChangeListener, Displa
|
| * Called when the <select> popup needs to be hidden. This calls
|
| * nativeSelectPopupMenuItems() with null indices.
|
| */
|
| - private void hideSelectPopupWithCancelMesage() {
|
| + private void hideSelectPopupWithCancelMessage() {
|
| if (mSelectPopup != null) mSelectPopup.hide(true);
|
| }
|
|
|
| @@ -2500,10 +2094,7 @@ public class ContentViewCore implements AccessibilityStateChangeListener, Displa
|
| @SuppressWarnings("unused")
|
| @CalledByNative
|
| private void onSelectionChanged(String text) {
|
| - mLastSelectedText = text;
|
| - if (mContextualSearchClient != null) {
|
| - mContextualSearchClient.onSelectionChanged(text);
|
| - }
|
| + mActionMode.onSelectionChanged(text);
|
| }
|
|
|
| @SuppressWarnings("unused")
|
| @@ -2512,58 +2103,17 @@ public class ContentViewCore implements AccessibilityStateChangeListener, Displa
|
| mContainerView.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS);
|
| }
|
|
|
| - @VisibleForTesting
|
| - public boolean isPastePopupShowing() {
|
| - if (mPastePopupMenu != null) return mPastePopupMenu.isShowing();
|
| - return false;
|
| - }
|
| -
|
| - // Coordinates are in DIP.
|
| @CalledByNative
|
| private void showPastePopup(int x, int y) {
|
| - if (mContainerView.getParent() == null || mContainerView.getVisibility() != View.VISIBLE) {
|
| - return;
|
| - }
|
| -
|
| - if (!mHasInsertion || (!supportsFloatingActionMode() && !canPaste())) return;
|
| -
|
| - PastePopupMenu pastePopupMenu = getPastePopup();
|
| - if (pastePopupMenu == null) return;
|
| -
|
| - final float deviceScale = mRenderCoordinates.getDeviceScaleFactor();
|
| - final int xPix = (int) (x * deviceScale);
|
| - final int yPix = (int) (y * deviceScale);
|
| - final float topControlsShownPix = mRenderCoordinates.getContentOffsetYPix();
|
| - try {
|
| - pastePopupMenu.show(xPix, (int) (yPix + topControlsShownPix));
|
| - } catch (WindowManager.BadTokenException e) {
|
| - }
|
| + mActionMode.showPastePopup(x, y);
|
| }
|
|
|
| private void hidePastePopup() {
|
| - if (mPastePopupMenu != null) mPastePopupMenu.hide();
|
| + mActionMode.hidePastePopup();
|
| }
|
|
|
| - private PastePopupMenu getPastePopup() {
|
| - if (mPastePopupMenu == null) {
|
| - PastePopupMenuDelegate delegate = new PastePopupMenuDelegate() {
|
| - @Override
|
| - public void paste() {
|
| - mWebContents.paste();
|
| - dismissTextHandles();
|
| - }
|
| - };
|
| - Context windowContext = getWindowAndroid().getContext().get();
|
| - if (windowContext == null) return null;
|
| - if (supportsFloatingActionMode()) {
|
| - mPastePopupMenu = new FloatingPastePopupMenu(
|
| - windowContext, getContainerView(), delegate);
|
| - } else {
|
| - mPastePopupMenu = new LegacyPastePopupMenu(
|
| - windowContext, getContainerView(), delegate);
|
| - }
|
| - }
|
| - return mPastePopupMenu;
|
| + private void destroyPastePopup() {
|
| + mActionMode.destroyPastePopup();
|
| }
|
|
|
| private boolean canPaste() {
|
| @@ -3021,17 +2571,7 @@ public class ContentViewCore implements AccessibilityStateChangeListener, Displa
|
| * @param data the reply that contains the processed text.
|
| */
|
| public void onReceivedProcessTextResult(int resultCode, Intent data) {
|
| - if (mWebContents == null || !isSelectionEditable() || resultCode != Activity.RESULT_OK
|
| - || data == null) {
|
| - return;
|
| - }
|
| -
|
| - CharSequence result = data.getCharSequenceExtra(Intent.EXTRA_PROCESS_TEXT);
|
| - if (result != null) {
|
| - // TODO(hush): Use a variant of replace that re-selects the replaced text.
|
| - // crbug.com/546710
|
| - mWebContents.replace(result.toString());
|
| - }
|
| + mActionMode.onReceivedProcessTextResult(resultCode, data);
|
| }
|
|
|
| /**
|
| @@ -3239,7 +2779,7 @@ public class ContentViewCore implements AccessibilityStateChangeListener, Displa
|
| // ActionMode#invalidate() won't be able to re-layout the floating
|
| // action mode menu items according to the new rotation. So Chrome
|
| // has to re-create the action mode.
|
| - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M && mActionMode != null) {
|
| + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M && mActionMode.isActionModeValid()) {
|
| hidePopupsAndPreserveSelection();
|
| showSelectActionMode(true);
|
| }
|
| @@ -3284,7 +2824,7 @@ public class ContentViewCore implements AccessibilityStateChangeListener, Displa
|
| * @param contextualSearchClient The client to notify for Contextual Search operations.
|
| */
|
| public void setContextualSearchClient(ContextualSearchClient contextualSearchClient) {
|
| - mContextualSearchClient = contextualSearchClient;
|
| + mActionMode.setContextualSearchClient(contextualSearchClient);
|
| }
|
|
|
| /**
|
| @@ -3385,7 +2925,6 @@ public class ContentViewCore implements AccessibilityStateChangeListener, Displa
|
| private native void nativePinchBy(long nativeContentViewCoreImpl, long timeMs,
|
| float anchorX, float anchorY, float deltaScale);
|
|
|
| - private native void nativeDismissTextHandles(long nativeContentViewCoreImpl);
|
| private native void nativeSetTextHandlesTemporarilyHidden(
|
| long nativeContentViewCoreImpl, boolean hidden);
|
|
|
|
|