diff options
Diffstat (limited to 'core/java/android/widget/TextView.java')
| -rw-r--r-- | core/java/android/widget/TextView.java | 210 |
1 files changed, 164 insertions, 46 deletions
diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java index 04c407032d63..cb930d6fbcb2 100644 --- a/core/java/android/widget/TextView.java +++ b/core/java/android/widget/TextView.java @@ -20,6 +20,7 @@ import android.R; import android.content.ClipData; import android.content.ClipboardManager; import android.content.Context; +import android.content.UndoManager; import android.content.res.ColorStateList; import android.content.res.CompatibilityInfo; import android.content.res.Resources; @@ -291,6 +292,13 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener private boolean mPreDrawRegistered; + // A flag to prevent repeated movements from escaping the enclosing text view. The idea here is + // that if a user is holding down a movement key to traverse text, we shouldn't also traverse + // the view hierarchy. On the other hand, if the user is using the movement key to traverse views + // (i.e. the first movement was to traverse out of this view, or this view was traversed into by + // the user holding the movement key down) then we shouldn't prevent the focus from changing. + private boolean mPreventDefaultMovement; + private TextUtils.TruncateAt mEllipsize; static class Drawables { @@ -351,9 +359,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener mDrawableRight = mDrawableStart; mDrawableSizeRight = mDrawableSizeStart; mDrawableHeightRight = mDrawableHeightStart; - } - if (mOverride) { mDrawableLeft = mDrawableEnd; mDrawableSizeLeft = mDrawableSizeEnd; mDrawableHeightLeft = mDrawableHeightEnd; @@ -366,9 +372,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener mDrawableLeft = mDrawableStart; mDrawableSizeLeft = mDrawableSizeStart; mDrawableHeightLeft = mDrawableHeightStart; - } - if (mOverride) { mDrawableRight = mDrawableEnd; mDrawableSizeRight = mDrawableSizeEnd; mDrawableHeightRight = mDrawableHeightEnd; @@ -547,7 +551,6 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener private InputFilter[] mFilters = NO_FILTERS; private volatile Locale mCurrentSpellCheckerLocaleCache; - private final ReentrantLock mCurrentTextServicesLocaleLock = new ReentrantLock(); // It is possible to have a selection even when mEditor is null (programmatically set, like when // a link is pressed). These highlight-related fields do not go in mEditor. @@ -1371,6 +1374,8 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener } else { dr.mDrawableSizeEnd = dr.mDrawableHeightEnd = 0; } + resetResolvedDrawables(); + resolveDrawables(); } } @@ -1510,6 +1515,51 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener } /** + * Retrieve the {@link android.content.UndoManager} that is currently associated + * with this TextView. By default there is no associated UndoManager, so null + * is returned. One can be associated with the TextView through + * {@link #setUndoManager(android.content.UndoManager, String)} + * + * @hide + */ + public final UndoManager getUndoManager() { + return mEditor == null ? null : mEditor.mUndoManager; + } + + /** + * Associate an {@link android.content.UndoManager} with this TextView. Once + * done, all edit operations on the TextView will result in appropriate + * {@link android.content.UndoOperation} objects pushed on the given UndoManager's + * stack. + * + * @param undoManager The {@link android.content.UndoManager} to associate with + * this TextView, or null to clear any existing association. + * @param tag String tag identifying this particular TextView owner in the + * UndoManager. This is used to keep the correct association with the + * {@link android.content.UndoOwner} of any operations inside of the UndoManager. + * + * @hide + */ + public final void setUndoManager(UndoManager undoManager, String tag) { + if (undoManager != null) { + createEditorIfNeeded(); + mEditor.mUndoManager = undoManager; + mEditor.mUndoOwner = undoManager.getOwner(tag, this); + mEditor.mUndoInputFilter = new Editor.UndoInputFilter(mEditor); + if (!(mText instanceof Editable)) { + setText(mText, BufferType.EDITABLE); + } + + setFilters((Editable) mText, mFilters); + } else if (mEditor != null) { + // XXX need to destroy all associated state. + mEditor.mUndoManager = null; + mEditor.mUndoOwner = null; + mEditor.mUndoInputFilter = null; + } + } + + /** * @return the current key listener for this TextView. * This will frequently be null for non-EditText TextViews. * @@ -1673,7 +1723,8 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener setText(mText); if (hasPasswordTransformationMethod()) { - notifyAccessibilityStateChanged(); + notifyViewAccessibilityStateChangedIfNeeded( + AccessibilityEvent.CONTENT_CHANGE_TYPE_UNDEFINED); } } @@ -1994,6 +2045,8 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener dr.mDrawableRightInitial = right; } + resetResolvedDrawables(); + resolveDrawables(); invalidate(); requestLayout(); } @@ -3774,6 +3827,8 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener sendOnTextChanged(text, 0, oldlen, textLength); onTextChanged(text, 0, oldlen, textLength); + notifyViewAccessibilityStateChangedIfNeeded(AccessibilityEvent.CONTENT_CHANGE_TYPE_TEXT); + if (needEditableForNotification) { sendAfterTextChanged((Editable) text); } @@ -4358,6 +4413,8 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener public void setError(CharSequence error, Drawable icon) { createEditorIfNeeded(); mEditor.setError(error, icon); + notifyViewAccessibilityStateChangedIfNeeded( + AccessibilityEvent.CONTENT_CHANGE_TYPE_UNDEFINED); } @Override @@ -4401,16 +4458,30 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener * and includes mInput in the list if it is an InputFilter. */ private void setFilters(Editable e, InputFilter[] filters) { - if (mEditor != null && mEditor.mKeyListener instanceof InputFilter) { - InputFilter[] nf = new InputFilter[filters.length + 1]; - - System.arraycopy(filters, 0, nf, 0, filters.length); - nf[filters.length] = (InputFilter) mEditor.mKeyListener; + if (mEditor != null) { + final boolean undoFilter = mEditor.mUndoInputFilter != null; + final boolean keyFilter = mEditor.mKeyListener instanceof InputFilter; + int num = 0; + if (undoFilter) num++; + if (keyFilter) num++; + if (num > 0) { + InputFilter[] nf = new InputFilter[filters.length + num]; + + System.arraycopy(filters, 0, nf, 0, filters.length); + num = 0; + if (undoFilter) { + nf[filters.length] = mEditor.mUndoInputFilter; + num++; + } + if (keyFilter) { + nf[filters.length + num] = (InputFilter) mEditor.mKeyListener; + } - e.setFilters(nf); - } else { - e.setFilters(filters); + e.setFilters(nf); + return; + } } + e.setFilters(filters); } /** @@ -4608,8 +4679,6 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener assumeLayout(); } - boolean changed = false; - if (mMovement != null) { /* This code also provides auto-scrolling when a cursor is moved using a * CursorController (insertion point or selection limits). @@ -4632,10 +4701,10 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener } if (curs >= 0) { - changed = bringPointIntoView(curs); + bringPointIntoView(curs); } } else { - changed = bringTextIntoView(); + bringTextIntoView(); } // This has to be checked here since: @@ -4656,7 +4725,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener getViewTreeObserver().removeOnPreDrawListener(this); mPreDrawRegistered = false; - return !changed; + return true; } @Override @@ -4801,8 +4870,10 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener @Override public boolean hasOverlappingRendering() { + // horizontal fading edge causes SaveLayerAlpha, which doesn't support alpha modulation return ((getBackground() != null && getBackground().getCurrent() != null) - || mText instanceof Spannable || hasSelection()); + || mText instanceof Spannable || hasSelection() + || isHorizontalFadingEdgeEnabled()); } /** @@ -5282,7 +5353,6 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener public boolean onKeyDown(int keyCode, KeyEvent event) { int which = doKeyDown(keyCode, event, null); if (which == 0) { - // Go through default dispatching. return super.onKeyDown(keyCode, event); } @@ -5380,6 +5450,15 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener return 0; } + // If this is the initial keydown, we don't want to prevent a movement away from this view. + // While this shouldn't be necessary because any time we're preventing default movement we + // should be restricting the focus to remain within this view, thus we'll also receive + // the key up event, occasionally key up events will get dropped and we don't want to + // prevent the user from traversing out of this on the next key down. + if (event.getRepeatCount() == 0 && !KeyEvent.isModifierKey(keyCode)) { + mPreventDefaultMovement = false; + } + switch (keyCode) { case KeyEvent.KEYCODE_ENTER: if (event.hasNoModifiers()) { @@ -5488,12 +5567,16 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener } } if (doDown) { - if (mMovement.onKeyDown(this, (Spannable)mText, keyCode, event)) + if (mMovement.onKeyDown(this, (Spannable)mText, keyCode, event)) { + if (event.getRepeatCount() == 0 && !KeyEvent.isModifierKey(keyCode)) { + mPreventDefaultMovement = true; + } return 2; + } } } - return 0; + return mPreventDefaultMovement && !KeyEvent.isModifierKey(keyCode) ? -1 : 0; } /** @@ -5526,6 +5609,10 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener return super.onKeyUp(keyCode, event); } + if (!KeyEvent.isModifierKey(keyCode)) { + mPreventDefaultMovement = false; + } + switch (keyCode) { case KeyEvent.KEYCODE_DPAD_CENTER: if (event.hasNoModifiers()) { @@ -7240,7 +7327,6 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener */ protected void onSelectionChanged(int selStart, int selEnd) { sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_TEXT_SELECTION_CHANGED); - notifyAccessibilityStateChanged(); } /** @@ -7285,27 +7371,42 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener } // The spans that are inside or intersect the modified region no longer make sense - removeIntersectingSpans(start, start + before, SpellCheckSpan.class); - removeIntersectingSpans(start, start + before, SuggestionSpan.class); + removeIntersectingNonAdjacentSpans(start, start + before, SpellCheckSpan.class); + removeIntersectingNonAdjacentSpans(start, start + before, SuggestionSpan.class); } // Removes all spans that are inside or actually overlap the start..end range - private <T> void removeIntersectingSpans(int start, int end, Class<T> type) { + private <T> void removeIntersectingNonAdjacentSpans(int start, int end, Class<T> type) { if (!(mText instanceof Editable)) return; Editable text = (Editable) mText; T[] spans = text.getSpans(start, end, type); final int length = spans.length; for (int i = 0; i < length; i++) { - final int s = text.getSpanStart(spans[i]); - final int e = text.getSpanEnd(spans[i]); - // Spans that are adjacent to the edited region will be handled in - // updateSpellCheckSpans. Result depends on what will be added (space or text) - if (e == start || s == end) break; + final int spanStart = text.getSpanStart(spans[i]); + final int spanEnd = text.getSpanEnd(spans[i]); + if (spanEnd == start || spanStart == end) break; text.removeSpan(spans[i]); } } + void removeAdjacentSuggestionSpans(final int pos) { + if (!(mText instanceof Editable)) return; + final Editable text = (Editable) mText; + + final SuggestionSpan[] spans = text.getSpans(pos, pos, SuggestionSpan.class); + final int length = spans.length; + for (int i = 0; i < length; i++) { + final int spanStart = text.getSpanStart(spans[i]); + final int spanEnd = text.getSpanEnd(spans[i]); + if (spanEnd == pos || spanStart == pos) { + if (SpellChecker.haveWordBoundariesChanged(text, pos, pos, spanStart, spanEnd)) { + text.removeSpan(spans[i]); + } + } + } + } + /** * Not private so it can be called from an inner class without going * through a thunk. @@ -7737,7 +7838,15 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener getCompoundPaddingLeft() - getCompoundPaddingRight() - mLayout.getLineLeft(0)) / getHorizontalFadingEdgeLength(); case Gravity.CENTER_HORIZONTAL: - return 0.0f; + case Gravity.FILL_HORIZONTAL: + final int textDirection = mLayout.getParagraphDirection(0); + if (textDirection == Layout.DIR_LEFT_TO_RIGHT) { + return 0.0f; + } else { + return (mLayout.getLineRight(0) - (mRight - mLeft) - + getCompoundPaddingLeft() - getCompoundPaddingRight() - + mLayout.getLineLeft(0)) / getHorizontalFadingEdgeLength(); + } } } } @@ -7766,9 +7875,14 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener return 0.0f; case Gravity.CENTER_HORIZONTAL: case Gravity.FILL_HORIZONTAL: - return (mLayout.getLineWidth(0) - ((mRight - mLeft) - + final int textDirection = mLayout.getParagraphDirection(0); + if (textDirection == Layout.DIR_RIGHT_TO_LEFT) { + return 0.0f; + } else { + return (mLayout.getLineWidth(0) - ((mRight - mLeft) - getCompoundPaddingLeft() - getCompoundPaddingRight())) / getHorizontalFadingEdgeLength(); + } } } } @@ -7958,16 +8072,13 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener } private void updateTextServicesLocaleAsync() { + // AsyncTask.execute() uses a serial executor which means we don't have + // to lock around updateTextServicesLocaleLocked() to prevent it from + // being executed n times in parallel. AsyncTask.execute(new Runnable() { @Override public void run() { - if (mCurrentTextServicesLocaleLock.tryLock()) { - try { - updateTextServicesLocaleLocked(); - } finally { - mCurrentTextServicesLocaleLock.unlock(); - } - } + updateTextServicesLocaleLocked(); } }); } @@ -8056,6 +8167,14 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener info.setEditable(true); } + if (mEditor != null) { + info.setInputType(mEditor.mInputType); + + if (mEditor.mError != null) { + info.setContentInvalid(true); + } + } + if (!TextUtils.isEmpty(mText)) { info.addAction(AccessibilityNodeInfo.ACTION_NEXT_AT_MOVEMENT_GRANULARITY); info.addAction(AccessibilityNodeInfo.ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY); @@ -8080,6 +8199,10 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener info.addAction(AccessibilityNodeInfo.ACTION_CUT); } } + + if (!isSingleLine()) { + info.setMultiLine(true); + } } @Override @@ -8088,7 +8211,6 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener case AccessibilityNodeInfo.ACTION_COPY: { if (isFocused() && canCopy()) { if (onTextContextMenuItem(ID_COPY)) { - notifyAccessibilityStateChanged(); return true; } } @@ -8096,7 +8218,6 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener case AccessibilityNodeInfo.ACTION_PASTE: { if (isFocused() && canPaste()) { if (onTextContextMenuItem(ID_PASTE)) { - notifyAccessibilityStateChanged(); return true; } } @@ -8104,7 +8225,6 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener case AccessibilityNodeInfo.ACTION_CUT: { if (isFocused() && canCut()) { if (onTextContextMenuItem(ID_CUT)) { - notifyAccessibilityStateChanged(); return true; } } @@ -8123,7 +8243,6 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener // No arguments clears the selection. if (start == end && end == -1) { Selection.removeSelection((Spannable) text); - notifyAccessibilityStateChanged(); return true; } if (start >= 0 && start <= end && end <= text.length()) { @@ -8132,7 +8251,6 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener if (mEditor != null) { mEditor.startSelectionActionMode(); } - notifyAccessibilityStateChanged(); return true; } } |
