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 @Override hasFocusStateSpecified()772 public boolean hasFocusStateSpecified() { 773 return mLayerState.hasFocusStateSpecified(); 774 } 775 776 @Override onStateChange(int[] state)777 protected boolean onStateChange(int[] state) { 778 boolean changed = false; 779 780 final ChildDrawable[] array = mLayerState.mChildren; 781 for (int i = 0; i < mLayerState.N_CHILDREN; i++) { 782 final Drawable dr = array[i].mDrawable; 783 if (dr != null && dr.isStateful() && dr.setState(state)) { 784 changed = true; 785 } 786 } 787 788 if (changed) { 789 updateLayerBounds(getBounds()); 790 } 791 792 return changed; 793 } 794 795 @Override onLevelChange(int level)796 protected boolean onLevelChange(int level) { 797 boolean changed = false; 798 799 final ChildDrawable[] array = mLayerState.mChildren; 800 for (int i = 0; i < mLayerState.N_CHILDREN; i++) { 801 final Drawable dr = array[i].mDrawable; 802 if (dr != null && dr.setLevel(level)) { 803 changed = true; 804 } 805 } 806 807 if (changed) { 808 updateLayerBounds(getBounds()); 809 } 810 811 return changed; 812 } 813 814 @Override getIntrinsicWidth()815 public int getIntrinsicWidth() { 816 return (int)(getMaxIntrinsicWidth() * DEFAULT_VIEW_PORT_SCALE); 817 } 818 getMaxIntrinsicWidth()819 private int getMaxIntrinsicWidth() { 820 int width = -1; 821 for (int i = 0; i < mLayerState.N_CHILDREN; i++) { 822 final ChildDrawable r = mLayerState.mChildren[i]; 823 if (r.mDrawable == null) { 824 continue; 825 } 826 final int w = r.mDrawable.getIntrinsicWidth(); 827 if (w > width) { 828 width = w; 829 } 830 } 831 return width; 832 } 833 834 @Override getIntrinsicHeight()835 public int getIntrinsicHeight() { 836 return (int)(getMaxIntrinsicHeight() * DEFAULT_VIEW_PORT_SCALE); 837 } 838 getMaxIntrinsicHeight()839 private int getMaxIntrinsicHeight() { 840 int height = -1; 841 for (int i = 0; i < mLayerState.N_CHILDREN; i++) { 842 final ChildDrawable r = mLayerState.mChildren[i]; 843 if (r.mDrawable == null) { 844 continue; 845 } 846 final int h = r.mDrawable.getIntrinsicHeight(); 847 if (h > height) { 848 height = h; 849 } 850 } 851 return height; 852 } 853 854 @Override getConstantState()855 public ConstantState getConstantState() { 856 if (mLayerState.canConstantState()) { 857 mLayerState.mChangingConfigurations = getChangingConfigurations(); 858 return mLayerState; 859 } 860 return null; 861 } 862 863 @Override mutate()864 public Drawable mutate() { 865 if (!mMutated && super.mutate() == this) { 866 mLayerState = createConstantState(mLayerState, null); 867 for (int i = 0; i < mLayerState.N_CHILDREN; i++) { 868 final Drawable dr = mLayerState.mChildren[i].mDrawable; 869 if (dr != null) { 870 dr.mutate(); 871 } 872 } 873 mMutated = true; 874 } 875 return this; 876 } 877 878 /** 879 * @hide 880 */ clearMutated()881 public void clearMutated() { 882 super.clearMutated(); 883 final ChildDrawable[] array = mLayerState.mChildren; 884 for (int i = 0; i < mLayerState.N_CHILDREN; i++) { 885 final Drawable dr = array[i].mDrawable; 886 if (dr != null) { 887 dr.clearMutated(); 888 } 889 } 890 mMutated = false; 891 } 892 893 static class ChildDrawable { 894 public Drawable mDrawable; 895 public int[] mThemeAttrs; 896 public int mDensity = DisplayMetrics.DENSITY_DEFAULT; 897 ChildDrawable(int density)898 ChildDrawable(int density) { 899 mDensity = density; 900 } 901 ChildDrawable(@onNull ChildDrawable orig, @NonNull AdaptiveIconDrawable owner, @Nullable Resources res)902 ChildDrawable(@NonNull ChildDrawable orig, @NonNull AdaptiveIconDrawable owner, 903 @Nullable Resources res) { 904 905 final Drawable dr = orig.mDrawable; 906 final Drawable clone; 907 if (dr != null) { 908 final ConstantState cs = dr.getConstantState(); 909 if (cs == null) { 910 clone = dr; 911 } else if (res != null) { 912 clone = cs.newDrawable(res); 913 } else { 914 clone = cs.newDrawable(); 915 } 916 clone.setCallback(owner); 917 clone.setBounds(dr.getBounds()); 918 clone.setLevel(dr.getLevel()); 919 } else { 920 clone = null; 921 } 922 923 mDrawable = clone; 924 mThemeAttrs = orig.mThemeAttrs; 925 926 mDensity = Drawable.resolveDensity(res, orig.mDensity); 927 } 928 canApplyTheme()929 public boolean canApplyTheme() { 930 return mThemeAttrs != null 931 || (mDrawable != null && mDrawable.canApplyTheme()); 932 } 933 setDensity(int targetDensity)934 public final void setDensity(int targetDensity) { 935 if (mDensity != targetDensity) { 936 mDensity = targetDensity; 937 } 938 } 939 } 940 941 static class LayerState extends ConstantState { 942 private int[] mThemeAttrs; 943 944 final static int N_CHILDREN = 2; 945 ChildDrawable[] mChildren; 946 947 // The density at which to render the drawable and its children. 948 int mDensity; 949 950 // The density to use when inflating/looking up the children drawables. A value of 0 means 951 // use the system's density. 952 int mSrcDensityOverride = 0; 953 954 int mOpacityOverride = PixelFormat.UNKNOWN; 955 956 @Config int mChangingConfigurations; 957 @Config int mChildrenChangingConfigurations; 958 959 @DrawableRes int mSourceDrawableId = Resources.ID_NULL; 960 961 private boolean mCheckedOpacity; 962 private int mOpacity; 963 964 private boolean mCheckedStateful; 965 private boolean mIsStateful; 966 private boolean mAutoMirrored = false; 967 LayerState(@ullable LayerState orig, @NonNull AdaptiveIconDrawable owner, @Nullable Resources res)968 LayerState(@Nullable LayerState orig, @NonNull AdaptiveIconDrawable owner, 969 @Nullable Resources res) { 970 mDensity = Drawable.resolveDensity(res, orig != null ? orig.mDensity : 0); 971 mChildren = new ChildDrawable[N_CHILDREN]; 972 if (orig != null) { 973 final ChildDrawable[] origChildDrawable = orig.mChildren; 974 975 mChangingConfigurations = orig.mChangingConfigurations; 976 mChildrenChangingConfigurations = orig.mChildrenChangingConfigurations; 977 mSourceDrawableId = orig.mSourceDrawableId; 978 979 for (int i = 0; i < N_CHILDREN; i++) { 980 final ChildDrawable or = origChildDrawable[i]; 981 mChildren[i] = new ChildDrawable(or, owner, res); 982 } 983 984 mCheckedOpacity = orig.mCheckedOpacity; 985 mOpacity = orig.mOpacity; 986 mCheckedStateful = orig.mCheckedStateful; 987 mIsStateful = orig.mIsStateful; 988 mAutoMirrored = orig.mAutoMirrored; 989 mThemeAttrs = orig.mThemeAttrs; 990 mOpacityOverride = orig.mOpacityOverride; 991 mSrcDensityOverride = orig.mSrcDensityOverride; 992 } else { 993 for (int i = 0; i < N_CHILDREN; i++) { 994 mChildren[i] = new ChildDrawable(mDensity); 995 } 996 } 997 } 998 setDensity(int targetDensity)999 public final void setDensity(int targetDensity) { 1000 if (mDensity != targetDensity) { 1001 mDensity = targetDensity; 1002 } 1003 } 1004 1005 @Override canApplyTheme()1006 public boolean canApplyTheme() { 1007 if (mThemeAttrs != null || super.canApplyTheme()) { 1008 return true; 1009 } 1010 1011 final ChildDrawable[] array = mChildren; 1012 for (int i = 0; i < N_CHILDREN; i++) { 1013 final ChildDrawable layer = array[i]; 1014 if (layer.canApplyTheme()) { 1015 return true; 1016 } 1017 } 1018 return false; 1019 } 1020 1021 @Override newDrawable()1022 public Drawable newDrawable() { 1023 return new AdaptiveIconDrawable(this, null); 1024 } 1025 1026 @Override newDrawable(@ullable Resources res)1027 public Drawable newDrawable(@Nullable Resources res) { 1028 return new AdaptiveIconDrawable(this, res); 1029 } 1030 1031 @Override getChangingConfigurations()1032 public @Config int getChangingConfigurations() { 1033 return mChangingConfigurations 1034 | mChildrenChangingConfigurations; 1035 } 1036 getOpacity()1037 public final int getOpacity() { 1038 if (mCheckedOpacity) { 1039 return mOpacity; 1040 } 1041 1042 final ChildDrawable[] array = mChildren; 1043 1044 // Seek to the first non-null drawable. 1045 int firstIndex = -1; 1046 for (int i = 0; i < N_CHILDREN; i++) { 1047 if (array[i].mDrawable != null) { 1048 firstIndex = i; 1049 break; 1050 } 1051 } 1052 1053 int op; 1054 if (firstIndex >= 0) { 1055 op = array[firstIndex].mDrawable.getOpacity(); 1056 } else { 1057 op = PixelFormat.TRANSPARENT; 1058 } 1059 1060 // Merge all remaining non-null drawables. 1061 for (int i = firstIndex + 1; i < N_CHILDREN; i++) { 1062 final Drawable dr = array[i].mDrawable; 1063 if (dr != null) { 1064 op = Drawable.resolveOpacity(op, dr.getOpacity()); 1065 } 1066 } 1067 1068 mOpacity = op; 1069 mCheckedOpacity = true; 1070 return op; 1071 } 1072 isStateful()1073 public final boolean isStateful() { 1074 if (mCheckedStateful) { 1075 return mIsStateful; 1076 } 1077 1078 final ChildDrawable[] array = mChildren; 1079 boolean isStateful = false; 1080 for (int i = 0; i < N_CHILDREN; i++) { 1081 final Drawable dr = array[i].mDrawable; 1082 if (dr != null && dr.isStateful()) { 1083 isStateful = true; 1084 break; 1085 } 1086 } 1087 1088 mIsStateful = isStateful; 1089 mCheckedStateful = true; 1090 return isStateful; 1091 } 1092 hasFocusStateSpecified()1093 public final boolean hasFocusStateSpecified() { 1094 final ChildDrawable[] array = mChildren; 1095 for (int i = 0; i < N_CHILDREN; i++) { 1096 final Drawable dr = array[i].mDrawable; 1097 if (dr != null && dr.hasFocusStateSpecified()) { 1098 return true; 1099 } 1100 } 1101 return false; 1102 } 1103 canConstantState()1104 public final boolean canConstantState() { 1105 final ChildDrawable[] array = mChildren; 1106 for (int i = 0; i < N_CHILDREN; i++) { 1107 final Drawable dr = array[i].mDrawable; 1108 if (dr != null && dr.getConstantState() == null) { 1109 return false; 1110 } 1111 } 1112 1113 // Don't cache the result, this method is not called very often. 1114 return true; 1115 } 1116 invalidateCache()1117 public void invalidateCache() { 1118 mCheckedOpacity = false; 1119 mCheckedStateful = false; 1120 } 1121 } 1122 } 1123