FontsContract.java revision 54084b64b1860b652b1c50ba942b4cfc7fb28805
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 */ 16package android.provider; 17 18import static android.graphics.fonts.FontVariationAxis.InvalidFormatException; 19import static java.lang.annotation.RetentionPolicy.SOURCE; 20 21import android.annotation.IntDef; 22import android.annotation.IntRange; 23import android.annotation.NonNull; 24import android.annotation.Nullable; 25import android.content.ContentResolver; 26import android.content.ContentUris; 27import android.content.Context; 28import android.content.pm.PackageInfo; 29import android.content.pm.PackageManager.NameNotFoundException; 30import android.content.pm.PackageManager; 31import android.content.pm.ProviderInfo; 32import android.content.pm.Signature; 33import android.database.Cursor; 34import android.graphics.Typeface; 35import android.graphics.fonts.FontRequest; 36import android.graphics.fonts.FontVariationAxis; 37import android.net.Uri; 38import android.os.Bundle; 39import android.os.CancellationSignal; 40import android.os.Handler; 41import android.os.HandlerThread; 42import android.os.ParcelFileDescriptor; 43import android.os.Process; 44import android.os.ResultReceiver; 45import android.util.ArraySet; 46import android.util.Log; 47import android.util.LruCache; 48 49import com.android.internal.annotations.GuardedBy; 50import com.android.internal.annotations.VisibleForTesting; 51import com.android.internal.util.Preconditions; 52 53import java.io.FileInputStream; 54import java.io.FileNotFoundException; 55import java.io.IOException; 56import java.lang.annotation.Retention; 57import java.lang.annotation.RetentionPolicy; 58import java.nio.ByteBuffer; 59import java.nio.channels.FileChannel; 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.Set; 68 69/** 70 * Utility class to deal with Font ContentProviders. 71 */ 72public class FontsContract { 73 private static final String TAG = "FontsContract"; 74 75 /** 76 * Defines the constants used in a response from a Font Provider. The cursor returned from the 77 * query should have the ID column populated with the content uri ID for the resulting font. 78 * This should point to a real file or shared memory, as the client will mmap the given file 79 * descriptor. Pipes, sockets and other non-mmap-able file descriptors will fail to load in the 80 * client application. 81 */ 82 public static final class Columns implements BaseColumns { 83 84 // Do not instantiate. 85 private Columns() {} 86 87 /** 88 * Constant used to request data from a font provider. The cursor returned from the query 89 * may populate this column with a long for the font file ID. The client will request a file 90 * descriptor to "file/FILE_ID" with this ID immediately under the top-level content URI. If 91 * not present, the client will request a file descriptor to the top-level URI with the 92 * given base font ID. Note that several results may return the same file ID, e.g. for TTC 93 * files with different indices. 94 */ 95 public static final String FILE_ID = "file_id"; 96 /** 97 * Constant used to request data from a font provider. The cursor returned from the query 98 * should have this column populated with an int for the ttc index for the resulting font. 99 */ 100 public static final String TTC_INDEX = "font_ttc_index"; 101 /** 102 * Constant used to request data from a font provider. The cursor returned from the query 103 * may populate this column with the font variation settings String information for the 104 * font. 105 */ 106 public static final String VARIATION_SETTINGS = "font_variation_settings"; 107 /** 108 * Constant used to request data from a font provider. The cursor returned from the query 109 * should have this column populated with the int weight for the resulting font. This value 110 * should be between 100 and 900. The most common values are 400 for regular weight and 700 111 * for bold weight. 112 */ 113 public static final String WEIGHT = "font_weight"; 114 /** 115 * Constant used to request data from a font provider. The cursor returned from the query 116 * should have this column populated with the int italic for the resulting font. This should 117 * be 0 for regular style and 1 for italic. 118 */ 119 public static final String ITALIC = "font_italic"; 120 /** 121 * Constant used to request data from a font provider. The cursor returned from the query 122 * should have this column populated to indicate the result status of the 123 * query. This will be checked before any other data in the cursor. Possible values are 124 * {@link #RESULT_CODE_OK}, {@link #RESULT_CODE_FONT_NOT_FOUND}, 125 * {@link #RESULT_CODE_MALFORMED_QUERY} and {@link #RESULT_CODE_FONT_UNAVAILABLE} for system 126 * defined values. You may also define your own values in the 0x000010000..0xFFFF0000 range. 127 * If not present, {@link #RESULT_CODE_OK} will be assumed. 128 */ 129 public static final String RESULT_CODE = "result_code"; 130 131 /** 132 * Constant used to represent a result was retrieved successfully. The given fonts will be 133 * attempted to retrieve immediately via 134 * {@link android.content.ContentProvider#openFile(Uri, String)}. See {@link #RESULT_CODE}. 135 */ 136 public static final int RESULT_CODE_OK = 0; 137 /** 138 * Constant used to represent a result was not found. See {@link #RESULT_CODE}. 139 */ 140 public static final int RESULT_CODE_FONT_NOT_FOUND = 1; 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 = 2; 147 /** 148 * Constant used to represent that the query was not in a supported format by the provider. 149 * See {@link #RESULT_CODE}. 150 */ 151 public static final int RESULT_CODE_MALFORMED_QUERY = 3; 152 } 153 154 private static final Object sLock = new Object(); 155 @GuardedBy("sLock") 156 private static Handler sHandler; 157 @GuardedBy("sLock") 158 private static HandlerThread sThread; 159 @GuardedBy("sLock") 160 private static Set<String> sInQueueSet; 161 162 private volatile static Context sContext; // set once in setApplicationContextForResources 163 164 private static final LruCache<String, Typeface> sTypefaceCache = new LruCache<>(16); 165 166 private FontsContract() { 167 } 168 169 /** @hide */ 170 public static void setApplicationContextForResources(Context context) { 171 sContext = context.getApplicationContext(); 172 } 173 174 /** 175 * Object represent a font entry in the family returned from {@link #fetchFonts}. 176 */ 177 public static class FontInfo { 178 private final Uri mUri; 179 private final int mTtcIndex; 180 private final FontVariationAxis[] mAxes; 181 private final int mWeight; 182 private final boolean mItalic; 183 private final int mResultCode; 184 185 /** 186 * Creates a Font with all the information needed about a provided font. 187 * @param uri A URI associated to the font file. 188 * @param ttcIndex If providing a TTC_INDEX file, the index to point to. Otherwise, 0. 189 * @param axes If providing a variation font, the settings for it. May be null. 190 * @param weight An integer that indicates the font weight. 191 * @param italic A boolean that indicates the font is italic style or not. 192 * @param resultCode A boolean that indicates the font contents is ready. 193 */ 194 /** @hide */ 195 public FontInfo(@NonNull Uri uri, @IntRange(from = 0) int ttcIndex, 196 @Nullable FontVariationAxis[] axes, @IntRange(from = 1, to = 1000) int weight, 197 boolean italic, int resultCode) { 198 mUri = Preconditions.checkNotNull(uri); 199 mTtcIndex = ttcIndex; 200 mAxes = axes; 201 mWeight = weight; 202 mItalic = italic; 203 mResultCode = resultCode; 204 } 205 206 /** 207 * Returns a URI associated to this record. 208 */ 209 public @NonNull Uri getUri() { 210 return mUri; 211 } 212 213 /** 214 * Returns the index to be used to access this font when accessing a TTC file. 215 */ 216 public @IntRange(from = 0) int getTtcIndex() { 217 return mTtcIndex; 218 } 219 220 /** 221 * Returns the list of axes associated to this font. 222 */ 223 public @Nullable FontVariationAxis[] getAxes() { 224 return mAxes; 225 } 226 227 /** 228 * Returns the weight value for this font. 229 */ 230 public @IntRange(from = 1, to = 1000) int getWeight() { 231 return mWeight; 232 } 233 234 /** 235 * Returns whether this font is italic. 236 */ 237 public boolean isItalic() { 238 return mItalic; 239 } 240 241 /** 242 * Returns result code. 243 * 244 * {@link FontsContract.Columns#RESULT_CODE} 245 */ 246 public int getResultCode() { 247 return mResultCode; 248 } 249 } 250 251 /** 252 * Object returned from {@link #fetchFonts}. 253 */ 254 public static class FontFamilyResult { 255 /** 256 * Constant represents that the font was successfully retrieved. Note that when this value 257 * is set and {@link #getFonts} returns an empty array, it means there were no fonts 258 * matching the given query. 259 */ 260 public static final int STATUS_OK = 0; 261 262 /** 263 * Constant represents that the given certificate was not matched with the provider's 264 * signature. {@link #getFonts} returns null if this status was set. 265 */ 266 public static final int STATUS_WRONG_CERTIFICATES = 1; 267 268 /** 269 * Constant represents that the provider returns unexpected data. {@link #getFonts} returns 270 * null if this status was set. For example, this value is set when the font provider 271 * gives invalid format of variation settings. 272 */ 273 public static final int STATUS_UNEXPECTED_DATA_PROVIDED = 2; 274 275 /** 276 * Constant represents that the fetching font data was rejected by system. This happens if 277 * the passed context is restricted. 278 */ 279 public static final int STATUS_REJECTED = 3; 280 281 /** @hide */ 282 @IntDef({STATUS_OK, STATUS_WRONG_CERTIFICATES, STATUS_UNEXPECTED_DATA_PROVIDED}) 283 @Retention(RetentionPolicy.SOURCE) 284 @interface FontResultStatus {} 285 286 private final @FontResultStatus int mStatusCode; 287 private final FontInfo[] mFonts; 288 289 /** @hide */ 290 public FontFamilyResult(@FontResultStatus int statusCode, @Nullable FontInfo[] fonts) { 291 mStatusCode = statusCode; 292 mFonts = fonts; 293 } 294 295 public @FontResultStatus int getStatusCode() { 296 return mStatusCode; 297 } 298 299 public @NonNull FontInfo[] getFonts() { 300 return mFonts; 301 } 302 } 303 304 private static final int THREAD_RENEWAL_THRESHOLD_MS = 10000; 305 306 // We use a background thread to post the content resolving work for all requests on. This 307 // thread should be quit/stopped after all requests are done. 308 // TODO: Factor out to other class. Consider to switch MessageQueue.IdleHandler. 309 private static final Runnable sReplaceDispatcherThreadRunnable = new Runnable() { 310 @Override 311 public void run() { 312 synchronized (sLock) { 313 if (sThread != null) { 314 sThread.quitSafely(); 315 sThread = null; 316 sHandler = null; 317 sInQueueSet = null; 318 } 319 } 320 } 321 }; 322 323 /** @hide */ 324 public static Typeface getFontOrWarmUpCache(FontRequest request) { 325 final String id = request.getIdentifier(); 326 Typeface cachedTypeface = sTypefaceCache.get(id); 327 if (cachedTypeface != null) { 328 return cachedTypeface; 329 } 330 331 // Unfortunately the typeface is not available at this time, but requesting from the font 332 // provider takes too much time. For now, request the font data to ensure it is in the cache 333 // next time and return. 334 synchronized (sLock) { 335 if (sHandler == null) { 336 sThread = new HandlerThread("fonts", Process.THREAD_PRIORITY_BACKGROUND); 337 sThread.start(); 338 sHandler = new Handler(sThread.getLooper()); 339 sInQueueSet = new ArraySet<>(); 340 } 341 if (sInQueueSet.contains(id)) { 342 return null; // Already requested. 343 } 344 sInQueueSet.add(id); 345 sHandler.post(() -> { 346 synchronized (sLock) { 347 sInQueueSet.remove(id); 348 } 349 try { 350 FontFamilyResult result = fetchFonts(sContext, null, request); 351 if (result.getStatusCode() == FontFamilyResult.STATUS_OK) { 352 Typeface typeface = buildTypeface(sContext, null, result.getFonts()); 353 if (typeface != null) { 354 sTypefaceCache.put(id, typeface); 355 } 356 } 357 } catch (NameNotFoundException e) { 358 // Ignore. 359 } 360 }); 361 sHandler.removeCallbacks(sReplaceDispatcherThreadRunnable); 362 sHandler.postDelayed(sReplaceDispatcherThreadRunnable, THREAD_RENEWAL_THRESHOLD_MS); 363 } 364 return null; 365 } 366 367 /** 368 * Interface used to receive asynchronously fetched typefaces. 369 */ 370 public static class FontRequestCallback { 371 /** 372 * Constant returned by {@link #onTypefaceRequestFailed(int)} signaling that the given 373 * provider was not found on the device. 374 */ 375 public static final int FAIL_REASON_PROVIDER_NOT_FOUND = -1; 376 /** 377 * Constant returned by {@link #onTypefaceRequestFailed(int)} signaling that the given 378 * provider must be authenticated and the given certificates do not match its signature. 379 */ 380 public static final int FAIL_REASON_WRONG_CERTIFICATES = -2; 381 /** 382 * Constant returned by {@link #onTypefaceRequestFailed(int)} signaling that the font 383 * returned by the provider was not loaded properly. 384 */ 385 public static final int FAIL_REASON_FONT_LOAD_ERROR = -3; 386 /** 387 * Constant returned by {@link #onTypefaceRequestFailed(int)} signaling that the font 388 * provider did not return any results for the given query. 389 */ 390 public static final int FAIL_REASON_FONT_NOT_FOUND = Columns.RESULT_CODE_FONT_NOT_FOUND; 391 /** 392 * Constant returned by {@link #onTypefaceRequestFailed(int)} signaling that the font 393 * provider found the queried font, but it is currently unavailable. 394 */ 395 public static final int FAIL_REASON_FONT_UNAVAILABLE = Columns.RESULT_CODE_FONT_UNAVAILABLE; 396 /** 397 * Constant returned by {@link #onTypefaceRequestFailed(int)} signaling that the given 398 * query was not supported by the provider. 399 */ 400 public static final int FAIL_REASON_MALFORMED_QUERY = Columns.RESULT_CODE_MALFORMED_QUERY; 401 402 /** @hide */ 403 @IntDef({ FAIL_REASON_PROVIDER_NOT_FOUND, FAIL_REASON_FONT_LOAD_ERROR, 404 FAIL_REASON_FONT_NOT_FOUND, FAIL_REASON_FONT_UNAVAILABLE, 405 FAIL_REASON_MALFORMED_QUERY }) 406 @Retention(RetentionPolicy.SOURCE) 407 @interface FontRequestFailReason {} 408 409 public FontRequestCallback() {} 410 411 /** 412 * Called then a Typeface request done via {@link #requestFont} is complete. Note that this 413 * method will not be called if {@link #onTypefaceRequestFailed(int)} is called instead. 414 * @param typeface The Typeface object retrieved. 415 */ 416 public void onTypefaceRetrieved(Typeface typeface) {} 417 418 /** 419 * Called when a Typeface request done via {@link #requestFont}} fails. 420 * @param reason One of {@link #FAIL_REASON_PROVIDER_NOT_FOUND}, 421 * {@link #FAIL_REASON_FONT_NOT_FOUND}, 422 * {@link #FAIL_REASON_FONT_LOAD_ERROR}, 423 * {@link #FAIL_REASON_FONT_UNAVAILABLE} or 424 * {@link #FAIL_REASON_MALFORMED_QUERY} if returned by the system. May also be 425 * a positive value greater than 0 defined by the font provider as an 426 * additional error code. Refer to the provider's documentation for more 427 * information on possible returned error codes. 428 */ 429 public void onTypefaceRequestFailed(@FontRequestFailReason int reason) {} 430 } 431 432 /** 433 * Create a typeface object given a font request. The font will be asynchronously fetched, 434 * therefore the result is delivered to the given callback. See {@link FontRequest}. 435 * Only one of the methods in callback will be invoked, depending on whether the request 436 * succeeds or fails. These calls will happen on the caller thread. 437 * 438 * Note that the result Typeface may be cached internally and the same instance will be returned 439 * the next time you call this method with the same request. If you want to bypass this cache, 440 * use {@link #fetchFonts} and {@link #buildTypeface} instead. 441 * 442 * @param context A context to be used for fetching from font provider. 443 * @param request A {@link FontRequest} object that identifies the provider and query for the 444 * request. May not be null. 445 * @param callback A callback that will be triggered when results are obtained. May not be null. 446 * @param handler A handler to be processed the font fetching. 447 */ 448 public static void requestFont(@NonNull Context context, @NonNull FontRequest request, 449 @NonNull FontRequestCallback callback, @NonNull Handler handler) { 450 451 final Handler callerThreadHandler = new Handler(); 452 final Typeface cachedTypeface = sTypefaceCache.get(request.getIdentifier()); 453 if (cachedTypeface != null) { 454 callerThreadHandler.post(() -> callback.onTypefaceRetrieved(cachedTypeface)); 455 return; 456 } 457 458 handler.post(() -> { 459 FontFamilyResult result; 460 try { 461 result = fetchFonts(context, null /* cancellation signal */, request); 462 } catch (NameNotFoundException e) { 463 callerThreadHandler.post(() -> callback.onTypefaceRequestFailed( 464 FontRequestCallback.FAIL_REASON_PROVIDER_NOT_FOUND)); 465 return; 466 } 467 468 // Same request might be dispatched during fetchFonts. Check the cache again. 469 final Typeface anotherCachedTypeface = sTypefaceCache.get(request.getIdentifier()); 470 if (anotherCachedTypeface != null) { 471 callerThreadHandler.post(() -> callback.onTypefaceRetrieved(anotherCachedTypeface)); 472 return; 473 } 474 475 if (result.getStatusCode() != FontFamilyResult.STATUS_OK) { 476 switch (result.getStatusCode()) { 477 case FontFamilyResult.STATUS_WRONG_CERTIFICATES: 478 callerThreadHandler.post(() -> callback.onTypefaceRequestFailed( 479 FontRequestCallback.FAIL_REASON_WRONG_CERTIFICATES)); 480 return; 481 case FontFamilyResult.STATUS_UNEXPECTED_DATA_PROVIDED: 482 callerThreadHandler.post(() -> callback.onTypefaceRequestFailed( 483 FontRequestCallback.FAIL_REASON_FONT_LOAD_ERROR)); 484 return; 485 default: 486 // fetchFont returns unexpected status type. Fallback to load error. 487 callerThreadHandler.post(() -> callback.onTypefaceRequestFailed( 488 FontRequestCallback.FAIL_REASON_FONT_LOAD_ERROR)); 489 return; 490 } 491 } 492 493 final FontInfo[] fonts = result.getFonts(); 494 if (fonts == null || fonts.length == 0) { 495 callerThreadHandler.post(() -> callback.onTypefaceRequestFailed( 496 FontRequestCallback.FAIL_REASON_FONT_NOT_FOUND)); 497 return; 498 } 499 for (final FontInfo font : fonts) { 500 if (font.getResultCode() != Columns.RESULT_CODE_OK) { 501 // We proceed if all font entry is ready to use. Otherwise report the first 502 // error. 503 final int resultCode = font.getResultCode(); 504 if (resultCode < 0) { 505 // Negative values are reserved for internal errors. Fallback to load error. 506 callerThreadHandler.post(() -> callback.onTypefaceRequestFailed( 507 FontRequestCallback.FAIL_REASON_FONT_LOAD_ERROR)); 508 } else { 509 callerThreadHandler.post(() -> callback.onTypefaceRequestFailed( 510 resultCode)); 511 } 512 return; 513 } 514 } 515 516 final Typeface typeface = buildTypeface(context, null /* cancellation signal */, fonts); 517 if (typeface == null) { 518 // Something went wrong during reading font files. This happens if the given font 519 // file is an unsupported font type. 520 callerThreadHandler.post(() -> callback.onTypefaceRequestFailed( 521 FontRequestCallback.FAIL_REASON_FONT_LOAD_ERROR)); 522 return; 523 } 524 525 sTypefaceCache.put(request.getIdentifier(), typeface); 526 callerThreadHandler.post(() -> callback.onTypefaceRetrieved(typeface)); 527 }); 528 } 529 530 /** 531 * Fetch fonts given a font request. 532 * 533 * @param context A {@link Context} to be used for fetching fonts. 534 * @param cancellationSignal A signal to cancel the operation in progress, or null if none. If 535 * the operation is canceled, then {@link 536 * android.os.OperationCanceledException} will be thrown when the 537 * query is executed. 538 * @param request A {@link FontRequest} object that identifies the provider and query for the 539 * request. 540 * 541 * @return {@link FontFamilyResult} 542 * 543 * @throws NameNotFoundException If requested package or authority was not found in system. 544 */ 545 public static @NonNull FontFamilyResult fetchFonts( 546 @NonNull Context context, @Nullable CancellationSignal cancellationSignal, 547 @NonNull FontRequest request) throws NameNotFoundException { 548 if (context.isRestricted()) { 549 // TODO: Should we allow if the peer process is system or myself? 550 return new FontFamilyResult(FontFamilyResult.STATUS_REJECTED, null); 551 } 552 ProviderInfo providerInfo = getProvider(context.getPackageManager(), request); 553 if (providerInfo == null) { 554 return new FontFamilyResult(FontFamilyResult.STATUS_WRONG_CERTIFICATES, null); 555 556 } 557 try { 558 FontInfo[] fonts = getFontFromProvider( 559 context, request, providerInfo.authority, cancellationSignal); 560 return new FontFamilyResult(FontFamilyResult.STATUS_OK, fonts); 561 } catch (InvalidFormatException e) { 562 return new FontFamilyResult(FontFamilyResult.STATUS_UNEXPECTED_DATA_PROVIDED, null); 563 } 564 } 565 566 /** 567 * Build a Typeface from an array of {@link FontInfo}. Results that are marked as not ready 568 * will be skipped. 569 * 570 * @param context A {@link Context} that will be used to fetch the font contents. 571 * @param cancellationSignal A signal to cancel the operation in progress, or null if none. If 572 * the operation is canceled, then {@link 573 * android.os.OperationCanceledException} will be thrown. 574 * @param fonts An array of {@link FontInfo} to be used to create a Typeface. 575 * @param weight A weight value to be used for selecting a font from a font family. 576 * @param italic {@code true} if this font is of italic style. This will be used for font 577 * selection from a font family. 578 * @param fallbackFontName A fallback font name used if this method fails to create the 579 * Typeface. By passing {@code null}, this method returns {@code null} 580 * if typeface creation fails. 581 * @return A Typeface object. May return {@code null} if that is the value passed to {@code 582 * fallBackFontName}. 583 */ 584 public static Typeface buildTypeface(@NonNull Context context, 585 @Nullable CancellationSignal cancellationSignal, @NonNull FontInfo[] fonts, 586 int weight, boolean italic, @Nullable String fallbackFontName) { 587 if (context.isRestricted()) { 588 // TODO: Should we allow if the peer process is system or myself? 589 return null; 590 } 591 final Map<Uri, ByteBuffer> uriBuffer = 592 prepareFontData(context, fonts, cancellationSignal); 593 return new Typeface.Builder(fonts, uriBuffer) 594 .setFallback(fallbackFontName) 595 .setWeight(weight) 596 .setItalic(italic) 597 .build(); 598 } 599 600 /** 601 * Build a Typeface from an array of {@link FontInfo} 602 * 603 * Results that are marked as not ready will be skipped. 604 * 605 * @param context A {@link Context} that will be used to fetch the font contents. 606 * @param cancellationSignal A signal to cancel the operation in progress, or null if none. If 607 * the operation is canceled, then {@link 608 * android.os.OperationCanceledException} will be thrown. 609 * @param fonts An array of {@link FontInfo} to be used to create a Typeface. 610 * @return A Typeface object. Returns null if typeface creation fails. 611 */ 612 public static Typeface buildTypeface(@NonNull Context context, 613 @Nullable CancellationSignal cancellationSignal, @NonNull FontInfo[] fonts) { 614 if (context.isRestricted()) { 615 // TODO: Should we allow if the peer process is system or myself? 616 return null; 617 } 618 final Map<Uri, ByteBuffer> uriBuffer = 619 prepareFontData(context, fonts, cancellationSignal); 620 return new Typeface.Builder(fonts, uriBuffer).build(); 621 } 622 623 /** 624 * A helper function to create a mapping from {@link Uri} to {@link ByteBuffer}. 625 * 626 * Skip if the file contents is not ready to be read. 627 * 628 * @param context A {@link Context} to be used for resolving content URI in 629 * {@link FontInfo}. 630 * @param fonts An array of {@link FontInfo}. 631 * @return A map from {@link Uri} to {@link ByteBuffer}. 632 */ 633 private static Map<Uri, ByteBuffer> prepareFontData(Context context, FontInfo[] fonts, 634 CancellationSignal cancellationSignal) { 635 final HashMap<Uri, ByteBuffer> out = new HashMap<>(); 636 final ContentResolver resolver = context.getContentResolver(); 637 638 for (FontInfo font : fonts) { 639 if (font.getResultCode() != Columns.RESULT_CODE_OK) { 640 continue; 641 } 642 643 final Uri uri = font.getUri(); 644 if (out.containsKey(uri)) { 645 continue; 646 } 647 648 ByteBuffer buffer = null; 649 try (final ParcelFileDescriptor pfd = 650 resolver.openFileDescriptor(uri, "r", cancellationSignal); 651 final FileInputStream fis = new FileInputStream(pfd.getFileDescriptor())) { 652 final FileChannel fileChannel = fis.getChannel(); 653 final long size = fileChannel.size(); 654 buffer = fileChannel.map(FileChannel.MapMode.READ_ONLY, 0, size); 655 } catch (IOException e) { 656 // ignore 657 } 658 659 // TODO: try other approach?, e.g. read all contents instead of mmap. 660 661 out.put(uri, buffer); 662 } 663 return Collections.unmodifiableMap(out); 664 } 665 666 /** @hide */ 667 @VisibleForTesting 668 public static @Nullable ProviderInfo getProvider( 669 PackageManager packageManager, FontRequest request) throws NameNotFoundException { 670 String providerAuthority = request.getProviderAuthority(); 671 ProviderInfo info = packageManager.resolveContentProvider(providerAuthority, 0); 672 if (info == null) { 673 throw new NameNotFoundException("No package found for authority: " + providerAuthority); 674 } 675 676 if (!info.packageName.equals(request.getProviderPackage())) { 677 throw new NameNotFoundException("Found content provider " + providerAuthority 678 + ", but package was not " + request.getProviderPackage()); 679 } 680 // Trust system apps without signature checks 681 if (info.applicationInfo.isSystemApp()) { 682 return info; 683 } 684 685 List<byte[]> signatures; 686 PackageInfo packageInfo = packageManager.getPackageInfo(info.packageName, 687 PackageManager.GET_SIGNATURES); 688 signatures = convertToByteArrayList(packageInfo.signatures); 689 Collections.sort(signatures, sByteArrayComparator); 690 691 List<List<byte[]>> requestCertificatesList = request.getCertificates(); 692 for (int i = 0; i < requestCertificatesList.size(); ++i) { 693 // Make a copy so we can sort it without modifying the incoming data. 694 List<byte[]> requestSignatures = new ArrayList<>(requestCertificatesList.get(i)); 695 Collections.sort(requestSignatures, sByteArrayComparator); 696 if (equalsByteArrayList(signatures, requestSignatures)) { 697 return info; 698 } 699 } 700 return null; 701 } 702 703 private static final Comparator<byte[]> sByteArrayComparator = (l, r) -> { 704 if (l.length != r.length) { 705 return l.length - r.length; 706 } 707 for (int i = 0; i < l.length; ++i) { 708 if (l[i] != r[i]) { 709 return l[i] - r[i]; 710 } 711 } 712 return 0; 713 }; 714 715 private static boolean equalsByteArrayList( 716 List<byte[]> signatures, List<byte[]> requestSignatures) { 717 if (signatures.size() != requestSignatures.size()) { 718 return false; 719 } 720 for (int i = 0; i < signatures.size(); ++i) { 721 if (!Arrays.equals(signatures.get(i), requestSignatures.get(i))) { 722 return false; 723 } 724 } 725 return true; 726 } 727 728 private static List<byte[]> convertToByteArrayList(Signature[] signatures) { 729 List<byte[]> shas = new ArrayList<>(); 730 for (int i = 0; i < signatures.length; ++i) { 731 shas.add(signatures[i].toByteArray()); 732 } 733 return shas; 734 } 735 736 /** @hide */ 737 @VisibleForTesting 738 public static @NonNull FontInfo[] getFontFromProvider( 739 Context context, FontRequest request, String authority, 740 CancellationSignal cancellationSignal) throws InvalidFormatException { 741 ArrayList<FontInfo> result = new ArrayList<>(); 742 final Uri uri = new Uri.Builder().scheme(ContentResolver.SCHEME_CONTENT) 743 .authority(authority) 744 .build(); 745 final Uri fileBaseUri = new Uri.Builder().scheme(ContentResolver.SCHEME_CONTENT) 746 .authority(authority) 747 .appendPath("file") 748 .build(); 749 try (Cursor cursor = context.getContentResolver().query(uri, new String[] { Columns._ID, 750 Columns.FILE_ID, Columns.TTC_INDEX, Columns.VARIATION_SETTINGS, 751 Columns.WEIGHT, Columns.ITALIC, Columns.RESULT_CODE }, 752 "query = ?", new String[] { request.getQuery() }, null, cancellationSignal);) { 753 // TODO: Should we restrict the amount of fonts that can be returned? 754 // TODO: Write documentation explaining that all results should be from the same family. 755 if (cursor != null && cursor.getCount() > 0) { 756 final int resultCodeColumnIndex = cursor.getColumnIndex(Columns.RESULT_CODE); 757 result = new ArrayList<>(); 758 final int idColumnIndex = cursor.getColumnIndexOrThrow(Columns._ID); 759 final int fileIdColumnIndex = cursor.getColumnIndex(Columns.FILE_ID); 760 final int ttcIndexColumnIndex = cursor.getColumnIndex(Columns.TTC_INDEX); 761 final int vsColumnIndex = cursor.getColumnIndex(Columns.VARIATION_SETTINGS); 762 final int weightColumnIndex = cursor.getColumnIndex(Columns.WEIGHT); 763 final int italicColumnIndex = cursor.getColumnIndex(Columns.ITALIC); 764 while (cursor.moveToNext()) { 765 int resultCode = resultCodeColumnIndex != -1 766 ? cursor.getInt(resultCodeColumnIndex) : Columns.RESULT_CODE_OK; 767 final int ttcIndex = ttcIndexColumnIndex != -1 768 ? cursor.getInt(ttcIndexColumnIndex) : 0; 769 final String variationSettings = vsColumnIndex != -1 770 ? cursor.getString(vsColumnIndex) : null; 771 772 Uri fileUri; 773 if (fileIdColumnIndex == -1) { 774 long id = cursor.getLong(idColumnIndex); 775 fileUri = ContentUris.withAppendedId(uri, id); 776 } else { 777 long id = cursor.getLong(fileIdColumnIndex); 778 fileUri = ContentUris.withAppendedId(fileBaseUri, id); 779 } 780 int weight; 781 boolean italic; 782 if (weightColumnIndex != -1 && italicColumnIndex != -1) { 783 weight = cursor.getInt(weightColumnIndex); 784 italic = cursor.getInt(italicColumnIndex) == 1; 785 } else { 786 weight = Typeface.Builder.NORMAL_WEIGHT; 787 italic = false; 788 } 789 FontVariationAxis[] axes = 790 FontVariationAxis.fromFontVariationSettings(variationSettings); 791 result.add(new FontInfo(fileUri, ttcIndex, axes, weight, italic, resultCode)); 792 } 793 } 794 } 795 return result.toArray(new FontInfo[0]); 796 } 797} 798