Merge "Stop using DynamicLayout in case of non selectable PrecomputedText" into pi-dev

am: a1ff74c68d

Change-Id: Ic003d918ce83224e3daa513a0a8c8ee161f62c42
This commit is contained in:
Seigo Nonaka
2018-03-27 01:11:24 +00:00
committed by android-build-merger
2 changed files with 139 additions and 42 deletions

View File

@@ -644,8 +644,12 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
*/
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 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
setImportantForAutofill(IMPORTANT_FOR_AUTOFILL_YES);
}
mText = "";
setTextInternal("");
final Resources res = getResources();
final CompatibilityInfo compat = res.getCompatibilityInfo();
@@ -1615,6 +1619,13 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
}
}
// 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 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
}
}
}
} 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 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
if (mMovement != movement) {
mMovement = movement;
if (movement != null && !(mText instanceof Spannable)) {
if (movement != null && mSpannable == null) {
setText(mText);
}
@@ -2409,8 +2420,8 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
return;
}
if (mTransformation != null) {
if (mText instanceof Spannable) {
((Spannable) mText).removeSpan(mTransformation);
if (mSpannable != null) {
mSpannable.removeSpan(mTransformation);
}
}
@@ -5254,7 +5265,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
((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 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
}
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 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
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 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
* 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 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
}
mBufferType = type;
mText = text;
setTextInternal(text);
if (mTransformation == null) {
mTransformed = text;
@@ -5825,8 +5836,8 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
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 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
}
if (!isSuggestionsEnabled()) {
mText = removeSuggestionSpans(mText);
setTextInternal(removeSuggestionSpans(mText));
}
InputMethodManager imm = InputMethodManager.peekInstance();
@@ -6948,8 +6959,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
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 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
@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 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
} 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 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
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 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
}
}
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 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
}
if (mMovement != null && mLayout != null) {
if (mMovement.onKeyUp(this, (Spannable) mText, keyCode, event)) {
if (mMovement.onKeyUp(this, mSpannable, keyCode, event)) {
return true;
}
}
@@ -8313,6 +8322,16 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
if (mEditor != null) mEditor.prepareCursorControllers();
}
/**
* Returns true if DynamicLayout is required
*
* @hide
*/
@VisibleForTesting
public boolean useDynamicLayout() {
return isTextSelectable() || (mSpannable != null && mPrecomputed == null);
}
/**
* @hide
*/
@@ -8320,7 +8339,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
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 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
}
if (newStart != start) {
Selection.setSelection((Spannable) mText, newStart);
Selection.setSelection(mSpannable, newStart);
return true;
}
@@ -9999,9 +10018,8 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
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 class TextView extends View implements ViewTreeObserver.OnPreDrawListener
*/
public void clearComposingText() {
if (mText instanceof Spannable) {
BaseInputConnection.removeComposingSpans((Spannable) mText);
BaseInputConnection.removeComposingSpans(mSpannable);
}
}
@@ -10095,7 +10113,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
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 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
// 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 class TextView extends View implements ViewTreeObserver.OnPreDrawListener
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 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
@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 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
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 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
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 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
}
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 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
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 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
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;

View File

@@ -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.rule.ActivityTestRule;
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 @@ public class TextViewTest {
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);