Merge "Remove unnecessary letter spaceing from the left and right of line" into main
diff --git a/core/api/current.txt b/core/api/current.txt
index f09a73e..d4cec2d 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -16396,6 +16396,8 @@
     field public static final int START_HYPHEN_EDIT_NO_EDIT = 0; // 0x0
     field public static final int STRIKE_THRU_TEXT_FLAG = 16; // 0x10
     field public static final int SUBPIXEL_TEXT_FLAG = 128; // 0x80
+    field @FlaggedApi("com.android.text.flags.inter_character_justification") public static final int TEXT_RUN_FLAG_LEFT_EDGE = 8192; // 0x2000
+    field @FlaggedApi("com.android.text.flags.inter_character_justification") public static final int TEXT_RUN_FLAG_RIGHT_EDGE = 16384; // 0x4000
     field public static final int UNDERLINE_TEXT_FLAG = 8; // 0x8
   }
 
diff --git a/core/java/android/text/MeasuredParagraph.java b/core/java/android/text/MeasuredParagraph.java
index ac5eb3c..b268c2e 100644
--- a/core/java/android/text/MeasuredParagraph.java
+++ b/core/java/android/text/MeasuredParagraph.java
@@ -671,9 +671,18 @@
         if (mLtrWithoutBidi) {
             // If the whole text is LTR direction, just apply whole region.
             if (builder == null) {
-                mWholeWidth += paint.getTextRunAdvances(
-                        mCopiedBuffer, start, end - start, start, end - start, false /* isRtl */,
-                        mWidths.getRawArray(), start);
+                // For the compatibility reasons, the letter spacing should not be dropped at the
+                // left and right edge.
+                int oldFlag = paint.getFlags();
+                paint.setFlags(paint.getFlags()
+                        | (Paint.TEXT_RUN_FLAG_LEFT_EDGE | Paint.TEXT_RUN_FLAG_RIGHT_EDGE));
+                try {
+                    mWholeWidth += paint.getTextRunAdvances(
+                            mCopiedBuffer, start, end - start, start, end - start,
+                            false /* isRtl */, mWidths.getRawArray(), start);
+                } finally {
+                    paint.setFlags(oldFlag);
+                }
             } else {
                 builder.appendStyleRun(paint, config, end - start, false /* isRtl */);
             }
@@ -690,9 +699,16 @@
                     final boolean isRtl = (level & 0x1) != 0;
                     if (builder == null) {
                         final int levelLength = levelEnd - levelStart;
-                        mWholeWidth += paint.getTextRunAdvances(
-                                mCopiedBuffer, levelStart, levelLength, levelStart, levelLength,
-                                isRtl, mWidths.getRawArray(), levelStart);
+                        int oldFlag = paint.getFlags();
+                        paint.setFlags(paint.getFlags()
+                                | (Paint.TEXT_RUN_FLAG_LEFT_EDGE | Paint.TEXT_RUN_FLAG_RIGHT_EDGE));
+                        try {
+                            mWholeWidth += paint.getTextRunAdvances(
+                                    mCopiedBuffer, levelStart, levelLength, levelStart, levelLength,
+                                    isRtl, mWidths.getRawArray(), levelStart);
+                        } finally {
+                            paint.setFlags(oldFlag);
+                        }
                     } else {
                         builder.appendStyleRun(paint, config, levelEnd - levelStart, isRtl);
                     }
diff --git a/core/java/android/text/TextLine.java b/core/java/android/text/TextLine.java
index 135935c..2175b47 100644
--- a/core/java/android/text/TextLine.java
+++ b/core/java/android/text/TextLine.java
@@ -291,6 +291,97 @@
     }
 
     /**
+     * Returns the run flag of at the given BiDi run.
+     *
+     * @param bidiRunIndex a BiDi run index.
+     * @return a run flag of the given BiDi run.
+     */
+    @VisibleForTesting
+    public static int calculateRunFlag(int bidiRunIndex, int bidiRunCount, int lineDirection) {
+        if (bidiRunCount == 1) {
+            // Easy case. If there is only single run, it is most left and most right run.
+            return Paint.TEXT_RUN_FLAG_LEFT_EDGE | Paint.TEXT_RUN_FLAG_RIGHT_EDGE;
+        }
+        if (bidiRunIndex != 0 && bidiRunIndex != (bidiRunCount - 1)) {
+            // Easy case. If the given run is the middle of the line, it is not the most left or
+            // the most right run.
+            return 0;
+        }
+
+        int runFlag = 0;
+        // For the historical reasons, the BiDi implementation of Android works differently
+        // from the Java BiDi APIs. The mDirections holds the BiDi runs in visual order, but
+        // it is reversed order if the paragraph direction is RTL. So, the first BiDi run of
+        // mDirections is located the most left of the line if the paragraph direction is LTR.
+        // If the paragraph direction is RTL, the first BiDi run is located the most right of
+        // the line.
+        if (bidiRunIndex == 0) {
+            if (lineDirection == Layout.DIR_LEFT_TO_RIGHT) {
+                runFlag |= Paint.TEXT_RUN_FLAG_LEFT_EDGE;
+            } else {
+                runFlag |= Paint.TEXT_RUN_FLAG_RIGHT_EDGE;
+            }
+        }
+        if (bidiRunIndex == (bidiRunCount - 1)) {
+            if (lineDirection == Layout.DIR_LEFT_TO_RIGHT) {
+                runFlag |= Paint.TEXT_RUN_FLAG_RIGHT_EDGE;
+            } else {
+                runFlag |= Paint.TEXT_RUN_FLAG_LEFT_EDGE;
+            }
+        }
+        return runFlag;
+    }
+
+    /**
+     * Resolve the runFlag for the inline span range.
+     *
+     * @param runFlag the runFlag of the current BiDi run.
+     * @param isRtlRun true for RTL run, false for LTR run.
+     * @param runStart the inclusive BiDi run start offset.
+     * @param runEnd the exclusive BiDi run end offset.
+     * @param spanStart the inclusive span start offset.
+     * @param spanEnd the exclusive span end offset.
+     * @return the resolved runFlag.
+     */
+    @VisibleForTesting
+    public static int resolveRunFlagForSubSequence(int runFlag, boolean isRtlRun, int runStart,
+            int runEnd, int spanStart, int spanEnd) {
+        if (runFlag == 0) {
+            // Easy case. If the run is in the middle of the line, any inline span is also in the
+            // middle of the line.
+            return 0;
+        }
+        int localRunFlag = runFlag;
+        if ((runFlag & Paint.TEXT_RUN_FLAG_LEFT_EDGE) != 0) {
+            if (isRtlRun) {
+                if (spanEnd != runEnd) {
+                    // In the RTL context, the last run is the most left run.
+                    localRunFlag &= ~Paint.TEXT_RUN_FLAG_LEFT_EDGE;
+                }
+            } else {  // LTR
+                if (spanStart != runStart) {
+                    // In the LTR context, the first run is the most left run.
+                    localRunFlag &= ~Paint.TEXT_RUN_FLAG_LEFT_EDGE;
+                }
+            }
+        }
+        if ((runFlag & Paint.TEXT_RUN_FLAG_RIGHT_EDGE) != 0) {
+            if (isRtlRun) {
+                if (spanStart != runStart) {
+                    // In the RTL context, the start of the run is the most right run.
+                    localRunFlag &= ~Paint.TEXT_RUN_FLAG_RIGHT_EDGE;
+                }
+            } else {  // LTR
+                if (spanEnd != runEnd) {
+                    // In the LTR context, the last run is the most right position.
+                    localRunFlag &= ~Paint.TEXT_RUN_FLAG_RIGHT_EDGE;
+                }
+            }
+        }
+        return localRunFlag;
+    }
+
+    /**
      * Renders the TextLine.
      *
      * @param c the canvas to render on
@@ -308,11 +399,13 @@
             final int runLimit = Math.min(runStart + mDirections.getRunLength(runIndex), mLen);
             final boolean runIsRtl = mDirections.isRunRtl(runIndex);
 
+            final int runFlag = calculateRunFlag(runIndex, runCount, mDir);
+
             int segStart = runStart;
             for (int j = mHasTabs ? runStart : runLimit; j <= runLimit; j++) {
                 if (j == runLimit || charAt(j) == TAB_CHAR) {
                     h += drawRun(c, segStart, j, runIsRtl, x + h, top, y, bottom,
-                            runIndex != (runCount - 1) || j != mLen);
+                            runIndex != (runCount - 1) || j != mLen, runFlag);
 
                     if (j != runLimit) {  // charAt(j) == TAB_CHAR
                         h = mDir * nextTab(h * mDir);
@@ -371,11 +464,12 @@
             final int runLimit = Math.min(runStart + mDirections.getRunLength(runIndex), mLen);
             final boolean runIsRtl = mDirections.isRunRtl(runIndex);
 
+            final int runFlag = calculateRunFlag(runIndex, runCount, mDir);
             int segStart = runStart;
             for (int j = mHasTabs ? runStart : runLimit; j <= runLimit; j++) {
                 if (j == runLimit || charAt(j) == TAB_CHAR) {
                     horizontal += shapeRun(consumer, segStart, j, runIsRtl, x + horizontal,
-                            runIndex != (runCount - 1) || j != mLen);
+                            runIndex != (runCount - 1) || j != mLen, runFlag);
 
                     if (j != runLimit) {  // charAt(j) == TAB_CHAR
                         horizontal = mDir * nextTab(horizontal * mDir);
@@ -441,11 +535,13 @@
         }
 
         float h = 0;
-        for (int runIndex = 0; runIndex < mDirections.getRunCount(); runIndex++) {
+        final int runCount = mDirections.getRunCount();
+        for (int runIndex = 0; runIndex < runCount; runIndex++) {
             final int runStart = mDirections.getRunStart(runIndex);
             if (runStart > mLen) break;
             final int runLimit = Math.min(runStart + mDirections.getRunLength(runIndex), mLen);
             final boolean runIsRtl = mDirections.isRunRtl(runIndex);
+            final int runFlag = calculateRunFlag(runIndex, runCount, mDir);
 
             int segStart = runStart;
             for (int j = mHasTabs ? runStart : runLimit; j <= runLimit; j++) {
@@ -455,16 +551,16 @@
 
                     if (targetIsInThisSegment && sameDirection) {
                         return h + measureRun(segStart, offset, j, runIsRtl, fmi, drawBounds, null,
-                                0, h, lineInfo);
+                                0, h, lineInfo, runFlag);
                     }
 
                     final float segmentWidth = measureRun(segStart, j, j, runIsRtl, fmi, drawBounds,
-                            null, 0, h, lineInfo);
+                            null, 0, h, lineInfo, runFlag);
                     h += sameDirection ? segmentWidth : -segmentWidth;
 
                     if (targetIsInThisSegment) {
                         return h + measureRun(segStart, offset, j, runIsRtl, null, null,  null, 0,
-                                h, lineInfo);
+                                h, lineInfo, runFlag);
                     }
 
                     if (j != runLimit) {  // charAt(j) == TAB_CHAR
@@ -543,20 +639,21 @@
                     + "result, needed: " + mLen + " had: " + advances.length);
         }
         float h = 0;
-        for (int runIndex = 0; runIndex < mDirections.getRunCount(); runIndex++) {
+        final int runCount = mDirections.getRunCount();
+        for (int runIndex = 0; runIndex < runCount; runIndex++) {
             final int runStart = mDirections.getRunStart(runIndex);
             if (runStart > mLen) break;
             final int runLimit = Math.min(runStart + mDirections.getRunLength(runIndex), mLen);
             final boolean runIsRtl = mDirections.isRunRtl(runIndex);
+            final int runFlag = calculateRunFlag(runIndex, runCount, mDir);
 
             int segStart = runStart;
             for (int j = mHasTabs ? runStart : runLimit; j <= runLimit; j++) {
                 if (j == runLimit || charAt(j) == TAB_CHAR) {
                     final boolean sameDirection = (mDir == Layout.DIR_RIGHT_TO_LEFT) == runIsRtl;
-
                     final float segmentWidth =
                             measureRun(segStart, j, j, runIsRtl, null, null, advances, segStart, 0,
-                                    null);
+                                    null, runFlag);
 
                     final float oldh = h;
                     h += sameDirection ? segmentWidth : -segmentWidth;
@@ -608,11 +705,13 @@
         }
 
         float horizontal = 0;
-        for (int runIndex = 0; runIndex < mDirections.getRunCount(); runIndex++) {
+        final int runCount = mDirections.getRunCount();
+        for (int runIndex = 0; runIndex < runCount; runIndex++) {
             final int runStart = mDirections.getRunStart(runIndex);
             if (runStart > mLen) break;
             final int runLimit = Math.min(runStart + mDirections.getRunLength(runIndex), mLen);
             final boolean runIsRtl = mDirections.isRunRtl(runIndex);
+            final int runFlag = calculateRunFlag(runIndex, runCount, mDir);
 
             int segStart = runStart;
             for (int j = mHasTabs ? runStart : runLimit; j <= runLimit; ++j) {
@@ -629,7 +728,7 @@
                     final float previousSegEndHorizontal = measurement[segStart];
                     final float width =
                             measureRun(segStart, j, j, runIsRtl, fmi, null, measurement, segStart,
-                                    0, null);
+                                    0, null, runFlag);
                     horizontal += sameDirection ? width : -width;
 
                     float currHorizontal = sameDirection ? oldHorizontal : horizontal;
@@ -686,22 +785,24 @@
      * @param y the baseline
      * @param bottom the bottom of the line
      * @param needWidth true if the width value is required.
+     * @param runFlag the run flag to be applied for this run.
      * @return the signed width of the run, based on the paragraph direction.
      * Only valid if needWidth is true.
      */
     private float drawRun(Canvas c, int start,
             int limit, boolean runIsRtl, float x, int top, int y, int bottom,
-            boolean needWidth) {
+            boolean needWidth, int runFlag) {
 
         if ((mDir == Layout.DIR_LEFT_TO_RIGHT) == runIsRtl) {
-            float w = -measureRun(start, limit, limit, runIsRtl, null, null, null, 0, 0, null);
+            float w = -measureRun(start, limit, limit, runIsRtl, null, null, null, 0, 0, null,
+                    runFlag);
             handleRun(start, limit, limit, runIsRtl, c, null, x + w, top,
-                    y, bottom, null, null, false, null, 0, null);
+                    y, bottom, null, null, false, null, 0, null, runFlag);
             return w;
         }
 
         return handleRun(start, limit, limit, runIsRtl, c, null, x, top,
-                y, bottom, null, null, needWidth, null, 0, null);
+                y, bottom, null, null, needWidth, null, 0, null, runFlag);
     }
 
     /**
@@ -718,19 +819,21 @@
      * @param advancesIndex the start index to fill in the advance information.
      * @param x horizontal offset of the run.
      * @param lineInfo an optional output parameter for filling line information.
+     * @param runFlag the run flag to be applied for this run.
      * @return the signed width from the start of the run to the leading edge
      * of the character at offset, based on the run (not paragraph) direction
      */
     private float measureRun(int start, int offset, int limit, boolean runIsRtl,
             @Nullable FontMetricsInt fmi, @Nullable RectF drawBounds, @Nullable float[] advances,
-            int advancesIndex, float x, @Nullable LineInfo lineInfo) {
+            int advancesIndex, float x, @Nullable LineInfo lineInfo, int runFlag) {
         if (drawBounds != null && (mDir == Layout.DIR_LEFT_TO_RIGHT) == runIsRtl) {
-            float w = -measureRun(start, offset, limit, runIsRtl, null, null, null, 0, 0, null);
+            float w = -measureRun(start, offset, limit, runIsRtl, null, null, null, 0, 0, null,
+                    runFlag);
             return handleRun(start, offset, limit, runIsRtl, null, null, x + w, 0, 0, 0, fmi,
-                    drawBounds, true, advances, advancesIndex, lineInfo);
+                    drawBounds, true, advances, advancesIndex, lineInfo, runFlag);
         }
         return handleRun(start, offset, limit, runIsRtl, null, null, x, 0, 0, 0, fmi, drawBounds,
-                true, advances, advancesIndex, lineInfo);
+                true, advances, advancesIndex, lineInfo, runFlag);
     }
 
     /**
@@ -742,21 +845,23 @@
      * @param runIsRtl true if the run is right-to-left
      * @param x the position of the run that is closest to the leading margin
      * @param needWidth true if the width value is required.
+     * @param runFlag the run flag to be applied for this run.
      * @return the signed width of the run, based on the paragraph direction.
      * Only valid if needWidth is true.
      */
     private float shapeRun(TextShaper.GlyphsConsumer consumer, int start,
-            int limit, boolean runIsRtl, float x, boolean needWidth) {
+            int limit, boolean runIsRtl, float x, boolean needWidth, int runFlag) {
 
         if ((mDir == Layout.DIR_LEFT_TO_RIGHT) == runIsRtl) {
-            float w = -measureRun(start, limit, limit, runIsRtl, null, null, null, 0, 0, null);
+            float w = -measureRun(start, limit, limit, runIsRtl, null, null, null, 0, 0, null,
+                    runFlag);
             handleRun(start, limit, limit, runIsRtl, null, consumer, x + w, 0, 0, 0, null, null,
-                    false, null, 0, null);
+                    false, null, 0, null, runFlag);
             return w;
         }
 
         return handleRun(start, limit, limit, runIsRtl, null, consumer, x, 0, 0, 0, null, null,
-                needWidth, null, 0, null);
+                needWidth, null, 0, null, runFlag);
     }
 
 
