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