| 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 6f22fcf9811528abd9d58739344be7b401c1f721..831f9e1691a06034b7758c7a004287350c6849f8 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; | 
| - | 
| // These values are obtained from Samsung. | 
| private static final int SPEN_ACTION_DOWN = 211; | 
| private static final int SPEN_ACTION_UP = 212; | 
| @@ -270,13 +270,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; | 
| @@ -290,13 +290,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; | 
| @@ -390,9 +387,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>(); | 
| @@ -550,7 +544,7 @@ public class ContentViewCore | 
| public void onImeEvent(boolean isFinish) { | 
| getContentViewClient().onImeEvent(); | 
| if (!isFinish) { | 
| -                            hideHandles(); | 
| +                            hideTextHandles(); | 
| } | 
| } | 
|  | 
| @@ -613,15 +607,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.getNativePointer(); | 
| assert windowNativePointer != 0; | 
| mViewAndroid = new ViewAndroid(windowAndroid, getViewAndroidDelegate()); | 
| @@ -691,10 +676,9 @@ public class ContentViewCore | 
| public void setContainerView(ViewGroup containerView) { | 
| TraceEvent.begin(); | 
| if (mContainerView != null) { | 
| -            mPositionObserver.removeListener(mPositionListener); | 
| -            mSelectionHandleController = null; | 
| -            mInsertionHandleController = null; | 
| +            mPastePopupMenu = null; | 
| mInputConnection = null; | 
| +            hidePopups(); | 
| } | 
|  | 
| mContainerView = containerView; | 
| @@ -1166,6 +1150,11 @@ 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 { | 
| int eventAction = event.getActionMasked(); | 
| @@ -1176,18 +1165,7 @@ public class ContentViewCore | 
|  | 
| if (isSPenSupported(mContext)) | 
| eventAction = convertSPenEventAction(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. | 
| -            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; | 
|  | 
| @@ -1210,7 +1188,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; | 
| @@ -1219,6 +1198,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(); | 
| } | 
| @@ -1232,7 +1223,6 @@ public class ContentViewCore | 
| private void onFlingStartEventConsumed(int vx, int vy) { | 
| mTouchScrollInProgress = false; | 
| mPotentiallyActiveFlingCount++; | 
| -        temporarilyHideTextHandles(); | 
| for (mGestureStateListenersIterator.rewind(); | 
| mGestureStateListenersIterator.hasNext();) { | 
| mGestureStateListenersIterator.next().onFlingStartGesture( | 
| @@ -1260,7 +1250,7 @@ public class ContentViewCore | 
| @CalledByNative | 
| private void onScrollBeginEventAck() { | 
| mTouchScrollInProgress = true; | 
| -        temporarilyHideTextHandles(); | 
| +        hidePastePopup(); | 
| mZoomControlsDelegate.invokeZoomPicker(); | 
| updateGestureStateListener(GestureEventType.SCROLL_START); | 
| } | 
| @@ -1286,7 +1276,6 @@ public class ContentViewCore | 
| @SuppressWarnings("unused") | 
| @CalledByNative | 
| private void onPinchBeginEventAck() { | 
| -        temporarilyHideTextHandles(); | 
| updateGestureStateListener(GestureEventType.PINCH_BEGIN); | 
| } | 
|  | 
| @@ -1305,12 +1294,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. | 
| */ | 
| @@ -1494,9 +1477,11 @@ public class ContentViewCore | 
| } | 
|  | 
| private void hidePopups() { | 
| -        hideSelectPopup(); | 
| -        hideHandles(); | 
| +        mUnselectAllOnActionModeDismiss = true; | 
| hideSelectActionBar(); | 
| +        hidePastePopup(); | 
| +        hideSelectPopup(); | 
| +        hideTextHandles(); | 
| } | 
|  | 
| public void hideSelectActionBar() { | 
| @@ -1947,14 +1932,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(); | 
| -        } | 
| } | 
|  | 
| /** | 
| @@ -2017,106 +1994,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)); | 
| -                } | 
| - | 
| -                @Override | 
| -                public void showHandle() { | 
| -                    super.showHandle(); | 
| -                } | 
| -            }; | 
| - | 
| -            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(); | 
| @@ -2203,7 +2080,7 @@ public class ContentViewCore | 
| public void onDestroyActionMode() { | 
| mActionMode = null; | 
| if (mUnselectAllOnActionModeDismiss) { | 
| -                    hideHandles(); | 
| +                    hideTextHandles(); | 
| if (isSelectionEditable()) { | 
| int selectionEnd = Selection.getSelectionEnd(mEditable); | 
| mInputConnection.setSelection(selectionEnd, selectionEnd); | 
| @@ -2247,6 +2124,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); | 
| @@ -2269,59 +2194,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); | 
| } | 
|  | 
| /** | 
| @@ -2386,7 +2262,6 @@ public class ContentViewCore | 
|  | 
| final boolean needHidePopupZoomer = contentSizeChanged || scrollChanged; | 
| final boolean needUpdateZoomControls = scaleLimitsChanged || scrollChanged; | 
| -        final boolean needTemporarilyHideHandles = scrollChanged; | 
|  | 
| if (needHidePopupZoomer) mPopupZoomer.hide(true); | 
|  | 
| @@ -2414,9 +2289,7 @@ public class ContentViewCore | 
| } | 
| } | 
|  | 
| -        if (needTemporarilyHideHandles) temporarilyHideTextHandles(); | 
| if (needUpdateZoomControls) mZoomControlsDelegate.updateZoomControls(); | 
| -        if (contentOffsetChanged) updateHandleScreenPositions(); | 
|  | 
| // Update offsets for fullscreen. | 
| final float controlsOffsetPix = controlsOffsetYCss * deviceScale; | 
| @@ -2436,6 +2309,7 @@ public class ContentViewCore | 
| boolean isNonImeChange) { | 
| TraceEvent.begin(); | 
| mFocusedNodeEditable = (textInputType != ImeAdapter.getTextInputTypeNone()); | 
| +        if (!mFocusedNodeEditable) hidePastePopup(); | 
|  | 
| mImeAdapter.updateKeyboardVisibility( | 
| nativeImeAdapterAndroid, textInputType, showImeIfNeeded); | 
| @@ -2505,7 +2379,6 @@ public class ContentViewCore | 
| private void showDisambiguationPopup(Rect targetRect, Bitmap zoomedBitmap) { | 
| mPopupZoomer.setBitmap(zoomedBitmap); | 
| mPopupZoomer.show(targetRect); | 
| -        temporarilyHideTextHandles(); | 
| } | 
|  | 
| @SuppressWarnings("unused") | 
| @@ -2516,84 +2389,31 @@ public class ContentViewCore | 
|  | 
| @SuppressWarnings("unused") | 
| @CalledByNative | 
| -    private void onSelectionChanged(String text) { | 
| -        mLastSelectedText = text; | 
| -        getContentViewClient().onSelectionChanged(text); | 
| -    } | 
| +    private PopupTouchHandleDrawable createPopupTouchHandleDrawable() { | 
| +        if (mTouchHandleDelegate == null) { | 
| +            mTouchHandleDelegate = new PopupTouchHandleDrawableDelegate() { | 
| +                public View getParent() { | 
| +                    return getContainerView(); | 
| +                } | 
|  | 
| -    @SuppressWarnings("unused") | 
| -    @CalledByNative | 
| -    private void showSelectionHandlesAutomatically() { | 
| -        getSelectionHandleController().allowAutomaticShowing(); | 
| +                public PositionObserver getParentPositionObserver() { | 
| +                    return mPositionObserver; | 
| +                } | 
| + | 
| +                public boolean onTouchHandleEvent(MotionEvent event) { | 
| +                    final boolean isTouchHandleEvent = true; | 
| +                    return onTouchEventImpl(event, isTouchHandleEvent); | 
| +                } | 
| +            }; | 
| +        } | 
| +        return new PopupTouchHandleDrawable(mTouchHandleDelegate); | 
| } | 
|  | 
| @SuppressWarnings("unused") | 
| @CalledByNative | 
| -    private void onSelectionBoundsChanged(Rect anchorRectDip, int anchorDir, Rect focusRectDip, | 
| -            int focusDir, boolean isAnchorFirst) { | 
| -        // All coordinates are in DIP. | 
| -        int x1 = anchorRectDip.left; | 
| -        int y1 = anchorRectDip.bottom; | 
| -        int x2 = focusRectDip.left; | 
| -        int y2 = focusRectDip.bottom; | 
| - | 
| -        if (x1 != x2 || y1 != y2 || | 
| -                (mSelectionHandleController != null && mSelectionHandleController.isDragging())) { | 
| -            if (mInsertionHandleController != null) { | 
| -                mInsertionHandleController.hide(); | 
| -            } | 
| -            if (isAnchorFirst) { | 
| -                mStartHandlePoint.setLocalDip(x1, y1); | 
| -                mEndHandlePoint.setLocalDip(x2, y2); | 
| -            } else { | 
| -                mStartHandlePoint.setLocalDip(x2, y2); | 
| -                mEndHandlePoint.setLocalDip(x1, y1); | 
| -            } | 
| - | 
| -            boolean wereSelectionHandlesShowing = getSelectionHandleController().isShowing(); | 
| - | 
| -            getSelectionHandleController().onSelectionChanged(anchorDir, focusDir); | 
| -            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 (x1 != 0 && y1 != 0 && mFocusedNodeEditable) { | 
| -                // Selection is a caret, and a text field is focused. | 
| -                if (mSelectionHandleController != null) { | 
| -                    mSelectionHandleController.hide(); | 
| -                } | 
| -                mInsertionHandlePoint.setLocalDip(x1, y1); | 
| - | 
| -                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 | 
| -                if (mSelectionHandleController != null) { | 
| -                    mSelectionHandleController.hideAndDisallowAutomaticShowing(); | 
| -                } | 
| -                if (mInsertionHandleController != null) { | 
| -                    mInsertionHandleController.hideAndDisallowAutomaticShowing(); | 
| -                } | 
| -            } | 
| -            mHasSelection = false; | 
| -        } | 
| -        if (isSelectionHandleShowing() || isInsertionHandleShowing()) { | 
| -            mPositionObserver.addListener(mPositionListener); | 
| -        } | 
| +    private void onSelectionChanged(String text) { | 
| +        mLastSelectedText = text; | 
| +        getContentViewClient().onSelectionChanged(text); | 
| } | 
|  | 
| @SuppressWarnings("unused") | 
| @@ -2606,10 +2426,28 @@ 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(); | 
| +                        hideTextHandles(); | 
| +                    } | 
| +                    public boolean canPaste() { | 
| +                        if (!mFocusedNodeEditable) return false; | 
| +                        return ((ClipboardManager) mContext.getSystemService( | 
| +                                Context.CLIPBOARD_SERVICE)).hasPrimaryClip(); | 
| +                    } | 
| +                }); | 
| +        } | 
| +        return mPastePopupMenu; | 
| } | 
|  | 
| @SuppressWarnings("unused") | 
| @@ -3294,7 +3132,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); | 
| @@ -3339,6 +3178,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); | 
|  |