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