diff --git a/core/java/android/text/Layout.java b/core/java/android/text/Layout.java index 2c84ba0053523..ecefce917a7da 100644 --- a/core/java/android/text/Layout.java +++ b/core/java/android/text/Layout.java @@ -2067,9 +2067,11 @@ public abstract class Layout { final String ellipsisString = TextUtils.getEllipsisString(method); final int ellipsisStringLen = ellipsisString.length(); + // Use the ellipsis string only if there are that at least as many characters to replace. + final boolean useEllipsisString = ellipsisCount >= ellipsisStringLen; for (int i = 0; i < ellipsisCount; i++) { final char c; - if (i < ellipsisStringLen && ellipsisCount <= ellipsisStringLen) { + if (useEllipsisString && i < ellipsisStringLen) { c = ellipsisString.charAt(i); } else { c = TextUtils.ELLIPSIS_FILLER; diff --git a/core/java/android/text/StaticLayout.java b/core/java/android/text/StaticLayout.java index 3474c2b560908..41404648f82f6 100644 --- a/core/java/android/text/StaticLayout.java +++ b/core/java/android/text/StaticLayout.java @@ -559,7 +559,7 @@ public class StaticLayout extends Layout { Builder.recycle(b); } - /* package */ StaticLayout(CharSequence text) { + /* package */ StaticLayout(@Nullable CharSequence text) { super(text, null, 0, null, 0, 0); mColumns = COLUMNS_ELLIPSIZE; @@ -601,7 +601,7 @@ public class StaticLayout extends Layout { } /* package */ void generate(Builder b, boolean includepad, boolean trackpad) { - CharSequence source = b.mText; + final CharSequence source = b.mText; int bufStart = b.mStart; int bufEnd = b.mEnd; TextPaint paint = b.mPaint; @@ -720,7 +720,7 @@ public class StaticLayout extends Layout { // TODO: Support more justification mode, e.g. letter spacing, stretching. b.mJustificationMode != Layout.JUSTIFICATION_MODE_NONE); if (mLeftIndents != null || mRightIndents != null) { - // TODO(raph) performance: it would be better to do this once per layout rather + // TODO(performance): it would be better to do this once per layout rather // than once per paragraph, but that would require a change to the native // interface. int leftLen = mLeftIndents == null ? 0 : mLeftIndents.length; @@ -807,7 +807,7 @@ public class StaticLayout extends Layout { width += widths[j]; } } - flag |= flags[i] & TAB_MASK; + flag |= flags[i] & TAB_MASK; // XXX May need to also have starting hyphen edit } // Treat the last line and overflowed lines as a single line. breaks[remainingLineCount - 1] = breaks[breakCount - 1]; @@ -909,17 +909,16 @@ public class StaticLayout extends Layout { } } - private int out(CharSequence text, int start, int end, - int above, int below, int top, int bottom, int v, - float spacingmult, float spacingadd, - LineHeightSpan[] chooseHt, int[] chooseHtv, - Paint.FontMetricsInt fm, int flags, - boolean needMultiply, byte[] chdirs, int dir, - boolean easy, int bufEnd, boolean includePad, - boolean trackPad, boolean addLastLineLineSpacing, char[] chs, - float[] widths, int widthStart, TextUtils.TruncateAt ellipsize, - float ellipsisWidth, float textWidth, - TextPaint paint, boolean moreChars) { + // The parameters that are not changed in the method are marked as final to make the code + // easier to understand. + private int out(final CharSequence text, final int start, final int end, int above, int below, + int top, int bottom, int v, final float spacingmult, final float spacingadd, + final LineHeightSpan[] chooseHt, final int[] chooseHtv, final Paint.FontMetricsInt fm, + final int flags, final boolean needMultiply, final byte[] chdirs, final int dir, + final boolean easy, final int bufEnd, final boolean includePad, final boolean trackPad, + final boolean addLastLineLineSpacing, final char[] chs, final float[] widths, + final int widthStart, final TextUtils.TruncateAt ellipsize, final float ellipsisWidth, + final float textWidth, final TextPaint paint, final boolean moreChars) { final int j = mLineCount; final int off = j * mColumns; final int want = off + mColumns + TOP; @@ -939,30 +938,30 @@ public class StaticLayout extends Layout { mLineDirections = grow; } - if (chooseHt != null) { - fm.ascent = above; - fm.descent = below; - fm.top = top; - fm.bottom = bottom; + lines[off + START] = start; + lines[off + TOP] = v; - for (int i = 0; i < chooseHt.length; i++) { - if (chooseHt[i] instanceof LineHeightSpan.WithDensity) { - ((LineHeightSpan.WithDensity) chooseHt[i]). - chooseHeight(text, start, end, chooseHtv[i], v, fm, paint); + // Information about hyphenation, tabs, and directions are needed for determining + // ellipsization, so the values should be assigned before ellipsization. - } else { - chooseHt[i].chooseHeight(text, start, end, chooseHtv[i], v, fm); - } - } + // TODO: could move TAB to share same column as HYPHEN, simplifying this code and gaining + // one bit for start field + lines[off + TAB] |= flags & TAB_MASK; + lines[off + HYPHEN] = flags; - above = fm.ascent; - below = fm.descent; - top = fm.top; - bottom = fm.bottom; + lines[off + DIR] |= dir << DIR_SHIFT; + // easy means all chars < the first RTL, so no emoji, no nothing + // XXX a run with no text or all spaces is easy but might be an empty + // RTL paragraph. Make sure easy is false if this is the case. + if (easy) { + mLineDirections[j] = DIRS_ALL_LEFT_TO_RIGHT; + } else { + mLineDirections[j] = AndroidBidi.directions(dir, chdirs, start - widthStart, chs, + start - widthStart, end - start); } - boolean firstLine = (j == 0); - boolean currentLineIsTheLastVisibleOne = (j + 1 == mMaximumVisibleLineCount); + final boolean firstLine = (j == 0); + final boolean currentLineIsTheLastVisibleOne = (j + 1 == mMaximumVisibleLineCount); if (ellipsize != null) { // If there is only one line, then do any type of ellipsis except when it is MARQUEE @@ -975,9 +974,9 @@ public class StaticLayout extends Layout { (!firstLine && (currentLineIsTheLastVisibleOne || !moreChars) && ellipsize == TextUtils.TruncateAt.END); if (doEllipsis) { - calculateEllipsis(start, end, widths, widthStart, - ellipsisWidth, ellipsize, j, - textWidth, paint, forceEllipsis); + calculateEllipsis(text, start, end, widths, widthStart, + ellipsisWidth - getTotalInsets(j), ellipsize, j, + textWidth, paint, forceEllipsis, dir); } } @@ -996,6 +995,28 @@ public class StaticLayout extends Layout { } } + if (chooseHt != null) { + fm.ascent = above; + fm.descent = below; + fm.top = top; + fm.bottom = bottom; + + for (int i = 0; i < chooseHt.length; i++) { + if (chooseHt[i] instanceof LineHeightSpan.WithDensity) { + ((LineHeightSpan.WithDensity) chooseHt[i]) + .chooseHeight(text, start, end, chooseHtv[i], v, fm, paint); + + } else { + chooseHt[i].chooseHeight(text, start, end, chooseHtv[i], v, fm); + } + } + + above = fm.ascent; + below = fm.descent; + top = fm.top; + bottom = fm.bottom; + } + if (firstLine) { if (trackPad) { mTopPadding = top - above; @@ -1006,8 +1027,6 @@ public class StaticLayout extends Layout { } } - int extra; - if (lastLine) { if (trackPad) { mBottomPadding = bottom - below; @@ -1018,8 +1037,9 @@ public class StaticLayout extends Layout { } } + final int extra; if (needMultiply && (addLastLineLineSpacing || !lastLine)) { - double ex = (below - above) * (spacingmult - 1) + spacingadd; + final double ex = (below - above) * (spacingmult - 1) + spacingadd; if (ex >= 0) { extra = (int)(ex + EXTRA_ROUNDING); } else { @@ -1029,8 +1049,6 @@ public class StaticLayout extends Layout { extra = 0; } - lines[off + START] = start; - lines[off + TOP] = v; lines[off + DESCENT] = below + extra; lines[off + EXTRA] = extra; @@ -1038,7 +1056,7 @@ public class StaticLayout extends Layout { // store the height as if it was ellipsized if (!mEllipsized && currentLineIsTheLastVisibleOne) { // below calculation as if it was the last line - int maxLineBelow = includePad ? bottom : below; + final int maxLineBelow = includePad ? bottom : below; // similar to the calculation of v below, without the extra. mMaxLineHeight = v + (maxLineBelow - above); } @@ -1047,33 +1065,13 @@ public class StaticLayout extends Layout { lines[off + mColumns + START] = end; lines[off + mColumns + TOP] = v; - // TODO: could move TAB to share same column as HYPHEN, simplifying this code and gaining - // one bit for start field - lines[off + TAB] |= flags & TAB_MASK; - lines[off + HYPHEN] = flags; - - lines[off + DIR] |= dir << DIR_SHIFT; - Directions linedirs = DIRS_ALL_LEFT_TO_RIGHT; - // easy means all chars < the first RTL, so no emoji, no nothing - // XXX a run with no text or all spaces is easy but might be an empty - // RTL paragraph. Make sure easy is false if this is the case. - if (easy) { - mLineDirections[j] = linedirs; - } else { - mLineDirections[j] = AndroidBidi.directions(dir, chdirs, start - widthStart, chs, - start - widthStart, end - start); - } - mLineCount++; return v; } - private void calculateEllipsis(int lineStart, int lineEnd, - float[] widths, int widthStart, - float avail, TextUtils.TruncateAt where, - int line, float textWidth, TextPaint paint, - boolean forceEllipsis) { - avail -= getTotalInsets(line); + private void calculateEllipsis(CharSequence text, int lineStart, int lineEnd, float[] widths, + int widthStart, float avail, TextUtils.TruncateAt where, int line, float textWidth, + TextPaint paint, boolean forceEllipsis, int dir) { if (textWidth <= avail && !forceEllipsis) { // Everything fits! mLines[mColumns * line + ELLIPSIS_START] = 0; @@ -1081,11 +1079,47 @@ public class StaticLayout extends Layout { return; } - float ellipsisWidth = paint.measureText(TextUtils.getEllipsisString(where)); - int ellipsisStart = 0; - int ellipsisCount = 0; - int len = lineEnd - lineStart; + float tempAvail = avail; + int numberOfTries = 0; + boolean lineFits = false; + do { + final float ellipsizedWidth = guessEllipsis(text, lineStart, lineEnd, widths, + widthStart, tempAvail, where, line, textWidth, paint, forceEllipsis, dir); + if (ellipsizedWidth <= avail) { + lineFits = true; + } else { + numberOfTries++; + if (numberOfTries > 10) { + // If the text still doesn't fit after ten tries, assume it will never fit and + // ellipsize it all. + mLines[mColumns * line + ELLIPSIS_START] = 0; + mLines[mColumns * line + ELLIPSIS_COUNT] = lineEnd - lineStart; + lineFits = true; + } else { + // Some side effect of ellipsization has caused the text to go over the + // available width. Let's make the available width shorter by exactly that + // amount and retry. + tempAvail -= ellipsizedWidth - avail; + } + } + } while (!lineFits); + mEllipsized = true; + } + // Returns the width of the ellipsized line which in some rare cases can actually be larger + // than 'avail' (due to kerning or other context-based effect of replacement of text by + // ellipsis). If all the line needs to ellipsized away, or it's an invalud hyphenation mode, + // returns 0 so the caller can stop iterating. + private float guessEllipsis(CharSequence text, int lineStart, int lineEnd, float[] widths, + int widthStart, float avail, TextUtils.TruncateAt where, int line, float textWidth, + TextPaint paint, boolean forceEllipsis, int dir) { + final float ellipsisWidth = paint.measureText(TextUtils.getEllipsisString(where)); + final int ellipsisStart; + final int ellipsisCount; + final int len = lineEnd - lineStart; + final int offset = lineStart - widthStart; + + int hyphen = getHyphen(line); // We only support start ellipsis on a single line if (where == TextUtils.TruncateAt.START) { if (mMaximumVisibleLineCount == 1) { @@ -1093,9 +1127,9 @@ public class StaticLayout extends Layout { int i; for (i = len; i > 0; i--) { - float w = widths[i - 1 + lineStart - widthStart]; + final float w = widths[i - 1 + offset]; if (w + sum + ellipsisWidth > avail) { - while (i < len && widths[i + lineStart - widthStart] == 0.0f) { + while (i < len && widths[i + offset] == 0.0f) { i++; } break; @@ -1106,9 +1140,13 @@ public class StaticLayout extends Layout { ellipsisStart = 0; ellipsisCount = i; + // Strip the potential hyphenation at beginning of line. + hyphen &= ~Paint.HYPHENEDIT_MASK_START_OF_LINE; } else { + ellipsisStart = 0; + ellipsisCount = 0; if (Log.isLoggable(TAG, Log.WARN)) { - Log.w(TAG, "Start Ellipsis only supported with one line"); + Log.w(TAG, "Start ellipsis only supported with one line"); } } } else if (where == TextUtils.TruncateAt.END || where == TextUtils.TruncateAt.MARQUEE || @@ -1117,7 +1155,7 @@ public class StaticLayout extends Layout { int i; for (i = 0; i < len; i++) { - float w = widths[i + lineStart - widthStart]; + final float w = widths[i + offset]; if (w + sum + ellipsisWidth > avail) { break; @@ -1126,24 +1164,27 @@ public class StaticLayout extends Layout { sum += w; } - ellipsisStart = i; - ellipsisCount = len - i; - if (forceEllipsis && ellipsisCount == 0 && len > 0) { + if (forceEllipsis && i == len && len > 0) { ellipsisStart = len - 1; ellipsisCount = 1; + } else { + ellipsisStart = i; + ellipsisCount = len - i; } - } else { - // where = TextUtils.TruncateAt.MIDDLE We only support middle ellipsis on a single line + // Strip the potential hyphenation at end of line. + hyphen &= ~Paint.HYPHENEDIT_MASK_END_OF_LINE; + } else { // where = TextUtils.TruncateAt.MIDDLE + // We only support middle ellipsis on a single line. if (mMaximumVisibleLineCount == 1) { float lsum = 0, rsum = 0; int left = 0, right = len; - float ravail = (avail - ellipsisWidth) / 2; + final float ravail = (avail - ellipsisWidth) / 2; for (right = len; right > 0; right--) { - float w = widths[right - 1 + lineStart - widthStart]; + final float w = widths[right - 1 + offset]; if (w + rsum > ravail) { - while (right < len && widths[right + lineStart - widthStart] == 0.0f) { + while (right < len && widths[right + offset] == 0.0f) { right++; } break; @@ -1151,9 +1192,9 @@ public class StaticLayout extends Layout { rsum += w; } - float lavail = avail - ellipsisWidth - rsum; + final float lavail = avail - ellipsisWidth - rsum; for (left = 0; left < right; left++) { - float w = widths[left + lineStart - widthStart]; + final float w = widths[left + offset]; if (w + lsum > lavail) { break; @@ -1165,14 +1206,53 @@ public class StaticLayout extends Layout { ellipsisStart = left; ellipsisCount = right - left; } else { + ellipsisStart = 0; + ellipsisCount = 0; if (Log.isLoggable(TAG, Log.WARN)) { - Log.w(TAG, "Middle Ellipsis only supported with one line"); + Log.w(TAG, "Middle ellipsis only supported with one line"); } } } - mEllipsized = true; mLines[mColumns * line + ELLIPSIS_START] = ellipsisStart; mLines[mColumns * line + ELLIPSIS_COUNT] = ellipsisCount; + + if (ellipsisStart == 0 && (ellipsisCount == 0 || ellipsisCount == len)) { + // Unsupported ellipsization mode or all text is ellipsized away. Return 0. + return 0.0f; + } + + final boolean isSpanned = text instanceof Spanned; + final Ellipsizer ellipsizedText = isSpanned + ? new SpannedEllipsizer(text) + : new Ellipsizer(text); + ellipsizedText.mLayout = this; + ellipsizedText.mMethod = where; + + final boolean hasTabs = getLineContainsTab(line); + final TabStops tabStops; + if (hasTabs && isSpanned) { + final TabStopSpan[] tabs = getParagraphSpans((Spanned) ellipsizedText, lineStart, + lineEnd, TabStopSpan.class); + if (tabs.length == 0) { + tabStops = null; + } else { + tabStops = new TabStops(TAB_INCREMENT, tabs); + } + } else { + tabStops = null; + } + final TextLine textline = TextLine.obtain(); + paint.setHyphenEdit(hyphen); + textline.set(paint, ellipsizedText, lineStart, lineEnd, dir, getLineDirections(line), + hasTabs, tabStops); + // Since TextLine.metric() returns negative values for RTL text, multiplication by dir + // converts it to an actual width. Note that we don't want to use the absolute value, + // since we may actually have glyphs with negative advances, which by definition always + // fit. + final float ellipsizedWidth = textline.metrics(null) * dir; + paint.setHyphenEdit(0); + TextLine.recycle(textline); + return ellipsizedWidth; } private float getTotalInsets(int line) { diff --git a/core/java/android/text/TextUtils.java b/core/java/android/text/TextUtils.java index 979c43ccfcec5..463a98686208f 100644 --- a/core/java/android/text/TextUtils.java +++ b/core/java/android/text/TextUtils.java @@ -89,8 +89,8 @@ public class TextUtils { /** {@hide} */ @NonNull - public static String getEllipsisString(@NonNull TextUtils.TruncateAt method) { - return (method == TextUtils.TruncateAt.END_SMALL) ? ELLIPSIS_TWO_DOTS : ELLIPSIS_NORMAL; + public static String getEllipsisString(@NonNull TruncateAt method) { + return (method == TruncateAt.END_SMALL) ? ELLIPSIS_TWO_DOTS : ELLIPSIS_NORMAL; } @@ -1187,9 +1187,11 @@ public class TextUtils { * or, if it does not fit, a truncated * copy with ellipsis character added at the specified edge or center. */ - public static CharSequence ellipsize(CharSequence text, - TextPaint p, - float avail, TruncateAt where) { + @NonNull + public static CharSequence ellipsize(@NonNull CharSequence text, + @NonNull TextPaint p, + @FloatRange(from = 0.0) float avail, + @NonNull TruncateAt where) { return ellipsize(text, p, avail, where, false, null); } @@ -1205,9 +1207,11 @@ public class TextUtils { * report the start and end of the ellipsized range. TextDirection * is determined by the first strong directional character. */ - public static CharSequence ellipsize(CharSequence text, - TextPaint paint, - float avail, TruncateAt where, + @NonNull + public static CharSequence ellipsize(@NonNull CharSequence text, + @NonNull TextPaint paint, + @FloatRange(from = 0.0) float avail, + @NonNull TruncateAt where, boolean preserveLength, @Nullable EllipsizeCallback callback) { return ellipsize(text, paint, avail, where, preserveLength, callback, @@ -1228,16 +1232,19 @@ public class TextUtils { * * @hide */ - public static CharSequence ellipsize(CharSequence text, - TextPaint paint, - float avail, TruncateAt where, + @NonNull + public static CharSequence ellipsize(@NonNull CharSequence text, + @NonNull TextPaint paint, + @FloatRange(from = 0.0) float avail, + @NonNull TruncateAt where, boolean preserveLength, @Nullable EllipsizeCallback callback, - TextDirectionHeuristic textDir, String ellipsis) { + @NonNull TextDirectionHeuristic textDir, + @NonNull String ellipsis) { - int len = text.length(); - - MeasuredText mt = MeasuredText.obtain(); + final int len = text.length(); + final MeasuredText mt = MeasuredText.obtain(); + MeasuredText resultMt = null; try { float width = setPara(mt, paint, text, 0, text.length(), textDir); @@ -1245,74 +1252,107 @@ public class TextUtils { if (callback != null) { callback.ellipsized(0, 0); } - return text; } - // XXX assumes ellipsis string does not require shaping and - // is unaffected by style - float ellipsiswid = paint.measureText(ellipsis); - avail -= ellipsiswid; + resultMt = MeasuredText.obtain(); + // First estimate of effective width of ellipsis. + float ellipsisWidth = paint.measureText(ellipsis); + int numberOfTries = 0; + boolean textFits = false; + int start, end; + CharSequence result; + do { + if (avail < ellipsisWidth) { + // Even the ellipsis can't fit. So it all goes. + start = 0; + end = len; + } else { + final float remainingWidth = avail - ellipsisWidth; + if (where == TruncateAt.START) { + start = 0; + end = len - mt.breakText(len, false /* backwards */, remainingWidth); + } else if (where == TruncateAt.END || where == TruncateAt.END_SMALL) { + start = mt.breakText(len, true /* forwards */, remainingWidth); + end = len; + } else { + end = len - mt.breakText(len, false /* backwards */, remainingWidth / 2); + start = mt.breakText(end, true /* forwards */, + remainingWidth - mt.measure(end, len)); + } + } - int left = 0; - int right = len; - if (avail < 0) { - // it all goes - } else if (where == TruncateAt.START) { - right = len - mt.breakText(len, false, avail); - } else if (where == TruncateAt.END || where == TruncateAt.END_SMALL) { - left = mt.breakText(len, true, avail); - } else { - right = len - mt.breakText(len, false, avail / 2); - avail -= mt.measure(right, len); - left = mt.breakText(right, true, avail); - } + final char[] buf = mt.mChars; + final Spanned sp = text instanceof Spanned ? (Spanned) text : null; + final int removed = end - start; + final int remaining = len - removed; + if (preserveLength) { + int pos = start; + if (remaining > 0 && removed >= ellipsis.length()) { + ellipsis.getChars(0, ellipsis.length(), buf, start); + pos += ellipsis.length(); + } // else eliminate the ellipsis + while (pos < end) { + buf[pos++] = ELLIPSIS_FILLER; + } + final String s = new String(buf, 0, len); + if (sp == null) { + result = s; + } else { + final SpannableString ss = new SpannableString(s); + copySpansFrom(sp, 0, len, Object.class, ss, 0); + result = ss; + } + } else { + if (remaining == 0) { + result = ""; + } else if (sp == null) { + final StringBuilder sb = new StringBuilder(remaining + ellipsis.length()); + sb.append(buf, 0, start); + sb.append(ellipsis); + sb.append(buf, end, len - end); + result = sb.toString(); + } else { + final SpannableStringBuilder ssb = new SpannableStringBuilder(); + ssb.append(text, 0, start); + ssb.append(ellipsis); + ssb.append(text, end, len); + result = ssb; + } + } + + if (remaining == 0) { // All text is gone. + textFits = true; + } else { + width = setPara(resultMt, paint, result, 0, result.length(), textDir); + if (width <= avail) { + textFits = true; + } else { + numberOfTries++; + if (numberOfTries > 10) { + // If the text still doesn't fit after ten tries, assume it will never + // fit and ellipsize it all. We do this by setting the width of the + // ellipsis to be positive infinity, so we get to empty text in the next + // round. + ellipsisWidth = Float.POSITIVE_INFINITY; + } else { + // Adjust the width of the ellipsis by adding the amount 'width' is + // still over. + ellipsisWidth += width - avail; + } + } + } + } while (!textFits); if (callback != null) { - callback.ellipsized(left, right); + callback.ellipsized(start, end); } - - char[] buf = mt.mChars; - Spanned sp = text instanceof Spanned ? (Spanned) text : null; - - final int removed = right - left; - final int remaining = len - removed; - if (preserveLength) { - if (remaining > 0 && removed >= ellipsis.length()) { - ellipsis.getChars(0, ellipsis.length(), buf, left); - left += ellipsis.length(); - } // else skip the ellipsis - for (int i = left; i < right; i++) { - buf[i] = ELLIPSIS_FILLER; - } - String s = new String(buf, 0, len); - if (sp == null) { - return s; - } - SpannableString ss = new SpannableString(s); - copySpansFrom(sp, 0, len, Object.class, ss, 0); - return ss; - } - - if (remaining == 0) { - return ""; - } - - if (sp == null) { - StringBuilder sb = new StringBuilder(remaining + ellipsis.length()); - sb.append(buf, 0, left); - sb.append(ellipsis); - sb.append(buf, right, len - right); - return sb.toString(); - } - - SpannableStringBuilder ssb = new SpannableStringBuilder(); - ssb.append(text, 0, left); - ssb.append(ellipsis); - ssb.append(text, right, len); - return ssb; + return result; } finally { MeasuredText.recycle(mt); + if (resultMt != null) { + MeasuredText.recycle(resultMt); + } } } @@ -1343,7 +1383,6 @@ public class TextUtils { * @return the formatted CharSequence. If even the shortest sequence (e.g. {@code "A, 11 more"}) * doesn't fit, it will return an empty string. */ - public static CharSequence listEllipsize(@Nullable Context context, @Nullable List elements, @NonNull String separator, @NonNull TextPaint paint, @FloatRange(from=0.0,fromInclusive=false) float avail, diff --git a/graphics/java/android/graphics/Paint.java b/graphics/java/android/graphics/Paint.java index aa9227c9bb089..16eab45cddcf2 100644 --- a/graphics/java/android/graphics/Paint.java +++ b/graphics/java/android/graphics/Paint.java @@ -2747,7 +2747,7 @@ public class Paint { * @param offset index of caret position * @return width measurement between start and offset */ - public float getRunAdvance(CharSequence text, int start, int end, int contextStart, + public float getRunAdvance(@NonNull CharSequence text, int start, int end, int contextStart, int contextEnd, boolean isRtl, int offset) { if (text == null) { throw new IllegalArgumentException("text cannot be null");