Introduce PremeasuredText

By measuring the character widths beforehand, we can save at least 40%
of the StaticLayout construction time which typically happens on UI
thread.
Also verified this doesn't cause performance regression for not
premeasured text.

Raw performance score (Not premeasured -> premeasured, median, N=100)

No Style,   Greedy, Hyphenation OFF:  7,812,975 ->    503,245 (-93.6%)
No Style, Balanced, Hyphenation OFF:  7,843,254 ->    396,892 (-95.0%)

No Style,   Greedy, Hyphenation ON : 19,134,214 -> 11,658,928 (-39.1%)
No Style, Balanced, Hyphenation ON : 19,348,062 -> 11,634,942 (-39.9%)

Styled,     Greedy, Hyphenation OFF: 14,353,673 ->    572,840 (-96.0%)

Raw performance score (w/o patch -> w/ patch, median, N=100):

No Style,   Greedy, Hyphenation OFF:  7,732,894 ->  7,812,975 (+1.04%)
No Style, Balanced, Hyphenation OFF:  7,884,510 ->  7,843,254 (-0.52%)

No Style,   Greedy, Hyphenation ON : 18,986,958 -> 19,134,214 (+0.78%)
No Style, Balanced, Hyphenation ON : 19,232,791 -> 19,348,062 (+0.60%)

Styled,     Greedy, Hyphenation OFF: 14,319,690 -> 14,353,673 (+0.24%)

Bug: 67504091
Test: bit CtsTextTestCases:*
Test: bit CtsGraphicsTestCases:*
Test: bit CtsWidgetTestCases:*
Test: FrameworksCoreTests:android.text.MeasuredTextTest
Change-Id: I0b46f04b42cc012606a9c722eca0d51147a0dcc7
diff --git a/core/java/android/text/StaticLayout.java b/core/java/android/text/StaticLayout.java
index 53ddd16..2e10fe8 100644
--- a/core/java/android/text/StaticLayout.java
+++ b/core/java/android/text/StaticLayout.java
@@ -610,8 +610,8 @@
 
     /* package */ void generate(Builder b, boolean includepad, boolean trackpad) {
         final CharSequence source = b.mText;
-        int bufStart = b.mStart;
-        int bufEnd = b.mEnd;
+        final int bufStart = b.mStart;
+        final int bufEnd = b.mEnd;
         TextPaint paint = b.mPaint;
         int outerWidth = b.mWidth;
         TextDirectionHeuristic textDir = b.mTextDir;
@@ -634,10 +634,6 @@
         Paint.FontMetricsInt fm = b.mFontMetricsInt;
         int[] chooseHtv = null;
 
-        Spanned spanned = null;
-        if (source instanceof Spanned)
-            spanned = (Spanned) source;
-
         final int[] indents;
         if (mLeftIndents != null || mRightIndents != null) {
             final int leftLen = mLeftIndents == null ? 0 : mLeftIndents.length;
@@ -660,16 +656,34 @@
                 b.mJustificationMode != Layout.JUSTIFICATION_MODE_NONE,
                 indents, mLeftPaddings, mRightPaddings);
 
-        MeasuredText measured = null;
+        PremeasuredText premeasured = null;
+        final Spanned spanned;
+        if (source instanceof PremeasuredText) {
+            premeasured = (PremeasuredText) source;
+
+            final CharSequence original = premeasured.getText();
+            spanned = (original instanceof Spanned) ? (Spanned) original : null;
+
+            if (bufStart != premeasured.getStart() || bufEnd != premeasured.getEnd()) {
+                // The buffer position has changed. Re-measure here.
+                premeasured = PremeasuredText.build(original, paint, textDir, bufStart, bufEnd);
+            } else {
+                // We can use premeasured information.
+
+                // Overwrite with the one when premeasured.
+                // TODO: Give an option for developer not to overwrite and measure again here?
+                textDir = premeasured.getTextDir();
+                paint = premeasured.getPaint();
+            }
+        } else {
+            premeasured = PremeasuredText.build(source, paint, textDir, bufStart, bufEnd);
+            spanned = (source instanceof Spanned) ? (Spanned) source : null;
+        }
+
         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++;
-                }
+            for (int paraIndex = 0; paraIndex < premeasured.getParagraphCount(); paraIndex++) {
+                final int paraStart = premeasured.getParagraphStart(paraIndex);
+                final int paraEnd = premeasured.getParagraphEnd(paraIndex);
 
                 int firstWidthLineCount = 1;
                 int firstWidth = outerWidth;
@@ -735,8 +749,7 @@
                     }
                 }
 
-                measured = MeasuredText.buildForStaticLayout(
-                        paint, source, paraStart, paraEnd, textDir, measured);
+                final MeasuredText measured = premeasured.getMeasuredText(paraIndex);
                 final char[] chs = measured.getChars();
                 final int[] spanEndCache = measured.getSpanEndCache().getRawArray();
                 final int[] fmCache = measured.getFontMetrics().getRawArray();
@@ -887,7 +900,8 @@
 
             if ((bufEnd == bufStart || source.charAt(bufEnd - 1) == CHAR_NEW_LINE)
                     && mLineCount < mMaximumVisibleLineCount) {
-                measured = MeasuredText.buildForBidi(source, bufEnd, bufEnd, textDir, measured);
+                final MeasuredText measured =
+                        MeasuredText.buildForBidi(source, bufEnd, bufEnd, textDir, null);
                 paint.getFontMetricsInt(fm);
                 v = out(source,
                         bufEnd, bufEnd, fm.ascent, fm.descent,
@@ -901,9 +915,6 @@
                         ellipsizedWidth, 0, paint, false);
             }
         } finally {
-            if (measured != null) {
-                measured.recycle();
-            }
             nFinish(nativePtr);
         }
     }