Add hyphenationFrequency attribute to TextView and StaticLayout.

This patch adds plumbing to TextView and StaticLayout to control the
frequency of automatic hyphenation used in laying out paragraphs.

Bug: 21038249
Change-Id: Ib45de190eb0a1ed738e69fd61f2b39561b11aec7
diff --git a/api/current.txt b/api/current.txt
index c307199..9ff983f 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -653,6 +653,7 @@
     field public static final int horizontalScrollViewStyle = 16843603; // 0x1010353
     field public static final int horizontalSpacing = 16843028; // 0x1010114
     field public static final int host = 16842792; // 0x1010028
+    field public static final int hyphenationFrequency = 16844024; // 0x10104f8
     field public static final int icon = 16842754; // 0x1010002
     field public static final int iconPreview = 16843337; // 0x1010249
     field public static final int iconTint = 16843999; // 0x10104df
@@ -32166,6 +32167,9 @@
     field public static final int BREAK_STRATEGY_SIMPLE = 0; // 0x0
     field public static final int DIR_LEFT_TO_RIGHT = 1; // 0x1
     field public static final int DIR_RIGHT_TO_LEFT = -1; // 0xffffffff
+    field public static final int HYPHENATION_FREQUENCY_FULL = 2; // 0x2
+    field public static final int HYPHENATION_FREQUENCY_NONE = 0; // 0x0
+    field public static final int HYPHENATION_FREQUENCY_NORMAL = 1; // 0x1
   }
 
   public static final class Layout.Alignment extends java.lang.Enum {
@@ -32364,6 +32368,7 @@
     method public android.text.StaticLayout.Builder setBreakStrategy(int);
     method public android.text.StaticLayout.Builder setEllipsize(android.text.TextUtils.TruncateAt);
     method public android.text.StaticLayout.Builder setEllipsizedWidth(int);
+    method public android.text.StaticLayout.Builder setHyphenationFrequency(int);
     method public android.text.StaticLayout.Builder setIncludePad(boolean);
     method public android.text.StaticLayout.Builder setIndents(int[], int[]);
     method public android.text.StaticLayout.Builder setLineSpacing(float, float);
@@ -41446,6 +41451,7 @@
     method public int getHighlightColor();
     method public java.lang.CharSequence getHint();
     method public final android.content.res.ColorStateList getHintTextColors();
+    method public int getHyphenationFrequency();
     method public int getImeActionId();
     method public java.lang.CharSequence getImeActionLabel();
     method public int getImeOptions();
@@ -41552,6 +41558,7 @@
     method public final void setHintTextColor(int);
     method public final void setHintTextColor(android.content.res.ColorStateList);
     method public void setHorizontallyScrolling(boolean);
+    method public void setHyphenationFrequency(int);
     method public void setImeActionLabel(java.lang.CharSequence, int);
     method public void setImeOptions(int);
     method public void setIncludeFontPadding(boolean);
diff --git a/api/system-current.txt b/api/system-current.txt
index 57b4c6c..f3fc88d 100644
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -725,6 +725,7 @@
     field public static final int horizontalScrollViewStyle = 16843603; // 0x1010353
     field public static final int horizontalSpacing = 16843028; // 0x1010114
     field public static final int host = 16842792; // 0x1010028
+    field public static final int hyphenationFrequency = 16844024; // 0x10104f8
     field public static final int icon = 16842754; // 0x1010002
     field public static final int iconPreview = 16843337; // 0x1010249
     field public static final int iconTint = 16843999; // 0x10104df
@@ -34388,6 +34389,9 @@
     field public static final int BREAK_STRATEGY_SIMPLE = 0; // 0x0
     field public static final int DIR_LEFT_TO_RIGHT = 1; // 0x1
     field public static final int DIR_RIGHT_TO_LEFT = -1; // 0xffffffff
+    field public static final int HYPHENATION_FREQUENCY_FULL = 2; // 0x2
+    field public static final int HYPHENATION_FREQUENCY_NONE = 0; // 0x0
+    field public static final int HYPHENATION_FREQUENCY_NORMAL = 1; // 0x1
   }
 
   public static final class Layout.Alignment extends java.lang.Enum {
@@ -34586,6 +34590,7 @@
     method public android.text.StaticLayout.Builder setBreakStrategy(int);
     method public android.text.StaticLayout.Builder setEllipsize(android.text.TextUtils.TruncateAt);
     method public android.text.StaticLayout.Builder setEllipsizedWidth(int);
+    method public android.text.StaticLayout.Builder setHyphenationFrequency(int);
     method public android.text.StaticLayout.Builder setIncludePad(boolean);
     method public android.text.StaticLayout.Builder setIndents(int[], int[]);
     method public android.text.StaticLayout.Builder setLineSpacing(float, float);
@@ -43979,6 +43984,7 @@
     method public int getHighlightColor();
     method public java.lang.CharSequence getHint();
     method public final android.content.res.ColorStateList getHintTextColors();
+    method public int getHyphenationFrequency();
     method public int getImeActionId();
     method public java.lang.CharSequence getImeActionLabel();
     method public int getImeOptions();
@@ -44085,6 +44091,7 @@
     method public final void setHintTextColor(int);
     method public final void setHintTextColor(android.content.res.ColorStateList);
     method public void setHorizontallyScrolling(boolean);
+    method public void setHyphenationFrequency(int);
     method public void setImeActionLabel(java.lang.CharSequence, int);
     method public void setImeOptions(int);
     method public void setIncludeFontPadding(boolean);
diff --git a/core/java/android/text/DynamicLayout.java b/core/java/android/text/DynamicLayout.java
index fc65f63..e99a960 100644
--- a/core/java/android/text/DynamicLayout.java
+++ b/core/java/android/text/DynamicLayout.java
@@ -79,7 +79,8 @@
                          boolean includepad,
                          TextUtils.TruncateAt ellipsize, int ellipsizedWidth) {
         this(base, display, paint, width, align, TextDirectionHeuristics.FIRSTSTRONG_LTR,
-                spacingmult, spacingadd, includepad, StaticLayout.BREAK_STRATEGY_SIMPLE,
+                spacingmult, spacingadd, includepad,
+                StaticLayout.BREAK_STRATEGY_SIMPLE, StaticLayout.HYPHENATION_FREQUENCY_NONE,
                 ellipsize, ellipsizedWidth);
     }
 
