Optimize drawHardwareAccelerated method in Editor class

When inserting a new line or breaking a line,
every display list was invalidated and rebuilt before.
However, we can reuse the display lists above intactly and also
reuse the display lists below with only updating drawing locations.

This patch reuses the display lists if possible.
The display lists above the inserted line are just reused
and the display lists below are reused with only updating
drawing locations not fullly rebuilt.

mIndexOfFirstChangedBlock is the index of the first block
which is moved by inserting or breaking a line.
So the display list whose index is >= mIndexOfFirstChangedBlock
only needs to update its drawing location.

Change-Id: Ica20deb0ebb5750de21356ed31fa9f86e657ff92
Signed-off-by: Sangkyu Lee <sk82.lee@lge.com>
diff --git a/core/java/android/text/DynamicLayout.java b/core/java/android/text/DynamicLayout.java
index d909362..122f8a1 100644
--- a/core/java/android/text/DynamicLayout.java
+++ b/core/java/android/text/DynamicLayout.java
@@ -503,8 +503,15 @@
 
         mNumberOfBlocks = newNumberOfBlocks;
         final int deltaLines = newLineCount - (endLine - startLine + 1);
-        for (int i = firstBlock + numAddedBlocks; i < mNumberOfBlocks; i++) {
-            mBlockEndLines[i] += deltaLines;
+        if (deltaLines != 0) {
+            // Display list whose index is >= mIndexFirstChangedBlock is valid
+            // but it needs to update its drawing location.
+            mIndexFirstChangedBlock = firstBlock + numAddedBlocks;
+            for (int i = mIndexFirstChangedBlock; i < mNumberOfBlocks; i++) {
+                mBlockEndLines[i] += deltaLines;
+            }
+        } else {
+            mIndexFirstChangedBlock = mNumberOfBlocks;
         }
 
         int blockIndex = firstBlock;
@@ -559,6 +566,20 @@
         return mNumberOfBlocks;
     }
 
+    /**
+     * @hide
+     */
+    public int getIndexFirstChangedBlock() {
+        return mIndexFirstChangedBlock;
+    }
+
+    /**
+     * @hide
+     */
+    public void setIndexFirstChangedBlock(int i) {
+        mIndexFirstChangedBlock = i;
+    }
+
     @Override
     public int getLineCount() {
         return mInts.size() - 1;
@@ -697,6 +718,8 @@
     private int[] mBlockIndices;
     // Number of items actually currently being used in the above 2 arrays
     private int mNumberOfBlocks;
+    // The first index of the blocks whose locations are changed
+    private int mIndexFirstChangedBlock;
 
     private int mTopPadding, mBottomPadding;
 
diff --git a/core/java/android/widget/Editor.java b/core/java/android/widget/Editor.java
index 41d6033..4eaa78a 100644
--- a/core/java/android/widget/Editor.java
+++ b/core/java/android/widget/Editor.java
@@ -124,7 +124,6 @@
     InputMethodState mInputMethodState;
 
     DisplayList[] mTextDisplayLists;
-    int mLastLayoutHeight;
 
     boolean mFrozenWithFocus;
     boolean mSelectionMoved;
@@ -1289,20 +1288,11 @@
                 mTextDisplayLists = new DisplayList[ArrayUtils.idealObjectArraySize(0)];
             }
 
-            // If the height of the layout changes (usually when inserting or deleting a line,
-            // but could be changes within a span), invalidate everything. We could optimize
-            // more aggressively (for example, adding offsets to blocks) but it would be more
-            // complex and we would only get the benefit in some cases.
-            int layoutHeight = layout.getHeight();
-            if (mLastLayoutHeight != layoutHeight) {
-                invalidateTextDisplayList();
-                mLastLayoutHeight = layoutHeight;
-            }
-
             DynamicLayout dynamicLayout = (DynamicLayout) layout;
             int[] blockEndLines = dynamicLayout.getBlockEndLines();
             int[] blockIndices = dynamicLayout.getBlockIndices();
             final int numberOfBlocks = dynamicLayout.getNumberOfBlocks();
