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