Sub display list in TextView

TextView uses a sub-display list to 'cache' the rendering of its
text. This saves time when drawing an editable text, where the blinking
cursor forces a re-draw twice per second, which creates pauses during
scrolling.

Added a sub-display list invalidation when an appearance span is
modified/added/removed.

Also added an invalidation of the display list when selection range
is changed.

Change-Id: I41e8068a12902b8a745c5bb77de8c77def76a270
diff --git a/core/java/android/view/DisplayList.java b/core/java/android/view/DisplayList.java
index 8f4ece0..fec0d4b 100644
--- a/core/java/android/view/DisplayList.java
+++ b/core/java/android/view/DisplayList.java
@@ -32,20 +32,20 @@
      * 
      * @return A canvas to record drawing operations.
      */
-    abstract HardwareCanvas start();
+    public abstract HardwareCanvas start();
 
     /**
      * Ends the recording for this display list. A display list cannot be
      * replayed if recording is not finished. 
      */
-    abstract void end();
+    public abstract void end();
 
     /**
      * Invalidates the display list, indicating that it should be repopulated
      * with new drawing commands prior to being used again. Calling this method
      * causes calls to {@link #isValid()} to return <code>false</code>.
      */
-    abstract void invalidate();
+    public abstract void invalidate();
 
     /**
      * Returns whether the display list is currently usable. If this returns false,
@@ -53,12 +53,12 @@
      *
      * @return boolean true if the display list is able to be replayed, false otherwise.
      */
-    abstract boolean isValid();
+    public abstract boolean isValid();
 
     /**
      * Return the amount of memory used by this display list.
      * 
      * @return The size of this display list in bytes
      */
-    abstract int getSize();
+    public abstract int getSize();
 }
diff --git a/core/java/android/view/GLES20Canvas.java b/core/java/android/view/GLES20Canvas.java
index 43a451d..8a9be85 100644
--- a/core/java/android/view/GLES20Canvas.java
+++ b/core/java/android/view/GLES20Canvas.java
@@ -247,7 +247,7 @@
     private static native void nDisableVsync();
 
     @Override
