Typeface.java revision 5706a1b12dd891098fb7bf479623d42979171144
1/*
2 * Copyright (C) 2006 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.graphics;
18
19import static android.content.res.FontResourcesParser.ProviderResourceEntry;
20import static android.content.res.FontResourcesParser.FontFileResourceEntry;
21import static android.content.res.FontResourcesParser.FontFamilyFilesResourceEntry;
22import static android.content.res.FontResourcesParser.FamilyResourceEntry;
23
24import static java.lang.annotation.RetentionPolicy.SOURCE;
25
26import android.annotation.IntDef;
27import android.annotation.IntRange;
28import android.annotation.NonNull;
29import android.annotation.Nullable;
30import android.content.Context;
31import android.content.res.AssetManager;
32import android.graphics.FontListParser;
33import android.graphics.fonts.FontRequest;
34import android.graphics.fonts.FontResult;
35import android.graphics.fonts.FontVariationAxis;
36import android.graphics.fonts.FontVariationAxis.InvalidFormatException;
37import android.net.Uri;
38import android.os.Bundle;
39import android.os.Handler;
40import android.os.ParcelFileDescriptor;
41import android.os.ResultReceiver;
42import android.provider.FontsContract;
43import android.text.FontConfig;
44import android.util.Base64;
45import android.util.Log;
46import android.util.LongSparseArray;
47import android.util.LruCache;
48import android.util.SparseArray;
49
50import com.android.internal.annotations.GuardedBy;
51import com.android.internal.util.Preconditions;
52
53import libcore.io.IoUtils;
54
55import org.xmlpull.v1.XmlPullParserException;
56
57import java.io.File;
58import java.io.FileDescriptor;
59import java.io.FileInputStream;
60import java.io.FileNotFoundException;
61import java.io.IOException;
62import java.lang.annotation.Retention;
63import java.lang.annotation.RetentionPolicy;
64import java.nio.ByteBuffer;
65import java.nio.channels.FileChannel;
66import java.util.Arrays;
67import java.util.ArrayList;
68import java.util.Arrays;
69import java.util.Collections;
70import java.util.HashMap;
71import java.util.List;
72import java.util.Map;
73import java.util.concurrent.atomic.AtomicReference;
74
75/**
76 * The Typeface class specifies the typeface and intrinsic style of a font.
77 * This is used in the paint, along with optionally Paint settings like
78 * textSize, textSkewX, textScaleX to specify
79 * how text appears when drawn (and measured).
80 */
81public class Typeface {
82
83    private static String TAG = "Typeface";
84
85    /** The default NORMAL typeface object */
86    public static final Typeface DEFAULT;
87    /**
88     * The default BOLD typeface object. Note: this may be not actually be
89     * bold, depending on what fonts are installed. Call getStyle() to know
90     * for sure.
91     */
92    public static final Typeface DEFAULT_BOLD;
93    /** The NORMAL style of the default sans serif typeface. */
94    public static final Typeface SANS_SERIF;
95    /** The NORMAL style of the default serif typeface. */
96    public static final Typeface SERIF;
97    /** The NORMAL style of the default monospace typeface. */
98    public static final Typeface MONOSPACE;
99
100    static Typeface[] sDefaults;
101    private static final LongSparseArray<SparseArray<Typeface>> sTypefaceCache =
102            new LongSparseArray<>(3);
103    @GuardedBy("sLock")
104    private static FontsContract sFontsContract;
105    @GuardedBy("sLock")
106    private static Handler sHandler;
107
108    /**
109     * Cache for Typeface objects dynamically loaded from assets. Currently max size is 16.
110     */
111    @GuardedBy("sLock")
112    private static final LruCache<String, Typeface> sDynamicTypefaceCache = new LruCache<>(16);
113
114    static Typeface sDefaultTypeface;
115    static Map<String, Typeface> sSystemFontMap;
116    static FontFamily[] sFallbackFonts;
117    private static final Object sLock = new Object();
118
119    static final String FONTS_CONFIG = "fonts.xml";
120
121    /**
122     * @hide
123     */
124    public long native_instance;
125
126    // Style
127    public static final int NORMAL = 0;
128    public static final int BOLD = 1;
129    public static final int ITALIC = 2;
130    public static final int BOLD_ITALIC = 3;
131
132    private int mStyle = 0;
133    private int mBaseWeight = 0;
134
135    // Value for weight and italic. Indicates the value is resolved by font metadata.
136    // Must be the same as the C++ constant in core/jni/android/graphics/FontFamily.cpp
137    /** @hide */
138    public static final int RESOLVE_BY_FONT_TABLE = -1;
139
140    // Style value for building typeface.
141    private static final int STYLE_NORMAL = 0;
142    private static final int STYLE_ITALIC = 1;
143
144    private int[] mSupportedAxes;
145    private static final int[] EMPTY_AXES = {};
146
147    private static void setDefault(Typeface t) {
148        sDefaultTypeface = t;
149        nativeSetDefault(t.native_instance);
150    }
151
152    /** Returns the typeface's intrinsic style attributes */
153    public int getStyle() {
154        return mStyle;
155    }
156
157    /** Returns true if getStyle() has the BOLD bit set. */
158    public final boolean isBold() {
159        return (mStyle & BOLD) != 0;
160    }
161
162    /** Returns true if getStyle() has the ITALIC bit set. */
163    public final boolean isItalic() {
164        return (mStyle & ITALIC) != 0;
165    }
166
167    /**
168     * @hide
169     * Used by Resources to load a font resource of type font file.
170     */
171    @Nullable
172    public static Typeface createFromResources(AssetManager mgr, String path, int cookie) {
173        if (sFallbackFonts != null) {
174            synchronized (sDynamicTypefaceCache) {
175                final String key = Builder.createAssetUid(
176                        mgr, path, 0 /* ttcIndex */, null /* axes */,
177                        RESOLVE_BY_FONT_TABLE /* weight */, RESOLVE_BY_FONT_TABLE /* italic */);
178                Typeface typeface = sDynamicTypefaceCache.get(key);
179                if (typeface != null) return typeface;
180
181                FontFamily fontFamily = new FontFamily();
182                // TODO: introduce ttc index and variation settings to resource type font.
183                if (fontFamily.addFontFromAssetManager(mgr, path, cookie, false /* isAsset */,
184                        0 /* ttcIndex */, RESOLVE_BY_FONT_TABLE /* weight */,
185                        RESOLVE_BY_FONT_TABLE /* italic */, null /* axes */)) {
186                    if (!fontFamily.freeze()) {
187                        return null;
188                    }
189                    FontFamily[] families = {fontFamily};
190                    typeface = createFromFamiliesWithDefault(families,
191                            RESOLVE_BY_FONT_TABLE, RESOLVE_BY_FONT_TABLE);
192                    sDynamicTypefaceCache.put(key, typeface);
193                    return typeface;
194                }
195            }
196        }
197        return null;
198    }
199
200    /**
201     * @hide
202     * Used by Resources to load a font resource of type xml.
203     */
204    @Nullable
205    public static Typeface createFromResources(
206            FamilyResourceEntry entry, AssetManager mgr, String path) {
207        if (sFallbackFonts != null) {
208            Typeface typeface = findFromCache(mgr, path);
209            if (typeface != null) return typeface;
210
211            if (entry instanceof ProviderResourceEntry) {
212                final ProviderResourceEntry providerEntry = (ProviderResourceEntry) entry;
213                // Downloadable font
214                typeface = findFromCache(providerEntry.getAuthority(), providerEntry.getQuery());
215                if (typeface != null) {
216                    return typeface;
217                }
218                List<List<String>> givenCerts = providerEntry.getCerts();
219                List<List<byte[]>> certs = new ArrayList<>();
220                if (givenCerts != null) {
221                    for (int i = 0; i < givenCerts.size(); i++) {
222                        List<String> certSet = givenCerts.get(i);
223                        List<byte[]> byteArraySet = new ArrayList<>();
224                        for (int j = 0; j < certSet.size(); j++) {
225                            byteArraySet.add(Base64.decode(certSet.get(j), Base64.DEFAULT));
226                        }
227                        certs.add(byteArraySet);
228                    }
229                }
230                // Downloaded font and it wasn't cached, request it again and return a
231                // default font instead (nothing we can do now).
232                create(new FontRequest(providerEntry.getAuthority(), providerEntry.getPackage(),
233                        providerEntry.getQuery(), certs), NO_OP_REQUEST_CALLBACK);
234                return DEFAULT;
235            }
236
237            // family is FontFamilyFilesResourceEntry
238            final FontFamilyFilesResourceEntry filesEntry =
239                    (FontFamilyFilesResourceEntry) entry;
240
241            FontFamily fontFamily = new FontFamily();
242            for (final FontFileResourceEntry fontFile : filesEntry.getEntries()) {
243                if (!fontFamily.addFontFromAssetManager(mgr, fontFile.getFileName(),
244                        0 /* resourceCookie */, false /* isAsset */, 0 /* ttcIndex */,
245                        fontFile.getWeight(), fontFile.isItalic() ? STYLE_ITALIC : STYLE_NORMAL,
246                        null /* axes */)) {
247                    return null;
248                }
249            }
250            // Due to backward compatibility, even if the font is not supported by our font stack,
251            // we need to place the empty font at the first place. The typeface with empty font
252            // behaves different from default typeface especially in fallback font selection.
253            fontFamily.allowUnsupportedFont();
254            fontFamily.freeze();
255            FontFamily[] familyChain = { fontFamily };
256            typeface = createFromFamiliesWithDefault(familyChain,
257                    RESOLVE_BY_FONT_TABLE, RESOLVE_BY_FONT_TABLE);
258            synchronized (sDynamicTypefaceCache) {
259                final String key = Builder.createAssetUid(mgr, path, 0 /* ttcIndex */,
260                        null /* axes */, RESOLVE_BY_FONT_TABLE /* weight */,
261                        RESOLVE_BY_FONT_TABLE /* italic */);
262                sDynamicTypefaceCache.put(key, typeface);
263            }
264            return typeface;
265        }
266        return null;
267    }
268
269    /**
270     * Used by resources for cached loading if the font is available.
271     * @hide
272     */
273    public static Typeface findFromCache(AssetManager mgr, String path) {
274        synchronized (sDynamicTypefaceCache) {
275            final String key = Builder.createAssetUid(mgr, path, 0 /* ttcIndex */, null /* axes */,
276                    RESOLVE_BY_FONT_TABLE /* weight */, RESOLVE_BY_FONT_TABLE /* italic */);
277            Typeface typeface = sDynamicTypefaceCache.get(key);
278            if (typeface != null) {
279                return typeface;
280            }
281        }
282        return null;
283    }
284
285    /**
286     * Set the application context so we can generate font requests from the provider. This should
287     * be called from ActivityThread when the application binds, as we preload fonts.
288     * @hide
289     */
290    public static void setApplicationContext(Context context) {
291        synchronized (sLock) {
292            if (sFontsContract == null) {
293                sFontsContract = new FontsContract(context);
294                sHandler = new Handler();
295            }
296        }
297    }
298
299    /**
300     * Create a typeface object given a font request. The font will be asynchronously fetched,
301     * therefore the result is delivered to the given callback. See {@link FontRequest}.
302     * Only one of the methods in callback will be invoked, depending on whether the request
303     * succeeds or fails. These calls will happen on the main thread.
304     * @param request A {@link FontRequest} object that identifies the provider and query for the
305     *                request. May not be null.
306     * @param callback A callback that will be triggered when results are obtained. May not be null.
307     */
308    @Deprecated
309    public static void create(@NonNull FontRequest request, @NonNull FontRequestCallback callback) {
310        // Check the cache first
311        // TODO: would the developer want to avoid a cache hit and always ask for the freshest
312        // result?
313        Typeface cachedTypeface = findFromCache(
314                request.getProviderAuthority(), request.getQuery());
315        if (cachedTypeface != null) {
316            sHandler.post(() -> callback.onTypefaceRetrieved(cachedTypeface));
317            return;
318        }
319        synchronized (sLock) {
320            if (sFontsContract == null) {
321                throw new RuntimeException("Context not initialized, can't query provider");
322            }
323            final ResultReceiver receiver = new ResultReceiver(null) {
324                @Override
325                public void onReceiveResult(int resultCode, Bundle resultData) {
326                    sHandler.post(() -> receiveResult(request, callback, resultCode, resultData));
327                }
328            };
329            sFontsContract.getFont(request, receiver);
330        }
331    }
332
333    private static Typeface findFromCache(String providerAuthority, String query) {
334        synchronized (sDynamicTypefaceCache) {
335            final String key = createProviderUid(providerAuthority, query);
336            Typeface typeface = sDynamicTypefaceCache.get(key);
337            if (typeface != null) {
338                return typeface;
339            }
340        }
341        return null;
342    }
343
344    private static void receiveResult(FontRequest request, FontRequestCallback callback,
345            int resultCode, Bundle resultData) {
346        Typeface cachedTypeface = findFromCache(
347                request.getProviderAuthority(), request.getQuery());
348        if (cachedTypeface != null) {
349            // We already know the result.
350            // Probably the requester requests the same font again in a short interval.
351            callback.onTypefaceRetrieved(cachedTypeface);
352            return;
353        }
354        if (resultCode != FontsContract.Columns.RESULT_CODE_OK) {
355            callback.onTypefaceRequestFailed(resultCode);
356            return;
357        }
358        if (resultData == null) {
359            callback.onTypefaceRequestFailed(
360                    FontRequestCallback.FAIL_REASON_FONT_NOT_FOUND);
361            return;
362        }
363        List<FontResult> resultList =
364                resultData.getParcelableArrayList(FontsContract.PARCEL_FONT_RESULTS);
365        if (resultList == null || resultList.isEmpty()) {
366            callback.onTypefaceRequestFailed(
367                    FontRequestCallback.FAIL_REASON_FONT_NOT_FOUND);
368            return;
369        }
370        FontFamily fontFamily = new FontFamily();
371        for (int i = 0; i < resultList.size(); ++i) {
372            FontResult result = resultList.get(i);
373            ParcelFileDescriptor fd = result.getFileDescriptor();
374            if (fd == null) {
375                callback.onTypefaceRequestFailed(
376                        FontRequestCallback.FAIL_REASON_FONT_LOAD_ERROR);
377                return;
378            }
379            try (FileInputStream is = new FileInputStream(fd.getFileDescriptor())) {
380                FileChannel fileChannel = is.getChannel();
381                long fontSize = fileChannel.size();
382                ByteBuffer fontBuffer = fileChannel.map(
383                        FileChannel.MapMode.READ_ONLY, 0, fontSize);
384                int weight = result.getWeight();
385                int italic = result.getItalic() ? STYLE_ITALIC : STYLE_NORMAL;
386                FontVariationAxis[] axes = null;
387                try {
388                    axes = FontVariationAxis.fromFontVariationSettings(
389                            result.getFontVariationSettings());
390                } catch (FontVariationAxis.InvalidFormatException e) {
391                    // TODO: Nice to pass FontVariationAxis[] directly instead of string.
392                }
393                if (!fontFamily.addFontFromBuffer(fontBuffer, result.getTtcIndex(),
394                        axes, weight, italic)) {
395                    Log.e(TAG, "Error creating font " + request.getQuery());
396                    callback.onTypefaceRequestFailed(
397                            FontRequestCallback.FAIL_REASON_FONT_LOAD_ERROR);
398                    return;
399                }
400            } catch (IOException e) {
401                Log.e(TAG, "Error reading font " + request.getQuery(), e);
402                callback.onTypefaceRequestFailed(
403                        FontRequestCallback.FAIL_REASON_FONT_LOAD_ERROR);
404                return;
405            } finally {
406                IoUtils.closeQuietly(fd);
407            }
408        }
409        if (!fontFamily.freeze()) {
410            callback.onTypefaceRequestFailed(
411                    FontRequestCallback.FAIL_REASON_FONT_LOAD_ERROR);
412            return;
413        }
414        Typeface typeface = Typeface.createFromFamiliesWithDefault(
415                new FontFamily[] { fontFamily },
416                RESOLVE_BY_FONT_TABLE, RESOLVE_BY_FONT_TABLE);
417        synchronized (sDynamicTypefaceCache) {
418            String key = createProviderUid(request.getProviderAuthority(), request.getQuery());
419            sDynamicTypefaceCache.put(key, typeface);
420        }
421        callback.onTypefaceRetrieved(typeface);
422    }
423
424    /**
425     * Interface used to receive asynchronously fetched typefaces.
426     */
427    @Deprecated
428    public interface FontRequestCallback {
429        /**
430         * Constant returned by {@link #onTypefaceRequestFailed(int)} signaling that the given
431         * provider was not found on the device.
432         */
433        int FAIL_REASON_PROVIDER_NOT_FOUND = FontsContract.RESULT_CODE_PROVIDER_NOT_FOUND;
434        /**
435         * Constant returned by {@link #onTypefaceRequestFailed(int)} signaling that the given
436         * provider must be authenticated and the given certificates do not match its signature.
437         */
438        int FAIL_REASON_WRONG_CERTIFICATES = FontsContract.RESULT_CODE_WRONG_CERTIFICATES;
439        /**
440         * Constant returned by {@link #onTypefaceRequestFailed(int)} signaling that the font
441         * returned by the provider was not loaded properly.
442         */
443        int FAIL_REASON_FONT_LOAD_ERROR = -3;
444        /**
445         * Constant returned by {@link #onTypefaceRequestFailed(int)} signaling that the font
446         * provider did not return any results for the given query.
447         */
448        int FAIL_REASON_FONT_NOT_FOUND = FontsContract.Columns.RESULT_CODE_FONT_NOT_FOUND;
449        /**
450         * Constant returned by {@link #onTypefaceRequestFailed(int)} signaling that the font
451         * provider found the queried font, but it is currently unavailable.
452         */
453        int FAIL_REASON_FONT_UNAVAILABLE = FontsContract.Columns.RESULT_CODE_FONT_UNAVAILABLE;
454        /**
455         * Constant returned by {@link #onTypefaceRequestFailed(int)} signaling that the given
456         * query was not supported by the provider.
457         */
458        int FAIL_REASON_MALFORMED_QUERY = FontsContract.Columns.RESULT_CODE_MALFORMED_QUERY;
459
460        /** @hide */
461        @IntDef({ FAIL_REASON_PROVIDER_NOT_FOUND, FAIL_REASON_FONT_LOAD_ERROR,
462                FAIL_REASON_FONT_NOT_FOUND, FAIL_REASON_FONT_UNAVAILABLE,
463                FAIL_REASON_MALFORMED_QUERY })
464        @Retention(RetentionPolicy.SOURCE)
465        @interface FontRequestFailReason {}
466
467        /**
468         * Called then a Typeface request done via {@link Typeface#create(FontRequest,
469         * FontRequestCallback)} is complete. Note that this method will not be called if
470         * {@link #onTypefaceRequestFailed(int)} is called instead.
471         * @param typeface  The Typeface object retrieved.
472         */
473        void onTypefaceRetrieved(Typeface typeface);
474
475        /**
476         * Called when a Typeface request done via {@link Typeface#create(FontRequest,
477         * FontRequestCallback)} fails.
478         * @param reason May be one of {@link #FAIL_REASON_PROVIDER_NOT_FOUND},
479         *               {@link #FAIL_REASON_FONT_NOT_FOUND},
480         *               {@link #FAIL_REASON_FONT_LOAD_ERROR},
481         *               {@link #FAIL_REASON_FONT_UNAVAILABLE} or
482         *               {@link #FAIL_REASON_MALFORMED_QUERY} if returned by the system. May also be
483         *               a positive value greater than 0 defined by the font provider as an
484         *               additional error code. Refer to the provider's documentation for more
485         *               information on possible returned error codes.
486         */
487        void onTypefaceRequestFailed(@FontRequestFailReason int reason);
488    }
489
490    private static final FontRequestCallback NO_OP_REQUEST_CALLBACK = new FontRequestCallback() {
491        @Override
492        public void onTypefaceRetrieved(Typeface typeface) {
493            // Do nothing.
494        }
495
496        @Override
497        public void onTypefaceRequestFailed(@FontRequestFailReason int reason) {
498            // Do nothing.
499        }
500    };
501
502    /**
503     * A builder class for creating new Typeface instance.
504     *
505     * <p>
506     * Examples,
507     * 1) Create Typeface from ttf file.
508     * <pre>
509     * <code>
510     * Typeface.Builder buidler = new Typeface.Builder("your_font_file.ttf");
511     * Typeface typeface = builder.build();
512     * </code>
513     * </pre>
514     *
515     * 2) Create Typeface from ttc file in assets directory.
516     * <pre>
517     * <code>
518     * Typeface.Builder buidler = new Typeface.Builder(getAssets(), "your_font_file.ttc");
519     * builder.setTtcIndex(2);  // Set index of font collection.
520     * Typeface typeface = builder.build();
521     * </code>
522     * </pre>
523     *
524     * 3) Create Typeface with variation settings.
525     * <pre>
526     * <code>
527     * Typeface.Builder buidler = new Typeface.Builder("your_font_file.ttf");
528     * builder.setFontVariationSettings("'wght' 700, 'slnt' 20, 'ital' 1");
529     * builder.setWeight(700);  // Tell the system that this is a bold font.
530     * builder.setItalic(true);  // Tell the system that this is an italic style font.
531     * Typeface typeface = builder.build();
532     * </code>
533     * </pre>
534     * </p>
535     */
536    public static final class Builder {
537        /** @hide */
538        public static final int NORMAL_WEIGHT = 400;
539        /** @hide */
540        public static final int BOLD_WEIGHT = 700;
541
542        private int mTtcIndex;
543        private FontVariationAxis[] mAxes;
544
545        private AssetManager mAssetManager;
546        private String mPath;
547        private FileDescriptor mFd;
548
549        private FontsContract.FontInfo[] mFonts;
550        private Map<Uri, ByteBuffer> mFontBuffers;
551
552        private String mFallbackFamilyName;
553
554        private int mWeight = RESOLVE_BY_FONT_TABLE;
555        private int mItalic = RESOLVE_BY_FONT_TABLE;
556
557        /**
558         * Constructs a builder with a file path.
559         *
560         * @param path The file object refers to the font file.
561         */
562        public Builder(@NonNull File path) {
563            mPath = path.getAbsolutePath();
564        }
565
566        /**
567         * Constructs a builder with a file descriptor.
568         *
569         * @param fd The file descriptor. The passed fd must be mmap-able.
570         */
571        public Builder(@NonNull FileDescriptor fd) {
572            mFd = fd;
573        }
574
575        /**
576         * Constructs a builder with a file path.
577         *
578         * @param path The full path to the font file.
579         */
580        public Builder(@NonNull String path) {
581            mPath = path;
582        }
583
584        /**
585         * Constructs a builder from an asset manager and a file path in an asset directory.
586         *
587         * @param assetManager The application's asset manager
588         * @param path The file name of the font data in the asset directory
589         */
590        public Builder(@NonNull AssetManager assetManager, @NonNull String path) {
591            mAssetManager = Preconditions.checkNotNull(assetManager);
592            mPath = Preconditions.checkStringNotEmpty(path);
593        }
594
595        /**
596         * Constracts a builder from an array of FontsContract.FontInfo.
597         *
598         * Since {@link FontsContract.FontInfo} holds information about TTC indices and
599         * variation settings, there is no need to call {@link #setTtcIndex} or
600         * {@link #setFontVariationSettings}. Similary, {@link FontsContract.FontInfo} holds
601         * weight and italic information, so {@link #setWeight} and {@link #setItalic} are used
602         * for style matching during font selection.
603         *
604         * @param results The array of {@link FontsContract.FontInfo}
605         * @param buffers The mapping from URI to buffers to be used during building.
606         * @hide
607         */
608        public Builder(@NonNull FontsContract.FontInfo[] fonts,
609                @NonNull Map<Uri, ByteBuffer> buffers) {
610            mFonts = fonts;
611            mFontBuffers = buffers;
612        }
613
614        /**
615         * Sets weight of the font.
616         *
617         * Tells the system the weight of the given font. If not provided, the system will resolve
618         * the weight value by reading font tables.
619         * @param weight a weight value.
620         */
621        public Builder setWeight(@IntRange(from = 1, to = 1000) int weight) {
622            mWeight = weight;
623            return this;
624        }
625
626        /**
627         * Sets italic information of the font.
628         *
629         * Tells the system the style of the given font. If not provided, the system will resolve
630         * the style by reading font tables.
631         * @param italic {@code true} if the font is italic. Otherwise {@code false}.
632         */
633        public Builder setItalic(boolean italic) {
634            mItalic = italic ? STYLE_ITALIC : STYLE_NORMAL;
635            return this;
636        }
637
638        /**
639         * Sets an index of the font collection.
640         *
641         * Can not be used for Typeface source. build() method will return null for invalid index.
642         * @param ttcIndex An index of the font collection. If the font source is not font
643         *                 collection, do not call this method or specify 0.
644         */
645        public Builder setTtcIndex(@IntRange(from = 0) int ttcIndex) {
646            if (mFonts != null) {
647                throw new IllegalArgumentException(
648                        "TTC index can not be specified for FontResult source.");
649            }
650            mTtcIndex = ttcIndex;
651            return this;
652        }
653
654        /**
655         * Sets a font variation settings.
656         *
657         * @param variationSettings See {@link android.widget.TextView#setFontVariationSettings}.
658         * @throws InvalidFormatException If given string is not a valid font variation settings
659         *                                format.
660         */
661        public Builder setFontVariationSettings(@Nullable String variationSettings)
662                throws InvalidFormatException {
663            if (mFonts != null) {
664                throw new IllegalArgumentException(
665                        "Font variation settings can not be specified for FontResult source.");
666            }
667            if (mAxes != null) {
668                throw new IllegalStateException("Font variation settings are already set.");
669            }
670            mAxes = FontVariationAxis.fromFontVariationSettings(variationSettings);
671            return this;
672        }
673
674        /**
675         * Sets a font variation settings.
676         *
677         * @param axes An array of font variation axis tag-value pairs.
678         */
679        public Builder setFontVariationSettings(@Nullable FontVariationAxis[] axes) {
680            if (mFonts != null) {
681                throw new IllegalArgumentException(
682                        "Font variation settings can not be specified for FontResult source.");
683            }
684            if (mAxes != null) {
685                throw new IllegalStateException("Font variation settings are already set.");
686            }
687            mAxes = axes;
688            return this;
689        }
690
691        /**
692         * Sets a fallback family name.
693         *
694         * By specifying a fallback family name, a fallback Typeface will be returned if the
695         * {@link #build} method fails to create a Typeface from the provided font. The fallback
696         * family will be resolved with the provided weight and italic information specified by
697         * {@link #setWeight} and {@link #setItalic}.
698         *
699         * If {@link #setWeight} is not called, the fallback family keeps the default weight.
700         * Similary, if {@link #setItalic} is not called, the fallback family keeps the default
701         * italic information. For example, calling {@code builder.setFallback("sans-serif-light")}
702         * is equivalent to calling {@code builder.setFallback("sans-serif").setWeight(300)} in
703         * terms of fallback. The default weight and italic information are overridden by calling
704         * {@link #setWeight} and {@link #setItalic}. For example, if a Typeface is constructed
705         * using {@code builder.setFallback("sans-serif-light").setWeight(700)}, the fallback text
706         * will render as sans serif bold.
707         *
708         * @param familyName A family name to be used for fallback if the provided font can not be
709         *                   used. By passing {@code null}, build() returns {@code null}.
710         *                   If {@link #setFallback} is not called on the builder, {@code null}
711         *                   is assumed.
712         */
713        public Builder setFallback(@Nullable String familyName) {
714            mFallbackFamilyName = familyName;
715            return this;
716        }
717
718        /**
719         * Creates a unique id for a given AssetManager and asset path.
720         *
721         * @param mgr  AssetManager instance
722         * @param path The path for the asset.
723         * @param ttcIndex The TTC index for the font.
724         * @param axes The font variation settings.
725         * @return Unique id for a given AssetManager and asset path.
726         */
727        private static String createAssetUid(final AssetManager mgr, String path, int ttcIndex,
728                @Nullable FontVariationAxis[] axes, int weight, int italic) {
729            final SparseArray<String> pkgs = mgr.getAssignedPackageIdentifiers();
730            final StringBuilder builder = new StringBuilder();
731            final int size = pkgs.size();
732            for (int i = 0; i < size; i++) {
733                builder.append(pkgs.valueAt(i));
734                builder.append("-");
735            }
736            builder.append(path);
737            builder.append("-");
738            builder.append(Integer.toString(ttcIndex));
739            builder.append("-");
740            builder.append(Integer.toString(weight));
741            builder.append("-");
742            builder.append(Integer.toString(italic));
743            builder.append("-");
744            if (axes != null) {
745                for (FontVariationAxis axis : axes) {
746                    builder.append(axis.getTag());
747                    builder.append("-");
748                    builder.append(Float.toString(axis.getStyleValue()));
749                }
750            }
751            return builder.toString();
752        }
753
754        private static final Object sLock = new Object();
755        // TODO: Unify with Typeface.sTypefaceCache.
756        @GuardedBy("sLock")
757        private static final LongSparseArray<SparseArray<Typeface>> sTypefaceCache =
758                new LongSparseArray<>(3);
759
760        private Typeface resolveFallbackTypeface() {
761            if (mFallbackFamilyName == null) {
762                return null;
763            }
764
765            Typeface base =  sSystemFontMap.get(mFallbackFamilyName);
766            if (base == null) {
767                base = sDefaultTypeface;
768            }
769
770            if (mWeight == RESOLVE_BY_FONT_TABLE && mItalic == RESOLVE_BY_FONT_TABLE) {
771                return base;
772            }
773
774            final int weight = (mWeight == RESOLVE_BY_FONT_TABLE) ? base.mBaseWeight : mWeight;
775            final boolean italic =
776                    (mItalic == RESOLVE_BY_FONT_TABLE) ? (base.mStyle & ITALIC) != 0 : mItalic == 1;
777            final int key = weight << 1 | (italic ? 1 : 0);
778
779            Typeface typeface;
780            synchronized(sLock) {
781                SparseArray<Typeface> innerCache = sTypefaceCache.get(base.native_instance);
782                if (innerCache != null) {
783                    typeface = innerCache.get(key);
784                    if (typeface != null) {
785                        return typeface;
786                    }
787                }
788
789                typeface = new Typeface(
790                        nativeCreateFromTypefaceWithExactStyle(
791                                base.native_instance, weight, italic));
792
793                if (innerCache == null) {
794                    innerCache = new SparseArray<>(4); // [regular, bold] x [upright, italic]
795                    sTypefaceCache.put(base.native_instance, innerCache);
796                }
797                innerCache.put(key, typeface);
798            }
799            return typeface;
800        }
801
802        /**
803         * Generates new Typeface from specified configuration.
804         *
805         * @return Newly created Typeface. May return null if some parameters are invalid.
806         */
807        public Typeface build() {
808            if (mFd != null) {  // Builder is created with file descriptor.
809                try (FileInputStream fis = new FileInputStream(mFd)) {
810                    FileChannel channel = fis.getChannel();
811                    long size = channel.size();
812                    ByteBuffer buffer = channel.map(FileChannel.MapMode.READ_ONLY, 0, size);
813
814                    final FontFamily fontFamily = new FontFamily();
815                    if (!fontFamily.addFontFromBuffer(buffer, mTtcIndex, mAxes, mWeight, mItalic)) {
816                        fontFamily.abortCreation();
817                        return resolveFallbackTypeface();
818                    }
819                    if (!fontFamily.freeze()) {
820                        return resolveFallbackTypeface();
821                    }
822                    FontFamily[] families = { fontFamily };
823                    return createFromFamiliesWithDefault(families, mWeight, mItalic);
824                } catch (IOException e) {
825                    return resolveFallbackTypeface();
826                }
827            } else if (mAssetManager != null) {  // Builder is created with asset manager.
828                final String key = createAssetUid(
829                        mAssetManager, mPath, mTtcIndex, mAxes, mWeight, mItalic);
830                synchronized (sLock) {
831                    Typeface typeface = sDynamicTypefaceCache.get(key);
832                    if (typeface != null) return typeface;
833                    final FontFamily fontFamily = new FontFamily();
834                    if (!fontFamily.addFontFromAssetManager(mAssetManager, mPath, mTtcIndex,
835                            true /* isAsset */, mTtcIndex, mWeight, mItalic, mAxes)) {
836                        fontFamily.abortCreation();
837                        return resolveFallbackTypeface();
838                    }
839                    if (!fontFamily.freeze()) {
840                        return resolveFallbackTypeface();
841                    }
842                    FontFamily[] families = { fontFamily };
843                    typeface = createFromFamiliesWithDefault(families, mWeight, mItalic);
844                    sDynamicTypefaceCache.put(key, typeface);
845                    return typeface;
846                }
847            } else if (mPath != null) {  // Builder is created with file path.
848                final FontFamily fontFamily = new FontFamily();
849                if (!fontFamily.addFont(mPath, mTtcIndex, mAxes, mWeight, mItalic)) {
850                    fontFamily.abortCreation();
851                    return resolveFallbackTypeface();
852                }
853                if (!fontFamily.freeze()) {
854                    return resolveFallbackTypeface();
855                }
856                FontFamily[] families = { fontFamily };
857                return createFromFamiliesWithDefault(families, mWeight, mItalic);
858            } else if (mFonts != null) {
859                final FontFamily fontFamily = new FontFamily();
860                boolean atLeastOneFont = false;
861                for (FontsContract.FontInfo font : mFonts) {
862                    final ByteBuffer fontBuffer = mFontBuffers.get(font.getUri());
863                    if (fontBuffer == null) {
864                        continue;  // skip
865                    }
866                    final boolean success = fontFamily.addFontFromBuffer(fontBuffer,
867                            font.getTtcIndex(), font.getAxes(), font.getWeight(),
868                            font.isItalic() ? STYLE_ITALIC : STYLE_NORMAL);
869                    if (!success) {
870                        fontFamily.abortCreation();
871                        return null;
872                    }
873                    atLeastOneFont = true;
874                }
875                if (!atLeastOneFont) {
876                    // No fonts are avaialble. No need to create new Typeface and returns fallback
877                    // Typeface instead.
878                    fontFamily.abortCreation();
879                    return null;
880                }
881                fontFamily.freeze();
882                FontFamily[] families = { fontFamily };
883                return createFromFamiliesWithDefault(families, mWeight, mItalic);
884            }
885
886            // Must not reach here.
887            throw new IllegalArgumentException("No source was set.");
888        }
889    }
890
891    /**
892     * Create a typeface object given a family name, and option style information.
893     * If null is passed for the name, then the "default" font will be chosen.
894     * The resulting typeface object can be queried (getStyle()) to discover what
895     * its "real" style characteristics are.
896     *
897     * @param familyName May be null. The name of the font family.
898     * @param style  The style (normal, bold, italic) of the typeface.
899     *               e.g. NORMAL, BOLD, ITALIC, BOLD_ITALIC
900     * @return The best matching typeface.
901     */
902    public static Typeface create(String familyName, int style) {
903        if (sSystemFontMap != null) {
904            return create(sSystemFontMap.get(familyName), style);
905        }
906        return null;
907    }
908
909    /**
910     * Create a typeface object that best matches the specified existing
911     * typeface and the specified Style. Use this call if you want to pick a new
912     * style from the same family of an existing typeface object. If family is
913     * null, this selects from the default font's family.
914     *
915     * @param family May be null. The name of the existing type face.
916     * @param style  The style (normal, bold, italic) of the typeface.
917     *               e.g. NORMAL, BOLD, ITALIC, BOLD_ITALIC
918     * @return The best matching typeface.
919     */
920    public static Typeface create(Typeface family, int style) {
921        if (style < 0 || style > 3) {
922            style = 0;
923        }
924        long ni = 0;
925        if (family != null) {
926            // Return early if we're asked for the same face/style
927            if (family.mStyle == style) {
928                return family;
929            }
930
931            ni = family.native_instance;
932        }
933
934        Typeface typeface;
935        SparseArray<Typeface> styles = sTypefaceCache.get(ni);
936
937        if (styles != null) {
938            typeface = styles.get(style);
939            if (typeface != null) {
940                return typeface;
941            }
942        }
943
944        typeface = new Typeface(nativeCreateFromTypeface(ni, style));
945        if (styles == null) {
946            styles = new SparseArray<Typeface>(4);
947            sTypefaceCache.put(ni, styles);
948        }
949        styles.put(style, typeface);
950
951        return typeface;
952    }
953
954    /** @hide */
955    public static Typeface createFromTypefaceWithVariation(Typeface family,
956            List<FontVariationAxis> axes) {
957        final long ni = family == null ? 0 : family.native_instance;
958        return new Typeface(nativeCreateFromTypefaceWithVariation(ni, axes));
959    }
960
961    /**
962     * Returns one of the default typeface objects, based on the specified style
963     *
964     * @return the default typeface that corresponds to the style
965     */
966    public static Typeface defaultFromStyle(int style) {
967        return sDefaults[style];
968    }
969
970    /**
971     * Create a new typeface from the specified font data.
972     *
973     * @param mgr  The application's asset manager
974     * @param path The file name of the font data in the assets directory
975     * @return The new typeface.
976     */
977    public static Typeface createFromAsset(AssetManager mgr, String path) {
978        if (path == null) {
979            throw new NullPointerException();  // for backward compatibility
980        }
981        if (sFallbackFonts != null) {
982            synchronized (sLock) {
983                Typeface typeface = new Builder(mgr, path).build();
984                if (typeface != null) return typeface;
985
986                final String key = Builder.createAssetUid(mgr, path, 0 /* ttcIndex */,
987                        null /* axes */, RESOLVE_BY_FONT_TABLE, RESOLVE_BY_FONT_TABLE);
988                typeface = sDynamicTypefaceCache.get(key);
989                if (typeface != null) return typeface;
990
991                final FontFamily fontFamily = new FontFamily();
992                if (fontFamily.addFontFromAssetManager(mgr, path, 0, true /* isAsset */,
993                        0 /* ttc index */, RESOLVE_BY_FONT_TABLE, RESOLVE_BY_FONT_TABLE,
994                        null /* axes */)) {
995                    // Due to backward compatibility, even if the font is not supported by our font
996                    // stack, we need to place the empty font at the first place. The typeface with
997                    // empty font behaves different from default typeface especially in fallback
998                    // font selection.
999                    fontFamily.allowUnsupportedFont();
1000                    fontFamily.freeze();
1001                    final FontFamily[] families = { fontFamily };
1002                    typeface = createFromFamiliesWithDefault(families, RESOLVE_BY_FONT_TABLE,
1003                            RESOLVE_BY_FONT_TABLE);
1004                    sDynamicTypefaceCache.put(key, typeface);
1005                    return typeface;
1006                } else {
1007                    fontFamily.abortCreation();
1008                }
1009            }
1010        }
1011        throw new RuntimeException("Font asset not found " + path);
1012    }
1013
1014    /**
1015     * Creates a unique id for a given font provider and query.
1016     */
1017    private static String createProviderUid(String authority, String query) {
1018        final StringBuilder builder = new StringBuilder();
1019        builder.append("provider:");
1020        builder.append(authority);
1021        builder.append("-");
1022        builder.append(query);
1023        return builder.toString();
1024    }
1025
1026    /**
1027     * Create a new typeface from the specified font file.
1028     *
1029     * @param path The path to the font data.
1030     * @return The new typeface.
1031     */
1032    public static Typeface createFromFile(@Nullable File path) {
1033        // For the compatibility reasons, leaving possible NPE here.
1034        // See android.graphics.cts.TypefaceTest#testCreateFromFileByFileReferenceNull
1035        return createFromFile(path.getAbsolutePath());
1036    }
1037
1038    /**
1039     * Create a new typeface from the specified font file.
1040     *
1041     * @param path The full path to the font data.
1042     * @return The new typeface.
1043     */
1044    public static Typeface createFromFile(@Nullable String path) {
1045        if (sFallbackFonts != null) {
1046            final FontFamily fontFamily = new FontFamily();
1047            if (fontFamily.addFont(path, 0 /* ttcIndex */, null /* axes */,
1048                      RESOLVE_BY_FONT_TABLE, RESOLVE_BY_FONT_TABLE)) {
1049                // Due to backward compatibility, even if the font is not supported by our font
1050                // stack, we need to place the empty font at the first place. The typeface with
1051                // empty font behaves different from default typeface especially in fallback font
1052                // selection.
1053                fontFamily.allowUnsupportedFont();
1054                fontFamily.freeze();
1055                FontFamily[] families = { fontFamily };
1056                return createFromFamiliesWithDefault(families, RESOLVE_BY_FONT_TABLE,
1057                        RESOLVE_BY_FONT_TABLE);
1058            } else {
1059                fontFamily.abortCreation();
1060            }
1061        }
1062        throw new RuntimeException("Font not found " + path);
1063    }
1064
1065    /**
1066     * Create a new typeface from an array of font families.
1067     *
1068     * @param families array of font families
1069     */
1070    private static Typeface createFromFamilies(FontFamily[] families) {
1071        long[] ptrArray = new long[families.length];
1072        for (int i = 0; i < families.length; i++) {
1073            ptrArray[i] = families[i].mNativePtr;
1074        }
1075        return new Typeface(nativeCreateFromArray(
1076                ptrArray, RESOLVE_BY_FONT_TABLE, RESOLVE_BY_FONT_TABLE));
1077    }
1078
1079    /**
1080     * Create a new typeface from an array of font families, including
1081     * also the font families in the fallback list.
1082     * @param weight the weight for this family. {@link RESOLVE_BY_FONT_TABLE} can be used. In that
1083     *               case, the table information in the first family's font is used. If the first
1084     *               family has multiple fonts, the closest to the regular weight and upright font
1085     *               is used.
1086     * @param italic the italic information for this family. {@link RESOLVE_BY_FONT_TABLE} can be
1087     *               used. In that case, the table information in the first family's font is used.
1088     *               If the first family has multiple fonts, the closest to the regular weight and
1089     *               upright font is used.
1090     * @param families array of font families
1091     */
1092    private static Typeface createFromFamiliesWithDefault(FontFamily[] families,
1093                int weight, int italic) {
1094        long[] ptrArray = new long[families.length + sFallbackFonts.length];
1095        for (int i = 0; i < families.length; i++) {
1096            ptrArray[i] = families[i].mNativePtr;
1097        }
1098        for (int i = 0; i < sFallbackFonts.length; i++) {
1099            ptrArray[i + families.length] = sFallbackFonts[i].mNativePtr;
1100        }
1101        return new Typeface(nativeCreateFromArray(ptrArray, weight, italic));
1102    }
1103
1104    // don't allow clients to call this directly
1105    private Typeface(long ni) {
1106        if (ni == 0) {
1107            throw new RuntimeException("native typeface cannot be made");
1108        }
1109
1110        native_instance = ni;
1111        mStyle = nativeGetStyle(ni);
1112        mBaseWeight = nativeGetBaseWeight(ni);
1113    }
1114
1115    private static FontFamily makeFamilyFromParsed(FontConfig.Family family,
1116            Map<String, ByteBuffer> bufferForPath) {
1117        FontFamily fontFamily = new FontFamily(family.getLanguage(), family.getVariant());
1118        for (FontConfig.Font font : family.getFonts()) {
1119            String fullPathName = "/system/fonts/" + font.getFontName();
1120            ByteBuffer fontBuffer = bufferForPath.get(fullPathName);
1121            if (fontBuffer == null) {
1122                try (FileInputStream file = new FileInputStream(fullPathName)) {
1123                    FileChannel fileChannel = file.getChannel();
1124                    long fontSize = fileChannel.size();
1125                    fontBuffer = fileChannel.map(FileChannel.MapMode.READ_ONLY, 0, fontSize);
1126                    bufferForPath.put(fullPathName, fontBuffer);
1127                } catch (IOException e) {
1128                    Log.e(TAG, "Error mapping font file " + fullPathName);
1129                    continue;
1130                }
1131            }
1132            if (!fontFamily.addFontFromBuffer(fontBuffer, font.getTtcIndex(), font.getAxes(),
1133                    font.getWeight(), font.isItalic() ? STYLE_ITALIC : STYLE_NORMAL)) {
1134                Log.e(TAG, "Error creating font " + fullPathName + "#" + font.getTtcIndex());
1135            }
1136        }
1137        if (!fontFamily.freeze()) {
1138            // Treat as system error since reaching here means that a system pre-installed font
1139            // can't be used by our font stack.
1140            Log.e(TAG, "Unable to load Family: " + family.getName() + ":" + family.getLanguage());
1141            return null;
1142        }
1143        return fontFamily;
1144    }
1145
1146    /*
1147     * (non-Javadoc)
1148     *
1149     * This should only be called once, from the static class initializer block.
1150     */
1151    private static void init() {
1152        // Load font config and initialize Minikin state
1153        File systemFontConfigLocation = getSystemFontConfigLocation();
1154        File configFilename = new File(systemFontConfigLocation, FONTS_CONFIG);
1155        try {
1156            FileInputStream fontsIn = new FileInputStream(configFilename);
1157            FontConfig fontConfig = FontListParser.parse(fontsIn);
1158
1159            Map<String, ByteBuffer> bufferForPath = new HashMap<String, ByteBuffer>();
1160
1161            List<FontFamily> familyList = new ArrayList<FontFamily>();
1162            // Note that the default typeface is always present in the fallback list;
1163            // this is an enhancement from pre-Minikin behavior.
1164            for (int i = 0; i < fontConfig.getFamilies().length; i++) {
1165                FontConfig.Family f = fontConfig.getFamilies()[i];
1166                if (i == 0 || f.getName() == null) {
1167                    FontFamily family = makeFamilyFromParsed(f, bufferForPath);
1168                    if (family != null) {
1169                        familyList.add(family);
1170                    }
1171                }
1172            }
1173            sFallbackFonts = familyList.toArray(new FontFamily[familyList.size()]);
1174            setDefault(Typeface.createFromFamilies(sFallbackFonts));
1175
1176            Map<String, Typeface> systemFonts = new HashMap<String, Typeface>();
1177            for (int i = 0; i < fontConfig.getFamilies().length; i++) {
1178                Typeface typeface;
1179                FontConfig.Family f = fontConfig.getFamilies()[i];
1180                if (f.getName() != null) {
1181                    if (i == 0) {
1182                        // The first entry is the default typeface; no sense in
1183                        // duplicating the corresponding FontFamily.
1184                        typeface = sDefaultTypeface;
1185                    } else {
1186                        FontFamily fontFamily = makeFamilyFromParsed(f, bufferForPath);
1187                        if (fontFamily == null) {
1188                            continue;
1189                        }
1190                        FontFamily[] families = { fontFamily };
1191                        typeface = Typeface.createFromFamiliesWithDefault(families,
1192                                RESOLVE_BY_FONT_TABLE, RESOLVE_BY_FONT_TABLE);
1193                    }
1194                    systemFonts.put(f.getName(), typeface);
1195                }
1196            }
1197            for (FontConfig.Alias alias : fontConfig.getAliases()) {
1198                Typeface base = systemFonts.get(alias.getToName());
1199                Typeface newFace = base;
1200                int weight = alias.getWeight();
1201                if (weight != 400) {
1202                    newFace = new Typeface(nativeCreateWeightAlias(base.native_instance, weight));
1203                }
1204                systemFonts.put(alias.getName(), newFace);
1205            }
1206            sSystemFontMap = systemFonts;
1207
1208        } catch (RuntimeException e) {
1209            Log.w(TAG, "Didn't create default family (most likely, non-Minikin build)", e);
1210            // TODO: normal in non-Minikin case, remove or make error when Minikin-only
1211        } catch (FileNotFoundException e) {
1212            Log.e(TAG, "Error opening " + configFilename, e);
1213        } catch (IOException e) {
1214            Log.e(TAG, "Error reading " + configFilename, e);
1215        } catch (XmlPullParserException e) {
1216            Log.e(TAG, "XML parse exception for " + configFilename, e);
1217        }
1218    }
1219
1220    static {
1221        init();
1222        // Set up defaults and typefaces exposed in public API
1223        DEFAULT         = create((String) null, 0);
1224        DEFAULT_BOLD    = create((String) null, Typeface.BOLD);
1225        SANS_SERIF      = create("sans-serif", 0);
1226        SERIF           = create("serif", 0);
1227        MONOSPACE       = create("monospace", 0);
1228
1229        sDefaults = new Typeface[] {
1230            DEFAULT,
1231            DEFAULT_BOLD,
1232            create((String) null, Typeface.ITALIC),
1233            create((String) null, Typeface.BOLD_ITALIC),
1234        };
1235
1236    }
1237
1238    private static File getSystemFontConfigLocation() {
1239        return new File("/system/etc/");
1240    }
1241
1242    @Override
1243    protected void finalize() throws Throwable {
1244        try {
1245            nativeUnref(native_instance);
1246            native_instance = 0;  // Other finalizers can still call us.
1247        } finally {
1248            super.finalize();
1249        }
1250    }
1251
1252    @Override
1253    public boolean equals(Object o) {
1254        if (this == o) return true;
1255        if (o == null || getClass() != o.getClass()) return false;
1256
1257        Typeface typeface = (Typeface) o;
1258
1259        return mStyle == typeface.mStyle && native_instance == typeface.native_instance;
1260    }
1261
1262    @Override
1263    public int hashCode() {
1264        /*
1265         * Modified method for hashCode with long native_instance derived from
1266         * http://developer.android.com/reference/java/lang/Object.html
1267         */
1268        int result = 17;
1269        result = 31 * result + (int) (native_instance ^ (native_instance >>> 32));
1270        result = 31 * result + mStyle;
1271        return result;
1272    }
1273
1274    /** @hide */
1275    public boolean isSupportedAxes(int axis) {
1276        if (mSupportedAxes == null) {
1277            synchronized (this) {
1278                if (mSupportedAxes == null) {
1279                    mSupportedAxes = nativeGetSupportedAxes(native_instance);
1280                    if (mSupportedAxes == null) {
1281                        mSupportedAxes = EMPTY_AXES;
1282                    }
1283                }
1284            }
1285        }
1286        return Arrays.binarySearch(mSupportedAxes, axis) > 0;
1287    }
1288
1289    private static native long nativeCreateFromTypeface(long native_instance, int style);
1290    private static native long nativeCreateFromTypefaceWithExactStyle(
1291            long native_instance, int weight, boolean italic);
1292    // TODO: clean up: change List<FontVariationAxis> to FontVariationAxis[]
1293    private static native long nativeCreateFromTypefaceWithVariation(
1294            long native_instance, List<FontVariationAxis> axes);
1295    private static native long nativeCreateWeightAlias(long native_instance, int weight);
1296    private static native void nativeUnref(long native_instance);
1297    private static native int  nativeGetStyle(long native_instance);
1298    private static native int  nativeGetBaseWeight(long native_instance);
1299    private static native long nativeCreateFromArray(long[] familyArray, int weight, int italic);
1300    private static native void nativeSetDefault(long native_instance);
1301    private static native int[] nativeGetSupportedAxes(long native_instance);
1302}
1303