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 17package android.support.v4.graphics; 18 19import static android.support.annotation.RestrictTo.Scope.LIBRARY_GROUP; 20 21import android.content.Context; 22import android.content.res.Resources; 23import android.graphics.Typeface; 24import android.net.Uri; 25import android.os.CancellationSignal; 26import android.support.annotation.NonNull; 27import android.support.annotation.Nullable; 28import android.support.annotation.RequiresApi; 29import android.support.annotation.RestrictTo; 30import android.support.v4.content.res.FontResourcesParserCompat.FontFamilyFilesResourceEntry; 31import android.support.v4.content.res.FontResourcesParserCompat.FontFileResourceEntry; 32import android.support.v4.provider.FontsContractCompat.FontInfo; 33import android.support.v4.util.SimpleArrayMap; 34import android.util.Log; 35 36import java.lang.reflect.Array; 37import java.lang.reflect.Constructor; 38import java.lang.reflect.InvocationTargetException; 39import java.lang.reflect.Method; 40import java.nio.ByteBuffer; 41import java.util.List; 42 43 44/** 45 * Implementation of the Typeface compat methods for API 24 and above. 46 * @hide 47 */ 48@RestrictTo(LIBRARY_GROUP) 49@RequiresApi(24) 50class TypefaceCompatApi24Impl extends TypefaceCompatBaseImpl { 51 private static final String TAG = "TypefaceCompatApi24Impl"; 52 53 private static final String FONT_FAMILY_CLASS = "android.graphics.FontFamily"; 54 private static final String ADD_FONT_WEIGHT_STYLE_METHOD = "addFontWeightStyle"; 55 private static final String CREATE_FROM_FAMILIES_WITH_DEFAULT_METHOD = 56 "createFromFamiliesWithDefault"; 57 private static final Class sFontFamily; 58 private static final Constructor sFontFamilyCtor; 59 private static final Method sAddFontWeightStyle; 60 private static final Method sCreateFromFamiliesWithDefault; 61 62 static { 63 Class fontFamilyClass; 64 Constructor fontFamilyCtor; 65 Method addFontMethod; 66 Method createFromFamiliesWithDefaultMethod; 67 try { 68 fontFamilyClass = Class.forName(FONT_FAMILY_CLASS); 69 fontFamilyCtor = fontFamilyClass.getConstructor(); 70 addFontMethod = fontFamilyClass.getMethod(ADD_FONT_WEIGHT_STYLE_METHOD, 71 ByteBuffer.class, Integer.TYPE, List.class, Integer.TYPE, Boolean.TYPE); 72 Object familyArray = Array.newInstance(fontFamilyClass, 1); 73 createFromFamiliesWithDefaultMethod = 74 Typeface.class.getMethod(CREATE_FROM_FAMILIES_WITH_DEFAULT_METHOD, 75 familyArray.getClass()); 76 } catch (ClassNotFoundException | NoSuchMethodException e) { 77 Log.e(TAG, e.getClass().getName(), e); 78 fontFamilyClass = null; 79 fontFamilyCtor = null; 80 addFontMethod = null; 81 createFromFamiliesWithDefaultMethod = null; 82 } 83 sFontFamilyCtor = fontFamilyCtor; 84 sFontFamily = fontFamilyClass; 85 sAddFontWeightStyle = addFontMethod; 86 sCreateFromFamiliesWithDefault = createFromFamiliesWithDefaultMethod; 87 } 88 89 /** 90 * Returns true if API24 implementation is usable. 91 */ 92 public static boolean isUsable() { 93 if (sAddFontWeightStyle == null) { 94 Log.w(TAG, "Unable to collect necessary private methods." 95 + "Fallback to legacy implementation."); 96 } 97 return sAddFontWeightStyle != null; 98 } 99 100 private static Object newFamily() { 101 try { 102 return sFontFamilyCtor.newInstance(); 103 } catch (IllegalAccessException | InstantiationException | InvocationTargetException e) { 104 throw new RuntimeException(e); 105 } 106 } 107 108 private static boolean addFontWeightStyle(Object family, ByteBuffer buffer, int ttcIndex, 109 int weight, boolean style) { 110 try { 111 final Boolean result = (Boolean) sAddFontWeightStyle.invoke( 112 family, buffer, ttcIndex, null /* variation axis */, weight, style); 113 return result.booleanValue(); 114 } catch (IllegalAccessException | InvocationTargetException e) { 115 throw new RuntimeException(e); 116 } 117 } 118 119 private static Typeface createFromFamiliesWithDefault(Object family) { 120 try { 121 Object familyArray = Array.newInstance(sFontFamily, 1); 122 Array.set(familyArray, 0, family); 123 return (Typeface) sCreateFromFamiliesWithDefault.invoke( 124 null /* static method */, familyArray); 125 } catch (IllegalAccessException | InvocationTargetException e) { 126 throw new RuntimeException(e); 127 } 128 } 129 130 @Override 131 public Typeface createFromFontInfo(Context context, 132 @Nullable CancellationSignal cancellationSignal, @NonNull FontInfo[] fonts, int style) { 133 Object family = newFamily(); 134 SimpleArrayMap<Uri, ByteBuffer> bufferCache = new SimpleArrayMap<>(); 135 136 for (final FontInfo font : fonts) { 137 final Uri uri = font.getUri(); 138 ByteBuffer buffer = bufferCache.get(uri); 139 if (buffer == null) { 140 buffer = TypefaceCompatUtil.mmap(context, cancellationSignal, uri); 141 bufferCache.put(uri, buffer); 142 } 143 if (!addFontWeightStyle(family, buffer, font.getTtcIndex(), font.getWeight(), 144 font.isItalic())) { 145 return null; 146 } 147 } 148 return createFromFamiliesWithDefault(family); 149 } 150 151 @Override 152 public Typeface createFromFontFamilyFilesResourceEntry(Context context, 153 FontFamilyFilesResourceEntry entry, Resources resources, int style) { 154 Object family = newFamily(); 155 for (final FontFileResourceEntry e : entry.getEntries()) { 156 final ByteBuffer buffer = 157 TypefaceCompatUtil.copyToDirectBuffer(context, resources, e.getResourceId()); 158 if (buffer == null) { 159 return null; 160 } 161 // TODO: support ttc index. 162 if (!addFontWeightStyle(family, buffer, 0, e.getWeight(), e.isItalic())) { 163 return null; 164 } 165 } 166 return createFromFamiliesWithDefault(family); 167 } 168} 169