summaryrefslogtreecommitdiffstats
path: root/core/java/android/widget/TextView.java
diff options
context:
space:
mode:
Diffstat (limited to 'core/java/android/widget/TextView.java')
-rw-r--r--core/java/android/widget/TextView.java4652
1 files changed, 2402 insertions, 2250 deletions
diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java
index f66da29..f91da47 100644
--- a/core/java/android/widget/TextView.java
+++ b/core/java/android/widget/TextView.java
@@ -232,25 +232,72 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
static final String LOG_TAG = "TextView";
static final boolean DEBUG_EXTRACT = false;
- private static final int PRIORITY = 100;
- private int mCurrentAlpha = 255;
+ // Enum for the "typeface" XML parameter.
+ // TODO: How can we get this from the XML instead of hardcoding it here?
+ private static final int SANS = 1;
+ private static final int SERIF = 2;
+ private static final int MONOSPACE = 3;
- final int[] mTempCoords = new int[2];
- Rect mTempRect;
+ // Bitfield for the "numeric" XML parameter.
+ // TODO: How can we get this from the XML instead of hardcoding it here?
+ private static final int SIGNED = 2;
+ private static final int DECIMAL = 4;
+
+ private static enum TEXT_ALIGN {
+ INHERIT, GRAVITY, TEXT_START, TEXT_END, CENTER, VIEW_START, VIEW_END;
+ }
+
+ /**
+ * Draw marquee text with fading edges as usual
+ */
+ private static final int MARQUEE_FADE_NORMAL = 0;
+
+ /**
+ * Draw marquee text as ellipsize end while inactive instead of with the fade.
+ * (Useful for devices where the fade can be expensive if overdone)
+ */
+ private static final int MARQUEE_FADE_SWITCH_SHOW_ELLIPSIS = 1;
+
+ /**
+ * Draw marquee text with fading edges because it is currently active/animating.
+ */
+ private static final int MARQUEE_FADE_SWITCH_SHOW_FADE = 2;
+
+ private static final int LINES = 1;
+ private static final int EMS = LINES;
+ private static final int PIXELS = 2;
+
+ private static final RectF TEMP_RECTF = new RectF();
+ private static final float[] TEMP_POSITION = new float[2];
+
+ // XXX should be much larger
+ private static final int VERY_WIDE = 1024*1024;
+ private static final int BLINK = 500;
+ private static final int ANIMATED_SCROLL_GAP = 250;
+
+ private static final InputFilter[] NO_FILTERS = new InputFilter[0];
+ private static final Spanned EMPTY_SPANNED = new SpannedString("");
+
+ private static int DRAG_SHADOW_MAX_TEXT_LENGTH = 20;
+ private static final int CHANGE_WATCHER_PRIORITY = 100;
+
+ // New state used to change background based on whether this TextView is multiline.
+ private static final int[] MULTILINE_STATE_SET = { R.attr.state_multiline };
+
+ // System wide time for last cut or copy action.
+ private static long LAST_CUT_OR_COPY_TIME;
+
+ private int mCurrentAlpha = 255;
private ColorStateList mTextColor;
- private int mCurTextColor;
private ColorStateList mHintTextColor;
private ColorStateList mLinkTextColor;
+ private int mCurTextColor;
private int mCurHintTextColor;
private boolean mFreezesText;
- private boolean mFrozenWithFocus;
private boolean mTemporaryDetach;
private boolean mDispatchTemporaryDetach;
- private boolean mDiscardNextActionUp = false;
- private boolean mIgnoreActionUpEvent = false;
-
private Editable.Factory mEditableFactory = Editable.Factory.getInstance();
private Spannable.Factory mSpannableFactory = Spannable.Factory.getInstance();
@@ -258,18 +305,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
private boolean mPreDrawRegistered;
- private TextUtils.TruncateAt mEllipsize = null;
-
- // Enum for the "typeface" XML parameter.
- // TODO: How can we get this from the XML instead of hardcoding it here?
- private static final int SANS = 1;
- private static final int SERIF = 2;
- private static final int MONOSPACE = 3;
-
- // Bitfield for the "numeric" XML parameter.
- // TODO: How can we get this from the XML instead of hardcoding it here?
- private static final int SIGNED = 2;
- private static final int DECIMAL = 4;
+ private TextUtils.TruncateAt mEllipsize;
static class Drawables {
final Rect mCompoundRect = new Rect();
@@ -283,96 +319,20 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
}
private Drawables mDrawables;
- private DisplayList mTextDisplayList;
- private boolean mTextDisplayListIsValid;
-
- private CharSequence mError;
- private boolean mErrorWasChanged;
- private ErrorPopup mPopup;
- /**
- * This flag is set if the TextView tries to display an error before it
- * is attached to the window (so its position is still unknown).
- * It causes the error to be shown later, when onAttachedToWindow()
- * is called.
- */
- private boolean mShowErrorAfterAttach;
-
- private CharWrapper mCharWrapper = null;
-
- private boolean mSelectionMoved = false;
- private boolean mTouchFocusSelected = false;
+ private CharWrapper mCharWrapper;
private Marquee mMarquee;
private boolean mRestartMarquee;
private int mMarqueeRepeatLimit = 3;
- static class InputContentType {
- int imeOptions = EditorInfo.IME_NULL;
- String privateImeOptions;
- CharSequence imeActionLabel;
- int imeActionId;
- Bundle extras;
- OnEditorActionListener onEditorActionListener;
- boolean enterDown;
- }
- InputContentType mInputContentType;
-
- static class InputMethodState {
- Rect mCursorRectInWindow = new Rect();
- RectF mTmpRectF = new RectF();
- float[] mTmpOffset = new float[2];
- ExtractedTextRequest mExtracting;
- final ExtractedText mTmpExtracted = new ExtractedText();
- int mBatchEditNesting;
- boolean mCursorChanged;
- boolean mSelectionModeChanged;
- boolean mContentChanged;
- int mChangedStart, mChangedEnd, mChangedDelta;
- }
- InputMethodState mInputMethodState;
-
- private int mTextSelectHandleLeftRes;
- private int mTextSelectHandleRightRes;
- private int mTextSelectHandleRes;
-
- private int mTextEditSuggestionItemLayout;
- private SuggestionsPopupWindow mSuggestionsPopupWindow;
- private SuggestionRangeSpan mSuggestionRangeSpan;
- private Runnable mShowSuggestionRunnable;
-
- private int mCursorDrawableRes;
- private final Drawable[] mCursorDrawable = new Drawable[2];
- private int mCursorCount; // Actual current number of used mCursorDrawable: 0, 1 or 2 (split)
-
- private Drawable mSelectHandleLeft;
- private Drawable mSelectHandleRight;
- private Drawable mSelectHandleCenter;
-
- // Global listener that detects changes in the global position of the TextView
- private PositionListener mPositionListener;
-
- private float mLastDownPositionX, mLastDownPositionY;
- private Callback mCustomSelectionActionModeCallback;
-
- // Set when this TextView gained focus with some text selected. Will start selection mode.
- private boolean mCreatedWithASelection = false;
-
- private WordIterator mWordIterator;
-
- private SpellChecker mSpellChecker;
-
// The alignment to pass to Layout, or null if not resolved.
private Layout.Alignment mLayoutAlignment;
// The default value for mTextAlign.
- private TextAlign mTextAlign = TextAlign.INHERIT;
-
- private static enum TextAlign {
- INHERIT, GRAVITY, TEXT_START, TEXT_END, CENTER, VIEW_START, VIEW_END;
- }
+ private TEXT_ALIGN mTextAlign = TEXT_ALIGN.INHERIT;
- private boolean mResolvedDrawables = false;
+ private boolean mResolvedDrawables;
/**
* On some devices the fading edges add a performance penalty if used
@@ -387,21 +347,84 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
*/
private Layout mSavedMarqueeModeLayout;
- /**
- * Draw marquee text with fading edges as usual
- */
- private static final int MARQUEE_FADE_NORMAL = 0;
+ @ViewDebug.ExportedProperty(category = "text")
+ private CharSequence mText;
+ private CharSequence mTransformed;
+ private BufferType mBufferType = BufferType.NORMAL;
- /**
- * Draw marquee text as ellipsize end while inactive instead of with the fade.
- * (Useful for devices where the fade can be expensive if overdone)
- */
- private static final int MARQUEE_FADE_SWITCH_SHOW_ELLIPSIS = 1;
+ private CharSequence mHint;
+ private Layout mHintLayout;
+
+ private MovementMethod mMovement;
+
+ private TransformationMethod mTransformation;
+ private boolean mAllowTransformationLengthChange;
+ private ChangeWatcher mChangeWatcher;
+
+ private ArrayList<TextWatcher> mListeners;
+
+ // display attributes
+ private final TextPaint mTextPaint;
+ private boolean mUserSetTextScaleX;
+ private Layout mLayout;
+
+ private int mGravity = Gravity.TOP | Gravity.START;
+ private boolean mHorizontallyScrolling;
+
+ private int mAutoLinkMask;
+ private boolean mLinksClickable = true;
+
+ private float mSpacingMult = 1.0f;
+ private float mSpacingAdd = 0.0f;
+
+ private int mMaximum = Integer.MAX_VALUE;
+ private int mMaxMode = LINES;
+ private int mMinimum = 0;
+ private int mMinMode = LINES;
+
+ private int mOldMaximum = mMaximum;
+ private int mOldMaxMode = mMaxMode;
+
+ private int mMaxWidth = Integer.MAX_VALUE;
+ private int mMaxWidthMode = PIXELS;
+ private int mMinWidth = 0;
+ private int mMinWidthMode = PIXELS;
+
+ private boolean mSingleLine;
+ private int mDesiredHeightAtMeasure = -1;
+ private boolean mIncludePad = true;
+
+ // tmp primitives, so we don't alloc them on each draw
+ private Rect mTempRect;
+ private long mLastScroll;
+ private Scroller mScroller;
+
+ private BoringLayout.Metrics mBoring, mHintBoring;
+ private BoringLayout mSavedLayout, mSavedHintLayout;
+
+ private TextDirectionHeuristic mTextDir;
+
+ private InputFilter[] mFilters = NO_FILTERS;
+
+ // Although these fields are specific to editable text, they are not added to Editor because
+ // they are defined by the TextView's style and are theme-dependent.
+ private int mHighlightColor = 0x6633B5E5;
+ private int mCursorDrawableRes;
+ // These four fields, could be moved to Editor, since we know their default values and we
+ // could condition the creation of the Editor to a non standard value. This is however
+ // brittle since the hardcoded values here (such as
+ // com.android.internal.R.drawable.text_select_handle_left) would have to be updated if the
+ // default style is modified.
+ private int mTextSelectHandleLeftRes;
+ private int mTextSelectHandleRightRes;
+ private int mTextSelectHandleRes;
+ private int mTextEditSuggestionItemLayout;
/**
- * Draw marquee text with fading edges because it is currently active/animating.
+ * EditText specific data, created on demand when one of the Editor fields is used.
+ * See {@link #createEditorIfNeeded(String)}.
*/
- private static final int MARQUEE_FADE_SWITCH_SHOW_FADE = 2;
+ private Editor mEditor;
/*
* Kick-start the font cache for the zygote process (to pay the cost of
@@ -454,14 +477,8 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
mTextPaint.density = res.getDisplayMetrics().density;
mTextPaint.setCompatibilityScaling(compat.applicationScale);
- // If we get the paint from the skin, we should set it to left, since
- // the layout always wants it to be left.
- // mTextPaint.setTextAlign(Paint.Align.LEFT);
-
- mHighlightPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
- mHighlightPaint.setCompatibilityScaling(compat.applicationScale);
-
mMovement = getDefaultMovementMethod();
+
mTransformation = null;
int textColorHighlight = 0;
@@ -608,12 +625,6 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
mLinksClickable = a.getBoolean(attr, true);
break;
-// TODO uncomment when this attribute is made public in the next release
-// also add TextView_showSoftInputOnFocus to the list of attributes above
-// case com.android.internal.R.styleable.TextView_showSoftInputOnFocus:
-// setShowSoftInputOnFocus(a.getBoolean(attr, true));
-// break;
-
case com.android.internal.R.styleable.TextView_drawableLeft:
drawableLeft = a.getDrawable(attr);
break;
@@ -805,30 +816,33 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
break;
case com.android.internal.R.styleable.TextView_inputType:
- inputType = a.getInt(attr, mInputType);
+ inputType = a.getInt(attr, EditorInfo.TYPE_NULL);
break;
case com.android.internal.R.styleable.TextView_imeOptions:
- if (mInputContentType == null) {
- mInputContentType = new InputContentType();
+ createEditorIfNeeded("IME options specified in constructor");
+ if (getEditor().mInputContentType == null) {
+ getEditor().mInputContentType = new InputContentType();
}
- mInputContentType.imeOptions = a.getInt(attr,
- mInputContentType.imeOptions);
+ getEditor().mInputContentType.imeOptions = a.getInt(attr,
+ getEditor().mInputContentType.imeOptions);
break;
case com.android.internal.R.styleable.TextView_imeActionLabel:
- if (mInputContentType == null) {
- mInputContentType = new InputContentType();
+ createEditorIfNeeded("IME action label specified in constructor");
+ if (getEditor().mInputContentType == null) {
+ getEditor().mInputContentType = new InputContentType();
}
- mInputContentType.imeActionLabel = a.getText(attr);
+ getEditor().mInputContentType.imeActionLabel = a.getText(attr);
break;
case com.android.internal.R.styleable.TextView_imeActionId:
- if (mInputContentType == null) {
- mInputContentType = new InputContentType();
+ createEditorIfNeeded("IME action id specified in constructor");
+ if (getEditor().mInputContentType == null) {
+ getEditor().mInputContentType = new InputContentType();
}
- mInputContentType.imeActionId = a.getInt(attr,
- mInputContentType.imeActionId);
+ getEditor().mInputContentType.imeActionId = a.getInt(attr,
+ getEditor().mInputContentType.imeActionId);
break;
case com.android.internal.R.styleable.TextView_privateImeOptions:
@@ -866,7 +880,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
break;
case com.android.internal.R.styleable.TextView_textIsSelectable:
- mTextIsSelectable = a.getBoolean(attr, false);
+ setTextIsSelectable(a.getBoolean(attr, false));
break;
case com.android.internal.R.styleable.TextView_textAllCaps:
@@ -897,35 +911,39 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
}
try {
- mInput = (KeyListener) c.newInstance();
+ createEditorIfNeeded("inputMethod in ctor");
+ getEditor().mKeyListener = (KeyListener) c.newInstance();
} catch (InstantiationException ex) {
throw new RuntimeException(ex);
} catch (IllegalAccessException ex) {
throw new RuntimeException(ex);
}
try {
- mInputType = inputType != EditorInfo.TYPE_NULL
+ getEditor().mInputType = inputType != EditorInfo.TYPE_NULL
? inputType
- : mInput.getInputType();
+ : getEditor().mKeyListener.getInputType();
} catch (IncompatibleClassChangeError e) {
- mInputType = EditorInfo.TYPE_CLASS_TEXT;
+ getEditor().mInputType = EditorInfo.TYPE_CLASS_TEXT;
}
} else if (digits != null) {
- mInput = DigitsKeyListener.getInstance(digits.toString());
+ createEditorIfNeeded("digits in ctor");
+ getEditor().mKeyListener = DigitsKeyListener.getInstance(digits.toString());
// If no input type was specified, we will default to generic
// text, since we can't tell the IME about the set of digits
// that was selected.
- mInputType = inputType != EditorInfo.TYPE_NULL
+ getEditor().mInputType = inputType != EditorInfo.TYPE_NULL
? inputType : EditorInfo.TYPE_CLASS_TEXT;
} else if (inputType != EditorInfo.TYPE_NULL) {
setInputType(inputType, true);
// If set, the input type overrides what was set using the deprecated singleLine flag.
singleLine = !isMultilineInputType(inputType);
} else if (phone) {
- mInput = DialerKeyListener.getInstance();
- mInputType = inputType = EditorInfo.TYPE_CLASS_PHONE;
+ createEditorIfNeeded("dialer in ctor");
+ getEditor().mKeyListener = DialerKeyListener.getInstance();
+ getEditor().mInputType = inputType = EditorInfo.TYPE_CLASS_PHONE;
} else if (numeric != 0) {
- mInput = DigitsKeyListener.getInstance((numeric & SIGNED) != 0,
+ createEditorIfNeeded("numeric in ctor");
+ getEditor().mKeyListener = DigitsKeyListener.getInstance((numeric & SIGNED) != 0,
(numeric & DECIMAL) != 0);
inputType = EditorInfo.TYPE_CLASS_NUMBER;
if ((numeric & SIGNED) != 0) {
@@ -934,7 +952,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
if ((numeric & DECIMAL) != 0) {
inputType |= EditorInfo.TYPE_NUMBER_FLAG_DECIMAL;
}
- mInputType = inputType;
+ getEditor().mInputType = inputType;
} else if (autotext || autocap != -1) {
TextKeyListener.Capitalize cap;
@@ -961,22 +979,24 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
break;
}
- mInput = TextKeyListener.getInstance(autotext, cap);
- mInputType = inputType;
- } else if (mTextIsSelectable) {
+ createEditorIfNeeded("text input in ctor");
+ getEditor().mKeyListener = TextKeyListener.getInstance(autotext, cap);
+ getEditor().mInputType = inputType;
+ } else if (isTextSelectable()) {
// Prevent text changes from keyboard.
- mInputType = EditorInfo.TYPE_NULL;
- mInput = null;
+ if (mEditor != null) {
+ getEditor().mKeyListener = null;
+ getEditor().mInputType = EditorInfo.TYPE_NULL;
+ }
bufferType = BufferType.SPANNABLE;
- // Required to request focus while in touch mode.
- setFocusableInTouchMode(true);
// So that selection can be changed using arrow keys and touch is handled.
setMovementMethod(ArrowKeyMovementMethod.getInstance());
} else if (editable) {
- mInput = TextKeyListener.getInstance();
- mInputType = EditorInfo.TYPE_CLASS_TEXT;
+ createEditorIfNeeded("editable input in ctor");
+ getEditor().mKeyListener = TextKeyListener.getInstance();
+ getEditor().mInputType = EditorInfo.TYPE_CLASS_TEXT;
} else {
- mInput = null;
+ if (mEditor != null) getEditor().mKeyListener = null;
switch (buffertype) {
case 0:
@@ -991,27 +1011,12 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
}
}
- // mInputType has been set from inputType, possibly modified by mInputMethod.
- // Specialize mInputType to [web]password if we have a text class and the original input
- // type was a password.
- if ((mInputType & EditorInfo.TYPE_MASK_CLASS) == EditorInfo.TYPE_CLASS_TEXT) {
- if (password || passwordInputType) {
- mInputType = (mInputType & ~(EditorInfo.TYPE_MASK_VARIATION))
- | EditorInfo.TYPE_TEXT_VARIATION_PASSWORD;
- }
- if (webPasswordInputType) {
- mInputType = (mInputType & ~(EditorInfo.TYPE_MASK_VARIATION))
- | EditorInfo.TYPE_TEXT_VARIATION_WEB_PASSWORD;
- }
- } else if ((mInputType & EditorInfo.TYPE_MASK_CLASS) == EditorInfo.TYPE_CLASS_NUMBER) {
- if (numberPasswordInputType) {
- mInputType = (mInputType & ~(EditorInfo.TYPE_MASK_VARIATION))
- | EditorInfo.TYPE_NUMBER_VARIATION_PASSWORD;
- }
- }
+ if (mEditor != null) getEditor().adjustInputType(password, passwordInputType, webPasswordInputType,
+ numberPasswordInputType);
if (selectallonfocus) {
- mSelectAllOnFocus = true;
+ createEditorIfNeeded("selectallonfocus in constructor");
+ getEditor().mSelectAllOnFocus = true;
if (bufferType == BufferType.NORMAL)
bufferType = BufferType.SPANNABLE;
@@ -1027,7 +1032,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
setInputTypeSingleLine(singleLine);
applySingleLine(singleLine, singleLine, singleLine);
- if (singleLine && mInput == null && ellipsize < 0) {
+ if (singleLine && getKeyListener() == null && ellipsize < 0) {
ellipsize = 3; // END
}
@@ -1068,7 +1073,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
if (password || passwordInputType || webPasswordInputType || numberPasswordInputType) {
setTransformationMethod(PasswordTransformationMethod.getInstance());
typefaceIndex = MONOSPACE;
- } else if ((mInputType & (EditorInfo.TYPE_MASK_CLASS | EditorInfo.TYPE_MASK_VARIATION))
+ } else if (mEditor != null && (getEditor().mInputType & (EditorInfo.TYPE_MASK_CLASS | EditorInfo.TYPE_MASK_VARIATION))
== (EditorInfo.TYPE_CLASS_TEXT | EditorInfo.TYPE_TEXT_VARIATION_PASSWORD)) {
typefaceIndex = MONOSPACE;
}
@@ -1097,7 +1102,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
com.android.internal.R.styleable.View,
defStyle, 0);
- boolean focusable = mMovement != null || mInput != null;
+ boolean focusable = mMovement != null || getKeyListener() != null;
boolean clickable = focusable;
boolean longClickable = focusable;
@@ -1205,7 +1210,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
if (imm != null) imm.restartInput(this);
}
- mTextDisplayListIsValid = false;
+ if (mEditor != null) getEditor().mTextDisplayListIsValid = false;
prepareCursorControllers();
// start or stop the cursor blinking as appropriate
@@ -1310,7 +1315,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
* This will frequently be null for non-EditText TextViews.
*/
public final KeyListener getKeyListener() {
- return mInput;
+ return mEditor == null ? null : getEditor().mKeyListener;
}
/**
@@ -1340,16 +1345,17 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
fixFocusableAndClickableSettings();
if (input != null) {
+ createEditorIfNeeded("input is not null");
try {
- mInputType = mInput.getInputType();
+ getEditor().mInputType = getEditor().mKeyListener.getInputType();
} catch (IncompatibleClassChangeError e) {
- mInputType = EditorInfo.TYPE_CLASS_TEXT;
+ getEditor().mInputType = EditorInfo.TYPE_CLASS_TEXT;
}
// Change inputType, without affecting transformation.
// No need to applySingleLine since mSingleLine is unchanged.
setInputTypeSingleLine(mSingleLine);
} else {
- mInputType = EditorInfo.TYPE_NULL;
+ if (mEditor != null) getEditor().mInputType = EditorInfo.TYPE_NULL;
}
InputMethodManager imm = InputMethodManager.peekInstance();
@@ -1357,11 +1363,17 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
}
private void setKeyListenerOnly(KeyListener input) {
- mInput = input;
- if (mInput != null && !(mText instanceof Editable))
- setText(mText);
+ if (mEditor == null && input == null) return; // null is the default value
+
+ createEditorIfNeeded("setKeyListenerOnly");
+ if (getEditor().mKeyListener != input) {
+ getEditor().mKeyListener = input;
+ if (input != null && !(mText instanceof Editable)) {
+ setText(mText);
+ }
- setFilters((Editable) mText, mFilters);
+ setFilters((Editable) mText, mFilters);
+ }
}
/**
@@ -1384,19 +1396,22 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
* back the way you want it.
*/
public final void setMovementMethod(MovementMethod movement) {
- mMovement = movement;
+ if (mMovement != movement) {
+ mMovement = movement;
- if (mMovement != null && !(mText instanceof Spannable))
- setText(mText);
+ if (movement != null && !(mText instanceof Spannable)) {
+ setText(mText);
+ }
- fixFocusableAndClickableSettings();
+ fixFocusableAndClickableSettings();
- // SelectionModifierCursorController depends on textCanBeSelected, which depends on mMovement
- prepareCursorControllers();
+ // SelectionModifierCursorController depends on textCanBeSelected, which depends on mMovement
+ prepareCursorControllers();
+ }
}
private void fixFocusableAndClickableSettings() {
- if ((mMovement != null) || mInput != null) {
+ if (mMovement != null || (mEditor != null && getEditor().mKeyListener != null)) {
setFocusable(true);
setClickable(true);
setLongClickable(true);
@@ -1439,7 +1454,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
if (method instanceof TransformationMethod2) {
TransformationMethod2 method2 = (TransformationMethod2) method;
- mAllowTransformationLengthChange = !mTextIsSelectable && !(mText instanceof Editable);
+ mAllowTransformationLengthChange = !isTextSelectable() && !(mText instanceof Editable);
method2.setLengthChangesAllowed(mAllowTransformationLengthChange);
} else {
mAllowTransformationLengthChange = false;
@@ -2311,7 +2326,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
public void setHighlightColor(int color) {
if (mHighlightColor != color) {
mHighlightColor = color;
- mTextDisplayListIsValid = false;
+ if (mEditor != null) getEditor().mTextDisplayListIsValid = false;
invalidate();
}
}
@@ -2332,7 +2347,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
mShadowDx = dx;
mShadowDy = dy;
- mTextDisplayListIsValid = false;
+ if (mEditor != null) getEditor().mTextDisplayListIsValid = false;
invalidate();
}
@@ -2824,7 +2839,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
}
}
if (inval) {
- mTextDisplayListIsValid = false;
+ if (mEditor != null) getEditor().mTextDisplayListIsValid = false;
invalidate();
}
}
@@ -2862,73 +2877,6 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
}
}
- /**
- * User interface state that is stored by TextView for implementing
- * {@link View#onSaveInstanceState}.
- */
- public static class SavedState extends BaseSavedState {
- int selStart;
- int selEnd;
- CharSequence text;
- boolean frozenWithFocus;
- CharSequence error;
-
- SavedState(Parcelable superState) {
- super(superState);
- }
-
- @Override
- public void writeToParcel(Parcel out, int flags) {
- super.writeToParcel(out, flags);
- out.writeInt(selStart);
- out.writeInt(selEnd);
- out.writeInt(frozenWithFocus ? 1 : 0);
- TextUtils.writeToParcel(text, out, flags);
-
- if (error == null) {
- out.writeInt(0);
- } else {
- out.writeInt(1);
- TextUtils.writeToParcel(error, out, flags);
- }
- }
-
- @Override
- public String toString() {
- String str = "TextView.SavedState{"
- + Integer.toHexString(System.identityHashCode(this))
- + " start=" + selStart + " end=" + selEnd;
- if (text != null) {
- str += " text=" + text;
- }
- return str + "}";
- }
-
- @SuppressWarnings("hiding")
- public static final Parcelable.Creator<SavedState> CREATOR
- = new Parcelable.Creator<SavedState>() {
- public SavedState createFromParcel(Parcel in) {
- return new SavedState(in);
- }
-
- public SavedState[] newArray(int size) {
- return new SavedState[size];
- }
- };
-
- private SavedState(Parcel in) {
- super(in);
- selStart = in.readInt();
- selEnd = in.readInt();
- frozenWithFocus = (in.readInt() != 0);
- text = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in);
-
- if (in.readInt() != 0) {
- error = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in);
- }
- }
- }
-
@Override
public Parcelable onSaveInstanceState() {
Parcelable superState = super.onSaveInstanceState();
@@ -2968,8 +2916,10 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
sp.removeSpan(cw);
}
- removeMisspelledSpans(sp);
- sp.removeSpan(mSuggestionRangeSpan);
+ if (mEditor != null) {
+ removeMisspelledSpans(sp);
+ sp.removeSpan(getEditor().mSuggestionRangeSpan);
+ }
ss.text = sp;
} else {
@@ -2980,7 +2930,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
ss.frozenWithFocus = true;
}
- ss.error = mError;
+ ss.error = getError();
return ss;
}
@@ -3034,7 +2984,8 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
ss.selEnd);
if (ss.frozenWithFocus) {
- mFrozenWithFocus = true;
+ createEditorIfNeeded("restore instance with focus");
+ getEditor().mFrozenWithFocus = true;
}
}
}
@@ -3192,7 +3143,8 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
needEditableForNotification = true;
}
- if (type == BufferType.EDITABLE || mInput != null || needEditableForNotification) {
+ if (type == BufferType.EDITABLE || getKeyListener() != null || needEditableForNotification) {
+ createEditorIfNeeded("setText with BufferType.EDITABLE or non null mInput");
Editable t = mEditableFactory.newEditable(text);
text = t;
setFilters(t, mFilters);
@@ -3257,10 +3209,10 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
mChangeWatcher = new ChangeWatcher();
sp.setSpan(mChangeWatcher, 0, textLength, Spanned.SPAN_INCLUSIVE_INCLUSIVE |
- (PRIORITY << Spanned.SPAN_PRIORITY_SHIFT));
+ (CHANGE_WATCHER_PRIORITY << Spanned.SPAN_PRIORITY_SHIFT));
- if (mInput != null) {
- sp.setSpan(mInput, 0, textLength, Spanned.SPAN_INCLUSIVE_INCLUSIVE);
+ if (mEditor != null && getEditor().mKeyListener != null) {
+ sp.setSpan(getEditor().mKeyListener, 0, textLength, Spanned.SPAN_INCLUSIVE_INCLUSIVE);
}
if (mTransformation != null) {
@@ -3275,7 +3227,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
* selection, so reset mSelectionMoved to keep that from
* interfering with the normal on-focus selection-setting.
*/
- mSelectionMoved = false;
+ if (mEditor != null) getEditor().mSelectionMoved = false;
}
}
@@ -3329,100 +3281,6 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
setText(mCharWrapper, mBufferType, false, oldlen);
}
- private static class CharWrapper implements CharSequence, GetChars, GraphicsOperations {
- private char[] mChars;
- private int mStart, mLength;
-
- public CharWrapper(char[] chars, int start, int len) {
- mChars = chars;
- mStart = start;
- mLength = len;
- }
-
- /* package */ void set(char[] chars, int start, int len) {
- mChars = chars;
- mStart = start;
- mLength = len;
- }
-
- public int length() {
- return mLength;
- }
-
- public char charAt(int off) {
- return mChars[off + mStart];
- }
-
- @Override
- public String toString() {
- return new String(mChars, mStart, mLength);
- }
-
- public CharSequence subSequence(int start, int end) {
- if (start < 0 || end < 0 || start > mLength || end > mLength) {
- throw new IndexOutOfBoundsException(start + ", " + end);
- }
-
- return new String(mChars, start + mStart, end - start);
- }
-
- public void getChars(int start, int end, char[] buf, int off) {
- if (start < 0 || end < 0 || start > mLength || end > mLength) {
- throw new IndexOutOfBoundsException(start + ", " + end);
- }
-
- System.arraycopy(mChars, start + mStart, buf, off, end - start);
- }
-
- public void drawText(Canvas c, int start, int end,
- float x, float y, Paint p) {
- c.drawText(mChars, start + mStart, end - start, x, y, p);
- }
-
- public void drawTextRun(Canvas c, int start, int end,
- int contextStart, int contextEnd, float x, float y, int flags, Paint p) {
- int count = end - start;
- int contextCount = contextEnd - contextStart;
- c.drawTextRun(mChars, start + mStart, count, contextStart + mStart,
- contextCount, x, y, flags, p);
- }
-
- public float measureText(int start, int end, Paint p) {
- return p.measureText(mChars, start + mStart, end - start);
- }
-
- public int getTextWidths(int start, int end, float[] widths, Paint p) {
- return p.getTextWidths(mChars, start + mStart, end - start, widths);
- }
-
- public float getTextRunAdvances(int start, int end, int contextStart,
- int contextEnd, int flags, float[] advances, int advancesIndex,
- Paint p) {
- int count = end - start;
- int contextCount = contextEnd - contextStart;
- return p.getTextRunAdvances(mChars, start + mStart, count,
- contextStart + mStart, contextCount, flags, advances,
- advancesIndex);
- }
-
- public float getTextRunAdvances(int start, int end, int contextStart,
- int contextEnd, int flags, float[] advances, int advancesIndex,
- Paint p, int reserved) {
- int count = end - start;
- int contextCount = contextEnd - contextStart;
- return p.getTextRunAdvances(mChars, start + mStart, count,
- contextStart + mStart, contextCount, flags, advances,
- advancesIndex, reserved);
- }
-
- public int getTextRunCursor(int contextStart, int contextEnd, int flags,
- int offset, int cursorOpt, Paint p) {
- int contextCount = contextEnd - contextStart;
- return p.getTextRunCursor(mChars, contextStart + mStart,
- contextCount, flags, offset + mStart, cursorOpt);
- }
- }
-
/**
* Like {@link #setText(CharSequence, android.widget.TextView.BufferType)},
* except that the cursor position (if any) is retained in the new text.
@@ -3474,7 +3332,9 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
}
// Invalidate display list if hint will be used
- if (mText.length() == 0 && mHint != null) mTextDisplayListIsValid = false;
+ if (mEditor != null && mText.length() == 0 && mHint != null) {
+ getEditor().mTextDisplayListIsValid = false;
+ }
}
/**
@@ -3520,8 +3380,8 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
* @attr ref android.R.styleable#TextView_inputType
*/
public void setInputType(int type) {
- final boolean wasPassword = isPasswordInputType(mInputType);
- final boolean wasVisiblePassword = isVisiblePasswordInputType(mInputType);
+ final boolean wasPassword = isPasswordInputType(getInputType());
+ final boolean wasVisiblePassword = isVisiblePasswordInputType(getInputType());
setInputType(type, false);
final boolean isPassword = isPasswordInputType(type);
final boolean isVisiblePassword = isVisiblePasswordInputType(type);
@@ -3605,7 +3465,9 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
* @attr ref android.R.styleable#TextView_inputType
*/
public void setRawInputType(int type) {
- mInputType = type;
+ if (type == InputType.TYPE_NULL && mEditor == null) return; //TYPE_NULL is the default value
+ createEditorIfNeeded("non null input type");
+ getEditor().mInputType = type;
}
private void setInputType(int type, boolean direct) {
@@ -3646,20 +3508,22 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
input = TextKeyListener.getInstance();
}
setRawInputType(type);
- if (direct) mInput = input;
- else {
+ if (direct) {
+ createEditorIfNeeded("setInputType");
+ getEditor().mKeyListener = input;
+ } else {
setKeyListenerOnly(input);
}
}
/**
- * Get the type of the content.
+ * Get the type of the editable content.
*
* @see #setInputType(int)
* @see android.text.InputType
*/
public int getInputType() {
- return mInputType;
+ return mEditor == null ? EditorInfo.TYPE_NULL : getEditor().mInputType;
}
/**
@@ -3671,10 +3535,11 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
* @attr ref android.R.styleable#TextView_imeOptions
*/
public void setImeOptions(int imeOptions) {
- if (mInputContentType == null) {
- mInputContentType = new InputContentType();
+ createEditorIfNeeded("IME options specified");
+ if (getEditor().mInputContentType == null) {
+ getEditor().mInputContentType = new InputContentType();
}
- mInputContentType.imeOptions = imeOptions;
+ getEditor().mInputContentType.imeOptions = imeOptions;
}
/**
@@ -3684,8 +3549,8 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
* @see android.view.inputmethod.EditorInfo
*/
public int getImeOptions() {
- return mInputContentType != null
- ? mInputContentType.imeOptions : EditorInfo.IME_NULL;
+ return mEditor != null && getEditor().mInputContentType != null
+ ? getEditor().mInputContentType.imeOptions : EditorInfo.IME_NULL;
}
/**
@@ -3699,11 +3564,12 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
* @attr ref android.R.styleable#TextView_imeActionId
*/
public void setImeActionLabel(CharSequence label, int actionId) {
- if (mInputContentType == null) {
- mInputContentType = new InputContentType();
+ createEditorIfNeeded("IME action label specified");
+ if (getEditor().mInputContentType == null) {
+ getEditor().mInputContentType = new InputContentType();
}
- mInputContentType.imeActionLabel = label;
- mInputContentType.imeActionId = actionId;
+ getEditor().mInputContentType.imeActionLabel = label;
+ getEditor().mInputContentType.imeActionId = actionId;
}
/**
@@ -3713,8 +3579,8 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
* @see android.view.inputmethod.EditorInfo
*/
public CharSequence getImeActionLabel() {
- return mInputContentType != null
- ? mInputContentType.imeActionLabel : null;
+ return mEditor != null && getEditor().mInputContentType != null
+ ? getEditor().mInputContentType.imeActionLabel : null;
}
/**
@@ -3724,8 +3590,8 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
* @see android.view.inputmethod.EditorInfo
*/
public int getImeActionId() {
- return mInputContentType != null
- ? mInputContentType.imeActionId : 0;
+ return mEditor != null && getEditor().mInputContentType != null
+ ? getEditor().mInputContentType.imeActionId : 0;
}
/**
@@ -3737,12 +3603,13 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
* modifier will, however, allow the user to insert a newline character.
*/
public void setOnEditorActionListener(OnEditorActionListener l) {
- if (mInputContentType == null) {
- mInputContentType = new InputContentType();
+ createEditorIfNeeded("Editor action listener set");
+ if (getEditor().mInputContentType == null) {
+ getEditor().mInputContentType = new InputContentType();
}
- mInputContentType.onEditorActionListener = l;
+ getEditor().mInputContentType.onEditorActionListener = l;
}
-
+
/**
* Called when an attached input method calls
* {@link InputConnection#performEditorAction(int)
@@ -3764,7 +3631,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
* @see #setOnEditorActionListener
*/
public void onEditorAction(int actionCode) {
- final InputContentType ict = mInputContentType;
+ final InputContentType ict = mEditor == null ? null : getEditor().mInputContentType;
if (ict != null) {
if (ict.onEditorActionListener != null) {
if (ict.onEditorActionListener.onEditorAction(this,
@@ -3835,8 +3702,10 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
* @attr ref android.R.styleable#TextView_privateImeOptions
*/
public void setPrivateImeOptions(String type) {
- if (mInputContentType == null) mInputContentType = new InputContentType();
- mInputContentType.privateImeOptions = type;
+ createEditorIfNeeded("Private IME option set");
+ if (getEditor().mInputContentType == null)
+ getEditor().mInputContentType = new InputContentType();
+ getEditor().mInputContentType.privateImeOptions = type;
}
/**
@@ -3846,8 +3715,8 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
* @see EditorInfo#privateImeOptions
*/
public String getPrivateImeOptions() {
- return mInputContentType != null
- ? mInputContentType.privateImeOptions : null;
+ return mEditor != null && getEditor().mInputContentType != null
+ ? getEditor().mInputContentType.privateImeOptions : null;
}
/**
@@ -3861,12 +3730,13 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
* @see EditorInfo#extras
* @attr ref android.R.styleable#TextView_editorExtras
*/
- public void setInputExtras(int xmlResId)
- throws XmlPullParserException, IOException {
+ public void setInputExtras(int xmlResId) throws XmlPullParserException, IOException {
+ createEditorIfNeeded("Input extra set");
XmlResourceParser parser = getResources().getXml(xmlResId);
- if (mInputContentType == null) mInputContentType = new InputContentType();
- mInputContentType.extras = new Bundle();
- getResources().parseBundleExtras(parser, mInputContentType.extras);
+ if (getEditor().mInputContentType == null)
+ getEditor().mInputContentType = new InputContentType();
+ getEditor().mInputContentType.extras = new Bundle();
+ getResources().parseBundleExtras(parser, getEditor().mInputContentType.extras);
}
/**
@@ -3880,15 +3750,17 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
* @attr ref android.R.styleable#TextView_editorExtras
*/
public Bundle getInputExtras(boolean create) {
- if (mInputContentType == null) {
+ if (mEditor == null && !create) return null;
+ createEditorIfNeeded("get Input extra");
+ if (getEditor().mInputContentType == null) {
if (!create) return null;
- mInputContentType = new InputContentType();
+ getEditor().mInputContentType = new InputContentType();
}
- if (mInputContentType.extras == null) {
+ if (getEditor().mInputContentType.extras == null) {
if (!create) return null;
- mInputContentType.extras = new Bundle();
+ getEditor().mInputContentType.extras = new Bundle();
}
- return mInputContentType.extras;
+ return getEditor().mInputContentType.extras;
}
/**
@@ -3897,7 +3769,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
* or if it the error was cleared by the widget after user input.
*/
public CharSequence getError() {
- return mError;
+ return mEditor == null ? null : getEditor().mError;
}
/**
@@ -3931,10 +3803,11 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
* be cleared (and you should provide a <code>null</code> icon as well).
*/
public void setError(CharSequence error, Drawable icon) {
+ createEditorIfNeeded("setError");
error = TextUtils.stringOrSpannedString(error);
- mError = error;
- mErrorWasChanged = true;
+ getEditor().mError = error;
+ getEditor().mErrorWasChanged = true;
final Drawables dr = mDrawables;
if (dr != null) {
switch (getResolvedLayoutDirection()) {
@@ -3953,12 +3826,12 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
}
if (error == null) {
- if (mPopup != null) {
- if (mPopup.isShowing()) {
- mPopup.dismiss();
+ if (getEditor().mErrorPopup != null) {
+ if (getEditor().mErrorPopup.isShowing()) {
+ getEditor().mErrorPopup.dismiss();
}
- mPopup = null;
+ getEditor().mErrorPopup = null;
}
} else {
if (isFocused()) {
@@ -3969,83 +3842,29 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
private void showError() {
if (getWindowToken() == null) {
- mShowErrorAfterAttach = true;
+ getEditor().mShowErrorAfterAttach = true;
return;
}
- if (mPopup == null) {
+ if (getEditor().mErrorPopup == null) {
LayoutInflater inflater = LayoutInflater.from(getContext());
final TextView err = (TextView) inflater.inflate(
com.android.internal.R.layout.textview_hint, null);
final float scale = getResources().getDisplayMetrics().density;
- mPopup = new ErrorPopup(err, (int) (200 * scale + 0.5f), (int) (50 * scale + 0.5f));
- mPopup.setFocusable(false);
+ getEditor().mErrorPopup = new ErrorPopup(err, (int) (200 * scale + 0.5f), (int) (50 * scale + 0.5f));
+ getEditor().mErrorPopup.setFocusable(false);
// The user is entering text, so the input method is needed. We
// don't want the popup to be displayed on top of it.
- mPopup.setInputMethodMode(PopupWindow.INPUT_METHOD_NEEDED);
- }
-
- TextView tv = (TextView) mPopup.getContentView();
- chooseSize(mPopup, mError, tv);
- tv.setText(mError);
-
- mPopup.showAsDropDown(this, getErrorX(), getErrorY());
- mPopup.fixDirection(mPopup.isAboveAnchor());
- }
-
- private static class ErrorPopup extends PopupWindow {
- private boolean mAbove = false;
- private final TextView mView;
- private int mPopupInlineErrorBackgroundId = 0;
- private int mPopupInlineErrorAboveBackgroundId = 0;
-
- ErrorPopup(TextView v, int width, int height) {
- super(v, width, height);
- mView = v;
- // Make sure the TextView has a background set as it will be used the first time it is
- // shown and positionned. Initialized with below background, which should have
- // dimensions identical to the above version for this to work (and is more likely).
- mPopupInlineErrorBackgroundId = getResourceId(mPopupInlineErrorBackgroundId,
- com.android.internal.R.styleable.Theme_errorMessageBackground);
- mView.setBackgroundResource(mPopupInlineErrorBackgroundId);
- }
-
- void fixDirection(boolean above) {
- mAbove = above;
-
- if (above) {
- mPopupInlineErrorAboveBackgroundId =
- getResourceId(mPopupInlineErrorAboveBackgroundId,
- com.android.internal.R.styleable.Theme_errorMessageAboveBackground);
- } else {
- mPopupInlineErrorBackgroundId = getResourceId(mPopupInlineErrorBackgroundId,
- com.android.internal.R.styleable.Theme_errorMessageBackground);
- }
-
- mView.setBackgroundResource(above ? mPopupInlineErrorAboveBackgroundId :
- mPopupInlineErrorBackgroundId);
- }
-
- private int getResourceId(int currentId, int index) {
- if (currentId == 0) {
- TypedArray styledAttributes = mView.getContext().obtainStyledAttributes(
- R.styleable.Theme);
- currentId = styledAttributes.getResourceId(index, 0);
- styledAttributes.recycle();
- }
- return currentId;
+ getEditor().mErrorPopup.setInputMethodMode(PopupWindow.INPUT_METHOD_NEEDED);
}
- @Override
- public void update(int x, int y, int w, int h, boolean force) {
- super.update(x, y, w, h, force);
+ TextView tv = (TextView) getEditor().mErrorPopup.getContentView();
+ chooseSize(getEditor().mErrorPopup, getEditor().mError, tv);
+ tv.setText(getEditor().mError);
- boolean above = isAboveAnchor();
- if (above != mAbove) {
- fixDirection(above);
- }
- }
+ getEditor().mErrorPopup.showAsDropDown(this, getErrorX(), getErrorY());
+ getEditor().mErrorPopup.fixDirection(getEditor().mErrorPopup.isAboveAnchor());
}
/**
@@ -4060,7 +3879,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
final float scale = getResources().getDisplayMetrics().density;
final Drawables dr = mDrawables;
- return getWidth() - mPopup.getWidth() - getPaddingRight() -
+ return getWidth() - getEditor().mErrorPopup.getWidth() - getPaddingRight() -
(dr != null ? dr.mDrawableSizeRight : 0) / 2 + (int) (25 * scale + 0.5f);
}
@@ -4090,13 +3909,13 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
}
private void hideError() {
- if (mPopup != null) {
- if (mPopup.isShowing()) {
- mPopup.dismiss();
+ if (getEditor().mErrorPopup != null) {
+ if (getEditor().mErrorPopup.isShowing()) {
+ getEditor().mErrorPopup.dismiss();
}
}
- mShowErrorAfterAttach = false;
+ getEditor().mShowErrorAfterAttach = false;
}
private void chooseSize(PopupWindow pop, CharSequence text, TextView tv) {
@@ -4125,12 +3944,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
protected boolean setFrame(int l, int t, int r, int b) {
boolean result = super.setFrame(l, t, r, b);
- if (mPopup != null) {
- TextView tv = (TextView) mPopup.getContentView();
- chooseSize(mPopup, mError, tv);
- mPopup.update(this, getErrorX(), getErrorY(),
- mPopup.getWidth(), mPopup.getHeight());
- }
+ if (mEditor != null) getEditor().setFrame();
restartMarqueeIfNeeded();
@@ -4146,7 +3960,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
/**
* Sets the list of input filters that will be used if the buffer is
- * Editable. Has no effect otherwise.
+ * Editable. Has no effect otherwise.
*
* @attr ref android.R.styleable#TextView_maxLength
*/
@@ -4167,11 +3981,11 @@ 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 (mInput instanceof InputFilter) {
+ if (mEditor != null && getEditor().mKeyListener instanceof InputFilter) {
InputFilter[] nf = new InputFilter[filters.length + 1];
System.arraycopy(filters, 0, nf, 0, filters.length);
- nf[filters.length] = (InputFilter) mInput;
+ nf[filters.length] = (InputFilter) getEditor().mKeyListener;
e.setFilters(nf);
} else {
@@ -4251,14 +4065,14 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
}
private void invalidateCursorPath() {
- if (mHighlightPathBogus) {
+ if (getEditor().mHighlightPathBogus) {
invalidateCursor();
} else {
final int horizontalPadding = getCompoundPaddingLeft();
final int verticalPadding = getExtendedPaddingTop() + getVerticalOffset(true);
- if (mCursorCount == 0) {
- synchronized (sTempRect) {
+ if (getEditor().mCursorCount == 0) {
+ synchronized (TEMP_RECTF) {
/*
* The reason for this concern about the thickness of the
* cursor and doing the floor/ceil on the coordinates is that
@@ -4275,16 +4089,16 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
thick /= 2.0f;
- mHighlightPath.computeBounds(sTempRect, false);
+ getEditor().mHighlightPath.computeBounds(TEMP_RECTF, false);
- invalidate((int) FloatMath.floor(horizontalPadding + sTempRect.left - thick),
- (int) FloatMath.floor(verticalPadding + sTempRect.top - thick),
- (int) FloatMath.ceil(horizontalPadding + sTempRect.right + thick),
- (int) FloatMath.ceil(verticalPadding + sTempRect.bottom + thick));
+ invalidate((int) FloatMath.floor(horizontalPadding + TEMP_RECTF.left - thick),
+ (int) FloatMath.floor(verticalPadding + TEMP_RECTF.top - thick),
+ (int) FloatMath.ceil(horizontalPadding + TEMP_RECTF.right + thick),
+ (int) FloatMath.ceil(verticalPadding + TEMP_RECTF.bottom + thick));
}
} else {
- for (int i = 0; i < mCursorCount; i++) {
- Rect bounds = mCursorDrawable[i].getBounds();
+ for (int i = 0; i < getEditor().mCursorCount; i++) {
+ Rect bounds = getEditor().mCursorDrawable[i].getBounds();
invalidate(bounds.left + horizontalPadding, bounds.top + verticalPadding,
bounds.right + horizontalPadding, bounds.bottom + verticalPadding);
}
@@ -4338,8 +4152,8 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
int bottom = mLayout.getLineBottom(lineEnd);
if (invalidateCursor) {
- for (int i = 0; i < mCursorCount; i++) {
- Rect bounds = mCursorDrawable[i].getBounds();
+ for (int i = 0; i < getEditor().mCursorCount; i++) {
+ Rect bounds = getEditor().mCursorDrawable[i].getBounds();
top = Math.min(top, bounds.top);
bottom = Math.max(bottom, bounds.bottom);
}
@@ -4389,8 +4203,8 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
*/
int curs = getSelectionEnd();
// Do not create the controller if it is not already created.
- if (mSelectionModifierCursorController != null &&
- mSelectionModifierCursorController.isSelectionStartDragged()) {
+ if (mEditor != null && getEditor().mSelectionModifierCursorController != null &&
+ getEditor().mSelectionModifierCursorController.isSelectionStartDragged()) {
curs = getSelectionStart();
}
@@ -4399,8 +4213,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
* it already was before the text changed. I'm not sure
* of a good way to tell from here if it was.
*/
- if (curs < 0 &&
- (mGravity & Gravity.VERTICAL_GRAVITY_MASK) == Gravity.BOTTOM) {
+ if (curs < 0 && (mGravity & Gravity.VERTICAL_GRAVITY_MASK) == Gravity.BOTTOM) {
curs = mText.length();
}
@@ -4414,9 +4227,9 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
// This has to be checked here since:
// - 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 (mCreatedWithASelection) {
+ if (mEditor != null && getEditor().mCreatedWithASelection) {
startSelectionActionMode();
- mCreatedWithASelection = false;
+ getEditor().mCreatedWithASelection = false;
}
// Phone specific code (there is no ExtractEditText on tablets).
@@ -4438,25 +4251,15 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
mTemporaryDetach = false;
- if (mShowErrorAfterAttach) {
+ if (mEditor != null && getEditor().mShowErrorAfterAttach) {
showError();
- mShowErrorAfterAttach = false;
- }
-
- final ViewTreeObserver observer = getViewTreeObserver();
- // No need to create the controller.
- // The get method will add the listener on controller creation.
- if (mInsertionPointCursorController != null) {
- observer.addOnTouchModeChangeListener(mInsertionPointCursorController);
- }
- if (mSelectionModifierCursorController != null) {
- observer.addOnTouchModeChangeListener(mSelectionModifierCursorController);
+ getEditor().mShowErrorAfterAttach = false;
}
// Resolve drawables as the layout direction has been resolved
resolveDrawables();
- updateSpellCheckSpans(0, mText.length(), true /* create the spell checker if needed */);
+ if (mEditor != null) getEditor().onAttachedToWindow();
}
@Override
@@ -4468,40 +4271,9 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
mPreDrawRegistered = false;
}
- if (mError != null) {
- hideError();
- }
-
- if (mBlink != null) {
- mBlink.removeCallbacks(mBlink);
- }
-
- if (mInsertionPointCursorController != null) {
- mInsertionPointCursorController.onDetached();
- }
-
- if (mSelectionModifierCursorController != null) {
- mSelectionModifierCursorController.onDetached();
- }
-
- if (mShowSuggestionRunnable != null) {
- removeCallbacks(mShowSuggestionRunnable);
- }
-
- hideControllers();
-
resetResolvedDrawables();
- if (mTextDisplayList != null) {
- mTextDisplayList.invalidate();
- }
-
- if (mSpellChecker != null) {
- mSpellChecker.closeSession();
- // Forces the creation of a new SpellChecker next time this window is created.
- // Will handle the cases where the settings has been changed in the meantime.
- mSpellChecker = null;
- }
+ if (mEditor != null) getEditor().onDetachedFromWindow();
}
@Override
@@ -4648,13 +4420,13 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
if (dr.mDrawableStart != null) dr.mDrawableStart.mutate().setAlpha(alpha);
if (dr.mDrawableEnd != null) dr.mDrawableEnd.mutate().setAlpha(alpha);
}
- mTextDisplayListIsValid = false;
+ if (mEditor != null) getEditor().mTextDisplayListIsValid = false;
}
return true;
}
if (mCurrentAlpha != 255) {
- mTextDisplayListIsValid = false;
+ if (mEditor != null) getEditor().mTextDisplayListIsValid = false;
}
mCurrentAlpha = 255;
return false;
@@ -4678,12 +4450,12 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
* @attr ref android.R.styleable#TextView_textIsSelectable
*/
public boolean isTextSelectable() {
- return mTextIsSelectable;
+ return mEditor == null ? false : getEditor().mTextIsSelectable;
}
/**
* Sets whether or not (default) the content of this view is selectable by the user.
- *
+ *
* Note that this methods affect the {@link #setFocusable(boolean)},
* {@link #setFocusableInTouchMode(boolean)} {@link #setClickable(boolean)} and
* {@link #setLongClickable(boolean)} states and you may want to restore these if they were
@@ -4694,16 +4466,18 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
* @param selectable Whether or not the content of this TextView should be selectable.
*/
public void setTextIsSelectable(boolean selectable) {
- if (mTextIsSelectable == selectable) return;
+ if (!selectable && mEditor == null) return; // false is default value with no edit data
- mTextIsSelectable = selectable;
+ createEditorIfNeeded("setTextIsSelectable");
+ if (getEditor().mTextIsSelectable == selectable) return;
+ getEditor().mTextIsSelectable = selectable;
setFocusableInTouchMode(selectable);
setFocusable(selectable);
setClickable(selectable);
setLongClickable(selectable);
- // mInputType is already EditorInfo.TYPE_NULL and mInput is null;
+ // mInputType should already be EditorInfo.TYPE_NULL and mInput should be null
setMovementMethod(selectable ? ArrowKeyMovementMethod.getInstance() : null);
setText(getText(), selectable ? BufferType.SPANNABLE : BufferType.NORMAL);
@@ -4723,7 +4497,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
mergeDrawableStates(drawableState, MULTILINE_STATE_SET);
}
- if (mTextIsSelectable) {
+ if (isTextSelectable()) {
// Disable pressed state, which was introduced when TextView was made clickable.
// Prevents text color change.
// setClickable(false) would have a similar effect, but it also disables focus changes
@@ -4822,7 +4596,6 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
}
Layout layout = mLayout;
- int cursorcolor = color;
if (mHint != null && mText.length() == 0) {
if (mHintTextColor != null) {
@@ -4870,14 +4643,12 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
int voffsetCursor = 0;
// translate in by our padding
- {
- /* shortcircuit calling getVerticaOffset() */
- if ((mGravity & Gravity.VERTICAL_GRAVITY_MASK) != Gravity.TOP) {
- voffsetText = getVerticalOffset(false);
- voffsetCursor = getVerticalOffset(true);
- }
- canvas.translate(compoundPaddingLeft, extendedPaddingTop + voffsetText);
+ /* shortcircuit calling getVerticaOffset() */
+ if ((mGravity & Gravity.VERTICAL_GRAVITY_MASK) != Gravity.TOP) {
+ voffsetText = getVerticalOffset(false);
+ voffsetCursor = getVerticalOffset(true);
}
+ canvas.translate(compoundPaddingLeft, extendedPaddingTop + voffsetText);
final int layoutDirection = getResolvedLayoutDirection();
final int absoluteGravity = Gravity.getAbsoluteGravity(mGravity, layoutDirection);
@@ -4894,154 +4665,17 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
}
}
- Path highlight = null;
- int selStart = -1, selEnd = -1;
- boolean drawCursor = false;
-
- // If there is no movement method, then there can be no selection.
- // Check that first and attempt to skip everything having to do with
- // the cursor.
- // XXX This is not strictly true -- a program could set the
- // selection manually if it really wanted to.
- if (mMovement != null && (isFocused() || isPressed())) {
- selStart = getSelectionStart();
- selEnd = getSelectionEnd();
-
- if (selStart >= 0) {
- if (mHighlightPath == null) mHighlightPath = new Path();
-
- if (selStart == selEnd) {
- if (isCursorVisible() &&
- (SystemClock.uptimeMillis() - mShowCursor) % (2 * BLINK) < BLINK) {
- if (mHighlightPathBogus) {
- mHighlightPath.reset();
- mLayout.getCursorPath(selStart, mHighlightPath, mText);
- updateCursorsPositions();
- mHighlightPathBogus = false;
- }
-
- // XXX should pass to skin instead of drawing directly
- mHighlightPaint.setColor(cursorcolor);
- if (mCurrentAlpha != 255) {
- mHighlightPaint.setAlpha(
- (mCurrentAlpha * Color.alpha(cursorcolor)) / 255);
- }
- mHighlightPaint.setStyle(Paint.Style.STROKE);
- highlight = mHighlightPath;
- drawCursor = mCursorCount > 0;
- }
- } else if (textCanBeSelected()) {
- if (mHighlightPathBogus) {
- mHighlightPath.reset();
- mLayout.getSelectionPath(selStart, selEnd, mHighlightPath);
- mHighlightPathBogus = false;
- }
-
- // XXX should pass to skin instead of drawing directly
- mHighlightPaint.setColor(mHighlightColor);
- if (mCurrentAlpha != 255) {
- mHighlightPaint.setAlpha(
- (mCurrentAlpha * Color.alpha(mHighlightColor)) / 255);
- }
- mHighlightPaint.setStyle(Paint.Style.FILL);
-
- highlight = mHighlightPath;
- }
- }
- }
-
- final InputMethodState ims = mInputMethodState;
final int cursorOffsetVertical = voffsetCursor - voffsetText;
- if (ims != null && ims.mBatchEditNesting == 0) {
- InputMethodManager imm = InputMethodManager.peekInstance();
- if (imm != null) {
- if (imm.isActive(this)) {
- boolean reported = false;
- if (ims.mContentChanged || ims.mSelectionModeChanged) {
- // We are in extract mode and the content has changed
- // in some way... just report complete new text to the
- // input method.
- reported = reportExtractedText();
- }
- if (!reported && highlight != null) {
- int candStart = -1;
- int candEnd = -1;
- if (mText instanceof Spannable) {
- Spannable sp = (Spannable)mText;
- candStart = EditableInputConnection.getComposingSpanStart(sp);
- candEnd = EditableInputConnection.getComposingSpanEnd(sp);
- }
- imm.updateSelection(this, selStart, selEnd, candStart, candEnd);
- }
- }
-
- if (imm.isWatchingCursor(this) && highlight != null) {
- highlight.computeBounds(ims.mTmpRectF, true);
- ims.mTmpOffset[0] = ims.mTmpOffset[1] = 0;
-
- canvas.getMatrix().mapPoints(ims.mTmpOffset);
- ims.mTmpRectF.offset(ims.mTmpOffset[0], ims.mTmpOffset[1]);
-
- ims.mTmpRectF.offset(0, cursorOffsetVertical);
-
- ims.mCursorRectInWindow.set((int)(ims.mTmpRectF.left + 0.5),
- (int)(ims.mTmpRectF.top + 0.5),
- (int)(ims.mTmpRectF.right + 0.5),
- (int)(ims.mTmpRectF.bottom + 0.5));
-
- imm.updateCursor(this,
- ims.mCursorRectInWindow.left, ims.mCursorRectInWindow.top,
- ims.mCursorRectInWindow.right, ims.mCursorRectInWindow.bottom);
- }
- }
- }
-
- if (mCorrectionHighlighter != null) {
- mCorrectionHighlighter.draw(canvas, cursorOffsetVertical);
- }
-
- if (drawCursor) {
- drawCursor(canvas, cursorOffsetVertical);
- // Rely on the drawable entirely, do not draw the cursor line.
- // Has to be done after the IMM related code above which relies on the highlight.
- highlight = null;
- }
-
- if (canHaveDisplayList() && canvas.isHardwareAccelerated()) {
- final int width = mRight - mLeft;
- final int height = mBottom - mTop;
-
- if (mTextDisplayList == null || !mTextDisplayList.isValid() ||
- !mTextDisplayListIsValid) {
- if (mTextDisplayList == null) {
- mTextDisplayList = getHardwareRenderer().createDisplayList("Text");
- }
- final HardwareCanvas hardwareCanvas = mTextDisplayList.start();
- try {
- hardwareCanvas.setViewport(width, height);
- // The dirty rect should always be null for a display list
- hardwareCanvas.onPreDraw(null);
- hardwareCanvas.translate(-mScrollX, -mScrollY);
- layout.draw(hardwareCanvas, highlight, mHighlightPaint, cursorOffsetVertical);
- hardwareCanvas.translate(mScrollX, mScrollY);
- } finally {
- hardwareCanvas.onPostDraw();
- mTextDisplayList.end();
- mTextDisplayListIsValid = true;
- }
- }
- canvas.translate(mScrollX, mScrollY);
- ((HardwareCanvas) canvas).drawDisplayList(mTextDisplayList, width, height, null,
- DisplayList.FLAG_CLIP_CHILDREN);
- canvas.translate(-mScrollX, -mScrollY);
+ if (mEditor != null) {
+ getEditor().onDraw(canvas, layout, cursorOffsetVertical);
} else {
- layout.draw(canvas, highlight, mHighlightPaint, cursorOffsetVertical);
- }
+ layout.draw(canvas, null, null, cursorOffsetVertical);
- if (mMarquee != null && mMarquee.shouldDrawGhost()) {
- canvas.translate((int) mMarquee.getGhostOffset(), 0.0f);
- layout.draw(canvas, highlight, mHighlightPaint, cursorOffsetVertical);
+ if (mMarquee != null && mMarquee.shouldDrawGhost()) {
+ canvas.translate((int) mMarquee.getGhostOffset(), 0.0f);
+ layout.draw(canvas, null, null, cursorOffsetVertical);
+ }
}
canvas.restore();
@@ -5049,7 +4683,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
private void updateCursorsPositions() {
if (mCursorDrawableRes == 0) {
- mCursorCount = 0;
+ getEditor().mCursorCount = 0;
return;
}
@@ -5058,40 +4692,39 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
final int top = mLayout.getLineTop(line);
final int bottom = mLayout.getLineTop(line + 1);
- mCursorCount = mLayout.isLevelBoundary(offset) ? 2 : 1;
+ getEditor().mCursorCount = mLayout.isLevelBoundary(offset) ? 2 : 1;
int middle = bottom;
- if (mCursorCount == 2) {
+ if (getEditor().mCursorCount == 2) {
// Similar to what is done in {@link Layout.#getCursorPath(int, Path, CharSequence)}
middle = (top + bottom) >> 1;
}
updateCursorPosition(0, top, middle, mLayout.getPrimaryHorizontal(offset));
- if (mCursorCount == 2) {
+ if (getEditor().mCursorCount == 2) {
updateCursorPosition(1, middle, bottom, mLayout.getSecondaryHorizontal(offset));
}
}
private void updateCursorPosition(int cursorIndex, int top, int bottom, float horizontal) {
- if (mCursorDrawable[cursorIndex] == null)
- mCursorDrawable[cursorIndex] = mContext.getResources().getDrawable(mCursorDrawableRes);
+ if (getEditor().mCursorDrawable[cursorIndex] == null)
+ getEditor().mCursorDrawable[cursorIndex] = mContext.getResources().getDrawable(mCursorDrawableRes);
if (mTempRect == null) mTempRect = new Rect();
-
- mCursorDrawable[cursorIndex].getPadding(mTempRect);
- final int width = mCursorDrawable[cursorIndex].getIntrinsicWidth();
+ getEditor().mCursorDrawable[cursorIndex].getPadding(mTempRect);
+ final int width = getEditor().mCursorDrawable[cursorIndex].getIntrinsicWidth();
horizontal = Math.max(0.5f, horizontal - 0.5f);
final int left = (int) (horizontal) - mTempRect.left;
- mCursorDrawable[cursorIndex].setBounds(left, top - mTempRect.top, left + width,
+ getEditor().mCursorDrawable[cursorIndex].setBounds(left, top - mTempRect.top, left + width,
bottom + mTempRect.bottom);
}
private void drawCursor(Canvas canvas, int cursorOffsetVertical) {
final boolean translate = cursorOffsetVertical != 0;
if (translate) canvas.translate(0, cursorOffsetVertical);
- for (int i = 0; i < mCursorCount; i++) {
- mCursorDrawable[i].draw(canvas);
+ for (int i = 0; i < getEditor().mCursorCount; i++) {
+ getEditor().mCursorDrawable[i].draw(canvas);
}
if (translate) canvas.translate(0, -cursorOffsetVertical);
}
@@ -5125,18 +4758,23 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
r.left = (int) mLayout.getPrimaryHorizontal(selStart);
r.right = (int) mLayout.getPrimaryHorizontal(selEnd);
} else {
- // Selection extends across multiple lines -- the focused
- // rect covers the entire width.
- if (mHighlightPath == null) mHighlightPath = new Path();
- if (mHighlightPathBogus) {
- mHighlightPath.reset();
- mLayout.getSelectionPath(selStart, selEnd, mHighlightPath);
- mHighlightPathBogus = false;
- }
- synchronized (sTempRect) {
- mHighlightPath.computeBounds(sTempRect, true);
- r.left = (int)sTempRect.left-1;
- r.right = (int)sTempRect.right+1;
+ // Selection extends across multiple lines -- make the focused
+ // rect cover the entire width.
+ if (mEditor != null) {
+ if (getEditor().mHighlightPath == null) getEditor().mHighlightPath = new Path();
+ if (getEditor().mHighlightPathBogus) {
+ getEditor().mHighlightPath.reset();
+ mLayout.getSelectionPath(selStart, selEnd, getEditor().mHighlightPath);
+ getEditor().mHighlightPathBogus = false;
+ }
+ synchronized (TEMP_RECTF) {
+ getEditor().mHighlightPath.computeBounds(TEMP_RECTF, true);
+ r.left = (int)TEMP_RECTF.left-1;
+ r.right = (int)TEMP_RECTF.right+1;
+ }
+ } else {
+ r.left = 0;
+ r.right = getMeasuredWidth();
}
}
}
@@ -5232,7 +4870,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
@Override
public boolean onKeyPreIme(int keyCode, KeyEvent event) {
if (keyCode == KeyEvent.KEYCODE_BACK) {
- boolean isInSelectionMode = mSelectionActionMode != null;
+ boolean isInSelectionMode = mEditor != null && getEditor().mSelectionActionMode != null;
if (isInSelectionMode) {
if (event.getAction() == KeyEvent.ACTION_DOWN && event.getRepeatCount() == 0) {
@@ -5290,14 +4928,16 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
// but adding that is a more complicated change.
KeyEvent up = KeyEvent.changeAction(event, KeyEvent.ACTION_UP);
if (which == 1) {
- mInput.onKeyUp(this, (Editable)mText, keyCode, up);
+ // mEditor and getEditor().mInput are not null from doKeyDown
+ getEditor().mKeyListener.onKeyUp(this, (Editable)mText, keyCode, up);
while (--repeatCount > 0) {
- mInput.onKeyDown(this, (Editable)mText, keyCode, down);
- mInput.onKeyUp(this, (Editable)mText, keyCode, up);
+ getEditor().mKeyListener.onKeyDown(this, (Editable)mText, keyCode, down);
+ getEditor().mKeyListener.onKeyUp(this, (Editable)mText, keyCode, up);
}
hideErrorIfUnchanged();
} else if (which == 2) {
+ // mMovement is not null from doKeyDown
mMovement.onKeyUp(this, (Spannable)mText, keyCode, up);
while (--repeatCount > 0) {
mMovement.onKeyDown(this, (Spannable)mText, keyCode, down);
@@ -5315,7 +4955,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
* lines but where it doesn't make sense to insert newlines.
*/
private boolean shouldAdvanceFocusOnEnter() {
- if (mInput == null) {
+ if (getKeyListener() == null) {
return false;
}
@@ -5323,8 +4963,8 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
return true;
}
- if ((mInputType & EditorInfo.TYPE_MASK_CLASS) == EditorInfo.TYPE_CLASS_TEXT) {
- int variation = mInputType & EditorInfo.TYPE_MASK_VARIATION;
+ if (mEditor != null && (getEditor().mInputType & EditorInfo.TYPE_MASK_CLASS) == EditorInfo.TYPE_CLASS_TEXT) {
+ int variation = getEditor().mInputType & EditorInfo.TYPE_MASK_VARIATION;
if (variation == EditorInfo.TYPE_TEXT_VARIATION_EMAIL_ADDRESS
|| variation == EditorInfo.TYPE_TEXT_VARIATION_EMAIL_SUBJECT) {
return true;
@@ -5339,9 +4979,9 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
* of inserting the character. Insert tabs only in multi-line editors.
*/
private boolean shouldAdvanceFocusOnTab() {
- if (mInput != null && !mSingleLine) {
- if ((mInputType & EditorInfo.TYPE_MASK_CLASS) == EditorInfo.TYPE_CLASS_TEXT) {
- int variation = mInputType & EditorInfo.TYPE_MASK_VARIATION;
+ if (getKeyListener() != null && !mSingleLine) {
+ if (mEditor != null && (getEditor().mInputType & EditorInfo.TYPE_MASK_CLASS) == EditorInfo.TYPE_CLASS_TEXT) {
+ int variation = getEditor().mInputType & EditorInfo.TYPE_MASK_VARIATION;
if (variation == EditorInfo.TYPE_TEXT_FLAG_IME_MULTI_LINE
|| variation == EditorInfo.TYPE_TEXT_FLAG_MULTI_LINE) {
return false;
@@ -5363,13 +5003,13 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
// running in a "modern" cupcake environment, so don't need
// to worry about the application trying to capture
// enter key events.
- if (mInputContentType != null) {
+ if (mEditor != null && getEditor().mInputContentType != null) {
// If there is an action listener, given them a
// chance to consume the event.
- if (mInputContentType.onEditorActionListener != null &&
- mInputContentType.onEditorActionListener.onEditorAction(
+ if (getEditor().mInputContentType.onEditorActionListener != null &&
+ getEditor().mInputContentType.onEditorActionListener.onEditorAction(
this, EditorInfo.IME_NULL, event)) {
- mInputContentType.enterDown = true;
+ getEditor().mInputContentType.enterDown = true;
// We are consuming the enter key for them.
return -1;
}
@@ -5406,21 +5046,21 @@ 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 (mSelectionActionMode != null) {
+ if (mEditor != null && getEditor().mSelectionActionMode != null) {
stopSelectionActionMode();
return -1;
}
break;
}
- if (mInput != null) {
+ if (mEditor != null && getEditor().mKeyListener != null) {
resetErrorChangedFlag();
boolean doDown = true;
if (otherEvent != null) {
try {
beginBatchEdit();
- final boolean handled = mInput.onKeyOther(this, (Editable) mText, otherEvent);
+ final boolean handled = getEditor().mKeyListener.onKeyOther(this, (Editable) mText, otherEvent);
hideErrorIfUnchanged();
doDown = false;
if (handled) {
@@ -5436,7 +5076,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
if (doDown) {
beginBatchEdit();
- final boolean handled = mInput.onKeyDown(this, (Editable) mText, keyCode, event);
+ final boolean handled = getEditor().mKeyListener.onKeyDown(this, (Editable) mText, keyCode, event);
endBatchEdit();
hideErrorIfUnchanged();
if (handled) return 1;
@@ -5482,14 +5122,14 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
* that error showing. Otherwise, we take down whatever
* error was showing when the user types something.
*/
- mErrorWasChanged = false;
+ if (mEditor != null) getEditor().mErrorWasChanged = false;
}
/**
* @hide
*/
public void hideErrorIfUnchanged() {
- if (mError != null && !mErrorWasChanged) {
+ if (mEditor != null && getEditor().mError != null && !getEditor().mErrorWasChanged) {
setError(null, null);
}
}
@@ -5527,11 +5167,11 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
case KeyEvent.KEYCODE_ENTER:
if (event.hasNoModifiers()) {
- if (mInputContentType != null
- && mInputContentType.onEditorActionListener != null
- && mInputContentType.enterDown) {
- mInputContentType.enterDown = false;
- if (mInputContentType.onEditorActionListener.onEditorAction(
+ if (mEditor != null && getEditor().mInputContentType != null
+ && getEditor().mInputContentType.onEditorActionListener != null
+ && getEditor().mInputContentType.enterDown) {
+ getEditor().mInputContentType.enterDown = false;
+ if (getEditor().mInputContentType.onEditorActionListener.onEditorAction(
this, EditorInfo.IME_NULL, event)) {
return true;
}
@@ -5582,8 +5222,8 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
break;
}
- if (mInput != null)
- if (mInput.onKeyUp(this, (Editable) mText, keyCode, event))
+ if (mEditor != null && getEditor().mKeyListener != null)
+ if (getEditor().mKeyListener.onKeyUp(this, (Editable) mText, keyCode, event))
return true;
if (mMovement != null && mLayout != null)
@@ -5595,22 +5235,23 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
@Override
public boolean onCheckIsTextEditor() {
- return mInputType != EditorInfo.TYPE_NULL;
+ return mEditor != null && getEditor().mInputType != EditorInfo.TYPE_NULL;
}
@Override
public InputConnection onCreateInputConnection(EditorInfo outAttrs) {
+ createEditorIfNeeded("onCreateInputConnection");
if (onCheckIsTextEditor() && isEnabled()) {
- if (mInputMethodState == null) {
- mInputMethodState = new InputMethodState();
- }
- outAttrs.inputType = mInputType;
- if (mInputContentType != null) {
- outAttrs.imeOptions = mInputContentType.imeOptions;
- outAttrs.privateImeOptions = mInputContentType.privateImeOptions;
- outAttrs.actionLabel = mInputContentType.imeActionLabel;
- outAttrs.actionId = mInputContentType.imeActionId;
- outAttrs.extras = mInputContentType.extras;
+ if (getEditor().mInputMethodState == null) {
+ getEditor().mInputMethodState = new InputMethodState();
+ }
+ outAttrs.inputType = getInputType();
+ if (getEditor().mInputContentType != null) {
+ outAttrs.imeOptions = getEditor().mInputContentType.imeOptions;
+ outAttrs.privateImeOptions = getEditor().mInputContentType.privateImeOptions;
+ outAttrs.actionLabel = getEditor().mInputContentType.imeActionLabel;
+ outAttrs.actionId = getEditor().mInputContentType.imeActionId;
+ outAttrs.extras = getEditor().mInputContentType.extras;
} else {
outAttrs.imeOptions = EditorInfo.IME_NULL;
}
@@ -5644,7 +5285,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
InputConnection ic = new EditableInputConnection(this);
outAttrs.initialSelStart = getSelectionStart();
outAttrs.initialSelEnd = getSelectionEnd();
- outAttrs.initialCapsMode = ic.getCursorCapsMode(mInputType);
+ outAttrs.initialCapsMode = ic.getCursorCapsMode(getInputType());
return ic;
}
}
@@ -5736,13 +5377,13 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
}
boolean reportExtractedText() {
- final InputMethodState ims = mInputMethodState;
+ final InputMethodState ims = getEditor().mInputMethodState;
if (ims != null) {
final boolean contentChanged = ims.mContentChanged;
if (contentChanged || ims.mSelectionModeChanged) {
ims.mContentChanged = false;
ims.mSelectionModeChanged = false;
- final ExtractedTextRequest req = mInputMethodState.mExtracting;
+ final ExtractedTextRequest req = ims.mExtracting;
if (req != null) {
InputMethodManager imm = InputMethodManager.peekInstance();
if (imm != null) {
@@ -5758,8 +5399,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
+ ims.mTmpExtracted.partialStartOffset
+ " end=" + ims.mTmpExtracted.partialEndOffset
+ ": " + ims.mTmpExtracted.text);
- imm.updateExtractedText(this, req.token,
- mInputMethodState.mTmpExtracted);
+ imm.updateExtractedText(this, req.token, ims.mTmpExtracted);
ims.mChangedStart = EXTRACT_UNKNOWN;
ims.mChangedEnd = EXTRACT_UNKNOWN;
ims.mChangedDelta = 0;
@@ -5836,8 +5476,8 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
* @hide
*/
public void setExtracting(ExtractedTextRequest req) {
- if (mInputMethodState != null) {
- mInputMethodState.mExtracting = req;
+ if (getEditor().mInputMethodState != null) {
+ getEditor().mInputMethodState.mExtracting = req;
}
// This would stop a possible selection mode, but no such mode is started in case
// extracted mode will start. Some text is selected though, and will trigger an action mode
@@ -5868,109 +5508,20 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
* @param info The auto correct info about the text that was corrected.
*/
public void onCommitCorrection(CorrectionInfo info) {
- if (mCorrectionHighlighter == null) {
- mCorrectionHighlighter = new CorrectionHighlighter();
+ if (mEditor == null) return;
+ if (getEditor().mCorrectionHighlighter == null) {
+ getEditor().mCorrectionHighlighter = new CorrectionHighlighter();
} else {
- mCorrectionHighlighter.invalidate(false);
- }
-
- mCorrectionHighlighter.highlight(info);
- }
-
- private class CorrectionHighlighter {
- private final Path mPath = new Path();
- private final Paint mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
- private int mStart, mEnd;
- private long mFadingStartTime;
- private final static int FADE_OUT_DURATION = 400;
-
- public CorrectionHighlighter() {
- mPaint.setCompatibilityScaling(getResources().getCompatibilityInfo().applicationScale);
- mPaint.setStyle(Paint.Style.FILL);
- }
-
- public void highlight(CorrectionInfo info) {
- mStart = info.getOffset();
- mEnd = mStart + info.getNewText().length();
- mFadingStartTime = SystemClock.uptimeMillis();
-
- if (mStart < 0 || mEnd < 0) {
- stopAnimation();
- }
- }
-
- public void draw(Canvas canvas, int cursorOffsetVertical) {
- if (updatePath() && updatePaint()) {
- if (cursorOffsetVertical != 0) {
- canvas.translate(0, cursorOffsetVertical);
- }
-
- canvas.drawPath(mPath, mPaint);
-
- if (cursorOffsetVertical != 0) {
- canvas.translate(0, -cursorOffsetVertical);
- }
- invalidate(true); // TODO invalidate cursor region only
- } else {
- stopAnimation();
- invalidate(false); // TODO invalidate cursor region only
- }
- }
-
- private boolean updatePaint() {
- final long duration = SystemClock.uptimeMillis() - mFadingStartTime;
- if (duration > FADE_OUT_DURATION) return false;
-
- final float coef = 1.0f - (float) duration / FADE_OUT_DURATION;
- final int highlightColorAlpha = Color.alpha(mHighlightColor);
- final int color = (mHighlightColor & 0x00FFFFFF) +
- ((int) (highlightColorAlpha * coef) << 24);
- mPaint.setColor(color);
- return true;
- }
-
- private boolean updatePath() {
- final Layout layout = TextView.this.mLayout;
- if (layout == null) return false;
-
- // Update in case text is edited while the animation is run
- final int length = mText.length();
- int start = Math.min(length, mStart);
- int end = Math.min(length, mEnd);
-
- mPath.reset();
- TextView.this.mLayout.getSelectionPath(start, end, mPath);
- return true;
- }
-
- private void invalidate(boolean delayed) {
- if (TextView.this.mLayout == null) return;
-
- synchronized (sTempRect) {
- mPath.computeBounds(sTempRect, false);
-
- int left = getCompoundPaddingLeft();
- int top = getExtendedPaddingTop() + getVerticalOffset(true);
-
- if (delayed) {
- TextView.this.postInvalidateDelayed(16, // 60 Hz update
- left + (int) sTempRect.left, top + (int) sTempRect.top,
- left + (int) sTempRect.right, top + (int) sTempRect.bottom);
- } else {
- TextView.this.postInvalidate((int) sTempRect.left, (int) sTempRect.top,
- (int) sTempRect.right, (int) sTempRect.bottom);
- }
- }
+ getEditor().mCorrectionHighlighter.invalidate(false);
}
- private void stopAnimation() {
- TextView.this.mCorrectionHighlighter = null;
- }
+ getEditor().mCorrectionHighlighter.highlight(info);
}
public void beginBatchEdit() {
- mInBatchEditControllers = true;
- final InputMethodState ims = mInputMethodState;
+ if (mEditor == null) return;
+ getEditor().mInBatchEditControllers = true;
+ final InputMethodState ims = getEditor().mInputMethodState;
if (ims != null) {
int nesting = ++ims.mBatchEditNesting;
if (nesting == 1) {
@@ -5992,8 +5543,9 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
}
public void endBatchEdit() {
- mInBatchEditControllers = false;
- final InputMethodState ims = mInputMethodState;
+ if (mEditor == null) return;
+ getEditor().mInBatchEditControllers = false;
+ final InputMethodState ims = getEditor().mInputMethodState;
if (ims != null) {
int nesting = --ims.mBatchEditNesting;
if (nesting == 0) {
@@ -6003,7 +5555,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
}
void ensureEndedBatchEdit() {
- final InputMethodState ims = mInputMethodState;
+ final InputMethodState ims = getEditor().mInputMethodState;
if (ims != null && ims.mBatchEditNesting != 0) {
ims.mBatchEditNesting = 0;
finishBatchEdit(ims);
@@ -6031,7 +5583,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
}
if (curs >= 0) {
- mHighlightPathBogus = true;
+ getEditor().mHighlightPathBogus = true;
makeBlink();
bringPointIntoView(curs);
}
@@ -6046,7 +5598,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
public void onBeginBatchEdit() {
// intentionally empty
}
-
+
/**
* Called by the framework in response to a request to end a batch
* of edit operations through a call to link {@link #endBatchEdit}.
@@ -6111,8 +5663,8 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
super.resetResolvedLayoutDirection();
if (mLayoutAlignment != null &&
- (mTextAlign == TextAlign.VIEW_START ||
- mTextAlign == TextAlign.VIEW_END)) {
+ (mTextAlign == TEXT_ALIGN.VIEW_START ||
+ mTextAlign == TEXT_ALIGN.VIEW_END)) {
mLayoutAlignment = null;
}
}
@@ -6120,7 +5672,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
private Layout.Alignment getLayoutAlignment() {
if (mLayoutAlignment == null) {
Layout.Alignment alignment;
- TextAlign textAlign = mTextAlign;
+ TEXT_ALIGN textAlign = mTextAlign;
switch (textAlign) {
case INHERIT:
// fall through to gravity temporarily
@@ -6188,7 +5740,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
mOldMaximum = mMaximum;
mOldMaxMode = mMaxMode;
- mHighlightPathBogus = true;
+ if (mEditor != null) getEditor().mHighlightPathBogus = true;
if (wantWidth < 0) {
wantWidth = 0;
@@ -6198,7 +5750,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
}
Layout.Alignment alignment = getLayoutAlignment();
- boolean shouldEllipsize = mEllipsize != null && mInput == null;
+ boolean shouldEllipsize = mEllipsize != null && getKeyListener() == null;
final boolean switchEllipsize = mEllipsize == TruncateAt.MARQUEE &&
mMarqueeFadeMode != MARQUEE_FADE_NORMAL;
TruncateAt effectiveEllipsize = mEllipsize;
@@ -6315,7 +5867,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
if (mText instanceof Spannable) {
result = new DynamicLayout(mText, mTransformed, mTextPaint, wantWidth,
alignment, mTextDir, mSpacingMult,
- mSpacingAdd, mIncludePad, mInput == null ? effectiveEllipsize : null,
+ mSpacingAdd, mIncludePad, getKeyListener() == null ? effectiveEllipsize : null,
ellipsisWidth);
} else {
if (boring == UNKNOWN_BORING) {
@@ -6776,7 +6328,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
super.onLayout(changed, left, top, right, bottom);
- if (changed) mTextDisplayListIsValid = false;
+ if (changed && mEditor != null) getEditor().mTextDisplayListIsValid = false;
}
/**
@@ -7002,11 +6554,11 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
// This offsets because getInterestingRect() is in terms of viewport coordinates, but
// requestRectangleOnScreen() is in terms of content coordinates.
- if (mTempRect == null) mTempRect = new Rect();
// The offsets here are to ensure the rectangle we are using is
// within our view bounds, in case the cursor is on the far left
// or right. If it isn't withing the bounds, then this request
// will be ignored.
+ if (mTempRect == null) mTempRect = new Rect();
mTempRect.set(x - 2, top, x + 2, bottom);
getInterestingRect(mTempRect, line);
mTempRect.offset(mScrollX, mScrollY);
@@ -7226,11 +6778,11 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
* @param singleLine
*/
private void setInputTypeSingleLine(boolean singleLine) {
- if ((mInputType & EditorInfo.TYPE_MASK_CLASS) == EditorInfo.TYPE_CLASS_TEXT) {
+ if (mEditor != null && (getEditor().mInputType & EditorInfo.TYPE_MASK_CLASS) == EditorInfo.TYPE_CLASS_TEXT) {
if (singleLine) {
- mInputType &= ~EditorInfo.TYPE_TEXT_FLAG_MULTI_LINE;
+ getEditor().mInputType &= ~EditorInfo.TYPE_TEXT_FLAG_MULTI_LINE;
} else {
- mInputType |= EditorInfo.TYPE_TEXT_FLAG_MULTI_LINE;
+ getEditor().mInputType |= EditorInfo.TYPE_TEXT_FLAG_MULTI_LINE;
}
}
}
@@ -7309,7 +6861,8 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
*/
@android.view.RemotableViewMethod
public void setSelectAllOnFocus(boolean selectAllOnFocus) {
- mSelectAllOnFocus = selectAllOnFocus;
+ createEditorIfNeeded("setSelectAllOnFocus");
+ getEditor().mSelectAllOnFocus = selectAllOnFocus;
if (selectAllOnFocus && !(mText instanceof Spannable)) {
setText(mText, BufferType.SPANNABLE);
@@ -7323,8 +6876,10 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
*/
@android.view.RemotableViewMethod
public void setCursorVisible(boolean visible) {
- if (mCursorVisible != visible) {
- mCursorVisible = visible;
+ if (visible && mEditor == null) return; // visible is the default value with no edit data
+ createEditorIfNeeded("setCursorVisible");
+ if (getEditor().mCursorVisible != visible) {
+ getEditor().mCursorVisible = visible;
invalidate();
makeBlink();
@@ -7335,7 +6890,8 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
}
private boolean isCursorVisible() {
- return mCursorVisible && isTextEditable();
+ // The default value is true, even when there is no associated Editor
+ return mEditor == null ? true : (getEditor().mCursorVisible && isTextEditable());
}
private boolean canMarquee() {
@@ -7347,7 +6903,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
private void startMarquee() {
// Do not ellipsize EditText
- if (mInput != null) return;
+ if (getKeyListener() != null) return;
if (compressText(getWidth() - getCompoundPaddingLeft() - getCompoundPaddingRight())) {
return;
@@ -7397,142 +6953,6 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
}
}
- private static final class Marquee extends Handler {
- // TODO: Add an option to configure this
- private static final float MARQUEE_DELTA_MAX = 0.07f;
- private static final int MARQUEE_DELAY = 1200;
- private static final int MARQUEE_RESTART_DELAY = 1200;
- private static final int MARQUEE_RESOLUTION = 1000 / 30;
- private static final int MARQUEE_PIXELS_PER_SECOND = 30;
-
- private static final byte MARQUEE_STOPPED = 0x0;
- private static final byte MARQUEE_STARTING = 0x1;
- private static final byte MARQUEE_RUNNING = 0x2;
-
- private static final int MESSAGE_START = 0x1;
- private static final int MESSAGE_TICK = 0x2;
- private static final int MESSAGE_RESTART = 0x3;
-
- private final WeakReference<TextView> mView;
-
- private byte mStatus = MARQUEE_STOPPED;
- private final float mScrollUnit;
- private float mMaxScroll;
- float mMaxFadeScroll;
- private float mGhostStart;
- private float mGhostOffset;
- private float mFadeStop;
- private int mRepeatLimit;
-
- float mScroll;
-
- Marquee(TextView v) {
- final float density = v.getContext().getResources().getDisplayMetrics().density;
- mScrollUnit = (MARQUEE_PIXELS_PER_SECOND * density) / MARQUEE_RESOLUTION;
- mView = new WeakReference<TextView>(v);
- }
-
- @Override
- public void handleMessage(Message msg) {
- switch (msg.what) {
- case MESSAGE_START:
- mStatus = MARQUEE_RUNNING;
- tick();
- break;
- case MESSAGE_TICK:
- tick();
- break;
- case MESSAGE_RESTART:
- if (mStatus == MARQUEE_RUNNING) {
- if (mRepeatLimit >= 0) {
- mRepeatLimit--;
- }
- start(mRepeatLimit);
- }
- break;
- }
- }
-
- void tick() {
- if (mStatus != MARQUEE_RUNNING) {
- return;
- }
-
- removeMessages(MESSAGE_TICK);
-
- final TextView textView = mView.get();
- if (textView != null && (textView.isFocused() || textView.isSelected())) {
- mScroll += mScrollUnit;
- if (mScroll > mMaxScroll) {
- mScroll = mMaxScroll;
- sendEmptyMessageDelayed(MESSAGE_RESTART, MARQUEE_RESTART_DELAY);
- } else {
- sendEmptyMessageDelayed(MESSAGE_TICK, MARQUEE_RESOLUTION);
- }
- textView.invalidate();
- }
- }
-
- void stop() {
- mStatus = MARQUEE_STOPPED;
- removeMessages(MESSAGE_START);
- removeMessages(MESSAGE_RESTART);
- removeMessages(MESSAGE_TICK);
- resetScroll();
- }
-
- private void resetScroll() {
- mScroll = 0.0f;
- final TextView textView = mView.get();
- if (textView != null) textView.invalidate();
- }
-
- void start(int repeatLimit) {
- if (repeatLimit == 0) {
- stop();
- return;
- }
- mRepeatLimit = repeatLimit;
- final TextView textView = mView.get();
- if (textView != null && textView.mLayout != null) {
- mStatus = MARQUEE_STARTING;
- mScroll = 0.0f;
- final int textWidth = textView.getWidth() - textView.getCompoundPaddingLeft() -
- textView.getCompoundPaddingRight();
- final float lineWidth = textView.mLayout.getLineWidth(0);
- final float gap = textWidth / 3.0f;
- mGhostStart = lineWidth - textWidth + gap;
- mMaxScroll = mGhostStart + textWidth;
- mGhostOffset = lineWidth + gap;
- mFadeStop = lineWidth + textWidth / 6.0f;
- mMaxFadeScroll = mGhostStart + lineWidth + lineWidth;
-
- textView.invalidate();
- sendEmptyMessageDelayed(MESSAGE_START, MARQUEE_DELAY);
- }
- }
-
- float getGhostOffset() {
- return mGhostOffset;
- }
-
- boolean shouldDrawLeftFade() {
- return mScroll <= mFadeStop;
- }
-
- boolean shouldDrawGhost() {
- return mStatus == MARQUEE_RUNNING && mScroll > mGhostStart;
- }
-
- boolean isRunning() {
- return mStatus == MARQUEE_RUNNING;
- }
-
- boolean isStopped() {
- return mStatus == MARQUEE_STOPPED;
- }
- }
-
/**
* This method is called when the text is changed, in case any subclasses
* would like to know.
@@ -7561,7 +6981,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
*/
protected void onSelectionChanged(int selStart, int selEnd) {
sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_TEXT_SELECTION_CHANGED);
- mTextDisplayListIsValid = false;
+ getEditor().mTextDisplayListIsValid = false;
}
/**
@@ -7640,13 +7060,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
}
}
- updateSpellCheckSpans(start, start + after, false);
- mTextDisplayListIsValid = false;
-
- // Hide the controllers as soon as text is modified (typing, procedural...)
- // 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();
+ if (mEditor != null) getEditor().sendOnTextChanged(start, after);
}
/**
@@ -7668,7 +7082,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
* through a thunk.
*/
void handleTextChanged(CharSequence buffer, int start, int before, int after) {
- final InputMethodState ims = mInputMethodState;
+ final InputMethodState ims = mEditor == null ? null : getEditor().mInputMethodState;
if (ims == null || ims.mBatchEditNesting == 0) {
updateAfterEdit();
}
@@ -7687,7 +7101,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
sendOnTextChanged(buffer, start, before, after);
onTextChanged(buffer, start, before, after);
}
-
+
/**
* Not private so it can be called from an inner class without going
* through a thunk.
@@ -7698,18 +7112,13 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
boolean selChanged = false;
int newSelStart=-1, newSelEnd=-1;
-
- final InputMethodState ims = mInputMethodState;
-
+
+ final InputMethodState ims = mEditor == null ? null : getEditor().mInputMethodState;
+
if (what == Selection.SELECTION_END) {
- mHighlightPathBogus = true;
selChanged = true;
newSelEnd = newStart;
- if (!isFocused()) {
- mSelectionMoved = true;
- }
-
if (oldStart >= 0 || newStart >= 0) {
invalidateCursor(Selection.getSelectionStart(buf), oldStart, newStart);
registerForPreDraw();
@@ -7718,14 +7127,9 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
}
if (what == Selection.SELECTION_START) {
- mHighlightPathBogus = true;
selChanged = true;
newSelStart = newStart;
- if (!isFocused()) {
- mSelectionMoved = true;
- }
-
if (oldStart >= 0 || newStart >= 0) {
int end = Selection.getSelectionEnd(buf);
invalidateCursor(end, oldStart, newStart);
@@ -7733,6 +7137,11 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
}
if (selChanged) {
+ if (mEditor != null) {
+ getEditor().mHighlightPathBogus = true;
+ if (!isFocused()) getEditor().mSelectionMoved = true;
+ }
+
if ((buf.getSpanFlags(what)&Spanned.SPAN_INTERMEDIATE) == 0) {
if (newSelStart < 0) {
newSelStart = Selection.getSelectionStart(buf);
@@ -7748,16 +7157,16 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
what instanceof CharacterStyle) {
if (ims == null || ims.mBatchEditNesting == 0) {
invalidate();
- mHighlightPathBogus = true;
+ if (mEditor != null) getEditor().mHighlightPathBogus = true;
checkForResize();
} else {
ims.mContentChanged = true;
}
- mTextDisplayListIsValid = false;
+ if (mEditor != null) getEditor().mTextDisplayListIsValid = false;
}
if (MetaKeyKeyListener.isMetaTracker(buf, what)) {
- mHighlightPathBogus = true;
+ if (mEditor != null) getEditor().mHighlightPathBogus = true;
if (ims != null && MetaKeyKeyListener.isSelectingMetaTracker(buf, what)) {
ims.mSelectionModeChanged = true;
}
@@ -7801,8 +7210,8 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
}
}
- if (mSpellChecker != null && newStart < 0 && what instanceof SpellCheckSpan) {
- mSpellChecker.removeSpellCheckSpan((SpellCheckSpan) what);
+ if (mEditor != null && getEditor().mSpellChecker != null && newStart < 0 && what instanceof SpellCheckSpan) {
+ getEditor().mSpellChecker.removeSpellCheckSpan((SpellCheckSpan) what);
}
}
@@ -7811,289 +7220,16 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
*/
private void updateSpellCheckSpans(int start, int end, boolean createSpellChecker) {
if (isTextEditable() && isSuggestionsEnabled() && !(this instanceof ExtractEditText)) {
- if (mSpellChecker == null && createSpellChecker) {
- mSpellChecker = new SpellChecker(this);
- }
- if (mSpellChecker != null) {
- mSpellChecker.spellCheck(start, end);
- }
- }
- }
-
- /**
- * Controls the {@link EasyEditSpan} monitoring when it is added, and when the related
- * pop-up should be displayed.
- */
- private class EasyEditSpanController {
-
- private static final int DISPLAY_TIMEOUT_MS = 3000; // 3 secs
-
- private EasyEditPopupWindow mPopupWindow;
-
- private EasyEditSpan mEasyEditSpan;
-
- private Runnable mHidePopup;
-
- private void hide() {
- if (mPopupWindow != null) {
- mPopupWindow.hide();
- TextView.this.removeCallbacks(mHidePopup);
- }
- removeSpans(mText);
- mEasyEditSpan = null;
- }
-
- /**
- * Monitors the changes in the text.
- *
- * <p>{@link ChangeWatcher#onSpanAdded(Spannable, Object, int, int)} cannot be used,
- * as the notifications are not sent when a spannable (with spans) is inserted.
- */
- public void onTextChange(CharSequence buffer) {
- adjustSpans(mText);
-
- if (getWindowVisibility() != View.VISIBLE) {
- // The window is not visible yet, ignore the text change.
- return;
- }
-
- if (mLayout == null) {
- // The view has not been layout yet, ignore the text change
- return;
- }
-
- InputMethodManager imm = InputMethodManager.peekInstance();
- if (!(TextView.this instanceof ExtractEditText)
- && imm != null && imm.isFullscreenMode()) {
- // The input is in extract mode. We do not have to handle the easy edit in the
- // original TextView, as the ExtractEditText will do
- return;
- }
-
- // Remove the current easy edit span, as the text changed, and remove the pop-up
- // (if any)
- if (mEasyEditSpan != null) {
- if (mText instanceof Spannable) {
- ((Spannable) mText).removeSpan(mEasyEditSpan);
- }
- mEasyEditSpan = null;
- }
- if (mPopupWindow != null && mPopupWindow.isShowing()) {
- mPopupWindow.hide();
- }
-
- // Display the new easy edit span (if any).
- if (buffer instanceof Spanned) {
- mEasyEditSpan = getSpan((Spanned) buffer);
- if (mEasyEditSpan != null) {
- if (mPopupWindow == null) {
- mPopupWindow = new EasyEditPopupWindow();
- mHidePopup = new Runnable() {
- @Override
- public void run() {
- hide();
- }
- };
- }
- mPopupWindow.show(mEasyEditSpan);
- TextView.this.removeCallbacks(mHidePopup);
- TextView.this.postDelayed(mHidePopup, DISPLAY_TIMEOUT_MS);
- }
+ if (getEditor().mSpellChecker == null && createSpellChecker) {
+ getEditor().mSpellChecker = new SpellChecker(this);
}
- }
-
- /**
- * Adjusts the spans by removing all of them except the last one.
- */
- private void adjustSpans(CharSequence buffer) {
- // This method enforces that only one easy edit span is attached to the text.
- // A better way to enforce this would be to listen for onSpanAdded, but this method
- // cannot be used in this scenario as no notification is triggered when a text with
- // spans is inserted into a text.
- if (buffer instanceof Spannable) {
- Spannable spannable = (Spannable) buffer;
- EasyEditSpan[] spans = spannable.getSpans(0, spannable.length(),
- EasyEditSpan.class);
- for (int i = 0; i < spans.length - 1; i++) {
- spannable.removeSpan(spans[i]);
- }
- }
- }
-
- /**
- * Removes all the {@link EasyEditSpan} currently attached.
- */
- private void removeSpans(CharSequence buffer) {
- if (buffer instanceof Spannable) {
- Spannable spannable = (Spannable) buffer;
- EasyEditSpan[] spans = spannable.getSpans(0, spannable.length(),
- EasyEditSpan.class);
- for (int i = 0; i < spans.length; i++) {
- spannable.removeSpan(spans[i]);
- }
- }
- }
-
- private EasyEditSpan getSpan(Spanned spanned) {
- EasyEditSpan[] easyEditSpans = spanned.getSpans(0, spanned.length(),
- EasyEditSpan.class);
- if (easyEditSpans.length == 0) {
- return null;
- } else {
- return easyEditSpans[0];
+ if (getEditor().mSpellChecker != null) {
+ getEditor().mSpellChecker.spellCheck(start, end);
}
}
}
/**
- * Displays the actions associated to an {@link EasyEditSpan}. The pop-up is controlled
- * by {@link EasyEditSpanController}.
- */
- private class EasyEditPopupWindow extends PinnedPopupWindow
- implements OnClickListener {
- private static final int POPUP_TEXT_LAYOUT =
- com.android.internal.R.layout.text_edit_action_popup_text;
- private TextView mDeleteTextView;
- private EasyEditSpan mEasyEditSpan;
-
- @Override
- protected void createPopupWindow() {
- mPopupWindow = new PopupWindow(TextView.this.mContext, null,
- com.android.internal.R.attr.textSelectHandleWindowStyle);
- mPopupWindow.setInputMethodMode(PopupWindow.INPUT_METHOD_NOT_NEEDED);
- mPopupWindow.setClippingEnabled(true);
- }
-
- @Override
- protected void initContentView() {
- LinearLayout linearLayout = new LinearLayout(TextView.this.getContext());
- linearLayout.setOrientation(LinearLayout.HORIZONTAL);
- mContentView = linearLayout;
- mContentView.setBackgroundResource(
- com.android.internal.R.drawable.text_edit_side_paste_window);
-
- LayoutInflater inflater = (LayoutInflater)TextView.this.mContext.
- getSystemService(Context.LAYOUT_INFLATER_SERVICE);
-
- LayoutParams wrapContent = new LayoutParams(
- ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);
-
- mDeleteTextView = (TextView) inflater.inflate(POPUP_TEXT_LAYOUT, null);
- mDeleteTextView.setLayoutParams(wrapContent);
- mDeleteTextView.setText(com.android.internal.R.string.delete);
- mDeleteTextView.setOnClickListener(this);
- mContentView.addView(mDeleteTextView);
- }
-
- public void show(EasyEditSpan easyEditSpan) {
- mEasyEditSpan = easyEditSpan;
- super.show();
- }
-
- @Override
- public void onClick(View view) {
- if (view == mDeleteTextView) {
- Editable editable = (Editable) mText;
- int start = editable.getSpanStart(mEasyEditSpan);
- int end = editable.getSpanEnd(mEasyEditSpan);
- if (start >= 0 && end >= 0) {
- deleteText_internal(start, end);
- }
- }
- }
-
- @Override
- protected int getTextOffset() {
- // Place the pop-up at the end of the span
- Editable editable = (Editable) mText;
- return editable.getSpanEnd(mEasyEditSpan);
- }
-
- @Override
- protected int getVerticalLocalPosition(int line) {
- return mLayout.getLineBottom(line);
- }
-
- @Override
- protected int clipVertically(int positionY) {
- // As we display the pop-up below the span, no vertical clipping is required.
- return positionY;
- }
- }
-
- private class ChangeWatcher implements TextWatcher, SpanWatcher {
-
- private CharSequence mBeforeText;
-
- private EasyEditSpanController mEasyEditSpanController;
-
- private ChangeWatcher() {
- mEasyEditSpanController = new EasyEditSpanController();
- }
-
- public void beforeTextChanged(CharSequence buffer, int start,
- int before, int after) {
- if (DEBUG_EXTRACT) Log.v(LOG_TAG, "beforeTextChanged start=" + start
- + " before=" + before + " after=" + after + ": " + buffer);
-
- if (AccessibilityManager.getInstance(mContext).isEnabled()
- && !isPasswordInputType(mInputType)
- && !hasPasswordTransformationMethod()) {
- mBeforeText = buffer.toString();
- }
-
- TextView.this.sendBeforeTextChanged(buffer, start, before, after);
- }
-
- public void onTextChanged(CharSequence buffer, int start,
- int before, int after) {
- if (DEBUG_EXTRACT) Log.v(LOG_TAG, "onTextChanged start=" + start
- + " before=" + before + " after=" + after + ": " + buffer);
- TextView.this.handleTextChanged(buffer, start, before, after);
-
- mEasyEditSpanController.onTextChange(buffer);
-
- if (AccessibilityManager.getInstance(mContext).isEnabled() &&
- (isFocused() || isSelected() && isShown())) {
- sendAccessibilityEventTypeViewTextChanged(mBeforeText, start, before, after);
- mBeforeText = null;
- }
- }
-
- public void afterTextChanged(Editable buffer) {
- if (DEBUG_EXTRACT) Log.v(LOG_TAG, "afterTextChanged: " + buffer);
- TextView.this.sendAfterTextChanged(buffer);
-
- if (MetaKeyKeyListener.getMetaState(buffer, MetaKeyKeyListener.META_SELECTING) != 0) {
- MetaKeyKeyListener.stopSelecting(TextView.this, buffer);
- }
- }
-
- public void onSpanChanged(Spannable buf,
- Object what, int s, int e, int st, int en) {
- if (DEBUG_EXTRACT) Log.v(LOG_TAG, "onSpanChanged s=" + s + " e=" + e
- + " st=" + st + " en=" + en + " what=" + what + ": " + buf);
- TextView.this.spanChange(buf, what, s, st, e, en);
- }
-
- public void onSpanAdded(Spannable buf, Object what, int s, int e) {
- if (DEBUG_EXTRACT) Log.v(LOG_TAG, "onSpanAdded s=" + s + " e=" + e
- + " what=" + what + ": " + buf);
- TextView.this.spanChange(buf, what, -1, s, -1, e);
- }
-
- public void onSpanRemoved(Spannable buf, Object what, int s, int e) {
- if (DEBUG_EXTRACT) Log.v(LOG_TAG, "onSpanRemoved s=" + s + " e=" + e
- + " what=" + what + ": " + buf);
- TextView.this.spanChange(buf, what, s, -1, e, -1);
- }
-
- private void hideControllers() {
- mEasyEditSpanController.hide();
- }
- }
-
- /**
* @hide
*/
@Override
@@ -8113,7 +7249,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
// Because of View recycling in ListView, there is no easy way to know when a TextView with
// selection becomes visible again. Until a better solution is found, stop text selection
// mode (if any) as soon as this TextView is recycled.
- hideControllers();
+ if (mEditor != null) hideControllers();
}
@Override
@@ -8131,95 +7267,14 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
super.onFocusChanged(focused, direction, previouslyFocusedRect);
return;
}
-
- mShowCursor = SystemClock.uptimeMillis();
- ensureEndedBatchEdit();
+ if (mEditor != null) getEditor().onFocusChanged(focused, direction);
if (focused) {
- int selStart = getSelectionStart();
- int selEnd = getSelectionEnd();
-
- // SelectAllOnFocus fields are highlighted and not selected. Do not start text selection
- // mode for these, unless there was a specific selection already started.
- final boolean isFocusHighlighted = mSelectAllOnFocus && selStart == 0 &&
- selEnd == mText.length();
- mCreatedWithASelection = mFrozenWithFocus && hasSelection() && !isFocusHighlighted;
-
- if (!mFrozenWithFocus || (selStart < 0 || selEnd < 0)) {
- // If a tap was used to give focus to that view, move cursor at tap position.
- // Has to be done before onTakeFocus, which can be overloaded.
- final int lastTapPosition = getLastTapPosition();
- if (lastTapPosition >= 0) {
- Selection.setSelection((Spannable) mText, lastTapPosition);
- }
-
- if (mMovement != null) {
- mMovement.onTakeFocus(this, (Spannable) mText, direction);
- }
-
- // The DecorView does not have focus when the 'Done' ExtractEditText button is
- // pressed. Since it is the ViewAncestor's mView, it requests focus before
- // ExtractEditText clears focus, which gives focus to the ExtractEditText.
- // This special case ensure that we keep current selection in that case.
- // It would be better to know why the DecorView does not have focus at that time.
- if (((this instanceof ExtractEditText) || mSelectionMoved) &&
- selStart >= 0 && selEnd >= 0) {
- /*
- * Someone intentionally set the selection, so let them
- * do whatever it is that they wanted to do instead of
- * the default on-focus behavior. We reset the selection
- * here instead of just skipping the onTakeFocus() call
- * because some movement methods do something other than
- * just setting the selection in theirs and we still
- * need to go through that path.
- */
- Selection.setSelection((Spannable) mText, selStart, selEnd);
- }
-
- if (mSelectAllOnFocus) {
- selectAll();
- }
-
- mTouchFocusSelected = true;
- }
-
- mFrozenWithFocus = false;
- mSelectionMoved = false;
-
if (mText instanceof Spannable) {
Spannable sp = (Spannable) mText;
MetaKeyKeyListener.resetMetaState(sp);
}
-
- makeBlink();
-
- if (mError != null) {
- showError();
- }
- } else {
- if (mError != null) {
- hideError();
- }
- // Don't leave us in the middle of a batch edit.
- onEndBatchEdit();
-
- if (this instanceof ExtractEditText) {
- // terminateTextSelectionMode removes selection, which we want to keep when
- // ExtractEditText goes out of focus.
- final int selStart = getSelectionStart();
- final int selEnd = getSelectionEnd();
- hideControllers();
- Selection.setSelection((Spannable) mText, selStart, selEnd);
- } else {
- hideControllers();
- downgradeEasyCorrectionSpans();
- }
-
- // No need to create the controller
- if (mSelectionModifierCursorController != null) {
- mSelectionModifierCursorController.resetTouchOffsets();
- }
}
startStopMarquee(focused);
@@ -8231,48 +7286,11 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
super.onFocusChanged(focused, direction, previouslyFocusedRect);
}
- private int getLastTapPosition() {
- // No need to create the controller at that point, no last tap position saved
- if (mSelectionModifierCursorController != null) {
- int lastTapPosition = mSelectionModifierCursorController.getMinTouchOffset();
- if (lastTapPosition >= 0) {
- // Safety check, should not be possible.
- if (lastTapPosition > mText.length()) {
- Log.e(LOG_TAG, "Invalid tap focus position (" + lastTapPosition + " vs "
- + mText.length() + ")");
- lastTapPosition = mText.length();
- }
- return lastTapPosition;
- }
- }
-
- return -1;
- }
-
@Override
public void onWindowFocusChanged(boolean hasWindowFocus) {
super.onWindowFocusChanged(hasWindowFocus);
- if (hasWindowFocus) {
- if (mBlink != null) {
- mBlink.uncancel();
- makeBlink();
- }
- } else {
- if (mBlink != null) {
- mBlink.cancel();
- }
- // Don't leave us in the middle of a batch edit.
- onEndBatchEdit();
- if (mInputContentType != null) {
- mInputContentType.enterDown = false;
- }
-
- hideControllers();
- if (mSuggestionsPopupWindow != null) {
- mSuggestionsPopupWindow.onParentLostFocus();
- }
- }
+ if (mEditor != null) getEditor().onWindowFocusChanged(hasWindowFocus);
startStopMarquee(hasWindowFocus);
}
@@ -8280,7 +7298,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
@Override
protected void onVisibilityChanged(View changedView, int visibility) {
super.onVisibilityChanged(changedView, visibility);
- if (visibility != VISIBLE) {
+ if (mEditor != null && visibility != VISIBLE) {
hideControllers();
}
}
@@ -8315,23 +7333,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
public boolean onTouchEvent(MotionEvent event) {
final int action = event.getActionMasked();
- if (hasSelectionController()) {
- getSelectionController().onTouchEvent(event);
- }
-
- if (mShowSuggestionRunnable != null) {
- removeCallbacks(mShowSuggestionRunnable);
- }
-
- if (action == MotionEvent.ACTION_DOWN) {
- mLastDownPositionX = event.getX();
- mLastDownPositionY = event.getY();
-
- // Reset this state; it will be re-set if super.onTouchEvent
- // causes focus to move to the view.
- mTouchFocusSelected = false;
- mIgnoreActionUpEvent = false;
- }
+ if (mEditor != null) getEditor().onTouchEvent(event);
final boolean superResult = super.onTouchEvent(event);
@@ -8340,13 +7342,13 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
* move the selection away from whatever the menu action was
* trying to affect.
*/
- if (mDiscardNextActionUp && action == MotionEvent.ACTION_UP) {
- mDiscardNextActionUp = false;
+ if (mEditor != null && getEditor().mDiscardNextActionUp && action == MotionEvent.ACTION_UP) {
+ getEditor().mDiscardNextActionUp = false;
return superResult;
}
final boolean touchIsFinished = (action == MotionEvent.ACTION_UP) &&
- !mIgnoreActionUpEvent && isFocused();
+ (mEditor == null || !getEditor().mIgnoreActionUpEvent) && isFocused();
if ((mMovement != null || onCheckIsTextEditor()) && isEnabled()
&& mText instanceof Spannable && mLayout != null) {
@@ -8356,7 +7358,8 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
handled |= mMovement.onTouchEvent(this, (Spannable) mText, event);
}
- if (touchIsFinished && mLinksClickable && mAutoLinkMask != 0 && mTextIsSelectable) {
+ final boolean textIsSelectable = isTextSelectable();
+ if (touchIsFinished && mLinksClickable && mAutoLinkMask != 0 && textIsSelectable) {
// The LinkMovementMethod which should handle taps on links has not been installed
// on non editable text that support text selection.
// We reproduce its behavior here to open links for these.
@@ -8369,34 +7372,33 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
}
}
- if (touchIsFinished && (isTextEditable() || mTextIsSelectable)) {
+ if (touchIsFinished && (isTextEditable() || textIsSelectable)) {
// Show the IME, except when selecting in read-only text.
final InputMethodManager imm = InputMethodManager.peekInstance();
viewClicked(imm);
- if (!mTextIsSelectable) {
+ if (!textIsSelectable) {
handled |= imm != null && imm.showSoftInput(this, 0);
}
- boolean selectAllGotFocus = mSelectAllOnFocus && didTouchFocusSelect();
+ boolean selectAllGotFocus = getEditor().mSelectAllOnFocus && didTouchFocusSelect();
hideControllers();
if (!selectAllGotFocus && mText.length() > 0) {
// Move cursor
final int offset = getOffsetForPosition(event.getX(), event.getY());
Selection.setSelection((Spannable) mText, offset);
- if (mSpellChecker != null) {
+ if (getEditor().mSpellChecker != null) {
// When the cursor moves, the word that was typed may need spell check
- mSpellChecker.onSelectionChanged();
+ getEditor().mSpellChecker.onSelectionChanged();
}
if (!extractedTextModeWillBeStarted()) {
if (isCursorInsideEasyCorrectionSpan()) {
- if (mShowSuggestionRunnable == null) {
- mShowSuggestionRunnable = new Runnable() {
- public void run() {
- showSuggestions();
- }
- };
- }
- postDelayed(mShowSuggestionRunnable,
+ getEditor().mShowSuggestionRunnable = new Runnable() {
+ public void run() {
+ showSuggestions();
+ }
+ };
+ // removeCallbacks is performed on every touch
+ postDelayed(getEditor().mShowSuggestionRunnable,
ViewConfiguration.getDoubleTapTimeout());
} else if (hasInsertionController()) {
getInsertionController().show();
@@ -8479,6 +7481,8 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
}
private void prepareCursorControllers() {
+ if (mEditor == null) return;
+
boolean windowSupportsHandles = false;
ViewGroup.LayoutParams params = getRootView().getLayoutParams();
@@ -8488,23 +7492,23 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
|| windowParams.type > WindowManager.LayoutParams.LAST_SUB_WINDOW;
}
- mInsertionControllerEnabled = windowSupportsHandles && isCursorVisible() && mLayout != null;
- mSelectionControllerEnabled = windowSupportsHandles && textCanBeSelected() &&
+ getEditor().mInsertionControllerEnabled = windowSupportsHandles && isCursorVisible() && mLayout != null;
+ getEditor().mSelectionControllerEnabled = windowSupportsHandles && textCanBeSelected() &&
mLayout != null;
- if (!mInsertionControllerEnabled) {
+ if (!getEditor().mInsertionControllerEnabled) {
hideInsertionPointCursorController();
- if (mInsertionPointCursorController != null) {
- mInsertionPointCursorController.onDetached();
- mInsertionPointCursorController = null;
+ if (getEditor().mInsertionPointCursorController != null) {
+ getEditor().mInsertionPointCursorController.onDetached();
+ getEditor().mInsertionPointCursorController = null;
}
}
- if (!mSelectionControllerEnabled) {
+ if (!getEditor().mSelectionControllerEnabled) {
stopSelectionActionMode();
- if (mSelectionModifierCursorController != null) {
- mSelectionModifierCursorController.onDetached();
- mSelectionModifierCursorController = null;
+ if (getEditor().mSelectionModifierCursorController != null) {
+ getEditor().mSelectionModifierCursorController.onDetached();
+ getEditor().mSelectionModifierCursorController = null;
}
}
}
@@ -8524,19 +7528,18 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
* of interest.
*/
public boolean didTouchFocusSelect() {
- return mTouchFocusSelected;
+ return mEditor != null && getEditor().mTouchFocusSelected;
}
@Override
public void cancelLongPress() {
super.cancelLongPress();
- mIgnoreActionUpEvent = true;
+ if (mEditor != null) getEditor().mIgnoreActionUpEvent = true;
}
@Override
public boolean onTrackballEvent(MotionEvent event) {
- if (mMovement != null && mText instanceof Spannable &&
- mLayout != null) {
+ if (mMovement != null && mText instanceof Spannable && mLayout != null) {
if (mMovement.onTrackballEvent(this, (Spannable) mText, event)) {
return true;
}
@@ -8549,49 +7552,11 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
mScroller = s;
}
- private static class Blink extends Handler implements Runnable {
- private final WeakReference<TextView> mView;
- private boolean mCancelled;
-
- public Blink(TextView v) {
- mView = new WeakReference<TextView>(v);
- }
-
- public void run() {
- if (mCancelled) {
- return;
- }
-
- removeCallbacks(Blink.this);
-
- TextView tv = mView.get();
-
- if (tv != null && tv.shouldBlink()) {
- if (tv.mLayout != null) {
- tv.invalidateCursorPath();
- }
-
- postAtTime(this, SystemClock.uptimeMillis() + BLINK);
- }
- }
-
- void cancel() {
- if (!mCancelled) {
- removeCallbacks(Blink.this);
- mCancelled = true;
- }
- }
-
- void uncancel() {
- mCancelled = false;
- }
- }
-
/**
* @return True when the TextView isFocused and has a valid zero-length selection (cursor).
*/
private boolean shouldBlink() {
- if (!isCursorVisible() || !isFocused()) return false;
+ if (mEditor == null || !isCursorVisible() || !isFocused()) return false;
final int start = getSelectionStart();
if (start < 0) return false;
@@ -8604,12 +7569,12 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
private void makeBlink() {
if (shouldBlink()) {
- mShowCursor = SystemClock.uptimeMillis();
- if (mBlink == null) mBlink = new Blink(this);
- mBlink.removeCallbacks(mBlink);
- mBlink.postAtTime(mBlink, mShowCursor + BLINK);
+ getEditor().mShowCursor = SystemClock.uptimeMillis();
+ if (getEditor().mBlink == null) getEditor().mBlink = new Blink(this);
+ getEditor().mBlink.removeCallbacks(getEditor().mBlink);
+ getEditor().mBlink.postAtTime(getEditor().mBlink, getEditor().mShowCursor + BLINK);
} else {
- if (mBlink != null) mBlink.removeCallbacks(mBlink);
+ if (mEditor != null && getEditor().mBlink != null) getEditor().mBlink.removeCallbacks(getEditor().mBlink);
}
}
@@ -8808,7 +7773,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
// If you change this condition, make sure prepareCursorController is called anywhere
// the value of this condition might be changed.
if (mMovement == null || !mMovement.canSelectArbitrarily()) return false;
- return isTextEditable() || (mTextIsSelectable && mText instanceof Spannable && isEnabled());
+ return isTextEditable() || (isTextSelectable() && mText instanceof Spannable && isEnabled());
}
private boolean canCut() {
@@ -8816,7 +7781,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
return false;
}
- if (mText.length() > 0 && hasSelection() && mText instanceof Editable && mInput != null) {
+ if (mText.length() > 0 && hasSelection() && mText instanceof Editable && mEditor != null && getEditor().mKeyListener != null) {
return true;
}
@@ -8837,7 +7802,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
private boolean canPaste() {
return (mText instanceof Editable &&
- mInput != null &&
+ mEditor != null && getEditor().mKeyListener != null &&
getSelectionStart() >= 0 &&
getSelectionEnd() >= 0 &&
((ClipboardManager)getContext().getSystemService(Context.CLIPBOARD_SERVICE)).
@@ -8878,8 +7843,9 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
return selectAll();
}
- int klass = mInputType & InputType.TYPE_MASK_CLASS;
- int variation = mInputType & InputType.TYPE_MASK_VARIATION;
+ int inputType = getInputType();
+ int klass = inputType & InputType.TYPE_MASK_CLASS;
+ int variation = inputType & InputType.TYPE_MASK_VARIATION;
// Specific text field types: select the entire text for these
if (klass == InputType.TYPE_CLASS_NUMBER ||
@@ -8949,17 +7915,17 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
void onLocaleChanged() {
// Will be re-created on demand in getWordIterator with the proper new locale
- mWordIterator = null;
+ getEditor().mWordIterator = null;
}
/**
* @hide
*/
public WordIterator getWordIterator() {
- if (mWordIterator == null) {
- mWordIterator = new WordIterator(getTextServicesLocale());
+ if (getEditor().mWordIterator == null) {
+ getEditor().mWordIterator = new WordIterator(getTextServicesLocale());
}
- return mWordIterator;
+ return getEditor().mWordIterator;
}
private long getCharRange(int offset) {
@@ -9213,17 +8179,6 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
return new DragShadowBuilder(shadowView);
}
- private static class DragLocalState {
- public TextView sourceTextView;
- public int start, end;
-
- public DragLocalState(TextView sourceTextView, int start, int end) {
- this.sourceTextView = sourceTextView;
- this.start = start;
- this.end = end;
- }
- }
-
@Override
public boolean performLongClick() {
boolean handled = false;
@@ -9234,9 +8189,9 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
}
// Long press in empty space moves cursor and shows the Paste affordance if available.
- if (!handled && !isPositionOnText(mLastDownPositionX, mLastDownPositionY) &&
- mInsertionControllerEnabled) {
- final int offset = getOffsetForPosition(mLastDownPositionX, mLastDownPositionY);
+ if (!handled && mEditor != null && !isPositionOnText(getEditor().mLastDownPositionX, getEditor().mLastDownPositionY) &&
+ getEditor().mInsertionControllerEnabled) {
+ final int offset = getOffsetForPosition(getEditor().mLastDownPositionX, getEditor().mLastDownPositionY);
stopSelectionActionMode();
Selection.setSelection((Spannable) mText, offset);
getInsertionController().showWithActionPopup();
@@ -9244,7 +8199,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
vibrate = false;
}
- if (!handled && mSelectionActionMode != null) {
+ if (!handled && (mEditor == null || getEditor().mSelectionActionMode != null)) {
if (touchPositionIsInSelection()) {
// Start a drag
final int start = getSelectionStart();
@@ -9271,8 +8226,8 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
performHapticFeedback(HapticFeedbackConstants.LONG_PRESS);
}
- if (handled) {
- mDiscardNextActionUp = true;
+ if (handled && mEditor != null) {
+ getEditor().mDiscardNextActionUp = true;
}
return handled;
@@ -9301,10 +8256,10 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
}
private PositionListener getPositionListener() {
- if (mPositionListener == null) {
- mPositionListener = new PositionListener();
+ if (getEditor().mPositionListener == null) {
+ getEditor().mPositionListener = new PositionListener();
}
- return mPositionListener;
+ return getEditor().mPositionListener;
}
private interface TextViewPositionListener {
@@ -9312,6 +8267,1420 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
boolean parentPositionChanged, boolean parentScrolled);
}
+ private boolean isPositionVisible(int positionX, int positionY) {
+ synchronized (TEMP_POSITION) {
+ final float[] position = TEMP_POSITION;
+ position[0] = positionX;
+ position[1] = positionY;
+ View view = this;
+
+ while (view != null) {
+ if (view != this) {
+ // Local scroll is already taken into account in positionX/Y
+ position[0] -= view.getScrollX();
+ position[1] -= view.getScrollY();
+ }
+
+ if (position[0] < 0 || position[1] < 0 ||
+ position[0] > view.getWidth() || position[1] > view.getHeight()) {
+ return false;
+ }
+
+ if (!view.getMatrix().isIdentity()) {
+ view.getMatrix().mapPoints(position);
+ }
+
+ position[0] += view.getLeft();
+ position[1] += view.getTop();
+
+ final ViewParent parent = view.getParent();
+ if (parent instanceof View) {
+ view = (View) parent;
+ } else {
+ // We've reached the ViewRoot, stop iterating
+ view = null;
+ }
+ }
+ }
+
+ // We've been able to walk up the view hierarchy and the position was never clipped
+ return true;
+ }
+
+ private boolean isOffsetVisible(int offset) {
+ final int line = mLayout.getLineForOffset(offset);
+ final int lineBottom = mLayout.getLineBottom(line);
+ final int primaryHorizontal = (int) mLayout.getPrimaryHorizontal(offset);
+ return isPositionVisible(primaryHorizontal + viewportToContentHorizontalOffset(),
+ lineBottom + viewportToContentVerticalOffset());
+ }
+
+ @Override
+ protected void onScrollChanged(int horiz, int vert, int oldHoriz, int oldVert) {
+ super.onScrollChanged(horiz, vert, oldHoriz, oldVert);
+ if (mEditor != null && getEditor().mPositionListener != null) {
+ getEditor().mPositionListener.onScrollChanged();
+ }
+ }
+
+ /**
+ * Removes the suggestion spans.
+ */
+ CharSequence removeSuggestionSpans(CharSequence text) {
+ if (text instanceof Spanned) {
+ Spannable spannable;
+ if (text instanceof Spannable) {
+ spannable = (Spannable) text;
+ } else {
+ spannable = new SpannableString(text);
+ text = spannable;
+ }
+
+ SuggestionSpan[] spans = spannable.getSpans(0, text.length(), SuggestionSpan.class);
+ for (int i = 0; i < spans.length; i++) {
+ spannable.removeSpan(spans[i]);
+ }
+ }
+ return text;
+ }
+
+ void showSuggestions() {
+ if (getEditor().mSuggestionsPopupWindow == null) {
+ getEditor().mSuggestionsPopupWindow = new SuggestionsPopupWindow();
+ }
+ hideControllers();
+ getEditor().mSuggestionsPopupWindow.show();
+ }
+
+ boolean areSuggestionsShown() {
+ return getEditor().mSuggestionsPopupWindow != null && getEditor().mSuggestionsPopupWindow.isShowing();
+ }
+
+ /**
+ * Return whether or not suggestions are enabled on this TextView. The suggestions are generated
+ * by the IME or by the spell checker as the user types. This is done by adding
+ * {@link SuggestionSpan}s to the text.
+ *
+ * When suggestions are enabled (default), this list of suggestions will be displayed when the
+ * user asks for them on these parts of the text. This value depends on the inputType of this
+ * TextView.
+ *
+ * The class of the input type must be {@link InputType#TYPE_CLASS_TEXT}.
+ *
+ * In addition, the type variation must be one of
+ * {@link InputType#TYPE_TEXT_VARIATION_NORMAL},
+ * {@link InputType#TYPE_TEXT_VARIATION_EMAIL_SUBJECT},
+ * {@link InputType#TYPE_TEXT_VARIATION_LONG_MESSAGE},
+ * {@link InputType#TYPE_TEXT_VARIATION_SHORT_MESSAGE} or
+ * {@link InputType#TYPE_TEXT_VARIATION_WEB_EDIT_TEXT}.
+ *
+ * And finally, the {@link InputType#TYPE_TEXT_FLAG_NO_SUGGESTIONS} flag must <i>not</i> be set.
+ *
+ * @return true if the suggestions popup window is enabled, based on the inputType.
+ */
+ public boolean isSuggestionsEnabled() {
+ if (mEditor == null) return false;
+ if ((getEditor().mInputType & InputType.TYPE_MASK_CLASS) != InputType.TYPE_CLASS_TEXT) return false;
+ if ((getEditor().mInputType & InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS) > 0) return false;
+
+ final int variation = getEditor().mInputType & EditorInfo.TYPE_MASK_VARIATION;
+ return (variation == EditorInfo.TYPE_TEXT_VARIATION_NORMAL ||
+ variation == EditorInfo.TYPE_TEXT_VARIATION_EMAIL_SUBJECT ||
+ variation == EditorInfo.TYPE_TEXT_VARIATION_LONG_MESSAGE ||
+ variation == EditorInfo.TYPE_TEXT_VARIATION_SHORT_MESSAGE ||
+ variation == EditorInfo.TYPE_TEXT_VARIATION_WEB_EDIT_TEXT);
+ }
+
+ /**
+ * 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.
+ *
+ * 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 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.
+ *
+ * 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 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.
+ */
+ public void setCustomSelectionActionModeCallback(ActionMode.Callback actionModeCallback) {
+ createEditorIfNeeded("custom selection action mode set");
+ getEditor().mCustomSelectionActionModeCallback = actionModeCallback;
+ }
+
+ /**
+ * Retrieves the value set in {@link #setCustomSelectionActionModeCallback}. Default is null.
+ *
+ * @return The current custom selection callback.
+ */
+ public ActionMode.Callback getCustomSelectionActionModeCallback() {
+ return mEditor == null ? null : getEditor().mCustomSelectionActionModeCallback;
+ }
+
+ /**
+ *
+ * @return true if the selection mode was actually started.
+ */
+ private boolean startSelectionActionMode() {
+ if (getEditor().mSelectionActionMode != null) {
+ // Selection action mode is already started
+ return false;
+ }
+
+ if (!canSelectText() || !requestFocus()) {
+ Log.w(LOG_TAG, "TextView does not support text selection. Action mode cancelled.");
+ return false;
+ }
+
+ if (!hasSelection()) {
+ // There may already be a selection on device rotation
+ if (!selectCurrentWord()) {
+ // No word found under cursor or text selection not permitted.
+ return false;
+ }
+ }
+
+ boolean willExtract = extractedTextModeWillBeStarted();
+
+ // 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();
+ getEditor().mSelectionActionMode = startActionMode(actionModeCallback);
+ }
+
+ final boolean selectionStarted = getEditor().mSelectionActionMode != null || willExtract;
+ if (selectionStarted && !isTextSelectable()) {
+ // Show the IME to be able to replace text, except when selecting non editable text.
+ final InputMethodManager imm = InputMethodManager.peekInstance();
+ if (imm != null) {
+ imm.showSoftInput(this, 0, null);
+ }
+ }
+
+ return selectionStarted;
+ }
+
+ private boolean extractedTextModeWillBeStarted() {
+ if (!(this instanceof ExtractEditText)) {
+ final InputMethodManager imm = InputMethodManager.peekInstance();
+ return imm != null && imm.isFullscreenMode();
+ }
+ return false;
+ }
+
+ /**
+ * @hide
+ */
+ protected void stopSelectionActionMode() {
+ if (getEditor().mSelectionActionMode != null) {
+ // This will hide the mSelectionModifierCursorController
+ getEditor().mSelectionActionMode.finish();
+ }
+ }
+
+ /**
+ * Paste clipboard content between min and max positions.
+ */
+ private void paste(int min, int max) {
+ ClipboardManager clipboard =
+ (ClipboardManager) getContext().getSystemService(Context.CLIPBOARD_SERVICE);
+ ClipData clip = clipboard.getPrimaryClip();
+ if (clip != null) {
+ boolean didFirst = false;
+ for (int i=0; i<clip.getItemCount(); i++) {
+ CharSequence paste = clip.getItemAt(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);
+ didFirst = true;
+ } else {
+ ((Editable) mText).insert(getSelectionEnd(), "\n");
+ ((Editable) mText).insert(getSelectionEnd(), paste);
+ }
+ }
+ }
+ stopSelectionActionMode();
+ LAST_CUT_OR_COPY_TIME = 0;
+ }
+ }
+
+ private void setPrimaryClip(ClipData clip) {
+ ClipboardManager clipboard = (ClipboardManager) getContext().
+ getSystemService(Context.CLIPBOARD_SERVICE);
+ clipboard.setPrimaryClip(clip);
+ LAST_CUT_OR_COPY_TIME = SystemClock.uptimeMillis();
+ }
+
+ private void hideInsertionPointCursorController() {
+ // No need to create the controller to hide it.
+ if (getEditor().mInsertionPointCursorController != null) {
+ getEditor().mInsertionPointCursorController.hide();
+ }
+ }
+
+ /**
+ * Hides the insertion controller and stops text selection mode, hiding the selection controller
+ */
+ private void hideControllers() {
+ hideCursorControllers();
+ hideSpanControllers();
+ }
+
+ private void hideSpanControllers() {
+ if (mChangeWatcher != null) {
+ mChangeWatcher.hideControllers();
+ }
+ }
+
+ private void hideCursorControllers() {
+ if (getEditor().mSuggestionsPopupWindow != null && !getEditor().mSuggestionsPopupWindow.isShowingUp()) {
+ // Should be done before hide insertion point controller since it triggers a show of it
+ getEditor().mSuggestionsPopupWindow.hide();
+ }
+ hideInsertionPointCursorController();
+ stopSelectionActionMode();
+ }
+
+ /**
+ * Get the character offset closest to the specified absolute position. A typical use case is to
+ * pass the result of {@link MotionEvent#getX()} and {@link MotionEvent#getY()} to this method.
+ *
+ * @param x The horizontal absolute position of a point on screen
+ * @param y The vertical absolute position of a point on screen
+ * @return the character offset for the character whose position is closest to the specified
+ * position. Returns -1 if there is no layout.
+ */
+ public int getOffsetForPosition(float x, float y) {
+ if (getLayout() == null) return -1;
+ final int line = getLineAtCoordinate(y);
+ final int offset = getOffsetAtCoordinate(line, x);
+ return offset;
+ }
+
+ private float convertToLocalHorizontalCoordinate(float x) {
+ x -= getTotalPaddingLeft();
+ // Clamp the position to inside of the view.
+ x = Math.max(0.0f, x);
+ x = Math.min(getWidth() - getTotalPaddingRight() - 1, x);
+ x += getScrollX();
+ return x;
+ }
+
+ private int getLineAtCoordinate(float y) {
+ y -= getTotalPaddingTop();
+ // Clamp the position to inside of the view.
+ y = Math.max(0.0f, y);
+ y = Math.min(getHeight() - getTotalPaddingBottom() - 1, y);
+ y += getScrollY();
+ return getLayout().getLineForVertical((int) y);
+ }
+
+ private int getOffsetAtCoordinate(int line, float x) {
+ x = convertToLocalHorizontalCoordinate(x);
+ return getLayout().getOffsetForHorizontal(line, x);
+ }
+
+ /** Returns true if the screen coordinates position (x,y) corresponds to a character displayed
+ * in the view. Returns false when the position is in the empty space of left/right of text.
+ */
+ private boolean isPositionOnText(float x, float y) {
+ if (getLayout() == null) return false;
+
+ final int line = getLineAtCoordinate(y);
+ x = convertToLocalHorizontalCoordinate(x);
+
+ if (x < getLayout().getLineLeft(line)) return false;
+ if (x > getLayout().getLineRight(line)) return false;
+ return true;
+ }
+
+ @Override
+ public boolean onDragEvent(DragEvent event) {
+ switch (event.getAction()) {
+ case DragEvent.ACTION_DRAG_STARTED:
+ return hasInsertionController();
+
+ case DragEvent.ACTION_DRAG_ENTERED:
+ TextView.this.requestFocus();
+ return true;
+
+ case DragEvent.ACTION_DRAG_LOCATION:
+ final int offset = getOffsetForPosition(event.getX(), event.getY());
+ Selection.setSelection((Spannable)mText, offset);
+ return true;
+
+ case DragEvent.ACTION_DROP:
+ onDrop(event);
+ return true;
+
+ case DragEvent.ACTION_DRAG_ENDED:
+ case DragEvent.ACTION_DRAG_EXITED:
+ default:
+ return true;
+ }
+ }
+
+ private void onDrop(DragEvent event) {
+ StringBuilder content = new StringBuilder("");
+ ClipData clipData = event.getClipData();
+ final int itemCount = clipData.getItemCount();
+ for (int i=0; i < itemCount; i++) {
+ Item item = clipData.getItemAt(i);
+ content.append(item.coerceToText(TextView.this.mContext));
+ }
+
+ final int offset = getOffsetForPosition(event.getX(), event.getY());
+
+ Object localState = event.getLocalState();
+ DragLocalState dragLocalState = null;
+ if (localState instanceof DragLocalState) {
+ dragLocalState = (DragLocalState) localState;
+ }
+ boolean dragDropIntoItself = dragLocalState != null &&
+ dragLocalState.sourceTextView == this;
+
+ if (dragDropIntoItself) {
+ if (offset >= dragLocalState.start && offset < dragLocalState.end) {
+ // A drop inside the original selection discards the drop.
+ return;
+ }
+ }
+
+ final int originalLength = mText.length();
+ long minMax = prepareSpacesAroundPaste(offset, offset, content);
+ int min = extractRangeStartFromLong(minMax);
+ int max = extractRangeEndFromLong(minMax);
+
+ Selection.setSelection((Spannable) mText, max);
+ replaceText_internal(min, max, content);
+
+ if (dragDropIntoItself) {
+ int dragSourceStart = dragLocalState.start;
+ int dragSourceEnd = dragLocalState.end;
+ if (max <= dragSourceStart) {
+ // Inserting text before selection has shifted positions
+ final int shift = mText.length() - originalLength;
+ dragSourceStart += shift;
+ dragSourceEnd += shift;
+ }
+
+ // Delete original selection
+ deleteText_internal(dragSourceStart, dragSourceEnd);
+
+ // Make sure we do not leave two adjacent spaces.
+ if ((dragSourceStart == 0 ||
+ Character.isSpaceChar(mTransformed.charAt(dragSourceStart - 1))) &&
+ (dragSourceStart == mText.length() ||
+ Character.isSpaceChar(mTransformed.charAt(dragSourceStart)))) {
+ final int pos = dragSourceStart == mText.length() ?
+ dragSourceStart - 1 : dragSourceStart;
+ deleteText_internal(pos, pos + 1);
+ }
+ }
+ }
+
+ /**
+ * @return True if this view supports insertion handles.
+ */
+ boolean hasInsertionController() {
+ return getEditor().mInsertionControllerEnabled;
+ }
+
+ /**
+ * @return True if this view supports selection handles.
+ */
+ boolean hasSelectionController() {
+ return getEditor().mSelectionControllerEnabled;
+ }
+
+ InsertionPointCursorController getInsertionController() {
+ if (!getEditor().mInsertionControllerEnabled) {
+ return null;
+ }
+
+ if (getEditor().mInsertionPointCursorController == null) {
+ getEditor().mInsertionPointCursorController = new InsertionPointCursorController();
+
+ final ViewTreeObserver observer = getViewTreeObserver();
+ observer.addOnTouchModeChangeListener(getEditor().mInsertionPointCursorController);
+ }
+
+ return getEditor().mInsertionPointCursorController;
+ }
+
+ SelectionModifierCursorController getSelectionController() {
+ if (!getEditor().mSelectionControllerEnabled) {
+ return null;
+ }
+
+ if (getEditor().mSelectionModifierCursorController == null) {
+ getEditor().mSelectionModifierCursorController = new SelectionModifierCursorController();
+
+ final ViewTreeObserver observer = getViewTreeObserver();
+ observer.addOnTouchModeChangeListener(getEditor().mSelectionModifierCursorController);
+ }
+
+ return getEditor().mSelectionModifierCursorController;
+ }
+
+ boolean isInBatchEditMode() {
+ if (mEditor == null) return false;
+ final InputMethodState ims = getEditor().mInputMethodState;
+ if (ims != null) {
+ return ims.mBatchEditNesting > 0;
+ }
+ return getEditor().mInBatchEditControllers;
+ }
+
+ @Override
+ public void onResolveTextDirection() {
+ if (hasPasswordTransformationMethod()) {
+ mTextDir = TextDirectionHeuristics.LOCALE;
+ return;
+ }
+
+ // Always need to resolve layout direction first
+ final boolean defaultIsRtl = (getResolvedLayoutDirection() == LAYOUT_DIRECTION_RTL);
+
+ // Now, we can select the heuristic
+ int textDir = getResolvedTextDirection();
+ switch (textDir) {
+ default:
+ case TEXT_DIRECTION_FIRST_STRONG:
+ mTextDir = (defaultIsRtl ? TextDirectionHeuristics.FIRSTSTRONG_RTL :
+ TextDirectionHeuristics.FIRSTSTRONG_LTR);
+ break;
+ case TEXT_DIRECTION_ANY_RTL:
+ mTextDir = TextDirectionHeuristics.ANYRTL_LTR;
+ break;
+ case TEXT_DIRECTION_LTR:
+ mTextDir = TextDirectionHeuristics.LTR;
+ break;
+ case TEXT_DIRECTION_RTL:
+ mTextDir = TextDirectionHeuristics.RTL;
+ break;
+ case TEXT_DIRECTION_LOCALE:
+ mTextDir = TextDirectionHeuristics.LOCALE;
+ break;
+ }
+ }
+
+ /**
+ * Subclasses will need to override this method to implement their own way of resolving
+ * drawables depending on the layout direction.
+ *
+ * A call to the super method will be required from the subclasses implementation.
+ */
+ protected void resolveDrawables() {
+ // No need to resolve twice
+ if (mResolvedDrawables) {
+ return;
+ }
+ // No drawable to resolve
+ if (mDrawables == null) {
+ return;
+ }
+ // No relative drawable to resolve
+ if (mDrawables.mDrawableStart == null && mDrawables.mDrawableEnd == null) {
+ mResolvedDrawables = true;
+ return;
+ }
+
+ Drawables dr = mDrawables;
+ switch(getResolvedLayoutDirection()) {
+ case LAYOUT_DIRECTION_RTL:
+ if (dr.mDrawableStart != null) {
+ dr.mDrawableRight = dr.mDrawableStart;
+
+ dr.mDrawableSizeRight = dr.mDrawableSizeStart;
+ dr.mDrawableHeightRight = dr.mDrawableHeightStart;
+ }
+ if (dr.mDrawableEnd != null) {
+ dr.mDrawableLeft = dr.mDrawableEnd;
+
+ dr.mDrawableSizeLeft = dr.mDrawableSizeEnd;
+ dr.mDrawableHeightLeft = dr.mDrawableHeightEnd;
+ }
+ break;
+
+ case LAYOUT_DIRECTION_LTR:
+ default:
+ if (dr.mDrawableStart != null) {
+ dr.mDrawableLeft = dr.mDrawableStart;
+
+ dr.mDrawableSizeLeft = dr.mDrawableSizeStart;
+ dr.mDrawableHeightLeft = dr.mDrawableHeightStart;
+ }
+ if (dr.mDrawableEnd != null) {
+ dr.mDrawableRight = dr.mDrawableEnd;
+
+ dr.mDrawableSizeRight = dr.mDrawableSizeEnd;
+ dr.mDrawableHeightRight = dr.mDrawableHeightEnd;
+ }
+ break;
+ }
+ mResolvedDrawables = true;
+ }
+
+ protected void resetResolvedDrawables() {
+ mResolvedDrawables = false;
+ }
+
+ /**
+ * @hide
+ */
+ protected void viewClicked(InputMethodManager imm) {
+ if (imm != null) {
+ imm.viewClicked(this);
+ }
+ }
+
+ /**
+ * Deletes the range of text [start, end[.
+ * @hide
+ */
+ protected void deleteText_internal(int start, int end) {
+ ((Editable) mText).delete(start, end);
+ }
+
+ /**
+ * Replaces the range of text [start, end[ by replacement text
+ * @hide
+ */
+ protected void replaceText_internal(int start, int end, CharSequence text) {
+ ((Editable) mText).replace(start, end, text);
+ }
+
+ /**
+ * Sets a span on the specified range of text
+ * @hide
+ */
+ protected void setSpan_internal(Object span, int start, int end, int flags) {
+ ((Editable) mText).setSpan(span, start, end, flags);
+ }
+
+ /**
+ * Moves the cursor to the specified offset position in text
+ * @hide
+ */
+ protected void setCursorPosition_internal(int start, int end) {
+ Selection.setSelection(((Editable) mText), start, end);
+ }
+
+ /**
+ * An Editor should be created as soon as any of the editable-specific fields (grouped
+ * inside the Editor object) is assigned to a non-default value.
+ * This method will create the Editor if needed.
+ *
+ * A standard TextView (as well as buttons, checkboxes...) should not qualify and hence will
+ * have a null Editor, unlike an EditText. Inconsistent in-between states will have an
+ * Editor for backward compatibility, as soon as one of these fields is assigned.
+ *
+ * Also note that for performance reasons, the mEditor is created when needed, but not
+ * reset when no more edit-specific fields are needed.
+ */
+ private void createEditorIfNeeded(String reason) {
+ if (mEditor == null) {
+ if (!(this instanceof EditText)) {
+ Log.e(LOG_TAG + " EDITOR", "Creating Editor on TextView. " + reason);
+ }
+ mEditor = new Editor();
+ } else {
+ if (!(this instanceof EditText)) {
+ Log.d(LOG_TAG + " EDITOR", "Redundant Editor creation. " + reason);
+ }
+ }
+ }
+
+ private Editor getEditor() {
+ if (mEditor == null) {
+ //createEditorIfNeeded("Problem: mEditor is not initialized!");
+ Log.e(LOG_TAG, "mEditor not initialized. Please send a bug report to debunne@");
+ }
+ return mEditor;
+ }
+
+ /**
+ * User interface state that is stored by TextView for implementing
+ * {@link View#onSaveInstanceState}.
+ */
+ public static class SavedState extends BaseSavedState {
+ int selStart;
+ int selEnd;
+ CharSequence text;
+ boolean frozenWithFocus;
+ CharSequence error;
+
+ SavedState(Parcelable superState) {
+ super(superState);
+ }
+
+ @Override
+ public void writeToParcel(Parcel out, int flags) {
+ super.writeToParcel(out, flags);
+ out.writeInt(selStart);
+ out.writeInt(selEnd);
+ out.writeInt(frozenWithFocus ? 1 : 0);
+ TextUtils.writeToParcel(text, out, flags);
+
+ if (error == null) {
+ out.writeInt(0);
+ } else {
+ out.writeInt(1);
+ TextUtils.writeToParcel(error, out, flags);
+ }
+ }
+
+ @Override
+ public String toString() {
+ String str = "TextView.SavedState{"
+ + Integer.toHexString(System.identityHashCode(this))
+ + " start=" + selStart + " end=" + selEnd;
+ if (text != null) {
+ str += " text=" + text;
+ }
+ return str + "}";
+ }
+
+ @SuppressWarnings("hiding")
+ public static final Parcelable.Creator<SavedState> CREATOR
+ = new Parcelable.Creator<SavedState>() {
+ public SavedState createFromParcel(Parcel in) {
+ return new SavedState(in);
+ }
+
+ public SavedState[] newArray(int size) {
+ return new SavedState[size];
+ }
+ };
+
+ private SavedState(Parcel in) {
+ super(in);
+ selStart = in.readInt();
+ selEnd = in.readInt();
+ frozenWithFocus = (in.readInt() != 0);
+ text = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in);
+
+ if (in.readInt() != 0) {
+ error = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in);
+ }
+ }
+ }
+
+ private static class CharWrapper implements CharSequence, GetChars, GraphicsOperations {
+ private char[] mChars;
+ private int mStart, mLength;
+
+ public CharWrapper(char[] chars, int start, int len) {
+ mChars = chars;
+ mStart = start;
+ mLength = len;
+ }
+
+ /* package */ void set(char[] chars, int start, int len) {
+ mChars = chars;
+ mStart = start;
+ mLength = len;
+ }
+
+ public int length() {
+ return mLength;
+ }
+
+ public char charAt(int off) {
+ return mChars[off + mStart];
+ }
+
+ @Override
+ public String toString() {
+ return new String(mChars, mStart, mLength);
+ }
+
+ public CharSequence subSequence(int start, int end) {
+ if (start < 0 || end < 0 || start > mLength || end > mLength) {
+ throw new IndexOutOfBoundsException(start + ", " + end);
+ }
+
+ return new String(mChars, start + mStart, end - start);
+ }
+
+ public void getChars(int start, int end, char[] buf, int off) {
+ if (start < 0 || end < 0 || start > mLength || end > mLength) {
+ throw new IndexOutOfBoundsException(start + ", " + end);
+ }
+
+ System.arraycopy(mChars, start + mStart, buf, off, end - start);
+ }
+
+ public void drawText(Canvas c, int start, int end,
+ float x, float y, Paint p) {
+ c.drawText(mChars, start + mStart, end - start, x, y, p);
+ }
+
+ public void drawTextRun(Canvas c, int start, int end,
+ int contextStart, int contextEnd, float x, float y, int flags, Paint p) {
+ int count = end - start;
+ int contextCount = contextEnd - contextStart;
+ c.drawTextRun(mChars, start + mStart, count, contextStart + mStart,
+ contextCount, x, y, flags, p);
+ }
+
+ public float measureText(int start, int end, Paint p) {
+ return p.measureText(mChars, start + mStart, end - start);
+ }
+
+ public int getTextWidths(int start, int end, float[] widths, Paint p) {
+ return p.getTextWidths(mChars, start + mStart, end - start, widths);
+ }
+
+ public float getTextRunAdvances(int start, int end, int contextStart,
+ int contextEnd, int flags, float[] advances, int advancesIndex,
+ Paint p) {
+ int count = end - start;
+ int contextCount = contextEnd - contextStart;
+ return p.getTextRunAdvances(mChars, start + mStart, count,
+ contextStart + mStart, contextCount, flags, advances,
+ advancesIndex);
+ }
+
+ public float getTextRunAdvances(int start, int end, int contextStart,
+ int contextEnd, int flags, float[] advances, int advancesIndex,
+ Paint p, int reserved) {
+ int count = end - start;
+ int contextCount = contextEnd - contextStart;
+ return p.getTextRunAdvances(mChars, start + mStart, count,
+ contextStart + mStart, contextCount, flags, advances,
+ advancesIndex, reserved);
+ }
+
+ public int getTextRunCursor(int contextStart, int contextEnd, int flags,
+ int offset, int cursorOpt, Paint p) {
+ int contextCount = contextEnd - contextStart;
+ return p.getTextRunCursor(mChars, contextStart + mStart,
+ contextCount, flags, offset + mStart, cursorOpt);
+ }
+ }
+
+ private static class ErrorPopup extends PopupWindow {
+ private boolean mAbove = false;
+ private final TextView mView;
+ private int mPopupInlineErrorBackgroundId = 0;
+ private int mPopupInlineErrorAboveBackgroundId = 0;
+
+ ErrorPopup(TextView v, int width, int height) {
+ super(v, width, height);
+ mView = v;
+ // Make sure the TextView has a background set as it will be used the first time it is
+ // shown and positionned. Initialized with below background, which should have
+ // dimensions identical to the above version for this to work (and is more likely).
+ mPopupInlineErrorBackgroundId = getResourceId(mPopupInlineErrorBackgroundId,
+ com.android.internal.R.styleable.Theme_errorMessageBackground);
+ mView.setBackgroundResource(mPopupInlineErrorBackgroundId);
+ }
+
+ void fixDirection(boolean above) {
+ mAbove = above;
+
+ if (above) {
+ mPopupInlineErrorAboveBackgroundId =
+ getResourceId(mPopupInlineErrorAboveBackgroundId,
+ com.android.internal.R.styleable.Theme_errorMessageAboveBackground);
+ } else {
+ mPopupInlineErrorBackgroundId = getResourceId(mPopupInlineErrorBackgroundId,
+ com.android.internal.R.styleable.Theme_errorMessageBackground);
+ }
+
+ mView.setBackgroundResource(above ? mPopupInlineErrorAboveBackgroundId :
+ mPopupInlineErrorBackgroundId);
+ }
+
+ private int getResourceId(int currentId, int index) {
+ if (currentId == 0) {
+ TypedArray styledAttributes = mView.getContext().obtainStyledAttributes(
+ R.styleable.Theme);
+ currentId = styledAttributes.getResourceId(index, 0);
+ styledAttributes.recycle();
+ }
+ return currentId;
+ }
+
+ @Override
+ public void update(int x, int y, int w, int h, boolean force) {
+ super.update(x, y, w, h, force);
+
+ boolean above = isAboveAnchor();
+ if (above != mAbove) {
+ fixDirection(above);
+ }
+ }
+ }
+
+ private class CorrectionHighlighter {
+ private final Path mPath = new Path();
+ private final Paint mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
+ private int mStart, mEnd;
+ private long mFadingStartTime;
+ private final static int FADE_OUT_DURATION = 400;
+
+ public CorrectionHighlighter() {
+ mPaint.setCompatibilityScaling(getResources().getCompatibilityInfo().applicationScale);
+ mPaint.setStyle(Paint.Style.FILL);
+ }
+
+ public void highlight(CorrectionInfo info) {
+ mStart = info.getOffset();
+ mEnd = mStart + info.getNewText().length();
+ mFadingStartTime = SystemClock.uptimeMillis();
+
+ if (mStart < 0 || mEnd < 0) {
+ stopAnimation();
+ }
+ }
+
+ public void draw(Canvas canvas, int cursorOffsetVertical) {
+ if (updatePath() && updatePaint()) {
+ if (cursorOffsetVertical != 0) {
+ canvas.translate(0, cursorOffsetVertical);
+ }
+
+ canvas.drawPath(mPath, mPaint);
+
+ if (cursorOffsetVertical != 0) {
+ canvas.translate(0, -cursorOffsetVertical);
+ }
+ invalidate(true); // TODO invalidate cursor region only
+ } else {
+ stopAnimation();
+ invalidate(false); // TODO invalidate cursor region only
+ }
+ }
+
+ private boolean updatePaint() {
+ final long duration = SystemClock.uptimeMillis() - mFadingStartTime;
+ if (duration > FADE_OUT_DURATION) return false;
+
+ final float coef = 1.0f - (float) duration / FADE_OUT_DURATION;
+ final int highlightColorAlpha = Color.alpha(mHighlightColor);
+ final int color = (mHighlightColor & 0x00FFFFFF) +
+ ((int) (highlightColorAlpha * coef) << 24);
+ mPaint.setColor(color);
+ return true;
+ }
+
+ private boolean updatePath() {
+ final Layout layout = TextView.this.mLayout;
+ if (layout == null) return false;
+
+ // Update in case text is edited while the animation is run
+ final int length = mText.length();
+ int start = Math.min(length, mStart);
+ int end = Math.min(length, mEnd);
+
+ mPath.reset();
+ TextView.this.mLayout.getSelectionPath(start, end, mPath);
+ return true;
+ }
+
+ private void invalidate(boolean delayed) {
+ if (TextView.this.mLayout == null) return;
+
+ synchronized (TEMP_RECTF) {
+ mPath.computeBounds(TEMP_RECTF, false);
+
+ int left = getCompoundPaddingLeft();
+ int top = getExtendedPaddingTop() + getVerticalOffset(true);
+
+ if (delayed) {
+ TextView.this.postInvalidateDelayed(16, // 60 Hz update
+ left + (int) TEMP_RECTF.left, top + (int) TEMP_RECTF.top,
+ left + (int) TEMP_RECTF.right, top + (int) TEMP_RECTF.bottom);
+ } else {
+ TextView.this.postInvalidate((int) TEMP_RECTF.left, (int) TEMP_RECTF.top,
+ (int) TEMP_RECTF.right, (int) TEMP_RECTF.bottom);
+ }
+ }
+ }
+
+ private void stopAnimation() {
+ TextView.this.getEditor().mCorrectionHighlighter = null;
+ }
+ }
+
+ private static final class Marquee extends Handler {
+ // TODO: Add an option to configure this
+ private static final float MARQUEE_DELTA_MAX = 0.07f;
+ private static final int MARQUEE_DELAY = 1200;
+ private static final int MARQUEE_RESTART_DELAY = 1200;
+ private static final int MARQUEE_RESOLUTION = 1000 / 30;
+ private static final int MARQUEE_PIXELS_PER_SECOND = 30;
+
+ private static final byte MARQUEE_STOPPED = 0x0;
+ private static final byte MARQUEE_STARTING = 0x1;
+ private static final byte MARQUEE_RUNNING = 0x2;
+
+ private static final int MESSAGE_START = 0x1;
+ private static final int MESSAGE_TICK = 0x2;
+ private static final int MESSAGE_RESTART = 0x3;
+
+ private final WeakReference<TextView> mView;
+
+ private byte mStatus = MARQUEE_STOPPED;
+ private final float mScrollUnit;
+ private float mMaxScroll;
+ float mMaxFadeScroll;
+ private float mGhostStart;
+ private float mGhostOffset;
+ private float mFadeStop;
+ private int mRepeatLimit;
+
+ float mScroll;
+
+ Marquee(TextView v) {
+ final float density = v.getContext().getResources().getDisplayMetrics().density;
+ mScrollUnit = (MARQUEE_PIXELS_PER_SECOND * density) / MARQUEE_RESOLUTION;
+ mView = new WeakReference<TextView>(v);
+ }
+
+ @Override
+ public void handleMessage(Message msg) {
+ switch (msg.what) {
+ case MESSAGE_START:
+ mStatus = MARQUEE_RUNNING;
+ tick();
+ break;
+ case MESSAGE_TICK:
+ tick();
+ break;
+ case MESSAGE_RESTART:
+ if (mStatus == MARQUEE_RUNNING) {
+ if (mRepeatLimit >= 0) {
+ mRepeatLimit--;
+ }
+ start(mRepeatLimit);
+ }
+ break;
+ }
+ }
+
+ void tick() {
+ if (mStatus != MARQUEE_RUNNING) {
+ return;
+ }
+
+ removeMessages(MESSAGE_TICK);
+
+ final TextView textView = mView.get();
+ if (textView != null && (textView.isFocused() || textView.isSelected())) {
+ mScroll += mScrollUnit;
+ if (mScroll > mMaxScroll) {
+ mScroll = mMaxScroll;
+ sendEmptyMessageDelayed(MESSAGE_RESTART, MARQUEE_RESTART_DELAY);
+ } else {
+ sendEmptyMessageDelayed(MESSAGE_TICK, MARQUEE_RESOLUTION);
+ }
+ textView.invalidate();
+ }
+ }
+
+ void stop() {
+ mStatus = MARQUEE_STOPPED;
+ removeMessages(MESSAGE_START);
+ removeMessages(MESSAGE_RESTART);
+ removeMessages(MESSAGE_TICK);
+ resetScroll();
+ }
+
+ private void resetScroll() {
+ mScroll = 0.0f;
+ final TextView textView = mView.get();
+ if (textView != null) textView.invalidate();
+ }
+
+ void start(int repeatLimit) {
+ if (repeatLimit == 0) {
+ stop();
+ return;
+ }
+ mRepeatLimit = repeatLimit;
+ final TextView textView = mView.get();
+ if (textView != null && textView.mLayout != null) {
+ mStatus = MARQUEE_STARTING;
+ mScroll = 0.0f;
+ final int textWidth = textView.getWidth() - textView.getCompoundPaddingLeft() -
+ textView.getCompoundPaddingRight();
+ final float lineWidth = textView.mLayout.getLineWidth(0);
+ final float gap = textWidth / 3.0f;
+ mGhostStart = lineWidth - textWidth + gap;
+ mMaxScroll = mGhostStart + textWidth;
+ mGhostOffset = lineWidth + gap;
+ mFadeStop = lineWidth + textWidth / 6.0f;
+ mMaxFadeScroll = mGhostStart + lineWidth + lineWidth;
+
+ textView.invalidate();
+ sendEmptyMessageDelayed(MESSAGE_START, MARQUEE_DELAY);
+ }
+ }
+
+ float getGhostOffset() {
+ return mGhostOffset;
+ }
+
+ boolean shouldDrawLeftFade() {
+ return mScroll <= mFadeStop;
+ }
+
+ boolean shouldDrawGhost() {
+ return mStatus == MARQUEE_RUNNING && mScroll > mGhostStart;
+ }
+
+ boolean isRunning() {
+ return mStatus == MARQUEE_RUNNING;
+ }
+
+ boolean isStopped() {
+ return mStatus == MARQUEE_STOPPED;
+ }
+ }
+
+ /**
+ * Controls the {@link EasyEditSpan} monitoring when it is added, and when the related
+ * pop-up should be displayed.
+ */
+ private class EasyEditSpanController {
+
+ private static final int DISPLAY_TIMEOUT_MS = 3000; // 3 secs
+
+ private EasyEditPopupWindow mPopupWindow;
+
+ private EasyEditSpan mEasyEditSpan;
+
+ private Runnable mHidePopup;
+
+ private void hide() {
+ if (mPopupWindow != null) {
+ mPopupWindow.hide();
+ TextView.this.removeCallbacks(mHidePopup);
+ }
+ removeSpans(mText);
+ mEasyEditSpan = null;
+ }
+
+ /**
+ * Monitors the changes in the text.
+ *
+ * <p>{@link ChangeWatcher#onSpanAdded(Spannable, Object, int, int)} cannot be used,
+ * as the notifications are not sent when a spannable (with spans) is inserted.
+ */
+ public void onTextChange(CharSequence buffer) {
+ adjustSpans(mText);
+
+ if (getWindowVisibility() != View.VISIBLE) {
+ // The window is not visible yet, ignore the text change.
+ return;
+ }
+
+ if (mLayout == null) {
+ // The view has not been layout yet, ignore the text change
+ return;
+ }
+
+ InputMethodManager imm = InputMethodManager.peekInstance();
+ if (!(TextView.this instanceof ExtractEditText)
+ && imm != null && imm.isFullscreenMode()) {
+ // The input is in extract mode. We do not have to handle the easy edit in the
+ // original TextView, as the ExtractEditText will do
+ return;
+ }
+
+ // Remove the current easy edit span, as the text changed, and remove the pop-up
+ // (if any)
+ if (mEasyEditSpan != null) {
+ if (mText instanceof Spannable) {
+ ((Spannable) mText).removeSpan(mEasyEditSpan);
+ }
+ mEasyEditSpan = null;
+ }
+ if (mPopupWindow != null && mPopupWindow.isShowing()) {
+ mPopupWindow.hide();
+ }
+
+ // Display the new easy edit span (if any).
+ if (buffer instanceof Spanned) {
+ mEasyEditSpan = getSpan((Spanned) buffer);
+ if (mEasyEditSpan != null) {
+ if (mPopupWindow == null) {
+ mPopupWindow = new EasyEditPopupWindow();
+ mHidePopup = new Runnable() {
+ @Override
+ public void run() {
+ hide();
+ }
+ };
+ }
+ mPopupWindow.show(mEasyEditSpan);
+ TextView.this.removeCallbacks(mHidePopup);
+ TextView.this.postDelayed(mHidePopup, DISPLAY_TIMEOUT_MS);
+ }
+ }
+ }
+
+ /**
+ * Adjusts the spans by removing all of them except the last one.
+ */
+ private void adjustSpans(CharSequence buffer) {
+ // This method enforces that only one easy edit span is attached to the text.
+ // A better way to enforce this would be to listen for onSpanAdded, but this method
+ // cannot be used in this scenario as no notification is triggered when a text with
+ // spans is inserted into a text.
+ if (buffer instanceof Spannable) {
+ Spannable spannable = (Spannable) buffer;
+ EasyEditSpan[] spans = spannable.getSpans(0, spannable.length(),
+ EasyEditSpan.class);
+ for (int i = 0; i < spans.length - 1; i++) {
+ spannable.removeSpan(spans[i]);
+ }
+ }
+ }
+
+ /**
+ * Removes all the {@link EasyEditSpan} currently attached.
+ */
+ private void removeSpans(CharSequence buffer) {
+ if (buffer instanceof Spannable) {
+ Spannable spannable = (Spannable) buffer;
+ EasyEditSpan[] spans = spannable.getSpans(0, spannable.length(),
+ EasyEditSpan.class);
+ for (int i = 0; i < spans.length; i++) {
+ spannable.removeSpan(spans[i]);
+ }
+ }
+ }
+
+ private EasyEditSpan getSpan(Spanned spanned) {
+ EasyEditSpan[] easyEditSpans = spanned.getSpans(0, spanned.length(),
+ EasyEditSpan.class);
+ if (easyEditSpans.length == 0) {
+ return null;
+ } else {
+ return easyEditSpans[0];
+ }
+ }
+ }
+
+ /**
+ * Displays the actions associated to an {@link EasyEditSpan}. The pop-up is controlled
+ * by {@link EasyEditSpanController}.
+ */
+ private class EasyEditPopupWindow extends PinnedPopupWindow
+ implements OnClickListener {
+ private static final int POPUP_TEXT_LAYOUT =
+ com.android.internal.R.layout.text_edit_action_popup_text;
+ private TextView mDeleteTextView;
+ private EasyEditSpan mEasyEditSpan;
+
+ @Override
+ protected void createPopupWindow() {
+ mPopupWindow = new PopupWindow(TextView.this.mContext, null,
+ com.android.internal.R.attr.textSelectHandleWindowStyle);
+ mPopupWindow.setInputMethodMode(PopupWindow.INPUT_METHOD_NOT_NEEDED);
+ mPopupWindow.setClippingEnabled(true);
+ }
+
+ @Override
+ protected void initContentView() {
+ LinearLayout linearLayout = new LinearLayout(TextView.this.getContext());
+ linearLayout.setOrientation(LinearLayout.HORIZONTAL);
+ mContentView = linearLayout;
+ mContentView.setBackgroundResource(
+ com.android.internal.R.drawable.text_edit_side_paste_window);
+
+ LayoutInflater inflater = (LayoutInflater)TextView.this.mContext.
+ getSystemService(Context.LAYOUT_INFLATER_SERVICE);
+
+ LayoutParams wrapContent = new LayoutParams(
+ ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);
+
+ mDeleteTextView = (TextView) inflater.inflate(POPUP_TEXT_LAYOUT, null);
+ mDeleteTextView.setLayoutParams(wrapContent);
+ mDeleteTextView.setText(com.android.internal.R.string.delete);
+ mDeleteTextView.setOnClickListener(this);
+ mContentView.addView(mDeleteTextView);
+ }
+
+ public void show(EasyEditSpan easyEditSpan) {
+ mEasyEditSpan = easyEditSpan;
+ super.show();
+ }
+
+ @Override
+ public void onClick(View view) {
+ if (view == mDeleteTextView) {
+ Editable editable = (Editable) mText;
+ int start = editable.getSpanStart(mEasyEditSpan);
+ int end = editable.getSpanEnd(mEasyEditSpan);
+ if (start >= 0 && end >= 0) {
+ deleteText_internal(start, end);
+ }
+ }
+ }
+
+ @Override
+ protected int getTextOffset() {
+ // Place the pop-up at the end of the span
+ Editable editable = (Editable) mText;
+ return editable.getSpanEnd(mEasyEditSpan);
+ }
+
+ @Override
+ protected int getVerticalLocalPosition(int line) {
+ return mLayout.getLineBottom(line);
+ }
+
+ @Override
+ protected int clipVertically(int positionY) {
+ // As we display the pop-up below the span, no vertical clipping is required.
+ return positionY;
+ }
+ }
+
+ private class ChangeWatcher implements TextWatcher, SpanWatcher {
+
+ private CharSequence mBeforeText;
+
+ private EasyEditSpanController mEasyEditSpanController;
+
+ private ChangeWatcher() {
+ mEasyEditSpanController = new EasyEditSpanController();
+ }
+
+ public void beforeTextChanged(CharSequence buffer, int start,
+ int before, int after) {
+ if (DEBUG_EXTRACT) Log.v(LOG_TAG, "beforeTextChanged start=" + start
+ + " before=" + before + " after=" + after + ": " + buffer);
+
+ if (AccessibilityManager.getInstance(mContext).isEnabled()
+ && !isPasswordInputType(getInputType())
+ && !hasPasswordTransformationMethod()) {
+ mBeforeText = buffer.toString();
+ }
+
+ TextView.this.sendBeforeTextChanged(buffer, start, before, after);
+ }
+
+ public void onTextChanged(CharSequence buffer, int start,
+ int before, int after) {
+ if (DEBUG_EXTRACT) Log.v(LOG_TAG, "onTextChanged start=" + start
+ + " before=" + before + " after=" + after + ": " + buffer);
+ TextView.this.handleTextChanged(buffer, start, before, after);
+
+ mEasyEditSpanController.onTextChange(buffer);
+
+ if (AccessibilityManager.getInstance(mContext).isEnabled() &&
+ (isFocused() || isSelected() && isShown())) {
+ sendAccessibilityEventTypeViewTextChanged(mBeforeText, start, before, after);
+ mBeforeText = null;
+ }
+ }
+
+ public void afterTextChanged(Editable buffer) {
+ if (DEBUG_EXTRACT) Log.v(LOG_TAG, "afterTextChanged: " + buffer);
+ TextView.this.sendAfterTextChanged(buffer);
+
+ if (MetaKeyKeyListener.getMetaState(buffer, MetaKeyKeyListener.META_SELECTING) != 0) {
+ MetaKeyKeyListener.stopSelecting(TextView.this, buffer);
+ }
+ }
+
+ public void onSpanChanged(Spannable buf,
+ Object what, int s, int e, int st, int en) {
+ if (DEBUG_EXTRACT) Log.v(LOG_TAG, "onSpanChanged s=" + s + " e=" + e
+ + " st=" + st + " en=" + en + " what=" + what + ": " + buf);
+ TextView.this.spanChange(buf, what, s, st, e, en);
+ }
+
+ public void onSpanAdded(Spannable buf, Object what, int s, int e) {
+ if (DEBUG_EXTRACT) Log.v(LOG_TAG, "onSpanAdded s=" + s + " e=" + e
+ + " what=" + what + ": " + buf);
+ TextView.this.spanChange(buf, what, -1, s, -1, e);
+ }
+
+ public void onSpanRemoved(Spannable buf, Object what, int s, int e) {
+ if (DEBUG_EXTRACT) Log.v(LOG_TAG, "onSpanRemoved s=" + s + " e=" + e
+ + " what=" + what + ": " + buf);
+ TextView.this.spanChange(buf, what, s, -1, e, -1);
+ }
+
+ private void hideControllers() {
+ mEasyEditSpanController.hide();
+ }
+ }
+
+ private static class Blink extends Handler implements Runnable {
+ private final WeakReference<TextView> mView;
+ private boolean mCancelled;
+
+ public Blink(TextView v) {
+ mView = new WeakReference<TextView>(v);
+ }
+
+ public void run() {
+ if (mCancelled) {
+ return;
+ }
+
+ removeCallbacks(Blink.this);
+
+ TextView tv = mView.get();
+
+ if (tv != null && tv.shouldBlink()) {
+ if (tv.mLayout != null) {
+ tv.invalidateCursorPath();
+ }
+
+ postAtTime(this, SystemClock.uptimeMillis() + BLINK);
+ }
+ }
+
+ void cancel() {
+ if (!mCancelled) {
+ removeCallbacks(Blink.this);
+ mCancelled = true;
+ }
+ }
+
+ void uncancel() {
+ mCancelled = false;
+ }
+ }
+
+ private static class DragLocalState {
+ public TextView sourceTextView;
+ public int start, end;
+
+ public DragLocalState(TextView sourceTextView, int start, int end) {
+ this.sourceTextView = sourceTextView;
+ this.start = start;
+ this.end = end;
+ }
+ }
+
private class PositionListener implements ViewTreeObserver.OnPreDrawListener {
// 3 handles
// 3 ActionPopup [replace, suggestion, easyedit] (suggestionsPopup first hides the others)
@@ -9324,6 +9693,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
private int mPositionX, mPositionY;
private int mNumberOfListeners;
private boolean mScrollHasChanged;
+ final int[] mTempCoords = new int[2];
public void addSubscriber(TextViewPositionListener positionListener, boolean canMove) {
if (mNumberOfListeners == 0) {
@@ -9402,62 +9772,6 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
}
}
- private boolean isPositionVisible(int positionX, int positionY) {
- synchronized (sTmpPosition) {
- final float[] position = sTmpPosition;
- position[0] = positionX;
- position[1] = positionY;
- View view = this;
-
- while (view != null) {
- if (view != this) {
- // Local scroll is already taken into account in positionX/Y
- position[0] -= view.getScrollX();
- position[1] -= view.getScrollY();
- }
-
- if (position[0] < 0 || position[1] < 0 ||
- position[0] > view.getWidth() || position[1] > view.getHeight()) {
- return false;
- }
-
- if (!view.getMatrix().isIdentity()) {
- view.getMatrix().mapPoints(position);
- }
-
- position[0] += view.getLeft();
- position[1] += view.getTop();
-
- final ViewParent parent = view.getParent();
- if (parent instanceof View) {
- view = (View) parent;
- } else {
- // We've reached the ViewRoot, stop iterating
- view = null;
- }
- }
- }
-
- // We've been able to walk up the view hierarchy and the position was never clipped
- return true;
- }
-
- private boolean isOffsetVisible(int offset) {
- final int line = mLayout.getLineForOffset(offset);
- final int lineBottom = mLayout.getLineBottom(line);
- final int primaryHorizontal = (int) mLayout.getPrimaryHorizontal(offset);
- return isPositionVisible(primaryHorizontal + viewportToContentHorizontalOffset(),
- lineBottom + viewportToContentVerticalOffset());
- }
-
- @Override
- protected void onScrollChanged(int horiz, int vert, int oldHoriz, int oldVert) {
- super.onScrollChanged(horiz, vert, oldHoriz, oldVert);
- if (mPositionListener != null) {
- mPositionListener.onScrollChanged();
- }
- }
-
private abstract class PinnedPopupWindow implements TextViewPositionListener {
protected PopupWindow mPopupWindow;
protected ViewGroup mContentView;
@@ -9585,7 +9899,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
TextView.this.getPositionListener().removeSubscriber(SuggestionsPopupWindow.this);
// Safe cast since show() checks that mText is an Editable
- ((Spannable) mText).removeSpan(mSuggestionRangeSpan);
+ ((Spannable) mText).removeSpan(getEditor().mSuggestionRangeSpan);
setCursorVisible(mCursorWasVisibleBeforeSuggestions);
if (hasInsertionController()) {
@@ -9595,7 +9909,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
}
public SuggestionsPopupWindow() {
- mCursorWasVisibleBeforeSuggestions = mCursorVisible;
+ mCursorWasVisibleBeforeSuggestions = getEditor().mCursorVisible;
mSuggestionSpanComparator = new SuggestionSpanComparator();
mSpansLengths = new HashMap<SuggestionSpan, Integer>();
}
@@ -9733,7 +10047,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
if (!(mText instanceof Editable)) return;
if (updateSuggestions()) {
- mCursorWasVisibleBeforeSuggestions = mCursorVisible;
+ mCursorWasVisibleBeforeSuggestions = getEditor().mCursorVisible;
setCursorVisible(false);
mIsShowingUp = true;
super.show();
@@ -9888,17 +10202,17 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
mNumberOfSuggestions++;
- if (mSuggestionRangeSpan == null) mSuggestionRangeSpan = new SuggestionRangeSpan();
+ if (getEditor().mSuggestionRangeSpan == null) getEditor().mSuggestionRangeSpan = new SuggestionRangeSpan();
if (underlineColor == 0) {
// Fallback on the default highlight color when the first span does not provide one
- mSuggestionRangeSpan.setBackgroundColor(mHighlightColor);
+ getEditor().mSuggestionRangeSpan.setBackgroundColor(mHighlightColor);
} else {
final float BACKGROUND_TRANSPARENCY = 0.4f;
final int newAlpha = (int) (Color.alpha(underlineColor) * BACKGROUND_TRANSPARENCY);
- mSuggestionRangeSpan.setBackgroundColor(
+ getEditor().mSuggestionRangeSpan.setBackgroundColor(
(underlineColor & 0x00FFFFFF) + (newAlpha << 24));
}
- spannable.setSpan(mSuggestionRangeSpan, spanUnionStart, spanUnionEnd,
+ spannable.setSpan(getEditor().mSuggestionRangeSpan, spanUnionStart, spanUnionEnd,
Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
mSuggestionsAdapter.notifyDataSetChanged();
@@ -9931,8 +10245,8 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
SuggestionInfo suggestionInfo = mSuggestionInfos[position];
if (suggestionInfo.suggestionIndex == DELETE_TEXT) {
- final int spanUnionStart = editable.getSpanStart(mSuggestionRangeSpan);
- int spanUnionEnd = editable.getSpanEnd(mSuggestionRangeSpan);
+ final int spanUnionStart = editable.getSpanStart(getEditor().mSuggestionRangeSpan);
+ int spanUnionEnd = editable.getSpanEnd(getEditor().mSuggestionRangeSpan);
if (spanUnionStart >= 0 && spanUnionEnd > spanUnionStart) {
// Do not leave two adjacent spaces after deletion, or one at beginning of text
if (spanUnionEnd < editable.length() &&
@@ -10032,209 +10346,6 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
}
/**
- * Removes the suggestion spans.
- */
- CharSequence removeSuggestionSpans(CharSequence text) {
- if (text instanceof Spanned) {
- Spannable spannable;
- if (text instanceof Spannable) {
- spannable = (Spannable) text;
- } else {
- spannable = new SpannableString(text);
- text = spannable;
- }
-
- SuggestionSpan[] spans = spannable.getSpans(0, text.length(), SuggestionSpan.class);
- for (int i = 0; i < spans.length; i++) {
- spannable.removeSpan(spans[i]);
- }
- }
- return text;
- }
-
- void showSuggestions() {
- if (mSuggestionsPopupWindow == null) {
- mSuggestionsPopupWindow = new SuggestionsPopupWindow();
- }
- hideControllers();
- mSuggestionsPopupWindow.show();
- }
-
- boolean areSuggestionsShown() {
- return mSuggestionsPopupWindow != null && mSuggestionsPopupWindow.isShowing();
- }
-
- /**
- * Return whether or not suggestions are enabled on this TextView. The suggestions are generated
- * by the IME or by the spell checker as the user types. This is done by adding
- * {@link SuggestionSpan}s to the text.
- *
- * When suggestions are enabled (default), this list of suggestions will be displayed when the
- * user asks for them on these parts of the text. This value depends on the inputType of this
- * TextView.
- *
- * The class of the input type must be {@link InputType#TYPE_CLASS_TEXT}.
- *
- * In addition, the type variation must be one of
- * {@link InputType#TYPE_TEXT_VARIATION_NORMAL},
- * {@link InputType#TYPE_TEXT_VARIATION_EMAIL_SUBJECT},
- * {@link InputType#TYPE_TEXT_VARIATION_LONG_MESSAGE},
- * {@link InputType#TYPE_TEXT_VARIATION_SHORT_MESSAGE} or
- * {@link InputType#TYPE_TEXT_VARIATION_WEB_EDIT_TEXT}.
- *
- * And finally, the {@link InputType#TYPE_TEXT_FLAG_NO_SUGGESTIONS} flag must <i>not</i> be set.
- *
- * @return true if the suggestions popup window is enabled, based on the inputType.
- */
- public boolean isSuggestionsEnabled() {
- if ((mInputType & InputType.TYPE_MASK_CLASS) != InputType.TYPE_CLASS_TEXT) return false;
- if ((mInputType & InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS) > 0) return false;
-
- final int variation = mInputType & EditorInfo.TYPE_MASK_VARIATION;
- return (variation == EditorInfo.TYPE_TEXT_VARIATION_NORMAL ||
- variation == EditorInfo.TYPE_TEXT_VARIATION_EMAIL_SUBJECT ||
- variation == EditorInfo.TYPE_TEXT_VARIATION_LONG_MESSAGE ||
- variation == EditorInfo.TYPE_TEXT_VARIATION_SHORT_MESSAGE ||
- variation == EditorInfo.TYPE_TEXT_VARIATION_WEB_EDIT_TEXT);
- }
-
- /**
- * 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.
- *
- * 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 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.
- *
- * 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 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.
- */
- public void setCustomSelectionActionModeCallback(ActionMode.Callback actionModeCallback) {
- mCustomSelectionActionModeCallback = actionModeCallback;
- }
-
- /**
- * Retrieves the value set in {@link #setCustomSelectionActionModeCallback}. Default is null.
- *
- * @return The current custom selection callback.
- */
- public ActionMode.Callback getCustomSelectionActionModeCallback() {
- return mCustomSelectionActionModeCallback;
- }
-
- /**
- *
- * @return true if the selection mode was actually started.
- */
- private boolean startSelectionActionMode() {
- if (mSelectionActionMode != null) {
- // Selection action mode is already started
- return false;
- }
-
- if (!canSelectText() || !requestFocus()) {
- Log.w(LOG_TAG, "TextView does not support text selection. Action mode cancelled.");
- return false;
- }
-
- if (!hasSelection()) {
- // There may already be a selection on device rotation
- if (!selectCurrentWord()) {
- // No word found under cursor or text selection not permitted.
- return false;
- }
- }
-
- boolean willExtract = extractedTextModeWillBeStarted();
-
- // 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 = startActionMode(actionModeCallback);
- }
-
- final boolean selectionStarted = mSelectionActionMode != null || willExtract;
- if (selectionStarted && !mTextIsSelectable) {
- // Show the IME to be able to replace text, except when selecting non editable text.
- final InputMethodManager imm = InputMethodManager.peekInstance();
- if (imm != null) {
- imm.showSoftInput(this, 0, null);
- }
- }
-
- return selectionStarted;
- }
-
- private boolean extractedTextModeWillBeStarted() {
- if (!(this instanceof ExtractEditText)) {
- final InputMethodManager imm = InputMethodManager.peekInstance();
- return imm != null && imm.isFullscreenMode();
- }
- return false;
- }
-
- /**
- * @hide
- */
- protected void stopSelectionActionMode() {
- if (mSelectionActionMode != null) {
- // This will hide the mSelectionModifierCursorController
- mSelectionActionMode.finish();
- }
- }
-
- /**
- * Paste clipboard content between min and max positions.
- */
- private void paste(int min, int max) {
- ClipboardManager clipboard =
- (ClipboardManager) getContext().getSystemService(Context.CLIPBOARD_SERVICE);
- ClipData clip = clipboard.getPrimaryClip();
- if (clip != null) {
- boolean didFirst = false;
- for (int i=0; i<clip.getItemCount(); i++) {
- CharSequence paste = clip.getItemAt(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);
- didFirst = true;
- } else {
- ((Editable) mText).insert(getSelectionEnd(), "\n");
- ((Editable) mText).insert(getSelectionEnd(), paste);
- }
- }
- }
- stopSelectionActionMode();
- sLastCutOrCopyTime = 0;
- }
- }
-
- private void setPrimaryClip(ClipData clip) {
- ClipboardManager clipboard = (ClipboardManager) getContext().
- getSystemService(Context.CLIPBOARD_SERVICE);
- clipboard.setPrimaryClip(clip);
- 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
@@ -10296,8 +10407,8 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
styledAttributes.recycle();
- if (mCustomSelectionActionModeCallback != null) {
- if (!mCustomSelectionActionModeCallback.onCreateActionMode(mode, menu)) {
+ if (getEditor().mCustomSelectionActionModeCallback != null) {
+ if (!getEditor().mCustomSelectionActionModeCallback.onCreateActionMode(mode, menu)) {
// The custom mode can choose to cancel the action mode
return false;
}
@@ -10313,16 +10424,16 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
@Override
public boolean onPrepareActionMode(ActionMode mode, Menu menu) {
- if (mCustomSelectionActionModeCallback != null) {
- return mCustomSelectionActionModeCallback.onPrepareActionMode(mode, menu);
+ if (getEditor().mCustomSelectionActionModeCallback != null) {
+ return getEditor().mCustomSelectionActionModeCallback.onPrepareActionMode(mode, menu);
}
return true;
}
@Override
public boolean onActionItemClicked(ActionMode mode, MenuItem item) {
- if (mCustomSelectionActionModeCallback != null &&
- mCustomSelectionActionModeCallback.onActionItemClicked(mode, item)) {
+ if (getEditor().mCustomSelectionActionModeCallback != null &&
+ getEditor().mCustomSelectionActionModeCallback.onActionItemClicked(mode, item)) {
return true;
}
return onTextContextMenuItem(item.getItemId());
@@ -10330,16 +10441,16 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
@Override
public void onDestroyActionMode(ActionMode mode) {
- if (mCustomSelectionActionModeCallback != null) {
- mCustomSelectionActionModeCallback.onDestroyActionMode(mode);
+ if (getEditor().mCustomSelectionActionModeCallback != null) {
+ getEditor().mCustomSelectionActionModeCallback.onDestroyActionMode(mode);
}
Selection.setSelection((Spannable) mText, getSelectionEnd());
- if (mSelectionModifierCursorController != null) {
- mSelectionModifierCursorController.hide();
+ if (getEditor().mSelectionModifierCursorController != null) {
+ getEditor().mSelectionModifierCursorController.hide();
}
- mSelectionActionMode = null;
+ getEditor().mSelectionActionMode = null;
}
}
@@ -10353,7 +10464,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
protected void createPopupWindow() {
mPopupWindow = new PopupWindow(TextView.this.mContext, null,
com.android.internal.R.attr.textSelectHandleWindowStyle);
- mPopupWindow.setClippingEnabled(true);
+ mPopupWindow.setClippingEnabled(true);
}
@Override
@@ -10753,7 +10864,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
public void show() {
super.show();
- final long durationSinceCutOrCopy = SystemClock.uptimeMillis() - sLastCutOrCopyTime;
+ final long durationSinceCutOrCopy = SystemClock.uptimeMillis() - LAST_CUT_OR_COPY_TIME;
if (durationSinceCutOrCopy < RECENT_CUT_COPY_DURATION) {
showActionPopupWindow(0);
}
@@ -10767,13 +10878,14 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
}
private void hideAfterDelay() {
- removeHiderCallback();
if (mHider == null) {
mHider = new Runnable() {
public void run() {
hide();
}
};
+ } else {
+ removeHiderCallback();
}
TextView.this.postDelayed(mHider, DELAY_BEFORE_HANDLE_FADES_OUT);
}
@@ -10994,12 +11106,12 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
}
private InsertionHandleView getHandle() {
- if (mSelectHandleCenter == null) {
- mSelectHandleCenter = mContext.getResources().getDrawable(
+ if (getEditor().mSelectHandleCenter == null) {
+ getEditor().mSelectHandleCenter = mContext.getResources().getDrawable(
mTextSelectHandleRes);
}
if (mHandle == null) {
- mHandle = new InsertionHandleView(mSelectHandleCenter);
+ mHandle = new InsertionHandleView(getEditor().mSelectHandleCenter);
}
return mHandle;
}
@@ -11040,12 +11152,12 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
}
private void initDrawables() {
- if (mSelectHandleLeft == null) {
- mSelectHandleLeft = mContext.getResources().getDrawable(
+ if (getEditor().mSelectHandleLeft == null) {
+ getEditor().mSelectHandleLeft = mContext.getResources().getDrawable(
mTextSelectHandleLeftRes);
}
- if (mSelectHandleRight == null) {
- mSelectHandleRight = mContext.getResources().getDrawable(
+ if (getEditor().mSelectHandleRight == null) {
+ getEditor().mSelectHandleRight = mContext.getResources().getDrawable(
mTextSelectHandleRightRes);
}
}
@@ -11053,10 +11165,10 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
private void initHandles() {
// Lazy object creation has to be done before updatePosition() is called.
if (mStartHandle == null) {
- mStartHandle = new SelectionStartHandleView(mSelectHandleLeft, mSelectHandleRight);
+ mStartHandle = new SelectionStartHandleView(getEditor().mSelectHandleLeft, getEditor().mSelectHandleRight);
}
if (mEndHandle == null) {
- mEndHandle = new SelectionEndHandleView(mSelectHandleRight, mSelectHandleLeft);
+ mEndHandle = new SelectionEndHandleView(getEditor().mSelectHandleRight, getEditor().mSelectHandleLeft);
}
mStartHandle.show();
@@ -11101,7 +11213,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
if (stayedInArea && isPositionOnText(x, y)) {
startSelectionActionMode();
- mDiscardNextActionUp = true;
+ getEditor().mDiscardNextActionUp = true;
}
}
}
@@ -11190,461 +11302,501 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
}
}
- private void hideInsertionPointCursorController() {
- // No need to create the controller to hide it.
- if (mInsertionPointCursorController != null) {
- mInsertionPointCursorController.hide();
- }
+ static class InputContentType {
+ int imeOptions = EditorInfo.IME_NULL;
+ String privateImeOptions;
+ CharSequence imeActionLabel;
+ int imeActionId;
+ Bundle extras;
+ OnEditorActionListener onEditorActionListener;
+ boolean enterDown;
}
- /**
- * Hides the insertion controller and stops text selection mode, hiding the selection controller
- */
- private void hideControllers() {
- hideCursorControllers();
- hideSpanControllers();
+ static class InputMethodState {
+ Rect mCursorRectInWindow = new Rect();
+ RectF mTmpRectF = new RectF();
+ float[] mTmpOffset = new float[2];
+ ExtractedTextRequest mExtracting;
+ final ExtractedText mTmpExtracted = new ExtractedText();
+ int mBatchEditNesting;
+ boolean mCursorChanged;
+ boolean mSelectionModeChanged;
+ boolean mContentChanged;
+ int mChangedStart, mChangedEnd, mChangedDelta;
}
- private void hideSpanControllers() {
- if (mChangeWatcher != null) {
- mChangeWatcher.hideControllers();
+ private class Editor {
+ Editor() {
+ mHighlightPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
+ final CompatibilityInfo compat = TextView.this.getResources().getCompatibilityInfo();
+ mHighlightPaint.setCompatibilityScaling(compat.applicationScale);
}
- }
- private void hideCursorControllers() {
- if (mSuggestionsPopupWindow != null && !mSuggestionsPopupWindow.isShowingUp()) {
- // Should be done before hide insertion point controller since it triggers a show of it
- mSuggestionsPopupWindow.hide();
- }
- hideInsertionPointCursorController();
- stopSelectionActionMode();
- }
+ // Cursor Controllers.
+ InsertionPointCursorController mInsertionPointCursorController;
+ SelectionModifierCursorController mSelectionModifierCursorController;
+ ActionMode mSelectionActionMode;
+ boolean mInsertionControllerEnabled;
+ boolean mSelectionControllerEnabled;
- /**
- * Get the character offset closest to the specified absolute position. A typical use case is to
- * pass the result of {@link MotionEvent#getX()} and {@link MotionEvent#getY()} to this method.
- *
- * @param x The horizontal absolute position of a point on screen
- * @param y The vertical absolute position of a point on screen
- * @return the character offset for the character whose position is closest to the specified
- * position. Returns -1 if there is no layout.
- */
- public int getOffsetForPosition(float x, float y) {
- if (getLayout() == null) return -1;
- final int line = getLineAtCoordinate(y);
- final int offset = getOffsetAtCoordinate(line, x);
- return offset;
- }
+ // Used to highlight a word when it is corrected by the IME
+ CorrectionHighlighter mCorrectionHighlighter;
- private float convertToLocalHorizontalCoordinate(float x) {
- x -= getTotalPaddingLeft();
- // Clamp the position to inside of the view.
- x = Math.max(0.0f, x);
- x = Math.min(getWidth() - getTotalPaddingRight() - 1, x);
- x += getScrollX();
- return x;
- }
+ InputContentType mInputContentType;
+ InputMethodState mInputMethodState;
- private int getLineAtCoordinate(float y) {
- y -= getTotalPaddingTop();
- // Clamp the position to inside of the view.
- y = Math.max(0.0f, y);
- y = Math.min(getHeight() - getTotalPaddingBottom() - 1, y);
- y += getScrollY();
- return getLayout().getLineForVertical((int) y);
- }
+ Path mHighlightPath;
+ boolean mHighlightPathBogus = true;
+ final Paint mHighlightPaint;
- private int getOffsetAtCoordinate(int line, float x) {
- x = convertToLocalHorizontalCoordinate(x);
- return getLayout().getOffsetForHorizontal(line, x);
- }
+ DisplayList mTextDisplayList;
+ boolean mTextDisplayListIsValid;
- /** Returns true if the screen coordinates position (x,y) corresponds to a character displayed
- * in the view. Returns false when the position is in the empty space of left/right of text.
- */
- private boolean isPositionOnText(float x, float y) {
- if (getLayout() == null) return false;
+ boolean mFrozenWithFocus;
+ boolean mSelectionMoved;
+ boolean mTouchFocusSelected;
- final int line = getLineAtCoordinate(y);
- x = convertToLocalHorizontalCoordinate(x);
+ KeyListener mKeyListener;
+ int mInputType = EditorInfo.TYPE_NULL;
- if (x < getLayout().getLineLeft(line)) return false;
- if (x > getLayout().getLineRight(line)) return false;
- return true;
- }
+ boolean mDiscardNextActionUp;
+ boolean mIgnoreActionUpEvent;
- @Override
- public boolean onDragEvent(DragEvent event) {
- switch (event.getAction()) {
- case DragEvent.ACTION_DRAG_STARTED:
- return hasInsertionController();
+ long mShowCursor;
+ Blink mBlink;
- case DragEvent.ACTION_DRAG_ENTERED:
- TextView.this.requestFocus();
- return true;
+ boolean mCursorVisible = true;
+ boolean mSelectAllOnFocus;
+ boolean mTextIsSelectable;
- case DragEvent.ACTION_DRAG_LOCATION:
- final int offset = getOffsetForPosition(event.getX(), event.getY());
- Selection.setSelection((Spannable)mText, offset);
- return true;
+ CharSequence mError;
+ boolean mErrorWasChanged;
+ ErrorPopup mErrorPopup;
+ /**
+ * This flag is set if the TextView tries to display an error before it
+ * is attached to the window (so its position is still unknown).
+ * It causes the error to be shown later, when onAttachedToWindow()
+ * is called.
+ */
+ boolean mShowErrorAfterAttach;
- case DragEvent.ACTION_DROP:
- onDrop(event);
- return true;
+ boolean mInBatchEditControllers;
- case DragEvent.ACTION_DRAG_ENDED:
- case DragEvent.ACTION_DRAG_EXITED:
- default:
- return true;
- }
- }
+ SuggestionsPopupWindow mSuggestionsPopupWindow;
+ SuggestionRangeSpan mSuggestionRangeSpan;
+ Runnable mShowSuggestionRunnable;
- private void onDrop(DragEvent event) {
- StringBuilder content = new StringBuilder("");
- ClipData clipData = event.getClipData();
- final int itemCount = clipData.getItemCount();
- for (int i=0; i < itemCount; i++) {
- Item item = clipData.getItemAt(i);
- content.append(item.coerceToText(TextView.this.mContext));
- }
+ final Drawable[] mCursorDrawable = new Drawable[2];
+ int mCursorCount; // Actual current number of used mCursorDrawable: 0, 1 or 2 (split)
- final int offset = getOffsetForPosition(event.getX(), event.getY());
+ Drawable mSelectHandleLeft;
+ Drawable mSelectHandleRight;
+ Drawable mSelectHandleCenter;
- Object localState = event.getLocalState();
- DragLocalState dragLocalState = null;
- if (localState instanceof DragLocalState) {
- dragLocalState = (DragLocalState) localState;
- }
- boolean dragDropIntoItself = dragLocalState != null &&
- dragLocalState.sourceTextView == this;
+ // Global listener that detects changes in the global position of the TextView
+ PositionListener mPositionListener;
- if (dragDropIntoItself) {
- if (offset >= dragLocalState.start && offset < dragLocalState.end) {
- // A drop inside the original selection discards the drop.
- return;
+ float mLastDownPositionX, mLastDownPositionY;
+ Callback mCustomSelectionActionModeCallback;
+
+ // Set when this TextView gained focus with some text selected. Will start selection mode.
+ boolean mCreatedWithASelection;
+
+ WordIterator mWordIterator;
+ SpellChecker mSpellChecker;
+
+ void onAttachedToWindow() {
+ final ViewTreeObserver observer = getViewTreeObserver();
+ // No need to create the controller.
+ // The get method will add the listener on controller creation.
+ if (mInsertionPointCursorController != null) {
+ observer.addOnTouchModeChangeListener(mInsertionPointCursorController);
+ }
+ if (mSelectionModifierCursorController != null) {
+ observer.addOnTouchModeChangeListener(mSelectionModifierCursorController);
}
+ updateSpellCheckSpans(0, mText.length(), true /* create the spell checker if needed */);
}
- final int originalLength = mText.length();
- long minMax = prepareSpacesAroundPaste(offset, offset, content);
- int min = extractRangeStartFromLong(minMax);
- int max = extractRangeEndFromLong(minMax);
+ void onDetachedFromWindow() {
+ if (mError != null) {
+ hideError();
+ }
- Selection.setSelection((Spannable) mText, max);
- replaceText_internal(min, max, content);
+ if (mBlink != null) {
+ mBlink.removeCallbacks(mBlink);
+ }
- if (dragDropIntoItself) {
- int dragSourceStart = dragLocalState.start;
- int dragSourceEnd = dragLocalState.end;
- if (max <= dragSourceStart) {
- // Inserting text before selection has shifted positions
- final int shift = mText.length() - originalLength;
- dragSourceStart += shift;
- dragSourceEnd += shift;
+ if (mInsertionPointCursorController != null) {
+ mInsertionPointCursorController.onDetached();
}
- // Delete original selection
- deleteText_internal(dragSourceStart, dragSourceEnd);
+ if (mSelectionModifierCursorController != null) {
+ mSelectionModifierCursorController.onDetached();
+ }
- // Make sure we do not leave two adjacent spaces.
- if ((dragSourceStart == 0 ||
- Character.isSpaceChar(mTransformed.charAt(dragSourceStart - 1))) &&
- (dragSourceStart == mText.length() ||
- Character.isSpaceChar(mTransformed.charAt(dragSourceStart)))) {
- final int pos = dragSourceStart == mText.length() ?
- dragSourceStart - 1 : dragSourceStart;
- deleteText_internal(pos, pos + 1);
+ if (mShowSuggestionRunnable != null) {
+ removeCallbacks(mShowSuggestionRunnable);
}
- }
- }
- /**
- * @return True if this view supports insertion handles.
- */
- boolean hasInsertionController() {
- return mInsertionControllerEnabled;
- }
+ if (mTextDisplayList != null) {
+ mTextDisplayList.invalidate();
+ }
- /**
- * @return True if this view supports selection handles.
- */
- boolean hasSelectionController() {
- return mSelectionControllerEnabled;
- }
+ if (mSpellChecker != null) {
+ mSpellChecker.closeSession();
+ // Forces the creation of a new SpellChecker next time this window is created.
+ // Will handle the cases where the settings has been changed in the meantime.
+ mSpellChecker = null;
+ }
- InsertionPointCursorController getInsertionController() {
- if (!mInsertionControllerEnabled) {
- return null;
+ hideControllers();
}
- if (mInsertionPointCursorController == null) {
- mInsertionPointCursorController = new InsertionPointCursorController();
-
- final ViewTreeObserver observer = getViewTreeObserver();
- observer.addOnTouchModeChangeListener(mInsertionPointCursorController);
+ void adjustInputType(boolean password, boolean passwordInputType,
+ boolean webPasswordInputType, boolean numberPasswordInputType) {
+ // mInputType has been set from inputType, possibly modified by mInputMethod.
+ // Specialize mInputType to [web]password if we have a text class and the original input
+ // type was a password.
+ if ((mInputType & EditorInfo.TYPE_MASK_CLASS) == EditorInfo.TYPE_CLASS_TEXT) {
+ if (password || passwordInputType) {
+ mInputType = (mInputType & ~(EditorInfo.TYPE_MASK_VARIATION))
+ | EditorInfo.TYPE_TEXT_VARIATION_PASSWORD;
+ }
+ if (webPasswordInputType) {
+ mInputType = (mInputType & ~(EditorInfo.TYPE_MASK_VARIATION))
+ | EditorInfo.TYPE_TEXT_VARIATION_WEB_PASSWORD;
+ }
+ } else if ((mInputType & EditorInfo.TYPE_MASK_CLASS) == EditorInfo.TYPE_CLASS_NUMBER) {
+ if (numberPasswordInputType) {
+ mInputType = (mInputType & ~(EditorInfo.TYPE_MASK_VARIATION))
+ | EditorInfo.TYPE_NUMBER_VARIATION_PASSWORD;
+ }
+ }
}
- return mInsertionPointCursorController;
- }
-
- SelectionModifierCursorController getSelectionController() {
- if (!mSelectionControllerEnabled) {
- return null;
+ void setFrame() {
+ if (mErrorPopup != null) {
+ TextView tv = (TextView) mErrorPopup.getContentView();
+ chooseSize(mErrorPopup, mError, tv);
+ mErrorPopup.update(TextView.this, getErrorX(), getErrorY(),
+ mErrorPopup.getWidth(), mErrorPopup.getHeight());
+ }
}
- if (mSelectionModifierCursorController == null) {
- mSelectionModifierCursorController = new SelectionModifierCursorController();
-
- final ViewTreeObserver observer = getViewTreeObserver();
- observer.addOnTouchModeChangeListener(mSelectionModifierCursorController);
- }
+ void onFocusChanged(boolean focused, int direction) {
+ mShowCursor = SystemClock.uptimeMillis();
+ ensureEndedBatchEdit();
- return mSelectionModifierCursorController;
- }
+ if (focused) {
+ int selStart = getSelectionStart();
+ int selEnd = getSelectionEnd();
- boolean isInBatchEditMode() {
- final InputMethodState ims = mInputMethodState;
- if (ims != null) {
- return ims.mBatchEditNesting > 0;
- }
- return mInBatchEditControllers;
- }
+ // SelectAllOnFocus fields are highlighted and not selected. Do not start text selection
+ // mode for these, unless there was a specific selection already started.
+ final boolean isFocusHighlighted = mSelectAllOnFocus && selStart == 0 &&
+ selEnd == mText.length();
- @Override
- public void onResolveTextDirection() {
- if (hasPasswordTransformationMethod()) {
- mTextDir = TextDirectionHeuristics.LOCALE;
- return;
- }
+ mCreatedWithASelection = mFrozenWithFocus && hasSelection() && !isFocusHighlighted;
- // Always need to resolve layout direction first
- final boolean defaultIsRtl = (getResolvedLayoutDirection() == LAYOUT_DIRECTION_RTL);
+ if (!mFrozenWithFocus || (selStart < 0 || selEnd < 0)) {
+ // If a tap was used to give focus to that view, move cursor at tap position.
+ // Has to be done before onTakeFocus, which can be overloaded.
+ final int lastTapPosition = getLastTapPosition();
+ if (lastTapPosition >= 0) {
+ Selection.setSelection((Spannable) mText, lastTapPosition);
+ }
- // Now, we can select the heuristic
- int textDir = getResolvedTextDirection();
- switch (textDir) {
- default:
- case TEXT_DIRECTION_FIRST_STRONG:
- mTextDir = (defaultIsRtl ? TextDirectionHeuristics.FIRSTSTRONG_RTL :
- TextDirectionHeuristics.FIRSTSTRONG_LTR);
- break;
- case TEXT_DIRECTION_ANY_RTL:
- mTextDir = TextDirectionHeuristics.ANYRTL_LTR;
- break;
- case TEXT_DIRECTION_LTR:
- mTextDir = TextDirectionHeuristics.LTR;
- break;
- case TEXT_DIRECTION_RTL:
- mTextDir = TextDirectionHeuristics.RTL;
- break;
- case TEXT_DIRECTION_LOCALE:
- mTextDir = TextDirectionHeuristics.LOCALE;
- break;
- }
- }
+ // Note this may have to be moved out of the Editor class
+ if (mMovement != null) {
+ mMovement.onTakeFocus(TextView.this, (Spannable) mText, direction);
+ }
- /**
- * Subclasses will need to override this method to implement their own way of resolving
- * drawables depending on the layout direction.
- *
- * A call to the super method will be required from the subclasses implementation.
- */
- protected void resolveDrawables() {
- // No need to resolve twice
- if (mResolvedDrawables) {
- return;
- }
- // No drawable to resolve
- if (mDrawables == null) {
- return;
- }
- // No relative drawable to resolve
- if (mDrawables.mDrawableStart == null && mDrawables.mDrawableEnd == null) {
- mResolvedDrawables = true;
- return;
- }
+ // The DecorView does not have focus when the 'Done' ExtractEditText button is
+ // pressed. Since it is the ViewAncestor's mView, it requests focus before
+ // ExtractEditText clears focus, which gives focus to the ExtractEditText.
+ // This special case ensure that we keep current selection in that case.
+ // It would be better to know why the DecorView does not have focus at that time.
+ if (((TextView.this instanceof ExtractEditText) || mSelectionMoved) &&
+ selStart >= 0 && selEnd >= 0) {
+ /*
+ * Someone intentionally set the selection, so let them
+ * do whatever it is that they wanted to do instead of
+ * the default on-focus behavior. We reset the selection
+ * here instead of just skipping the onTakeFocus() call
+ * because some movement methods do something other than
+ * just setting the selection in theirs and we still
+ * need to go through that path.
+ */
+ Selection.setSelection((Spannable) mText, selStart, selEnd);
+ }
- Drawables dr = mDrawables;
- switch(getResolvedLayoutDirection()) {
- case LAYOUT_DIRECTION_RTL:
- if (dr.mDrawableStart != null) {
- dr.mDrawableRight = dr.mDrawableStart;
+ if (mSelectAllOnFocus) {
+ selectAll();
+ }
- dr.mDrawableSizeRight = dr.mDrawableSizeStart;
- dr.mDrawableHeightRight = dr.mDrawableHeightStart;
+ mTouchFocusSelected = true;
}
- if (dr.mDrawableEnd != null) {
- dr.mDrawableLeft = dr.mDrawableEnd;
- dr.mDrawableSizeLeft = dr.mDrawableSizeEnd;
- dr.mDrawableHeightLeft = dr.mDrawableHeightEnd;
- }
- break;
+ mFrozenWithFocus = false;
+ mSelectionMoved = false;
- case LAYOUT_DIRECTION_LTR:
- default:
- if (dr.mDrawableStart != null) {
- dr.mDrawableLeft = dr.mDrawableStart;
+ if (mError != null) {
+ showError();
+ }
- dr.mDrawableSizeLeft = dr.mDrawableSizeStart;
- dr.mDrawableHeightLeft = dr.mDrawableHeightStart;
+ makeBlink();
+ } else {
+ if (mError != null) {
+ hideError();
+ }
+ // Don't leave us in the middle of a batch edit.
+ onEndBatchEdit();
+
+ if (TextView.this instanceof ExtractEditText) {
+ // terminateTextSelectionMode removes selection, which we want to keep when
+ // ExtractEditText goes out of focus.
+ final int selStart = getSelectionStart();
+ final int selEnd = getSelectionEnd();
+ hideControllers();
+ Selection.setSelection((Spannable) mText, selStart, selEnd);
+ } else {
+ hideControllers();
+ downgradeEasyCorrectionSpans();
}
- if (dr.mDrawableEnd != null) {
- dr.mDrawableRight = dr.mDrawableEnd;
- dr.mDrawableSizeRight = dr.mDrawableSizeEnd;
- dr.mDrawableHeightRight = dr.mDrawableHeightEnd;
+ // No need to create the controller
+ if (mSelectionModifierCursorController != null) {
+ mSelectionModifierCursorController.resetTouchOffsets();
}
- break;
+ }
}
- mResolvedDrawables = true;
- }
- protected void resetResolvedDrawables() {
- mResolvedDrawables = false;
- }
+ void sendOnTextChanged(int start, int after) {
+ updateSpellCheckSpans(start, start + after, false);
+ mTextDisplayListIsValid = false;
- /**
- * @hide
- */
- protected void viewClicked(InputMethodManager imm) {
- if (imm != null) {
- imm.viewClicked(this);
+ // Hide the controllers as soon as text is modified (typing, procedural...)
+ // 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();
}
- }
-
- /**
- * Deletes the range of text [start, end[.
- * @hide
- */
- protected void deleteText_internal(int start, int end) {
- ((Editable) mText).delete(start, end);
- }
- /**
- * Replaces the range of text [start, end[ by replacement text
- * @hide
- */
- protected void replaceText_internal(int start, int end, CharSequence text) {
- ((Editable) mText).replace(start, end, text);
- }
+ private int getLastTapPosition() {
+ // No need to create the controller at that point, no last tap position saved
+ if (mSelectionModifierCursorController != null) {
+ int lastTapPosition = mSelectionModifierCursorController.getMinTouchOffset();
+ if (lastTapPosition >= 0) {
+ // Safety check, should not be possible.
+ if (lastTapPosition > mText.length()) {
+ Log.e(LOG_TAG, "Invalid tap focus position (" + lastTapPosition + " vs "
+ + mText.length() + ")");
+ lastTapPosition = mText.length();
+ }
+ return lastTapPosition;
+ }
+ }
- /**
- * Sets a span on the specified range of text
- * @hide
- */
- protected void setSpan_internal(Object span, int start, int end, int flags) {
- ((Editable) mText).setSpan(span, start, end, flags);
- }
+ return -1;
+ }
- /**
- * Moves the cursor to the specified offset position in text
- * @hide
- */
- protected void setCursorPosition_internal(int start, int end) {
- Selection.setSelection(((Editable) mText), start, end);
- }
+ void onWindowFocusChanged(boolean hasWindowFocus) {
+ if (hasWindowFocus) {
+ if (mBlink != null) {
+ mBlink.uncancel();
+ makeBlink();
+ }
+ } else {
+ if (mBlink != null) {
+ mBlink.cancel();
+ }
+ if (mInputContentType != null) {
+ mInputContentType.enterDown = false;
+ }
+ // Order matters! Must be done before onParentLostFocus to rely on isShowingUp
+ hideControllers();
+ if (mSuggestionsPopupWindow != null) {
+ mSuggestionsPopupWindow.onParentLostFocus();
+ }
- @ViewDebug.ExportedProperty(category = "text")
- private CharSequence mText;
- private CharSequence mTransformed;
- private BufferType mBufferType = BufferType.NORMAL;
+ // Don't leave us in the middle of a batch edit.
+ onEndBatchEdit();
+ }
+ }
- private int mInputType = EditorInfo.TYPE_NULL;
- private CharSequence mHint;
- private Layout mHintLayout;
+ void onTouchEvent(MotionEvent event) {
+ if (hasSelectionController()) {
+ getSelectionController().onTouchEvent(event);
+ }
- private KeyListener mInput;
+ if (mShowSuggestionRunnable != null) {
+ removeCallbacks(mShowSuggestionRunnable);
+ mShowSuggestionRunnable = null;
+ }
- private MovementMethod mMovement;
- private TransformationMethod mTransformation;
- private boolean mAllowTransformationLengthChange;
- private ChangeWatcher mChangeWatcher;
+ if (event.getActionMasked() == MotionEvent.ACTION_DOWN) {
+ mLastDownPositionX = event.getX();
+ mLastDownPositionY = event.getY();
- private ArrayList<TextWatcher> mListeners = null;
+ // Reset this state; it will be re-set if super.onTouchEvent
+ // causes focus to move to the view.
+ mTouchFocusSelected = false;
+ mIgnoreActionUpEvent = false;
+ }
+ }
- // display attributes
- private final TextPaint mTextPaint;
- private boolean mUserSetTextScaleX;
- private final Paint mHighlightPaint;
- private int mHighlightColor = 0x6633B5E5;
- private Layout mLayout;
+ void onDraw(Canvas canvas, Layout layout, int cursorOffsetVertical) {
+ Path highlight = null;
+ Paint highlightPaint = null;
- private long mShowCursor;
- private Blink mBlink;
- private boolean mCursorVisible = true;
+ int selStart = -1, selEnd = -1;
+ boolean drawCursor = false;
- // Cursor Controllers.
- private InsertionPointCursorController mInsertionPointCursorController;
- private SelectionModifierCursorController mSelectionModifierCursorController;
- private ActionMode mSelectionActionMode;
- private boolean mInsertionControllerEnabled;
- private boolean mSelectionControllerEnabled;
- private boolean mInBatchEditControllers;
+ highlightPaint = mHighlightPaint;
+ // If there is no movement method, then there can be no selection.
+ // Check that first and attempt to skip everything having to do with
+ // the cursor.
+ // XXX This is not strictly true -- a program could set the
+ // selection manually if it really wanted to.
+ if (mMovement != null && (isFocused() || isPressed())) {
+ selStart = getSelectionStart();
+ selEnd = getSelectionEnd();
- private boolean mSelectAllOnFocus = false;
+ if (selStart >= 0) {
+ if (mHighlightPath == null) mHighlightPath = new Path();
- private int mGravity = Gravity.TOP | Gravity.START;
- private boolean mHorizontallyScrolling;
+ if (selStart == selEnd) {
+ if (isCursorVisible() &&
+ (SystemClock.uptimeMillis() - mShowCursor) % (2 * BLINK) < BLINK) {
+ if (mHighlightPathBogus) {
+ mHighlightPath.reset();
+ mLayout.getCursorPath(selStart, mHighlightPath, mText);
+ updateCursorsPositions();
+ mHighlightPathBogus = false;
+ }
- private int mAutoLinkMask;
- private boolean mLinksClickable = true;
+ // XXX should pass to skin instead of drawing directly
+ highlightPaint.setColor(mCurTextColor);
+ if (mCurrentAlpha != 255) {
+ highlightPaint.setAlpha(
+ (mCurrentAlpha * Color.alpha(mCurTextColor)) / 255);
+ }
+ highlightPaint.setStyle(Paint.Style.STROKE);
+ highlight = mHighlightPath;
+ drawCursor = mCursorCount > 0;
+ }
+ } else if (textCanBeSelected()) {
+ if (mHighlightPathBogus) {
+ mHighlightPath.reset();
+ mLayout.getSelectionPath(selStart, selEnd, mHighlightPath);
+ mHighlightPathBogus = false;
+ }
- private float mSpacingMult = 1.0f;
- private float mSpacingAdd = 0.0f;
- private boolean mTextIsSelectable = false;
+ // XXX should pass to skin instead of drawing directly
+ highlightPaint.setColor(mHighlightColor);
+ if (mCurrentAlpha != 255) {
+ highlightPaint.setAlpha(
+ (mCurrentAlpha * Color.alpha(mHighlightColor)) / 255);
+ }
+ highlightPaint.setStyle(Paint.Style.FILL);
- private static final int LINES = 1;
- private static final int EMS = LINES;
- private static final int PIXELS = 2;
+ highlight = mHighlightPath;
+ }
+ }
+ }
- private int mMaximum = Integer.MAX_VALUE;
- private int mMaxMode = LINES;
- private int mMinimum = 0;
- private int mMinMode = LINES;
+ final InputMethodState ims = mInputMethodState;
+ if (ims != null && ims.mBatchEditNesting == 0) {
+ InputMethodManager imm = InputMethodManager.peekInstance();
+ if (imm != null) {
+ if (imm.isActive(TextView.this)) {
+ boolean reported = false;
+ if (ims.mContentChanged || ims.mSelectionModeChanged) {
+ // We are in extract mode and the content has changed
+ // in some way... just report complete new text to the
+ // input method.
+ reported = reportExtractedText();
+ }
+ if (!reported && highlight != null) {
+ int candStart = -1;
+ int candEnd = -1;
+ if (mText instanceof Spannable) {
+ Spannable sp = (Spannable)mText;
+ candStart = EditableInputConnection.getComposingSpanStart(sp);
+ candEnd = EditableInputConnection.getComposingSpanEnd(sp);
+ }
+ imm.updateSelection(TextView.this, selStart, selEnd, candStart, candEnd);
+ }
+ }
- private int mOldMaximum = mMaximum;
- private int mOldMaxMode = mMaxMode;
+ if (imm.isWatchingCursor(TextView.this) && highlight != null) {
+ highlight.computeBounds(ims.mTmpRectF, true);
+ ims.mTmpOffset[0] = ims.mTmpOffset[1] = 0;
- private int mMaxWidth = Integer.MAX_VALUE;
- private int mMaxWidthMode = PIXELS;
- private int mMinWidth = 0;
- private int mMinWidthMode = PIXELS;
+ canvas.getMatrix().mapPoints(ims.mTmpOffset);
+ ims.mTmpRectF.offset(ims.mTmpOffset[0], ims.mTmpOffset[1]);
- private boolean mSingleLine;
- private int mDesiredHeightAtMeasure = -1;
- private boolean mIncludePad = true;
+ ims.mTmpRectF.offset(0, cursorOffsetVertical);
- // tmp primitives, so we don't alloc them on each draw
- private Path mHighlightPath;
- private boolean mHighlightPathBogus = true;
- private static final RectF sTempRect = new RectF();
- private static final float[] sTmpPosition = new float[2];
+ ims.mCursorRectInWindow.set((int)(ims.mTmpRectF.left + 0.5),
+ (int)(ims.mTmpRectF.top + 0.5),
+ (int)(ims.mTmpRectF.right + 0.5),
+ (int)(ims.mTmpRectF.bottom + 0.5));
- // XXX should be much larger
- private static final int VERY_WIDE = 1024*1024;
+ imm.updateCursor(TextView.this,
+ ims.mCursorRectInWindow.left, ims.mCursorRectInWindow.top,
+ ims.mCursorRectInWindow.right, ims.mCursorRectInWindow.bottom);
+ }
+ }
+ }
- private static final int BLINK = 500;
+ if (mCorrectionHighlighter != null) {
+ mCorrectionHighlighter.draw(canvas, cursorOffsetVertical);
+ }
- private static final int ANIMATED_SCROLL_GAP = 250;
- private long mLastScroll;
- private Scroller mScroller = null;
+ if (drawCursor) {
+ drawCursor(canvas, cursorOffsetVertical);
+ // Rely on the drawable entirely, do not draw the cursor line.
+ // Has to be done after the IMM related code above which relies on the highlight.
+ highlight = null;
+ }
- private BoringLayout.Metrics mBoring;
- private BoringLayout.Metrics mHintBoring;
+ if (canHaveDisplayList() && canvas.isHardwareAccelerated()) {
+ final int width = mRight - mLeft;
+ final int height = mBottom - mTop;
- private BoringLayout mSavedLayout, mSavedHintLayout;
+ if (mTextDisplayList == null || !mTextDisplayList.isValid() ||
+ !mTextDisplayListIsValid) {
+ if (mTextDisplayList == null) {
+ mTextDisplayList = getHardwareRenderer().createDisplayList("Text");
+ }
- private TextDirectionHeuristic mTextDir = null;
+ final HardwareCanvas hardwareCanvas = mTextDisplayList.start();
+ try {
+ hardwareCanvas.setViewport(width, height);
+ // The dirty rect should always be null for a display list
+ hardwareCanvas.onPreDraw(null);
+ hardwareCanvas.translate(-mScrollX, -mScrollY);
+ layout.draw(hardwareCanvas, highlight, highlightPaint, cursorOffsetVertical);
+ hardwareCanvas.translate(mScrollX, mScrollY);
+ } finally {
+ hardwareCanvas.onPostDraw();
+ mTextDisplayList.end();
+ mTextDisplayListIsValid = true;
+ }
+ }
+ canvas.translate(mScrollX, mScrollY);
+ ((HardwareCanvas) canvas).drawDisplayList(mTextDisplayList, width, height, null,
+ DisplayList.FLAG_CLIP_CHILDREN);
+ canvas.translate(-mScrollX, -mScrollY);
+ } else {
+ layout.draw(canvas, highlight, highlightPaint, cursorOffsetVertical);
+ }
- private static final InputFilter[] NO_FILTERS = new InputFilter[0];
- private InputFilter[] mFilters = NO_FILTERS;
- private static final Spanned EMPTY_SPANNED = new SpannedString("");
- private static int DRAG_SHADOW_MAX_TEXT_LENGTH = 20;
- // System wide time for last cut or copy action.
- private static long sLastCutOrCopyTime;
- // Used to highlight a word when it is corrected by the IME
- private CorrectionHighlighter mCorrectionHighlighter;
- // New state used to change background based on whether this TextView is multiline.
- private static final int[] MULTILINE_STATE_SET = { R.attr.state_multiline };
+ if (mMarquee != null && mMarquee.shouldDrawGhost()) {
+ canvas.translate((int) mMarquee.getGhostOffset(), 0.0f);
+ layout.draw(canvas, highlight, highlightPaint, cursorOffsetVertical);
+ }
+ }
+ }
}