-    void onPreDraw(Rect dirty) {
+    public void onPreDraw(Rect dirty) {
         if (dirty != null) {
             nPrepareDirty(mRenderer, dirty.left, dirty.top, dirty.right, dirty.bottom, mOpaque);
         } else {
@@ -260,7 +260,7 @@
             boolean opaque);
 
     @Override
-    void onPostDraw() {
+    public void onPostDraw() {
         nFinish(mRenderer);
     }
 
diff --git a/core/java/android/view/GLES20DisplayList.java b/core/java/android/view/GLES20DisplayList.java
index 4ca5e98..0cb9449 100644
--- a/core/java/android/view/GLES20DisplayList.java
+++ b/core/java/android/view/GLES20DisplayList.java
@@ -43,7 +43,7 @@
     }
 
     @Override
-    HardwareCanvas start() {
+    public HardwareCanvas start() {
         if (mCanvas != null) {
             throw new IllegalStateException("Recording has already started");
         }
@@ -55,7 +55,7 @@
     }
 
     @Override
-    void invalidate() {
+    public void invalidate() {
         if (mCanvas != null) {
             mCanvas.recycle();
             mCanvas = null;
@@ -64,12 +64,12 @@
     }
 
     @Override
-    boolean isValid() {
+    public boolean isValid() {
         return mValid;
     }
 
     @Override
-    void end() {
+    public void end() {
         if (mCanvas != null) {
             if (mFinalizer != null) {
                 mCanvas.end(mFinalizer.mNativeDisplayList);
@@ -83,7 +83,7 @@
     }
 
     @Override
-    int getSize() {
+    public int getSize() {
         if (mFinalizer == null) return 0;
         return GLES20Canvas.getDisplayListSize(mFinalizer.mNativeDisplayList);
     }
diff --git a/core/java/android/view/HardwareCanvas.java b/core/java/android/view/HardwareCanvas.java
index 23b3abc..cbdbbde 100644
--- a/core/java/android/view/HardwareCanvas.java
+++ b/core/java/android/view/HardwareCanvas.java
@@ -42,12 +42,12 @@
      * 
      * @param dirty The dirty rectangle to update, can be null.
      */
-    abstract void onPreDraw(Rect dirty);
+    public abstract void onPreDraw(Rect dirty);
 
     /**
      * Invoked after all drawing operation have been performed.
      */
-    abstract void onPostDraw();
+    public abstract void onPostDraw();
     
     /**
      * Draws the specified display list onto this canvas.
@@ -61,7 +61,7 @@
      * @return True if the content of the display list requires another
      *         drawing pass (invalidate()), false otherwise
      */
-    abstract boolean drawDisplayList(DisplayList displayList, int width, int height, Rect dirty);
+    public abstract boolean drawDisplayList(DisplayList displayList, int width, int height, Rect dirty);
 
     /**
      * Outputs the specified display list to the log. This method exists for use by
diff --git a/core/java/android/view/HardwareRenderer.java b/core/java/android/view/HardwareRenderer.java
index 3f793bb..b7ccc7a7 100644
--- a/core/java/android/view/HardwareRenderer.java
+++ b/core/java/android/view/HardwareRenderer.java
@@ -276,7 +276,7 @@
      * 
      * @return A new display list.
      */
-    abstract DisplayList createDisplayList();
+    public abstract DisplayList createDisplayList();
 
     /**
      * Creates a new hardware layer. A hardware layer built by calling this
@@ -1083,7 +1083,7 @@
         }
 
         @Override
-        DisplayList createDisplayList() {
+        public DisplayList createDisplayList() {
             return new GLES20DisplayList();
         }
 
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index 64f862a..e280286 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -10433,6 +10433,19 @@
     }
 
     /**
+     * @return The HardwareRenderer associated with that view or null if hardware rendering
+     * is not supported or this this has not been attached to a window.
+     *
+     * @hide
+     */
+    public HardwareRenderer getHardwareRenderer() {
+        if (mAttachInfo != null) {
+            return mAttachInfo.mHardwareRenderer;
+        }
+        return null;
+    }
+
+    /**
      * <p>Returns a display list that can be used to draw this view again
      * without executing its draw method.</p>
      *
diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java
index 3dd7a9f..acea1a1 100644
--- a/core/java/android/widget/TextView.java
+++ b/core/java/android/widget/TextView.java
@@ -84,6 +84,7 @@
 import android.text.method.TransformationMethod;
 import android.text.method.TransformationMethod2;
 import android.text.method.WordIterator;
+import android.text.style.CharacterStyle;
 import android.text.style.ClickableSpan;
 import android.text.style.EasyEditSpan;
 import android.text.style.ParagraphStyle;
@@ -101,9 +102,11 @@
 import android.util.TypedValue;
 import android.view.ActionMode;
 import android.view.ActionMode.Callback;
+import android.view.DisplayList;
 import android.view.DragEvent;
 import android.view.Gravity;
 import android.view.HapticFeedbackConstants;
+import android.view.HardwareCanvas;
 import android.view.KeyCharacterMap;
 import android.view.KeyEvent;
 import android.view.LayoutInflater;
@@ -283,6 +286,9 @@
     }
     private Drawables mDrawables;
 
+    private DisplayList mTextDisplayList;
+    private boolean mTextDisplayListIsValid;
+
     private CharSequence mError;
     private boolean mErrorWasChanged;
     private ErrorPopup mPopup;
@@ -4520,6 +4526,10 @@
 
         resetResolvedDrawables();
 
+        if (mTextDisplayList != null) {
+            mTextDisplayList.invalidate();
+        }
+
         if (mSpellChecker != null) {
             mSpellChecker.closeSession();
             // Forces the creation of a new SpellChecker next time this window is created.
@@ -4970,17 +4980,6 @@
             }
         }
 
-        /*  Comment out until we decide what to do about animations
-        boolean isLinearTextOn = false;
-        if (currentTransformation != null) {
-            isLinearTextOn = mTextPaint.isLinearTextOn();
-            Matrix m = currentTransformation.getMatrix();
-            if (!m.isIdentity()) {
-                // mTextPaint.setLinearTextOn(true);
-            }
-        }
-        */
-
         final InputMethodState ims = mInputMethodState;
         final int cursorOffsetVertical = voffsetCursor - voffsetText;
         if (ims != null && ims.mBatchEditNesting == 0) {
@@ -5038,19 +5037,39 @@
             highlight = null;
         }
 
-        layout.draw(canvas, highlight, mHighlightPaint, cursorOffsetVertical);
+        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();
+                }
+
+                final HardwareCanvas hardwareCanvas = mTextDisplayList.start();
+                try {
+                    hardwareCanvas.setViewport(width, height);
+                    // The dirty rect should always be null for a display list
+                    hardwareCanvas.onPreDraw(null);
+                    layout.draw(hardwareCanvas, highlight, mHighlightPaint, cursorOffsetVertical);
+                } finally {
+                    hardwareCanvas.onPostDraw();
+                    mTextDisplayList.end();
+                    mTextDisplayListIsValid = true;
+                }
+            }
+            ((HardwareCanvas) canvas).drawDisplayList(mTextDisplayList,
+                    mScrollX + width, mScrollY + height, null);
+        } else {
+            layout.draw(canvas, highlight, mHighlightPaint, cursorOffsetVertical);
+        }
 
         if (mMarquee != null && mMarquee.shouldDrawGhost()) {
             canvas.translate((int) mMarquee.getGhostOffset(), 0.0f);
             layout.draw(canvas, highlight, mHighlightPaint, cursorOffsetVertical);
         }
 
-        /*  Comment out until we decide what to do about animations
-        if (currentTransformation != null) {
-            mTextPaint.setLinearTextOn(isLinearTextOn);
-        }
-        */
-
         canvas.restore();
     }
 
@@ -7562,6 +7581,7 @@
      */
     protected void onSelectionChanged(int selStart, int selEnd) {
         sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_TEXT_SELECTION_CHANGED);
+        mTextDisplayListIsValid = false;
     }
 
     /**
@@ -7641,6 +7661,7 @@
         }
 
         updateSpellCheckSpans(start, start + after, false);
+        mTextDisplayListIsValid = false;
 
         // Hide the controllers as soon as text is modified (typing, procedural...)
         // We do not hide the span controllers, since they can be added when a new text is
@@ -7743,7 +7764,8 @@
             }
         }
 
-        if (what instanceof UpdateAppearance || what instanceof ParagraphStyle) {
+        if (what instanceof UpdateAppearance || what instanceof ParagraphStyle ||
+                what instanceof CharacterStyle) {
             if (ims == null || ims.mBatchEditNesting == 0) {
                 invalidate();
                 mHighlightPathBogus = true;
@@ -7751,6 +7773,7 @@
             } else {
                 ims.mContentChanged = true;
             }
+            mTextDisplayListIsValid = false;
         }
 
         if (MetaKeyKeyListener.isMetaTracker(buf, what)) {