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