1/* 2 * Copyright 2018 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 17package androidx.emoji.text; 18 19import static android.content.res.AssetManager.ACCESS_BUFFER; 20 21import static androidx.core.provider.FontsContractCompat.Columns.RESULT_CODE_FONT_NOT_FOUND; 22import static androidx.core.provider.FontsContractCompat.Columns.RESULT_CODE_FONT_UNAVAILABLE; 23import static androidx.core.provider.FontsContractCompat.Columns.RESULT_CODE_MALFORMED_QUERY; 24import static androidx.core.provider.FontsContractCompat.Columns.RESULT_CODE_OK; 25import static androidx.core.provider.FontsContractCompat.FontFamilyResult.STATUS_OK; 26import static androidx.core.provider.FontsContractCompat.FontFamilyResult.STATUS_WRONG_CERTIFICATES; 27 28import static org.hamcrest.CoreMatchers.containsString; 29import static org.junit.Assert.assertThat; 30import static org.junit.Assert.fail; 31import static org.mockito.Matchers.any; 32import static org.mockito.Matchers.eq; 33import static org.mockito.Matchers.same; 34import static org.mockito.Mockito.atLeastOnce; 35import static org.mockito.Mockito.doReturn; 36import static org.mockito.Mockito.doThrow; 37import static org.mockito.Mockito.mock; 38import static org.mockito.Mockito.never; 39import static org.mockito.Mockito.spy; 40import static org.mockito.Mockito.times; 41import static org.mockito.Mockito.verify; 42 43import android.content.Context; 44import android.content.pm.PackageManager.NameNotFoundException; 45import android.database.ContentObserver; 46import android.net.Uri; 47import android.os.Handler; 48import android.os.HandlerThread; 49import android.support.test.InstrumentationRegistry; 50import android.support.test.filters.SdkSuppress; 51import android.support.test.filters.SmallTest; 52import android.support.test.runner.AndroidJUnit4; 53 54import androidx.annotation.GuardedBy; 55import androidx.annotation.NonNull; 56import androidx.annotation.Nullable; 57import androidx.core.provider.FontRequest; 58import androidx.core.provider.FontsContractCompat.FontFamilyResult; 59import androidx.core.provider.FontsContractCompat.FontInfo; 60 61import org.junit.Before; 62import org.junit.Test; 63import org.junit.runner.RunWith; 64import org.mockito.ArgumentCaptor; 65 66import java.io.File; 67import java.io.FileOutputStream; 68import java.io.IOException; 69import java.io.InputStream; 70import java.util.ArrayList; 71import java.util.List; 72import java.util.concurrent.CountDownLatch; 73import java.util.concurrent.TimeUnit; 74 75@SmallTest 76@RunWith(AndroidJUnit4.class) 77public class FontRequestEmojiCompatConfigTest { 78 private static final int DEFAULT_TIMEOUT_MILLIS = 3000; 79 private Context mContext; 80 private FontRequest mFontRequest; 81 private FontRequestEmojiCompatConfig.FontProviderHelper mFontProviderHelper; 82 83 @Before 84 public void setup() { 85 mContext = InstrumentationRegistry.getContext(); 86 mFontRequest = new FontRequest("authority", "package", "query", 87 new ArrayList<List<byte[]>>()); 88 mFontProviderHelper = mock(FontRequestEmojiCompatConfig.FontProviderHelper.class); 89 } 90 91 @Test(expected = NullPointerException.class) 92 public void testConstructor_withNullContext() { 93 new FontRequestEmojiCompatConfig(null, mFontRequest); 94 } 95 96 @Test(expected = NullPointerException.class) 97 public void testConstructor_withNullFontRequest() { 98 new FontRequestEmojiCompatConfig(mContext, null); 99 } 100 101 @Test 102 @SdkSuppress(minSdkVersion = 19) 103 public void testLoad_whenGetFontThrowsException() throws NameNotFoundException { 104 final Exception exception = new RuntimeException(); 105 doThrow(exception).when(mFontProviderHelper).fetchFonts( 106 any(Context.class), any(FontRequest.class)); 107 final WaitingLoaderCallback callback = spy(new WaitingLoaderCallback()); 108 final EmojiCompat.Config config = new FontRequestEmojiCompatConfig(mContext, mFontRequest, 109 mFontProviderHelper); 110 111 config.getMetadataRepoLoader().load(callback); 112 callback.await(DEFAULT_TIMEOUT_MILLIS); 113 verify(callback, times(1)).onFailed(same(exception)); 114 } 115 116 @Test 117 @SdkSuppress(minSdkVersion = 19) 118 public void testLoad_providerNotFound() throws NameNotFoundException { 119 doThrow(new NameNotFoundException()).when(mFontProviderHelper).fetchFonts( 120 any(Context.class), any(FontRequest.class)); 121 final WaitingLoaderCallback callback = spy(new WaitingLoaderCallback()); 122 final EmojiCompat.Config config = new FontRequestEmojiCompatConfig(mContext, 123 mFontRequest, mFontProviderHelper); 124 125 config.getMetadataRepoLoader().load(callback); 126 callback.await(DEFAULT_TIMEOUT_MILLIS); 127 128 final ArgumentCaptor<Throwable> argumentCaptor = ArgumentCaptor.forClass(Throwable.class); 129 verify(callback, times(1)).onFailed(argumentCaptor.capture()); 130 assertThat(argumentCaptor.getValue().getMessage(), containsString("provider not found")); 131 } 132 133 @Test 134 @SdkSuppress(minSdkVersion = 19) 135 public void testLoad_wrongCertificate() throws NameNotFoundException { 136 verifyLoaderOnFailedCalled(STATUS_WRONG_CERTIFICATES, null /* fonts */, 137 "fetchFonts failed (" + STATUS_WRONG_CERTIFICATES + ")"); 138 } 139 140 @Test 141 @SdkSuppress(minSdkVersion = 19) 142 public void testLoad_fontNotFound() throws NameNotFoundException { 143 verifyLoaderOnFailedCalled(STATUS_OK, 144 getTestFontInfoWithInvalidPath(RESULT_CODE_FONT_NOT_FOUND), 145 "fetchFonts result is not OK. (" + RESULT_CODE_FONT_NOT_FOUND + ")"); 146 } 147 148 @Test 149 @SdkSuppress(minSdkVersion = 19) 150 public void testLoad_fontUnavailable() throws NameNotFoundException { 151 verifyLoaderOnFailedCalled(STATUS_OK, 152 getTestFontInfoWithInvalidPath(RESULT_CODE_FONT_UNAVAILABLE), 153 "fetchFonts result is not OK. (" + RESULT_CODE_FONT_UNAVAILABLE + ")"); 154 } 155 156 @Test 157 @SdkSuppress(minSdkVersion = 19) 158 public void testLoad_malformedQuery() throws NameNotFoundException { 159 verifyLoaderOnFailedCalled(STATUS_OK, 160 getTestFontInfoWithInvalidPath(RESULT_CODE_MALFORMED_QUERY), 161 "fetchFonts result is not OK. (" + RESULT_CODE_MALFORMED_QUERY + ")"); 162 } 163 164 @Test 165 @SdkSuppress(minSdkVersion = 19) 166 public void testLoad_resultNotFound() throws NameNotFoundException { 167 verifyLoaderOnFailedCalled(STATUS_OK, new FontInfo[] {}, 168 "fetchFonts failed (empty result)"); 169 } 170 171 @Test 172 @SdkSuppress(minSdkVersion = 19) 173 public void testLoad_nullFontInfo() throws NameNotFoundException { 174 verifyLoaderOnFailedCalled(STATUS_OK, null /* fonts */, 175 "fetchFonts failed (empty result)"); 176 } 177 178 @Test 179 @SdkSuppress(minSdkVersion = 19) 180 public void testLoad_cannotLoadTypeface() throws NameNotFoundException { 181 // getTestFontInfoWithInvalidPath returns FontInfo with invalid path to file. 182 verifyLoaderOnFailedCalled(STATUS_OK, 183 getTestFontInfoWithInvalidPath(RESULT_CODE_OK), 184 "Unable to open file."); 185 } 186 187 @Test 188 @SdkSuppress(minSdkVersion = 19) 189 public void testLoad_success() throws IOException, NameNotFoundException { 190 final File file = loadFont(mContext, "NotoColorEmojiCompat.ttf"); 191 final FontInfo[] fonts = new FontInfo[] { 192 new FontInfo(Uri.fromFile(file), 0 /* ttc index */, 400 /* weight */, 193 false /* italic */, RESULT_CODE_OK) 194 }; 195 doReturn(new FontFamilyResult(STATUS_OK, fonts)).when(mFontProviderHelper).fetchFonts( 196 any(Context.class), any(FontRequest.class)); 197 final WaitingLoaderCallback callback = spy(new WaitingLoaderCallback()); 198 final EmojiCompat.Config config = new FontRequestEmojiCompatConfig(mContext, 199 mFontRequest, mFontProviderHelper); 200 201 config.getMetadataRepoLoader().load(callback); 202 callback.await(DEFAULT_TIMEOUT_MILLIS); 203 verify(callback, times(1)).onLoaded(any(MetadataRepo.class)); 204 } 205 206 @Test 207 @SdkSuppress(minSdkVersion = 19) 208 public void testLoad_retryPolicy() throws IOException, NameNotFoundException { 209 final File file = loadFont(mContext, "NotoColorEmojiCompat.ttf"); 210 final FontInfo[] fonts = new FontInfo[] { 211 new FontInfo(Uri.fromFile(file), 0 /* ttc index */, 400 /* weight */, 212 false /* italic */, RESULT_CODE_FONT_UNAVAILABLE) 213 }; 214 doReturn(new FontFamilyResult(STATUS_OK, fonts)).when(mFontProviderHelper).fetchFonts( 215 any(Context.class), any(FontRequest.class)); 216 final WaitingLoaderCallback callback = spy(new WaitingLoaderCallback()); 217 final WaitingRetryPolicy retryPolicy = spy(new WaitingRetryPolicy(-1, 1)); 218 final EmojiCompat.Config config = new FontRequestEmojiCompatConfig(mContext, 219 mFontRequest, mFontProviderHelper).setRetryPolicy(retryPolicy); 220 221 config.getMetadataRepoLoader().load(callback); 222 callback.await(DEFAULT_TIMEOUT_MILLIS); 223 verify(callback, never()).onLoaded(any(MetadataRepo.class)); 224 verify(callback, times(1)).onFailed(any(Throwable.class)); 225 verify(retryPolicy, times(1)).getRetryDelay(); 226 } 227 228 @Test 229 @SdkSuppress(minSdkVersion = 19) 230 public void testLoad_keepRetryingAndGiveUp() throws IOException, NameNotFoundException { 231 final File file = loadFont(mContext, "NotoColorEmojiCompat.ttf"); 232 final FontInfo[] fonts = new FontInfo[] { 233 new FontInfo(Uri.fromFile(file), 0 /* ttc index */, 400 /* weight */, 234 false /* italic */, RESULT_CODE_FONT_UNAVAILABLE) 235 }; 236 doReturn(new FontFamilyResult(STATUS_OK, fonts)).when(mFontProviderHelper).fetchFonts( 237 any(Context.class), any(FontRequest.class)); 238 final WaitingLoaderCallback callback = spy(new WaitingLoaderCallback()); 239 final WaitingRetryPolicy retryPolicy = spy(new WaitingRetryPolicy(500, 1)); 240 final EmojiCompat.Config config = new FontRequestEmojiCompatConfig(mContext, 241 mFontRequest, mFontProviderHelper).setRetryPolicy(retryPolicy); 242 243 config.getMetadataRepoLoader().load(callback); 244 retryPolicy.await(DEFAULT_TIMEOUT_MILLIS); 245 verify(callback, never()).onLoaded(any(MetadataRepo.class)); 246 verify(callback, never()).onFailed(any(Throwable.class)); 247 verify(retryPolicy, atLeastOnce()).getRetryDelay(); 248 retryPolicy.changeReturnValue(-1); 249 callback.await(DEFAULT_TIMEOUT_MILLIS); 250 verify(callback, never()).onLoaded(any(MetadataRepo.class)); 251 verify(callback, times(1)).onFailed(any(Throwable.class)); 252 } 253 254 @Test 255 @SdkSuppress(minSdkVersion = 19) 256 public void testLoad_keepRetryingAndFail() throws IOException, NameNotFoundException { 257 final File file = loadFont(mContext, "NotoColorEmojiCompat.ttf"); 258 final Uri uri = Uri.fromFile(file); 259 260 final FontInfo[] fonts = new FontInfo[] { 261 new FontInfo(uri, 0 /* ttc index */, 400 /* weight */, 262 false /* italic */, RESULT_CODE_FONT_UNAVAILABLE) 263 }; 264 doReturn(new FontFamilyResult(STATUS_OK, fonts)).when(mFontProviderHelper).fetchFonts( 265 any(Context.class), any(FontRequest.class)); 266 final WaitingLoaderCallback callback = spy(new WaitingLoaderCallback()); 267 final WaitingRetryPolicy retryPolicy = spy(new WaitingRetryPolicy(500, 1)); 268 269 HandlerThread thread = new HandlerThread("testThread"); 270 thread.start(); 271 try { 272 Handler handler = new Handler(thread.getLooper()); 273 274 final EmojiCompat.Config config = new FontRequestEmojiCompatConfig(mContext, 275 mFontRequest, mFontProviderHelper).setHandler(handler) 276 .setRetryPolicy(retryPolicy); 277 278 config.getMetadataRepoLoader().load(callback); 279 retryPolicy.await(DEFAULT_TIMEOUT_MILLIS); 280 verify(callback, never()).onLoaded(any(MetadataRepo.class)); 281 verify(callback, never()).onFailed(any(Throwable.class)); 282 verify(retryPolicy, atLeastOnce()).getRetryDelay(); 283 284 // To avoid race condition, change the fetchFonts result on the handler thread. 285 handler.post(new Runnable() { 286 @Override 287 public void run() { 288 try { 289 final FontInfo[] fontsSuccess = new FontInfo[] { 290 new FontInfo(uri, 0 /* ttc index */, 400 /* weight */, 291 false /* italic */, RESULT_CODE_FONT_NOT_FOUND) 292 }; 293 294 doReturn(new FontFamilyResult(STATUS_OK, fontsSuccess)).when( 295 mFontProviderHelper).fetchFonts(any(Context.class), 296 any(FontRequest.class)); 297 } catch (NameNotFoundException e) { 298 throw new RuntimeException(e); 299 } 300 } 301 }); 302 303 callback.await(DEFAULT_TIMEOUT_MILLIS); 304 verify(callback, never()).onLoaded(any(MetadataRepo.class)); 305 verify(callback, times(1)).onFailed(any(Throwable.class)); 306 } finally { 307 thread.quit(); 308 } 309 } 310 311 @Test 312 @SdkSuppress(minSdkVersion = 19) 313 public void testLoad_keepRetryingAndSuccess() throws IOException, NameNotFoundException { 314 final File file = loadFont(mContext, "NotoColorEmojiCompat.ttf"); 315 final Uri uri = Uri.fromFile(file); 316 317 final FontInfo[] fonts = new FontInfo[]{ 318 new FontInfo(uri, 0 /* ttc index */, 400 /* weight */, 319 false /* italic */, RESULT_CODE_FONT_UNAVAILABLE) 320 }; 321 doReturn(new FontFamilyResult(STATUS_OK, fonts)).when(mFontProviderHelper).fetchFonts( 322 any(Context.class), any(FontRequest.class)); 323 final WaitingLoaderCallback callback = spy(new WaitingLoaderCallback()); 324 final WaitingRetryPolicy retryPolicy = spy(new WaitingRetryPolicy(500, 1)); 325 326 HandlerThread thread = new HandlerThread("testThread"); 327 thread.start(); 328 try { 329 Handler handler = new Handler(thread.getLooper()); 330 331 final EmojiCompat.Config config = new FontRequestEmojiCompatConfig(mContext, 332 mFontRequest, mFontProviderHelper).setHandler(handler) 333 .setRetryPolicy(retryPolicy); 334 335 config.getMetadataRepoLoader().load(callback); 336 retryPolicy.await(DEFAULT_TIMEOUT_MILLIS); 337 verify(callback, never()).onLoaded(any(MetadataRepo.class)); 338 verify(callback, never()).onFailed(any(Throwable.class)); 339 verify(retryPolicy, atLeastOnce()).getRetryDelay(); 340 341 final FontInfo[] fontsSuccess = new FontInfo[]{ 342 new FontInfo(uri, 0 /* ttc index */, 400 /* weight */, 343 false /* italic */, RESULT_CODE_OK) 344 }; 345 346 // To avoid race condition, change the fetchFonts result on the handler thread. 347 handler.post(new Runnable() { 348 @Override 349 public void run() { 350 try { 351 doReturn(new FontFamilyResult(STATUS_OK, fontsSuccess)).when( 352 mFontProviderHelper).fetchFonts(any(Context.class), 353 any(FontRequest.class)); 354 } catch (NameNotFoundException e) { 355 throw new RuntimeException(e); 356 } 357 } 358 }); 359 360 callback.await(DEFAULT_TIMEOUT_MILLIS); 361 verify(callback, times(1)).onLoaded(any(MetadataRepo.class)); 362 verify(callback, never()).onFailed(any(Throwable.class)); 363 } finally { 364 thread.quit(); 365 } 366 } 367 368 @Test 369 @SdkSuppress(minSdkVersion = 19) 370 public void testLoad_ObserverNotifyAndSuccess() throws IOException, NameNotFoundException { 371 final File file = loadFont(mContext, "NotoColorEmojiCompat.ttf"); 372 final Uri uri = Uri.fromFile(file); 373 final FontInfo[] fonts = new FontInfo[]{ 374 new FontInfo(uri, 0 /* ttc index */, 400 /* weight */, 375 false /* italic */, RESULT_CODE_FONT_UNAVAILABLE) 376 }; 377 doReturn(new FontFamilyResult(STATUS_OK, fonts)).when(mFontProviderHelper).fetchFonts( 378 any(Context.class), any(FontRequest.class)); 379 final WaitingLoaderCallback callback = spy(new WaitingLoaderCallback()); 380 final WaitingRetryPolicy retryPolicy = spy(new WaitingRetryPolicy(500, 2)); 381 382 HandlerThread thread = new HandlerThread("testThread"); 383 thread.start(); 384 try { 385 Handler handler = new Handler(thread.getLooper()); 386 final EmojiCompat.Config config = new FontRequestEmojiCompatConfig(mContext, 387 mFontRequest, mFontProviderHelper).setHandler(handler) 388 .setRetryPolicy(retryPolicy); 389 390 ArgumentCaptor<ContentObserver> observerCaptor = 391 ArgumentCaptor.forClass(ContentObserver.class); 392 393 config.getMetadataRepoLoader().load(callback); 394 retryPolicy.await(DEFAULT_TIMEOUT_MILLIS); 395 verify(callback, never()).onLoaded(any(MetadataRepo.class)); 396 verify(callback, never()).onFailed(any(Throwable.class)); 397 verify(retryPolicy, atLeastOnce()).getRetryDelay(); 398 verify(mFontProviderHelper, times(1)).registerObserver( 399 any(Context.class), eq(uri), observerCaptor.capture()); 400 401 final FontInfo[] fontsSuccess = new FontInfo[]{ 402 new FontInfo(uri, 0 /* ttc index */, 400 /* weight */, 403 false /* italic */, RESULT_CODE_OK) 404 }; 405 doReturn(new FontFamilyResult(STATUS_OK, fontsSuccess)).when( 406 mFontProviderHelper).fetchFonts(any(Context.class), any(FontRequest.class)); 407 408 final ContentObserver observer = observerCaptor.getValue(); 409 handler.post(new Runnable() { 410 @Override 411 public void run() { 412 observer.onChange(false /* self change */, uri); 413 } 414 }); 415 416 callback.await(DEFAULT_TIMEOUT_MILLIS); 417 verify(callback, times(1)).onLoaded(any(MetadataRepo.class)); 418 verify(callback, never()).onFailed(any(Throwable.class)); 419 } finally { 420 thread.quit(); 421 } 422 } 423 424 @Test 425 @SdkSuppress(minSdkVersion = 19) 426 public void testLoad_ObserverNotifyAndFail() throws IOException, NameNotFoundException { 427 final File file = loadFont(mContext, "NotoColorEmojiCompat.ttf"); 428 final Uri uri = Uri.fromFile(file); 429 final FontInfo[] fonts = new FontInfo[]{ 430 new FontInfo(uri, 0 /* ttc index */, 400 /* weight */, 431 false /* italic */, RESULT_CODE_FONT_UNAVAILABLE) 432 }; 433 doReturn(new FontFamilyResult(STATUS_OK, fonts)).when(mFontProviderHelper).fetchFonts( 434 any(Context.class), any(FontRequest.class)); 435 final WaitingLoaderCallback callback = spy(new WaitingLoaderCallback()); 436 final WaitingRetryPolicy retryPolicy = spy(new WaitingRetryPolicy(500, 2)); 437 438 HandlerThread thread = new HandlerThread("testThread"); 439 thread.start(); 440 try { 441 Handler handler = new Handler(thread.getLooper()); 442 final EmojiCompat.Config config = new FontRequestEmojiCompatConfig(mContext, 443 mFontRequest, mFontProviderHelper).setHandler(handler) 444 .setRetryPolicy(retryPolicy); 445 446 ArgumentCaptor<ContentObserver> observerCaptor = 447 ArgumentCaptor.forClass(ContentObserver.class); 448 449 config.getMetadataRepoLoader().load(callback); 450 retryPolicy.await(DEFAULT_TIMEOUT_MILLIS); 451 verify(callback, never()).onLoaded(any(MetadataRepo.class)); 452 verify(callback, never()).onFailed(any(Throwable.class)); 453 verify(retryPolicy, atLeastOnce()).getRetryDelay(); 454 verify(mFontProviderHelper, times(1)).registerObserver( 455 any(Context.class), eq(uri), observerCaptor.capture()); 456 457 final FontInfo[] fontsSuccess = new FontInfo[]{ 458 new FontInfo(uri, 0 /* ttc index */, 400 /* weight */, 459 false /* italic */, RESULT_CODE_FONT_NOT_FOUND) 460 }; 461 doReturn(new FontFamilyResult(STATUS_OK, fontsSuccess)).when( 462 mFontProviderHelper).fetchFonts(any(Context.class), any(FontRequest.class)); 463 464 final ContentObserver observer = observerCaptor.getValue(); 465 handler.post(new Runnable() { 466 @Override 467 public void run() { 468 observer.onChange(false /* self change */, uri); 469 } 470 }); 471 472 callback.await(DEFAULT_TIMEOUT_MILLIS); 473 verify(callback, never()).onLoaded(any(MetadataRepo.class)); 474 verify(callback, times(1)).onFailed(any(Throwable.class)); 475 } finally { 476 thread.quit(); 477 } 478 } 479 480 private void verifyLoaderOnFailedCalled(final int statusCode, 481 final FontInfo[] fonts, String exceptionMessage) throws NameNotFoundException { 482 doReturn(new FontFamilyResult(statusCode, fonts)).when(mFontProviderHelper).fetchFonts( 483 any(Context.class), any(FontRequest.class)); 484 final WaitingLoaderCallback callback = spy(new WaitingLoaderCallback()); 485 final EmojiCompat.Config config = new FontRequestEmojiCompatConfig(mContext, mFontRequest, 486 mFontProviderHelper); 487 488 config.getMetadataRepoLoader().load(callback); 489 callback.await(DEFAULT_TIMEOUT_MILLIS); 490 491 final ArgumentCaptor<Throwable> argumentCaptor = ArgumentCaptor.forClass(Throwable.class); 492 verify(callback, times(1)).onFailed(argumentCaptor.capture()); 493 assertThat(argumentCaptor.getValue().getMessage(), containsString(exceptionMessage)); 494 } 495 496 public static class WaitingRetryPolicy extends FontRequestEmojiCompatConfig.RetryPolicy { 497 private final CountDownLatch mLatch; 498 private final Object mLock = new Object(); 499 @GuardedBy("mLock") 500 private long mReturnValue; 501 502 public WaitingRetryPolicy(long returnValue, int callCount) { 503 mLatch = new CountDownLatch(callCount); 504 synchronized (mLock) { 505 mReturnValue = returnValue; 506 } 507 } 508 509 @Override 510 public long getRetryDelay() { 511 mLatch.countDown(); 512 synchronized (mLock) { 513 return mReturnValue; 514 } 515 } 516 517 public void changeReturnValue(long value) { 518 synchronized (mLock) { 519 mReturnValue = value; 520 } 521 } 522 523 public void await(long timeoutMillis) { 524 try { 525 mLatch.await(timeoutMillis, TimeUnit.MILLISECONDS); 526 } catch (InterruptedException e) { 527 throw new RuntimeException(e); 528 } 529 } 530 } 531 532 public static class WaitingLoaderCallback extends EmojiCompat.MetadataRepoLoaderCallback { 533 final CountDownLatch mLatch; 534 535 public WaitingLoaderCallback() { 536 mLatch = new CountDownLatch(1); 537 } 538 539 @Override 540 public void onLoaded(@NonNull MetadataRepo metadataRepo) { 541 mLatch.countDown(); 542 } 543 544 @Override 545 public void onFailed(@Nullable Throwable throwable) { 546 mLatch.countDown(); 547 } 548 549 public void await(long timeoutMillis) { 550 try { 551 mLatch.await(timeoutMillis, TimeUnit.MILLISECONDS); 552 } catch (InterruptedException e) { 553 throw new RuntimeException(e); 554 } 555 } 556 } 557 558 public static File loadFont(Context context, String fileName) { 559 File cacheFile = new File(context.getCacheDir(), fileName); 560 try { 561 copyToCacheFile(context, fileName, cacheFile); 562 return cacheFile; 563 } catch (IOException e) { 564 fail(); 565 } 566 return null; 567 } 568 569 private static void copyToCacheFile(final Context context, final String assetPath, 570 final File cacheFile) throws IOException { 571 try (InputStream is = context.getAssets().open(assetPath, ACCESS_BUFFER); 572 FileOutputStream fos = new FileOutputStream(cacheFile, false)) { 573 byte[] buffer = new byte[1024]; 574 int readLen; 575 while ((readLen = is.read(buffer)) != -1) { 576 fos.write(buffer, 0, readLen); 577 } 578 } 579 } 580 581 private FontInfo[] getTestFontInfoWithInvalidPath(int resultCode) { 582 return new FontInfo[] { new FontInfo(Uri.parse("file:///some/dummy/file"), 583 0 /* ttc index */, 400 /* weight */, false /* italic */, resultCode) }; 584 } 585} 586