@@ -1160,6 +1265,7 @@
      * @param advances receives the advance information about the requested run, can be null.
      * @param advancesIndex the start index to fill in the advance information.
      * @param lineInfo an optional output parameter for filling line information.
+     * @param runFlag the run flag to be applied for this run.
      * @return the signed width of the run based on the run direction; only
      * valid if needWidth is true
      */
@@ -1168,8 +1274,8 @@
             Canvas c, TextShaper.GlyphsConsumer consumer, float x, int top, int y, int bottom,
             FontMetricsInt fmi, RectF drawBounds, boolean needWidth, int offset,
             @Nullable ArrayList<DecorationInfo> decorations,
-            @Nullable float[] advances, int advancesIndex, @Nullable LineInfo lineInfo) {
-
+            @Nullable float[] advances, int advancesIndex, @Nullable LineInfo lineInfo,
+            int runFlag) {
         if (mIsJustifying) {
             wp.setWordSpacing(mAddedWidthForJustify);
         }
@@ -1187,7 +1293,16 @@
         }
 
         float totalWidth = 0;
-
+        if ((runFlag & Paint.TEXT_RUN_FLAG_LEFT_EDGE) == Paint.TEXT_RUN_FLAG_LEFT_EDGE) {
+            wp.setFlags(wp.getFlags() | Paint.TEXT_RUN_FLAG_LEFT_EDGE);
+        } else {
+            wp.setFlags(wp.getFlags() & ~Paint.TEXT_RUN_FLAG_LEFT_EDGE);
+        }
+        if ((runFlag & Paint.TEXT_RUN_FLAG_RIGHT_EDGE) == Paint.TEXT_RUN_FLAG_RIGHT_EDGE) {
+            wp.setFlags(wp.getFlags() | Paint.TEXT_RUN_FLAG_RIGHT_EDGE);
+        } else {
+            wp.setFlags(wp.getFlags() & ~Paint.TEXT_RUN_FLAG_RIGHT_EDGE);
+        }
         final int numDecorations = decorations == null ? 0 : decorations.size();
         if (needWidth || ((c != null || consumer != null) && (wp.bgColor != 0
                 || numDecorations != 0 || runIsRtl))) {
@@ -1419,6 +1534,7 @@
      * @param advances receives the advance information about the requested run, can be null.
      * @param advancesIndex the start index to fill in the advance information.
      * @param lineInfo an optional output parameter for filling line information.
+     * @param runFlag the run flag to be applied for this run.
      * @return the signed width of the run based on the run direction; only
      * valid if needWidth is true
      */
@@ -1426,7 +1542,8 @@
             int limit, boolean runIsRtl, Canvas c,
             TextShaper.GlyphsConsumer consumer, float x, int top, int y,
             int bottom, FontMetricsInt fmi, RectF drawBounds, boolean needWidth,
-            @Nullable float[] advances, int advancesIndex, @Nullable LineInfo lineInfo) {
+            @Nullable float[] advances, int advancesIndex, @Nullable LineInfo lineInfo,
+            int runFlag) {
 
         if (measureLimit < start || measureLimit > limit) {
             throw new IndexOutOfBoundsException("measureLimit (" + measureLimit + ") is out of "
@@ -1473,7 +1590,7 @@
             wp.setEndHyphenEdit(adjustEndHyphenEdit(limit, wp.getEndHyphenEdit()));
             return handleText(wp, start, limit, start, limit, runIsRtl, c, consumer, x, top,
                     y, bottom, fmi, drawBounds, needWidth, measureLimit, null, advances,
-                    advancesIndex, lineInfo);
+                    advancesIndex, lineInfo, runFlag);
         }
 
         // Shaping needs to take into account context up to metric boundaries,
@@ -1554,6 +1671,9 @@
                     // and use.
                     activePaint.set(wp);
                 } else if (!equalAttributes(wp, activePaint)) {
+                    final int spanRunFlag = resolveRunFlagForSubSequence(
+                            runFlag, runIsRtl, start, measureLimit, activeStart, activeEnd);
+
                     // The style of the present chunk of text is substantially different from the
                     // style of the previous chunk. We need to handle the active piece of text
                     // and restart with the present chunk.
@@ -1565,7 +1685,7 @@
                             consumer, x, top, y, bottom, fmi, drawBounds,
                             needWidth || activeEnd < measureLimit,
                             Math.min(activeEnd, mlimit), mDecorations,
-                            advances, advancesIndex + activeStart - start, lineInfo);
+                            advances, advancesIndex + activeStart - start, lineInfo, spanRunFlag);
 
                     activeStart = j;
                     activePaint.set(wp);
