Introduce NativeLineBreaker.Builder and ParagraphConstraint

To make NativeLineBreaker public API, introduce
NativeLineBreaker.Builder and ParagraphConstraint.

Here is a performance differences:

android.text.StaticLayoutPerfTest:
    PrecomputedText Balanced Hyphenation  :    635 ->    684: ( +49, +7.7%)
    PrecomputedText Balanced NoHyphenation:    477 ->    523: ( +46, +9.6%)
    PrecomputedText Greedy Hyphenation    :    413 ->    463: ( +50, +12.1%)
    PrecomputedText Greedy NoHyphenation  :    413 ->    463: ( +50, +12.1%)
    RandomText Balanced Hyphenation       : 18,030 -> 18,157: (+127, +0.7%)
    RandomText Balanced NoHyphenation     :  7,390 ->  7,388: (  -2, -0.0%)
    RandomText Greedy Hyphenation         :  7,319 ->  7,331: ( +12, +0.2%)
    RandomText Greedy NoHyphenation       :  7,316 ->  7,375: ( +59, +0.8%)
  draw
    PrecomputedText NoStyle               :    631 ->    627: (  -4, -0.6%)
    PrecomputedText Style                 :    842 ->    847: (  +5, +0.6%)
    RandomText NoStyle                    :    531 ->    547: ( +16, +3.0%)
    RandomText Style                      :    754 ->    758: (  +4, +0.5%)

Bug: 112327179
Test: atest CtsWidgetTestCases:EditTextTest
    CtsWidgetTestCases:TextViewFadingEdgeTest
    FrameworksCoreTests:TextViewFallbackLineSpacingTest
    FrameworksCoreTests:TextViewTest FrameworksCoreTests:TypefaceTest
    CtsGraphicsTestCases:TypefaceTest CtsWidgetTestCases:TextViewTest
    CtsTextTestCases FrameworksCoreTests:android.text
    CtsWidgetTestCases:TextViewPrecomputedTextTest
    CtsGraphicsTestCases:android.graphics.fonts

