1 package com.android.customization.widget; 2 3 import android.content.res.ColorStateList; 4 import android.content.res.Resources; 5 import android.content.res.Resources.Theme; 6 import android.graphics.Bitmap; 7 import android.graphics.BitmapShader; 8 import android.graphics.Canvas; 9 import android.graphics.Color; 10 import android.graphics.ColorFilter; 11 import android.graphics.Matrix; 12 import android.graphics.Outline; 13 import android.graphics.Paint; 14 import android.graphics.Path; 15 import android.graphics.PixelFormat; 16 import android.graphics.PorterDuff.Mode; 17 import android.graphics.Rect; 18 import android.graphics.Region; 19 import android.graphics.Shader; 20 import android.graphics.Shader.TileMode; 21 import android.graphics.drawable.AdaptiveIconDrawable; 22 import android.graphics.drawable.Drawable; 23 import android.util.AttributeSet; 24 import android.util.DisplayMetrics; 25 26 import androidx.annotation.NonNull; 27 import androidx.annotation.Nullable; 28 29 import org.xmlpull.v1.XmlPullParser; 30 import org.xmlpull.v1.XmlPullParserException; 31 32 import java.io.IOException; 33 34 /** 35 * This is basically a copy of {@link AdaptiveIconDrawable} but which allows a custom path for 36 * the icon mask instead of using the one defined in the system. 37 * Note: Unlike AdaptiveIconDrawable we don't need to deal with densityOverride here so that 38 * logic is omitted. 39 */ 40 public class DynamicAdaptiveIconDrawable extends Drawable implements Drawable.Callback { 41 42 /** 43 * Mask path is defined inside device configuration in following dimension: [100 x 100] 44 */ 45 private static final float MASK_SIZE = 100f; 46 47 /** 48 * All four sides of the layers are padded with extra inset so as to provide 49 * extra content to reveal within the clip path when performing affine transformations on the 50 * layers. 51 * 52 * Each layers will reserve 25% of it's width and height. 53 * 54 * As a result, the view port of the layers is smaller than their intrinsic width and height. 55 */ 56 private static final float EXTRA_INSET_PERCENTAGE = 1 / 4f; 57 private static final float DEFAULT_VIEW_PORT_SCALE = 1f / (1 + 2 * EXTRA_INSET_PERCENTAGE); 58 59 private final Path mOriginalMask; 60 61 /** 62 * Scaled mask based on the view bounds. 63 */ 64 private final Path mMask; 65 private final Path mMaskScaleOnly; 66 private final Matrix mMaskMatrix; 67 private final Region mTransparentRegion; 68 69 /** 70 * Indices used to access {@link #mLayerState.mChildren} array for foreground and 71 * background layer. 72 */ 73 private static final int BACKGROUND_ID = 0; 74 private static final int FOREGROUND_ID = 1; 75 76 /** 77 * State variable that maintains the {@link ChildDrawable} array. 78 */ 79 private LayerState mLayerState; 80 81 private Shader mLayersShader; 82 private Bitmap mLayersBitmap; 83 84 private final Rect mTmpOutRect = new Rect(); 85 private Rect mHotspotBounds; 86 private boolean mMutated; 87 88 private boolean mSuspendChildInvalidation; 89 private boolean mChildRequestedInvalidation; 90 private final Canvas mCanvas; 91 private Paint mPaint = new Paint(Paint.ANTI_ALIAS_FLAG | Paint.DITHER_FLAG | 92 Paint.FILTER_BITMAP_FLAG); 93 94 /** 95 * Constructor used for xml inflation. 96 */ DynamicAdaptiveIconDrawable()97 DynamicAdaptiveIconDrawable() { 98 this((LayerState) null, null, null); 99 } 100 101 /** 102 * The one constructor to rule them all. This is called by all public 103 * constructors to set the state and initialize local properties. 104 */ DynamicAdaptiveIconDrawable(@ullable LayerState state, @Nullable Resources res, Path iconMask)105 private DynamicAdaptiveIconDrawable(@Nullable LayerState state, @Nullable Resources res, 106 Path iconMask) { 107 mLayerState = createConstantState(state, res); 108 109 mOriginalMask = iconMask; 110 mMask = new Path(iconMask); 111 mMaskScaleOnly = new Path(mMask); 112 mMaskMatrix = new Matrix(); 113 mCanvas = new Canvas(); 114 mTransparentRegion = new Region(); 115 } 116 createChildDrawable(Drawable drawable)117 private ChildDrawable createChildDrawable(Drawable drawable) { 118 final ChildDrawable layer = new ChildDrawable(mLayerState.mDensity); 119 layer.mDrawable = drawable; 120 layer.mDrawable.setCallback(this); 121 mLayerState.mChildrenChangingConfigurations |= 122 layer.mDrawable.getChangingConfigurations(); 123 return layer; 124 } 125 createConstantState(@ullable LayerState state, @Nullable Resources res)126 private LayerState createConstantState(@Nullable LayerState state, @Nullable Resources res) { 127 return new LayerState(state, this, res); 128 } 129 130 /** 131 * Constructor used to dynamically create this drawable. 132 * 133 * @param backgroundDrawable drawable that should be rendered in the background 134 * @param foregroundDrawable drawable that should be rendered in the foreground 135 * @param iconMask path to use to mask the icon 136 */ DynamicAdaptiveIconDrawable(Drawable backgroundDrawable, Drawable foregroundDrawable, Path iconMask)137 public DynamicAdaptiveIconDrawable(Drawable backgroundDrawable, 138 Drawable foregroundDrawable, Path iconMask) { 139 this((LayerState)null, null, iconMask); 140 if (backgroundDrawable != null) { 141 addLayer(BACKGROUND_ID, createChildDrawable(backgroundDrawable)); 142 } 143 if (foregroundDrawable != null) { 144 addLayer(FOREGROUND_ID, createChildDrawable(foregroundDrawable)); 145 } 146 } 147 148 /** 149 * Sets the layer to the {@param index} and invalidates cache. 150 * 151 * @param index The index of the layer. 152 * @param layer The layer to add. 153 */ addLayer(int index, @NonNull ChildDrawable layer)154 private void addLayer(int index, @NonNull ChildDrawable layer) { 155 mLayerState.mChildren[index] = layer; 156 mLayerState.invalidateCache(); 157 } 158 159 @Override inflate(@onNull Resources r, @NonNull XmlPullParser parser, @NonNull AttributeSet attrs, @Nullable Theme theme)160 public void inflate(@NonNull Resources r, @NonNull XmlPullParser parser, 161 @NonNull AttributeSet attrs, @Nullable Theme theme) 162 throws XmlPullParserException, IOException { 163 super.inflate(r, parser, attrs, theme); 164 165 final LayerState state = mLayerState; 166 if (state == null) { 167 return; 168 } 169 170 inflateLayers(r, parser, attrs, theme); 171 } 172 173 /** 174 * When called before the bound is set, the returned path is identical to 175 * R.string.config_icon_mask. After the bound is set, the 176 * returned path's computed bound is same as the #getBounds(). 177 * 178 * @return the mask path object used to clip the drawable 179 */ getIconMask()180 public Path getIconMask() { 181 return mMask; 182 } 183 184 /** 185 * Returns the foreground drawable managed by this class. 186 * 187 * @return the foreground drawable managed by this drawable 188 */ getForeground()189 public Drawable getForeground() { 190 return mLayerState.mChildren[FOREGROUND_ID].mDrawable; 191 } 192 193 /** 194 * Returns the foreground drawable managed by this class. The bound of this drawable is 195 * extended by {@link #getExtraInsetFraction()} * getBounds().width on left/right sides and by 196 * {@link #getExtraInsetFraction()} * getBounds().height on top/bottom sides. 197 * 198 * @return the background drawable managed by this drawable 199 */ getBackground()200 public Drawable getBackground() { 201 return mLayerState.mChildren[BACKGROUND_ID].mDrawable; 202 } 203 204 @Override onBoundsChange(Rect bounds)205 protected void onBoundsChange(Rect bounds) { 206 if (bounds.isEmpty()) { 207 return; 208 } 209 updateLayerBounds(bounds); 210 } 211 updateLayerBounds(Rect bounds)212 private void updateLayerBounds(Rect bounds) { 213 if (bounds.isEmpty()) { 214 return; 215 } 216 try { 217 suspendChildInvalidation(); 218 updateLayerBoundsInternal(bounds); 219 updateMaskBoundsInternal(bounds); 220 } finally { 221 resumeChildInvalidation(); 222 } 223 } 224 225 /** 226 * Set the child layer bounds bigger than the view port size by {@link #DEFAULT_VIEW_PORT_SCALE} 227 */ updateLayerBoundsInternal(Rect bounds)228 private void updateLayerBoundsInternal(Rect bounds) { 229 int cX = bounds.width() / 2; 230 int cY = bounds.height() / 2; 231 232 for (int i = 0, count = mLayerState.N_CHILDREN; i < count; i++) { 233 final ChildDrawable r = mLayerState.mChildren[i]; 234 if (r == null) { 235 continue; 236 } 237 final Drawable d = r.mDrawable; 238 if (d == null) { 239 continue; 240 } 241 242 int insetWidth = (int) (bounds.width() / (DEFAULT_VIEW_PORT_SCALE * 2)); 243 int insetHeight = (int) (bounds.height() / (DEFAULT_VIEW_PORT_SCALE * 2)); 244 final Rect outRect = mTmpOutRect; 245 outRect.set(cX - insetWidth, cY - insetHeight, cX + insetWidth, cY + insetHeight); 246 247 d.setBounds(outRect); 248 } 249 } 250 updateMaskBoundsInternal(Rect b)251 private void updateMaskBoundsInternal(Rect b) { 252 // reset everything that depends on the view bounds 253 mMaskMatrix.setScale(b.width() / MASK_SIZE, b.height() / MASK_SIZE); 254 mOriginalMask.transform(mMaskMatrix, mMaskScaleOnly); 255 256 mMaskMatrix.postTranslate(b.left, b.top); 257 mOriginalMask.transform(mMaskMatrix, mMask); 258 259 if (mLayersBitmap == null || mLayersBitmap.getWidth() != b.width() 260 || mLayersBitmap.getHeight() != b.height()) { 261 mLayersBitmap = Bitmap.createBitmap(b.width(), b.height(), Bitmap.Config.ARGB_8888); 262 } 263 264 mPaint.setShader(null); 265 mTransparentRegion.setEmpty(); 266 mLayersShader = null; 267 } 268 269 @Override draw(Canvas canvas)270 public void draw(Canvas canvas) { 271 if (mLayersBitmap == null) { 272 return; 273 } 274 if (mLayersShader == null) { 275 mCanvas.setBitmap(mLayersBitmap); 276 mCanvas.drawColor(Color.BLACK); 277 for (int i = 0; i < mLayerState.N_CHILDREN; i++) { 278 if (mLayerState.mChildren[i] == null) { 279 continue; 280 } 281 final Drawable dr = mLayerState.mChildren[i].mDrawable; 282 if (dr != null) { 283 dr.draw(mCanvas); 284 } 285 } 286 mLayersShader = new BitmapShader(mLayersBitmap, TileMode.CLAMP, TileMode.CLAMP); 287 mPaint.setShader(mLayersShader); 288 } 289 if (mMaskScaleOnly != null) { 290 Rect bounds = getBounds(); 291 canvas.translate(bounds.left, bounds.top); 292 canvas.drawPath(mMaskScaleOnly, mPaint); 293 canvas.translate(-bounds.left, -bounds.top); 294 } 295 } 296 297 @Override invalidateSelf()298 public void invalidateSelf() { 299 mLayersShader = null; 300 super.invalidateSelf(); 301 } 302 303 @Override getOutline(@onNull Outline outline)304 public void getOutline(@NonNull Outline outline) { 305 outline.setConvexPath(mMask); 306 } 307 308 @Override getTransparentRegion()309 public @Nullable Region getTransparentRegion() { 310 if (mTransparentRegion.isEmpty()) { 311 mMask.toggleInverseFillType(); 312 mTransparentRegion.set(getBounds()); 313 mTransparentRegion.setPath(mMask, mTransparentRegion); 314 mMask.toggleInverseFillType(); 315 } 316 return mTransparentRegion; 317 } 318 319 @Override applyTheme(@onNull Theme t)320 public void applyTheme(@NonNull Theme t) { 321 super.applyTheme(t); 322 323 final LayerState state = mLayerState; 324 if (state == null) { 325 return; 326 } 327 328 final ChildDrawable[] array = state.mChildren; 329 for (int i = 0; i < state.N_CHILDREN; i++) { 330 final ChildDrawable layer = array[i]; 331 332 final Drawable d = layer.mDrawable; 333 if (d != null && d.canApplyTheme()) { 334 d.applyTheme(t); 335 336 // Update cached mask of child changing configurations. 337 state.mChildrenChangingConfigurations |= d.getChangingConfigurations(); 338 } 339 } 340 } 341 342 /** 343 * Inflates child layers using the specified parser. 344 */ inflateLayers(@onNull Resources r, @NonNull XmlPullParser parser, @NonNull AttributeSet attrs, @Nullable Theme theme)345 private void inflateLayers(@NonNull Resources r, @NonNull XmlPullParser parser, 346 @NonNull AttributeSet attrs, @Nullable Theme theme) 347 throws XmlPullParserException, IOException { 348 final LayerState state = mLayerState; 349 350 final int innerDepth = parser.getDepth() + 1; 351 int type; 352 int depth; 353 int childIndex = 0; 354 while ((type = parser.next()) != XmlPullParser.END_DOCUMENT 355 && ((depth = parser.getDepth()) >= innerDepth || type != XmlPullParser.END_TAG)) { 356 if (type != XmlPullParser.START_TAG) { 357 continue; 358 } 359 360 if (depth > innerDepth) { 361 continue; 362 } 363 String tagName = parser.getName(); 364 if (tagName.equals("background")) { 365 childIndex = BACKGROUND_ID; 366 } else if (tagName.equals("foreground")) { 367 childIndex = FOREGROUND_ID; 368 } else { 369 continue; 370 } 371 372 final ChildDrawable layer = new ChildDrawable(state.mDensity); 373 374 // If the layer doesn't have a drawable or unresolved theme 375 // attribute for a drawable, attempt to parse one from the child 376 // element. If multiple child elements exist, we'll only use the 377 // first one. 378 if (layer.mDrawable == null && (layer.mThemeAttrs == null)) { 379 while ((type = parser.next()) == XmlPullParser.TEXT) { 380 } 381 if (type != XmlPullParser.START_TAG) { 382 throw new XmlPullParserException(parser.getPositionDescription() 383 + ": <foreground> or <background> tag requires a 'drawable'" 384 + "attribute or child tag defining a drawable"); 385 } 386 387 // We found a child drawable. Take ownership. 388 layer.mDrawable = Drawable.createFromXmlInner(r, parser, attrs, theme); 389 layer.mDrawable.setCallback(this); 390 state.mChildrenChangingConfigurations |= 391 layer.mDrawable.getChangingConfigurations(); 392 } 393 addLayer(childIndex, layer); 394 } 395 } 396 397 @Override canApplyTheme()398 public boolean canApplyTheme() { 399 return (mLayerState != null && mLayerState.canApplyTheme()) || super.canApplyTheme(); 400 } 401 402 @Override isProjected()403 public boolean isProjected() { 404 if (super.isProjected()) { 405 return true; 406 } 407 408 final ChildDrawable[] layers = mLayerState.mChildren; 409 for (int i = 0; i < mLayerState.N_CHILDREN; i++) { 410 if (layers[i].mDrawable != null && layers[i].mDrawable.isProjected()) { 411 return true; 412 } 413 } 414 return false; 415 } 416 417 /** 418 * Temporarily suspends child invalidation. 419 * 420 * @see #resumeChildInvalidation() 421 */ suspendChildInvalidation()422 private void suspendChildInvalidation() { 423 mSuspendChildInvalidation = true; 424 } 425 426 /** 427 * Resumes child invalidation after suspension, immediately performing an 428 * invalidation if one was requested by a child during suspension. 429 * 430 * @see #suspendChildInvalidation() 431 */ resumeChildInvalidation()432 private void resumeChildInvalidation() { 433 mSuspendChildInvalidation = false; 434 435 if (mChildRequestedInvalidation) { 436 mChildRequestedInvalidation = false; 437 invalidateSelf(); 438 } 439 } 440 441 @Override invalidateDrawable(@onNull Drawable who)442 public void invalidateDrawable(@NonNull Drawable who) { 443 if (mSuspendChildInvalidation) { 444 mChildRequestedInvalidation = true; 445 } else { 446 invalidateSelf(); 447 } 448 } 449 450 @Override scheduleDrawable(@onNull Drawable who, @NonNull Runnable what, long when)451 public void scheduleDrawable(@NonNull Drawable who, @NonNull Runnable what, long when) { 452 scheduleSelf(what, when); 453 } 454 455 @Override unscheduleDrawable(@onNull Drawable who, @NonNull Runnable what)456 public void unscheduleDrawable(@NonNull Drawable who, @NonNull Runnable what) { 457 unscheduleSelf(what); 458 } 459 460 @Override getChangingConfigurations()461 public int getChangingConfigurations() { 462 return super.getChangingConfigurations() | mLayerState.getChangingConfigurations(); 463 } 464 465 @Override setHotspot(float x, float y)466 public void setHotspot(float x, float y) { 467 final ChildDrawable[] array = mLayerState.mChildren; 468 for (int i = 0; i < mLayerState.N_CHILDREN; i++) { 469 final Drawable dr = array[i].mDrawable; 470 if (dr != null) { 471 dr.setHotspot(x, y); 472 } 473 } 474 } 475 476 @Override setHotspotBounds(int left, int top, int right, int bottom)477 public void setHotspotBounds(int left, int top, int right, int bottom) { 478 final ChildDrawable[] array = mLayerState.mChildren; 479 for (int i = 0; i < mLayerState.N_CHILDREN; i++) { 480 final Drawable dr = array[i].mDrawable; 481 if (dr != null) { 482 dr.setHotspotBounds(left, top, right, bottom); 483 } 484 } 485 486 if (mHotspotBounds == null) { 487 mHotspotBounds = new Rect(left, top, right, bottom); 488 } else { 489 mHotspotBounds.set(left, top, right, bottom); 490 } 491 } 492 493 @Override getHotspotBounds(Rect outRect)494 public void getHotspotBounds(Rect outRect) { 495 if (mHotspotBounds != null) { 496 outRect.set(mHotspotBounds); 497 } else { 498 super.getHotspotBounds(outRect); 499 } 500 } 501 502 @Override setVisible(boolean visible, boolean restart)503 public boolean setVisible(boolean visible, boolean restart) { 504 final boolean changed = super.setVisible(visible, restart); 505 final ChildDrawable[] array = mLayerState.mChildren; 506 507 for (int i = 0; i < mLayerState.N_CHILDREN; i++) { 508 final Drawable dr = array[i].mDrawable; 509 if (dr != null) { 510 dr.setVisible(visible, restart); 511 } 512 } 513 514 return changed; 515 } 516 517 @Override setDither(boolean dither)518 public void setDither(boolean dither) { 519 final ChildDrawable[] array = mLayerState.mChildren; 520 for (int i = 0; i < mLayerState.N_CHILDREN; i++) { 521 final Drawable dr = array[i].mDrawable; 522 if (dr != null) { 523 dr.setDither(dither); 524 } 525 } 526 } 527 528 @Override setAlpha(int alpha)529 public void setAlpha(int alpha) { 530 mPaint.setAlpha(alpha); 531 } 532 533 @Override getAlpha()534 public int getAlpha() { 535 return mPaint.getAlpha(); 536 } 537 538 @Override setColorFilter(ColorFilter colorFilter)539 public void setColorFilter(ColorFilter colorFilter) { 540 final ChildDrawable[] array = mLayerState.mChildren; 541 for (int i = 0; i < mLayerState.N_CHILDREN; i++) { 542 final Drawable dr = array[i].mDrawable; 543 if (dr != null) { 544 dr.setColorFilter(colorFilter); 545 } 546 } 547 } 548 549 @Override setTintList(ColorStateList tint)550 public void setTintList(ColorStateList tint) { 551 final ChildDrawable[] array = mLayerState.mChildren; 552 final int N = mLayerState.N_CHILDREN; 553 for (int i = 0; i < N; i++) { 554 final Drawable dr = array[i].mDrawable; 555 if (dr != null) { 556 dr.setTintList(tint); 557 } 558 } 559 } 560 561 @Override setTintMode(Mode tintMode)562 public void setTintMode(Mode tintMode) { 563 final ChildDrawable[] array = mLayerState.mChildren; 564 final int N = mLayerState.N_CHILDREN; 565 for (int i = 0; i < N; i++) { 566 final Drawable dr = array[i].mDrawable; 567 if (dr != null) { 568 dr.setTintMode(tintMode); 569 } 570 } 571 } 572 setOpacity(int opacity)573 public void setOpacity(int opacity) { 574 mLayerState.mOpacityOverride = opacity; 575 } 576 577 @Override getOpacity()578 public int getOpacity() { 579 return PixelFormat.TRANSLUCENT; 580 } 581 582 @Override setAutoMirrored(boolean mirrored)583 public void setAutoMirrored(boolean mirrored) { 584 mLayerState.mAutoMirrored = mirrored; 585 586 final ChildDrawable[] array = mLayerState.mChildren; 587 for (int i = 0; i < mLayerState.N_CHILDREN; i++) { 588 final Drawable dr = array[i].mDrawable; 589 if (dr != null) { 590 dr.setAutoMirrored(mirrored); 591 } 592 } 593 } 594 595 @Override isAutoMirrored()596 public boolean isAutoMirrored() { 597 return mLayerState.mAutoMirrored; 598 } 599 600 @Override jumpToCurrentState()601 public void jumpToCurrentState() { 602 final ChildDrawable[] array = mLayerState.mChildren; 603 for (int i = 0; i < mLayerState.N_CHILDREN; i++) { 604 final Drawable dr = array[i].mDrawable; 605 if (dr != null) { 606 dr.jumpToCurrentState(); 607 } 608 } 609 } 610 611 @Override isStateful()612 public boolean isStateful() { 613 return mLayerState.isStateful(); 614 } 615 616 @Override onStateChange(int[] state)617 protected boolean onStateChange(int[] state) { 618 boolean changed = false; 619 620 final ChildDrawable[] array = mLayerState.mChildren; 621 for (int i = 0; i < mLayerState.N_CHILDREN; i++) { 622 final Drawable dr = array[i].mDrawable; 623 if (dr != null && dr.isStateful() && dr.setState(state)) { 624 changed = true; 625 } 626 } 627 628 if (changed) { 629 updateLayerBounds(getBounds()); 630 } 631 632 return changed; 633 } 634 635 @Override onLevelChange(int level)636 protected boolean onLevelChange(int level) { 637 boolean changed = false; 638 639 final ChildDrawable[] array = mLayerState.mChildren; 640 for (int i = 0; i < mLayerState.N_CHILDREN; i++) { 641 final Drawable dr = array[i].mDrawable; 642 if (dr != null && dr.setLevel(level)) { 643 changed = true; 644 } 645 } 646 647 if (changed) { 648 updateLayerBounds(getBounds()); 649 } 650 651 return changed; 652 } 653 654 @Override getIntrinsicWidth()655 public int getIntrinsicWidth() { 656 return (int)(getMaxIntrinsicWidth() * DEFAULT_VIEW_PORT_SCALE); 657 } 658 getMaxIntrinsicWidth()659 private int getMaxIntrinsicWidth() { 660 int width = -1; 661 for (int i = 0; i < mLayerState.N_CHILDREN; i++) { 662 final ChildDrawable r = mLayerState.mChildren[i]; 663 if (r.mDrawable == null) { 664 continue; 665 } 666 final int w = r.mDrawable.getIntrinsicWidth(); 667 if (w > width) { 668 width = w; 669 } 670 } 671 return width; 672 } 673 674 @Override getIntrinsicHeight()675 public int getIntrinsicHeight() { 676 return (int)(getMaxIntrinsicHeight() * DEFAULT_VIEW_PORT_SCALE); 677 } 678 getMaxIntrinsicHeight()679 private int getMaxIntrinsicHeight() { 680 int height = -1; 681 for (int i = 0; i < mLayerState.N_CHILDREN; i++) { 682 final ChildDrawable r = mLayerState.mChildren[i]; 683 if (r.mDrawable == null) { 684 continue; 685 } 686 final int h = r.mDrawable.getIntrinsicHeight(); 687 if (h > height) { 688 height = h; 689 } 690 } 691 return height; 692 } 693 694 @Override getConstantState()695 public ConstantState getConstantState() { 696 if (mLayerState.canConstantState()) { 697 mLayerState.mChangingConfigurations = getChangingConfigurations(); 698 return mLayerState; 699 } 700 return null; 701 } 702 703 @Override mutate()704 public Drawable mutate() { 705 if (!mMutated && super.mutate() == this) { 706 mLayerState = createConstantState(mLayerState, null); 707 for (int i = 0; i < mLayerState.N_CHILDREN; i++) { 708 final Drawable dr = mLayerState.mChildren[i].mDrawable; 709 if (dr != null) { 710 dr.mutate(); 711 } 712 } 713 mMutated = true; 714 } 715 return this; 716 } 717 718 static class ChildDrawable { 719 public Drawable mDrawable; 720 public int[] mThemeAttrs; 721 public int mDensity = DisplayMetrics.DENSITY_DEFAULT; 722 ChildDrawable(int density)723 ChildDrawable(int density) { 724 mDensity = density; 725 } 726 ChildDrawable(@onNull ChildDrawable orig, @NonNull DynamicAdaptiveIconDrawable owner, @Nullable Resources res)727 ChildDrawable(@NonNull ChildDrawable orig, @NonNull DynamicAdaptiveIconDrawable owner, 728 @Nullable Resources res) { 729 730 final Drawable dr = orig.mDrawable; 731 final Drawable clone; 732 if (dr != null) { 733 final ConstantState cs = dr.getConstantState(); 734 if (cs == null) { 735 clone = dr; 736 } else if (res != null) { 737 clone = cs.newDrawable(res); 738 } else { 739 clone = cs.newDrawable(); 740 } 741 clone.setCallback(owner); 742 clone.setBounds(dr.getBounds()); 743 clone.setLevel(dr.getLevel()); 744 } else { 745 clone = null; 746 } 747 748 mDrawable = clone; 749 mThemeAttrs = orig.mThemeAttrs; 750 } 751 canApplyTheme()752 public boolean canApplyTheme() { 753 return mThemeAttrs != null 754 || (mDrawable != null && mDrawable.canApplyTheme()); 755 } 756 setDensity(int targetDensity)757 public final void setDensity(int targetDensity) { 758 if (mDensity != targetDensity) { 759 mDensity = targetDensity; 760 } 761 } 762 } 763 764 static class LayerState extends ConstantState { 765 private final DynamicAdaptiveIconDrawable mOwner; 766 767 private int[] mThemeAttrs; 768 769 final static int N_CHILDREN = 2; 770 ChildDrawable[] mChildren; 771 772 // The density at which to render the drawable and its children. 773 int mDensity; 774 775 // The density to use when inflating/looking up the children drawables. A value of 0 means 776 // use the system's density. 777 int mSrcDensityOverride = 0; 778 779 int mOpacityOverride = PixelFormat.UNKNOWN; 780 781 int mChangingConfigurations; 782 int mChildrenChangingConfigurations; 783 784 private boolean mCheckedOpacity; 785 private int mOpacity; 786 787 private boolean mCheckedStateful; 788 private boolean mIsStateful; 789 private boolean mAutoMirrored = false; 790 LayerState(@ullable LayerState orig, @NonNull DynamicAdaptiveIconDrawable owner, @Nullable Resources res)791 LayerState(@Nullable LayerState orig, @NonNull DynamicAdaptiveIconDrawable owner, 792 @Nullable Resources res) { 793 mOwner = owner; 794 mChildren = new ChildDrawable[N_CHILDREN]; 795 if (orig != null) { 796 final ChildDrawable[] origChildDrawable = orig.mChildren; 797 798 mChangingConfigurations = orig.mChangingConfigurations; 799 mChildrenChangingConfigurations = orig.mChildrenChangingConfigurations; 800 801 for (int i = 0; i < N_CHILDREN; i++) { 802 final ChildDrawable or = origChildDrawable[i]; 803 mChildren[i] = new ChildDrawable(or, mOwner, res); 804 } 805 806 mCheckedOpacity = orig.mCheckedOpacity; 807 mOpacity = orig.mOpacity; 808 mCheckedStateful = orig.mCheckedStateful; 809 mIsStateful = orig.mIsStateful; 810 mAutoMirrored = orig.mAutoMirrored; 811 mThemeAttrs = orig.mThemeAttrs; 812 mOpacityOverride = orig.mOpacityOverride; 813 mSrcDensityOverride = orig.mSrcDensityOverride; 814 } else { 815 for (int i = 0; i < N_CHILDREN; i++) { 816 mChildren[i] = new ChildDrawable(mDensity); 817 } 818 } 819 } 820 setDensity(int targetDensity)821 public final void setDensity(int targetDensity) { 822 if (mDensity != targetDensity) { 823 mDensity = targetDensity; 824 } 825 } 826 827 @Override canApplyTheme()828 public boolean canApplyTheme() { 829 if (mThemeAttrs != null || super.canApplyTheme()) { 830 return true; 831 } 832 833 final ChildDrawable[] array = mChildren; 834 for (int i = 0; i < N_CHILDREN; i++) { 835 final ChildDrawable layer = array[i]; 836 if (layer.canApplyTheme()) { 837 return true; 838 } 839 } 840 return false; 841 } 842 843 @Override newDrawable()844 public Drawable newDrawable() { 845 return new DynamicAdaptiveIconDrawable(mOwner.getBackground(), mOwner.getForeground(), 846 mOwner.mOriginalMask); 847 } 848 849 @Override newDrawable(@ullable Resources res)850 public Drawable newDrawable(@Nullable Resources res) { 851 return newDrawable(); 852 } 853 854 @Override getChangingConfigurations()855 public int getChangingConfigurations() { 856 return mChangingConfigurations 857 | mChildrenChangingConfigurations; 858 } 859 getOpacity()860 public final int getOpacity() { 861 if (mCheckedOpacity) { 862 return mOpacity; 863 } 864 865 final ChildDrawable[] array = mChildren; 866 867 // Seek to the first non-null drawable. 868 int firstIndex = -1; 869 for (int i = 0; i < N_CHILDREN; i++) { 870 if (array[i].mDrawable != null) { 871 firstIndex = i; 872 break; 873 } 874 } 875 876 int op; 877 if (firstIndex >= 0) { 878 op = array[firstIndex].mDrawable.getOpacity(); 879 } else { 880 op = PixelFormat.TRANSPARENT; 881 } 882 883 // Merge all remaining non-null drawables. 884 for (int i = firstIndex + 1; i < N_CHILDREN; i++) { 885 final Drawable dr = array[i].mDrawable; 886 if (dr != null) { 887 op = Drawable.resolveOpacity(op, dr.getOpacity()); 888 } 889 } 890 891 mOpacity = op; 892 mCheckedOpacity = true; 893 return op; 894 } 895 isStateful()896 public final boolean isStateful() { 897 if (mCheckedStateful) { 898 return mIsStateful; 899 } 900 901 final ChildDrawable[] array = mChildren; 902 boolean isStateful = false; 903 for (int i = 0; i < N_CHILDREN; i++) { 904 final Drawable dr = array[i].mDrawable; 905 if (dr != null && dr.isStateful()) { 906 isStateful = true; 907 break; 908 } 909 } 910 911 mIsStateful = isStateful; 912 mCheckedStateful = true; 913 return isStateful; 914 } 915 canConstantState()916 public final boolean canConstantState() { 917 final ChildDrawable[] array = mChildren; 918 for (int i = 0; i < N_CHILDREN; i++) { 919 final Drawable dr = array[i].mDrawable; 920 if (dr != null && dr.getConstantState() == null) { 921 return false; 922 } 923 } 924 925 // Don't cache the result, this method is not called very often. 926 return true; 927 } 928 invalidateCache()929 public void invalidateCache() { 930 mCheckedOpacity = false; 931 mCheckedStateful = false; 932 } 933 } 934 }