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