Make LineBreakConfig and its span parcelable

To be able to use it in remote views, make LineBreakConfigSpan
ParcelableSpan.
Also, to be able to LineBreakConfigSpan parcelable, it need to be
final class. Due to this change, NoBreakSpan and NoHyphantionSpan is
no longer possible. So, changed them to a create methods.

Bug: 307666854
Test: atest CtsTextTestCases
Change-Id: Ifbf5a264d61c47a79ccb48a673f853f355e17c61
diff --git a/core/api/current.txt b/core/api/current.txt
index 31fb78a..1543201 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -17708,11 +17708,14 @@
 
 package android.graphics.text {
 
-  public final class LineBreakConfig {
+  public final class LineBreakConfig implements android.os.Parcelable {
+    method @FlaggedApi("com.android.text.flags.no_break_no_hyphenation_span") public int describeContents();
     method @FlaggedApi("com.android.text.flags.no_break_no_hyphenation_span") public int getHyphenation();
     method public int getLineBreakStyle();
     method public int getLineBreakWordStyle();
     method @FlaggedApi("com.android.text.flags.no_break_no_hyphenation_span") @NonNull public android.graphics.text.LineBreakConfig merge(@NonNull android.graphics.text.LineBreakConfig);
+    method @FlaggedApi("com.android.text.flags.no_break_no_hyphenation_span") public void writeToParcel(@NonNull android.os.Parcel, int);
+    field @FlaggedApi("com.android.text.flags.no_break_no_hyphenation_span") @NonNull public static final android.os.Parcelable.Creator<android.graphics.text.LineBreakConfig> CREATOR;
     field @FlaggedApi("com.android.text.flags.no_break_no_hyphenation_span") public static final int HYPHENATION_DISABLED = 0; // 0x0
     field @FlaggedApi("com.android.text.flags.no_break_no_hyphenation_span") public static final int HYPHENATION_ENABLED = 1; // 0x1
     field @FlaggedApi("com.android.text.flags.no_break_no_hyphenation_span") public static final int HYPHENATION_UNSPECIFIED = -1; // 0xffffffff
@@ -48011,17 +48014,15 @@
     method public void writeToParcel(@NonNull android.os.Parcel, int);
   }
 
-  @FlaggedApi("com.android.text.flags.no_break_no_hyphenation_span") public class LineBreakConfigSpan {
+  @FlaggedApi("com.android.text.flags.no_break_no_hyphenation_span") public final class LineBreakConfigSpan implements android.text.ParcelableSpan {
     ctor @FlaggedApi("com.android.text.flags.no_break_no_hyphenation_span") public LineBreakConfigSpan(@NonNull android.graphics.text.LineBreakConfig);
+    method @FlaggedApi("com.android.text.flags.no_break_no_hyphenation_span") @NonNull public static android.text.style.LineBreakConfigSpan createNoBreakSpan();
+    method @FlaggedApi("com.android.text.flags.no_break_no_hyphenation_span") @NonNull public static android.text.style.LineBreakConfigSpan createNoHyphenationSpan();
+    method public int describeContents();
     method @FlaggedApi("com.android.text.flags.no_break_no_hyphenation_span") @NonNull public android.graphics.text.LineBreakConfig getLineBreakConfig();
-  }
-
-  @FlaggedApi("com.android.text.flags.no_break_no_hyphenation_span") public static final class LineBreakConfigSpan.NoBreakSpan extends android.text.style.LineBreakConfigSpan {
-    ctor @FlaggedApi("com.android.text.flags.no_break_no_hyphenation_span") public LineBreakConfigSpan.NoBreakSpan();
-  }
-
-  @FlaggedApi("com.android.text.flags.no_break_no_hyphenation_span") public static final class LineBreakConfigSpan.NoHyphenationSpan extends android.text.style.LineBreakConfigSpan {
-    ctor @FlaggedApi("com.android.text.flags.no_break_no_hyphenation_span") public LineBreakConfigSpan.NoHyphenationSpan();
+    method public int getSpanTypeId();
+    method public void writeToParcel(@NonNull android.os.Parcel, int);
+    field @NonNull public static final android.os.Parcelable.Creator<android.text.style.LineBreakConfigSpan> CREATOR;
   }
 
   public interface LineHeightSpan extends android.text.style.ParagraphStyle android.text.style.WrapTogetherSpan {
diff --git a/core/java/android/content/res/StringBlock.java b/core/java/android/content/res/StringBlock.java
index c143acb..0070a6f 100644
--- a/core/java/android/content/res/StringBlock.java
+++ b/core/java/android/content/res/StringBlock.java
@@ -24,6 +24,7 @@
 import android.graphics.Paint;
 import android.graphics.Rect;
 import android.graphics.Typeface;
+import android.graphics.text.LineBreakConfig;
 import android.text.Annotation;
 import android.text.Spannable;
 import android.text.SpannableString;
@@ -35,6 +36,7 @@
 import android.text.style.BulletSpan;
 import android.text.style.CharacterStyle;
 import android.text.style.ForegroundColorSpan;
+import android.text.style.LineBreakConfigSpan;
 import android.text.style.LineHeightSpan;
 import android.text.style.RelativeSizeSpan;
 import android.text.style.StrikethroughSpan;
@@ -176,6 +178,10 @@
                         mStyleIDs.listItemId = styleId;
                     } else if (styleTag.equals("marquee")) {
                         mStyleIDs.marqueeId = styleId;
+                    } else if (styleTag.equals("nobreak")) {
+                        mStyleIDs.mNoBreakId = styleId;
+                    } else if (styleTag.equals("nohyphen")) {
+                        mStyleIDs.mNoHyphenId = styleId;
                     }
                 }
 
@@ -224,6 +230,8 @@
         private int strikeId = -1;
         private int listItemId = -1;
         private int marqueeId = -1;
+        private int mNoBreakId = -1;
+        private int mNoHyphenId = -1;
     }
 
     @Nullable
@@ -285,12 +293,19 @@
                 buffer.setSpan(TextUtils.TruncateAt.MARQUEE,
                                style[i+1], style[i+2]+1,
                                Spannable.SPAN_INCLUSIVE_INCLUSIVE);
+            } else if (type == ids.mNoBreakId) {
+                buffer.setSpan(LineBreakConfigSpan.createNoBreakSpan(),
+                        style[i + 1], style[i + 2] + 1,
+                        Spannable.SPAN_INCLUSIVE_EXCLUSIVE);
+            } else if (type == ids.mNoHyphenId) {
+                buffer.setSpan(LineBreakConfigSpan.createNoHyphenationSpan(),
+                        style[i + 1], style[i + 2] + 1,
+                        Spannable.SPAN_INCLUSIVE_EXCLUSIVE);
             } else {
                 String tag = nativeGetString(mNative, type);
                 if (tag == null) {
                     return null;
                 }
-
                 if (tag.startsWith("font;")) {
                     String sub;
 
@@ -367,6 +382,44 @@
                                        style[i+1], style[i+2]+1,
                                        Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
                     }
+                } else if (tag.startsWith("lineBreakConfig;")) {
+                    String lbStyleStr = subtag(tag, ";style=");
+                    int lbStyle = LineBreakConfig.LINE_BREAK_STYLE_UNSPECIFIED;
+                    if (lbStyleStr != null) {
+                        if (lbStyleStr.equals("none")) {
+                            lbStyle = LineBreakConfig.LINE_BREAK_STYLE_NONE;
+                        } else if (lbStyleStr.equals("normal")) {
+                            lbStyle = LineBreakConfig.LINE_BREAK_STYLE_NORMAL;
+                        } else if (lbStyleStr.equals("loose")) {
+                            lbStyle = LineBreakConfig.LINE_BREAK_STYLE_LOOSE;
+                        } else if (lbStyleStr.equals("strict")) {
+                            lbStyle = LineBreakConfig.LINE_BREAK_STYLE_STRICT;
+                        } else {
+                            Log.w(TAG, "Unknown LineBreakConfig style: " + lbStyleStr);
+                        }
+                    }
+
+                    String lbWordStyleStr = subtag(tag, ";wordStyle=");
+                    int lbWordStyle = LineBreakConfig.LINE_BREAK_STYLE_UNSPECIFIED;
+                    if (lbWordStyleStr != null) {
+                        if (lbWordStyleStr.equals("none")) {
+                            lbWordStyle = LineBreakConfig.LINE_BREAK_WORD_STYLE_NONE;
+                        } else if (lbWordStyleStr.equals("phrase")) {
+                            lbWordStyle = LineBreakConfig.LINE_BREAK_WORD_STYLE_PHRASE;
+                        } else {
+                            Log.w(TAG, "Unknown LineBreakConfig word style: " + lbWordStyleStr);
+                        }
+                    }
+
+                    // Attach span only when the both lbStyle and lbWordStyle are valid.
+                    if (lbStyle != LineBreakConfig.LINE_BREAK_STYLE_UNSPECIFIED
+                            || lbWordStyle != LineBreakConfig.LINE_BREAK_WORD_STYLE_UNSPECIFIED) {
+                        buffer.setSpan(new LineBreakConfigSpan(
+                                new LineBreakConfig(lbStyle, lbWordStyle,
+                                        LineBreakConfig.HYPHENATION_UNSPECIFIED)),
+                                style[i + 1], style[i + 2] + 1,
+                                Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
+                    }
                 }
             }
 
