Refresh action mode always when selection is modified.
Text action mode was not correctly updated when the selection is modified by keyboard, app or IME. With this CL, Editor#refreshTextActionMode is called always when selection is updated. This method properly starts, stops or invalidates text action mode if selection was modified outside of cursor controllers. Bug: 10527362 Bug: 22937774 Change-Id: Ic025c109539c3b59638226be4c4c9adf5ea0c38c
This commit is contained in:
@@ -215,7 +215,7 @@ public class Editor {
|
||||
|
||||
boolean mInBatchEditControllers;
|
||||
boolean mShowSoftInputOnFocus = true;
|
||||
boolean mPreserveDetachedSelection;
|
||||
private boolean mPreserveDetachedSelection;
|
||||
boolean mTemporaryDetach;
|
||||
|
||||
boolean mIsBeingLongClicked;
|
||||
@@ -352,7 +352,6 @@ public class Editor {
|
||||
|
||||
void replace() {
|
||||
int middle = (mTextView.getSelectionStart() + mTextView.getSelectionEnd()) / 2;
|
||||
stopTextActionMode();
|
||||
Selection.setSelection((Spannable) mTextView.getText(), middle);
|
||||
showSuggestions();
|
||||
}
|
||||
@@ -429,10 +428,8 @@ public class Editor {
|
||||
mSpellChecker = null;
|
||||
}
|
||||
|
||||
mPreserveDetachedSelection = true;
|
||||
hideCursorAndSpanControllers();
|
||||
stopTextActionMode();
|
||||
mPreserveDetachedSelection = false;
|
||||
stopTextActionModeWithPreservingSelection();
|
||||
mTemporaryDetach = false;
|
||||
}
|
||||
|
||||
@@ -1103,7 +1100,6 @@ public class Editor {
|
||||
mInsertionControllerEnabled) {
|
||||
final int offset = mTextView.getOffsetForPosition(mLastDownPositionX,
|
||||
mLastDownPositionY);
|
||||
stopTextActionMode();
|
||||
Selection.setSelection((Spannable) mTextView.getText(), offset);
|
||||
getInsertionController().show();
|
||||
mIsInsertionActionModeStartPending = true;
|
||||
@@ -1207,18 +1203,15 @@ public class Editor {
|
||||
mTextView.onEndBatchEdit();
|
||||
|
||||
if (mTextView.isInExtractedMode()) {
|
||||
// terminateTextSelectionMode removes selection, which we want to keep when
|
||||
// ExtractEditText goes out of focus.
|
||||
final int selStart = mTextView.getSelectionStart();
|
||||
final int selEnd = mTextView.getSelectionEnd();
|
||||
hideCursorAndSpanControllers();
|
||||
stopTextActionMode();
|
||||
Selection.setSelection((Spannable) mTextView.getText(), selStart, selEnd);
|
||||
stopTextActionModeWithPreservingSelection();
|
||||
} else {
|
||||
if (mTemporaryDetach) mPreserveDetachedSelection = true;
|
||||
hideCursorAndSpanControllers();
|
||||
stopTextActionMode();
|
||||
if (mTemporaryDetach) mPreserveDetachedSelection = false;
|
||||
if (mTemporaryDetach) {
|
||||
stopTextActionModeWithPreservingSelection();
|
||||
} else {
|
||||
stopTextActionMode();
|
||||
}
|
||||
downgradeEasyCorrectionSpans();
|
||||
}
|
||||
// No need to create the controller
|
||||
@@ -1289,10 +1282,8 @@ public class Editor {
|
||||
makeBlink();
|
||||
}
|
||||
final InputMethodManager imm = InputMethodManager.peekInstance();
|
||||
final boolean immFullScreen = (imm != null && imm.isFullscreenMode());
|
||||
if (mSelectionModifierCursorController != null && mTextView.hasSelection()
|
||||
&& !immFullScreen && mTextActionMode != null) {
|
||||
mSelectionModifierCursorController.show();
|
||||
if (mTextView.hasSelection() && !extractedTextModeWillBeStarted()) {
|
||||
startSelectionActionMode();
|
||||
}
|
||||
} else {
|
||||
if (mBlink != null) {
|
||||
@@ -1303,9 +1294,7 @@ public class Editor {
|
||||
}
|
||||
// Order matters! Must be done before onParentLostFocus to rely on isShowingUp
|
||||
hideCursorAndSpanControllers();
|
||||
if (mSelectionModifierCursorController != null) {
|
||||
mSelectionModifierCursorController.hide();
|
||||
}
|
||||
stopTextActionModeWithPreservingSelection();
|
||||
if (mSuggestionsPopupWindow != null) {
|
||||
mSuggestionsPopupWindow.onParentLostFocus();
|
||||
}
|
||||
@@ -1855,6 +1844,38 @@ public class Editor {
|
||||
}
|
||||
}
|
||||
|
||||
void refreshTextActionMode() {
|
||||
if (extractedTextModeWillBeStarted()) {
|
||||
return;
|
||||
}
|
||||
final boolean hasSelection = mTextView.hasSelection();
|
||||
final SelectionModifierCursorController selectionController = getSelectionController();
|
||||
final InsertionPointCursorController insertionController = getInsertionController();
|
||||
if ((selectionController != null && selectionController.isCursorBeingModified())
|
||||
|| (insertionController != null && insertionController.isCursorBeingModified())) {
|
||||
// ActionMode should be managed by the currently active cursor controller.
|
||||
return;
|
||||
}
|
||||
if (hasSelection) {
|
||||
if (mTextActionMode == null || selectionController == null
|
||||
|| !selectionController.isActive()) {
|
||||
// Avoid dismissing the selection if it exists.
|
||||
stopTextActionModeWithPreservingSelection();
|
||||
startSelectionActionMode();
|
||||
} else {
|
||||
mTextActionMode.invalidateContentRect();
|
||||
}
|
||||
} else {
|
||||
// Insertion action mode is started only when insertion controller is explicitly
|
||||
// activated.
|
||||
if (insertionController == null || !insertionController.isActive()) {
|
||||
stopTextActionMode();
|
||||
} else if (mTextActionMode != null) {
|
||||
mTextActionMode.invalidateContentRect();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Start an Insertion action mode.
|
||||
*/
|
||||
@@ -1878,17 +1899,15 @@ public class Editor {
|
||||
|
||||
/**
|
||||
* Starts a Selection Action Mode with the current selection and ensures the selection handles
|
||||
* are shown if there is a selection, otherwise the insertion handle is shown. This should be
|
||||
* used when the mode is started from a non-touch event.
|
||||
* are shown if there is a selection. This should be used when the mode is started from a
|
||||
* non-touch event.
|
||||
*
|
||||
* @return true if the selection mode was actually started.
|
||||
*/
|
||||
boolean startSelectionActionMode() {
|
||||
private boolean startSelectionActionMode() {
|
||||
boolean selectionStarted = startSelectionActionModeInternal();
|
||||
if (selectionStarted) {
|
||||
getSelectionController().show();
|
||||
} else if (getInsertionController() != null) {
|
||||
getInsertionController().show();
|
||||
}
|
||||
return selectionStarted;
|
||||
}
|
||||
@@ -1906,66 +1925,52 @@ public class Editor {
|
||||
if (extractedTextModeWillBeStarted()) {
|
||||
return false;
|
||||
}
|
||||
if (mTextActionMode != null) {
|
||||
mTextActionMode.finish();
|
||||
}
|
||||
if (!checkFieldAndSelectCurrentWord()) {
|
||||
if (!checkField()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Avoid dismissing the selection if it exists.
|
||||
mPreserveDetachedSelection = true;
|
||||
stopTextActionMode();
|
||||
mPreserveDetachedSelection = false;
|
||||
|
||||
if (!mTextView.hasSelection() && !selectCurrentWord()) {
|
||||
// No selection and cannot select a word.
|
||||
return false;
|
||||
}
|
||||
stopTextActionModeWithPreservingSelection();
|
||||
getSelectionController().enterDrag(
|
||||
SelectionModifierCursorController.DRAG_ACCELERATOR_MODE_WORD);
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether a selection can be performed on the current TextView and if so selects
|
||||
* the current word.
|
||||
* Checks whether a selection can be performed on the current TextView.
|
||||
*
|
||||
* @return true if there already was a selection or if the current word was selected.
|
||||
* @return true if a selection can be performed
|
||||
*/
|
||||
boolean checkFieldAndSelectCurrentWord() {
|
||||
boolean checkField() {
|
||||
if (!mTextView.canSelectText() || !mTextView.requestFocus()) {
|
||||
Log.w(TextView.LOG_TAG,
|
||||
"TextView does not support text selection. Selection cancelled.");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!mTextView.hasSelection()) {
|
||||
// There may already be a selection on device rotation
|
||||
return selectCurrentWord();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private boolean startSelectionActionModeInternal() {
|
||||
if (extractedTextModeWillBeStarted()) {
|
||||
return false;
|
||||
}
|
||||
if (mTextActionMode != null) {
|
||||
// Text action mode is already started
|
||||
mTextActionMode.invalidate();
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!checkFieldAndSelectCurrentWord()) {
|
||||
if (!checkField() || !mTextView.hasSelection()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
boolean willExtract = extractedTextModeWillBeStarted();
|
||||
ActionMode.Callback actionModeCallback =
|
||||
new TextActionModeCallback(true /* hasSelection */);
|
||||
mTextActionMode = mTextView.startActionMode(actionModeCallback, ActionMode.TYPE_FLOATING);
|
||||
|
||||
// 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 TextActionModeCallback(true /* hasSelection */);
|
||||
mTextActionMode = mTextView.startActionMode(
|
||||
actionModeCallback, ActionMode.TYPE_FLOATING);
|
||||
}
|
||||
|
||||
final boolean selectionStarted = mTextActionMode != null || willExtract;
|
||||
final boolean selectionStarted = mTextActionMode != null;
|
||||
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();
|
||||
@@ -2106,6 +2111,12 @@ public class Editor {
|
||||
}
|
||||
}
|
||||
|
||||
private void stopTextActionModeWithPreservingSelection() {
|
||||
mPreserveDetachedSelection = true;
|
||||
stopTextActionMode();
|
||||
mPreserveDetachedSelection = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return True if this view supports insertion handles.
|
||||
*/
|
||||
@@ -2433,16 +2444,14 @@ public class Editor {
|
||||
if (offset == -1) {
|
||||
return;
|
||||
}
|
||||
mPreserveDetachedSelection = true;
|
||||
stopTextActionMode();
|
||||
mPreserveDetachedSelection = false;
|
||||
stopTextActionModeWithPreservingSelection();
|
||||
final boolean isOnSelection = mTextView.hasSelection()
|
||||
&& offset >= mTextView.getSelectionStart() && offset <= mTextView.getSelectionEnd();
|
||||
if (!isOnSelection) {
|
||||
// Right clicked position is not on the selection. Remove the selection and move the
|
||||
// cursor to the right clicked position.
|
||||
stopTextActionMode();
|
||||
Selection.setSelection((Spannable) mTextView.getText(), offset);
|
||||
stopTextActionMode();
|
||||
}
|
||||
|
||||
if (shouldOfferToShowSuggestions()) {
|
||||
@@ -3483,7 +3492,6 @@ public class Editor {
|
||||
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
|
||||
Editable editable = (Editable) mTextView.getText();
|
||||
SuggestionInfo suggestionInfo = mSuggestionInfos[position];
|
||||
|
||||
final int spanStart = editable.getSpanStart(suggestionInfo.mSuggestionSpan);
|
||||
final int spanEnd = editable.getSpanEnd(suggestionInfo.mSuggestionSpan);
|
||||
if (spanStart < 0 || spanEnd <= spanStart) {
|
||||
@@ -4384,7 +4392,7 @@ public class Editor {
|
||||
if (distanceSquared < touchSlop * touchSlop) {
|
||||
// Tapping on the handle toggles the insertion action mode.
|
||||
if (mTextActionMode != null) {
|
||||
mTextActionMode.finish();
|
||||
stopTextActionMode();
|
||||
} else {
|
||||
startInsertionActionMode();
|
||||
}
|
||||
@@ -4801,6 +4809,10 @@ public class Editor {
|
||||
* preventing the activity from being recycled.
|
||||
*/
|
||||
public void onDetached();
|
||||
|
||||
public boolean isCursorBeingModified();
|
||||
|
||||
public boolean isActive();
|
||||
}
|
||||
|
||||
private class InsertionPointCursorController implements CursorController {
|
||||
@@ -4844,6 +4856,16 @@ public class Editor {
|
||||
|
||||
if (mHandle != null) mHandle.onDetached();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isCursorBeingModified() {
|
||||
return mHandle != null && mHandle.isDragging();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isActive() {
|
||||
return mHandle != null && mHandle.isShowing();
|
||||
}
|
||||
}
|
||||
|
||||
class SelectionModifierCursorController implements CursorController {
|
||||
@@ -5033,9 +5055,7 @@ public class Editor {
|
||||
|
||||
if (mStartOffset != offset) {
|
||||
// Start character based drag accelerator.
|
||||
if (mTextActionMode != null) {
|
||||
mTextActionMode.finish();
|
||||
}
|
||||
stopTextActionMode();
|
||||
enterDrag(DRAG_ACCELERATOR_MODE_CHARACTER);
|
||||
mDiscardNextActionUp = true;
|
||||
mHaventMovedEnoughToStartDrag = false;
|
||||
@@ -5109,9 +5129,7 @@ public class Editor {
|
||||
if (mInsertionActionModeRunnable != null) {
|
||||
mTextView.removeCallbacks(mInsertionActionModeRunnable);
|
||||
}
|
||||
if (mTextActionMode != null) {
|
||||
mTextActionMode.finish();
|
||||
}
|
||||
stopTextActionMode();
|
||||
if (!selectCurrentParagraph()) {
|
||||
return false;
|
||||
}
|
||||
@@ -5220,6 +5238,12 @@ public class Editor {
|
||||
mStartOffset = -1;
|
||||
mDragAcceleratorMode = DRAG_ACCELERATOR_MODE_INACTIVE;
|
||||
mSwitchedLines = false;
|
||||
final int selectionStart = mTextView.getSelectionStart();
|
||||
final int selectionEnd = mTextView.getSelectionEnd();
|
||||
if (selectionStart > selectionEnd) {
|
||||
Selection.setSelection((Spannable) mTextView.getText(),
|
||||
selectionEnd, selectionStart);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -5229,6 +5253,12 @@ public class Editor {
|
||||
return mStartHandle != null && mStartHandle.isDragging();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isCursorBeingModified() {
|
||||
return isDragAcceleratorActive() || isSelectionStartDragged()
|
||||
|| (mEndHandle != null && mEndHandle.isDragging());
|
||||
}
|
||||
|
||||
/**
|
||||
* @return true if the user is selecting text using the drag accelerator.
|
||||
*/
|
||||
@@ -5250,6 +5280,11 @@ public class Editor {
|
||||
if (mStartHandle != null) mStartHandle.onDetached();
|
||||
if (mEndHandle != null) mEndHandle.onDetached();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isActive() {
|
||||
return mStartHandle != null && mStartHandle.isShowing();
|
||||
}
|
||||
}
|
||||
|
||||
private class CorrectionHighlighter {
|
||||
|
||||
@@ -1509,6 +1509,9 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
|
||||
if (result != null) {
|
||||
if (isTextEditable()) {
|
||||
replaceSelectionWithText(result);
|
||||
if (mEditor != null) {
|
||||
mEditor.refreshTextActionMode();
|
||||
}
|
||||
} else {
|
||||
if (result.length() > 0) {
|
||||
Toast.makeText(getContext(), String.valueOf(result), Toast.LENGTH_LONG)
|
||||
@@ -1518,12 +1521,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
|
||||
}
|
||||
} else if (mText instanceof Spannable) {
|
||||
// Reset the selection.
|
||||
stopTextActionMode();
|
||||
Selection.setSelection((Spannable) mText, getSelectionStart(), getSelectionEnd());
|
||||
}
|
||||
|
||||
if (mEditor.hasSelectionController()) {
|
||||
mEditor.startSelectionActionMode();
|
||||
Selection.setSelection((Spannable) mText, getSelectionEnd());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -5391,11 +5389,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) {
|
||||
if (mEditor.extractedTextModeWillBeStarted()) {
|
||||
mEditor.checkFieldAndSelectCurrentWord();
|
||||
} else {
|
||||
mEditor.startSelectionActionMode();
|
||||
}
|
||||
mEditor.refreshTextActionMode();
|
||||
mEditor.mCreatedWithASelection = false;
|
||||
}
|
||||
|
||||
@@ -6593,6 +6587,9 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
|
||||
// in the extracted view.
|
||||
mEditor.hideCursorAndSpanControllers();
|
||||
stopTextActionMode();
|
||||
if (mEditor.mSelectionModifierCursorController != null) {
|
||||
mEditor.mSelectionModifierCursorController.resetTouchOffsets();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -8288,6 +8285,9 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
|
||||
if (newSelEnd < 0) {
|
||||
newSelEnd = Selection.getSelectionEnd(buf);
|
||||
}
|
||||
if (mEditor != null) {
|
||||
mEditor.refreshTextActionMode();
|
||||
}
|
||||
onSelectionChanged(newSelStart, newSelEnd);
|
||||
}
|
||||
}
|
||||
@@ -9217,10 +9217,6 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
|
||||
}
|
||||
if (start >= 0 && start <= end && end <= text.length()) {
|
||||
Selection.setSelection((Spannable) text, start, end);
|
||||
// Make sure selection mode is engaged.
|
||||
if (mEditor != null) {
|
||||
mEditor.startSelectionActionMode();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -9411,16 +9407,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
|
||||
|
||||
switch (id) {
|
||||
case ID_SELECT_ALL:
|
||||
// This starts an action mode if triggered from another action mode. Text is
|
||||
// highlighted, so that it can be bulk edited, like selectAllOnFocus does. Returns
|
||||
// true even if text is empty.
|
||||
boolean shouldRestartActionMode =
|
||||
mEditor != null && mEditor.mTextActionMode != null;
|
||||
stopTextActionMode();
|
||||
selectAllText();
|
||||
if (shouldRestartActionMode) {
|
||||
mEditor.startSelectionActionMode();
|
||||
}
|
||||
return true;
|
||||
|
||||
case ID_UNDO:
|
||||
@@ -9446,7 +9433,6 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
|
||||
case ID_CUT:
|
||||
setPrimaryClip(ClipData.newPlainText(null, getTransformedText(min, max)));
|
||||
deleteText_internal(min, max);
|
||||
stopTextActionMode();
|
||||
return true;
|
||||
|
||||
case ID_COPY:
|
||||
@@ -9702,12 +9688,6 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
|
||||
}
|
||||
|
||||
boolean selectAllText() {
|
||||
// Need to hide insert point cursor controller before settings selection, otherwise insert
|
||||
// point cursor controller obtains cursor update event and update cursor with cancelling
|
||||
// selection.
|
||||
if (mEditor != null) {
|
||||
mEditor.hideInsertionPointCursorController();
|
||||
}
|
||||
final int length = mText.length();
|
||||
Selection.setSelection((Spannable) mText, 0, length);
|
||||
return length > 0;
|
||||
@@ -9746,7 +9726,6 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
|
||||
}
|
||||
}
|
||||
}
|
||||
stopTextActionMode();
|
||||
sLastCutCopyOrTextChangedTime = 0;
|
||||
}
|
||||
}
|
||||
@@ -9759,7 +9738,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));
|
||||
stopTextActionMode();
|
||||
Selection.setSelection((Spannable) mText, getSelectionEnd());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10077,6 +10056,12 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
|
||||
&& getAccessibilitySelectionEnd() == end) {
|
||||
return;
|
||||
}
|
||||
CharSequence text = getIterableTextForAccessibility();
|
||||
if (Math.min(start, end) >= 0 && Math.max(start, end) <= text.length()) {
|
||||
Selection.setSelection((Spannable) text, start, end);
|
||||
} else {
|
||||
Selection.removeSelection((Spannable) text);
|
||||
}
|
||||
// Hide all selection controllers used for adjusting selection
|
||||
// since we are doing so explicitlty by other means and these
|
||||
// controllers interact with how selection behaves.
|
||||
@@ -10084,12 +10069,6 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
|
||||
mEditor.hideCursorAndSpanControllers();
|
||||
mEditor.stopTextActionMode();
|
||||
}
|
||||
CharSequence text = getIterableTextForAccessibility();
|
||||
if (Math.min(start, end) >= 0 && Math.max(start, end) <= text.length()) {
|
||||
Selection.setSelection((Spannable) text, start, end);
|
||||
} else {
|
||||
Selection.removeSelection((Spannable) text);
|
||||
}
|
||||
}
|
||||
|
||||
/** @hide */
|
||||
|
||||
Reference in New Issue
Block a user