@@ -96,7 +97,7 @@
                          TextPaint paint,
                          int width, Alignment align, TextDirectionHeuristic textDir,
                          float spacingmult, float spacingadd,
-                         boolean includepad, int breakStrategy,
+                         boolean includepad, int breakStrategy, int hyphenationFrequency,
                          TextUtils.TruncateAt ellipsize, int ellipsizedWidth) {
         super((ellipsize == null)
                 ? display
@@ -122,6 +123,7 @@
 
         mIncludePad = includepad;
         mBreakStrategy = breakStrategy;
+        mHyphenationFrequency = hyphenationFrequency;
 
         /*
          * This is annoying, but we can't refer to the layout until
@@ -293,7 +295,8 @@
                 .setLineSpacing(getSpacingAdd(), getSpacingMultiplier())
                 .setEllipsizedWidth(mEllipsizedWidth)
                 .setEllipsize(mEllipsizeAt)
-                .setBreakStrategy(mBreakStrategy);
+                .setBreakStrategy(mBreakStrategy)
+                .setHyphenationFrequency(mHyphenationFrequency);
         reflowed.generate(b, false, true);
         int n = reflowed.getLineCount();
 
@@ -719,6 +722,7 @@
     private int mEllipsizedWidth;
     private TextUtils.TruncateAt mEllipsizeAt;
     private int mBreakStrategy;
+    private int mHyphenationFrequency;
 
     private PackedIntVector mInts;
     private PackedObjectVector<Directions> mObjects;
diff --git a/core/java/android/text/Layout.java b/core/java/android/text/Layout.java
index 60de02a..f176240 100644
--- a/core/java/android/text/Layout.java
+++ b/core/java/android/text/Layout.java
@@ -71,6 +71,35 @@
      */
     public static final int BREAK_STRATEGY_BALANCED = 2;
 
+    /** @hide */
+    @IntDef({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;
+
     private static final ParagraphStyle[] NO_PARA_SPANS =
         ArrayUtils.emptyArray(ParagraphStyle.class);
 
diff --git a/core/java/android/text/StaticLayout.java b/core/java/android/text/StaticLayout.java
index 59c7c6d..d6d046b 100644
--- a/core/java/android/text/StaticLayout.java
+++ b/core/java/android/text/StaticLayout.java
@@ -92,6 +92,7 @@
             b.mEllipsize = null;
             b.mMaxLines = Integer.MAX_VALUE;
             b.mBreakStrategy = Layout.BREAK_STRATEGY_SIMPLE;
+            b.mHyphenationFrequency = Layout.HYPHENATION_FREQUENCY_NONE;
 
             b.mMeasuredText = MeasuredText.obtain();
             return b;
@@ -276,6 +277,19 @@
         }
 
         /**
+         * Set hyphenation frequency, to control the amount of automatic hyphenation used. The
+         * default is {@link Layout#HYPHENATION_FREQUENCY_NONE}.
+         *
+         * @param hyphenationFrequency hyphenation frequency for the paragraph
+         * @return this builder, useful for chaining
+         * @see android.widget.TextView#setHyphenationFrequency
+         */
+        public Builder setHyphenationFrequency(@HyphenationFrequency int hyphenationFrequency) {
+            mHyphenationFrequency = hyphenationFrequency;
+            return this;
+        }
+
+        /**
          * Set indents. Arguments are arrays holding an indent amount, one per line, measured in
          * pixels. For lines past the last element in the array, the last element repeats.
          *
@@ -302,7 +316,8 @@
          * the native code is as follows.
          *
          * For each paragraph, do a nSetupParagraph, which sets paragraph text, line width, tab
-         * stops, break strategy (and possibly other parameters in the future).
+         * stops, break strategy, and hyphenation frequency (and possibly other parameters in the
+         * future).
          *
          * Then, for each run within the paragraph:
          *  - setLocale (this must be done at least for the first run, optional afterwards)
@@ -377,6 +392,7 @@
         TextUtils.TruncateAt mEllipsize;
         int mMaxLines;
         int mBreakStrategy;
+        int mHyphenationFrequency;
 
         Paint.FontMetricsInt mFontMetricsInt = new Paint.FontMetricsInt();
 
@@ -644,7 +660,7 @@
 
             nSetupParagraph(b.mNativePtr, chs, paraEnd - paraStart,
                     firstWidth, firstWidthLineCount, restWidth,
-                    variableTabStops, TAB_INCREMENT, b.mBreakStrategy);
+                    variableTabStops, TAB_INCREMENT, b.mBreakStrategy, b.mHyphenationFrequency);
 
             // measurement has to be done before performing line breaking
             // but we don't want to recompute fontmetrics or span ranges the
@@ -1153,7 +1169,7 @@
     // Set up paragraph text and settings; done as one big method to minimize jni crossings
     private static native void nSetupParagraph(long nativePtr, char[] text, int length,
             float firstWidth, int firstWidthLineCount, float restWidth,
-            int[] variableTabStops, int defaultTabStop, int breakStrategy);
+            int[] variableTabStops, int defaultTabStop, int breakStrategy, int hyphenationFrequency);
 
     private static native float nAddStyleRun(long nativePtr, long nativePaint,
             long nativeTypeface, int start, int end, boolean isRtl);
diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java
index 68c49cd..a989392 100644
--- a/core/java/android/widget/TextView.java
+++ b/core/java/android/widget/TextView.java
@@ -238,6 +238,7 @@
  * @attr ref android.R.styleable#TextView_letterSpacing
  * @attr ref android.R.styleable#TextView_fontFeatureSettings
  * @attr ref android.R.styleable#TextView_breakStrategy
+ * @attr ref android.R.styleable#TextView_hyphenationFrequency
  * @attr ref android.R.styleable#TextView_leftIndents
  * @attr ref android.R.styleable#TextView_rightIndents
  */
@@ -555,6 +556,7 @@
     private float mSpacingAdd = 0.0f;
 
     private int mBreakStrategy;
+    private int mHyphenationFrequency;
     private int[] mLeftIndents;
     private int[] mRightIndents;
 
@@ -696,6 +698,7 @@
         float letterSpacing = 0;
         String fontFeatureSettings = null;
         mBreakStrategy = Layout.BREAK_STRATEGY_SIMPLE;
+        mHyphenationFrequency = Layout.HYPHENATION_FREQUENCY_NONE;
 
         final Resources.Theme theme = context.getTheme();
 
@@ -1154,6 +1157,10 @@
                 mBreakStrategy = a.getInt(attr, Layout.BREAK_STRATEGY_SIMPLE);
                 break;
 
+            case com.android.internal.R.styleable.TextView_hyphenationFrequency:
+                mHyphenationFrequency = a.getInt(attr, Layout.HYPHENATION_FREQUENCY_NONE);
+                break;
+
             case com.android.internal.R.styleable.TextView_leftIndents:
                 TypedArray margins = res.obtainTypedArray(a.getResourceId(attr, View.NO_ID));
                 mLeftIndents = parseDimensionArray(margins);
@@ -3050,6 +3057,33 @@
     }
 
     /**
+     * Sets the hyphenation frequency. The default value for both TextView and EditText, which is set
+     * from the theme, is {@link Layout#HYPHENATION_FREQUENCY_NORMAL}.
+     *
+     * @attr ref android.R.styleable#TextView_hyphenationFrequency
+     * @see #getHyphenationFrequency()
+     */
+    public void setHyphenationFrequency(@Layout.HyphenationFrequency int hyphenationFrequency) {
+        mHyphenationFrequency = hyphenationFrequency;
+        if (mLayout != null) {
+            nullLayouts();
+            requestLayout();
+            invalidate();
+        }
+    }
+
+    /**
+     * @return the currently set hyphenation frequency.
+     *
+     * @attr ref android.R.styleable#TextView_hyphenationFrequency
+     * @see #setHyphenationFrequency(int)
+     */
+    @Layout.HyphenationFrequency
+    public int getHyphenationFrequency() {
+        return mHyphenationFrequency;
+    }
+
+    /**
      * Set indents. Arguments are arrays holding an indent amount, one per line, measured in
      * pixels. For lines past the last element in the array, the last element repeats.
      *
@@ -6637,7 +6671,8 @@
                         .setTextDir(mTextDir)
                         .setLineSpacing(mSpacingAdd, mSpacingMult)
                         .setIncludePad(mIncludePad)
-                        .setBreakStrategy(mBreakStrategy);
+                        .setBreakStrategy(mBreakStrategy)
+                        .setHyphenationFrequency(mHyphenationFrequency);
                 if (mLeftIndents != null || mRightIndents != null) {
                     builder.setIndents(mLeftIndents, mRightIndents);
                 }
@@ -6678,7 +6713,8 @@
         Layout result = null;
         if (mText instanceof Spannable) {
             result = new DynamicLayout(mText, mTransformed, mTextPaint, wantWidth,
-                    alignment, mTextDir, mSpacingMult, mSpacingAdd, mIncludePad, mBreakStrategy,
+                    alignment, mTextDir, mSpacingMult, mSpacingAdd, mIncludePad,
+                    mBreakStrategy, mHyphenationFrequency,
                     getKeyListener() == null ? effectiveEllipsize : null, ellipsisWidth);
         } else {
             if (boring == UNKNOWN_BORING) {
@@ -6726,7 +6762,8 @@
                     .setTextDir(mTextDir)
                     .setLineSpacing(mSpacingAdd, mSpacingMult)
                     .setIncludePad(mIncludePad)
-                    .setBreakStrategy(mBreakStrategy);
+                    .setBreakStrategy(mBreakStrategy)
+                    .setHyphenationFrequency(mHyphenationFrequency);
             if (mLeftIndents != null || mRightIndents != null) {
                 builder.setIndents(mLeftIndents, mRightIndents);
             }
diff --git a/core/jni/android_text_StaticLayout.cpp b/core/jni/android_text_StaticLayout.cpp
index 5e73ef2..90e4bb6 100644
--- a/core/jni/android_text_StaticLayout.cpp
+++ b/core/jni/android_text_StaticLayout.cpp
@@ -48,10 +48,11 @@
 static jclass gLineBreaks_class;
 static JLineBreaksID gLineBreaks_fieldID;
 
-// set text and set a number of parameters for creating a layout (width, tabstops, strategy)
+// set text and set a number of parameters for creating a layout (width, tabstops, strategy,
+// hyphenFrequency)
 static void nSetupParagraph(JNIEnv* env, jclass, jlong nativePtr, jcharArray text, jint length,
         jfloat firstWidth, jint firstWidthLineLimit, jfloat restWidth,
-        jintArray variableTabStops, jint defaultTabStop, jint strategy) {
+        jintArray variableTabStops, jint defaultTabStop, jint strategy, jint hyphenFrequency) {
     LineBreaker* b = reinterpret_cast<LineBreaker*>(nativePtr);
     b->resize(length);
     env->GetCharArrayRegion(text, 0, length, b->buffer());
@@ -64,6 +65,7 @@
         b->setTabStops(stops.get(), stops.size(), defaultTabStop);
     }
     b->setStrategy(static_cast<BreakStrategy>(strategy));
+    b->setHyphenationFrequency(static_cast<HyphenationFrequency>(hyphenFrequency));
 }
 
 static void recycleCopy(JNIEnv* env, jobject recycle, jintArray recycleBreaks,
@@ -177,7 +179,7 @@
     {"nFinishBuilder", "(J)V", (void*) nFinishBuilder},
     {"nLoadHyphenator", "(Ljava/lang/String;)J", (void*) nLoadHyphenator},
     {"nSetLocale", "(JLjava/lang/String;J)V", (void*) nSetLocale},
-    {"nSetupParagraph", "(J[CIFIF[III)V", (void*) nSetupParagraph},
+    {"nSetupParagraph", "(J[CIFIF[IIII)V", (void*) nSetupParagraph},
     {"nSetIndents", "(J[I)V", (void*) nSetIndents},
     {"nAddStyleRun", "(JJJIIZ)F", (void*) nAddStyleRun},
     {"nAddMeasuredRun", "(JII[F)V", (void*) nAddMeasuredRun},
diff --git a/core/res/res/values/attrs.xml b/core/res/res/values/attrs.xml
index 0b96d22..726401c 100644
--- a/core/res/res/values/attrs.xml
+++ b/core/res/res/values/attrs.xml
@@ -4369,6 +4369,17 @@
             <!-- Line breaking stratgegy balances line lengths. -->
             <enum name="balanced" value="2" />
         </attr>
+        <!-- Frequency of automatic hyphenation. -->
+        <attr name="hyphenationFrequency">
+            <!-- No hyphenation. -->
+            <enum name="none" value="0" />
+            <!-- Less frequent hyphenation, useful for informal use cases, such
+            as chat messages. -->
+            <enum name="normal" value="1" />
+            <!-- Standard amount of hyphenation, useful for running text and for
+            screens with limited space for text. -->
+            <enum name="full" value="2" />
+        </attr>
         <!-- Array of indents, one dimension value per line, left side. -->
         <attr name="leftIndents" format="reference" />
         <!-- Array of indents, one dimension value per line, right side. -->
diff --git a/core/res/res/values/public.xml b/core/res/res/values/public.xml
index e403a16..b60a333 100644
--- a/core/res/res/values/public.xml
+++ b/core/res/res/values/public.xml
@@ -2688,4 +2688,5 @@
   <public type="attr" name="stylusButtonPressable" />
   <public type="attr" name="supportsLaunchVoiceAssistFromKeyguard" />
   <public type="attr" name="scrollIndicators" />
+  <public type="attr" name="hyphenationFrequency" />
 </resources>
diff --git a/core/res/res/values/styles.xml b/core/res/res/values/styles.xml
index 3c3d286..4c02d79 100644
--- a/core/res/res/values/styles.xml
+++ b/core/res/res/values/styles.xml
@@ -498,6 +498,7 @@
         <item name="textEditSuggestionItemLayout">?attr/textEditSuggestionItemLayout</item>
         <item name="textCursorDrawable">?attr/textCursorDrawable</item>
         <item name="breakStrategy">high_quality</item>
+        <item name="hyphenationFrequency">normal</item>
     </style>
 
     <style name="Widget.CheckedTextView">
@@ -529,6 +530,7 @@
         <item name="textColor">?attr/editTextColor</item>
         <item name="gravity">center_vertical</item>
         <item name="breakStrategy">simple</item>
+        <item name="hyphenationFrequency">normal</item>
     </style>
 
     <style name="Widget.ExpandableListView" parent="Widget.ListView">