@@ -1585,6 +1705,9 @@
                     mDecorations.add(copy);
                 }
             }
+
+            final int spanRunFlag = resolveRunFlagForSubSequence(
+                    runFlag, runIsRtl, start, measureLimit, activeStart, activeEnd);
             // Handle the final piece of text.
             activePaint.setStartHyphenEdit(
                     adjustStartHyphenEdit(activeStart, mPaint.getStartHyphenEdit()));
@@ -1593,7 +1716,7 @@
             x += handleText(activePaint, activeStart, activeEnd, i, inext, runIsRtl, c, consumer, x,
                     top, y, bottom, fmi, drawBounds, needWidth || activeEnd < measureLimit,
                     Math.min(activeEnd, mlimit), mDecorations,
-                    advances, advancesIndex + activeStart - start, lineInfo);
+                    advances, advancesIndex + activeStart - start, lineInfo, spanRunFlag);
         }
 
         return x - originalX;
@@ -1614,7 +1737,6 @@
      */
     private void drawTextRun(Canvas c, TextPaint wp, int start, int end,
             int contextStart, int contextEnd, boolean runIsRtl, float x, int y) {
-
         if (mCharsValid) {
             int count = end - start;
             int contextCount = contextEnd - contextStart;
diff --git a/core/tests/coretests/src/android/text/TextLineLetterSpacingTest.kt b/core/tests/coretests/src/android/text/TextLineLetterSpacingTest.kt
new file mode 100644
index 0000000..27869bb
--- /dev/null
+++ b/core/tests/coretests/src/android/text/TextLineLetterSpacingTest.kt
@@ -0,0 +1,224 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.text
+
+import android.graphics.Paint
+import android.platform.test.annotations.RequiresFlagsEnabled
+import android.platform.test.flag.junit.DeviceFlagsValueProvider
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.text.flags.Flags.FLAG_INTER_CHARACTER_JUSTIFICATION
+import com.google.common.truth.Truth.assertThat
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+
+const val LEFT_EDGE = Paint.TEXT_RUN_FLAG_LEFT_EDGE
+const val RIGHT_EDGE = Paint.TEXT_RUN_FLAG_RIGHT_EDGE
+const val MIDDLE_OF_LINE = 0
+const val WHOLE_LINE = LEFT_EDGE or RIGHT_EDGE
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class TextLineLetterSpacingTest {
+
+    @Rule
+    @JvmField
+    val mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule()
+
+    @RequiresFlagsEnabled(FLAG_INTER_CHARACTER_JUSTIFICATION)
+    @Test
+    fun calculateRunFlagTest() {
+        // Only one Bidi run
+        assertThat(TextLine.calculateRunFlag(0, 1, Layout.DIR_LEFT_TO_RIGHT))
+                .isEqualTo(WHOLE_LINE)
+        assertThat(TextLine.calculateRunFlag(0, 1, Layout.DIR_RIGHT_TO_LEFT))
+                .isEqualTo(WHOLE_LINE)
+
+        // Two BiDi Runs.
+        // If the layout is LTR, the first run is the left most run.
+        assertThat(TextLine.calculateRunFlag(0, 2, Layout.DIR_LEFT_TO_RIGHT))
+                .isEqualTo(LEFT_EDGE)
+        // If the layout is LTR, the last run is the right most run.
+        assertThat(TextLine.calculateRunFlag(1, 2, Layout.DIR_LEFT_TO_RIGHT))
+                .isEqualTo(RIGHT_EDGE)
+        // If the layout is RTL, the first run is the right most run.
+        assertThat(TextLine.calculateRunFlag(0, 2, Layout.DIR_RIGHT_TO_LEFT))
+                .isEqualTo(RIGHT_EDGE)
+        // If the layout is RTL, the last run is the left most run.
+        assertThat(TextLine.calculateRunFlag(1, 2, Layout.DIR_RIGHT_TO_LEFT))
+                .isEqualTo(LEFT_EDGE)
+
+        // Three BiDi Runs.
+        // If the layout is LTR, the first run is the left most run.
+        assertThat(TextLine.calculateRunFlag(0, 3, Layout.DIR_LEFT_TO_RIGHT))
+                .isEqualTo(LEFT_EDGE)
+        // Regardless of the context direction, the middle run must not have any flags.
+        assertThat(TextLine.calculateRunFlag(1, 3, Layout.DIR_LEFT_TO_RIGHT))
+                .isEqualTo(MIDDLE_OF_LINE)
+        // If the layout is LTR, the last run is the right most run.
+        assertThat(TextLine.calculateRunFlag(2, 3, Layout.DIR_LEFT_TO_RIGHT))
+                .isEqualTo(RIGHT_EDGE)
+        // If the layout is RTL, the first run is the right most run.
+        assertThat(TextLine.calculateRunFlag(0, 3, Layout.DIR_RIGHT_TO_LEFT))
+                .isEqualTo(RIGHT_EDGE)
+        // Regardless of the context direction, the middle run must not have any flags.
+        assertThat(TextLine.calculateRunFlag(1, 3, Layout.DIR_RIGHT_TO_LEFT))
+                .isEqualTo(MIDDLE_OF_LINE)
+        // If the layout is RTL, the last run is the left most run.
+        assertThat(TextLine.calculateRunFlag(2, 3, Layout.DIR_RIGHT_TO_LEFT))
+                .isEqualTo(LEFT_EDGE)
+    }
+
+    @RequiresFlagsEnabled(FLAG_INTER_CHARACTER_JUSTIFICATION)
+    @Test
+    fun resolveRunFlagForSubSequenceTest() {
+        val runStart = 5
+        val runEnd = 15
+        // Regardless of the run directions, if the span covers entire Bidi run, the same runFlag
+        // should be returned.
+        assertThat(TextLine.resolveRunFlagForSubSequence(
+                LEFT_EDGE, false, runStart, runEnd, runStart, runEnd))
+                .isEqualTo(LEFT_EDGE)
+        assertThat(TextLine.resolveRunFlagForSubSequence(
+                LEFT_EDGE, true, runStart, runEnd, runStart, runEnd))
+                .isEqualTo(LEFT_EDGE)
+        assertThat(TextLine.resolveRunFlagForSubSequence(
+                RIGHT_EDGE, false, runStart, runEnd, runStart, runEnd))
+                .isEqualTo(RIGHT_EDGE)
+        assertThat(TextLine.resolveRunFlagForSubSequence(
+                RIGHT_EDGE, true, runStart, runEnd, runStart, runEnd))
+                .isEqualTo(RIGHT_EDGE)
+        assertThat(TextLine.resolveRunFlagForSubSequence(
+                WHOLE_LINE, false, runStart, runEnd, runStart, runEnd))
+                .isEqualTo(WHOLE_LINE)
+        assertThat(TextLine.resolveRunFlagForSubSequence(
+                WHOLE_LINE, true, runStart, runEnd, runStart, runEnd))
+                .isEqualTo(WHOLE_LINE)
+        assertThat(TextLine.resolveRunFlagForSubSequence(
+                MIDDLE_OF_LINE, false, runStart, runEnd, runStart, runEnd))
+                .isEqualTo(MIDDLE_OF_LINE)
+        assertThat(TextLine.resolveRunFlagForSubSequence(
+                MIDDLE_OF_LINE, true, runStart, runEnd, runStart, runEnd))
+                .isEqualTo(MIDDLE_OF_LINE)
+
+
+
+        // Left edge of LTR text, span start from run start offset but not cover the run.
+        assertThat(TextLine.resolveRunFlagForSubSequence(
+                LEFT_EDGE, false, runStart, runEnd, runStart, runEnd - 1))
+                .isEqualTo(LEFT_EDGE)
+        // Left edge of RTL text, span start from run start offset but not cover the run.
+        assertThat(TextLine.resolveRunFlagForSubSequence(
+                LEFT_EDGE, true, runStart, runEnd, runStart, runEnd - 1))
+                .isEqualTo(MIDDLE_OF_LINE)
+        // Right edge of LTR text, span start from run start offset but not cover the run.
+        assertThat(TextLine.resolveRunFlagForSubSequence(
+                RIGHT_EDGE, false, runStart, runEnd, runStart, runEnd - 1))
+                .isEqualTo(MIDDLE_OF_LINE)
+        // Right edge of RTL text, span start from run start offset but not cover the run.
+        assertThat(TextLine.resolveRunFlagForSubSequence(
+                RIGHT_EDGE, true, runStart, runEnd, runStart, runEnd - 1))
+                .isEqualTo(RIGHT_EDGE)
+        // Whole line of LTR text, span start from run start offset but not cover the run.
+        assertThat(TextLine.resolveRunFlagForSubSequence(
+                WHOLE_LINE, false, runStart, runEnd, runStart, runEnd - 1))
+                .isEqualTo(LEFT_EDGE)
+        // Whole line of RTL text, span start from run start offset but not cover the run.
+        assertThat(TextLine.resolveRunFlagForSubSequence(
+                WHOLE_LINE, true, runStart, runEnd, runStart, runEnd - 1))
+                .isEqualTo(RIGHT_EDGE)
+        // Middle of LTR text, span start from run start offset but not cover the run.
+        assertThat(TextLine.resolveRunFlagForSubSequence(
+                MIDDLE_OF_LINE, false, runStart, runEnd, runStart, runEnd - 1))
+                .isEqualTo(MIDDLE_OF_LINE)
+        // Middle of RTL text, span start from run start offset but not cover the run.
+        assertThat(TextLine.resolveRunFlagForSubSequence(
+                MIDDLE_OF_LINE, true, runStart, runEnd, runStart, runEnd - 1))
+                .isEqualTo(MIDDLE_OF_LINE)
+
+
+
+        // Left edge of LTR text, span start from middle of run start offset until end of run.
+        assertThat(TextLine.resolveRunFlagForSubSequence(
+                LEFT_EDGE, false, runStart, runEnd, runStart + 1, runEnd))
+                .isEqualTo(MIDDLE_OF_LINE)
+        // Left edge of RTL text, span start from middle of run start offset until end of run.
+        assertThat(TextLine.resolveRunFlagForSubSequence(
+                LEFT_EDGE, true, runStart, runEnd, runStart + 1, runEnd))
+                .isEqualTo(LEFT_EDGE)
+        // Right edge of LTR text, span start from middle of run start offset until end of run.
+        assertThat(TextLine.resolveRunFlagForSubSequence(
+                RIGHT_EDGE, false, runStart, runEnd, runStart + 1, runEnd))
+                .isEqualTo(RIGHT_EDGE)
+        // Right edge of RTL text, span start from middle of run start offset until end of run.
+        assertThat(TextLine.resolveRunFlagForSubSequence(
+                RIGHT_EDGE, true, runStart, runEnd, runStart + 1, runEnd))
+                .isEqualTo(MIDDLE_OF_LINE)
+        // Whole line of LTR text, span start from middle of run start offset until end of run.
+        assertThat(TextLine.resolveRunFlagForSubSequence(
+                WHOLE_LINE, false, runStart, runEnd, runStart + 1, runEnd))
+                .isEqualTo(RIGHT_EDGE)
+        // Whole line of RTL text, span start from middle of run start offset until end of run.
+        assertThat(TextLine.resolveRunFlagForSubSequence(
+                WHOLE_LINE, true, runStart, runEnd, runStart + 1, runEnd))
+                .isEqualTo(LEFT_EDGE)
+        // Middle of LTR text, span start from middle of run start offset until end of run.
+        assertThat(TextLine.resolveRunFlagForSubSequence(
+                MIDDLE_OF_LINE, false, runStart, runEnd, runStart + 1, runEnd))
+                .isEqualTo(MIDDLE_OF_LINE)
+        // Middle of RTL text, span start from middle of run start offset until end of run.
+        assertThat(TextLine.resolveRunFlagForSubSequence(
+                MIDDLE_OF_LINE, true, runStart, runEnd, runStart + 1, runEnd))
+                .isEqualTo(MIDDLE_OF_LINE)
+
+
+
+        // Left edge of LTR text, span start from middle of run.
+        assertThat(TextLine.resolveRunFlagForSubSequence(
+                LEFT_EDGE, false, runStart, runEnd, runStart + 1, runEnd - 1))
+                .isEqualTo(MIDDLE_OF_LINE)
+        // Left edge of RTL text, span start from middle of run.
+        assertThat(TextLine.resolveRunFlagForSubSequence(
+                LEFT_EDGE, true, runStart, runEnd, runStart + 1, runEnd - 1))
+                .isEqualTo(MIDDLE_OF_LINE)
+        // Right edge of LTR text, span start from middle of run.
+        assertThat(TextLine.resolveRunFlagForSubSequence(
+                RIGHT_EDGE, false, runStart, runEnd, runStart + 1, runEnd - 1))
+                .isEqualTo(MIDDLE_OF_LINE)
+        // Right edge of RTL text, span start from middle of run.
+        assertThat(TextLine.resolveRunFlagForSubSequence(
+                RIGHT_EDGE, true, runStart, runEnd, runStart + 1, runEnd - 1))
+                .isEqualTo(MIDDLE_OF_LINE)
+        // Whole line of LTR text, span start from middle of run.
+        assertThat(TextLine.resolveRunFlagForSubSequence(
+                WHOLE_LINE, false, runStart, runEnd, runStart + 1, runEnd - 1))
+                .isEqualTo(MIDDLE_OF_LINE)
+        // Whole line of RTL text, span start from middle of run.
+        assertThat(TextLine.resolveRunFlagForSubSequence(
+                WHOLE_LINE, true, runStart, runEnd, runStart + 1, runEnd - 1))
+                .isEqualTo(MIDDLE_OF_LINE)
+        // Middle of LTR text, span start from middle of run.
+        assertThat(TextLine.resolveRunFlagForSubSequence(
+                MIDDLE_OF_LINE, false, runStart, runEnd, runStart + 1, runEnd - 1))
+                .isEqualTo(MIDDLE_OF_LINE)
+        // Middle of RTL text, span start from middle of run.
+        assertThat(TextLine.resolveRunFlagForSubSequence(
+                MIDDLE_OF_LINE, true, runStart, runEnd, runStart + 1, runEnd - 1))
+                .isEqualTo(MIDDLE_OF_LINE)
+    }
+}
\ No newline at end of file
diff --git a/graphics/java/android/graphics/Paint.java b/graphics/java/android/graphics/Paint.java
index f6ba103..ae61a2d 100644
--- a/graphics/java/android/graphics/Paint.java
+++ b/graphics/java/android/graphics/Paint.java
@@ -17,6 +17,7 @@
 package android.graphics;
 
 import static com.android.text.flags.Flags.FLAG_FIX_LINE_HEIGHT_FOR_LOCALE;
+import static com.android.text.flags.Flags.FLAG_INTER_CHARACTER_JUSTIFICATION;
 
 import android.annotation.ColorInt;
 import android.annotation.ColorLong;
@@ -133,7 +134,9 @@
             FAKE_BOLD_TEXT_FLAG,
             LINEAR_TEXT_FLAG,
             SUBPIXEL_TEXT_FLAG,
-            EMBEDDED_BITMAP_TEXT_FLAG
+            EMBEDDED_BITMAP_TEXT_FLAG,
+            TEXT_RUN_FLAG_LEFT_EDGE,
+            TEXT_RUN_FLAG_RIGHT_EDGE
     })
     public @interface PaintFlag{}
 