diff --git a/core/java/android/text/TextUtils.java b/core/java/android/text/TextUtils.java
index 4e3deb6..c0a5629 100644
--- a/core/java/android/text/TextUtils.java
+++ b/core/java/android/text/TextUtils.java
@@ -47,6 +47,7 @@
 import android.text.style.ForegroundColorSpan;
 import android.text.style.LeadingMarginSpan;
 import android.text.style.LineBackgroundSpan;
+import android.text.style.LineBreakConfigSpan;
 import android.text.style.LineHeightSpan;
 import android.text.style.LocaleSpan;
 import android.text.style.ParagraphStyle;
@@ -787,7 +788,9 @@
     /** @hide */
     public static final int ACCESSIBILITY_REPLACEMENT_SPAN = 29;
     /** @hide */
-    public static final int LAST_SPAN = ACCESSIBILITY_REPLACEMENT_SPAN;
+    public static final int LINE_BREAK_CONFIG_SPAN = 30;
+    /** @hide */
+    public static final int LAST_SPAN = LINE_BREAK_CONFIG_SPAN;
 
     /**
      * Flatten a CharSequence and whatever styles can be copied across processes
@@ -991,6 +994,10 @@
                     span = new AccessibilityReplacementSpan(p);
                     break;
 
+                case LINE_BREAK_CONFIG_SPAN:
+                    span = LineBreakConfigSpan.CREATOR.createFromParcel(p);
+                    break;
+
                 default:
                     throw new RuntimeException("bogus span encoding " + kind);
                 }
diff --git a/core/java/android/text/style/LineBreakConfigSpan.java b/core/java/android/text/style/LineBreakConfigSpan.java
index 682ffa1..eeb6383 100644
--- a/core/java/android/text/style/LineBreakConfigSpan.java
+++ b/core/java/android/text/style/LineBreakConfigSpan.java
@@ -21,6 +21,9 @@
 import android.annotation.FlaggedApi;
 import android.annotation.NonNull;
 import android.graphics.text.LineBreakConfig;
+import android.os.Parcel;
+import android.text.ParcelableSpan;
+import android.text.TextUtils;
 
 import java.util.Objects;
 
@@ -28,7 +31,7 @@
  * LineBreakSpan for changing line break style of the specific region of the text.
  */
 @FlaggedApi(FLAG_NO_BREAK_NO_HYPHENATION_SPAN)
