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.app.ActivityThread; 23 import android.content.pm.ActivityInfo.Config; 24 import android.content.res.ColorStateList; 25 import android.content.res.Resources; 26 import android.content.res.Resources.Theme; 27 import android.content.res.TypedArray; 28 import android.graphics.Bitmap; 29 import android.graphics.BitmapShader; 30 import android.graphics.BlendMode; 31 import android.graphics.Canvas; 32 import android.graphics.Color; 33 import android.graphics.ColorFilter; 34 import android.graphics.Matrix; 35 import android.graphics.Outline; 36 import android.graphics.Paint; 37 import android.graphics.Path; 38 import android.graphics.PixelFormat; 39 import android.graphics.Rect; 40 import android.graphics.Region; 41 import android.graphics.Shader; 42 import android.graphics.Shader.TileMode; 43 import android.util.AttributeSet; 44 import android.util.DisplayMetrics; 45 import android.util.PathParser; 46 47 import com.android.internal.R; 48 49 import org.xmlpull.v1.XmlPullParser; 50 import org.xmlpull.v1.XmlPullParserException; 51 52 import 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 */ 77 public 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 @TestApi 84 public static final float MASK_SIZE = 100f; 85 86 /** 87 * Launcher icons design guideline 88 */ 89 private static final float SAFEZONE_SCALE = 66f/72f; 90 91 /** 92 * All four sides of the layers are padded with extra inset so as to provide 93 * extra content to reveal within the clip path when performing affine transformations on the 94 * layers. 95 * 96 * Each layers will reserve 25% of it's width and height. 97 * 98 * As a result, the view port of the layers is smaller than their intrinsic width and height. 99 */ 100 private static final float EXTRA_INSET_PERCENTAGE = 1 / 4f; 101 private static final float DEFAULT_VIEW_PORT_SCALE = 1f / (1 + 2 * EXTRA_INSET_PERCENTAGE); 102 103 /** 104 * Clip path defined in R.string.config_icon_mask. 105 */ 106 private static Path sMask; 107 108 /** 109 * Scaled mask based on the view bounds. 110 */ 111 private final Path mMask; 112 private final Path mMaskScaleOnly; 113 private final Matrix mMaskMatrix; 114 private final Region mTransparentRegion; 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 // config_icon_mask from context bound resource may have been chaged using 155 // OverlayManager. Read that one first. 156 Resources r = ActivityThread.currentActivityThread() == null 157 ? Resources.getSystem() 158 : ActivityThread.currentActivityThread().getApplication().getResources(); 159 // TODO: either make sMask update only when config_icon_mask changes OR 160 // get rid of it all-together in layoutlib 161 sMask = PathParser.createPathFromPathData(r.getString(R.string.config_icon_mask)); 162 mMask = new Path(sMask); 163 mMaskScaleOnly = new Path(mMask); 164 mMaskMatrix = new Matrix(); 165 mCanvas = new Canvas(); 166 mTransparentRegion = new Region(); 167 } 168 createChildDrawable(Drawable drawable)169 private ChildDrawable createChildDrawable(Drawable drawable) { 170 final ChildDrawable layer = new ChildDrawable(mLayerState.mDensity); 171 layer.mDrawable = drawable; 172 layer.mDrawable.setCallback(this); 173 mLayerState.mChildrenChangingConfigurations |= 174 layer.mDrawable.getChangingConfigurations(); 175 return layer; 176 } 177 createConstantState(@ullable LayerState state, @Nullable Resources res)178 LayerState createConstantState(@Nullable LayerState state, @Nullable Resources res) { 179 return new LayerState(state, this, res); 180 } 181 182 /** 183 * Constructor used to dynamically create this drawable. 184 * 185 * @param backgroundDrawable drawable that should be rendered in the background 186 * @param foregroundDrawable drawable that should be rendered in the foreground 187 */ AdaptiveIconDrawable(Drawable backgroundDrawable, Drawable foregroundDrawable)188 public AdaptiveIconDrawable(Drawable backgroundDrawable, 189 Drawable foregroundDrawable) { 190 this((LayerState)null, null); 191 if (backgroundDrawable != null) { 192 addLayer(BACKGROUND_ID, createChildDrawable(backgroundDrawable)); 193 } 194 if (foregroundDrawable != null) { 195 addLayer(FOREGROUND_ID, createChildDrawable(foregroundDrawable)); 196 } 197 } 198 199 /** 200 * Sets the layer to the {@param index} and invalidates cache. 201 * 202 * @param index The index of the layer. 203 * @param layer The layer to add. 204 */ addLayer(int index, @NonNull ChildDrawable layer)205 private void addLayer(int index, @NonNull ChildDrawable layer) { 206 mLayerState.mChildren[index] = layer; 207 mLayerState.invalidateCache(); 208 } 209 210 @Override inflate(@onNull Resources r, @NonNull XmlPullParser parser, @NonNull AttributeSet attrs, @Nullable Theme theme)211 public void inflate(@NonNull Resources r, @NonNull XmlPullParser parser, 212 @NonNull AttributeSet attrs, @Nullable Theme theme) 213 throws XmlPullParserException, IOException { 214 super.inflate(r, parser, attrs, theme); 215 216 final LayerState state = mLayerState; 217 if (state == null) { 218 return; 219 } 220 221 // The density may have changed since the last update. This will 222 // apply scaling to any existing constant state properties. 223 final int deviceDensity = Drawable.resolveDensity(r, 0); 224 state.setDensity(deviceDensity); 225 state.mSrcDensityOverride = mSrcDensityOverride; 226 227 final ChildDrawable[] array = state.mChildren; 228 for (int i = 0; i < state.mChildren.length; i++) { 229 final ChildDrawable layer = array[i]; 230 layer.setDensity(deviceDensity); 231 } 232 233 inflateLayers(r, parser, attrs, theme); 234 } 235 236 /** 237 * All four sides of the layers are padded with extra inset so as to provide 238 * extra content to reveal within the clip path when performing affine transformations on the 239 * layers. 240 * 241 * @see #getForeground() and #getBackground() for more info on how this value is used 242 */ getExtraInsetFraction()243 public static float getExtraInsetFraction() { 244 return EXTRA_INSET_PERCENTAGE; 245 } 246 247 /** 248 * @hide 249 */ getExtraInsetPercentage()250 public static float getExtraInsetPercentage() { 251 return EXTRA_INSET_PERCENTAGE; 252 } 253 254 /** 255 * When called before the bound is set, the returned path is identical to 256 * R.string.config_icon_mask. After the bound is set, the 257 * returned path's computed bound is same as the #getBounds(). 258 * 259 * @return the mask path object used to clip the drawable 260 */ getIconMask()261 public Path getIconMask() { 262 return mMask; 263 } 264 265 /** 266 * Returns the foreground drawable managed by this class. The bound of this drawable is 267 * extended by {@link #getExtraInsetFraction()} * getBounds().width on left/right sides and by 268 * {@link #getExtraInsetFraction()} * getBounds().height on top/bottom sides. 269 * 270 * @return the foreground drawable managed by this drawable 271 */ getForeground()272 public Drawable getForeground() { 273 return mLayerState.mChildren[FOREGROUND_ID].mDrawable; 274 } 275 276 /** 277 * Returns the foreground drawable managed by this class. The bound of this drawable is 278 * extended by {@link #getExtraInsetFraction()} * getBounds().width on left/right sides and by 279 * {@link #getExtraInsetFraction()} * getBounds().height on top/bottom sides. 280 * 281 * @return the background drawable managed by this drawable 282 */ getBackground()283 public Drawable getBackground() { 284 return mLayerState.mChildren[BACKGROUND_ID].mDrawable; 285 } 286 287 @Override onBoundsChange(Rect bounds)288 protected void onBoundsChange(Rect bounds) { 289 if (bounds.isEmpty()) { 290 return; 291 } 292 updateLayerBounds(bounds); 293 } 294 updateLayerBounds(Rect bounds)295 private void updateLayerBounds(Rect bounds) { 296 if (bounds.isEmpty()) { 297 return; 298 } 299 try { 300 suspendChildInvalidation(); 301 updateLayerBoundsInternal(bounds); 302 updateMaskBoundsInternal(bounds); 303 } finally { 304 resumeChildInvalidation(); 305 } 306 } 307 308 /** 309 * Set the child layer bounds bigger than the view port size by {@link #DEFAULT_VIEW_PORT_SCALE} 310 */ updateLayerBoundsInternal(Rect bounds)311 private void updateLayerBoundsInternal(Rect bounds) { 312 int cX = bounds.width() / 2; 313 int cY = bounds.height() / 2; 314 315 for (int i = 0, count = mLayerState.N_CHILDREN; i < count; i++) { 316 final ChildDrawable r = mLayerState.mChildren[i]; 317 if (r == null) { 318 continue; 319 } 320 final Drawable d = r.mDrawable; 321 if (d == null) { 322 continue; 323 } 324 325 int insetWidth = (int) (bounds.width() / (DEFAULT_VIEW_PORT_SCALE * 2)); 326 int insetHeight = (int) (bounds.height() / (DEFAULT_VIEW_PORT_SCALE * 2)); 327 final Rect outRect = mTmpOutRect; 328 outRect.set(cX - insetWidth, cY - insetHeight, cX + insetWidth, cY + insetHeight); 329 330 d.setBounds(outRect); 331 } 332 } 333 updateMaskBoundsInternal(Rect b)334 private void updateMaskBoundsInternal(Rect b) { 335 // reset everything that depends on the view bounds 336 mMaskMatrix.setScale(b.width() / MASK_SIZE, b.height() / MASK_SIZE); 337 sMask.transform(mMaskMatrix, mMaskScaleOnly); 338 339 mMaskMatrix.postTranslate(b.left, b.top); 340 sMask.transform(mMaskMatrix, mMask); 341 342 if (mLayersBitmap == null || mLayersBitmap.getWidth() != b.width() 343 || mLayersBitmap.getHeight() != b.height()) { 344 mLayersBitmap = Bitmap.createBitmap(b.width(), b.height(), Bitmap.Config.ARGB_8888); 345 } 346 347 mPaint.setShader(null); 348 mTransparentRegion.setEmpty(); 349 mLayersShader = null; 350 } 351 352 @Override draw(Canvas canvas)353 public void draw(Canvas canvas) { 354 if (mLayersBitmap == null) { 355 return; 356 } 357 if (mLayersShader == null) { 358 mCanvas.setBitmap(mLayersBitmap); 359 mCanvas.drawColor(Color.BLACK); 360 for (int i = 0; i < mLayerState.N_CHILDREN; i++) { 361 if (mLayerState.mChildren[i] == null) { 362 continue; 363 } 364 final Drawable dr = mLayerState.mChildren[i].mDrawable; 365 if (dr != null) { 366 dr.draw(mCanvas); 367 } 368 } 369 mLayersShader = new BitmapShader(mLayersBitmap, TileMode.CLAMP, TileMode.CLAMP); 370 mPaint.setShader(mLayersShader); 371 } 372 if (mMaskScaleOnly != null) { 373 Rect bounds = getBounds(); 374 canvas.translate(bounds.left, bounds.top); 375 canvas.drawPath(mMaskScaleOnly, mPaint); 376 canvas.translate(-bounds.left, -bounds.top); 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 != null && 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 mPaint.getAlpha(); 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 setTintBlendMode(@onNull BlendMode blendMode)704 public void setTintBlendMode(@NonNull BlendMode blendMode) { 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.setTintBlendMode(blendMode); 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 return PixelFormat.TRANSLUCENT; 722 } 723 724 @Override setAutoMirrored(boolean mirrored)725 public void setAutoMirrored(boolean mirrored) { 726 mLayerState.mAutoMirrored = mirrored; 727 728 final ChildDrawable[] array = mLayerState.mChildren; 729 for (int i = 0; i < mLayerState.N_CHILDREN; i++) { 730 final Drawable dr = array[i].mDrawable; 731 if (dr != null) { 732 dr.setAutoMirrored(mirrored); 733 } 734 } 735 } 736 737 @Override isAutoMirrored()738 public boolean isAutoMirrored() { 739 return mLayerState.mAutoMirrored; 740 } 741 742 @Override jumpToCurrentState()743 public void jumpToCurrentState() { 744 final ChildDrawable[] array = mLayerState.mChildren; 745 for (int i = 0; i < mLayerState.N_CHILDREN; i++) { 746 final Drawable dr = array[i].mDrawable; 747 if (dr != null) { 748 dr.jumpToCurrentState(); 749 } 750 } 751 } 752 753 @Override isStateful()754 public boolean isStateful() { 755 return mLayerState.isStateful(); 756 } 757 758 /** @hide */ 759 @Override hasFocusStateSpecified()760 public boolean hasFocusStateSpecified() { 761 return mLayerState.hasFocusStateSpecified(); 762 } 763 764 @Override onStateChange(int[] state)765 protected boolean onStateChange(int[] state) { 766 boolean changed = false; 767 768 final ChildDrawable[] array = mLayerState.mChildren; 769 for (int i = 0; i < mLayerState.N_CHILDREN; i++) { 770 final Drawable dr = array[i].mDrawable; 771 if (dr != null && dr.isStateful() && dr.setState(state)) { 772 changed = true; 773 } 774 } 775 776 if (changed) { 777 updateLayerBounds(getBounds()); 778 } 779 780 return changed; 781 } 782 783 @Override onLevelChange(int level)784 protected boolean onLevelChange(int level) { 785 boolean changed = false; 786 787 final ChildDrawable[] array = mLayerState.mChildren; 788 for (int i = 0; i < mLayerState.N_CHILDREN; i++) { 789 final Drawable dr = array[i].mDrawable; 790 if (dr != null && dr.setLevel(level)) { 791 changed = true; 792 } 793 } 794 795 if (changed) { 796 updateLayerBounds(getBounds()); 797 } 798 799 return changed; 800 } 801 802 @Override getIntrinsicWidth()803 public int getIntrinsicWidth() { 804 return (int)(getMaxIntrinsicWidth() * DEFAULT_VIEW_PORT_SCALE); 805 } 806 getMaxIntrinsicWidth()807 private int getMaxIntrinsicWidth() { 808 int width = -1; 809 for (int i = 0; i < mLayerState.N_CHILDREN; i++) { 810 final ChildDrawable r = mLayerState.mChildren[i]; 811 if (r.mDrawable == null) { 812 continue; 813 } 814 final int w = r.mDrawable.getIntrinsicWidth(); 815 if (w > width) { 816 width = w; 817 } 818 } 819 return width; 820 } 821 822 @Override getIntrinsicHeight()823 public int getIntrinsicHeight() { 824 return (int)(getMaxIntrinsicHeight() * DEFAULT_VIEW_PORT_SCALE); 825 } 826 getMaxIntrinsicHeight()827 private int getMaxIntrinsicHeight() { 828 int height = -1; 829 for (int i = 0; i < mLayerState.N_CHILDREN; i++) { 830 final ChildDrawable r = mLayerState.mChildren[i]; 831 if (r.mDrawable == null) { 832 continue; 833 } 834 final int h = r.mDrawable.getIntrinsicHeight(); 835 if (h > height) { 836 height = h; 837 } 838 } 839 return height; 840 } 841 842 @Override getConstantState()843 public ConstantState getConstantState() { 844 if (mLayerState.canConstantState()) { 845 mLayerState.mChangingConfigurations = getChangingConfigurations(); 846 return mLayerState; 847 } 848 return null; 849 } 850 851 @Override mutate()852 public Drawable mutate() { 853 if (!mMutated && super.mutate() == this) { 854 mLayerState = createConstantState(mLayerState, null); 855 for (int i = 0; i < mLayerState.N_CHILDREN; i++) { 856 final Drawable dr = mLayerState.mChildren[i].mDrawable; 857 if (dr != null) { 858 dr.mutate(); 859 } 860 } 861 mMutated = true; 862 } 863 return this; 864 } 865 866 /** 867 * @hide 868 */ clearMutated()869 public void clearMutated() { 870 super.clearMutated(); 871 final ChildDrawable[] array = mLayerState.mChildren; 872 for (int i = 0; i < mLayerState.N_CHILDREN; i++) { 873 final Drawable dr = array[i].mDrawable; 874 if (dr != null) { 875 dr.clearMutated(); 876 } 877 } 878 mMutated = false; 879 } 880 881 static class ChildDrawable { 882 public Drawable mDrawable; 883 public int[] mThemeAttrs; 884 public int mDensity = DisplayMetrics.DENSITY_DEFAULT; 885 ChildDrawable(int density)886 ChildDrawable(int density) { 887 mDensity = density; 888 } 889 ChildDrawable(@onNull ChildDrawable orig, @NonNull AdaptiveIconDrawable owner, @Nullable Resources res)890 ChildDrawable(@NonNull ChildDrawable orig, @NonNull AdaptiveIconDrawable owner, 891 @Nullable Resources res) { 892 893 final Drawable dr = orig.mDrawable; 894 final Drawable clone; 895 if (dr != null) { 896 final ConstantState cs = dr.getConstantState(); 897 if (cs == null) { 898 clone = dr; 899 } else if (res != null) { 900 clone = cs.newDrawable(res); 901 } else { 902 clone = cs.newDrawable(); 903 } 904 clone.setCallback(owner); 905 clone.setBounds(dr.getBounds()); 906 clone.setLevel(dr.getLevel()); 907 } else { 908 clone = null; 909 } 910 911 mDrawable = clone; 912 mThemeAttrs = orig.mThemeAttrs; 913 914 mDensity = Drawable.resolveDensity(res, orig.mDensity); 915 } 916 canApplyTheme()917 public boolean canApplyTheme() { 918 return mThemeAttrs != null 919 || (mDrawable != null && mDrawable.canApplyTheme()); 920 } 921 setDensity(int targetDensity)922 public final void setDensity(int targetDensity) { 923 if (mDensity != targetDensity) { 924 mDensity = targetDensity; 925 } 926 } 927 } 928 929 static class LayerState extends ConstantState { 930 private int[] mThemeAttrs; 931 932 final static int N_CHILDREN = 2; 933 ChildDrawable[] mChildren; 934 935 // The density at which to render the drawable and its children. 936 int mDensity; 937 938 // The density to use when inflating/looking up the children drawables. A value of 0 means 939 // use the system's density. 940 int mSrcDensityOverride = 0; 941 942 int mOpacityOverride = PixelFormat.UNKNOWN; 943 944 @Config int mChangingConfigurations; 945 @Config int mChildrenChangingConfigurations; 946 947 private boolean mCheckedOpacity; 948 private int mOpacity; 949 950 private boolean mCheckedStateful; 951 private boolean mIsStateful; 952 private boolean mAutoMirrored = false; 953 LayerState(@ullable LayerState orig, @NonNull AdaptiveIconDrawable owner, @Nullable Resources res)954 LayerState(@Nullable LayerState orig, @NonNull AdaptiveIconDrawable owner, 955 @Nullable Resources res) { 956 mDensity = Drawable.resolveDensity(res, orig != null ? orig.mDensity : 0); 957 mChildren = new ChildDrawable[N_CHILDREN]; 958 if (orig != null) { 959 final ChildDrawable[] origChildDrawable = orig.mChildren; 960 961 mChangingConfigurations = orig.mChangingConfigurations; 962 mChildrenChangingConfigurations = orig.mChildrenChangingConfigurations; 963 964 for (int i = 0; i < N_CHILDREN; i++) { 965 final ChildDrawable or = origChildDrawable[i]; 966 mChildren[i] = new ChildDrawable(or, owner, res); 967 } 968 969 mCheckedOpacity = orig.mCheckedOpacity; 970 mOpacity = orig.mOpacity; 971 mCheckedStateful = orig.mCheckedStateful; 972 mIsStateful = orig.mIsStateful; 973 mAutoMirrored = orig.mAutoMirrored; 974 mThemeAttrs = orig.mThemeAttrs; 975 mOpacityOverride = orig.mOpacityOverride; 976 mSrcDensityOverride = orig.mSrcDensityOverride; 977 } else { 978 for (int i = 0; i < N_CHILDREN; i++) { 979 mChildren[i] = new ChildDrawable(mDensity); 980 } 981 } 982 } 983 setDensity(int targetDensity)984 public final void setDensity(int targetDensity) { 985 if (mDensity != targetDensity) { 986 mDensity = targetDensity; 987 } 988 } 989 990 @Override canApplyTheme()991 public boolean canApplyTheme() { 992 if (mThemeAttrs != null || super.canApplyTheme()) { 993 return true; 994 } 995 996 final ChildDrawable[] array = mChildren; 997 for (int i = 0; i < N_CHILDREN; i++) { 998 final ChildDrawable layer = array[i]; 999 if (layer.canApplyTheme()) { 1000 return true; 1001 } 1002 } 1003 return false; 1004 } 1005 1006 @Override newDrawable()1007 public Drawable newDrawable() { 1008 return new AdaptiveIconDrawable(this, null); 1009 } 1010 1011 @Override newDrawable(@ullable Resources res)1012 public Drawable newDrawable(@Nullable Resources res) { 1013 return new AdaptiveIconDrawable(this, res); 1014 } 1015 1016 @Override getChangingConfigurations()1017 public @Config int getChangingConfigurations() { 1018 return mChangingConfigurations 1019 | mChildrenChangingConfigurations; 1020 } 1021 getOpacity()1022 public final int getOpacity() { 1023 if (mCheckedOpacity) { 1024 return mOpacity; 1025 } 1026 1027 final ChildDrawable[] array = mChildren; 1028 1029 // Seek to the first non-null drawable. 1030 int firstIndex = -1; 1031 for (int i = 0; i < N_CHILDREN; i++) { 1032 if (array[i].mDrawable != null) { 1033 firstIndex = i; 1034 break; 1035 } 1036 } 1037 1038 int op; 1039 if (firstIndex >= 0) { 1040 op = array[firstIndex].mDrawable.getOpacity(); 1041 } else { 1042 op = PixelFormat.TRANSPARENT; 1043 } 1044 1045 // Merge all remaining non-null drawables. 1046 for (int i = firstIndex + 1; i < N_CHILDREN; i++) { 1047 final Drawable dr = array[i].mDrawable; 1048 if (dr != null) { 1049 op = Drawable.resolveOpacity(op, dr.getOpacity()); 1050 } 1051 } 1052 1053 mOpacity = op; 1054 mCheckedOpacity = true; 1055 return op; 1056 } 1057 isStateful()1058 public final boolean isStateful() { 1059 if (mCheckedStateful) { 1060 return mIsStateful; 1061 } 1062 1063 final ChildDrawable[] array = mChildren; 1064 boolean isStateful = false; 1065 for (int i = 0; i < N_CHILDREN; i++) { 1066 final Drawable dr = array[i].mDrawable; 1067 if (dr != null && dr.isStateful()) { 1068 isStateful = true; 1069 break; 1070 } 1071 } 1072 1073 mIsStateful = isStateful; 1074 mCheckedStateful = true; 1075 return isStateful; 1076 } 1077 hasFocusStateSpecified()1078 public final boolean hasFocusStateSpecified() { 1079 final ChildDrawable[] array = mChildren; 1080 for (int i = 0; i < N_CHILDREN; i++) { 1081 final Drawable dr = array[i].mDrawable; 1082 if (dr != null && dr.hasFocusStateSpecified()) { 1083 return true; 1084 } 1085 } 1086 return false; 1087 } 1088 canConstantState()1089 public final boolean canConstantState() { 1090 final ChildDrawable[] array = mChildren; 1091 for (int i = 0; i < N_CHILDREN; i++) { 1092 final Drawable dr = array[i].mDrawable; 1093 if (dr != null && dr.getConstantState() == null) { 1094 return false; 1095 } 1096 } 1097 1098 // Don't cache the result, this method is not called very often. 1099 return true; 1100 } 1101 invalidateCache()1102 public void invalidateCache() { 1103 mCheckedOpacity = false; 1104 mCheckedStateful = false; 1105 } 1106 } 1107 } 1108