blob: 9411adee6f6b248b644270bbe21d50776decd675 [file] [log] [blame]
/*
* Copyright (C) 2017 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package androidx.core.graphics;
import static androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP;
import android.content.ContentResolver;
import android.content.Context;
import android.content.res.AssetManager;
import android.content.res.Resources;
import android.graphics.Typeface;
import android.graphics.fonts.FontVariationAxis;
import android.net.Uri;
import android.os.CancellationSignal;
import android.os.ParcelFileDescriptor;
import android.util.Log;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.RequiresApi;
import androidx.annotation.RestrictTo;
import androidx.core.content.res.FontResourcesParserCompat;
import androidx.core.content.res.FontResourcesParserCompat.FontFileResourceEntry;
import androidx.core.provider.FontsContractCompat;
import java.io.IOException;
import java.lang.reflect.Array;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.nio.ByteBuffer;
import java.util.Map;
/**
* Implementation of the Typeface compat methods for API 26 and above.
* @hide
*/
@RestrictTo(LIBRARY_GROUP)
@RequiresApi(26)
public class TypefaceCompatApi26Impl extends TypefaceCompatApi21Impl {
private static final String TAG = "TypefaceCompatApi26Impl";
private static final String FONT_FAMILY_CLASS = "android.graphics.FontFamily";
private static final String ADD_FONT_FROM_ASSET_MANAGER_METHOD = "addFontFromAssetManager";
private static final String ADD_FONT_FROM_BUFFER_METHOD = "addFontFromBuffer";
private static final String CREATE_FROM_FAMILIES_WITH_DEFAULT_METHOD =
"createFromFamiliesWithDefault";
private static final String FREEZE_METHOD = "freeze";
private static final String ABORT_CREATION_METHOD = "abortCreation";
private static final int RESOLVE_BY_FONT_TABLE = -1;
private static final String DEFAULT_FAMILY = "sans-serif";
protected final Class mFontFamily;
protected final Constructor mFontFamilyCtor;
protected final Method mAddFontFromAssetManager;
protected final Method mAddFontFromBuffer;
protected final Method mFreeze;
protected final Method mAbortCreation;
protected final Method mCreateFromFamiliesWithDefault;
public TypefaceCompatApi26Impl() {
Class fontFamily;
Constructor fontFamilyCtor;
Method addFontFromAssetManager;
Method addFontFromBuffer;
Method freeze;
Method abortCreation;
Method createFromFamiliesWithDefault;
try {
fontFamily = obtainFontFamily();
fontFamilyCtor = obtainFontFamilyCtor(fontFamily);
addFontFromAssetManager = obtainAddFontFromAssetManagerMethod(fontFamily);
addFontFromBuffer = obtainAddFontFromBufferMethod(fontFamily);
freeze = obtainFreezeMethod(fontFamily);
abortCreation = obtainAbortCreationMethod(fontFamily);
createFromFamiliesWithDefault = obtainCreateFromFamiliesWithDefaultMethod(fontFamily);
} catch (ClassNotFoundException | NoSuchMethodException e) {
Log.e(TAG, "Unable to collect necessary methods for class " + e.getClass().getName(),
e);
fontFamily = null;
fontFamilyCtor = null;
addFontFromAssetManager = null;
addFontFromBuffer = null;
freeze = null;
abortCreation = null;
createFromFamiliesWithDefault = null;
}
mFontFamily = fontFamily;
mFontFamilyCtor = fontFamilyCtor;
mAddFontFromAssetManager = addFontFromAssetManager;
mAddFontFromBuffer = addFontFromBuffer;
mFreeze = freeze;
mAbortCreation = abortCreation;
mCreateFromFamiliesWithDefault = createFromFamiliesWithDefault;
}
/**
* Returns true if all the necessary methods were found.
*/
private boolean isFontFamilyPrivateAPIAvailable() {
if (mAddFontFromAssetManager == null) {
Log.w(TAG, "Unable to collect necessary private methods. "
+ "Fallback to legacy implementation.");
}
return mAddFontFromAssetManager != null;
}
/**
* Create a new FontFamily instance
*/
private Object newFamily() {
try {
return mFontFamilyCtor.newInstance();
} catch (IllegalAccessException | InstantiationException | InvocationTargetException e) {
throw new RuntimeException(e);
}
}
/**
* Call FontFamily#addFontFromAssetManager(AssetManager mgr, String path, int cookie,
* boolean isAsset, int ttcIndex, int weight, int isItalic, FontVariationAxis[] axes)
*/
private boolean addFontFromAssetManager(Context context, Object family, String fileName,
int ttcIndex, int weight, int style, @Nullable FontVariationAxis[] axes) {
try {
final Boolean result = (Boolean) mAddFontFromAssetManager.invoke(family,
context.getAssets(), fileName, 0 /* cookie */, false /* isAsset */, ttcIndex,
weight, style, axes);
return result.booleanValue();
} catch (IllegalAccessException | InvocationTargetException e) {
throw new RuntimeException(e);
}
}
/**
* Call FontFamily#addFontFromBuffer(ByteBuffer font, int ttcIndex, FontVariationAxis[] axes,
* int weight, int italic)
*/
private boolean addFontFromBuffer(Object family, ByteBuffer buffer,
int ttcIndex, int weight, int style) {
try {
final Boolean result = (Boolean) mAddFontFromBuffer.invoke(family,
buffer, ttcIndex, null /* axes */, weight, style);
return result.booleanValue();
} catch (IllegalAccessException | InvocationTargetException e) {
throw new RuntimeException(e);
}
}
/**
* Call method Typeface#createFromFamiliesWithDefault(
* FontFamily[] families, int weight, int italic)
*/
protected Typeface createFromFamiliesWithDefault(Object family) {
try {
Object familyArray = Array.newInstance(mFontFamily, 1);
Array.set(familyArray, 0, family);
return (Typeface) mCreateFromFamiliesWithDefault.invoke(null /* static method */,
familyArray, RESOLVE_BY_FONT_TABLE, RESOLVE_BY_FONT_TABLE);
} catch (IllegalAccessException | InvocationTargetException e) {
throw new RuntimeException(e);
}
}
/**
* Call FontFamily#freeze()
*/
private boolean freeze(Object family) {
try {
Boolean result = (Boolean) mFreeze.invoke(family);
return result.booleanValue();
} catch (IllegalAccessException | InvocationTargetException e) {
throw new RuntimeException(e);
}
}
/**
* Call FontFamily#abortCreation()
*/
private void abortCreation(Object family) {
try {
mAbortCreation.invoke(family);
} catch (IllegalAccessException | InvocationTargetException e) {
throw new RuntimeException(e);
}
}
@Override
public Typeface createFromFontFamilyFilesResourceEntry(Context context,
FontResourcesParserCompat.FontFamilyFilesResourceEntry entry, Resources resources,
int style) {
if (!isFontFamilyPrivateAPIAvailable()) {
return super.createFromFontFamilyFilesResourceEntry(context, entry, resources, style);
}
Object fontFamily = newFamily();
for (final FontFileResourceEntry fontFile : entry.getEntries()) {
if (!addFontFromAssetManager(context, fontFamily, fontFile.getFileName(),
fontFile.getTtcIndex(), fontFile.getWeight(), fontFile.isItalic() ? 1 : 0,
FontVariationAxis.fromFontVariationSettings(fontFile.getVariationSettings()))) {
abortCreation(fontFamily);
return null;
}
}
if (!freeze(fontFamily)) {
return null;
}
return createFromFamiliesWithDefault(fontFamily);
}
@Override
public Typeface createFromFontInfo(Context context,
@Nullable CancellationSignal cancellationSignal,
@NonNull FontsContractCompat.FontInfo[] fonts, int style) {
if (fonts.length < 1) {
return null;
}
if (!isFontFamilyPrivateAPIAvailable()) {
// Even if the private API is not avaiable, don't use API 21 implemenation and use
// public API to create Typeface from file descriptor.
final FontsContractCompat.FontInfo bestFont = findBestInfo(fonts, style);
final ContentResolver resolver = context.getContentResolver();
try (ParcelFileDescriptor pfd =
resolver.openFileDescriptor(bestFont.getUri(), "r", cancellationSignal)) {
if (pfd == null) {
return null;
}
return new Typeface.Builder(pfd.getFileDescriptor())
.setWeight(bestFont.getWeight())
.setItalic(bestFont.isItalic())
.build();
} catch (IOException e) {
return null;
}
}
Map<Uri, ByteBuffer> uriBuffer = FontsContractCompat.prepareFontData(
context, fonts, cancellationSignal);
final Object fontFamily = newFamily();
boolean atLeastOneFont = false;
for (FontsContractCompat.FontInfo font : fonts) {
final ByteBuffer fontBuffer = uriBuffer.get(font.getUri());
if (fontBuffer == null) {
continue; // skip
}
final boolean success = addFontFromBuffer(fontFamily, fontBuffer,
font.getTtcIndex(), font.getWeight(), font.isItalic() ? 1 : 0);
if (!success) {
abortCreation(fontFamily);
return null;
}
atLeastOneFont = true;
}
if (!atLeastOneFont) {
abortCreation(fontFamily);
return null;
}
if (!freeze(fontFamily)) {
return null;
}
final Typeface typeface = createFromFamiliesWithDefault(fontFamily);
return Typeface.create(typeface, style);
}
/**
* Used by Resources to load a font resource of type font file.
*/
@Nullable
@Override
public Typeface createFromResourcesFontFile(
Context context, Resources resources, int id, String path, int style) {
if (!isFontFamilyPrivateAPIAvailable()) {
return super.createFromResourcesFontFile(context, resources, id, path, style);
}
Object fontFamily = newFamily();
if (!addFontFromAssetManager(context, fontFamily, path,
0 /* ttcIndex */, RESOLVE_BY_FONT_TABLE /* weight */,
RESOLVE_BY_FONT_TABLE /* italic */, null /* axes */)) {
abortCreation(fontFamily);
return null;
}
if (!freeze(fontFamily)) {
return null;
}
return createFromFamiliesWithDefault(fontFamily);
}
// The following getters retrieve by reflection the Typeface methods, belonging to the
// framework code, which will be invoked. Since the definitions of these methods can change
// across different API versions, inheriting classes should override these getters in order to
// reflect the method definitions in the API versions they represent.
//===========================================================================================
protected Class obtainFontFamily() throws ClassNotFoundException {
return Class.forName(FONT_FAMILY_CLASS);
}
protected Constructor obtainFontFamilyCtor(Class fontFamily) throws NoSuchMethodException {
return fontFamily.getConstructor();
}
protected Method obtainAddFontFromAssetManagerMethod(Class fontFamily)
throws NoSuchMethodException {
return fontFamily.getMethod(ADD_FONT_FROM_ASSET_MANAGER_METHOD,
AssetManager.class, String.class, Integer.TYPE, Boolean.TYPE, Integer.TYPE,
Integer.TYPE, Integer.TYPE, FontVariationAxis[].class);
}
protected Method obtainAddFontFromBufferMethod(Class fontFamily) throws NoSuchMethodException {
return fontFamily.getMethod(ADD_FONT_FROM_BUFFER_METHOD,
ByteBuffer.class, Integer.TYPE, FontVariationAxis[].class, Integer.TYPE,
Integer.TYPE);
}
protected Method obtainFreezeMethod(Class fontFamily) throws NoSuchMethodException {
return fontFamily.getMethod(FREEZE_METHOD);
}
protected Method obtainAbortCreationMethod(Class fontFamily) throws NoSuchMethodException {
return fontFamily.getMethod(ABORT_CREATION_METHOD);
}
protected Method obtainCreateFromFamiliesWithDefaultMethod(Class fontFamily)
throws NoSuchMethodException {
Object familyArray = Array.newInstance(fontFamily, 1);
Method m = Typeface.class.getDeclaredMethod(CREATE_FROM_FAMILIES_WITH_DEFAULT_METHOD,
familyArray.getClass(), Integer.TYPE, Integer.TYPE);
m.setAccessible(true);
return m;
}
}