@@ -264,6 +267,66 @@
     /** @hide bit mask for the flag enabling vertical rendering for text */
     public static final int VERTICAL_TEXT_FLAG = 0x1000;
 
+    /**
+     * A text run flag that indicates the run is located the visually most left segment of the line.
+     * <p>
+     * This flag is used for telling the underlying text layout engine that the text is located at
+     * the most left of the line. This flag is used for controlling the amount letter spacing
+     * added. If the text is in the middle of the line, the text layout engine assigns additional
+     * letter spacing to the both side of each letter. On the other hand, the letter spacing should
+     * not be added to the visually most left and right of the line. By setting this flag, text
+     * layout engine calculates the layout as it is located at the most visually left of the line
+     * and doesn't add letter spacing to the left of this run.
+     * <p>
+     * Note that the caller must resolve BiDi runs and reorder them visually and set this flag only
+     * if the target run is located visually most left position. This left does not always mean the
+     * beginning of the text.
+     * <p>
+     * If the run covers entire line, caller should set {@link #TEXT_RUN_FLAG_RIGHT_EDGE} as well.
+     * <p>
+     * Note that this flag is only effective for run based APIs. For example, this flag works for
+     * {@link Canvas#drawTextRun(CharSequence, int, int, int, int, float, float, boolean, Paint)}
+     * and
+     * {@link Paint#getRunCharacterAdvance(char[], int, int, int, int, boolean, int, float[], int)}.
+     * However, this doesn't work for
+     * {@link Canvas#drawText(CharSequence, int, int, float, float, Paint)} or
+     * {@link Paint#measureText(CharSequence, int, int)}. The non-run based APIs works as both
+     * {@link #TEXT_RUN_FLAG_LEFT_EDGE} and {@link #TEXT_RUN_FLAG_RIGHT_EDGE} are specified.
+     */
+    @FlaggedApi(FLAG_INTER_CHARACTER_JUSTIFICATION)
+    public static final int TEXT_RUN_FLAG_LEFT_EDGE = 0x2000;
+
+
+    /**
+     * A text run flag that indicates the run is located the visually most right segment of the
+     * line.
+     * <p>
+     * This flag is used for telling the underlying text layout engine that the text is located at
+     * the most right of the line. This flag is used for controlling the amount letter spacing
+     * added. If the text is in the middle of the line, the text layout engine assigns additional
+     * letter spacing to the both side of each letter. On the other hand, the letter spacing should
+     * not be added to the visually most left and right of the line. By setting this flag, text
+     * layout engine calculates the layout as it is located at the most visually left of the line
+     * and doesn't add letter spacing to the left of this run.
+     * <p>
+     * Note that the caller must resolve BiDi runs and reorder them visually and set this flag only
+     * if the target run is located visually most right position. This right does not always mean
+     * the end of the text.
+     * <p>
+     * If the run covers entire line, caller should set {@link #TEXT_RUN_FLAG_LEFT_EDGE} as well.
+     * <p>
+     * Note that this flag is only effective for run based APIs. For example, this flag works for
+     * {@link Canvas#drawTextRun(CharSequence, int, int, int, int, float, float, boolean, Paint)}
+     * and
+     * {@link Paint#getRunCharacterAdvance(char[], int, int, int, int, boolean, int, float[], int)}.
+     * However, this doesn't work for
+     * {@link Canvas#drawText(CharSequence, int, int, float, float, Paint)} or
+     * {@link Paint#measureText(CharSequence, int, int)}. The non-run based APIs works as both
+     * {@link #TEXT_RUN_FLAG_LEFT_EDGE} and {@link #TEXT_RUN_FLAG_RIGHT_EDGE} are specified.
+     */
+    @FlaggedApi(FLAG_INTER_CHARACTER_JUSTIFICATION)
+    public static final int TEXT_RUN_FLAG_RIGHT_EDGE = 0x4000;
+
     // These flags are always set on a new/reset paint, even if flags 0 is passed.
     static final int HIDDEN_DEFAULT_PAINT_FLAGS = DEV_KERN_TEXT_FLAG | EMBEDDED_BITMAP_TEXT_FLAG
             | FILTER_BITMAP_FLAG;
