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