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