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