Home | History | Annotate | Download | only in graphics
      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 
     17 package android.support.v4.graphics;
     18 
     19 import static android.support.annotation.RestrictTo.Scope.LIBRARY_GROUP;
     20 
     21 import android.content.ContentResolver;
     22 import android.content.Context;
     23 import android.content.res.AssetManager;
     24 import android.content.res.Resources;
     25 import android.graphics.Typeface;
     26 import android.graphics.fonts.FontVariationAxis;
     27 import android.net.Uri;
     28 import android.os.CancellationSignal;
     29 import android.os.ParcelFileDescriptor;
     30 import android.support.annotation.NonNull;
     31 import android.support.annotation.Nullable;
     32 import android.support.annotation.RequiresApi;
     33 import android.support.annotation.RestrictTo;
     34 import android.support.v4.content.res.FontResourcesParserCompat;
     35 import android.support.v4.content.res.FontResourcesParserCompat.FontFileResourceEntry;
     36 import android.support.v4.provider.FontsContractCompat;
     37 import android.util.Log;
     38 
     39 import java.io.IOException;
     40 import java.lang.reflect.Array;
     41 import java.lang.reflect.Constructor;
     42 import java.lang.reflect.InvocationTargetException;
     43 import java.lang.reflect.Method;
     44 import java.nio.ByteBuffer;
     45 import java.util.Map;
     46 
     47 /**
     48  * Implementation of the Typeface compat methods for API 26 and above.
     49  * @hide
     50  */
     51 @RestrictTo(LIBRARY_GROUP)
     52 @RequiresApi(26)
     53 public class TypefaceCompatApi26Impl extends TypefaceCompatApi21Impl {
     54     private static final String TAG = "TypefaceCompatApi26Impl";
     55 
     56     private static final String FONT_FAMILY_CLASS = "android.graphics.FontFamily";
     57     private static final String ADD_FONT_FROM_ASSET_MANAGER_METHOD = "addFontFromAssetManager";
     58     private static final String ADD_FONT_FROM_BUFFER_METHOD = "addFontFromBuffer";
     59     private static final String CREATE_FROM_FAMILIES_WITH_DEFAULT_METHOD =
     60             "createFromFamiliesWithDefault";
     61     private static final String FREEZE_METHOD = "freeze";
     62     private static final String ABORT_CREATION_METHOD = "abortCreation";
     63     private static final Class sFontFamily;
     64     private static final Constructor sFontFamilyCtor;
     65     private static final Method sAddFontFromAssetManager;
     66     private static final Method sAddFontFromBuffer;
     67     private static final Method sFreeze;
     68     private static final Method sAbortCreation;
     69     private static final Method sCreateFromFamiliesWithDefault;
     70     private static final int RESOLVE_BY_FONT_TABLE = -1;
     71 
     72     static {
     73         Class fontFamilyClass;
     74         Constructor fontFamilyCtor;
     75         Method addFontMethod;
     76         Method addFromBufferMethod;
     77         Method freezeMethod;
     78         Method abortCreationMethod;
     79         Method createFromFamiliesWithDefaultMethod;
     80         try {
     81             fontFamilyClass = Class.forName(FONT_FAMILY_CLASS);
     82             fontFamilyCtor = fontFamilyClass.getConstructor();
     83             addFontMethod = fontFamilyClass.getMethod(ADD_FONT_FROM_ASSET_MANAGER_METHOD,
     84                     AssetManager.class, String.class, Integer.TYPE, Boolean.TYPE, Integer.TYPE,
     85                     Integer.TYPE, Integer.TYPE, FontVariationAxis[].class);
     86             addFromBufferMethod = fontFamilyClass.getMethod(ADD_FONT_FROM_BUFFER_METHOD,
     87                     ByteBuffer.class, Integer.TYPE, FontVariationAxis[].class, Integer.TYPE,
     88                     Integer.TYPE);
     89             freezeMethod = fontFamilyClass.getMethod(FREEZE_METHOD);
     90             abortCreationMethod = fontFamilyClass.getMethod(ABORT_CREATION_METHOD);
     91             Object familyArray = Array.newInstance(fontFamilyClass, 1);
     92             createFromFamiliesWithDefaultMethod =
     93                     Typeface.class.getDeclaredMethod(CREATE_FROM_FAMILIES_WITH_DEFAULT_METHOD,
     94                             familyArray.getClass(), Integer.TYPE, Integer.TYPE);
     95             createFromFamiliesWithDefaultMethod.setAccessible(true);
     96         } catch (ClassNotFoundException | NoSuchMethodException e) {
     97             Log.e(TAG, "Unable to collect necessary methods for class " + e.getClass().getName(),
     98                     e);
     99             fontFamilyClass = null;
    100             fontFamilyCtor = null;
    101             addFontMethod = null;
    102             addFromBufferMethod = null;
    103             freezeMethod = null;
    104             abortCreationMethod = null;
    105             createFromFamiliesWithDefaultMethod = null;
    106         }
    107         sFontFamilyCtor = fontFamilyCtor;
    108         sFontFamily = fontFamilyClass;
    109         sAddFontFromAssetManager = addFontMethod;
    110         sAddFontFromBuffer = addFromBufferMethod;
    111         sFreeze = freezeMethod;
    112         sAbortCreation = abortCreationMethod;
    113         sCreateFromFamiliesWithDefault = createFromFamiliesWithDefaultMethod;
    114     }
    115 
    116     /**
    117      * Returns true if API26 implementation is usable.
    118      */
    119     private static boolean isFontFamilyPrivateAPIAvailable() {
    120         if (sAddFontFromAssetManager == null) {
    121             Log.w(TAG, "Unable to collect necessary private methods."
    122                     + "Fallback to legacy implementation.");
    123         }
    124         return sAddFontFromAssetManager != null;
    125     }
    126 
    127     /**
    128      * Create a new FontFamily instance
    129      */
    130     private static Object newFamily() {
    131         try {
    132             return sFontFamilyCtor.newInstance();
    133         } catch (IllegalAccessException | InstantiationException | InvocationTargetException e) {
    134             throw new RuntimeException(e);
    135         }
    136     }
    137 
    138     /**
    139      * Call FontFamily#addFontFromAssetManager(AssetManager mgr, String path, int cookie,
    140      *      boolean isAsset, int ttcIndex, int weight, int isItalic, FontVariationAxis[] axes)
    141      */
    142     private static boolean addFontFromAssetManager(Context context, Object family, String fileName,
    143             int ttcIndex, int weight, int style) {
    144         try {
    145             final Boolean result = (Boolean) sAddFontFromAssetManager.invoke(family,
    146                     context.getAssets(), fileName, 0 /* cookie */, false /* isAsset */, ttcIndex,
    147                     weight, style, null /* axes */);
    148             return result.booleanValue();
    149         } catch (IllegalAccessException | InvocationTargetException e) {
    150             throw new RuntimeException(e);
    151         }
    152     }
    153 
    154     /**
    155      * Call FontFamily#addFontFromBuffer(ByteBuffer font, int ttcIndex, FontVariationAxis[] axes,
    156      *      int weight, int italic)
    157      */
    158     private static boolean addFontFromBuffer(Object family, ByteBuffer buffer,
    159             int ttcIndex, int weight, int style) {
    160         try {
    161             final Boolean result = (Boolean) sAddFontFromBuffer.invoke(family,
    162                     buffer, ttcIndex, null /* axes */, weight, style);
    163             return result.booleanValue();
    164         } catch (IllegalAccessException | InvocationTargetException e) {
    165             throw new RuntimeException(e);
    166         }
    167     }
    168 
    169     /**
    170      * Call static method Typeface#createFromFamiliesWithDefault(
    171      *      FontFamily[] families, int weight, int italic)
    172      */
    173     private static Typeface createFromFamiliesWithDefault(Object family) {
    174         try {
    175             Object familyArray = Array.newInstance(sFontFamily, 1);
    176             Array.set(familyArray, 0, family);
    177             return (Typeface) sCreateFromFamiliesWithDefault.invoke(null /* static method */,
    178                     familyArray, RESOLVE_BY_FONT_TABLE, RESOLVE_BY_FONT_TABLE);
    179         } catch (IllegalAccessException | InvocationTargetException e) {
    180             throw new RuntimeException(e);
    181         }
    182     }
    183 
    184     /**
    185      * Call FontFamily#freeze()
    186      */
    187     private static boolean freeze(Object family) {
    188         try {
    189             Boolean result = (Boolean) sFreeze.invoke(family);
    190             return result.booleanValue();
    191         } catch (IllegalAccessException | InvocationTargetException e) {
    192             throw new RuntimeException(e);
    193         }
    194     }
    195 
    196     /**
    197      * Call FontFamily#abortCreation()
    198      */
    199     private static boolean abortCreation(Object family) {
    200         try {
    201             Boolean result = (Boolean) sAbortCreation.invoke(family);
    202             return result.booleanValue();
    203         } catch (IllegalAccessException | InvocationTargetException e) {
    204             throw new RuntimeException(e);
    205         }
    206     }
    207 
    208     @Override
    209     public Typeface createFromFontFamilyFilesResourceEntry(Context context,
    210             FontResourcesParserCompat.FontFamilyFilesResourceEntry entry, Resources resources,
    211             int style) {
    212         if (!isFontFamilyPrivateAPIAvailable()) {
    213             return super.createFromFontFamilyFilesResourceEntry(context, entry, resources, style);
    214         }
    215         Object fontFamily = newFamily();
    216         for (final FontFileResourceEntry fontFile : entry.getEntries()) {
    217             // TODO: Add ttc and variation font support. (b/37853920)
    218             if (!addFontFromAssetManager(context, fontFamily, fontFile.getFileName(),
    219                     0 /* ttcIndex */, fontFile.getWeight(), fontFile.isItalic() ? 1 : 0)) {
    220                 abortCreation(fontFamily);
    221                 return null;
    222             }
    223         }
    224         if (!freeze(fontFamily)) {
    225             return null;
    226         }
    227         return createFromFamiliesWithDefault(fontFamily);
    228     }
    229 
    230     @Override
    231     public Typeface createFromFontInfo(Context context,
    232             @Nullable CancellationSignal cancellationSignal,
    233             @NonNull FontsContractCompat.FontInfo[] fonts, int style) {
    234         if (fonts.length < 1) {
    235             return null;
    236         }
    237         if (!isFontFamilyPrivateAPIAvailable()) {
    238             // Even if the private API is not avaiable, don't use API 21 implemenation and use
    239             // public API to create Typeface from file descriptor.
    240             final FontsContractCompat.FontInfo bestFont = findBestInfo(fonts, style);
    241             final ContentResolver resolver = context.getContentResolver();
    242             try (ParcelFileDescriptor pfd =
    243                     resolver.openFileDescriptor(bestFont.getUri(), "r", cancellationSignal)) {
    244                 return new Typeface.Builder(pfd.getFileDescriptor())
    245                         .setWeight(bestFont.getWeight())
    246                         .setItalic(bestFont.isItalic())
    247                         .build();
    248             } catch (IOException e) {
    249                 return null;
    250             }
    251         }
    252         Map<Uri, ByteBuffer> uriBuffer = FontsContractCompat.prepareFontData(
    253                 context, fonts, cancellationSignal);
    254         final Object fontFamily = newFamily();
    255         boolean atLeastOneFont = false;
    256         for (FontsContractCompat.FontInfo font : fonts) {
    257             final ByteBuffer fontBuffer = uriBuffer.get(font.getUri());
    258             if (fontBuffer == null) {
    259                 continue;  // skip
    260             }
    261             final boolean success = addFontFromBuffer(fontFamily, fontBuffer,
    262                     font.getTtcIndex(), font.getWeight(), font.isItalic() ? 1 : 0);
    263             if (!success) {
    264                 abortCreation(fontFamily);
    265                 return null;
    266             }
    267             atLeastOneFont = true;
    268         }
    269         if (!atLeastOneFont) {
    270             abortCreation(fontFamily);
    271             return null;
    272         }
    273         if (!freeze(fontFamily)) {
    274             return null;
    275         }
    276         return createFromFamiliesWithDefault(fontFamily);
    277     }
    278 
    279     /**
    280      * Used by Resources to load a font resource of type font file.
    281      */
    282     @Nullable
    283     @Override
    284     public Typeface createFromResourcesFontFile(
    285             Context context, Resources resources, int id, String path, int style) {
    286         if (!isFontFamilyPrivateAPIAvailable()) {
    287             return super.createFromResourcesFontFile(context, resources, id, path, style);
    288         }
    289         Object fontFamily = newFamily();
    290         if (!addFontFromAssetManager(context, fontFamily, path,
    291                 0 /* ttcIndex */, RESOLVE_BY_FONT_TABLE /* weight */,
    292                 RESOLVE_BY_FONT_TABLE /* italic */)) {
    293             abortCreation(fontFamily);
    294             return null;
    295         }
    296         if (!freeze(fontFamily)) {
    297             return null;
    298         }
    299         return createFromFamiliesWithDefault(fontFamily);
    300     }
    301 }
    302