Home | History | Annotate | Download | only in provider
      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 package android.provider;
     17 
     18 import static java.lang.annotation.RetentionPolicy.SOURCE;
     19 
     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;
     46 
     47 import com.android.internal.annotations.GuardedBy;
     48 import com.android.internal.annotations.VisibleForTesting;
     49 import com.android.internal.util.Preconditions;
     50 
     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;
     64 import java.util.Map;
     65 import java.util.Set;
     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;
     72 
     73 /**
     74  * Utility class to deal with Font ContentProviders.
     75  */
     76 public 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({STATUS_OK, STATUS_WRONG_CERTIFICATES, STATUS_UNEXPECTED_DATA_PROVIDED})
    287         @Retention(RetentionPolicy.SOURCE)
    288         @interface FontResultStatus {}
    289 
    290         private final @FontResultStatus int mStatusCode;
    291         private final FontInfo[] mFonts;
    292 
    293         /** @hide */
    294         public FontFamilyResult(@FontResultStatus int statusCode, @Nullable FontInfo[] fonts) {
    295             mStatusCode = statusCode;
    296             mFonts = fonts;
    297         }
    298 
    299         public @FontResultStatus int getStatusCode() {
    300             return mStatusCode;
    301         }
    302 
    303         public @NonNull FontInfo[] getFonts() {
    304             return mFonts;
    305         }
    306     }
    307 
    308     private static final int THREAD_RENEWAL_THRESHOLD_MS = 10000;
    309 
    310     private static final long SYNC_FONT_FETCH_TIMEOUT_MS = 500;
    311 
    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() {
    316         @Override
    317         public void run() {
    318             synchronized (sLock) {
    319                 if (sThread != null) {
    320                     sThread.quitSafely();
    321                     sThread = null;
    322                     sHandler = null;
    323                 }
    324             }
    325         }
    326     };
    327 
    328     /** @hide */
    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;
    334         }
    335 
    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);
    342                 sThread.start();
    343                 sHandler = new Handler(sThread.getLooper());
    344             }
    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);
    350 
    351             sHandler.post(() -> {
    352                 try {
    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);
    358                         }
    359                         holder.set(typeface);
    360                     }
    361                 } catch (NameNotFoundException e) {
    362                     // Ignore.
    363                 }
    364                 lock.lock();
    365                 try {
    366                     if (!timeout.get()) {
    367                       waiting.set(false);
    368                       cond.signal();
    369                     }
    370                 } finally {
    371                     lock.unlock();
    372                 }
    373             });
    374             sHandler.removeCallbacks(sReplaceDispatcherThreadRunnable);
    375             sHandler.postDelayed(sReplaceDispatcherThreadRunnable, THREAD_RENEWAL_THRESHOLD_MS);
    376 
    377             long remaining = TimeUnit.MILLISECONDS.toNanos(SYNC_FONT_FETCH_TIMEOUT_MS);
    378             lock.lock();
    379             try {
    380                 if (!waiting.get()) {
    381                     return holder.get();
    382                 }
    383                 for (;;) {
    384                     try {
    385                         remaining = cond.awaitNanos(remaining);
    386                     } catch (InterruptedException e) {
    387                         // do nothing.
    388                     }
    389                     if (!waiting.get()) {
    390                         return holder.get();
    391                     }
    392                     if (remaining <= 0) {
    393                         timeout.set(true);
    394                         Log.w(TAG, "Remote font fetch timed out: " +
    395                                 request.getProviderAuthority() + "/" + request.getQuery());
    396                         return null;
    397                     }
    398                 }
    399             } finally {
    400                 lock.unlock();
    401             }
    402         }
    403     }
    404 
    405     /**
    406      * Interface used to receive asynchronously fetched typefaces.
    407      */
    408     public static class FontRequestCallback {
    409         /**
    410          * Constant returned by {@link #onTypefaceRequestFailed(int)} signaling that the given
    411          * provider was not found on the device.
    412          */
    413         public static final int FAIL_REASON_PROVIDER_NOT_FOUND = -1;
    414         /**
    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.
    417          */
    418         public static final int FAIL_REASON_WRONG_CERTIFICATES = -2;
    419         /**
    420          * Constant returned by {@link #onTypefaceRequestFailed(int)} signaling that the font
    421          * returned by the provider was not loaded properly.
    422          */
    423         public static final int FAIL_REASON_FONT_LOAD_ERROR = -3;
    424         /**
    425          * Constant returned by {@link #onTypefaceRequestFailed(int)} signaling that the font
    426          * provider did not return any results for the given query.
    427          */
    428         public static final int FAIL_REASON_FONT_NOT_FOUND = Columns.RESULT_CODE_FONT_NOT_FOUND;
    429         /**
    430          * Constant returned by {@link #onTypefaceRequestFailed(int)} signaling that the font
    431          * provider found the queried font, but it is currently unavailable.
    432          */
    433         public static final int FAIL_REASON_FONT_UNAVAILABLE = Columns.RESULT_CODE_FONT_UNAVAILABLE;
    434         /**
    435          * Constant returned by {@link #onTypefaceRequestFailed(int)} signaling that the given
    436          * query was not supported by the provider.
    437          */
    438         public static final int FAIL_REASON_MALFORMED_QUERY = Columns.RESULT_CODE_MALFORMED_QUERY;
    439 
    440         /** @hide */
    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 {}
    446 
    447         public FontRequestCallback() {}
    448 
    449         /**
    450          * Called then a Typeface request done via {@link #requestFonts} 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.
    453          */
    454         public void onTypefaceRetrieved(Typeface typeface) {}
    455 
    456         /**
    457          * Called when a Typeface request done via {@link #requestFonts}} 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.
    466          */
    467         public void onTypefaceRequestFailed(@FontRequestFailReason int reason) {}
    468     }
    469 
    470     /**
    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.
    475      *
    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.
    479      *
    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.
    488      */
    489     public static void requestFonts(@NonNull Context context, @NonNull FontRequest request,
    490             @NonNull Handler handler, @Nullable CancellationSignal cancellationSignal,
    491             @NonNull FontRequestCallback callback) {
    492 
    493         final Handler callerThreadHandler = new Handler();
    494         final Typeface cachedTypeface = sTypefaceCache.get(request.getIdentifier());
    495         if (cachedTypeface != null) {
    496             callerThreadHandler.post(() -> callback.onTypefaceRetrieved(cachedTypeface));
    497             return;
    498         }
    499 
    500         handler.post(() -> {
    501             FontFamilyResult result;
    502             try {
    503                 result = fetchFonts(context, cancellationSignal, request);
    504             } catch (NameNotFoundException e) {
    505                 callerThreadHandler.post(() -> callback.onTypefaceRequestFailed(
    506                         FontRequestCallback.FAIL_REASON_PROVIDER_NOT_FOUND));
    507                 return;
    508             }
    509 
    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));
    514                 return;
    515             }
    516 
    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));
    522                         return;
    523                     case FontFamilyResult.STATUS_UNEXPECTED_DATA_PROVIDED:
    524                         callerThreadHandler.post(() -> callback.onTypefaceRequestFailed(
    525                                 FontRequestCallback.FAIL_REASON_FONT_LOAD_ERROR));
    526                         return;
    527                     default:
    528                         // fetchFont returns unexpected status type. Fallback to load error.
    529                         callerThreadHandler.post(() -> callback.onTypefaceRequestFailed(
    530                                 FontRequestCallback.FAIL_REASON_FONT_LOAD_ERROR));
    531                         return;
    532                 }
    533             }
    534 
    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));
    539                 return;
    540             }
    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
    544                     // error.
    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));
    550                     } else {
    551                         callerThreadHandler.post(() -> callback.onTypefaceRequestFailed(
    552                                 resultCode));
    553                     }
    554                     return;
    555                 }
    556             }
    557 
    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));
    564                 return;
    565             }
    566 
    567             sTypefaceCache.put(request.getIdentifier(), typeface);
    568             callerThreadHandler.post(() -> callback.onTypefaceRetrieved(typeface));
    569         });
    570     }
    571 
    572     /**
    573      * Fetch fonts given a font request.
    574      *
    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
    579      *                           query is executed.
    580      * @param request A {@link FontRequest} object that identifies the provider and query for the
    581      *                request.
    582      *
    583      * @return {@link FontFamilyResult}
    584      *
    585      * @throws NameNotFoundException If requested package or authority was not found in system.
    586      */
    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);
    593         }
    594         ProviderInfo providerInfo = getProvider(context.getPackageManager(), request);
    595         if (providerInfo == null) {
    596             return new FontFamilyResult(FontFamilyResult.STATUS_WRONG_CERTIFICATES, null);
    597 
    598         }
    599         try {
    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);
    605         }
    606     }
    607 
    608     /**
    609      * Build a Typeface from an array of {@link FontInfo}
    610      *
    611      * Results that are marked as not ready will be skipped.
    612      *
    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.
    619      */
    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?
    624             return null;
    625         }
    626         final Map<Uri, ByteBuffer> uriBuffer =
    627                 prepareFontData(context, fonts, cancellationSignal);
    628         if (uriBuffer.isEmpty()) {
    629             return null;
    630         }
    631         return new Typeface.Builder(fonts, uriBuffer).build();
    632     }
    633 
    634     /**
    635      * A helper function to create a mapping from {@link Uri} to {@link ByteBuffer}.
    636      *
    637      * Skip if the file contents is not ready to be read.
    638      *
    639      * @param context A {@link Context} to be used for resolving content URI in
    640      *                {@link FontInfo}.
    641      * @param fonts An array of {@link FontInfo}.
    642      * @return A map from {@link Uri} to {@link ByteBuffer}.
    643      */
    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();
    648 
    649         for (FontInfo font : fonts) {
    650             if (font.getResultCode() != Columns.RESULT_CODE_OK) {
    651                 continue;
    652             }
    653 
    654             final Uri uri = font.getUri();
    655             if (out.containsKey(uri)) {
    656                 continue;
    657             }
    658 
    659             ByteBuffer buffer = null;
    660             try (final ParcelFileDescriptor pfd =
    661                     resolver.openFileDescriptor(uri, "r", cancellationSignal)) {
    662                 if (pfd != null) {
    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) {
    669                         // ignore
    670                     }
    671                 }
    672             } catch (IOException e) {
    673                 // ignore
    674             }
    675 
    676             // TODO: try other approach?, e.g. read all contents instead of mmap.
    677 
    678             out.put(uri, buffer);
    679         }
    680         return Collections.unmodifiableMap(out);
    681     }
    682 
    683     /** @hide */
    684     @VisibleForTesting
    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);
    689         if (info == null) {
    690             throw new NameNotFoundException("No package found for authority: " + providerAuthority);
    691         }
    692 
    693         if (!info.packageName.equals(request.getProviderPackage())) {
    694             throw new NameNotFoundException("Found content provider " + providerAuthority
    695                     + ", but package was not " + request.getProviderPackage());
    696         }
    697         // Trust system apps without signature checks
    698         if (info.applicationInfo.isSystemApp()) {
    699             return info;
    700         }
    701 
    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);
    707 
    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)) {
    714                 return info;
    715             }
    716         }
    717         return null;
    718     }
    719 
    720     private static final Comparator<byte[]> sByteArrayComparator = (l, r) -> {
    721         if (l.length != r.length) {
    722             return l.length - r.length;
    723         }
    724         for (int i = 0; i < l.length; ++i) {
    725             if (l[i] != r[i]) {
    726                 return l[i] - r[i];
    727             }
    728         }
    729         return 0;
    730     };
    731 
    732     private static boolean equalsByteArrayList(
    733             List<byte[]> signatures, List<byte[]> requestSignatures) {
    734         if (signatures.size() != requestSignatures.size()) {
    735             return false;
    736         }
    737         for (int i = 0; i < signatures.size(); ++i) {
    738             if (!Arrays.equals(signatures.get(i), requestSignatures.get(i))) {
    739                 return false;
    740             }
    741         }
    742         return true;
    743     }
    744 
    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());
    749         }
    750         return shas;
    751     }
    752 
    753     /** @hide */
    754     @VisibleForTesting
    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)
    761                 .build();
    762         final Uri fileBaseUri = new Uri.Builder().scheme(ContentResolver.SCHEME_CONTENT)
    763                 .authority(authority)
    764                 .appendPath("file")
    765                 .build();
    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;
    788 
    789                     Uri fileUri;
    790                     if (fileIdColumnIndex == -1) {
    791                         long id = cursor.getLong(idColumnIndex);
    792                         fileUri = ContentUris.withAppendedId(uri, id);
    793                     } else {
    794                         long id = cursor.getLong(fileIdColumnIndex);
    795                         fileUri = ContentUris.withAppendedId(fileBaseUri, id);
    796                     }
    797                     int weight;
    798                     boolean italic;
    799                     if (weightColumnIndex != -1 && italicColumnIndex != -1) {
    800                         weight = cursor.getInt(weightColumnIndex);
    801                         italic = cursor.getInt(italicColumnIndex) == 1;
    802                     } else {
    803                         weight = Typeface.Builder.NORMAL_WEIGHT;
    804                         italic = false;
    805                     }
    806                     FontVariationAxis[] axes =
    807                             FontVariationAxis.fromFontVariationSettings(variationSettings);
    808                     result.add(new FontInfo(fileUri, ttcIndex, axes, weight, italic, resultCode));
    809                 }
    810             }
    811         }
    812         return result.toArray(new FontInfo[0]);
    813     }
    814 }
    815