Reorganize JNI in StaticLayout

This CL reorganize the JNI strategy as follows:
- Remove nNewBuilder/nFreeBuilder.
- Make addStyleRun/addReplacementRun CriticalNative.
- Remove nSetupParagraph and pass necessary arguments to
  nComputeLineBreaks instead.

Here is a performance scores: (w/o patch -> w/ patch)

StaticLayoutPerfTest (median):
createRandom:           3,755,090 -> 3,781,394 (+0.70%)
createRandom Balanced:  3,702,837 -> 3,730,435 (+0.74%)

TextViewOnMeasurePerfTest (median):
measure_AtMost:        39,172,360 -> 35,883,014 (-8.4%)
measure_Exactly:       38,005,066 -> 34,585,052 (-9.0%)
measure_Unspecified:   67,191,780 -> 63,100,545 (-6.1%)

Bug: 65024629
Test: bit CtsTextTestCases:*
Test: bit CtsWidgetTestCases:*
Change-Id: If9db1fdd2b03a1cf240f87322c1e852cf8085fff
diff --git a/core/java/android/text/StaticLayout.java b/core/java/android/text/StaticLayout.java
index 5c60188..c0fc44f 100644
--- a/core/java/android/text/StaticLayout.java
+++ b/core/java/android/text/StaticLayout.java
@@ -32,6 +32,9 @@
 import com.android.internal.util.ArrayUtils;
 import com.android.internal.util.GrowingArrayUtils;
 
+import dalvik.annotation.optimization.CriticalNative;
+import dalvik.annotation.optimization.FastNative;
+
 import java.util.Arrays;
 
 /**
@@ -57,9 +60,7 @@
      * default values.
      */
     public final static class Builder {
-        private Builder() {
-            mNativePtr = nNewBuilder();
-        }
+        private Builder() {}
 
         /**
          * Obtain a builder for constructing StaticLayout objects.
@@ -116,13 +117,11 @@
             b.mRightIndents = null;
             b.mLeftPaddings = null;
             b.mRightPaddings = null;
-            nFinishBuilder(b.mNativePtr);
             sPool.release(b);
         }
 
         // release any expensive state
         /* package */ void finish() {
-            nFinishBuilder(mNativePtr);
             mText = null;
             mPaint = null;
             mLeftIndents = null;
@@ -405,32 +404,6 @@
         }
 
         /**
-         * Measurement and break iteration is done in native code. The protocol for using
-         * the native code is as follows.
-         *
-         * For each paragraph, do a nSetupParagraph, which sets paragraph text, line width, tab
-         * stops, break strategy, and hyphenation frequency (and possibly other parameters in the
-         * future).
-         *
-         * Then, for each run within the paragraph:
-         *  - one of the following, depending on the type of run:
-         *    + addStyleRun (a text run, to be measured in native code)
-         *    + addReplacementRun (a replacement run, width is given)
-         *
-         * Run nComputeLineBreaks() to obtain line breaks for the paragraph.
-         *
-         * After all paragraphs, call finish() to release expensive buffers.
-         */
-
-        /* package */ void addStyleRun(TextPaint paint, int start, int end, boolean isRtl) {
-            nAddStyleRun(mNativePtr, paint.getNativeInstance(), start, end, isRtl);
-        }
-
-        /* package */ void addReplacementRun(TextPaint paint, int start, int end, float width) {
-            nAddReplacementRun(mNativePtr, paint.getNativeInstance(), start, end, width);
-        }
-
-        /**
          * Build the {@link StaticLayout} after options have been set.
          *
          * <p>Note: the builder object must not be reused in any way after calling this
@@ -446,17 +419,6 @@
             return result;
         }
 
-        @Override
-        protected void finalize() throws Throwable {
-            try {
-                nFreeBuilder(mNativePtr);
-            } finally {
-                super.finalize();
-            }
-        }
-
-        /* package */ long mNativePtr;
-
         private CharSequence mText;
         private int mStart;
         private int mEnd;
@@ -694,270 +656,294 @@
             indents = null;
         }
 