@@ -2520,17 +2583,24 @@
         if (text.length == 0 || count == 0) {
             return 0f;
         }
-        if (!mHasCompatScaling) {
-            return (float) Math.ceil(nGetTextAdvances(mNativePaint, text,
-                    index, count, index, count, mBidiFlags, null, 0));
-        }
+        int oldFlag = getFlags();
+        setFlags(getFlags() | (TEXT_RUN_FLAG_LEFT_EDGE | TEXT_RUN_FLAG_RIGHT_EDGE));
+        try {
 
-        final float oldSize = getTextSize();
-        setTextSize(oldSize * mCompatScaling);
-        final float w = nGetTextAdvances(mNativePaint, text, index, count, index, count,
-                mBidiFlags, null, 0);
-        setTextSize(oldSize);
-        return (float) Math.ceil(w*mInvCompatScaling);
+            if (!mHasCompatScaling) {
+                return (float) Math.ceil(nGetTextAdvances(mNativePaint, text,
+                        index, count, index, count, mBidiFlags, null, 0));
+            }
+
+            final float oldSize = getTextSize();
+            setTextSize(oldSize * mCompatScaling);
+            final float w = nGetTextAdvances(mNativePaint, text, index, count, index, count,
+                    mBidiFlags, null, 0);
+            setTextSize(oldSize);
+            return (float) Math.ceil(w * mInvCompatScaling);
+        } finally {
+            setFlags(oldFlag);
+        }
     }
 
     /**
@@ -2552,16 +2622,22 @@
         if (text.length() == 0 || start == end) {
             return 0f;
         }
-        if (!mHasCompatScaling) {
-            return (float) Math.ceil(nGetTextAdvances(mNativePaint, text,
-                    start, end, start, end, mBidiFlags, null, 0));
+        int oldFlag = getFlags();
+        setFlags(getFlags() | (TEXT_RUN_FLAG_LEFT_EDGE | TEXT_RUN_FLAG_RIGHT_EDGE));
+        try {
+            if (!mHasCompatScaling) {
+                return (float) Math.ceil(nGetTextAdvances(mNativePaint, text,
+                        start, end, start, end, mBidiFlags, null, 0));
+            }
+            final float oldSize = getTextSize();
+            setTextSize(oldSize * mCompatScaling);
+            final float w = nGetTextAdvances(mNativePaint, text, start, end, start, end, mBidiFlags,
+                    null, 0);
+            setTextSize(oldSize);
+            return (float) Math.ceil(w * mInvCompatScaling);
+        } finally {
+            setFlags(oldFlag);
         }
-        final float oldSize = getTextSize();
-        setTextSize(oldSize * mCompatScaling);
-        final float w = nGetTextAdvances(mNativePaint, text, start, end, start, end, mBidiFlags,
-                null, 0);
-        setTextSize(oldSize);
-        return (float) Math.ceil(w * mInvCompatScaling);
     }
 
     /**
@@ -2766,19 +2842,26 @@
         if (text.length == 0 || count == 0) {
             return 0;
         }
-        if (!mHasCompatScaling) {
-            nGetTextAdvances(mNativePaint, text, index, count, index, count, mBidiFlags, widths, 0);
-            return count;
-        }
+        int oldFlag = getFlags();
+        setFlags(getFlags() | (TEXT_RUN_FLAG_LEFT_EDGE | TEXT_RUN_FLAG_RIGHT_EDGE));
+        try {
+            if (!mHasCompatScaling) {
+                nGetTextAdvances(mNativePaint, text, index, count, index, count, mBidiFlags, widths,
+                        0);
+                return count;
+            }
 
-        final float oldSize = getTextSize();
-        setTextSize(oldSize * mCompatScaling);
-        nGetTextAdvances(mNativePaint, text, index, count, index, count, mBidiFlags, widths, 0);
-        setTextSize(oldSize);
-        for (int i = 0; i < count; i++) {
-            widths[i] *= mInvCompatScaling;
+            final float oldSize = getTextSize();
+            setTextSize(oldSize * mCompatScaling);
+            nGetTextAdvances(mNativePaint, text, index, count, index, count, mBidiFlags, widths, 0);
+            setTextSize(oldSize);
+            for (int i = 0; i < count; i++) {
+                widths[i] *= mInvCompatScaling;
+            }
+            return count;
+        } finally {
+            setFlags(oldFlag);
         }
-        return count;
     }
 
     /**
@@ -2849,19 +2932,25 @@
         if (text.length() == 0 || start == end) {
             return 0;
         }
-        if (!mHasCompatScaling) {
-            nGetTextAdvances(mNativePaint, text, start, end, start, end, mBidiFlags, widths, 0);
-            return end - start;
-        }
+        int oldFlag = getFlags();
+        setFlags(getFlags() | (TEXT_RUN_FLAG_LEFT_EDGE | TEXT_RUN_FLAG_RIGHT_EDGE));
+        try {
+            if (!mHasCompatScaling) {
+                nGetTextAdvances(mNativePaint, text, start, end, start, end, mBidiFlags, widths, 0);
+                return end - start;
+            }
 
-        final float oldSize = getTextSize();
-        setTextSize(oldSize * mCompatScaling);
-        nGetTextAdvances(mNativePaint, text, start, end, start, end, mBidiFlags, widths, 0);
-        setTextSize(oldSize);
-        for (int i = 0; i < end - start; i++) {
-            widths[i] *= mInvCompatScaling;
+            final float oldSize = getTextSize();
+            setTextSize(oldSize * mCompatScaling);
+            nGetTextAdvances(mNativePaint, text, start, end, start, end, mBidiFlags, widths, 0);
+            setTextSize(oldSize);
+            for (int i = 0; i < end - start; i++) {
+                widths[i] *= mInvCompatScaling;
+            }
+            return end - start;
+        } finally {
+            setFlags(oldFlag);
         }
-        return end - start;
     }
 
     /**
diff --git a/graphics/java/android/graphics/text/MeasuredText.java b/graphics/java/android/graphics/text/MeasuredText.java
index 2d33e8d..6da0719 100644
--- a/graphics/java/android/graphics/text/MeasuredText.java
+++ b/graphics/java/android/graphics/text/MeasuredText.java
@@ -269,6 +269,10 @@
          * offset is zero. After the style is applied the internal offset is moved to {@code offset
          * + length}, and next call will start from this new position.
          *
+         * <p>
+         * {@link Paint#TEXT_RUN_FLAG_RIGHT_EDGE} and {@link Paint#TEXT_RUN_FLAG_LEFT_EDGE} are
+         * ignored and treated as both of them are set.
+         *
          * @param paint a paint
          * @param length a length to be applied with a given paint, can not exceed the length of the
          *               text
diff --git a/libs/hwui/FeatureFlags.h b/libs/hwui/FeatureFlags.h
index 00d049c..6ebfc63 100644
--- a/libs/hwui/FeatureFlags.h
+++ b/libs/hwui/FeatureFlags.h
@@ -41,6 +41,14 @@
 #endif  // __ANDROID__
 }
 
+inline bool inter_character_justification() {
+#ifdef __ANDROID__
+    return com_android_text_flags_inter_character_justification();
+#else
+    return true;
+#endif  // __ANDROID__
+}
+
 }  // namespace text_feature
 
 }  // namespace android
diff --git a/libs/hwui/hwui/MinikinUtils.cpp b/libs/hwui/hwui/MinikinUtils.cpp
index 833069f..5613369 100644
--- a/libs/hwui/hwui/MinikinUtils.cpp
+++ b/libs/hwui/hwui/MinikinUtils.cpp
@@ -72,10 +72,13 @@
     const minikin::Range contextRange(contextStart, contextStart + contextCount);
     const minikin::StartHyphenEdit startHyphen = paint->getStartHyphenEdit();
     const minikin::EndHyphenEdit endHyphen = paint->getEndHyphenEdit();
+    const minikin::RunFlag minikinRunFlag = text_feature::inter_character_justification()
+                                                    ? paint->getRunFlag()
+                                                    : minikin::RunFlag::NONE;
 
     if (mt == nullptr) {
         return minikin::Layout(textBuf.substr(contextRange), range - contextStart, bidiFlags,
-                               minikinPaint, startHyphen, endHyphen);
+                               minikinPaint, startHyphen, endHyphen, minikinRunFlag);
     } else {
         return mt->buildLayout(textBuf, range, contextRange, minikinPaint, startHyphen, endHyphen);
     }
@@ -102,9 +105,12 @@
     const minikin::Range range(start, start + count);
     const minikin::StartHyphenEdit startHyphen = paint->getStartHyphenEdit();
     const minikin::EndHyphenEdit endHyphen = paint->getEndHyphenEdit();
+    const minikin::RunFlag minikinRunFlag = text_feature::inter_character_justification()
+                                                    ? paint->getRunFlag()
+                                                    : minikin::RunFlag::NONE;
 
     return minikin::Layout::measureText(textBuf, range, bidiFlags, minikinPaint, startHyphen,
-                                        endHyphen, advances, bounds, clusterCount);
+                                        endHyphen, advances, bounds, clusterCount, minikinRunFlag);
 }
 
 minikin::MinikinExtent MinikinUtils::getFontExtent(const Paint* paint, minikin::Bidi bidiFlags,
diff --git a/libs/hwui/hwui/Paint.h b/libs/hwui/hwui/Paint.h
index ef4dce5..708f96e 100644
--- a/libs/hwui/hwui/Paint.h
+++ b/libs/hwui/hwui/Paint.h
@@ -25,6 +25,7 @@
 #include <minikin/FontFamily.h>
 #include <minikin/FontFeature.h>
 #include <minikin/Hyphenator.h>
+#include <minikin/Layout.h>
 
 #include <string>
 
@@ -144,6 +145,9 @@
     bool isDevKern() const { return mDevKern; }
     void setDevKern(bool d) { mDevKern = d; }
 
+    minikin::RunFlag getRunFlag() const { return mRunFlag; }
+    void setRunFlag(minikin::RunFlag runFlag) { mRunFlag = runFlag; }
+
     // Deprecated -- bitmapshaders will be taking this flag explicitly
     bool isFilterBitmap() const { return mFilterBitmap; }
     void setFilterBitmap(bool filter) { mFilterBitmap = filter; }
@@ -188,6 +192,7 @@
     bool mStrikeThru = false;
     bool mUnderline = false;
     bool mDevKern = false;
+    minikin::RunFlag mRunFlag = minikin::RunFlag::NONE;
 };
 
 }  // namespace android
diff --git a/libs/hwui/hwui/PaintImpl.cpp b/libs/hwui/hwui/PaintImpl.cpp
index aac928f..c32ea01 100644
--- a/libs/hwui/hwui/PaintImpl.cpp
+++ b/libs/hwui/hwui/PaintImpl.cpp
@@ -47,8 +47,8 @@
         , mFilterBitmap(paint.mFilterBitmap)
         , mStrikeThru(paint.mStrikeThru)
         , mUnderline(paint.mUnderline)
-        , mDevKern(paint.mDevKern) {}
-
+        , mDevKern(paint.mDevKern)
+        , mRunFlag(paint.mRunFlag) {}
 
 Paint::~Paint() {}
 
@@ -68,21 +68,19 @@
     mStrikeThru = other.mStrikeThru;
     mUnderline = other.mUnderline;
     mDevKern = other.mDevKern;
+    mRunFlag = other.mRunFlag;
     return *this;
 }
 
 bool operator==(const Paint& a, const Paint& b) {
-    return static_cast<const SkPaint&>(a) == static_cast<const SkPaint&>(b) &&
-           a.mFont == b.mFont &&
-           a.mLooper == b.mLooper && 
-           a.mLetterSpacing == b.mLetterSpacing && a.mWordSpacing == b.mWordSpacing &&
-           a.mFontFeatureSettings == b.mFontFeatureSettings &&
+    return static_cast<const SkPaint&>(a) == static_cast<const SkPaint&>(b) && a.mFont == b.mFont &&
+           a.mLooper == b.mLooper && a.mLetterSpacing == b.mLetterSpacing &&
+           a.mWordSpacing == b.mWordSpacing && a.mFontFeatureSettings == b.mFontFeatureSettings &&
            a.mMinikinLocaleListId == b.mMinikinLocaleListId &&
            a.mFamilyVariant == b.mFamilyVariant && a.mHyphenEdit == b.mHyphenEdit &&
            a.mTypeface == b.mTypeface && a.mAlign == b.mAlign &&
-           a.mFilterBitmap == b.mFilterBitmap &&
-           a.mStrikeThru == b.mStrikeThru && a.mUnderline == b.mUnderline &&
-           a.mDevKern == b.mDevKern;
+           a.mFilterBitmap == b.mFilterBitmap && a.mStrikeThru == b.mStrikeThru &&
+           a.mUnderline == b.mUnderline && a.mDevKern == b.mDevKern && a.mRunFlag == b.mRunFlag;
 }
 
 void Paint::reset() {
@@ -96,6 +94,7 @@
     mStrikeThru = false;
     mUnderline = false;
     mDevKern = false;
+    mRunFlag = minikin::RunFlag::NONE;
 }
 
 void Paint::setLooper(sk_sp<BlurDrawLooper> looper) {
@@ -133,6 +132,8 @@
 // flags related to minikin::Paint
 static const uint32_t sUnderlineFlag    = 0x08;
 static const uint32_t sStrikeThruFlag   = 0x10;
+static const uint32_t sTextRunLeftEdge = 0x2000;
+static const uint32_t sTextRunRightEdge = 0x4000;
 // flags no longer supported on native side (but mirrored for compatibility)
 static const uint32_t sDevKernFlag      = 0x100;
 
@@ -186,6 +187,12 @@
     flags |= -(int)mUnderline    & sUnderlineFlag;
     flags |= -(int)mDevKern      & sDevKernFlag;
     flags |= -(int)mFilterBitmap & sFilterBitmapFlag;
+    if (mRunFlag & minikin::RunFlag::LEFT_EDGE) {
+        flags |= sTextRunLeftEdge;
+    }
+    if (mRunFlag & minikin::RunFlag::RIGHT_EDGE) {
+        flags |= sTextRunRightEdge;
+    }
     return flags;
 }
 
@@ -196,6 +203,15 @@
     mUnderline    = (flags & sUnderlineFlag) != 0;
     mDevKern      = (flags & sDevKernFlag) != 0;
     mFilterBitmap = (flags & sFilterBitmapFlag) != 0;
+
+    std::underlying_type<minikin::RunFlag>::type rawFlag = minikin::RunFlag::NONE;
+    if (flags & sTextRunLeftEdge) {
+        rawFlag |= minikin::RunFlag::LEFT_EDGE;
+    }
+    if (flags & sTextRunRightEdge) {
+        rawFlag |= minikin::RunFlag::RIGHT_EDGE;
+    }
+    mRunFlag = static_cast<minikin::RunFlag>(rawFlag);
 }
 
 }  // namespace android
diff --git a/libs/hwui/jni/android_graphics_Canvas.cpp b/libs/hwui/jni/android_graphics_Canvas.cpp
index 8ba7503..d572593 100644
--- a/libs/hwui/jni/android_graphics_Canvas.cpp
+++ b/libs/hwui/jni/android_graphics_Canvas.cpp
@@ -613,6 +613,12 @@
     Paint* paint = reinterpret_cast<Paint*>(paintHandle);
     const Typeface* typeface = paint->getAndroidTypeface();
     ScopedCharArrayRO text(env, charArray);
+
+    // The drawText API is designed to draw entire line, so ignore the text run flag and draw the
+    // text as entire line mode.
+    const minikin::RunFlag originalRunFlag = paint->getRunFlag();
+    paint->setRunFlag(minikin::RunFlag::WHOLE_LINE);
+
     // drawTextString and drawTextChars doesn't use context info
     get_canvas(canvasHandle)->drawText(
             text.get() + index, count,  // text buffer
@@ -620,6 +626,7 @@
             0, count,  // context range
             x, y,  // draw position
             static_cast<minikin::Bidi>(bidiFlags), *paint, typeface, nullptr /* measured text */);
