1/* 2 * Copyright (C) 2017 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.core.provider; 18 19import static androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP; 20 21import android.annotation.SuppressLint; 22import android.content.ContentResolver; 23import android.content.ContentUris; 24import android.content.Context; 25import android.content.pm.PackageInfo; 26import android.content.pm.PackageManager; 27import android.content.pm.ProviderInfo; 28import android.content.pm.Signature; 29import android.content.res.Resources; 30import android.database.Cursor; 31import android.graphics.Typeface; 32import android.net.Uri; 33import android.os.Build; 34import android.os.CancellationSignal; 35import android.os.Handler; 36import android.os.ParcelFileDescriptor; 37import android.os.Process; 38import android.provider.BaseColumns; 39 40import androidx.annotation.GuardedBy; 41import androidx.annotation.IntDef; 42import androidx.annotation.IntRange; 43import androidx.annotation.NonNull; 44import androidx.annotation.Nullable; 45import androidx.annotation.RequiresApi; 46import androidx.annotation.RestrictTo; 47import androidx.annotation.VisibleForTesting; 48import androidx.collection.LruCache; 49import androidx.collection.SimpleArrayMap; 50import androidx.core.content.res.FontResourcesParserCompat; 51import androidx.core.content.res.ResourcesCompat; 52import androidx.core.graphics.TypefaceCompat; 53import androidx.core.graphics.TypefaceCompatUtil; 54import androidx.core.provider.SelfDestructiveThread.ReplyCallback; 55import androidx.core.util.Preconditions; 56 57import java.lang.annotation.Retention; 58import java.lang.annotation.RetentionPolicy; 59import java.nio.ByteBuffer; 60import java.util.ArrayList; 61import java.util.Arrays; 62import java.util.Collections; 63import java.util.Comparator; 64import java.util.HashMap; 65import java.util.List; 66import java.util.Map; 67import java.util.concurrent.Callable; 68 69/** 70 * Utility class to deal with Font ContentProviders. 71 */ 72public class FontsContractCompat { 73 private static final String TAG = "FontsContractCompat"; 74 75 private FontsContractCompat() { } 76 77 /** 78 * Defines the constants used in a response from a Font Provider. The cursor returned from the 79 * query should have the ID column populated with the content uri ID for the resulting font. 80 * This should point to a real file or shared memory, as the client will mmap the given file 81 * descriptor. Pipes, sockets and other non-mmap-able file descriptors will fail to load in the 82 * client application. 83 */ 84 public static final class Columns implements BaseColumns { 85 /** 86 * Constant used to request data from a font provider. The cursor returned from the query 87 * may populate this column with a long for the font file ID. The client will request a file 88 * descriptor to "file/FILE_ID" with this ID immediately under the top-level content URI. If 89 * not present, the client will request a file descriptor to the top-level URI with the 90 * given base font ID. Note that several results may return the same file ID, e.g. for TTC 91 * files with different indices. 92 */ 93 public static final String FILE_ID = "file_id"; 94 /** 95 * Constant used to request data from a font provider. The cursor returned from the query 96 * should have this column populated with an int for the ttc index for the resulting font. 97 */ 98 public static final String TTC_INDEX = android.provider.FontsContract.Columns.TTC_INDEX; 99 /** 100 * Constant used to request data from a font provider. The cursor returned from the query 101 * may populate this column with the font variation settings String information for the 102 * font. 103 */ 104 public static final String VARIATION_SETTINGS = 105 android.provider.FontsContract.Columns.VARIATION_SETTINGS; 106 /** 107 * Constant used to request data from a font provider. The cursor returned from the query 108 * should have this column populated with the int weight for the resulting font. This value 109 * should be between 100 and 900. The most common values are 400 for regular weight and 700 110 * for bold weight. 111 */ 112 public static final String WEIGHT = android.provider.FontsContract.Columns.WEIGHT; 113 /** 114 * Constant used to request data from a font provider. The cursor returned from the query 115 * should have this column populated with the int italic for the resulting font. This should 116 * be 0 for regular style and 1 for italic. 117 */ 118 public static final String ITALIC = android.provider.FontsContract.Columns.ITALIC; 119 /** 120 * Constant used to request data from a font provider. The cursor returned from the query 121 * should have this column populated to indicate the result status of the 122 * query. This will be checked before any other data in the cursor. Possible values are 123 * {@link #RESULT_CODE_OK}, {@link #RESULT_CODE_FONT_NOT_FOUND}, 124 * {@link #RESULT_CODE_MALFORMED_QUERY} and {@link #RESULT_CODE_FONT_UNAVAILABLE}. If not 125 * present, {@link #RESULT_CODE_OK} will be assumed. 126 */ 127 public static final String RESULT_CODE = android.provider.FontsContract.Columns.RESULT_CODE; 128 129 /** 130 * Constant used to represent a result was retrieved successfully. The given fonts will be 131 * attempted to retrieve immediately via 132 * {@link android.content.ContentProvider#openFile(Uri, String)}. See {@link #RESULT_CODE}. 133 */ 134 public static final int RESULT_CODE_OK = 135 android.provider.FontsContract.Columns.RESULT_CODE_OK; 136 /** 137 * Constant used to represent a result was not found. See {@link #RESULT_CODE}. 138 */ 139 public static final int RESULT_CODE_FONT_NOT_FOUND = 140 android.provider.FontsContract.Columns.RESULT_CODE_FONT_NOT_FOUND; 141 /** 142 * Constant used to represent a result was found, but cannot be provided at this moment. Use 143 * this to indicate, for example, that a font needs to be fetched from the network. See 144 * {@link #RESULT_CODE}. 145 */ 146 public static final int RESULT_CODE_FONT_UNAVAILABLE = 147 android.provider.FontsContract.Columns.RESULT_CODE_FONT_UNAVAILABLE; 148 /** 149 * Constant used to represent that the query was not in a supported format by the provider. 150 * See {@link #RESULT_CODE}. 151 */ 152 public static final int RESULT_CODE_MALFORMED_QUERY = 153 android.provider.FontsContract.Columns.RESULT_CODE_MALFORMED_QUERY; 154 } 155 156 /** 157 * Constant used to identify the List of {@link ParcelFileDescriptor} item in the Bundle 158 * returned to the ResultReceiver in getFont. 159 * @hide 160 */ 161 @RestrictTo(LIBRARY_GROUP) 162 public static final String PARCEL_FONT_RESULTS = "font_results"; 163 164 // Error codes internal to the system, which can not come from a provider. To keep the number 165 // space open for new provider codes, these should all be negative numbers. 166 /** @hide */ 167 @RestrictTo(LIBRARY_GROUP) 168 /* package */ static final int RESULT_CODE_PROVIDER_NOT_FOUND = -1; 169 /** @hide */ 170 @RestrictTo(LIBRARY_GROUP) 171 /* package */ static final int RESULT_CODE_WRONG_CERTIFICATES = -2; 172 // Note -3 is used by FontRequestCallback to indicate the font failed to load. 173 174 static final LruCache<String, Typeface> sTypefaceCache = new LruCache<>(16); 175 176 private static final int BACKGROUND_THREAD_KEEP_ALIVE_DURATION_MS = 10000; 177 private static final SelfDestructiveThread sBackgroundThread = 178 new SelfDestructiveThread("fonts", Process.THREAD_PRIORITY_BACKGROUND, 179 BACKGROUND_THREAD_KEEP_ALIVE_DURATION_MS); 180 181 @NonNull 182 static TypefaceResult getFontInternal(final Context context, final FontRequest request, 183 int style) { 184 FontFamilyResult result; 185 try { 186 result = fetchFonts(context, null /* CancellationSignal */, request); 187 } catch (PackageManager.NameNotFoundException e) { 188 return new TypefaceResult(null, FontRequestCallback.FAIL_REASON_PROVIDER_NOT_FOUND); 189 } 190 if (result.getStatusCode() == FontFamilyResult.STATUS_OK) { 191 final Typeface typeface = TypefaceCompat.createFromFontInfo( 192 context, null /* CancellationSignal */, result.getFonts(), style); 193 return new TypefaceResult(typeface, typeface != null 194 ? FontRequestCallback.RESULT_OK 195 : FontRequestCallback.FAIL_REASON_FONT_LOAD_ERROR); 196 } 197 int resultCode = result.getStatusCode() == FontFamilyResult.STATUS_WRONG_CERTIFICATES 198 ? FontRequestCallback.FAIL_REASON_WRONG_CERTIFICATES 199 : FontRequestCallback.FAIL_REASON_FONT_LOAD_ERROR; 200 return new TypefaceResult(null, resultCode); 201 } 202 203 static final Object sLock = new Object(); 204 @GuardedBy("sLock") 205 static final SimpleArrayMap<String, ArrayList<ReplyCallback<TypefaceResult>>> 206 sPendingReplies = new SimpleArrayMap<>(); 207 208 private static final class TypefaceResult { 209 final Typeface mTypeface; 210 @FontRequestCallback.FontRequestFailReason final int mResult; 211 212 TypefaceResult(@Nullable Typeface typeface, 213 @FontRequestCallback.FontRequestFailReason int result) { 214 mTypeface = typeface; 215 mResult = result; 216 } 217 } 218 219 /** 220 * Used for tests, should not be used otherwise. 221 * @hide 222 **/ 223 @RestrictTo(LIBRARY_GROUP) 224 public static void resetCache() { 225 sTypefaceCache.evictAll(); 226 } 227 228 /** @hide */ 229 @RestrictTo(LIBRARY_GROUP) 230 public static Typeface getFontSync(final Context context, final FontRequest request, 231 final @Nullable ResourcesCompat.FontCallback fontCallback, 232 final @Nullable Handler handler, boolean isBlockingFetch, int timeout, 233 final int style) { 234 final String id = request.getIdentifier() + "-" + style; 235 Typeface cached = sTypefaceCache.get(id); 236 if (cached != null) { 237 if (fontCallback != null) { 238 fontCallback.onFontRetrieved(cached); 239 } 240 return cached; 241 } 242 243 if (isBlockingFetch && timeout == FontResourcesParserCompat.INFINITE_TIMEOUT_VALUE) { 244 // Wait forever. No need to post to the thread. 245 TypefaceResult typefaceResult = getFontInternal(context, request, style); 246 if (fontCallback != null) { 247 if (typefaceResult.mResult == FontFamilyResult.STATUS_OK) { 248 fontCallback.callbackSuccessAsync(typefaceResult.mTypeface, handler); 249 } else { 250 fontCallback.callbackFailAsync(typefaceResult.mResult, handler); 251 } 252 } 253 return typefaceResult.mTypeface; 254 } 255 256 final Callable<TypefaceResult> fetcher = new Callable<TypefaceResult>() { 257 @Override 258 public TypefaceResult call() throws Exception { 259 TypefaceResult typeface = getFontInternal(context, request, style); 260 if (typeface.mTypeface != null) { 261 sTypefaceCache.put(id, typeface.mTypeface); 262 } 263 return typeface; 264 } 265 }; 266 267 if (isBlockingFetch) { 268 try { 269 return sBackgroundThread.postAndWait(fetcher, timeout).mTypeface; 270 } catch (InterruptedException e) { 271 return null; 272 } 273 } else { 274 final ReplyCallback<TypefaceResult> reply = fontCallback == null ? null 275 : new ReplyCallback<TypefaceResult>() { 276 @Override 277 public void onReply(final TypefaceResult typeface) { 278 if (typeface == null) { 279 fontCallback.callbackFailAsync( 280 FontRequestCallback.FAIL_REASON_FONT_NOT_FOUND, handler); 281 } else if (typeface.mResult == FontFamilyResult.STATUS_OK) { 282 fontCallback.callbackSuccessAsync(typeface.mTypeface, handler); 283 } else { 284 fontCallback.callbackFailAsync(typeface.mResult, handler); 285 } 286 } 287 }; 288 289 synchronized (sLock) { 290 if (sPendingReplies.containsKey(id)) { 291 // Already requested. Do not request the same provider again and insert the 292 // reply to the queue instead. 293 if (reply != null) { 294 sPendingReplies.get(id).add(reply); 295 } 296 return null; 297 } 298 if (reply != null) { 299 ArrayList<ReplyCallback<TypefaceResult>> pendingReplies = new ArrayList<>(); 300 pendingReplies.add(reply); 301 sPendingReplies.put(id, pendingReplies); 302 } 303 } 304 sBackgroundThread.postAndReply(fetcher, new ReplyCallback<TypefaceResult>() { 305 @Override 306 public void onReply(final TypefaceResult typeface) { 307 final ArrayList<ReplyCallback<TypefaceResult>> replies; 308 synchronized (sLock) { 309 replies = sPendingReplies.get(id); 310 if (replies == null) { 311 return; // Nobody requested replies. Do nothing. 312 } 313 sPendingReplies.remove(id); 314 } 315 for (int i = 0; i < replies.size(); ++i) { 316 replies.get(i).onReply(typeface); 317 } 318 } 319 }); 320 return null; 321 } 322 } 323 324 /** 325 * Object represent a font entry in the family returned from {@link #fetchFonts}. 326 */ 327 public static class FontInfo { 328 private final Uri mUri; 329 private final int mTtcIndex; 330 private final int mWeight; 331 private final boolean mItalic; 332 private final int mResultCode; 333 334 /** 335 * Creates a Font with all the information needed about a provided font. 336 * @param uri A URI associated to the font file. 337 * @param ttcIndex If providing a TTC_INDEX file, the index to point to. Otherwise, 0. 338 * @param weight An integer that indicates the font weight. 339 * @param italic A boolean that indicates the font is italic style or not. 340 * @param resultCode A boolean that indicates the font contents is ready. 341 * 342 * @hide 343 */ 344 @RestrictTo(LIBRARY_GROUP) 345 public FontInfo(@NonNull Uri uri, @IntRange(from = 0) int ttcIndex, 346 @IntRange(from = 1, to = 1000) int weight, 347 boolean italic, int resultCode) { 348 mUri = Preconditions.checkNotNull(uri); 349 mTtcIndex = ttcIndex; 350 mWeight = weight; 351 mItalic = italic; 352 mResultCode = resultCode; 353 } 354 355 /** 356 * Returns a URI associated to this record. 357 */ 358 public @NonNull Uri getUri() { 359 return mUri; 360 } 361 362 /** 363 * Returns the index to be used to access this font when accessing a TTC file. 364 */ 365 public @IntRange(from = 0) int getTtcIndex() { 366 return mTtcIndex; 367 } 368 369 /** 370 * Returns the weight value for this font. 371 */ 372 public @IntRange(from = 1, to = 1000) int getWeight() { 373 return mWeight; 374 } 375 376 /** 377 * Returns whether this font is italic. 378 */ 379 public boolean isItalic() { 380 return mItalic; 381 } 382 383 /** 384 * Returns result code. 385 * 386 * {@link FontsContractCompat.Columns#RESULT_CODE} 387 */ 388 public int getResultCode() { 389 return mResultCode; 390 } 391 } 392 393 /** 394 * Object returned from {@link #fetchFonts}. 395 */ 396 public static class FontFamilyResult { 397 /** 398 * Constant represents that the font was successfully retrieved. Note that when this value 399 * is set and {@link #getFonts} returns an empty array, it means there were no fonts 400 * matching the given query. 401 */ 402 public static final int STATUS_OK = 0; 403 404 /** 405 * Constant represents that the given certificate was not matched with the provider's 406 * signature. {@link #getFonts} returns null if this status was set. 407 */ 408 public static final int STATUS_WRONG_CERTIFICATES = 1; 409 410 /** 411 * Constant represents that the provider returns unexpected data. {@link #getFonts} returns 412 * null if this status was set. For example, this value is set when the font provider 413 * gives invalid format of variation settings. 414 */ 415 public static final int STATUS_UNEXPECTED_DATA_PROVIDED = 2; 416 417 /** @hide */ 418 @RestrictTo(LIBRARY_GROUP) 419 @IntDef({STATUS_OK, STATUS_WRONG_CERTIFICATES, STATUS_UNEXPECTED_DATA_PROVIDED}) 420 @Retention(RetentionPolicy.SOURCE) 421 @interface FontResultStatus {} 422 423 private final @FontResultStatus int mStatusCode; 424 private final FontInfo[] mFonts; 425 426 /** @hide */ 427 @RestrictTo(LIBRARY_GROUP) 428 public FontFamilyResult(@FontResultStatus int statusCode, @Nullable FontInfo[] fonts) { 429 mStatusCode = statusCode; 430 mFonts = fonts; 431 } 432 433 public @FontResultStatus int getStatusCode() { 434 return mStatusCode; 435 } 436 437 public FontInfo[] getFonts() { 438 return mFonts; 439 } 440 } 441 442 /** 443 * Interface used to receive asynchronously fetched typefaces. 444 */ 445 public static class FontRequestCallback { 446 /** @hide */ 447 @RestrictTo(LIBRARY_GROUP) 448 public static final int RESULT_OK = Columns.RESULT_CODE_OK; 449 /** 450 * Constant returned by {@link #onTypefaceRequestFailed(int)} signaling that the given 451 * provider was not found on the device. 452 */ 453 public static final int FAIL_REASON_PROVIDER_NOT_FOUND = RESULT_CODE_PROVIDER_NOT_FOUND; 454 /** 455 * Constant returned by {@link #onTypefaceRequestFailed(int)} signaling that the given 456 * provider must be authenticated and the given certificates do not match its signature. 457 */ 458 public static final int FAIL_REASON_WRONG_CERTIFICATES = RESULT_CODE_WRONG_CERTIFICATES; 459 /** 460 * Constant returned by {@link #onTypefaceRequestFailed(int)} signaling that the font 461 * returned by the provider was not loaded properly. 462 */ 463 public static final int FAIL_REASON_FONT_LOAD_ERROR = -3; 464 /** 465 * Constant that signals that the font was not loaded due to security issues. This usually 466 * means the font was attempted to load on a restricted context. 467 */ 468 public static final int FAIL_REASON_SECURITY_VIOLATION = -4; 469 /** 470 * Constant returned by {@link #onTypefaceRequestFailed(int)} signaling that the font 471 * provider did not return any results for the given query. 472 */ 473 public static final int FAIL_REASON_FONT_NOT_FOUND = Columns.RESULT_CODE_FONT_NOT_FOUND; 474 /** 475 * Constant returned by {@link #onTypefaceRequestFailed(int)} signaling that the font 476 * provider found the queried font, but it is currently unavailable. 477 */ 478 public static final int FAIL_REASON_FONT_UNAVAILABLE = Columns.RESULT_CODE_FONT_UNAVAILABLE; 479 /** 480 * Constant returned by {@link #onTypefaceRequestFailed(int)} signaling that the given 481 * query was not supported by the provider. 482 */ 483 public static final int FAIL_REASON_MALFORMED_QUERY = Columns.RESULT_CODE_MALFORMED_QUERY; 484 485 /** @hide */ 486 @RestrictTo(LIBRARY_GROUP) 487 @IntDef({ FAIL_REASON_PROVIDER_NOT_FOUND, FAIL_REASON_FONT_LOAD_ERROR, 488 FAIL_REASON_FONT_NOT_FOUND, FAIL_REASON_FONT_UNAVAILABLE, 489 FAIL_REASON_MALFORMED_QUERY, FAIL_REASON_WRONG_CERTIFICATES, 490 FAIL_REASON_SECURITY_VIOLATION, RESULT_OK }) 491 @Retention(RetentionPolicy.SOURCE) 492 public @interface FontRequestFailReason {} 493 494 public FontRequestCallback() {} 495 496 /** 497 * Called then a Typeface request done via {@link #requestFont(Context, FontRequest, 498 * FontRequestCallback, Handler)} is complete. Note that this method will not be called if 499 * {@link #onTypefaceRequestFailed(int)} is called instead. 500 * @param typeface The Typeface object retrieved. 501 */ 502 public void onTypefaceRetrieved(Typeface typeface) {} 503 504 /** 505 * Called when a Typeface request done via {@link #requestFont(Context, FontRequest, 506 * FontRequestCallback, Handler)} fails. 507 * @param reason May be one of {@link #FAIL_REASON_PROVIDER_NOT_FOUND}, 508 * {@link #FAIL_REASON_FONT_NOT_FOUND}, 509 * {@link #FAIL_REASON_FONT_LOAD_ERROR}, 510 * {@link #FAIL_REASON_FONT_UNAVAILABLE}, 511 * {@link #FAIL_REASON_MALFORMED_QUERY} or 512 * {@link #FAIL_REASON_WRONG_CERTIFICATES}, or a provider defined positive 513 * code number. 514 */ 515 public void onTypefaceRequestFailed(@FontRequestFailReason int reason) {} 516 } 517 518 /** 519 * Create a typeface object given a font request. The font will be asynchronously fetched, 520 * therefore the result is delivered to the given callback. See {@link FontRequest}. 521 * Only one of the methods in callback will be invoked, depending on whether the request 522 * succeeds or fails. These calls will happen on the caller thread. 523 * @param context A context to be used for fetching from font provider. 524 * @param request A {@link FontRequest} object that identifies the provider and query for the 525 * request. May not be null. 526 * @param callback A callback that will be triggered when results are obtained. May not be null. 527 * @param handler A handler to be processed the font fetching. 528 */ 529 public static void requestFont(final @NonNull Context context, 530 final @NonNull FontRequest request, final @NonNull FontRequestCallback callback, 531 final @NonNull Handler handler) { 532 final Handler callerThreadHandler = new Handler(); 533 handler.post(new Runnable() { 534 @Override 535 public void run() { 536 // TODO: Cache the result. 537 FontFamilyResult result; 538 try { 539 result = fetchFonts(context, null /* cancellation signal */, request); 540 } catch (PackageManager.NameNotFoundException e) { 541 callerThreadHandler.post(new Runnable() { 542 @Override 543 public void run() { 544 callback.onTypefaceRequestFailed( 545 FontRequestCallback.FAIL_REASON_PROVIDER_NOT_FOUND); 546 } 547 }); 548 return; 549 } 550 551 if (result.getStatusCode() != FontFamilyResult.STATUS_OK) { 552 switch (result.getStatusCode()) { 553 case FontFamilyResult.STATUS_WRONG_CERTIFICATES: 554 callerThreadHandler.post(new Runnable() { 555 @Override 556 public void run() { 557 callback.onTypefaceRequestFailed( 558 FontRequestCallback.FAIL_REASON_WRONG_CERTIFICATES); 559 } 560 }); 561 return; 562 case FontFamilyResult.STATUS_UNEXPECTED_DATA_PROVIDED: 563 callerThreadHandler.post(new Runnable() { 564 @Override 565 public void run() { 566 callback.onTypefaceRequestFailed( 567 FontRequestCallback.FAIL_REASON_FONT_LOAD_ERROR); 568 } 569 }); 570 return; 571 default: 572 // fetchFont returns unexpected status type. Fallback to load error. 573 callerThreadHandler.post(new Runnable() { 574 @Override 575 public void run() { 576 callback.onTypefaceRequestFailed( 577 FontRequestCallback.FAIL_REASON_FONT_LOAD_ERROR); 578 } 579 }); 580 return; 581 } 582 } 583 584 final FontInfo[] fonts = result.getFonts(); 585 if (fonts == null || fonts.length == 0) { 586 callerThreadHandler.post(new Runnable() { 587 @Override 588 public void run() { 589 callback.onTypefaceRequestFailed( 590 FontRequestCallback.FAIL_REASON_FONT_NOT_FOUND); 591 } 592 }); 593 return; 594 } 595 for (final FontInfo font : fonts) { 596 if (font.getResultCode() != Columns.RESULT_CODE_OK) { 597 // We proceed if all font entry is ready to use. Otherwise report the first 598 // error. 599 final int resultCode = font.getResultCode(); 600 if (resultCode < 0) { 601 // Negative values are reserved for internal errors. Fallback to load 602 // error. 603 callerThreadHandler.post(new Runnable() { 604 @Override 605 public void run() { 606 callback.onTypefaceRequestFailed( 607 FontRequestCallback.FAIL_REASON_FONT_LOAD_ERROR); 608 } 609 }); 610 } else { 611 callerThreadHandler.post(new Runnable() { 612 @Override 613 public void run() { 614 callback.onTypefaceRequestFailed(resultCode); 615 } 616 }); 617 } 618 return; 619 } 620 } 621 622 final Typeface typeface = buildTypeface(context, null /* cancellation signal */, 623 fonts); 624 if (typeface == null) { 625 // Something went wrong during reading font files. This happens if the given 626 // font file is an unsupported font type. 627 callerThreadHandler.post(new Runnable() { 628 @Override 629 public void run() { 630 callback.onTypefaceRequestFailed( 631 FontRequestCallback.FAIL_REASON_FONT_LOAD_ERROR); 632 } 633 }); 634 return; 635 } 636 637 callerThreadHandler.post(new Runnable() { 638 @Override 639 public void run() { 640 callback.onTypefaceRetrieved(typeface); 641 } 642 }); 643 } 644 }); 645 } 646 647 /** 648 * Build a Typeface from an array of {@link FontInfo} 649 * 650 * Results that are marked as not ready will be skipped. 651 * 652 * @param context A {@link Context} that will be used to fetch the font contents. 653 * @param cancellationSignal A signal to cancel the operation in progress, or null if none. If 654 * the operation is canceled, then {@link 655 * android.os.OperationCanceledException} will be thrown. 656 * @param fonts An array of {@link FontInfo} to be used to create a Typeface. 657 * @return A Typeface object. Returns null if typeface creation fails. 658 */ 659 @Nullable 660 public static Typeface buildTypeface(@NonNull Context context, 661 @Nullable CancellationSignal cancellationSignal, @NonNull FontInfo[] fonts) { 662 return TypefaceCompat.createFromFontInfo(context, cancellationSignal, fonts, 663 Typeface.NORMAL); 664 } 665 666 /** 667 * A helper function to create a mapping from {@link Uri} to {@link ByteBuffer}. 668 * 669 * Skip if the file contents is not ready to be read. 670 * 671 * @param context A {@link Context} to be used for resolving content URI in 672 * {@link FontInfo}. 673 * @param fonts An array of {@link FontInfo}. 674 * @return A map from {@link Uri} to {@link ByteBuffer}. 675 * @hide 676 */ 677 @RestrictTo(LIBRARY_GROUP) 678 @RequiresApi(19) 679 public static Map<Uri, ByteBuffer> prepareFontData(Context context, FontInfo[] fonts, 680 CancellationSignal cancellationSignal) { 681 final HashMap<Uri, ByteBuffer> out = new HashMap<>(); 682 683 for (FontInfo font : fonts) { 684 if (font.getResultCode() != Columns.RESULT_CODE_OK) { 685 continue; 686 } 687 688 final Uri uri = font.getUri(); 689 if (out.containsKey(uri)) { 690 continue; 691 } 692 693 ByteBuffer buffer = TypefaceCompatUtil.mmap(context, cancellationSignal, uri); 694 out.put(uri, buffer); 695 } 696 return Collections.unmodifiableMap(out); 697 } 698 699 /** 700 * Fetch fonts given a font request. 701 * 702 * @param context A {@link Context} to be used for fetching fonts. 703 * @param cancellationSignal A signal to cancel the operation in progress, or null if none. If 704 * the operation is canceled, then {@link 705 * android.os.OperationCanceledException} will be thrown when the 706 * query is executed. 707 * @param request A {@link FontRequest} object that identifies the provider and query for the 708 * request. 709 * 710 * @return {@link FontFamilyResult} 711 * 712 * @throws PackageManager.NameNotFoundException If requested package or authority was not found 713 * in the system. 714 */ 715 @NonNull 716 public static FontFamilyResult fetchFonts(@NonNull Context context, 717 @Nullable CancellationSignal cancellationSignal, @NonNull FontRequest request) 718 throws PackageManager.NameNotFoundException { 719 ProviderInfo providerInfo = getProvider( 720 context.getPackageManager(), request, context.getResources()); 721 if (providerInfo == null) { 722 return new FontFamilyResult(FontFamilyResult.STATUS_WRONG_CERTIFICATES, null); 723 724 } 725 FontInfo[] fonts = getFontFromProvider( 726 context, request, providerInfo.authority, cancellationSignal); 727 return new FontFamilyResult(FontFamilyResult.STATUS_OK, fonts); 728 } 729 730 /** @hide */ 731 @VisibleForTesting 732 @RestrictTo(LIBRARY_GROUP) 733 public static @Nullable ProviderInfo getProvider(@NonNull PackageManager packageManager, 734 @NonNull FontRequest request, @Nullable Resources resources) 735 throws PackageManager.NameNotFoundException { 736 String providerAuthority = request.getProviderAuthority(); 737 ProviderInfo info = packageManager.resolveContentProvider(providerAuthority, 0); 738 if (info == null) { 739 throw new PackageManager.NameNotFoundException("No package found for authority: " 740 + providerAuthority); 741 } 742 743 if (!info.packageName.equals(request.getProviderPackage())) { 744 throw new PackageManager.NameNotFoundException("Found content provider " 745 + providerAuthority 746 + ", but package was not " + request.getProviderPackage()); 747 } 748 749 List<byte[]> signatures; 750 // We correctly check all signatures returned, as advised in the lint error. 751 @SuppressLint("PackageManagerGetSignatures") 752 PackageInfo packageInfo = packageManager.getPackageInfo(info.packageName, 753 PackageManager.GET_SIGNATURES); 754 signatures = convertToByteArrayList(packageInfo.signatures); 755 Collections.sort(signatures, sByteArrayComparator); 756 List<List<byte[]>> requestCertificatesList = getCertificates(request, resources); 757 for (int i = 0; i < requestCertificatesList.size(); ++i) { 758 // Make a copy so we can sort it without modifying the incoming data. 759 List<byte[]> requestSignatures = new ArrayList<>(requestCertificatesList.get(i)); 760 Collections.sort(requestSignatures, sByteArrayComparator); 761 if (equalsByteArrayList(signatures, requestSignatures)) { 762 return info; 763 } 764 } 765 return null; 766 } 767 768 private static List<List<byte[]>> getCertificates(FontRequest request, Resources resources) { 769 if (request.getCertificates() != null) { 770 return request.getCertificates(); 771 } 772 int resourceId = request.getCertificatesArrayResId(); 773 return FontResourcesParserCompat.readCerts(resources, resourceId); 774 } 775 776 private static final Comparator<byte[]> sByteArrayComparator = new Comparator<byte[]>() { 777 @Override 778 public int compare(byte[] l, byte[] r) { 779 if (l.length != r.length) { 780 return l.length - r.length; 781 } 782 for (int i = 0; i < l.length; ++i) { 783 if (l[i] != r[i]) { 784 return l[i] - r[i]; 785 } 786 } 787 return 0; 788 } 789 }; 790 791 private static boolean equalsByteArrayList(List<byte[]> signatures, 792 List<byte[]> requestSignatures) { 793 if (signatures.size() != requestSignatures.size()) { 794 return false; 795 } 796 for (int i = 0; i < signatures.size(); ++i) { 797 if (!Arrays.equals(signatures.get(i), requestSignatures.get(i))) { 798 return false; 799 } 800 } 801 return true; 802 } 803 804 private static List<byte[]> convertToByteArrayList(Signature[] signatures) { 805 List<byte[]> shas = new ArrayList<>(); 806 for (int i = 0; i < signatures.length; ++i) { 807 shas.add(signatures[i].toByteArray()); 808 } 809 return shas; 810 } 811 812 @VisibleForTesting 813 @NonNull 814 static FontInfo[] getFontFromProvider(Context context, FontRequest request, String authority, 815 CancellationSignal cancellationSignal) { 816 ArrayList<FontInfo> result = new ArrayList<>(); 817 final Uri uri = new Uri.Builder().scheme(ContentResolver.SCHEME_CONTENT) 818 .authority(authority) 819 .build(); 820 final Uri fileBaseUri = new Uri.Builder().scheme(ContentResolver.SCHEME_CONTENT) 821 .authority(authority) 822 .appendPath("file") 823 .build(); 824 Cursor cursor = null; 825 try { 826 if (Build.VERSION.SDK_INT > 16) { 827 cursor = context.getContentResolver().query(uri, new String[] { 828 Columns._ID, Columns.FILE_ID, Columns.TTC_INDEX, 829 Columns.VARIATION_SETTINGS, Columns.WEIGHT, Columns.ITALIC, 830 Columns.RESULT_CODE }, 831 "query = ?", new String[] { request.getQuery() }, null, cancellationSignal); 832 } else { 833 // No cancellation signal. 834 cursor = context.getContentResolver().query(uri, new String[] { 835 Columns._ID, Columns.FILE_ID, Columns.TTC_INDEX, 836 Columns.VARIATION_SETTINGS, Columns.WEIGHT, Columns.ITALIC, 837 Columns.RESULT_CODE }, 838 "query = ?", new String[] { request.getQuery() }, null); 839 } 840 if (cursor != null && cursor.getCount() > 0) { 841 final int resultCodeColumnIndex = cursor.getColumnIndex(Columns.RESULT_CODE); 842 result = new ArrayList<>(); 843 final int idColumnIndex = cursor.getColumnIndex(Columns._ID); 844 final int fileIdColumnIndex = cursor.getColumnIndex(Columns.FILE_ID); 845 final int ttcIndexColumnIndex = cursor.getColumnIndex(Columns.TTC_INDEX); 846 final int weightColumnIndex = cursor.getColumnIndex(Columns.WEIGHT); 847 final int italicColumnIndex = cursor.getColumnIndex(Columns.ITALIC); 848 while (cursor.moveToNext()) { 849 int resultCode = resultCodeColumnIndex != -1 850 ? cursor.getInt(resultCodeColumnIndex) : Columns.RESULT_CODE_OK; 851 final int ttcIndex = ttcIndexColumnIndex != -1 852 ? cursor.getInt(ttcIndexColumnIndex) : 0; 853 Uri fileUri; 854 if (fileIdColumnIndex == -1) { 855 long id = cursor.getLong(idColumnIndex); 856 fileUri = ContentUris.withAppendedId(uri, id); 857 } else { 858 long id = cursor.getLong(fileIdColumnIndex); 859 fileUri = ContentUris.withAppendedId(fileBaseUri, id); 860 } 861 862 int weight = weightColumnIndex != -1 ? cursor.getInt(weightColumnIndex) : 400; 863 boolean italic = italicColumnIndex != -1 && cursor.getInt(italicColumnIndex) 864 == 1; 865 result.add(new FontInfo(fileUri, ttcIndex, weight, italic, resultCode)); 866 } 867 } 868 } finally { 869 if (cursor != null) { 870 cursor.close(); 871 } 872 } 873 return result.toArray(new FontInfo[0]); 874 } 875} 876