-public class LineBreakConfigSpan {
+public final class LineBreakConfigSpan implements ParcelableSpan {
     private final LineBreakConfig mLineBreakConfig;
 
     /**
@@ -49,6 +52,28 @@
         return mLineBreakConfig;
     }
 
+    /**
+     * A specialized {@link LineBreakConfigSpan} that used for preventing line break.
+     *
+     * This is useful when you want to preserve some words in the same line.
+     * Note that even if this style is specified, the grapheme based line break is still performed
+     * for preventing clipping text.
+     *
+     * @see LineBreakConfigSpan
+     */
+    @FlaggedApi(FLAG_NO_BREAK_NO_HYPHENATION_SPAN)
+    public static @NonNull LineBreakConfigSpan createNoBreakSpan() {
+        return new LineBreakConfigSpan(sNoBreakConfig);
+    }
+
+    /**
+     * A specialized {@link LineBreakConfigSpan} that used for preventing hyphenation.
+     */
+    @FlaggedApi(FLAG_NO_BREAK_NO_HYPHENATION_SPAN)
+    public static @NonNull LineBreakConfigSpan createNoHyphenationSpan() {
+        return new LineBreakConfigSpan(sNoHyphenationConfig);
+    }
+
     @Override
     public boolean equals(Object o) {
         if (this == o) return true;
@@ -75,37 +100,46 @@
             .setLineBreakStyle(LineBreakConfig.LINE_BREAK_STYLE_NO_BREAK)
             .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);
-        }
+    @Override
+    public int describeContents() {
+        return 0;
     }
 
-    /**
-     * A specialized {@link LineBreakConfigSpan} that used for preventing line break.
-     *
-     * This is useful when you want to preserve some words in the same line.
-     * Note that even if this style is specified, the grapheme based line break is still performed
-     * for preventing clipping text.
-     *
-     * @see LineBreakConfigSpan
-     */
-    @FlaggedApi(FLAG_NO_BREAK_NO_HYPHENATION_SPAN)
-    public static final class NoBreakSpan extends LineBreakConfigSpan {
-        /**
-         * Construct a new {@link NoBreakSpan}.
-         */
-        @FlaggedApi(FLAG_NO_BREAK_NO_HYPHENATION_SPAN)
-        public NoBreakSpan() {
-            super(sNoBreakConfig);
-        }
+    @Override
+    public void writeToParcel(@NonNull Parcel dest, int flags) {
+        writeToParcelInternal(dest, flags);
     }
+
+    @Override
+    public int getSpanTypeId() {
+        return getSpanTypeIdInternal();
+    }
+
+    /** @hide */
+    @Override
+    public int getSpanTypeIdInternal() {
+        return TextUtils.LINE_BREAK_CONFIG_SPAN;
+    }
+
+    /** @hide */
+    @Override
+    public void writeToParcelInternal(@NonNull Parcel dest, int flags) {
+        dest.writeParcelable(mLineBreakConfig, flags);
+    }
+
+    @NonNull
+    public static final Creator<LineBreakConfigSpan> CREATOR = new Creator<>() {
+
+        @Override
+        public LineBreakConfigSpan createFromParcel(Parcel source) {
+            LineBreakConfig lbc = source.readParcelable(
+                    LineBreakConfig.class.getClassLoader(), LineBreakConfig.class);
+            return new LineBreakConfigSpan(lbc);
+        }
+
+        @Override
+        public LineBreakConfigSpan[] newArray(int size) {
+            return new LineBreakConfigSpan[size];
+        }
+    };
 }
diff --git a/graphics/java/android/graphics/text/LineBreakConfig.java b/graphics/java/android/graphics/text/LineBreakConfig.java
index 62195856..c5e451a 100644
--- a/graphics/java/android/graphics/text/LineBreakConfig.java
+++ b/graphics/java/android/graphics/text/LineBreakConfig.java
@@ -28,6 +28,8 @@
 import android.compat.annotation.EnabledSince;
 import android.os.Build;
 import android.os.LocaleList;
+import android.os.Parcel;
+import android.os.Parcelable;
 
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
@@ -40,7 +42,7 @@
  * <a href="https://www.w3.org/TR/css-text-3/#line-break-property" class="external">
  * line-break property</a> for more information.
  */
-public final class LineBreakConfig {
+public final class LineBreakConfig implements Parcelable {
 
     /**
      * A feature ID for automatic line break word style.
@@ -161,12 +163,12 @@
      *
      * This is useful when you want to preserve some words in the same line by using
      * {@link android.text.style.LineBreakConfigSpan} or
-     * {@link android.text.style.LineBreakConfigSpan.NoBreakSpan} as a shorthand.
+     * {@link android.text.style.LineBreakConfigSpan#createNoBreakSpan()} as a shorthand.
      * Note that even if this style is specified, the grapheme based line break is still performed
      * for preventing clipping text.
      *
      * @see android.text.style.LineBreakConfigSpan
-     * @see android.text.style.LineBreakConfigSpan.NoBreakSpan
+     * @see android.text.style.LineBreakConfigSpan#createNoBreakSpan()
      */
     @FlaggedApi(FLAG_NO_BREAK_NO_HYPHENATION_SPAN)
     public static final int LINE_BREAK_STYLE_NO_BREAK = 4;
@@ -457,8 +459,9 @@
      *
      * <p>Use {@link LineBreakConfig.Builder} to create the
      * {@code LineBreakConfig} instance.
+     * @hide
      */
-    private LineBreakConfig(@LineBreakStyle int lineBreakStyle,
+    public LineBreakConfig(@LineBreakStyle int lineBreakStyle,
             @LineBreakWordStyle int lineBreakWordStyle,
             @Hyphenation int hyphenation) {
         mLineBreakStyle = lineBreakStyle;
@@ -606,4 +609,35 @@
                 + ", mHyphenation= " + mHyphenation
                 + '}';
     }
+
+    @FlaggedApi(FLAG_NO_BREAK_NO_HYPHENATION_SPAN)
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @FlaggedApi(FLAG_NO_BREAK_NO_HYPHENATION_SPAN)
+    @Override
+    public void writeToParcel(@NonNull Parcel dest, int flags) {
+        dest.writeInt(mLineBreakStyle);
+        dest.writeInt(mLineBreakWordStyle);
+        dest.writeInt(mHyphenation);
+    }
+
+    @FlaggedApi(FLAG_NO_BREAK_NO_HYPHENATION_SPAN)
+    public static final @NonNull Creator<LineBreakConfig> CREATOR = new Creator<>() {
+
+        @Override
+        public LineBreakConfig createFromParcel(Parcel source) {
+            final int lineBreakStyle = source.readInt();
+            final int lineBreakWordStyle = source.readInt();
+            final int hyphenation = source.readInt();
+            return new LineBreakConfig(lineBreakStyle, lineBreakWordStyle, hyphenation);
+        }
+
+        @Override
+        public LineBreakConfig[] newArray(int size) {
+            return new LineBreakConfig[size];
+        }
+    };
 }