diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java index d63af4e9000ad..1c954fcfc4f4d 100644 --- a/core/java/android/widget/TextView.java +++ b/core/java/android/widget/TextView.java @@ -4364,6 +4364,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener switch (keyCode) { case KeyEvent.KEYCODE_ENTER: + mEnterKeyIsDown = true; // If ALT modifier is held, then we always insert a // newline character. if ((event.getMetaState()&KeyEvent.META_ALT_ON) == 0) { @@ -4396,6 +4397,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener break; case KeyEvent.KEYCODE_DPAD_CENTER: + mDPadCenterIsDown = true; if (shouldAdvanceFocusOnEnter()) { return 0; } @@ -4490,6 +4492,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener switch (keyCode) { case KeyEvent.KEYCODE_DPAD_CENTER: + mDPadCenterIsDown = false; /* * If there is a click listener, just call through to * super, which will invoke it. @@ -4510,6 +4513,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener return super.onKeyUp(keyCode, event); case KeyEvent.KEYCODE_ENTER: + mEnterKeyIsDown = false; if (mInputContentType != null && mInputContentType.onEditorActionListener != null && mInputContentType.enterDown) { @@ -7288,9 +7292,21 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener } // Two ints packed in a long + return packRangeInLong(start, end); + } + + private static long packRangeInLong(int start, int end) { return (((long) start) << 32) | end; } + private static int extractRangeStartFromLong(long range) { + return (int) (range >>> 32); + } + + private static int extractRangeEndFromLong(long range) { + return (int) (range & 0x00000000FFFFFFFFL); + } + private void selectCurrentWord() { // In case selection mode is started after an orientation change or after a select all, // use the current selection instead of creating one @@ -7298,67 +7314,31 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener return; } - int selectionStart, selectionEnd; + int minOffset, maxOffset; - // selectionModifierCursorController is not null at that point - SelectionModifierCursorController selectionModifierCursorController = - ((SelectionModifierCursorController) mSelectionModifierCursorController); - int minOffset = selectionModifierCursorController.getMinTouchOffset(); - int maxOffset = selectionModifierCursorController.getMaxTouchOffset(); - - if (minOffset == maxOffset) { - int offset = Math.max(0, Math.min(minOffset, mTransformed.length())); - - // Tolerance, number of charaters around tapped position - final int range = 1; - final int max = mTransformed.length() - 1; - - // 'Smart' word selection: detect position between words - for (int i = -range; i <= range; i++) { - int index = offset + i; - if (index >= 0 && index <= max) { - if (Character.isSpaceChar(mTransformed.charAt(index))) { - // Select current space - selectionStart = index; - selectionEnd = selectionStart + 1; - - // Extend selection to maximum space range - while (selectionStart > 0 && - Character.isSpaceChar(mTransformed.charAt(selectionStart - 1))) { - selectionStart--; - } - while (selectionEnd < max && - Character.isSpaceChar(mTransformed.charAt(selectionEnd))) { - selectionEnd++; - } - - Selection.setSelection((Spannable) mText, selectionStart, selectionEnd); - return; - } - } - } - - // 'Smart' word selection: detect position at beginning or end of text. - if (offset <= range) { - Selection.setSelection((Spannable) mText, 0, 0); - return; - } - if (offset >= (max - range)) { - Selection.setSelection((Spannable) mText, max + 1, max + 1); - return; - } + if (mDPadCenterIsDown || mEnterKeyIsDown) { + minOffset = getSelectionStart(); + maxOffset = getSelectionEnd(); + } else { + // selectionModifierCursorController is not null at that point + SelectionModifierCursorController selectionModifierCursorController = + ((SelectionModifierCursorController) mSelectionModifierCursorController); + minOffset = selectionModifierCursorController.getMinTouchOffset(); + maxOffset = selectionModifierCursorController.getMaxTouchOffset(); } + int selectionStart, selectionEnd; + long wordLimits = getWordLimitsAt(minOffset); if (wordLimits >= 0) { - selectionStart = (int) (wordLimits >>> 32); + selectionStart = extractRangeStartFromLong(wordLimits); } else { selectionStart = Math.max(minOffset - 5, 0); } wordLimits = getWordLimitsAt(maxOffset); if (wordLimits >= 0) { - selectionEnd = (int) (wordLimits & 0x00000000FFFFFFFFL); + selectionEnd = extractRangeEndFromLong(wordLimits); } else { selectionEnd = Math.min(maxOffset + 5, mText.length()); } @@ -7487,7 +7467,6 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener switch (id) { case ID_COPY_URL: - URLSpan[] urls = ((Spanned) mText).getSpans(min, max, URLSpan.class); if (urls.length >= 1) { ClipData clip = null; @@ -7513,6 +7492,49 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener return false; } + /** + * Prepare text so that there are not zero or two spaces at beginning and end of region defined + * by [min, max] when replacing this region by paste. + */ + private long prepareSpacesAroundPaste(int min, int max, CharSequence paste) { + // Paste adds/removes spaces before or after insertion as needed. + if (Character.isSpaceChar(paste.charAt(0))) { + if (min > 0 && Character.isSpaceChar(mTransformed.charAt(min - 1))) { + // Two spaces at beginning of paste: remove one + final int originalLength = mText.length(); + ((Editable) mText).replace(min - 1, min, ""); + // Due to filters, there is no garantee that exactly one character was + // removed. Count instead. + final int delta = mText.length() - originalLength; + min += delta; + max += delta; + } + } else { + if (min > 0 && !Character.isSpaceChar(mTransformed.charAt(min - 1))) { + // No space at beginning of paste: add one + final int originalLength = mText.length(); + ((Editable) mText).replace(min, min, " "); + // Taking possible filters into account as above. + final int delta = mText.length() - originalLength; + min += delta; + max += delta; + } + } + + if (Character.isSpaceChar(paste.charAt(paste.length() - 1))) { + if (max < mText.length() && Character.isSpaceChar(mTransformed.charAt(max))) { + // Two spaces at end of paste: remove one + ((Editable) mText).replace(max, max + 1, ""); + } + } else { + if (max < mText.length() && !Character.isSpaceChar(mTransformed.charAt(max))) { + // No space at end of paste: add one + ((Editable) mText).replace(max, max, " "); + } + } + return packRangeInLong(min, max); + } + @Override public boolean performLongClick() { if (super.performLongClick()) { @@ -7704,6 +7726,9 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener CharSequence paste = clip.getItem(i).coerceToText(getContext()); if (paste != null) { if (!didfirst) { + long minMax = prepareSpacesAroundPaste(min, max, paste); + min = extractRangeStartFromLong(minMax); + max = extractRangeEndFromLong(minMax); Selection.setSelection((Spannable) mText, max); ((Editable) mText).replace(min, max, paste); } else { @@ -7714,7 +7739,6 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener } stopSelectionActionMode(); } - return true; case ID_CUT: @@ -8329,6 +8353,10 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener private CursorController mSelectionModifierCursorController; private ActionMode mSelectionActionMode; private int mLastTouchOffset = -1; + // These are needed to desambiguate a long click. If the long click comes from ones of these, we + // select from the current cursor position. Otherwise, select from long pressed position. + private boolean mDPadCenterIsDown = false; + private boolean mEnterKeyIsDown = false; // Created once and shared by different CursorController helper methods. // Only one cursor controller is active at any time which prevent race conditions. private static Rect sCursorControllerTempRect = new Rect();