summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--core/java/android/widget/SelectionActionModeHelper.java65
-rw-r--r--core/tests/coretests/src/android/widget/SelectionActionModeHelperTest.java151
2 files changed, 209 insertions, 7 deletions
diff --git a/core/java/android/widget/SelectionActionModeHelper.java b/core/java/android/widget/SelectionActionModeHelper.java
index 041b515f7e47..81a5eadf8173 100644
--- a/core/java/android/widget/SelectionActionModeHelper.java
+++ b/core/java/android/widget/SelectionActionModeHelper.java
@@ -56,7 +56,7 @@ import java.util.function.Supplier;
*/
@UiThread
@VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
-final class SelectionActionModeHelper {
+public final class SelectionActionModeHelper {
/**
* Maximum time (in milliseconds) to wait for a result before timing out.
@@ -262,17 +262,66 @@ final class SelectionActionModeHelper {
private List<RectF> convertSelectionToRectangles(final Layout layout, final int start,
final int end) {
final List<RectF> result = new ArrayList<>();
- // TODO filter out invalid rectangles
- // getSelection might give us overlapping and zero-dimension rectangles which will interfere
- // with the Smart Select animation
layout.getSelection(start, end, (left, top, right, bottom, textSelectionLayout) ->
- result.add(new RectF(left, top, right, bottom)));
+ mergeRectangleIntoList(result, new RectF(left, top, right, bottom)));
result.sort(SmartSelectSprite.RECTANGLE_COMPARATOR);
-
return result;
}
+ /**
+ * Merges a {@link RectF} into an existing list of rectangles. While merging, this method
+ * makes sure that:
+ *
+ * <ol>
+ * <li>No rectangle is redundant (contained within a bigger rectangle)</li>
+ * <li>Rectangles of the same height and vertical position that intersect get merged</li>
+ * </ol>
+ *
+ * @param list the list of rectangles to merge the new rectangle in
+ * @param candidate the {@link RectF} to merge into the list
+ * @hide
+ */
+ @VisibleForTesting
+ public static void mergeRectangleIntoList(List<RectF> list, RectF candidate) {
+ if (candidate.isEmpty()) {
+ return;
+ }
+
+ final int elementCount = list.size();
+ for (int index = 0; index < elementCount; ++index) {
+ final RectF existingRectangle = list.get(index);
+ if (existingRectangle.contains(candidate)) {
+ return;
+ }
+ if (candidate.contains(existingRectangle)) {
+ existingRectangle.setEmpty();
+ continue;
+ }
+
+ final boolean rectanglesContinueEachOther = candidate.left == existingRectangle.right
+ || candidate.right == existingRectangle.left;
+ final boolean canMerge = candidate.top == existingRectangle.top
+ && candidate.bottom == existingRectangle.bottom
+ && (RectF.intersects(candidate, existingRectangle)
+ || rectanglesContinueEachOther);
+
+ if (canMerge) {
+ candidate.union(existingRectangle);
+ existingRectangle.setEmpty();
+ }
+ }
+
+ for (int index = elementCount - 1; index >= 0; --index) {
+ if (list.get(index).isEmpty()) {
+ list.remove(index);
+ }
+ }
+
+ list.add(candidate);
+ }
+
+
/** @hide */
@VisibleForTesting
public static PointF movePointInsideNearestRectangle(final PointF point,
@@ -281,7 +330,9 @@ final class SelectionActionModeHelper {
float bestY = -1;
double bestDistance = Double.MAX_VALUE;
- for (final RectF rectangle : rectangles) {
+ final int elementCount = rectangles.size();
+ for (int index = 0; index < elementCount; ++index) {
+ final RectF rectangle = rectangles.get(index);
final float candidateY = rectangle.centerY();
final float candidateX;
diff --git a/core/tests/coretests/src/android/widget/SelectionActionModeHelperTest.java b/core/tests/coretests/src/android/widget/SelectionActionModeHelperTest.java
index d94a017c27fd..b881053a5872 100644
--- a/core/tests/coretests/src/android/widget/SelectionActionModeHelperTest.java
+++ b/core/tests/coretests/src/android/widget/SelectionActionModeHelperTest.java
@@ -25,6 +25,7 @@ import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;
+import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
@@ -110,4 +111,154 @@ public final class SelectionActionModeHelperTest {
assertEquals(expectedPointY, adjustedPoint.y, 0.0f);
}
+ @Test
+ public void testMergeRectangleIntoList_addThreeDisjointRectangles() {
+ testExpandRectangleList(
+ new RectF[] {
+ new RectF(0, 0, 1, 1),
+ new RectF(10, 10, 11, 11),
+ new RectF(20, 20, 21, 21)
+ },
+ new RectF[] {
+ new RectF(0, 0, 1, 1),
+ new RectF(10, 10, 11, 11),
+ new RectF(20, 20, 21, 21)
+ }
+ );
+ }
+
+ @Test
+ public void testMergeRectangleIntoList_addAnEmptyRectangle() {
+ testExpandRectangleList(
+ new RectF[] {
+ new RectF(0, 0, 0, 0)
+ },
+ new RectF[] {
+ }
+ );
+ }
+
+ @Test
+ public void testMergeRectangleIntoList_addAContainedRectangle() {
+ testExpandRectangleList(
+ new RectF[] {
+ new RectF(0, 0, 10, 10),
+ new RectF(9, 0, 10, 10)
+ },
+ new RectF[] {
+ new RectF(0, 0, 10, 10)
+ }
+ );
+ }
+
+ @Test
+ public void testMergeRectangleIntoList_addARectangleThatContainsExistingRectangles() {
+ testExpandRectangleList(
+ new RectF[] {
+ new RectF(0, 0, 1, 1),
+ new RectF(1, 0, 2, 1),
+ new RectF(0, 0, 2, 1)
+ },
+ new RectF[] {
+ new RectF(0, 0, 2, 1)
+ }
+ );
+ }
+
+ @Test
+ public void testMergeRectangleIntoList_addRectangleThatIntersectsAndHasTheSameHeightOnRight() {
+ testExpandRectangleList(
+ new RectF[] {
+ new RectF(0, 0, 1, 1),
+ new RectF(0.5f, 0, 1.5f, 1)
+ },
+ new RectF[] {
+ new RectF(0, 0, 1.5f, 1)
+ }
+ );
+ }
+
+ @Test
+ public void testMergeRectangleIntoList_addRectangleThatIntersectsAndHasTheSameHeightOnLeft() {
+ testExpandRectangleList(
+ new RectF[] {
+ new RectF(0.5f, 0, 1.5f, 1),
+ new RectF(0, 0, 1, 1)
+ },
+ new RectF[] {
+ new RectF(0, 0, 1.5f, 1)
+ }
+ );
+ }
+
+ @Test
+ public void testMergeRectangleIntoList_addRectangleThatExpandsToTheRight() {
+ testExpandRectangleList(
+ new RectF[] {
+ new RectF(0, 0, 1, 1),
+ new RectF(1, 0, 1.5f, 1)
+ },
+ new RectF[] {
+ new RectF(0, 0, 1.5f, 1)
+ }
+ );
+ }
+
+ @Test
+ public void testMergeRectangleIntoList_addRectangleThatExpandsToTheLeft() {
+ testExpandRectangleList(
+ new RectF[] {
+ new RectF(1, 0, 1.5f, 1),
+ new RectF(0, 0, 1, 1)
+ },
+ new RectF[] {
+ new RectF(0, 0, 1.5f, 1)
+ }
+ );
+ }
+
+
+ @Test
+ public void testMergeRectangleIntoList_addRectangleMadeObsoleteByMultipleExistingRectangles() {
+ testExpandRectangleList(
+ new RectF[] {
+ new RectF(0, 0, 1, 1),
+ new RectF(0.5f, 0, 1.5f, 1),
+ new RectF(0.25f, 0, 1.25f, 1)
+ },
+ new RectF[] {
+ new RectF(0, 0, 1.5f, 1)
+ }
+ );
+ }
+
+ @Test
+ public void testMergeRectangleIntoList_threeRectanglesThatTouchEachOther() {
+ testExpandRectangleList(
+ new RectF[] {
+ new RectF(0, 0, 1, 1),
+ new RectF(1, 0, 2, 1),
+ new RectF(2, 0, 3, 1)
+ },
+ new RectF[] {
+ new RectF(0, 0, 3, 1)
+ }
+ );
+ }
+
+
+ private void testExpandRectangleList(final RectF[] inputRectangles,
+ final RectF[] outputRectangles) {
+ final List<RectF> expectedOutput = Arrays.asList(outputRectangles);
+
+ final List<RectF> result = new ArrayList<>();
+ final int size = inputRectangles.length;
+ for (int index = 0; index < size; ++index) {
+ SelectionActionModeHelper.mergeRectangleIntoList(result, inputRectangles[index]);
+ }
+
+ assertEquals(expectedOutput, result);
+ }
+
+
}