Add getSelection(..., RectangleConsumer) to Layout

In order to generate the Smart Select animation, we need a list of
rectangles which represent a given selection. The current
getSelectionPath code can only fill out a Path, which loses the
rectangle information in the process.

Instead of working directly with a Path, a RectangleConsumer is
introduced, which is an abstraction over an element that can accept
rectangles (for example, a Path or a List<Rect>).

getSelectionPath is now implemented with this new method using a simple
consumer which takes the rectangles and feeds them to the path.

Test: cts-tradefed run cts-dev -m CtsWidgetTestCases -d -t android.widget.cts.TextViewTest#testGetFocusedRect
Test: manual - verify text selection still works
Change-Id: I32a24f237a4b1b89df23c982fe06566f30fec464
diff --git a/core/java/android/text/Layout.java b/core/java/android/text/Layout.java
index 2c84ba0..758cc82 100644
--- a/core/java/android/text/Layout.java
+++ b/core/java/android/text/Layout.java
@@ -1678,20 +1678,22 @@
     }
 
     private void addSelection(int line, int start, int end,
-                              int top, int bottom, Path dest) {
+            int top, int bottom, RectangleConsumer consumer) {
         int linestart = getLineStart(line);
         int lineend = getLineEnd(line);
         Directions dirs = getLineDirections(line);
 
-        if (lineend > linestart && mText.charAt(lineend - 1) == '\n')
+        if (lineend > linestart && mText.charAt(lineend - 1) == '\n') {
             lineend--;
+        }
 
         for (int i = 0; i < dirs.mDirections.length; i += 2) {
             int here = linestart + dirs.mDirections[i];
-            int there = here + (dirs.mDirections[i+1] & RUN_LENGTH_MASK);
+            int there = here + (dirs.mDirections[i + 1] & RUN_LENGTH_MASK);
 
-            if (there > lineend)
+            if (there > lineend) {
                 there = lineend;
+            }
 
             if (start <= there && end >= here) {
                 int st = Math.max(start, here);
@@ -1704,7 +1706,7 @@
                     float left = Math.min(h1, h2);
                     float right = Math.max(h1, h2);
 
-                    dest.addRect(left, top, right, bottom, Path.Direction.CW);
+                    consumer.accept(left, top, right, bottom);
                 }
             }
         }
@@ -1718,9 +1720,25 @@
      */
     public void getSelectionPath(int start, int end, Path dest) {
         dest.reset();
+        getSelection(start, end, (left, top, right, bottom) ->
+                dest.addRect(left, top, right, bottom, Path.Direction.CW));
+    }
 
-        if (start == end)
+    /**
+     * Calculates the rectangles which should be highlighted to indicate a selection between start
+     * and end and feeds them into the given {@link RectangleConsumer}.
+     *
+     * @param start    the starting index of the selection
+     * @param end      the ending index of the selection
+     * @param consumer the {@link RectangleConsumer} which will receive the generated rectangles. It
+     *                 will be called every time a rectangle is generated.
+     * @hide
+     * @see #getSelectionPath(int, int, Path)
+     */
+    public final void getSelection(int start, int end, final RectangleConsumer consumer) {
+        if (start == end) {
             return;
+        }
 
         if (end < start) {
             int temp = end;
@@ -1735,35 +1753,35 @@
         int bottom = getLineBottomWithoutSpacing(endline);
 
         if (startline == endline) {
-            addSelection(startline, start, end, top, bottom, dest);
+            addSelection(startline, start, end, top, bottom, consumer);
         } else {
             final float width = mWidth;
 
             addSelection(startline, start, getLineEnd(startline),
-                         top, getLineBottom(startline), dest);
+                    top, getLineBottom(startline), consumer);
 
-            if (getParagraphDirection(startline) == DIR_RIGHT_TO_LEFT)
-                dest.addRect(getLineLeft(startline), top,
-                              0, getLineBottom(startline), Path.Direction.CW);
-            else
-                dest.addRect(getLineRight(startline), top,
-                              width, getLineBottom(startline), Path.Direction.CW);
+            if (getParagraphDirection(startline) == DIR_RIGHT_TO_LEFT) {
+                consumer.accept(getLineLeft(startline), top, 0, getLineBottom(startline));
+            } else {
+                consumer.accept(getLineRight(startline), top, width, getLineBottom(startline));
+            }
 
             for (int i = startline + 1; i < endline; i++) {
                 top = getLineTop(i);
                 bottom = getLineBottom(i);
-                dest.addRect(0, top, width, bottom, Path.Direction.CW);
+                consumer.accept(0, top, width, bottom);
             }
 
             top = getLineTop(endline);
             bottom = getLineBottomWithoutSpacing(endline);
 
-            addSelection(endline, getLineStart(endline), end, top, bottom, dest);
+            addSelection(endline, getLineStart(endline), end, top, bottom, consumer);
 
-            if (getParagraphDirection(endline) == DIR_RIGHT_TO_LEFT)
-                dest.addRect(width, top, getLineRight(endline), bottom, Path.Direction.CW);
-            else
-                dest.addRect(0, top, getLineLeft(endline), bottom, Path.Direction.CW);
+            if (getParagraphDirection(endline) == DIR_RIGHT_TO_LEFT) {
+                consumer.accept(width, top, getLineRight(endline), bottom);
+            } else {
+                consumer.accept(0, top, getLineLeft(endline), bottom);
+            }
         }
     }
 
@@ -2262,4 +2280,18 @@
     public static final Directions DIRS_ALL_RIGHT_TO_LEFT =
         new Directions(new int[] { 0, RUN_LENGTH_MASK | RUN_RTL_FLAG });
 
+    /** @hide */
+    @FunctionalInterface
+    public interface RectangleConsumer {
+        /**
+         * Performs this operation on the given rectangle.
+         *
+         * @param left   the left edge of the rectangle
+         * @param top    the top edge of the rectangle
+         * @param right  the right edge of the rectangle
+         * @param bottom the bottom edge of the rectangle
+         */
+        void accept(float left, float top, float right, float bottom);
+    }
+
 }