Fix alignment issues with RTL paragraphs.

Also remove unused debugging code that depends on junit.
Remove trailing whitespace in changed code.

Change-Id: Ie02d1b8220c599a672ee6e91af0fba634e0f620c
diff --git a/core/java/android/text/Layout.java b/core/java/android/text/Layout.java
index 3b8f295..2f7720a 100644
--- a/core/java/android/text/Layout.java
+++ b/core/java/android/text/Layout.java
@@ -30,9 +30,10 @@
 import android.text.style.ParagraphStyle;
 import android.text.style.ReplacementSpan;
 import android.text.style.TabStopSpan;
+import android.text.style.LeadingMarginSpan.LeadingMarginSpan2;
 import android.view.KeyEvent;
 
-import junit.framework.Assert;
+import java.util.Arrays;
 
 /**
  * A base class that manages text layout in visual elements on
@@ -42,7 +43,6 @@
  * For text that will not change, use a {@link StaticLayout}.
  */
 public abstract class Layout {
-    private static final boolean DEBUG = false;
     private static final ParagraphStyle[] NO_PARA_SPANS =
         ArrayUtils.emptyArray(ParagraphStyle.class);
 
@@ -87,8 +87,7 @@
                 next = end;
 
             // note, omits trailing paragraph char
-            float w = measurePara(paint, workPaint,
-                                  source, i, next, true, null);
+            float w = measurePara(paint, workPaint, source, i, next);
 
             if (w > need)
                 need = w;
@@ -186,7 +185,6 @@
             dbottom = sTempRect.bottom;
         }
 
-
         int top = 0;
         int bottom = getLineTop(getLineCount());
 
@@ -209,13 +207,15 @@
         boolean spannedText = mSpannedText;
 
         ParagraphStyle[] spans = NO_PARA_SPANS;
-        int spanend = 0;
+        int spanEnd = 0;
         int textLength = 0;
 
         // First, draw LineBackgroundSpans.
