Add NoHyphenationSpan and no hyphenation config into LineBreakConfig

NoHyphenationSpan is a specialized class of LineBreakConfigSpan
that disables hyphenation.

Bug: 283193586
Test: atest LineBreakTest
Change-Id: Ie77d345d83d871a5049ff713211a9dab116656b7
diff --git a/core/api/current.txt b/core/api/current.txt
index d92c693..d2ff81b0 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -17653,9 +17653,13 @@
 package android.graphics.text {
 
   public final class LineBreakConfig {
+    method @FlaggedApi(FLAG_NO_BREAK_NO_HYPHENATION_SPAN) public int getHyphenation();
     method public int getLineBreakStyle();
     method public int getLineBreakWordStyle();
     method @NonNull public android.graphics.text.LineBreakConfig merge(@NonNull android.graphics.text.LineBreakConfig);
+    field @FlaggedApi(FLAG_NO_BREAK_NO_HYPHENATION_SPAN) public static final int HYPHENATION_DISABLED = 0; // 0x0
+    field @FlaggedApi(FLAG_NO_BREAK_NO_HYPHENATION_SPAN) public static final int HYPHENATION_ENABLED = 1; // 0x1
+    field @FlaggedApi(FLAG_NO_BREAK_NO_HYPHENATION_SPAN) public static final int HYPHENATION_UNSPECIFIED = -1; // 0xffffffff
     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
@@ -17670,6 +17674,7 @@
     ctor public LineBreakConfig.Builder();
     method @NonNull public android.graphics.text.LineBreakConfig build();
     method @NonNull public android.graphics.text.LineBreakConfig.Builder merge(@NonNull android.graphics.text.LineBreakConfig);
+    method @FlaggedApi(FLAG_NO_BREAK_NO_HYPHENATION_SPAN) @NonNull public android.graphics.text.LineBreakConfig.Builder setHyphenation(int);
     method @NonNull public android.graphics.text.LineBreakConfig.Builder setLineBreakStyle(int);
     method @NonNull public android.graphics.text.LineBreakConfig.Builder setLineBreakWordStyle(int);
   }
@@ -47875,11 +47880,15 @@
     method public void writeToParcel(@NonNull android.os.Parcel, int);
   }
 
