2 * Copyright (C) 2017 The Android Open Source Project
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
8 * http://www.apache.org/licenses/LICENSE-2.0
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.
16 package android.provider;
18 import static java.lang.annotation.RetentionPolicy.SOURCE;
20 import android.annotation.IntDef;
21 import android.annotation.IntRange;
22 import android.annotation.NonNull;
23 import android.annotation.Nullable;
24 import android.content.ContentResolver;
25 import android.content.ContentUris;
26 import android.content.Context;
27 import android.content.pm.PackageInfo;
28 import android.content.pm.PackageManager.NameNotFoundException;
29 import android.content.pm.PackageManager;
30 import android.content.pm.ProviderInfo;
31 import android.content.pm.Signature;
32 import android.database.Cursor;
33 import android.graphics.Typeface;
34 import android.graphics.fonts.FontVariationAxis;
35 import android.net.Uri;
36 import android.os.Bundle;
37 import android.os.CancellationSignal;
38 import android.os.Handler;
39 import android.os.HandlerThread;
40 import android.os.ParcelFileDescriptor;
41 import android.os.Process;
42 import android.os.ResultReceiver;
43 import android.util.ArraySet;
44 import android.util.Log;
45 import android.util.LruCache;
47 import com.android.internal.annotations.GuardedBy;
48 import com.android.internal.annotations.VisibleForTesting;
49 import com.android.internal.util.Preconditions;
51 import java.io.FileInputStream;
52 import java.io.FileNotFoundException;
53 import java.io.IOException;
54 import java.lang.annotation.Retention;
55 import java.lang.annotation.RetentionPolicy;
56 import java.nio.ByteBuffer;
57 import java.nio.channels.FileChannel;
58 import java.util.ArrayList;
59 import java.util.Arrays;
60 import java.util.Collections;
61 import java.util.Comparator;
62 import java.util.HashMap;
63 import java.util.List;
66 import java.util.concurrent.TimeUnit;
67 import java.util.concurrent.locks.Condition;
68 import java.util.concurrent.locks.Lock;
69 import java.util.concurrent.locks.ReentrantLock;
70 import java.util.concurrent.atomic.AtomicBoolean;
71 import java.util.concurrent.atomic.AtomicReference;
74 * Utility class to deal with Font ContentProviders.
76 public class FontsContract {
77 private static final String TAG = "FontsContract";
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
86 public static final class Columns implements BaseColumns {
88 // Do not instantiate.
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.
99 public static final String FILE_ID = "file_id";
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.
104 public static final String TTC_INDEX = "font_ttc_index";
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
110 public static final String VARIATION_SETTINGS = "font_variation_settings";
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
117 public static final String WEIGHT = "font_weight";
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.
123 public static final String ITALIC = "font_italic";
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.
133 public static final String RESULT_CODE = "result_code";
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}.
140 public static final int RESULT_CODE_OK = 0;
142 * Constant used to represent a result was not found. See {@link #RESULT_CODE}.
144 public static final int RESULT_CODE_FONT_NOT_FOUND = 1;
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}.
150 public static final int RESULT_CODE_FONT_UNAVAILABLE = 2;
152 * Constant used to represent that the query was not in a supported format by the provider.
153 * See {@link #RESULT_CODE}.
155 public static final int RESULT_CODE_MALFORMED_QUERY = 3;
158 private static final Object sLock = new Object();
160 private static Handler sHandler;
162 private static HandlerThread sThread;
164 private static Set<String> sInQueueSet;
166 private volatile static Context sContext; // set once in setApplicationContextForResources
168 private static final LruCache<String, Typeface> sTypefaceCache = new LruCache<>(16);
170 private FontsContract() {
174 public static void setApplicationContextForResources(Context context) {
175 sContext = context.getApplicationContext();
179 * Object represent a font entry in the family returned from {@link #fetchFonts}.
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;
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.
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;
207 mResultCode = resultCode;
211 * Returns a URI associated to this record.
213 public @NonNull Uri getUri() {
218 * Returns the index to be used to access this font when accessing a TTC file.
220 public @IntRange(from = 0) int getTtcIndex() {
225 * Returns the list of axes associated to this font.
227 public @Nullable FontVariationAxis[] getAxes() {
232 * Returns the weight value for this font.
234 public @IntRange(from = 1, to = 1000) int getWeight() {
239 * Returns whether this font is italic.
241 public boolean isItalic() {
246 * Returns result code.
248 * {@link FontsContract.Columns#RESULT_CODE}
250 public int getResultCode() {
256 * Object returned from {@link #fetchFonts}.
258 public static class FontFamilyResult {
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.
264 public static final int STATUS_OK = 0;
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.
270 public static final int STATUS_WRONG_CERTIFICATES = 1;
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.
277 public static final int STATUS_UNEXPECTED_DATA_PROVIDED = 2;
280 * Constant represents that the fetching font data was rejected by system. This happens if
281 * the passed context is restricted.
283 public static final int STATUS_REJECTED = 3;
286 @IntDef({STATUS_OK, STATUS_WRONG_CERTIFICATES, STATUS_UNEXPECTED_DATA_PROVIDED})
287 @Retention(RetentionPolicy.SOURCE)
288 @interface FontResultStatus {}
290 private final @FontResultStatus int mStatusCode;
291 private final FontInfo[] mFonts;
294 public FontFamilyResult(@FontResultStatus int statusCode, @Nullable FontInfo[] fonts) {
295 mStatusCode = statusCode;
299 public @FontResultStatus int getStatusCode() {
303 public @NonNull FontInfo[] getFonts() {
308 private static final int THREAD_RENEWAL_THRESHOLD_MS = 10000;
310 private static final long SYNC_FONT_FETCH_TIMEOUT_MS = 500;
312 // We use a background thread to post the content resolving work for all requests on. This
313 // thread should be quit/stopped after all requests are done.
314 // TODO: Factor out to other class. Consider to switch MessageQueue.IdleHandler.
315 private static final Runnable sReplaceDispatcherThreadRunnable = new Runnable() {
318 synchronized (sLock) {
319 if (sThread != null) {
320 sThread.quitSafely();
329 public static Typeface getFontSync(FontRequest request) {
330 final String id = request.getIdentifier();
331 Typeface cachedTypeface = sTypefaceCache.get(id);
332 if (cachedTypeface != null) {
333 return cachedTypeface;
336 // Unfortunately the typeface is not available at this time, but requesting from the font
337 // provider takes too much time. For now, request the font data to ensure it is in the cache
338 // next time and return.
339 synchronized (sLock) {
340 if (sHandler == null) {
341 sThread = new HandlerThread("fonts", Process.THREAD_PRIORITY_BACKGROUND);
343 sHandler = new Handler(sThread.getLooper());
345 final Lock lock = new ReentrantLock();
346 final Condition cond = lock.newCondition();
347 final AtomicReference<Typeface> holder = new AtomicReference<>();
348 final AtomicBoolean waiting = new AtomicBoolean(true);
349 final AtomicBoolean timeout = new AtomicBoolean(false);
351 sHandler.post(() -> {
353 FontFamilyResult result = fetchFonts(sContext, null, request);
354 if (result.getStatusCode() == FontFamilyResult.STATUS_OK) {
355 Typeface typeface = buildTypeface(sContext, null, result.getFonts());
356 if (typeface != null) {
357 sTypefaceCache.put(id, typeface);
359 holder.set(typeface);
361 } catch (NameNotFoundException e) {
366 if (!timeout.get()) {
374 sHandler.removeCallbacks(sReplaceDispatcherThreadRunnable);
375 sHandler.postDelayed(sReplaceDispatcherThreadRunnable, THREAD_RENEWAL_THRESHOLD_MS);
377 long remaining = TimeUnit.MILLISECONDS.toNanos(SYNC_FONT_FETCH_TIMEOUT_MS);
380 if (!waiting.get()) {
385 remaining = cond.awaitNanos(remaining);
386 } catch (InterruptedException e) {
389 if (!waiting.get()) {
392 if (remaining <= 0) {
394 Log.w(TAG, "Remote font fetch timed out: " +
395 request.getProviderAuthority() + "/" + request.getQuery());
406 * Interface used to receive asynchronously fetched typefaces.
408 public static class FontRequestCallback {
410 * Constant returned by {@link #onTypefaceRequestFailed(int)} signaling that the given
411 * provider was not found on the device.
413 public static final int FAIL_REASON_PROVIDER_NOT_FOUND = -1;
415 * Constant returned by {@link #onTypefaceRequestFailed(int)} signaling that the given
416 * provider must be authenticated and the given certificates do not match its signature.
418 public static final int FAIL_REASON_WRONG_CERTIFICATES = -2;
420 * Constant returned by {@link #onTypefaceRequestFailed(int)} signaling that the font
421 * returned by the provider was not loaded properly.
423 public static final int FAIL_REASON_FONT_LOAD_ERROR = -3;
425 * Constant returned by {@link #onTypefaceRequestFailed(int)} signaling that the font
426 * provider did not return any results for the given query.
428 public static final int FAIL_REASON_FONT_NOT_FOUND = Columns.RESULT_CODE_FONT_NOT_FOUND;
430 * Constant returned by {@link #onTypefaceRequestFailed(int)} signaling that the font
431 * provider found the queried font, but it is currently unavailable.
433 public static final int FAIL_REASON_FONT_UNAVAILABLE = Columns.RESULT_CODE_FONT_UNAVAILABLE;
435 * Constant returned by {@link #onTypefaceRequestFailed(int)} signaling that the given
436 * query was not supported by the provider.
438 public static final int FAIL_REASON_MALFORMED_QUERY = Columns.RESULT_CODE_MALFORMED_QUERY;
441 @IntDef({ FAIL_REASON_PROVIDER_NOT_FOUND, FAIL_REASON_FONT_LOAD_ERROR,
442 FAIL_REASON_FONT_NOT_FOUND, FAIL_REASON_FONT_UNAVAILABLE,
443 FAIL_REASON_MALFORMED_QUERY })
444 @Retention(RetentionPolicy.SOURCE)
445 @interface FontRequestFailReason {}
447 public FontRequestCallback() {}
450 * Called then a Typeface request done via {@link #requestFont} is complete. Note that this
451 * method will not be called if {@link #onTypefaceRequestFailed(int)} is called instead.
452 * @param typeface The Typeface object retrieved.
454 public void onTypefaceRetrieved(Typeface typeface) {}
457 * Called when a Typeface request done via {@link #requestFont}} fails.
458 * @param reason One of {@link #FAIL_REASON_PROVIDER_NOT_FOUND},
459 * {@link #FAIL_REASON_FONT_NOT_FOUND},
460 * {@link #FAIL_REASON_FONT_LOAD_ERROR},
461 * {@link #FAIL_REASON_FONT_UNAVAILABLE} or
462 * {@link #FAIL_REASON_MALFORMED_QUERY} if returned by the system. May also be
463 * a positive value greater than 0 defined by the font provider as an
464 * additional error code. Refer to the provider's documentation for more
465 * information on possible returned error codes.
467 public void onTypefaceRequestFailed(@FontRequestFailReason int reason) {}
471 * Create a typeface object given a font request. The font will be asynchronously fetched,
472 * therefore the result is delivered to the given callback. See {@link FontRequest}.
473 * Only one of the methods in callback will be invoked, depending on whether the request
474 * succeeds or fails. These calls will happen on the caller thread.
476 * Note that the result Typeface may be cached internally and the same instance will be returned
477 * the next time you call this method with the same request. If you want to bypass this cache,
478 * use {@link #fetchFonts} and {@link #buildTypeface} instead.
480 * @param context A context to be used for fetching from font provider.
481 * @param request A {@link FontRequest} object that identifies the provider and query for the
482 * request. May not be null.
483 * @param handler A handler to be processed the font fetching.
484 * @param cancellationSignal A signal to cancel the operation in progress, or null if none. If
485 * the operation is canceled, then {@link
486 * android.os.OperationCanceledException} will be thrown.
487 * @param callback A callback that will be triggered when results are obtained. May not be null.
489 public static void requestFonts(@NonNull Context context, @NonNull FontRequest request,
490 @NonNull Handler handler, @Nullable CancellationSignal cancellationSignal,
491 @NonNull FontRequestCallback callback) {
493 final Handler callerThreadHandler = new Handler();
494 final Typeface cachedTypeface = sTypefaceCache.get(request.getIdentifier());
495 if (cachedTypeface != null) {
496 callerThreadHandler.post(() -> callback.onTypefaceRetrieved(cachedTypeface));
501 FontFamilyResult result;
503 result = fetchFonts(context, cancellationSignal, request);
504 } catch (NameNotFoundException e) {
505 callerThreadHandler.post(() -> callback.onTypefaceRequestFailed(
506 FontRequestCallback.FAIL_REASON_PROVIDER_NOT_FOUND));
510 // Same request might be dispatched during fetchFonts. Check the cache again.
511 final Typeface anotherCachedTypeface = sTypefaceCache.get(request.getIdentifier());
512 if (anotherCachedTypeface != null) {
513 callerThreadHandler.post(() -> callback.onTypefaceRetrieved(anotherCachedTypeface));
517 if (result.getStatusCode() != FontFamilyResult.STATUS_OK) {
518 switch (result.getStatusCode()) {
519 case FontFamilyResult.STATUS_WRONG_CERTIFICATES:
520 callerThreadHandler.post(() -> callback.onTypefaceRequestFailed(
521 FontRequestCallback.FAIL_REASON_WRONG_CERTIFICATES));
523 case FontFamilyResult.STATUS_UNEXPECTED_DATA_PROVIDED:
524 callerThreadHandler.post(() -> callback.onTypefaceRequestFailed(
525 FontRequestCallback.FAIL_REASON_FONT_LOAD_ERROR));
528 // fetchFont returns unexpected status type. Fallback to load error.
529 callerThreadHandler.post(() -> callback.onTypefaceRequestFailed(
530 FontRequestCallback.FAIL_REASON_FONT_LOAD_ERROR));
535 final FontInfo[] fonts = result.getFonts();
536 if (fonts == null || fonts.length == 0) {
537 callerThreadHandler.post(() -> callback.onTypefaceRequestFailed(
538 FontRequestCallback.FAIL_REASON_FONT_NOT_FOUND));
541 for (final FontInfo font : fonts) {
542 if (font.getResultCode() != Columns.RESULT_CODE_OK) {
543 // We proceed if all font entry is ready to use. Otherwise report the first
545 final int resultCode = font.getResultCode();
546 if (resultCode < 0) {
547 // Negative values are reserved for internal errors. Fallback to load error.
548 callerThreadHandler.post(() -> callback.onTypefaceRequestFailed(
549 FontRequestCallback.FAIL_REASON_FONT_LOAD_ERROR));
551 callerThreadHandler.post(() -> callback.onTypefaceRequestFailed(
558 final Typeface typeface = buildTypeface(context, cancellationSignal, fonts);
559 if (typeface == null) {
560 // Something went wrong during reading font files. This happens if the given font
561 // file is an unsupported font type.
562 callerThreadHandler.post(() -> callback.onTypefaceRequestFailed(
563 FontRequestCallback.FAIL_REASON_FONT_LOAD_ERROR));
567 sTypefaceCache.put(request.getIdentifier(), typeface);
568 callerThreadHandler.post(() -> callback.onTypefaceRetrieved(typeface));
573 * Fetch fonts given a font request.
575 * @param context A {@link Context} to be used for fetching fonts.
576 * @param cancellationSignal A signal to cancel the operation in progress, or null if none. If
577 * the operation is canceled, then {@link
578 * android.os.OperationCanceledException} will be thrown when the
580 * @param request A {@link FontRequest} object that identifies the provider and query for the
583 * @return {@link FontFamilyResult}
585 * @throws NameNotFoundException If requested package or authority was not found in system.
587 public static @NonNull FontFamilyResult fetchFonts(
588 @NonNull Context context, @Nullable CancellationSignal cancellationSignal,
589 @NonNull FontRequest request) throws NameNotFoundException {
590 if (context.isRestricted()) {
591 // TODO: Should we allow if the peer process is system or myself?
592 return new FontFamilyResult(FontFamilyResult.STATUS_REJECTED, null);
594 ProviderInfo providerInfo = getProvider(context.getPackageManager(), request);
595 if (providerInfo == null) {
596 return new FontFamilyResult(FontFamilyResult.STATUS_WRONG_CERTIFICATES, null);
600 FontInfo[] fonts = getFontFromProvider(
601 context, request, providerInfo.authority, cancellationSignal);
602 return new FontFamilyResult(FontFamilyResult.STATUS_OK, fonts);
603 } catch (IllegalArgumentException e) {
604 return new FontFamilyResult(FontFamilyResult.STATUS_UNEXPECTED_DATA_PROVIDED, null);
609 * Build a Typeface from an array of {@link FontInfo}
611 * Results that are marked as not ready will be skipped.
613 * @param context A {@link Context} that will be used to fetch the font contents.
614 * @param cancellationSignal A signal to cancel the operation in progress, or null if none. If
615 * the operation is canceled, then {@link
616 * android.os.OperationCanceledException} will be thrown.
617 * @param fonts An array of {@link FontInfo} to be used to create a Typeface.
618 * @return A Typeface object. Returns null if typeface creation fails.
620 public static Typeface buildTypeface(@NonNull Context context,
621 @Nullable CancellationSignal cancellationSignal, @NonNull FontInfo[] fonts) {
622 if (context.isRestricted()) {
623 // TODO: Should we allow if the peer process is system or myself?
626 final Map<Uri, ByteBuffer> uriBuffer =
627 prepareFontData(context, fonts, cancellationSignal);
628 if (uriBuffer.isEmpty()) {
631 return new Typeface.Builder(fonts, uriBuffer).build();
635 * A helper function to create a mapping from {@link Uri} to {@link ByteBuffer}.
637 * Skip if the file contents is not ready to be read.
639 * @param context A {@link Context} to be used for resolving content URI in
641 * @param fonts An array of {@link FontInfo}.
642 * @return A map from {@link Uri} to {@link ByteBuffer}.
644 private static Map<Uri, ByteBuffer> prepareFontData(Context context, FontInfo[] fonts,
645 CancellationSignal cancellationSignal) {
646 final HashMap<Uri, ByteBuffer> out = new HashMap<>();
647 final ContentResolver resolver = context.getContentResolver();
649 for (FontInfo font : fonts) {
650 if (font.getResultCode() != Columns.RESULT_CODE_OK) {
654 final Uri uri = font.getUri();
655 if (out.containsKey(uri)) {
659 ByteBuffer buffer = null;
660 try (final ParcelFileDescriptor pfd =
661 resolver.openFileDescriptor(uri, "r", cancellationSignal)) {
663 try (final FileInputStream fis =
664 new FileInputStream(pfd.getFileDescriptor())) {
665 final FileChannel fileChannel = fis.getChannel();
666 final long size = fileChannel.size();
667 buffer = fileChannel.map(FileChannel.MapMode.READ_ONLY, 0, size);
668 } catch (IOException e) {
672 } catch (IOException e) {
676 // TODO: try other approach?, e.g. read all contents instead of mmap.
678 out.put(uri, buffer);
680 return Collections.unmodifiableMap(out);
685 public static @Nullable ProviderInfo getProvider(
686 PackageManager packageManager, FontRequest request) throws NameNotFoundException {
687 String providerAuthority = request.getProviderAuthority();
688 ProviderInfo info = packageManager.resolveContentProvider(providerAuthority, 0);
690 throw new NameNotFoundException("No package found for authority: " + providerAuthority);
693 if (!info.packageName.equals(request.getProviderPackage())) {
694 throw new NameNotFoundException("Found content provider " + providerAuthority
695 + ", but package was not " + request.getProviderPackage());
697 // Trust system apps without signature checks
698 if (info.applicationInfo.isSystemApp()) {
702 List<byte[]> signatures;
703 PackageInfo packageInfo = packageManager.getPackageInfo(info.packageName,
704 PackageManager.GET_SIGNATURES);
705 signatures = convertToByteArrayList(packageInfo.signatures);
706 Collections.sort(signatures, sByteArrayComparator);
708 List<List<byte[]>> requestCertificatesList = request.getCertificates();
709 for (int i = 0; i < requestCertificatesList.size(); ++i) {
710 // Make a copy so we can sort it without modifying the incoming data.
711 List<byte[]> requestSignatures = new ArrayList<>(requestCertificatesList.get(i));
712 Collections.sort(requestSignatures, sByteArrayComparator);
713 if (equalsByteArrayList(signatures, requestSignatures)) {
720 private static final Comparator<byte[]> sByteArrayComparator = (l, r) -> {
721 if (l.length != r.length) {
722 return l.length - r.length;
724 for (int i = 0; i < l.length; ++i) {
732 private static boolean equalsByteArrayList(
733 List<byte[]> signatures, List<byte[]> requestSignatures) {
734 if (signatures.size() != requestSignatures.size()) {
737 for (int i = 0; i < signatures.size(); ++i) {
738 if (!Arrays.equals(signatures.get(i), requestSignatures.get(i))) {
745 private static List<byte[]> convertToByteArrayList(Signature[] signatures) {
746 List<byte[]> shas = new ArrayList<>();
747 for (int i = 0; i < signatures.length; ++i) {
748 shas.add(signatures[i].toByteArray());
755 public static @NonNull FontInfo[] getFontFromProvider(
756 Context context, FontRequest request, String authority,
757 CancellationSignal cancellationSignal) {
758 ArrayList<FontInfo> result = new ArrayList<>();
759 final Uri uri = new Uri.Builder().scheme(ContentResolver.SCHEME_CONTENT)
760 .authority(authority)
762 final Uri fileBaseUri = new Uri.Builder().scheme(ContentResolver.SCHEME_CONTENT)
763 .authority(authority)
766 try (Cursor cursor = context.getContentResolver().query(uri, new String[] { Columns._ID,
767 Columns.FILE_ID, Columns.TTC_INDEX, Columns.VARIATION_SETTINGS,
768 Columns.WEIGHT, Columns.ITALIC, Columns.RESULT_CODE },
769 "query = ?", new String[] { request.getQuery() }, null, cancellationSignal);) {
770 // TODO: Should we restrict the amount of fonts that can be returned?
771 // TODO: Write documentation explaining that all results should be from the same family.
772 if (cursor != null && cursor.getCount() > 0) {
773 final int resultCodeColumnIndex = cursor.getColumnIndex(Columns.RESULT_CODE);
774 result = new ArrayList<>();
775 final int idColumnIndex = cursor.getColumnIndexOrThrow(Columns._ID);
776 final int fileIdColumnIndex = cursor.getColumnIndex(Columns.FILE_ID);
777 final int ttcIndexColumnIndex = cursor.getColumnIndex(Columns.TTC_INDEX);
778 final int vsColumnIndex = cursor.getColumnIndex(Columns.VARIATION_SETTINGS);
779 final int weightColumnIndex = cursor.getColumnIndex(Columns.WEIGHT);
780 final int italicColumnIndex = cursor.getColumnIndex(Columns.ITALIC);
781 while (cursor.moveToNext()) {
782 int resultCode = resultCodeColumnIndex != -1
783 ? cursor.getInt(resultCodeColumnIndex) : Columns.RESULT_CODE_OK;
784 final int ttcIndex = ttcIndexColumnIndex != -1
785 ? cursor.getInt(ttcIndexColumnIndex) : 0;
786 final String variationSettings = vsColumnIndex != -1
787 ? cursor.getString(vsColumnIndex) : null;
790 if (fileIdColumnIndex == -1) {
791 long id = cursor.getLong(idColumnIndex);
792 fileUri = ContentUris.withAppendedId(uri, id);
794 long id = cursor.getLong(fileIdColumnIndex);
795 fileUri = ContentUris.withAppendedId(fileBaseUri, id);
799 if (weightColumnIndex != -1 && italicColumnIndex != -1) {
800 weight = cursor.getInt(weightColumnIndex);
801 italic = cursor.getInt(italicColumnIndex) == 1;
803 weight = Typeface.Builder.NORMAL_WEIGHT;
806 FontVariationAxis[] axes =
807 FontVariationAxis.fromFontVariationSettings(variationSettings);
808 result.add(new FontInfo(fileUri, ttcIndex, axes, weight, italic, resultCode));
812 return result.toArray(new FontInfo[0]);