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; 18 19 import static android.text.TextUtils.formatSimple; 20 21 import static org.junit.Assert.assertEquals; 22 import static org.junit.Assert.assertFalse; 23 import static org.junit.Assert.assertNotNull; 24 import static org.junit.Assert.assertNull; 25 import static org.junit.Assert.assertSame; 26 import static org.junit.Assert.assertTrue; 27 import static org.junit.Assert.fail; 28 29 import android.os.Parcel; 30 import android.platform.test.annotations.Presubmit; 31 import android.test.MoreAsserts; 32 import android.text.style.StyleSpan; 33 import android.text.util.Rfc822Token; 34 import android.text.util.Rfc822Tokenizer; 35 import android.view.View; 36 37 import androidx.test.filters.LargeTest; 38 import androidx.test.filters.SmallTest; 39 import androidx.test.runner.AndroidJUnit4; 40 41 import com.google.android.collect.Lists; 42 43 import org.junit.Test; 44 import org.junit.runner.RunWith; 45 46 import java.nio.charset.StandardCharsets; 47 import java.util.ArrayList; 48 import java.util.List; 49 import java.util.Locale; 50 51 /** 52 * TextUtilsTest tests {@link TextUtils}. 53 */ 54 @Presubmit 55 @SmallTest 56 @RunWith(AndroidJUnit4.class) 57 public class TextUtilsTest { 58 59 @Test testBasic()60 public void testBasic() { 61 assertEquals("", TextUtils.concat()); 62 assertEquals("foo", TextUtils.concat("foo")); 63 assertEquals("foobar", TextUtils.concat("foo", "bar")); 64 assertEquals("foobarbaz", TextUtils.concat("foo", "bar", "baz")); 65 66 SpannableString foo = new SpannableString("foo"); 67 foo.setSpan("foo", 1, 2, Spannable.SPAN_EXCLUSIVE_INCLUSIVE); 68 69 SpannableString bar = new SpannableString("bar"); 70 bar.setSpan("bar", 1, 2, Spannable.SPAN_EXCLUSIVE_INCLUSIVE); 71 72 SpannableString baz = new SpannableString("baz"); 73 baz.setSpan("baz", 1, 2, Spannable.SPAN_EXCLUSIVE_INCLUSIVE); 74 75 assertEquals("foo", TextUtils.concat(foo).toString()); 76 assertEquals("foobar", TextUtils.concat(foo, bar).toString()); 77 assertEquals("foobarbaz", TextUtils.concat(foo, bar, baz).toString()); 78 79 assertEquals(1, ((Spanned) TextUtils.concat(foo)).getSpanStart("foo")); 80 81 assertEquals(1, ((Spanned) TextUtils.concat(foo, bar)).getSpanStart("foo")); 82 assertEquals(4, ((Spanned) TextUtils.concat(foo, bar)).getSpanStart("bar")); 83 84 assertEquals(1, ((Spanned) TextUtils.concat(foo, bar, baz)).getSpanStart("foo")); 85 assertEquals(4, ((Spanned) TextUtils.concat(foo, bar, baz)).getSpanStart("bar")); 86 assertEquals(7, ((Spanned) TextUtils.concat(foo, bar, baz)).getSpanStart("baz")); 87 88 assertTrue(TextUtils.concat("foo", "bar") instanceof String); 89 assertTrue(TextUtils.concat(foo, bar) instanceof SpannedString); 90 } 91 92 @Test testTemplateString()93 public void testTemplateString() { 94 CharSequence result; 95 96 result = TextUtils.expandTemplate("This is a ^1 of the ^2 broadcast ^3.", 97 "test", "emergency", "system"); 98 assertEquals("This is a test of the emergency broadcast system.", 99 result.toString()); 100 101 result = TextUtils.expandTemplate("^^^1^^^2^3^a^1^^b^^^c", 102 "one", "two", "three"); 103 assertEquals("^one^twothree^aone^b^^c", 104 result.toString()); 105 106 result = TextUtils.expandTemplate("^"); 107 assertEquals("^", result.toString()); 108 109 result = TextUtils.expandTemplate("^^"); 110 assertEquals("^", result.toString()); 111 112 result = TextUtils.expandTemplate("^^^"); 113 assertEquals("^^", result.toString()); 114 115 result = TextUtils.expandTemplate("shorter ^1 values ^2.", "a", ""); 116 assertEquals("shorter a values .", result.toString()); 117 118 try { 119 TextUtils.expandTemplate("Only ^1 value given, but ^2 used.", "foo"); 120 fail(); 121 } catch (IllegalArgumentException e) { 122 } 123 124 try { 125 TextUtils.expandTemplate("^1 value given, and ^0 used.", "foo"); 126 fail(); 127 } catch (IllegalArgumentException e) { 128 } 129 130 result = TextUtils.expandTemplate("^1 value given, and ^9 used.", 131 "one", "two", "three", "four", "five", 132 "six", "seven", "eight", "nine"); 133 assertEquals("one value given, and nine used.", result.toString()); 134 135 try { 136 TextUtils.expandTemplate("^1 value given, and ^10 used.", 137 "one", "two", "three", "four", "five", 138 "six", "seven", "eight", "nine", "ten"); 139 fail(); 140 } catch (IllegalArgumentException e) { 141 } 142 143 // putting carets in the values: expansion is not recursive. 144 145 result = TextUtils.expandTemplate("^2", "foo", "^^"); 146 assertEquals("^^", result.toString()); 147 148 result = TextUtils.expandTemplate("^^2", "foo", "1"); 149 assertEquals("^2", result.toString()); 150 151 result = TextUtils.expandTemplate("^1", "value with ^2 in it", "foo"); 152 assertEquals("value with ^2 in it", result.toString()); 153 } 154 155 /** Fail unless text+spans contains a span 'spanName' with the given start and end. */ checkContains(Spanned text, String[] spans, String spanName, int start, int end)156 private void checkContains(Spanned text, String[] spans, String spanName, 157 int start, int end) { 158 for (String i: spans) { 159 if (i.equals(spanName)) { 160 assertEquals(start, text.getSpanStart(i)); 161 assertEquals(end, text.getSpanEnd(i)); 162 return; 163 } 164 } 165 fail(); 166 } 167 168 @Test testTemplateSpan()169 public void testTemplateSpan() { 170 SpannableString template; 171 Spanned result; 172 String[] spans; 173 174 // ordinary replacement 175 176 template = new SpannableString("a^1b"); 177 template.setSpan("before", 0, 1, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); 178 template.setSpan("during", 1, 3, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); 179 template.setSpan("after", 3, 4, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); 180 template.setSpan("during+after", 1, 4, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); 181 182 result = (Spanned) TextUtils.expandTemplate(template, "foo"); 183 assertEquals(5, result.length()); 184 spans = result.getSpans(0, result.length(), String.class); 185 186 // value is one character longer, so span endpoints should change. 187 assertEquals(4, spans.length); 188 checkContains(result, spans, "before", 0, 1); 189 checkContains(result, spans, "during", 1, 4); 190 checkContains(result, spans, "after", 4, 5); 191 checkContains(result, spans, "during+after", 1, 5); 192 193 194 // replacement with empty string 195 196 result = (Spanned) TextUtils.expandTemplate(template, ""); 197 assertEquals(2, result.length()); 198 spans = result.getSpans(0, result.length(), String.class); 199 200 // the "during" span should disappear. 201 assertEquals(3, spans.length); 202 checkContains(result, spans, "before", 0, 1); 203 checkContains(result, spans, "after", 1, 2); 204 checkContains(result, spans, "during+after", 1, 2); 205 } 206 207 @Test testStringSplitterSimple()208 public void testStringSplitterSimple() { 209 stringSplitterTestHelper("a,b,cde", new String[] {"a", "b", "cde"}); 210 } 211 212 @Test testStringSplitterEmpty()213 public void testStringSplitterEmpty() { 214 stringSplitterTestHelper("", new String[] {}); 215 } 216 217 @Test testStringSplitterWithLeadingEmptyString()218 public void testStringSplitterWithLeadingEmptyString() { 219 stringSplitterTestHelper(",a,b,cde", new String[] {"", "a", "b", "cde"}); 220 } 221 222 @Test testStringSplitterWithInternalEmptyString()223 public void testStringSplitterWithInternalEmptyString() { 224 stringSplitterTestHelper("a,b,,cde", new String[] {"a", "b", "", "cde"}); 225 } 226 227 @Test testStringSplitterWithTrailingEmptyString()228 public void testStringSplitterWithTrailingEmptyString() { 229 // A single trailing emtpy string should be ignored. 230 stringSplitterTestHelper("a,b,cde,", new String[] {"a", "b", "cde"}); 231 } 232 stringSplitterTestHelper(String string, String[] expectedStrings)233 private void stringSplitterTestHelper(String string, String[] expectedStrings) { 234 TextUtils.StringSplitter splitter = new TextUtils.SimpleStringSplitter(','); 235 splitter.setString(string); 236 List<String> strings = Lists.newArrayList(); 237 for (String s : splitter) { 238 strings.add(s); 239 } 240 MoreAsserts.assertEquals(expectedStrings, strings.toArray(new String[]{})); 241 } 242 243 @Test testTrim()244 public void testTrim() { 245 String[] strings = { "abc", " abc", " abc", "abc ", "abc ", 246 " abc ", " abc ", "\nabc\n", "\nabc", "abc\n" }; 247 248 for (String s : strings) { 249 assertEquals(s.trim().length(), TextUtils.getTrimmedLength(s)); 250 } 251 } 252 253 @Test testRfc822TokenizerFullAddress()254 public void testRfc822TokenizerFullAddress() { 255 Rfc822Token[] tokens = Rfc822Tokenizer.tokenize("Foo Bar (something) <foo@google.com>"); 256 assertNotNull(tokens); 257 assertEquals(1, tokens.length); 258 assertEquals("foo@google.com", tokens[0].getAddress()); 259 assertEquals("Foo Bar", tokens[0].getName()); 260 assertEquals("something",tokens[0].getComment()); 261 } 262 263 @Test testRfc822TokenizeItemWithError()264 public void testRfc822TokenizeItemWithError() { 265 Rfc822Token[] tokens = Rfc822Tokenizer.tokenize("\"Foo Bar\\"); 266 assertNotNull(tokens); 267 assertEquals(1, tokens.length); 268 assertEquals("Foo Bar", tokens[0].getAddress()); 269 } 270 271 @Test testRfc822FindToken()272 public void testRfc822FindToken() { 273 Rfc822Tokenizer tokenizer = new Rfc822Tokenizer(); 274 // 0 1 2 3 4 275 // 0 1234 56789012345678901234 5678 90123456789012345 276 String address = "\"Foo\" <foo@google.com>, \"Bar\" <bar@google.com>"; 277 assertEquals(0, tokenizer.findTokenStart(address, 21)); 278 assertEquals(22, tokenizer.findTokenEnd(address, 21)); 279 assertEquals(24, tokenizer.findTokenStart(address, 25)); 280 assertEquals(46, tokenizer.findTokenEnd(address, 25)); 281 } 282 283 @Test testRfc822FindTokenWithError()284 public void testRfc822FindTokenWithError() { 285 assertEquals(9, new Rfc822Tokenizer().findTokenEnd("\"Foo Bar\\", 0)); 286 } 287 288 @LargeTest 289 @Test testEllipsize()290 public void testEllipsize() { 291 CharSequence s1 = "The quick brown fox jumps over \u00FEhe lazy dog."; 292 CharSequence s2 = new Wrapper(s1); 293 Spannable s3 = new SpannableString(s1); 294 s3.setSpan(new StyleSpan(0), 5, 10, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); 295 TextPaint p = new TextPaint(); 296 p.setFlags(p.getFlags() & ~p.DEV_KERN_TEXT_FLAG); 297 298 for (int i = 0; i < 100; i++) { 299 for (int j = 0; j < 3; j++) { 300 TextUtils.TruncateAt kind = null; 301 302 switch (j) { 303 case 0: 304 kind = TextUtils.TruncateAt.START; 305 break; 306 307 case 1: 308 kind = TextUtils.TruncateAt.END; 309 break; 310 311 case 2: 312 kind = TextUtils.TruncateAt.MIDDLE; 313 break; 314 } 315 316 String out1 = TextUtils.ellipsize(s1, p, i, kind).toString(); 317 String out2 = TextUtils.ellipsize(s2, p, i, kind).toString(); 318 String out3 = TextUtils.ellipsize(s3, p, i, kind).toString(); 319 320 String keep1 = TextUtils.ellipsize(s1, p, i, kind, true, null).toString(); 321 String keep2 = TextUtils.ellipsize(s2, p, i, kind, true, null).toString(); 322 String keep3 = TextUtils.ellipsize(s3, p, i, kind, true, null).toString(); 323 324 String trim1 = keep1.replace("\uFEFF", ""); 325 326 // Are all normal output strings identical? 327 assertEquals("wid " + i + " pass " + j, out1, out2); 328 assertEquals("wid " + i + " pass " + j, out2, out3); 329 330 // Are preserved output strings identical? 331 assertEquals("wid " + i + " pass " + j, keep1, keep2); 332 assertEquals("wid " + i + " pass " + j, keep2, keep3); 333 334 // Does trimming padding from preserved yield normal? 335 assertEquals("wid " + i + " pass " + j, out1, trim1); 336 337 // Did preserved output strings preserve length? 338 assertEquals("wid " + i + " pass " + j, keep1.length(), s1.length()); 339 340 // Does the output string actually fit in the space? 341 assertTrue("wid " + i + " pass " + j, p.measureText(out1) <= i); 342 343 // Is the padded output the same width as trimmed output? 344 assertTrue("wid " + i + " pass " + j, p.measureText(keep1) == p.measureText(out1)); 345 } 346 } 347 } 348 349 @Test testEllipsize_multiCodepoint()350 public void testEllipsize_multiCodepoint() { 351 final TextPaint paint = new TextPaint(); 352 final float wordWidth = paint.measureText("MMMM"); 353 354 // Establish the ground rules first, for single-codepoint cases. 355 final String ellipsis = "."; // one full stop character 356 assertEquals( 357 "MM.\uFEFF", 358 TextUtils.ellipsize("MMMM", paint, 0.7f * wordWidth, 359 TextUtils.TruncateAt.END, true /* preserve length */, 360 null /* no callback */, TextDirectionHeuristics.LTR, 361 ellipsis)); 362 assertEquals( 363 "MM.", 364 TextUtils.ellipsize("MMMM", paint, 0.7f * wordWidth, 365 TextUtils.TruncateAt.END, false /* preserve length */, 366 null /* no callback */, TextDirectionHeuristics.LTR, 367 ellipsis)); 368 assertEquals( 369 "M.", 370 TextUtils.ellipsize("MM", paint, 0.45f * wordWidth, 371 TextUtils.TruncateAt.END, true /* preserve length */, 372 null /* no callback */, TextDirectionHeuristics.LTR, 373 ellipsis)); 374 assertEquals( 375 "M.", 376 TextUtils.ellipsize("MM", paint, 0.45f * wordWidth, 377 TextUtils.TruncateAt.END, false /* preserve length */, 378 null /* no callback */, TextDirectionHeuristics.LTR, 379 ellipsis)); 380 381 // Now check the differences for multi-codepoint ellipsis. 382 final String longEllipsis = ".."; // two full stop characters 383 assertEquals( 384 "MM..", 385 TextUtils.ellipsize("MMMM", paint, 0.7f * wordWidth, 386 TextUtils.TruncateAt.END, true /* preserve length */, 387 null /* no callback */, TextDirectionHeuristics.LTR, 388 longEllipsis)); 389 assertEquals( 390 "MM..", 391 TextUtils.ellipsize("MMMM", paint, 0.7f * wordWidth, 392 TextUtils.TruncateAt.END, false /* preserve length */, 393 null /* no callback */, TextDirectionHeuristics.LTR, 394 longEllipsis)); 395 assertEquals( 396 "M\uFEFF", 397 TextUtils.ellipsize("MM", paint, 0.45f * wordWidth, 398 TextUtils.TruncateAt.END, true /* preserve length */, 399 null /* no callback */, TextDirectionHeuristics.LTR, 400 longEllipsis)); 401 assertEquals( 402 "M..", 403 TextUtils.ellipsize("MM", paint, 0.45f * wordWidth, 404 TextUtils.TruncateAt.END, false /* preserve length */, 405 null /* no callback */, TextDirectionHeuristics.LTR, 406 longEllipsis)); 407 } 408 409 @Test testDelimitedStringContains()410 public void testDelimitedStringContains() { 411 assertFalse(TextUtils.delimitedStringContains("", ',', null)); 412 assertFalse(TextUtils.delimitedStringContains(null, ',', "")); 413 // Whole match 414 assertTrue(TextUtils.delimitedStringContains("gps", ',', "gps")); 415 // At beginning. 416 assertTrue(TextUtils.delimitedStringContains("gps,gpsx,network,mock", ',', "gps")); 417 assertTrue(TextUtils.delimitedStringContains("gps,network,mock", ',', "gps")); 418 // In middle, both without, before & after a false match. 419 assertTrue(TextUtils.delimitedStringContains("network,gps,mock", ',', "gps")); 420 assertTrue(TextUtils.delimitedStringContains("network,gps,gpsx,mock", ',', "gps")); 421 assertTrue(TextUtils.delimitedStringContains("network,gpsx,gps,mock", ',', "gps")); 422 // At the end. 423 assertTrue(TextUtils.delimitedStringContains("network,mock,gps", ',', "gps")); 424 assertTrue(TextUtils.delimitedStringContains("network,mock,gpsx,gps", ',', "gps")); 425 // Not present (but with a false match) 426 assertFalse(TextUtils.delimitedStringContains("network,mock,gpsx", ',', "gps")); 427 } 428 429 @Test testCharSequenceCreator()430 public void testCharSequenceCreator() { 431 Parcel p = Parcel.obtain(); 432 CharSequence text; 433 try { 434 TextUtils.writeToParcel(null, p, 0); 435 text = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(p); 436 assertNull("null CharSequence should generate null from parcel", text); 437 } finally { 438 p.recycle(); 439 } 440 p = Parcel.obtain(); 441 try { 442 TextUtils.writeToParcel("test", p, 0); 443 p.setDataPosition(0); 444 text = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(p); 445 assertEquals("conversion to/from parcel failed", "test", text); 446 } finally { 447 p.recycle(); 448 } 449 } 450 451 @Test testCharSequenceCreatorNull()452 public void testCharSequenceCreatorNull() { 453 Parcel p; 454 CharSequence text; 455 p = Parcel.obtain(); 456 try { 457 TextUtils.writeToParcel(null, p, 0); 458 p.setDataPosition(0); 459 text = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(p); 460 assertNull("null CharSequence should generate null from parcel", text); 461 } finally { 462 p.recycle(); 463 } 464 } 465 466 @Test testCharSequenceCreatorSpannable()467 public void testCharSequenceCreatorSpannable() { 468 Parcel p; 469 CharSequence text; 470 p = Parcel.obtain(); 471 try { 472 TextUtils.writeToParcel(new SpannableString("test"), p, 0); 473 p.setDataPosition(0); 474 text = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(p); 475 assertEquals("conversion to/from parcel failed", "test", text.toString()); 476 } finally { 477 p.recycle(); 478 } 479 } 480 481 @Test testCharSequenceCreatorString()482 public void testCharSequenceCreatorString() { 483 Parcel p; 484 CharSequence text; 485 p = Parcel.obtain(); 486 try { 487 TextUtils.writeToParcel("test", p, 0); 488 p.setDataPosition(0); 489 text = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(p); 490 assertEquals("conversion to/from parcel failed", "test", text.toString()); 491 } finally { 492 p.recycle(); 493 } 494 } 495 496 /** 497 * CharSequence wrapper for testing the cases where text is copied into 498 * a char array instead of working from a String or a Spanned. 499 */ 500 private static class Wrapper implements CharSequence { 501 private CharSequence mString; 502 Wrapper(CharSequence s)503 public Wrapper(CharSequence s) { 504 mString = s; 505 } 506 507 @Override length()508 public int length() { 509 return mString.length(); 510 } 511 512 @Override charAt(int off)513 public char charAt(int off) { 514 return mString.charAt(off); 515 } 516 517 @Override toString()518 public String toString() { 519 return mString.toString(); 520 } 521 522 @Override subSequence(int start, int end)523 public CharSequence subSequence(int start, int end) { 524 return new Wrapper(mString.subSequence(start, end)); 525 } 526 } 527 528 @Test testRemoveEmptySpans()529 public void testRemoveEmptySpans() { 530 MockSpanned spanned = new MockSpanned(); 531 532 spanned.test(); 533 spanned.addSpan().test(); 534 spanned.addSpan().test(); 535 spanned.addSpan().test(); 536 spanned.addEmptySpan().test(); 537 spanned.addSpan().test(); 538 spanned.addEmptySpan().test(); 539 spanned.addEmptySpan().test(); 540 spanned.addSpan().test(); 541 542 spanned.clear(); 543 spanned.addEmptySpan().test(); 544 spanned.addEmptySpan().test(); 545 spanned.addEmptySpan().test(); 546 spanned.addSpan().test(); 547 spanned.addEmptySpan().test(); 548 spanned.addSpan().test(); 549 550 spanned.clear(); 551 spanned.addSpan().test(); 552 spanned.addEmptySpan().test(); 553 spanned.addSpan().test(); 554 spanned.addEmptySpan().test(); 555 spanned.addSpan().test(); 556 spanned.addSpan().test(); 557 } 558 559 protected static class MockSpanned implements Spanned { 560 561 private List<Object> allSpans = new ArrayList<Object>(); 562 private List<Object> nonEmptySpans = new ArrayList<Object>(); 563 clear()564 public void clear() { 565 allSpans.clear(); 566 nonEmptySpans.clear(); 567 } 568 addSpan()569 public MockSpanned addSpan() { 570 Object o = new Object(); 571 allSpans.add(o); 572 nonEmptySpans.add(o); 573 return this; 574 } 575 addEmptySpan()576 public MockSpanned addEmptySpan() { 577 Object o = new Object(); 578 allSpans.add(o); 579 return this; 580 } 581 test()582 public void test() { 583 Object[] nonEmpty = TextUtils.removeEmptySpans(allSpans.toArray(), this, Object.class); 584 assertEquals("Mismatched array size", nonEmptySpans.size(), nonEmpty.length); 585 for (int i=0; i<nonEmpty.length; i++) { 586 assertEquals("Span differ", nonEmptySpans.get(i), nonEmpty[i]); 587 } 588 } 589 590 @Override charAt(int arg0)591 public char charAt(int arg0) { 592 return 0; 593 } 594 595 @Override length()596 public int length() { 597 return 0; 598 } 599 600 @Override subSequence(int arg0, int arg1)601 public CharSequence subSequence(int arg0, int arg1) { 602 return null; 603 } 604 605 @Override getSpans(int start, int end, Class<T> type)606 public <T> T[] getSpans(int start, int end, Class<T> type) { 607 return null; 608 } 609 610 @Override getSpanStart(Object tag)611 public int getSpanStart(Object tag) { 612 return 0; 613 } 614 615 @Override getSpanEnd(Object tag)616 public int getSpanEnd(Object tag) { 617 return nonEmptySpans.contains(tag) ? 1 : 0; 618 } 619 620 @Override getSpanFlags(Object tag)621 public int getSpanFlags(Object tag) { 622 return 0; 623 } 624 625 @Override nextSpanTransition(int start, int limit, Class type)626 public int nextSpanTransition(int start, int limit, Class type) { 627 return 0; 628 } 629 } 630 631 @Test testGetLayoutDirectionFromLocale()632 public void testGetLayoutDirectionFromLocale() { 633 assertEquals(View.LAYOUT_DIRECTION_LTR, TextUtils.getLayoutDirectionFromLocale(null)); 634 assertEquals(View.LAYOUT_DIRECTION_LTR, 635 TextUtils.getLayoutDirectionFromLocale(Locale.ROOT)); 636 assertEquals(View.LAYOUT_DIRECTION_LTR, 637 TextUtils.getLayoutDirectionFromLocale(Locale.forLanguageTag("en"))); 638 assertEquals(View.LAYOUT_DIRECTION_LTR, 639 TextUtils.getLayoutDirectionFromLocale(Locale.forLanguageTag("en-US"))); 640 assertEquals(View.LAYOUT_DIRECTION_LTR, 641 TextUtils.getLayoutDirectionFromLocale(Locale.forLanguageTag("az"))); 642 assertEquals(View.LAYOUT_DIRECTION_LTR, 643 TextUtils.getLayoutDirectionFromLocale(Locale.forLanguageTag("az-AZ"))); 644 assertEquals(View.LAYOUT_DIRECTION_LTR, 645 TextUtils.getLayoutDirectionFromLocale(Locale.forLanguageTag("az-Latn"))); 646 assertEquals(View.LAYOUT_DIRECTION_LTR, 647 TextUtils.getLayoutDirectionFromLocale(Locale.forLanguageTag("en-EG"))); 648 assertEquals(View.LAYOUT_DIRECTION_LTR, 649 TextUtils.getLayoutDirectionFromLocale(Locale.forLanguageTag("ar-Latn"))); 650 651 assertEquals(View.LAYOUT_DIRECTION_RTL, 652 TextUtils.getLayoutDirectionFromLocale(Locale.forLanguageTag("ar"))); 653 assertEquals(View.LAYOUT_DIRECTION_RTL, 654 TextUtils.getLayoutDirectionFromLocale(Locale.forLanguageTag("fa"))); 655 assertEquals(View.LAYOUT_DIRECTION_RTL, 656 TextUtils.getLayoutDirectionFromLocale(Locale.forLanguageTag("he"))); 657 assertEquals(View.LAYOUT_DIRECTION_RTL, 658 TextUtils.getLayoutDirectionFromLocale(Locale.forLanguageTag("iw"))); 659 assertEquals(View.LAYOUT_DIRECTION_RTL, 660 TextUtils.getLayoutDirectionFromLocale(Locale.forLanguageTag("ur"))); 661 assertEquals(View.LAYOUT_DIRECTION_RTL, 662 TextUtils.getLayoutDirectionFromLocale(Locale.forLanguageTag("dv"))); 663 assertEquals(View.LAYOUT_DIRECTION_RTL, 664 TextUtils.getLayoutDirectionFromLocale(Locale.forLanguageTag("az-Arab"))); 665 assertEquals(View.LAYOUT_DIRECTION_RTL, 666 TextUtils.getLayoutDirectionFromLocale(Locale.forLanguageTag("az-IR"))); 667 assertEquals(View.LAYOUT_DIRECTION_RTL, 668 TextUtils.getLayoutDirectionFromLocale(Locale.forLanguageTag("fa-US"))); 669 assertEquals(View.LAYOUT_DIRECTION_RTL, 670 TextUtils.getLayoutDirectionFromLocale(Locale.forLanguageTag("tr-Arab"))); 671 } 672 673 @Test testToUpperCase()674 public void testToUpperCase() { 675 { 676 final CharSequence result = TextUtils.toUpperCase(null, "abc", false); 677 assertEquals(StringBuilder.class, result.getClass()); 678 assertEquals("ABC", result.toString()); 679 } 680 { 681 final SpannableString str = new SpannableString("abc"); 682 Object span = new Object(); 683 str.setSpan(span, 1, 2, Spanned.SPAN_INCLUSIVE_INCLUSIVE); 684 685 final CharSequence result = TextUtils.toUpperCase(null, str, true /* copySpans */); 686 assertEquals(SpannableStringBuilder.class, result.getClass()); 687 assertEquals("ABC", result.toString()); 688 final Spanned spanned = (Spanned) result; 689 final Object[] resultSpans = spanned.getSpans(0, result.length(), Object.class); 690 assertEquals(1, resultSpans.length); 691 assertSame(span, resultSpans[0]); 692 assertEquals(1, spanned.getSpanStart(span)); 693 assertEquals(2, spanned.getSpanEnd(span)); 694 assertEquals(Spanned.SPAN_INCLUSIVE_INCLUSIVE, spanned.getSpanFlags(span)); 695 } 696 { 697 final Locale turkish = new Locale("tr", "TR"); 698 final CharSequence result = TextUtils.toUpperCase(turkish, "i", false); 699 assertEquals(StringBuilder.class, result.getClass()); 700 assertEquals("İ", result.toString()); 701 } 702 { 703 final String str = "ABC"; 704 assertSame(str, TextUtils.toUpperCase(null, str, false)); 705 } 706 { 707 final SpannableString str = new SpannableString("ABC"); 708 str.setSpan(new Object(), 1, 2, Spanned.SPAN_INCLUSIVE_INCLUSIVE); 709 assertSame(str, TextUtils.toUpperCase(null, str, true /* copySpans */)); 710 } 711 } 712 713 // Copied from cts/tests/tests/widget/src/android/widget/cts/TextViewTest.java and modified 714 // for the TextUtils.toUpperCase method. 715 @Test testToUpperCase_SpansArePreserved()716 public void testToUpperCase_SpansArePreserved() { 717 final Locale greek = new Locale("el", "GR"); 718 final String lowerString = "ι\u0301ριδα"; // ίριδα with first letter decomposed 719 final String upperString = "ΙΡΙΔΑ"; // uppercased 720 // expected lowercase to uppercase index map 721 final int[] indexMap = {0, 1, 1, 2, 3, 4, 5}; 722 final int flags = Spanned.SPAN_INCLUSIVE_INCLUSIVE; 723 724 final Spannable source = new SpannableString(lowerString); 725 source.setSpan(new Object(), 0, 1, flags); 726 source.setSpan(new Object(), 1, 2, flags); 727 source.setSpan(new Object(), 2, 3, flags); 728 source.setSpan(new Object(), 3, 4, flags); 729 source.setSpan(new Object(), 4, 5, flags); 730 source.setSpan(new Object(), 5, 6, flags); 731 source.setSpan(new Object(), 0, 2, flags); 732 source.setSpan(new Object(), 1, 3, flags); 733 source.setSpan(new Object(), 2, 4, flags); 734 source.setSpan(new Object(), 0, 6, flags); 735 final Object[] sourceSpans = source.getSpans(0, source.length(), Object.class); 736 737 final CharSequence uppercase = TextUtils.toUpperCase(greek, source, true /* copySpans */); 738 assertEquals(SpannableStringBuilder.class, uppercase.getClass()); 739 final Spanned result = (Spanned) uppercase; 740 741 assertEquals(upperString, result.toString()); 742 final Object[] resultSpans = result.getSpans(0, result.length(), Object.class); 743 assertEquals(sourceSpans.length, resultSpans.length); 744 for (int i = 0; i < sourceSpans.length; i++) { 745 assertSame(sourceSpans[i], resultSpans[i]); 746 final Object span = sourceSpans[i]; 747 assertEquals(indexMap[source.getSpanStart(span)], result.getSpanStart(span)); 748 assertEquals(indexMap[source.getSpanEnd(span)], result.getSpanEnd(span)); 749 assertEquals(source.getSpanFlags(span), result.getSpanFlags(span)); 750 } 751 } 752 753 @Test testTrimToSize()754 public void testTrimToSize() { 755 final String testString = "a\uD800\uDC00a"; 756 assertEquals("Should return text as it is if size is longer than length", 757 testString, TextUtils.trimToSize(testString, 5)); 758 assertEquals("Should return text as it is if size is equal to length", 759 testString, TextUtils.trimToSize(testString, 4)); 760 assertEquals("Should trim text", 761 "a\uD800\uDC00", TextUtils.trimToSize(testString, 3)); 762 assertEquals("Should trim surrogate pairs if size is in the middle of a pair", 763 "a", TextUtils.trimToSize(testString, 2)); 764 assertEquals("Should trim text", 765 "a", TextUtils.trimToSize(testString, 1)); 766 assertEquals("Should handle null", 767 null, TextUtils.trimToSize(null, 1)); 768 769 assertEquals("Should trim high surrogate if invalid surrogate", 770 "a\uD800", TextUtils.trimToSize("a\uD800\uD800", 2)); 771 assertEquals("Should trim low surrogate if invalid surrogate", 772 "a\uDC00", TextUtils.trimToSize("a\uDC00\uDC00", 2)); 773 } 774 775 @Test(expected = IllegalArgumentException.class) testTrimToSizeThrowsExceptionForNegativeSize()776 public void testTrimToSizeThrowsExceptionForNegativeSize() { 777 TextUtils.trimToSize("", -1); 778 } 779 780 @Test(expected = IllegalArgumentException.class) testTrimToSizeThrowsExceptionForZeroSize()781 public void testTrimToSizeThrowsExceptionForZeroSize() { 782 TextUtils.trimToSize("abc", 0); 783 } 784 785 @Test truncateStringForUtf8Storage()786 public void truncateStringForUtf8Storage() { 787 assertEquals("", TextUtils.truncateStringForUtf8Storage("abc", 0)); 788 789 //================ long normal case ================ 790 StringBuilder builder = new StringBuilder(); 791 792 int n = 50; 793 for (int i = 0; i < 2 * n; i++) { 794 builder.append("哈"); 795 } 796 String initial = builder.toString(); 797 String result = TextUtils.truncateStringForUtf8Storage(initial, n); 798 799 // Result should be the beginning of initial 800 assertTrue(initial.startsWith(result)); 801 802 // Result should take less than n bytes in UTF-8 803 assertTrue(result.getBytes(StandardCharsets.UTF_8).length <= n); 804 805 // result + the next codePoint should take strictly more than 806 // n bytes in UTF-8 807 assertTrue(initial.substring(0, initial.offsetByCodePoints(result.length(), 1)) 808 .getBytes(StandardCharsets.UTF_8).length > n); 809 810 // =================== short normal case ===================== 811 String s = "sf\u20ACgk\u00E9ls\u00E9fg"; 812 result = TextUtils.truncateStringForUtf8Storage(s, 100); 813 assertEquals(s, result); 814 } 815 816 @Test testTruncateInMiddleOfSurrogate()817 public void testTruncateInMiddleOfSurrogate() { 818 StringBuilder builder = new StringBuilder(); 819 String beginning = "a"; 820 builder.append(beginning); 821 builder.append(Character.toChars(0x1D11E)); 822 823 String result = TextUtils.truncateStringForUtf8Storage(builder.toString(), 3); 824 825 // \u1D11E is a surrogate and needs 4 bytes in UTF-8. beginning == "a" uses 826 // only 1 bytes in UTF8 827 // As we allow only 3 bytes for the whole string, so just 2 for this 828 // codePoint, there is not enough place and the string will be truncated 829 // just before it 830 assertEquals(beginning, result); 831 } 832 833 @Test testTruncateInMiddleOfChar()834 public void testTruncateInMiddleOfChar() { 835 StringBuilder builder = new StringBuilder(); 836 String beginning = "a"; 837 builder.append(beginning); 838 builder.append(Character.toChars(0x20AC)); 839 840 String result = TextUtils.truncateStringForUtf8Storage(builder.toString(), 3); 841 842 // Like above, \u20AC uses 3 bytes in UTF-8, with "beginning", that makes 843 // 4 bytes so it is too big and should be truncated 844 assertEquals(beginning, result); 845 } 846 847 @Test testTruncateSubString()848 public void testTruncateSubString() { 849 String test = "sdgkl;hjsl;gjhdgkljdfhglkdj"; 850 String sub = test.substring(10, 20); 851 String res = TextUtils.truncateStringForUtf8Storage(sub, 255); 852 assertEquals(sub, res); 853 } 854 855 @Test(expected = IndexOutOfBoundsException.class) truncateStringForUtf8StorageThrowsExceptionForNegativeSize()856 public void truncateStringForUtf8StorageThrowsExceptionForNegativeSize() { 857 TextUtils.truncateStringForUtf8Storage("abc", -1); 858 } 859 860 @Test length()861 public void length() { 862 assertEquals(0, TextUtils.length(null)); 863 assertEquals(0, TextUtils.length("")); 864 assertEquals(2, TextUtils.length(" ")); 865 assertEquals(6, TextUtils.length("Hello!")); 866 } 867 868 @Test testTrimToLengthWithEllipsis()869 public void testTrimToLengthWithEllipsis() { 870 assertEquals("ABC...", TextUtils.trimToLengthWithEllipsis("ABCDEF", 3)); 871 assertEquals("ABC", TextUtils.trimToLengthWithEllipsis("ABC", 3)); 872 assertEquals("", TextUtils.trimToLengthWithEllipsis("", 3)); 873 assertNull(TextUtils.trimToLengthWithEllipsis(null, 3)); 874 } 875 876 @Test testFormatSimple_Types()877 public void testFormatSimple_Types() { 878 assertEquals("true", formatSimple("%b", true)); 879 assertEquals("false", formatSimple("%b", false)); 880 assertEquals("true", formatSimple("%b", this)); 881 assertEquals("false", formatSimple("%b", new Object[] { null })); 882 883 assertEquals("!", formatSimple("%c", '!')); 884 885 assertEquals("42", formatSimple("%d", 42)); 886 assertEquals("281474976710656", formatSimple("%d", 281474976710656L)); 887 888 assertEquals("3.14159", formatSimple("%f", 3.14159)); 889 assertEquals("NaN", formatSimple("%f", Float.NaN)); 890 891 assertEquals("example", formatSimple("%s", "example")); 892 assertEquals("null", formatSimple("%s", new Object[] { null })); 893 894 assertEquals("2a", formatSimple("%x", 42)); 895 assertEquals("1000000000000", formatSimple("%x", 281474976710656L)); 896 897 assertEquals("%", formatSimple("%%")); 898 } 899 900 @Test testFormatSimple_Width()901 public void testFormatSimple_Width() { 902 assertEquals("42", formatSimple("%1d", 42)); 903 assertEquals("42", formatSimple("%2d", 42)); 904 assertEquals(" 42", formatSimple("%3d", 42)); 905 assertEquals(" 42", formatSimple("%4d", 42)); 906 assertEquals(" 42 42", formatSimple("%4d%4d", 42, 42)); 907 assertEquals(" -42", formatSimple("%4d", -42)); 908 assertEquals(" 42", formatSimple("%10d", 42)); 909 910 assertEquals("42", formatSimple("%01d", 42)); 911 assertEquals("42", formatSimple("%02d", 42)); 912 assertEquals("042", formatSimple("%03d", 42)); 913 assertEquals("0042", formatSimple("%04d", 42)); 914 assertEquals("00420042", formatSimple("%04d%04d", 42, 42)); 915 assertEquals("-042", formatSimple("%04d", -42)); 916 assertEquals("0000000042", formatSimple("%010d", 42)); 917 } 918 919 @Test testFormatSimple_Empty()920 public void testFormatSimple_Empty() { 921 assertEquals("", formatSimple("")); 922 } 923 924 @Test testFormatSimple_Typical()925 public void testFormatSimple_Typical() { 926 assertEquals("String foobar and %% number -42 together", 927 formatSimple("String %s%s and %%%% number %d%d together", "foo", "bar", -4, 2)); 928 } 929 930 @Test testFormatSimple_Advanced()931 public void testFormatSimple_Advanced() { 932 assertEquals("000000000000002a.ext", 933 formatSimple("%016x.%s", 42, "ext")); 934 assertEquals("crtcl=0x002a:intrsv=Y:grnk=0x0018:gsmry=A:example:rnk=0x0000", 935 formatSimple("crtcl=0x%04x:intrsv=%c:grnk=0x%04x:gsmry=%c:%s:rnk=0x%04x", 936 42, 'Y', 24, 'A', "example", 0)); 937 } 938 939 @Test testFormatSimple_Mismatch()940 public void testFormatSimple_Mismatch() { 941 try { 942 formatSimple("%s"); 943 fail(); 944 } catch (IllegalArgumentException expected) { 945 } 946 try { 947 formatSimple("%s", "foo", "bar"); 948 fail(); 949 } catch (IllegalArgumentException expected) { 950 } 951 } 952 } 953