+    paint->setRunFlag(originalRunFlag);
 }
 
 static void drawTextString(JNIEnv* env, jobject, jlong canvasHandle, jstring strObj,
@@ -629,6 +636,12 @@
     Paint* paint = reinterpret_cast<Paint*>(paintHandle);
     const Typeface* typeface = paint->getAndroidTypeface();
     const int count = end - start;
+
+    // The drawText API is designed to draw entire line, so ignore the text run flag and draw the
+    // text as entire line mode.
+    const minikin::RunFlag originalRunFlag = paint->getRunFlag();
+    paint->setRunFlag(minikin::RunFlag::WHOLE_LINE);
+
     // drawTextString and drawTextChars doesn't use context info
     get_canvas(canvasHandle)->drawText(
             text.get() + start, count,  // text buffer
@@ -636,6 +649,7 @@
             0, count,  // context range
             x, y,  // draw position
             static_cast<minikin::Bidi>(bidiFlags), *paint, typeface, nullptr /* measured text */);
+    paint->setRunFlag(originalRunFlag);
 }
 
 static void drawTextRunChars(JNIEnv* env, jobject, jlong canvasHandle, jcharArray charArray,
@@ -681,9 +695,15 @@
 
     jchar* jchars = env->GetCharArrayElements(text, NULL);
 
+    // The drawText API is designed to draw entire line, so ignore the text run flag and draw the
+    // text as entire line mode.
+    const minikin::RunFlag originalRunFlag = paint->getRunFlag();
+    paint->setRunFlag(minikin::RunFlag::WHOLE_LINE);
+
     get_canvas(canvasHandle)->drawTextOnPath(jchars + index, count,
             static_cast<minikin::Bidi>(bidiFlags), *path, hOffset, vOffset, *paint, typeface);
 
+    paint->setRunFlag(originalRunFlag);
     env->ReleaseCharArrayElements(text, jchars, 0);
 }
 
@@ -697,9 +717,15 @@
     const jchar* jchars = env->GetStringChars(text, NULL);
     int count = env->GetStringLength(text);
 
+    // The drawText API is designed to draw entire line, so ignore the text run flag and draw the
+    // text as entire line mode.
+    const minikin::RunFlag originalRunFlag = paint->getRunFlag();
+    paint->setRunFlag(minikin::RunFlag::WHOLE_LINE);
+
     get_canvas(canvasHandle)->drawTextOnPath(jchars, count, static_cast<minikin::Bidi>(bidiFlags),
             *path, hOffset, vOffset, *paint, typeface);
 
+    paint->setRunFlag(originalRunFlag);
     env->ReleaseStringChars(text, jchars);
 }