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