OSDN Git Service

Fix Layout.primaryIsTrailingPreviousAllLineOffsets
[android-x86/frameworks-base.git] / core / java / android / text / Layout.java
index 312da86..8f9233f 100644 (file)
@@ -17,7 +17,6 @@
 package android.text;
 
 import android.annotation.IntDef;
-import android.emoji.EmojiFactory;
 import android.graphics.Canvas;
 import android.graphics.Paint;
 import android.graphics.Path;
@@ -31,6 +30,7 @@ import android.text.style.ParagraphStyle;
 import android.text.style.ReplacementSpan;
 import android.text.style.TabStopSpan;
 
+import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.util.ArrayUtils;
 import com.android.internal.util.GrowingArrayUtils;
 
@@ -103,22 +103,28 @@ public abstract class Layout {
     private static final ParagraphStyle[] NO_PARA_SPANS =
         ArrayUtils.emptyArray(ParagraphStyle.class);
 
-    /* package */ static final EmojiFactory EMOJI_FACTORY = EmojiFactory.newAvailableInstance();
-    /* package */ static final int MIN_EMOJI, MAX_EMOJI;
+    /** @hide */
+    @IntDef({JUSTIFICATION_MODE_NONE, JUSTIFICATION_MODE_INTER_WORD})
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface JustificationMode {}
 
-    static {
-        if (EMOJI_FACTORY != null) {
-            MIN_EMOJI = EMOJI_FACTORY.getMinimumAndroidPua();
-            MAX_EMOJI = EMOJI_FACTORY.getMaximumAndroidPua();
-        } else {
-            MIN_EMOJI = -1;
-            MAX_EMOJI = -1;
-        }
-    }
+    /**
+     * Value for justification mode indicating no justification.
+     */
+    public static final int JUSTIFICATION_MODE_NONE = 0;
 
     /**
-     * Return how wide a layout must be in order to display the
-     * specified text with one line per paragraph.
+     * Value for justification mode indicating the text is justified by stretching word spacing.
+     */
+    public static final int JUSTIFICATION_MODE_INTER_WORD = 1;
+
+    /**
+     * Return how wide a layout must be in order to display the specified text with one line per
+     * paragraph.
+     *
+     * <p>As of O, Uses
+     * {@link TextDirectionHeuristics#FIRSTSTRONG_LTR} as the default text direction heuristics. In
+     * the earlier versions uses {@link TextDirectionHeuristics#LTR} as the default.</p>
      */
     public static float getDesiredWidth(CharSequence source,
                                         TextPaint paint) {
@@ -126,12 +132,25 @@ public abstract class Layout {
     }
 
     /**
+     * Return how wide a layout must be in order to display the specified text slice with one
+     * line per paragraph.
+     *
+     * <p>As of O, Uses
+     * {@link TextDirectionHeuristics#FIRSTSTRONG_LTR} as the default text direction heuristics. In
+     * the earlier versions uses {@link TextDirectionHeuristics#LTR} as the default.</p>
+     */
+    public static float getDesiredWidth(CharSequence source, int start, int end, TextPaint paint) {
+        return getDesiredWidth(source, start, end, paint, TextDirectionHeuristics.FIRSTSTRONG_LTR);
+    }
+
+    /**
      * Return how wide a layout must be in order to display the
      * specified text slice with one line per paragraph.
+     *
+     * @hide
      */
-    public static float getDesiredWidth(CharSequence source,
-                                        int start, int end,
-                                        TextPaint paint) {
+    public static float getDesiredWidth(CharSequence source, int start, int end, TextPaint paint,
+            TextDirectionHeuristic textDir) {
         float need = 0;
 
         int next;
@@ -142,7 +161,7 @@ public abstract class Layout {
                 next = end;
 
             // note, omits trailing paragraph char
-            float w = measurePara(paint, source, i, next);
+            float w = measurePara(paint, source, i, next, textDir);
 
             if (w > need)
                 need = w;
@@ -214,6 +233,11 @@ public abstract class Layout {
         mTextDir = textDir;
     }
 
+    /** @hide */
+    protected void setJustificationMode(@JustificationMode int justificationMode) {
+        mJustificationMode = justificationMode;
+    }
+
     /**
      * Replace constructor properties of this Layout with new ones.  Be careful.
      */
@@ -262,6 +286,99 @@ public abstract class Layout {
         drawText(canvas, firstLine, lastLine);
     }
 
+    private boolean isJustificationRequired(int lineNum) {
+        if (mJustificationMode == JUSTIFICATION_MODE_NONE) return false;
+        final int lineEnd = getLineEnd(lineNum);
+        return lineEnd < mText.length() && mText.charAt(lineEnd - 1) != '\n';
+    }
+
+    private float getJustifyWidth(int lineNum) {
+        Alignment paraAlign = mAlignment;
+        TabStops tabStops = null;
+        boolean tabStopsIsInitialized = false;
+
+        int left = 0;
+        int right = mWidth;
+
+        final int dir = getParagraphDirection(lineNum);
+
+        ParagraphStyle[] spans = NO_PARA_SPANS;
+        if (mSpannedText) {
+            Spanned sp = (Spanned) mText;
+            final int start = getLineStart(lineNum);
+
+            final boolean isFirstParaLine = (start == 0 || mText.charAt(start - 1) == '\n');
+
+            if (isFirstParaLine) {
+                final int spanEnd = sp.nextSpanTransition(start, mText.length(),
+                        ParagraphStyle.class);
+                spans = getParagraphSpans(sp, start, spanEnd, ParagraphStyle.class);
+
+                for (int n = spans.length - 1; n >= 0; n--) {
+                    if (spans[n] instanceof AlignmentSpan) {
+                        paraAlign = ((AlignmentSpan) spans[n]).getAlignment();
+                        break;
+                    }
+                }
+            }
+
+            final int length = spans.length;
+            boolean useFirstLineMargin = isFirstParaLine;
+            for (int n = 0; n < length; n++) {
+                if (spans[n] instanceof LeadingMarginSpan2) {
+                    int count = ((LeadingMarginSpan2) spans[n]).getLeadingMarginLineCount();
+                    int startLine = getLineForOffset(sp.getSpanStart(spans[n]));
+                    if (lineNum < startLine + count) {
+                        useFirstLineMargin = true;
+                        break;
+                    }
+                }
+            }
+            for (int n = 0; n < length; n++) {
+                if (spans[n] instanceof LeadingMarginSpan) {
+                    LeadingMarginSpan margin = (LeadingMarginSpan) spans[n];
+                    if (dir == DIR_RIGHT_TO_LEFT) {
+                        right -= margin.getLeadingMargin(useFirstLineMargin);
+                    } else {
+                        left += margin.getLeadingMargin(useFirstLineMargin);
+                    }
+                }
+            }
+        }
+
+        if (getLineContainsTab(lineNum)) {
+            tabStops = new TabStops(TAB_INCREMENT, spans);
+        }
+
+        final Alignment align;
+        if (paraAlign == Alignment.ALIGN_LEFT) {
+            align = (dir == DIR_LEFT_TO_RIGHT) ?  Alignment.ALIGN_NORMAL : Alignment.ALIGN_OPPOSITE;
+        } else if (paraAlign == Alignment.ALIGN_RIGHT) {
+            align = (dir == DIR_LEFT_TO_RIGHT) ?  Alignment.ALIGN_OPPOSITE : Alignment.ALIGN_NORMAL;
+        } else {
+            align = paraAlign;
+        }
+
+        final int indentWidth;
+        if (align == Alignment.ALIGN_NORMAL) {
+            if (dir == DIR_LEFT_TO_RIGHT) {
+                indentWidth = getIndentAdjust(lineNum, Alignment.ALIGN_LEFT);
+            } else {
+                indentWidth = -getIndentAdjust(lineNum, Alignment.ALIGN_RIGHT);
+            }
+        } else if (align == Alignment.ALIGN_OPPOSITE) {
+            if (dir == DIR_LEFT_TO_RIGHT) {
+                indentWidth = -getIndentAdjust(lineNum, Alignment.ALIGN_RIGHT);
+            } else {
+                indentWidth = getIndentAdjust(lineNum, Alignment.ALIGN_LEFT);
+            }
+        } else { // Alignment.ALIGN_CENTER
+            indentWidth = getIndentAdjust(lineNum, Alignment.ALIGN_CENTER);
+        }
+
+        return right - left - indentWidth;
+    }
+
     /**
      * @hide
      */
@@ -270,7 +387,7 @@ public abstract class Layout {
         int previousLineEnd = getLineStart(firstLine);
         ParagraphStyle[] spans = NO_PARA_SPANS;
         int spanEnd = 0;
-        TextPaint paint = mPaint;
+        final TextPaint paint = mPaint;
         CharSequence buf = mText;
 
         Alignment paraAlign = mAlignment;
@@ -284,6 +401,7 @@ public abstract class Layout {
         for (int lineNum = firstLine; lineNum <= lastLine; lineNum++) {
             int start = previousLineEnd;
             previousLineEnd = getLineStart(lineNum + 1);
+            final boolean justify = isJustificationRequired(lineNum);
             int end = getLineVisibleEnd(lineNum, start, previousLineEnd);
 
             int ltop = previousLineBottom;
@@ -360,9 +478,9 @@ public abstract class Layout {
                 }
             }
 
-            boolean hasTabOrEmoji = getLineContainsTab(lineNum);
+            boolean hasTab = getLineContainsTab(lineNum);
             // Can't tell if we have tabs for sure, currently
-            if (hasTabOrEmoji && !tabStopsIsInitialized) {
+            if (hasTab && !tabStopsIsInitialized) {
                 if (tabStops == null) {
                     tabStops = new TabStops(TAB_INCREMENT, spans);
                 } else {
@@ -382,34 +500,42 @@ public abstract class Layout {
             }
 
             int x;
+            final int indentWidth;
             if (align == Alignment.ALIGN_NORMAL) {
                 if (dir == DIR_LEFT_TO_RIGHT) {
-                    x = left + getIndentAdjust(lineNum, Alignment.ALIGN_LEFT);
+                    indentWidth = getIndentAdjust(lineNum, Alignment.ALIGN_LEFT);
+                    x = left + indentWidth;
                 } else {
-                    x = right + getIndentAdjust(lineNum, Alignment.ALIGN_RIGHT);
+                    indentWidth = -getIndentAdjust(lineNum, Alignment.ALIGN_RIGHT);
+                    x = right - indentWidth;
                 }
             } else {
                 int max = (int)getLineExtent(lineNum, tabStops, false);
                 if (align == Alignment.ALIGN_OPPOSITE) {
                     if (dir == DIR_LEFT_TO_RIGHT) {
-                        x = right - max + getIndentAdjust(lineNum, Alignment.ALIGN_RIGHT);
+                        indentWidth = -getIndentAdjust(lineNum, Alignment.ALIGN_RIGHT);
+                        x = right - max - indentWidth;
                     } else {
-                        x = left - max + getIndentAdjust(lineNum, Alignment.ALIGN_LEFT);
+                        indentWidth = getIndentAdjust(lineNum, Alignment.ALIGN_LEFT);
+                        x = left - max + indentWidth;
                     }
                 } else { // Alignment.ALIGN_CENTER
+                    indentWidth = getIndentAdjust(lineNum, Alignment.ALIGN_CENTER);
                     max = max & ~1;
-                    x = ((right + left - max) >> 1) +
-                            getIndentAdjust(lineNum, Alignment.ALIGN_CENTER);
+                    x = ((right + left - max) >> 1) + indentWidth;
                 }
             }
 
             paint.setHyphenEdit(getHyphen(lineNum));
             Directions directions = getLineDirections(lineNum);
-            if (directions == DIRS_ALL_LEFT_TO_RIGHT && !mSpannedText && !hasTabOrEmoji) {
+            if (directions == DIRS_ALL_LEFT_TO_RIGHT && !mSpannedText && !hasTab && !justify) {
                 // XXX: assumes there's nothing additional to be done
                 canvas.drawText(buf, start, end, x, lbaseline, paint);
             } else {
-                tl.set(paint, buf, start, end, dir, directions, hasTabOrEmoji, tabStops);
+                tl.set(paint, buf, start, end, dir, directions, hasTab, tabStops);
+                if (justify) {
+                    tl.justify(right - left - indentWidth);
+                }
                 tl.draw(canvas, x, ltop, lbaseline, lbottom);
             }
             paint.setHyphenEdit(0);
@@ -630,6 +756,17 @@ public abstract class Layout {
     }
 
     /**
+     * Return the total height of this layout.
+     *
+     * @param cap if true and max lines is set, returns the height of the layout at the max lines.
+     *
+     * @hide
+     */
+    public int getHeight(boolean cap) {
+        return getHeight();
+    }
+
+    /**
      * Return the base alignment of this layout.
      */
     public final Alignment getAlignment() {
@@ -710,8 +847,7 @@ public abstract class Layout {
 
     /**
      * Returns whether the specified line contains one or more
-     * characters that need to be handled specially, like tabs
-     * or emoji.
+     * characters that need to be handled specially, like tabs.
      */
     public abstract boolean getLineContainsTab(int line);
 
@@ -815,6 +951,31 @@ public abstract class Layout {
     }
 
     /**
+     * Returns the range of the run that the character at offset belongs to.
+     * @param offset the offset
+     * @return The range of the run
+     * @hide
+     */
+    public long getRunRange(int offset) {
+        int line = getLineForOffset(offset);
+        Directions dirs = getLineDirections(line);
+        if (dirs == DIRS_ALL_LEFT_TO_RIGHT || dirs == DIRS_ALL_RIGHT_TO_LEFT) {
+            return TextUtils.packRangeInLong(0, getLineEnd(line));
+        }
+        int[] runs = dirs.mDirections;
+        int lineStart = getLineStart(line);
+        for (int i = 0; i < runs.length; i += 2) {
+            int start = lineStart + runs[i];
+            int limit = start + (runs[i+1] & RUN_LENGTH_MASK);
+            if (offset >= start && offset < limit) {
+                return TextUtils.packRangeInLong(start, limit);
+            }
+        }
+        // Should happen only if the offset is "out of bounds"
+        return TextUtils.packRangeInLong(0, getLineEnd(line));
+    }
+
+    /**
      * Checks if the trailing BiDi level should be used for an offset
      *
      * This method is useful when the offset is at the BiDi level transition point and determine
@@ -909,6 +1070,9 @@ public abstract class Layout {
             if (limit > lineEnd) {
                 limit = lineEnd;
             }
+            if (limit == start) {
+                continue;
+            }
             level[limit - lineStart - 1] =
                     (byte) ((runs[i + 1] >>> RUN_LEVEL_SHIFT) & RUN_LEVEL_MASK);
         }
@@ -962,6 +1126,10 @@ public abstract class Layout {
         return getHorizontal(offset, !trailing, clamped);
     }
 
+    private float getHorizontal(int offset, boolean primary) {
+        return primary ? getPrimaryHorizontal(offset) : getSecondaryHorizontal(offset);
+    }
+
     private float getHorizontal(int offset, boolean trailing, boolean clamped) {
         int line = getLineForOffset(offset);
 
@@ -972,11 +1140,11 @@ public abstract class Layout {
         int start = getLineStart(line);
         int end = getLineEnd(line);
         int dir = getParagraphDirection(line);
-        boolean hasTabOrEmoji = getLineContainsTab(line);
+        boolean hasTab = getLineContainsTab(line);
         Directions directions = getLineDirections(line);
 
         TabStops tabStops = null;
-        if (hasTabOrEmoji && mText instanceof Spanned) {
+        if (hasTab && mText instanceof Spanned) {
             // Just checking this line should be good enough, tabs should be
             // consistent across all lines in a paragraph.
             TabStopSpan[] tabs = getParagraphSpans((Spanned) mText, start, end, TabStopSpan.class);
@@ -986,7 +1154,7 @@ public abstract class Layout {
         }
 
         TextLine tl = TextLine.obtain();
-        tl.set(mPaint, mText, start, end, dir, directions, hasTabOrEmoji, tabStops);
+        tl.set(mPaint, mText, start, end, dir, directions, hasTab, tabStops);
         float wid = tl.measure(offset - start, trailing, null);
         TextLine.recycle(tl);
 
@@ -1000,8 +1168,8 @@ public abstract class Layout {
     }
 
     /**
-     * Computes in linear time the results of calling
-     * #getHorizontal for all offsets on a line.
+     * Computes in linear time the results of calling #getHorizontal for all offsets on a line.
+     *
      * @param line The line giving the offsets we compute information for
      * @param clamped Whether to clamp the results to the width of the layout
      * @param primary Whether the results should be the primary or the secondary horizontal
@@ -1036,7 +1204,7 @@ public abstract class Layout {
         TextLine.recycle(tl);
 
         if (clamped) {
-            for (int offset = 0; offset <= wid.length; ++offset) {
+            for (int offset = 0; offset < wid.length; ++offset) {
                 if (wid[offset] > mWidth) {
                     wid[offset] = mWidth;
                 }
@@ -1146,9 +1314,9 @@ public abstract class Layout {
         int start = getLineStart(line);
         int end = full ? getLineEnd(line) : getLineVisibleEnd(line);
 
-        boolean hasTabsOrEmoji = getLineContainsTab(line);
+        boolean hasTabs = getLineContainsTab(line);
         TabStops tabStops = null;
-        if (hasTabsOrEmoji && mText instanceof Spanned) {
+        if (hasTabs && mText instanceof Spanned) {
             // Just checking this line should be good enough, tabs should be
             // consistent across all lines in a paragraph.
             TabStopSpan[] tabs = getParagraphSpans((Spanned) mText, start, end, TabStopSpan.class);
@@ -1164,8 +1332,13 @@ public abstract class Layout {
         int dir = getParagraphDirection(line);
 
         TextLine tl = TextLine.obtain();
-        tl.set(mPaint, mText, start, end, dir, directions, hasTabsOrEmoji, tabStops);
+        mPaint.setHyphenEdit(getHyphen(line));
+        tl.set(mPaint, mText, start, end, dir, directions, hasTabs, tabStops);
+        if (isJustificationRequired(line)) {
+            tl.justify(getJustifyWidth(line));
+        }
         float width = tl.metrics(null);
+        mPaint.setHyphenEdit(0);
         TextLine.recycle(tl);
         return width;
     }
@@ -1181,13 +1354,18 @@ public abstract class Layout {
     private float getLineExtent(int line, TabStops tabStops, boolean full) {
         int start = getLineStart(line);
         int end = full ? getLineEnd(line) : getLineVisibleEnd(line);
-        boolean hasTabsOrEmoji = getLineContainsTab(line);
+        boolean hasTabs = getLineContainsTab(line);
         Directions directions = getLineDirections(line);
         int dir = getParagraphDirection(line);
 
         TextLine tl = TextLine.obtain();
-        tl.set(mPaint, mText, start, end, dir, directions, hasTabsOrEmoji, tabStops);
+        mPaint.setHyphenEdit(getHyphen(line));
+        tl.set(mPaint, mText, start, end, dir, directions, hasTabs, tabStops);
+        if (isJustificationRequired(line)) {
+            tl.justify(getJustifyWidth(line));
+        }
         float width = tl.metrics(null);
+        mPaint.setHyphenEdit(0);
         TextLine.recycle(tl);
         return width;
     }
@@ -1233,10 +1411,11 @@ public abstract class Layout {
                 low = guess;
         }
 
-        if (low < 0)
+        if (low < 0) {
             return 0;
-        else
+        } else {
             return low;
+        }
     }
 
     /**
@@ -1244,23 +1423,48 @@ public abstract class Layout {
      * closest to the specified horizontal position.
      */
     public int getOffsetForHorizontal(int line, float horiz) {
+        return getOffsetForHorizontal(line, horiz, true);
+    }
+
+    /**
+     * Get the character offset on the specified line whose position is
+     * closest to the specified horizontal position.
+     *
+     * @param line the line used to find the closest offset
+     * @param horiz the horizontal position used to find the closest offset
+     * @param primary whether to use the primary position or secondary position to find the offset
+     *
+     * @hide
+     */
+    public int getOffsetForHorizontal(int line, float horiz, boolean primary) {
         // TODO: use Paint.getOffsetForAdvance to avoid binary search
-        int max = getLineEnd(line) - 1;
-        int min = getLineStart(line);
-        Directions dirs = getLineDirections(line);
+        final int lineEndOffset = getLineEnd(line);
+        final int lineStartOffset = getLineStart(line);
 
-        if (line == getLineCount() - 1)
-            max++;
+        Directions dirs = getLineDirections(line);
 
+        TextLine tl = TextLine.obtain();
+        // XXX: we don't care about tabs as we just use TextLine#getOffsetToLeftRightOf here.
+        tl.set(mPaint, mText, lineStartOffset, lineEndOffset, getParagraphDirection(line), dirs,
+                false, null);
         final HorizontalMeasurementProvider horizontal =
-                new HorizontalMeasurementProvider(line);
-        int best = min;
-        float bestdist = Math.abs(horizontal.get(best) - horiz);
+                new HorizontalMeasurementProvider(line, primary);
+
+        final int max;
+        if (line == getLineCount() - 1) {
+            max = lineEndOffset;
+        } else {
+            max = tl.getOffsetToLeftRightOf(lineEndOffset - lineStartOffset,
+                    !isRtlCharAt(lineEndOffset - 1)) + lineStartOffset;
+        }
+        int best = lineStartOffset;
+        float bestdist = Math.abs(horizontal.get(lineStartOffset) - horiz);
 
         for (int i = 0; i < dirs.mDirections.length; i += 2) {
-            int here = min + dirs.mDirections[i];
+            int here = lineStartOffset + dirs.mDirections[i];
             int there = here + (dirs.mDirections[i+1] & RUN_LENGTH_MASK);
-            int swap = (dirs.mDirections[i+1] & RUN_RTL_FLAG) != 0 ? -1 : 1;
+            boolean isRtl = (dirs.mDirections[i+1] & RUN_RTL_FLAG) != 0;
+            int swap = isRtl ? -1 : 1;
 
             if (there > max)
                 there = max;
@@ -1270,33 +1474,34 @@ public abstract class Layout {
                 guess = (high + low) / 2;
                 int adguess = getOffsetAtStartOf(guess);
 
-                if (horizontal.get(adguess) * swap >= horiz * swap)
+                if (horizontal.get(adguess) * swap >= horiz * swap) {
                     high = guess;
-                else
+                } else {
                     low = guess;
+                }
             }
 
             if (low < here + 1)
                 low = here + 1;
 
             if (low < there) {
-                low = getOffsetAtStartOf(low);
-
-                float dist = Math.abs(horizontal.get(low) - horiz);
-
-                int aft = TextUtils.getOffsetAfter(mText, low);
-                if (aft < there) {
-                    float other = Math.abs(horizontal.get(aft) - horiz);
-
-                    if (other < dist) {
-                        dist = other;
-                        low = aft;
+                int aft = tl.getOffsetToLeftRightOf(low - lineStartOffset, isRtl) + lineStartOffset;
+                low = tl.getOffsetToLeftRightOf(aft - lineStartOffset, !isRtl) + lineStartOffset;
+                if (low >= here && low < there) {
+                    float dist = Math.abs(horizontal.get(low) - horiz);
+                    if (aft < there) {
+                        float other = Math.abs(horizontal.get(aft) - horiz);
+
+                        if (other < dist) {
+                            dist = other;
+                            low = aft;
+                        }
                     }
-                }
 
-                if (dist < bestdist) {
-                    bestdist = dist;
-                    best = low;
+                    if (dist < bestdist) {
+                        bestdist = dist;
+                        best = low;
+                    }
                 }
             }
 
@@ -1315,6 +1520,7 @@ public abstract class Layout {
             best = max;
         }
 
+        TextLine.recycle(tl);
         return best;
     }
 
@@ -1328,12 +1534,14 @@ public abstract class Layout {
      */
     private class HorizontalMeasurementProvider {
         private final int mLine;
+        private final boolean mPrimary;
 
         private float[] mHorizontals;
         private int mLineStartOffset;
 
-        HorizontalMeasurementProvider(final int line) {
+        HorizontalMeasurementProvider(final int line, final boolean primary) {
             mLine = line;
+            mPrimary = primary;
             init();
         }
 
@@ -1343,14 +1551,14 @@ public abstract class Layout {
                 return;
             }
 
-            mHorizontals = getLineHorizontals(mLine, false, true);
+            mHorizontals = getLineHorizontals(mLine, false, mPrimary);
             mLineStartOffset = getLineStart(mLine);
         }
 
         float get(final int offset) {
             if (mHorizontals == null || offset < mLineStartOffset
                     || offset >= mLineStartOffset + mHorizontals.length) {
-                return getPrimaryHorizontal(offset);
+                return getHorizontal(offset, mPrimary);
             } else {
                 return mHorizontals[offset - mLineStartOffset];
             }
@@ -1386,10 +1594,7 @@ public abstract class Layout {
                 return end - 1;
             }
 
-            // Note: keep this in sync with Minikin LineBreaker::isLineEndSpace()
-            if (!(ch == ' ' || ch == '\t' || ch == 0x1680 ||
-                    (0x2000 <= ch && ch <= 0x200A && ch != 0x2007) ||
-                    ch == 0x205F || ch == 0x3000)) {
+            if (!TextLine.isLineEndSpace(ch)) {
                 break;
             }
 
@@ -1795,12 +2000,12 @@ public abstract class Layout {
     }
 
     /* package */
-    static float measurePara(TextPaint paint, CharSequence text, int start, int end) {
-
+    static float measurePara(TextPaint paint, CharSequence text, int start, int end,
+            TextDirectionHeuristic textDir) {
         MeasuredText mt = MeasuredText.obtain();
         TextLine tl = TextLine.obtain();
         try {
-            mt.setPara(text, start, end, TextDirectionHeuristics.LTR, null);
+            mt.setPara(text, start, end, textDir, null);
             Directions directions;
             int dir;
             if (mt.mEasy) {
@@ -1842,7 +2047,7 @@ public abstract class Layout {
                 }
             }
             tl.set(paint, text, start, end, dir, directions, hasTabs, tabStops);
-            return margin + tl.metrics(null);
+            return margin + Math.abs(tl.metrics(null));
         } finally {
             TextLine.recycle(tl);
             MeasuredText.recycle(mt);
@@ -1985,7 +2190,11 @@ public abstract class Layout {
             return ArrayUtils.emptyArray(type);
         }
 
-        return text.getSpans(start, end, type);
+        if(text instanceof SpannableStringBuilder) {
+            return ((SpannableStringBuilder) text).getSpans(start, end, type, false);
+        } else {
+            return text.getSpans(start, end, type);
+        }
     }
 
     private char getEllipsisChar(TextUtils.TruncateAt method) {
@@ -2038,8 +2247,17 @@ public abstract class Layout {
         // To simply test for an RTL direction, test the bit using
         // DIR_RTL_FLAG, if set then the direction is rtl.
 
-        /* package */ int[] mDirections;
-        /* package */ Directions(int[] dirs) {
+        /**
+         * @hide
+         */
+        @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
+        public int[] mDirections;
+
+        /**
+         * @hide
+         */
+        @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
+        public Directions(int[] dirs) {
             mDirections = dirs;
         }
     }
@@ -2156,6 +2374,7 @@ public abstract class Layout {
     private boolean mSpannedText;
     private TextDirectionHeuristic mTextDir;
     private SpanSet<LineBackgroundSpan> mLineBackgroundSpans;
+    private int mJustificationMode;
 
     public static final int DIR_LEFT_TO_RIGHT = 1;
     public static final int DIR_RIGHT_TO_LEFT = -1;
@@ -2182,9 +2401,14 @@ public abstract class Layout {
 
     private static final int TAB_INCREMENT = 20;
 
-    /* package */ static final Directions DIRS_ALL_LEFT_TO_RIGHT =
+    /** @hide */
+    @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
+    public static final Directions DIRS_ALL_LEFT_TO_RIGHT =
         new Directions(new int[] { 0, RUN_LENGTH_MASK });
-    /* package */ static final Directions DIRS_ALL_RIGHT_TO_LEFT =
+
+    /** @hide */
+    @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
+    public static final Directions DIRS_ALL_RIGHT_TO_LEFT =
         new Directions(new int[] { 0, RUN_LENGTH_MASK | RUN_RTL_FLAG });
 
 }