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