EditText: Disable Paste/Replace menu pop-up on Text Selection Handler click event

CJBS picture CJBS · Jan 9, 2015 · Viewed 32.3k times · Source

My goal is to have an EditText that has no fancy features, just the Text Selection Handler for moving the cursor more easily -- so no context menus or pop-ups.

I've disabled the appearance of the text editing function actionbar (copy/Paste etc.) by consuming the ActionMode Callback event, as per this solution.

The middle Middle Text Select Handle (see image below) still appears when text exists in the field and a click occurs within the text. Great! I want to keep this behaviour. What I DON'T want is the "PASTE" menu to appear when the Text Select Handle itself is clicked.

Text selection handle with paste menu

I have also disabled long-click input for the EditText by setting android:longClickable="false" in the styles XML. Disabling the long click prevents the "Paste/Replace" menu from appearing when the mouse is clicked and held (i.e. long touch), however when the mouse is clicked (single touch) within the text, the text selection handle appears, and when the text selection handle itself is clicked, then the "paste" menu option appears (when there's text in the clipboard). This is what I'm trying to prevent.

From what I can see from the source, the ActionPopupWindow is what pops up with the PASTE/REPLACE options. ActionPopupWindow is a protected variable (mActionPopupWindow) in the private abstract class HandleView within public class android.widget.Editor...

Short of disabling the clipboard service or editing the Android Source code, is there a way that I can prevent this from showing? I tried to define a new style for android:textSelectHandleWindowStyle, and set android:visibility to gone, but it didn't work (app froze for a while when it would otherwise have shown).

Answer

CJBS picture CJBS · Mar 6, 2015

Solution: Override isSuggestionsEnabled and canPaste in EditText.

For the quick solution, copy the class below - this class overrides the EditText class, and blocks all events accordingly.

For the gritty details, keep reading.

The solution lies in preventing PASTE/REPLACE menu from appearing in the show() method of the (non-documented) android.widget.Editor class. Before the menu appears, a check is done to if (!canPaste && !canSuggest) return;. The two methods that are used as the basis to set these variables are both in the EditText class:

So incorporating these updates into a class that also has the setCustomSelectionActionModeCallback, and the disabled long-click, here is the full class to prevent all editing (but still display the text selection handler) for controlling the cursor:

package com.cjbs.widgets;

import android.content.Context;
import android.util.AttributeSet;
import android.view.ActionMode;
import android.view.Menu;
import android.view.MenuItem;
import android.widget.EditText;


/**
 *  This is a thin veneer over EditText, with copy/paste/spell-check removed.
 */
public class NoMenuEditText extends EditText
{
    private final Context context;

    /** This is a replacement method for the base TextView class' method of the same name. This 
     * method is used in hidden class android.widget.Editor to determine whether the PASTE/REPLACE popup
     * appears when triggered from the text insertion handle. Returning false forces this window
     * to never appear.
     * @return false
     */
    boolean canPaste()
    {
       return false;
    }

    /** This is a replacement method for the base TextView class' method of the same name. This method
     * is used in hidden class android.widget.Editor to determine whether the PASTE/REPLACE popup
     * appears when triggered from the text insertion handle. Returning false forces this window
     * to never appear.
     * @return false
     */
    @Override
    public boolean isSuggestionsEnabled()
    {
        return false;
    }

    public NoMenuEditText(Context context)
    {
        super(context);
        this.context = context;
        init();
    }

    public NoMenuEditText(Context context, AttributeSet attrs)
    {
        super(context, attrs);
        this.context = context;
        init();
    }

    public NoMenuEditText(Context context, AttributeSet attrs, int defStyle)
    {
        super(context, attrs, defStyle);
        this.context = context;
        init();
    }

    private void init()
    {
        this.setCustomSelectionActionModeCallback(new ActionModeCallbackInterceptor());
        this.setLongClickable(false);
    }


    /**
     * Prevents the action bar (top horizontal bar with cut, copy, paste, etc.) from appearing
     * by intercepting the callback that would cause it to be created, and returning false.
     */
    private class ActionModeCallbackInterceptor implements ActionMode.Callback
    {
        private final String TAG = NoMenuEditText.class.getSimpleName();

        public boolean onCreateActionMode(ActionMode mode, Menu menu) { return false; }
        public boolean onPrepareActionMode(ActionMode mode, Menu menu) { return false; }
        public boolean onActionItemClicked(ActionMode mode, MenuItem item) { return false; }
        public void onDestroyActionMode(ActionMode mode) {}
    }
} 

I've tested this in Android v4.4.2 and v4.4.3.