Merge "Stop using DynamicLayout in case of non selectable PrecomputedText" into pi-dev
diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java
index 7cf3e10..7b1acb1 100644
--- a/core/java/android/widget/TextView.java
+++ b/core/java/android/widget/TextView.java
@@ -644,8 +644,12 @@
*/
private Layout mSavedMarqueeModeLayout;
+ // Do not update following mText/mSpannable/mPrecomputed except for setTextInternal()
@ViewDebug.ExportedProperty(category = "text")
- private CharSequence mText;
+ private @Nullable CharSequence mText;
+ private @Nullable Spannable mSpannable;
+ private @Nullable PrecomputedText mPrecomputed;
+
private CharSequence mTransformed;
private BufferType mBufferType = BufferType.NORMAL;
@@ -874,7 +878,7 @@
setImportantForAutofill(IMPORTANT_FOR_AUTOFILL_YES);
}
- mText = "";
+ setTextInternal("");
final Resources res = getResources();
final CompatibilityInfo compat = res.getCompatibilityInfo();
@@ -1615,6 +1619,13 @@
}
}
+ // Update mText and mPrecomputed
+ private void setTextInternal(@Nullable CharSequence text) {
+ mText = text;
+ mSpannable = (text instanceof Spannable) ? (Spannable) text : null;
+ mPrecomputed = (text instanceof PrecomputedText) ? (PrecomputedText) text : null;
+ }
+
/**
* Specify whether this widget should automatically scale the text to try to perfectly fit
* within the layout bounds by using the default auto-size configuration.
@@ -1973,9 +1984,9 @@
}
}
}
- } else if (mText instanceof Spannable) {
+ } else if (mSpannable != null) {
// Reset the selection.
- Selection.setSelection((Spannable) mText, getSelectionEnd());
+ Selection.setSelection(mSpannable, getSelectionEnd());
}
}
}
@@ -2359,7 +2370,7 @@
if (mMovement != movement) {
mMovement = movement;
- if (movement != null && !(mText instanceof Spannable)) {
+ if (movement != null && mSpannable == null) {
setText(mText);
}
@@ -2409,8 +2420,8 @@
return;
}
if (mTransformation != null) {
- if (mText instanceof Spannable) {
- ((Spannable) mText).removeSpan(mTransformation);
+ if (mSpannable != null) {
+ mSpannable.removeSpan(mTransformation);
}
}
@@ -5254,7 +5265,7 @@
((Editable) mText).append(text, start, end);
if (mAutoLinkMask != 0) {
- boolean linksWereAdded = Linkify.addLinks((Spannable) mText, mAutoLinkMask);
+ boolean linksWereAdded = Linkify.addLinks(mSpannable, mAutoLinkMask);
// Do not change the movement method for text that support text selection as it
// would prevent an arbitrary cursor displacement.
if (linksWereAdded && mLinksClickable && !textCanBeSelected()) {
@@ -5413,7 +5424,7 @@
}
if (ss.selStart >= 0 && ss.selEnd >= 0) {
- if (mText instanceof Spannable) {
+ if (mSpannable != null) {
int len = mText.length();
if (ss.selStart > len || ss.selEnd > len) {
@@ -5426,7 +5437,7 @@
Log.e(LOG_TAG, "Saved cursor position " + ss.selStart + "/" + ss.selEnd
+ " out of range for " + restored + "text " + mText);
} else {
- Selection.setSelection((Spannable) mText, ss.selStart, ss.selEnd);
+ Selection.setSelection(mSpannable, ss.selStart, ss.selEnd);
if (ss.frozenWithFocus) {
createEditorIfNeeded();
@@ -5688,7 +5699,7 @@
* movement method, because setMovementMethod() may call
* setText() again to try to upgrade the buffer type.
*/
- mText = text;
+ setTextInternal(text);
// Do not change the movement method for text that support text selection as it
// would prevent an arbitrary cursor displacement.
@@ -5699,7 +5710,7 @@
}
mBufferType = type;
- mText = text;
+ setTextInternal(text);
if (mTransformation == null) {
mTransformed = text;
@@ -5825,8 +5836,8 @@
setText(text, type);
if (start >= 0 || end >= 0) {
- if (mText instanceof Spannable) {
- Selection.setSelection((Spannable) mText,
+ if (mSpannable != null) {
+ Selection.setSelection(mSpannable,
Math.max(0, Math.min(start, len)),
Math.max(0, Math.min(end, len)));
}
@@ -6020,7 +6031,7 @@
}
if (!isSuggestionsEnabled()) {
- mText = removeSuggestionSpans(mText);
+ setTextInternal(removeSuggestionSpans(mText));
}
InputMethodManager imm = InputMethodManager.peekInstance();
@@ -6948,8 +6959,7 @@
public boolean hasOverlappingRendering() {
// horizontal fading edge causes SaveLayerAlpha, which doesn't support alpha modulation
return ((getBackground() != null && getBackground().getCurrent() != null)
- || mText instanceof Spannable || hasSelection()
- || isHorizontalFadingEdgeEnabled());
+ || mSpannable != null || hasSelection() || isHorizontalFadingEdgeEnabled());
}
/**
@@ -7399,11 +7409,11 @@
@Override
public PointerIcon onResolvePointerIcon(MotionEvent event, int pointerIndex) {
- if (mText instanceof Spannable && mLinksClickable) {
+ if (mSpannable != null && mLinksClickable) {
final float x = event.getX(pointerIndex);
final float y = event.getY(pointerIndex);
final int offset = getOffsetForPosition(x, y);
- final ClickableSpan[] clickables = ((Spannable) mText).getSpans(offset, offset,
+ final ClickableSpan[] clickables = mSpannable.getSpans(offset, offset,
ClickableSpan.class);
if (clickables.length > 0) {
return PointerIcon.getSystemIcon(mContext, PointerIcon.TYPE_HAND);
@@ -7496,10 +7506,10 @@
} else if (which == KEY_DOWN_HANDLED_BY_MOVEMENT_METHOD) {
// mMovement is not null from doKeyDown
- mMovement.onKeyUp(this, (Spannable) mText, keyCode, up);
+ mMovement.onKeyUp(this, mSpannable, keyCode, up);
while (--repeatCount > 0) {
- mMovement.onKeyDown(this, (Spannable) mText, keyCode, down);
- mMovement.onKeyUp(this, (Spannable) mText, keyCode, up);
+ mMovement.onKeyDown(this, mSpannable, keyCode, down);
+ mMovement.onKeyUp(this, mSpannable, keyCode, up);
}
}
@@ -7694,8 +7704,7 @@
boolean doDown = true;
if (otherEvent != null) {
try {
- boolean handled = mMovement.onKeyOther(this, (Spannable) mText,
- otherEvent);
+ boolean handled = mMovement.onKeyOther(this, mSpannable, otherEvent);
doDown = false;
if (handled) {
return KEY_EVENT_HANDLED;
@@ -7706,7 +7715,7 @@
}
}
if (doDown) {
- if (mMovement.onKeyDown(this, (Spannable) mText, keyCode, event)) {
+ if (mMovement.onKeyDown(this, mSpannable, keyCode, event)) {
if (event.getRepeatCount() == 0 && !KeyEvent.isModifierKey(keyCode)) {
mPreventDefaultMovement = true;
}
@@ -7848,7 +7857,7 @@
}
if (mMovement != null && mLayout != null) {
- if (mMovement.onKeyUp(this, (Spannable) mText, keyCode, event)) {
+ if (mMovement.onKeyUp(this, mSpannable, keyCode, event)) {
return true;
}
}
@@ -8314,13 +8323,23 @@
}
/**
+ * Returns true if DynamicLayout is required
+ *
+ * @hide
+ */
+ @VisibleForTesting
+ public boolean useDynamicLayout() {
+ return isTextSelectable() || (mSpannable != null && mPrecomputed == null);
+ }
+
+ /**
* @hide
*/
protected Layout makeSingleLayout(int wantWidth, BoringLayout.Metrics boring, int ellipsisWidth,
Layout.Alignment alignment, boolean shouldEllipsize, TruncateAt effectiveEllipsize,
boolean useSaved) {
Layout result = null;
- if (mText instanceof Spannable) {
+ if (useDynamicLayout()) {
final DynamicLayout.Builder builder = DynamicLayout.Builder.obtain(mText, mTextPaint,
wantWidth)
.setDisplayText(mTransformed)
@@ -9262,7 +9281,7 @@
}
if (newStart != start) {
- Selection.setSelection((Spannable) mText, newStart);
+ Selection.setSelection(mSpannable, newStart);
return true;
}
@@ -9999,9 +10018,8 @@
if (mEditor != null) mEditor.onFocusChanged(focused, direction);
if (focused) {
- if (mText instanceof Spannable) {
- Spannable sp = (Spannable) mText;
- MetaKeyKeyListener.resetMetaState(sp);
+ if (mSpannable != null) {
+ MetaKeyKeyListener.resetMetaState(mSpannable);
}
}
@@ -10039,7 +10057,7 @@
*/
public void clearComposingText() {
if (mText instanceof Spannable) {
- BaseInputConnection.removeComposingSpans((Spannable) mText);
+ BaseInputConnection.removeComposingSpans(mSpannable);
}
}
@@ -10095,7 +10113,7 @@
boolean handled = false;
if (mMovement != null) {
- handled |= mMovement.onTouchEvent(this, (Spannable) mText, event);
+ handled |= mMovement.onTouchEvent(this, mSpannable, event);
}
final boolean textIsSelectable = isTextSelectable();
@@ -10103,7 +10121,7 @@
// 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.
- ClickableSpan[] links = ((Spannable) mText).getSpans(getSelectionStart(),
+ ClickableSpan[] links = mSpannable.getSpans(getSelectionStart(),
getSelectionEnd(), ClickableSpan.class);
if (links.length > 0) {
@@ -10138,7 +10156,7 @@
public boolean onGenericMotionEvent(MotionEvent event) {
if (mMovement != null && mText instanceof Spannable && mLayout != null) {
try {
- if (mMovement.onGenericMotionEvent(this, (Spannable) mText, event)) {
+ if (mMovement.onGenericMotionEvent(this, mSpannable, event)) {
return true;
}
} catch (AbstractMethodError ex) {
@@ -10199,8 +10217,8 @@
@Override
public boolean onTrackballEvent(MotionEvent event) {
- if (mMovement != null && mText instanceof Spannable && mLayout != null) {
- if (mMovement.onTrackballEvent(this, (Spannable) mText, event)) {
+ if (mMovement != null && mSpannable != null && mLayout != null) {
+ if (mMovement.onTrackballEvent(this, mSpannable, event)) {
return true;
}
}
@@ -11121,7 +11139,7 @@
if (mText != null) {
int updatedTextLength = mText.length();
if (updatedTextLength > 0) {
- Selection.setSelection((Spannable) mText, updatedTextLength);
+ Selection.setSelection(mSpannable, updatedTextLength);
}
}
} return true;
@@ -11697,7 +11715,7 @@
hideFloatingToolbar(FLOATING_TOOLBAR_SELECT_ALL_REFRESH_DELAY);
}
final int length = mText.length();
- Selection.setSelection((Spannable) mText, 0, length);
+ Selection.setSelection(mSpannable, 0, length);
return length > 0;
}
@@ -11725,7 +11743,7 @@
}
if (paste != null) {
if (!didFirst) {
- Selection.setSelection((Spannable) mText, max);
+ Selection.setSelection(mSpannable, max);
((Editable) mText).replace(min, max, paste);
didFirst = true;
} else {
@@ -11747,7 +11765,7 @@
selectedText = TextUtils.trimToParcelableSize(selectedText);
sharingIntent.putExtra(android.content.Intent.EXTRA_TEXT, selectedText);
getContext().startActivity(Intent.createChooser(sharingIntent, null));
- Selection.setSelection((Spannable) mText, getSelectionEnd());
+ Selection.setSelection(mSpannable, getSelectionEnd());
}
}
@@ -11822,7 +11840,7 @@
case DragEvent.ACTION_DRAG_LOCATION:
if (mText instanceof Spannable) {
final int offset = getOffsetForPosition(event.getX(), event.getY());
- Selection.setSelection((Spannable) mText, offset);
+ Selection.setSelection(mSpannable, offset);
}
return true;
diff --git a/core/tests/coretests/src/android/widget/TextViewTest.java b/core/tests/coretests/src/android/widget/TextViewTest.java
index 2b5b27b..4f1efbf 100644
--- a/core/tests/coretests/src/android/widget/TextViewTest.java
+++ b/core/tests/coretests/src/android/widget/TextViewTest.java
@@ -17,6 +17,7 @@
package android.widget;
import static junit.framework.Assert.assertEquals;
+import static junit.framework.Assert.assertFalse;
import static junit.framework.Assert.assertNotNull;
import static junit.framework.Assert.assertTrue;
@@ -32,9 +33,11 @@
import android.support.test.runner.AndroidJUnit4;
import android.text.GetChars;
import android.text.Layout;
+import android.text.PrecomputedText;
import android.text.Selection;
import android.text.Spannable;
import android.view.View;
+import android.widget.TextView.BufferType;
import org.junit.Before;
import org.junit.Rule;
@@ -241,6 +244,82 @@
mTextView.onTextContextMenuItem(TextView.ID_CUT);
}
+ @Test
+ public void testUseDynamicLayout() {
+ mTextView = new TextView(mActivity);
+ mTextView.setTextIsSelectable(true);
+ String text = "HelloWorld";
+ PrecomputedText precomputed =
+ PrecomputedText.create(text, mTextView.getTextMetricsParams());
+
+ mTextView.setTextIsSelectable(false);
+ mTextView.setText(text);
+ assertFalse(mTextView.useDynamicLayout());
+
+ mTextView.setTextIsSelectable(true);
+ mTextView.setText(text);
+ assertTrue(mTextView.useDynamicLayout());
+
+ mTextView.setTextIsSelectable(false);
+ mTextView.setText(precomputed);
+ assertFalse(mTextView.useDynamicLayout());
+
+ mTextView.setTextIsSelectable(true);
+ mTextView.setText(precomputed);
+ assertTrue(mTextView.useDynamicLayout());
+ }
+
+ @Test
+ public void testUseDynamicLayout_SPANNABLE() {
+ mTextView = new TextView(mActivity);
+ mTextView.setTextIsSelectable(true);
+ String text = "HelloWorld";
+ PrecomputedText precomputed =
+ PrecomputedText.create(text, mTextView.getTextMetricsParams());
+
+ mTextView.setTextIsSelectable(false);
+ mTextView.setText(text, BufferType.SPANNABLE);
+ android.util.Log.e("TextViewTest", "Text:" + mTextView.getText().getClass().getName());
+ assertTrue(mTextView.useDynamicLayout());
+
+ mTextView.setTextIsSelectable(true);
+ mTextView.setText(text, BufferType.SPANNABLE);
+ assertTrue(mTextView.useDynamicLayout());
+
+ mTextView.setTextIsSelectable(false);
+ mTextView.setText(precomputed, BufferType.SPANNABLE);
+ assertFalse(mTextView.useDynamicLayout());
+
+ mTextView.setTextIsSelectable(true);
+ mTextView.setText(precomputed, BufferType.SPANNABLE);
+ assertTrue(mTextView.useDynamicLayout());
+ }
+
+ @Test
+ public void testUseDynamicLayout_EDITABLE() {
+ mTextView = new TextView(mActivity);
+ mTextView.setTextIsSelectable(true);
+ String text = "HelloWorld";
+ PrecomputedText precomputed =
+ PrecomputedText.create(text, mTextView.getTextMetricsParams());
+
+ mTextView.setTextIsSelectable(false);
+ mTextView.setText(text, BufferType.EDITABLE);
+ assertTrue(mTextView.useDynamicLayout());
+
+ mTextView.setTextIsSelectable(true);
+ mTextView.setText(text, BufferType.EDITABLE);
+ assertTrue(mTextView.useDynamicLayout());
+
+ mTextView.setTextIsSelectable(false);
+ mTextView.setText(precomputed, BufferType.EDITABLE);
+ assertTrue(mTextView.useDynamicLayout());
+
+ mTextView.setTextIsSelectable(true);
+ mTextView.setText(precomputed, BufferType.EDITABLE);
+ assertTrue(mTextView.useDynamicLayout());
+ }
+
private String createLongText() {
int size = 600 * 1000;
final StringBuilder builder = new StringBuilder(size);