Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit f4dceb19 authored by Gilles Debunne's avatar Gilles Debunne
Browse files

Customized text selection action mode.

Refactored to use a custom Callback instance instead.

This instance can be shared by different TextView, which no longer have to
be overloaded.

Change-Id: I4749905d8e2057dab2b3ded62bd7c388d13d4e57
parent d66c63e5
Loading
Loading
Loading
Loading
+25 −1
Original line number Diff line number Diff line
@@ -246622,6 +246622,17 @@
 visibility="public"
>
</method>
<method name="getCustomSelectionActionModeCallback"
 return="android.view.ActionMode.Callback"
 abstract="false"
 native="false"
 synchronized="false"
 static="false"
 final="false"
 deprecated="not deprecated"
 visibility="public"
>
</method>
<method name="getDefaultEditable"
 return="boolean"
 abstract="false"
@@ -247420,6 +247431,19 @@
<parameter name="visible" type="boolean">
</parameter>
</method>
<method name="setCustomSelectionActionModeCallback"
 return="void"
 abstract="false"
 native="false"
 synchronized="false"
 static="false"
 final="false"
 deprecated="not deprecated"
 visibility="public"
>
<parameter name="actionModeCallback" type="android.view.ActionMode.Callback">
</parameter>
</method>
<method name="setEditableFactory"
 return="void"
 abstract="false"
@@ -250067,7 +250091,7 @@
 deprecated="not deprecated"
 visibility="public"
