Add line break word style parameter in the LineBreakConfig

The line break word style(lw) provides the phrase-based breaking opportunities. When the line break word style is set, it will be brought to ICU for calculation.

Bug: 183780874
Test: atest minikin_tests; atest TextViewTest; atest MeasuredTextTest; atest PrecomputedTextTest
Change-Id: Idd851497e46c1fca87ff590230d93f8bb5c9afae
diff --git a/core/api/current.txt b/core/api/current.txt
index 688b66e..1bc3e81 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -950,6 +950,7 @@
     field public static final int letterSpacing = 16843958; // 0x10104b6
     field public static final int level = 16844032; // 0x1010500
     field public static final int lineBreakStyle = 16844365; // 0x101064d
+    field public static final int lineBreakWordStyle = 16844366; // 0x101064e
     field public static final int lineHeight = 16844159; // 0x101057f
     field public static final int lineSpacingExtra = 16843287; // 0x1010217
     field public static final int lineSpacingMultiplier = 16843288; // 0x1010218
@@ -17581,12 +17582,16 @@
   public final class LineBreakConfig {
     ctor public LineBreakConfig();
     method public int getLineBreakStyle();
-    method public void set(@Nullable android.graphics.text.LineBreakConfig);
+    method public int getLineBreakWordStyle();
+    method public void set(@NonNull android.graphics.text.LineBreakConfig);
     method public void setLineBreakStyle(int);
+    method public void setLineBreakWordStyle(int);
     field public static final int LINE_BREAK_STYLE_LOOSE = 1; // 0x1
     field public static final int LINE_BREAK_STYLE_NONE = 0; // 0x0
     field public static final int LINE_BREAK_STYLE_NORMAL = 2; // 0x2
     field public static final int LINE_BREAK_STYLE_STRICT = 3; // 0x3
+    field public static final int LINE_BREAK_WORD_STYLE_NONE = 0; // 0x0
+    field public static final int LINE_BREAK_WORD_STYLE_PHRASE = 1; // 0x1
   }
 
   public class LineBreaker {
diff --git a/core/java/android/text/PrecomputedText.java b/core/java/android/text/PrecomputedText.java
index ce63376..be66db2 100644
--- a/core/java/android/text/PrecomputedText.java
+++ b/core/java/android/text/PrecomputedText.java
@@ -363,6 +363,9 @@
         public String toString() {
             int lineBreakStyle = (mLineBreakConfig != null)
                     ? mLineBreakConfig.getLineBreakStyle() : LineBreakConfig.LINE_BREAK_STYLE_NONE;
+            int lineBreakWordStyle = (mLineBreakConfig != null)
+                    ? mLineBreakConfig.getLineBreakWordStyle()
+                            : LineBreakConfig.LINE_BREAK_WORD_STYLE_NONE;
             return "{"
                 + "textSize=" + mPaint.getTextSize()
                 + ", textScaleX=" + mPaint.getTextScaleX()
@@ -376,6 +379,7 @@
                 + ", breakStrategy=" + mBreakStrategy
                 + ", hyphenationFrequency=" + mHyphenationFrequency
                 + ", lineBreakStyle=" + lineBreakStyle
+                + ", lineBreakWordStyle=" + lineBreakWordStyle
                 + "}";
         }
     };
diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java
index 0fe06be..a710a80 100644
--- a/core/java/android/widget/TextView.java
+++ b/core/java/android/widget/TextView.java
@@ -350,6 +350,7 @@
  * @attr ref android.R.styleable#TextView_breakStrategy
  * @attr ref android.R.styleable#TextView_hyphenationFrequency
  * @attr ref android.R.styleable#TextView_lineBreakStyle
+ * @attr ref android.R.styleable#TextView_lineBreakWordStyle
  * @attr ref android.R.styleable#TextView_autoSizeTextType
  * @attr ref android.R.styleable#TextView_autoSizeMinTextSize
  * @attr ref android.R.styleable#TextView_autoSizeMaxTextSize
@@ -458,6 +459,13 @@
 
     private static final int FLOATING_TOOLBAR_SELECT_ALL_REFRESH_DELAY = 500;
 
