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