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