public final class

ResourcesCompat

extends java.lang.Object

 java.lang.Object

↳androidx.core.content.res.ResourcesCompat

Gradle dependencies

compile group: 'androidx.core', name: 'core', version: '1.9.0-alpha04'

  • groupId: androidx.core
  • artifactId: core
  • version: 1.9.0-alpha04

Artifact androidx.core:core:1.9.0-alpha04 it located at Google repository (https://maven.google.com/)

Androidx artifact mapping:

androidx.core:core com.android.support:support-compat

Androidx class mapping:

androidx.core.content.res.ResourcesCompat android.support.v4.content.res.ResourcesCompat

Overview

Helper for accessing features in Resources.

Summary

Fields
public static final intID_NULL

The null resource ID.

Methods
public static voidclearCachesForTheme(Theme theme)

Clears cached values associated with the specified .

public static TypefacegetCachedFont(Context context, int id)

Returns a cached font Typeface associated with a particular resource ID.

public static intgetColor(Resources res, int id, Theme theme)

Returns a themed color integer associated with a particular resource ID.

public static ColorStateListgetColorStateList(Resources res, int id, Theme theme)

Returns a themed color state list associated with a particular resource ID.

public static DrawablegetDrawable(Resources res, int id, Theme theme)

Return a drawable object associated with a particular resource ID and styled for the specified theme.

public static DrawablegetDrawableForDensity(Resources res, int id, int density, Theme theme)

Return a drawable object associated with a particular resource ID for the given screen density in DPI and styled for the specified theme.

public static floatgetFloat(Resources res, int id)

Retrieve a floating-point value for a particular resource ID.

public static TypefacegetFont(Context context, int id)

Returns a font Typeface associated with a particular resource ID.

public static voidgetFont(Context context, int id, ResourcesCompat.FontCallback fontCallback, Handler handler)

Returns a font Typeface associated with a particular resource ID asynchronously.

public static TypefacegetFont(Context context, int id, TypedValue value, int style, ResourcesCompat.FontCallback fontCallback)

Used by TintTypedArray.

from java.lang.Objectclone, equals, finalize, getClass, hashCode, notify, notifyAll, toString, wait, wait, wait

Fields

public static final int ID_NULL

The null resource ID. This denotes an invalid resource ID that is returned by the system when a resource is not found or the value is set to @null in XML.

Methods

public static void clearCachesForTheme(Theme theme)

Clears cached values associated with the specified .

This method allows developers to work around issues related to stale cached resources that may occur on API level 32 and below following a call to . If you are not explicitly calling applyStyle in your code, you probably do not need to call this method.

Starting in Android T, the Theme class correctly implements and cached values will be appropriately cleared when the contents of a Theme object change.

Parameters:

theme: the theme for which associated values should be cleared from the resource cache

public static Drawable getDrawable(Resources res, int id, Theme theme)

Return a drawable object associated with a particular resource ID and styled for the specified theme. Various types of objects will be returned depending on the underlying resource -- for example, a solid color, PNG image, scalable image, etc.

Prior to API level 21, the theme will not be applied and this method simply calls through to Resources.

Parameters:

id: The desired resource identifier, as generated by the aapt tool. This integer encodes the package, type, and resource entry. The value 0 is an invalid identifier.
theme: The theme used to style the drawable attributes, may be null.

Returns:

Drawable An object that can be used to draw this resource.

public static Drawable getDrawableForDensity(Resources res, int id, int density, Theme theme)

Return a drawable object associated with a particular resource ID for the given screen density in DPI and styled for the specified theme.

Prior to API level 15, the theme and density will not be applied and this method simply calls through to Resources.

Prior to API level 21, the theme will not be applied and this method calls through to Resources#getDrawableForDensity(int, int).

Parameters:

id: The desired resource identifier, as generated by the aapt tool. This integer encodes the package, type, and resource entry. The value 0 is an invalid identifier.
density: The desired screen density indicated by the resource as found in .
theme: The theme used to style the drawable attributes, may be null.

Returns:

Drawable An object that can be used to draw this resource.

public static int getColor(Resources res, int id, Theme theme)

Returns a themed color integer associated with a particular resource ID. If the resource holds a complex , then the default color from the set is returned.

Prior to API level 23, the theme will not be applied and this method calls through to Resources.

Parameters:

id: The desired resource identifier, as generated by the aapt tool. This integer encodes the package, type, and resource entry. The value 0 is an invalid identifier.
theme: The theme used to style the color attributes, may be null.

Returns:

A single color value in the form 0xAARRGGBB.

public static ColorStateList getColorStateList(Resources res, int id, Theme theme)

Returns a themed color state list associated with a particular resource ID. The resource may contain either a single raw color value or a complex holding multiple possible colors.

Parameters:

id: The desired resource identifier of a , as generated by the aapt tool. This integer encodes the package, type, and resource entry. The value 0 is an invalid identifier.
theme: The theme used to style the color attributes, may be null.

Returns:

A themed ColorStateList object containing either a single solid color or multiple colors that can be selected based on a state.

public static float getFloat(Resources res, int id)

Retrieve a floating-point value for a particular resource ID.

Parameters:

id: The desired resource identifier, as generated by the aapt tool. This integer encodes the package, type, and resource entry. The value 0 is an invalid identifier.

Returns:

Returns the floating-point value contained in the resource.

public static Typeface getFont(Context context, int id)

Returns a font Typeface associated with a particular resource ID.

This method will block the calling thread to retrieve the requested font, including if it is from a font provider. If you wish to not have this behavior, use ResourcesCompat.getFont(Context, int, ResourcesCompat.FontCallback, Handler) instead.

Prior to API level 23, font resources with more than one font in a family will only load the font closest to a regular weight typeface.

Parameters:

context: A context to retrieve the Resources from.
id: The desired resource identifier of a , as generated by the aapt tool. This integer encodes the package, type, and resource entry. The value 0 is an invalid identifier.

Returns:

A font Typeface object.

See also: ResourcesCompat.getFont(Context, int, ResourcesCompat.FontCallback, Handler)

public static Typeface getCachedFont(Context context, int id)

Returns a cached font Typeface associated with a particular resource ID.

This method returns non-null Typeface if the requested font is already fetched. Otherwise immediately returns null without requesting to font provider.

Prior to API level 23, font resources with more than one font in a family will only load the font closest to a regular weight typeface.

Parameters:

context: A context to retrieve the Resources from.
id: The desired resource identifier of a , as generated by the aapt tool. This integer encodes the package, type, and resource entry. The value 0 is an invalid identifier.

Returns:

A font Typeface object.

See also: ResourcesCompat.getFont(Context, int, ResourcesCompat.FontCallback, Handler)

public static void getFont(Context context, int id, ResourcesCompat.FontCallback fontCallback, Handler handler)

Returns a font Typeface associated with a particular resource ID asynchronously.

Prior to API level 23, font resources with more than one font in a family will only load the font closest to a regular weight typeface.

Parameters:

context: A context to retrieve the Resources from.
id: The desired resource identifier of a , as generated by the aapt tool. This integer encodes the package, type, and resource entry. The value 0 is an invalid identifier.
fontCallback: A callback to receive async fetching of this font. The callback will be triggered on the UI thread.
handler: A handler for the thread the callback should be called on. If null, the callback will be called on the UI thread.

public static Typeface getFont(Context context, int id, TypedValue value, int style, ResourcesCompat.FontCallback fontCallback)

Used by TintTypedArray.

Source

/*
 * Copyright (C) 2014 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package androidx.core.content.res;

import static android.os.Build.VERSION.SDK_INT;

import static androidx.annotation.RestrictTo.Scope.LIBRARY;
import static androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX;

import android.annotation.SuppressLint;
import android.content.Context;
import android.content.res.ColorStateList;
import android.content.res.Configuration;
import android.content.res.Resources;
import android.content.res.Resources.NotFoundException;
import android.content.res.Resources.Theme;
import android.content.res.XmlResourceParser;
import android.graphics.Typeface;
import android.graphics.drawable.Drawable;
import android.os.Handler;
import android.os.Looper;
import android.util.DisplayMetrics;
import android.util.Log;
import android.util.SparseArray;
import android.util.TypedValue;

import androidx.annotation.AnyRes;
import androidx.annotation.ColorInt;
import androidx.annotation.ColorRes;
import androidx.annotation.DimenRes;
import androidx.annotation.DoNotInline;
import androidx.annotation.DrawableRes;
import androidx.annotation.FontRes;
import androidx.annotation.GuardedBy;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.RequiresApi;
import androidx.annotation.RestrictTo;
import androidx.core.content.res.FontResourcesParserCompat.FamilyResourceEntry;
import androidx.core.graphics.TypefaceCompat;
import androidx.core.provider.FontsContractCompat.FontRequestCallback;
import androidx.core.provider.FontsContractCompat.FontRequestCallback.FontRequestFailReason;
import androidx.core.util.ObjectsCompat;
import androidx.core.util.Preconditions;

import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;

import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Iterator;
import java.util.WeakHashMap;

/**
 * Helper for accessing features in {@link Resources}.
 */
@SuppressWarnings("unused")
public final class ResourcesCompat {
    private static final String TAG = "ResourcesCompat";
    private static final ThreadLocal<TypedValue> sTempTypedValue = new ThreadLocal<>();

    @GuardedBy("sColorStateCacheLock")
    private static final WeakHashMap<ColorStateListCacheKey, SparseArray<ColorStateListCacheEntry>>
            sColorStateCaches = new WeakHashMap<>(0);
    private static final Object sColorStateCacheLock = new Object();

    /**
     * The {@code null} resource ID. This denotes an invalid resource ID that is returned by the
     * system when a resource is not found or the value is set to {@code @null} in XML.
     */
    @AnyRes
    public static final int ID_NULL = 0;

    /**
     * Clears cached values associated with the specified {@link Theme}.
     * <p>
     * This method allows developers to work around issues related to stale cached resources that
     * may occur on API level 32 and below following a call to
     * {@link Theme#applyStyle(int, boolean)}. If you are not explicitly calling {@code
     * applyStyle} in your code, you probably do not need to call this method.
     * <p>
     * Starting in Android T, the Theme class correctly implements {@link Theme#hashCode()} and
     * cached values will be appropriately cleared when the contents of a Theme object change.
     *
     * @param theme the theme for which associated values should be cleared from the resource cache
     */
    public static void clearCachesForTheme(@NonNull Theme theme) {
        synchronized (sColorStateCacheLock) {
            Iterator<ColorStateListCacheKey> keys = sColorStateCaches.keySet().iterator();
            while (keys.hasNext()) {
                ColorStateListCacheKey key = keys.next();
                if (key != null && theme.equals(key.mTheme)) {
                    keys.remove();
                }
            }
        }
    }

    /**
     * Return a drawable object associated with a particular resource ID and
     * styled for the specified theme. Various types of objects will be
     * returned depending on the underlying resource -- for example, a solid
     * color, PNG image, scalable image, etc.
     * <p>
     * Prior to API level 21, the theme will not be applied and this method
     * simply calls through to {@link Resources#getDrawable(int)}.
     *
     * @param id    The desired resource identifier, as generated by the aapt
     *              tool. This integer encodes the package, type, and resource
     *              entry. The value 0 is an invalid identifier.
     * @param theme The theme used to style the drawable attributes, may be
     *              {@code null}.
     * @return Drawable An object that can be used to draw this resource.
     * @throws NotFoundException Throws NotFoundException if the given ID does
     *                           not exist.
     */
    @Nullable
    @SuppressWarnings("deprecation")
    public static Drawable getDrawable(@NonNull Resources res, @DrawableRes int id,
            @Nullable Theme theme) throws NotFoundException {
        if (SDK_INT >= 21) {
            return Api21Impl.getDrawable(res, id, theme);
        } else {
            return res.getDrawable(id);
        }
    }


    /**
     * Return a drawable object associated with a particular resource ID for
     * the given screen density in DPI and styled for the specified theme.
     * <p>
     * Prior to API level 15, the theme and density will not be applied and
     * this method simply calls through to {@link Resources#getDrawable(int)}.
     * <p>
     * Prior to API level 21, the theme will not be applied and this method
     * calls through to Resources#getDrawableForDensity(int, int).
     *
     * @param id      The desired resource identifier, as generated by the aapt
     *                tool. This integer encodes the package, type, and resource
     *                entry. The value 0 is an invalid identifier.
     * @param density The desired screen density indicated by the resource as
     *                found in {@link DisplayMetrics}.
     * @param theme   The theme used to style the drawable attributes, may be
     *                {@code null}.
     * @return Drawable An object that can be used to draw this resource.
     * @throws NotFoundException Throws NotFoundException if the given ID does
     *                           not exist.
     */
    @Nullable
    @SuppressWarnings("deprecation")
    public static Drawable getDrawableForDensity(@NonNull Resources res, @DrawableRes int id,
            int density, @Nullable Theme theme) throws NotFoundException {
        if (SDK_INT >= 21) {
            return Api21Impl.getDrawableForDensity(res, id, density, theme);
        } else if (SDK_INT >= 15) {
            return Api15Impl.getDrawableForDensity(res, id, density);
        } else {
            return res.getDrawable(id);
        }
    }

    /**
     * Returns a themed color integer associated with a particular resource ID.
     * If the resource holds a complex {@link ColorStateList}, then the default
     * color from the set is returned.
     * <p>
     * Prior to API level 23, the theme will not be applied and this method
     * calls through to {@link Resources#getColor(int)}.
     *
     * @param id    The desired resource identifier, as generated by the aapt
     *              tool. This integer encodes the package, type, and resource
     *              entry. The value 0 is an invalid identifier.
     * @param theme The theme used to style the color attributes, may be
     *              {@code null}.
     * @return A single color value in the form {@code 0xAARRGGBB}.
     * @throws NotFoundException Throws NotFoundException if the given ID does
     *                           not exist.
     */
    @ColorInt
    @SuppressWarnings("deprecation")
    public static int getColor(@NonNull Resources res, @ColorRes int id, @Nullable Theme theme)
            throws NotFoundException {
        if (SDK_INT >= 23) {
            return ResourcesCompat.Api23Impl.getColor(res, id, theme);
        } else {
            return res.getColor(id);
        }
    }

    /**
     * Returns a themed color state list associated with a particular resource
     * ID. The resource may contain either a single raw color value or a
     * complex {@link ColorStateList} holding multiple possible colors.
     *
     * @param id    The desired resource identifier of a {@link ColorStateList},
     *              as generated by the aapt tool. This integer encodes the
     *              package, type, and resource entry. The value 0 is an invalid
     *              identifier.
     * @param theme The theme used to style the color attributes, may be
     *              {@code null}.
     * @return A themed ColorStateList object containing either a single solid
     * color or multiple colors that can be selected based on a state.
     * @throws NotFoundException Throws NotFoundException if the given ID does
     *                           not exist.
     */
    @Nullable
    @SuppressWarnings("deprecation")
    public static ColorStateList getColorStateList(@NonNull Resources res, @ColorRes int id,
            @Nullable Theme theme) throws NotFoundException {
        // We explicitly do not attempt to use the platform Resources impl on S+
        // in case the CSL is using only app:lStar

        // First, try and handle the inflation ourselves
        ColorStateListCacheKey key = new ColorStateListCacheKey(res, theme);
        ColorStateList csl = getCachedColorStateList(key, id);
        if (csl != null) {
            return csl;
        }
        // Cache miss, so try and inflate it ourselves
        csl = inflateColorStateList(res, id, theme);
        if (csl != null) {
            // If we inflated it, add it to the cache and return
            addColorStateListToCache(key, id, csl, theme);
            return csl;
        }
        // If we reach here then we couldn't inflate it, so let the framework handle it
        if (SDK_INT >= 23) {
            return ResourcesCompat.Api23Impl.getColorStateList(res, id, theme);
        } else {
            return res.getColorStateList(id);
        }
    }

    /**
     * Inflates a {@link ColorStateList} from resources, honouring theme attributes.
     */
    @Nullable
    private static ColorStateList inflateColorStateList(Resources resources, int resId,
            @Nullable Theme theme) {
        if (isColorInt(resources, resId)) {
            // The resource is a color int, we can't handle it so return null
            return null;
        }
        final XmlPullParser xml = resources.getXml(resId);
        try {
            return ColorStateListInflaterCompat.createFromXml(resources, xml, theme);
        } catch (Exception e) {
            Log.w(TAG, "Failed to inflate ColorStateList, leaving it to the framework", e);
        }
        return null;
    }

    @Nullable
    private static ColorStateList getCachedColorStateList(@NonNull ColorStateListCacheKey key,
            @ColorRes int resId) {
        synchronized (sColorStateCacheLock) {
            final SparseArray<ColorStateListCacheEntry> entries = sColorStateCaches.get(key);
            if (entries != null && entries.size() > 0) {
                final ColorStateListCacheEntry entry = entries.get(resId);
                if (entry != null) {
                    if (entry.mConfiguration.equals(key.mResources.getConfiguration())
                            && ((key.mTheme == null && entry.mThemeHash == 0)
                            || (key.mTheme != null && entry.mThemeHash == key.mTheme.hashCode()))) {
                        // If the current configuration matches the entry's, we can use it
                        return entry.mValue;
                    } else {
                        // Otherwise we'll remove the entry
                        entries.remove(resId);
                    }
                }
            }
        }
        return null;
    }

    private static void addColorStateListToCache(@NonNull ColorStateListCacheKey key,
            @ColorRes int resId,
            @NonNull ColorStateList value,
            @Nullable Theme theme) {
        synchronized (sColorStateCacheLock) {
            SparseArray<ColorStateListCacheEntry> entries = sColorStateCaches.get(key);
            if (entries == null) {
                entries = new SparseArray<>();
                sColorStateCaches.put(key, entries);
            }
            entries.append(resId, new ColorStateListCacheEntry(value,
                    key.mResources.getConfiguration(), theme));
        }
    }

    private static boolean isColorInt(@NonNull Resources resources, @ColorRes int resId) {
        final TypedValue value = getTypedValue();
        resources.getValue(resId, value, true);
        return value.type >= TypedValue.TYPE_FIRST_COLOR_INT
                && value.type <= TypedValue.TYPE_LAST_COLOR_INT;
    }

    @NonNull
    private static TypedValue getTypedValue() {
        TypedValue tv = sTempTypedValue.get();
        if (tv == null) {
            tv = new TypedValue();
            sTempTypedValue.set(tv);
        }
        return tv;
    }

    private static final class ColorStateListCacheKey {
        final Resources mResources;
        final Theme mTheme;

        ColorStateListCacheKey(@NonNull Resources resources, @Nullable Theme theme) {
            mResources = resources;
            mTheme = theme;
        }

        @Override
        public boolean equals(Object o) {
            if (this == o) return true;
            if (o == null || getClass() != o.getClass()) return false;
            ColorStateListCacheKey that = (ColorStateListCacheKey) o;
            return mResources.equals(that.mResources)
                    && ObjectsCompat.equals(mTheme, that.mTheme);
        }

        @Override
        public int hashCode() {
            return ObjectsCompat.hash(mResources, mTheme);
        }
    }

    private static class ColorStateListCacheEntry {
        final ColorStateList mValue;
        final Configuration mConfiguration;
        final int mThemeHash;

        ColorStateListCacheEntry(@NonNull ColorStateList value,
                @NonNull Configuration configuration,
                @Nullable Theme theme) {
            mValue = value;
            mConfiguration = configuration;
            mThemeHash = theme == null ? 0 : theme.hashCode();
        }
    }

    /**
     * Retrieve a floating-point value for a particular resource ID.
     *
     * @param id The desired resource identifier, as generated by the aapt
     *           tool. This integer encodes the package, type, and resource
     *           entry. The value 0 is an invalid identifier.
     * @return Returns the floating-point value contained in the resource.
     * @throws NotFoundException Throws NotFoundException if the given ID does
     *                           not exist or is not a floating-point value.
     */
    public static float getFloat(@NonNull Resources res, @DimenRes int id) {
        if (SDK_INT >= 29) {
            return Api29Impl.getFloat(res, id);
        }

        TypedValue value = getTypedValue();
        res.getValue(id, value, true);
        if (value.type == TypedValue.TYPE_FLOAT) {
            return value.getFloat();
        }
        throw new NotFoundException("Resource ID #0x" + Integer.toHexString(id)
                + " type #0x" + Integer.toHexString(value.type) + " is not valid");
    }

    /**
     * Returns a font Typeface associated with a particular resource ID.
     * <p>
     * This method will block the calling thread to retrieve the requested font, including if it
     * is from a font provider. If you wish to not have this behavior, use
     * {@link #getFont(Context, int, FontCallback, Handler)} instead.
     * <p>
     * Prior to API level 23, font resources with more than one font in a family will only load the
     * font closest to a regular weight typeface.
     *
     * @param context A context to retrieve the Resources from.
     * @param id      The desired resource identifier of a {@link Typeface},
     *                as generated by the aapt tool. This integer encodes the
     *                package, type, and resource entry. The value 0 is an invalid
     *                identifier.
     * @return A font Typeface object.
     * @throws NotFoundException Throws NotFoundException if the given ID does not exist.
     * @see #getFont(Context, int, FontCallback, Handler)
     */
    @Nullable
    public static Typeface getFont(@NonNull Context context, @FontRes int id)
            throws NotFoundException {
        if (context.isRestricted()) {
            return null;
        }
        return loadFont(context, id, new TypedValue(), Typeface.NORMAL, null /* callback */,
                null /* handler */, false /* isXmlRequest */, false /* isCachedOnly */);
    }

    /**
     * Returns a cached font Typeface associated with a particular resource ID.
     * <p>
     * This method returns non-null Typeface if the requested font is already fetched. Otherwise
     * immediately returns null without requesting to font provider.
     * <p>
     * Prior to API level 23, font resources with more than one font in a family will only load the
     * font closest to a regular weight typeface.
     *
     * @param context A context to retrieve the Resources from.
     * @param id      The desired resource identifier of a {@link Typeface},
     *                as generated by the aapt tool. This integer encodes the
     *                package, type, and resource entry. The value 0 is an invalid
     *                identifier.
     * @return A font Typeface object.
     * @throws NotFoundException Throws NotFoundException if the given ID does not exist.
     * @see #getFont(Context, int, FontCallback, Handler)
     */
    @Nullable
    public static Typeface getCachedFont(@NonNull Context context, @FontRes int id)
            throws NotFoundException {
        if (context.isRestricted()) {
            return null;
        }
        return loadFont(context, id, new TypedValue(), Typeface.NORMAL, null /* callback */,
                null /* handler */, false /* isXmlRequest */, true);
    }

    /**
     * Interface used to receive asynchronous font fetching events.
     */
    public abstract static class FontCallback {

        /**
         * Called when an asynchronous font was finished loading.
         *
         * @param typeface The font that was loaded.
         */
        public abstract void onFontRetrieved(@NonNull Typeface typeface);

        /**
         * Called when an asynchronous font failed to load.
         *
         * @param reason The reason the font failed to load. One of
         *               {@link FontRequestFailReason#FAIL_REASON_PROVIDER_NOT_FOUND},
         *               {@link FontRequestFailReason#FAIL_REASON_WRONG_CERTIFICATES},
         *               {@link FontRequestFailReason#FAIL_REASON_FONT_LOAD_ERROR},
         *               {@link FontRequestFailReason#FAIL_REASON_SECURITY_VIOLATION},
         *               {@link FontRequestFailReason#FAIL_REASON_FONT_NOT_FOUND},
         *               {@link FontRequestFailReason#FAIL_REASON_FONT_UNAVAILABLE} or
         *               {@link FontRequestFailReason#FAIL_REASON_MALFORMED_QUERY}.
         */
        public abstract void onFontRetrievalFailed(@FontRequestFailReason int reason);

        /**
         * Call {@link #onFontRetrieved(Typeface)} on the handler given, or the Ui Thread if it is
         * null.
         *
         * @hide
         */
        @RestrictTo(LIBRARY_GROUP_PREFIX)
        public final void callbackSuccessAsync(final @NonNull Typeface typeface,
                @Nullable Handler handler) {
            getHandler(handler).post(() -> onFontRetrieved(typeface));
        }

        /**
         * Call {@link #onFontRetrievalFailed(int)} on the handler given, or the Ui Thread if it is
         * null.
         *
         * @hide
         */
        @RestrictTo(LIBRARY_GROUP_PREFIX)
        public final void callbackFailAsync(
                @FontRequestFailReason final int reason, @Nullable Handler handler) {
            getHandler(handler).post(() -> onFontRetrievalFailed(reason));
        }

        /** @hide */
        @RestrictTo(LIBRARY)
        @NonNull
        public static Handler getHandler(@Nullable Handler handler) {
            return handler == null ? new Handler(Looper.getMainLooper()) : handler;
        }
    }

    /**
     * Returns a font Typeface associated with a particular resource ID asynchronously.
     * <p>
     * Prior to API level 23, font resources with more than one font in a family will only load the
     * font closest to a regular weight typeface.
     * </p>
     *
     * @param context      A context to retrieve the Resources from.
     * @param id           The desired resource identifier of a {@link Typeface}, as generated by
     *                    the aapt
     *                     tool. This integer encodes the package, type, and resource entry. The
     *                     value 0 is an
     *                     invalid identifier.
     * @param fontCallback A callback to receive async fetching of this font. The callback will be
     *                     triggered on the UI thread.
     * @param handler      A handler for the thread the callback should be called on. If null, the
     *                     callback will be called on the UI thread.
     * @throws NotFoundException Throws NotFoundException if the given ID does not exist.
     */
    public static void getFont(@NonNull Context context, @FontRes int id,
            @NonNull FontCallback fontCallback, @Nullable Handler handler)
            throws NotFoundException {
        Preconditions.checkNotNull(fontCallback);
        if (context.isRestricted()) {
            fontCallback.callbackFailAsync(
                    FontRequestCallback.FAIL_REASON_SECURITY_VIOLATION, handler);
            return;
        }
        loadFont(context, id, new TypedValue(), Typeface.NORMAL, fontCallback, handler,
                false /* isXmlRequest */, false /* isCacheOnly */);
    }

    /**
     * Used by TintTypedArray.
     *
     * @hide
     */
    @Nullable
    @RestrictTo(LIBRARY_GROUP_PREFIX)
    public static Typeface getFont(@NonNull Context context, @FontRes int id,
            @NonNull TypedValue value, int style, @Nullable FontCallback fontCallback)
            throws NotFoundException {
        if (context.isRestricted()) {
            return null;
        }
        return loadFont(context, id, value, style, fontCallback, null /* handler */,
                true /* isXmlRequest */, false /* isCacheOnly */);
    }

    /**
     * @param context                     The Context to get Resources from
     * @param id                          The Resource id to load
     * @param value                       A TypedValue to use in the fetching
     * @param style                       The font style to load
     * @param fontCallback                A callback to trigger when the font is fetched or an
     *                                    error occurs
     * @param handler                     A handler to the thread the callback should be called on
     * @param isRequestFromLayoutInflator Whether this request originated from XML. This is used to
     *                                    determine if we use or ignore the
     *                                    fontProviderFetchStrategy attribute in
     *                                    font provider XML fonts.
     * @return The font as a Typeface
     * @throws NotFoundException if the resource ID could not be retrieved
     */
    private static Typeface loadFont(@NonNull Context context, int id, @NonNull TypedValue value,
            int style, @Nullable FontCallback fontCallback, @Nullable Handler handler,
            boolean isRequestFromLayoutInflator, boolean isCachedOnly) {
        final Resources resources = context.getResources();
        resources.getValue(id, value, true);
        Typeface typeface = loadFont(context, resources, value, id, style, fontCallback, handler,
                isRequestFromLayoutInflator, isCachedOnly);
        if (typeface == null && fontCallback == null && !isCachedOnly) {
            throw new NotFoundException("Font resource ID #0x"
                    + Integer.toHexString(id) + " could not be retrieved.");
        }
        return typeface;
    }

    /**
     * Load the given font. This method will always return null for asynchronous requests, which
     * provide a fontCallback, as there is no immediate result. When the callback is not provided,
     * the request is treated as synchronous and fails if async loading is required.
     *
     * @param context                     The Context to get Resources from
     * @param id                          The Resource id to load
     * @param value                       A TypedValue to use in the fetching
     * @param style                       The font style to load
     * @param fontCallback                A callback to trigger when the font is fetched or an
     *                                    error occurs
     * @param handler                     A handler to the thread the callback should be called on
     * @param isRequestFromLayoutInflator Whether this request originated from XML. This is used to
     *                                    determine if we use or ignore the
     *                                    fontProviderFetchStrategy attribute in
     *                                    font provider XML fonts.
     */
    private static Typeface loadFont(
            @NonNull Context context, Resources wrapper, @NonNull TypedValue value, int id,
            int style, @Nullable FontCallback fontCallback, @Nullable Handler handler,
            boolean isRequestFromLayoutInflator, boolean isCachedOnly) {
        if (value.string == null) {
            throw new NotFoundException("Resource \"" + wrapper.getResourceName(id) + "\" ("
                    + Integer.toHexString(id) + ") is not a Font: " + value);
        }

        final String file = value.string.toString();
        if (!file.startsWith("res/")) {
            // Early exit if the specified string is unlikely to be a resource path.
            if (fontCallback != null) {
                fontCallback.callbackFailAsync(
                        FontRequestCallback.FAIL_REASON_FONT_LOAD_ERROR, handler);
            }
            return null;
        }
        Typeface typeface = TypefaceCompat.findFromCache(wrapper, id, file, value.assetCookie,
                style);

        if (typeface != null) {
            if (fontCallback != null) {
                fontCallback.callbackSuccessAsync(typeface, handler);
            }
            return typeface;
        } else if (isCachedOnly) {
            return null;
        }

        try {
            if (file.toLowerCase().endsWith(".xml")) {
                final XmlResourceParser rp = wrapper.getXml(id);
                final FamilyResourceEntry familyEntry =
                        FontResourcesParserCompat.parse(rp, wrapper);
                if (familyEntry == null) {
                    Log.e(TAG, "Failed to find font-family tag");
                    if (fontCallback != null) {
                        fontCallback.callbackFailAsync(
                                FontRequestCallback.FAIL_REASON_FONT_LOAD_ERROR, handler);
                    }
                    return null;
                }
                return TypefaceCompat.createFromResourcesFamilyXml(context, familyEntry, wrapper,
                        id, file, value.assetCookie, style, fontCallback, handler,
                        isRequestFromLayoutInflator);
            }
            typeface = TypefaceCompat.createFromResourcesFontFile(
                    context, wrapper, id, file, value.assetCookie, style);
            if (fontCallback != null) {
                if (typeface != null) {
                    fontCallback.callbackSuccessAsync(typeface, handler);
                } else {
                    fontCallback.callbackFailAsync(
                            FontRequestCallback.FAIL_REASON_FONT_LOAD_ERROR, handler);
                }
            }
            return typeface;
        } catch (XmlPullParserException e) {
            Log.e(TAG, "Failed to parse xml resource " + file, e);
        } catch (IOException e) {
            Log.e(TAG, "Failed to read xml resource " + file, e);
        }
        if (fontCallback != null) {
            fontCallback.callbackFailAsync(
                    FontRequestCallback.FAIL_REASON_FONT_LOAD_ERROR, handler);
        }
        return null;
    }

    @RequiresApi(29)
    static class Api29Impl {
        private Api29Impl() {
            // This class is not instantiable.
        }

        @DoNotInline
        static float getFloat(@NonNull Resources res, @DimenRes int id) {
            return res.getFloat(id);
        }
    }

    @RequiresApi(23)
    static class Api23Impl {
        private Api23Impl() {
            // This class is not instantiable.
        }

        @DoNotInline
        @NonNull
        static ColorStateList getColorStateList(@NonNull Resources res, @ColorRes int id,
                @Nullable Theme theme) {
            return res.getColorStateList(id, theme);
        }

        @DoNotInline
        static int getColor(Resources resources, int id, Theme theme) {
            return resources.getColor(id, theme);
        }
    }

    @RequiresApi(21)
    static class Api21Impl {
        private Api21Impl() {
            // This class is not instantiable.
        }

        @DoNotInline
        static Drawable getDrawable(Resources resources, int id, Theme theme) {
            return resources.getDrawable(id, theme);
        }

        @DoNotInline
        static Drawable getDrawableForDensity(Resources resources, int id, int density,
                Theme theme) {
            return resources.getDrawableForDensity(id, density, theme);
        }
    }

    @RequiresApi(15)
    static class Api15Impl {
        private Api15Impl() {
            // This class is not instantiable.
        }

        @DoNotInline
        static Drawable getDrawableForDensity(Resources resources, int id, int density) {
            return resources.getDrawableForDensity(id, density);
        }

    }

    private ResourcesCompat() {
    }

    /**
     * Provides backward-compatible implementations for new {@link Theme} APIs.
     */
    public static final class ThemeCompat {
        private ThemeCompat() {
        }

        /**
         * Rebases the theme against the parent Resource object's current configuration by
         * re-applying the styles passed to {@link Theme#applyStyle(int, boolean)}.
         * <p>
         * Compatibility behavior:
         * <ul>
         * <li>API 29 and above, this method matches platform behavior.
         * <li>API 23 through 28, this method attempts to match platform behavior by calling into
         *     hidden platform APIs, but is not guaranteed to succeed.
         * <li>API 22 and earlier, this method does nothing.
         * </ul>
         *
         * @param theme the theme to rebase
         */
        public static void rebase(@NonNull Theme theme) {
            if (SDK_INT >= 29) {
                Api29Impl.rebase(theme);
            } else if (SDK_INT >= 23) {
                Api23Impl.rebase(theme);
            }
        }

        @RequiresApi(29)
        static class Api29Impl {
            private Api29Impl() {
                // This class is not instantiable.
            }

            @DoNotInline
            static void rebase(@NonNull Theme theme) {
                theme.rebase();
            }
        }

        @RequiresApi(23)
        static class Api23Impl {
            private Api23Impl() {
                // This class is not instantiable.
            }

            private static final Object sRebaseMethodLock = new Object();

            private static Method sRebaseMethod;
            private static boolean sRebaseMethodFetched;

            @SuppressLint("BanUncheckedReflection") // @RequiresApiRange(min=23, max=29)
            static void rebase(@NonNull Theme theme) {
                synchronized (sRebaseMethodLock) {
                    if (!sRebaseMethodFetched) {
                        try {
                            sRebaseMethod = Theme.class.getDeclaredMethod("rebase");
                            sRebaseMethod.setAccessible(true);
                        } catch (NoSuchMethodException e) {
                            Log.i(TAG, "Failed to retrieve rebase() method", e);
                        }
                        sRebaseMethodFetched = true;
                    }
                    if (sRebaseMethod != null) {
                        try {
                            sRebaseMethod.invoke(theme);
                        } catch (IllegalAccessException | InvocationTargetException e) {
                            Log.i(TAG, "Failed to invoke rebase() method via reflection", e);
                            sRebaseMethod = null;
                        }
                    }
                }
            }
        }
    }
}