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