-        int paraEnd;
-        for (int paraStart = bufStart; paraStart <= bufEnd; paraStart = paraEnd) {
-            paraEnd = TextUtils.indexOf(source, CHAR_NEW_LINE, paraStart, bufEnd);
-            if (paraEnd < 0)
-                paraEnd = bufEnd;
-            else
-                paraEnd++;
+        final long nativePtr = nInit(
+                b.mBreakStrategy, b.mHyphenationFrequency,
+                // TODO: Support more justification mode, e.g. letter spacing, stretching.
+                b.mJustificationMode != Layout.JUSTIFICATION_MODE_NONE,
+                indents, mLeftPaddings, mRightPaddings);
 
-            int firstWidthLineCount = 1;
-            int firstWidth = outerWidth;
-            int restWidth = outerWidth;
+        try {
+            int paraEnd;
+            for (int paraStart = bufStart; paraStart <= bufEnd; paraStart = paraEnd) {
+                paraEnd = TextUtils.indexOf(source, CHAR_NEW_LINE, paraStart, bufEnd);
+                if (paraEnd < 0) {
+                    paraEnd = bufEnd;
+                } else {
+                    paraEnd++;
+                }
 
-            LineHeightSpan[] chooseHt = null;
+                int firstWidthLineCount = 1;
+                int firstWidth = outerWidth;
+                int restWidth = outerWidth;
 
-            if (spanned != null) {
-                LeadingMarginSpan[] sp = getParagraphSpans(spanned, paraStart, paraEnd,
-                        LeadingMarginSpan.class);
-                for (int i = 0; i < sp.length; i++) {
-                    LeadingMarginSpan lms = sp[i];
-                    firstWidth -= sp[i].getLeadingMargin(true);
-                    restWidth -= sp[i].getLeadingMargin(false);
+                LineHeightSpan[] chooseHt = null;
 
-                    // LeadingMarginSpan2 is odd.  The count affects all
-                    // leading margin spans, not just this particular one
-                    if (lms instanceof LeadingMarginSpan2) {
-                        LeadingMarginSpan2 lms2 = (LeadingMarginSpan2) lms;
-                        firstWidthLineCount = Math.max(firstWidthLineCount,
-                                lms2.getLeadingMarginLineCount());
+                if (spanned != null) {
+                    LeadingMarginSpan[] sp = getParagraphSpans(spanned, paraStart, paraEnd,
+                            LeadingMarginSpan.class);
+                    for (int i = 0; i < sp.length; i++) {
+                        LeadingMarginSpan lms = sp[i];
+                        firstWidth -= sp[i].getLeadingMargin(true);
+                        restWidth -= sp[i].getLeadingMargin(false);
+
+                        // LeadingMarginSpan2 is odd.  The count affects all
+                        // leading margin spans, not just this particular one
+                        if (lms instanceof LeadingMarginSpan2) {
+                            LeadingMarginSpan2 lms2 = (LeadingMarginSpan2) lms;
+                            firstWidthLineCount = Math.max(firstWidthLineCount,
+                                    lms2.getLeadingMarginLineCount());
+                        }
+                    }
+
+                    chooseHt = getParagraphSpans(spanned, paraStart, paraEnd, LineHeightSpan.class);
+
+                    if (chooseHt.length == 0) {
+                        chooseHt = null; // So that out() would not assume it has any contents
+                    } else {
+                        if (chooseHtv == null || chooseHtv.length < chooseHt.length) {
+                            chooseHtv = ArrayUtils.newUnpaddedIntArray(chooseHt.length);
+                        }
+
+                        for (int i = 0; i < chooseHt.length; i++) {
+                            int o = spanned.getSpanStart(chooseHt[i]);
+
+                            if (o < paraStart) {
+                                // starts in this layout, before the
+                                // current paragraph
+
+                                chooseHtv[i] = getLineTop(getLineForOffset(o));
+                            } else {
+                                // starts in this paragraph
+
+                                chooseHtv[i] = v;
+                            }
+                        }
                     }
                 }
 
-                chooseHt = getParagraphSpans(spanned, paraStart, paraEnd, LineHeightSpan.class);
+                measured.setPara(source, paraStart, paraEnd, textDir);
+                char[] chs = measured.mChars;
+                float[] widths = measured.mWidths;
+                byte[] chdirs = measured.mLevels;
+                int dir = measured.mDir;
+                boolean easy = measured.mEasy;
 
-                if (chooseHt.length == 0) {
-                    chooseHt = null; // So that out() would not assume it has any contents
-                } else {
-                    if (chooseHtv == null ||
-                        chooseHtv.length < chooseHt.length) {
-                        chooseHtv = ArrayUtils.newUnpaddedIntArray(chooseHt.length);
+                // tab stop locations
+                int[] variableTabStops = null;
+                if (spanned != null) {
+                    TabStopSpan[] spans = getParagraphSpans(spanned, paraStart,
+                            paraEnd, TabStopSpan.class);
+                    if (spans.length > 0) {
+                        int[] stops = new int[spans.length];
+                        for (int i = 0; i < spans.length; i++) {
+                            stops[i] = spans[i].getTabStop();
+                        }
+                        Arrays.sort(stops, 0, stops.length);
+                        variableTabStops = stops;
+                    }
+                }
+
+                // measurement has to be done before performing line breaking
+                // but we don't want to recompute fontmetrics or span ranges the
+                // second time, so we cache those and then use those stored values
+                int fmCacheCount = 0;
+                int spanEndCacheCount = 0;
+                for (int spanStart = paraStart, spanEnd; spanStart < paraEnd; spanStart = spanEnd) {
+                    if (fmCacheCount * 4 >= fmCache.length) {
+                        int[] grow = new int[fmCacheCount * 4 * 2];
+                        System.arraycopy(fmCache, 0, grow, 0, fmCacheCount * 4);
+                        fmCache = grow;
                     }
 
-                    for (int i = 0; i < chooseHt.length; i++) {
-                        int o = spanned.getSpanStart(chooseHt[i]);
+                    if (spanEndCacheCount >= spanEndCache.length) {
+                        int[] grow = new int[spanEndCacheCount * 2];
+                        System.arraycopy(spanEndCache, 0, grow, 0, spanEndCacheCount);
+                        spanEndCache = grow;
+                    }
 
-                        if (o < paraStart) {
-                            // starts in this layout, before the
-                            // current paragraph
+                    if (spanned == null) {
+                        spanEnd = paraEnd;
+                        int spanLen = spanEnd - spanStart;
+                        measured.addStyleRun(paint, spanLen, fm, nativePtr);
+                    } else {
+                        spanEnd = spanned.nextSpanTransition(spanStart, paraEnd,
+                                MetricAffectingSpan.class);
+                        int spanLen = spanEnd - spanStart;
+                        MetricAffectingSpan[] spans =
+                                spanned.getSpans(spanStart, spanEnd, MetricAffectingSpan.class);
+                        spans = TextUtils.removeEmptySpans(spans, spanned,
+                                MetricAffectingSpan.class);
+                        measured.addStyleRun(paint, spans, spanLen, fm, nativePtr);
+                    }
 
-                            chooseHtv[i] = getLineTop(getLineForOffset(o));
+                    // the order of storage here (top, bottom, ascent, descent) has to match the
+                    // code below where these values are retrieved
+                    fmCache[fmCacheCount * 4 + 0] = fm.top;
+                    fmCache[fmCacheCount * 4 + 1] = fm.bottom;
+                    fmCache[fmCacheCount * 4 + 2] = fm.ascent;
+                    fmCache[fmCacheCount * 4 + 3] = fm.descent;
+                    fmCacheCount++;
+
+                    spanEndCache[spanEndCacheCount] = spanEnd;
+                    spanEndCacheCount++;
+                }
+
+                int breakCount = nComputeLineBreaks(
+                        nativePtr,
+
+                        // Inputs
+                        chs,
+                        paraEnd - paraStart,
+                        firstWidth,
+                        firstWidthLineCount,
+                        restWidth,
+                        variableTabStops,
+                        TAB_INCREMENT,
+                        mLineCount,
+
+                        // Outputs
+                        lineBreaks,
+                        lineBreaks.breaks.length,
+                        lineBreaks.breaks,
+                        lineBreaks.widths,
+                        lineBreaks.ascents,
+                        lineBreaks.descents,
+                        lineBreaks.flags,
+                        widths);
+
+                final int[] breaks = lineBreaks.breaks;
+                final float[] lineWidths = lineBreaks.widths;
+                final float[] ascents = lineBreaks.ascents;
+                final float[] descents = lineBreaks.descents;
+                final int[] flags = lineBreaks.flags;
+
+                final int remainingLineCount = mMaximumVisibleLineCount - mLineCount;
+                final boolean ellipsisMayBeApplied = ellipsize != null
+                        && (ellipsize == TextUtils.TruncateAt.END
+                            || (mMaximumVisibleLineCount == 1
+                                    && ellipsize != TextUtils.TruncateAt.MARQUEE));
+                if (0 < remainingLineCount && remainingLineCount < breakCount
+                        && ellipsisMayBeApplied) {
+                    // Calculate width and flag.
+                    float width = 0;
+                    int flag = 0; // XXX May need to also have starting hyphen edit
+                    for (int i = remainingLineCount - 1; i < breakCount; i++) {
+                        if (i == breakCount - 1) {
+                            width += lineWidths[i];
                         } else {
-                            // starts in this paragraph
-
-                            chooseHtv[i] = v;
+                            for (int j = (i == 0 ? 0 : breaks[i - 1]); j < breaks[i]; j++) {
+                                width += widths[j];
+                            }
                         }
+                        flag |= flags[i] & TAB_MASK;
                     }
-                }
-            }
+                    // Treat the last line and overflowed lines as a single line.
+                    breaks[remainingLineCount - 1] = breaks[breakCount - 1];
+                    lineWidths[remainingLineCount - 1] = width;
+                    flags[remainingLineCount - 1] = flag;
 
-            measured.setPara(source, paraStart, paraEnd, textDir, b);
-            char[] chs = measured.mChars;
-            float[] widths = measured.mWidths;
-            byte[] chdirs = measured.mLevels;
-            int dir = measured.mDir;
-            boolean easy = measured.mEasy;
-
-            // tab stop locations
-            int[] variableTabStops = null;
-            if (spanned != null) {
-                TabStopSpan[] spans = getParagraphSpans(spanned, paraStart,
-                        paraEnd, TabStopSpan.class);
-                if (spans.length > 0) {
-                    int[] stops = new int[spans.length];
-                    for (int i = 0; i < spans.length; i++) {
-                        stops[i] = spans[i].getTabStop();
-                    }
-                    Arrays.sort(stops, 0, stops.length);
-                    variableTabStops = stops;
-                }
-            }
-
-            nSetupParagraph(b.mNativePtr, chs, paraEnd - paraStart,
-                    firstWidth, firstWidthLineCount, restWidth,
-                    variableTabStops, TAB_INCREMENT, b.mBreakStrategy, b.mHyphenationFrequency,
-                    // TODO: Support more justification mode, e.g. letter spacing, stretching.
-                    b.mJustificationMode != Layout.JUSTIFICATION_MODE_NONE,
-                    // TODO: indents and paddings don't need to get passed to native code for every
-                    // paragraph. Pass them to native code just once.
-                    indents, mLeftPaddings, mRightPaddings, mLineCount);
-
-            // measurement has to be done before performing line breaking
-            // but we don't want to recompute fontmetrics or span ranges the
-            // second time, so we cache those and then use those stored values
-            int fmCacheCount = 0;
-            int spanEndCacheCount = 0;
-            for (int spanStart = paraStart, spanEnd; spanStart < paraEnd; spanStart = spanEnd) {
-                if (fmCacheCount * 4 >= fmCache.length) {
-                    int[] grow = new int[fmCacheCount * 4 * 2];
-                    System.arraycopy(fmCache, 0, grow, 0, fmCacheCount * 4);
-                    fmCache = grow;
+                    breakCount = remainingLineCount;
                 }
 
-                if (spanEndCacheCount >= spanEndCache.length) {
-                    int[] grow = new int[spanEndCacheCount * 2];
-                    System.arraycopy(spanEndCache, 0, grow, 0, spanEndCacheCount);
-                    spanEndCache = grow;
-                }
+                // here is the offset of the starting character of the line we are currently
+                // measuring
+                int here = paraStart;
 
-                if (spanned == null) {
-                    spanEnd = paraEnd;
-                    int spanLen = spanEnd - spanStart;
-                    measured.addStyleRun(paint, spanLen, fm);
-                } else {
-                    spanEnd = spanned.nextSpanTransition(spanStart, paraEnd,
-                            MetricAffectingSpan.class);
-                    int spanLen = spanEnd - spanStart;
-                    MetricAffectingSpan[] spans =
-                            spanned.getSpans(spanStart, spanEnd, MetricAffectingSpan.class);
-                    spans = TextUtils.removeEmptySpans(spans, spanned, MetricAffectingSpan.class);
-                    measured.addStyleRun(paint, spans, spanLen, fm);
-                }
+                int fmTop = 0, fmBottom = 0, fmAscent = 0, fmDescent = 0;
+                int fmCacheIndex = 0;
+                int spanEndCacheIndex = 0;
+                int breakIndex = 0;
+                for (int spanStart = paraStart, spanEnd; spanStart < paraEnd; spanStart = spanEnd) {
+                    // retrieve end of span
+                    spanEnd = spanEndCache[spanEndCacheIndex++];
 
-                // the order of storage here (top, bottom, ascent, descent) has to match the code below
-                // where these values are retrieved
-                fmCache[fmCacheCount * 4 + 0] = fm.top;
-                fmCache[fmCacheCount * 4 + 1] = fm.bottom;
-                fmCache[fmCacheCount * 4 + 2] = fm.ascent;
-                fmCache[fmCacheCount * 4 + 3] = fm.descent;
-                fmCacheCount++;
+                    // retrieve cached metrics, order matches above
+                    fm.top = fmCache[fmCacheIndex * 4 + 0];
+                    fm.bottom = fmCache[fmCacheIndex * 4 + 1];
+                    fm.ascent = fmCache[fmCacheIndex * 4 + 2];
+                    fm.descent = fmCache[fmCacheIndex * 4 + 3];
+                    fmCacheIndex++;
 
-                spanEndCache[spanEndCacheCount] = spanEnd;
-                spanEndCacheCount++;
-            }
-
-            int breakCount = nComputeLineBreaks(b.mNativePtr, lineBreaks, lineBreaks.breaks,
-                    lineBreaks.widths, lineBreaks.ascents, lineBreaks.descents, lineBreaks.flags,
-                    lineBreaks.breaks.length, widths);
-
-            final int[] breaks = lineBreaks.breaks;
-            final float[] lineWidths = lineBreaks.widths;
-            final float[] ascents = lineBreaks.ascents;
-            final float[] descents = lineBreaks.descents;
-            final int[] flags = lineBreaks.flags;
-
-            final int remainingLineCount = mMaximumVisibleLineCount - mLineCount;
-            final boolean ellipsisMayBeApplied = ellipsize != null
-                    && (ellipsize == TextUtils.TruncateAt.END
-                        || (mMaximumVisibleLineCount == 1
-                                && ellipsize != TextUtils.TruncateAt.MARQUEE));
-            if (0 < remainingLineCount && remainingLineCount < breakCount
-                    && ellipsisMayBeApplied) {
-                // Calculate width and flag.
-                float width = 0;
-                int flag = 0; // XXX May need to also have starting hyphen edit
-                for (int i = remainingLineCount - 1; i < breakCount; i++) {
-                    if (i == breakCount - 1) {
-                        width += lineWidths[i];
-                    } else {
-                        for (int j = (i == 0 ? 0 : breaks[i - 1]); j < breaks[i]; j++) {
-                            width += widths[j];
-                        }
-                    }
-                    flag |= flags[i] & TAB_MASK;
-                }
-                // Treat the last line and overflowed lines as a single line.
-                breaks[remainingLineCount - 1] = breaks[breakCount - 1];
-                lineWidths[remainingLineCount - 1] = width;
-                flags[remainingLineCount - 1] = flag;
-
-                breakCount = remainingLineCount;
-            }
-
-            // here is the offset of the starting character of the line we are currently measuring
-            int here = paraStart;
-
-            int fmTop = 0, fmBottom = 0, fmAscent = 0, fmDescent = 0;
-            int fmCacheIndex = 0;
-            int spanEndCacheIndex = 0;
-            int breakIndex = 0;
-            for (int spanStart = paraStart, spanEnd; spanStart < paraEnd; spanStart = spanEnd) {
-                // retrieve end of span
-                spanEnd = spanEndCache[spanEndCacheIndex++];
-
-                // retrieve cached metrics, order matches above
-                fm.top = fmCache[fmCacheIndex * 4 + 0];
-                fm.bottom = fmCache[fmCacheIndex * 4 + 1];
-                fm.ascent = fmCache[fmCacheIndex * 4 + 2];
-                fm.descent = fmCache[fmCacheIndex * 4 + 3];
-                fmCacheIndex++;
-
-                if (fm.top < fmTop) {
-                    fmTop = fm.top;
-                }
-                if (fm.ascent < fmAscent) {
-                    fmAscent = fm.ascent;
-                }
-                if (fm.descent > fmDescent) {
-                    fmDescent = fm.descent;
-                }
-                if (fm.bottom > fmBottom) {
-                    fmBottom = fm.bottom;
-                }
-
-                // skip breaks ending before current span range
-                while (breakIndex < breakCount && paraStart + breaks[breakIndex] < spanStart) {
-                    breakIndex++;
-                }
-
-                while (breakIndex < breakCount && paraStart + breaks[breakIndex] <= spanEnd) {
-                    int endPos = paraStart + breaks[breakIndex];
-
-                    boolean moreChars = (endPos < bufEnd);
-
-                    final int ascent = fallbackLineSpacing
-                            ? Math.min(fmAscent, Math.round(ascents[breakIndex]))
-                            : fmAscent;
-                    final int descent = fallbackLineSpacing
-                            ? Math.max(fmDescent, Math.round(descents[breakIndex]))
-                            : fmDescent;
-                    v = out(source, here, endPos,
-                            ascent, descent, fmTop, fmBottom,
-                            v, spacingmult, spacingadd, chooseHt, chooseHtv, fm, flags[breakIndex],
-                            needMultiply, chdirs, dir, easy, bufEnd, includepad, trackpad,
-                            addLastLineSpacing, chs, widths, paraStart, ellipsize,
-                            ellipsizedWidth, lineWidths[breakIndex], paint, moreChars);
-
-                    if (endPos < spanEnd) {
-                        // preserve metrics for current span
+                    if (fm.top < fmTop) {
                         fmTop = fm.top;
-                        fmBottom = fm.bottom;
+                    }
+                    if (fm.ascent < fmAscent) {
                         fmAscent = fm.ascent;
+                    }
+                    if (fm.descent > fmDescent) {
                         fmDescent = fm.descent;
-                    } else {
-                        fmTop = fmBottom = fmAscent = fmDescent = 0;
+                    }
+                    if (fm.bottom > fmBottom) {
+                        fmBottom = fm.bottom;
                     }
 
-                    here = endPos;
-                    breakIndex++;
-
-                    if (mLineCount >= mMaximumVisibleLineCount && mEllipsized) {
-                        return;
+                    // skip breaks ending before current span range
+                    while (breakIndex < breakCount && paraStart + breaks[breakIndex] < spanStart) {
+                        breakIndex++;
                     }
+
+                    while (breakIndex < breakCount && paraStart + breaks[breakIndex] <= spanEnd) {
+                        int endPos = paraStart + breaks[breakIndex];
+
+                        boolean moreChars = (endPos < bufEnd);
+
+                        final int ascent = fallbackLineSpacing
+                                ? Math.min(fmAscent, Math.round(ascents[breakIndex]))
+                                : fmAscent;
+                        final int descent = fallbackLineSpacing
+                                ? Math.max(fmDescent, Math.round(descents[breakIndex]))
+                                : fmDescent;
+                        v = out(source, here, endPos,
+                                ascent, descent, fmTop, fmBottom,
+                                v, spacingmult, spacingadd, chooseHt, chooseHtv, fm,
+                                flags[breakIndex], needMultiply, chdirs, dir, easy, bufEnd,
+                                includepad, trackpad, addLastLineSpacing, chs, widths, paraStart,
+                                ellipsize, ellipsizedWidth, lineWidths[breakIndex], paint,
+                                moreChars);
+
+                        if (endPos < spanEnd) {
+                            // preserve metrics for current span
+                            fmTop = fm.top;
+                            fmBottom = fm.bottom;
+                            fmAscent = fm.ascent;
+                            fmDescent = fm.descent;
+                        } else {
+                            fmTop = fmBottom = fmAscent = fmDescent = 0;
+                        }
+
+                        here = endPos;
+                        breakIndex++;
+
+                        if (mLineCount >= mMaximumVisibleLineCount && mEllipsized) {
+                            return;
+                        }
+                    }
+                }
+
+                if (paraEnd == bufEnd) {
+                    break;
                 }
             }
 
-            if (paraEnd == bufEnd)
-                break;
-        }
+            if ((bufEnd == bufStart || source.charAt(bufEnd - 1) == CHAR_NEW_LINE)
+                    && mLineCount < mMaximumVisibleLineCount) {
+                measured.setPara(source, bufEnd, bufEnd, textDir);
 
-        if ((bufEnd == bufStart || source.charAt(bufEnd - 1) == CHAR_NEW_LINE) &&
-                mLineCount < mMaximumVisibleLineCount) {
-            measured.setPara(source, bufEnd, bufEnd, textDir, b);
+                paint.getFontMetricsInt(fm);
 
-            paint.getFontMetricsInt(fm);
-
-            v = out(source,
-                    bufEnd, bufEnd, fm.ascent, fm.descent,
-                    fm.top, fm.bottom,
-                    v,
-                    spacingmult, spacingadd, null,
-                    null, fm, 0,
-                    needMultiply, measured.mLevels, measured.mDir, measured.mEasy, bufEnd,
-                    includepad, trackpad, addLastLineSpacing, null,
-                    null, bufStart, ellipsize,
-                    ellipsizedWidth, 0, paint, false);
+                v = out(source,
+                        bufEnd, bufEnd, fm.ascent, fm.descent,
+                        fm.top, fm.bottom,
+                        v,
+                        spacingmult, spacingadd, null,
+                        null, fm, 0,
+                        needMultiply, measured.mLevels, measured.mDir, measured.mEasy, bufEnd,
+                        includepad, trackpad, addLastLineSpacing, null,
+                        null, bufStart, ellipsize,
+                        ellipsizedWidth, 0, paint, false);
+            }
+        } finally {
+            nFinish(nativePtr);
         }
     }
 
@@ -1487,26 +1473,51 @@
                 mMaxLineHeight : super.getHeight();
     }
 
-    private static native long nNewBuilder();
-    private static native void nFreeBuilder(long nativePtr);
-    private static native void nFinishBuilder(long nativePtr);
+    /**
+     * Measurement and break iteration is done in native code. The protocol for using
+     * the native code is as follows.
+     *
+     * First, call nInit to setup native line breaker object. Then, for each paragraph, do the
+     * following:
+     *
+     *   - Call one of the following methods for each run within the paragraph depending on the type
+     *     of run:
+     *     + addStyleRun (a text run, to be measured in native code)
+     *     + addReplacementRun (a replacement run, width is given)
+     *
+     *   - Run nComputeLineBreaks() to obtain line breaks for the paragraph.
+     *
+     * After all paragraphs, call finish() to release expensive buffers.
+     */
 
-    // Set up paragraph text and settings; done as one big method to minimize jni crossings
-    private static native void nSetupParagraph(
-            /* non zero */ long nativePtr, @NonNull char[] text, @IntRange(from = 0) int length,
-            @FloatRange(from = 0.0f) float firstWidth, @IntRange(from = 0) int firstWidthLineCount,
-            @FloatRange(from = 0.0f) float restWidth, @Nullable int[] variableTabStops,
-            int defaultTabStop, @BreakStrategy int breakStrategy,
-            @HyphenationFrequency int hyphenationFrequency, boolean isJustified,
-            @Nullable int[] indents, @Nullable int[] leftPaddings, @Nullable int[] rightPaddings,
-            @IntRange(from = 0) int indentsOffset);
+    /* package */ static void addStyleRun(long nativePtr, TextPaint paint, int start, int end,
+            boolean isRtl) {
+        nAddStyleRun(nativePtr, paint.getNativeInstance(), start, end, isRtl);
+    }
 
-    // TODO: Make this method CriticalNative once native code defers doing layouts.
+    /* package */ static void addReplacementRun(long nativePtr, TextPaint paint, int start, int end,
+            float width) {
+        nAddReplacementRun(nativePtr, paint.getNativeInstance(), start, end, width);
+    }
+
+    @FastNative
+    private static native long nInit(
+            @BreakStrategy int breakStrategy,
+            @HyphenationFrequency int hyphenationFrequency,
+            boolean isJustified,
+            @Nullable int[] indents,
+            @Nullable int[] leftPaddings,
+            @Nullable int[] rightPaddings);
+
+    @CriticalNative
+    private static native void nFinish(long nativePtr);
+
+    @CriticalNative
     private static native void nAddStyleRun(
             /* non-zero */ long nativePtr, /* non-zero */ long nativePaint,
             @IntRange(from = 0) int start, @IntRange(from = 0) int end, boolean isRtl);
 
-    // TODO: Make this method CriticalNative once native code defers doing layouts.
+    @CriticalNative
     private static native void nAddReplacementRun(
             /* non-zero */ long nativePtr, /* non-zero */ long nativePaint,
             @IntRange(from = 0) int start, @IntRange(from = 0) int end,
@@ -1519,10 +1530,28 @@
     // arrays do not have to be resized
     // The individual character widths will be returned in charWidths. The length of charWidths must
     // be at least the length of the text.
-    private static native int nComputeLineBreaks(long nativePtr, LineBreaks recycle,
-            int[] recycleBreaks, float[] recycleWidths, float[] recycleAscents,
-            float[] recycleDescents, int[] recycleFlags, int recycleLength,
-            float[] charWidths);
+    private static native int nComputeLineBreaks(
+            /* non zero */ long nativePtr,
+
+            // Inputs
+            @NonNull char[] text,
+            @IntRange(from = 0) int length,
+            @FloatRange(from = 0.0f) float firstWidth,
+            @IntRange(from = 0) int firstWidthLineCount,
+            @FloatRange(from = 0.0f) float restWidth,
+            @Nullable int[] variableTabStops,
+            int defaultTabStop,
+            @IntRange(from = 0) int indentsOffset,
+
+            // Outputs
+            @NonNull LineBreaks recycle,
+            @IntRange(from  = 0) int recycleLength,
+            @NonNull int[] recycleBreaks,
+            @NonNull float[] recycleWidths,
+            @NonNull float[] recycleAscents,
+            @NonNull float[] recycleDescents,
+            @NonNull int[] recycleFlags,
+            @NonNull float[] charWidths);
 
     private int mLineCount;
     private int mTopPadding, mBottomPadding;