OSDN Git Service

Separate the SelectionActionMode into Insertion and Selection.
authorClara Bayarri <clarabayarri@google.com>
Tue, 2 Jun 2015 19:03:45 +0000 (20:03 +0100)
committerClara Bayarri <clarabayarri@google.com>
Thu, 4 Jun 2015 19:46:19 +0000 (20:46 +0100)
When we got rid of the paste popup, we merged it into the Selection
ActionMode and moved all its invocations to the ActionMode. Some apps
actually want the paste popup without the Selection ActionMode, hence
separating them again allows them to cancel the one they want.

Bug: 21571422
Change-Id: I91bcd0d9c3e68d9c736698fe0bec010b4c9f5cf3

api/current.txt
api/system-current.txt
core/java/android/inputmethodservice/ExtractEditText.java
core/java/android/widget/Editor.java
core/java/android/widget/TextView.java

index 4ef6a85..dfbfa33 100644 (file)
@@ -41523,6 +41523,7 @@ package android.widget {
     method public int getCompoundPaddingTop();
     method public final int getCurrentHintTextColor();
     method public final int getCurrentTextColor();
+    method public android.view.ActionMode.Callback getCustomInsertionActionModeCallback();
     method public android.view.ActionMode.Callback getCustomSelectionActionModeCallback();
     method protected boolean getDefaultEditable();
     method protected android.text.method.MovementMethod getDefaultMovementMethod();
@@ -41625,6 +41626,7 @@ package android.widget {
     method public void setCompoundDrawablesWithIntrinsicBounds(int, int, int, int);
     method public void setCompoundDrawablesWithIntrinsicBounds(android.graphics.drawable.Drawable, android.graphics.drawable.Drawable, android.graphics.drawable.Drawable, android.graphics.drawable.Drawable);
     method public void setCursorVisible(boolean);
+    method public void setCustomInsertionActionModeCallback(android.view.ActionMode.Callback);
     method public void setCustomSelectionActionModeCallback(android.view.ActionMode.Callback);
     method public final void setEditableFactory(android.text.Editable.Factory);
     method public void setElegantTextHeight(boolean);
index 0263336..0d473d4 100644 (file)
@@ -44102,6 +44102,7 @@ package android.widget {
     method public int getCompoundPaddingTop();
     method public final int getCurrentHintTextColor();
     method public final int getCurrentTextColor();
+    method public android.view.ActionMode.Callback getCustomInsertionActionModeCallback();
     method public android.view.ActionMode.Callback getCustomSelectionActionModeCallback();
     method protected boolean getDefaultEditable();
     method protected android.text.method.MovementMethod getDefaultMovementMethod();
@@ -44204,6 +44205,7 @@ package android.widget {
     method public void setCompoundDrawablesWithIntrinsicBounds(int, int, int, int);
     method public void setCompoundDrawablesWithIntrinsicBounds(android.graphics.drawable.Drawable, android.graphics.drawable.Drawable, android.graphics.drawable.Drawable, android.graphics.drawable.Drawable);
     method public void setCursorVisible(boolean);
+    method public void setCustomInsertionActionModeCallback(android.view.ActionMode.Callback);
     method public void setCustomSelectionActionModeCallback(android.view.ActionMode.Callback);
     method public final void setEditableFactory(android.text.Editable.Factory);
     method public void setElegantTextHeight(boolean);
index 48b604c..f965f54 100644 (file)
@@ -106,7 +106,7 @@ public class ExtractEditText extends EditText {
         if (mIME != null && mIME.onExtractTextContextMenuItem(id)) {
             // Mode was started on Extracted, needs to be stopped here.
             // Cut and paste will change the text, which stops selection mode.
-            if (id == android.R.id.copy) stopSelectionActionMode();
+            if (id == android.R.id.copy) stopTextActionMode();
             return true;
         }
         return super.onTextContextMenuItem(id);
index 20b898d..c9c2248 100644 (file)
@@ -134,7 +134,8 @@ public class Editor {
     // Cursor Controllers.
     InsertionPointCursorController mInsertionPointCursorController;
     SelectionModifierCursorController mSelectionModifierCursorController;
-    ActionMode mSelectionActionMode;
+    // Action mode used when text is selected or when actions on an insertion cursor are triggered.
+    ActionMode mTextActionMode;
     boolean mInsertionControllerEnabled;
     boolean mSelectionControllerEnabled;
 
@@ -205,13 +206,14 @@ public class Editor {
 
     float mLastDownPositionX, mLastDownPositionY;
     Callback mCustomSelectionActionModeCallback;
+    Callback mCustomInsertionActionModeCallback;
 
     // Set when this TextView gained focus with some text selected. Will start selection mode.
     boolean mCreatedWithASelection;
 
     boolean mDoubleTap = false;
 
-    private Runnable mSelectionModeWithoutSelectionRunnable;
+    private Runnable mInsertionActionModeRunnable;
 
     // The span controller helps monitoring the changes to which the Editor needs to react:
     // - EasyEditSpans, for which we have some UI to display on attach and on hide
@@ -236,8 +238,8 @@ public class Editor {
     private final Runnable mHideFloatingToolbar = new Runnable() {
         @Override
         public void run() {
-            if (mSelectionActionMode != null) {
-                mSelectionActionMode.snooze(ActionMode.SNOOZE_TIME_DEFAULT);
+            if (mTextActionMode != null) {
+                mTextActionMode.snooze(ActionMode.SNOOZE_TIME_DEFAULT);
             }
         }
     };
@@ -245,8 +247,8 @@ public class Editor {
     private final Runnable mShowFloatingToolbar = new Runnable() {
         @Override
         public void run() {
-            if (mSelectionActionMode != null) {
-                mSelectionActionMode.snooze(0);  // snooze off.
+            if (mTextActionMode != null) {
+                mTextActionMode.snooze(0);  // snooze off.
             }
         }
     };
@@ -310,7 +312,7 @@ public class Editor {
 
     void replace() {
         int middle = (mTextView.getSelectionStart() + mTextView.getSelectionEnd()) / 2;
-        stopSelectionActionMode();
+        stopTextActionMode();
         Selection.setSelection((Spannable) mTextView.getText(), middle);
         showSuggestions();
     }
@@ -343,7 +345,7 @@ public class Editor {
             mTextView.setHasTransientState(false);
 
             // We had an active selection from before, start the selection mode.
-            startSelectionActionModeWithSelection();
+            startSelectionActionMode();
         }
 
         getPositionListener().addSubscriber(mCursorAnchorInfoNotifier, true);
@@ -372,8 +374,8 @@ public class Editor {
         }
 
         // Cancel the single tap delayed runnable.
-        if (mSelectionModeWithoutSelectionRunnable != null) {
-            mTextView.removeCallbacks(mSelectionModeWithoutSelectionRunnable);
+        if (mInsertionActionModeRunnable != null) {
+            mTextView.removeCallbacks(mInsertionActionModeRunnable);
         }
 
         mTextView.removeCallbacks(mHideFloatingToolbar);
@@ -390,7 +392,7 @@ public class Editor {
 
         mPreserveDetachedSelection = true;
         hideControllers();
-        stopSelectionActionMode();
+        stopTextActionMode();
         mPreserveDetachedSelection = false;
         mTemporaryDetach = false;
     }
@@ -586,7 +588,7 @@ public class Editor {
         }
 
         if (!mSelectionControllerEnabled) {
-            stopSelectionActionMode();
+            stopTextActionMode();
             if (mSelectionModifierCursorController != null) {
                 mSelectionModifierCursorController.onDetached();
                 mSelectionModifierCursorController = null;
@@ -984,14 +986,14 @@ public class Editor {
                 mInsertionControllerEnabled) {
             final int offset = mTextView.getOffsetForPosition(mLastDownPositionX,
                     mLastDownPositionY);
-            stopSelectionActionMode();
+            stopTextActionMode();
             Selection.setSelection((Spannable) mTextView.getText(), offset);
             getInsertionController().show();
-            startSelectionActionModeWithoutSelection();
+            startInsertionActionMode();
             handled = true;
         }
 
-        if (!handled && mSelectionActionMode != null) {
+        if (!handled && mTextActionMode != null) {
             if (touchPositionIsInSelection()) {
                 // Start a drag
                 final int start = mTextView.getSelectionStart();
@@ -1001,9 +1003,9 @@ public class Editor {
                 DragLocalState localState = new DragLocalState(mTextView, start, end);
                 mTextView.startDrag(data, getTextThumbnailBuilder(selectedText), localState,
                         View.DRAG_FLAG_GLOBAL);
-                stopSelectionActionMode();
+                stopTextActionMode();
             } else {
-                stopSelectionActionMode();
+                stopTextActionMode();
                 selectCurrentWordAndStartDrag();
             }
             handled = true;
@@ -1101,12 +1103,12 @@ public class Editor {
                 final int selStart = mTextView.getSelectionStart();
                 final int selEnd = mTextView.getSelectionEnd();
                 hideControllers();
-                stopSelectionActionMode();
+                stopTextActionMode();
                 Selection.setSelection((Spannable) mTextView.getText(), selStart, selEnd);
             } else {
                 if (mTemporaryDetach) mPreserveDetachedSelection = true;
                 hideControllers();
-                stopSelectionActionMode();
+                stopTextActionMode();
                 if (mTemporaryDetach) mPreserveDetachedSelection = false;
                 downgradeEasyCorrectionSpans();
             }
@@ -1149,7 +1151,7 @@ public class Editor {
         // We do not hide the span controllers, since they can be added when a new text is
         // inserted into the text view (voice IME).
         hideCursorControllers();
-        stopSelectionActionMode();
+        stopTextActionMode();
     }
 
     private int getLastTapPosition() {
@@ -1216,7 +1218,7 @@ public class Editor {
     }
 
     private void updateFloatingToolbarVisibility(MotionEvent event) {
-        if (mSelectionActionMode != null) {
+        if (mTextActionMode != null) {
             switch (event.getActionMasked()) {
                 case MotionEvent.ACTION_MOVE:
                     hideFloatingToolbar();
@@ -1229,7 +1231,7 @@ public class Editor {
     }
 
     private void hideFloatingToolbar() {
-        if (mSelectionActionMode != null) {
+        if (mTextActionMode != null) {
             mTextView.removeCallbacks(mShowFloatingToolbar);
             // Delay the "hide" a little bit just in case a "show" will happen almost immediately.
             mTextView.postDelayed(mHideFloatingToolbar, 100);
@@ -1237,7 +1239,7 @@ public class Editor {
     }
 
     private void showFloatingToolbar() {
-        if (mSelectionActionMode != null) {
+        if (mTextActionMode != null) {
             mTextView.removeCallbacks(mHideFloatingToolbar);
             // Delay "show" so it doesn't interfere with click confirmations
             // or double-clicks that could "dismiss" the floating toolbar.
@@ -1701,26 +1703,20 @@ public class Editor {
     /**
      * @return true if the selection mode was actually started.
      */
-    private boolean startSelectionActionModeWithoutSelection() {
+    private boolean startInsertionActionMode() {
+        if (mInsertionActionModeRunnable != null) {
+            mTextView.removeCallbacks(mInsertionActionModeRunnable);
+        }
         if (extractedTextModeWillBeStarted()) {
-            // Cancel the single tap delayed runnable.
-            if (mSelectionModeWithoutSelectionRunnable != null) {
-                mTextView.removeCallbacks(mSelectionModeWithoutSelectionRunnable);
-            }
-
             return false;
         }
+        stopTextActionMode();
 
-        if (mSelectionActionMode != null) {
-            // Selection action mode is already started
-            // TODO: revisit invocations to minimize this case.
-            mSelectionActionMode.invalidate();
-            return false;
-        }
-        ActionMode.Callback actionModeCallback = new SelectionActionModeCallback();
-        mSelectionActionMode = mTextView.startActionMode(
+        ActionMode.Callback actionModeCallback =
+                new TextActionModeCallback(false /* hasSelection */);
+        mTextActionMode = mTextView.startActionMode(
                 actionModeCallback, ActionMode.TYPE_FLOATING);
-        return mSelectionActionMode != null;
+        return mTextActionMode != null;
     }
 
     /**
@@ -1730,8 +1726,8 @@ public class Editor {
      *
      * @return true if the selection mode was actually started.
      */
-    boolean startSelectionActionModeWithSelection() {
-        boolean selectionStarted = startSelectionActionModeWithSelectionInternal();
+    boolean startSelectionActionMode() {
+        boolean selectionStarted = startSelectionActionModeInternal();
         if (selectionStarted) {
             getSelectionController().show();
         } else if (getInsertionController() != null) {
@@ -1749,13 +1745,13 @@ public class Editor {
     private boolean selectCurrentWordAndStartDrag() {
         if (extractedTextModeWillBeStarted()) {
             // Cancel the single tap delayed runnable.
-            if (mSelectionModeWithoutSelectionRunnable != null) {
-                mTextView.removeCallbacks(mSelectionModeWithoutSelectionRunnable);
+            if (mInsertionActionModeRunnable != null) {
+                mTextView.removeCallbacks(mInsertionActionModeRunnable);
             }
             return false;
         }
-        if (mSelectionActionMode != null) {
-            mSelectionActionMode.finish();
+        if (mTextActionMode != null) {
+            mTextActionMode.finish();
         }
         if (!checkFieldAndSelectCurrentWord()) {
             return false;
@@ -1784,10 +1780,10 @@ public class Editor {
         return true;
     }
 
-    private boolean startSelectionActionModeWithSelectionInternal() {
-        if (mSelectionActionMode != null) {
+    private boolean startSelectionActionModeInternal() {
+        if (mTextActionMode != null) {
             // Selection action mode is already started
-            mSelectionActionMode.invalidate();
+            mTextActionMode.invalidate();
             return false;
         }
 
@@ -1800,12 +1796,13 @@ public class Editor {
         // Do not start the action mode when extracted text will show up full screen, which would
         // immediately hide the newly created action bar and would be visually distracting.
         if (!willExtract) {
-            ActionMode.Callback actionModeCallback = new SelectionActionModeCallback();
-            mSelectionActionMode = mTextView.startActionMode(
+            ActionMode.Callback actionModeCallback =
+                    new TextActionModeCallback(true /* hasSelection */);
+            mTextActionMode = mTextView.startActionMode(
                     actionModeCallback, ActionMode.TYPE_FLOATING);
         }
 
-        final boolean selectionStarted = mSelectionActionMode != null || willExtract;
+        final boolean selectionStarted = mTextActionMode != null || willExtract;
         if (selectionStarted && !mTextView.isTextSelectable() && mShowSoftInputOnFocus) {
             // Show the IME to be able to replace text, except when selecting non editable text.
             final InputMethodManager imm = InputMethodManager.peekInstance();
@@ -1895,7 +1892,7 @@ public class Editor {
     void onTouchUpEvent(MotionEvent event) {
         boolean selectAllGotFocus = mSelectAllOnFocus && mTextView.didTouchFocusSelect();
         hideControllers();
-        stopSelectionActionMode();
+        stopTextActionMode();
         CharSequence text = mTextView.getText();
         if (!selectAllGotFocus && text.length() > 0) {
             // Move cursor
@@ -1909,8 +1906,8 @@ public class Editor {
             if (!extractedTextModeWillBeStarted()) {
                 if (isCursorInsideEasyCorrectionSpan()) {
                     // Cancel the single tap delayed runnable.
-                    if (mSelectionModeWithoutSelectionRunnable != null) {
-                        mTextView.removeCallbacks(mSelectionModeWithoutSelectionRunnable);
+                    if (mInsertionActionModeRunnable != null) {
+                        mTextView.removeCallbacks(mInsertionActionModeRunnable);
                     }
 
                     mShowSuggestionRunnable = new Runnable() {
@@ -1928,10 +1925,10 @@ public class Editor {
         }
     }
 
-    protected void stopSelectionActionMode() {
-        if (mSelectionActionMode != null) {
+    protected void stopTextActionMode() {
+        if (mTextActionMode != null) {
             // This will hide the mSelectionModifierCursorController
-            mSelectionActionMode.finish();
+            mTextActionMode.finish();
         }
     }
 
@@ -2016,7 +2013,7 @@ public class Editor {
             mSuggestionsPopupWindow = new SuggestionsPopupWindow();
         }
         hideControllers();
-        stopSelectionActionMode();
+        stopTextActionMode();
         mSuggestionsPopupWindow.show();
     }
 
@@ -2024,8 +2021,8 @@ public class Editor {
         if (mPositionListener != null) {
             mPositionListener.onScrollChanged();
         }
-        if (mSelectionActionMode != null) {
-            mSelectionActionMode.invalidateContentRect();
+        if (mTextActionMode != null) {
+            mTextActionMode.invalidateContentRect();
         }
     }
 
@@ -3076,45 +3073,51 @@ public class Editor {
     }
 
     /**
-     * An ActionMode Callback class that is used to provide actions while in text selection mode.
+     * An ActionMode Callback class that is used to provide actions while in text insertion or
+     * selection mode.
      *
-     * The default callback provides a subset of Select All, Cut, Copy and Paste actions, depending
-     * on which of these this TextView supports.
+     * The default callback provides a subset of Select All, Cut, Copy, Paste, Share and Replace
+     * actions, depending on which of these this TextView supports and the current selection.
      */
-    private class SelectionActionModeCallback extends ActionMode.Callback2 {
+    private class TextActionModeCallback extends ActionMode.Callback2 {
         private final Path mSelectionPath = new Path();
         private final RectF mSelectionBounds = new RectF();
-
-        private int mSelectionHandleHeight;
-        private int mInsertionHandleHeight;
-
-        public SelectionActionModeCallback() {
-            SelectionModifierCursorController selectionController = getSelectionController();
-            if (selectionController.mStartHandle == null) {
-                // As these are for initializing selectionController, hide() must be called.
-                selectionController.initDrawables();
-                selectionController.initHandles();
-                selectionController.hide();
-            }
-            mSelectionHandleHeight = Math.max(
-                    mSelectHandleLeft.getMinimumHeight(), mSelectHandleRight.getMinimumHeight());
-            InsertionPointCursorController insertionController = getInsertionController();
-            if (insertionController != null) {
-                insertionController.getHandle();
-                mInsertionHandleHeight = mSelectHandleCenter.getMinimumHeight();
+        private final boolean mHasSelection;
+
+        private int mHandleHeight;
+
+        public TextActionModeCallback(boolean hasSelection) {
+            mHasSelection = hasSelection;
+            if (mHasSelection) {
+                SelectionModifierCursorController selectionController = getSelectionController();
+                if (selectionController.mStartHandle == null) {
+                    // As these are for initializing selectionController, hide() must be called.
+                    selectionController.initDrawables();
+                    selectionController.initHandles();
+                    selectionController.hide();
+                }
+                mHandleHeight = Math.max(
+                        mSelectHandleLeft.getMinimumHeight(),
+                        mSelectHandleRight.getMinimumHeight());
+            } else {
+                InsertionPointCursorController insertionController = getInsertionController();
+                if (insertionController != null) {
+                    insertionController.getHandle();
+                    mHandleHeight = mSelectHandleCenter.getMinimumHeight();
+                }
             }
         }
 
         @Override
         public boolean onCreateActionMode(ActionMode mode, Menu menu) {
-            mode.setTitle(mTextView.getContext().getString(
-                    com.android.internal.R.string.textSelectionCABTitle));
+            mode.setTitle(null);
             mode.setSubtitle(null);
             mode.setTitleOptionalHint(true);
             populateMenuWithItems(menu);
 
-            if (mCustomSelectionActionModeCallback != null) {
-                if (!mCustomSelectionActionModeCallback.onCreateActionMode(mode, menu)) {
+            Callback customCallback = getCustomCallback();
+            if (customCallback != null) {
+                if (!customCallback.onCreateActionMode(mode, menu)) {
                     // The custom mode can choose to cancel the action mode
                     return false;
                 }
@@ -3130,6 +3133,12 @@ public class Editor {
             }
         }
 
+        private Callback getCustomCallback() {
+            return mHasSelection
+                    ? mCustomSelectionActionModeCallback
+                    : mCustomInsertionActionModeCallback;
+        }
+
         private void populateMenuWithItems(Menu menu) {
             if (mTextView.canCut()) {
                 menu.add(0, TextView.ID_CUT, 0, com.android.internal.R.string.cut).
@@ -3192,8 +3201,9 @@ public class Editor {
         public boolean onPrepareActionMode(ActionMode mode, Menu menu) {
             updateReplaceItem(menu);
 
-            if (mCustomSelectionActionModeCallback != null) {
-                return mCustomSelectionActionModeCallback.onPrepareActionMode(mode, menu);
+            Callback customCallback = getCustomCallback();
+            if (customCallback != null) {
+                return customCallback.onPrepareActionMode(mode, menu);
             }
             return true;
         }
@@ -3219,8 +3229,8 @@ public class Editor {
                         item.getIntent(), TextView.PROCESS_TEXT_REQUEST_CODE);
                 return true;
             }
-            if (mCustomSelectionActionModeCallback != null &&
-                 mCustomSelectionActionModeCallback.onActionItemClicked(mode, item)) {
+            Callback customCallback = getCustomCallback();
+            if (customCallback != null && customCallback.onActionItemClicked(mode, item)) {
                 return true;
             }
             return mTextView.onTextContextMenuItem(item.getItemId());
@@ -3228,8 +3238,9 @@ public class Editor {
 
         @Override
         public void onDestroyActionMode(ActionMode mode) {
-            if (mCustomSelectionActionModeCallback != null) {
-                mCustomSelectionActionModeCallback.onDestroyActionMode(mode);
+            Callback customCallback = getCustomCallback();
+            if (customCallback != null) {
+                customCallback.onDestroyActionMode(mode);
             }
 
             /*
@@ -3248,7 +3259,7 @@ public class Editor {
                 mSelectionModifierCursorController.resetTouchOffsets();
             }
 
-            mSelectionActionMode = null;
+            mTextActionMode = null;
         }
 
         @Override
@@ -3263,7 +3274,7 @@ public class Editor {
                 mTextView.getLayout().getSelectionPath(
                         mTextView.getSelectionStart(), mTextView.getSelectionEnd(), mSelectionPath);
                 mSelectionPath.computeBounds(mSelectionBounds, true);
-                mSelectionBounds.bottom += mSelectionHandleHeight;
+                mSelectionBounds.bottom += mHandleHeight;
             } else if (mCursorCount == 2) {
                 // We have a split cursor. In this case, we take the rectangle that includes both
                 // parts of the cursor to ensure we don't obscure either of them.
@@ -3274,7 +3285,7 @@ public class Editor {
                         Math.min(firstCursorBounds.top, secondCursorBounds.top),
                         Math.max(firstCursorBounds.right, secondCursorBounds.right),
                         Math.max(firstCursorBounds.bottom, secondCursorBounds.bottom)
-                            + mInsertionHandleHeight);
+                                + mHandleHeight);
             } else {
                 // We have a single cursor.
                 int line = mTextView.getLayout().getLineForOffset(mTextView.getSelectionStart());
@@ -3284,7 +3295,7 @@ public class Editor {
                         primaryHorizontal,
                         mTextView.getLayout().getLineTop(line),
                         primaryHorizontal + 1,
-                        mTextView.getLayout().getLineTop(line + 1) + mInsertionHandleHeight);
+                        mTextView.getLayout().getLineTop(line + 1) + mHandleHeight);
             }
             // Take TextView's padding and scroll into account.
             int textHorizontalOffset = mTextView.viewportToContentHorizontalOffset();
@@ -3836,25 +3847,25 @@ public class Editor {
                     SystemClock.uptimeMillis() - TextView.sLastCutCopyOrTextChangedTime;
 
             // Cancel the single tap delayed runnable.
-            if (mSelectionModeWithoutSelectionRunnable != null
+            if (mInsertionActionModeRunnable != null
                     && (mDoubleTap || isCursorInsideEasyCorrectionSpan())) {
-                mTextView.removeCallbacks(mSelectionModeWithoutSelectionRunnable);
+                mTextView.removeCallbacks(mInsertionActionModeRunnable);
             }
 
             // Prepare and schedule the single tap runnable to run exactly after the double tap
             // timeout has passed.
             if (!mDoubleTap && !isCursorInsideEasyCorrectionSpan()
                     && (durationSinceCutOrCopy < RECENT_CUT_COPY_DURATION)) {
-                if (mSelectionModeWithoutSelectionRunnable == null) {
-                    mSelectionModeWithoutSelectionRunnable = new Runnable() {
+                if (mInsertionActionModeRunnable == null) {
+                    mInsertionActionModeRunnable = new Runnable() {
                         public void run() {
-                            startSelectionActionModeWithoutSelection();
+                            startInsertionActionMode();
                         }
                     };
                 }
 
                 mTextView.postDelayed(
-                        mSelectionModeWithoutSelectionRunnable,
+                        mInsertionActionModeRunnable,
                         ViewConfiguration.getDoubleTapTimeout() + 1);
             }
 
@@ -3923,15 +3934,15 @@ public class Editor {
 
                         if (distanceSquared < touchSlop * touchSlop) {
                             // Tapping on the handle toggles the selection action mode.
-                            if (mSelectionActionMode != null) {
-                                mSelectionActionMode.finish();
+                            if (mTextActionMode != null) {
+                                mTextActionMode.finish();
                             } else {
-                                startSelectionActionModeWithoutSelection();
+                                startInsertionActionMode();
                             }
                         }
                     } else {
-                        if (mSelectionActionMode != null) {
-                            mSelectionActionMode.invalidateContentRect();
+                        if (mTextActionMode != null) {
+                            mTextActionMode.invalidateContentRect();
                         }
                     }
                     hideAfterDelay();
@@ -3961,8 +3972,8 @@ public class Editor {
         @Override
         public void updatePosition(float x, float y) {
             positionAtCursorOffset(mTextView.getOffsetForPosition(x, y), false);
-            if (mSelectionActionMode != null) {
-                mSelectionActionMode.invalidate();
+            if (mTextActionMode != null) {
+                mTextActionMode.invalidate();
             }
         }
 
@@ -4013,8 +4024,8 @@ public class Editor {
             Selection.setSelection((Spannable) mTextView.getText(), offset,
                     mTextView.getSelectionEnd());
             updateDrawable();
-            if (mSelectionActionMode != null) {
-                mSelectionActionMode.invalidate();
+            if (mTextActionMode != null) {
+                mTextActionMode.invalidate();
             }
         }
 
@@ -4139,8 +4150,8 @@ public class Editor {
         public void updateSelection(int offset) {
             Selection.setSelection((Spannable) mTextView.getText(),
                     mTextView.getSelectionStart(), offset);
-            if (mSelectionActionMode != null) {
-                mSelectionActionMode.invalidate();
+            if (mTextActionMode != null) {
+                mTextActionMode.invalidate();
             }
             updateDrawable();
         }
@@ -4504,7 +4515,7 @@ public class Editor {
                         mEndHandle.showAtLocation(endOffset);
 
                         // No longer the first dragging motion, reset.
-                        startSelectionActionModeWithSelection();
+                        startSelectionActionMode();
                         mDragAcceleratorActive = false;
                         mStartOffset = -1;
                     }
index 3a85476..6872caa 100644 (file)
@@ -1481,7 +1481,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
                 }
             }
             if (mEditor.hasSelectionController()) {
-                mEditor.startSelectionActionModeWithSelection();
+                mEditor.startSelectionActionMode();
             }
         }
     }
@@ -5282,7 +5282,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
         // - onFocusChanged cannot start it when focus is given to a view with selected text (after
         //   a screen rotation) since layout is not yet initialized at that point.
         if (mEditor != null && mEditor.mCreatedWithASelection) {
-            mEditor.startSelectionActionModeWithSelection();
+            mEditor.startSelectionActionMode();
             mEditor.mCreatedWithASelection = false;
         }
 
@@ -5290,7 +5290,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
         // ExtractEditText does not call onFocus when it is displayed, and mHasSelectionOnFocus can
         // not be set. Do the test here instead.
         if (this instanceof ExtractEditText && hasSelection() && mEditor != null) {
-            mEditor.startSelectionActionModeWithSelection();
+            mEditor.startSelectionActionMode();
         }
 
         unregisterForPreDraw();
@@ -5908,7 +5908,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
     @Override
     public boolean onKeyPreIme(int keyCode, KeyEvent event) {
         if (keyCode == KeyEvent.KEYCODE_BACK) {
-            boolean isInSelectionMode = mEditor != null && mEditor.mSelectionActionMode != null;
+            boolean isInSelectionMode = mEditor != null && mEditor.mTextActionMode != null;
 
             if (isInSelectionMode) {
                 if (event.getAction() == KeyEvent.ACTION_DOWN && event.getRepeatCount() == 0) {
@@ -5923,7 +5923,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
                         state.handleUpEvent(event);
                     }
                     if (event.isTracking() && !event.isCanceled()) {
-                        stopSelectionActionMode();
+                        stopTextActionMode();
                         return true;
                     }
                 }
@@ -6092,8 +6092,8 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
 
                 // Has to be done on key down (and not on key up) to correctly be intercepted.
             case KeyEvent.KEYCODE_BACK:
-                if (mEditor != null && mEditor.mSelectionActionMode != null) {
-                    stopSelectionActionMode();
+                if (mEditor != null && mEditor.mTextActionMode != null) {
+                    stopTextActionMode();
                     return -1;
                 }
                 break;
@@ -6423,7 +6423,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
         // extracted mode will start. Some text is selected though, and will trigger an action mode
         // in the extracted view.
         mEditor.hideControllers();
-        stopSelectionActionMode();
+        stopTextActionMode();
     }
 
     /**
@@ -8258,7 +8258,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
         super.onVisibilityChanged(changedView, visibility);
         if (mEditor != null && visibility != VISIBLE) {
             mEditor.hideControllers();
-            stopSelectionActionMode();
+            stopTextActionMode();
         }
     }
 
@@ -8976,7 +8976,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
                             Selection.setSelection((Spannable) text, start, end);
                             // Make sure selection mode is engaged.
                             if (mEditor != null) {
-                                mEditor.startSelectionActionModeWithSelection();
+                                mEditor.startSelectionActionMode();
                             }
                             return true;
                         }
@@ -9100,12 +9100,12 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
             case ID_CUT:
                 setPrimaryClip(ClipData.newPlainText(null, getTransformedText(min, max)));
                 deleteText_internal(min, max);
-                stopSelectionActionMode();
+                stopTextActionMode();
                 return true;
 
             case ID_COPY:
                 setPrimaryClip(ClipData.newPlainText(null, getTransformedText(min, max)));
-                stopSelectionActionMode();
+                stopTextActionMode();
                 return true;
 
             case ID_REPLACE:
@@ -9195,14 +9195,14 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
      * selection is initiated in this View.
      *
      * The standard implementation populates the menu with a subset of Select All, Cut, Copy,
-     * Paste and Share actions, depending on what this View supports.
+     * Paste, Replace and Share actions, depending on what this View supports.
      *
      * A custom implementation can add new entries in the default menu in its
      * {@link android.view.ActionMode.Callback#onPrepareActionMode(ActionMode, Menu)} method. The
      * default actions can also be removed from the menu using
      * {@link android.view.Menu#removeItem(int)} and passing {@link android.R.id#selectAll},
-     * {@link android.R.id#cut}, {@link android.R.id#copy}, {@link android.R.id#paste} or
-     * {@link android.R.id#shareText} ids as parameters.
+     * {@link android.R.id#cut}, {@link android.R.id#copy}, {@link android.R.id#paste},
+     * {@link android.R.id#replaceText} or {@link android.R.id#shareText} ids as parameters.
      *
      * Returning false from
      * {@link android.view.ActionMode.Callback#onCreateActionMode(ActionMode, Menu)} will prevent
@@ -9230,11 +9230,48 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
     }
 
     /**
+     * If provided, this ActionMode.Callback will be used to create the ActionMode when text
+     * insertion is initiated in this View.
+     *
+     * The standard implementation populates the menu with a subset of Select All,
+     * Paste and Replace actions, depending on what this View supports.
+     *
+     * A custom implementation can add new entries in the default menu in its
+     * {@link android.view.ActionMode.Callback#onPrepareActionMode(ActionMode, Menu)} method. The
+     * default actions can also be removed from the menu using
+     * {@link android.view.Menu#removeItem(int)} and passing {@link android.R.id#selectAll},
+     * {@link android.R.id#paste} or {@link android.R.id#replaceText} ids as parameters.
+     *
+     * Returning false from
+     * {@link android.view.ActionMode.Callback#onCreateActionMode(ActionMode, Menu)} will prevent
+     * the action mode from being started.
+     *
+     * Action click events should be handled by the custom implementation of
+     * {@link android.view.ActionMode.Callback#onActionItemClicked(ActionMode, MenuItem)}.
+     *
+     * Note that text insertion mode is not started when a TextView receives focus and the
+     * {@link android.R.attr#selectAllOnFocus} flag has been set.
+     */
+    public void setCustomInsertionActionModeCallback(ActionMode.Callback actionModeCallback) {
+        createEditorIfNeeded();
+        mEditor.mCustomInsertionActionModeCallback = actionModeCallback;
+    }
+
+    /**
+     * Retrieves the value set in {@link #setCustomInsertionActionModeCallback}. Default is null.
+     *
+     * @return The current custom insertion callback.
+     */
+    public ActionMode.Callback getCustomInsertionActionModeCallback() {
+        return mEditor == null ? null : mEditor.mCustomInsertionActionModeCallback;
+    }
+
+    /**
      * @hide
      */
-    protected void stopSelectionActionMode() {
+    protected void stopTextActionMode() {
         if (mEditor != null) {
-            mEditor.stopSelectionActionMode();
+            mEditor.stopTextActionMode();
         }
     }
 
@@ -9346,7 +9383,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
                     }
                 }
             }
-            stopSelectionActionMode();
+            stopTextActionMode();
             sLastCutCopyOrTextChangedTime = 0;
         }
     }
@@ -9359,7 +9396,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
             sharingIntent.removeExtra(android.content.Intent.EXTRA_TEXT);
             sharingIntent.putExtra(android.content.Intent.EXTRA_TEXT, selectedText);
             getContext().startActivity(Intent.createChooser(sharingIntent, null));
-            stopSelectionActionMode();
+            stopTextActionMode();
         }
     }