+    // The default value of the line break style.
+    private static final int DEFAULT_LINE_BREAK_STYLE = LineBreakConfig.LINE_BREAK_STYLE_NONE;
+
+    // The default value of the line break word style.
+    private static final int DEFAULT_LINE_BREAK_WORD_STYLE =
+            LineBreakConfig.LINE_BREAK_WORD_STYLE_NONE;
+
     /**
      * This change ID enables the fallback text line spacing (line height) for BoringLayout.
      * @hide
@@ -1450,6 +1458,11 @@
                             a.getInt(attr, LineBreakConfig.LINE_BREAK_STYLE_NONE));
                     break;
 
+                case com.android.internal.R.styleable.TextView_lineBreakWordStyle:
+                    mLineBreakConfig.setLineBreakWordStyle(
+                            a.getInt(attr, LineBreakConfig.LINE_BREAK_WORD_STYLE_NONE));
+                    break;
+
                 case com.android.internal.R.styleable.TextView_autoSizeTextType:
                     mAutoSizeTextType = a.getInt(attr, AUTO_SIZE_TEXT_TYPE_NONE);
                     break;
@@ -3982,6 +3995,10 @@
         float mLetterSpacing = 0;
         String mFontFeatureSettings = null;
         String mFontVariationSettings = null;
+        boolean mHasLineBreakStyle = false;
+        boolean mHasLineBreakWordStyle = false;
+        int mLineBreakStyle = DEFAULT_LINE_BREAK_STYLE;
+        int mLineBreakWordStyle = DEFAULT_LINE_BREAK_WORD_STYLE;
 
         @Override
         public String toString() {
@@ -4012,6 +4029,10 @@
                     + "    mLetterSpacing:" + mLetterSpacing + "\n"
                     + "    mFontFeatureSettings:" + mFontFeatureSettings + "\n"
                     + "    mFontVariationSettings:" + mFontVariationSettings + "\n"
+                    + "    mHasLineBreakStyle:" + mHasLineBreakStyle + "\n"
+                    + "    mHasLineBreakWordStyle:" + mHasLineBreakWordStyle + "\n"
+                    + "    mLineBreakStyle:" + mLineBreakStyle + "\n"
+                    + "    mLineBreakWordStyle:" + mLineBreakWordStyle + "\n"
                     + "}";
         }
     }
@@ -4059,6 +4080,10 @@
                 com.android.internal.R.styleable.TextAppearance_fontFeatureSettings);
         sAppearanceValues.put(com.android.internal.R.styleable.TextView_fontVariationSettings,
                 com.android.internal.R.styleable.TextAppearance_fontVariationSettings);
+        sAppearanceValues.put(com.android.internal.R.styleable.TextView_lineBreakStyle,
+                com.android.internal.R.styleable.TextAppearance_lineBreakStyle);
+        sAppearanceValues.put(com.android.internal.R.styleable.TextView_lineBreakWordStyle,
+                com.android.internal.R.styleable.TextAppearance_lineBreakWordStyle);
     }
 
     /**
@@ -4174,6 +4199,16 @@
                 case com.android.internal.R.styleable.TextAppearance_fontVariationSettings:
                     attributes.mFontVariationSettings = appearance.getString(attr);
                     break;
+                case com.android.internal.R.styleable.TextAppearance_lineBreakStyle:
+                    attributes.mHasLineBreakStyle = true;
+                    attributes.mLineBreakStyle =
+                            appearance.getInt(attr, attributes.mLineBreakStyle);
+                    break;
+                case com.android.internal.R.styleable.TextAppearance_lineBreakWordStyle:
+                    attributes.mHasLineBreakWordStyle = true;
+                    attributes.mLineBreakWordStyle =
+                            appearance.getInt(attr, attributes.mLineBreakWordStyle);
+                    break;
                 default:
             }
         }
@@ -4239,9 +4274,46 @@
         if (attributes.mFontVariationSettings != null) {
             setFontVariationSettings(attributes.mFontVariationSettings);
         }
+
+        if (attributes.mHasLineBreakStyle || attributes.mHasLineBreakWordStyle) {
+            updateLineBreakConfigFromTextAppearance(attributes.mHasLineBreakStyle,
+                    attributes.mHasLineBreakWordStyle, attributes.mLineBreakStyle,
+                    attributes.mLineBreakWordStyle);
+        }
     }
 
     /**
+     * Updates the LineBreakConfig from the TextAppearance.
+     *
+     * This method updates the given line configuration from the TextAppearance. This method will
+     * request new layout if line break config has been changed.
+     *
+     * @param isLineBreakStyleSpecified true if the line break style is specified.
+     * @param isLineBreakWordStyleSpecified true if the line break word style is specified.
+     * @param lineBreakStyle the value of the line break style in the TextAppearance.
+     * @param lineBreakWordStyle the value of the line break word style in the TextAppearance.
+     */
+    private void updateLineBreakConfigFromTextAppearance(boolean isLineBreakStyleSpecified,
+            boolean isLineBreakWordStyleSpecified,
+            @LineBreakConfig.LineBreakStyle int lineBreakStyle,
+            @LineBreakConfig.LineBreakWordStyle int lineBreakWordStyle) {
+        boolean updated = false;
+        if (isLineBreakStyleSpecified && mLineBreakConfig.getLineBreakStyle() != lineBreakStyle) {
+            mLineBreakConfig.setLineBreakStyle(lineBreakStyle);
+            updated = true;
+        }
+        if (isLineBreakWordStyleSpecified
+                && mLineBreakConfig.getLineBreakWordStyle() != lineBreakWordStyle) {
+            mLineBreakConfig.setLineBreakWordStyle(lineBreakWordStyle);
+            updated = true;
+        }
+        if (updated && mLayout != null) {
+            nullLayouts();
+            requestLayout();
+            invalidate();
+        }
+    }
+    /**
      * Get the default primary {@link Locale} of the text in this TextView. This will always be
      * the first member of {@link #getTextLocales()}.
      * @return the default primary {@link Locale} of the text in this TextView.
@@ -4797,18 +4869,29 @@
 
     /**
      * Sets line break configuration indicates which strategy needs to be used when calculating the
-     * text wrapping. There are thee strategies for the line break style(lb):
+     * text wrapping.
+     * <P>
+     * There are two types of line break rules that can be configured at the same time. One is
+     * line break style(lb) and the other is line break word style(lw). The line break style
+     * affects rule-based breaking. The line break word style affects dictionary-based breaking
+     * and provide phrase-based breaking opportunities. There are several types for the
+     * line break style:
      * {@link LineBreakConfig#LINE_BREAK_STYLE_LOOSE},
      * {@link LineBreakConfig#LINE_BREAK_STYLE_NORMAL} and
      * {@link LineBreakConfig#LINE_BREAK_STYLE_STRICT}.
-     * The default value of the line break style is {@link LineBreakConfig#LINE_BREAK_STYLE_NONE},
-     * which means no line break style is specified.
+     * The type for the line break word style is
+     * {@link LineBreakConfig#LINE_BREAK_WORD_STYLE_PHRASE}.
+     * The default values of the line break style and the line break word style are
+     * {@link LineBreakConfig#LINE_BREAK_STYLE_NONE} and
+     * {@link LineBreakConfig#LINE_BREAK_WORD_STYLE_NONE} respectively, indicating that no line
+     * breaking rules are specified.
      * See <a href="https://drafts.csswg.org/css-text/#line-break-property">
      *         the line-break property</a>
      *
      * @param lineBreakConfig the line break config for text wrapping.
      */
     public void setLineBreakConfig(@NonNull LineBreakConfig lineBreakConfig) {
+        Objects.requireNonNull(lineBreakConfig);
         if (mLineBreakConfig.equals(lineBreakConfig)) {
             return;
         }
@@ -4855,7 +4938,13 @@
         mTextDir = params.getTextDirection();
         mBreakStrategy = params.getBreakStrategy();
         mHyphenationFrequency = params.getHyphenationFrequency();
-        mLineBreakConfig.set(params.getLineBreakConfig());
+        if (params.getLineBreakConfig() != null) {
+            mLineBreakConfig.set(params.getLineBreakConfig());
+        } else {
+            // Set default value if the line break config in the PrecomputedText.Params is null.
+            mLineBreakConfig.setLineBreakStyle(DEFAULT_LINE_BREAK_STYLE);
+            mLineBreakConfig.setLineBreakWordStyle(DEFAULT_LINE_BREAK_WORD_STYLE);
+        }
         if (mLayout != null) {
             nullLayouts();
             requestLayout();
diff --git a/core/res/res/values/attrs.xml b/core/res/res/values/attrs.xml
index e232d85..69fdea4 100644
--- a/core/res/res/values/attrs.xml
+++ b/core/res/res/values/attrs.xml
@@ -5030,6 +5030,10 @@
         <attr name="fontFeatureSettings" format="string" />
         <!-- Font variation settings. -->
         <attr name="fontVariationSettings" format="string"/>
+        <!-- Specifies the strictness of line-breaking rules applied within an element. -->
+        <attr name="lineBreakStyle" />
+        <!-- Specifies the phrase-based breaking opportunities. -->
+        <attr name="lineBreakWordStyle" />
     </declare-styleable>
     <declare-styleable name="TextClock">
         <!-- Specifies the formatting pattern used to show the time and/or date
@@ -5428,6 +5432,13 @@
             <!-- ndicates breaking text with the most strictest line-breaking rules. -->
             <enum name="strict" value="3" />
         </attr>
+        <!-- Specify the phrase-based line break can be used when calculating the text wrapping.-->
+        <attr name="lineBreakWordStyle">
+            <!-- No line break word style specific. -->
+            <enum name="none" value="0" />
+            <!-- Specify the phrase based breaking. -->
+            <enum name="phrase" value="1" />
+        </attr>
         <!-- Specify the type of auto-size. Note that this feature is not supported by EditText,
         works only for TextView. -->
         <attr name="autoSizeTextType" format="enum">
diff --git a/core/res/res/values/public.xml b/core/res/res/values/public.xml
index e83f4a6..6aff511 100644
--- a/core/res/res/values/public.xml
+++ b/core/res/res/values/public.xml
@@ -3212,6 +3212,7 @@
 
   <public type="attr" name="shouldUseDefaultUnfoldTransition" id="0x0101064c" />
   <public type="attr" name="lineBreakStyle" id="0x0101064d" />
+  <public type="attr" name="lineBreakWordStyle" id="0x0101064e" />
 
   <staging-public-group-final type="id" first-id="0x01fe0000">
     <public name="accessibilityActionDragStart" />
diff --git a/graphics/java/android/graphics/text/LineBreakConfig.java b/graphics/java/android/graphics/text/LineBreakConfig.java
index 4d81858..cffdf28 100644
--- a/graphics/java/android/graphics/text/LineBreakConfig.java
+++ b/graphics/java/android/graphics/text/LineBreakConfig.java
@@ -17,7 +17,7 @@
 package android.graphics.text;
 
 import android.annotation.IntDef;
-import android.annotation.Nullable;
+import android.annotation.NonNull;
 
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
@@ -58,7 +58,28 @@
     @Retention(RetentionPolicy.SOURCE)
     public @interface LineBreakStyle {}
 
+    /**
+     * No line break word style specified.
+     */
+    public static final int LINE_BREAK_WORD_STYLE_NONE = 0;
+
+    /**
+     * Indicates the line breaking is based on the phrased. This makes text wrapping only on
+     * meaningful words. The support of the text wrapping word style varies depending on the
+     * locales. If the locale does not support the phrase based text wrapping,
+     * there will be no effect.
+     */
+    public static final int LINE_BREAK_WORD_STYLE_PHRASE = 1;
+
+    /** @hide */
+    @IntDef(prefix = { "LINE_BREAK_WORD_STYLE_" }, value = {
+        LINE_BREAK_WORD_STYLE_NONE, LINE_BREAK_WORD_STYLE_PHRASE
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface LineBreakWordStyle {}
+
     private @LineBreakStyle int mLineBreakStyle = LINE_BREAK_STYLE_NONE;
+    private @LineBreakWordStyle int mLineBreakWordStyle = LINE_BREAK_WORD_STYLE_NONE;
 
     public LineBreakConfig() {
     }
@@ -66,14 +87,12 @@
     /**
      * Set the line break configuration.
      *
-     * @param config the new line break configuration.
+     * @param lineBreakConfig the new line break configuration.
      */
-    public void set(@Nullable LineBreakConfig config) {
-        if (config != null) {
-            mLineBreakStyle = config.getLineBreakStyle();
-        } else {
-            mLineBreakStyle = LineBreakConfig.LINE_BREAK_STYLE_NONE;
-        }
+    public void set(@NonNull LineBreakConfig lineBreakConfig) {
+        Objects.requireNonNull(lineBreakConfig);
+        mLineBreakStyle = lineBreakConfig.getLineBreakStyle();
+        mLineBreakWordStyle = lineBreakConfig.getLineBreakWordStyle();
     }
 
     /**
@@ -94,17 +113,36 @@
         mLineBreakStyle = lineBreakStyle;
     }
 
+    /**
+     * Get the line break word style.
+     *
+     * @return The current line break word style to be used for the text wrapping.
+     */
+    public @LineBreakWordStyle int getLineBreakWordStyle() {
+        return mLineBreakWordStyle;
+    }
+
+    /**
+     * Set the line break word style.
+     *
+     * @param lineBreakWordStyle the new line break word style.
+     */
+    public void setLineBreakWordStyle(@LineBreakWordStyle int lineBreakWordStyle) {
+        mLineBreakWordStyle = lineBreakWordStyle;
+    }
+
     @Override
     public boolean equals(Object o) {
         if (o == null) return false;
         if (this == o) return true;
         if (!(o instanceof LineBreakConfig)) return false;
         LineBreakConfig that = (LineBreakConfig) o;
-        return mLineBreakStyle == that.mLineBreakStyle;
+        return (mLineBreakStyle == that.mLineBreakStyle)
+                && (mLineBreakWordStyle == that.mLineBreakWordStyle);
     }
 
     @Override
     public int hashCode() {
-        return Objects.hash(mLineBreakStyle);
+        return Objects.hash(mLineBreakStyle, mLineBreakWordStyle);
     }
 }
diff --git a/graphics/java/android/graphics/text/MeasuredText.java b/graphics/java/android/graphics/text/MeasuredText.java
index 5f4afb7..6d691c1 100644
--- a/graphics/java/android/graphics/text/MeasuredText.java
+++ b/graphics/java/android/graphics/text/MeasuredText.java
@@ -264,8 +264,10 @@
             Preconditions.checkArgument(end <= mText.length, "Style exceeds the text length");
             int lbStyle = (lineBreakConfig != null) ? lineBreakConfig.getLineBreakStyle() :
                     LineBreakConfig.LINE_BREAK_STYLE_NONE;
-            nAddStyleRun(mNativePtr, paint.getNativeInstance(), lbStyle, mCurrentOffset, end,
-                    isRtl);
+            int lbWordStyle = (lineBreakConfig != null) ? lineBreakConfig.getLineBreakWordStyle() :
+                    LineBreakConfig.LINE_BREAK_WORD_STYLE_NONE;
+            nAddStyleRun(mNativePtr, paint.getNativeInstance(), lbStyle, lbWordStyle,
+                    mCurrentOffset, end, isRtl);
             mCurrentOffset = end;
             return this;
         }
@@ -445,7 +447,8 @@
          *
          * @param nativeBuilderPtr The native MeasuredParagraph builder pointer.
          * @param paintPtr The native paint pointer to be applied.
-         * @param lineBreakStyle The line break style of the text.
+         * @param lineBreakStyle The line break style(lb) of the text.
+         * @param lineBreakWordStyle The line break word style(lw) of the text.
          * @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.
@@ -453,6 +456,7 @@
         private static native void nAddStyleRun(/* Non Zero */ long nativeBuilderPtr,
                                                 /* Non Zero */ long paintPtr,
                                                 int lineBreakStyle,
+                                                int lineBreakWordStyle,
                                                 @IntRange(from = 0) int start,
                                                 @IntRange(from = 0) int end,
                                                 boolean isRtl);
diff --git a/libs/hwui/jni/text/MeasuredText.cpp b/libs/hwui/jni/text/MeasuredText.cpp
index 09539ec..76ea2d5 100644
--- a/libs/hwui/jni/text/MeasuredText.cpp
+++ b/libs/hwui/jni/text/MeasuredText.cpp
@@ -65,11 +65,13 @@
 
 // Regular JNI
 static void nAddStyleRun(JNIEnv* /* unused */, jclass /* unused */, jlong builderPtr,
-                         jlong paintPtr, jint lbStyle, jint start, jint end, jboolean isRtl) {
+                         jlong paintPtr, jint lbStyle, jint lbWordStyle, jint start, jint end,
+                         jboolean isRtl) {
     Paint* paint = toPaint(paintPtr);
     const Typeface* typeface = Typeface::resolveDefault(paint->getAndroidTypeface());
     minikin::MinikinPaint minikinPaint = MinikinUtils::prepareMinikinPaint(paint, typeface);
-    toBuilder(builderPtr)->addStyleRun(start, end, std::move(minikinPaint), lbStyle, isRtl);
+    toBuilder(builderPtr)
+            ->addStyleRun(start, end, std::move(minikinPaint), lbStyle, lbWordStyle, isRtl);
 }
 
 // Regular JNI
@@ -144,7 +146,7 @@
 static const JNINativeMethod gMTBuilderMethods[] = {
         // MeasuredParagraphBuilder native functions.
         {"nInitBuilder", "()J", (void*)nInitBuilder},
-        {"nAddStyleRun", "(JJIIIZ)V", (void*)nAddStyleRun},
+        {"nAddStyleRun", "(JJIIIIZ)V", (void*)nAddStyleRun},
         {"nAddReplacementRun", "(JJIIF)V", (void*)nAddReplacementRun},
         {"nBuildMeasuredText", "(JJ[CZZZ)J", (void*)nBuildMeasuredText},
         {"nFreeBuilder", "(J)V", (void*)nFreeBuilder},