xref: /aosp_15_r20/cts/tests/tests/text/src/android/text/cts/DynamicLayoutTest.java (revision b7c941bb3fa97aba169d73cee0bed2de8ac964bf)
1 /*
2  * Copyright (C) 2008 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package android.text.cts;
18 
19 import static android.text.Layout.Alignment.ALIGN_NORMAL;
20 import static android.text.Spanned.SPAN_EXCLUSIVE_EXCLUSIVE;
21 
22 import static com.google.common.truth.Truth.assertThat;
23 import static com.google.common.truth.Truth.assertWithMessage;
24 
25 import static org.junit.Assert.assertEquals;
26 import static org.junit.Assert.assertFalse;
27 import static org.junit.Assert.assertNotNull;
28 import static org.junit.Assert.assertTrue;
29 import static org.junit.Assert.fail;
30 import static org.mockito.Mockito.mock;
31 
32 import android.graphics.Paint.FontMetricsInt;
33 import android.graphics.text.LineBreakConfig;
34 import android.text.DynamicLayout;
35 import android.text.Layout;
36 import android.text.SpannableStringBuilder;
37 import android.text.StaticLayout;
38 import android.text.TextPaint;
39 import android.text.TextUtils;
40 import android.text.style.TypefaceSpan;
41 
42 import androidx.test.ext.junit.runners.AndroidJUnit4;
43 import androidx.test.filters.SmallTest;
44 
45 import org.junit.Before;
46 import org.junit.Test;
47 import org.junit.runner.RunWith;
48 
49 @SmallTest
50 @RunWith(AndroidJUnit4.class)
51 public class DynamicLayoutTest {
52 
53     private static final float SPACING_MULT_NO_SCALE = 1.0f;
54     private static final float SPACING_ADD_NO_SCALE = 0.0f;
55     private static final int DEFAULT_OUTER_WIDTH = 150;
56     private static final int ELLIPSIZE_WIDTH = 8;
57     private static final CharSequence SINGLELINE_CHAR_SEQUENCE = "......";
58     private static final String[] TEXT = {"CharSequence\n", "Char\tSequence\n", "CharSequence"};
59     private static final CharSequence MULTLINE_CHAR_SEQUENCE = TEXT[0] + TEXT[1] + TEXT[2];
60     private static final Layout.Alignment DEFAULT_ALIGN = Layout.Alignment.ALIGN_CENTER;
61     private TextPaint mDefaultPaint;
62 
63     private static final int LINE0 = 0;
64     private static final int LINE0_TOP = 0;
65     private static final int LINE1 = 1;
66     private static final int LINE2 = 2;
67     private static final int LINE3 = 3;
68 
69     private static final int ELLIPSIS_UNDEFINED = 0x80000000;
70 
71     private DynamicLayout mDynamicLayout;
72 
73     @Before
setup()74     public void setup() {
75         mDefaultPaint = new TextPaint();
76         mDynamicLayout = createBuilderWithDefaults(MULTLINE_CHAR_SEQUENCE).build();
77     }
78 
79     @Test
testConstructors()80     public void testConstructors() {
81         new DynamicLayout(SINGLELINE_CHAR_SEQUENCE,
82                 MULTLINE_CHAR_SEQUENCE,
83                 mDefaultPaint,
84                 DEFAULT_OUTER_WIDTH,
85                 DEFAULT_ALIGN,
86                 SPACING_MULT_NO_SCALE,
87                 SPACING_ADD_NO_SCALE,
88                 true);
89         new DynamicLayout(SINGLELINE_CHAR_SEQUENCE,
90                 MULTLINE_CHAR_SEQUENCE,
91                 mDefaultPaint,
92                 DEFAULT_OUTER_WIDTH,
93                 DEFAULT_ALIGN,
94                 SPACING_MULT_NO_SCALE,
95                 SPACING_ADD_NO_SCALE,
96                 true,
97                 TextUtils.TruncateAt.START,
98                 DEFAULT_OUTER_WIDTH);
99         new DynamicLayout(MULTLINE_CHAR_SEQUENCE,
100                 mDefaultPaint,
101                 DEFAULT_OUTER_WIDTH,
102                 DEFAULT_ALIGN,
103                 SPACING_MULT_NO_SCALE,
104                 SPACING_ADD_NO_SCALE,
105                 true);
106     }
107 
108     /*
109      * Test the ellipsis result when no ellipsis is needed, for a singleline text.
110      */
111     @Test
testEllipsis_singlelineNotEllipsized()112     public void testEllipsis_singlelineNotEllipsized() {
113         final DynamicLayout dynamicLayout = new DynamicLayout(SINGLELINE_CHAR_SEQUENCE,
114                 SINGLELINE_CHAR_SEQUENCE,
115                 mDefaultPaint,
116                 DEFAULT_OUTER_WIDTH,
117                 DEFAULT_ALIGN,
118                 SPACING_MULT_NO_SCALE,
119                 SPACING_ADD_NO_SCALE,
120                 true,
121                 TextUtils.TruncateAt.START,
122                 DEFAULT_OUTER_WIDTH);
123         assertThat(dynamicLayout.getEllipsisCount(LINE0)).isEqualTo(0);
124         assertThat(dynamicLayout.getEllipsisStart(LINE0)).isEqualTo(0);
125         assertThat(dynamicLayout.getEllipsisCount(LINE1)).isEqualTo(0);
126         assertThat(dynamicLayout.getEllipsisStart(LINE1)).isEqualTo(ELLIPSIS_UNDEFINED);
127         assertThat(dynamicLayout.getEllipsizedWidth()).isEqualTo(DEFAULT_OUTER_WIDTH);
128     }
129 
130     /*
131      * Test the ellipsis result when no ellipsis is needed, for a multiline text.
132      */
133     @Test
testEllipsis_multilineNotEllipsized()134     public void testEllipsis_multilineNotEllipsized() {
135         final DynamicLayout dynamicLayout = new DynamicLayout(MULTLINE_CHAR_SEQUENCE,
136                 MULTLINE_CHAR_SEQUENCE,
137                 mDefaultPaint,
138                 DEFAULT_OUTER_WIDTH,
139                 DEFAULT_ALIGN,
140                 SPACING_MULT_NO_SCALE,
141                 SPACING_ADD_NO_SCALE,
142                 true,
143                 TextUtils.TruncateAt.START,
144                 DEFAULT_OUTER_WIDTH);
145         assertThat(dynamicLayout.getLineCount()).isEqualTo(3);
146         for (int i = 0; i < LINE3; i++) {
147             assertWithMessage("Ellipsis count for line " + i)
148                     .that(dynamicLayout.getEllipsisCount(i)).isEqualTo(0);
149             assertWithMessage("Ellipsis start for line " + i)
150                     .that(dynamicLayout.getEllipsisStart(i)).isEqualTo(0);
151         }
152         assertThat(dynamicLayout.getEllipsisCount(LINE3)).isEqualTo(0);
153         assertThat(dynamicLayout.getEllipsisStart(LINE3)).isEqualTo(ELLIPSIS_UNDEFINED);
154         assertThat(dynamicLayout.getEllipsizedWidth()).isEqualTo(DEFAULT_OUTER_WIDTH);
155     }
156 
157     /*
158      * Test the ellipsis result when no ellipsis is needed, when the display text is different from
159      * the base.
160      */
161     @Test
testEllipsis_transformedNotEllipsized()162     public void testEllipsis_transformedNotEllipsized() {
163         final DynamicLayout dynamicLayout = new DynamicLayout(SINGLELINE_CHAR_SEQUENCE,
164                 MULTLINE_CHAR_SEQUENCE,
165                 mDefaultPaint,
166                 DEFAULT_OUTER_WIDTH,
167                 DEFAULT_ALIGN,
168                 SPACING_MULT_NO_SCALE,
169                 SPACING_ADD_NO_SCALE,
170                 true,
171                 TextUtils.TruncateAt.START,
172                 DEFAULT_OUTER_WIDTH);
173         assertThat(dynamicLayout.getLineCount()).isEqualTo(3);
174         for (int i = 0; i < LINE3; i++) {
175             assertWithMessage("Ellipsis count for line " + i)
176                     .that(dynamicLayout.getEllipsisCount(i)).isEqualTo(0);
177             assertWithMessage("Ellipsis start for line " + i)
178                     .that(dynamicLayout.getEllipsisStart(i)).isEqualTo(0);
179         }
180         assertThat(dynamicLayout.getEllipsisCount(LINE3)).isEqualTo(0);
181         assertThat(dynamicLayout.getEllipsisStart(LINE3)).isEqualTo(ELLIPSIS_UNDEFINED);
182         assertThat(dynamicLayout.getEllipsizedWidth()).isEqualTo(DEFAULT_OUTER_WIDTH);
183     }
184 
185     /*
186      * Test whether include the padding to calculate the layout.
187      * 1. Include padding while calculate the layout.
188      * 2. Don't include padding while calculate the layout.
189      */
190     @Test
testIncludePadding()191     public void testIncludePadding() {
192         final FontMetricsInt fontMetricsInt = mDefaultPaint.getFontMetricsInt();
193 
194         DynamicLayout dynamicLayout = new DynamicLayout(SINGLELINE_CHAR_SEQUENCE,
195                 mDefaultPaint,
196                 DEFAULT_OUTER_WIDTH,
197                 DEFAULT_ALIGN,
198                 SPACING_MULT_NO_SCALE,
199                 SPACING_ADD_NO_SCALE,
200                 true);
201         assertEquals(fontMetricsInt.top - fontMetricsInt.ascent, dynamicLayout.getTopPadding());
202         assertEquals(fontMetricsInt.bottom - fontMetricsInt.descent,
203                 dynamicLayout.getBottomPadding());
204 
205         dynamicLayout = new DynamicLayout(SINGLELINE_CHAR_SEQUENCE,
206                 mDefaultPaint,
207                 DEFAULT_OUTER_WIDTH,
208                 DEFAULT_ALIGN,
209                 SPACING_MULT_NO_SCALE,
210                 SPACING_ADD_NO_SCALE,
211                 false);
212         assertEquals(0, dynamicLayout.getTopPadding());
213         assertEquals(0, dynamicLayout.getBottomPadding());
214     }
215 
216     /*
217      * Test the line count and whether include the Tab the layout.
218      * 1. Include Tab. 2. Don't include Tab Use the Y-coordinate to calculate the line number
219      * Test the line top
220      * 1. the Y-coordinate of line top.2. the Y-coordinate of baseline.
221      */
222     @Test
testLineLayout()223     public void testLineLayout() {
224         assertEquals(TEXT.length, mDynamicLayout.getLineCount());
225         assertFalse(mDynamicLayout.getLineContainsTab(LINE0));
226         assertTrue(mDynamicLayout.getLineContainsTab(LINE1));
227 
228         assertEquals(LINE0_TOP, mDynamicLayout.getLineTop(LINE0));
229 
230         assertEquals(mDynamicLayout.getLineBottom(LINE0), mDynamicLayout.getLineTop(LINE1));
231         assertEquals(mDynamicLayout.getLineBottom(LINE1), mDynamicLayout.getLineTop(LINE2));
232         assertEquals(mDynamicLayout.getLineBottom(LINE2), mDynamicLayout.getLineTop(LINE3));
233 
234         try {
235             assertEquals(mDynamicLayout.getLineBottom(mDynamicLayout.getLineCount()),
236                     mDynamicLayout.getLineTop(mDynamicLayout.getLineCount() + 1));
237             fail("Test DynamicLayout fail, should throw IndexOutOfBoundsException.");
238         } catch (IndexOutOfBoundsException e) {
239             // expected
240         }
241 
242         assertEquals(mDynamicLayout.getLineDescent(LINE0) - mDynamicLayout.getLineAscent(LINE0),
243                 mDynamicLayout.getLineBottom(LINE0));
244 
245         assertEquals(mDynamicLayout.getLineDescent(LINE1) - mDynamicLayout.getLineAscent(LINE1),
246                 mDynamicLayout.getLineBottom(LINE1) - mDynamicLayout.getLineBottom(LINE0));
247 
248         assertEquals(mDynamicLayout.getLineDescent(LINE2) - mDynamicLayout.getLineAscent(LINE2),
249                 mDynamicLayout.getLineBottom(LINE2) - mDynamicLayout.getLineBottom(LINE1));
250 
251         assertEquals(LINE0, mDynamicLayout.getLineForVertical(mDynamicLayout.getLineTop(LINE0)));
252 
253         assertNotNull(mDynamicLayout.getLineDirections(LINE0));
254         assertEquals(Layout.DIR_LEFT_TO_RIGHT, mDynamicLayout.getParagraphDirection(LINE0));
255 
256         assertEquals(0, mDynamicLayout.getLineStart(LINE0));
257         assertEquals(TEXT[0].length(), mDynamicLayout.getLineStart(LINE1));
258         assertEquals(TEXT[0].length() + TEXT[1].length(), mDynamicLayout.getLineStart(LINE2));
259     }
260 
261     @Test
testLineSpacing()262     public void testLineSpacing() {
263         SpannableStringBuilder text = new SpannableStringBuilder("a\nb\nc");
264         final float spacingMultiplier = 2f;
265         final float spacingAdd = 4;
266         final int width = 1000;
267         final TextPaint textPaint = new TextPaint();
268         // create the DynamicLayout
269         final DynamicLayout dynamicLayout = new DynamicLayout(text,
270                 textPaint,
271                 width,
272                 ALIGN_NORMAL,
273                 spacingMultiplier,
274                 spacingAdd,
275                 false /*includepad*/);
276 
277         // create a StaticLayout with same text, this will define the expectations
278         Layout expected = createStaticLayout(text.toString(), textPaint, width, spacingAdd,
279                 spacingMultiplier);
280         assertLineSpecs(expected, dynamicLayout);
281 
282         // add a new line to the end, DynamicLayout will re-calculate
283         text = text.append("\nd");
284         expected = createStaticLayout(text.toString(), textPaint, width, spacingAdd,
285                 spacingMultiplier);
286         assertLineSpecs(expected, dynamicLayout);
287 
288         // insert a next line and a char as the new second line
289         text = text.insert(TextUtils.indexOf(text, '\n') + 1, "a1\n");
290         expected = createStaticLayout(text.toString(), textPaint, width, spacingAdd,
291                 spacingMultiplier);
292         assertLineSpecs(expected, dynamicLayout);
293     }
294 
295     @Test
testLineSpacing_textEndingWithNextLine()296     public void testLineSpacing_textEndingWithNextLine() {
297         final SpannableStringBuilder text = new SpannableStringBuilder("a\n");
298         final float spacingMultiplier = 2f;
299         final float spacingAdd = 4f;
300         final int width = 1000;
301         final TextPaint textPaint = new TextPaint();
302         // create the DynamicLayout
303         final DynamicLayout dynamicLayout = new DynamicLayout(text,
304                 textPaint,
305                 width,
306                 ALIGN_NORMAL,
307                 spacingMultiplier,
308                 spacingAdd,
309                 false /*includepad*/);
310 
311         // create a StaticLayout with same text, this will define the expectations
312         final Layout expected = createStaticLayout(text.toString(), textPaint, width, spacingAdd,
313                 spacingMultiplier);
314         assertLineSpecs(expected, dynamicLayout);
315     }
316 
createStaticLayout(CharSequence text, TextPaint textPaint, int width, float spacingAdd, float spacingMultiplier)317     private Layout createStaticLayout(CharSequence text, TextPaint textPaint, int width,
318             float spacingAdd, float spacingMultiplier) {
319         return StaticLayout.Builder.obtain(text, 0,
320                 text.length(), textPaint, width)
321                 .setAlignment(ALIGN_NORMAL)
322                 .setIncludePad(false)
323                 .setLineSpacing(spacingAdd, spacingMultiplier)
324                 .build();
325     }
326 
assertLineSpecs(Layout expected, DynamicLayout actual)327     private void assertLineSpecs(Layout expected, DynamicLayout actual) {
328         final int lineCount = expected.getLineCount();
329         assertTrue(lineCount > 1);
330         assertEquals(lineCount, actual.getLineCount());
331 
332         for (int i = 0; i < lineCount; i++) {
333             assertEquals(expected.getLineTop(i), actual.getLineTop(i));
334             assertEquals(expected.getLineDescent(i), actual.getLineDescent(i));
335             assertEquals(expected.getLineBaseline(i), actual.getLineBaseline(i));
336             assertEquals(expected.getLineBottom(i), actual.getLineBottom(i));
337         }
338     }
339 
340     @Test
testLineSpacing_notAffectedByPreviousEllipsization()341     public void testLineSpacing_notAffectedByPreviousEllipsization() {
342         // Create an ellipsized DynamicLayout, but throw it away.
343         final String ellipsizedText = "Some arbitrary relatively long text";
344         final DynamicLayout ellipsizedLayout = new DynamicLayout(
345                 ellipsizedText,
346                 ellipsizedText,
347                 mDefaultPaint,
348                 1 << 20 /* width */,
349                 DEFAULT_ALIGN,
350                 SPACING_MULT_NO_SCALE,
351                 SPACING_ADD_NO_SCALE,
352                 true /* include pad */,
353                 TextUtils.TruncateAt.END,
354                 2 * (int) mDefaultPaint.getTextSize() /* ellipsizedWidth */);
355 
356         // Now try to measure linespacing in a non-ellipsized DynamicLayout.
357         final String text = "a\nb\nc";
358         final float spacingMultiplier = 2f;
359         final float spacingAdd = 4f;
360         final int width = 1000;
361         final TextPaint textPaint = new TextPaint();
362         // create the DynamicLayout
363         final DynamicLayout dynamicLayout = new DynamicLayout(text,
364                 textPaint,
365                 width,
366                 ALIGN_NORMAL,
367                 spacingMultiplier,
368                 spacingAdd,
369                 false /*includepad*/);
370 
371         // create a StaticLayout with same text, this will define the expectations
372         Layout expected = createStaticLayout(text.toString(), textPaint, width, spacingAdd,
373                 spacingMultiplier);
374         assertLineSpecs(expected, dynamicLayout);
375     }
376 
377     /*
378      * Tests that the ellipsis result, for the case of TruncateAt.START and no ellipsization needed,
379      * isn't affected by a previous ellipsization. This tests the fix for a bug where the static
380      * StaticLayout instance reused internally was not properly reinitialized for this specific
381      * case.
382      */
383     @Test
testEllipsis_notAffectedByPreviousEllipsization()384     public void testEllipsis_notAffectedByPreviousEllipsization() {
385         // Create an ellipsized DynamicLayout, but throw it away.
386         final String ellipsizedText = "Some arbitrary relatively long text";
387         final DynamicLayout ellipsizedLayout =
388                 DynamicLayout.Builder.obtain(ellipsizedText, mDefaultPaint, 1 << 20 /* width */)
389                         .setEllipsize(TextUtils.TruncateAt.END)
390                         .setEllipsizedWidth(2 * (int) mDefaultPaint.getTextSize())
391                         .build();
392         // Make sure it was actually ellipsized.
393         assertThat(ellipsizedLayout.getEllipsisCount(LINE0)).isGreaterThan(0);
394 
395         // Create a DynamicLayout that would trigger the bug.
396         final String text = "a\nb";
397         final DynamicLayout dynamicLayout =
398                 createBuilderWithDefaults(text).setEllipsize(TextUtils.TruncateAt.START).build();
399 
400         assertThat(dynamicLayout.getEllipsisCount(LINE0)).isEqualTo(0);
401     }
402 
403     @Test
testBuilder_obtain()404     public void testBuilder_obtain() {
405         final DynamicLayout.Builder builder = DynamicLayout.Builder.obtain(MULTLINE_CHAR_SEQUENCE,
406                 mDefaultPaint, DEFAULT_OUTER_WIDTH);
407         final DynamicLayout layout = builder.build();
408         // Check values passed to obtain().
409         assertEquals(MULTLINE_CHAR_SEQUENCE, layout.getText());
410         assertEquals(mDefaultPaint, layout.getPaint());
411         assertEquals(DEFAULT_OUTER_WIDTH, layout.getWidth());
412         // Check default values.
413         assertEquals(Layout.Alignment.ALIGN_NORMAL, layout.getAlignment());
414         assertEquals(0.0f, layout.getSpacingAdd(), 0.0f);
415         assertEquals(1.0f, layout.getSpacingMultiplier(), 0.0f);
416         assertEquals(DEFAULT_OUTER_WIDTH, layout.getEllipsizedWidth());
417     }
418 
419     @Test(expected = NullPointerException.class)
testBuilder_obtainWithNullText()420     public void testBuilder_obtainWithNullText() {
421         final DynamicLayout.Builder builder = DynamicLayout.Builder.obtain(null, mDefaultPaint, 0);
422         final DynamicLayout layout = builder.build();
423     }
424 
425     @Test(expected = NullPointerException.class)
testBuilder_obtainWithNullPaint()426     public void testBuilder_obtainWithNullPaint() {
427         final DynamicLayout.Builder builder = DynamicLayout.Builder.obtain(MULTLINE_CHAR_SEQUENCE,
428                 null, 0);
429         final DynamicLayout layout = builder.build();
430     }
431 
432     @Test
testBuilder_setDisplayTest()433     public void testBuilder_setDisplayTest() {
434         final DynamicLayout.Builder builder = DynamicLayout.Builder.obtain(MULTLINE_CHAR_SEQUENCE,
435                 mDefaultPaint, DEFAULT_OUTER_WIDTH);
436         builder.setDisplayText(SINGLELINE_CHAR_SEQUENCE);
437         final DynamicLayout layout = builder.build();
438         assertEquals(SINGLELINE_CHAR_SEQUENCE, layout.getText());
439     }
440 
441     @Test
testBuilder_setAlignment()442     public void testBuilder_setAlignment() {
443         final DynamicLayout.Builder builder = DynamicLayout.Builder.obtain(MULTLINE_CHAR_SEQUENCE,
444                 mDefaultPaint, DEFAULT_OUTER_WIDTH);
445         builder.setAlignment(DEFAULT_ALIGN);
446         final DynamicLayout layout = builder.build();
447         assertEquals(DEFAULT_ALIGN, layout.getAlignment());
448     }
449 
450     @Test
testBuilder_setLineSpacing()451     public void testBuilder_setLineSpacing() {
452         final DynamicLayout.Builder builder = DynamicLayout.Builder.obtain(MULTLINE_CHAR_SEQUENCE,
453                 mDefaultPaint, DEFAULT_OUTER_WIDTH);
454         builder.setLineSpacing(1.0f, 2.0f);
455         final DynamicLayout layout = builder.build();
456         assertEquals(1.0f, layout.getSpacingAdd(), 0.0f);
457         assertEquals(2.0f, layout.getSpacingMultiplier(), 0.0f);
458     }
459 
460     @Test
testBuilder_setLineBreakConfig()461     public void testBuilder_setLineBreakConfig() {
462         final DynamicLayout.Builder builder = DynamicLayout.Builder.obtain(MULTLINE_CHAR_SEQUENCE,
463                 mDefaultPaint, DEFAULT_OUTER_WIDTH);
464         LineBreakConfig.Builder lbcBuilder = new LineBreakConfig.Builder();
465         LineBreakConfig lbc = lbcBuilder.setLineBreakStyle(LineBreakConfig.LINE_BREAK_STYLE_STRICT)
466                 .setLineBreakWordStyle(LineBreakConfig.LINE_BREAK_WORD_STYLE_PHRASE).build();
467         builder.setLineBreakConfig(lbc);
468         final DynamicLayout layout = builder.build();
469         assertEquals(lbc, layout.getLineBreakConfig());
470     }
471 
472     @Test
testBuilder_ellipsization()473     public void testBuilder_ellipsization() {
474         final DynamicLayout.Builder builder = DynamicLayout.Builder.obtain(MULTLINE_CHAR_SEQUENCE,
475                 mDefaultPaint, DEFAULT_OUTER_WIDTH);
476         builder.setEllipsize(TextUtils.TruncateAt.END)
477                 .setEllipsizedWidth(ELLIPSIZE_WIDTH);
478         final DynamicLayout layout = builder.build();
479         assertEquals(ELLIPSIZE_WIDTH, layout.getEllipsizedWidth());
480         assertEquals(DEFAULT_OUTER_WIDTH, layout.getWidth());
481         for (int i = 0; i < TEXT.length; i++) {
482             if (i == TEXT.length - 1) { // last line
483                 assertTrue(layout.getEllipsisCount(i) > 0);
484             } else {
485                 assertEquals(0, layout.getEllipsisCount(i));
486             }
487         }
488     }
489 
490     @Test
testBuilder_otherSetters()491     public void testBuilder_otherSetters() {
492         // Setter methods that cannot be directly tested.
493         // setBreakStrategy, setHyphenationFrequency, setIncludePad, and setJustificationMode.
494         final DynamicLayout.Builder builder = DynamicLayout.Builder.obtain(MULTLINE_CHAR_SEQUENCE,
495                 mDefaultPaint, DEFAULT_OUTER_WIDTH);
496         builder.setBreakStrategy(Layout.BREAK_STRATEGY_HIGH_QUALITY)
497                 .setHyphenationFrequency(Layout.HYPHENATION_FREQUENCY_FULL)
498                 .setIncludePad(true)
499                 .setJustificationMode(Layout.JUSTIFICATION_MODE_INTER_WORD);
500         final DynamicLayout layout = builder.build();
501         assertNotNull(layout);
502     }
503 
504     /*
505      * Tests that DynamicLayout accounts for TransformationMethods that can change text length, such
506      * as AllCapsTransformationMethod ("ß" becomes "SS") and TranslationTransformationMethod
507      * (arbitrary length changes).
508      */
509     @Test
testDisplayTextUsedInsteadOfBase()510     public void testDisplayTextUsedInsteadOfBase() {
511         DynamicLayout layout =
512                 createBuilderWithDefaults(SINGLELINE_CHAR_SEQUENCE)
513                         .setDisplayText(MULTLINE_CHAR_SEQUENCE)
514                         .setEllipsize(TextUtils.TruncateAt.END)
515                         .setEllipsizedWidth(ELLIPSIZE_WIDTH)
516                         .build();
517 
518         assertThat(layout.getLineCount()).isEqualTo(TEXT.length);
519 
520         assertThat(layout.getLineStart(LINE0)).isEqualTo(0);
521         assertThat(layout.getLineStart(LINE1)).isEqualTo(TEXT[0].length());
522         assertThat(layout.getLineStart(LINE2)).isEqualTo(TEXT[0].length() + TEXT[1].length());
523 
524         assertThat(layout.getEllipsisCount(LINE0)).isEqualTo(0);
525         assertThat(layout.getEllipsisCount(LINE1)).isEqualTo(0);
526         assertThat(layout.getEllipsisCount(LINE2)).isGreaterThan(0);
527     }
528 
529     @Test
testReflow_afterSpanChangedShouldNotThrowException()530     public void testReflow_afterSpanChangedShouldNotThrowException() {
531         final SpannableStringBuilder builder = new SpannableStringBuilder("crash crash crash!!");
532 
533         final TypefaceSpan span = mock(TypefaceSpan.class);
534         builder.setSpan(span, 1, 4, SPAN_EXCLUSIVE_EXCLUSIVE);
535 
536         final DynamicLayout layout = DynamicLayout.Builder.obtain(builder,
537                 new TextPaint(), Integer.MAX_VALUE).build();
538         try {
539             builder.insert(1, "Hello there\n\n");
540         } catch (Throwable e) {
541             throw new RuntimeException("Inserting text into DynamicLayout should not crash", e);
542         }
543     }
544 
createBuilderWithDefaults(CharSequence base)545     private DynamicLayout.Builder createBuilderWithDefaults(CharSequence base) {
546         final DynamicLayout.Builder builder =
547                 DynamicLayout.Builder.obtain(base, mDefaultPaint, DEFAULT_OUTER_WIDTH);
548         return builder.setAlignment(DEFAULT_ALIGN)
549                 .setLineSpacing(SPACING_ADD_NO_SCALE, SPACING_MULT_NO_SCALE)
550                 .setIncludePad(true);
551     }
552 }
553