1484205f290d5cc989074248f876f72d10000eba1Siyamed Sinir/*
2ac5fe7c617c66850fff75a9fce9979c6e5674b0fAurimas Liutikas * Copyright 2018 The Android Open Source Project
3484205f290d5cc989074248f876f72d10000eba1Siyamed Sinir *
4484205f290d5cc989074248f876f72d10000eba1Siyamed Sinir * Licensed under the Apache License, Version 2.0 (the "License");
5484205f290d5cc989074248f876f72d10000eba1Siyamed Sinir * you may not use this file except in compliance with the License.
6484205f290d5cc989074248f876f72d10000eba1Siyamed Sinir * You may obtain a copy of the License at
7484205f290d5cc989074248f876f72d10000eba1Siyamed Sinir *
8484205f290d5cc989074248f876f72d10000eba1Siyamed Sinir *      http://www.apache.org/licenses/LICENSE-2.0
9484205f290d5cc989074248f876f72d10000eba1Siyamed Sinir *
10484205f290d5cc989074248f876f72d10000eba1Siyamed Sinir * Unless required by applicable law or agreed to in writing, software
11484205f290d5cc989074248f876f72d10000eba1Siyamed Sinir * distributed under the License is distributed on an "AS IS" BASIS,
12484205f290d5cc989074248f876f72d10000eba1Siyamed Sinir * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13484205f290d5cc989074248f876f72d10000eba1Siyamed Sinir * See the License for the specific language governing permissions and
14484205f290d5cc989074248f876f72d10000eba1Siyamed Sinir * limitations under the License.
15484205f290d5cc989074248f876f72d10000eba1Siyamed Sinir */
16484205f290d5cc989074248f876f72d10000eba1Siyamed Sinir
17ac5fe7c617c66850fff75a9fce9979c6e5674b0fAurimas Liutikaspackage androidx.emoji.text;
18484205f290d5cc989074248f876f72d10000eba1Siyamed Sinir
19484205f290d5cc989074248f876f72d10000eba1Siyamed Sinirimport android.content.Context;
208346a9a10895008f08f50969689a1ce16fbd577cSeigo Nonakaimport android.content.pm.PackageManager.NameNotFoundException;
216436cc6a00500b2b50b1e46d0c12379c15fd4f04Seigo Nonakaimport android.database.ContentObserver;
22484205f290d5cc989074248f876f72d10000eba1Siyamed Sinirimport android.graphics.Typeface;
236436cc6a00500b2b50b1e46d0c12379c15fd4f04Seigo Nonakaimport android.net.Uri;
246436cc6a00500b2b50b1e46d0c12379c15fd4f04Seigo Nonakaimport android.os.Handler;
256436cc6a00500b2b50b1e46d0c12379c15fd4f04Seigo Nonakaimport android.os.HandlerThread;
266436cc6a00500b2b50b1e46d0c12379c15fd4f04Seigo Nonakaimport android.os.Process;
276436cc6a00500b2b50b1e46d0c12379c15fd4f04Seigo Nonakaimport android.os.SystemClock;
2838746a682208c764867ffe4415d0b62fb22b5b9aAurimas Liutikas
29ac5fe7c617c66850fff75a9fce9979c6e5674b0fAurimas Liutikasimport androidx.annotation.GuardedBy;
30ac5fe7c617c66850fff75a9fce9979c6e5674b0fAurimas Liutikasimport androidx.annotation.NonNull;
31ac5fe7c617c66850fff75a9fce9979c6e5674b0fAurimas Liutikasimport androidx.annotation.Nullable;
32ac5fe7c617c66850fff75a9fce9979c6e5674b0fAurimas Liutikasimport androidx.annotation.RequiresApi;
33ac5fe7c617c66850fff75a9fce9979c6e5674b0fAurimas Liutikasimport androidx.annotation.RestrictTo;
34ac5fe7c617c66850fff75a9fce9979c6e5674b0fAurimas Liutikasimport androidx.core.graphics.TypefaceCompatUtil;
35ac5fe7c617c66850fff75a9fce9979c6e5674b0fAurimas Liutikasimport androidx.core.provider.FontRequest;
36ac5fe7c617c66850fff75a9fce9979c6e5674b0fAurimas Liutikasimport androidx.core.provider.FontsContractCompat;
37ac5fe7c617c66850fff75a9fce9979c6e5674b0fAurimas Liutikasimport androidx.core.provider.FontsContractCompat.FontFamilyResult;
38ac5fe7c617c66850fff75a9fce9979c6e5674b0fAurimas Liutikasimport androidx.core.util.Preconditions;
39484205f290d5cc989074248f876f72d10000eba1Siyamed Sinir
40484205f290d5cc989074248f876f72d10000eba1Siyamed Sinirimport java.nio.ByteBuffer;
41484205f290d5cc989074248f876f72d10000eba1Siyamed Sinir
42484205f290d5cc989074248f876f72d10000eba1Siyamed Sinir/**
43484205f290d5cc989074248f876f72d10000eba1Siyamed Sinir * {@link EmojiCompat.Config} implementation that asynchronously fetches the required font and the
44484205f290d5cc989074248f876f72d10000eba1Siyamed Sinir * metadata using a {@link FontRequest}. FontRequest should be constructed to fetch an EmojiCompat
45484205f290d5cc989074248f876f72d10000eba1Siyamed Sinir * compatible emoji font.
46484205f290d5cc989074248f876f72d10000eba1Siyamed Sinir * <p/>
47484205f290d5cc989074248f876f72d10000eba1Siyamed Sinir */
48484205f290d5cc989074248f876f72d10000eba1Siyamed Sinirpublic class FontRequestEmojiCompatConfig extends EmojiCompat.Config {
496436cc6a00500b2b50b1e46d0c12379c15fd4f04Seigo Nonaka
506436cc6a00500b2b50b1e46d0c12379c15fd4f04Seigo Nonaka    /**
516436cc6a00500b2b50b1e46d0c12379c15fd4f04Seigo Nonaka     * Retry policy used when the font provider is not ready to give the font file.
526436cc6a00500b2b50b1e46d0c12379c15fd4f04Seigo Nonaka     *
536436cc6a00500b2b50b1e46d0c12379c15fd4f04Seigo Nonaka     * To control the thread the retries are handled on, see
546436cc6a00500b2b50b1e46d0c12379c15fd4f04Seigo Nonaka     * {@link FontRequestEmojiCompatConfig#setHandler}.
556436cc6a00500b2b50b1e46d0c12379c15fd4f04Seigo Nonaka     */
566436cc6a00500b2b50b1e46d0c12379c15fd4f04Seigo Nonaka    public abstract static class RetryPolicy {
576436cc6a00500b2b50b1e46d0c12379c15fd4f04Seigo Nonaka        /**
586436cc6a00500b2b50b1e46d0c12379c15fd4f04Seigo Nonaka         * Called each time the metadata loading fails.
596436cc6a00500b2b50b1e46d0c12379c15fd4f04Seigo Nonaka         *
606436cc6a00500b2b50b1e46d0c12379c15fd4f04Seigo Nonaka         * This is primarily due to a pending download of the font.
616436cc6a00500b2b50b1e46d0c12379c15fd4f04Seigo Nonaka         * If a value larger than zero is returned, metadata loader will retry after the given
626436cc6a00500b2b50b1e46d0c12379c15fd4f04Seigo Nonaka         * milliseconds.
636436cc6a00500b2b50b1e46d0c12379c15fd4f04Seigo Nonaka         * <br />
646436cc6a00500b2b50b1e46d0c12379c15fd4f04Seigo Nonaka         * If {@code zero} is returned, metadata loader will retry immediately.
656436cc6a00500b2b50b1e46d0c12379c15fd4f04Seigo Nonaka         * <br/>
666436cc6a00500b2b50b1e46d0c12379c15fd4f04Seigo Nonaka         * If a value less than 0 is returned, the metadata loader will stop retrying and
676436cc6a00500b2b50b1e46d0c12379c15fd4f04Seigo Nonaka         * EmojiCompat will get into {@link EmojiCompat#LOAD_STATE_FAILED} state.
686436cc6a00500b2b50b1e46d0c12379c15fd4f04Seigo Nonaka         * <p/>
696436cc6a00500b2b50b1e46d0c12379c15fd4f04Seigo Nonaka         * Note that the retry may happen earlier than you specified if the font provider notifies
706436cc6a00500b2b50b1e46d0c12379c15fd4f04Seigo Nonaka         * that the download is completed.
716436cc6a00500b2b50b1e46d0c12379c15fd4f04Seigo Nonaka         *
726436cc6a00500b2b50b1e46d0c12379c15fd4f04Seigo Nonaka         * @return long milliseconds to wait until next retry
736436cc6a00500b2b50b1e46d0c12379c15fd4f04Seigo Nonaka         */
746436cc6a00500b2b50b1e46d0c12379c15fd4f04Seigo Nonaka        public abstract long getRetryDelay();
756436cc6a00500b2b50b1e46d0c12379c15fd4f04Seigo Nonaka    }
766436cc6a00500b2b50b1e46d0c12379c15fd4f04Seigo Nonaka
776436cc6a00500b2b50b1e46d0c12379c15fd4f04Seigo Nonaka    /**
786436cc6a00500b2b50b1e46d0c12379c15fd4f04Seigo Nonaka     * A retry policy implementation that doubles the amount of time in between retries.
796436cc6a00500b2b50b1e46d0c12379c15fd4f04Seigo Nonaka     *
806436cc6a00500b2b50b1e46d0c12379c15fd4f04Seigo Nonaka     * If downloading hasn't finish within given amount of time, this policy give up and the
816436cc6a00500b2b50b1e46d0c12379c15fd4f04Seigo Nonaka     * EmojiCompat will get into {@link EmojiCompat#LOAD_STATE_FAILED} state.
826436cc6a00500b2b50b1e46d0c12379c15fd4f04Seigo Nonaka     */
836436cc6a00500b2b50b1e46d0c12379c15fd4f04Seigo Nonaka    public static class ExponentialBackoffRetryPolicy extends RetryPolicy {
846436cc6a00500b2b50b1e46d0c12379c15fd4f04Seigo Nonaka        private final long mTotalMs;
856436cc6a00500b2b50b1e46d0c12379c15fd4f04Seigo Nonaka        private long mRetryOrigin;
866436cc6a00500b2b50b1e46d0c12379c15fd4f04Seigo Nonaka
876436cc6a00500b2b50b1e46d0c12379c15fd4f04Seigo Nonaka        /**
886436cc6a00500b2b50b1e46d0c12379c15fd4f04Seigo Nonaka         * @param totalMs A total amount of time to wait in milliseconds.
896436cc6a00500b2b50b1e46d0c12379c15fd4f04Seigo Nonaka         */
906436cc6a00500b2b50b1e46d0c12379c15fd4f04Seigo Nonaka        public ExponentialBackoffRetryPolicy(long totalMs) {
916436cc6a00500b2b50b1e46d0c12379c15fd4f04Seigo Nonaka            mTotalMs = totalMs;
926436cc6a00500b2b50b1e46d0c12379c15fd4f04Seigo Nonaka        }
936436cc6a00500b2b50b1e46d0c12379c15fd4f04Seigo Nonaka
946436cc6a00500b2b50b1e46d0c12379c15fd4f04Seigo Nonaka        @Override
956436cc6a00500b2b50b1e46d0c12379c15fd4f04Seigo Nonaka        public long getRetryDelay() {
966436cc6a00500b2b50b1e46d0c12379c15fd4f04Seigo Nonaka            if (mRetryOrigin == 0) {
976436cc6a00500b2b50b1e46d0c12379c15fd4f04Seigo Nonaka                mRetryOrigin = SystemClock.uptimeMillis();
986436cc6a00500b2b50b1e46d0c12379c15fd4f04Seigo Nonaka                // Since download may be completed after getting query result and before registering
996436cc6a00500b2b50b1e46d0c12379c15fd4f04Seigo Nonaka                // observer, requesting later at the same time.
1006436cc6a00500b2b50b1e46d0c12379c15fd4f04Seigo Nonaka                return 0;
1016436cc6a00500b2b50b1e46d0c12379c15fd4f04Seigo Nonaka            } else {
1026436cc6a00500b2b50b1e46d0c12379c15fd4f04Seigo Nonaka                // Retry periodically since we can't trust notify change event. Some font provider
1036436cc6a00500b2b50b1e46d0c12379c15fd4f04Seigo Nonaka                // may not notify us.
1046436cc6a00500b2b50b1e46d0c12379c15fd4f04Seigo Nonaka                final long elapsedMillis = SystemClock.uptimeMillis() - mRetryOrigin;
1056436cc6a00500b2b50b1e46d0c12379c15fd4f04Seigo Nonaka                if (elapsedMillis > mTotalMs) {
1066436cc6a00500b2b50b1e46d0c12379c15fd4f04Seigo Nonaka                    return -1;  // Give up since download hasn't finished in 10 min.
1076436cc6a00500b2b50b1e46d0c12379c15fd4f04Seigo Nonaka                }
1086436cc6a00500b2b50b1e46d0c12379c15fd4f04Seigo Nonaka                // Wait until the same amount of the time from the first scheduled time, but adjust
1096436cc6a00500b2b50b1e46d0c12379c15fd4f04Seigo Nonaka                // the minimum request interval is 1 sec and never exceeds 10 min in total.
1106436cc6a00500b2b50b1e46d0c12379c15fd4f04Seigo Nonaka                return Math.min(Math.max(elapsedMillis, 1000), mTotalMs - elapsedMillis);
1116436cc6a00500b2b50b1e46d0c12379c15fd4f04Seigo Nonaka            }
1126436cc6a00500b2b50b1e46d0c12379c15fd4f04Seigo Nonaka        }
1136436cc6a00500b2b50b1e46d0c12379c15fd4f04Seigo Nonaka    };
1146436cc6a00500b2b50b1e46d0c12379c15fd4f04Seigo Nonaka
115484205f290d5cc989074248f876f72d10000eba1Siyamed Sinir    /**
116484205f290d5cc989074248f876f72d10000eba1Siyamed Sinir     * @param context Context instance, cannot be {@code null}
117484205f290d5cc989074248f876f72d10000eba1Siyamed Sinir     * @param request {@link FontRequest} to fetch the font asynchronously, cannot be {@code null}
118484205f290d5cc989074248f876f72d10000eba1Siyamed Sinir     */
119484205f290d5cc989074248f876f72d10000eba1Siyamed Sinir    public FontRequestEmojiCompatConfig(@NonNull Context context, @NonNull FontRequest request) {
1208346a9a10895008f08f50969689a1ce16fbd577cSeigo Nonaka        super(new FontRequestMetadataLoader(context, request, DEFAULT_FONTS_CONTRACT));
121484205f290d5cc989074248f876f72d10000eba1Siyamed Sinir    }
122484205f290d5cc989074248f876f72d10000eba1Siyamed Sinir
123484205f290d5cc989074248f876f72d10000eba1Siyamed Sinir    /**
124484205f290d5cc989074248f876f72d10000eba1Siyamed Sinir     * @hide
125484205f290d5cc989074248f876f72d10000eba1Siyamed Sinir     */
126484205f290d5cc989074248f876f72d10000eba1Siyamed Sinir    @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
127484205f290d5cc989074248f876f72d10000eba1Siyamed Sinir    public FontRequestEmojiCompatConfig(@NonNull Context context, @NonNull FontRequest request,
1286436cc6a00500b2b50b1e46d0c12379c15fd4f04Seigo Nonaka            @NonNull FontProviderHelper fontProviderHelper) {
1296436cc6a00500b2b50b1e46d0c12379c15fd4f04Seigo Nonaka        super(new FontRequestMetadataLoader(context, request, fontProviderHelper));
1306436cc6a00500b2b50b1e46d0c12379c15fd4f04Seigo Nonaka    }
1316436cc6a00500b2b50b1e46d0c12379c15fd4f04Seigo Nonaka
1326436cc6a00500b2b50b1e46d0c12379c15fd4f04Seigo Nonaka    /**
1336436cc6a00500b2b50b1e46d0c12379c15fd4f04Seigo Nonaka     * Sets the custom handler to be used for initialization.
1346436cc6a00500b2b50b1e46d0c12379c15fd4f04Seigo Nonaka     *
1356436cc6a00500b2b50b1e46d0c12379c15fd4f04Seigo Nonaka     * Since font fetch take longer time, the metadata loader will fetch the fonts on the background
1366436cc6a00500b2b50b1e46d0c12379c15fd4f04Seigo Nonaka     * thread. You can pass your own handler for this background fetching. This handler is also used
1376436cc6a00500b2b50b1e46d0c12379c15fd4f04Seigo Nonaka     * for retrying.
1386436cc6a00500b2b50b1e46d0c12379c15fd4f04Seigo Nonaka     *
1396436cc6a00500b2b50b1e46d0c12379c15fd4f04Seigo Nonaka     * @param handler A {@link Handler} to be used for initialization. Can be {@code null}. In case
1406436cc6a00500b2b50b1e46d0c12379c15fd4f04Seigo Nonaka     *               of {@code null}, the metadata loader creates own {@link HandlerThread} for
1416436cc6a00500b2b50b1e46d0c12379c15fd4f04Seigo Nonaka     *               initialization.
1426436cc6a00500b2b50b1e46d0c12379c15fd4f04Seigo Nonaka     */
1436436cc6a00500b2b50b1e46d0c12379c15fd4f04Seigo Nonaka    public FontRequestEmojiCompatConfig setHandler(Handler handler) {
1446436cc6a00500b2b50b1e46d0c12379c15fd4f04Seigo Nonaka        ((FontRequestMetadataLoader) getMetadataRepoLoader()).setHandler(handler);
1456436cc6a00500b2b50b1e46d0c12379c15fd4f04Seigo Nonaka        return this;
146484205f290d5cc989074248f876f72d10000eba1Siyamed Sinir    }
147484205f290d5cc989074248f876f72d10000eba1Siyamed Sinir
1486436cc6a00500b2b50b1e46d0c12379c15fd4f04Seigo Nonaka    /**
1496436cc6a00500b2b50b1e46d0c12379c15fd4f04Seigo Nonaka     * Sets the retry policy.
1506436cc6a00500b2b50b1e46d0c12379c15fd4f04Seigo Nonaka     *
1516436cc6a00500b2b50b1e46d0c12379c15fd4f04Seigo Nonaka     * {@see RetryPolicy}
1526436cc6a00500b2b50b1e46d0c12379c15fd4f04Seigo Nonaka     * @param policy The policy to be used when the font provider is not ready to give the font
1536436cc6a00500b2b50b1e46d0c12379c15fd4f04Seigo Nonaka     *              file. Can be {@code null}. In case of {@code null}, the metadata loader never
1546436cc6a00500b2b50b1e46d0c12379c15fd4f04Seigo Nonaka     *              retries.
1556436cc6a00500b2b50b1e46d0c12379c15fd4f04Seigo Nonaka     */
1566436cc6a00500b2b50b1e46d0c12379c15fd4f04Seigo Nonaka    public FontRequestEmojiCompatConfig setRetryPolicy(RetryPolicy policy) {
1576436cc6a00500b2b50b1e46d0c12379c15fd4f04Seigo Nonaka        ((FontRequestMetadataLoader) getMetadataRepoLoader()).setRetryPolicy(policy);
1586436cc6a00500b2b50b1e46d0c12379c15fd4f04Seigo Nonaka        return this;
1596436cc6a00500b2b50b1e46d0c12379c15fd4f04Seigo Nonaka    }
160484205f290d5cc989074248f876f72d10000eba1Siyamed Sinir
161484205f290d5cc989074248f876f72d10000eba1Siyamed Sinir    /**
162c685244d1fa0ac20b32f3b62a301b7d7fbd5a832Siyamed Sinir     * MetadataRepoLoader implementation that uses FontsContractCompat and TypefaceCompat to load a
1630b03693667d95d2202dfbb24866665ff061acce1Seigo Nonaka     * given FontRequest.
164484205f290d5cc989074248f876f72d10000eba1Siyamed Sinir     */
165c685244d1fa0ac20b32f3b62a301b7d7fbd5a832Siyamed Sinir    private static class FontRequestMetadataLoader implements EmojiCompat.MetadataRepoLoader {
166484205f290d5cc989074248f876f72d10000eba1Siyamed Sinir        private final Context mContext;
167484205f290d5cc989074248f876f72d10000eba1Siyamed Sinir        private final FontRequest mRequest;
1686436cc6a00500b2b50b1e46d0c12379c15fd4f04Seigo Nonaka        private final FontProviderHelper mFontProviderHelper;
1696436cc6a00500b2b50b1e46d0c12379c15fd4f04Seigo Nonaka
1706436cc6a00500b2b50b1e46d0c12379c15fd4f04Seigo Nonaka        private final Object mLock = new Object();
1716436cc6a00500b2b50b1e46d0c12379c15fd4f04Seigo Nonaka        @GuardedBy("mLock")
1726436cc6a00500b2b50b1e46d0c12379c15fd4f04Seigo Nonaka        private Handler mHandler;
1736436cc6a00500b2b50b1e46d0c12379c15fd4f04Seigo Nonaka        @GuardedBy("mLock")
1746436cc6a00500b2b50b1e46d0c12379c15fd4f04Seigo Nonaka        private HandlerThread mThread;
1756436cc6a00500b2b50b1e46d0c12379c15fd4f04Seigo Nonaka        @GuardedBy("mLock")
1766436cc6a00500b2b50b1e46d0c12379c15fd4f04Seigo Nonaka        private @Nullable RetryPolicy mRetryPolicy;
1776436cc6a00500b2b50b1e46d0c12379c15fd4f04Seigo Nonaka
1786436cc6a00500b2b50b1e46d0c12379c15fd4f04Seigo Nonaka        // Following three variables must be touched only on the thread associated with mHandler.
1796436cc6a00500b2b50b1e46d0c12379c15fd4f04Seigo Nonaka        private EmojiCompat.MetadataRepoLoaderCallback mCallback;
1806436cc6a00500b2b50b1e46d0c12379c15fd4f04Seigo Nonaka        private ContentObserver mObserver;
1816436cc6a00500b2b50b1e46d0c12379c15fd4f04Seigo Nonaka        private Runnable mHandleMetadataCreationRunner;
182484205f290d5cc989074248f876f72d10000eba1Siyamed Sinir
183484205f290d5cc989074248f876f72d10000eba1Siyamed Sinir        FontRequestMetadataLoader(@NonNull Context context, @NonNull FontRequest request,
1846436cc6a00500b2b50b1e46d0c12379c15fd4f04Seigo Nonaka                @NonNull FontProviderHelper fontProviderHelper) {
185484205f290d5cc989074248f876f72d10000eba1Siyamed Sinir            Preconditions.checkNotNull(context, "Context cannot be null");
186484205f290d5cc989074248f876f72d10000eba1Siyamed Sinir            Preconditions.checkNotNull(request, "FontRequest cannot be null");
187484205f290d5cc989074248f876f72d10000eba1Siyamed Sinir            mContext = context.getApplicationContext();
188484205f290d5cc989074248f876f72d10000eba1Siyamed Sinir            mRequest = request;
1896436cc6a00500b2b50b1e46d0c12379c15fd4f04Seigo Nonaka            mFontProviderHelper = fontProviderHelper;
1906436cc6a00500b2b50b1e46d0c12379c15fd4f04Seigo Nonaka        }
1916436cc6a00500b2b50b1e46d0c12379c15fd4f04Seigo Nonaka
1926436cc6a00500b2b50b1e46d0c12379c15fd4f04Seigo Nonaka        public void setHandler(Handler handler) {
1936436cc6a00500b2b50b1e46d0c12379c15fd4f04Seigo Nonaka            synchronized (mLock) {
1946436cc6a00500b2b50b1e46d0c12379c15fd4f04Seigo Nonaka                mHandler = handler;
1956436cc6a00500b2b50b1e46d0c12379c15fd4f04Seigo Nonaka            }
1966436cc6a00500b2b50b1e46d0c12379c15fd4f04Seigo Nonaka        }
1976436cc6a00500b2b50b1e46d0c12379c15fd4f04Seigo Nonaka
1986436cc6a00500b2b50b1e46d0c12379c15fd4f04Seigo Nonaka        public void setRetryPolicy(RetryPolicy policy) {
1996436cc6a00500b2b50b1e46d0c12379c15fd4f04Seigo Nonaka            synchronized (mLock) {
2006436cc6a00500b2b50b1e46d0c12379c15fd4f04Seigo Nonaka                mRetryPolicy = policy;
2016436cc6a00500b2b50b1e46d0c12379c15fd4f04Seigo Nonaka            }
202484205f290d5cc989074248f876f72d10000eba1Siyamed Sinir        }
203484205f290d5cc989074248f876f72d10000eba1Siyamed Sinir
204484205f290d5cc989074248f876f72d10000eba1Siyamed Sinir        @Override
20577b5c5b734f9f665577d1e3d178615db43ae1d4fSiyamed Sinir        @RequiresApi(19)
206c685244d1fa0ac20b32f3b62a301b7d7fbd5a832Siyamed Sinir        public void load(@NonNull final EmojiCompat.MetadataRepoLoaderCallback loaderCallback) {
207484205f290d5cc989074248f876f72d10000eba1Siyamed Sinir            Preconditions.checkNotNull(loaderCallback, "LoaderCallback cannot be null");
2086436cc6a00500b2b50b1e46d0c12379c15fd4f04Seigo Nonaka            synchronized (mLock) {
2096436cc6a00500b2b50b1e46d0c12379c15fd4f04Seigo Nonaka                if (mHandler == null) {
2106436cc6a00500b2b50b1e46d0c12379c15fd4f04Seigo Nonaka                    // Developer didn't give a thread for fetching. Create our own one.
2116436cc6a00500b2b50b1e46d0c12379c15fd4f04Seigo Nonaka                    mThread = new HandlerThread("emojiCompat", Process.THREAD_PRIORITY_BACKGROUND);
2126436cc6a00500b2b50b1e46d0c12379c15fd4f04Seigo Nonaka                    mThread.start();
2136436cc6a00500b2b50b1e46d0c12379c15fd4f04Seigo Nonaka                    mHandler = new Handler(mThread.getLooper());
2146436cc6a00500b2b50b1e46d0c12379c15fd4f04Seigo Nonaka                }
2156436cc6a00500b2b50b1e46d0c12379c15fd4f04Seigo Nonaka                mHandler.post(new Runnable() {
2166436cc6a00500b2b50b1e46d0c12379c15fd4f04Seigo Nonaka                    @Override
2176436cc6a00500b2b50b1e46d0c12379c15fd4f04Seigo Nonaka                    public void run() {
2186436cc6a00500b2b50b1e46d0c12379c15fd4f04Seigo Nonaka                        mCallback = loaderCallback;
2196436cc6a00500b2b50b1e46d0c12379c15fd4f04Seigo Nonaka                        createMetadata();
2206436cc6a00500b2b50b1e46d0c12379c15fd4f04Seigo Nonaka                    }
2216436cc6a00500b2b50b1e46d0c12379c15fd4f04Seigo Nonaka                });
2226436cc6a00500b2b50b1e46d0c12379c15fd4f04Seigo Nonaka            }
223484205f290d5cc989074248f876f72d10000eba1Siyamed Sinir        }
224484205f290d5cc989074248f876f72d10000eba1Siyamed Sinir
2256436cc6a00500b2b50b1e46d0c12379c15fd4f04Seigo Nonaka        private FontsContractCompat.FontInfo retrieveFontInfo() {
2266436cc6a00500b2b50b1e46d0c12379c15fd4f04Seigo Nonaka            final FontsContractCompat.FontFamilyResult result;
2276436cc6a00500b2b50b1e46d0c12379c15fd4f04Seigo Nonaka            try {
2286436cc6a00500b2b50b1e46d0c12379c15fd4f04Seigo Nonaka                result = mFontProviderHelper.fetchFonts(mContext, mRequest);
2296436cc6a00500b2b50b1e46d0c12379c15fd4f04Seigo Nonaka            } catch (NameNotFoundException e) {
2306436cc6a00500b2b50b1e46d0c12379c15fd4f04Seigo Nonaka                throw new RuntimeException("provider not found", e);
2316436cc6a00500b2b50b1e46d0c12379c15fd4f04Seigo Nonaka            }
2326436cc6a00500b2b50b1e46d0c12379c15fd4f04Seigo Nonaka            if (result.getStatusCode() != FontsContractCompat.FontFamilyResult.STATUS_OK) {
2336436cc6a00500b2b50b1e46d0c12379c15fd4f04Seigo Nonaka                throw new RuntimeException("fetchFonts failed (" + result.getStatusCode() + ")");
2346436cc6a00500b2b50b1e46d0c12379c15fd4f04Seigo Nonaka            }
2356436cc6a00500b2b50b1e46d0c12379c15fd4f04Seigo Nonaka            final FontsContractCompat.FontInfo[] fonts = result.getFonts();
2366436cc6a00500b2b50b1e46d0c12379c15fd4f04Seigo Nonaka            if (fonts == null || fonts.length == 0) {
2376436cc6a00500b2b50b1e46d0c12379c15fd4f04Seigo Nonaka                throw new RuntimeException("fetchFonts failed (empty result)");
2386436cc6a00500b2b50b1e46d0c12379c15fd4f04Seigo Nonaka            }
2396436cc6a00500b2b50b1e46d0c12379c15fd4f04Seigo Nonaka            return fonts[0];  // Assuming the GMS Core provides only one font file.
240484205f290d5cc989074248f876f72d10000eba1Siyamed Sinir        }
241484205f290d5cc989074248f876f72d10000eba1Siyamed Sinir
2426436cc6a00500b2b50b1e46d0c12379c15fd4f04Seigo Nonaka        // Must be called on the mHandler.
2436436cc6a00500b2b50b1e46d0c12379c15fd4f04Seigo Nonaka        @RequiresApi(19)
2446436cc6a00500b2b50b1e46d0c12379c15fd4f04Seigo Nonaka        private void scheduleRetry(Uri uri, long waitMs) {
2456436cc6a00500b2b50b1e46d0c12379c15fd4f04Seigo Nonaka            synchronized (mLock) {
2466436cc6a00500b2b50b1e46d0c12379c15fd4f04Seigo Nonaka                if (mObserver == null) {
2476436cc6a00500b2b50b1e46d0c12379c15fd4f04Seigo Nonaka                    mObserver = new ContentObserver(mHandler) {
2486436cc6a00500b2b50b1e46d0c12379c15fd4f04Seigo Nonaka                        @Override
2496436cc6a00500b2b50b1e46d0c12379c15fd4f04Seigo Nonaka                        public void onChange(boolean selfChange, Uri uri) {
2506436cc6a00500b2b50b1e46d0c12379c15fd4f04Seigo Nonaka                            createMetadata();
2516436cc6a00500b2b50b1e46d0c12379c15fd4f04Seigo Nonaka                        }
2526436cc6a00500b2b50b1e46d0c12379c15fd4f04Seigo Nonaka                    };
2536436cc6a00500b2b50b1e46d0c12379c15fd4f04Seigo Nonaka                    mFontProviderHelper.registerObserver(mContext, uri, mObserver);
2548346a9a10895008f08f50969689a1ce16fbd577cSeigo Nonaka                }
2556436cc6a00500b2b50b1e46d0c12379c15fd4f04Seigo Nonaka                if (mHandleMetadataCreationRunner == null) {
2566436cc6a00500b2b50b1e46d0c12379c15fd4f04Seigo Nonaka                    mHandleMetadataCreationRunner = new Runnable() {
2576436cc6a00500b2b50b1e46d0c12379c15fd4f04Seigo Nonaka                        @Override
2586436cc6a00500b2b50b1e46d0c12379c15fd4f04Seigo Nonaka                        public void run() {
2596436cc6a00500b2b50b1e46d0c12379c15fd4f04Seigo Nonaka                            createMetadata();
2606436cc6a00500b2b50b1e46d0c12379c15fd4f04Seigo Nonaka                        }
2616436cc6a00500b2b50b1e46d0c12379c15fd4f04Seigo Nonaka                    };
2628346a9a10895008f08f50969689a1ce16fbd577cSeigo Nonaka                }
2636436cc6a00500b2b50b1e46d0c12379c15fd4f04Seigo Nonaka                mHandler.postDelayed(mHandleMetadataCreationRunner, waitMs);
2646436cc6a00500b2b50b1e46d0c12379c15fd4f04Seigo Nonaka            }
2656436cc6a00500b2b50b1e46d0c12379c15fd4f04Seigo Nonaka        }
2666436cc6a00500b2b50b1e46d0c12379c15fd4f04Seigo Nonaka
2676436cc6a00500b2b50b1e46d0c12379c15fd4f04Seigo Nonaka        // Must be called on the mHandler.
2686436cc6a00500b2b50b1e46d0c12379c15fd4f04Seigo Nonaka        private void cleanUp() {
2696436cc6a00500b2b50b1e46d0c12379c15fd4f04Seigo Nonaka            mCallback = null;
2706436cc6a00500b2b50b1e46d0c12379c15fd4f04Seigo Nonaka            if (mObserver != null) {
2716436cc6a00500b2b50b1e46d0c12379c15fd4f04Seigo Nonaka                mFontProviderHelper.unregisterObserver(mContext, mObserver);
2726436cc6a00500b2b50b1e46d0c12379c15fd4f04Seigo Nonaka                mObserver = null;
2736436cc6a00500b2b50b1e46d0c12379c15fd4f04Seigo Nonaka            }
2746436cc6a00500b2b50b1e46d0c12379c15fd4f04Seigo Nonaka            synchronized (mLock) {
2756436cc6a00500b2b50b1e46d0c12379c15fd4f04Seigo Nonaka                mHandler.removeCallbacks(mHandleMetadataCreationRunner);
2766436cc6a00500b2b50b1e46d0c12379c15fd4f04Seigo Nonaka                if (mThread != null) {
2776436cc6a00500b2b50b1e46d0c12379c15fd4f04Seigo Nonaka                    mThread.quit();
2788346a9a10895008f08f50969689a1ce16fbd577cSeigo Nonaka                }
2796436cc6a00500b2b50b1e46d0c12379c15fd4f04Seigo Nonaka                mHandler = null;
2806436cc6a00500b2b50b1e46d0c12379c15fd4f04Seigo Nonaka                mThread = null;
2816436cc6a00500b2b50b1e46d0c12379c15fd4f04Seigo Nonaka            }
2826436cc6a00500b2b50b1e46d0c12379c15fd4f04Seigo Nonaka        }
2836436cc6a00500b2b50b1e46d0c12379c15fd4f04Seigo Nonaka
2846436cc6a00500b2b50b1e46d0c12379c15fd4f04Seigo Nonaka        // Must be called on the mHandler.
2856436cc6a00500b2b50b1e46d0c12379c15fd4f04Seigo Nonaka        @RequiresApi(19)
2866436cc6a00500b2b50b1e46d0c12379c15fd4f04Seigo Nonaka        private void createMetadata() {
2876436cc6a00500b2b50b1e46d0c12379c15fd4f04Seigo Nonaka            if (mCallback == null) {
2886436cc6a00500b2b50b1e46d0c12379c15fd4f04Seigo Nonaka                return;  // Already handled or cancelled. Do nothing.
2896436cc6a00500b2b50b1e46d0c12379c15fd4f04Seigo Nonaka            }
2906436cc6a00500b2b50b1e46d0c12379c15fd4f04Seigo Nonaka            try {
2916436cc6a00500b2b50b1e46d0c12379c15fd4f04Seigo Nonaka                final FontsContractCompat.FontInfo font = retrieveFontInfo();
2928346a9a10895008f08f50969689a1ce16fbd577cSeigo Nonaka
2936436cc6a00500b2b50b1e46d0c12379c15fd4f04Seigo Nonaka                final int resultCode = font.getResultCode();
2946436cc6a00500b2b50b1e46d0c12379c15fd4f04Seigo Nonaka                if (resultCode == FontsContractCompat.Columns.RESULT_CODE_FONT_UNAVAILABLE) {
2956436cc6a00500b2b50b1e46d0c12379c15fd4f04Seigo Nonaka                    // The font provider is now downloading. Ask RetryPolicy for when to retry next.
2966436cc6a00500b2b50b1e46d0c12379c15fd4f04Seigo Nonaka                    synchronized (mLock) {
2976436cc6a00500b2b50b1e46d0c12379c15fd4f04Seigo Nonaka                        if (mRetryPolicy != null) {
2986436cc6a00500b2b50b1e46d0c12379c15fd4f04Seigo Nonaka                            final long delayMs = mRetryPolicy.getRetryDelay();
2996436cc6a00500b2b50b1e46d0c12379c15fd4f04Seigo Nonaka                            if (delayMs >= 0) {
3006436cc6a00500b2b50b1e46d0c12379c15fd4f04Seigo Nonaka                                scheduleRetry(font.getUri(), delayMs);
3016436cc6a00500b2b50b1e46d0c12379c15fd4f04Seigo Nonaka                                return;
3026436cc6a00500b2b50b1e46d0c12379c15fd4f04Seigo Nonaka                            }
3036436cc6a00500b2b50b1e46d0c12379c15fd4f04Seigo Nonaka                        }
3046436cc6a00500b2b50b1e46d0c12379c15fd4f04Seigo Nonaka                    }
3058346a9a10895008f08f50969689a1ce16fbd577cSeigo Nonaka                }
3068346a9a10895008f08f50969689a1ce16fbd577cSeigo Nonaka
3076436cc6a00500b2b50b1e46d0c12379c15fd4f04Seigo Nonaka                if (resultCode != FontsContractCompat.Columns.RESULT_CODE_OK) {
3086436cc6a00500b2b50b1e46d0c12379c15fd4f04Seigo Nonaka                    throw new RuntimeException("fetchFonts result is not OK. (" + resultCode + ")");
309484205f290d5cc989074248f876f72d10000eba1Siyamed Sinir                }
3108346a9a10895008f08f50969689a1ce16fbd577cSeigo Nonaka
3116436cc6a00500b2b50b1e46d0c12379c15fd4f04Seigo Nonaka                // TODO: Good to add new API to create Typeface from FD not to open FD twice.
3126436cc6a00500b2b50b1e46d0c12379c15fd4f04Seigo Nonaka                final Typeface typeface = mFontProviderHelper.buildTypeface(mContext, font);
3136436cc6a00500b2b50b1e46d0c12379c15fd4f04Seigo Nonaka                final ByteBuffer buffer = TypefaceCompatUtil.mmap(mContext, null, font.getUri());
3146436cc6a00500b2b50b1e46d0c12379c15fd4f04Seigo Nonaka                if (buffer == null) {
3156436cc6a00500b2b50b1e46d0c12379c15fd4f04Seigo Nonaka                    throw new RuntimeException("Unable to open file.");
3166436cc6a00500b2b50b1e46d0c12379c15fd4f04Seigo Nonaka                }
3176436cc6a00500b2b50b1e46d0c12379c15fd4f04Seigo Nonaka                mCallback.onLoaded(MetadataRepo.create(typeface, buffer));
3186436cc6a00500b2b50b1e46d0c12379c15fd4f04Seigo Nonaka                cleanUp();
319484205f290d5cc989074248f876f72d10000eba1Siyamed Sinir            } catch (Throwable t) {
3206436cc6a00500b2b50b1e46d0c12379c15fd4f04Seigo Nonaka                mCallback.onFailed(t);
3216436cc6a00500b2b50b1e46d0c12379c15fd4f04Seigo Nonaka                cleanUp();
322484205f290d5cc989074248f876f72d10000eba1Siyamed Sinir            }
323484205f290d5cc989074248f876f72d10000eba1Siyamed Sinir        }
324484205f290d5cc989074248f876f72d10000eba1Siyamed Sinir    }
325484205f290d5cc989074248f876f72d10000eba1Siyamed Sinir
3268346a9a10895008f08f50969689a1ce16fbd577cSeigo Nonaka    /**
3278346a9a10895008f08f50969689a1ce16fbd577cSeigo Nonaka     * Delegate class for mocking FontsContractCompat.fetchFonts.
3288346a9a10895008f08f50969689a1ce16fbd577cSeigo Nonaka     * @hide
3298346a9a10895008f08f50969689a1ce16fbd577cSeigo Nonaka     */
3308346a9a10895008f08f50969689a1ce16fbd577cSeigo Nonaka    @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
3316436cc6a00500b2b50b1e46d0c12379c15fd4f04Seigo Nonaka    public static class FontProviderHelper {
3328346a9a10895008f08f50969689a1ce16fbd577cSeigo Nonaka        /** Calls FontsContractCompat.fetchFonts. */
3338346a9a10895008f08f50969689a1ce16fbd577cSeigo Nonaka        public FontFamilyResult fetchFonts(@NonNull Context context,
3348346a9a10895008f08f50969689a1ce16fbd577cSeigo Nonaka                @NonNull FontRequest request) throws NameNotFoundException {
3358346a9a10895008f08f50969689a1ce16fbd577cSeigo Nonaka            return FontsContractCompat.fetchFonts(context, null /* cancellation signal */, request);
3368346a9a10895008f08f50969689a1ce16fbd577cSeigo Nonaka        }
3378346a9a10895008f08f50969689a1ce16fbd577cSeigo Nonaka
3386436cc6a00500b2b50b1e46d0c12379c15fd4f04Seigo Nonaka        /** Calls FontsContractCompat.buildTypeface. */
3396436cc6a00500b2b50b1e46d0c12379c15fd4f04Seigo Nonaka        public Typeface buildTypeface(@NonNull Context context,
3406436cc6a00500b2b50b1e46d0c12379c15fd4f04Seigo Nonaka                @NonNull FontsContractCompat.FontInfo font) throws NameNotFoundException {
3416436cc6a00500b2b50b1e46d0c12379c15fd4f04Seigo Nonaka            return FontsContractCompat.buildTypeface(context, null /* cancellation signal */,
3426436cc6a00500b2b50b1e46d0c12379c15fd4f04Seigo Nonaka                new FontsContractCompat.FontInfo[] { font });
3436436cc6a00500b2b50b1e46d0c12379c15fd4f04Seigo Nonaka        }
3446436cc6a00500b2b50b1e46d0c12379c15fd4f04Seigo Nonaka
3456436cc6a00500b2b50b1e46d0c12379c15fd4f04Seigo Nonaka        /** Calls Context.getContentObserver().registerObserver */
3466436cc6a00500b2b50b1e46d0c12379c15fd4f04Seigo Nonaka        public void registerObserver(@NonNull Context context, @NonNull Uri uri,
3476436cc6a00500b2b50b1e46d0c12379c15fd4f04Seigo Nonaka                @NonNull ContentObserver observer) {
3486436cc6a00500b2b50b1e46d0c12379c15fd4f04Seigo Nonaka            context.getContentResolver().registerContentObserver(
3496436cc6a00500b2b50b1e46d0c12379c15fd4f04Seigo Nonaka                    uri, false /* notifyForDescendants */, observer);
3506436cc6a00500b2b50b1e46d0c12379c15fd4f04Seigo Nonaka
3516436cc6a00500b2b50b1e46d0c12379c15fd4f04Seigo Nonaka        }
3526436cc6a00500b2b50b1e46d0c12379c15fd4f04Seigo Nonaka        /** Calls Context.getContentObserver().unregisterObserver */
3536436cc6a00500b2b50b1e46d0c12379c15fd4f04Seigo Nonaka        public void unregisterObserver(@NonNull Context context,
3546436cc6a00500b2b50b1e46d0c12379c15fd4f04Seigo Nonaka                @NonNull ContentObserver observer) {
3556436cc6a00500b2b50b1e46d0c12379c15fd4f04Seigo Nonaka            context.getContentResolver().unregisterContentObserver(observer);
3566436cc6a00500b2b50b1e46d0c12379c15fd4f04Seigo Nonaka        }
3576436cc6a00500b2b50b1e46d0c12379c15fd4f04Seigo Nonaka    };
3586436cc6a00500b2b50b1e46d0c12379c15fd4f04Seigo Nonaka
3596436cc6a00500b2b50b1e46d0c12379c15fd4f04Seigo Nonaka    private static final FontProviderHelper DEFAULT_FONTS_CONTRACT = new FontProviderHelper();
3608346a9a10895008f08f50969689a1ce16fbd577cSeigo Nonaka
361484205f290d5cc989074248f876f72d10000eba1Siyamed Sinir}
362