-  public class LineBreakConfigSpan {
+  @FlaggedApi(FLAG_NO_BREAK_NO_HYPHENATION_SPAN) public class LineBreakConfigSpan {
     ctor public LineBreakConfigSpan(@NonNull android.graphics.text.LineBreakConfig);
     method @NonNull public android.graphics.text.LineBreakConfig getLineBreakConfig();
   }
 
+  @FlaggedApi(FLAG_NO_BREAK_NO_HYPHENATION_SPAN) public static final class LineBreakConfigSpan.NoHyphenationSpan extends android.text.style.LineBreakConfigSpan {
+    ctor @FlaggedApi(FLAG_NO_BREAK_NO_HYPHENATION_SPAN) public LineBreakConfigSpan.NoHyphenationSpan();
+  }
+
   public interface LineHeightSpan extends android.text.style.ParagraphStyle android.text.style.WrapTogetherSpan {
     method public void chooseHeight(CharSequence, int, int, int, int, android.graphics.Paint.FontMetricsInt);
   }
diff --git a/core/java/android/text/flags/no_break_no_hyphenation_span.aconfig b/core/java/android/text/flags/no_break_no_hyphenation_span.aconfig
new file mode 100644
index 0000000..60f1e88
--- /dev/null
+++ b/core/java/android/text/flags/no_break_no_hyphenation_span.aconfig
@@ -0,0 +1,8 @@
+package: "com.android.text.flags"
+
+flag {
+  name: "no_break_no_hyphenation_span"
+  namespace: "text"
+  description: "A feature flag that adding new spans that prevents line breaking and hyphenation."
+  bug: "283193586"
+}
diff --git a/core/java/android/text/style/LineBreakConfigSpan.java b/core/java/android/text/style/LineBreakConfigSpan.java
index 90a79c6..b8033a9 100644
--- a/core/java/android/text/style/LineBreakConfigSpan.java
+++ b/core/java/android/text/style/LineBreakConfigSpan.java
@@ -16,6 +16,9 @@
 
 package android.text.style;
 
+import static com.android.text.flags.Flags.FLAG_NO_BREAK_NO_HYPHENATION_SPAN;
+
+import android.annotation.FlaggedApi;
 import android.annotation.NonNull;
 import android.graphics.text.LineBreakConfig;
 
@@ -24,6 +27,7 @@
 /**
  * LineBreakSpan for changing line break style of the specific region of the text.
  */
+@FlaggedApi(FLAG_NO_BREAK_NO_HYPHENATION_SPAN)
 public class LineBreakConfigSpan {
     private final LineBreakConfig mLineBreakConfig;
 
@@ -60,4 +64,22 @@
     public String toString() {
         return "LineBreakConfigSpan{mLineBreakConfig=" + mLineBreakConfig + '}';
     }
+
+    private static final LineBreakConfig sNoHyphenationConfig = new LineBreakConfig.Builder()
+            .setHyphenation(LineBreakConfig.HYPHENATION_DISABLED)
+            .build();
+
+    /**
+     * A specialized {@link LineBreakConfigSpan} that used for preventing hyphenation.
+     */
+    @FlaggedApi(FLAG_NO_BREAK_NO_HYPHENATION_SPAN)
+    public static final class NoHyphenationSpan extends LineBreakConfigSpan {
+        /**
+         * Construct a new {@link NoHyphenationSpan}.
+         */
+        @FlaggedApi(FLAG_NO_BREAK_NO_HYPHENATION_SPAN)
+        public NoHyphenationSpan() {
+            super(sNoHyphenationConfig);
+        }
+    }
 }
diff --git a/graphics/java/android/graphics/text/LineBreakConfig.java b/graphics/java/android/graphics/text/LineBreakConfig.java
index 7b204f2..13540e0 100644
--- a/graphics/java/android/graphics/text/LineBreakConfig.java
+++ b/graphics/java/android/graphics/text/LineBreakConfig.java
@@ -16,6 +16,9 @@
 
 package android.graphics.text;
 
+import static com.android.text.flags.Flags.FLAG_NO_BREAK_NO_HYPHENATION_SPAN;
+
+import android.annotation.FlaggedApi;
 import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
@@ -34,6 +37,56 @@
 public final class LineBreakConfig {
 
     /**
+     * No hyphenation preference is specified.
+     *
+     * This is a special value of hyphenation preference indicating no hyphenation preference is
+     * specified. When overriding a {@link LineBreakConfig} with another {@link LineBreakConfig}
+     * with {@link Builder#merge(LineBreakConfig)} function, the hyphenation preference of
+     * overridden config will be kept if the hyphenation preference of overriding config is
+     * {@link #HYPHENATION_UNSPECIFIED}.
+     *
+     * <pre>
+     *     val override = LineBreakConfig.Builder()
+     *          .setLineBreakWordStyle(LineBreakConfig.LINE_BREAK_WORD_STYLE_PHRASE)
+     *          .build();  // UNSPECIFIED if no setHyphenation is called.
+     *     val config = LineBreakConfig.Builder()
+     *          .setHyphenation(LineBreakConfig.HYPHENATION_DISABLED)
+     *          .merge(override)
+     *          .build()
+     *     // Here, config has HYPHENATION_DISABLED for line break config and
+     *     // LINE_BREAK_WORD_STYLE_PHRASE for line break word style.
+     * </pre>
+     *
+     * This value is resolved to {@link #HYPHENATION_ENABLED} if this value is used for text
+     * layout/rendering.
+     */
+    @FlaggedApi(FLAG_NO_BREAK_NO_HYPHENATION_SPAN)
+    public static final int HYPHENATION_UNSPECIFIED = -1;
+
+    /**
+     * The hyphenation is disabled.
+     */
+    @FlaggedApi(FLAG_NO_BREAK_NO_HYPHENATION_SPAN)
+    public static final int HYPHENATION_DISABLED = 0;
+
+    /**
+     * The hyphenation is enabled.
+     *
+     * Note: Even if the hyphenation is enabled with a line break strategy
+     * {@link LineBreaker#BREAK_STRATEGY_SIMPLE}, the hyphenation will not be performed unless a
+     * single word cannot meet width constraints.
+     */
+    @FlaggedApi(FLAG_NO_BREAK_NO_HYPHENATION_SPAN)
+    public static final int HYPHENATION_ENABLED = 1;
+
+    /** @hide */
+    @IntDef(prefix = { "HYPHENATION_" }, value = {
+            HYPHENATION_UNSPECIFIED, HYPHENATION_ENABLED, HYPHENATION_DISABLED,
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface Hyphenation {}
+
+    /**
      * No line break style is specified.
      *
      * This is a special value of line break style indicating no style value is specified.
@@ -147,6 +200,8 @@
         private @LineBreakWordStyle int mLineBreakWordStyle =
                 LineBreakConfig.LINE_BREAK_WORD_STYLE_UNSPECIFIED;
 
+        private @Hyphenation int mHyphenation = LineBreakConfig.HYPHENATION_UNSPECIFIED;
+
         /**
          * Builder constructor.
          */
@@ -188,6 +243,9 @@
             if (config.mLineBreakWordStyle != LINE_BREAK_WORD_STYLE_UNSPECIFIED) {
                 mLineBreakWordStyle = config.mLineBreakWordStyle;
             }
+            if (config.mHyphenation != HYPHENATION_UNSPECIFIED) {
+                mHyphenation = config.mHyphenation;
+            }
             return this;
         }
 
@@ -201,9 +259,11 @@
             if (config == null) {
                 mLineBreakStyle = LINE_BREAK_STYLE_UNSPECIFIED;
                 mLineBreakWordStyle = LINE_BREAK_WORD_STYLE_UNSPECIFIED;
+                mHyphenation = HYPHENATION_UNSPECIFIED;
             } else {
                 mLineBreakStyle = config.mLineBreakStyle;
                 mLineBreakWordStyle = config.mLineBreakWordStyle;
+                mHyphenation = config.mHyphenation;
             }
             return this;
         }
@@ -215,6 +275,11 @@
          * {@link #LINE_BREAK_STYLE_UNSPECIFIED}, the line break style is reset to
          * {@link #LINE_BREAK_STYLE_UNSPECIFIED}.
          *
+         * @see <a href="https://unicode.org/reports/tr35/#UnicodeLineBreakStyleIdentifier">
+         *     Unicode Line Break Style Identifier</a>
+         * @see <a href="https://drafts.csswg.org/css-text/#line-break-property">
+         *     CSS Line Break Property</a>
+         *
          * @param lineBreakStyle The new line-break style.
          * @return This {@code Builder}.
          */
@@ -230,6 +295,11 @@
          * with {@link #LINE_BREAK_WORD_STYLE_UNSPECIFIED}, the line break style is reset to
          * {@link #LINE_BREAK_WORD_STYLE_UNSPECIFIED}.
          *
+         * @see <a href="https://unicode.org/reports/tr35/#UnicodeLineBreakWordIdentifier">
+         *     Unicode Line Break Word Identifier</a>
+         * @see <a href="https://drafts.csswg.org/css-text/#word-break-property">
+         *     CSS Word Break Property</a>
+         *
          * @param lineBreakWordStyle The new line-break word style.
          * @return This {@code Builder}.
          */
@@ -239,6 +309,27 @@
         }
 
         /**
+         * Sets the hyphenation preference
+         *
+         * Note: Even if the {@link LineBreakConfig#HYPHENATION_ENABLED} is specified, the
+         * hyphenation will not be performed if the {@link android.widget.TextView} or underlying
+         * {@link android.text.StaticLayout}, {@link LineBreaker} are configured with
+         * {@link LineBreaker#HYPHENATION_FREQUENCY_NONE}.
+         *
+         * Note: Even if the hyphenation is enabled with a line break strategy
+         * {@link LineBreaker#BREAK_STRATEGY_SIMPLE}, the hyphenation will not be performed unless a
+         * single word cannot meet width constraints.
+         *
+         * @param hyphenation The hyphenation preference.
+         * @return This {@code Builder}.
+         */
+        @FlaggedApi(FLAG_NO_BREAK_NO_HYPHENATION_SPAN)
+        public @NonNull Builder setHyphenation(@Hyphenation int hyphenation) {
+            mHyphenation = hyphenation;
+            return this;
+        }
+
+        /**
          * Builds a {@link LineBreakConfig} instance.
          *
          * This method can be called multiple times for generating multiple {@link LineBreakConfig}
@@ -247,7 +338,7 @@
          * @return The {@code LineBreakConfig} instance.
          */
         public @NonNull LineBreakConfig build() {
-            return new LineBreakConfig(mLineBreakStyle, mLineBreakWordStyle);
+            return new LineBreakConfig(mLineBreakStyle, mLineBreakWordStyle, mHyphenation);
         }
     }
 
@@ -275,6 +366,7 @@
 
     private final @LineBreakStyle int mLineBreakStyle;
     private final @LineBreakWordStyle int mLineBreakWordStyle;
+    private final @Hyphenation int mHyphenation;
 
     /**
      * Constructor with line-break parameters.
@@ -283,9 +375,11 @@
      * {@code LineBreakConfig} instance.
      */
     private LineBreakConfig(@LineBreakStyle int lineBreakStyle,
-            @LineBreakWordStyle int lineBreakWordStyle) {
+            @LineBreakWordStyle int lineBreakWordStyle,
+            @Hyphenation int hyphenation) {
         mLineBreakStyle = lineBreakStyle;
         mLineBreakWordStyle = lineBreakWordStyle;
+        mHyphenation = hyphenation;
     }
 
     /**
@@ -340,6 +434,34 @@
     }
 
     /**
+     * Returns a hyphenation preference.
+     *
+     * @return A hyphenation preference.
+     */
+    @FlaggedApi(FLAG_NO_BREAK_NO_HYPHENATION_SPAN)
+    public @Hyphenation  int getHyphenation() {
+        return mHyphenation;
+    }
+
+    /**
+     * Returns a hyphenation preference.
+     *
+     * This method never returns {@link #HYPHENATION_UNSPECIFIED}.
+     *
+     * @return A hyphenation preference.
+     * @hide
+     */
+    public static @Hyphenation int getResolvedHyphenation(
+            @Nullable LineBreakConfig config) {
+        if (config == null) {
+            return HYPHENATION_ENABLED;
+        }
+        return config.mHyphenation == HYPHENATION_UNSPECIFIED
+                ? HYPHENATION_ENABLED : config.mHyphenation;
+    }
+
+
+    /**
      * Generates a new {@link LineBreakConfig} instance merged with given {@code config}.
      *
      * If values of passing {@code config} are unspecified, the original values are kept. For
@@ -366,7 +488,9 @@
                 config.mLineBreakStyle == LINE_BREAK_STYLE_UNSPECIFIED
                         ? mLineBreakStyle : config.mLineBreakStyle,
                 config.mLineBreakWordStyle == LINE_BREAK_WORD_STYLE_UNSPECIFIED
-                        ? mLineBreakWordStyle : config.mLineBreakWordStyle);
+                        ? mLineBreakWordStyle : config.mLineBreakWordStyle,
+                config.mHyphenation == HYPHENATION_UNSPECIFIED
+                        ? mHyphenation : config.mHyphenation);
     }
 
     @Override
@@ -376,12 +500,13 @@
         if (!(o instanceof LineBreakConfig)) return false;
         LineBreakConfig that = (LineBreakConfig) o;
         return (mLineBreakStyle == that.mLineBreakStyle)
-                && (mLineBreakWordStyle == that.mLineBreakWordStyle);
+                && (mLineBreakWordStyle == that.mLineBreakWordStyle)
+                && (mHyphenation == that.mHyphenation);
     }
 
     @Override
     public int hashCode() {
-        return Objects.hash(mLineBreakStyle, mLineBreakWordStyle);
+        return Objects.hash(mLineBreakStyle, mLineBreakWordStyle, mHyphenation);
     }
 
     @Override
@@ -389,6 +514,7 @@
         return "LineBreakConfig{"
                 + "mLineBreakStyle=" + mLineBreakStyle
                 + ", mLineBreakWordStyle=" + mLineBreakWordStyle
+                + ", mHyphenation= " + mHyphenation
                 + '}';
     }
 }
diff --git a/graphics/java/android/graphics/text/MeasuredText.java b/graphics/java/android/graphics/text/MeasuredText.java
index 8317985..2d33e8d 100644
--- a/graphics/java/android/graphics/text/MeasuredText.java
+++ b/graphics/java/android/graphics/text/MeasuredText.java
@@ -301,7 +301,9 @@
             Preconditions.checkArgument(end <= mText.length, "Style exceeds the text length");
             int lbStyle = LineBreakConfig.getResolvedLineBreakStyle(lineBreakConfig);
             int lbWordStyle = LineBreakConfig.getResolvedLineBreakWordStyle(lineBreakConfig);
-            nAddStyleRun(mNativePtr, paint.getNativeInstance(), lbStyle, lbWordStyle,
+            boolean hyphenation = LineBreakConfig.getResolvedHyphenation(lineBreakConfig)
+                    == LineBreakConfig.HYPHENATION_ENABLED;
+            nAddStyleRun(mNativePtr, paint.getNativeInstance(), lbStyle, lbWordStyle, hyphenation,
                     mCurrentOffset, end, isRtl);
             mCurrentOffset = end;
 
@@ -510,6 +512,7 @@
                                                 /* Non Zero */ long paintPtr,
                                                 int lineBreakStyle,
                                                 int lineBreakWordStyle,
+                                                boolean hyphenation,
                                                 @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 f6ae169..746745a 100644
--- a/libs/hwui/jni/text/MeasuredText.cpp
+++ b/libs/hwui/jni/text/MeasuredText.cpp
@@ -62,13 +62,14 @@
 
 // Regular JNI
 static void nAddStyleRun(JNIEnv* /* unused */, jclass /* unused */, jlong builderPtr,
-                         jlong paintPtr, jint lbStyle, jint lbWordStyle, jint start, jint end,
-                         jboolean isRtl) {
+                         jlong paintPtr, jint lbStyle, jint lbWordStyle, jboolean hyphenation,
+                         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, lbWordStyle, isRtl);
+            ->addStyleRun(start, end, std::move(minikinPaint), lbStyle, lbWordStyle, hyphenation,
+                          isRtl);
 }
 
 // Regular JNI
@@ -159,7 +160,7 @@
 static const JNINativeMethod gMTBuilderMethods[] = {
         // MeasuredParagraphBuilder native functions.
         {"nInitBuilder", "()J", (void*)nInitBuilder},
-        {"nAddStyleRun", "(JJIIIIZ)V", (void*)nAddStyleRun},
+        {"nAddStyleRun", "(JJIIZIIZ)V", (void*)nAddStyleRun},
         {"nAddReplacementRun", "(JJIIF)V", (void*)nAddReplacementRun},
         {"nBuildMeasuredText", "(JJ[CZZZZ)J", (void*)nBuildMeasuredText},
         {"nFreeBuilder", "(J)V", (void*)nFreeBuilder},