+            final int indexFirstChangedBlock = dynamicLayout.getIndexFirstChangedBlock();
 
             int endOfPreviousBlock = -1;
             int searchStartIndex = 0;
@@ -1327,7 +1317,8 @@
                     if (blockIsInvalid) blockDisplayList.invalidate();
                 }
 
-                if (!blockDisplayList.isValid()) {
+                final boolean blockDisplayListIsInvalid = !blockDisplayList.isValid();
+                if (i >= indexFirstChangedBlock || blockDisplayListIsInvalid) {
                     final int blockBeginLine = endOfPreviousBlock + 1;
                     final int top = layout.getLineTop(blockBeginLine);
                     final int bottom = layout.getLineBottom(blockEndLine);
@@ -1344,24 +1335,30 @@
                         right = (int) (max + 0.5f);
                     }
 
-                    final HardwareCanvas hardwareCanvas = blockDisplayList.start();
-                    try {
-                        // Tighten the bounds of the viewport to the actual text size
-                        hardwareCanvas.setViewport(right - left, bottom - top);
-                        // The dirty rect should always be null for a display list
-                        hardwareCanvas.onPreDraw(null);
-                        // drawText is always relative to TextView's origin, this translation brings
-                        // this range of text back to the top left corner of the viewport
-                        hardwareCanvas.translate(-left, -top);
-                        layout.drawText(hardwareCanvas, blockBeginLine, blockEndLine);
-                        // No need to untranslate, previous context is popped after drawDisplayList
-                    } finally {
-                        hardwareCanvas.onPostDraw();
-                        blockDisplayList.end();
-                        blockDisplayList.setLeftTopRightBottom(left, top, right, bottom);
-                        // Same as drawDisplayList below, handled by our TextView's parent
-                        blockDisplayList.setClipChildren(false);
+                    // Rebuild display list if it is invalid
+                    if (blockDisplayListIsInvalid) {
+                        final HardwareCanvas hardwareCanvas = blockDisplayList.start();
+                        try {
+                            // Tighten the bounds of the viewport to the actual text size
+                            hardwareCanvas.setViewport(right - left, bottom - top);
+                            // The dirty rect should always be null for a display list
+                            hardwareCanvas.onPreDraw(null);
+                            // drawText is always relative to TextView's origin, this translation brings
+                            // this range of text back to the top left corner of the viewport
+                            hardwareCanvas.translate(-left, -top);
+                            layout.drawText(hardwareCanvas, blockBeginLine, blockEndLine);
+                            // No need to untranslate, previous context is popped after drawDisplayList
+                        } finally {
+                            hardwareCanvas.onPostDraw();
+                            blockDisplayList.end();
+                            // Same as drawDisplayList below, handled by our TextView's parent
+                            blockDisplayList.setClipChildren(false);
+                        }
                     }
+
+                    // Valid disply list whose index is >= indexFirstChangedBlock
+                    // only needs to update its drawing location.
+                    blockDisplayList.setLeftTopRightBottom(left, top, right, bottom);
                 }
 
                 ((HardwareCanvas) canvas).drawDisplayList(blockDisplayList, null,
@@ -1369,6 +1366,8 @@
 
                 endOfPreviousBlock = blockEndLine;
             }
+
+            dynamicLayout.setIndexFirstChangedBlock(numberOfBlocks);
         } else {
             // Boring layout is used for empty and hint text
             layout.drawText(canvas, firstLine, lastLine);
diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java
index d635de6..f8db622 100644
--- a/core/java/android/widget/TextView.java
+++ b/core/java/android/widget/TextView.java
@@ -6459,7 +6459,6 @@
             mDeferScroll = -1;
             bringPointIntoView(Math.min(curs, mText.length()));
         }
-        if (changed && mEditor != null) mEditor.invalidateTextDisplayList();
     }
 
     private boolean isShowingHint() {