CJBS
CJBS

Reputation: 15685

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

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).

Upvotes: 24

Views: 44222

Answers (16)

Said Torres
Said Torres

Reputation: 653

With this simple code you can disable Text Selection Toolbar:

// MainActivity.java
@Override
public void onActionModeStarted(android.view.ActionMode mode) {
    mode.getMenu().clear();
    super.onActionModeStarted(mode);
}

Upvotes: 2

Benjamin Charais
Benjamin Charais

Reputation: 1368

You can completely remove the menuItem by doing the following:

Java:

ActionMode.Callback callback = new ActionMode.Callback() {
            @Override
            public boolean onCreateActionMode(ActionMode mode, Menu menu) {
                return true;
            }

            @Override
            public boolean onPrepareActionMode(ActionMode mode, Menu menu) {
                if (menu != null) {
                    menu.removeItem(android.R.id.paste);
                }
                return true;
            }

            @Override
            public boolean onActionItemClicked(ActionMode mode, MenuItem item) {
                return false;
            }

            @Override
            public void onDestroyActionMode(ActionMode mode) {

            }
        };

        mEditText.setCustomInsertionActionModeCallback(callback);

        mEditText.setCustomSelectionActionModeCallback(callback);

Kotlin:

val callback = object : ActionMode.Callback {
    override fun onActionItemClicked(mode: ActionMode?, item: MenuItem?): Boolean {
        return false
    }

    override fun onCreateActionMode(mode: ActionMode?, menu: Menu?): Boolean {
        return true
    }

    override fun onPrepareActionMode(mode: ActionMode?, menu: Menu?): Boolean {
        menu?.removeItem(android.R.id.paste)
        return true
    }

    override fun onDestroyActionMode(mode: ActionMode?) {}
}

then for use site in EditText :

fun preventPaste() {
    customInsertionActionModeCallback = callback
    customSelectionActionModeCallback = callback
}

Upvotes: 6

Harsh Mittal
Harsh Mittal

Reputation: 459

I just used some of the above solutions in Android 11 and it's working fine, you can use the below gist. https://gist.github.com/harshmittal2810/26429eb426dd1b31750cb33b47f449a6 enter image description here

Upvotes: -1

mani
mani

Reputation: 799

It got it fixed with all the mentioned below 3 changes

fun TextView.disableCopyPaste() {
isLongClickable = false.  //  change 1 ,  disable Long click
setTextIsSelectable(false). //  change 2  ,  disable text selection click

//change 3 ,  return false from all actionmode 
 customSelectionActionModeCallback = object : ActionMode.Callback {
    override fun onCreateActionMode(mode: ActionMode?, menu: Menu): Boolean {
        return false
    }

    override fun onPrepareActionMode(mode: ActionMode?, menu: Menu): Boolean {
        return false
    }

    override fun onActionItemClicked(mode: ActionMode?, item: MenuItem): Boolean {
        return false
    }

    override fun onDestroyActionMode(mode: ActionMode?) {}
}

}

Upvotes: 2

Richa
Richa

Reputation: 3193

I have found one easy solution.Hope it will help to someone, Extend Edittetxt class and ovverride below methods. Same way if you want to disable other options by comparing menu.getItem(i).getTitle() you can do it.