-        // LineBackgroundSpans know nothing about the alignment or direction of
-        // the layout or line.  XXX: Should they?
+        // LineBackgroundSpans know nothing about the alignment, margins, or 
+        // direction of the layout or line.  XXX: Should they?
+        // They are evaluated at each line.
         if (spannedText) {
+            Spanned sp = (Spanned) buf;
             textLength = buf.length();
             for (int i = first; i <= last; i++) {
                 int start = previousLineEnd;
@@ -227,12 +227,14 @@
                 previousLineBottom = lbottom;
                 int lbaseline = lbottom - getLineDescent(i);
 
-                if (start >= spanend) {
-                   Spanned sp = (Spanned) buf;
-                   spanend = sp.nextSpanTransition(start, textLength,
-                                                   LineBackgroundSpan.class);
-                   spans = sp.getSpans(start, spanend,
-                                       LineBackgroundSpan.class);
+                if (start >= spanEnd) {
+                    // These should be infrequent, so we'll use this so that
+                    // we don't have to check as often.
+                    spanEnd = sp.nextSpanTransition(start, textLength, 
+                            LineBackgroundSpan.class);
+                    // All LineBackgroundSpans on a line contribute to its
+                    // background.
+                   spans = sp.getSpans(start, end, LineBackgroundSpan.class);
                 }
 
                 for (int n = 0; n < spans.length; n++) {
@@ -245,7 +247,7 @@
                 }
             }
             // reset to their original values
-            spanend = 0;
+            spanEnd = 0;
             previousLineBottom = getLineTop(first);
             previousLineEnd = getLineStart(first);
             spans = NO_PARA_SPANS;
@@ -266,8 +268,11 @@
         }
 
         Alignment align = mAlignment;
+        TabStops tabStops = null;
+        boolean tabStopsIsInitialized = false;
 
         TextLine tl = TextLine.obtain();
+
         // Next draw the lines, one at a time.
         // the baseline is the top of the following line minus the current
         // line's descent.
@@ -282,18 +287,29 @@
             previousLineBottom = lbottom;
             int lbaseline = lbottom - getLineDescent(i);
 
-            boolean isFirstParaLine = false;
+            int dir = getParagraphDirection(i);
+            int left = 0;
+            int right = mWidth;
+
             if (spannedText) {
-                if (start == 0 || buf.charAt(start - 1) == '\n') {
-                    isFirstParaLine = true;
-                }
-                // New batch of paragraph styles, compute the alignment.
-                // Last alignment style wins.
-                if (start >= spanend) {
-                    Spanned sp = (Spanned) buf;
-                    spanend = sp.nextSpanTransition(start, textLength,
+                Spanned sp = (Spanned) buf;
+                boolean isFirstParaLine = (start == 0 ||
+                        buf.charAt(start - 1) == '\n');
+                
+                // New batch of paragraph styles, collect into spans array.
+                // Compute the alignment, last alignment style wins.
+                // Reset tabStops, we'll rebuild if we encounter a line with
+                // tabs.
+                // We expect paragraph spans to be relatively infrequent, use
+                // spanEnd so that we can check less frequently.  Since
+                // paragraph styles ought to apply to entire paragraphs, we can
+                // just collect the ones present at the start of the paragraph.
+                // If spanEnd is before the end of the paragraph, that's not
+                // our problem.
+                if (start >= spanEnd && (i == first || isFirstParaLine)) {
+                    spanEnd = sp.nextSpanTransition(start, textLength,
                                                     ParagraphStyle.class);
-                    spans = sp.getSpans(start, spanend, ParagraphStyle.class);
+                    spans = sp.getSpans(start, spanEnd, ParagraphStyle.class);
 
                     align = mAlignment;
                     for (int n = spans.length-1; n >= 0; n--) {
@@ -302,45 +318,49 @@
                             break;
                         }
                     }
+                    
+                    tabStopsIsInitialized = false;
                 }
-            }
 
-            int dir = getParagraphDirection(i);
-            int left = 0;
-            int right = mWidth;
-
-            // Draw all leading margin spans.  Adjust left or right according
-            // to the paragraph direction of the line.
-            if (spannedText) {
+                // Draw all leading margin spans.  Adjust left or right according
+                // to the paragraph direction of the line.
                 final int length = spans.length;
                 for (int n = 0; n < length; n++) {
                     if (spans[n] instanceof LeadingMarginSpan) {
                         LeadingMarginSpan margin = (LeadingMarginSpan) spans[n];
+                        boolean useFirstLineMargin = isFirstParaLine;
+                        if (margin instanceof LeadingMarginSpan2) {
+                            int count = ((LeadingMarginSpan2) margin).getLeadingMarginLineCount();
+                            int startLine = getLineForOffset(sp.getSpanStart(margin));
+                            useFirstLineMargin = i < startLine + count;
+                        }
 
                         if (dir == DIR_RIGHT_TO_LEFT) {
                             margin.drawLeadingMargin(c, paint, right, dir, ltop,
                                                      lbaseline, lbottom, buf,
                                                      start, end, isFirstParaLine, this);
-
-                            right -= margin.getLeadingMargin(isFirstParaLine);
+                            right -= margin.getLeadingMargin(useFirstLineMargin);
                         } else {
                             margin.drawLeadingMargin(c, paint, left, dir, ltop,
                                                      lbaseline, lbottom, buf,
                                                      start, end, isFirstParaLine, this);
-
-                            boolean useMargin = isFirstParaLine;
-                            if (margin instanceof LeadingMarginSpan.LeadingMarginSpan2) {
-                                int count = ((LeadingMarginSpan.LeadingMarginSpan2)margin).getLeadingMarginLineCount();
-                                useMargin = count > i;
-                            }
-                            left += margin.getLeadingMargin(useMargin);
+                            left += margin.getLeadingMargin(useFirstLineMargin);
                         }
                     }
                 }
             }
 
-            // Adjust the point at which to start rendering depending on the
-            // alignment of the paragraph.
+            boolean hasTabOrEmoji = getLineContainsTab(i);
+            // Can't tell if we have tabs for sure, currently
+            if (hasTabOrEmoji && !tabStopsIsInitialized) {
+                if (tabStops == null) {
+                    tabStops = new TabStops(TAB_INCREMENT, spans);
+                } else {
+                    tabStops.reset(TAB_INCREMENT, spans);
+                }
+                tabStopsIsInitialized = true;
+            }
+
             int x;
             if (align == Alignment.ALIGN_NORMAL) {
                 if (dir == DIR_LEFT_TO_RIGHT) {
@@ -349,44 +369,83 @@
                     x = right;
                 }
             } else {
-                int max = (int)getLineMax(i, spans, false);
+                int max = (int)getLineExtent(i, tabStops, false);
                 if (align == Alignment.ALIGN_OPPOSITE) {
-                    if (dir == DIR_RIGHT_TO_LEFT) {
-                        x = left + max;
-                    } else {
+                    if (dir == DIR_LEFT_TO_RIGHT) {
                         x = right - max;
-                    }
-                } else {
-                    // Alignment.ALIGN_CENTER
-                    max = max & ~1;
-                    int half = (right - left - max) >> 1;
-                    if (dir == DIR_RIGHT_TO_LEFT) {
-                        x = right - half;
                     } else {
-                        x = left + half;
+                        x = left - max;
                     }
+                } else { // Alignment.ALIGN_CENTER
+                    max = max & ~1;
+                    x = (right + left - max) >> 1;
                 }
             }
 
             Directions directions = getLineDirections(i);
-            boolean hasTab = getLineContainsTab(i);
             if (directions == DIRS_ALL_LEFT_TO_RIGHT &&
-                    !spannedText && !hasTab) {
-                if (DEBUG) {
-                    Assert.assertTrue(dir == DIR_LEFT_TO_RIGHT);
-                    Assert.assertNotNull(c);
-                }
+                    !spannedText && !hasTabOrEmoji) {
                 // XXX: assumes there's nothing additional to be done
                 c.drawText(buf, start, end, x, lbaseline, paint);
             } else {
-                tl.set(paint, buf, start, end, dir, directions, hasTab, spans);
+                tl.set(paint, buf, start, end, dir, directions, hasTabOrEmoji, tabStops);
                 tl.draw(c, x, ltop, lbaseline, lbottom);
             }
         }
+
         TextLine.recycle(tl);
     }
 
     /**
+     * Return the start position of the line, given the left and right bounds
+     * of the margins.
+     * 
+     * @param line the line index
+     * @param left the left bounds (0, or leading margin if ltr para)
+     * @param right the right bounds (width, minus leading margin if rtl para)
+     * @return the start position of the line (to right of line if rtl para)
+     */
+    private int getLineStartPos(int line, int left, int right) {
+        // Adjust the point at which to start rendering depending on the
+        // alignment of the paragraph.
+        Alignment align = getParagraphAlignment(line);
+        int dir = getParagraphDirection(line);
+
+        int x;
+        if (align == Alignment.ALIGN_NORMAL) {
+            if (dir == DIR_LEFT_TO_RIGHT) {
+                x = left;
+            } else {
+                x = right;
+            }
+        } else {
+            TabStops tabStops = null;
+            if (mSpannedText && getLineContainsTab(line)) {
+                Spanned spanned = (Spanned) mText;
+                int start = getLineStart(line);
+                int spanEnd = spanned.nextSpanTransition(start, spanned.length(),
+                        TabStopSpan.class);
+                TabStopSpan[] tabSpans = spanned.getSpans(start, spanEnd, TabStopSpan.class);
+                if (tabSpans.length > 0) {
+                    tabStops = new TabStops(TAB_INCREMENT, tabSpans);
+                }
+            }
+            int max = (int)getLineExtent(line, tabStops, false);
+            if (align == Alignment.ALIGN_OPPOSITE) {
+                if (dir == DIR_LEFT_TO_RIGHT) {
+                    x = right - max;
+                } else {
+                    x = left - max;
+                }
+            } else { // Alignment.ALIGN_CENTER
+                max = max & ~1;
+                x = (left + right - max) >> 1;
+            }
+        }
+        return x;
+    }
+
+    /**
      * Return the text that is displayed by this Layout.
      */
     public final CharSequence getText() {
@@ -647,45 +706,28 @@
         int start = getLineStart(line);
         int end = getLineEnd(line);
         int dir = getParagraphDirection(line);
-        boolean tab = getLineContainsTab(line);
+        boolean hasTabOrEmoji = getLineContainsTab(line);
         Directions directions = getLineDirections(line);
 
-        TabStopSpan[] tabs = null;
-        if (tab && mText instanceof Spanned) {
-            tabs = ((Spanned) mText).getSpans(start, end, TabStopSpan.class);
+        TabStops tabStops = null;
+        if (hasTabOrEmoji && mText instanceof Spanned) {
+            // Just checking this line should be good enough, tabs should be
+            // consistent across all lines in a paragraph.
+            TabStopSpan[] tabs = ((Spanned) mText).getSpans(start, end, TabStopSpan.class);
+            if (tabs.length > 0) {
+                tabStops = new TabStops(TAB_INCREMENT, tabs); // XXX should reuse
+            }
         }
 
         TextLine tl = TextLine.obtain();
-        tl.set(mPaint, mText, start, end, dir, directions, tab, tabs);
+        tl.set(mPaint, mText, start, end, dir, directions, hasTabOrEmoji, tabStops);
         float wid = tl.measure(offset - start, trailing, null);
         TextLine.recycle(tl);
 
-        Alignment align = getParagraphAlignment(line);
         int left = getParagraphLeft(line);
         int right = getParagraphRight(line);
 
-        if (align == Alignment.ALIGN_NORMAL) {
-            if (dir == DIR_RIGHT_TO_LEFT)
-                return right + wid;
-            else
-                return left + wid;
-        }
-
-        float max = getLineMax(line);
-
-        if (align == Alignment.ALIGN_OPPOSITE) {
-            if (dir == DIR_RIGHT_TO_LEFT)
-                return left + max + wid;
-            else
-                return right - max + wid;
-        } else { /* align == Alignment.ALIGN_CENTER */
-            int imax = ((int) max) & ~1;
-
-            if (dir == DIR_RIGHT_TO_LEFT)
-                return right - (((right - left) - imax) / 2) + wid;
-            else
-                return left + ((right - left) - imax) / 2 + wid;
-        }
+        return getLineStartPos(line, left, right) + wid;
     }
 
     /**
@@ -743,29 +785,73 @@
     }
 
     /**
-     * Gets the horizontal extent of the specified line, excluding
-     * trailing whitespace.
+     * Gets the unsigned horizontal extent of the specified line, including 
+     * leading margin indent, but excluding trailing whitespace.
      */
     public float getLineMax(int line) {
-        return getLineMax(line, null, false);
+        float margin = getParagraphLeadingMargin(line);
+        float signedExtent = getLineExtent(line, false);
+        return margin + signedExtent >= 0 ? signedExtent : -signedExtent;
     }
 
     /**
-     * Gets the horizontal extent of the specified line, including
-     * trailing whitespace.
+     * Gets the unsigned horizontal extent of the specified line, including
+     * leading margin indent and trailing whitespace.
      */
     public float getLineWidth(int line) {
-        return getLineMax(line, null, true);
+        float margin = getParagraphLeadingMargin(line);
+        float signedExtent = getLineExtent(line, true);
+        return margin + signedExtent >= 0 ? signedExtent : -signedExtent;
     }
 
-    private float getLineMax(int line, Object[] tabs, boolean full) {
+    /**
+     * Like {@link #getLineExtent(int,TabStops,boolean)} but determines the
+     * tab stops instead of using the ones passed in.
+     * @param line the index of the line
+     * @param full whether to include trailing whitespace
+     * @return the extent of the line
+     */
+    private float getLineExtent(int line, boolean full) {
         int start = getLineStart(line);
         int end = full ? getLineEnd(line) : getLineVisibleEnd(line);
-        boolean hasTabs = getLineContainsTab(line);
+
+        boolean hasTabsOrEmoji = getLineContainsTab(line);
+        TabStops tabStops = null;
+        if (hasTabsOrEmoji && mText instanceof Spanned) {
+            // Just checking this line should be good enough, tabs should be
+            // consistent across all lines in a paragraph.
+            TabStopSpan[] tabs = ((Spanned) mText).getSpans(start, end, TabStopSpan.class);
+            if (tabs.length > 0) {
+                tabStops = new TabStops(TAB_INCREMENT, tabs); // XXX should reuse
+            }
+        }
         Directions directions = getLineDirections(line);
+        int dir = getParagraphDirection(line);
 
         TextLine tl = TextLine.obtain();
-        tl.set(mPaint, mText, start, end, 1, directions, hasTabs, tabs);
+        tl.set(mPaint, mText, start, end, dir, directions, hasTabsOrEmoji, tabStops);
+        float width = tl.metrics(null);
+        TextLine.recycle(tl);
+        return width;
+    }
+
+    /**
+     * Returns the signed horizontal extent of the specified line, excluding
+     * leading margin.  If full is false, excludes trailing whitespace.
+     * @param line the index of the line
+     * @param tabStops the tab stops, can be null if we know they're not used.
+     * @param full whether to include trailing whitespace
+     * @return the extent of the text on this line
+     */
+    private float getLineExtent(int line, TabStops tabStops, boolean full) {
+        int start = getLineStart(line);
+        int end = full ? getLineEnd(line) : getLineVisibleEnd(line);
+        boolean hasTabsOrEmoji = getLineContainsTab(line);
+        Directions directions = getLineDirections(line);
+        int dir = getParagraphDirection(line);
+
+        TextLine tl = TextLine.obtain();
+        tl.set(mPaint, mText, start, end, dir, directions, hasTabsOrEmoji, tabStops);
         float width = tl.metrics(null);
         TextLine.recycle(tl);
         return width;
@@ -910,10 +996,6 @@
     }
 
     private int getLineVisibleEnd(int line, int start, int end) {
-        if (DEBUG) {
-            Assert.assertTrue(getLineStart(line) == start && getLineStart(line+1) == end);
-        }
-
         CharSequence text = mText;
         char ch;
         if (line == getLineCount() - 1) {
@@ -1243,84 +1325,107 @@
      * Get the left edge of the specified paragraph, inset by left margins.
      */
     public final int getParagraphLeft(int line) {
-        int dir = getParagraphDirection(line);
-
         int left = 0;
-
-        boolean par = false;
-        int off = getLineStart(line);
-        if (off == 0 || mText.charAt(off - 1) == '\n')
-            par = true;
-
-        if (dir == DIR_LEFT_TO_RIGHT) {
-            if (mSpannedText) {
-                Spanned sp = (Spanned) mText;
-                LeadingMarginSpan[] spans = sp.getSpans(getLineStart(line),
-                                                        getLineEnd(line),
-                                                        LeadingMarginSpan.class);
-
-                for (int i = 0; i < spans.length; i++) {
-                    boolean margin = par;
-                    LeadingMarginSpan span = spans[i];
-                    if (span instanceof LeadingMarginSpan.LeadingMarginSpan2) {
-                        int count = ((LeadingMarginSpan.LeadingMarginSpan2)span).getLeadingMarginLineCount();
-                        margin = count >= line;
-                    }
-                    left += span.getLeadingMargin(margin);
-                }
-            }
+        int dir = getParagraphDirection(line);
+        if (dir == DIR_RIGHT_TO_LEFT || !mSpannedText) {
+            return left; // leading margin has no impact, or no styles
         }
-
-        return left;
+        return getParagraphLeadingMargin(line);
     }
 
     /**
      * Get the right edge of the specified paragraph, inset by right margins.
      */
     public final int getParagraphRight(int line) {
-        int dir = getParagraphDirection(line);
-
         int right = mWidth;
+        int dir = getParagraphDirection(line);
+        if (dir == DIR_LEFT_TO_RIGHT || !mSpannedText) {
+            return right; // leading margin has no impact, or no styles
+        }
+        return right - getParagraphLeadingMargin(line);
+    }
 
-        boolean par = false;
-        int off = getLineStart(line);
-        if (off == 0 || mText.charAt(off - 1) == '\n')
-            par = true;
-
-
-        if (dir == DIR_RIGHT_TO_LEFT) {
-            if (mSpannedText) {
-                Spanned sp = (Spanned) mText;
-                LeadingMarginSpan[] spans = sp.getSpans(getLineStart(line),
-                                                        getLineEnd(line),
-                                                        LeadingMarginSpan.class);
-
-                for (int i = 0; i < spans.length; i++) {
-                    right -= spans[i].getLeadingMargin(par);
-                }
+    /**
+     * Returns the effective leading margin (unsigned) for this line,
+     * taking into account LeadingMarginSpan and LeadingMarginSpan2.
+     * @param line the line index
+     * @return the leading margin of this line
+     */
+    private int getParagraphLeadingMargin(int line) {
+        if (!mSpannedText) {
+            return 0;
+        }
+        Spanned spanned = (Spanned) mText;
+        
+        int lineStart = getLineStart(line);
+        int lineEnd = getLineEnd(line);
+        int spanEnd = spanned.nextSpanTransition(lineStart, lineEnd, 
+                LeadingMarginSpan.class);
+        LeadingMarginSpan[] spans = spanned.getSpans(lineStart, spanEnd,
+                                                LeadingMarginSpan.class);
+        if (spans.length == 0) {
+            return 0; // no leading margin span;
+        }
+        
+        int margin = 0;
+        
+        boolean isFirstParaLine = lineStart == 0 || 
+            spanned.charAt(lineStart - 1) == '\n';
+        
+        for (int i = 0; i < spans.length; i++) {
+            LeadingMarginSpan span = spans[i];
+            boolean useFirstLineMargin = isFirstParaLine;
+            if (span instanceof LeadingMarginSpan2) {
+                int spStart = spanned.getSpanStart(span);
+                int spanLine = getLineForOffset(spStart);
+                int count = ((LeadingMarginSpan2)span).getLeadingMarginLineCount();
+                useFirstLineMargin = line < spanLine + count; 
             }
+            margin += span.getLeadingMargin(useFirstLineMargin);
         }
 
-        return right;
+        return margin;
     }
 
     /* package */
     static float measurePara(TextPaint paint, TextPaint workPaint,
-            CharSequence text, int start, int end, boolean hasTabs,
-            Object[] tabs) {
+            CharSequence text, int start, int end) {
 
         MeasuredText mt = MeasuredText.obtain();
         TextLine tl = TextLine.obtain();
         try {
             mt.setPara(text, start, end, DIR_REQUEST_LTR);
             Directions directions;
-            if (mt.mEasy){
+            int dir;
+            if (mt.mEasy) {
                 directions = DIRS_ALL_LEFT_TO_RIGHT;
+                dir = Layout.DIR_LEFT_TO_RIGHT;
             } else {
                 directions = AndroidBidi.directions(mt.mDir, mt.mLevels,
                     0, mt.mChars, 0, mt.mLen);
+                dir = mt.mDir;
             }
-            tl.set(paint, text, start, end, 1, directions, hasTabs, tabs);
+            char[] chars = mt.mChars;
+            int len = mt.mLen;
+            boolean hasTabs = false;
+            TabStops tabStops = null;
+            for (int i = 0; i < len; ++i) {
+                if (chars[i] == '\t') {
+                    hasTabs = true;
+                    if (text instanceof Spanned) {
+                        Spanned spanned = (Spanned) text;
+                        int spanEnd = spanned.nextSpanTransition(start, end, 
+                                TabStopSpan.class);
+                        TabStopSpan[] spans = spanned.getSpans(start, spanEnd, 
+                                TabStopSpan.class);
+                        if (spans.length > 0) {
+                            tabStops = new TabStops(TAB_INCREMENT, spans);
+                        }
+                    }
+                    break;
+                }
+            }
+            tl.set(paint, text, start, end, dir, directions, hasTabs, tabStops);
             return tl.metrics(null);
         } finally {
             TextLine.recycle(tl);
@@ -1329,6 +1434,67 @@
     }
 
     /**
+     * @hide
+     */
+    /* package */ static class TabStops {
+        private int[] mStops;
+        private int mNumStops;
+        private int mIncrement;
+        
+        TabStops(int increment, Object[] spans) {
+            reset(increment, spans);
+        }
+        
+        void reset(int increment, Object[] spans) {
+            this.mIncrement = increment;
+
+            int ns = 0;
+            if (spans != null) {
+                int[] stops = this.mStops;
+                for (Object o : spans) {
+                    if (o instanceof TabStopSpan) {
+                        if (stops == null) {
+                            stops = new int[10];
+                        } else if (ns == stops.length) {
+                            int[] nstops = new int[ns * 2];
+                            for (int i = 0; i < ns; ++i) {
+                                nstops[i] = stops[i];
+                            }
+                            stops = nstops;
+                        }
+                        stops[ns++] = ((TabStopSpan) o).getTabStop();
+                    }
+                }
+                if (ns > 1) {
+                    Arrays.sort(stops, 0, ns);
+                }
+                if (stops != this.mStops) {
+                    this.mStops = stops;
+                }
+            }
+            this.mNumStops = ns;
+        }
+        
+        float nextTab(float h) {
+            int ns = this.mNumStops;
+            if (ns > 0) {
+                int[] stops = this.mStops;
+                for (int i = 0; i < ns; ++i) {
+                    int stop = stops[i];
+                    if (stop > h) {
+                        return stop;
+                    }
+                }
+            }
+            return nextDefaultStop(h, mIncrement);
+        }
+
+        public static float nextDefaultStop(float h, int inc) {
+            return ((int) ((h + inc) / inc)) * inc;
+        }
+    }
+    
+    /**
      * Returns the position of the next tab stop after h on the line.
      *
      * @param text the text
diff --git a/core/java/android/text/StaticLayout.java b/core/java/android/text/StaticLayout.java
index 3d7e35b..1646b9e 100644
--- a/core/java/android/text/StaticLayout.java
+++ b/core/java/android/text/StaticLayout.java
@@ -23,6 +23,8 @@
 import android.text.style.LeadingMarginSpan;
 import android.text.style.LineHeightSpan;
 import android.text.style.MetricAffectingSpan;
+import android.text.style.TabStopSpan;
+import android.text.style.LeadingMarginSpan.LeadingMarginSpan2;
 
 /**
  * StaticLayout is a Layout for text that will not be edited after it
@@ -146,7 +148,7 @@
                 paraEnd++;
             int paraLen = paraEnd - paraStart;
 
-            int firstWidthLineCount = 1;
+            int firstWidthLineLimit = mLineCount + 1;
             int firstwidth = outerwidth;
             int restwidth = outerwidth;
 
@@ -159,10 +161,16 @@
                     LeadingMarginSpan lms = sp[i];
                     firstwidth -= sp[i].getLeadingMargin(true);
                     restwidth -= sp[i].getLeadingMargin(false);
-                    if (lms instanceof LeadingMarginSpan.LeadingMarginSpan2) {
-                        firstWidthLineCount =
-                            ((LeadingMarginSpan.LeadingMarginSpan2)lms)
-                            .getLeadingMarginLineCount();
+                    
+                    // LeadingMarginSpan2 is odd.  The count affects all
+                    // leading margin spans, not just this particular one,
+                    // and start from the top of the span, not the top of the
+                    // paragraph.
+                    if (lms instanceof LeadingMarginSpan2) {
+                        LeadingMarginSpan2 lms2 = (LeadingMarginSpan2) lms;
+                        int lmsFirstLine = getLineForOffset(spanned.getSpanStart(lms2));
+                        firstWidthLineLimit = lmsFirstLine + 
+                            lms2.getLeadingMarginLineCount();
                     }
                 }
 
@@ -214,7 +222,9 @@
             float fitwidth = w;
             int fitascent = 0, fitdescent = 0, fittop = 0, fitbottom = 0;
 
-            boolean tab = false;
+            boolean hasTabOrEmoji = false;
+            boolean hasTab = false;
+            TabStops tabStops = null;
 
             for (int spanStart = paraStart, spanEnd = spanStart, nextSpanStart;
                     spanStart < paraEnd; spanStart = nextSpanStart) {
@@ -252,8 +262,21 @@
                     if (c == '\n') {
                         ;
                     } else if (c == '\t') {
-                        w = Layout.nextTab(sub, paraStart, paraEnd, w, null);
-                        tab = true;
+                        if (hasTab == false) {
+                            hasTab = true;
+                            hasTabOrEmoji = true;
+                            // First tab this para, check for tabstops
+                            TabStopSpan[] spans = spanned.getSpans(paraStart, 
+                                    paraEnd, TabStopSpan.class);
+                            if (spans.length > 0) {
+                                tabStops = new TabStops(TAB_INCREMENT, spans);
+                            }
+                        }
+                        if (tabStops != null) {
+                            w = tabStops.nextTab(w);
+                        } else {
+                            w = TabStops.nextDefaultStop(w, TAB_INCREMENT);
+                        }
                     } else if (c >= 0xD800 && c <= 0xDFFF && j + 1 < spanEnd) {
                         int emoji = Character.codePointAt(chs, j - paraStart);
 
@@ -275,7 +298,7 @@
                                             bm.getHeight();
 
                                 w += wid;
-                                tab = true;
+                                hasTabOrEmoji = true;
                                 j++;
                             } else {
                                 w += widths[j - paraStart];
@@ -351,7 +374,7 @@
                                     okascent, okdescent, oktop, okbottom,
                                     v,
                                     spacingmult, spacingadd, chooseht,
-                                    choosehtv, fm, tab,
+                                    choosehtv, fm, hasTabOrEmoji,
                                     needMultiply, paraStart, chdirs, dir, easy,
                                     ok == bufend, includepad, trackpad,
                                     chs, widths, here - paraStart,
@@ -387,7 +410,7 @@
                                     okascent, okdescent, oktop, okbottom,
                                     v,
                                     spacingmult, spacingadd, chooseht,
-                                    choosehtv, fm, tab,
+                                    choosehtv, fm, hasTabOrEmoji,
                                     needMultiply, paraStart, chdirs, dir, easy,
                                     ok == bufend, includepad, trackpad,
                                     chs, widths, here - paraStart,
@@ -403,7 +426,7 @@
                                     fittop, fitbottom,
                                     v,
                                     spacingmult, spacingadd, chooseht,
-                                    choosehtv, fm, tab,
+                                    choosehtv, fm, hasTabOrEmoji,
                                     needMultiply, paraStart, chdirs, dir, easy,
                                     fit == bufend, includepad, trackpad,
                                     chs, widths, here - paraStart,
@@ -424,7 +447,7 @@
                                     fm.top, fm.bottom,
                                     v,
                                     spacingmult, spacingadd, chooseht,
-                                    choosehtv, fm, tab,
+                                    choosehtv, fm, hasTabOrEmoji,
                                     needMultiply, paraStart, chdirs, dir, easy,
                                     here + 1 == bufend, includepad,
                                     trackpad,
@@ -449,7 +472,7 @@
                         fitascent = fitdescent = fittop = fitbottom = 0;
                         okascent = okdescent = oktop = okbottom = 0;
 
-                        if (--firstWidthLineCount <= 0) {
+                        if (--firstWidthLineLimit <= 0) {
                             width = restwidth;
                         }
                     }
@@ -473,7 +496,7 @@
                         fittop, fitbottom,
                         v,
                         spacingmult, spacingadd, chooseht,
-                        choosehtv, fm, tab,
+                        choosehtv, fm, hasTabOrEmoji,
                         needMultiply, paraStart, chdirs, dir, easy,
                         paraEnd == bufend, includepad, trackpad,
                         chs, widths, here - paraStart,
@@ -613,7 +636,7 @@
                       int above, int below, int top, int bottom, int v,
                       float spacingmult, float spacingadd,
                       LineHeightSpan[] chooseht, int[] choosehtv,
-                      Paint.FontMetricsInt fm, boolean tab,
+                      Paint.FontMetricsInt fm, boolean hasTabOrEmoji,
                       boolean needMultiply, int pstart, byte[] chdirs,
                       int dir, boolean easy, boolean last,
                       boolean includepad, boolean trackpad,
@@ -700,7 +723,7 @@
         lines[off + mColumns + START] = end;
         lines[off + mColumns + TOP] = v;
 
-        if (tab)
+        if (hasTabOrEmoji)
             lines[off + TAB] |= TAB_MASK;
 
         lines[off + DIR] |= dir << DIR_SHIFT;
@@ -914,7 +937,7 @@
     private static final int DIR_SHIFT  = 30;
     private static final int TAB_MASK   = 0x20000000;
 
-    private static final char FIRST_RIGHT_TO_LEFT = '\u0590';
+    private static final int TAB_INCREMENT = 20; // same as Layout, but that's private
 
     /*
      * This is reused across calls to generate()
diff --git a/core/java/android/text/TextLine.java b/core/java/android/text/TextLine.java
index fae3fc3..bd410c8 100644
--- a/core/java/android/text/TextLine.java
+++ b/core/java/android/text/TextLine.java
@@ -25,10 +25,10 @@
 import android.graphics.Paint.FontMetricsInt;
 import android.icu.text.ArabicShaping;
 import android.text.Layout.Directions;
+import android.text.Layout.TabStops;
 import android.text.style.CharacterStyle;
 import android.text.style.MetricAffectingSpan;
 import android.text.style.ReplacementSpan;
-import android.text.style.TabStopSpan;
 import android.util.Log;
 
 /**
@@ -51,7 +51,7 @@
     private int mDir;
     private Directions mDirections;
     private boolean mHasTabs;
-    private TabStopSpan[] mTabs;
+    private TabStops mTabs;
 
     private char[] mChars;
     private boolean mCharsValid;
@@ -117,11 +117,10 @@
      * @param dir the paragraph direction of this line
      * @param directions the directions information of this line
      * @param hasTabs true if the line might contain tabs or emoji
-     * @param spans array of paragraph-level spans, of which only TabStopSpans
-     * are used.  Can be null.
+     * @param tabStops the tabStops. Can be null.
      */
     void set(TextPaint paint, CharSequence text, int start, int limit, int dir,
-            Directions directions, boolean hasTabs, Object[] spans) {
+            Directions directions, boolean hasTabs, TabStops tabStops) {
         mPaint = paint;
         mText = text;
         mStart = start;
@@ -148,38 +147,8 @@
                 mChars = new char[ArrayUtils.idealCharArraySize(mLen)];
             }
             TextUtils.getChars(text, start, limit, mChars, 0);
-
-            if (hasTabs) {
-                TabStopSpan[] tabs = mTabs;
-                int tabLen = 0;
-                if (mSpanned != null && spans == null) {
-                    TabStopSpan[] newTabs = mSpanned.getSpans(start, limit,
-                            TabStopSpan.class);
-                    if (tabs == null || tabs.length < newTabs.length) {
-                        tabs = newTabs;
-                    } else {
-                        for (int i = 0; i < newTabs.length; ++i) {
-                            tabs[i] = newTabs[i];
-                        }
-                    }
-                    tabLen = newTabs.length;
-                } else if (spans != null) {
-                    if (tabs == null || tabs.length < spans.length) {
-                        tabs = new TabStopSpan[spans.length];
-                    }
-                    for (int i = 0; i < spans.length; ++i) {
-                        if (spans[i] instanceof TabStopSpan) {
-                            tabs[tabLen++] = (TabStopSpan) spans[i];
-                        }
-                    }
-                }
-
-                if (tabs != null && tabLen < tabs.length){
-                    tabs[tabLen] = null;
-                }
-                mTabs = tabs;
-            }
         }
+        mTabs = tabStops;
     }
 
     /**
@@ -993,23 +962,10 @@
      * @return the (unsigned) tab position after this offset
      */
     float nextTab(float h) {
-        float nh = Float.MAX_VALUE;
-        boolean alltabs = false;
-
-        if (mHasTabs && mTabs != null) {
-            TabStopSpan[] tabs = mTabs;
-            for (int i = 0; i < tabs.length && tabs[i] != null; ++i) {
-                int where = tabs[i].getTabStop();
-                if (where < nh && where > h) {
-                    nh = where;
-                }
-            }
-            if (nh != Float.MAX_VALUE) {
-                return nh;
-            }
+        if (mTabs != null) {
+            return mTabs.nextTab(h);
         }
-
-        return ((int) ((h + TAB_INCREMENT) / TAB_INCREMENT)) * TAB_INCREMENT;
+        return TabStops.nextDefaultStop(h, TAB_INCREMENT);
     }
 
     private static final int TAB_INCREMENT = 20;
diff --git a/core/java/android/text/method/Touch.java b/core/java/android/text/method/Touch.java
index 42ad10e..c30db20 100644
--- a/core/java/android/text/method/Touch.java
+++ b/core/java/android/text/method/Touch.java
@@ -18,13 +18,12 @@
 
 import android.text.Layout;
 import android.text.NoCopySpan;
-import android.text.Layout.Alignment;
 import android.text.Spannable;
-import android.util.Log;
+import android.text.Layout.Alignment;
+import android.view.KeyEvent;
 import android.view.MotionEvent;
 import android.view.ViewConfiguration;
 import android.widget.TextView;
-import android.view.KeyEvent;
 
 public class Touch {
     private Touch() { }
@@ -45,6 +44,7 @@
         int left = Integer.MAX_VALUE;
         int right = 0;
         Alignment a = null;
+        boolean ltr = true;
 
         for (int i = top; i <= bottom; i++) {
             left = (int) Math.min(left, layout.getLineLeft(i));
@@ -52,6 +52,7 @@
 
             if (a == null) {
                 a = layout.getParagraphAlignment(i);
+                ltr = layout.getParagraphDirection(i) > 0;
             }
         }
 
@@ -59,10 +60,12 @@
         int width = widget.getWidth();
         int diff = 0;
 
+        // align_opposite does NOT mean align_right, we need the paragraph
+        // direction to resolve it to left or right
         if (right - left < width - padding) {
             if (a == Alignment.ALIGN_CENTER) {
                 diff = (width - padding - (right - left)) / 2;
-            } else if (a == Alignment.ALIGN_OPPOSITE) {
+            } else if (ltr == (a == Alignment.ALIGN_OPPOSITE)) {
                 diff = width - padding - (right - left);
             }
         }
diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java
index 3466c17..0de4f4e 100644
--- a/core/java/android/widget/TextView.java
+++ b/core/java/android/widget/TextView.java
@@ -4847,6 +4847,8 @@
                 break;
 
             case Gravity.RIGHT:
+                // Note, Layout resolves ALIGN_OPPOSITE to left or
+                // right based on the paragraph direction.
                 alignment = Layout.Alignment.ALIGN_OPPOSITE;
                 break;
 
@@ -5668,11 +5670,15 @@
         final int leftChar = mLayout.getOffsetForHorizontal(line, hs);
         final int rightChar = mLayout.getOffsetForHorizontal(line, hspace+hs);
         
+        // line might contain bidirectional text
+        final int lowChar = leftChar < rightChar ? leftChar : rightChar;
+        final int highChar = leftChar > rightChar ? leftChar : rightChar;
+
         int newStart = start;
-        if (newStart < leftChar) {
-            newStart = leftChar;
-        } else if (newStart > rightChar) {
-            newStart = rightChar;
+        if (newStart < lowChar) {
+            newStart = lowChar;
+        } else if (newStart > highChar) {
+            newStart = highChar;
         }
         
         if (newStart != start) {