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.view.View.LAYOUT_DIRECTION_LTR; 20 import static android.view.View.LAYOUT_DIRECTION_RTL; 21 22 import static org.junit.Assert.assertEquals; 23 import static org.junit.Assert.assertFalse; 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 import static org.mockito.ArgumentMatchers.any; 29 import static org.mockito.ArgumentMatchers.anyInt; 30 import static org.mockito.Mockito.mock; 31 import static org.mockito.Mockito.when; 32 33 import android.content.Context; 34 import android.content.res.ColorStateList; 35 import android.content.res.Configuration; 36 import android.content.res.Resources; 37 import android.graphics.Color; 38 import android.graphics.Typeface; 39 import android.os.LocaleList; 40 import android.os.Parcel; 41 import android.os.Parcelable; 42 import android.platform.test.annotations.IgnoreUnderRavenwood; 43 import android.platform.test.ravenwood.RavenwoodRule; 44 import android.text.GetChars; 45 import android.text.SpannableString; 46 import android.text.SpannableStringBuilder; 47 import android.text.Spanned; 48 import android.text.SpannedString; 49 import android.text.TextPaint; 50 import android.text.TextUtils; 51 import android.text.TextUtils.TruncateAt; 52 import android.text.style.BackgroundColorSpan; 53 import android.text.style.ReplacementSpan; 54 import android.text.style.TextAppearanceSpan; 55 import android.text.style.URLSpan; 56 import android.util.StringBuilderPrinter; 57 58 import androidx.test.filters.SmallTest; 59 import androidx.test.platform.app.InstrumentationRegistry; 60 import androidx.test.runner.AndroidJUnit4; 61 62 import org.junit.Before; 63 import org.junit.Rule; 64 import org.junit.Test; 65 import org.junit.runner.RunWith; 66 67 import java.util.ArrayList; 68 import java.util.Arrays; 69 import java.util.List; 70 import java.util.Locale; 71 import java.util.regex.Pattern; 72 73 /** 74 * Test {@link TextUtils}. 75 */ 76 @SmallTest 77 @RunWith(AndroidJUnit4.class) 78 public class TextUtilsTest { 79 @Rule public final RavenwoodRule mRavenwood = new RavenwoodRule(); 80 81 private Context mContext; 82 private String mEllipsis; 83 private int mStart; 84 private int mEnd; 85 86 @Before setup()87 public void setup() { 88 if (!mRavenwood.isUnderRavenwood()) { 89 mContext = InstrumentationRegistry.getInstrumentation().getTargetContext(); 90 mEllipsis = getEllipsis(); 91 } 92 resetRange(); 93 } 94 resetRange()95 private void resetRange() { 96 mStart = -1; 97 mEnd = -1; 98 } 99 100 /** 101 * Get the ellipsis from system. 102 * @return the string of ellipsis. 103 */ getEllipsis()104 private static String getEllipsis() { 105 String text = "xxxxx"; 106 TextPaint p = new TextPaint(); 107 float width = p.measureText(text.substring(1)); 108 String re = TextUtils.ellipsize(text, p, width, TruncateAt.START).toString(); 109 return re.substring(0, re.indexOf("x")); 110 } 111 112 /** 113 * @return the number of times the code unit appears in the CharSequence. 114 */ countChars(CharSequence s, char c)115 private static int countChars(CharSequence s, char c) { 116 int count = 0; 117 for (int i = 0; i < s.length(); i++) { 118 if (s.charAt(i) == c) { 119 count++; 120 } 121 } 122 return count; 123 } 124 125 @Test 126 @IgnoreUnderRavenwood(blockedBy = Resources.class) testListEllipsize()127 public void testListEllipsize() { 128 final TextPaint paint = new TextPaint(); 129 // "one more" for 1, "%d more" for other 130 final int moreId = mContext.getResources().getIdentifier("list_ellipsize_test", 131 "plurals", "android.text.cts"); 132 133 final List fullList = Arrays.asList("A", "B", "C", "D", "E", "F", "G", "H", "I", "J"); 134 final String separator = ", "; 135 final String fullString = TextUtils.join(separator, fullList); 136 final float fullWidth = paint.measureText(fullString); 137 assertEquals("", 138 TextUtils.listEllipsize(mContext, null, separator, paint, fullWidth, moreId)); 139 140 final List<CharSequence> emptyList = new ArrayList<>(); 141 assertEquals("", 142 TextUtils.listEllipsize(mContext, emptyList, separator, paint, fullWidth, moreId)); 143 144 // Null context should cause ellipsis to be used at the end. 145 final String ellipsizedWithNull = TextUtils.listEllipsize( 146 null, fullList, separator, paint, fullWidth / 2, 0).toString(); 147 assertTrue(ellipsizedWithNull.endsWith(getEllipsis())); 148 149 // Test that the empty string gets returned if there's no space. 150 assertEquals("", 151 TextUtils.listEllipsize(mContext, fullList, separator, paint, 1.0f, moreId)); 152 153 // Test that the full string itself can get returned if there's enough space. 154 assertEquals(fullString, 155 TextUtils.listEllipsize(mContext, fullList, separator, paint, fullWidth, moreId) 156 .toString()); 157 assertEquals(fullString, 158 TextUtils.listEllipsize(mContext, fullList, separator, paint, fullWidth * 2, 159 moreId).toString()); 160 161 final float epsilon = fullWidth / 20; 162 for (float width = epsilon; width < fullWidth - epsilon / 2; width += epsilon) { 163 final String ellipsized = TextUtils.listEllipsize( 164 mContext, fullList, separator, paint, width, moreId).toString(); 165 // Since we don't have the full space, test that we are not getting the full string. 166 assertFalse(fullString.equals(ellipsized)); 167 168 if (!ellipsized.isEmpty()) { 169 assertTrue(ellipsized.endsWith(" more")); 170 // Test that the number of separators (which equals the number of output elements), 171 // plus the number output before more always equals the number of original elements. 172 final int lastSpace = ellipsized.lastIndexOf(' '); 173 final int penultimateSpace = ellipsized.lastIndexOf(' ', lastSpace - 1); 174 assertEquals(',', ellipsized.charAt(penultimateSpace - 1)); 175 final String moreCountString = ellipsized.substring( 176 penultimateSpace + 1, lastSpace); 177 final int moreCount = (moreCountString.equals("one")) 178 ? 1 : Integer.parseInt(moreCountString); 179 final int commaCount = countChars(ellipsized, ','); 180 assertEquals(fullList.size(), commaCount + moreCount); 181 } 182 } 183 } 184 185 @Test 186 @IgnoreUnderRavenwood(blockedBy = Resources.class) testListEllipsize_rtl()187 public void testListEllipsize_rtl() { 188 final Resources res = mContext.getResources(); 189 final Configuration newConfig = new Configuration(res.getConfiguration()); 190 191 // save the locales and set them to just Arabic 192 final LocaleList previousLocales = newConfig.getLocales(); 193 newConfig.setLocales(LocaleList.forLanguageTags("ar")); 194 res.updateConfiguration(newConfig, null); 195 196 try { 197 final TextPaint paint = new TextPaint(); 198 // "one more" for 1, else "%d more" 199 final int moreId = mContext.getResources().getIdentifier("list_ellipsize_test", 200 "plurals", "android.text.cts"); 201 202 final String RLM = "\u200F"; 203 final String LRE = "\u202A"; 204 final String PDF = "\u202C"; 205 206 final List fullList = Arrays.asList("A", "B"); 207 final String separator = ", "; 208 final String expectedString = 209 RLM + LRE + "A" + PDF + RLM + ", " + RLM + LRE + "B" + PDF + RLM; 210 final float enoughWidth = paint.measureText(expectedString); 211 212 assertEquals(expectedString, 213 TextUtils.listEllipsize(mContext, fullList, separator, paint, enoughWidth, 214 moreId).toString()); 215 } finally { 216 // Restore the original locales 217 newConfig.setLocales(previousLocales); 218 res.updateConfiguration(newConfig, null); 219 } 220 } 221 222 @Test 223 @IgnoreUnderRavenwood(blockedBy = TextPaint.class) testCommaEllipsize()224 public void testCommaEllipsize() { 225 TextPaint p = new TextPaint(); 226 String text = "long, string, to, truncate"; 227 228 float textWidth = p.measureText("long, 3 plus"); 229 // avail is shorter than text width for only one item plus the appropriate ellipsis. 230 // issue 1688347, the expected result for this case does not be described 231 // in the javadoc of commaEllipsize(). 232 assertEquals("", 233 TextUtils.commaEllipsize(text, p, textWidth - 1.4f, "plus 1", "%d plus").toString()); 234 // avail is long enough for only one item plus the appropriate ellipsis. 235 assertEquals("long, 3 plus", 236 TextUtils.commaEllipsize(text, p, textWidth, "plus 1", "%d plus").toString()); 237 238 // avail is long enough for two item plus the appropriate ellipsis. 239 textWidth = p.measureText("long, string, 2 more"); 240 assertEquals("long, string, 2 more", 241 TextUtils.commaEllipsize(text, p, textWidth, "more 1", "%d more").toString()); 242 243 // avail is long enough for the whole sentence. 244 textWidth = p.measureText("long, string, to, truncate"); 245 assertEquals("long, string, to, truncate", 246 TextUtils.commaEllipsize(text, p, textWidth, "more 1", "%d more").toString()); 247 248 // the sentence is extended, avail is NOT long enough for the whole sentence. 249 assertEquals("long, string, to, more 1", TextUtils.commaEllipsize( 250 text + "-extended", p, textWidth, "more 1", "%d more").toString()); 251 252 // exceptional value 253 assertEquals("", TextUtils.commaEllipsize(text, p, -1f, "plus 1", "%d plus").toString()); 254 255 assertEquals(text, TextUtils.commaEllipsize( 256 text, p, Float.MAX_VALUE, "more 1", "%d more").toString()); 257 258 assertEquals("long, string, to, null", TextUtils.commaEllipsize( 259 text + "-extended", p, textWidth, null, "%d more").toString()); 260 261 try { 262 TextUtils.commaEllipsize(null, p, textWidth, "plus 1", "%d plus"); 263 fail("Should throw NullPointerException"); 264 } catch (NullPointerException e) { 265 // issue 1688347, not clear what is supposed to happen if the text to truncate is null. 266 } 267 268 try { 269 TextUtils.commaEllipsize(text, null, textWidth, "plus 1", "%d plus"); 270 fail("Should throw NullPointerException"); 271 } catch (NullPointerException e) { 272 // issue 1688347, not clear what is supposed to happen if TextPaint is null. 273 } 274 } 275 276 @Test 277 @IgnoreUnderRavenwood(blockedBy = SpannableString.class) testConcat()278 public void testConcat() { 279 assertEquals("", TextUtils.concat().toString()); 280 281 assertEquals("first", TextUtils.concat("first").toString()); 282 283 assertEquals("first, second", TextUtils.concat("first", ", ", "second").toString()); 284 285 SpannableString string1 = new SpannableString("first"); 286 SpannableString string2 = new SpannableString("second"); 287 final String url = "www.test_url.com"; 288 URLSpan urlSpan = new URLSpan(url); 289 string1.setSpan(urlSpan, 0, string1.length() - 1, Spanned.SPAN_INCLUSIVE_INCLUSIVE); 290 BackgroundColorSpan bgColorSpan = new BackgroundColorSpan(Color.GREEN); 291 string2.setSpan(bgColorSpan, 0, string2.length() - 1, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); 292 293 final String comma = ", "; 294 Spanned strResult = (Spanned) TextUtils.concat(string1, comma, string2); 295 assertEquals(string1.toString() + comma + string2.toString(), strResult.toString()); 296 Object spans[] = strResult.getSpans(0, strResult.length(), Object.class); 297 assertEquals(2, spans.length); 298 assertTrue(spans[0] instanceof URLSpan); 299 assertEquals(url, ((URLSpan) spans[0]).getURL()); 300 assertTrue(spans[1] instanceof BackgroundColorSpan); 301 assertEquals(Color.GREEN, ((BackgroundColorSpan) spans[1]).getBackgroundColor()); 302 assertEquals(0, strResult.getSpanStart(urlSpan)); 303 assertEquals(string1.length() - 1, strResult.getSpanEnd(urlSpan)); 304 assertEquals(string1.length() + comma.length(), strResult.getSpanStart(bgColorSpan)); 305 assertEquals(strResult.length() - 1, strResult.getSpanEnd(bgColorSpan)); 306 307 assertEquals(string1, TextUtils.concat(string1)); 308 309 assertEquals(null, TextUtils.concat((CharSequence) null)); 310 } 311 312 @Test(expected = NullPointerException.class) 313 @IgnoreUnderRavenwood(blockedBy = SpannableString.class) testConcat_NullArray()314 public void testConcat_NullArray() { 315 TextUtils.concat((CharSequence[]) null); 316 } 317 318 @Test 319 @IgnoreUnderRavenwood(blockedBy = SpannableString.class) testConcat_NullParameters()320 public void testConcat_NullParameters() { 321 assertEquals("nullA", TextUtils.concat(null, "A")); 322 assertEquals("Anull", TextUtils.concat("A", null)); 323 assertEquals("AnullB", TextUtils.concat("A", null, "B")); 324 325 final SpannableString piece = new SpannableString("A"); 326 final Object span = new Object(); 327 piece.setSpan(span, 0, piece.length(), Spanned.SPAN_INCLUSIVE_EXCLUSIVE); 328 final Spanned result = (Spanned) TextUtils.concat(piece, null); 329 assertEquals("Anull", result.toString()); 330 final Object[] spans = result.getSpans(0, result.length(), Object.class); 331 assertEquals(1, spans.length); 332 assertSame(span, spans[0]); 333 assertEquals(0, result.getSpanStart(spans[0])); 334 assertEquals(piece.length(), result.getSpanEnd(spans[0])); 335 } 336 337 @Test 338 @IgnoreUnderRavenwood(blockedBy = SpannableString.class) testConcat_twoParagraphSpans()339 public void testConcat_twoParagraphSpans() { 340 // Two paragraph spans. The first will get extended to cover the whole string and the second 341 // will be dropped. 342 final SpannableString string1 = new SpannableString("a"); 343 final SpannableString string2 = new SpannableString("b"); 344 final Object span1 = new Object(); 345 final Object span2 = new Object(); 346 string1.setSpan(span1, 0, string1.length(), Spanned.SPAN_PARAGRAPH); 347 string2.setSpan(span2, 0, string2.length(), Spanned.SPAN_PARAGRAPH); 348 349 final Spanned result = (Spanned) TextUtils.concat(string1, string2); 350 assertEquals("ab", result.toString()); 351 final Object[] spans = result.getSpans(0, result.length(), Object.class); 352 assertEquals(1, spans.length); 353 assertSame(span1, spans[0]); 354 assertEquals(0, result.getSpanStart(spans[0])); 355 assertEquals(result.length(), result.getSpanEnd(spans[0])); 356 } 357 358 @Test 359 @IgnoreUnderRavenwood(blockedBy = SpannableString.class) testConcat_oneParagraphSpanAndOneInclusiveSpan()360 public void testConcat_oneParagraphSpanAndOneInclusiveSpan() { 361 // One paragraph span and one double-inclusive span. The first will get extended to cover 362 // the whole string and the second will be kept. 363 final SpannableString string1 = new SpannableString("a"); 364 final SpannableString string2 = new SpannableString("b"); 365 final Object span1 = new Object(); 366 final Object span2 = new Object(); 367 string1.setSpan(span1, 0, string1.length(), Spanned.SPAN_PARAGRAPH); 368 string2.setSpan(span2, 0, string2.length(), Spanned.SPAN_INCLUSIVE_INCLUSIVE); 369 370 final Spanned result = (Spanned) TextUtils.concat(string1, string2); 371 assertEquals("ab", result.toString()); 372 final Object[] spans = result.getSpans(0, result.length(), Object.class); 373 assertEquals(2, spans.length); 374 assertSame(span1, spans[0]); 375 assertEquals(0, result.getSpanStart(spans[0])); 376 assertEquals(result.length(), result.getSpanEnd(spans[0])); 377 assertSame(span2, spans[1]); 378 assertEquals(string1.length(), result.getSpanStart(spans[1])); 379 assertEquals(result.length(), result.getSpanEnd(spans[1])); 380 } 381 382 @Test 383 @IgnoreUnderRavenwood(blockedBy = SpannableString.class) testCopySpansFrom()384 public void testCopySpansFrom() { 385 Object[] spans; 386 String text = "content"; 387 SpannableString source1 = new SpannableString(text); 388 int midPos = source1.length() / 2; 389 final String url = "www.test_url.com"; 390 URLSpan urlSpan = new URLSpan(url); 391 source1.setSpan(urlSpan, 0, midPos, Spanned.SPAN_INCLUSIVE_INCLUSIVE); 392 BackgroundColorSpan bgColorSpan = new BackgroundColorSpan(Color.GREEN); 393 source1.setSpan(bgColorSpan, midPos - 1, 394 source1.length() - 1, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); 395 396 // normal test 397 SpannableString dest1 = new SpannableString(text); 398 TextUtils.copySpansFrom(source1, 0, source1.length(), Object.class, dest1, 0); 399 spans = dest1.getSpans(0, dest1.length(), Object.class); 400 assertEquals(2, spans.length); 401 assertTrue(spans[0] instanceof URLSpan); 402 assertEquals(url, ((URLSpan) spans[0]).getURL()); 403 assertTrue(spans[1] instanceof BackgroundColorSpan); 404 assertEquals(Color.GREEN, ((BackgroundColorSpan) spans[1]).getBackgroundColor()); 405 assertEquals(0, dest1.getSpanStart(urlSpan)); 406 assertEquals(midPos, dest1.getSpanEnd(urlSpan)); 407 assertEquals(Spanned.SPAN_INCLUSIVE_INCLUSIVE, dest1.getSpanFlags(urlSpan)); 408 assertEquals(midPos - 1, dest1.getSpanStart(bgColorSpan)); 409 assertEquals(source1.length() - 1, dest1.getSpanEnd(bgColorSpan)); 410 assertEquals(Spanned.SPAN_EXCLUSIVE_EXCLUSIVE, dest1.getSpanFlags(bgColorSpan)); 411 412 SpannableString source2 = new SpannableString(text); 413 source2.setSpan(urlSpan, 0, source2.length() - 1, Spanned.SPAN_EXCLUSIVE_INCLUSIVE); 414 SpannableString dest2 = new SpannableString(text); 415 TextUtils.copySpansFrom(source2, 0, source2.length(), Object.class, dest2, 0); 416 spans = dest2.getSpans(0, dest2.length(), Object.class); 417 assertEquals(1, spans.length); 418 assertTrue(spans[0] instanceof URLSpan); 419 assertEquals(url, ((URLSpan) spans[0]).getURL()); 420 assertEquals(0, dest2.getSpanStart(urlSpan)); 421 assertEquals(source2.length() - 1, dest2.getSpanEnd(urlSpan)); 422 assertEquals(Spanned.SPAN_EXCLUSIVE_INCLUSIVE, dest2.getSpanFlags(urlSpan)); 423 424 SpannableString dest3 = new SpannableString(text); 425 TextUtils.copySpansFrom(source2, 0, source2.length(), BackgroundColorSpan.class, dest3, 0); 426 spans = dest3.getSpans(0, dest3.length(), Object.class); 427 assertEquals(0, spans.length); 428 TextUtils.copySpansFrom(source2, 0, source2.length(), URLSpan.class, dest3, 0); 429 spans = dest3.getSpans(0, dest3.length(), Object.class); 430 assertEquals(1, spans.length); 431 432 SpannableString dest4 = new SpannableString("short"); 433 try { 434 TextUtils.copySpansFrom(source2, 0, source2.length(), Object.class, dest4, 0); 435 fail("Should throw IndexOutOfBoundsException"); 436 } catch (IndexOutOfBoundsException e) { 437 // expected 438 } 439 TextUtils.copySpansFrom(source2, 0, dest4.length(), Object.class, dest4, 0); 440 spans = dest4.getSpans(0, dest4.length(), Object.class); 441 assertEquals(1, spans.length); 442 assertEquals(0, dest4.getSpanStart(spans[0])); 443 // issue 1688347, not clear the expected result when 'start ~ end' only 444 // covered a part of the span. 445 assertEquals(dest4.length(), dest4.getSpanEnd(spans[0])); 446 447 SpannableString dest5 = new SpannableString("longer content"); 448 TextUtils.copySpansFrom(source2, 0, source2.length(), Object.class, dest5, 0); 449 spans = dest5.getSpans(0, 1, Object.class); 450 assertEquals(1, spans.length); 451 452 dest5 = new SpannableString("longer content"); 453 TextUtils.copySpansFrom(source2, 0, source2.length(), Object.class, dest5, 2); 454 spans = dest5.getSpans(0, 1, Object.class); 455 assertEquals(0, spans.length); 456 spans = dest5.getSpans(2, dest5.length(), Object.class); 457 assertEquals(1, spans.length); 458 try { 459 TextUtils.copySpansFrom(source2, 0, source2.length(), 460 Object.class, dest5, dest5.length() - source2.length() + 2); 461 fail("Should throw IndexOutOfBoundsException"); 462 } catch (IndexOutOfBoundsException e) { 463 // expected 464 } 465 466 // issue 1688347, no javadoc about the expected behavior of the exceptional argument. 467 // exceptional source start 468 SpannableString dest6 = new SpannableString("exceptional test"); 469 TextUtils.copySpansFrom(source2, -1, source2.length(), Object.class, dest6, 0); 470 spans = dest6.getSpans(0, dest6.length(), Object.class); 471 assertEquals(1, spans.length); 472 dest6 = new SpannableString("exceptional test"); 473 TextUtils.copySpansFrom(source2, Integer.MAX_VALUE, source2.length() - 1, 474 Object.class, dest6, 0); 475 spans = dest6.getSpans(0, dest6.length(), Object.class); 476 assertEquals(0, spans.length); 477 478 // exceptional source end 479 dest6 = new SpannableString("exceptional test"); 480 TextUtils.copySpansFrom(source2, 0, -1, Object.class, dest6, 0); 481 spans = dest6.getSpans(0, dest6.length(), Object.class); 482 assertEquals(0, spans.length); 483 TextUtils.copySpansFrom(source2, 0, Integer.MAX_VALUE, Object.class, dest6, 0); 484 spans = dest6.getSpans(0, dest6.length(), Object.class); 485 assertEquals(1, spans.length); 486 487 // exceptional class kind 488 dest6 = new SpannableString("exceptional test"); 489 TextUtils.copySpansFrom(source2, 0, source2.length(), null, dest6, 0); 490 spans = dest6.getSpans(0, dest6.length(), Object.class); 491 assertEquals(1, spans.length); 492 493 // exceptional destination offset 494 dest6 = new SpannableString("exceptional test"); 495 try { 496 TextUtils.copySpansFrom(source2, 0, source2.length(), Object.class, dest6, -1); 497 fail("Should throw IndexOutOfBoundsException"); 498 } catch (IndexOutOfBoundsException e) { 499 // expect 500 } 501 try { 502 TextUtils.copySpansFrom(source2, 0, source2.length(), 503 Object.class, dest6, Integer.MAX_VALUE); 504 fail("Should throw IndexOutOfBoundsException"); 505 } catch (IndexOutOfBoundsException e) { 506 // expect 507 } 508 509 // exceptional source 510 try { 511 TextUtils.copySpansFrom(null, 0, source2.length(), Object.class, dest6, 0); 512 fail("Should throw NullPointerException"); 513 } catch (NullPointerException e) { 514 // expect 515 } 516 517 // exceptional destination 518 try { 519 TextUtils.copySpansFrom(source2, 0, source2.length(), Object.class, null, 0); 520 fail("Should throw NullPointerException"); 521 } catch (NullPointerException e) { 522 // expect 523 } 524 } 525 526 @Test 527 @IgnoreUnderRavenwood(blockedBy = TextPaint.class) testEllipsize()528 public void testEllipsize() { 529 TextPaint p = new TextPaint(); 530 531 // turn off kerning. with kerning enabled, different methods of measuring the same text 532 // produce different results. 533 p.setFlags(p.getFlags() & ~p.DEV_KERN_TEXT_FLAG); 534 535 CharSequence text = "long string to truncate"; 536 537 float textWidth = p.measureText(mEllipsis) + p.measureText("uncate"); 538 assertEquals(mEllipsis + "uncate", 539 TextUtils.ellipsize(text, p, textWidth, TruncateAt.START).toString()); 540 541 textWidth = p.measureText("long str") + p.measureText(mEllipsis); 542 assertEquals("long str" + mEllipsis, 543 TextUtils.ellipsize(text, p, textWidth, TruncateAt.END).toString()); 544 545 textWidth = p.measureText("long") + p.measureText(mEllipsis) + p.measureText("ate"); 546 assertEquals("long" + mEllipsis + "ate", 547 TextUtils.ellipsize(text, p, textWidth, TruncateAt.MIDDLE).toString()); 548 549 // issue 1688347, ellipsize() is not defined for TruncateAt.MARQUEE. 550 // In the code it looks like this does the same as MIDDLE. 551 // In other methods, MARQUEE is equivalent to END, except for the first line. 552 assertEquals("long" + mEllipsis + "ate", 553 TextUtils.ellipsize(text, p, textWidth, TruncateAt.MARQUEE).toString()); 554 555 textWidth = p.measureText(mEllipsis); 556 assertEquals("", TextUtils.ellipsize(text, p, textWidth, TruncateAt.END).toString()); 557 assertEquals("", TextUtils.ellipsize(text, p, textWidth - 1, TruncateAt.END).toString()); 558 assertEquals("", TextUtils.ellipsize(text, p, -1f, TruncateAt.END).toString()); 559 assertEquals(text, 560 TextUtils.ellipsize(text, p, Float.MAX_VALUE, TruncateAt.END).toString()); 561 562 assertEquals("", TextUtils.ellipsize(text, p, textWidth, TruncateAt.START).toString()); 563 assertEquals("", TextUtils.ellipsize(text, p, textWidth, TruncateAt.MIDDLE).toString()); 564 565 try { 566 TextUtils.ellipsize(text, null, textWidth, TruncateAt.MIDDLE); 567 fail("Should throw NullPointerException"); 568 } catch (NullPointerException e) { 569 // expected 570 } 571 572 try { 573 TextUtils.ellipsize(null, p, textWidth, TruncateAt.MIDDLE); 574 fail("Should throw NullPointerException"); 575 } catch (NullPointerException e) { 576 // expected 577 } 578 } 579 580 @Test 581 @IgnoreUnderRavenwood(blockedBy = TextPaint.class) testEllipsize_emoji()582 public void testEllipsize_emoji() { 583 // 2 family emojis (11 code units + 11 code units). 584 final String text = "\uD83D\uDC68\u200D\uD83D\uDC69\u200D\uD83D\uDC67\u200D\uD83D\uDC66" 585 + "\uD83D\uDC68\u200D\uD83D\uDC69\u200D\uD83D\uDC67\u200D\uD83D\uDC66"; 586 587 final TextPaint p = new TextPaint(); 588 final float width = p.measureText(text); 589 590 final TextUtils.TruncateAt[] kinds = {TextUtils.TruncateAt.START, 591 TextUtils.TruncateAt.MIDDLE, TextUtils.TruncateAt.END}; 592 for (final TextUtils.TruncateAt kind : kinds) { 593 for (int i = 0; i <= 8; i++) { 594 float avail = width * i / 7.0f; 595 final String out = TextUtils.ellipsize(text, p, avail, kind).toString(); 596 assertTrue("kind: " + kind + ", avail: " + avail + ", out length: " + out.length(), 597 out.length() == text.length() 598 || out.length() == text.length() / 2 + 1 599 || out.length() == 0); 600 } 601 } 602 } 603 604 @Test 605 @IgnoreUnderRavenwood(blockedBy = TextPaint.class) testEllipsizeCallback()606 public void testEllipsizeCallback() { 607 TextPaint p = new TextPaint(); 608 609 // turn off kerning. with kerning enabled, different methods of measuring the same text 610 // produce different results. 611 p.setFlags(p.getFlags() & ~p.DEV_KERN_TEXT_FLAG); 612 613 TextUtils.EllipsizeCallback callback = new TextUtils.EllipsizeCallback() { 614 public void ellipsized(final int start, final int end) { 615 mStart = start; 616 mEnd = end; 617 } 618 }; 619 620 String text = "long string to truncate"; 621 622 // TruncateAt.START, does not specify preserveLength 623 resetRange(); 624 float textWidth = p.measureText(mEllipsis + "uncate"); 625 assertEquals(mEllipsis + "uncate", 626 TextUtils.ellipsize(text, p, textWidth, TruncateAt.START, false, 627 callback).toString()); 628 assertEquals(0, mStart); 629 assertEquals(text.length() - "uncate".length(), mEnd); 630 631 // TruncateAt.START, specify preserveLength 632 resetRange(); 633 int ellipsisNum = text.length() - "uncate".length(); 634 assertEquals(getBlankString(true, ellipsisNum) + "uncate", 635 TextUtils.ellipsize(text, p, textWidth, TruncateAt.START, true, 636 callback).toString()); 637 assertEquals(0, mStart); 638 assertEquals(text.length() - "uncate".length(), mEnd); 639 640 // TruncateAt.END, specify preserveLength 641 resetRange(); 642 textWidth = p.measureText("long str") + p.measureText(mEllipsis); 643 ellipsisNum = text.length() - "long str".length(); 644 assertEquals("long str" + getBlankString(true, ellipsisNum), 645 TextUtils.ellipsize(text, p, textWidth, TruncateAt.END, true, callback).toString()); 646 assertEquals("long str".length(), mStart); 647 assertEquals(text.length(), mEnd); 648 649 // TruncateAt.MIDDLE, specify preserveLength 650 resetRange(); 651 textWidth = p.measureText("long" + mEllipsis + "ate"); 652 ellipsisNum = text.length() - "long".length() - "ate".length(); 653 assertEquals("long" + getBlankString(true, ellipsisNum) + "ate", 654 TextUtils.ellipsize(text, p, textWidth, TruncateAt.MIDDLE, true, 655 callback).toString()); 656 assertEquals("long".length(), mStart); 657 assertEquals(text.length() - "ate".length(), mEnd); 658 659 // TruncateAt.MIDDLE, specify preserveLength, but does not specify callback. 660 resetRange(); 661 assertEquals("long" + getBlankString(true, ellipsisNum) + "ate", 662 TextUtils.ellipsize(text, p, textWidth, TruncateAt.MIDDLE, true, 663 null).toString()); 664 assertEquals(-1, mStart); 665 assertEquals(-1, mEnd); 666 667 // TruncateAt.MARQUEE, specify preserveLength 668 // issue 1688347, ellipsize() is not defined for TruncateAt.MARQUEE. 669 // In the code it looks like this does the same as MIDDLE. 670 // In other methods, MARQUEE is equivalent to END, except for the first line. 671 resetRange(); 672 textWidth = p.measureText("long" + mEllipsis + "ate"); 673 ellipsisNum = text.length() - "long".length() - "ate".length(); 674 assertEquals("long" + getBlankString(true, ellipsisNum) + "ate", 675 TextUtils.ellipsize(text, p, textWidth, TruncateAt.MARQUEE, true, 676 callback).toString()); 677 assertEquals("long".length(), mStart); 678 assertEquals(text.length() - "ate".length(), mEnd); 679 680 // avail is not long enough for ELLIPSIS, and preserveLength is specified. 681 resetRange(); 682 textWidth = p.measureText(mEllipsis); 683 assertEquals(getBlankString(false, text.length()), 684 TextUtils.ellipsize(text, p, textWidth - 1f, TruncateAt.END, true, 685 callback).toString()); 686 assertEquals(0, mStart); 687 assertEquals(text.length(), mEnd); 688 689 // avail is not long enough for ELLIPSIS, and preserveLength doesn't be specified. 690 resetRange(); 691 assertEquals("", 692 TextUtils.ellipsize(text, p, textWidth - 1f, TruncateAt.END, false, 693 callback).toString()); 694 assertEquals(0, mStart); 695 assertEquals(text.length(), mEnd); 696 697 // avail is long enough for ELLIPSIS, and preserveLength is specified. 698 resetRange(); 699 assertEquals(getBlankString(false, text.length()), 700 TextUtils.ellipsize(text, p, textWidth, TruncateAt.END, true, callback).toString()); 701 assertEquals(0, mStart); 702 assertEquals(text.length(), mEnd); 703 704 // avail is long enough for ELLIPSIS, and preserveLength doesn't be specified. 705 resetRange(); 706 assertEquals("", 707 TextUtils.ellipsize(text, p, textWidth, TruncateAt.END, false, 708 callback).toString()); 709 assertEquals(0, mStart); 710 assertEquals(text.length(), mEnd); 711 712 // avail is long enough for the whole sentence. 713 resetRange(); 714 assertEquals(text, 715 TextUtils.ellipsize(text, p, Float.MAX_VALUE, TruncateAt.END, true, 716 callback).toString()); 717 assertEquals(0, mStart); 718 assertEquals(0, mEnd); 719 720 textWidth = p.measureText("long str" + mEllipsis); 721 try { 722 TextUtils.ellipsize(text, null, textWidth, TruncateAt.END, true, callback); 723 } catch (NullPointerException e) { 724 // expected 725 } 726 727 try { 728 TextUtils.ellipsize(null, p, textWidth, TruncateAt.END, true, callback); 729 } catch (NullPointerException e) { 730 // expected 731 } 732 } 733 734 /** 735 * Get a blank string which is filled up by '\uFEFF'. 736 * 737 * @param isNeedStart - boolean whether need to start with char '\u2026' in the string. 738 * @param len - int length of string. 739 * @return a blank string which is filled up by '\uFEFF'. 740 */ getBlankString(boolean isNeedStart, int len)741 private static String getBlankString(boolean isNeedStart, int len) { 742 StringBuilder buf = new StringBuilder(); 743 744 int i = 0; 745 if (isNeedStart) { 746 buf.append('\u2026'); 747 i++; 748 } 749 for (; i < len; i++) { 750 buf.append('\uFEFF'); 751 } 752 753 return buf.toString(); 754 } 755 756 @Test 757 @IgnoreUnderRavenwood(blockedBy = SpannableString.class) testEquals()758 public void testEquals() { 759 // compare with itself. 760 // String is a subclass of CharSequence and overrides equals(). 761 String string = "same object"; 762 assertTrue(TextUtils.equals(string, string)); 763 764 // SpannableString is a subclass of CharSequence and does NOT override equals(). 765 SpannableString spanString = new SpannableString("same object"); 766 final String url = "www.test_url.com"; 767 spanString.setSpan(new URLSpan(url), 0, spanString.length(), 768 Spanned.SPAN_INCLUSIVE_INCLUSIVE); 769 assertTrue(TextUtils.equals(spanString, spanString)); 770 771 // compare with other objects which have same content. 772 assertTrue(TextUtils.equals("different object", "different object")); 773 774 SpannableString urlSpanString = new SpannableString("same content"); 775 SpannableString bgColorSpanString = new SpannableString( 776 "same content"); 777 URLSpan urlSpan = new URLSpan(url); 778 urlSpanString.setSpan(urlSpan, 0, urlSpanString.length(), 779 Spanned.SPAN_INCLUSIVE_INCLUSIVE); 780 BackgroundColorSpan bgColorSpan = new BackgroundColorSpan(Color.GREEN); 781 bgColorSpanString.setSpan(bgColorSpan, 0, bgColorSpanString.length(), 782 Spanned.SPAN_INCLUSIVE_INCLUSIVE); 783 784 assertTrue(TextUtils.equals(bgColorSpanString, urlSpanString)); 785 786 // compare with other objects which have different content. 787 assertFalse(TextUtils.equals("different content A", "different content B")); 788 assertFalse(TextUtils.equals(spanString, urlSpanString)); 789 assertFalse(TextUtils.equals(spanString, bgColorSpanString)); 790 791 // compare with null 792 assertTrue(TextUtils.equals(null, null)); 793 assertFalse(TextUtils.equals(spanString, null)); 794 assertFalse(TextUtils.equals(null, string)); 795 } 796 797 @Test 798 @IgnoreUnderRavenwood(blockedBy = Resources.class) testExpandTemplate()799 public void testExpandTemplate() { 800 // ^1 at the start of template string. 801 assertEquals("value1 template to be expanded", 802 TextUtils.expandTemplate("^1 template to be expanded", "value1").toString()); 803 // ^1 at the end of template string. 804 assertEquals("template to be expanded value1", 805 TextUtils.expandTemplate("template to be expanded ^1", "value1").toString()); 806 // ^1 in the middle of template string. 807 assertEquals("template value1 to be expanded", 808 TextUtils.expandTemplate("template ^1 to be expanded", "value1").toString()); 809 // ^1 followed by a '0' 810 assertEquals("template value10 to be expanded", 811 TextUtils.expandTemplate("template ^10 to be expanded", "value1").toString()); 812 // ^1 followed by a 'a' 813 assertEquals("template value1a to be expanded", 814 TextUtils.expandTemplate("template ^1a to be expanded", "value1").toString()); 815 // no ^1 816 assertEquals("template ^a to be expanded", 817 TextUtils.expandTemplate("template ^a to be expanded", "value1").toString()); 818 assertEquals("template to be expanded", 819 TextUtils.expandTemplate("template to be expanded", "value1").toString()); 820 // two consecutive ^ in the input to produce a single ^ in the output. 821 assertEquals("template ^ to be expanded", 822 TextUtils.expandTemplate("template ^^ to be expanded", "value1").toString()); 823 // two ^ with a space in the middle. 824 assertEquals("template ^ ^ to be expanded", 825 TextUtils.expandTemplate("template ^ ^ to be expanded", "value1").toString()); 826 // ^1 follow a '^' 827 assertEquals("template ^1 to be expanded", 828 TextUtils.expandTemplate("template ^^1 to be expanded", "value1").toString()); 829 // ^1 followed by a '^' 830 assertEquals("template value1^ to be expanded", 831 TextUtils.expandTemplate("template ^1^ to be expanded", "value1").toString()); 832 833 // 9 replacement values 834 final int MAX_SUPPORTED_VALUES_NUM = 9; 835 CharSequence values[] = createCharSequenceArray(MAX_SUPPORTED_VALUES_NUM); 836 String expected = "value1 value2 template value3 value4 to value5 value6" + 837 " be value7 value8 expanded value9"; 838 String template = "^1 ^2 template ^3 ^4 to ^5 ^6 be ^7 ^8 expanded ^9"; 839 assertEquals(expected, TextUtils.expandTemplate(template, values).toString()); 840 841 // only up to 9 replacement values are supported 842 values = createCharSequenceArray(MAX_SUPPORTED_VALUES_NUM + 1); 843 try { 844 TextUtils.expandTemplate(template, values); 845 fail("Should throw IllegalArgumentException!"); 846 } catch (IllegalArgumentException e) { 847 // expect 848 } 849 } 850 851 @Test(expected=IllegalArgumentException.class) 852 @IgnoreUnderRavenwood(blockedBy = Resources.class) testExpandTemplateCaret0WithValue()853 public void testExpandTemplateCaret0WithValue() { 854 // template string is ^0 855 TextUtils.expandTemplate("template ^0 to be expanded", "value1"); 856 } 857 858 @Test(expected=IllegalArgumentException.class) 859 @IgnoreUnderRavenwood(blockedBy = Resources.class) testExpandTemplateCaret0NoValues()860 public void testExpandTemplateCaret0NoValues() { 861 // template string is ^0 862 TextUtils.expandTemplate("template ^0 to be expanded"); 863 } 864 865 @Test(expected=IllegalArgumentException.class) 866 @IgnoreUnderRavenwood(blockedBy = Resources.class) testExpandTemplateNotEnoughValues()867 public void testExpandTemplateNotEnoughValues() { 868 // the template requests 2 values but only 1 is provided 869 TextUtils.expandTemplate("template ^2 to be expanded", "value1"); 870 } 871 872 @Test(expected=NullPointerException.class) 873 @IgnoreUnderRavenwood(blockedBy = Resources.class) testExpandTemplateNullValues()874 public void testExpandTemplateNullValues() { 875 // values is null 876 TextUtils.expandTemplate("template ^2 to be expanded", (CharSequence[]) null); 877 } 878 879 @Test(expected=IllegalArgumentException.class) 880 @IgnoreUnderRavenwood(blockedBy = Resources.class) testExpandTemplateNotEnoughValuesAndFirstIsNull()881 public void testExpandTemplateNotEnoughValuesAndFirstIsNull() { 882 // the template requests 2 values but only one null value is provided 883 TextUtils.expandTemplate("template ^2 to be expanded", (CharSequence) null); 884 } 885 886 @Test(expected=NullPointerException.class) 887 @IgnoreUnderRavenwood(blockedBy = Resources.class) testExpandTemplateAllValuesAreNull()888 public void testExpandTemplateAllValuesAreNull() { 889 // the template requests 2 values and 2 values is provided, but all values are null. 890 TextUtils.expandTemplate("template ^2 to be expanded", 891 (CharSequence) null, (CharSequence) null); 892 } 893 894 @Test(expected=IllegalArgumentException.class) 895 @IgnoreUnderRavenwood(blockedBy = Resources.class) testExpandTemplateNoValues()896 public void testExpandTemplateNoValues() { 897 // the template requests 2 values but no value is provided. 898 TextUtils.expandTemplate("template ^2 to be expanded"); 899 } 900 901 @Test(expected=NullPointerException.class) 902 @IgnoreUnderRavenwood(blockedBy = Resources.class) testExpandTemplateNullTemplate()903 public void testExpandTemplateNullTemplate() { 904 // template is null 905 TextUtils.expandTemplate(null, "value1"); 906 } 907 908 /** 909 * Create a char sequence array with the specified length 910 * @param len the length of the array 911 * @return The char sequence array with the specified length. 912 * The value of each item is "value[index+1]" 913 */ createCharSequenceArray(int len)914 private static CharSequence[] createCharSequenceArray(int len) { 915 CharSequence array[] = new CharSequence[len]; 916 917 for (int i = 0; i < len; i++) { 918 array[i] = "value" + (i + 1); 919 } 920 921 return array; 922 } 923 924 @Test testGetChars()925 public void testGetChars() { 926 char[] destOriginal = "destination".toCharArray(); 927 char[] destResult = destOriginal.clone(); 928 929 // check whether GetChars.getChars() is called and with the proper parameters. 930 MockGetChars mockGetChars = new MockGetChars(); 931 int start = 1; 932 int end = destResult.length; 933 int destOff = 2; 934 TextUtils.getChars(mockGetChars, start, end, destResult, destOff); 935 assertTrue(mockGetChars.hasCalledGetChars()); 936 assertEquals(start, mockGetChars.ReadGetCharsParams().start); 937 assertEquals(end, mockGetChars.ReadGetCharsParams().end); 938 assertEquals(destResult, mockGetChars.ReadGetCharsParams().dest); 939 assertEquals(destOff, mockGetChars.ReadGetCharsParams().destoff); 940 941 // use MockCharSequence to do the test includes corner cases. 942 MockCharSequence mockCharSequence = new MockCharSequence("source string mock"); 943 // get chars to place at the beginning of the destination except the latest one char. 944 destResult = destOriginal.clone(); 945 start = 0; 946 end = destResult.length - 1; 947 destOff = 0; 948 TextUtils.getChars(mockCharSequence, start, end, destResult, destOff); 949 // chars before end are copied from the mockCharSequence. 950 for (int i = 0; i < end - start; i++) { 951 assertEquals(mockCharSequence.charAt(start + i), destResult[destOff + i]); 952 } 953 // chars after end doesn't be changed. 954 for (int i = destOff + (end - start); i < destOriginal.length; i++) { 955 assertEquals(destOriginal[i], destResult[i]); 956 } 957 958 // get chars to place at the end of the destination except the earliest two chars. 959 destResult = destOriginal.clone(); 960 start = 0; 961 end = destResult.length - 2; 962 destOff = 2; 963 TextUtils.getChars(mockCharSequence, start, end, destResult, destOff); 964 // chars before start doesn't be changed. 965 for (int i = 0; i < destOff; i++) { 966 assertEquals(destOriginal[i], destResult[i]); 967 } 968 // chars after start are copied from the mockCharSequence. 969 for (int i = 0; i < end - start; i++) { 970 assertEquals(mockCharSequence.charAt(start + i), destResult[destOff + i]); 971 } 972 973 // get chars to place at the end of the destination except the earliest two chars 974 // and the latest one word. 975 destResult = destOriginal.clone(); 976 start = 1; 977 end = destResult.length - 2; 978 destOff = 0; 979 TextUtils.getChars(mockCharSequence, start, end, destResult, destOff); 980 for (int i = 0; i < destOff; i++) { 981 assertEquals(destOriginal[i], destResult[i]); 982 } 983 for (int i = 0; i < end - start; i++) { 984 assertEquals(mockCharSequence.charAt(start + i), destResult[destOff + i]); 985 } 986 for (int i = destOff + (end - start); i < destOriginal.length; i++) { 987 assertEquals(destOriginal[i], destResult[i]); 988 } 989 990 // get chars to place the whole of the destination 991 destResult = destOriginal.clone(); 992 start = 0; 993 end = destResult.length; 994 destOff = 0; 995 TextUtils.getChars(mockCharSequence, start, end, destResult, destOff); 996 for (int i = 0; i < end - start; i++) { 997 assertEquals(mockCharSequence.charAt(start + i), destResult[destOff + i]); 998 } 999 1000 // exceptional start. 1001 end = 2; 1002 destOff = 0; 1003 destResult = destOriginal.clone(); 1004 try { 1005 TextUtils.getChars(mockCharSequence, -1, end, destResult, destOff); 1006 fail("Should throw IndexOutOfBoundsException!"); 1007 } catch (IndexOutOfBoundsException e) { 1008 // expected 1009 } 1010 1011 destResult = destOriginal.clone(); 1012 TextUtils.getChars(mockCharSequence, Integer.MAX_VALUE, end, destResult, destOff); 1013 for (int i = 0; i < destResult.length; i++) { 1014 assertEquals(destOriginal[i], destResult[i]); 1015 } 1016 1017 // exceptional end. 1018 destResult = destOriginal.clone(); 1019 start = 0; 1020 destOff = 0; 1021 try { 1022 TextUtils.getChars(mockCharSequence, start, destResult.length + 1, destResult, destOff); 1023 fail("Should throw IndexOutOfBoundsException!"); 1024 } catch (IndexOutOfBoundsException e) { 1025 // expected 1026 } 1027 1028 destResult = destOriginal.clone(); 1029 TextUtils.getChars(mockCharSequence, start, -1, destResult, destOff); 1030 for (int i = 0; i < destResult.length; i++) { 1031 assertEquals(destOriginal[i], destResult[i]); 1032 } 1033 1034 // exceptional destOff. 1035 destResult = destOriginal.clone(); 1036 start = 0; 1037 end = 2; 1038 try { 1039 TextUtils.getChars(mockCharSequence, start, end, destResult, Integer.MAX_VALUE); 1040 fail("Should throw IndexOutOfBoundsException!"); 1041 } catch (IndexOutOfBoundsException e) { 1042 // expect 1043 } 1044 try { 1045 TextUtils.getChars(mockCharSequence, start, end, destResult, Integer.MIN_VALUE); 1046 fail("Should throw IndexOutOfBoundsException!"); 1047 } catch (IndexOutOfBoundsException e) { 1048 // expect 1049 } 1050 1051 // exceptional source 1052 start = 0; 1053 end = 2; 1054 destOff =0; 1055 try { 1056 TextUtils.getChars(null, start, end, destResult, destOff); 1057 fail("Should throw NullPointerException!"); 1058 } catch (NullPointerException e) { 1059 // expected 1060 } 1061 1062 // exceptional destination 1063 try { 1064 TextUtils.getChars(mockCharSequence, start, end, null, destOff); 1065 fail("Should throw NullPointerException!"); 1066 } catch (NullPointerException e) { 1067 // expected 1068 } 1069 } 1070 1071 /** 1072 * MockGetChars for test. 1073 */ 1074 private static class MockGetChars implements GetChars { 1075 private boolean mHasCalledGetChars; 1076 private GetCharsParams mGetCharsParams = new GetCharsParams(); 1077 1078 class GetCharsParams { 1079 int start; 1080 int end; 1081 char[] dest; 1082 int destoff; 1083 } 1084 hasCalledGetChars()1085 public boolean hasCalledGetChars() { 1086 return mHasCalledGetChars; 1087 } 1088 reset()1089 public void reset() { 1090 mHasCalledGetChars = false; 1091 } 1092 ReadGetCharsParams()1093 public GetCharsParams ReadGetCharsParams() { 1094 return mGetCharsParams; 1095 } 1096 getChars(int start, int end, char[] dest, int destoff)1097 public void getChars(int start, int end, char[] dest, int destoff) { 1098 mHasCalledGetChars = true; 1099 mGetCharsParams.start = start; 1100 mGetCharsParams.end = end; 1101 mGetCharsParams.dest = dest; 1102 mGetCharsParams.destoff = destoff; 1103 } 1104 charAt(int arg0)1105 public char charAt(int arg0) { 1106 return 0; 1107 } 1108 length()1109 public int length() { 1110 return 100; 1111 } 1112 subSequence(int arg0, int arg1)1113 public CharSequence subSequence(int arg0, int arg1) { 1114 return null; 1115 } 1116 } 1117 1118 /** 1119 * MockCharSequence for test. 1120 */ 1121 private static class MockCharSequence implements CharSequence { 1122 private char mText[]; 1123 MockCharSequence(String text)1124 public MockCharSequence(String text) { 1125 mText = text.toCharArray(); 1126 } 1127 charAt(int arg0)1128 public char charAt(int arg0) { 1129 if (arg0 >= 0 && arg0 < mText.length) { 1130 return mText[arg0]; 1131 } 1132 throw new IndexOutOfBoundsException(); 1133 } 1134 length()1135 public int length() { 1136 return mText.length; 1137 } 1138 subSequence(int arg0, int arg1)1139 public CharSequence subSequence(int arg0, int arg1) { 1140 return null; 1141 } 1142 } 1143 1144 @Test 1145 @IgnoreUnderRavenwood(blockedBy = SpannableString.class) testGetOffsetAfter()1146 public void testGetOffsetAfter() { 1147 // the first '\uD800' is index 9, the second 'uD800' is index 16 1148 // the '\uDBFF' is index 26 1149 final int POS_FIRST_D800 = 9; // the position of the first '\uD800'. 1150 final int POS_SECOND_D800 = 16; 1151 final int POS_FIRST_DBFF = 26; 1152 final int SUPPLEMENTARY_CHARACTERS_OFFSET = 2; // the offset for a supplementary characters 1153 final int NORMAL_CHARACTERS_OFFSET = 1; 1154 SpannableString text = new SpannableString( 1155 "string to\uD800\uDB00 get \uD800\uDC00 offset \uDBFF\uDFFF after"); 1156 assertEquals(0 + 1, TextUtils.getOffsetAfter(text, 0)); 1157 assertEquals(text.length(), TextUtils.getOffsetAfter(text, text.length())); 1158 assertEquals(text.length(), TextUtils.getOffsetAfter(text, text.length() - 1)); 1159 assertEquals(POS_FIRST_D800 + NORMAL_CHARACTERS_OFFSET, 1160 TextUtils.getOffsetAfter(text, POS_FIRST_D800)); 1161 assertEquals(POS_SECOND_D800 + SUPPLEMENTARY_CHARACTERS_OFFSET, 1162 TextUtils.getOffsetAfter(text, POS_SECOND_D800)); 1163 assertEquals(POS_FIRST_DBFF + SUPPLEMENTARY_CHARACTERS_OFFSET, 1164 TextUtils.getOffsetAfter(text, POS_FIRST_DBFF)); 1165 1166 // the CharSequence string has a span. 1167 ReplacementSpan mockReplacementSpan = mock(ReplacementSpan.class); 1168 when(mockReplacementSpan.getSize(any(), any(), anyInt(), anyInt(), any())).thenReturn(0); 1169 text.setSpan(mockReplacementSpan, POS_FIRST_D800 - 1, text.length() - 1, 1170 Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); 1171 assertEquals(text.length() - 1, TextUtils.getOffsetAfter(text, POS_FIRST_D800)); 1172 1173 try { 1174 TextUtils.getOffsetAfter(text, -1); 1175 fail("Should throw IndexOutOfBoundsException!"); 1176 } catch (IndexOutOfBoundsException e) { 1177 } 1178 1179 try { 1180 TextUtils.getOffsetAfter(text, Integer.MAX_VALUE); 1181 fail("Should throw IndexOutOfBoundsException!"); 1182 } catch (IndexOutOfBoundsException e) { 1183 } 1184 1185 try { 1186 TextUtils.getOffsetAfter(null, 0); 1187 fail("Should throw NullPointerException!"); 1188 } catch (NullPointerException e) { 1189 // expected 1190 } 1191 } 1192 1193 @Test 1194 @IgnoreUnderRavenwood(blockedBy = SpannableString.class) testGetOffsetBefore()1195 public void testGetOffsetBefore() { 1196 // the first '\uDC00' is index 10, the second 'uDC00' is index 17 1197 // the '\uDFFF' is index 27 1198 final int POS_FIRST_DC00 = 10; 1199 final int POS_SECOND_DC00 = 17; 1200 final int POS_FIRST_DFFF = 27; 1201 final int SUPPLYMENTARY_CHARACTERS_OFFSET = 2; 1202 final int NORMAL_CHARACTERS_OFFSET = 1; 1203 SpannableString text = new SpannableString( 1204 "string to\uD700\uDC00 get \uD800\uDC00 offset \uDBFF\uDFFF before"); 1205 assertEquals(0, TextUtils.getOffsetBefore(text, 0)); 1206 assertEquals(0, TextUtils.getOffsetBefore(text, 1)); 1207 assertEquals(text.length() - 1, TextUtils.getOffsetBefore(text, text.length())); 1208 assertEquals(POS_FIRST_DC00 + 1 - NORMAL_CHARACTERS_OFFSET, 1209 TextUtils.getOffsetBefore(text, POS_FIRST_DC00 + 1)); 1210 assertEquals(POS_SECOND_DC00 + 1 - SUPPLYMENTARY_CHARACTERS_OFFSET, 1211 TextUtils.getOffsetBefore(text, POS_SECOND_DC00 + 1)); 1212 assertEquals(POS_FIRST_DFFF + 1 - SUPPLYMENTARY_CHARACTERS_OFFSET, 1213 TextUtils.getOffsetBefore(text, POS_FIRST_DFFF + 1)); 1214 1215 // the CharSequence string has a span. 1216 ReplacementSpan mockReplacementSpan = mock(ReplacementSpan.class); 1217 when(mockReplacementSpan.getSize(any(), any(), anyInt(), anyInt(), any())).thenReturn(0); 1218 text.setSpan(mockReplacementSpan, 0, POS_FIRST_DC00 + 1, 1219 Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); 1220 assertEquals(0, TextUtils.getOffsetBefore(text, POS_FIRST_DC00)); 1221 1222 try { 1223 TextUtils.getOffsetBefore(text, -1); 1224 fail("Should throw IndexOutOfBoundsException!"); 1225 } catch (IndexOutOfBoundsException e) { 1226 } 1227 1228 try { 1229 TextUtils.getOffsetBefore(text, Integer.MAX_VALUE); 1230 fail("Should throw IndexOutOfBoundsException!"); 1231 } catch (IndexOutOfBoundsException e) { 1232 } 1233 1234 try { 1235 TextUtils.getOffsetBefore(null, POS_FIRST_DC00); 1236 fail("Should throw NullPointerException!"); 1237 } catch (NullPointerException e) { 1238 // expected 1239 } 1240 } 1241 1242 @Test 1243 @IgnoreUnderRavenwood(reason = "deprecated") testGetReverse()1244 public void testGetReverse() { 1245 String source = "string to be reversed"; 1246 assertEquals("gnirts", TextUtils.getReverse(source, 0, "string".length()).toString()); 1247 assertEquals("desrever", 1248 TextUtils.getReverse(source, source.length() - "reversed".length(), 1249 source.length()).toString()); 1250 assertEquals("", TextUtils.getReverse(source, 0, 0).toString()); 1251 1252 // issue 1695243, exception is thrown after the result of some cases 1253 // convert to a string, is this expected? 1254 CharSequence result = TextUtils.getReverse(source, -1, "string".length()); 1255 try { 1256 result.toString(); 1257 fail("Should throw IndexOutOfBoundsException!"); 1258 } catch (IndexOutOfBoundsException e) { 1259 } 1260 1261 TextUtils.getReverse(source, 0, source.length() + 1); 1262 try { 1263 result.toString(); 1264 fail("Should throw IndexOutOfBoundsException!"); 1265 } catch (IndexOutOfBoundsException e) { 1266 } 1267 1268 TextUtils.getReverse(source, "string".length(), 0); 1269 try { 1270 result.toString(); 1271 fail("Should throw IndexOutOfBoundsException!"); 1272 } catch (IndexOutOfBoundsException e) { 1273 } 1274 1275 TextUtils.getReverse(source, 0, Integer.MAX_VALUE); 1276 try { 1277 result.toString(); 1278 fail("Should throw IndexOutOfBoundsException!"); 1279 } catch (IndexOutOfBoundsException e) { 1280 } 1281 1282 TextUtils.getReverse(source, Integer.MIN_VALUE, "string".length()); 1283 try { 1284 result.toString(); 1285 fail("Should throw IndexOutOfBoundsException!"); 1286 } catch (IndexOutOfBoundsException e) { 1287 } 1288 1289 TextUtils.getReverse(null, 0, "string".length()); 1290 try { 1291 result.toString(); 1292 fail("Should throw IndexOutOfBoundsException!"); 1293 } catch (IndexOutOfBoundsException e) { 1294 // expected 1295 } 1296 } 1297 1298 @Test testGetTrimmedLength()1299 public void testGetTrimmedLength() { 1300 assertEquals("normalstring".length(), TextUtils.getTrimmedLength("normalstring")); 1301 assertEquals("normal string".length(), TextUtils.getTrimmedLength("normal string")); 1302 assertEquals("blank before".length(), TextUtils.getTrimmedLength(" \t blank before")); 1303 assertEquals("blank after".length(), TextUtils.getTrimmedLength("blank after \n ")); 1304 assertEquals("blank both".length(), TextUtils.getTrimmedLength(" \t blank both \n ")); 1305 1306 char[] allTrimmedChars = new char[]{ 1307 '\u0000', '\u0001', '\u0002', '\u0003', '\u0004', '\u0005', '\u0006', '\u0007', 1308 '\u0008', '\u0009', '\u0010', '\u0011', '\u0012', '\u0013', '\u0014', '\u0015', 1309 '\u0016', '\u0017', '\u0018', '\u0019', '\u0020' 1310 }; 1311 assertEquals(0, TextUtils.getTrimmedLength(String.valueOf(allTrimmedChars))); 1312 } 1313 1314 @Test(expected=NullPointerException.class) testGetTrimmedLengthNull()1315 public void testGetTrimmedLengthNull() { 1316 TextUtils.getTrimmedLength(null); 1317 } 1318 1319 @Test testHtmlEncode()1320 public void testHtmlEncode() { 1321 assertEquals("<_html_>\\ &"'string'"", 1322 TextUtils.htmlEncode("<_html_>\\ &\"'string'\"")); 1323 } 1324 1325 @Test(expected=NullPointerException.class) testHtmlEncodeNull()1326 public void testHtmlEncodeNull() { 1327 TextUtils.htmlEncode(null); 1328 } 1329 1330 @Test testIndexOf1()1331 public void testIndexOf1() { 1332 String searchString = "string to be searched"; 1333 final int INDEX_OF_FIRST_R = 2; // first occurrence of 'r' 1334 final int INDEX_OF_FIRST_T = 1; 1335 final int INDEX_OF_FIRST_D = searchString.length() - 1; 1336 1337 assertEquals(INDEX_OF_FIRST_T, TextUtils.indexOf(searchString, 't')); 1338 assertEquals(INDEX_OF_FIRST_R, TextUtils.indexOf(searchString, 'r')); 1339 assertEquals(INDEX_OF_FIRST_D, TextUtils.indexOf(searchString, 'd')); 1340 assertEquals(-1, TextUtils.indexOf(searchString, 'f')); 1341 1342 StringBuffer stringBuffer = new StringBuffer(searchString); 1343 assertEquals(INDEX_OF_FIRST_R, TextUtils.indexOf(stringBuffer, 'r')); 1344 1345 StringBuilder stringBuilder = new StringBuilder(searchString); 1346 assertEquals(INDEX_OF_FIRST_R, TextUtils.indexOf(stringBuilder, 'r')); 1347 1348 MockGetChars mockGetChars = new MockGetChars(); 1349 assertFalse(mockGetChars.hasCalledGetChars()); 1350 TextUtils.indexOf(mockGetChars, 'r'); 1351 assertTrue(mockGetChars.hasCalledGetChars()); 1352 1353 MockCharSequence mockCharSequence = new MockCharSequence(searchString); 1354 assertEquals(INDEX_OF_FIRST_R, TextUtils.indexOf(mockCharSequence, 'r')); 1355 } 1356 1357 @Test testIndexOf2()1358 public void testIndexOf2() { 1359 String searchString = "string to be searched"; 1360 final int INDEX_OF_FIRST_R = 2; 1361 final int INDEX_OF_SECOND_R = 16; 1362 1363 assertEquals(INDEX_OF_FIRST_R, TextUtils.indexOf(searchString, 'r', 0)); 1364 assertEquals(INDEX_OF_SECOND_R, TextUtils.indexOf(searchString, 'r', INDEX_OF_FIRST_R + 1)); 1365 assertEquals(-1, TextUtils.indexOf(searchString, 'r', searchString.length())); 1366 assertEquals(INDEX_OF_FIRST_R, TextUtils.indexOf(searchString, 'r', Integer.MIN_VALUE)); 1367 assertEquals(-1, TextUtils.indexOf(searchString, 'r', Integer.MAX_VALUE)); 1368 1369 StringBuffer stringBuffer = new StringBuffer(searchString); 1370 assertEquals(INDEX_OF_SECOND_R, TextUtils.indexOf(stringBuffer, 'r', INDEX_OF_FIRST_R + 1)); 1371 try { 1372 TextUtils.indexOf(stringBuffer, 'r', Integer.MIN_VALUE); 1373 fail("Should throw IndexOutOfBoundsException!"); 1374 } catch (IndexOutOfBoundsException e) { 1375 // expect 1376 } 1377 assertEquals(-1, TextUtils.indexOf(stringBuffer, 'r', Integer.MAX_VALUE)); 1378 1379 StringBuilder stringBuilder = new StringBuilder(searchString); 1380 assertEquals(INDEX_OF_SECOND_R, 1381 TextUtils.indexOf(stringBuilder, 'r', INDEX_OF_FIRST_R + 1)); 1382 1383 MockGetChars mockGetChars = new MockGetChars(); 1384 TextUtils.indexOf(mockGetChars, 'r', INDEX_OF_FIRST_R + 1); 1385 assertTrue(mockGetChars.hasCalledGetChars()); 1386 1387 MockCharSequence mockCharSequence = new MockCharSequence(searchString); 1388 assertEquals(INDEX_OF_SECOND_R, TextUtils.indexOf(mockCharSequence, 'r', 1389 INDEX_OF_FIRST_R + 1)); 1390 } 1391 1392 @Test testIndexOf3()1393 public void testIndexOf3() { 1394 String searchString = "string to be searched"; 1395 final int INDEX_OF_FIRST_R = 2; 1396 final int INDEX_OF_SECOND_R = 16; 1397 1398 assertEquals(INDEX_OF_FIRST_R, 1399 TextUtils.indexOf(searchString, 'r', 0, searchString.length())); 1400 assertEquals(INDEX_OF_SECOND_R, TextUtils.indexOf(searchString, 'r', 1401 INDEX_OF_FIRST_R + 1, searchString.length())); 1402 assertEquals(-1, TextUtils.indexOf(searchString, 'r', 1403 INDEX_OF_FIRST_R + 1, INDEX_OF_SECOND_R)); 1404 1405 try { 1406 TextUtils.indexOf(searchString, 'r', Integer.MIN_VALUE, INDEX_OF_SECOND_R); 1407 fail("Should throw IndexOutOfBoundsException!"); 1408 } catch (IndexOutOfBoundsException e) { 1409 // expect 1410 } 1411 assertEquals(-1, 1412 TextUtils.indexOf(searchString, 'r', Integer.MAX_VALUE, INDEX_OF_SECOND_R)); 1413 assertEquals(-1, TextUtils.indexOf(searchString, 'r', 0, Integer.MIN_VALUE)); 1414 try { 1415 TextUtils.indexOf(searchString, 'r', 0, Integer.MAX_VALUE); 1416 fail("Should throw IndexOutOfBoundsException!"); 1417 } catch (IndexOutOfBoundsException e) { 1418 // expect 1419 } 1420 1421 StringBuffer stringBuffer = new StringBuffer(searchString); 1422 assertEquals(INDEX_OF_SECOND_R, TextUtils.indexOf(stringBuffer, 'r', 1423 INDEX_OF_FIRST_R + 1, searchString.length())); 1424 1425 StringBuilder stringBuilder = new StringBuilder(searchString); 1426 assertEquals(INDEX_OF_SECOND_R, TextUtils.indexOf(stringBuilder, 'r', 1427 INDEX_OF_FIRST_R + 1, searchString.length())); 1428 1429 MockGetChars mockGetChars = new MockGetChars(); 1430 TextUtils.indexOf(mockGetChars, 'r', INDEX_OF_FIRST_R + 1, searchString.length()); 1431 assertTrue(mockGetChars.hasCalledGetChars()); 1432 1433 MockCharSequence mockCharSequence = new MockCharSequence(searchString); 1434 assertEquals(INDEX_OF_SECOND_R, TextUtils.indexOf(mockCharSequence, 'r', 1435 INDEX_OF_FIRST_R + 1, searchString.length())); 1436 } 1437 1438 @Test testIndexOf4()1439 public void testIndexOf4() { 1440 String searchString = "string to be searched by string"; 1441 final int SEARCH_INDEX = 13; 1442 1443 assertEquals(0, TextUtils.indexOf(searchString, "string")); 1444 assertEquals(SEARCH_INDEX, TextUtils.indexOf(searchString, "search")); 1445 assertEquals(-1, TextUtils.indexOf(searchString, "tobe")); 1446 assertEquals(0, TextUtils.indexOf(searchString, "")); 1447 1448 StringBuffer stringBuffer = new StringBuffer(searchString); 1449 assertEquals(SEARCH_INDEX, TextUtils.indexOf(stringBuffer, "search")); 1450 1451 StringBuilder stringBuilder = new StringBuilder(searchString); 1452 assertEquals(SEARCH_INDEX, TextUtils.indexOf(stringBuilder, "search")); 1453 1454 MockGetChars mockGetChars = new MockGetChars(); 1455 TextUtils.indexOf(mockGetChars, "search"); 1456 assertTrue(mockGetChars.hasCalledGetChars()); 1457 1458 MockCharSequence mockCharSequence = new MockCharSequence(searchString); 1459 assertEquals(SEARCH_INDEX, TextUtils.indexOf(mockCharSequence, "search")); 1460 } 1461 1462 @Test testIndexOf5()1463 public void testIndexOf5() { 1464 String searchString = "string to be searched by string"; 1465 final int INDEX_OF_FIRST_STRING = 0; 1466 final int INDEX_OF_SECOND_STRING = 25; 1467 1468 assertEquals(INDEX_OF_FIRST_STRING, TextUtils.indexOf(searchString, "string", 0)); 1469 assertEquals(INDEX_OF_SECOND_STRING, TextUtils.indexOf(searchString, "string", 1470 INDEX_OF_FIRST_STRING + 1)); 1471 assertEquals(-1, TextUtils.indexOf(searchString, "string", INDEX_OF_SECOND_STRING + 1)); 1472 assertEquals(INDEX_OF_FIRST_STRING, TextUtils.indexOf(searchString, "string", 1473 Integer.MIN_VALUE)); 1474 assertEquals(-1, TextUtils.indexOf(searchString, "string", Integer.MAX_VALUE)); 1475 1476 assertEquals(1, TextUtils.indexOf(searchString, "", 1)); 1477 assertEquals(Integer.MAX_VALUE, TextUtils.indexOf(searchString, "", Integer.MAX_VALUE)); 1478 1479 assertEquals(0, TextUtils.indexOf(searchString, searchString, 0)); 1480 assertEquals(-1, TextUtils.indexOf(searchString, searchString + "longer needle", 0)); 1481 1482 StringBuffer stringBuffer = new StringBuffer(searchString); 1483 assertEquals(INDEX_OF_SECOND_STRING, TextUtils.indexOf(stringBuffer, "string", 1484 INDEX_OF_FIRST_STRING + 1)); 1485 try { 1486 TextUtils.indexOf(stringBuffer, "string", Integer.MIN_VALUE); 1487 fail("Should throw IndexOutOfBoundsException!"); 1488 } catch (IndexOutOfBoundsException e) { 1489 // expect 1490 } 1491 assertEquals(-1, TextUtils.indexOf(stringBuffer, "string", Integer.MAX_VALUE)); 1492 1493 StringBuilder stringBuilder = new StringBuilder(searchString); 1494 assertEquals(INDEX_OF_SECOND_STRING, TextUtils.indexOf(stringBuilder, "string", 1495 INDEX_OF_FIRST_STRING + 1)); 1496 1497 MockGetChars mockGetChars = new MockGetChars(); 1498 assertFalse(mockGetChars.hasCalledGetChars()); 1499 TextUtils.indexOf(mockGetChars, "string", INDEX_OF_FIRST_STRING + 1); 1500 assertTrue(mockGetChars.hasCalledGetChars()); 1501 1502 MockCharSequence mockCharSequence = new MockCharSequence(searchString); 1503 assertEquals(INDEX_OF_SECOND_STRING, TextUtils.indexOf(mockCharSequence, "string", 1504 INDEX_OF_FIRST_STRING + 1)); 1505 } 1506 1507 @Test testIndexOf6()1508 public void testIndexOf6() { 1509 String searchString = "string to be searched by string"; 1510 final int INDEX_OF_FIRST_STRING = 0; 1511 final int INDEX_OF_SECOND_STRING = 25; 1512 1513 assertEquals(INDEX_OF_FIRST_STRING, TextUtils.indexOf(searchString, "string", 0, 1514 searchString.length())); 1515 assertEquals(INDEX_OF_SECOND_STRING, TextUtils.indexOf(searchString, "string", 1516 INDEX_OF_FIRST_STRING + 1, searchString.length())); 1517 assertEquals(-1, TextUtils.indexOf(searchString, "string", INDEX_OF_FIRST_STRING + 1, 1518 INDEX_OF_SECOND_STRING - 1)); 1519 assertEquals(INDEX_OF_FIRST_STRING, TextUtils.indexOf(searchString, "string", 1520 Integer.MIN_VALUE, INDEX_OF_SECOND_STRING - 1)); 1521 assertEquals(-1, TextUtils.indexOf(searchString, "string", Integer.MAX_VALUE, 1522 INDEX_OF_SECOND_STRING - 1)); 1523 1524 assertEquals(INDEX_OF_SECOND_STRING, TextUtils.indexOf(searchString, "string", 1525 INDEX_OF_FIRST_STRING + 1, Integer.MIN_VALUE)); 1526 assertEquals(INDEX_OF_SECOND_STRING, TextUtils.indexOf(searchString, "string", 1527 INDEX_OF_FIRST_STRING + 1, Integer.MAX_VALUE)); 1528 1529 StringBuffer stringBuffer = new StringBuffer(searchString); 1530 assertEquals(INDEX_OF_SECOND_STRING, TextUtils.indexOf(stringBuffer, "string", 1531 INDEX_OF_FIRST_STRING + 1, searchString.length())); 1532 try { 1533 TextUtils.indexOf(stringBuffer, "string", Integer.MIN_VALUE, 1534 INDEX_OF_SECOND_STRING - 1); 1535 fail("Should throw IndexOutOfBoundsException!"); 1536 } catch (IndexOutOfBoundsException e) { 1537 // expect 1538 } 1539 assertEquals(-1, TextUtils.indexOf(stringBuffer, "string", Integer.MAX_VALUE, 1540 searchString.length())); 1541 assertEquals(INDEX_OF_SECOND_STRING, TextUtils.indexOf(stringBuffer, 1542 "string", INDEX_OF_FIRST_STRING + 1, Integer.MIN_VALUE)); 1543 assertEquals(INDEX_OF_SECOND_STRING, TextUtils.indexOf(stringBuffer, 1544 "string", INDEX_OF_FIRST_STRING + 1, Integer.MAX_VALUE)); 1545 1546 StringBuilder stringBuilder = new StringBuilder(searchString); 1547 assertEquals(INDEX_OF_SECOND_STRING, TextUtils.indexOf(stringBuilder, "string", 1548 INDEX_OF_FIRST_STRING + 1, searchString.length())); 1549 1550 MockGetChars mockGetChars = new MockGetChars(); 1551 TextUtils.indexOf(mockGetChars, "string", INDEX_OF_FIRST_STRING + 1, searchString.length()); 1552 assertTrue(mockGetChars.hasCalledGetChars()); 1553 1554 MockCharSequence mockCharSequence = new MockCharSequence(searchString); 1555 assertEquals(INDEX_OF_SECOND_STRING, TextUtils.indexOf(mockCharSequence, "string", 1556 INDEX_OF_FIRST_STRING + 1, searchString.length())); 1557 } 1558 1559 @Test testIsDigitsOnly()1560 public void testIsDigitsOnly() { 1561 assertTrue(TextUtils.isDigitsOnly("")); 1562 assertFalse(TextUtils.isDigitsOnly("no digit")); 1563 assertFalse(TextUtils.isDigitsOnly("character and 56 digits")); 1564 assertTrue(TextUtils.isDigitsOnly("0123456789")); 1565 assertFalse(TextUtils.isDigitsOnly("1234 56789")); 1566 1567 // U+104A0 OSMANYA DIGIT ZERO 1568 assertTrue(TextUtils.isDigitsOnly(new String(Character.toChars(0x104A0)))); 1569 // U+10858 IMPERIAL ARAMAIC NUMBER ONE 1570 assertFalse(TextUtils.isDigitsOnly(new String(Character.toChars(0x10858)))); 1571 1572 assertFalse(TextUtils.isDigitsOnly("\uD801")); // lonely lead surrogate 1573 assertFalse(TextUtils.isDigitsOnly("\uDCA0")); // lonely trailing surrogate 1574 } 1575 1576 @Test(expected=NullPointerException.class) testIsDigitsOnlyNull()1577 public void testIsDigitsOnlyNull() { 1578 TextUtils.isDigitsOnly(null); 1579 } 1580 1581 @Test testIsEmpty()1582 public void testIsEmpty() { 1583 assertFalse(TextUtils.isEmpty("not empty")); 1584 assertFalse(TextUtils.isEmpty(" ")); 1585 assertTrue(TextUtils.isEmpty("")); 1586 assertTrue(TextUtils.isEmpty(null)); 1587 } 1588 1589 @Test testIsGraphicChar()1590 public void testIsGraphicChar() { 1591 assertTrue(TextUtils.isGraphic('a')); 1592 assertTrue(TextUtils.isGraphic('\uBA00')); 1593 1594 // LINE_SEPARATOR 1595 assertFalse(TextUtils.isGraphic('\u2028')); 1596 1597 // PARAGRAPH_SEPARATOR 1598 assertFalse(TextUtils.isGraphic('\u2029')); 1599 1600 // CONTROL 1601 assertFalse(TextUtils.isGraphic('\u0085')); 1602 1603 // UNASSIGNED 1604 assertFalse(TextUtils.isGraphic('\uFFFF')); 1605 1606 // SURROGATE 1607 assertFalse(TextUtils.isGraphic('\uD800')); 1608 1609 // SPACE_SEPARATOR 1610 assertFalse(TextUtils.isGraphic('\u0020')); 1611 } 1612 1613 @Test(expected=NullPointerException.class) 1614 @SuppressWarnings("NullArgumentForNonNullParameter") testIsGraphicCharNull()1615 public void testIsGraphicCharNull() { 1616 assertFalse(TextUtils.isGraphic((Character) null)); 1617 } 1618 1619 @Test testIsGraphicCharSequence()1620 public void testIsGraphicCharSequence() { 1621 assertTrue(TextUtils.isGraphic("printable characters")); 1622 1623 assertFalse(TextUtils.isGraphic("\u2028\u2029\u0085\uFFFF\uD800\u0020")); 1624 1625 assertTrue(TextUtils.isGraphic("a\u2028\u2029\u0085\uFFFF\uD800\u0020")); 1626 1627 assertTrue(TextUtils.isGraphic("\uD83D\uDC0C")); // U+1F40C SNAIL 1628 assertFalse(TextUtils.isGraphic("\uDB40\uDC01")); // U+E0000 (unassigned) 1629 assertFalse(TextUtils.isGraphic("\uDB3D")); // unpaired high surrogate 1630 assertFalse(TextUtils.isGraphic("\uDC0C")); // unpaired low surrogate 1631 } 1632 1633 @Test(expected=NullPointerException.class) testIsGraphicCharSequenceNull()1634 public void testIsGraphicCharSequenceNull() { 1635 TextUtils.isGraphic(null); 1636 } 1637 1638 @Test 1639 @IgnoreUnderRavenwood(blockedBy = SpannableString.class) testJoinIterable()1640 public void testJoinIterable() { 1641 ArrayList<CharSequence> charTokens = new ArrayList<>(); 1642 charTokens.add("string1"); 1643 charTokens.add("string2"); 1644 charTokens.add("string3"); 1645 assertEquals("string1|string2|string3", TextUtils.join("|", charTokens)); 1646 assertEquals("string1; string2; string3", TextUtils.join("; ", charTokens)); 1647 assertEquals("string1string2string3", TextUtils.join("", charTokens)); 1648 1649 // issue 1695243, not clear what is supposed result if the delimiter or tokens are null. 1650 assertEquals("string1nullstring2nullstring3", TextUtils.join(null, charTokens)); 1651 1652 ArrayList<SpannableString> spannableStringTokens = new ArrayList<SpannableString>(); 1653 spannableStringTokens.add(new SpannableString("span 1")); 1654 spannableStringTokens.add(new SpannableString("span 2")); 1655 spannableStringTokens.add(new SpannableString("span 3")); 1656 assertEquals("span 1;span 2;span 3", TextUtils.join(";", spannableStringTokens)); 1657 1658 assertEquals("", TextUtils.join("|", new ArrayList<CharSequence>())); 1659 } 1660 1661 @Test(expected=NullPointerException.class) 1662 @IgnoreUnderRavenwood(blockedBy = SpannableString.class) testJoinIterableNull()1663 public void testJoinIterableNull() { 1664 TextUtils.join("|", (Iterable) null); 1665 } 1666 1667 @Test 1668 @IgnoreUnderRavenwood(blockedBy = SpannableString.class) testJoinArray()1669 public void testJoinArray() { 1670 CharSequence[] charTokens = new CharSequence[] { "string1", "string2", "string3" }; 1671 assertEquals("string1|string2|string3", TextUtils.join("|", charTokens)); 1672 assertEquals("string1; string2; string3", TextUtils.join("; ", charTokens)); 1673 assertEquals("string1string2string3", TextUtils.join("", charTokens)); 1674 1675 // issue 1695243, not clear what is supposed result if the delimiter or tokens are null. 1676 assertEquals("string1nullstring2nullstring3", TextUtils.join(null, charTokens)); 1677 1678 SpannableString[] spannableStringTokens = new SpannableString[] { 1679 new SpannableString("span 1"), 1680 new SpannableString("span 2"), 1681 new SpannableString("span 3") }; 1682 assertEquals("span 1;span 2;span 3", TextUtils.join(";", spannableStringTokens)); 1683 1684 assertEquals("", TextUtils.join("|", new String[0])); 1685 } 1686 1687 @Test(expected=NullPointerException.class) 1688 @IgnoreUnderRavenwood(blockedBy = SpannableString.class) testJoinArrayNull()1689 public void testJoinArrayNull() { 1690 TextUtils.join("|", (Object[]) null); 1691 } 1692 1693 @Test testLastIndexOf1()1694 public void testLastIndexOf1() { 1695 String searchString = "string to be searched"; 1696 final int INDEX_OF_LAST_R = 16; 1697 final int INDEX_OF_LAST_T = 7; 1698 final int INDEX_OF_LAST_D = searchString.length() - 1; 1699 1700 assertEquals(INDEX_OF_LAST_T, TextUtils.lastIndexOf(searchString, 't')); 1701 assertEquals(INDEX_OF_LAST_R, TextUtils.lastIndexOf(searchString, 'r')); 1702 assertEquals(INDEX_OF_LAST_D, TextUtils.lastIndexOf(searchString, 'd')); 1703 assertEquals(-1, TextUtils.lastIndexOf(searchString, 'f')); 1704 1705 StringBuffer stringBuffer = new StringBuffer(searchString); 1706 assertEquals(INDEX_OF_LAST_R, TextUtils.lastIndexOf(stringBuffer, 'r')); 1707 1708 StringBuilder stringBuilder = new StringBuilder(searchString); 1709 assertEquals(INDEX_OF_LAST_R, TextUtils.lastIndexOf(stringBuilder, 'r')); 1710 1711 MockGetChars mockGetChars = new MockGetChars(); 1712 TextUtils.lastIndexOf(mockGetChars, 'r'); 1713 assertTrue(mockGetChars.hasCalledGetChars()); 1714 1715 MockCharSequence mockCharSequence = new MockCharSequence(searchString); 1716 assertEquals(INDEX_OF_LAST_R, TextUtils.lastIndexOf(mockCharSequence, 'r')); 1717 } 1718 1719 @Test testLastIndexOf2()1720 public void testLastIndexOf2() { 1721 String searchString = "string to be searched"; 1722 final int INDEX_OF_FIRST_R = 2; 1723 final int INDEX_OF_SECOND_R = 16; 1724 1725 assertEquals(INDEX_OF_SECOND_R, 1726 TextUtils.lastIndexOf(searchString, 'r', searchString.length())); 1727 assertEquals(-1, TextUtils.lastIndexOf(searchString, 'r', 0)); 1728 assertEquals(INDEX_OF_FIRST_R, 1729 TextUtils.lastIndexOf(searchString, 'r', INDEX_OF_FIRST_R)); 1730 assertEquals(-1, TextUtils.lastIndexOf(searchString, 'r', Integer.MIN_VALUE)); 1731 assertEquals(INDEX_OF_SECOND_R, 1732 TextUtils.lastIndexOf(searchString, 'r', Integer.MAX_VALUE)); 1733 1734 StringBuffer stringBuffer = new StringBuffer(searchString); 1735 assertEquals(INDEX_OF_FIRST_R, 1736 TextUtils.lastIndexOf(stringBuffer, 'r', INDEX_OF_FIRST_R)); 1737 assertEquals(-1, TextUtils.lastIndexOf(stringBuffer, 'r', Integer.MIN_VALUE)); 1738 assertEquals(INDEX_OF_SECOND_R, 1739 TextUtils.lastIndexOf(stringBuffer, 'r', Integer.MAX_VALUE)); 1740 1741 StringBuilder stringBuilder = new StringBuilder(searchString); 1742 assertEquals(INDEX_OF_FIRST_R, 1743 TextUtils.lastIndexOf(stringBuilder, 'r', INDEX_OF_FIRST_R)); 1744 1745 MockGetChars mockGetChars = new MockGetChars(); 1746 TextUtils.lastIndexOf(mockGetChars, 'r', INDEX_OF_FIRST_R); 1747 assertTrue(mockGetChars.hasCalledGetChars()); 1748 1749 MockCharSequence mockCharSequence = new MockCharSequence(searchString); 1750 assertEquals(INDEX_OF_FIRST_R, 1751 TextUtils.lastIndexOf(mockCharSequence, 'r', INDEX_OF_FIRST_R)); 1752 } 1753 1754 @Test testLastIndexOf3()1755 public void testLastIndexOf3() { 1756 String searchString = "string to be searched"; 1757 final int INDEX_OF_FIRST_R = 2; 1758 final int INDEX_OF_SECOND_R = 16; 1759 1760 assertEquals(INDEX_OF_SECOND_R, TextUtils.lastIndexOf(searchString, 'r', 0, 1761 searchString.length())); 1762 assertEquals(INDEX_OF_FIRST_R, TextUtils.lastIndexOf(searchString, 'r', 0, 1763 INDEX_OF_SECOND_R - 1)); 1764 assertEquals(-1, TextUtils.lastIndexOf(searchString, 'r', 0, INDEX_OF_FIRST_R - 1)); 1765 1766 try { 1767 TextUtils.lastIndexOf(searchString, 'r', Integer.MIN_VALUE, INDEX_OF_SECOND_R - 1); 1768 fail("Should throw IndexOutOfBoundsException!"); 1769 } catch (IndexOutOfBoundsException e) { 1770 // expect 1771 } 1772 assertEquals(-1, TextUtils.lastIndexOf(searchString, 'r', Integer.MAX_VALUE, 1773 INDEX_OF_SECOND_R - 1)); 1774 assertEquals(-1, TextUtils.lastIndexOf(searchString, 'r', 0, Integer.MIN_VALUE)); 1775 assertEquals(INDEX_OF_SECOND_R, TextUtils.lastIndexOf(searchString, 'r', 0, 1776 Integer.MAX_VALUE)); 1777 1778 StringBuffer stringBuffer = new StringBuffer(searchString); 1779 assertEquals(INDEX_OF_FIRST_R, TextUtils.lastIndexOf(stringBuffer, 'r', 0, 1780 INDEX_OF_SECOND_R - 1)); 1781 1782 StringBuilder stringBuilder = new StringBuilder(searchString); 1783 assertEquals(INDEX_OF_FIRST_R, TextUtils.lastIndexOf(stringBuilder, 'r', 0, 1784 INDEX_OF_SECOND_R - 1)); 1785 1786 MockGetChars mockGetChars = new MockGetChars(); 1787 TextUtils.lastIndexOf(mockGetChars, 'r', 0, INDEX_OF_SECOND_R - 1); 1788 assertTrue(mockGetChars.hasCalledGetChars()); 1789 1790 MockCharSequence mockCharSequence = new MockCharSequence(searchString); 1791 assertEquals(INDEX_OF_FIRST_R, TextUtils.lastIndexOf(mockCharSequence, 'r', 0, 1792 INDEX_OF_SECOND_R - 1)); 1793 } 1794 1795 @Test testRegionMatches()1796 public void testRegionMatches() { 1797 assertFalse(TextUtils.regionMatches("one", 0, "two", 0, "one".length())); 1798 assertTrue(TextUtils.regionMatches("one", 0, "one", 0, "one".length())); 1799 try { 1800 TextUtils.regionMatches("one", 0, "one", 0, "one".length() + 1); 1801 fail("Should throw IndexOutOfBoundsException!"); 1802 } catch (IndexOutOfBoundsException e) { 1803 } 1804 1805 String one = "Hello Android, hello World!"; 1806 String two = "Hello World"; 1807 // match "Hello" 1808 assertTrue(TextUtils.regionMatches(one, 0, two, 0, "Hello".length())); 1809 1810 // match "Hello A" and "Hello W" 1811 assertFalse(TextUtils.regionMatches(one, 0, two, 0, "Hello A".length())); 1812 1813 // match "World" 1814 assertTrue(TextUtils.regionMatches(one, "Hello Android, hello ".length(), 1815 two, "Hello ".length(), "World".length())); 1816 assertFalse(TextUtils.regionMatches(one, "Hello Android, hello ".length(), 1817 two, 0, "World".length())); 1818 1819 try { 1820 TextUtils.regionMatches(one, Integer.MIN_VALUE, two, 0, "Hello".length()); 1821 fail("Should throw IndexOutOfBoundsException!"); 1822 } catch (IndexOutOfBoundsException e) { 1823 } 1824 try { 1825 TextUtils.regionMatches(one, Integer.MAX_VALUE, two, 0, "Hello".length()); 1826 fail("Should throw IndexOutOfBoundsException!"); 1827 } catch (IndexOutOfBoundsException e) { 1828 } 1829 1830 try { 1831 TextUtils.regionMatches(one, 0, two, Integer.MIN_VALUE, "Hello".length()); 1832 fail("Should throw IndexOutOfBoundsException!"); 1833 } catch (IndexOutOfBoundsException e) { 1834 } 1835 try { 1836 TextUtils.regionMatches(one, 0, two, Integer.MAX_VALUE, "Hello".length()); 1837 fail("Should throw IndexOutOfBoundsException!"); 1838 } catch (IndexOutOfBoundsException e) { 1839 } 1840 1841 try { 1842 TextUtils.regionMatches(one, 0, two, 0, Integer.MIN_VALUE); 1843 fail("Should throw IndexOutOfBoundsException!"); 1844 } catch (IndexOutOfBoundsException e) { 1845 } 1846 try { 1847 TextUtils.regionMatches(one, 0, two, 0, Integer.MAX_VALUE); 1848 fail("Should throw IndexOutOfBoundsException!"); 1849 } catch (IndexOutOfBoundsException e) { 1850 } 1851 1852 try { 1853 TextUtils.regionMatches(null, 0, two, 0, "Hello".length()); 1854 fail("Should throw NullPointerException!"); 1855 } catch (NullPointerException e) { 1856 // expect 1857 } 1858 try { 1859 TextUtils.regionMatches(one, 0, null, 0, "Hello".length()); 1860 fail("Should throw NullPointerException!"); 1861 } catch (NullPointerException e) { 1862 // expect 1863 } 1864 } 1865 1866 @Test 1867 @IgnoreUnderRavenwood(blockedBy = SpannableString.class) testReplace()1868 public void testReplace() { 1869 String template = "this is a string to be as the template for replacement"; 1870 1871 String sources[] = new String[] { "string" }; 1872 CharSequence destinations[] = new CharSequence[] { "text" }; 1873 SpannableStringBuilder replacedString = (SpannableStringBuilder) TextUtils.replace(template, 1874 sources, destinations); 1875 assertEquals("this is a text to be as the template for replacement", 1876 replacedString.toString()); 1877 1878 sources = new String[] {"is", "the", "for replacement"}; 1879 destinations = new CharSequence[] {"was", "", "to be replaced"}; 1880 replacedString = (SpannableStringBuilder)TextUtils.replace(template, sources, destinations); 1881 assertEquals("thwas is a string to be as template to be replaced", 1882 replacedString.toString()); 1883 1884 sources = new String[] {"is", "for replacement"}; 1885 destinations = new CharSequence[] {"was", "", "to be replaced"}; 1886 replacedString = (SpannableStringBuilder)TextUtils.replace(template, sources, destinations); 1887 assertEquals("thwas is a string to be as the template ", replacedString.toString()); 1888 1889 sources = new String[] {"is", "the", "for replacement"}; 1890 destinations = new CharSequence[] {"was", "to be replaced"}; 1891 try { 1892 TextUtils.replace(template, sources, destinations); 1893 fail("Should throw ArrayIndexOutOfBoundsException!"); 1894 } catch (ArrayIndexOutOfBoundsException e) { 1895 // expected 1896 } 1897 1898 try { 1899 TextUtils.replace(null, sources, destinations); 1900 fail("Should throw NullPointerException!"); 1901 } catch (NullPointerException e) { 1902 // expected 1903 } 1904 try { 1905 TextUtils.replace(template, null, destinations); 1906 fail("Should throw NullPointerException!"); 1907 } catch (NullPointerException e) { 1908 // expected 1909 } 1910 try { 1911 TextUtils.replace(template, sources, null); 1912 fail("Should throw NullPointerException!"); 1913 } catch (NullPointerException e) { 1914 // expected 1915 } 1916 } 1917 1918 @Test testSplitPattern()1919 public void testSplitPattern() { 1920 assertEquals(0, TextUtils.split("", Pattern.compile("")).length); 1921 assertEquals(0, TextUtils.split("", Pattern.compile("not found")).length); 1922 1923 String testString = "abccbadecdebz"; 1924 assertEquals(calculateCharsCount(testString, "c") + 1, 1925 TextUtils.split(testString, Pattern.compile("c")).length); 1926 assertEquals(calculateCharsCount(testString, "a") + 1, 1927 TextUtils.split(testString, Pattern.compile("a")).length); 1928 assertEquals(calculateCharsCount(testString, "z") + 1, 1929 TextUtils.split(testString, Pattern.compile("z")).length); 1930 assertEquals(calculateCharsCount(testString, "de") + 1, 1931 TextUtils.split(testString, Pattern.compile("de")).length); 1932 int totalCount = 1 + calculateCharsCount(testString, "a") 1933 + calculateCharsCount(testString, "b") + calculateCharsCount(testString, "c"); 1934 assertEquals(totalCount, 1935 TextUtils.split(testString, Pattern.compile("[a-c]")).length); 1936 assertEquals(0, TextUtils.split("", Pattern.compile("a")).length); 1937 // issue 1695243, not clear what is supposed result if the pattern string is empty. 1938 assertEquals( 1939 Arrays.asList("a", "b", "c", "c", "b", "a", "d", "e", "c", "d", "e", "b", "z", ""), 1940 Arrays.asList(TextUtils.split(testString, Pattern.compile("")))); 1941 } 1942 1943 @Test(expected=NullPointerException.class) testSplitPatternNullText()1944 public void testSplitPatternNullText() { 1945 TextUtils.split(null, Pattern.compile("a")); 1946 } 1947 1948 @Test(expected=NullPointerException.class) testSplitPatternNullPattern()1949 public void testSplitPatternNullPattern() { 1950 TextUtils.split("abccbadecdebz", (Pattern) null); 1951 } 1952 1953 /* 1954 * return the appearance count of searched chars in text. 1955 */ calculateCharsCount(CharSequence text, CharSequence searches)1956 private static int calculateCharsCount(CharSequence text, CharSequence searches) { 1957 int count = 0; 1958 int start = TextUtils.indexOf(text, searches, 0); 1959 1960 while (start != -1) { 1961 count++; 1962 start = TextUtils.indexOf(text, searches, start + 1); 1963 } 1964 return count; 1965 } 1966 1967 @Test testSplitString()1968 public void testSplitString() { 1969 assertEquals(0, TextUtils.split("", "").length); 1970 assertEquals(0, TextUtils.split("", "not found").length); 1971 1972 // The case mentioned in the documentation. 1973 assertEquals(Arrays.asList("a", ""), Arrays.asList(TextUtils.split("a,", ","))); 1974 1975 String testString = "abccbadecdebz"; 1976 assertEquals(calculateCharsCount(testString, "c") + 1, 1977 TextUtils.split("abccbadecdebz", "c").length); 1978 assertEquals(calculateCharsCount(testString, "a") + 1, 1979 TextUtils.split("abccbadecdebz", "a").length); 1980 assertEquals(calculateCharsCount(testString, "z") + 1, 1981 TextUtils.split("abccbadecdebz", "z").length); 1982 assertEquals(calculateCharsCount(testString, "de") + 1, 1983 TextUtils.split("abccbadecdebz", "de").length); 1984 assertEquals(0, TextUtils.split("", "a").length); 1985 // issue 1695243, not clear what is supposed result if the pattern string is empty. 1986 assertEquals( 1987 Arrays.asList("a", "b", "c", "c", "b", "a", "d", "e", "c", "d", "e", "b", "z", ""), 1988 Arrays.asList(TextUtils.split("abccbadecdebz", ""))); 1989 } 1990 1991 @Test(expected=NullPointerException.class) testSplitStringNullText()1992 public void testSplitStringNullText() { 1993 TextUtils.split(null, "a"); 1994 } 1995 1996 @Test(expected=NullPointerException.class) testSplitStringNullPattern()1997 public void testSplitStringNullPattern() { 1998 TextUtils.split("abccbadecdebz", (String) null); 1999 } 2000 2001 @Test 2002 @IgnoreUnderRavenwood(blockedBy = SpannableString.class) testStringOrSpannedString()2003 public void testStringOrSpannedString() { 2004 assertNull(TextUtils.stringOrSpannedString(null)); 2005 2006 SpannedString spannedString = new SpannedString("Spanned String"); 2007 assertSame(spannedString, TextUtils.stringOrSpannedString(spannedString)); 2008 2009 SpannableString spannableString = new SpannableString("Spannable String"); 2010 assertEquals("Spannable String", 2011 TextUtils.stringOrSpannedString(spannableString).toString()); 2012 assertEquals(SpannedString.class, 2013 TextUtils.stringOrSpannedString(spannableString).getClass()); 2014 2015 StringBuffer stringBuffer = new StringBuffer("String Buffer"); 2016 assertEquals("String Buffer", 2017 TextUtils.stringOrSpannedString(stringBuffer).toString()); 2018 assertEquals(String.class, 2019 TextUtils.stringOrSpannedString(stringBuffer).getClass()); 2020 } 2021 2022 @Test testSubString()2023 public void testSubString() { 2024 String string = "String"; 2025 assertSame(string, TextUtils.substring(string, 0, string.length())); 2026 assertEquals("Strin", TextUtils.substring(string, 0, string.length() - 1)); 2027 assertEquals("", TextUtils.substring(string, 1, 1)); 2028 2029 try { 2030 TextUtils.substring(string, string.length(), 0); 2031 fail("Should throw IndexOutOfBoundsException!"); 2032 } catch (IndexOutOfBoundsException e) { 2033 // expected 2034 } 2035 2036 try { 2037 TextUtils.substring(string, -1, string.length()); 2038 fail("Should throw IndexOutOfBoundsException!"); 2039 } catch (IndexOutOfBoundsException e) { 2040 // expected 2041 } 2042 2043 try { 2044 TextUtils.substring(string, Integer.MAX_VALUE, string.length()); 2045 fail("Should throw IndexOutOfBoundsException!"); 2046 } catch (IndexOutOfBoundsException e) { 2047 // expected 2048 } 2049 2050 try { 2051 TextUtils.substring(string, 0, -1); 2052 fail("Should throw IndexOutOfBoundsException!"); 2053 } catch (IndexOutOfBoundsException e) { 2054 // expected 2055 } 2056 2057 try { 2058 TextUtils.substring(string, 0, Integer.MAX_VALUE); 2059 fail("Should throw IndexOutOfBoundsException!"); 2060 } catch (IndexOutOfBoundsException e) { 2061 // expected 2062 } 2063 2064 try { 2065 TextUtils.substring(null, 0, string.length()); 2066 fail("Should throw NullPointerException!"); 2067 } catch (NullPointerException e) { 2068 // expected 2069 } 2070 2071 StringBuffer stringBuffer = new StringBuffer("String Buffer"); 2072 assertEquals("Strin", TextUtils.substring(stringBuffer, 0, string.length() - 1)); 2073 assertEquals("", TextUtils.substring(stringBuffer, 1, 1)); 2074 2075 MockGetChars mockGetChars = new MockGetChars(); 2076 TextUtils.substring(mockGetChars, 0, string.length()); 2077 assertTrue(mockGetChars.hasCalledGetChars()); 2078 } 2079 2080 @Test 2081 @IgnoreUnderRavenwood(blockedBy = SpannableString.class) testWriteToParcel()2082 public void testWriteToParcel() { 2083 Parcelable.Creator<CharSequence> creator = TextUtils.CHAR_SEQUENCE_CREATOR; 2084 String string = "String"; 2085 Parcel p = Parcel.obtain(); 2086 try { 2087 TextUtils.writeToParcel(string, p, 0); 2088 p.setDataPosition(0); 2089 assertEquals(string, creator.createFromParcel(p).toString()); 2090 } finally { 2091 p.recycle(); 2092 } 2093 2094 p = Parcel.obtain(); 2095 try { 2096 TextUtils.writeToParcel(null, p, 0); 2097 p.setDataPosition(0); 2098 assertNull(creator.createFromParcel(p)); 2099 } finally { 2100 p.recycle(); 2101 } 2102 2103 SpannableString spannableString = new SpannableString("Spannable String"); 2104 int urlSpanStart = spannableString.length() >> 1; 2105 int urlSpanEnd = spannableString.length(); 2106 p = Parcel.obtain(); 2107 try { 2108 URLSpan urlSpan = new URLSpan("URL Span"); 2109 spannableString.setSpan(urlSpan, urlSpanStart, urlSpanEnd, 2110 Spanned.SPAN_INCLUSIVE_INCLUSIVE); 2111 TextUtils.writeToParcel(spannableString, p, 0); 2112 p.setDataPosition(0); 2113 SpannableString ret = (SpannableString) creator.createFromParcel(p); 2114 assertEquals("Spannable String", ret.toString()); 2115 Object[] spans = ret.getSpans(0, ret.length(), Object.class); 2116 assertEquals(1, spans.length); 2117 assertEquals("URL Span", ((URLSpan) spans[0]).getURL()); 2118 assertEquals(urlSpanStart, ret.getSpanStart(spans[0])); 2119 assertEquals(urlSpanEnd, ret.getSpanEnd(spans[0])); 2120 assertEquals(Spanned.SPAN_INCLUSIVE_INCLUSIVE, ret.getSpanFlags(spans[0])); 2121 } finally { 2122 p.recycle(); 2123 } 2124 2125 p = Parcel.obtain(); 2126 try { 2127 ColorStateList colors = new ColorStateList(new int[][] { 2128 new int[] {android.R.attr.state_focused}, new int[0]}, 2129 new int[] {Color.rgb(0, 255, 0), Color.BLACK}); 2130 int textSize = 20; 2131 TextAppearanceSpan textAppearanceSpan = new TextAppearanceSpan( 2132 null, Typeface.ITALIC, textSize, colors, null); 2133 int textAppearanceSpanStart = 0; 2134 int textAppearanceSpanEnd = spannableString.length() >> 1; 2135 spannableString.setSpan(textAppearanceSpan, textAppearanceSpanStart, 2136 textAppearanceSpanEnd, Spanned.SPAN_INCLUSIVE_EXCLUSIVE); 2137 TextUtils.writeToParcel(spannableString, p, -1); 2138 p.setDataPosition(0); 2139 SpannableString ret = (SpannableString) creator.createFromParcel(p); 2140 assertEquals("Spannable String", ret.toString()); 2141 Object[] spans = ret.getSpans(0, ret.length(), Object.class); 2142 assertEquals(2, spans.length); 2143 assertEquals("URL Span", ((URLSpan) spans[0]).getURL()); 2144 assertEquals(urlSpanStart, ret.getSpanStart(spans[0])); 2145 assertEquals(urlSpanEnd, ret.getSpanEnd(spans[0])); 2146 assertEquals(Spanned.SPAN_INCLUSIVE_INCLUSIVE, ret.getSpanFlags(spans[0])); 2147 assertEquals(null, ((TextAppearanceSpan) spans[1]).getFamily()); 2148 2149 assertEquals(Typeface.ITALIC, ((TextAppearanceSpan) spans[1]).getTextStyle()); 2150 assertEquals(textSize, ((TextAppearanceSpan) spans[1]).getTextSize()); 2151 2152 assertEquals(colors.toString(), ((TextAppearanceSpan) spans[1]).getTextColor().toString()); 2153 assertEquals(null, ((TextAppearanceSpan) spans[1]).getLinkTextColor()); 2154 assertEquals(textAppearanceSpanStart, ret.getSpanStart(spans[1])); 2155 assertEquals(textAppearanceSpanEnd, ret.getSpanEnd(spans[1])); 2156 assertEquals(Spanned.SPAN_INCLUSIVE_EXCLUSIVE, ret.getSpanFlags(spans[1])); 2157 } finally { 2158 p.recycle(); 2159 } 2160 2161 try { 2162 TextUtils.writeToParcel(spannableString, null, 0); 2163 fail("Should throw NullPointerException!"); 2164 } catch (NullPointerException e) { 2165 // expected 2166 } 2167 } 2168 2169 @Test testGetCapsMode()2170 public void testGetCapsMode() { 2171 final int CAP_MODE_ALL = TextUtils.CAP_MODE_CHARACTERS 2172 | TextUtils.CAP_MODE_WORDS | TextUtils.CAP_MODE_SENTENCES; 2173 final int CAP_MODE_CHARACTERS_AND_WORD = 2174 TextUtils.CAP_MODE_CHARACTERS | TextUtils.CAP_MODE_WORDS; 2175 String testString = "Start. Sentence word!No space before\n\t" + 2176 "Paragraph? (\"\'skip begin\'\"). skip end"; 2177 2178 // CAP_MODE_SENTENCES should be in effect in the whole text. 2179 for (int i = 0; i < testString.length(); i++) { 2180 assertEquals(TextUtils.CAP_MODE_CHARACTERS, 2181 TextUtils.getCapsMode(testString, i, TextUtils.CAP_MODE_CHARACTERS)); 2182 } 2183 2184 // all modes should be in effect at the start of the text. 2185 assertEquals(TextUtils.CAP_MODE_WORDS, 2186 TextUtils.getCapsMode(testString, 0, TextUtils.CAP_MODE_WORDS)); 2187 // issue 1586346 2188 assertEquals(TextUtils.CAP_MODE_WORDS, 2189 TextUtils.getCapsMode(testString, 0, TextUtils.CAP_MODE_SENTENCES)); 2190 assertEquals(CAP_MODE_CHARACTERS_AND_WORD, 2191 TextUtils.getCapsMode(testString, 0, CAP_MODE_ALL)); 2192 2193 // all mode should be in effect at the position after "." or "?" or "!" + " ". 2194 int offset = testString.indexOf("Sentence word!"); 2195 assertEquals(TextUtils.CAP_MODE_WORDS, 2196 TextUtils.getCapsMode(testString, offset, TextUtils.CAP_MODE_WORDS)); 2197 assertEquals(TextUtils.CAP_MODE_SENTENCES, 2198 TextUtils.getCapsMode(testString, offset, TextUtils.CAP_MODE_SENTENCES)); 2199 // issue 1586346 2200 assertEquals(CAP_MODE_CHARACTERS_AND_WORD, 2201 TextUtils.getCapsMode(testString, 0, CAP_MODE_ALL)); 2202 2203 // CAP_MODE_SENTENCES should NOT be in effect at the position after other words + " ". 2204 offset = testString.indexOf("word!"); 2205 assertEquals(TextUtils.CAP_MODE_WORDS, 2206 TextUtils.getCapsMode(testString, offset, TextUtils.CAP_MODE_WORDS)); 2207 assertEquals(0, 2208 TextUtils.getCapsMode(testString, offset, TextUtils.CAP_MODE_SENTENCES)); 2209 // issue 1586346 2210 assertEquals(TextUtils.CAP_MODE_CHARACTERS, 2211 TextUtils.getCapsMode(testString, offset, CAP_MODE_ALL)); 2212 2213 // if no space after "." or "?" or "!", CAP_MODE_SENTENCES and CAP_MODE_WORDS 2214 // should NOT be in effect. 2215 offset = testString.indexOf("No space before"); 2216 assertEquals(0, 2217 TextUtils.getCapsMode(testString, offset, TextUtils.CAP_MODE_WORDS)); 2218 assertEquals(0, 2219 TextUtils.getCapsMode(testString, offset, TextUtils.CAP_MODE_SENTENCES)); 2220 assertEquals(TextUtils.CAP_MODE_CHARACTERS, 2221 TextUtils.getCapsMode(testString, offset, CAP_MODE_ALL)); 2222 2223 // all mode should be in effect at a beginning of a new paragraph. 2224 offset = testString.indexOf("Paragraph"); 2225 assertEquals(TextUtils.CAP_MODE_WORDS, 2226 TextUtils.getCapsMode(testString, offset, TextUtils.CAP_MODE_WORDS)); 2227 // issue 1586346 2228 assertEquals(TextUtils.CAP_MODE_WORDS, 2229 TextUtils.getCapsMode(testString, offset, TextUtils.CAP_MODE_SENTENCES)); 2230 assertEquals(CAP_MODE_CHARACTERS_AND_WORD, 2231 TextUtils.getCapsMode(testString, offset, CAP_MODE_ALL)); 2232 2233 // some special word which means the start of a sentence should be skipped. 2234 offset = testString.indexOf("skip begin"); 2235 assertEquals(TextUtils.CAP_MODE_WORDS, 2236 TextUtils.getCapsMode(testString, offset, TextUtils.CAP_MODE_WORDS)); 2237 assertEquals(TextUtils.CAP_MODE_SENTENCES, 2238 TextUtils.getCapsMode(testString, offset, TextUtils.CAP_MODE_SENTENCES)); 2239 // issue 1586346 2240 assertEquals(TextUtils.CAP_MODE_SENTENCES | TextUtils.CAP_MODE_CHARACTERS, 2241 TextUtils.getCapsMode(testString, offset, CAP_MODE_ALL)); 2242 2243 // some special word which means the end of a sentence should be skipped. 2244 offset = testString.indexOf("skip end"); 2245 assertEquals(TextUtils.CAP_MODE_WORDS, 2246 TextUtils.getCapsMode(testString, offset, TextUtils.CAP_MODE_WORDS)); 2247 assertEquals(TextUtils.CAP_MODE_SENTENCES, 2248 TextUtils.getCapsMode(testString, offset, TextUtils.CAP_MODE_SENTENCES)); 2249 // issue 1586346 2250 assertEquals(TextUtils.CAP_MODE_SENTENCES | TextUtils.CAP_MODE_CHARACTERS, 2251 TextUtils.getCapsMode(testString, offset, CAP_MODE_ALL)); 2252 } 2253 2254 @Test testGetCapsModeException()2255 public void testGetCapsModeException() { 2256 String testString = "Start. Sentence word!No space before\n\t" + 2257 "Paragraph? (\"\'skip begin\'\"). skip end"; 2258 2259 int offset = testString.indexOf("Sentence word!"); 2260 assertEquals(TextUtils.CAP_MODE_CHARACTERS, 2261 TextUtils.getCapsMode(null, offset, TextUtils.CAP_MODE_CHARACTERS)); 2262 2263 try { 2264 TextUtils.getCapsMode(null, offset, TextUtils.CAP_MODE_SENTENCES); 2265 fail("Should throw NullPointerException!"); 2266 } catch (NullPointerException e) { 2267 // expected 2268 } 2269 2270 assertEquals(0, TextUtils.getCapsMode(testString, -1, TextUtils.CAP_MODE_SENTENCES)); 2271 2272 try { 2273 TextUtils.getCapsMode(testString, testString.length() + 1, 2274 TextUtils.CAP_MODE_SENTENCES); 2275 fail("Should throw IndexOutOfBoundsException!"); 2276 } catch (IndexOutOfBoundsException e) { 2277 // expected 2278 } 2279 } 2280 2281 @Test 2282 @IgnoreUnderRavenwood(blockedBy = SpannableString.class) testDumpSpans()2283 public void testDumpSpans() { 2284 StringBuilder builder = new StringBuilder(); 2285 StringBuilderPrinter printer = new StringBuilderPrinter(builder); 2286 CharSequence source = "test dump spans"; 2287 String prefix = "prefix"; 2288 2289 assertEquals(0, builder.length()); 2290 TextUtils.dumpSpans(source, printer, prefix); 2291 assertTrue(builder.length() > 0); 2292 2293 builder = new StringBuilder(); 2294 printer = new StringBuilderPrinter(builder); 2295 assertEquals(0, builder.length()); 2296 SpannableString spanned = new SpannableString(source); 2297 spanned.setSpan(new Object(), 0, source.length(), Spanned.SPAN_INCLUSIVE_INCLUSIVE); 2298 TextUtils.dumpSpans(spanned, printer, prefix); 2299 assertTrue(builder.length() > 0); 2300 } 2301 2302 @Test 2303 @IgnoreUnderRavenwood(reason = "ICU") testGetLayoutDirectionFromLocale()2304 public void testGetLayoutDirectionFromLocale() { 2305 assertEquals(LAYOUT_DIRECTION_LTR, 2306 TextUtils.getLayoutDirectionFromLocale(null)); 2307 2308 assertEquals(LAYOUT_DIRECTION_LTR, 2309 TextUtils.getLayoutDirectionFromLocale(Locale.ENGLISH)); 2310 assertEquals(LAYOUT_DIRECTION_LTR, 2311 TextUtils.getLayoutDirectionFromLocale(Locale.CANADA)); 2312 assertEquals(LAYOUT_DIRECTION_LTR, 2313 TextUtils.getLayoutDirectionFromLocale(Locale.CANADA_FRENCH)); 2314 assertEquals(LAYOUT_DIRECTION_LTR, 2315 TextUtils.getLayoutDirectionFromLocale(Locale.FRANCE)); 2316 assertEquals(LAYOUT_DIRECTION_LTR, 2317 TextUtils.getLayoutDirectionFromLocale(Locale.FRENCH)); 2318 assertEquals(LAYOUT_DIRECTION_LTR, 2319 TextUtils.getLayoutDirectionFromLocale(Locale.GERMAN)); 2320 assertEquals(LAYOUT_DIRECTION_LTR, 2321 TextUtils.getLayoutDirectionFromLocale(Locale.GERMANY)); 2322 assertEquals(LAYOUT_DIRECTION_LTR, 2323 TextUtils.getLayoutDirectionFromLocale(Locale.ITALIAN)); 2324 assertEquals(LAYOUT_DIRECTION_LTR, 2325 TextUtils.getLayoutDirectionFromLocale(Locale.ITALY)); 2326 assertEquals(LAYOUT_DIRECTION_LTR, 2327 TextUtils.getLayoutDirectionFromLocale(Locale.UK)); 2328 assertEquals(LAYOUT_DIRECTION_LTR, 2329 TextUtils.getLayoutDirectionFromLocale(Locale.US)); 2330 2331 assertEquals(LAYOUT_DIRECTION_LTR, 2332 TextUtils.getLayoutDirectionFromLocale(Locale.ROOT)); 2333 2334 assertEquals(LAYOUT_DIRECTION_LTR, 2335 TextUtils.getLayoutDirectionFromLocale(Locale.CHINA)); 2336 assertEquals(LAYOUT_DIRECTION_LTR, 2337 TextUtils.getLayoutDirectionFromLocale(Locale.CHINESE)); 2338 assertEquals(LAYOUT_DIRECTION_LTR, 2339 TextUtils.getLayoutDirectionFromLocale(Locale.JAPAN)); 2340 assertEquals(LAYOUT_DIRECTION_LTR, 2341 TextUtils.getLayoutDirectionFromLocale(Locale.JAPANESE)); 2342 assertEquals(LAYOUT_DIRECTION_LTR, 2343 TextUtils.getLayoutDirectionFromLocale(Locale.KOREA)); 2344 assertEquals(LAYOUT_DIRECTION_LTR, 2345 TextUtils.getLayoutDirectionFromLocale(Locale.KOREAN)); 2346 assertEquals(LAYOUT_DIRECTION_LTR, 2347 TextUtils.getLayoutDirectionFromLocale(Locale.PRC)); 2348 assertEquals(LAYOUT_DIRECTION_LTR, 2349 TextUtils.getLayoutDirectionFromLocale(Locale.SIMPLIFIED_CHINESE)); 2350 assertEquals(LAYOUT_DIRECTION_LTR, 2351 TextUtils.getLayoutDirectionFromLocale(Locale.TAIWAN)); 2352 assertEquals(LAYOUT_DIRECTION_LTR, 2353 TextUtils.getLayoutDirectionFromLocale(Locale.TRADITIONAL_CHINESE)); 2354 2355 // Some languages always use an RTL script. 2356 for (Locale l : Locale.getAvailableLocales()) { 2357 String languageCode = l.getLanguage(); 2358 if (languageCode.equals("ar") || 2359 languageCode.equals("fa") || 2360 languageCode.equals("iw") || 2361 languageCode.equals("he") || 2362 languageCode.equals("ps") || 2363 languageCode.equals("ur")) { 2364 int direction = TextUtils.getLayoutDirectionFromLocale(l); 2365 assertEquals(l.toLanguageTag() + " not RTL: " + direction, 2366 LAYOUT_DIRECTION_RTL, direction); 2367 } 2368 } 2369 2370 // Other languages have some cases where they use an RTL script. 2371 String[] tags = { 2372 "pa-Arab", 2373 "pa-Arab-PK", 2374 "ps", 2375 "ps-AF", 2376 "uz-Arab", 2377 "uz-Arab-AF", 2378 }; 2379 for (String tag : tags) { 2380 Locale l = Locale.forLanguageTag(tag); 2381 int direction = TextUtils.getLayoutDirectionFromLocale(l); 2382 assertEquals(l.toLanguageTag() + " not RTL: " + direction, 2383 LAYOUT_DIRECTION_RTL, direction); 2384 } 2385 2386 // Locale without a real language 2387 Locale locale = Locale.forLanguageTag("zz"); 2388 assertEquals(LAYOUT_DIRECTION_LTR, 2389 TextUtils.getLayoutDirectionFromLocale(locale)); 2390 } 2391 } 2392