Rename PremeasuredText to MeasuredText

There is already MeasuredText, so renamed existing MeasuredText to
MeasuredParagraph, then renamed PremeasuredText to MeasuredText.

Bug: 67504091
Test: bit CtsWidgetTestCases:android.widget.cts.TextViewTest
Test: bit CtsTextTestCases:*
Change-Id: Ie20bea9501b18fabb36f64d388a7851c4643d4c3
diff --git a/core/java/android/text/Layout.java b/core/java/android/text/Layout.java
index bf4b6ac..aa97b2a 100644
--- a/core/java/android/text/Layout.java
+++ b/core/java/android/text/Layout.java
@@ -1917,10 +1917,10 @@
 
     private static float measurePara(TextPaint paint, CharSequence text, int start, int end,
             TextDirectionHeuristic textDir) {
-        MeasuredText mt = null;
+        MeasuredParagraph mt = null;
         TextLine tl = TextLine.obtain();
         try {
-            mt = MeasuredText.buildForBidi(text, start, end, textDir, mt);
+            mt = MeasuredParagraph.buildForBidi(text, start, end, textDir, mt);
             final char[] chars = mt.getChars();
             final int len = chars.length;
             final Directions directions = mt.getDirections(0, len);
diff --git a/core/java/android/text/MeasuredParagraph.java b/core/java/android/text/MeasuredParagraph.java
new file mode 100644
index 0000000..c93e036
--- /dev/null
+++ b/core/java/android/text/MeasuredParagraph.java
@@ -0,0 +1,677 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.text;
+
+import android.annotation.FloatRange;
+import android.annotation.IntRange;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.graphics.Paint;
+import android.text.AutoGrowArray.ByteArray;
+import android.text.AutoGrowArray.FloatArray;
+import android.text.AutoGrowArray.IntArray;
+import android.text.Layout.Directions;
+import android.text.style.MetricAffectingSpan;
+import android.text.style.ReplacementSpan;
+import android.util.Pools.SynchronizedPool;
+
+import dalvik.annotation.optimization.CriticalNative;
+
+import libcore.util.NativeAllocationRegistry;
+
+import java.util.Arrays;
+
+/**
+ * MeasuredParagraph provides text information for rendering purpose.
+ *
+ * The first motivation of this class is identify the text directions and retrieving individual
+ * character widths. However retrieving character widths is slower than identifying text directions.
+ * Thus, this class provides several builder methods for specific purposes.
+ *
+ * - buildForBidi:
+ *   Compute only text directions.
+ * - buildForMeasurement:
+ *   Compute text direction and all character widths.
+ * - buildForStaticLayout:
+ *   This is bit special. StaticLayout also needs to know text direction and character widths for
+ *   line breaking, but all things are done in native code. Similarly, text measurement is done
+ *   in native code. So instead of storing result to Java array, this keeps the result in native
+ *   code since there is no good reason to move the results to Java layer.
+ *
+ * In addition to the character widths, some additional information is computed for each purposes,
+ * e.g. whole text length for measurement or font metrics for static layout.
+ *
+ * MeasuredParagraph is NOT a thread safe object.
+ * @hide
+ */
+public class MeasuredParagraph {
+    private static final char OBJECT_REPLACEMENT_CHARACTER = '\uFFFC';
+
+    private static final NativeAllocationRegistry sRegistry = new NativeAllocationRegistry(
+            MeasuredParagraph.class.getClassLoader(), nGetReleaseFunc(), 1024);
+
+    private MeasuredParagraph() {}  // Use build static functions instead.
+
+    private static final SynchronizedPool<MeasuredParagraph> sPool = new SynchronizedPool<>(1);
+
+    private static @NonNull MeasuredParagraph obtain() { // Use build static functions instead.
+        final MeasuredParagraph mt = sPool.acquire();
+        return mt != null ? mt : new MeasuredParagraph();
+    }
+
+    /**
+     * Recycle the MeasuredParagraph.
+     *
+     * Do not call any methods after you call this method.
+     */
+    public void recycle() {
+        release();
+        sPool.release(this);
+    }
+
+    // The casted original text.
+    //
+    // This may be null if the passed text is not a Spanned.
+    private @Nullable Spanned mSpanned;
+
+    // The start offset of the target range in the original text (mSpanned);
+    private @IntRange(from = 0) int mTextStart;
+
+    // The length of the target range in the original text.
+    private @IntRange(from = 0) int mTextLength;
+
+    // The copied character buffer for measuring text.
+    //
+    // The length of this array is mTextLength.
+    private @Nullable char[] mCopiedBuffer;
+
+    // The whole paragraph direction.
+    private @Layout.Direction int mParaDir;
+
+    // True if the text is LTR direction and doesn't contain any bidi characters.
+    private boolean mLtrWithoutBidi;
+
+    // The bidi level for individual characters.
+    //
+    // This is empty if mLtrWithoutBidi is true.
+    private @NonNull ByteArray mLevels = new ByteArray();
+
+    // The whole width of the text.
+    // See getWholeWidth comments.
+    private @FloatRange(from = 0.0f) float mWholeWidth;
+
+    // Individual characters' widths.
+    // See getWidths comments.
+    private @Nullable FloatArray mWidths = new FloatArray();
+
+    // The span end positions.
+    // See getSpanEndCache comments.
+    private @Nullable IntArray mSpanEndCache = new IntArray(4);
+
+    // The font metrics.
+    // See getFontMetrics comments.
+    private @Nullable IntArray mFontMetrics = new IntArray(4 * 4);
+
+    // The native MeasuredParagraph.
+    // See getNativePtr comments.
+    // Do not modify these members directly. Use bindNativeObject/unbindNativeObject instead.
+    private /* Maybe Zero */ long mNativePtr = 0;
+    private @Nullable Runnable mNativeObjectCleaner;
+
+    // Associate the native object to this Java object.
+    private void bindNativeObject(/* Non Zero*/ long nativePtr) {
+        mNativePtr = nativePtr;
+        mNativeObjectCleaner = sRegistry.registerNativeAllocation(this, nativePtr);
+    }
+
+    // Decouple the native object from this Java object and release the native object.
+    private void unbindNativeObject() {
+        if (mNativePtr != 0) {
+            mNativeObjectCleaner.run();
+            mNativePtr = 0;
+        }
+    }
+
+    // Following two objects are for avoiding object allocation.
+    private @NonNull TextPaint mCachedPaint = new TextPaint();
+    private @Nullable Paint.FontMetricsInt mCachedFm;
+
+    /**
+     * Releases internal buffers.
+     */
+    public void release() {
+        reset();
+        mLevels.clearWithReleasingLargeArray();
+        mWidths.clearWithReleasingLargeArray();
+        mFontMetrics.clearWithReleasingLargeArray();
+        mSpanEndCache.clearWithReleasingLargeArray();
+    }
+
+    /**
+     * Resets the internal state for starting new text.
+     */
+    private void reset() {
+        mSpanned = null;
+        mCopiedBuffer = null;
+        mWholeWidth = 0;
+        mLevels.clear();
+        mWidths.clear();
+        mFontMetrics.clear();
+        mSpanEndCache.clear();
+        unbindNativeObject();
+    }
+
+    /**
+     * Returns the characters to be measured.
+     *
+     * This is always available.
+     */
+    public @NonNull char[] getChars() {
+        return mCopiedBuffer;
+    }
+
+    /**
+     * Returns the paragraph direction.
+     *
+     * This is always available.
+     */
+    public @Layout.Direction int getParagraphDir() {
+        return mParaDir;
+    }
+
+    /**
+     * Returns the directions.
+     *
+     * This is always available.
+     */
+    public Directions getDirections(@IntRange(from = 0) int start,  // inclusive
+                                    @IntRange(from = 0) int end) {  // exclusive
+        if (mLtrWithoutBidi) {
+            return Layout.DIRS_ALL_LEFT_TO_RIGHT;
+        }
+
+        final int length = end - start;
+        return AndroidBidi.directions(mParaDir, mLevels.getRawArray(), start, mCopiedBuffer, start,
+                length);
+    }
+
+    /**
+     * Returns the whole text width.
+     *
+     * This is available only if the MeasureText is computed with computeForMeasurement.
+     * Returns 0 in other cases.
+     */
+    public @FloatRange(from = 0.0f) float getWholeWidth() {
+        return mWholeWidth;
+    }
+
+    /**
+     * Returns the individual character's width.
+     *
+     * This is available only if the MeasureText is computed with computeForMeasurement.
+     * Returns empty array in other cases.
+     */
+    public @NonNull FloatArray getWidths() {
+        return mWidths;
+    }
+
+    /**
+     * Returns the MetricsAffectingSpan end indices.
+     *
+     * If the input text is not a spanned string, this has one value that is the length of the text.
+     *
+     * This is available only if the MeasureText is computed with computeForStaticLayout.
+     * Returns empty array in other cases.
+     */
+    public @NonNull IntArray getSpanEndCache() {
+        return mSpanEndCache;
+    }
+
+    /**
+     * Returns the int array which holds FontMetrics.
+     *
+     * This array holds the repeat of top, bottom, ascent, descent of font metrics value.
+     *
+     * This is available only if the MeasureText is computed with computeForStaticLayout.
+     * Returns empty array in other cases.
+     */
+    public @NonNull IntArray getFontMetrics() {
+        return mFontMetrics;
+    }
+
+    /**
+     * Returns the native ptr of the MeasuredParagraph.
+     *
+     * This is available only if the MeasureText is computed with computeForStaticLayout.
+     * Returns 0 in other cases.
+     */
+    public /* Maybe Zero */ long getNativePtr() {
+        return mNativePtr;
+    }
+
+    /**
+     * Generates new MeasuredParagraph for Bidi computation.
+     *
+     * If recycle is null, this returns new instance. If recycle is not null, this fills computed
+     * result to recycle and returns recycle.
+     *
+     * @param text the character sequence to be measured
+     * @param start the inclusive start offset of the target region in the text
+     * @param end the exclusive end offset of the target region in the text
+     * @param textDir the text direction
+     * @param recycle pass existing MeasuredParagraph if you want to recycle it.
+     *
+     * @return measured text
+     */
+    public static @NonNull MeasuredParagraph buildForBidi(@NonNull CharSequence text,
+                                                     @IntRange(from = 0) int start,
+                                                     @IntRange(from = 0) int end,
+                                                     @NonNull TextDirectionHeuristic textDir,
+                                                     @Nullable MeasuredParagraph recycle) {
+        final MeasuredParagraph mt = recycle == null ? obtain() : recycle;
+        mt.resetAndAnalyzeBidi(text, start, end, textDir);
+        return mt;
+    }
+
+    /**
+     * Generates new MeasuredParagraph for measuring texts.
+     *
+     * If recycle is null, this returns new instance. If recycle is not null, this fills computed
+     * result to recycle and returns recycle.
+     *
+     * @param paint the paint to be used for rendering the text.
+     * @param text the character sequence to be measured
+     * @param start the inclusive start offset of the target region in the text
+     * @param end the exclusive end offset of the target region in the text
+     * @param textDir the text direction
+     * @param recycle pass existing MeasuredParagraph if you want to recycle it.
+     *
+     * @return measured text
+     */
+    public static @NonNull MeasuredParagraph buildForMeasurement(@NonNull TextPaint paint,
+                                                            @NonNull CharSequence text,
+                                                            @IntRange(from = 0) int start,
+                                                            @IntRange(from = 0) int end,
+                                                            @NonNull TextDirectionHeuristic textDir,
+                                                            @Nullable MeasuredParagraph recycle) {
+        final MeasuredParagraph mt = recycle == null ? obtain() : recycle;
+        mt.resetAndAnalyzeBidi(text, start, end, textDir);
+
+        mt.mWidths.resize(mt.mTextLength);
+        if (mt.mTextLength == 0) {
+            return mt;
+        }
+
+        if (mt.mSpanned == null) {
+            // No style change by MetricsAffectingSpan. Just measure all text.
+            mt.applyMetricsAffectingSpan(
+                    paint, null /* spans */, start, end, 0 /* native static layout ptr */);
+        } else {
+            // There may be a MetricsAffectingSpan. Split into span transitions and apply styles.
+            int spanEnd;
+            for (int spanStart = start; spanStart < end; spanStart = spanEnd) {
+                spanEnd = mt.mSpanned.nextSpanTransition(spanStart, end, MetricAffectingSpan.class);
+                MetricAffectingSpan[] spans = mt.mSpanned.getSpans(spanStart, spanEnd,
+                        MetricAffectingSpan.class);
+                spans = TextUtils.removeEmptySpans(spans, mt.mSpanned, MetricAffectingSpan.class);
+                mt.applyMetricsAffectingSpan(
+                        paint, spans, spanStart, spanEnd, 0 /* native static layout ptr */);
+            }
+        }
+        return mt;
+    }
+
+    /**
+     * Generates new MeasuredParagraph for StaticLayout.
+     *
+     * If recycle is null, this returns new instance. If recycle is not null, this fills computed
+     * result to recycle and returns recycle.
+     *
+     * @param paint the paint to be used for rendering the text.
+     * @param text the character sequence to be measured
+     * @param start the inclusive start offset of the target region in the text
+     * @param end the exclusive end offset of the target region in the text
+     * @param textDir the text direction
+     * @param recycle pass existing MeasuredParagraph if you want to recycle it.
+     *
+     * @return measured text
+     */
+    public static @NonNull MeasuredParagraph buildForStaticLayout(
+            @NonNull TextPaint paint,
+            @NonNull CharSequence text,
+            @IntRange(from = 0) int start,
+            @IntRange(from = 0) int end,
+            @NonNull TextDirectionHeuristic textDir,
+            @Nullable MeasuredParagraph recycle) {
+        final MeasuredParagraph mt = recycle == null ? obtain() : recycle;
+        mt.resetAndAnalyzeBidi(text, start, end, textDir);
+        if (mt.mTextLength == 0) {
+            // Need to build empty native measured text for StaticLayout.
+            // TODO: Stop creating empty measured text for empty lines.
+            long nativeBuilderPtr = nInitBuilder();
+            try {
+                mt.bindNativeObject(
+                        nBuildNativeMeasuredParagraph(nativeBuilderPtr, mt.mCopiedBuffer));
+            } finally {
+                nFreeBuilder(nativeBuilderPtr);
+            }
+            return mt;
+        }
+
+        long nativeBuilderPtr = nInitBuilder();
+        try {
+            if (mt.mSpanned == null) {
+                // No style change by MetricsAffectingSpan. Just measure all text.
+                mt.applyMetricsAffectingSpan(paint, null /* spans */, start, end, nativeBuilderPtr);
+                mt.mSpanEndCache.append(end);
+            } else {
+                // There may be a MetricsAffectingSpan. Split into span transitions and apply
+                // styles.
+                int spanEnd;
+                for (int spanStart = start; spanStart < end; spanStart = spanEnd) {
+                    spanEnd = mt.mSpanned.nextSpanTransition(spanStart, end,
+                                                             MetricAffectingSpan.class);
+                    MetricAffectingSpan[] spans = mt.mSpanned.getSpans(spanStart, spanEnd,
+                            MetricAffectingSpan.class);
+                    spans = TextUtils.removeEmptySpans(spans, mt.mSpanned,
+                                                       MetricAffectingSpan.class);
+                    mt.applyMetricsAffectingSpan(paint, spans, spanStart, spanEnd,
+                                                 nativeBuilderPtr);
+                    mt.mSpanEndCache.append(spanEnd);
+                }
+            }
+            mt.bindNativeObject(nBuildNativeMeasuredParagraph(nativeBuilderPtr, mt.mCopiedBuffer));
+        } finally {
+            nFreeBuilder(nativeBuilderPtr);
+        }
+
+        return mt;
+    }
+
+    /**
+     * Reset internal state and analyzes text for bidirectional runs.
+     *
+     * @param text the character sequence to be measured
+     * @param start the inclusive start offset of the target region in the text
+     * @param end the exclusive end offset of the target region in the text
+     * @param textDir the text direction
+     */
+    private void resetAndAnalyzeBidi(@NonNull CharSequence text,
+                                     @IntRange(from = 0) int start,  // inclusive
+                                     @IntRange(from = 0) int end,  // exclusive
+                                     @NonNull TextDirectionHeuristic textDir) {
+        reset();
+        mSpanned = text instanceof Spanned ? (Spanned) text : null;
+        mTextStart = start;
+        mTextLength = end - start;
+
+        if (mCopiedBuffer == null || mCopiedBuffer.length != mTextLength) {
+            mCopiedBuffer = new char[mTextLength];
+        }
+        TextUtils.getChars(text, start, end, mCopiedBuffer, 0);
+
+        // Replace characters associated with ReplacementSpan to U+FFFC.
+        if (mSpanned != null) {
+            ReplacementSpan[] spans = mSpanned.getSpans(start, end, ReplacementSpan.class);
+
+            for (int i = 0; i < spans.length; i++) {
+                int startInPara = mSpanned.getSpanStart(spans[i]) - start;
+                int endInPara = mSpanned.getSpanEnd(spans[i]) - start;
+                // The span interval may be larger and must be restricted to [start, end)
+                if (startInPara < 0) startInPara = 0;
+                if (endInPara > mTextLength) endInPara = mTextLength;
+                Arrays.fill(mCopiedBuffer, startInPara, endInPara, OBJECT_REPLACEMENT_CHARACTER);
+            }
+        }
+
+        if ((textDir == TextDirectionHeuristics.LTR
+                || textDir == TextDirectionHeuristics.FIRSTSTRONG_LTR
+                || textDir == TextDirectionHeuristics.ANYRTL_LTR)
+                && TextUtils.doesNotNeedBidi(mCopiedBuffer, 0, mTextLength)) {
+            mLevels.clear();
+            mParaDir = Layout.DIR_LEFT_TO_RIGHT;
+            mLtrWithoutBidi = true;
+        } else {
+            final int bidiRequest;
+            if (textDir == TextDirectionHeuristics.LTR) {
+                bidiRequest = Layout.DIR_REQUEST_LTR;
+            } else if (textDir == TextDirectionHeuristics.RTL) {
+                bidiRequest = Layout.DIR_REQUEST_RTL;
+            } else if (textDir == TextDirectionHeuristics.FIRSTSTRONG_LTR) {
+                bidiRequest = Layout.DIR_REQUEST_DEFAULT_LTR;
+            } else if (textDir == TextDirectionHeuristics.FIRSTSTRONG_RTL) {
+                bidiRequest = Layout.DIR_REQUEST_DEFAULT_RTL;
+            } else {
+                final boolean isRtl = textDir.isRtl(mCopiedBuffer, 0, mTextLength);
+                bidiRequest = isRtl ? Layout.DIR_REQUEST_RTL : Layout.DIR_REQUEST_LTR;
+            }
+            mLevels.resize(mTextLength);
+            mParaDir = AndroidBidi.bidi(bidiRequest, mCopiedBuffer, mLevels.getRawArray());
+            mLtrWithoutBidi = false;
+        }
+    }
+
+    private void applyReplacementRun(@NonNull ReplacementSpan replacement,
+                                     @IntRange(from = 0) int start,  // inclusive, in copied buffer
+                                     @IntRange(from = 0) int end,  // exclusive, in copied buffer
+                                     /* Maybe Zero */ long nativeBuilderPtr) {
+        // Use original text. Shouldn't matter.
+        // TODO: passing uninitizlied FontMetrics to developers. Do we need to keep this for
+        //       backward compatibility? or Should we initialize them for getFontMetricsInt?
+        final float width = replacement.getSize(
+                mCachedPaint, mSpanned, start + mTextStart, end + mTextStart, mCachedFm);
+        if (nativeBuilderPtr == 0) {
+            // Assigns all width to the first character. This is the same behavior as minikin.
+            mWidths.set(start, width);
+            if (end > start + 1) {
+                Arrays.fill(mWidths.getRawArray(), start + 1, end, 0.0f);
+            }
+            mWholeWidth += width;
+        } else {
+            nAddReplacementRun(nativeBuilderPtr, mCachedPaint.getNativeInstance(), start, end,
+                               width);
+        }
+    }
+
+    private void applyStyleRun(@IntRange(from = 0) int start,  // inclusive, in copied buffer
+                               @IntRange(from = 0) int end,  // exclusive, in copied buffer
+                               /* Maybe Zero */ long nativeBuilderPtr) {
+        if (nativeBuilderPtr != 0) {
+            mCachedPaint.getFontMetricsInt(mCachedFm);
+        }
+
+        if (mLtrWithoutBidi) {
+            // If the whole text is LTR direction, just apply whole region.
+            if (nativeBuilderPtr == 0) {
+                mWholeWidth += mCachedPaint.getTextRunAdvances(
+                        mCopiedBuffer, start, end - start, start, end - start, false /* isRtl */,
+                        mWidths.getRawArray(), start);
+            } else {
+                nAddStyleRun(nativeBuilderPtr, mCachedPaint.getNativeInstance(), start, end,
+                        false /* isRtl */);
+            }
+        } else {
+            // If there is multiple bidi levels, split into individual bidi level and apply style.
+            byte level = mLevels.get(start);
+            // Note that the empty text or empty range won't reach this method.
+            // Safe to search from start + 1.
+            for (int levelStart = start, levelEnd = start + 1;; ++levelEnd) {
+                if (levelEnd == end || mLevels.get(levelEnd) != level) {  // transition point
+                    final boolean isRtl = (level & 0x1) != 0;
+                    if (nativeBuilderPtr == 0) {
+                        final int levelLength = levelEnd - levelStart;
+                        mWholeWidth += mCachedPaint.getTextRunAdvances(
+                                mCopiedBuffer, levelStart, levelLength, levelStart, levelLength,
+                                isRtl, mWidths.getRawArray(), levelStart);
+                    } else {
+                        nAddStyleRun(nativeBuilderPtr, mCachedPaint.getNativeInstance(), levelStart,
+                                levelEnd, isRtl);
+                    }
+                    if (levelEnd == end) {
+                        break;
+                    }
+                    levelStart = levelEnd;
+                    level = mLevels.get(levelEnd);
+                }
+            }
+        }
+    }
+
+    private void applyMetricsAffectingSpan(
+            @NonNull TextPaint paint,
+            @Nullable MetricAffectingSpan[] spans,
+            @IntRange(from = 0) int start,  // inclusive, in original text buffer
+            @IntRange(from = 0) int end,  // exclusive, in original text buffer
+            /* Maybe Zero */ long nativeBuilderPtr) {
+        mCachedPaint.set(paint);
+        // XXX paint should not have a baseline shift, but...
+        mCachedPaint.baselineShift = 0;
+
+        final boolean needFontMetrics = nativeBuilderPtr != 0;
+
+        if (needFontMetrics && mCachedFm == null) {
+            mCachedFm = new Paint.FontMetricsInt();
+        }
+
+        ReplacementSpan replacement = null;
+        if (spans != null) {
+            for (int i = 0; i < spans.length; i++) {
+                MetricAffectingSpan span = spans[i];
+                if (span instanceof ReplacementSpan) {
+                    // The last ReplacementSpan is effective for backward compatibility reasons.
+                    replacement = (ReplacementSpan) span;
+                } else {
+                    // TODO: No need to call updateMeasureState for ReplacementSpan as well?
+                    span.updateMeasureState(mCachedPaint);
+                }
+            }
+        }
+
+        final int startInCopiedBuffer = start - mTextStart;
+        final int endInCopiedBuffer = end - mTextStart;
+
+        if (replacement != null) {
+            applyReplacementRun(replacement, startInCopiedBuffer, endInCopiedBuffer,
+                                nativeBuilderPtr);
+        } else {
+            applyStyleRun(startInCopiedBuffer, endInCopiedBuffer, nativeBuilderPtr);
+        }
+
+        if (needFontMetrics) {
+            if (mCachedPaint.baselineShift < 0) {
+                mCachedFm.ascent += mCachedPaint.baselineShift;
+                mCachedFm.top += mCachedPaint.baselineShift;
+            } else {
+                mCachedFm.descent += mCachedPaint.baselineShift;
+                mCachedFm.bottom += mCachedPaint.baselineShift;
+            }
+
+            mFontMetrics.append(mCachedFm.top);
+            mFontMetrics.append(mCachedFm.bottom);
+            mFontMetrics.append(mCachedFm.ascent);
+            mFontMetrics.append(mCachedFm.descent);
+        }
+    }
+
+    /**
+     * Returns the maximum index that the accumulated width not exceeds the width.
+     *
+     * If forward=false is passed, returns the minimum index from the end instead.
+     *
+     * This only works if the MeasuredParagraph is computed with computeForMeasurement.
+     * Undefined behavior in other case.
+     */
+    @IntRange(from = 0) int breakText(int limit, boolean forwards, float width) {
+        float[] w = mWidths.getRawArray();
+        if (forwards) {
+            int i = 0;
+            while (i < limit) {
+                width -= w[i];
+                if (width < 0.0f) break;
+                i++;
+            }
+            while (i > 0 && mCopiedBuffer[i - 1] == ' ') i--;
+            return i;
+        } else {
+            int i = limit - 1;
+            while (i >= 0) {
+                width -= w[i];
+                if (width < 0.0f) break;
+                i--;
+            }
+            while (i < limit - 1 && (mCopiedBuffer[i + 1] == ' ' || w[i + 1] == 0.0f)) {
+                i++;
+            }
+            return limit - i - 1;
+        }
+    }
+
+    /**
+     * Returns the length of the substring.
+     *
+     * This only works if the MeasuredParagraph is computed with computeForMeasurement.
+     * Undefined behavior in other case.
+     */
+    @FloatRange(from = 0.0f) float measure(int start, int limit) {
+        float width = 0;
+        float[] w = mWidths.getRawArray();
+        for (int i = start; i < limit; ++i) {
+            width += w[i];
+        }
+        return width;
+    }
+
+    private static native /* Non Zero */ long nInitBuilder();
+
+    /**
+     * Apply style to make native measured text.
+     *
+     * @param nativeBuilderPtr The native MeasuredParagraph builder pointer.
+     * @param paintPtr The native paint pointer to be applied.
+     * @param start The start offset in the copied buffer.
+     * @param end The end offset in the copied buffer.
+     * @param isRtl True if the text is RTL.
+     */
+    private static native void nAddStyleRun(/* Non Zero */ long nativeBuilderPtr,
+                                            /* Non Zero */ long paintPtr,
+                                            @IntRange(from = 0) int start,
+                                            @IntRange(from = 0) int end,
+                                            boolean isRtl);
+
+    /**
+     * Apply ReplacementRun to make native measured text.
+     *
+     * @param nativeBuilderPtr The native MeasuredParagraph builder pointer.
+     * @param paintPtr The native paint pointer to be applied.
+     * @param start The start offset in the copied buffer.
+     * @param end The end offset in the copied buffer.
+     * @param width The width of the replacement.
+     */
+    private static native void nAddReplacementRun(/* Non Zero */ long nativeBuilderPtr,
+                                                  /* Non Zero */ long paintPtr,
+                                                  @IntRange(from = 0) int start,
+                                                  @IntRange(from = 0) int end,
+                                                  @FloatRange(from = 0) float width);
+
+    private static native long nBuildNativeMeasuredParagraph(/* Non Zero */ long nativeBuilderPtr,
+                                                 @NonNull char[] text);
+
+    private static native void nFreeBuilder(/* Non Zero */ long nativeBuilderPtr);
+
+    @CriticalNative
+    private static native /* Non Zero */ long nGetReleaseFunc();
+}
diff --git a/core/java/android/text/MeasuredText.java b/core/java/android/text/MeasuredText.java
index 14d6f9e..2c30360 100644
--- a/core/java/android/text/MeasuredText.java
+++ b/core/java/android/text/MeasuredText.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2010 The Android Open Source Project
+ * Copyright (C) 2017 The Android Open Source Project
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -16,661 +16,255 @@
 
 package android.text;
 
-import android.annotation.FloatRange;
 import android.annotation.IntRange;
 import android.annotation.NonNull;
-import android.annotation.Nullable;
-import android.graphics.Paint;
-import android.text.AutoGrowArray.ByteArray;
-import android.text.AutoGrowArray.FloatArray;
-import android.text.AutoGrowArray.IntArray;
-import android.text.Layout.Directions;
-import android.text.style.MetricAffectingSpan;
-import android.text.style.ReplacementSpan;
-import android.util.Pools.SynchronizedPool;
+import android.util.IntArray;
 
-import dalvik.annotation.optimization.CriticalNative;
+import com.android.internal.util.ArrayUtils;
+import com.android.internal.util.Preconditions;
 
-import libcore.util.NativeAllocationRegistry;
-
-import java.util.Arrays;
+import java.util.ArrayList;
 
 /**
- * MeasuredText provides text information for rendering purpose.
- *
- * The first motivation of this class is identify the text directions and retrieving individual
- * character widths. However retrieving character widths is slower than identifying text directions.
- * Thus, this class provides several builder methods for specific purposes.
- *
- * - buildForBidi:
- *   Compute only text directions.
- * - buildForMeasurement:
- *   Compute text direction and all character widths.
- * - buildForStaticLayout:
- *   This is bit special. StaticLayout also needs to know text direction and character widths for
- *   line breaking, but all things are done in native code. Similarly, text measurement is done
- *   in native code. So instead of storing result to Java array, this keeps the result in native
- *   code since there is no good reason to move the results to Java layer.
- *
- * In addition to the character widths, some additional information is computed for each purposes,
- * e.g. whole text length for measurement or font metrics for static layout.
- *
- * MeasuredText is NOT a thread safe object.
- * @hide
+ * A text which has already been measured.
  */
-public class MeasuredText {
-    private static final char OBJECT_REPLACEMENT_CHARACTER = '\uFFFC';
+public class MeasuredText implements Spanned {
+    private static final char LINE_FEED = '\n';
 
-    private static final NativeAllocationRegistry sRegistry = new NativeAllocationRegistry(
-            MeasuredText.class.getClassLoader(), nGetReleaseFunc(), 1024);
+    // The original text.
+    private final @NonNull CharSequence mText;
 
-    private MeasuredText() {}  // Use build static functions instead.
+    // The inclusive start offset of the measuring target.
+    private final @IntRange(from = 0) int mStart;
 
-    private static final SynchronizedPool<MeasuredText> sPool = new SynchronizedPool<>(1);
+    // The exclusive end offset of the measuring target.
+    private final @IntRange(from = 0) int mEnd;
 
-    private static @NonNull MeasuredText obtain() { // Use build static functions instead.
-        final MeasuredText mt = sPool.acquire();
-        return mt != null ? mt : new MeasuredText();
+    // The TextPaint used for measurement.
+    private final @NonNull TextPaint mPaint;
+
+    // The requested text direction.
+    private final @NonNull TextDirectionHeuristic mTextDir;
+
+    // The measured paragraph texts.
+    private final @NonNull MeasuredParagraph[] mMeasuredParagraphs;
+
+    // The sorted paragraph end offsets.
+    private final @NonNull int[] mParagraphBreakPoints;
+
+    /**
+     * Build MeasuredText from the text.
+     *
+     * @param text The text to be measured.
+     * @param paint The paint to be used for drawing.
+     * @param textDir The text direction.
+     * @return The measured text.
+     */
+    public static @NonNull MeasuredText build(@NonNull CharSequence text,
+                                              @NonNull TextPaint paint,
+                                              @NonNull TextDirectionHeuristic textDir) {
+        return MeasuredText.build(text, paint, textDir, 0, text.length());
     }
 
     /**
-     * Recycle the MeasuredText.
+     * Build MeasuredText from the specific range of the text..
      *
-     * Do not call any methods after you call this method.
+     * @param text The text to be measured.
+     * @param paint The paint to be used for drawing.
+     * @param textDir The text direction.
+     * @param start The inclusive start offset of the text.
+     * @param end The exclusive start offset of the text.
+     * @return The measured text.
      */
-    public void recycle() {
-        release();
-        sPool.release(this);
+    public static @NonNull MeasuredText build(@NonNull CharSequence text,
+                                              @NonNull TextPaint paint,
+                                              @NonNull TextDirectionHeuristic textDir,
+                                              @IntRange(from = 0) int start,
+                                              @IntRange(from = 0) int end) {
+        Preconditions.checkNotNull(text);
+        Preconditions.checkNotNull(paint);
+        Preconditions.checkNotNull(textDir);
+        Preconditions.checkArgumentInRange(start, 0, text.length(), "start");
+        Preconditions.checkArgumentInRange(end, 0, text.length(), "end");
+
+        final IntArray paragraphEnds = new IntArray();
+        final ArrayList<MeasuredParagraph> measuredTexts = new ArrayList<>();
+
+        int paraEnd = 0;
+        for (int paraStart = start; paraStart < end; paraStart = paraEnd) {
+            paraEnd = TextUtils.indexOf(text, LINE_FEED, paraStart, end);
+            if (paraEnd < 0) {
+                // No LINE_FEED(U+000A) character found. Use end of the text as the paragraph end.
+                paraEnd = end;
+            } else {
+                paraEnd++;  // Includes LINE_FEED(U+000A) to the prev paragraph.
+            }
+
+            paragraphEnds.add(paraEnd);
+            measuredTexts.add(MeasuredParagraph.buildForStaticLayout(
+                    paint, text, paraStart, paraEnd, textDir, null /* no recycle */));
+        }
+
+        return new MeasuredText(text, start, end, paint, textDir,
+                                measuredTexts.toArray(new MeasuredParagraph[measuredTexts.size()]),
+                                paragraphEnds.toArray());
     }
 
-    // The casted original text.
+    // Use MeasuredText.build instead.
+    private MeasuredText(@NonNull CharSequence text,
+                         @IntRange(from = 0) int start,
+                         @IntRange(from = 0) int end,
+                         @NonNull TextPaint paint,
+                         @NonNull TextDirectionHeuristic textDir,
+                         @NonNull MeasuredParagraph[] measuredTexts,
+                         @NonNull int[] paragraphBreakPoints) {
+        mText = text;
+        mStart = start;
+        mEnd = end;
+        mPaint = paint;
+        mMeasuredParagraphs = measuredTexts;
+        mParagraphBreakPoints = paragraphBreakPoints;
+        mTextDir = textDir;
+    }
+
+    /**
+     * Return the underlying text.
+     */
+    public @NonNull CharSequence getText() {
+        return mText;
+    }
+
+    /**
+     * Returns the inclusive start offset of measured region.
+     */
+    public @IntRange(from = 0) int getStart() {
+        return mStart;
+    }
+
+    /**
+     * Returns the exclusive end offset of measured region.
+     */
+    public @IntRange(from = 0) int getEnd() {
+        return mEnd;
+    }
+
+    /**
+     * Returns the text direction associated with char sequence.
+     */
+    public @NonNull TextDirectionHeuristic getTextDir() {
+        return mTextDir;
+    }
+
+    /**
+     * Returns the paint used to measure this text.
+     */
+    public @NonNull TextPaint getPaint() {
+        return mPaint;
+    }
+
+    /**
+     * Returns the length of the paragraph of this text.
+     */
+    public @IntRange(from = 0) int getParagraphCount() {
+        return mParagraphBreakPoints.length;
+    }
+
+    /**
+     * Returns the paragraph start offset of the text.
+     */
+    public @IntRange(from = 0) int getParagraphStart(@IntRange(from = 0) int paraIndex) {
+        Preconditions.checkArgumentInRange(paraIndex, 0, getParagraphCount(), "paraIndex");
+        return paraIndex == 0 ? mStart : mParagraphBreakPoints[paraIndex - 1];
+    }
+
+    /**
+     * Returns the paragraph end offset of the text.
+     */
+    public @IntRange(from = 0) int getParagraphEnd(@IntRange(from = 0) int paraIndex) {
+        Preconditions.checkArgumentInRange(paraIndex, 0, getParagraphCount(), "paraIndex");
+        return mParagraphBreakPoints[paraIndex];
+    }
+
+    /** @hide */
+    public @NonNull MeasuredParagraph getMeasuredParagraph(@IntRange(from = 0) int paraIndex) {
+        return mMeasuredParagraphs[paraIndex];
+    }
+
+    ///////////////////////////////////////////////////////////////////////////////////////////////
+    // Spanned overrides
     //
-    // This may be null if the passed text is not a Spanned.
-    private @Nullable Spanned mSpanned;
+    // Just proxy for underlying mText if appropriate.
 
-    // The start offset of the target range in the original text (mSpanned);
-    private @IntRange(from = 0) int mTextStart;
+    @Override
+    public <T> T[] getSpans(int start, int end, Class<T> type) {
+        if (mText instanceof Spanned) {
+            return ((Spanned) mText).getSpans(start, end, type);
+        } else {
+            return ArrayUtils.emptyArray(type);
+        }
+    }
 
-    // The length of the target range in the original text.
-    private @IntRange(from = 0) int mTextLength;
+    @Override
+    public int getSpanStart(Object tag) {
+        if (mText instanceof Spanned) {
+            return ((Spanned) mText).getSpanStart(tag);
+        } else {
+            return -1;
+        }
+    }
 
-    // The copied character buffer for measuring text.
+    @Override
+    public int getSpanEnd(Object tag) {
+        if (mText instanceof Spanned) {
+            return ((Spanned) mText).getSpanEnd(tag);
+        } else {
+            return -1;
+        }
+    }
+
+    @Override
+    public int getSpanFlags(Object tag) {
+        if (mText instanceof Spanned) {
+            return ((Spanned) mText).getSpanFlags(tag);
+        } else {
+            return 0;
+        }
+    }
+
+    @Override
+    public int nextSpanTransition(int start, int limit, Class type) {
+        if (mText instanceof Spanned) {
+            return ((Spanned) mText).nextSpanTransition(start, limit, type);
+        } else {
+            return mText.length();
+        }
+    }
+
+    ///////////////////////////////////////////////////////////////////////////////////////////////
+    // CharSequence overrides.
     //
-    // The length of this array is mTextLength.
-    private @Nullable char[] mCopiedBuffer;
+    // Just proxy for underlying mText.
 
-    // The whole paragraph direction.
-    private @Layout.Direction int mParaDir;
-
-    // True if the text is LTR direction and doesn't contain any bidi characters.
-    private boolean mLtrWithoutBidi;
-
-    // The bidi level for individual characters.
-    //
-    // This is empty if mLtrWithoutBidi is true.
-    private @NonNull ByteArray mLevels = new ByteArray();
-
-    // The whole width of the text.
-    // See getWholeWidth comments.
-    private @FloatRange(from = 0.0f) float mWholeWidth;
-
-    // Individual characters' widths.
-    // See getWidths comments.
-    private @Nullable FloatArray mWidths = new FloatArray();
-
-    // The span end positions.
-    // See getSpanEndCache comments.
-    private @Nullable IntArray mSpanEndCache = new IntArray(4);
-
-    // The font metrics.
-    // See getFontMetrics comments.
-    private @Nullable IntArray mFontMetrics = new IntArray(4 * 4);
-
-    // The native MeasuredText.
-    // See getNativePtr comments.
-    // Do not modify these members directly. Use bindNativeObject/unbindNativeObject instead.
-    private /* Maybe Zero */ long mNativePtr = 0;
-    private @Nullable Runnable mNativeObjectCleaner;
-
-    // Associate the native object to this Java object.
-    private void bindNativeObject(/* Non Zero*/ long nativePtr) {
-        mNativePtr = nativePtr;
-        mNativeObjectCleaner = sRegistry.registerNativeAllocation(this, nativePtr);
+    @Override
+    public int length() {
+        return mText.length();
     }
 
-    // Decouple the native object from this Java object and release the native object.
-    private void unbindNativeObject() {
-        if (mNativePtr != 0) {
-            mNativeObjectCleaner.run();
-            mNativePtr = 0;
-        }
+    @Override
+    public char charAt(int index) {
+        // TODO: Should this be index + mStart ?
+        return mText.charAt(index);
     }
 
-    // Following two objects are for avoiding object allocation.
-    private @NonNull TextPaint mCachedPaint = new TextPaint();
-    private @Nullable Paint.FontMetricsInt mCachedFm;
-
-    /**
-     * Releases internal buffers.
-     */
-    public void release() {
-        reset();
-        mLevels.clearWithReleasingLargeArray();
-        mWidths.clearWithReleasingLargeArray();
-        mFontMetrics.clearWithReleasingLargeArray();
-        mSpanEndCache.clearWithReleasingLargeArray();
+    @Override
+    public CharSequence subSequence(int start, int end) {
+        // TODO: return MeasuredText.
+        // TODO: Should this be index + mStart, end + mStart ?
+        return mText.subSequence(start, end);
     }
 
-    /**
-     * Resets the internal state for starting new text.
-     */
-    private void reset() {
-        mSpanned = null;
-        mCopiedBuffer = null;
-        mWholeWidth = 0;
-        mLevels.clear();
-        mWidths.clear();
-        mFontMetrics.clear();
-        mSpanEndCache.clear();
-        unbindNativeObject();
+    @Override
+    public String toString() {
+        return mText.toString();
     }
-
-    /**
-     * Returns the characters to be measured.
-     *
-     * This is always available.
-     */
-    public @NonNull char[] getChars() {
-        return mCopiedBuffer;
-    }
-
-    /**
-     * Returns the paragraph direction.
-     *
-     * This is always available.
-     */
-    public @Layout.Direction int getParagraphDir() {
-        return mParaDir;
-    }
-
-    /**
-     * Returns the directions.
-     *
-     * This is always available.
-     */
-    public Directions getDirections(@IntRange(from = 0) int start,  // inclusive
-                                    @IntRange(from = 0) int end) {  // exclusive
-        if (mLtrWithoutBidi) {
-            return Layout.DIRS_ALL_LEFT_TO_RIGHT;
-        }
-
-        final int length = end - start;
-        return AndroidBidi.directions(mParaDir, mLevels.getRawArray(), start, mCopiedBuffer, start,
-                length);
-    }
-
-    /**
-     * Returns the whole text width.
-     *
-     * This is available only if the MeasureText is computed with computeForMeasurement.
-     * Returns 0 in other cases.
-     */
-    public @FloatRange(from = 0.0f) float getWholeWidth() {
-        return mWholeWidth;
-    }
-
-    /**
-     * Returns the individual character's width.
-     *
-     * This is available only if the MeasureText is computed with computeForMeasurement.
-     * Returns empty array in other cases.
-     */
-    public @NonNull FloatArray getWidths() {
-        return mWidths;
-    }
-
-    /**
-     * Returns the MetricsAffectingSpan end indices.
-     *
-     * If the input text is not a spanned string, this has one value that is the length of the text.
-     *
-     * This is available only if the MeasureText is computed with computeForStaticLayout.
-     * Returns empty array in other cases.
-     */
-    public @NonNull IntArray getSpanEndCache() {
-        return mSpanEndCache;
-    }
-
-    /**
-     * Returns the int array which holds FontMetrics.
-     *
-     * This array holds the repeat of top, bottom, ascent, descent of font metrics value.
-     *
-     * This is available only if the MeasureText is computed with computeForStaticLayout.
-     * Returns empty array in other cases.
-     */
-    public @NonNull IntArray getFontMetrics() {
-        return mFontMetrics;
-    }
-
-    /**
-     * Returns the native ptr of the MeasuredText.
-     *
-     * This is available only if the MeasureText is computed with computeForStaticLayout.
-     * Returns 0 in other cases.
-     */
-    public /* Maybe Zero */ long getNativePtr() {
-        return mNativePtr;
-    }
-
-    /**
-     * Generates new MeasuredText for Bidi computation.
-     *
-     * If recycle is null, this returns new instance. If recycle is not null, this fills computed
-     * result to recycle and returns recycle.
-     *
-     * @param text the character sequence to be measured
-     * @param start the inclusive start offset of the target region in the text
-     * @param end the exclusive end offset of the target region in the text
-     * @param textDir the text direction
-     * @param recycle pass existing MeasuredText if you want to recycle it.
-     *
-     * @return measured text
-     */
-    public static @NonNull MeasuredText buildForBidi(@NonNull CharSequence text,
-                                                     @IntRange(from = 0) int start,
-                                                     @IntRange(from = 0) int end,
-                                                     @NonNull TextDirectionHeuristic textDir,
-                                                     @Nullable MeasuredText recycle) {
-        final MeasuredText mt = recycle == null ? obtain() : recycle;
-        mt.resetAndAnalyzeBidi(text, start, end, textDir);
-        return mt;
-    }
-
-    /**
-     * Generates new MeasuredText for measuring texts.
-     *
-     * If recycle is null, this returns new instance. If recycle is not null, this fills computed
-     * result to recycle and returns recycle.
-     *
-     * @param paint the paint to be used for rendering the text.
-     * @param text the character sequence to be measured
-     * @param start the inclusive start offset of the target region in the text
-     * @param end the exclusive end offset of the target region in the text
-     * @param textDir the text direction
-     * @param recycle pass existing MeasuredText if you want to recycle it.
-     *
-     * @return measured text
-     */
-    public static @NonNull MeasuredText buildForMeasurement(@NonNull TextPaint paint,
-                                                            @NonNull CharSequence text,
-                                                            @IntRange(from = 0) int start,
-                                                            @IntRange(from = 0) int end,
-                                                            @NonNull TextDirectionHeuristic textDir,
-                                                            @Nullable MeasuredText recycle) {
-        final MeasuredText mt = recycle == null ? obtain() : recycle;
-        mt.resetAndAnalyzeBidi(text, start, end, textDir);
-
-        mt.mWidths.resize(mt.mTextLength);
-        if (mt.mTextLength == 0) {
-            return mt;
-        }
-
-        if (mt.mSpanned == null) {
-            // No style change by MetricsAffectingSpan. Just measure all text.
-            mt.applyMetricsAffectingSpan(
-                    paint, null /* spans */, start, end, 0 /* native static layout ptr */);
-        } else {
-            // There may be a MetricsAffectingSpan. Split into span transitions and apply styles.
-            int spanEnd;
-            for (int spanStart = start; spanStart < end; spanStart = spanEnd) {
-                spanEnd = mt.mSpanned.nextSpanTransition(spanStart, end, MetricAffectingSpan.class);
-                MetricAffectingSpan[] spans = mt.mSpanned.getSpans(spanStart, spanEnd,
-                        MetricAffectingSpan.class);
-                spans = TextUtils.removeEmptySpans(spans, mt.mSpanned, MetricAffectingSpan.class);
-                mt.applyMetricsAffectingSpan(
-                        paint, spans, spanStart, spanEnd, 0 /* native static layout ptr */);
-            }
-        }
-        return mt;
-    }
-
-    /**
-     * Generates new MeasuredText for StaticLayout.
-     *
-     * If recycle is null, this returns new instance. If recycle is not null, this fills computed
-     * result to recycle and returns recycle.
-     *
-     * @param paint the paint to be used for rendering the text.
-     * @param text the character sequence to be measured
-     * @param start the inclusive start offset of the target region in the text
-     * @param end the exclusive end offset of the target region in the text
-     * @param textDir the text direction
-     * @param recycle pass existing MeasuredText if you want to recycle it.
-     *
-     * @return measured text
-     */
-    public static @NonNull MeasuredText buildForStaticLayout(
-            @NonNull TextPaint paint,
-            @NonNull CharSequence text,
-            @IntRange(from = 0) int start,
-            @IntRange(from = 0) int end,
-            @NonNull TextDirectionHeuristic textDir,
-            @Nullable MeasuredText recycle) {
-        final MeasuredText mt = recycle == null ? obtain() : recycle;
-        mt.resetAndAnalyzeBidi(text, start, end, textDir);
-        if (mt.mTextLength == 0) {
-            // Need to build empty native measured text for StaticLayout.
-            // TODO: Stop creating empty measured text for empty lines.
-            long nativeBuilderPtr = nInitBuilder();
-            try {
-                mt.bindNativeObject(nBuildNativeMeasuredText(nativeBuilderPtr, mt.mCopiedBuffer));
-            } finally {
-                nFreeBuilder(nativeBuilderPtr);
-            }
-            return mt;
-        }
-
-        long nativeBuilderPtr = nInitBuilder();
-        try {
-            if (mt.mSpanned == null) {
-                // No style change by MetricsAffectingSpan. Just measure all text.
-                mt.applyMetricsAffectingSpan(paint, null /* spans */, start, end, nativeBuilderPtr);
-                mt.mSpanEndCache.append(end);
-            } else {
-                // There may be a MetricsAffectingSpan. Split into span transitions and apply
-                // styles.
-                int spanEnd;
-                for (int spanStart = start; spanStart < end; spanStart = spanEnd) {
-                    spanEnd = mt.mSpanned.nextSpanTransition(spanStart, end,
-                                                             MetricAffectingSpan.class);
-                    MetricAffectingSpan[] spans = mt.mSpanned.getSpans(spanStart, spanEnd,
-                            MetricAffectingSpan.class);
-                    spans = TextUtils.removeEmptySpans(spans, mt.mSpanned,
-                                                       MetricAffectingSpan.class);
-                    mt.applyMetricsAffectingSpan(paint, spans, spanStart, spanEnd,
-                                                 nativeBuilderPtr);
-                    mt.mSpanEndCache.append(spanEnd);
-                }
-            }
-            mt.bindNativeObject(nBuildNativeMeasuredText(nativeBuilderPtr, mt.mCopiedBuffer));
-        } finally {
-            nFreeBuilder(nativeBuilderPtr);
-        }
-
-        return mt;
-    }
-
-    /**
-     * Reset internal state and analyzes text for bidirectional runs.
-     *
-     * @param text the character sequence to be measured
-     * @param start the inclusive start offset of the target region in the text
-     * @param end the exclusive end offset of the target region in the text
-     * @param textDir the text direction
-     */
-    private void resetAndAnalyzeBidi(@NonNull CharSequence text,
-                                     @IntRange(from = 0) int start,  // inclusive
-                                     @IntRange(from = 0) int end,  // exclusive
-                                     @NonNull TextDirectionHeuristic textDir) {
-        reset();
-        mSpanned = text instanceof Spanned ? (Spanned) text : null;
-        mTextStart = start;
-        mTextLength = end - start;
-
-        if (mCopiedBuffer == null || mCopiedBuffer.length != mTextLength) {
-            mCopiedBuffer = new char[mTextLength];
-        }
-        TextUtils.getChars(text, start, end, mCopiedBuffer, 0);
-
-        // Replace characters associated with ReplacementSpan to U+FFFC.
-        if (mSpanned != null) {
-            ReplacementSpan[] spans = mSpanned.getSpans(start, end, ReplacementSpan.class);
-
-            for (int i = 0; i < spans.length; i++) {
-                int startInPara = mSpanned.getSpanStart(spans[i]) - start;
-                int endInPara = mSpanned.getSpanEnd(spans[i]) - start;
-                // The span interval may be larger and must be restricted to [start, end)
-                if (startInPara < 0) startInPara = 0;
-                if (endInPara > mTextLength) endInPara = mTextLength;
-                Arrays.fill(mCopiedBuffer, startInPara, endInPara, OBJECT_REPLACEMENT_CHARACTER);
-            }
-        }
-
-        if ((textDir == TextDirectionHeuristics.LTR ||
-                textDir == TextDirectionHeuristics.FIRSTSTRONG_LTR ||
-                textDir == TextDirectionHeuristics.ANYRTL_LTR) &&
-                TextUtils.doesNotNeedBidi(mCopiedBuffer, 0, mTextLength)) {
-            mLevels.clear();
-            mParaDir = Layout.DIR_LEFT_TO_RIGHT;
-            mLtrWithoutBidi = true;
-        } else {
-            final int bidiRequest;
-            if (textDir == TextDirectionHeuristics.LTR) {
-                bidiRequest = Layout.DIR_REQUEST_LTR;
-            } else if (textDir == TextDirectionHeuristics.RTL) {
-                bidiRequest = Layout.DIR_REQUEST_RTL;
-            } else if (textDir == TextDirectionHeuristics.FIRSTSTRONG_LTR) {
-                bidiRequest = Layout.DIR_REQUEST_DEFAULT_LTR;
-            } else if (textDir == TextDirectionHeuristics.FIRSTSTRONG_RTL) {
-                bidiRequest = Layout.DIR_REQUEST_DEFAULT_RTL;
-            } else {
-                final boolean isRtl = textDir.isRtl(mCopiedBuffer, 0, mTextLength);
-                bidiRequest = isRtl ? Layout.DIR_REQUEST_RTL : Layout.DIR_REQUEST_LTR;
-            }
-            mLevels.resize(mTextLength);
-            mParaDir = AndroidBidi.bidi(bidiRequest, mCopiedBuffer, mLevels.getRawArray());
-            mLtrWithoutBidi = false;
-        }
-    }
-
-    private void applyReplacementRun(@NonNull ReplacementSpan replacement,
-                                     @IntRange(from = 0) int start,  // inclusive, in copied buffer
-                                     @IntRange(from = 0) int end,  // exclusive, in copied buffer
-                                     /* Maybe Zero */ long nativeBuilderPtr) {
-        // Use original text. Shouldn't matter.
-        // TODO: passing uninitizlied FontMetrics to developers. Do we need to keep this for
-        //       backward compatibility? or Should we initialize them for getFontMetricsInt?
-        final float width = replacement.getSize(
-                mCachedPaint, mSpanned, start + mTextStart, end + mTextStart, mCachedFm);
-        if (nativeBuilderPtr == 0) {
-            // Assigns all width to the first character. This is the same behavior as minikin.
-            mWidths.set(start, width);
-            if (end > start + 1) {
-                Arrays.fill(mWidths.getRawArray(), start + 1, end, 0.0f);
-            }
-            mWholeWidth += width;
-        } else {
-            nAddReplacementRun(nativeBuilderPtr, mCachedPaint.getNativeInstance(), start, end,
-                               width);
-        }
-    }
-
-    private void applyStyleRun(@IntRange(from = 0) int start,  // inclusive, in copied buffer
-                               @IntRange(from = 0) int end,  // exclusive, in copied buffer
-                               /* Maybe Zero */ long nativeBuilderPtr) {
-        if (nativeBuilderPtr != 0) {
-            mCachedPaint.getFontMetricsInt(mCachedFm);
-        }
-
-        if (mLtrWithoutBidi) {
-            // If the whole text is LTR direction, just apply whole region.
-            if (nativeBuilderPtr == 0) {
-                mWholeWidth += mCachedPaint.getTextRunAdvances(
-                        mCopiedBuffer, start, end - start, start, end - start, false /* isRtl */,
-                        mWidths.getRawArray(), start);
-            } else {
-                nAddStyleRun(nativeBuilderPtr, mCachedPaint.getNativeInstance(), start, end,
-                        false /* isRtl */);
-            }
-        } else {
-            // If there is multiple bidi levels, split into individual bidi level and apply style.
-            byte level = mLevels.get(start);
-            // Note that the empty text or empty range won't reach this method.
-            // Safe to search from start + 1.
-            for (int levelStart = start, levelEnd = start + 1;; ++levelEnd) {
-                if (levelEnd == end || mLevels.get(levelEnd) != level) {  // transition point
-                    final boolean isRtl = (level & 0x1) != 0;
-                    if (nativeBuilderPtr == 0) {
-                        final int levelLength = levelEnd - levelStart;
-                        mWholeWidth += mCachedPaint.getTextRunAdvances(
-                                mCopiedBuffer, levelStart, levelLength, levelStart, levelLength,
-                                isRtl, mWidths.getRawArray(), levelStart);
-                    } else {
-                        nAddStyleRun(nativeBuilderPtr, mCachedPaint.getNativeInstance(), levelStart,
-                                levelEnd, isRtl);
-                    }
-                    if (levelEnd == end) {
-                        break;
-                    }
-                    levelStart = levelEnd;
-                    level = mLevels.get(levelEnd);
-                }
-            }
-        }
-    }
-
-    private void applyMetricsAffectingSpan(
-            @NonNull TextPaint paint,
-            @Nullable MetricAffectingSpan[] spans,
-            @IntRange(from = 0) int start,  // inclusive, in original text buffer
-            @IntRange(from = 0) int end,  // exclusive, in original text buffer
-            /* Maybe Zero */ long nativeBuilderPtr) {
-        mCachedPaint.set(paint);
-        // XXX paint should not have a baseline shift, but...
-        mCachedPaint.baselineShift = 0;
-
-        final boolean needFontMetrics = nativeBuilderPtr != 0;
-
-        if (needFontMetrics && mCachedFm == null) {
-            mCachedFm = new Paint.FontMetricsInt();
-        }
-
-        ReplacementSpan replacement = null;
-        if (spans != null) {
-            for (int i = 0; i < spans.length; i++) {
-                MetricAffectingSpan span = spans[i];
-                if (span instanceof ReplacementSpan) {
-                    // The last ReplacementSpan is effective for backward compatibility reasons.
-                    replacement = (ReplacementSpan) span;
-                } else {
-                    // TODO: No need to call updateMeasureState for ReplacementSpan as well?
-                    span.updateMeasureState(mCachedPaint);
-                }
-            }
-        }
-
-        final int startInCopiedBuffer = start - mTextStart;
-        final int endInCopiedBuffer = end - mTextStart;
-
-        if (replacement != null) {
-            applyReplacementRun(replacement, startInCopiedBuffer, endInCopiedBuffer,
-                                nativeBuilderPtr);
-        } else {
-            applyStyleRun(startInCopiedBuffer, endInCopiedBuffer, nativeBuilderPtr);
-        }
-
-        if (needFontMetrics) {
-            if (mCachedPaint.baselineShift < 0) {
-                mCachedFm.ascent += mCachedPaint.baselineShift;
-                mCachedFm.top += mCachedPaint.baselineShift;
-            } else {
-                mCachedFm.descent += mCachedPaint.baselineShift;
-                mCachedFm.bottom += mCachedPaint.baselineShift;
-            }
-
-            mFontMetrics.append(mCachedFm.top);
-            mFontMetrics.append(mCachedFm.bottom);
-            mFontMetrics.append(mCachedFm.ascent);
-            mFontMetrics.append(mCachedFm.descent);
-        }
-    }
-
-    /**
-     * Returns the maximum index that the accumulated width not exceeds the width.
-     *
-     * If forward=false is passed, returns the minimum index from the end instead.
-     *
-     * This only works if the MeasuredText is computed with computeForMeasurement.
-     * Undefined behavior in other case.
-     */
-    @IntRange(from = 0) int breakText(int limit, boolean forwards, float width) {
-        float[] w = mWidths.getRawArray();
-        if (forwards) {
-            int i = 0;
-            while (i < limit) {
-                width -= w[i];
-                if (width < 0.0f) break;
-                i++;
-            }
-            while (i > 0 && mCopiedBuffer[i - 1] == ' ') i--;
-            return i;
-        } else {
-            int i = limit - 1;
-            while (i >= 0) {
-                width -= w[i];
-                if (width < 0.0f) break;
-                i--;
-            }
-            while (i < limit - 1 && (mCopiedBuffer[i + 1] == ' ' || w[i + 1] == 0.0f)) {
-                i++;
-            }
-            return limit - i - 1;
-        }
-    }
-
-    /**
-     * Returns the length of the substring.
-     *
-     * This only works if the MeasuredText is computed with computeForMeasurement.
-     * Undefined behavior in other case.
-     */
-    @FloatRange(from = 0.0f) float measure(int start, int limit) {
-        float width = 0;
-        float[] w = mWidths.getRawArray();
-        for (int i = start; i < limit; ++i) {
-            width += w[i];
-        }
-        return width;
-    }
-
-    private static native /* Non Zero */ long nInitBuilder();
-
-    /**
-     * Apply style to make native measured text.
-     *
-     * @param nativeBuilderPtr The native MeasuredText builder pointer.
-     * @param paintPtr The native paint pointer to be applied.
-     * @param start The start offset in the copied buffer.
-     * @param end The end offset in the copied buffer.
-     * @param isRtl True if the text is RTL.
-     */
-    private static native void nAddStyleRun(/* Non Zero */ long nativeBuilderPtr,
-                                            /* Non Zero */ long paintPtr,
-                                            @IntRange(from = 0) int start,
-                                            @IntRange(from = 0) int end,
-                                            boolean isRtl);
-
-    /**
-     * Apply ReplacementRun to make native measured text.
-     *
-     * @param nativeBuilderPtr The native MeasuredText builder pointer.
-     * @param paintPtr The native paint pointer to be applied.
-     * @param start The start offset in the copied buffer.
-     * @param end The end offset in the copied buffer.
-     * @param width The width of the replacement.
-     */
-    private static native void nAddReplacementRun(/* Non Zero */ long nativeBuilderPtr,
-                                                  /* Non Zero */ long paintPtr,
-                                                  @IntRange(from = 0) int start,
-                                                  @IntRange(from = 0) int end,
-                                                  @FloatRange(from = 0) float width);
-
-    private static native long nBuildNativeMeasuredText(/* Non Zero */ long nativeBuilderPtr,
-                                                 @NonNull char[] text);
-
-    private static native void nFreeBuilder(/* Non Zero */ long nativeBuilderPtr);
-
-    @CriticalNative
-    private static native /* Non Zero */ long nGetReleaseFunc();
 }
diff --git a/core/java/android/text/PremeasuredText.java b/core/java/android/text/PremeasuredText.java
deleted file mode 100644
index 465314d..0000000
--- a/core/java/android/text/PremeasuredText.java
+++ /dev/null
@@ -1,272 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.text;
-
-import android.annotation.IntRange;
-import android.annotation.NonNull;
-import android.util.IntArray;
-
-import com.android.internal.util.ArrayUtils;
-import com.android.internal.util.Preconditions;
-
-import java.util.ArrayList;
-
-/**
- * A text which has already been measured.
- *
- * TODO: Rename to better name? e.g. MeasuredText, FrozenText etc.
- */
-public class PremeasuredText implements Spanned {
-    private static final char LINE_FEED = '\n';
-
-    // The original text.
-    private final @NonNull CharSequence mText;
-
-    // The inclusive start offset of the measuring target.
-    private final @IntRange(from = 0) int mStart;
-
-    // The exclusive end offset of the measuring target.
-    private final @IntRange(from = 0) int mEnd;
-
-    // The TextPaint used for measurement.
-    private final @NonNull TextPaint mPaint;
-
-    // The requested text direction.
-    private final @NonNull TextDirectionHeuristic mTextDir;
-
-    // The measured paragraph texts.
-    private final @NonNull MeasuredText[] mMeasuredTexts;
-
-    // The sorted paragraph end offsets.
-    private final @NonNull int[] mParagraphBreakPoints;
-
-    /**
-     * Build PremeasuredText from the text.
-     *
-     * @param text The text to be measured.
-     * @param paint The paint to be used for drawing.
-     * @param textDir The text direction.
-     * @return The measured text.
-     */
-    public static @NonNull PremeasuredText build(@NonNull CharSequence text,
-                                                 @NonNull TextPaint paint,
-                                                 @NonNull TextDirectionHeuristic textDir) {
-        return PremeasuredText.build(text, paint, textDir, 0, text.length());
-    }
-
-    /**
-     * Build PremeasuredText from the specific range of the text..
-     *
-     * @param text The text to be measured.
-     * @param paint The paint to be used for drawing.
-     * @param textDir The text direction.
-     * @param start The inclusive start offset of the text.
-     * @param end The exclusive start offset of the text.
-     * @return The measured text.
-     */
-    public static @NonNull PremeasuredText build(@NonNull CharSequence text,
-                                                 @NonNull TextPaint paint,
-                                                 @NonNull TextDirectionHeuristic textDir,
-                                                 @IntRange(from = 0) int start,
-                                                 @IntRange(from = 0) int end) {
-        Preconditions.checkNotNull(text);
-        Preconditions.checkNotNull(paint);
-        Preconditions.checkNotNull(textDir);
-        Preconditions.checkArgumentInRange(start, 0, text.length(), "start");
-        Preconditions.checkArgumentInRange(end, 0, text.length(), "end");
-
-        final IntArray paragraphEnds = new IntArray();
-        final ArrayList<MeasuredText> measuredTexts = new ArrayList<>();
-
-        int paraEnd = 0;
-        for (int paraStart = start; paraStart < end; paraStart = paraEnd) {
-            paraEnd = TextUtils.indexOf(text, LINE_FEED, paraStart, end);
-            if (paraEnd < 0) {
-                // No LINE_FEED(U+000A) character found. Use end of the text as the paragraph end.
-                paraEnd = end;
-            } else {
-                paraEnd++;  // Includes LINE_FEED(U+000A) to the prev paragraph.
-            }
-
-            paragraphEnds.add(paraEnd);
-            measuredTexts.add(MeasuredText.buildForStaticLayout(
-                    paint, text, paraStart, paraEnd, textDir, null /* no recycle */));
-        }
-
-        return new PremeasuredText(text, start, end, paint, textDir,
-                                   measuredTexts.toArray(new MeasuredText[measuredTexts.size()]),
-                                   paragraphEnds.toArray());
-    }
-
-    // Use PremeasuredText.build instead.
-    private PremeasuredText(@NonNull CharSequence text,
-                            @IntRange(from = 0) int start,
-                            @IntRange(from = 0) int end,
-                            @NonNull TextPaint paint,
-                            @NonNull TextDirectionHeuristic textDir,
-                            @NonNull MeasuredText[] measuredTexts,
-                            @NonNull int[] paragraphBreakPoints) {
-        mText = text;
-        mStart = start;
-        mEnd = end;
-        mPaint = paint;
-        mMeasuredTexts = measuredTexts;
-        mParagraphBreakPoints = paragraphBreakPoints;
-        mTextDir = textDir;
-    }
-
-    /**
-     * Return the underlying text.
-     */
-    public @NonNull CharSequence getText() {
-        return mText;
-    }
-
-    /**
-     * Returns the inclusive start offset of measured region.
-     */
-    public @IntRange(from = 0) int getStart() {
-        return mStart;
-    }
-
-    /**
-     * Returns the exclusive end offset of measured region.
-     */
-    public @IntRange(from = 0) int getEnd() {
-        return mEnd;
-    }
-
-    /**
-     * Returns the text direction associated with char sequence.
-     */
-    public @NonNull TextDirectionHeuristic getTextDir() {
-        return mTextDir;
-    }
-
-    /**
-     * Returns the paint used to measure this text.
-     */
-    public @NonNull TextPaint getPaint() {
-        return mPaint;
-    }
-
-    /**
-     * Returns the length of the paragraph of this text.
-     */
-    public @IntRange(from = 0) int getParagraphCount() {
-        return mParagraphBreakPoints.length;
-    }
-
-    /**
-     * Returns the paragraph start offset of the text.
-     */
-    public @IntRange(from = 0) int getParagraphStart(@IntRange(from = 0) int paraIndex) {
-        Preconditions.checkArgumentInRange(paraIndex, 0, getParagraphCount(), "paraIndex");
-        return paraIndex == 0 ? mStart : mParagraphBreakPoints[paraIndex - 1];
-    }
-
-    /**
-     * Returns the paragraph end offset of the text.
-     */
-    public @IntRange(from = 0) int getParagraphEnd(@IntRange(from = 0) int paraIndex) {
-        Preconditions.checkArgumentInRange(paraIndex, 0, getParagraphCount(), "paraIndex");
-        return mParagraphBreakPoints[paraIndex];
-    }
-
-    /** @hide */
-    public @NonNull MeasuredText getMeasuredText(@IntRange(from = 0) int paraIndex) {
-        return mMeasuredTexts[paraIndex];
-    }
-
-    ///////////////////////////////////////////////////////////////////////////////////////////////
-    // Spanned overrides
-    //
-    // Just proxy for underlying mText if appropriate.
-
-    @Override
-    public <T> T[] getSpans(int start, int end, Class<T> type) {
-        if (mText instanceof Spanned) {
-            return ((Spanned) mText).getSpans(start, end, type);
-        } else {
-            return ArrayUtils.emptyArray(type);
-        }
-    }
-
-    @Override
-    public int getSpanStart(Object tag) {
-        if (mText instanceof Spanned) {
-            return ((Spanned) mText).getSpanStart(tag);
-        } else {
-            return -1;
-        }
-    }
-
-    @Override
-    public int getSpanEnd(Object tag) {
-        if (mText instanceof Spanned) {
-            return ((Spanned) mText).getSpanEnd(tag);
-        } else {
-            return -1;
-        }
-    }
-
-    @Override
-    public int getSpanFlags(Object tag) {
-        if (mText instanceof Spanned) {
-            return ((Spanned) mText).getSpanFlags(tag);
-        } else {
-            return 0;
-        }
-    }
-
-    @Override
-    public int nextSpanTransition(int start, int limit, Class type) {
-        if (mText instanceof Spanned) {
-            return ((Spanned) mText).nextSpanTransition(start, limit, type);
-        } else {
-            return mText.length();
-        }
-    }
-
-    ///////////////////////////////////////////////////////////////////////////////////////////////
-    // CharSequence overrides.
-    //
-    // Just proxy for underlying mText.
-
-    @Override
-    public int length() {
-        return mText.length();
-    }
-
-    @Override
-    public char charAt(int index) {
-        // TODO: Should this be index + mStart ?
-        return mText.charAt(index);
-    }
-
-    @Override
-    public CharSequence subSequence(int start, int end) {
-        // TODO: return PremeasuredText.
-        // TODO: Should this be index + mStart, end + mStart ?
-        return mText.subSequence(start, end);
-    }
-
-    @Override
-    public String toString() {
-        return mText.toString();
-    }
-}
diff --git a/core/java/android/text/StaticLayout.java b/core/java/android/text/StaticLayout.java
index d69b119..36bec86 100644
--- a/core/java/android/text/StaticLayout.java
+++ b/core/java/android/text/StaticLayout.java
@@ -55,7 +55,8 @@
      * First, call nInit to setup native line breaker object. Then, for each paragraph, do the
      * following:
      *
-     *   - Create MeasuredText by MeasuredText.buildForStaticLayout which measures in native.
+     *   - Create MeasuredParagraph by MeasuredParagraph.buildForStaticLayout which measures in
+     *     native.
      *   - Run nComputeLineBreaks() to obtain line breaks for the paragraph.
      *
      * After all paragraphs, call finish() to release expensive buffers.
@@ -650,34 +651,34 @@
                 b.mJustificationMode != Layout.JUSTIFICATION_MODE_NONE,
                 indents, mLeftPaddings, mRightPaddings);
 
-        PremeasuredText premeasured = null;
+        MeasuredText measured = null;
         final Spanned spanned;
-        if (source instanceof PremeasuredText) {
-            premeasured = (PremeasuredText) source;
+        if (source instanceof MeasuredText) {
+            measured = (MeasuredText) source;
 
-            final CharSequence original = premeasured.getText();
+            final CharSequence original = measured.getText();
             spanned = (original instanceof Spanned) ? (Spanned) original : null;
 
-            if (bufStart != premeasured.getStart() || bufEnd != premeasured.getEnd()) {
+            if (bufStart != measured.getStart() || bufEnd != measured.getEnd()) {
                 // The buffer position has changed. Re-measure here.
-                premeasured = PremeasuredText.build(original, paint, textDir, bufStart, bufEnd);
+                measured = MeasuredText.build(original, paint, textDir, bufStart, bufEnd);
             } else {
-                // We can use premeasured information.
+                // We can use measured information.
 
-                // Overwrite with the one when premeasured.
+                // Overwrite with the one when emeasured.
                 // TODO: Give an option for developer not to overwrite and measure again here?
-                textDir = premeasured.getTextDir();
-                paint = premeasured.getPaint();
+                textDir = measured.getTextDir();
+                paint = measured.getPaint();
             }
         } else {
-            premeasured = PremeasuredText.build(source, paint, textDir, bufStart, bufEnd);
+            measured = MeasuredText.build(source, paint, textDir, bufStart, bufEnd);
             spanned = (source instanceof Spanned) ? (Spanned) source : null;
         }
 
         try {
-            for (int paraIndex = 0; paraIndex < premeasured.getParagraphCount(); paraIndex++) {
-                final int paraStart = premeasured.getParagraphStart(paraIndex);
-                final int paraEnd = premeasured.getParagraphEnd(paraIndex);
+            for (int paraIndex = 0; paraIndex < measured.getParagraphCount(); paraIndex++) {
+                final int paraStart = measured.getParagraphStart(paraIndex);
+                final int paraEnd = measured.getParagraphEnd(paraIndex);
 
                 int firstWidthLineCount = 1;
                 int firstWidth = outerWidth;
@@ -743,10 +744,10 @@
                     }
                 }
 
-                final MeasuredText measured = premeasured.getMeasuredText(paraIndex);
-                final char[] chs = measured.getChars();
-                final int[] spanEndCache = measured.getSpanEndCache().getRawArray();
-                final int[] fmCache = measured.getFontMetrics().getRawArray();
+                final MeasuredParagraph measuredPara = measured.getMeasuredParagraph(paraIndex);
+                final char[] chs = measuredPara.getChars();
+                final int[] spanEndCache = measuredPara.getSpanEndCache().getRawArray();
+                final int[] fmCache = measuredPara.getFontMetrics().getRawArray();
                 // TODO: Stop keeping duplicated width copy in native and Java.
                 widths.resize(chs.length);
 
@@ -759,7 +760,7 @@
 
                         // Inputs
                         chs,
-                        measured.getNativePtr(),
+                        measuredPara.getNativePtr(),
                         paraEnd - paraStart,
                         firstWidth,
                         firstWidthLineCount,
@@ -863,7 +864,7 @@
                         v = out(source, here, endPos,
                                 ascent, descent, fmTop, fmBottom,
                                 v, spacingmult, spacingadd, chooseHt, chooseHtv, fm,
-                                flags[breakIndex], needMultiply, measured, bufEnd,
+                                flags[breakIndex], needMultiply, measuredPara, bufEnd,
                                 includepad, trackpad, addLastLineSpacing, chs, widths.getRawArray(),
                                 paraStart, ellipsize, ellipsizedWidth, lineWidths[breakIndex],
                                 paint, moreChars);
@@ -894,8 +895,8 @@
 
             if ((bufEnd == bufStart || source.charAt(bufEnd - 1) == CHAR_NEW_LINE)
                     && mLineCount < mMaximumVisibleLineCount) {
-                final MeasuredText measured =
-                        MeasuredText.buildForBidi(source, bufEnd, bufEnd, textDir, null);
+                final MeasuredParagraph measuredPara =
+                        MeasuredParagraph.buildForBidi(source, bufEnd, bufEnd, textDir, null);
                 paint.getFontMetricsInt(fm);
                 v = out(source,
                         bufEnd, bufEnd, fm.ascent, fm.descent,
@@ -903,7 +904,7 @@
                         v,
                         spacingmult, spacingadd, null,
                         null, fm, 0,
-                        needMultiply, measured, bufEnd,
+                        needMultiply, measuredPara, bufEnd,
                         includepad, trackpad, addLastLineSpacing, null,
                         null, bufStart, ellipsize,
                         ellipsizedWidth, 0, paint, false);
@@ -918,7 +919,7 @@
     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, @NonNull final MeasuredText measured,
+            final int flags, final boolean needMultiply, @NonNull final MeasuredParagraph measured,
             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,
diff --git a/core/java/android/text/TextUtils.java b/core/java/android/text/TextUtils.java
index 9c9fbf2..409e514 100644
--- a/core/java/android/text/TextUtils.java
+++ b/core/java/android/text/TextUtils.java
@@ -1250,10 +1250,10 @@
             @NonNull String ellipsis) {
 
         final int len = text.length();
-        MeasuredText mt = null;
-        MeasuredText resultMt = null;
+        MeasuredParagraph mt = null;
+        MeasuredParagraph resultMt = null;
         try {
-            mt = MeasuredText.buildForMeasurement(paint, text, 0, text.length(), textDir, mt);
+            mt = MeasuredParagraph.buildForMeasurement(paint, text, 0, text.length(), textDir, mt);
             float width = mt.getWholeWidth();
 
             if (width <= avail) {
@@ -1332,7 +1332,7 @@
                 if (remaining == 0) { // All text is gone.
                     textFits = true;
                 } else {
-                    resultMt = MeasuredText.buildForMeasurement(
+                    resultMt = MeasuredParagraph.buildForMeasurement(
                             paint, result, 0, result.length(), textDir, resultMt);
                     width = resultMt.getWholeWidth();
                     if (width <= avail) {
@@ -1479,11 +1479,11 @@
     public static CharSequence commaEllipsize(CharSequence text, TextPaint p,
          float avail, String oneMore, String more, TextDirectionHeuristic textDir) {
 
-        MeasuredText mt = null;
-        MeasuredText tempMt = null;
+        MeasuredParagraph mt = null;
+        MeasuredParagraph tempMt = null;
         try {
             int len = text.length();
-            mt = MeasuredText.buildForMeasurement(p, text, 0, len, textDir, mt);
+            mt = MeasuredParagraph.buildForMeasurement(p, text, 0, len, textDir, mt);
             final float width = mt.getWholeWidth();
             if (width <= avail) {
                 return text;
@@ -1523,7 +1523,7 @@
                     }
 
                     // XXX this is probably ok, but need to look at it more
-                    tempMt = MeasuredText.buildForMeasurement(
+                    tempMt = MeasuredParagraph.buildForMeasurement(
                             p, format, 0, format.length(), textDir, tempMt);
                     float moreWid = tempMt.getWholeWidth();
 
diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java
index ff374fa..42c9eeb 100644
--- a/core/java/android/widget/TextView.java
+++ b/core/java/android/widget/TextView.java
@@ -77,8 +77,8 @@
 import android.text.InputFilter;
 import android.text.InputType;
 import android.text.Layout;
+import android.text.MeasuredText;
 import android.text.ParcelableSpan;
-import android.text.PremeasuredText;
 import android.text.Selection;
 import android.text.SpanWatcher;
 import android.text.Spannable;
@@ -5393,7 +5393,7 @@
             if (imm != null) imm.restartInput(this);
         } else if (type == BufferType.SPANNABLE || mMovement != null) {
             text = mSpannableFactory.newSpannable(text);
-        } else if (!(text instanceof PremeasuredText || text instanceof CharWrapper)) {
+        } else if (!(text instanceof MeasuredText || text instanceof CharWrapper)) {
             text = TextUtils.stringOrSpannedString(text);
         }
 
diff --git a/core/jni/Android.bp b/core/jni/Android.bp
index b3f66e9..96f3308 100644
--- a/core/jni/Android.bp
+++ b/core/jni/Android.bp
@@ -84,7 +84,7 @@
         "android_view_VelocityTracker.cpp",
         "android_text_AndroidCharacter.cpp",
         "android_text_Hyphenator.cpp",
-        "android_text_MeasuredText.cpp",
+        "android_text_MeasuredParagraph.cpp",
         "android_text_StaticLayout.cpp",
         "android_os_Debug.cpp",
         "android_os_GraphicsEnvironment.cpp",
diff --git a/core/jni/AndroidRuntime.cpp b/core/jni/AndroidRuntime.cpp
index 6d7fe05..6569b47 100644
--- a/core/jni/AndroidRuntime.cpp
+++ b/core/jni/AndroidRuntime.cpp
@@ -178,7 +178,7 @@
 extern int register_android_net_NetworkUtils(JNIEnv* env);
 extern int register_android_text_AndroidCharacter(JNIEnv *env);
 extern int register_android_text_Hyphenator(JNIEnv *env);
-extern int register_android_text_MeasuredText(JNIEnv* env);
+extern int register_android_text_MeasuredParagraph(JNIEnv* env);
 extern int register_android_text_StaticLayout(JNIEnv *env);
 extern int register_android_opengl_classes(JNIEnv *env);
 extern int register_android_ddm_DdmHandleNativeHeap(JNIEnv *env);
@@ -1342,7 +1342,7 @@
     REG_JNI(register_android_content_XmlBlock),
     REG_JNI(register_android_text_AndroidCharacter),
     REG_JNI(register_android_text_Hyphenator),
-    REG_JNI(register_android_text_MeasuredText),
+    REG_JNI(register_android_text_MeasuredParagraph),
     REG_JNI(register_android_text_StaticLayout),
     REG_JNI(register_android_view_InputDevice),
     REG_JNI(register_android_view_KeyCharacterMap),
diff --git a/core/jni/android_text_MeasuredText.cpp b/core/jni/android_text_MeasuredParagraph.cpp
similarity index 83%
rename from core/jni/android_text_MeasuredText.cpp
rename to core/jni/android_text_MeasuredParagraph.cpp
index af9d131..bdae0b2 100644
--- a/core/jni/android_text_MeasuredText.cpp
+++ b/core/jni/android_text_MeasuredParagraph.cpp
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-#define LOG_TAG "MeasuredText"
+#define LOG_TAG "MeasuredParagraph"
 
 #include "ScopedIcuLocale.h"
 #include "unicode/locid.h"
@@ -49,7 +49,7 @@
     return reinterpret_cast<Paint*>(ptr);
 }
 
-static inline minikin::MeasuredText* toMeasuredText(jlong ptr) {
+static inline minikin::MeasuredText* toMeasuredParagraph(jlong ptr) {
     return reinterpret_cast<minikin::MeasuredText*>(ptr);
 }
 
@@ -57,8 +57,8 @@
     return reinterpret_cast<jlong>(ptr);
 }
 
-static void releaseMeasuredText(jlong measuredTextPtr) {
-    delete toMeasuredText(measuredTextPtr);
+static void releaseMeasuredParagraph(jlong measuredTextPtr) {
+    delete toMeasuredParagraph(measuredTextPtr);
 }
 
 // Regular JNI
@@ -84,7 +84,7 @@
 }
 
 // Regular JNI
-static jlong nBuildNativeMeasuredText(JNIEnv* env, jclass /* unused */, jlong builderPtr,
+static jlong nBuildNativeMeasuredParagraph(JNIEnv* env, jclass /* unused */, jlong builderPtr,
                                       jcharArray javaText) {
     ScopedCharArrayRO text(env, javaText);
     const minikin::U16StringPiece textBuffer(text.get(), text.size());
@@ -100,23 +100,23 @@
 
 // CriticalNative
 static jlong nGetReleaseFunc() {
-    return toJLong(&releaseMeasuredText);
+    return toJLong(&releaseMeasuredParagraph);
 }
 
 static const JNINativeMethod gMethods[] = {
-    // MeasuredTextBuilder native functions.
+    // MeasuredParagraphBuilder native functions.
     {"nInitBuilder", "()J", (void*) nInitBuilder},
     {"nAddStyleRun", "(JJIIZ)V", (void*) nAddStyleRun},
     {"nAddReplacementRun", "(JJIIF)V", (void*) nAddReplacementRun},
-    {"nBuildNativeMeasuredText", "(J[C)J", (void*) nBuildNativeMeasuredText},
+    {"nBuildNativeMeasuredParagraph", "(J[C)J", (void*) nBuildNativeMeasuredParagraph},
     {"nFreeBuilder", "(J)V", (void*) nFreeBuilder},
 
-    // MeasuredText native functions.
+    // MeasuredParagraph native functions.
     {"nGetReleaseFunc", "()J", (void*) nGetReleaseFunc},  // Critical Natives
 };
 
-int register_android_text_MeasuredText(JNIEnv* env) {
-    return RegisterMethodsOrDie(env, "android/text/MeasuredText", gMethods, NELEM(gMethods));
+int register_android_text_MeasuredParagraph(JNIEnv* env) {
+    return RegisterMethodsOrDie(env, "android/text/MeasuredParagraph", gMethods, NELEM(gMethods));
 }
 
 }
diff --git a/core/jni/android_text_StaticLayout.cpp b/core/jni/android_text_StaticLayout.cpp
index b5c23df..682dc873 100644
--- a/core/jni/android_text_StaticLayout.cpp
+++ b/core/jni/android_text_StaticLayout.cpp
@@ -174,7 +174,7 @@
 
         // Inputs
         "[C"  // text
-        "J"  // MeasuredText ptr.
+        "J"  // MeasuredParagraph ptr.
         "I"  // length
         "F"  // firstWidth
         "I"  // firstWidthLineCount
diff --git a/core/tests/coretests/src/android/text/MeasuredTextTest.java b/core/tests/coretests/src/android/text/MeasuredParagraphTest.java
similarity index 87%
rename from core/tests/coretests/src/android/text/MeasuredTextTest.java
rename to core/tests/coretests/src/android/text/MeasuredParagraphTest.java
index ddef0c6..5d33397 100644
--- a/core/tests/coretests/src/android/text/MeasuredTextTest.java
+++ b/core/tests/coretests/src/android/text/MeasuredParagraphTest.java
@@ -31,7 +31,7 @@
 
 @SmallTest
 @RunWith(AndroidJUnit4.class)
-public class MeasuredTextTest {
+public class MeasuredParagraphTest {
     private static final TextDirectionHeuristic LTR = TextDirectionHeuristics.LTR;
     private static final TextDirectionHeuristic RTL = TextDirectionHeuristics.RTL;
 
@@ -60,9 +60,9 @@
 
     @Test
     public void buildForBidi() {
-        MeasuredText mt = null;
+        MeasuredParagraph mt = null;
 
-        mt = MeasuredText.buildForBidi("XXX", 0, 3, LTR, null);
+        mt = MeasuredParagraph.buildForBidi("XXX", 0, 3, LTR, null);
         assertNotNull(mt);
         assertNotNull(mt.getChars());
         assertEquals("XXX", charsToString(mt.getChars()));
@@ -75,7 +75,7 @@
         assertEquals(0, mt.getNativePtr());
 
         // Recycle it
-        MeasuredText mt2 = MeasuredText.buildForBidi("_VVV_", 1, 4, RTL, mt);
+        MeasuredParagraph mt2 = MeasuredParagraph.buildForBidi("_VVV_", 1, 4, RTL, mt);
         assertEquals(mt2, mt);
         assertNotNull(mt2.getChars());
         assertEquals("VVV", charsToString(mt.getChars()));
@@ -91,9 +91,9 @@
 
     @Test
     public void buildForMeasurement() {
-        MeasuredText mt = null;
+        MeasuredParagraph mt = null;
 
-        mt = MeasuredText.buildForMeasurement(PAINT, "XXX", 0, 3, LTR, null);
+        mt = MeasuredParagraph.buildForMeasurement(PAINT, "XXX", 0, 3, LTR, null);
         assertNotNull(mt);
         assertNotNull(mt.getChars());
         assertEquals("XXX", charsToString(mt.getChars()));
@@ -109,7 +109,8 @@
         assertEquals(0, mt.getNativePtr());
 
         // Recycle it
-        MeasuredText mt2 = MeasuredText.buildForMeasurement(PAINT, "_VVV_", 1, 4, RTL, mt);
+        MeasuredParagraph mt2 =
+                MeasuredParagraph.buildForMeasurement(PAINT, "_VVV_", 1, 4, RTL, mt);
         assertEquals(mt2, mt);
         assertNotNull(mt2.getChars());
         assertEquals("VVV", charsToString(mt.getChars()));
@@ -129,9 +130,9 @@
 
     @Test
     public void buildForStaticLayout() {
-        MeasuredText mt = null;
+        MeasuredParagraph mt = null;
 
-        mt = MeasuredText.buildForStaticLayout(PAINT, "XXX", 0, 3, LTR, null);
+        mt = MeasuredParagraph.buildForStaticLayout(PAINT, "XXX", 0, 3, LTR, null);
         assertNotNull(mt);
         assertNotNull(mt.getChars());
         assertEquals("XXX", charsToString(mt.getChars()));
@@ -145,7 +146,8 @@
         assertNotEquals(0, mt.getNativePtr());
 
         // Recycle it
-        MeasuredText mt2 = MeasuredText.buildForStaticLayout(PAINT, "_VVV_", 1, 4, RTL, mt);
+        MeasuredParagraph mt2 =
+                MeasuredParagraph.buildForStaticLayout(PAINT, "_VVV_", 1, 4, RTL, mt);
         assertEquals(mt2, mt);
         assertNotNull(mt2.getChars());
         assertEquals("VVV", charsToString(mt.getChars()));
@@ -163,6 +165,6 @@
 
     @Test
     public void testFor70146381() {
-        MeasuredText.buildForMeasurement(PAINT, "X…", 0, 2, RTL, null);
+        MeasuredParagraph.buildForMeasurement(PAINT, "X…", 0, 2, RTL, null);
     }
 }