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