 Chromium Code Reviews
 Chromium Code Reviews Issue 335943002:
  [Android] Composited selection handle rendering  (Closed) 
  Base URL: https://chromium.googlesource.com/chromium/src.git@input_native_handles_final
    
  
    Issue 335943002:
  [Android] Composited selection handle rendering  (Closed) 
  Base URL: https://chromium.googlesource.com/chromium/src.git@input_native_handles_final| 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 a8d204192d9b66c5e572b92c0fe2ec629079797b..622b62b4eec710ff351aa0c25fd0eac07844219a 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 | 
| @@ -7,6 +7,7 @@ package org.chromium.content.browser; | 
| import android.annotation.SuppressLint; | 
| import android.app.Activity; | 
| import android.app.SearchManager; | 
| +import android.content.ClipboardManager; | 
| import android.content.ContentResolver; | 
| import android.content.Context; | 
| import android.content.Intent; | 
| @@ -62,16 +63,18 @@ import org.chromium.content.browser.accessibility.AccessibilityInjector; | 
| import org.chromium.content.browser.accessibility.BrowserAccessibilityManager; | 
| import org.chromium.content.browser.input.AdapterInputConnection; | 
| import org.chromium.content.browser.input.GamepadList; | 
| -import org.chromium.content.browser.input.HandleView; | 
| import org.chromium.content.browser.input.ImeAdapter; | 
| import org.chromium.content.browser.input.ImeAdapter.AdapterInputConnectionFactory; | 
| import org.chromium.content.browser.input.InputMethodManagerWrapper; | 
| -import org.chromium.content.browser.input.InsertionHandleController; | 
| +import org.chromium.content.browser.input.PastePopupMenu; | 
| +import org.chromium.content.browser.input.PastePopupMenu.PastePopupMenuDelegate; | 
| +import org.chromium.content.browser.input.PopupTouchHandleDrawable; | 
| +import org.chromium.content.browser.input.PopupTouchHandleDrawable.PopupTouchHandleDrawableDelegate; | 
| import org.chromium.content.browser.input.SelectPopup; | 
| import org.chromium.content.browser.input.SelectPopupDialog; | 
| import org.chromium.content.browser.input.SelectPopupDropdown; | 
| import org.chromium.content.browser.input.SelectPopupItem; | 
| -import org.chromium.content.browser.input.SelectionHandleController; | 
| +import org.chromium.content.browser.input.SelectionEventType; | 
| import org.chromium.content.common.ContentSwitches; | 
| import org.chromium.content_public.browser.GestureStateListener; | 
| import org.chromium.content_public.browser.WebContents; | 
| @@ -108,9 +111,6 @@ public class ContentViewCore | 
| private static final int IS_LONG_PRESS = 1; | 
| private static final int IS_LONG_TAP = 2; | 
| - // Length of the delay (in ms) before fading in handles after the last page movement. | 
| - private static final int TEXT_HANDLE_FADE_IN_DELAY = 300; | 
| - | 
| // If the embedder adds a JavaScript interface object that contains an indirect reference to | 
| // the ContentViewCore, then storing a strong ref to the interface object on the native | 
| // side would prevent garbage collection of the ContentViewCore (as that strong ref would | 
| @@ -263,13 +263,13 @@ public class ContentViewCore | 
| private AdapterInputConnection mInputConnection; | 
| private InputMethodManagerWrapper mInputMethodManagerWrapper; | 
| - private SelectionHandleController mSelectionHandleController; | 
| - private InsertionHandleController mInsertionHandleController; | 
| + // Lazily created paste popup menu, triggered either via long press in an | 
| + // editable region or from tapping the insertion handle. | 
| + private PastePopupMenu mPastePopupMenu; | 
| - private Runnable mDeferredHandleFadeInRunnable; | 
| + private PopupTouchHandleDrawableDelegate mTouchHandleDelegate; | 
| private PositionObserver mPositionObserver; | 
| - private PositionObserver.Listener mPositionListener; | 
| // Size of the viewport in physical pixels as set from onSizeChanged. | 
| private int mViewportWidthPix; | 
| @@ -283,13 +283,10 @@ public class ContentViewCore | 
| // Cached copy of all positions and scales as reported by the renderer. | 
| private final RenderCoordinates mRenderCoordinates; | 
| - private final RenderCoordinates.NormalizedPoint mStartHandlePoint; | 
| - private final RenderCoordinates.NormalizedPoint mEndHandlePoint; | 
| - private final RenderCoordinates.NormalizedPoint mInsertionHandlePoint; | 
| - | 
| // 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 String mLastSelectedText; | 
| private boolean mFocusedNodeEditable; | 
| private ActionMode mActionMode; | 
| @@ -383,9 +380,6 @@ public class ContentViewCore | 
| deviceScaleFactor = Float.valueOf(forceScaleFactor); | 
| } | 
| mRenderCoordinates.setDeviceScaleFactor(deviceScaleFactor); | 
| - mStartHandlePoint = mRenderCoordinates.createNormalizedPoint(); | 
| - mEndHandlePoint = mRenderCoordinates.createNormalizedPoint(); | 
| - mInsertionHandlePoint = mRenderCoordinates.createNormalizedPoint(); | 
| mAccessibilityManager = (AccessibilityManager) | 
| getContext().getSystemService(Context.ACCESSIBILITY_SERVICE); | 
| mGestureStateListeners = new ObserverList<GestureStateListener>(); | 
| @@ -543,7 +537,7 @@ public class ContentViewCore | 
| public void onImeEvent(boolean isFinish) { | 
| getContentViewClient().onImeEvent(); | 
| if (!isFinish) { | 
| - hideHandles(); | 
| + hideTextHandles(); | 
| } | 
| } | 
| @@ -606,15 +600,6 @@ public class ContentViewCore | 
| long nativeWebContents, WindowAndroid windowAndroid) { | 
| setContainerView(containerView); | 
| - mPositionListener = new PositionObserver.Listener() { | 
| - @Override | 
| - public void onPositionChanged(int x, int y) { | 
| - if (isSelectionHandleShowing() || isInsertionHandleShowing()) { | 
| - temporarilyHideTextHandles(); | 
| - } | 
| - } | 
| - }; | 
| - | 
| long windowNativePointer = windowAndroid != null ? windowAndroid.getNativePointer() : 0; | 
| long viewAndroidNativePointer = 0; | 
| @@ -686,9 +671,7 @@ public class ContentViewCore | 
| public void setContainerView(ViewGroup containerView) { | 
| TraceEvent.begin(); | 
| if (mContainerView != null) { | 
| - mPositionObserver.removeListener(mPositionListener); | 
| - mSelectionHandleController = null; | 
| - mInsertionHandleController = null; | 
| + mPastePopupMenu = null; | 
| mInputConnection = null; | 
| } | 
| @@ -1128,23 +1111,17 @@ public class ContentViewCore | 
| * @see View#onTouchEvent(MotionEvent) | 
| */ | 
| public boolean onTouchEvent(MotionEvent event) { | 
| + final boolean isTouchHandleEvent = false; | 
| + return onTouchEventImpl(event, isTouchHandleEvent); | 
| + } | 
| + | 
| + private boolean onTouchEventImpl(MotionEvent event, boolean isTouchHandleEvent) { | 
| TraceEvent.begin("onTouchEvent"); | 
| try { | 
| cancelRequestToScrollFocusedEditableNodeIntoView(); | 
| final int eventAction = event.getActionMasked(); | 
| - | 
| - // Only these actions have any effect on gesture detection. Other | 
| - // actions have no corresponding WebTouchEvent type and may confuse the | 
| - // touch pipline, so we ignore them entirely. | 
| - if (eventAction != MotionEvent.ACTION_DOWN | 
| - && eventAction != MotionEvent.ACTION_UP | 
| - && eventAction != MotionEvent.ACTION_CANCEL | 
| - && eventAction != MotionEvent.ACTION_MOVE | 
| - && eventAction != MotionEvent.ACTION_POINTER_DOWN | 
| - && eventAction != MotionEvent.ACTION_POINTER_UP) { | 
| - return false; | 
| - } | 
| + if (!isValidTouchEventActionForNative(eventAction)) return false; | 
| if (mNativeContentViewCore == 0) return false; | 
| @@ -1167,7 +1144,8 @@ public class ContentViewCore | 
| event.getRawX(), event.getRawY(), | 
| event.getToolType(0), | 
| pointerCount > 1 ? event.getToolType(1) : MotionEvent.TOOL_TYPE_UNKNOWN, | 
| - event.getButtonState()); | 
| + event.getButtonState(), | 
| + isTouchHandleEvent); | 
| if (offset != null) offset.recycle(); | 
| return consumed; | 
| @@ -1176,6 +1154,18 @@ public class ContentViewCore | 
| } | 
| } | 
| + private static boolean isValidTouchEventActionForNative(int eventAction) { | 
| + // Only these actions have any effect on gesture detection. Other | 
| + // actions have no corresponding WebTouchEvent type and may confuse the | 
| + // touch pipline, so we ignore them entirely. | 
| + return eventAction == MotionEvent.ACTION_DOWN | 
| + || eventAction == MotionEvent.ACTION_UP | 
| + || eventAction == MotionEvent.ACTION_CANCEL | 
| + || eventAction == MotionEvent.ACTION_MOVE | 
| + || eventAction == MotionEvent.ACTION_POINTER_DOWN | 
| + || eventAction == MotionEvent.ACTION_POINTER_UP; | 
| + } | 
| + | 
| public void setIgnoreRemainingTouchEvents() { | 
| resetGestureDetection(); | 
| } | 
| @@ -1189,7 +1179,6 @@ public class ContentViewCore | 
| private void onFlingStartEventConsumed(int vx, int vy) { | 
| mTouchScrollInProgress = false; | 
| mPotentiallyActiveFlingCount++; | 
| - temporarilyHideTextHandles(); | 
| for (mGestureStateListenersIterator.rewind(); | 
| mGestureStateListenersIterator.hasNext();) { | 
| mGestureStateListenersIterator.next().onFlingStartGesture( | 
| @@ -1216,8 +1205,8 @@ public class ContentViewCore | 
| @SuppressWarnings("unused") | 
| @CalledByNative | 
| private void onScrollBeginEventAck() { | 
| + hidePastePopup(); | 
| mTouchScrollInProgress = true; | 
| - temporarilyHideTextHandles(); | 
| mZoomControlsDelegate.invokeZoomPicker(); | 
| updateGestureStateListener(GestureEventType.SCROLL_START); | 
| } | 
| @@ -1243,7 +1232,6 @@ public class ContentViewCore | 
| @SuppressWarnings("unused") | 
| @CalledByNative | 
| private void onPinchBeginEventAck() { | 
| - temporarilyHideTextHandles(); | 
| updateGestureStateListener(GestureEventType.PINCH_BEGIN); | 
| } | 
| @@ -1262,12 +1250,6 @@ public class ContentViewCore | 
| } | 
| } | 
| - @SuppressWarnings("unused") | 
| - @CalledByNative | 
| - private void onDoubleTapEventAck() { | 
| - temporarilyHideTextHandles(); | 
| - } | 
| - | 
| /** | 
| * Called just prior to a tap or press gesture being forwarded to the renderer. | 
| */ | 
| @@ -1433,9 +1415,11 @@ public class ContentViewCore | 
| } | 
| private void hidePopups() { | 
| - hideSelectPopup(); | 
| - hideHandles(); | 
| + mUnselectAllOnActionModeDismiss = true; | 
| hideSelectActionBar(); | 
| + hidePastePopup(); | 
| + hideSelectPopup(); | 
| + hideTextHandles(); | 
| } | 
| public void hideSelectActionBar() { | 
| @@ -1889,14 +1873,6 @@ public class ContentViewCore | 
| mLastTapX = (int) xPix; | 
| mLastTapY = (int) yPix; | 
| - | 
| - if (type == GestureEventType.LONG_PRESS | 
| - || type == GestureEventType.LONG_TAP) { | 
| - getInsertionHandleController().allowAutomaticShowing(); | 
| - getSelectionHandleController().allowAutomaticShowing(); | 
| - } else { | 
| - if (mFocusedNodeEditable) getInsertionHandleController().allowAutomaticShowing(); | 
| - } | 
| } | 
| /** | 
| @@ -1959,101 +1935,6 @@ public class ContentViewCore | 
| return mDownloadDelegate; | 
| } | 
| - private SelectionHandleController getSelectionHandleController() { | 
| - if (mSelectionHandleController == null) { | 
| - mSelectionHandleController = new SelectionHandleController( | 
| - getContainerView(), mPositionObserver) { | 
| - @Override | 
| - public void selectBetweenCoordinates(int x1, int y1, int x2, int y2) { | 
| - if (mNativeContentViewCore != 0 && !(x1 == x2 && y1 == y2)) { | 
| - nativeSelectBetweenCoordinates(mNativeContentViewCore, | 
| - x1, y1 - mRenderCoordinates.getContentOffsetYPix(), | 
| - x2, y2 - mRenderCoordinates.getContentOffsetYPix()); | 
| - } | 
| - } | 
| - | 
| - @Override | 
| - public void showHandles(int startDir, int endDir) { | 
| - final boolean wasShowing = isShowing(); | 
| - super.showHandles(startDir, endDir); | 
| - if (!wasShowing || mActionMode == null) showSelectActionBar(); | 
| - } | 
| - | 
| - }; | 
| - | 
| - mSelectionHandleController.hideAndDisallowAutomaticShowing(); | 
| - } | 
| - | 
| - return mSelectionHandleController; | 
| - } | 
| - | 
| - private InsertionHandleController getInsertionHandleController() { | 
| - if (mInsertionHandleController == null) { | 
| - mInsertionHandleController = new InsertionHandleController( | 
| - getContainerView(), mPositionObserver) { | 
| - private static final int AVERAGE_LINE_HEIGHT = 14; | 
| - | 
| - @Override | 
| - public void setCursorPosition(int x, int y) { | 
| - if (mNativeContentViewCore != 0) { | 
| - nativeMoveCaret(mNativeContentViewCore, | 
| - x, y - mRenderCoordinates.getContentOffsetYPix()); | 
| - } | 
| - } | 
| - | 
| - @Override | 
| - public void paste() { | 
| - mImeAdapter.paste(); | 
| - hideHandles(); | 
| - } | 
| - | 
| - @Override | 
| - public int getLineHeight() { | 
| - return (int) Math.ceil( | 
| - mRenderCoordinates.fromLocalCssToPix(AVERAGE_LINE_HEIGHT)); | 
| - } | 
| - }; | 
| - | 
| - mInsertionHandleController.hideAndDisallowAutomaticShowing(); | 
| - } | 
| - | 
| - return mInsertionHandleController; | 
| - } | 
| - | 
| - @VisibleForTesting | 
| - public InsertionHandleController getInsertionHandleControllerForTest() { | 
| - return mInsertionHandleController; | 
| - } | 
| - | 
| - @VisibleForTesting | 
| - public SelectionHandleController getSelectionHandleControllerForTest() { | 
| - return mSelectionHandleController; | 
| - } | 
| - | 
| - private void updateHandleScreenPositions() { | 
| - if (isSelectionHandleShowing()) { | 
| - mSelectionHandleController.setStartHandlePosition( | 
| - mStartHandlePoint.getXPix(), mStartHandlePoint.getYPix()); | 
| - mSelectionHandleController.setEndHandlePosition( | 
| - mEndHandlePoint.getXPix(), mEndHandlePoint.getYPix()); | 
| - } | 
| - | 
| - if (isInsertionHandleShowing()) { | 
| - mInsertionHandleController.setHandlePosition( | 
| - mInsertionHandlePoint.getXPix(), mInsertionHandlePoint.getYPix()); | 
| - } | 
| - } | 
| - | 
| - private void hideHandles() { | 
| - if (mSelectionHandleController != null) { | 
| - mSelectionHandleController.hideAndDisallowAutomaticShowing(); | 
| - } | 
| - if (mInsertionHandleController != null) { | 
| - mInsertionHandleController.hideAndDisallowAutomaticShowing(); | 
| - } | 
| - mPositionObserver.removeListener(mPositionListener); | 
| - } | 
| - | 
| private void showSelectActionBar() { | 
| if (mActionMode != null) { | 
| mActionMode.invalidate(); | 
| @@ -2183,6 +2064,54 @@ public class ContentViewCore | 
| } | 
| } | 
| + private void hidePastePopup() { | 
| + if (mPastePopupMenu == null) return; | 
| + mPastePopupMenu.hide(); | 
| + } | 
| + | 
| + @CalledByNative | 
| + private void onSelectionEvent(int eventType, float posXDip, float posYDip) { | 
| + switch (eventType) { | 
| + case SelectionEventType.SELECTION_SHOWN: | 
| + mHasSelection = true; | 
| + // TODO(cjhopman): Remove this when there is a better signal that long press caused | 
| + // a selection. See http://crbug.com/150151. | 
| + mContainerView.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS); | 
| + showSelectActionBar(); | 
| + break; | 
| + | 
| + case SelectionEventType.SELECTION_CLEARED: | 
| + mHasSelection = false; | 
| + mUnselectAllOnActionModeDismiss = false; | 
| + hideSelectActionBar(); | 
| + break; | 
| + | 
| + case SelectionEventType.INSERTION_SHOWN: | 
| + mHasInsertion = true; | 
| + break; | 
| + | 
| + case SelectionEventType.INSERTION_MOVED: | 
| + // TODO(jdduke): Handle case where movement triggered by focus. | 
| + hidePastePopup(); | 
| + break; | 
| + | 
| + case SelectionEventType.INSERTION_TAPPED: | 
| + if (getPastePopup().isShowing()) | 
| + mPastePopupMenu.hide(); | 
| + else | 
| + showPastePopup((int) posXDip, (int) posYDip); | 
| + break; | 
| + | 
| + case SelectionEventType.INSERTION_CLEARED: | 
| + mHasInsertion = false; | 
| + hidePastePopup(); | 
| + break; | 
| + | 
| + default: | 
| + assert false : "Invalid selection event type."; | 
| + } | 
| + } | 
| + | 
| public boolean getUseDesktopUserAgent() { | 
| if (mNativeContentViewCore != 0) { | 
| return nativeGetUseDesktopUserAgent(mNativeContentViewCore); | 
| @@ -2205,59 +2134,10 @@ public class ContentViewCore | 
| if (mNativeContentViewCore != 0) nativeClearSslPreferences(mNativeContentViewCore); | 
| } | 
| - private boolean isSelectionHandleShowing() { | 
| - return mSelectionHandleController != null && mSelectionHandleController.isShowing(); | 
| - } | 
| - | 
| - private boolean isInsertionHandleShowing() { | 
| - return mInsertionHandleController != null && mInsertionHandleController.isShowing(); | 
| - } | 
| - | 
| - // Makes the insertion/selection handles invisible. They will fade back in shortly after the | 
| - // last call to scheduleTextHandleFadeIn (or temporarilyHideTextHandles). | 
| - private void temporarilyHideTextHandles() { | 
| - if (isSelectionHandleShowing() && !mSelectionHandleController.isDragging()) { | 
| - mSelectionHandleController.setHandleVisibility(HandleView.INVISIBLE); | 
| - } | 
| - if (isInsertionHandleShowing() && !mInsertionHandleController.isDragging()) { | 
| - mInsertionHandleController.setHandleVisibility(HandleView.INVISIBLE); | 
| - } | 
| - scheduleTextHandleFadeIn(); | 
| - } | 
| - | 
| - private boolean allowTextHandleFadeIn() { | 
| - if (mTouchScrollInProgress) return false; | 
| - | 
| - if (mPopupZoomer.isShowing()) return false; | 
| - | 
| - return true; | 
| - } | 
| - | 
| - // Cancels any pending fade in and schedules a new one. | 
| - private void scheduleTextHandleFadeIn() { | 
| - if (!isInsertionHandleShowing() && !isSelectionHandleShowing()) return; | 
| - | 
| - if (mDeferredHandleFadeInRunnable == null) { | 
| - mDeferredHandleFadeInRunnable = new Runnable() { | 
| - @Override | 
| - public void run() { | 
| - if (!allowTextHandleFadeIn()) { | 
| - // Delay fade in until it is allowed. | 
| - scheduleTextHandleFadeIn(); | 
| - } else { | 
| - if (isSelectionHandleShowing()) { | 
| - mSelectionHandleController.beginHandleFadeIn(); | 
| - } | 
| - if (isInsertionHandleShowing()) { | 
| - mInsertionHandleController.beginHandleFadeIn(); | 
| - } | 
| - } | 
| - } | 
| - }; | 
| - } | 
| - | 
| - mContainerView.removeCallbacks(mDeferredHandleFadeInRunnable); | 
| - mContainerView.postDelayed(mDeferredHandleFadeInRunnable, TEXT_HANDLE_FADE_IN_DELAY); | 
| + private void hideTextHandles() { | 
| + mHasSelection = false; | 
| + mHasInsertion = false; | 
| + if (mNativeContentViewCore != 0) nativeHideTextHandles(mNativeContentViewCore); | 
| } | 
| /** | 
| @@ -2321,7 +2201,6 @@ public class ContentViewCore | 
| final boolean needHidePopupZoomer = contentSizeChanged || scrollChanged; | 
| final boolean needUpdateZoomControls = scaleLimitsChanged || scrollChanged; | 
| - final boolean needTemporarilyHideHandles = scrollChanged; | 
| if (needHidePopupZoomer) mPopupZoomer.hide(true); | 
| @@ -2349,9 +2228,7 @@ public class ContentViewCore | 
| } | 
| } | 
| - if (needTemporarilyHideHandles) temporarilyHideTextHandles(); | 
| if (needUpdateZoomControls) mZoomControlsDelegate.updateZoomControls(); | 
| - if (contentOffsetChanged) updateHandleScreenPositions(); | 
| // Update offsets for fullscreen. | 
| final float controlsOffsetPix = controlsOffsetYCss * deviceScale; | 
| @@ -2371,6 +2248,7 @@ public class ContentViewCore | 
| boolean isNonImeChange) { | 
| TraceEvent.begin(); | 
| mFocusedNodeEditable = (textInputType != ImeAdapter.getTextInputTypeNone()); | 
| + if (!mFocusedNodeEditable) hidePastePopup(); | 
| mImeAdapter.updateKeyboardVisibility( | 
| nativeImeAdapterAndroid, textInputType, showImeIfNeeded); | 
| @@ -2440,7 +2318,6 @@ public class ContentViewCore | 
| private void showDisambiguationPopup(Rect targetRect, Bitmap zoomedBitmap) { | 
| mPopupZoomer.setBitmap(zoomedBitmap); | 
| mPopupZoomer.show(targetRect); | 
| - temporarilyHideTextHandles(); | 
| } | 
| @SuppressWarnings("unused") | 
| @@ -2451,69 +2328,24 @@ public class ContentViewCore | 
| @SuppressWarnings("unused") | 
| @CalledByNative | 
| - private void onSelectionChanged(String text) { | 
| - mLastSelectedText = text; | 
| - getContentViewClient().onSelectionChanged(text); | 
| - } | 
| - | 
| - @SuppressWarnings("unused") | 
| - @CalledByNative | 
| - private void showSelectionHandlesAutomatically() { | 
| - getSelectionHandleController().allowAutomaticShowing(); | 
| + private PopupTouchHandleDrawable createPopupTouchHandleDrawable() { | 
| + if (mTouchHandleDelegate == null) { | 
| + mTouchHandleDelegate = new PopupTouchHandleDrawableDelegate() { | 
| + public boolean onTouchHandleEvent(MotionEvent event) { | 
| + final boolean isTouchHandleEvent = true; | 
| + return onTouchEventImpl(event, isTouchHandleEvent); | 
| + } | 
| + }; | 
| + } | 
| + return new PopupTouchHandleDrawable( | 
| 
jdduke (slow)
2014/07/10 21:11:28
So, I think the one major remaining issue is WebVi
 
cjhopman
2014/07/14 18:23:52
Yeah, this whole structure (not this change specif
 | 
| + mTouchHandleDelegate, getContainerView(), mPositionObserver); | 
| } | 
| @SuppressWarnings("unused") | 
| @CalledByNative | 
| - private void onSelectionBoundsChanged( | 
| - float startXDip, float startYDip, float endXDip, float endYDip, | 
| - int startDir, int endDir) { | 
| - if (endXDip != startXDip || endYDip != startYDip || | 
| - (mSelectionHandleController != null && mSelectionHandleController.isDragging())) { | 
| - if (mInsertionHandleController != null) { | 
| - mInsertionHandleController.hide(); | 
| - } | 
| - mStartHandlePoint.setLocalDip(startXDip, startYDip); | 
| - mEndHandlePoint.setLocalDip(endXDip, endYDip); | 
| - | 
| - boolean wereSelectionHandlesShowing = getSelectionHandleController().isShowing(); | 
| - | 
| - getSelectionHandleController().onSelectionChanged(startDir, endDir); | 
| - updateHandleScreenPositions(); | 
| - mHasSelection = true; | 
| - | 
| - if (!wereSelectionHandlesShowing && getSelectionHandleController().isShowing()) { | 
| - // TODO(cjhopman): Remove this when there is a better signal that long press caused | 
| - // a selection. See http://crbug.com/150151. | 
| - mContainerView.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS); | 
| - } | 
| - | 
| - } else { | 
| - mUnselectAllOnActionModeDismiss = false; | 
| - hideSelectActionBar(); | 
| - if (startXDip != 0 && startYDip != 0 && mFocusedNodeEditable) { | 
| - // Selection is a caret, and a text field is focused. | 
| - if (mSelectionHandleController != null) { | 
| - mSelectionHandleController.hide(); | 
| - } | 
| - mInsertionHandlePoint.setLocalDip(startXDip, startYDip); | 
| - | 
| - getInsertionHandleController().onCursorPositionChanged(); | 
| - updateHandleScreenPositions(); | 
| - if (mInputMethodManagerWrapper.isWatchingCursor(mContainerView)) { | 
| - final int xPix = (int) mInsertionHandlePoint.getXPix(); | 
| - final int yPix = (int) mInsertionHandlePoint.getYPix(); | 
| - mInputMethodManagerWrapper.updateCursor( | 
| - mContainerView, xPix, yPix, xPix, yPix); | 
| - } | 
| - } else { | 
| - // Deselection | 
| - hideHandles(); | 
| - } | 
| - mHasSelection = false; | 
| - } | 
| - if (isSelectionHandleShowing() || isInsertionHandleShowing()) { | 
| - mPositionObserver.addListener(mPositionListener); | 
| - } | 
| + private void onSelectionChanged(String text) { | 
| + mLastSelectedText = text; | 
| + getContentViewClient().onSelectionChanged(text); | 
| } | 
| @SuppressWarnings("unused") | 
| @@ -2526,10 +2358,27 @@ public class ContentViewCore | 
| @SuppressWarnings("unused") | 
| @CalledByNative | 
| private void showPastePopup(int xDip, int yDip) { | 
| - mInsertionHandlePoint.setLocalDip(xDip, yDip); | 
| - getInsertionHandleController().showHandle(); | 
| - updateHandleScreenPositions(); | 
| - getInsertionHandleController().showHandleWithPastePopup(); | 
| + final float contentOffsetYPix = mRenderCoordinates.getContentOffsetYPix(); | 
| + getPastePopup().showAt( | 
| + (int) mRenderCoordinates.fromDipToPix(xDip), | 
| + (int) (mRenderCoordinates.fromDipToPix(yDip) + contentOffsetYPix)); | 
| + } | 
| + | 
| + private PastePopupMenu getPastePopup() { | 
| + if (mPastePopupMenu == null) { | 
| + mPastePopupMenu = new PastePopupMenu(getContainerView(), | 
| + new PastePopupMenuDelegate() { | 
| + public void paste() { | 
| + mImeAdapter.paste(); | 
| + } | 
| + public boolean canPaste() { | 
| + if (!mFocusedNodeEditable) return false; | 
| + return ((ClipboardManager) mContext.getSystemService( | 
| + Context.CLIPBOARD_SERVICE)).hasPrimaryClip(); | 
| + } | 
| + }); | 
| + } | 
| + return mPastePopupMenu; | 
| } | 
| @SuppressWarnings("unused") | 
| @@ -3218,7 +3067,8 @@ public class ContentViewCore | 
| int pointerId0, int pointerId1, | 
| float touchMajor0, float touchMajor1, | 
| float rawX, float rawY, | 
| - int androidToolType0, int androidToolType1, int androidButtonState); | 
| + int androidToolType0, int androidToolType1, int androidButtonState, | 
| + boolean isTouchHandleEvent); | 
| private native int nativeSendMouseMoveEvent( | 
| long nativeContentViewCoreImpl, long timeMs, float x, float y); | 
| @@ -3263,6 +3113,8 @@ public class ContentViewCore | 
| private native void nativeMoveCaret(long nativeContentViewCoreImpl, float x, float y); | 
| + private native void nativeHideTextHandles(long nativeContentViewCoreImpl); | 
| + | 
| private native void nativeResetGestureDetection(long nativeContentViewCoreImpl); | 
| private native void nativeSetDoubleTapSupportEnabled( | 
| long nativeContentViewCoreImpl, boolean enabled); |