Merge "EditText caches only text in its internal display list."
diff --git a/core/java/android/text/Layout.java b/core/java/android/text/Layout.java
index bdfe940..516ce2a 100644
--- a/core/java/android/text/Layout.java
+++ b/core/java/android/text/Layout.java
@@ -76,7 +76,6 @@
                                         int start, int end,
                                         TextPaint paint) {
         float need = 0;
-        TextPaint workPaint = new TextPaint();
 
         int next;
         for (int i = start; i <= end; i = next) {
@@ -86,7 +85,7 @@
                 next = end;
 
             // note, omits trailing paragraph char
-            float w = measurePara(paint, workPaint, source, i, next);
+            float w = measurePara(paint, source, i, next);
 
             if (w > need)
                 need = w;
@@ -189,106 +188,34 @@
      * Draw this Layout on the specified canvas, with the highlight path drawn
      * between the background and the text.
      *
-     * @param c the canvas
+     * @param canvas the canvas
      * @param highlight the path of the highlight or cursor; can be null
      * @param highlightPaint the paint for the highlight
      * @param cursorOffsetVertical the amount to temporarily translate the
      *        canvas while rendering the highlight
      */
-    public void draw(Canvas c, Path highlight, Paint highlightPaint,
-                     int cursorOffsetVertical) {
-        int dtop, dbottom;
+    public void draw(Canvas canvas, Path highlight, Paint highlightPaint,
+            int cursorOffsetVertical) {
+        final long lineRange = getLineRangeForDraw(canvas);
+        int firstLine = TextUtils.unpackRangeStartFromLong(lineRange);
+        int lastLine = TextUtils.unpackRangeEndFromLong(lineRange);
+        if (lastLine < 0) return;
 
-        synchronized (sTempRect) {
-            if (!c.getClipBounds(sTempRect)) {
-                return;
-            }
+        drawBackground(canvas, highlight, highlightPaint, cursorOffsetVertical,
+                firstLine, lastLine);
+        drawText(canvas, firstLine, lastLine);
+    }
 
-            dtop = sTempRect.top;
-            dbottom = sTempRect.bottom;
-        }
-
-        int top = 0;
-        int bottom = getLineTop(getLineCount());
-
-        if (dtop > top) {
-            top = dtop;
-        }
-        if (dbottom < bottom) {
-            bottom = dbottom;
-        }
-
-        int first = getLineForVertical(top);
-        int last = getLineForVertical(bottom);
-
-        int previousLineBottom = getLineTop(first);
-        int previousLineEnd = getLineStart(first);
-
-        TextPaint paint = mPaint;
-        CharSequence buf = mText;
-        int width = mWidth;
-        boolean spannedText = mSpannedText;
-
+    /**
+     * @hide
+     */
+    public void drawText(Canvas canvas, int firstLine, int lastLine) {
+        int previousLineBottom = getLineTop(firstLine);
+        int previousLineEnd = getLineStart(firstLine);
         ParagraphStyle[] spans = NO_PARA_SPANS;
         int spanEnd = 0;
-        int textLength = 0;
-
-        // First, draw LineBackgroundSpans.
-        // 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;
-                int end = getLineStart(i+1);
-                previousLineEnd = end;
-
-                int ltop = previousLineBottom;
-                int lbottom = getLineTop(i+1);
-                previousLineBottom = lbottom;
-                int lbaseline = lbottom - getLineDescent(i);
-
-                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 = getParagraphSpans(sp, start, end, LineBackgroundSpan.class);
-                }
-
-                for (int n = 0; n < spans.length; n++) {
-                    LineBackgroundSpan back = (LineBackgroundSpan) spans[n];
-
-                    back.drawBackground(c, paint, 0, width,
-                                       ltop, lbaseline, lbottom,
-                                       buf, start, end,
-                                       i);
-                }
-            }
-            // reset to their original values
-            spanEnd = 0;
-            previousLineBottom = getLineTop(first);
-            previousLineEnd = getLineStart(first);
-            spans = NO_PARA_SPANS;
-        }
-
-        // There can be a highlight even without spans if we are drawing
-        // a non-spanned transformation of a spanned editing buffer.
-        if (highlight != null) {
-            if (cursorOffsetVertical != 0) {
-                c.translate(0, cursorOffsetVertical);
-            }
-
-            c.drawPath(highlight, highlightPaint);
-
-            if (cursorOffsetVertical != 0) {
-                c.translate(0, -cursorOffsetVertical);
-            }
-        }
+        TextPaint paint = mPaint;
+        CharSequence buf = mText;
 
         Alignment paraAlign = mAlignment;
         TabStops tabStops = null;
@@ -296,13 +223,11 @@
 
         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.
-        for (int i = first; i <= last; i++) {
+        // Draw the lines, one at a time.
+        // The baseline is the top of the following line minus the current line's descent.
+        for (int i = firstLine; i <= lastLine; i++) {
             int start = previousLineEnd;
-
-            previousLineEnd = getLineStart(i+1);
+            previousLineEnd = getLineStart(i + 1);
             int end = getLineVisibleEnd(i, start, previousLineEnd);
 
             int ltop = previousLineBottom;
@@ -314,10 +239,10 @@
             int left = 0;
             int right = mWidth;
 
-            if (spannedText) {
+            if (mSpannedText) {
                 Spanned sp = (Spanned) buf;
-                boolean isFirstParaLine = (start == 0 ||
-                        buf.charAt(start - 1) == '\n');
+                int textLength = buf.length();
+                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.
@@ -329,13 +254,13 @@
                 // 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)) {
+                if (start >= spanEnd && (i == firstLine || isFirstParaLine)) {
                     spanEnd = sp.nextSpanTransition(start, textLength,
                                                     ParagraphStyle.class);
                     spans = getParagraphSpans(sp, start, spanEnd, ParagraphStyle.class);
 
                     paraAlign = mAlignment;
-                    for (int n = spans.length-1; n >= 0; n--) {
+                    for (int n = spans.length - 1; n >= 0; n--) {
                         if (spans[n] instanceof AlignmentSpan) {
                             paraAlign = ((AlignmentSpan) spans[n]).getAlignment();
                             break;
@@ -359,12 +284,12 @@
                         }
 
                         if (dir == DIR_RIGHT_TO_LEFT) {
-                            margin.drawLeadingMargin(c, paint, right, dir, ltop,
+                            margin.drawLeadingMargin(canvas, paint, right, dir, ltop,
                                                      lbaseline, lbottom, buf,
                                                      start, end, isFirstParaLine, this);
                             right -= margin.getLeadingMargin(useFirstLineMargin);
                         } else {
-                            margin.drawLeadingMargin(c, paint, left, dir, ltop,
+                            margin.drawLeadingMargin(canvas, paint, left, dir, ltop,
                                                      lbaseline, lbottom, buf,
                                                      start, end, isFirstParaLine, this);
                             left += margin.getLeadingMargin(useFirstLineMargin);
@@ -416,13 +341,12 @@
             }
 
             Directions directions = getLineDirections(i);
-            if (directions == DIRS_ALL_LEFT_TO_RIGHT &&
-                    !spannedText && !hasTabOrEmoji) {
+            if (directions == DIRS_ALL_LEFT_TO_RIGHT && !mSpannedText && !hasTabOrEmoji) {
                 // XXX: assumes there's nothing additional to be done
-                c.drawText(buf, start, end, x, lbaseline, paint);
+                canvas.drawText(buf, start, end, x, lbaseline, paint);
             } else {
                 tl.set(paint, buf, start, end, dir, directions, hasTabOrEmoji, tabStops);
-                tl.draw(c, x, ltop, lbaseline, lbottom);
+                tl.draw(canvas, x, ltop, lbaseline, lbottom);
             }
         }
 
@@ -430,6 +354,85 @@
     }
 
     /**
+     * @hide
+     */
+    public void drawBackground(Canvas canvas, Path highlight, Paint highlightPaint,
+            int cursorOffsetVertical, int firstLine, int lastLine) {
+        // First, draw LineBackgroundSpans.
+        // LineBackgroundSpans know nothing about the alignment, margins, or
+        // direction of the layout or line.  XXX: Should they?
+        // They are evaluated at each line.
+        if (mSpannedText) {
+            int previousLineBottom = getLineTop(firstLine);
+            int previousLineEnd = getLineStart(firstLine);
+            ParagraphStyle[] spans = NO_PARA_SPANS;
+            TextPaint paint = mPaint;
+            CharSequence buf = mText;
+            int spanEnd = 0;
+            final int width = mWidth;
+            Spanned sp = (Spanned) buf;
+            int textLength = buf.length();
+            for (int i = firstLine; i <= lastLine; i++) {
+                int start = previousLineEnd;
+                int end = getLineStart(i + 1);
+                previousLineEnd = end;
+
+                int ltop = previousLineBottom;
+                int lbottom = getLineTop(i + 1);
+                previousLineBottom = lbottom;
+                int lbaseline = lbottom - getLineDescent(i);
+
+                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 = getParagraphSpans(sp, start, end, LineBackgroundSpan.class);
+                }
+
+                for (int n = 0; n < spans.length; n++) {
+                    LineBackgroundSpan back = (LineBackgroundSpan) spans[n];
+                    back.drawBackground(canvas, paint, 0, width,
+                            ltop, lbaseline, lbottom,
+                            buf, start, end, i);
+                }
+            }
+        }
+
+        // There can be a highlight even without spans if we are drawing
+        // a non-spanned transformation of a spanned editing buffer.
+        if (highlight != null) {
+            if (cursorOffsetVertical != 0) canvas.translate(0, cursorOffsetVertical);
+            canvas.drawPath(highlight, highlightPaint);
+            if (cursorOffsetVertical != 0) canvas.translate(0, -cursorOffsetVertical);
+        }
+    }
+
+    /**
+     * @param canvas
+     * @return The range of lines that need to be drawn, possibly empty.
+     * @hide
+     */
+    public long getLineRangeForDraw(Canvas canvas) {
+        int dtop, dbottom;
+
+        synchronized (sTempRect) {
+            if (!canvas.getClipBounds(sTempRect)) {
+                // Negative range end used as a special flag
+                return TextUtils.packRangeInLong(0, -1);
+            }
+
+            dtop = sTempRect.top;
+            dbottom = sTempRect.bottom;
+        }
+
+        final int top = Math.max(dtop, 0);
+        final int bottom = Math.min(getLineTop(getLineCount()), dbottom);
+
+        return TextUtils.packRangeInLong(getLineForVertical(top), getLineForVertical(bottom));
+    }
+
+    /**
      * Return the start position of the line, given the left and right bounds
      * of the margins.
      *
@@ -460,7 +463,8 @@
                 int start = getLineStart(line);
                 int spanEnd = spanned.nextSpanTransition(start, spanned.length(),
                         TabStopSpan.class);
-                TabStopSpan[] tabSpans = getParagraphSpans(spanned, start, spanEnd, TabStopSpan.class);
+                TabStopSpan[] tabSpans = getParagraphSpans(spanned, start, spanEnd,
+                        TabStopSpan.class);
                 if (tabSpans.length > 0) {
                     tabStops = new TabStops(TAB_INCREMENT, tabSpans);
                 }
@@ -1481,8 +1485,7 @@
     }
 
     /* package */
-    static float measurePara(TextPaint paint, TextPaint workPaint,
-            CharSequence text, int start, int end) {
+    static float measurePara(TextPaint paint, CharSequence text, int start, int end) {
 
         MeasuredText mt = MeasuredText.obtain();
         TextLine tl = TextLine.obtain();
@@ -1659,7 +1662,7 @@
      */
     /* package */ static <T> T[] getParagraphSpans(Spanned text, int start, int end, Class<T> type) {
         if (start == end && start > 0) {
-            return (T[]) ArrayUtils.emptyArray(type);
+            return ArrayUtils.emptyArray(type);
         }
 
         return text.getSpans(start, end, type);
@@ -1777,8 +1780,7 @@
 
     }
 
-    /* package */ static class SpannedEllipsizer
-                    extends Ellipsizer implements Spanned {
+    /* package */ static class SpannedEllipsizer extends Ellipsizer implements Spanned {
         private Spanned mSpanned;
 
         public SpannedEllipsizer(CharSequence display) {
@@ -1802,6 +1804,7 @@
             return mSpanned.getSpanFlags(tag);
         }
 
+        @SuppressWarnings("rawtypes")
         public int nextSpanTransition(int start, int limit, Class type) {
             return mSpanned.nextSpanTransition(start, limit, type);
         }
diff --git a/core/java/android/text/TextUtils.java b/core/java/android/text/TextUtils.java
index 43dfc81..afae5bb2 100644
--- a/core/java/android/text/TextUtils.java
+++ b/core/java/android/text/TextUtils.java
@@ -1664,6 +1664,36 @@
         }
     }
 
+    /**
+     * Pack 2 int values into a long, useful as a return value for a range
+     * @see #unpackRangeStartFromLong(long)
+     * @see #unpackRangeEndFromLong(long)
+     * @hide
+     */
+    public static long packRangeInLong(int start, int end) {
+        return (((long) start) << 32) | end;
+    }
+
+    /**
+     * Get the start value from a range packed in a long by {@link #packRangeInLong(int, int)}
+     * @see #unpackRangeEndFromLong(long)
+     * @see #packRangeInLong(int, int)
+     * @hide
+     */
+    public static int unpackRangeStartFromLong(long range) {
+        return (int) (range >>> 32);
+    }
+
+    /**
+     * Get the end value from a range packed in a long by {@link #packRangeInLong(int, int)}
+     * @see #unpackRangeStartFromLong(long)
+     * @see #packRangeInLong(int, int)
+     * @hide
+     */
+    public static int unpackRangeEndFromLong(long range) {
+        return (int) (range & 0x00000000FFFFFFFFL);
+    }
+
     private static Object sLock = new Object();
     private static char[] sTemp = null;
 
diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java
index 56a0d1e..cbacd7a 100644
--- a/core/java/android/widget/TextView.java
+++ b/core/java/android/widget/TextView.java
@@ -6946,7 +6946,6 @@
      */
     protected void onSelectionChanged(int selStart, int selEnd) {
         sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_TEXT_SELECTION_CHANGED);
-        if (mEditor != null) getEditor().mTextDisplayListIsValid = false;
     }
 
     /**
@@ -7772,18 +7771,6 @@
                 hasPrimaryClip());
     }
 
-    private static long packRangeInLong(int start, int end) {
-        return (((long) start) << 32) | end;
-    }
-
-    private static int extractRangeStartFromLong(long range) {
-        return (int) (range >>> 32);
-    }
-
-    private static int extractRangeEndFromLong(long range) {
-        return (int) (range & 0x00000000FFFFFFFFL);
-    }
-
     private boolean selectAll() {
         final int length = mText.length();
         Selection.setSelection((Spannable) mText, 0, length);
@@ -7822,8 +7809,8 @@
         }
 
         long lastTouchOffsets = getLastTouchOffsets();
-        final int minOffset = extractRangeStartFromLong(lastTouchOffsets);
-        final int maxOffset = extractRangeEndFromLong(lastTouchOffsets);
+        final int minOffset = TextUtils.unpackRangeStartFromLong(lastTouchOffsets);
+        final int maxOffset = TextUtils.unpackRangeEndFromLong(lastTouchOffsets);
 
         // Safety check in case standard touch event handling has been bypassed
         if (minOffset < 0 || minOffset >= mText.length()) return false;
@@ -7848,8 +7835,8 @@
                     selectionStart == selectionEnd) {
                 // Possible when the word iterator does not properly handle the text's language
                 long range = getCharRange(minOffset);
-                selectionStart = extractRangeStartFromLong(range);
-                selectionEnd = extractRangeEndFromLong(range);
+                selectionStart = TextUtils.unpackRangeStartFromLong(range);
+                selectionEnd = TextUtils.unpackRangeEndFromLong(range);
             }
         }
 
@@ -7897,30 +7884,30 @@
             final char currentChar = mText.charAt(offset);
             final char nextChar = mText.charAt(offset + 1);
             if (Character.isSurrogatePair(currentChar, nextChar)) {
-                return packRangeInLong(offset,  offset + 2);
+                return TextUtils.packRangeInLong(offset,  offset + 2);
             }
         }
         if (offset < textLength) {
-            return packRangeInLong(offset,  offset + 1);
+            return TextUtils.packRangeInLong(offset,  offset + 1);
         }
         if (offset - 2 >= 0) {
             final char previousChar = mText.charAt(offset - 1);
             final char previousPreviousChar = mText.charAt(offset - 2);
             if (Character.isSurrogatePair(previousPreviousChar, previousChar)) {
-                return packRangeInLong(offset - 2,  offset);
+                return TextUtils.packRangeInLong(offset - 2,  offset);
             }
         }
         if (offset - 1 >= 0) {
-            return packRangeInLong(offset - 1,  offset);
+            return TextUtils.packRangeInLong(offset - 1,  offset);
         }
-        return packRangeInLong(offset,  offset);
+        return TextUtils.packRangeInLong(offset,  offset);
     }
 
     private long getLastTouchOffsets() {
         SelectionModifierCursorController selectionController = getSelectionController();
         final int minOffset = selectionController.getMinTouchOffset();
         final int maxOffset = selectionController.getMaxTouchOffset();
-        return packRangeInLong(minOffset, maxOffset);
+        return TextUtils.packRangeInLong(minOffset, maxOffset);
     }
 
     @Override
@@ -8111,7 +8098,7 @@
             }
         }
 
-        return packRangeInLong(min, max);
+        return TextUtils.packRangeInLong(min, max);
     }
 
     private DragShadowBuilder getTextThumbnailBuilder(CharSequence text) {
@@ -8470,8 +8457,8 @@
                 if (paste != null) {
                     if (!didFirst) {
                         long minMax = prepareSpacesAroundPaste(min, max, paste);
-                        min = extractRangeStartFromLong(minMax);
-                        max = extractRangeEndFromLong(minMax);
+                        min = TextUtils.unpackRangeStartFromLong(minMax);
+                        max = TextUtils.unpackRangeEndFromLong(minMax);
                         Selection.setSelection((Spannable) mText, max);
                         ((Editable) mText).replace(min, max, paste);
                         didFirst = true;
@@ -8630,8 +8617,8 @@
 
         final int originalLength = mText.length();
         long minMax = prepareSpacesAroundPaste(offset, offset, content);
-        int min = extractRangeStartFromLong(minMax);
-        int max = extractRangeEndFromLong(minMax);
+        int min = TextUtils.unpackRangeStartFromLong(minMax);
+        int max = TextUtils.unpackRangeEndFromLong(minMax);
 
         Selection.setSelection((Spannable) mText, max);
         replaceText_internal(min, max, content);
@@ -11667,33 +11654,7 @@
             }
 
             if (canHaveDisplayList() && canvas.isHardwareAccelerated()) {
-                final int width = mRight - mLeft;
-                final int height = mBottom - mTop;
-
-                if (mTextDisplayList == null || !mTextDisplayList.isValid() ||
-                        !mTextDisplayListIsValid) {
-                    if (mTextDisplayList == null) {
-                        mTextDisplayList = getHardwareRenderer().createDisplayList("Text");
-                    }
-
-                    final HardwareCanvas hardwareCanvas = mTextDisplayList.start();
-                    try {
-                        hardwareCanvas.setViewport(width, height);
-                        // The dirty rect should always be null for a display list
-                        hardwareCanvas.onPreDraw(null);
-                        hardwareCanvas.translate(-mScrollX, -mScrollY);
-                        layout.draw(hardwareCanvas, highlight, mHighlightPaint, cursorOffsetVertical);
-                        hardwareCanvas.translate(mScrollX, mScrollY);
-                    } finally {
-                        hardwareCanvas.onPostDraw();
-                        mTextDisplayList.end();
-                        mTextDisplayListIsValid = true;
-                    }
-                }
-                canvas.translate(mScrollX, mScrollY);
-                ((HardwareCanvas) canvas).drawDisplayList(mTextDisplayList, width, height, null,
-                        DisplayList.FLAG_CLIP_CHILDREN);
-                canvas.translate(-mScrollX, -mScrollY);
+                drawHardwareAccelerated(canvas, layout, highlight, cursorOffsetVertical);
             } else {
                 layout.draw(canvas, highlight, mHighlightPaint, cursorOffsetVertical);
             }
@@ -11704,6 +11665,46 @@
             }
         }
 
+        private void drawHardwareAccelerated(Canvas canvas, Layout layout, Path highlight,
+                int cursorOffsetVertical) {
+            final int width = mRight - mLeft;
+            final int height = mBottom - mTop;
+
+            final long lineRange = layout.getLineRangeForDraw(canvas);
+            int firstLine = TextUtils.unpackRangeStartFromLong(lineRange);
+            int lastLine = TextUtils.unpackRangeEndFromLong(lineRange);
+            if (lastLine < 0) return;
+
+            layout.drawBackground(canvas, highlight, mHighlightPaint, cursorOffsetVertical,
+                    firstLine, lastLine);
+
+            if (mTextDisplayList == null || !mTextDisplayList.isValid() ||
+                    !mTextDisplayListIsValid) {
+                if (mTextDisplayList == null) {
+                    mTextDisplayList = getHardwareRenderer().createDisplayList("Text");
+                }
+
+                final HardwareCanvas hardwareCanvas = mTextDisplayList.start();
+                try {
+                    hardwareCanvas.setViewport(width, height);
+                    // The dirty rect should always be null for a display list
+                    hardwareCanvas.onPreDraw(null);
+                    hardwareCanvas.translate(-mScrollX, -mScrollY);
+                    layout.drawText(hardwareCanvas, firstLine, lastLine);
+                    //layout.draw(hardwareCanvas, highlight, mHighlightPaint, cursorOffsetVertical);
+                    hardwareCanvas.translate(mScrollX, mScrollY);
+                } finally {
+                    hardwareCanvas.onPostDraw();
+                    mTextDisplayList.end();
+                    mTextDisplayListIsValid = true;
+                }
+            }
+            canvas.translate(mScrollX, mScrollY);
+            ((HardwareCanvas) canvas).drawDisplayList(mTextDisplayList, width, height, null,
+                    DisplayList.FLAG_CLIP_CHILDREN);
+            canvas.translate(-mScrollX, -mScrollY);
+        }
+
         private void drawCursor(Canvas canvas, int cursorOffsetVertical) {
             final boolean translate = cursorOffsetVertical != 0;
             if (translate) canvas.translate(0, cursorOffsetVertical);