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