private class ActionModeCallbackInterceptor implements ActionMode.Callback
    {
        public boolean onCreateActionMode(ActionMode mode, Menu menu) {
            return true;
        }

        public boolean onPrepareActionMode(ActionMode mode, Menu menu) {
            for(int i =0;i<menu.size();i++){
                if(menu.getItem(i).getTitle().toString().equals("Clipboard") 
               || menu.getItem(i).getTitle().toString().equals("Paste")) {
                    menu.getItem(i).setVisible(false);
                }
            }
              return false;
        }

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

Upvotes: 0

Oleksandr Kucherenko
Oleksandr Kucherenko

Reputation: 2039

Just override one method:

@Override
protected MovementMethod getDefaultMovementMethod() {
    // we don't need arrow key, return null will also disable the copy/paste/cut pop-up menu.
    return null;
}

Upvotes: -1

Cheok Yan Cheng
Cheok Yan Cheng

Reputation: 42710

I found a simple yet reliable way. The idea is the consume away the touch event, to prevent the touch event reach underlining default code.

  1. To disable copy/paste popup.
  2. To disable text selection handler.
  3. Still showing cursor at end of text.
  4. Still showing keyboard.

maskedEditText.setOnTouchListener(new View.OnTouchListener() {
    @Override
    public boolean onTouch(View view, MotionEvent motionEvent) {
        focusAndShowKeyboard(view.getContext(), maskedEditText);
        // Consume the event.
        return true;
    }
});

private static void focusAndShowKeyboard(Context context, EditText editText) {
    editText.requestFocus();
    InputMethodManager imm = (InputMethodManager) context.getSystemService(Context.INPUT_METHOD_SERVICE);
    imm.toggleSoftInput(InputMethodManager.SHOW_FORCED, InputMethodManager.HIDE_IMPLICIT_ONLY);
}

enter image description here

Note, blinking cursor is still showing at end of text. Just that the screenshot unable to capture it.

Upvotes: 0

Mustafa
Mustafa

Reputation: 69

If you need remove the PASTE suggestion, clear the clipboard before the long click.

//class 
ClipboardManager clipboard;

//oncreate 
clipboard = (ClipboardManager) getSystemService(Context.CLIPBOARD_SERVICE);
ClipData clip = ClipData.newPlainText("","");
clipboard.setPrimaryClip(clip);

Upvotes: 0

Anton Tananaev
Anton Tananaev

Reputation: 2523

Here is a hack to disable "paste" popup. You have to override EditText method:

@Override
public int getSelectionStart() {
    for (StackTraceElement element : Thread.currentThread().getStackTrace()) {
        if (element.getMethodName().equals("canPaste")) {
            return -1;
        }
    }
    return super.getSelectionStart();
}

This solution works on newer versions of Android as well, unlike the accepted answer.

Upvotes: 12

CJBS
CJBS

Reputation: 15685

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.

Upvotes: 28

Thalescm
Thalescm

Reputation: 155

None of the above solutions worked for me. I've managed to do my solution (explanation after), which disabled pasting anything on the EditText while maintaining all other operations valid.
Mainly, you have to override this method on your implementation of EditText:

@Override
public boolean onTextContextMenuItem (int id) {
    if (id == android.R.id.paste) return false;

    return super.onTextContextMenuItem(id);
}

So investigating EditText code, after all checks, paste (and all ContextMenu actions on the EditText) happen at a method called onTextContextMenuItem:

public boolean onTextContextMenuItem(int id) {
    int min = 0;
    int max = mText.length();

    if (isFocused()) {
        final int selStart = getSelectionStart();
        final int selEnd = getSelectionEnd();

        min = Math.max(0, Math.min(selStart, selEnd));
        max = Math.max(0, Math.max(selStart, selEnd));
    }

    switch (id) {
        case ID_SELECT_ALL:
            // This does not enter text selection mode. Text is highlighted, so that it can be
            // bulk edited, like selectAllOnFocus does. Returns true even if text is empty.
            selectAllText();
            return true;

        case ID_PASTE:
            paste(min, max);
            return true;

        case ID_CUT:
            setPrimaryClip(ClipData.newPlainText(null, getTransformedText(min, max)));
            deleteText_internal(min, max);
            stopSelectionActionMode();
            return true;

        case ID_COPY:
            setPrimaryClip(ClipData.newPlainText(null, getTransformedText(min, max)));
            stopSelectionActionMode();
            return true;
    }
    return false;
}

If you notice, pasting will only occur when id == ID_PASTE, so, again, looking at the EditText code:

static final int ID_PASTE = android.R.id.paste;

Upvotes: 0

Libin
Libin

Reputation: 17085

I don't find a way to hide the menu popup , But you can disable from pasting if user tap on the menu

Create a custom EditText and override the onTextContextMenuItem method and return false for android.R.id.paste and android.R.id.pasteAsPlainText menu id's.

@Override
public boolean onTextContextMenuItem(int id) {
    switch (id){
        case android.R.id.paste:
        case android.R.id.pasteAsPlainText:
            return false;

    }
    return super.onTextContextMenuItem(id);
}

Upvotes: 9

user1242611
user1242611

Reputation: 75

Use this in java file

if (android.os.Build.VERSION.SDK_INT < 11) {
    editText.setOnCreateContextMenuListener(new OnCreateContextMenuListener() {

        @Override`enter code here`
        public void onCreateContextMenu(ContextMenu menu, View v,
                ContextMenuInfo menuInfo) {
            // TODO Auto-generated method stub
            menu.clear();
        }
    });
} else {
    editText.setCustomSelectionActionModeCallback(new ActionMode.Callback() {

        public boolean onPrepareActionMode(ActionMode mode, Menu menu) {
            // TODO Auto-generated method stub
            return false;
        }

        public void onDestroyActionMode(ActionMode mode) {
            // TODO Auto-generated method stub

        }

        public boolean onCreateActionMode(ActionMode mode, Menu menu) {
            // TODO Auto-generated method stub
            return false;
        }

        public boolean onActionItemClicked(ActionMode mode,
                MenuItem item) {
            // TODO Auto-generated method stub
            return false;
        }`enter code here`
    });
}


With this code also add android:textSelectHandle="@drawable/handle"
<shape xmlns:android="http://schemas.android.com/apk/res/android"
 android:shape="rectangle" >

 <size
    android:height="0dp"
    android:width="0dp" />
 </shape>


By Using these two combinations my problem is solved.

Upvotes: 1

Vladimir Ryhlitskiy
Vladimir Ryhlitskiy

Reputation: 257

Found another solution when the blue view (insertion controller) is not appeared at all. I used reflection to set target boolean field of Editor class. Look at the android.widget.Editor and android.widget.TextView for more details.

Add the following code into your custom EditText (with all previous code in this topic):

@Override
public boolean onTouchEvent(MotionEvent event) {
    if (event.getAction() == MotionEvent.ACTION_DOWN) {
        // setInsertionDisabled when user touches the view
        this.setInsertionDisabled();
    }
    return super.onTouchEvent(event);
}

/**
 * This method sets TextView#Editor#mInsertionControllerEnabled field to false
 * to return false from the Editor#hasInsertionController() method to PREVENT showing
 * of the insertionController from EditText
 * The Editor#hasInsertionController() method is called in  Editor#onTouchUpEvent(MotionEvent event) method.
 */

private void setInsertionDisabled() {
    try {
        Field editorField = TextView.class.getDeclaredField("mEditor");
        editorField.setAccessible(true);
        Object editorObject = editorField.get(this);

        Class editorClass = Class.forName("android.widget.Editor");
        Field mInsertionControllerEnabledField = editorClass.getDeclaredField("mInsertionControllerEnabled");
        mInsertionControllerEnabledField.setAccessible(true);
        mInsertionControllerEnabledField.set(editorObject, false);
    }
    catch (Exception ignored) {
        // ignore exception here
    }
}

Also, maybe you can find the better place than onTouch() to call the target method.

Tested on Android 5.1

Upvotes: 6

Murtaza Khursheed Hussain
Murtaza Khursheed Hussain

Reputation: 15336

or simply just use

yourEditText.setLongClickable(false);

OR in XML

android:longClickable="false"

Update

Actually the user wants to disable the text selection handle itself

1. Create a shape (handle.xml)

 <shape xmlns:android="http://schemas.android.com/apk/res/android"
 android:shape="rectangle" >

 <size
    android:height="0dp"
    android:width="0dp" />
 </shape>

2. In your EditText

 android:textSelectHandle="@drawable/handle"

Upvotes: 22

Hardik Chauhan
Hardik Chauhan

Reputation: 2746

You can use this code:

if (android.os.Build.VERSION.SDK_INT < 11) {
    editText.setOnCreateContextMenuListener(new OnCreateContextMenuListener() {

        @Override
        public void onCreateContextMenu(ContextMenu menu, View v,
                ContextMenuInfo menuInfo) {
            // TODO Auto-generated method stub
            menu.clear();
        }
    });
} else {
    editText.setCustomSelectionActionModeCallback(new ActionMode.Callback() {

        public boolean onPrepareActionMode(ActionMode mode, Menu menu) {
            // TODO Auto-generated method stub
            return false;
        }

        public void onDestroyActionMode(ActionMode mode) {
            // TODO Auto-generated method stub

        }

        public boolean onCreateActionMode(ActionMode mode, Menu menu) {
            // TODO Auto-generated method stub
            return false;
        }

        public boolean onActionItemClicked(ActionMode mode,
                MenuItem item) {
            // TODO Auto-generated method stub
            return false;
        }
    });
}

Returning false from onCreateActionMode will disable the cut,copy,paste options in API level greater than 11.

Upvotes: -1

Related Questions