>
<parameter name="arg0" type="T">
<parameter name="t" type="T">
</parameter>
</method>
</interface>
+69 −28
Original line number Diff line number Diff line
@@ -89,6 +89,7 @@ import android.util.FloatMath;
import android.util.Log;
import android.util.TypedValue;
import android.view.ActionMode;
import android.view.ActionMode.Callback;
import android.view.ContextMenu;
import android.view.DragEvent;
import android.view.Gravity;
@@ -314,6 +315,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
    Drawable mSelectHandleCenter;

    private int mLastDownPositionX, mLastDownPositionY;
    private Callback mCustomSelectionActionModeCallback;

    /*
     * Kick-start the font cache for the zygote process (to pay the cost of
@@ -6869,7 +6871,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
                }

                if (mSelectAllOnFocus) {
                    Selection.setSelection((Spannable) mText, 0, mText.length());
                    selectAll();
                }

                mTouchFocusSelected = true;
@@ -7401,11 +7403,9 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
            return false;
        }

        if (mText.length() > 0 && hasSelection()) {
            if (mText instanceof Editable && mInput != null) {
        if (mText.length() > 0 && hasSelection() && mText instanceof Editable && mInput != null) {
            return true;
        }
        }

        return false;
    }
@@ -7524,13 +7524,17 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
        return (int) (range & 0x00000000FFFFFFFFL);
    }

    private void selectAll() {
        Selection.setSelection((Spannable) mText, 0, mText.length());
    }

    private void selectCurrentWord() {
        if (hasPasswordTransformationMethod()) {
            // selectCurrentWord is not available on a password field and would return an
            // arbitrary 10-charater selection around pressed position. Select all instead.
            // Note that cut/copy menu entries are not available for passwords.
            // This is however useful to delete or paste to replace the entire content.
            Selection.setSelection((Spannable) mText, 0, mText.length());
            selectAll();
            return;
        }

@@ -7862,16 +7866,36 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
    }

    /**
     * Provides the callback used to start a selection action mode.
     * If provided, this ActionMode.Callback will be used to create the ActionMode when text
     * selection is initiated in this View.
     *
     * The standard implementation populates the menu with a subset of Select All, Cut, Copy and
     * Paste actions, depending on what this View supports.
     *
     * @return A callback instance that will be used to start selection mode, or null if selection
     * mode is not available.
     * A custom implementation can add new entries in the default menu in its
     * {@link ActionMode.Callback#onPrepareActionMode(ActionMode, Menu)} method. The default actions
     * can also be removed from the menu using {@link Menu#removeItem(int)} and passing
     * {@link android.R.id#selectAll}, {@link android.R.id#cut}, {@link android.R.id#copy} or
     * {@link android.R.id#paste} ids as parameters.
     *
     * Action click events should be handled by the custom implementation of
     * {@link ActionMode.Callback#onActionItemClicked(ActionMode, MenuItem)}.
     *
     * Note that text selection mode is not started when a TextView receives focus and the
     * {@link android.R.attr#selectAllOnFocus} flag has been set. The content is highlighted in
     * that case, to allow for quick replacement.
     */
    private ActionMode.Callback getActionModeCallback() {
        if (canSelectText()) {
            return new SelectionActionModeCallback();
    public void setCustomSelectionActionModeCallback(ActionMode.Callback actionModeCallback) {
        mCustomSelectionActionModeCallback = actionModeCallback;
    }
        return null;

    /**
     * Retrieves the value set in {@link #setCustomSelectionActionModeCallback}. Default is null.
     *
     * @return The current custom selection callback.
     */
    public ActionMode.Callback getCustomSelectionActionModeCallback() {
        return mCustomSelectionActionModeCallback;
    }

    /**
@@ -7884,7 +7908,8 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
            return false;
        }

        ActionMode.Callback actionModeCallback = getActionModeCallback();
        selectCurrentWord();
        ActionMode.Callback actionModeCallback = new SelectionActionModeCallback();
        if (actionModeCallback != null) {
            mSelectionActionMode = startActionMode(actionModeCallback);
            return mSelectionActionMode != null;
@@ -7950,6 +7975,12 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
        sLastCutOrCopyTime = SystemClock.uptimeMillis();
    }

    /**
     * An ActionMode Callback class that is used to provide actions while in text selection mode.
     *
     * The default callback provides a subset of Select All, Cut, Copy and Paste actions, depending
     * on which of these this TextView supports.
     */
    private class SelectionActionModeCallback implements ActionMode.Callback {

        @Override
@@ -7968,48 +7999,47 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
            mode.setTitle(mContext.getString(com.android.internal.R.string.textSelectionCABTitle));
            mode.setSubtitle(null);

            boolean atLeastOne = false;

            if (canSelectText()) {
                selectCurrentWord();

                menu.add(0, ID_SELECT_ALL, 0, com.android.internal.R.string.selectAll).
                    setAlphabeticShortcut('a').
                    setShowAsAction(
                            MenuItem.SHOW_AS_ACTION_ALWAYS | MenuItem.SHOW_AS_ACTION_WITH_TEXT);
                atLeastOne = true;
            }

            if (canCut()) {
                menu.add(0, ID_CUT, 0, com.android.internal.R.string.cut).
                    setIcon(styledAttributes.getResourceId(R.styleable.Theme_actionModeCutDrawable, 0)).
                    setIcon(styledAttributes.getResourceId(
                            R.styleable.Theme_actionModeCutDrawable, 0)).
                    setAlphabeticShortcut('x').
                    setShowAsAction(
                            MenuItem.SHOW_AS_ACTION_ALWAYS | MenuItem.SHOW_AS_ACTION_WITH_TEXT);
                atLeastOne = true;
            }

            if (canCopy()) {
                menu.add(0, ID_COPY, 0, com.android.internal.R.string.copy).
                    setIcon(styledAttributes.getResourceId(R.styleable.Theme_actionModeCopyDrawable, 0)).
                    setIcon(styledAttributes.getResourceId(
                            R.styleable.Theme_actionModeCopyDrawable, 0)).
                    setAlphabeticShortcut('c').
                    setShowAsAction(
                            MenuItem.SHOW_AS_ACTION_ALWAYS | MenuItem.SHOW_AS_ACTION_WITH_TEXT);
                atLeastOne = true;
            }

            if (canPaste()) {
                menu.add(0, ID_PASTE, 0, com.android.internal.R.string.paste).
                        setIcon(styledAttributes.getResourceId(R.styleable.Theme_actionModePasteDrawable, 0)).
                        setIcon(styledAttributes.getResourceId(
                                R.styleable.Theme_actionModePasteDrawable, 0)).
                        setAlphabeticShortcut('v').
                        setShowAsAction(
                                MenuItem.SHOW_AS_ACTION_ALWAYS | MenuItem.SHOW_AS_ACTION_WITH_TEXT);
                atLeastOne = true;
            }

            styledAttributes.recycle();

            if (atLeastOne) {
            if (mCustomSelectionActionModeCallback != null) {
                mCustomSelectionActionModeCallback.onCreateActionMode(mode, menu);
            }

            if (menu.hasVisibleItems() || mode.getCustomView() != null) {
                getSelectionController().show();
                return true;
            } else {
@@ -8019,15 +8049,23 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener

        @Override
        public boolean onPrepareActionMode(ActionMode mode, Menu menu) {
            if (mCustomSelectionActionModeCallback != null) {
                return mCustomSelectionActionModeCallback.onPrepareActionMode(mode, menu);
            }
            return true;
        }

        @Override
        public boolean onActionItemClicked(ActionMode mode, MenuItem item) {
            if (mCustomSelectionActionModeCallback != null &&
                 mCustomSelectionActionModeCallback.onActionItemClicked(mode, item)) {
                return true;
            }

            final int itemId = item.getItemId();

            if (itemId == ID_SELECT_ALL) {
                Selection.setSelection((Spannable) mText, 0, mText.length());
                selectAll();
                // Update controller positions after selection change.
                if (hasSelectionController()) {
                    getSelectionController().show();
@@ -8070,6 +8108,9 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener

        @Override
        public void onDestroyActionMode(ActionMode mode) {
            if (mCustomSelectionActionModeCallback != null) {
                mCustomSelectionActionModeCallback.onDestroyActionMode(mode);
            }
            Selection.setSelection((Spannable) mText, getSelectionStart());
            hideSelectionModifierCursorController();
            mSelectionActionMode = null;