Change-Id: I5b817bbc9f00e5806efa98f239ee20ff3d688c50
diff --git a/core/java/android/text/Layout.java b/core/java/android/text/Layout.java
index ca952da..33c977b 100644
--- a/core/java/android/text/Layout.java
+++ b/core/java/android/text/Layout.java
@@ -50,9 +50,9 @@
 public abstract class Layout {
     /** @hide */
     @IntDef(prefix = { "BREAK_STRATEGY_" }, value = {
-            BREAK_STRATEGY_SIMPLE,
-            BREAK_STRATEGY_HIGH_QUALITY,
-            BREAK_STRATEGY_BALANCED
+            NativeLineBreaker.BREAK_STRATEGY_SIMPLE,
+            NativeLineBreaker.BREAK_STRATEGY_HIGH_QUALITY,
+            NativeLineBreaker.BREAK_STRATEGY_BALANCED
     })
     @Retention(RetentionPolicy.SOURCE)
     public @interface BreakStrategy {}
@@ -63,19 +63,20 @@
      * before it (which yields a more consistent user experience when editing), but layout may not
      * be the highest quality.
      */
-    public static final int BREAK_STRATEGY_SIMPLE = 0;
+    public static final int BREAK_STRATEGY_SIMPLE = NativeLineBreaker.BREAK_STRATEGY_SIMPLE;
 
     /**
      * Value for break strategy indicating high quality line breaking, including automatic
      * hyphenation and doing whole-paragraph optimization of line breaks.
      */
-    public static final int BREAK_STRATEGY_HIGH_QUALITY = 1;
+    public static final int BREAK_STRATEGY_HIGH_QUALITY =
+            NativeLineBreaker.BREAK_STRATEGY_HIGH_QUALITY;
 
     /**
      * Value for break strategy indicating balanced line breaking. The breaks are chosen to
      * make all lines as close to the same length as possible, including automatic hyphenation.
      */
-    public static final int BREAK_STRATEGY_BALANCED = 2;
+    public static final int BREAK_STRATEGY_BALANCED = NativeLineBreaker.BREAK_STRATEGY_BALANCED;
 
     /** @hide */
     @IntDef(prefix = { "HYPHENATION_FREQUENCY_" }, value = {
@@ -93,29 +94,32 @@
      * layout and there is otherwise no valid break. Soft hyphens are ignored and will not be used
      * as suggestions for potential line breaks.
      */
-    public static final int HYPHENATION_FREQUENCY_NONE = 0;
+    public static final int HYPHENATION_FREQUENCY_NONE =
+            NativeLineBreaker.HYPHENATION_FREQUENCY_NONE;
 
     /**
      * Value for hyphenation frequency indicating a light amount of automatic hyphenation, which
      * is a conservative default. Useful for informal cases, such as short sentences or chat
      * messages.
      */
-    public static final int HYPHENATION_FREQUENCY_NORMAL = 1;
+    public static final int HYPHENATION_FREQUENCY_NORMAL =
+            NativeLineBreaker.HYPHENATION_FREQUENCY_NORMAL;
 
     /**
      * Value for hyphenation frequency indicating the full amount of automatic hyphenation, typical
      * in typography. Useful for running text and where it's important to put the maximum amount of
      * text in a screen with limited space.
      */
-    public static final int HYPHENATION_FREQUENCY_FULL = 2;
+    public static final int HYPHENATION_FREQUENCY_FULL =
+            NativeLineBreaker.HYPHENATION_FREQUENCY_FULL;
 
     private static final ParagraphStyle[] NO_PARA_SPANS =
         ArrayUtils.emptyArray(ParagraphStyle.class);
 
     /** @hide */
     @IntDef(prefix = { "JUSTIFICATION_MODE_" }, value = {
-            JUSTIFICATION_MODE_NONE,
-            JUSTIFICATION_MODE_INTER_WORD
+            NativeLineBreaker.JUSTIFICATION_MODE_NONE,
+            NativeLineBreaker.JUSTIFICATION_MODE_INTER_WORD
     })
     @Retention(RetentionPolicy.SOURCE)
     public @interface JustificationMode {}
@@ -123,12 +127,13 @@
     /**
      * Value for justification mode indicating no justification.
      */
-    public static final int JUSTIFICATION_MODE_NONE = 0;
+    public static final int JUSTIFICATION_MODE_NONE = NativeLineBreaker.JUSTIFICATION_MODE_NONE;
 
     /**
      * Value for justification mode indicating the text is justified by stretching word spacing.
      */
-    public static final int JUSTIFICATION_MODE_INTER_WORD = 1;
+    public static final int JUSTIFICATION_MODE_INTER_WORD =
+            NativeLineBreaker.JUSTIFICATION_MODE_INTER_WORD;
 
     /*
      * Line spacing multiplier for default line spacing.
diff --git a/core/java/android/text/NativeLineBreaker.java b/core/java/android/text/NativeLineBreaker.java
index a31b336..2bcfa5f 100644
--- a/core/java/android/text/NativeLineBreaker.java
+++ b/core/java/android/text/NativeLineBreaker.java
@@ -17,6 +17,7 @@
 package android.text;
 
 import android.annotation.FloatRange;
+import android.annotation.IntDef;
 import android.annotation.IntRange;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
@@ -26,12 +27,233 @@
 
 import libcore.util.NativeAllocationRegistry;
 
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
 /**
  * A native implementation of the line breaker.
  * TODO: Consider to make this class public.
  * @hide
  */
 public class NativeLineBreaker {
+    @IntDef(prefix = { "BREAK_STRATEGY_" }, value = {
+            BREAK_STRATEGY_SIMPLE,
+            BREAK_STRATEGY_HIGH_QUALITY,
+            BREAK_STRATEGY_BALANCED
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface BreakStrategy {}
+
+    /**
+     * Value for break strategy indicating simple line breaking. Automatic hyphens are not added
+     * (though soft hyphens are respected), and modifying text generally doesn't affect the layout
+     * before it (which yields a more consistent user experience when editing), but layout may not
+     * be the highest quality.
+     */
+    public static final int BREAK_STRATEGY_SIMPLE = 0;
+
+    /**
+     * Value for break strategy indicating high quality line breaking, including automatic
+     * hyphenation and doing whole-paragraph optimization of line breaks.
+     */
+    public static final int BREAK_STRATEGY_HIGH_QUALITY = 1;
+
+    /**
+     * Value for break strategy indicating balanced line breaking. The breaks are chosen to
+     * make all lines as close to the same length as possible, including automatic hyphenation.
+     */
+    public static final int BREAK_STRATEGY_BALANCED = 2;
+
+    @IntDef(prefix = { "HYPHENATION_FREQUENCY_" }, value = {
+            HYPHENATION_FREQUENCY_NORMAL,
+            HYPHENATION_FREQUENCY_FULL,
+            HYPHENATION_FREQUENCY_NONE
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface HyphenationFrequency {}
+
+    /**
+     * Value for hyphenation frequency indicating no automatic hyphenation. Useful
+     * for backward compatibility, and for cases where the automatic hyphenation algorithm results
+     * in incorrect hyphenation. Mid-word breaks may still happen when a word is wider than the
+     * layout and there is otherwise no valid break. Soft hyphens are ignored and will not be used
+     * as suggestions for potential line breaks.
+     */
+    public static final int HYPHENATION_FREQUENCY_NONE = 0;
+
+    /**
+     * Value for hyphenation frequency indicating a light amount of automatic hyphenation, which
+     * is a conservative default. Useful for informal cases, such as short sentences or chat
+     * messages.
+     */
+    public static final int HYPHENATION_FREQUENCY_NORMAL = 1;
+
+    /**
+     * Value for hyphenation frequency indicating the full amount of automatic hyphenation, typical
+     * in typography. Useful for running text and where it's important to put the maximum amount of
+     * text in a screen with limited space.
+     */
+    public static final int HYPHENATION_FREQUENCY_FULL = 2;
+
+    @IntDef(prefix = { "JUSTIFICATION_MODE_" }, value = {
+            JUSTIFICATION_MODE_NONE,
+            JUSTIFICATION_MODE_INTER_WORD
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface JustificationMode {}
+
+    /**
+     * Value for justification mode indicating no justification.
+     */
+    public static final int JUSTIFICATION_MODE_NONE = 0;
+
+    /**
+     * Value for justification mode indicating the text is justified by stretching word spacing.
+     */
+    public static final int JUSTIFICATION_MODE_INTER_WORD = 1;
+
+    /**
+     * A builder class of NativeLineBreaker.
+     */
+    public static class Builder {
+        private @BreakStrategy int mBreakStrategy = BREAK_STRATEGY_SIMPLE;
+        private @HyphenationFrequency int mHyphenationFrequency = HYPHENATION_FREQUENCY_NONE;
+        private @JustificationMode int mJustified = JUSTIFICATION_MODE_NONE;
+        private @Nullable int[] mIndents = null;
+
+        /**
+         * Construct a builder class.
+         */
+        public Builder() {}
+
+        /**
+         * Set break strategy.
+         */
+        public Builder setBreakStrategy(@BreakStrategy int breakStrategy) {
+            mBreakStrategy = breakStrategy;
+            return this;
+        }
+
+        /**
+         * Set hyphenation frequency.
+         */
+        public Builder setHyphenationFrequency(@HyphenationFrequency int hyphenationFrequency) {
+            mHyphenationFrequency = hyphenationFrequency;
+            return this;
+        }
+
+        /**
+         * Set whether the text is justified.
+         */
+        public Builder setJustified(@JustificationMode int justified) {
+            mJustified = justified;
+            return this;
+        }
+
+        /**
+         * Set indents for entire text.
+         *
+         * Sets the total (left + right) indents in pixel per lines.
+         */
+        public Builder setIndents(@Nullable int[] indents) {
+            mIndents = indents;
+            return this;
+        }
+
+        /**
+         * Returns the NativeLineBreaker with given parameters.
+         */
+        NativeLineBreaker build() {
+            return new NativeLineBreaker(mBreakStrategy, mHyphenationFrequency, mJustified,
+                    mIndents);
+        }
+    }
+
+    /**
+     * Line breaking constraints for single paragraph.
+     */
+    public static class ParagraphConstraints {
+        private @FloatRange(from = 0.0f) float mWidth = 0;
+        private @FloatRange(from = 0.0f) float mFirstWidth = 0;
+        private @IntRange(from = 0) int mFirstWidthLineCount = 0;
+        private @Nullable int[] mVariableTabStops = null;
+        private @IntRange(from = 0) int mDefaultTabStop = 0;
+
+        public ParagraphConstraints() {}
+
+        /**
+         * Set width for this paragraph.
+         */
+        public void setWidth(@FloatRange(from = 0.0f) float width) {
+            mWidth = width;
+        }
+
+        /**
+         * Set indent for this paragraph.
+         *
+         * @param firstWidth the line width of the starting of the paragraph
+         * @param firstWidthLineCount the number of lines that applies the firstWidth
+         */
+        public void setIndent(@FloatRange(from = 0.0f) float firstWidth,
+                @IntRange(from = 0) int firstWidthLineCount) {
+            mFirstWidth = firstWidth;
+            mFirstWidthLineCount = firstWidthLineCount;
+        }
+
+        /**
+         * Set tab stops for this paragraph.
+         *
+         * @param tabStops the array of pixels of tap stopping position
+         * @param defaultTabStop pixels of the default tab stopping position
+         */
+        public void setTabStops(@Nullable int[] tabStops, @IntRange(from = 0) int defaultTabStop) {
+            mVariableTabStops = tabStops;
+            mDefaultTabStop = defaultTabStop;
+        }
+
+        /**
+         * Return the width for this paragraph in pixels.
+         */
+        public @FloatRange(from = 0.0f) float getWidth() {
+            return mWidth;
+        }
+
+        /**
+         * Return the first line's width for this paragraph in pixel.
+         *
+         * @see #setIndent(float, int)
+         */
+        public @FloatRange(from = 0.0f) float getFirstWidth() {
+            return mFirstWidth;
+        }
+
+        /**
+         * Return the number of lines to apply the first line's width.
+         *
+         * @see #setIndent(float, int)
+         */
+        public @IntRange(from = 0) int getFirstWidthLineCount() {
+            return mFirstWidthLineCount;
+        }
+
+        /**
+         * Returns the array of tab stops in pixels.
+         *
+         * @see #setTabStops(int[], int)
+         */
+        public @Nullable int[] getTabStops() {
+            return mVariableTabStops;
+        }
+
+        /**
+         * Returns the default tab stops in pixels.
+         *
+         * @see #setTabStop(int[], int)
+         */
+        public @IntRange(from = 0) int getDefaultTabStop() {
+            return mDefaultTabStop;
+        }
+    }
 
     /**
      * A result object of a line breaking
@@ -43,6 +265,7 @@
         public float[] widths = new float[INITIAL_SIZE];
         public float[] ascents = new float[INITIAL_SIZE];
         public float[] descents = new float[INITIAL_SIZE];
+        // TODO: Introduce Hyphenator for explaining the meaning of flags.
         public int[] flags = new int[INITIAL_SIZE];
         // breaks, widths, and flags should all have the same length
     }
@@ -53,54 +276,44 @@
     private final long mNativePtr;
 
     /**
-     * A constructor of NativeLineBreaker
+     * Use Builder instead.
      */
-    public NativeLineBreaker(@Layout.BreakStrategy int breakStrategy,
-            @Layout.HyphenationFrequency int hyphenationFrequency,
-            boolean justify, @Nullable int[] indents) {
-        mNativePtr = nInit(breakStrategy, hyphenationFrequency, justify, indents);
+    private NativeLineBreaker(@BreakStrategy int breakStrategy,
+            @HyphenationFrequency int hyphenationFrequency, @JustificationMode int justify,
+            @Nullable int[] indents) {
+        mNativePtr = nInit(breakStrategy, hyphenationFrequency,
+                justify == JUSTIFICATION_MODE_INTER_WORD, indents);
         sRegistry.registerNativeAllocation(this, mNativePtr);
     }
 
     /**
-     * Break text into lines
+     * Break paragraph into lines.
      *
-     * @param chars an array of characters
+     * The result is filled to out param.
+     *
      * @param measuredPara a result of the text measurement
-     * @param length a length of the target text from the begining
-     * @param firstWidth a width of the first width of the line in this paragraph
-     * @param firstWidthLineCount a number of lines that has the length of the firstWidth
-     * @param restWidth a width of the rest of the lines.
-     * @param variableTabStops an array of tab stop widths
-     * @param defaultTabStop a width of the tab stop
-     * @param indentsOffset an offset of the indents to be used.
-     * @param out output buffer
-     * @return a number of the lines
+     * @param constraints for a single paragraph
+     * @param lineNumber a line number of this paragraph
+     * @param out object to set line break information for the given paragraph
      */
-    @NonNull public int computeLineBreaks(
-            @NonNull char[] chars,
+    public void computeLineBreaks(
             @NonNull NativeMeasuredParagraph measuredPara,
-            @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,
+            @NonNull ParagraphConstraints constraints,
+            @IntRange(from = 0) int lineNumber,
             @NonNull LineBreaks out) {
-        return nComputeLineBreaks(
+        out.breakCount = nComputeLineBreaks(
                 mNativePtr,
 
                 // Inputs
-                chars,
+                measuredPara.getChars(),
                 measuredPara.getNativePtr(),
-                length,
-                firstWidth,
-                firstWidthLineCount,
-                restWidth,
-                variableTabStops,
-                defaultTabStop,
-                indentsOffset,
+                measuredPara.getChars().length,
+                constraints.mFirstWidth,
+                constraints.mFirstWidthLineCount,
+                constraints.mWidth,
+                constraints.mVariableTabStops,
+                constraints.mDefaultTabStop,
+                lineNumber,
 
                 // Outputs
                 out,
@@ -114,10 +327,8 @@
     }
 
     @FastNative
-    private static native long nInit(
-            @Layout.BreakStrategy int breakStrategy,
-            @Layout.HyphenationFrequency int hyphenationFrequency,
-            boolean isJustified,
+    private static native long nInit(@BreakStrategy int breakStrategy,
+            @HyphenationFrequency int hyphenationFrequency, boolean isJustified,
             @Nullable int[] indents);
 
     @CriticalNative
diff --git a/core/java/android/text/NativeMeasuredParagraph.java b/core/java/android/text/NativeMeasuredParagraph.java
index d03674f..bfdccca 100644
--- a/core/java/android/text/NativeMeasuredParagraph.java
+++ b/core/java/android/text/NativeMeasuredParagraph.java
@@ -36,10 +36,19 @@
             NativeMeasuredParagraph.class.getClassLoader(), nGetReleaseFunc(), 1024);
 
     private long mNativePtr;
+    private @NonNull char[] mChars;
 
     // Use builder instead.
-    private NativeMeasuredParagraph(long ptr) {
+    private NativeMeasuredParagraph(long ptr, @NonNull char[] chars) {
         mNativePtr = ptr;
+        mChars = chars;
+    }
+
+    /**
+     * Returns a characters of this paragraph.
+     */
+    public char[] getChars() {
+        return mChars;
     }
 
     /**
@@ -126,7 +135,7 @@
             try {
                 long ptr = nBuildNativeMeasuredParagraph(mNativePtr, text, computeHyphenation,
                         computeLayout);
-                NativeMeasuredParagraph res = new NativeMeasuredParagraph(ptr);
+                NativeMeasuredParagraph res = new NativeMeasuredParagraph(ptr, text);
                 sRegistry.registerNativeAllocation(res, ptr);
                 return res;
             } finally {
diff --git a/core/java/android/text/StaticLayout.java b/core/java/android/text/StaticLayout.java
index dc01178..128f860 100644
--- a/core/java/android/text/StaticLayout.java
+++ b/core/java/android/text/StaticLayout.java
@@ -626,11 +626,16 @@
             indents = null;
         }
 
-        final NativeLineBreaker lineBreaker = new NativeLineBreaker(
-                b.mBreakStrategy, b.mHyphenationFrequency,
+        final NativeLineBreaker lineBreaker = new NativeLineBreaker.Builder()
+                .setBreakStrategy(b.mBreakStrategy)
+                .setHyphenationFrequency(b.mHyphenationFrequency)
                 // TODO: Support more justification mode, e.g. letter spacing, stretching.
-                b.mJustificationMode != Layout.JUSTIFICATION_MODE_NONE,
-                indents);
+                .setJustified(b.mJustificationMode)
+                .setIndents(indents)
+                .build();
+
+        NativeLineBreaker.ParagraphConstraints constraints =
+                new NativeLineBreaker.ParagraphConstraints();
 
         PrecomputedText.ParagraphInfo[] paragraphInfo = null;
         final Spanned spanned = (source instanceof Spanned) ? (Spanned) source : null;
@@ -721,18 +726,14 @@
             final char[] chs = measuredPara.getChars();
             final int[] spanEndCache = measuredPara.getSpanEndCache().getRawArray();
             final int[] fmCache = measuredPara.getFontMetrics().getRawArray();
-            int breakCount = lineBreaker.computeLineBreaks(
-                    measuredPara.getChars(),
-                    measuredPara.getNativeMeasuredParagraph(),
-                    paraEnd - paraStart,
-                    firstWidth,
-                    firstWidthLineCount,
-                    restWidth,
-                    variableTabStops,
-                    TAB_INCREMENT,
-                    mLineCount,
-                    lineBreaks);
 
+            constraints.setWidth(restWidth);
+            constraints.setIndent(firstWidth, firstWidthLineCount);
+            constraints.setTabStops(variableTabStops, TAB_INCREMENT);
+
+            lineBreaker.computeLineBreaks(measuredPara.getNativeMeasuredParagraph(),
+                    constraints, mLineCount, lineBreaks);
+            int breakCount = lineBreaks.breakCount;
             final int[] breaks = lineBreaks.breaks;
             final float[] lineWidths = lineBreaks.widths;
             final float[] ascents = lineBreaks.ascents;