Typeface.java revision b12397e57e79c5dd9e8b2cb3839f5cd30b5d515f
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 android.annotation.IntDef;
20import android.annotation.NonNull;
21import android.annotation.Nullable;
22import android.content.res.AssetManager;
23import android.graphics.fonts.FontRequest;
24import android.graphics.fonts.FontResult;
25import android.os.Bundle;
26import android.os.Handler;
27import android.os.ParcelFileDescriptor;
28import android.os.ResultReceiver;
29import android.provider.FontsContract;
30import android.text.FontConfig;
31import android.util.Log;
32import android.util.LongSparseArray;
33import android.util.LruCache;
34import android.util.SparseArray;
35import android.graphics.FontListParser;
36
37import com.android.internal.annotations.GuardedBy;
38
39import libcore.io.IoUtils;
40
41import org.xmlpull.v1.XmlPullParserException;
42
43import java.io.File;
44import java.io.FileInputStream;
45import java.io.FileNotFoundException;
46import java.io.IOException;
47import java.lang.annotation.Retention;
48import java.lang.annotation.RetentionPolicy;
49import java.nio.ByteBuffer;
50import java.nio.channels.FileChannel;
51import java.util.ArrayList;
52import java.util.HashMap;
53import java.util.List;
54import java.util.Map;
55
56/**
57 * The Typeface class specifies the typeface and intrinsic style of a font.
58 * This is used in the paint, along with optionally Paint settings like
59 * textSize, textSkewX, textScaleX to specify
60 * how text appears when drawn (and measured).
61 */
62public class Typeface {
63
64    private static String TAG = "Typeface";
65
66    /** The default NORMAL typeface object */
67    public static final Typeface DEFAULT;
68    /**
69     * The default BOLD typeface object. Note: this may be not actually be
70     * bold, depending on what fonts are installed. Call getStyle() to know
71     * for sure.
72     */
73    public static final Typeface DEFAULT_BOLD;
74    /** The NORMAL style of the default sans serif typeface. */
75    public static final Typeface SANS_SERIF;
76    /** The NORMAL style of the default serif typeface. */
77    public static final Typeface SERIF;
78    /** The NORMAL style of the default monospace typeface. */
79    public static final Typeface MONOSPACE;
80
81    static Typeface[] sDefaults;
82    private static final LongSparseArray<SparseArray<Typeface>> sTypefaceCache =
83            new LongSparseArray<>(3);
84    @GuardedBy("sLock")
85    private static FontsContract sFontsContract;
86    @GuardedBy("sLock")
87    private static Handler mHandler;
88
89    /**
90     * Cache for Typeface objects dynamically loaded from assets. Currently max size is 16.
91     */
92    private static final LruCache<String, Typeface> sDynamicTypefaceCache = new LruCache<>(16);
93
94    static Typeface sDefaultTypeface;
95    static Map<String, Typeface> sSystemFontMap;
96    static FontFamily[] sFallbackFonts;
97    private static final Object sLock = new Object();
98
99    static final String FONTS_CONFIG = "fonts.xml";
100
101    /**
102     * @hide
103     */
104    public long native_instance;
105
106    // Style
107    public static final int NORMAL = 0;
108    public static final int BOLD = 1;
109    public static final int ITALIC = 2;
110    public static final int BOLD_ITALIC = 3;
111
112    private int mStyle = 0;
113
114    private static void setDefault(Typeface t) {
115        sDefaultTypeface = t;
116        nativeSetDefault(t.native_instance);
117    }
118
119    /** Returns the typeface's intrinsic style attributes */
120    public int getStyle() {
121        return mStyle;
122    }
123
124    /** Returns true if getStyle() has the BOLD bit set. */
125    public final boolean isBold() {
126        return (mStyle & BOLD) != 0;
127    }
128
129    /** Returns true if getStyle() has the ITALIC bit set. */
130    public final boolean isItalic() {
131        return (mStyle & ITALIC) != 0;
132    }
133
134    /**
135     * @hide
136     * Used by Resources to load a font resource of type font file.
137     */
138    @Nullable
139    public static Typeface createFromResources(AssetManager mgr, String path, int cookie) {
140        if (sFallbackFonts != null) {
141            synchronized (sDynamicTypefaceCache) {
142                final String key = createAssetUid(mgr, path);
143                Typeface typeface = sDynamicTypefaceCache.get(key);
144                if (typeface != null) return typeface;
145
146                FontFamily fontFamily = new FontFamily();
147                if (fontFamily.addFontFromAssetManager(mgr, path, cookie, false /* isAsset */)) {
148                    fontFamily.freeze();
149                    FontFamily[] families = {fontFamily};
150                    typeface = createFromFamiliesWithDefault(families);
151                    sDynamicTypefaceCache.put(key, typeface);
152                    return typeface;
153                }
154            }
155        }
156        return null;
157    }
158
159    /**
160     * @hide
161     * Used by Resources to load a font resource of type xml.
162     */
163    @Nullable
164    public static Typeface createFromResources(FontConfig config, AssetManager mgr, String path) {
165        if (sFallbackFonts != null) {
166            synchronized (sDynamicTypefaceCache) {
167                final String key = createAssetUid(mgr, path);
168                Typeface typeface = sDynamicTypefaceCache.get(key);
169                if (typeface != null) return typeface;
170
171                List<FontConfig.Family> families = config.getFamilies();
172                if (families == null || families.isEmpty()) {
173                    throw new RuntimeException("Font resource contained no fonts.");
174                }
175                if (families.size() > 1) {
176                    throw new RuntimeException("Font resource contained more than one family.");
177                }
178                FontConfig.Family family = families.get(0);
179
180                FontFamily fontFamily = new FontFamily();
181                List<FontConfig.Font> fonts = family.getFonts();
182                for (int i = 0; i < fonts.size(); i++) {
183                    FontConfig.Font font = fonts.get(i);
184                    // TODO: Use style and weight info
185                    if (!fontFamily.addFontFromAssetManager(mgr, font.getFontName(),
186                            0 /* resourceCookie */, false /* isAsset */)) {
187                        return null;
188                    }
189                }
190                fontFamily.freeze();
191                FontFamily[] familyChain = { fontFamily };
192                typeface = createFromFamiliesWithDefault(familyChain);
193                sDynamicTypefaceCache.put(key, typeface);
194                return typeface;
195            }
196        }
197        return null;
198    }
199
200    /**
201     * Used by resources for cached loading if the font is available.
202     * @hide
203     */
204    public static Typeface findFromCache(AssetManager mgr, String path) {
205        synchronized (sDynamicTypefaceCache) {
206            final String key = createAssetUid(mgr, path);
207            Typeface typeface = sDynamicTypefaceCache.get(key);
208            if (typeface != null) {
209                return typeface;
210            }
211        }
212        return null;
213    }
214
215    /**
216     * Create a typeface object given a font request. The font will be asynchronously fetched,
217     * therefore the result is delivered to the given callback. See {@link FontRequest}.
218     * Only one of the methods in callback will be invoked, depending on whether the request
219     * succeeds or fails. These calls will happen on the main thread.
220     * @param request A {@link FontRequest} object that identifies the provider and query for the
221     *                request. May not be null.
222     * @param callback A callback that will be triggered when results are obtained. May not be null.
223     */
224    public static void create(@NonNull FontRequest request, @NonNull FontRequestCallback callback) {
225        // Check the cache first
226        // TODO: would the developer want to avoid a cache hit and always ask for the freshest
227        // result?
228        Typeface cachedTypeface = findFromCache(
229                request.getProviderAuthority(), request.getQuery());
230        if (cachedTypeface != null) {
231            mHandler.post(() -> callback.onTypefaceRetrieved(cachedTypeface));
232            return;
233        }
234        synchronized (sLock) {
235            if (sFontsContract == null) {
236                sFontsContract = new FontsContract();
237                mHandler = new Handler();
238            }
239            final ResultReceiver receiver = new ResultReceiver(null) {
240                @Override
241                public void onReceiveResult(int resultCode, Bundle resultData) {
242                    mHandler.post(() -> receiveResult(request, callback, resultCode, resultData));
243                }
244            };
245            sFontsContract.getFont(request, receiver);
246        }
247    }
248
249    private static Typeface findFromCache(String providerAuthority, String query) {
250        synchronized (sDynamicTypefaceCache) {
251            final String key = createProviderUid(providerAuthority, query);
252            Typeface typeface = sDynamicTypefaceCache.get(key);
253            if (typeface != null) {
254                return typeface;
255            }
256        }
257        return null;
258    }
259
260    private static void receiveResult(FontRequest request, FontRequestCallback callback,
261            int resultCode, Bundle resultData) {
262        Typeface cachedTypeface = findFromCache(
263                request.getProviderAuthority(), request.getQuery());
264        if (cachedTypeface != null) {
265            // We already know the result.
266            // Probably the requester requests the same font again in a short interval.
267            callback.onTypefaceRetrieved(cachedTypeface);
268            return;
269        }
270        if (resultCode == FontsContract.RESULT_CODE_PROVIDER_NOT_FOUND) {
271            callback.onTypefaceRequestFailed(
272                    FontRequestCallback.FAIL_REASON_PROVIDER_NOT_FOUND);
273            return;
274        }
275        if (resultCode == FontsContract.RESULT_CODE_FONT_NOT_FOUND
276                || resultData == null) {
277            callback.onTypefaceRequestFailed(
278                    FontRequestCallback.FAIL_REASON_FONT_NOT_FOUND);
279            return;
280        }
281        List<FontResult> resultList =
282                resultData.getParcelableArrayList(FontsContract.PARCEL_FONT_RESULTS);
283        if (resultList == null || resultList.isEmpty()) {
284            callback.onTypefaceRequestFailed(
285                    FontRequestCallback.FAIL_REASON_FONT_NOT_FOUND);
286            return;
287        }
288        FontFamily fontFamily = new FontFamily();
289        for (int i = 0; i < resultList.size(); ++i) {
290            FontResult result = resultList.get(i);
291            ParcelFileDescriptor fd = result.getFileDescriptor();
292            if (fd == null) {
293                callback.onTypefaceRequestFailed(
294                        FontRequestCallback.FAIL_REASON_FONT_LOAD_ERROR);
295                return;
296            }
297            try (FileInputStream is = new FileInputStream(fd.getFileDescriptor())) {
298                FileChannel fileChannel = is.getChannel();
299                long fontSize = fileChannel.size();
300                ByteBuffer fontBuffer = fileChannel.map(
301                        FileChannel.MapMode.READ_ONLY, 0, fontSize);
302                int style = result.getStyle();
303                int weight = (style & BOLD) != 0 ? 700 : 400;
304                // TODO: this method should be
305                // create(fd, ttcIndex, fontVariationSettings, style).
306                if (!fontFamily.addFontWeightStyle(fontBuffer, result.getTtcIndex(),
307                                null, weight, (style & ITALIC) != 0)) {
308                    Log.e(TAG, "Error creating font " + request.getQuery());
309                    callback.onTypefaceRequestFailed(
310                            FontRequestCallback.FAIL_REASON_FONT_LOAD_ERROR);
311                    return;
312                }
313            } catch (IOException e) {
314                Log.e(TAG, "Error reading font " + request.getQuery(), e);
315                callback.onTypefaceRequestFailed(
316                        FontRequestCallback.FAIL_REASON_FONT_LOAD_ERROR);
317                return;
318            } finally {
319                IoUtils.closeQuietly(fd);
320            }
321        }
322        fontFamily.freeze();
323        Typeface typeface = Typeface.createFromFamiliesWithDefault(new FontFamily[] { fontFamily });
324        synchronized (sDynamicTypefaceCache) {
325            String key = createProviderUid(request.getProviderAuthority(), request.getQuery());
326            sDynamicTypefaceCache.put(key, typeface);
327        }
328        callback.onTypefaceRetrieved(typeface);
329    }
330
331    /**
332     * Interface used to receive asynchronously fetched typefaces.
333     */
334    public interface FontRequestCallback {
335        /**
336         * Constant returned by {@link #onTypefaceRequestFailed(int)} signaling that the given
337         * provider was not found on the device.
338         */
339        int FAIL_REASON_PROVIDER_NOT_FOUND = 0;
340        /**
341         * Constant returned by {@link #onTypefaceRequestFailed(int)} signaling that the font
342         * returned by the provider was not loaded properly.
343         */
344        int FAIL_REASON_FONT_LOAD_ERROR = 1;
345        /**
346         * Constant returned by {@link #onTypefaceRequestFailed(int)} signaling that the given
347         * provider did not return any results for the given query.
348         */
349        int FAIL_REASON_FONT_NOT_FOUND = 2;
350
351        /** @hide */
352        @IntDef({FAIL_REASON_PROVIDER_NOT_FOUND, FAIL_REASON_FONT_LOAD_ERROR,
353                FAIL_REASON_FONT_NOT_FOUND})
354        @Retention(RetentionPolicy.SOURCE)
355        @interface FontRequestFailReason {}
356
357        /**
358         * Called then a Typeface request done via {@link Typeface#create(FontRequest,
359         * FontRequestCallback)} is complete. Note that this method will not be called if
360         * {@link #onTypefaceRequestFailed(int)} is called instead.
361         * @param typeface  The Typeface object retrieved.
362         */
363        void onTypefaceRetrieved(Typeface typeface);
364
365        /**
366         * Called when a Typeface request done via {@link Typeface#create(FontRequest,
367         * FontRequestCallback)} fails.
368         * @param reason One of {@link #FAIL_REASON_PROVIDER_NOT_FOUND},
369         *               {@link #FAIL_REASON_FONT_NOT_FOUND} or
370         *               {@link #FAIL_REASON_FONT_LOAD_ERROR}.
371         */
372        void onTypefaceRequestFailed(@FontRequestFailReason int reason);
373    }
374
375    /**
376     * Create a typeface object given a family name, and option style information.
377     * If null is passed for the name, then the "default" font will be chosen.
378     * The resulting typeface object can be queried (getStyle()) to discover what
379     * its "real" style characteristics are.
380     *
381     * @param familyName May be null. The name of the font family.
382     * @param style  The style (normal, bold, italic) of the typeface.
383     *               e.g. NORMAL, BOLD, ITALIC, BOLD_ITALIC
384     * @return The best matching typeface.
385     */
386    public static Typeface create(String familyName, int style) {
387        if (sSystemFontMap != null) {
388            return create(sSystemFontMap.get(familyName), style);
389        }
390        return null;
391    }
392
393    /**
394     * Create a typeface object that best matches the specified existing
395     * typeface and the specified Style. Use this call if you want to pick a new
396     * style from the same family of an existing typeface object. If family is
397     * null, this selects from the default font's family.
398     *
399     * @param family May be null. The name of the existing type face.
400     * @param style  The style (normal, bold, italic) of the typeface.
401     *               e.g. NORMAL, BOLD, ITALIC, BOLD_ITALIC
402     * @return The best matching typeface.
403     */
404    public static Typeface create(Typeface family, int style) {
405        if (style < 0 || style > 3) {
406            style = 0;
407        }
408        long ni = 0;
409        if (family != null) {
410            // Return early if we're asked for the same face/style
411            if (family.mStyle == style) {
412                return family;
413            }
414
415            ni = family.native_instance;
416        }
417
418        Typeface typeface;
419        SparseArray<Typeface> styles = sTypefaceCache.get(ni);
420
421        if (styles != null) {
422            typeface = styles.get(style);
423            if (typeface != null) {
424                return typeface;
425            }
426        }
427
428        typeface = new Typeface(nativeCreateFromTypeface(ni, style));
429        if (styles == null) {
430            styles = new SparseArray<Typeface>(4);
431            sTypefaceCache.put(ni, styles);
432        }
433        styles.put(style, typeface);
434
435        return typeface;
436    }
437
438    /** @hide */
439    public static Typeface createFromTypefaceWithVariation(Typeface family,
440            String fontVariationSettings) {
441        final long ni = family == null ? 0 : family.native_instance;
442        ArrayList<FontConfig.Axis> axes =
443                FontListParser.parseFontVariationSettings(fontVariationSettings);
444        return new Typeface(nativeCreateFromTypefaceWithVariation(ni, axes));
445    }
446
447    /**
448     * Returns one of the default typeface objects, based on the specified style
449     *
450     * @return the default typeface that corresponds to the style
451     */
452    public static Typeface defaultFromStyle(int style) {
453        return sDefaults[style];
454    }
455
456    /**
457     * Create a new typeface from the specified font data.
458     *
459     * @param mgr  The application's asset manager
460     * @param path The file name of the font data in the assets directory
461     * @return The new typeface.
462     */
463    public static Typeface createFromAsset(AssetManager mgr, String path) {
464        if (sFallbackFonts != null) {
465            synchronized (sDynamicTypefaceCache) {
466                final String key = createAssetUid(mgr, path);
467                Typeface typeface = sDynamicTypefaceCache.get(key);
468                if (typeface != null) return typeface;
469
470                FontFamily fontFamily = new FontFamily();
471                if (fontFamily.addFontFromAssetManager(mgr, path, 0, true /* isAsset */)) {
472                    fontFamily.freeze();
473                    FontFamily[] families = { fontFamily };
474                    typeface = createFromFamiliesWithDefault(families);
475                    sDynamicTypefaceCache.put(key, typeface);
476                    return typeface;
477                } else {
478                    fontFamily.abortCreation();
479                }
480            }
481        }
482        throw new RuntimeException("Font asset not found " + path);
483    }
484
485    /**
486     * Creates a unique id for a given AssetManager and asset path.
487     *
488     * @param mgr  AssetManager instance
489     * @param path The path for the asset.
490     * @return Unique id for a given AssetManager and asset path.
491     */
492    private static String createAssetUid(final AssetManager mgr, String path) {
493        final SparseArray<String> pkgs = mgr.getAssignedPackageIdentifiers();
494        final StringBuilder builder = new StringBuilder();
495        builder.append("asset:");
496        final int size = pkgs.size();
497        for (int i = 0; i < size; i++) {
498            builder.append(pkgs.valueAt(i));
499            builder.append("-");
500        }
501        builder.append(path);
502        return builder.toString();
503    }
504
505    /**
506     * Creates a unique id for a given font provider and query.
507     */
508    private static String createProviderUid(String authority, String query) {
509        final StringBuilder builder = new StringBuilder();
510        builder.append("provider:");
511        builder.append(authority);
512        builder.append("-");
513        builder.append(query);
514        return builder.toString();
515    }
516
517    /**
518     * Create a new typeface from the specified font file.
519     *
520     * @param path The path to the font data.
521     * @return The new typeface.
522     */
523    public static Typeface createFromFile(File path) {
524        return createFromFile(path.getAbsolutePath());
525    }
526
527    /**
528     * Create a new typeface from the specified font file.
529     *
530     * @param path The full path to the font data.
531     * @return The new typeface.
532     */
533    public static Typeface createFromFile(String path) {
534        if (sFallbackFonts != null) {
535            FontFamily fontFamily = new FontFamily();
536            if (fontFamily.addFont(path, 0 /* ttcIndex */)) {
537                fontFamily.freeze();
538                FontFamily[] families = { fontFamily };
539                return createFromFamiliesWithDefault(families);
540            } else {
541                fontFamily.abortCreation();
542            }
543        }
544        throw new RuntimeException("Font not found " + path);
545    }
546
547    /**
548     * Create a new typeface from an array of font families.
549     *
550     * @param families array of font families
551     * @hide
552     */
553    public static Typeface createFromFamilies(FontFamily[] families) {
554        long[] ptrArray = new long[families.length];
555        for (int i = 0; i < families.length; i++) {
556            ptrArray[i] = families[i].mNativePtr;
557        }
558        return new Typeface(nativeCreateFromArray(ptrArray));
559    }
560
561    /**
562     * Create a new typeface from an array of font families, including
563     * also the font families in the fallback list.
564     *
565     * @param families array of font families
566     * @hide
567     */
568    public static Typeface createFromFamiliesWithDefault(FontFamily[] families) {
569        long[] ptrArray = new long[families.length + sFallbackFonts.length];
570        for (int i = 0; i < families.length; i++) {
571            ptrArray[i] = families[i].mNativePtr;
572        }
573        for (int i = 0; i < sFallbackFonts.length; i++) {
574            ptrArray[i + families.length] = sFallbackFonts[i].mNativePtr;
575        }
576        return new Typeface(nativeCreateFromArray(ptrArray));
577    }
578
579    // don't allow clients to call this directly
580    private Typeface(long ni) {
581        if (ni == 0) {
582            throw new RuntimeException("native typeface cannot be made");
583        }
584
585        native_instance = ni;
586        mStyle = nativeGetStyle(ni);
587    }
588
589    private static FontFamily makeFamilyFromParsed(FontConfig.Family family,
590            Map<String, ByteBuffer> bufferForPath) {
591        FontFamily fontFamily = new FontFamily(family.getLanguage(), family.getVariant());
592        for (FontConfig.Font font : family.getFonts()) {
593            ByteBuffer fontBuffer = bufferForPath.get(font.getFontName());
594            if (fontBuffer == null) {
595                try (FileInputStream file = new FileInputStream(font.getFontName())) {
596                    FileChannel fileChannel = file.getChannel();
597                    long fontSize = fileChannel.size();
598                    fontBuffer = fileChannel.map(FileChannel.MapMode.READ_ONLY, 0, fontSize);
599                    bufferForPath.put(font.getFontName(), fontBuffer);
600                } catch (IOException e) {
601                    Log.e(TAG, "Error mapping font file " + font.getFontName());
602                    continue;
603                }
604            }
605            if (!fontFamily.addFontWeightStyle(fontBuffer, font.getTtcIndex(), font.getAxes(),
606                    font.getWeight(), font.isItalic())) {
607                Log.e(TAG, "Error creating font " + font.getFontName() + "#" + font.getTtcIndex());
608            }
609        }
610        fontFamily.freeze();
611        return fontFamily;
612    }
613
614    /*
615     * (non-Javadoc)
616     *
617     * This should only be called once, from the static class initializer block.
618     */
619    private static void init() {
620        // Load font config and initialize Minikin state
621        File systemFontConfigLocation = getSystemFontConfigLocation();
622        File configFilename = new File(systemFontConfigLocation, FONTS_CONFIG);
623        try {
624            FileInputStream fontsIn = new FileInputStream(configFilename);
625            FontConfig fontConfig = FontListParser.parse(fontsIn);
626
627            Map<String, ByteBuffer> bufferForPath = new HashMap<String, ByteBuffer>();
628
629            List<FontFamily> familyList = new ArrayList<FontFamily>();
630            // Note that the default typeface is always present in the fallback list;
631            // this is an enhancement from pre-Minikin behavior.
632            for (int i = 0; i < fontConfig.getFamilies().size(); i++) {
633                FontConfig.Family f = fontConfig.getFamilies().get(i);
634                if (i == 0 || f.getName() == null) {
635                    familyList.add(makeFamilyFromParsed(f, bufferForPath));
636                }
637            }
638            sFallbackFonts = familyList.toArray(new FontFamily[familyList.size()]);
639            setDefault(Typeface.createFromFamilies(sFallbackFonts));
640
641            Map<String, Typeface> systemFonts = new HashMap<String, Typeface>();
642            for (int i = 0; i < fontConfig.getFamilies().size(); i++) {
643                Typeface typeface;
644                FontConfig.Family f = fontConfig.getFamilies().get(i);
645                if (f.getName() != null) {
646                    if (i == 0) {
647                        // The first entry is the default typeface; no sense in
648                        // duplicating the corresponding FontFamily.
649                        typeface = sDefaultTypeface;
650                    } else {
651                        FontFamily fontFamily = makeFamilyFromParsed(f, bufferForPath);
652                        FontFamily[] families = { fontFamily };
653                        typeface = Typeface.createFromFamiliesWithDefault(families);
654                    }
655                    systemFonts.put(f.getName(), typeface);
656                }
657            }
658            for (FontConfig.Alias alias : fontConfig.getAliases()) {
659                Typeface base = systemFonts.get(alias.getToName());
660                Typeface newFace = base;
661                int weight = alias.getWeight();
662                if (weight != 400) {
663                    newFace = new Typeface(nativeCreateWeightAlias(base.native_instance, weight));
664                }
665                systemFonts.put(alias.getName(), newFace);
666            }
667            sSystemFontMap = systemFonts;
668
669        } catch (RuntimeException e) {
670            Log.w(TAG, "Didn't create default family (most likely, non-Minikin build)", e);
671            // TODO: normal in non-Minikin case, remove or make error when Minikin-only
672        } catch (FileNotFoundException e) {
673            Log.e(TAG, "Error opening " + configFilename, e);
674        } catch (IOException e) {
675            Log.e(TAG, "Error reading " + configFilename, e);
676        } catch (XmlPullParserException e) {
677            Log.e(TAG, "XML parse exception for " + configFilename, e);
678        }
679    }
680
681    static {
682        init();
683        // Set up defaults and typefaces exposed in public API
684        DEFAULT         = create((String) null, 0);
685        DEFAULT_BOLD    = create((String) null, Typeface.BOLD);
686        SANS_SERIF      = create("sans-serif", 0);
687        SERIF           = create("serif", 0);
688        MONOSPACE       = create("monospace", 0);
689
690        sDefaults = new Typeface[] {
691            DEFAULT,
692            DEFAULT_BOLD,
693            create((String) null, Typeface.ITALIC),
694            create((String) null, Typeface.BOLD_ITALIC),
695        };
696
697    }
698
699    private static File getSystemFontConfigLocation() {
700        return new File("/system/etc/");
701    }
702
703    @Override
704    protected void finalize() throws Throwable {
705        try {
706            nativeUnref(native_instance);
707            native_instance = 0;  // Other finalizers can still call us.
708        } finally {
709            super.finalize();
710        }
711    }
712
713    @Override
714    public boolean equals(Object o) {
715        if (this == o) return true;
716        if (o == null || getClass() != o.getClass()) return false;
717
718        Typeface typeface = (Typeface) o;
719
720        return mStyle == typeface.mStyle && native_instance == typeface.native_instance;
721    }
722
723    @Override
724    public int hashCode() {
725        /*
726         * Modified method for hashCode with long native_instance derived from
727         * http://developer.android.com/reference/java/lang/Object.html
728         */
729        int result = 17;
730        result = 31 * result + (int) (native_instance ^ (native_instance >>> 32));
731        result = 31 * result + mStyle;
732        return result;
733    }
734
735    private static native long nativeCreateFromTypeface(long native_instance, int style);
736    private static native long nativeCreateFromTypefaceWithVariation(
737            long native_instance, List<FontConfig.Axis> axes);
738    private static native long nativeCreateWeightAlias(long native_instance, int weight);
739    private static native void nativeUnref(long native_instance);
740    private static native int  nativeGetStyle(long native_instance);
741    private static native long nativeCreateFromArray(long[] familyArray);
742    private static native void nativeSetDefault(long native_instance);
743}
744