1 /*
2  * Copyright (C) 2019 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 
17 package androidx.appcompat.widget;
18 
19 import static androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX;
20 
21 import android.annotation.SuppressLint;
22 import android.content.Context;
23 import android.content.res.ColorStateList;
24 import android.content.res.Resources;
25 import android.graphics.Color;
26 import android.graphics.PorterDuff;
27 import android.graphics.PorterDuffColorFilter;
28 import android.graphics.drawable.Drawable;
29 import android.graphics.drawable.Drawable.ConstantState;
30 import android.graphics.drawable.LayerDrawable;
31 import android.os.Build;
32 import android.util.AttributeSet;
33 import android.util.Log;
34 import android.util.TypedValue;
35 import android.util.Xml;
36 
37 import androidx.annotation.DrawableRes;
38 import androidx.annotation.RestrictTo;
39 import androidx.appcompat.graphics.drawable.AnimatedStateListDrawableCompat;
40 import androidx.appcompat.resources.Compatibility;
41 import androidx.appcompat.resources.R;
42 import androidx.collection.LongSparseArray;
43 import androidx.collection.LruCache;
44 import androidx.collection.SimpleArrayMap;
45 import androidx.collection.SparseArrayCompat;
46 import androidx.core.content.ContextCompat;
47 import androidx.core.graphics.drawable.DrawableCompat;
48 import androidx.vectordrawable.graphics.drawable.AnimatedVectorDrawableCompat;
49 import androidx.vectordrawable.graphics.drawable.VectorDrawableCompat;
50 
51 import org.jspecify.annotations.NonNull;
52 import org.jspecify.annotations.Nullable;
53 import org.xmlpull.v1.XmlPullParser;
54 import org.xmlpull.v1.XmlPullParserException;
55 
56 import java.lang.ref.WeakReference;
57 import java.util.WeakHashMap;
58 
59 /**
60  */
61 @RestrictTo(LIBRARY_GROUP_PREFIX)
62 public final class ResourceManagerInternal {
63     @RestrictTo(LIBRARY_GROUP_PREFIX)
64     public interface ResourceManagerHooks {
createDrawableFor( @onNull ResourceManagerInternal appCompatDrawableManager, @NonNull Context context, @DrawableRes final int resId)65         @Nullable Drawable createDrawableFor(
66                 @NonNull ResourceManagerInternal appCompatDrawableManager,
67                 @NonNull Context context, @DrawableRes final int resId);
tintDrawable(@onNull Context context, @DrawableRes int resId, @NonNull Drawable drawable)68         boolean tintDrawable(@NonNull Context context, @DrawableRes int resId,
69                 @NonNull Drawable drawable);
getTintListForDrawableRes( @onNull Context context, @DrawableRes int resId)70         @Nullable ColorStateList getTintListForDrawableRes(
71                 @NonNull Context context, @DrawableRes int resId);
tintDrawableUsingColorFilter(@onNull Context context, @DrawableRes final int resId, @NonNull Drawable drawable)72         boolean tintDrawableUsingColorFilter(@NonNull Context context,
73                 @DrawableRes final int resId, @NonNull Drawable drawable);
getTintModeForDrawableRes(final int resId)74         PorterDuff.@Nullable Mode getTintModeForDrawableRes(final int resId);
75     }
76 
77     private interface InflateDelegate {
createFromXmlInner(@onNull Context context, @NonNull XmlPullParser parser, @NonNull AttributeSet attrs, Resources.@Nullable Theme theme)78         Drawable createFromXmlInner(@NonNull Context context, @NonNull XmlPullParser parser,
79                 @NonNull AttributeSet attrs, Resources.@Nullable Theme theme);
80     }
81 
82     private static final String TAG = "ResourceManagerInternal";
83     private static final boolean DEBUG = false;
84     private static final PorterDuff.Mode DEFAULT_MODE = PorterDuff.Mode.SRC_IN;
85     private static final String SKIP_DRAWABLE_TAG = "appcompat_skip_skip";
86 
87     private static final String PLATFORM_VD_CLAZZ = "android.graphics.drawable.VectorDrawable";
88 
89     private static ResourceManagerInternal INSTANCE;
90 
91     /**
92      * Returns the singleton instance of this class.
93      */
get()94     public static synchronized ResourceManagerInternal get() {
95         if (INSTANCE == null) {
96             INSTANCE = new ResourceManagerInternal();
97             installDefaultInflateDelegates(INSTANCE);
98         }
99         return INSTANCE;
100     }
101 
installDefaultInflateDelegates(@onNull ResourceManagerInternal manager)102     private static void installDefaultInflateDelegates(@NonNull ResourceManagerInternal manager) {
103         // This sdk version check will affect src:appCompat code path.
104         // Although VectorDrawable exists in Android framework from Lollipop, AppCompat will use
105         // (Animated)VectorDrawableCompat before Nougat to utilize bug fixes & feature backports.
106         if (Build.VERSION.SDK_INT < 24) {
107             manager.addDelegate("vector", new VdcInflateDelegate());
108             manager.addDelegate("animated-vector", new AvdcInflateDelegate());
109             manager.addDelegate("animated-selector", new AsldcInflateDelegate());
110             manager.addDelegate("drawable", new DrawableDelegate());
111         }
112     }
113 
114     private static final ColorFilterLruCache COLOR_FILTER_CACHE = new ColorFilterLruCache(6);
115 
116     private WeakHashMap<Context, SparseArrayCompat<ColorStateList>> mTintLists;
117     private SimpleArrayMap<String, InflateDelegate> mDelegates;
118     private SparseArrayCompat<String> mKnownDrawableIdTags;
119 
120     private final WeakHashMap<Context, LongSparseArray<WeakReference<ConstantState>>>
121             mDrawableCaches = new WeakHashMap<>(0);
122 
123     private TypedValue mTypedValue;
124 
125     private boolean mHasCheckedVectorDrawableSetup;
126 
127     private ResourceManagerHooks mHooks;
128 
setHooks(ResourceManagerHooks hooks)129     public synchronized void setHooks(ResourceManagerHooks hooks) {
130         mHooks = hooks;
131     }
132 
getDrawable(@onNull Context context, @DrawableRes int resId)133     public synchronized Drawable getDrawable(@NonNull Context context, @DrawableRes int resId) {
134         return getDrawable(context, resId, false);
135     }
136 
getDrawable(@onNull Context context, @DrawableRes int resId, boolean failIfNotKnown)137     synchronized Drawable getDrawable(@NonNull Context context, @DrawableRes int resId,
138             boolean failIfNotKnown) {
139         checkVectorDrawableSetup(context);
140 
141         Drawable drawable = loadDrawableFromDelegates(context, resId);
142         if (drawable == null) {
143             drawable = createDrawableIfNeeded(context, resId);
144         }
145         if (drawable == null) {
146             drawable = ContextCompat.getDrawable(context, resId);
147         }
148 
149         if (drawable != null) {
150             // Tint it if needed
151             drawable = tintDrawable(context, resId, failIfNotKnown, drawable);
152         }
153         if (drawable != null) {
154             // See if we need to 'fix' the drawable
155             DrawableUtils.fixDrawable(drawable);
156         }
157         return drawable;
158     }
159 
onConfigurationChanged(@onNull Context context)160     public synchronized void onConfigurationChanged(@NonNull Context context) {
161         LongSparseArray<WeakReference<ConstantState>> cache = mDrawableCaches.get(context);
162         if (cache != null) {
163             // Crude, but we'll just clear the cache when the configuration changes
164             cache.clear();
165         }
166     }
167 
createCacheKey(TypedValue tv)168     private static long createCacheKey(TypedValue tv) {
169         return (((long) tv.assetCookie) << 32) | tv.data;
170     }
171 
createDrawableIfNeeded(@onNull Context context, @DrawableRes final int resId)172     private Drawable createDrawableIfNeeded(@NonNull Context context,
173             @DrawableRes final int resId) {
174         if (mTypedValue == null) {
175             mTypedValue = new TypedValue();
176         }
177         final TypedValue tv = mTypedValue;
178         context.getResources().getValue(resId, tv, true);
179         final long key = createCacheKey(tv);
180 
181         Drawable dr = getCachedDrawable(context, key);
182         if (dr != null) {
183             // If we got a cached drawable, return it
184             return dr;
185         }
186 
187         // Else we need to try and create one...
188         dr = (this.mHooks == null) ? null
189             : this.mHooks.createDrawableFor(this, context, resId);
190 
191         if (dr != null) {
192             dr.setChangingConfigurations(tv.changingConfigurations);
193             // If we reached here then we created a new drawable, add it to the cache
194             addDrawableToCache(context, key, dr);
195         }
196 
197         return dr;
198     }
199 
tintDrawable(@onNull Context context, @DrawableRes int resId, boolean failIfNotKnown, @NonNull Drawable drawable)200     private Drawable tintDrawable(@NonNull Context context, @DrawableRes int resId,
201             boolean failIfNotKnown, @NonNull Drawable drawable) {
202         final ColorStateList tintList = getTintList(context, resId);
203         if (tintList != null) {
204             // First mutate the Drawable, then wrap it and set the tint list
205             drawable = drawable.mutate();
206             drawable = DrawableCompat.wrap(drawable);
207             DrawableCompat.setTintList(drawable, tintList);
208 
209             // If there is a blending mode specified for the drawable, use it
210             final PorterDuff.Mode tintMode = getTintMode(resId);
211             if (tintMode != null) {
212                 DrawableCompat.setTintMode(drawable, tintMode);
213             }
214         } else if ((mHooks != null) && mHooks.tintDrawable(context, resId, drawable)) {
215             // If we're here, the installed hooks reported successful tinting of the
216             // specific drawable
217         } else {
218             final boolean tinted = tintDrawableUsingColorFilter(context, resId, drawable);
219             if (!tinted && failIfNotKnown) {
220                 // If we didn't tint using a ColorFilter, and we're set to fail if we don't
221                 // know the id, return null
222                 drawable = null;
223             }
224         }
225         return drawable;
226     }
227 
loadDrawableFromDelegates(@onNull Context context, @DrawableRes int resId)228     private Drawable loadDrawableFromDelegates(@NonNull Context context, @DrawableRes int resId) {
229         if (mDelegates != null && !mDelegates.isEmpty()) {
230             if (mKnownDrawableIdTags != null) {
231                 final String cachedTagName = mKnownDrawableIdTags.get(resId);
232                 if (SKIP_DRAWABLE_TAG.equals(cachedTagName)
233                         || (cachedTagName != null && mDelegates.get(cachedTagName) == null)) {
234                     // If we don't have a delegate for the drawable tag, or we've been set to
235                     // skip it, fail fast and return null
236                     if (DEBUG) {
237                         Log.d(TAG, "[loadDrawableFromDelegates] Skipping drawable: "
238                                 + context.getResources().getResourceName(resId));
239                     }
240                     return null;
241                 }
242             } else {
243                 // Create an id cache as we'll need one later
244                 mKnownDrawableIdTags = new SparseArrayCompat<>();
245             }
246 
247             if (mTypedValue == null) {
248                 mTypedValue = new TypedValue();
249             }
250             final TypedValue tv = mTypedValue;
251             final Resources res = context.getResources();
252             res.getValue(resId, tv, true);
253 
254             final long key = createCacheKey(tv);
255 
256             Drawable dr = getCachedDrawable(context, key);
257             if (dr != null) {
258                 if (DEBUG) {
259                     Log.i(TAG, "[loadDrawableFromDelegates] Returning cached drawable: " +
260                             context.getResources().getResourceName(resId));
261                 }
262                 // We have a cached drawable, return it!
263                 return dr;
264             }
265 
266             if (tv.string != null && tv.string.toString().endsWith(".xml")) {
267                 // If the resource is an XML file, let's try and parse it
268                 try {
269                     @SuppressLint("ResourceType") final XmlPullParser parser = res.getXml(resId);
270                     final AttributeSet attrs = Xml.asAttributeSet(parser);
271                     int type;
272                     while ((type = parser.next()) != XmlPullParser.START_TAG &&
273                             type != XmlPullParser.END_DOCUMENT) {
274                         // Empty loop
275                     }
276                     if (type != XmlPullParser.START_TAG) {
277                         throw new XmlPullParserException("No start tag found");
278                     }
279 
280                     final String tagName = parser.getName();
281                     // Add the tag name to the cache
282                     mKnownDrawableIdTags.append(resId, tagName);
283 
284                     // Now try and find a delegate for the tag name and inflate if found
285                     final InflateDelegate delegate = mDelegates.get(tagName);
286                     if (delegate != null) {
287                         dr = delegate.createFromXmlInner(context, parser, attrs,
288                                 context.getTheme());
289                     }
290                     if (dr != null) {
291                         // Add it to the drawable cache
292                         dr.setChangingConfigurations(tv.changingConfigurations);
293                         if (addDrawableToCache(context, key, dr) && DEBUG) {
294                             Log.i(TAG, "[loadDrawableFromDelegates] Saved drawable to cache: " +
295                                     context.getResources().getResourceName(resId));
296                         }
297                     }
298                 } catch (Exception e) {
299                     Log.e(TAG, "Exception while inflating drawable", e);
300                 }
301             }
302             if (dr == null) {
303                 // If we reach here then the delegate inflation of the resource failed. Mark it as
304                 // bad so we skip the id next time
305                 mKnownDrawableIdTags.append(resId, SKIP_DRAWABLE_TAG);
306             }
307             return dr;
308         }
309 
310         return null;
311     }
312 
getCachedDrawable(final @NonNull Context context, final long key)313     private synchronized Drawable getCachedDrawable(final @NonNull Context context,
314             final long key) {
315         final LongSparseArray<WeakReference<ConstantState>> cache = mDrawableCaches.get(context);
316         if (cache == null) {
317             return null;
318         }
319 
320         final WeakReference<ConstantState> wr = cache.get(key);
321         if (wr != null) {
322             // We have the key, and the secret
323             ConstantState entry = wr.get();
324             if (entry != null) {
325                 return entry.newDrawable(context.getResources());
326             } else {
327                 // Our entry has been purged
328                 cache.remove(key);
329             }
330         }
331         return null;
332     }
333 
addDrawableToCache(final @NonNull Context context, final long key, final @NonNull Drawable drawable)334     private synchronized boolean addDrawableToCache(final @NonNull Context context, final long key,
335             final @NonNull Drawable drawable) {
336         final ConstantState cs = drawable.getConstantState();
337         if (cs != null) {
338             LongSparseArray<WeakReference<ConstantState>> cache = mDrawableCaches.get(context);
339             if (cache == null) {
340                 cache = new LongSparseArray<>();
341                 mDrawableCaches.put(context, cache);
342             }
343             cache.put(key, new WeakReference<>(cs));
344             return true;
345         }
346         return false;
347     }
348 
onDrawableLoadedFromResources(@onNull Context context, @NonNull VectorEnabledTintResources resources, @DrawableRes final int resId)349     synchronized Drawable onDrawableLoadedFromResources(@NonNull Context context,
350             @NonNull VectorEnabledTintResources resources, @DrawableRes final int resId) {
351         Drawable drawable = loadDrawableFromDelegates(context, resId);
352         if (drawable == null) {
353             drawable = resources.getDrawableCanonical(resId);
354         }
355         if (drawable != null) {
356             return tintDrawable(context, resId, false, drawable);
357         }
358         return null;
359     }
360 
tintDrawableUsingColorFilter(@onNull Context context, @DrawableRes final int resId, @NonNull Drawable drawable)361     boolean tintDrawableUsingColorFilter(@NonNull Context context,
362             @DrawableRes final int resId, @NonNull Drawable drawable) {
363         return (mHooks != null) && mHooks.tintDrawableUsingColorFilter(context, resId, drawable);
364     }
365 
addDelegate(@onNull String tagName, @NonNull InflateDelegate delegate)366     private void addDelegate(@NonNull String tagName, @NonNull InflateDelegate delegate) {
367         if (mDelegates == null) {
368             mDelegates = new SimpleArrayMap<>();
369         }
370         mDelegates.put(tagName, delegate);
371     }
372 
getTintMode(final int resId)373     PorterDuff.Mode getTintMode(final int resId) {
374         return (mHooks == null) ? null : mHooks.getTintModeForDrawableRes(resId);
375     }
376 
getTintList(@onNull Context context, @DrawableRes int resId)377     synchronized ColorStateList getTintList(@NonNull Context context, @DrawableRes int resId) {
378         // Try the cache first (if it exists)
379         ColorStateList tint = getTintListFromCache(context, resId);
380 
381         if (tint == null) {
382             // ...if the cache did not contain a color state list, try and create one
383             tint = (mHooks == null) ? null : mHooks.getTintListForDrawableRes(context, resId);
384 
385             if (tint != null) {
386                 addTintListToCache(context, resId, tint);
387             }
388         }
389         return tint;
390     }
391 
getTintListFromCache(@onNull Context context, @DrawableRes int resId)392     private ColorStateList getTintListFromCache(@NonNull Context context, @DrawableRes int resId) {
393         if (mTintLists != null) {
394             final SparseArrayCompat<ColorStateList> tints = mTintLists.get(context);
395             return tints != null ? tints.get(resId) : null;
396         }
397         return null;
398     }
399 
addTintListToCache(@onNull Context context, @DrawableRes int resId, @NonNull ColorStateList tintList)400     private void addTintListToCache(@NonNull Context context, @DrawableRes int resId,
401             @NonNull ColorStateList tintList) {
402         if (mTintLists == null) {
403             mTintLists = new WeakHashMap<>();
404         }
405         SparseArrayCompat<ColorStateList> themeTints = mTintLists.get(context);
406         if (themeTints == null) {
407             themeTints = new SparseArrayCompat<>();
408             mTintLists.put(context, themeTints);
409         }
410         themeTints.append(resId, tintList);
411     }
412 
413     private static class ColorFilterLruCache extends LruCache<Integer, PorterDuffColorFilter> {
414 
ColorFilterLruCache(int maxSize)415         public ColorFilterLruCache(int maxSize) {
416             super(maxSize);
417         }
418 
get(int color, PorterDuff.Mode mode)419         PorterDuffColorFilter get(int color, PorterDuff.Mode mode) {
420             return get(generateCacheKey(color, mode));
421         }
422 
put(int color, PorterDuff.Mode mode, PorterDuffColorFilter filter)423         PorterDuffColorFilter put(int color, PorterDuff.Mode mode, PorterDuffColorFilter filter) {
424             return put(generateCacheKey(color, mode), filter);
425         }
426 
generateCacheKey(int color, PorterDuff.Mode mode)427         private static int generateCacheKey(int color, PorterDuff.Mode mode) {
428             int hashCode = 1;
429             hashCode = 31 * hashCode + color;
430             hashCode = 31 * hashCode + mode.hashCode();
431             return hashCode;
432         }
433     }
434 
tintDrawable(Drawable drawable, TintInfo tint, int[] state)435     static void tintDrawable(Drawable drawable, TintInfo tint, int[] state) {
436         int[] drawableState = drawable.getState();
437 
438         boolean mutated = drawable.mutate() == drawable;
439         if (!mutated) {
440             Log.d(TAG, "Mutated drawable is not the same instance as the input.");
441             return;
442         }
443 
444         // Workaround for b/232275112 where LayerDrawable loses its state on mutate().
445         if (drawable instanceof LayerDrawable && drawable.isStateful()) {
446             // Clear state first, otherwise setState() is a no-op.
447             drawable.setState(new int[0]);
448             drawable.setState(drawableState);
449         }
450 
451         if (tint.mHasTintList || tint.mHasTintMode) {
452             drawable.setColorFilter(createTintFilter(
453                     tint.mHasTintList ? tint.mTintList : null,
454                     tint.mHasTintMode ? tint.mTintMode : DEFAULT_MODE,
455                     state));
456         } else {
457             drawable.clearColorFilter();
458         }
459 
460         if (Build.VERSION.SDK_INT <= 23) {
461             // Pre-v23 there is no guarantee that a state change will invoke an invalidation,
462             // so we force it ourselves
463             drawable.invalidateSelf();
464         }
465     }
466 
createTintFilter(ColorStateList tint, PorterDuff.Mode tintMode, final int[] state)467     private static PorterDuffColorFilter createTintFilter(ColorStateList tint,
468             PorterDuff.Mode tintMode, final int[] state) {
469         if (tint == null || tintMode == null) {
470             return null;
471         }
472         final int color = tint.getColorForState(state, Color.TRANSPARENT);
473         return getPorterDuffColorFilter(color, tintMode);
474     }
475 
getPorterDuffColorFilter( int color, PorterDuff.Mode mode)476     public static synchronized PorterDuffColorFilter getPorterDuffColorFilter(
477             int color, PorterDuff.Mode mode) {
478         // First, let's see if the cache already contains the color filter
479         PorterDuffColorFilter filter = COLOR_FILTER_CACHE.get(color, mode);
480 
481         if (filter == null) {
482             // Cache miss, so create a color filter and add it to the cache
483             filter = new PorterDuffColorFilter(color, mode);
484             COLOR_FILTER_CACHE.put(color, mode, filter);
485         }
486 
487         return filter;
488     }
489 
checkVectorDrawableSetup(@onNull Context context)490     private void checkVectorDrawableSetup(@NonNull Context context) {
491         if (mHasCheckedVectorDrawableSetup) {
492             // We've already checked so return now...
493             return;
494         }
495         // Here we will check that a known Vector drawable resource inside AppCompat can be
496         // correctly decoded
497         mHasCheckedVectorDrawableSetup = true;
498         final Drawable d = getDrawable(context, R.drawable.abc_vector_test);
499         if (d == null || !isVectorDrawable(d)) {
500             mHasCheckedVectorDrawableSetup = false;
501             throw new IllegalStateException("This app has been built with an incorrect "
502                     + "configuration. Please configure your build for VectorDrawableCompat.");
503         }
504     }
505 
isVectorDrawable(@onNull Drawable d)506     private static boolean isVectorDrawable(@NonNull Drawable d) {
507         return d instanceof VectorDrawableCompat
508                 || PLATFORM_VD_CLAZZ.equals(d.getClass().getName());
509     }
510 
511     private static class VdcInflateDelegate implements InflateDelegate {
VdcInflateDelegate()512         VdcInflateDelegate() {
513         }
514 
515         @Override
createFromXmlInner(@onNull Context context, @NonNull XmlPullParser parser, @NonNull AttributeSet attrs, Resources.@Nullable Theme theme)516         public Drawable createFromXmlInner(@NonNull Context context, @NonNull XmlPullParser parser,
517                 @NonNull AttributeSet attrs, Resources.@Nullable Theme theme) {
518             try {
519                 return VectorDrawableCompat
520                         .createFromXmlInner(context.getResources(), parser, attrs, theme);
521             } catch (Exception e) {
522                 Log.e("VdcInflateDelegate", "Exception while inflating <vector>", e);
523                 return null;
524             }
525         }
526     }
527 
528     private static class AvdcInflateDelegate implements InflateDelegate {
AvdcInflateDelegate()529         AvdcInflateDelegate() {
530         }
531 
532         @Override
createFromXmlInner(@onNull Context context, @NonNull XmlPullParser parser, @NonNull AttributeSet attrs, Resources.@Nullable Theme theme)533         public Drawable createFromXmlInner(@NonNull Context context, @NonNull XmlPullParser parser,
534                 @NonNull AttributeSet attrs, Resources.@Nullable Theme theme) {
535             try {
536                 return AnimatedVectorDrawableCompat
537                         .createFromXmlInner(context, context.getResources(), parser, attrs, theme);
538             } catch (Exception e) {
539                 Log.e("AvdcInflateDelegate", "Exception while inflating <animated-vector>", e);
540                 return null;
541             }
542         }
543     }
544 
545     static class AsldcInflateDelegate implements InflateDelegate {
546         @Override
createFromXmlInner(@onNull Context context, @NonNull XmlPullParser parser, @NonNull AttributeSet attrs, Resources.@Nullable Theme theme)547         public Drawable createFromXmlInner(@NonNull Context context, @NonNull XmlPullParser parser,
548                 @NonNull AttributeSet attrs, Resources.@Nullable Theme theme) {
549             try {
550                 return AnimatedStateListDrawableCompat
551                         .createFromXmlInner(context, context.getResources(), parser, attrs, theme);
552             } catch (Exception e) {
553                 Log.e("AsldcInflateDelegate", "Exception while inflating <animated-selector>", e);
554                 return null;
555             }
556         }
557     }
558 
559     static class DrawableDelegate implements InflateDelegate {
560         @Override
createFromXmlInner(@onNull Context context, @NonNull XmlPullParser parser, @NonNull AttributeSet attrs, Resources.@Nullable Theme theme)561         public Drawable createFromXmlInner(@NonNull Context context, @NonNull XmlPullParser parser,
562                 @NonNull AttributeSet attrs, Resources.@Nullable Theme theme) {
563             String className = attrs.getClassAttribute();
564             if (className != null) {
565                 try {
566                     Class<? extends Drawable> drawableClass =
567                             DrawableDelegate.class.getClassLoader().loadClass(className)
568                                     .asSubclass(Drawable.class);
569                     Drawable drawable = drawableClass.getDeclaredConstructor().newInstance();
570                     if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
571                         Compatibility.Api21Impl.inflate(drawable, context.getResources(), parser,
572                                 attrs, theme);
573                     } else {
574                         drawable.inflate(context.getResources(), parser, attrs);
575                     }
576                     return drawable;
577                 } catch (Exception e) {
578                     Log.e("DrawableDelegate", "Exception while inflating <drawable>", e);
579                     return null;
580                 }
581             }
582             return null;
583         }
584     }
585 }
586