Loading core/java/android/text/Layout.java +40 −14 Original line number Diff line number Diff line Loading @@ -1704,7 +1704,7 @@ public abstract class Layout { } private void addSelection(int line, int start, int end, int top, int bottom, RectangleConsumer consumer) { int top, int bottom, SelectionRectangleConsumer consumer) { int linestart = getLineStart(line); int lineend = getLineEnd(line); Directions dirs = getLineDirections(line); Loading Loading @@ -1732,7 +1732,14 @@ public abstract class Layout { float left = Math.min(h1, h2); float right = Math.max(h1, h2); consumer.accept(left, top, right, bottom); final boolean isRtl = (dirs.mDirections[i + 1] & RUN_RTL_FLAG) != 0; if (isRtl) { consumer.accept(left, top, right, bottom, TextSelectionLayout.RIGHT_TO_LEFT); } else { consumer.accept(left, top, right, bottom, TextSelectionLayout.LEFT_TO_RIGHT); } } } } Loading @@ -1746,22 +1753,22 @@ public abstract class Layout { */ public void getSelectionPath(int start, int end, Path dest) { dest.reset(); getSelection(start, end, (left, top, right, bottom) -> getSelection(start, end, (left, top, right, bottom, textSelectionLayout) -> dest.addRect(left, top, right, bottom, Path.Direction.CW)); } /** * Calculates the rectangles which should be highlighted to indicate a selection between start * and end and feeds them into the given {@link RectangleConsumer}. * and end and feeds them into the given {@link SelectionRectangleConsumer}. * * @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. * @param consumer the {@link SelectionRectangleConsumer} 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) { public final void getSelection(int start, int end, final SelectionRectangleConsumer consumer) { if (start == end) { return; } Loading @@ -1787,15 +1794,21 @@ public abstract class Layout { top, getLineBottom(startline), consumer); if (getParagraphDirection(startline) == DIR_RIGHT_TO_LEFT) { consumer.accept(getLineLeft(startline), top, 0, getLineBottom(startline)); consumer.accept(getLineLeft(startline), top, 0, getLineBottom(startline), TextSelectionLayout.RIGHT_TO_LEFT); } else { consumer.accept(getLineRight(startline), top, width, getLineBottom(startline)); consumer.accept(getLineRight(startline), top, width, getLineBottom(startline), TextSelectionLayout.LEFT_TO_RIGHT); } for (int i = startline + 1; i < endline; i++) { top = getLineTop(i); bottom = getLineBottom(i); consumer.accept(0, top, width, bottom); if (getParagraphDirection(i) == DIR_RIGHT_TO_LEFT) { consumer.accept(0, top, width, bottom, TextSelectionLayout.RIGHT_TO_LEFT); } else { consumer.accept(0, top, width, bottom, TextSelectionLayout.LEFT_TO_RIGHT); } } top = getLineTop(endline); Loading @@ -1804,9 +1817,11 @@ public abstract class Layout { addSelection(endline, getLineStart(endline), end, top, bottom, consumer); if (getParagraphDirection(endline) == DIR_RIGHT_TO_LEFT) { consumer.accept(width, top, getLineRight(endline), bottom); consumer.accept(width, top, getLineRight(endline), bottom, TextSelectionLayout.RIGHT_TO_LEFT); } else { consumer.accept(0, top, getLineLeft(endline), bottom); consumer.accept(0, top, getLineLeft(endline), bottom, TextSelectionLayout.LEFT_TO_RIGHT); } } } Loading Loading @@ -2309,9 +2324,17 @@ public abstract class Layout { public static final Directions DIRS_ALL_RIGHT_TO_LEFT = new Directions(new int[] { 0, RUN_LENGTH_MASK | RUN_RTL_FLAG }); /** @hide */ @Retention(RetentionPolicy.SOURCE) @IntDef({TextSelectionLayout.RIGHT_TO_LEFT, TextSelectionLayout.LEFT_TO_RIGHT}) public @interface TextSelectionLayout { int RIGHT_TO_LEFT = 0; int LEFT_TO_RIGHT = 1; } /** @hide */ @FunctionalInterface public interface RectangleConsumer { public interface SelectionRectangleConsumer { /** * Performs this operation on the given rectangle. * Loading @@ -2319,8 +2342,11 @@ public abstract class Layout { * @param top the top edge of the rectangle * @param right the right edge of the rectangle * @param bottom the bottom edge of the rectangle * @param textSelectionLayout the layout (RTL or LTR) of the text covered by this * selection rectangle */ void accept(float left, float top, float right, float bottom); void accept(float left, float top, float right, float bottom, @TextSelectionLayout int textSelectionLayout); } } core/java/android/widget/SelectionActionModeHelper.java +1 −1 Original line number Diff line number Diff line Loading @@ -265,7 +265,7 @@ final class SelectionActionModeHelper { // 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) -> layout.getSelection(start, end, (left, top, right, bottom, textSelectionLayout) -> result.add(new RectF(left, top, right, bottom))); result.sort(SmartSelectSprite.RECTANGLE_COMPARATOR); Loading core/tests/coretests/src/android/text/LayoutTest.java +48 −5 Original line number Diff line number Diff line Loading @@ -299,7 +299,8 @@ public class LayoutTest { */ layout.getSelection(5 /* startIndex */, 5 /* endIndex */, (left, top, right, bottom) -> fail(String.format(Locale.getDefault(), (left, top, right, bottom, textSelectionLayout) -> fail( String.format(Locale.getDefault(), "Did not expect any rectangles, got a rectangle with (left: %f," + " top: %f), (right: %f, bottom: %f)", left, top, right, bottom))); Loading @@ -313,7 +314,8 @@ public class LayoutTest { final List<RectF> rectangles = new ArrayList<>(); layout.getSelection(0 /* startIndex */, 1 /* endIndex */, (left, top, right, bottom) -> rectangles.add(new RectF(left, top, right, bottom))); (left, top, right, bottom, textSelectionLayout) -> rectangles.add( new RectF(left, top, right, bottom))); /* * The selection we expect will only cover the letter "a". Hence, we expect one rectangle Loading Loading @@ -343,7 +345,8 @@ public class LayoutTest { final List<RectF> rectangles = new ArrayList<>(); layout.getSelection(0 /* startIndex */, 2 /* endIndex */, (left, top, right, bottom) -> rectangles.add(new RectF(left, top, right, bottom))); (left, top, right, bottom, textSelectionLayout) -> rectangles.add( new RectF(left, top, right, bottom))); /* * The selection that will be selected is "a\n" - the selection starts at the beginning Loading Loading @@ -388,7 +391,8 @@ public class LayoutTest { final List<RectF> rectangles = new ArrayList<>(); layout.getSelection(0 /* startIndex */, 3 /* endIndex */, (left, top, right, bottom) -> rectangles.add(new RectF(left, top, right, bottom))); (left, top, right, bottom, textSelectionLayout) -> rectangles.add( new RectF(left, top, right, bottom))); /* * The selection that will be selected is "a\nb" - the selection starts at the beginning Loading Loading @@ -430,7 +434,8 @@ public class LayoutTest { final List<RectF> rectangles = new ArrayList<>(); layout.getSelection(0 /* startIndex */, 1 /* endIndex */, (left, top, right, bottom) -> rectangles.add(new RectF(left, top, right, bottom))); (left, top, right, bottom, textSelectionLayout) -> rectangles.add( new RectF(left, top, right, bottom))); /* * In the single line selection case, we expect that only one rectangle covering the letter Loading @@ -454,6 +459,44 @@ public class LayoutTest { assertEquals(rectangle, pathRectangle); } @Test public void testGetSelection_latinTextDirection() { final Layout layout = new StaticLayout("abc", mTextPaint, Integer.MAX_VALUE, Alignment.ALIGN_LEFT, mSpacingMult, mSpacingAdd, false); layout.getSelection(0 /* startIndex */, 2 /* endIndex */, (left, top, right, bottom, textSelectionLayout) -> assertEquals(Layout.TextSelectionLayout.LEFT_TO_RIGHT, textSelectionLayout)); } @Test public void testGetSelection_arabicTextDirection() { final Layout layout = new StaticLayout("غينيا", mTextPaint, Integer.MAX_VALUE, Alignment.ALIGN_LEFT, mSpacingMult, mSpacingAdd, false); layout.getSelection(0 /* startIndex */, 2 /* endIndex */, (left, top, right, bottom, textSelectionLayout) -> assertEquals(Layout.TextSelectionLayout.RIGHT_TO_LEFT, textSelectionLayout)); } @Test public void testGetSelection_mixedLatinAndArabicTextDirection() { final Layout layout = new StaticLayout("abcغينيا", mTextPaint, Integer.MAX_VALUE, Alignment.ALIGN_LEFT, mSpacingMult, mSpacingAdd, false); final List<Integer> layouts = new ArrayList<>(2); layout.getSelection(0 /* startIndex */, 6 /* endIndex */, (left, top, right, bottom, textSelectionLayout) -> layouts.add( textSelectionLayout)); assertEquals(2, layouts.size()); assertEquals(Layout.TextSelectionLayout.LEFT_TO_RIGHT, (long) layouts.get(0)); assertEquals(Layout.TextSelectionLayout.RIGHT_TO_LEFT, (long) layouts.get(1)); } @Test public void testIsSpanned() { MockLayout layout = new MockLayout(LAYOUT_TEXT, mTextPaint, mWidth, Loading Loading
core/java/android/text/Layout.java +40 −14 Original line number Diff line number Diff line Loading @@ -1704,7 +1704,7 @@ public abstract class Layout { } private void addSelection(int line, int start, int end, int top, int bottom, RectangleConsumer consumer) { int top, int bottom, SelectionRectangleConsumer consumer) { int linestart = getLineStart(line); int lineend = getLineEnd(line); Directions dirs = getLineDirections(line); Loading Loading @@ -1732,7 +1732,14 @@ public abstract class Layout { float left = Math.min(h1, h2); float right = Math.max(h1, h2); consumer.accept(left, top, right, bottom); final boolean isRtl = (dirs.mDirections[i + 1] & RUN_RTL_FLAG) != 0; if (isRtl) { consumer.accept(left, top, right, bottom, TextSelectionLayout.RIGHT_TO_LEFT); } else { consumer.accept(left, top, right, bottom, TextSelectionLayout.LEFT_TO_RIGHT); } } } } Loading @@ -1746,22 +1753,22 @@ public abstract class Layout { */ public void getSelectionPath(int start, int end, Path dest) { dest.reset(); getSelection(start, end, (left, top, right, bottom) -> getSelection(start, end, (left, top, right, bottom, textSelectionLayout) -> dest.addRect(left, top, right, bottom, Path.Direction.CW)); } /** * Calculates the rectangles which should be highlighted to indicate a selection between start * and end and feeds them into the given {@link RectangleConsumer}. * and end and feeds them into the given {@link SelectionRectangleConsumer}. * * @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. * @param consumer the {@link SelectionRectangleConsumer} 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) { public final void getSelection(int start, int end, final SelectionRectangleConsumer consumer) { if (start == end) { return; } Loading @@ -1787,15 +1794,21 @@ public abstract class Layout { top, getLineBottom(startline), consumer); if (getParagraphDirection(startline) == DIR_RIGHT_TO_LEFT) { consumer.accept(getLineLeft(startline), top, 0, getLineBottom(startline)); consumer.accept(getLineLeft(startline), top, 0, getLineBottom(startline), TextSelectionLayout.RIGHT_TO_LEFT); } else { consumer.accept(getLineRight(startline), top, width, getLineBottom(startline)); consumer.accept(getLineRight(startline), top, width, getLineBottom(startline), TextSelectionLayout.LEFT_TO_RIGHT); } for (int i = startline + 1; i < endline; i++) { top = getLineTop(i); bottom = getLineBottom(i); consumer.accept(0, top, width, bottom); if (getParagraphDirection(i) == DIR_RIGHT_TO_LEFT) { consumer.accept(0, top, width, bottom, TextSelectionLayout.RIGHT_TO_LEFT); } else { consumer.accept(0, top, width, bottom, TextSelectionLayout.LEFT_TO_RIGHT); } } top = getLineTop(endline); Loading @@ -1804,9 +1817,11 @@ public abstract class Layout { addSelection(endline, getLineStart(endline), end, top, bottom, consumer); if (getParagraphDirection(endline) == DIR_RIGHT_TO_LEFT) { consumer.accept(width, top, getLineRight(endline), bottom); consumer.accept(width, top, getLineRight(endline), bottom, TextSelectionLayout.RIGHT_TO_LEFT); } else { consumer.accept(0, top, getLineLeft(endline), bottom); consumer.accept(0, top, getLineLeft(endline), bottom, TextSelectionLayout.LEFT_TO_RIGHT); } } } Loading Loading @@ -2309,9 +2324,17 @@ public abstract class Layout { public static final Directions DIRS_ALL_RIGHT_TO_LEFT = new Directions(new int[] { 0, RUN_LENGTH_MASK | RUN_RTL_FLAG }); /** @hide */ @Retention(RetentionPolicy.SOURCE) @IntDef({TextSelectionLayout.RIGHT_TO_LEFT, TextSelectionLayout.LEFT_TO_RIGHT}) public @interface TextSelectionLayout { int RIGHT_TO_LEFT = 0; int LEFT_TO_RIGHT = 1; } /** @hide */ @FunctionalInterface public interface RectangleConsumer { public interface SelectionRectangleConsumer { /** * Performs this operation on the given rectangle. * Loading @@ -2319,8 +2342,11 @@ public abstract class Layout { * @param top the top edge of the rectangle * @param right the right edge of the rectangle * @param bottom the bottom edge of the rectangle * @param textSelectionLayout the layout (RTL or LTR) of the text covered by this * selection rectangle */ void accept(float left, float top, float right, float bottom); void accept(float left, float top, float right, float bottom, @TextSelectionLayout int textSelectionLayout); } }
core/java/android/widget/SelectionActionModeHelper.java +1 −1 Original line number Diff line number Diff line Loading @@ -265,7 +265,7 @@ final class SelectionActionModeHelper { // 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) -> layout.getSelection(start, end, (left, top, right, bottom, textSelectionLayout) -> result.add(new RectF(left, top, right, bottom))); result.sort(SmartSelectSprite.RECTANGLE_COMPARATOR); Loading
core/tests/coretests/src/android/text/LayoutTest.java +48 −5 Original line number Diff line number Diff line Loading @@ -299,7 +299,8 @@ public class LayoutTest { */ layout.getSelection(5 /* startIndex */, 5 /* endIndex */, (left, top, right, bottom) -> fail(String.format(Locale.getDefault(), (left, top, right, bottom, textSelectionLayout) -> fail( String.format(Locale.getDefault(), "Did not expect any rectangles, got a rectangle with (left: %f," + " top: %f), (right: %f, bottom: %f)", left, top, right, bottom))); Loading @@ -313,7 +314,8 @@ public class LayoutTest { final List<RectF> rectangles = new ArrayList<>(); layout.getSelection(0 /* startIndex */, 1 /* endIndex */, (left, top, right, bottom) -> rectangles.add(new RectF(left, top, right, bottom))); (left, top, right, bottom, textSelectionLayout) -> rectangles.add( new RectF(left, top, right, bottom))); /* * The selection we expect will only cover the letter "a". Hence, we expect one rectangle Loading Loading @@ -343,7 +345,8 @@ public class LayoutTest { final List<RectF> rectangles = new ArrayList<>(); layout.getSelection(0 /* startIndex */, 2 /* endIndex */, (left, top, right, bottom) -> rectangles.add(new RectF(left, top, right, bottom))); (left, top, right, bottom, textSelectionLayout) -> rectangles.add( new RectF(left, top, right, bottom))); /* * The selection that will be selected is "a\n" - the selection starts at the beginning Loading Loading @@ -388,7 +391,8 @@ public class LayoutTest { final List<RectF> rectangles = new ArrayList<>(); layout.getSelection(0 /* startIndex */, 3 /* endIndex */, (left, top, right, bottom) -> rectangles.add(new RectF(left, top, right, bottom))); (left, top, right, bottom, textSelectionLayout) -> rectangles.add( new RectF(left, top, right, bottom))); /* * The selection that will be selected is "a\nb" - the selection starts at the beginning Loading Loading @@ -430,7 +434,8 @@ public class LayoutTest { final List<RectF> rectangles = new ArrayList<>(); layout.getSelection(0 /* startIndex */, 1 /* endIndex */, (left, top, right, bottom) -> rectangles.add(new RectF(left, top, right, bottom))); (left, top, right, bottom, textSelectionLayout) -> rectangles.add( new RectF(left, top, right, bottom))); /* * In the single line selection case, we expect that only one rectangle covering the letter Loading @@ -454,6 +459,44 @@ public class LayoutTest { assertEquals(rectangle, pathRectangle); } @Test public void testGetSelection_latinTextDirection() { final Layout layout = new StaticLayout("abc", mTextPaint, Integer.MAX_VALUE, Alignment.ALIGN_LEFT, mSpacingMult, mSpacingAdd, false); layout.getSelection(0 /* startIndex */, 2 /* endIndex */, (left, top, right, bottom, textSelectionLayout) -> assertEquals(Layout.TextSelectionLayout.LEFT_TO_RIGHT, textSelectionLayout)); } @Test public void testGetSelection_arabicTextDirection() { final Layout layout = new StaticLayout("غينيا", mTextPaint, Integer.MAX_VALUE, Alignment.ALIGN_LEFT, mSpacingMult, mSpacingAdd, false); layout.getSelection(0 /* startIndex */, 2 /* endIndex */, (left, top, right, bottom, textSelectionLayout) -> assertEquals(Layout.TextSelectionLayout.RIGHT_TO_LEFT, textSelectionLayout)); } @Test public void testGetSelection_mixedLatinAndArabicTextDirection() { final Layout layout = new StaticLayout("abcغينيا", mTextPaint, Integer.MAX_VALUE, Alignment.ALIGN_LEFT, mSpacingMult, mSpacingAdd, false); final List<Integer> layouts = new ArrayList<>(2); layout.getSelection(0 /* startIndex */, 6 /* endIndex */, (left, top, right, bottom, textSelectionLayout) -> layouts.add( textSelectionLayout)); assertEquals(2, layouts.size()); assertEquals(Layout.TextSelectionLayout.LEFT_TO_RIGHT, (long) layouts.get(0)); assertEquals(Layout.TextSelectionLayout.RIGHT_TO_LEFT, (long) layouts.get(1)); } @Test public void testIsSpanned() { MockLayout layout = new MockLayout(LAYOUT_TEXT, mTextPaint, mWidth, Loading