Home | History | Annotate | Download | only in widget
      1 /*
      2  * Copyright (C) 2006 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.widget;
     18 
     19 import android.annotation.DrawableRes;
     20 import android.annotation.NonNull;
     21 import android.annotation.Nullable;
     22 import android.annotation.TestApi;
     23 import android.annotation.UnsupportedAppUsage;
     24 import android.content.ContentResolver;
     25 import android.content.Context;
     26 import android.content.res.ColorStateList;
     27 import android.content.res.Resources;
     28 import android.content.res.TypedArray;
     29 import android.graphics.Bitmap;
     30 import android.graphics.BlendMode;
     31 import android.graphics.Canvas;
     32 import android.graphics.ColorFilter;
     33 import android.graphics.ImageDecoder;
     34 import android.graphics.Matrix;
     35 import android.graphics.PixelFormat;
     36 import android.graphics.PorterDuff;
     37 import android.graphics.PorterDuffColorFilter;
     38 import android.graphics.Rect;
     39 import android.graphics.RectF;
     40 import android.graphics.Xfermode;
     41 import android.graphics.drawable.BitmapDrawable;
     42 import android.graphics.drawable.Drawable;
     43 import android.graphics.drawable.Icon;
     44 import android.net.Uri;
     45 import android.os.Build;
     46 import android.os.Handler;
     47 import android.text.TextUtils;
     48 import android.util.AttributeSet;
     49 import android.util.Log;
     50 import android.view.RemotableViewMethod;
     51 import android.view.View;
     52 import android.view.ViewDebug;
     53 import android.view.ViewHierarchyEncoder;
     54 import android.view.accessibility.AccessibilityEvent;
     55 import android.view.inspector.InspectableProperty;
     56 import android.widget.RemoteViews.RemoteView;
     57 
     58 import com.android.internal.R;
     59 
     60 import java.io.IOException;
     61 
     62 /**
     63  * Displays image resources, for example {@link android.graphics.Bitmap}
     64  * or {@link android.graphics.drawable.Drawable} resources.
     65  * ImageView is also commonly used to {@link #setImageTintMode(PorterDuff.Mode)
     66  * apply tints to an image} and handle {@link #setScaleType(ScaleType) image scaling}.
     67  *
     68  * <p>
     69  * The following XML snippet is a common example of using an ImageView to display an image resource:
     70  * </p>
     71  * <pre>
     72  * &lt;LinearLayout
     73  *     xmlns:android="http://schemas.android.com/apk/res/android"
     74  *     android:layout_width="match_parent"
     75  *     android:layout_height="match_parent"&gt;
     76  *     &lt;ImageView
     77  *         android:layout_width="wrap_content"
     78  *         android:layout_height="wrap_content"
     79  *         android:src="@mipmap/ic_launcher"
     80  *         /&gt;
     81  * &lt;/LinearLayout&gt;
     82  * </pre>
     83  *
     84  * <p>
     85  * To learn more about Drawables, see: <a href="{@docRoot}guide/topics/resources/drawable-resource.html">Drawable Resources</a>.
     86  * To learn more about working with Bitmaps, see: <a href="{@docRoot}topic/performance/graphics/index.html">Handling Bitmaps</a>.
     87  * </p>
     88  *
     89  * @attr ref android.R.styleable#ImageView_adjustViewBounds
     90  * @attr ref android.R.styleable#ImageView_src
     91  * @attr ref android.R.styleable#ImageView_maxWidth
     92  * @attr ref android.R.styleable#ImageView_maxHeight
     93  * @attr ref android.R.styleable#ImageView_tint
     94  * @attr ref android.R.styleable#ImageView_scaleType
     95  * @attr ref android.R.styleable#ImageView_cropToPadding
     96  */
     97 @RemoteView
     98 public class ImageView extends View {
     99     private static final String LOG_TAG = "ImageView";
    100 
    101     // settable by the client
    102     @UnsupportedAppUsage
    103     private Uri mUri;
    104     @UnsupportedAppUsage
    105     private int mResource = 0;
    106     private Matrix mMatrix;
    107     private ScaleType mScaleType;
    108     private boolean mHaveFrame = false;
    109     @UnsupportedAppUsage
    110     private boolean mAdjustViewBounds = false;
    111     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P)
    112     private int mMaxWidth = Integer.MAX_VALUE;
    113     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P)
    114     private int mMaxHeight = Integer.MAX_VALUE;
    115 
    116     // these are applied to the drawable
    117     private ColorFilter mColorFilter = null;
    118     private boolean mHasColorFilter = false;
    119     private Xfermode mXfermode;
    120     private boolean mHasXfermode = false;
    121     @UnsupportedAppUsage
    122     private int mAlpha = 255;
    123     private boolean mHasAlpha = false;
    124     private final int mViewAlphaScale = 256;
    125 
    126     @UnsupportedAppUsage
    127     private Drawable mDrawable = null;
    128     @UnsupportedAppUsage
    129     private BitmapDrawable mRecycleableBitmapDrawable = null;
    130     private ColorStateList mDrawableTintList = null;
    131     private BlendMode mDrawableBlendMode = null;
    132     private boolean mHasDrawableTint = false;
    133     private boolean mHasDrawableBlendMode = false;
    134 
    135     private int[] mState = null;
    136     private boolean mMergeState = false;
    137     private int mLevel = 0;
    138     @UnsupportedAppUsage
    139     private int mDrawableWidth;
    140     @UnsupportedAppUsage
    141     private int mDrawableHeight;
    142     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 124051687)
    143     private Matrix mDrawMatrix = null;
    144 
    145     // Avoid allocations...
    146     private final RectF mTempSrc = new RectF();
    147     private final RectF mTempDst = new RectF();
    148 
    149     @UnsupportedAppUsage
    150     private boolean mCropToPadding;
    151 
    152     private int mBaseline = -1;
    153     private boolean mBaselineAlignBottom = false;
    154 
    155     /** Compatibility modes dependent on targetSdkVersion of the app. */
    156     private static boolean sCompatDone;
    157 
    158     /** AdjustViewBounds behavior will be in compatibility mode for older apps. */
    159     private static boolean sCompatAdjustViewBounds;
    160 
    161     /** Whether to pass Resources when creating the source from a stream. */
    162     private static boolean sCompatUseCorrectStreamDensity;
    163 
    164     /** Whether to use pre-Nougat drawable visibility dispatching conditions. */
    165     private static boolean sCompatDrawableVisibilityDispatch;
    166 
    167     private static final ScaleType[] sScaleTypeArray = {
    168         ScaleType.MATRIX,
    169         ScaleType.FIT_XY,
    170         ScaleType.FIT_START,
    171         ScaleType.FIT_CENTER,
    172         ScaleType.FIT_END,
    173         ScaleType.CENTER,
    174         ScaleType.CENTER_CROP,
    175         ScaleType.CENTER_INSIDE
    176     };
    177 
    178     public ImageView(Context context) {
    179         super(context);
    180         initImageView();
    181     }
    182 
    183     public ImageView(Context context, @Nullable AttributeSet attrs) {
    184         this(context, attrs, 0);
    185     }
    186 
    187     public ImageView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
    188         this(context, attrs, defStyleAttr, 0);
    189     }
    190 
    191     public ImageView(Context context, @Nullable AttributeSet attrs, int defStyleAttr,
    192             int defStyleRes) {
    193         super(context, attrs, defStyleAttr, defStyleRes);
    194 
    195         initImageView();
    196 
    197         // ImageView is not important by default, unless app developer overrode attribute.
    198         if (getImportantForAutofill() == IMPORTANT_FOR_AUTOFILL_AUTO) {
    199             setImportantForAutofill(IMPORTANT_FOR_AUTOFILL_NO);
    200         }
    201 
    202         final TypedArray a = context.obtainStyledAttributes(
    203                 attrs, R.styleable.ImageView, defStyleAttr, defStyleRes);
    204         saveAttributeDataForStyleable(context, R.styleable.ImageView,
    205                 attrs, a, defStyleAttr, defStyleRes);
    206 
    207         final Drawable d = a.getDrawable(R.styleable.ImageView_src);
    208         if (d != null) {
    209             setImageDrawable(d);
    210         }
    211 
    212         mBaselineAlignBottom = a.getBoolean(R.styleable.ImageView_baselineAlignBottom, false);
    213         mBaseline = a.getDimensionPixelSize(R.styleable.ImageView_baseline, -1);
    214 
    215         setAdjustViewBounds(a.getBoolean(R.styleable.ImageView_adjustViewBounds, false));
    216         setMaxWidth(a.getDimensionPixelSize(R.styleable.ImageView_maxWidth, Integer.MAX_VALUE));
    217         setMaxHeight(a.getDimensionPixelSize(R.styleable.ImageView_maxHeight, Integer.MAX_VALUE));
    218 
    219         final int index = a.getInt(R.styleable.ImageView_scaleType, -1);
    220         if (index >= 0) {
    221             setScaleType(sScaleTypeArray[index]);
    222         }
    223 
    224         if (a.hasValue(R.styleable.ImageView_tint)) {
    225             mDrawableTintList = a.getColorStateList(R.styleable.ImageView_tint);
    226             mHasDrawableTint = true;
    227 
    228             // Prior to L, this attribute would always set a color filter with
    229             // blending mode SRC_ATOP. Preserve that default behavior.
    230             mDrawableBlendMode = BlendMode.SRC_ATOP;
    231             mHasDrawableBlendMode = true;
    232         }
    233 
    234         if (a.hasValue(R.styleable.ImageView_tintMode)) {
    235             mDrawableBlendMode = Drawable.parseBlendMode(a.getInt(
    236                     R.styleable.ImageView_tintMode, -1), mDrawableBlendMode);
    237             mHasDrawableBlendMode = true;
    238         }
    239 
    240         applyImageTint();
    241 
    242         final int alpha = a.getInt(R.styleable.ImageView_drawableAlpha, 255);
    243         if (alpha != 255) {
    244             setImageAlpha(alpha);
    245         }
    246 
    247         mCropToPadding = a.getBoolean(
    248                 R.styleable.ImageView_cropToPadding, false);
    249 
    250         a.recycle();
    251 
    252         //need inflate syntax/reader for matrix
    253     }
    254 
    255     private void initImageView() {
    256         mMatrix = new Matrix();
    257         mScaleType = ScaleType.FIT_CENTER;
    258 
    259         if (!sCompatDone) {
    260             final int targetSdkVersion = mContext.getApplicationInfo().targetSdkVersion;
    261             sCompatAdjustViewBounds = targetSdkVersion <= Build.VERSION_CODES.JELLY_BEAN_MR1;
    262             sCompatUseCorrectStreamDensity = targetSdkVersion > Build.VERSION_CODES.M;
    263             sCompatDrawableVisibilityDispatch = targetSdkVersion < Build.VERSION_CODES.N;
    264             sCompatDone = true;
    265         }
    266     }
    267 
    268     @Override
    269     protected boolean verifyDrawable(@NonNull Drawable dr) {
    270         return mDrawable == dr || super.verifyDrawable(dr);
    271     }
    272 
    273     @Override
    274     public void jumpDrawablesToCurrentState() {
    275         super.jumpDrawablesToCurrentState();
    276         if (mDrawable != null) mDrawable.jumpToCurrentState();
    277     }
    278 
    279     @Override
    280     public void invalidateDrawable(@NonNull Drawable dr) {
    281         if (dr == mDrawable) {
    282             if (dr != null) {
    283                 // update cached drawable dimensions if they've changed
    284                 final int w = dr.getIntrinsicWidth();
    285                 final int h = dr.getIntrinsicHeight();
    286                 if (w != mDrawableWidth || h != mDrawableHeight) {
    287                     mDrawableWidth = w;
    288                     mDrawableHeight = h;
    289                     // updates the matrix, which is dependent on the bounds
    290                     configureBounds();
    291                 }
    292             }
    293             /* we invalidate the whole view in this case because it's very
    294              * hard to know where the drawable actually is. This is made
    295              * complicated because of the offsets and transformations that
    296              * can be applied. In theory we could get the drawable's bounds
    297              * and run them through the transformation and offsets, but this
    298              * is probably not worth the effort.
    299              */
    300             invalidate();
    301         } else {
    302             super.invalidateDrawable(dr);
    303         }
    304     }
    305 
    306     @Override
    307     public boolean hasOverlappingRendering() {
    308         return (getBackground() != null && getBackground().getCurrent() != null);
    309     }
    310 
    311     /** @hide */
    312     @Override
    313     public void onPopulateAccessibilityEventInternal(AccessibilityEvent event) {
    314         super.onPopulateAccessibilityEventInternal(event);
    315 
    316         final CharSequence contentDescription = getContentDescription();
    317         if (!TextUtils.isEmpty(contentDescription)) {
    318             event.getText().add(contentDescription);
    319         }
    320     }
    321 
    322     /**
    323      * True when ImageView is adjusting its bounds
    324      * to preserve the aspect ratio of its drawable
    325      *
    326      * @return whether to adjust the bounds of this view
    327      * to preserve the original aspect ratio of the drawable
    328      *
    329      * @see #setAdjustViewBounds(boolean)
    330      *
    331      * @attr ref android.R.styleable#ImageView_adjustViewBounds
    332      */
    333     @InspectableProperty
    334     public boolean getAdjustViewBounds() {
    335         return mAdjustViewBounds;
    336     }
    337 
    338     /**
    339      * Set this to true if you want the ImageView to adjust its bounds
    340      * to preserve the aspect ratio of its drawable.
    341      *
    342      * <p><strong>Note:</strong> If the application targets API level 17 or lower,
    343      * adjustViewBounds will allow the drawable to shrink the view bounds, but not grow
    344      * to fill available measured space in all cases. This is for compatibility with
    345      * legacy {@link android.view.View.MeasureSpec MeasureSpec} and
    346      * {@link android.widget.RelativeLayout RelativeLayout} behavior.</p>
    347      *
    348      * @param adjustViewBounds Whether to adjust the bounds of this view
    349      * to preserve the original aspect ratio of the drawable.
    350      *
    351      * @see #getAdjustViewBounds()
    352      *
    353      * @attr ref android.R.styleable#ImageView_adjustViewBounds
    354      */
    355     @android.view.RemotableViewMethod
    356     public void setAdjustViewBounds(boolean adjustViewBounds) {
    357         mAdjustViewBounds = adjustViewBounds;
    358         if (adjustViewBounds) {
    359             setScaleType(ScaleType.FIT_CENTER);
    360         }
    361     }
    362 
    363     /**
    364      * The maximum width of this view.
    365      *
    366      * @return The maximum width of this view
    367      *
    368      * @see #setMaxWidth(int)
    369      *
    370      * @attr ref android.R.styleable#ImageView_maxWidth
    371      */
    372     @InspectableProperty
    373     public int getMaxWidth() {
    374         return mMaxWidth;
    375     }
    376 
    377     /**
    378      * An optional argument to supply a maximum width for this view. Only valid if
    379      * {@link #setAdjustViewBounds(boolean)} has been set to true. To set an image to be a maximum
    380      * of 100 x 100 while preserving the original aspect ratio, do the following: 1) set
    381      * adjustViewBounds to true 2) set maxWidth and maxHeight to 100 3) set the height and width
    382      * layout params to WRAP_CONTENT.
    383      *
    384      * <p>
    385      * Note that this view could be still smaller than 100 x 100 using this approach if the original
    386      * image is small. To set an image to a fixed size, specify that size in the layout params and
    387      * then use {@link #setScaleType(android.widget.ImageView.ScaleType)} to determine how to fit
    388      * the image within the bounds.
    389      * </p>
    390      *
    391      * @param maxWidth maximum width for this view
    392      *
    393      * @see #getMaxWidth()
    394      *
    395      * @attr ref android.R.styleable#ImageView_maxWidth
    396      */
    397     @android.view.RemotableViewMethod
    398     public void setMaxWidth(int maxWidth) {
    399         mMaxWidth = maxWidth;
    400     }
    401 
    402     /**
    403      * The maximum height of this view.
    404      *
    405      * @return The maximum height of this view
    406      *
    407      * @see #setMaxHeight(int)
    408      *
    409      * @attr ref android.R.styleable#ImageView_maxHeight
    410      */
    411     @InspectableProperty
    412     public int getMaxHeight() {
    413         return mMaxHeight;
    414     }
    415 
    416     /**
    417      * An optional argument to supply a maximum height for this view. Only valid if
    418      * {@link #setAdjustViewBounds(boolean)} has been set to true. To set an image to be a
    419      * maximum of 100 x 100 while preserving the original aspect ratio, do the following: 1) set
    420      * adjustViewBounds to true 2) set maxWidth and maxHeight to 100 3) set the height and width
    421      * layout params to WRAP_CONTENT.
    422      *
    423      * <p>
    424      * Note that this view could be still smaller than 100 x 100 using this approach if the original
    425      * image is small. To set an image to a fixed size, specify that size in the layout params and
    426      * then use {@link #setScaleType(android.widget.ImageView.ScaleType)} to determine how to fit
    427      * the image within the bounds.
    428      * </p>
    429      *
    430      * @param maxHeight maximum height for this view
    431      *
    432      * @see #getMaxHeight()
    433      *
    434      * @attr ref android.R.styleable#ImageView_maxHeight
    435      */
    436     @android.view.RemotableViewMethod
    437     public void setMaxHeight(int maxHeight) {
    438         mMaxHeight = maxHeight;
    439     }
    440 
    441     /**
    442      * Gets the current Drawable, or null if no Drawable has been
    443      * assigned.
    444      *
    445      * @return the view's drawable, or null if no drawable has been
    446      * assigned.
    447      */
    448     @InspectableProperty(name = "src")
    449     public Drawable getDrawable() {
    450         if (mDrawable == mRecycleableBitmapDrawable) {
    451             // Consider our cached version dirty since app code now has a reference to it
    452             mRecycleableBitmapDrawable = null;
    453         }
    454         return mDrawable;
    455     }
    456 
    457     private class ImageDrawableCallback implements Runnable {
    458 
    459         private final Drawable drawable;
    460         private final Uri uri;
    461         private final int resource;
    462 
    463         ImageDrawableCallback(Drawable drawable, Uri uri, int resource) {
    464             this.drawable = drawable;
    465             this.uri = uri;
    466             this.resource = resource;
    467         }
    468 
    469         @Override
    470         public void run() {
    471             setImageDrawable(drawable);
    472             mUri = uri;
    473             mResource = resource;
    474         }
    475     }
    476 
    477     /**
    478      * Sets a drawable as the content of this ImageView.
    479      * <p class="note">This does Bitmap reading and decoding on the UI
    480      * thread, which can cause a latency hiccup.  If that's a concern,
    481      * consider using {@link #setImageDrawable(android.graphics.drawable.Drawable)} or
    482      * {@link #setImageBitmap(android.graphics.Bitmap)} and
    483      * {@link android.graphics.BitmapFactory} instead.</p>
    484      *
    485      * @param resId the resource identifier of the drawable
    486      *
    487      * @attr ref android.R.styleable#ImageView_src
    488      */
    489     @android.view.RemotableViewMethod(asyncImpl="setImageResourceAsync")
    490     public void setImageResource(@DrawableRes int resId) {
    491         // The resource configuration may have changed, so we should always
    492         // try to load the resource even if the resId hasn't changed.
    493         final int oldWidth = mDrawableWidth;
    494         final int oldHeight = mDrawableHeight;
    495 
    496         updateDrawable(null);
    497         mResource = resId;
    498         mUri = null;
    499 
    500         resolveUri();
    501 
    502         if (oldWidth != mDrawableWidth || oldHeight != mDrawableHeight) {
    503             requestLayout();
    504         }
    505         invalidate();
    506     }
    507 
    508     /** @hide **/
    509     @UnsupportedAppUsage
    510     public Runnable setImageResourceAsync(@DrawableRes int resId) {
    511         Drawable d = null;
    512         if (resId != 0) {
    513             try {
    514                 d = getContext().getDrawable(resId);
    515             } catch (Exception e) {
    516                 Log.w(LOG_TAG, "Unable to find resource: " + resId, e);
    517                 resId = 0;
    518             }
    519         }
    520         return new ImageDrawableCallback(d, null, resId);
    521     }
    522 
    523     /**
    524      * Sets the content of this ImageView to the specified Uri.
    525      * Note that you use this method to load images from a local Uri only.
    526      * <p/>
    527      * To learn how to display images from a remote Uri see: <a href="https://developer.android.com/topic/performance/graphics/index.html">Handling Bitmaps</a>
    528      * <p/>
    529      * <p class="note">This does Bitmap reading and decoding on the UI
    530      * thread, which can cause a latency hiccup.  If that's a concern,
    531      * consider using {@link #setImageDrawable(Drawable)} or
    532      * {@link #setImageBitmap(android.graphics.Bitmap)} and
    533      * {@link android.graphics.BitmapFactory} instead.</p>
    534      *
    535      * <p class="note">On devices running SDK < 24, this method will fail to
    536      * apply correct density scaling to images loaded from
    537      * {@link ContentResolver#SCHEME_CONTENT content} and
    538      * {@link ContentResolver#SCHEME_FILE file} schemes. Applications running
    539      * on devices with SDK >= 24 <strong>MUST</strong> specify the
    540      * {@code targetSdkVersion} in their manifest as 24 or above for density
    541      * scaling to be applied to images loaded from these schemes.</p>
    542      *
    543      * @param uri the Uri of an image, or {@code null} to clear the content
    544      */
    545     @android.view.RemotableViewMethod(asyncImpl="setImageURIAsync")
    546     public void setImageURI(@Nullable Uri uri) {
    547         if (mResource != 0 || (mUri != uri && (uri == null || mUri == null || !uri.equals(mUri)))) {
    548             updateDrawable(null);
    549             mResource = 0;
    550             mUri = uri;
    551 
    552             final int oldWidth = mDrawableWidth;
    553             final int oldHeight = mDrawableHeight;
    554 
    555             resolveUri();
    556 
    557             if (oldWidth != mDrawableWidth || oldHeight != mDrawableHeight) {
    558                 requestLayout();
    559             }
    560             invalidate();
    561         }
    562     }
    563 
    564     /** @hide **/
    565     @UnsupportedAppUsage
    566     public Runnable setImageURIAsync(@Nullable Uri uri) {
    567         if (mResource != 0 || (mUri != uri && (uri == null || mUri == null || !uri.equals(mUri)))) {
    568             Drawable d = uri == null ? null : getDrawableFromUri(uri);
    569             if (d == null) {
    570                 // Do not set the URI if the drawable couldn't be loaded.
    571                 uri = null;
    572             }
    573             return new ImageDrawableCallback(d, uri, 0);
    574         }
    575         return null;
    576     }
    577 
    578     /**
    579      * Sets a drawable as the content of this ImageView.
    580      *
    581      * @param drawable the Drawable to set, or {@code null} to clear the
    582      *                 content
    583      */
    584     public void setImageDrawable(@Nullable Drawable drawable) {
    585         if (mDrawable != drawable) {
    586             mResource = 0;
    587             mUri = null;
    588 
    589             final int oldWidth = mDrawableWidth;
    590             final int oldHeight = mDrawableHeight;
    591 
    592             updateDrawable(drawable);
    593 
    594             if (oldWidth != mDrawableWidth || oldHeight != mDrawableHeight) {
    595                 requestLayout();
    596             }
    597             invalidate();
    598         }
    599     }
    600 
    601     /**
    602      * Sets the content of this ImageView to the specified Icon.
    603      *
    604      * <p class="note">Depending on the Icon type, this may do Bitmap reading
    605      * and decoding on the UI thread, which can cause UI jank.  If that's a
    606      * concern, consider using
    607      * {@link Icon#loadDrawableAsync(Context, Icon.OnDrawableLoadedListener, Handler)}
    608      * and then {@link #setImageDrawable(android.graphics.drawable.Drawable)}
    609      * instead.</p>
    610      *
    611      * @param icon an Icon holding the desired image, or {@code null} to clear
    612      *             the content
    613      */
    614     @android.view.RemotableViewMethod(asyncImpl="setImageIconAsync")
    615     public void setImageIcon(@Nullable Icon icon) {
    616         setImageDrawable(icon == null ? null : icon.loadDrawable(mContext));
    617     }
    618 
    619     /** @hide **/
    620     public Runnable setImageIconAsync(@Nullable Icon icon) {
    621         return new ImageDrawableCallback(icon == null ? null : icon.loadDrawable(mContext), null, 0);
    622     }
    623 
    624     /**
    625      * Applies a tint to the image drawable. Does not modify the current tint
    626      * mode, which is {@link PorterDuff.Mode#SRC_IN} by default.
    627      * <p>
    628      * Subsequent calls to {@link #setImageDrawable(Drawable)} will automatically
    629      * mutate the drawable and apply the specified tint and tint mode using
    630      * {@link Drawable#setTintList(ColorStateList)}.
    631      * <p>
    632      * <em>Note:</em> The default tint mode used by this setter is NOT
    633      * consistent with the default tint mode used by the
    634      * {@link android.R.styleable#ImageView_tint android:tint}
    635      * attribute. If the {@code android:tint} attribute is specified, the
    636      * default tint mode will be set to {@link PorterDuff.Mode#SRC_ATOP} to
    637      * ensure consistency with earlier versions of the platform.
    638      *
    639      * @param tint the tint to apply, may be {@code null} to clear tint
    640      *
    641      * @attr ref android.R.styleable#ImageView_tint
    642      * @see #getImageTintList()
    643      * @see Drawable#setTintList(ColorStateList)
    644      */
    645     public void setImageTintList(@Nullable ColorStateList tint) {
    646         mDrawableTintList = tint;
    647         mHasDrawableTint = true;
    648 
    649         applyImageTint();
    650     }
    651 
    652     /**
    653      * Get the current {@link android.content.res.ColorStateList} used to tint the image Drawable,
    654      * or null if no tint is applied.
    655      *
    656      * @return the tint applied to the image drawable
    657      * @attr ref android.R.styleable#ImageView_tint
    658      * @see #setImageTintList(ColorStateList)
    659      */
    660     @Nullable
    661     @InspectableProperty(name = "tint")
    662     public ColorStateList getImageTintList() {
    663         return mDrawableTintList;
    664     }
    665 
    666     /**
    667      * Specifies the blending mode used to apply the tint specified by
    668      * {@link #setImageTintList(ColorStateList)}} to the image drawable. The default
    669      * mode is {@link PorterDuff.Mode#SRC_IN}.
    670      *
    671      * @param tintMode the blending mode used to apply the tint, may be
    672      *                 {@code null} to clear tint
    673      * @attr ref android.R.styleable#ImageView_tintMode
    674      * @see #getImageTintMode()
    675      * @see Drawable#setTintMode(PorterDuff.Mode)
    676      */
    677     public void setImageTintMode(@Nullable PorterDuff.Mode tintMode) {
    678         setImageTintBlendMode(tintMode != null ? BlendMode.fromValue(tintMode.nativeInt) : null);
    679     }
    680 
    681     /**
    682      * Specifies the blending mode used to apply the tint specified by
    683      * {@link #setImageTintList(ColorStateList)}} to the image drawable. The default
    684      * mode is {@link BlendMode#SRC_IN}.
    685      *
    686      * @param blendMode the blending mode used to apply the tint, may be
    687      *                 {@code null} to clear tint
    688      * @attr ref android.R.styleable#ImageView_tintMode
    689      * @see #getImageTintMode()
    690      * @see Drawable#setTintBlendMode(BlendMode)
    691      */
    692     public void setImageTintBlendMode(@Nullable BlendMode blendMode) {
    693         mDrawableBlendMode = blendMode;
    694         mHasDrawableBlendMode = true;
    695 
    696         applyImageTint();
    697     }
    698 
    699     /**
    700      * Gets the blending mode used to apply the tint to the image Drawable
    701      * @return the blending mode used to apply the tint to the image Drawable
    702      * @attr ref android.R.styleable#ImageView_tintMode
    703      * @see #setImageTintMode(PorterDuff.Mode)
    704      */
    705     @Nullable
    706     @InspectableProperty(name = "tintMode")
    707     public PorterDuff.Mode getImageTintMode() {
    708         return mDrawableBlendMode != null
    709                 ? BlendMode.blendModeToPorterDuffMode(mDrawableBlendMode) : null;
    710     }
    711 
    712     /**
    713      * Gets the blending mode used to apply the tint to the image Drawable
    714      * @return the blending mode used to apply the tint to the image Drawable
    715      * @attr ref android.R.styleable#ImageView_tintMode
    716      * @see #setImageTintBlendMode(BlendMode)
    717      */
    718     @Nullable
    719     @InspectableProperty(name = "blendMode", attributeId = android.R.styleable.ImageView_tintMode)
    720     public BlendMode getImageTintBlendMode() {
    721         return mDrawableBlendMode;
    722     }
    723 
    724     private void applyImageTint() {
    725         if (mDrawable != null && (mHasDrawableTint || mHasDrawableBlendMode)) {
    726             mDrawable = mDrawable.mutate();
    727 
    728             if (mHasDrawableTint) {
    729                 mDrawable.setTintList(mDrawableTintList);
    730             }
    731 
    732             if (mHasDrawableBlendMode) {
    733                 mDrawable.setTintBlendMode(mDrawableBlendMode);
    734             }
    735 
    736             // The drawable (or one of its children) may not have been
    737             // stateful before applying the tint, so let's try again.
    738             if (mDrawable.isStateful()) {
    739                 mDrawable.setState(getDrawableState());
    740             }
    741         }
    742     }
    743 
    744     /**
    745      * Sets a Bitmap as the content of this ImageView.
    746      *
    747      * @param bm The bitmap to set
    748      */
    749     @android.view.RemotableViewMethod
    750     public void setImageBitmap(Bitmap bm) {
    751         // Hacky fix to force setImageDrawable to do a full setImageDrawable
    752         // instead of doing an object reference comparison
    753         mDrawable = null;
    754         if (mRecycleableBitmapDrawable == null) {
    755             mRecycleableBitmapDrawable = new BitmapDrawable(mContext.getResources(), bm);
    756         } else {
    757             mRecycleableBitmapDrawable.setBitmap(bm);
    758         }
    759         setImageDrawable(mRecycleableBitmapDrawable);
    760     }
    761 
    762     /**
    763      * Set the state of the current {@link android.graphics.drawable.StateListDrawable}.
    764      * For more information about State List Drawables, see: <a href="https://developer.android.com/guide/topics/resources/drawable-resource.html#StateList">the Drawable Resource Guide</a>.
    765      *
    766      * @param state the state to set for the StateListDrawable
    767      * @param merge if true, merges the state values for the state you specify into the current state
    768      */
    769     public void setImageState(int[] state, boolean merge) {
    770         mState = state;
    771         mMergeState = merge;
    772         if (mDrawable != null) {
    773             refreshDrawableState();
    774             resizeFromDrawable();
    775         }
    776     }
    777 
    778     @Override
    779     public void setSelected(boolean selected) {
    780         super.setSelected(selected);
    781         resizeFromDrawable();
    782     }
    783 
    784     /**
    785      * Sets the image level, when it is constructed from a
    786      * {@link android.graphics.drawable.LevelListDrawable}.
    787      *
    788      * @param level The new level for the image.
    789      */
    790     @android.view.RemotableViewMethod
    791     public void setImageLevel(int level) {
    792         mLevel = level;
    793         if (mDrawable != null) {
    794             mDrawable.setLevel(level);
    795             resizeFromDrawable();
    796         }
    797     }
    798 
    799     /**
    800      * Options for scaling the bounds of an image to the bounds of this view.
    801      */
    802     public enum ScaleType {
    803         /**
    804          * Scale using the image matrix when drawing. The image matrix can be set using
    805          * {@link ImageView#setImageMatrix(Matrix)}. From XML, use this syntax:
    806          * <code>android:scaleType="matrix"</code>.
    807          */
    808         MATRIX      (0),
    809         /**
    810          * Scale the image using {@link Matrix.ScaleToFit#FILL}.
    811          * From XML, use this syntax: <code>android:scaleType="fitXY"</code>.
    812          */
    813         FIT_XY      (1),
    814         /**
    815          * Scale the image using {@link Matrix.ScaleToFit#START}.
    816          * From XML, use this syntax: <code>android:scaleType="fitStart"</code>.
    817          */
    818         FIT_START   (2),
    819         /**
    820          * Scale the image using {@link Matrix.ScaleToFit#CENTER}.
    821          * From XML, use this syntax:
    822          * <code>android:scaleType="fitCenter"</code>.
    823          */
    824         FIT_CENTER  (3),
    825         /**
    826          * Scale the image using {@link Matrix.ScaleToFit#END}.
    827          * From XML, use this syntax: <code>android:scaleType="fitEnd"</code>.
    828          */
    829         FIT_END     (4),
    830         /**
    831          * Center the image in the view, but perform no scaling.
    832          * From XML, use this syntax: <code>android:scaleType="center"</code>.
    833          */
    834         CENTER      (5),
    835         /**
    836          * Scale the image uniformly (maintain the image's aspect ratio) so
    837          * that both dimensions (width and height) of the image will be equal
    838          * to or larger than the corresponding dimension of the view
    839          * (minus padding). The image is then centered in the view.
    840          * From XML, use this syntax: <code>android:scaleType="centerCrop"</code>.
    841          */
    842         CENTER_CROP (6),
    843         /**
    844          * Scale the image uniformly (maintain the image's aspect ratio) so
    845          * that both dimensions (width and height) of the image will be equal
    846          * to or less than the corresponding dimension of the view
    847          * (minus padding). The image is then centered in the view.
    848          * From XML, use this syntax: <code>android:scaleType="centerInside"</code>.
    849          */
    850         CENTER_INSIDE (7);
    851 
    852         ScaleType(int ni) {
    853             nativeInt = ni;
    854         }
    855         final int nativeInt;
    856     }
    857 
    858     /**
    859      * Controls how the image should be resized or moved to match the size
    860      * of this ImageView.
    861      *
    862      * @param scaleType The desired scaling mode.
    863      *
    864      * @attr ref android.R.styleable#ImageView_scaleType
    865      */
    866     public void setScaleType(ScaleType scaleType) {
    867         if (scaleType == null) {
    868             throw new NullPointerException();
    869         }
    870 
    871         if (mScaleType != scaleType) {
    872             mScaleType = scaleType;
    873 
    874             requestLayout();
    875             invalidate();
    876         }
    877     }
    878 
    879     /**
    880      * Returns the current ScaleType that is used to scale the bounds of an image to the bounds of the ImageView.
    881      * @return The ScaleType used to scale the image.
    882      * @see ImageView.ScaleType
    883      * @attr ref android.R.styleable#ImageView_scaleType
    884      */
    885     @InspectableProperty
    886     public ScaleType getScaleType() {
    887         return mScaleType;
    888     }
    889 
    890     /** Returns the view's optional matrix. This is applied to the
    891         view's drawable when it is drawn. If there is no matrix,
    892         this method will return an identity matrix.
    893         Do not change this matrix in place but make a copy.
    894         If you want a different matrix applied to the drawable,
    895         be sure to call setImageMatrix().
    896     */
    897     public Matrix getImageMatrix() {
    898         if (mDrawMatrix == null) {
    899             return new Matrix(Matrix.IDENTITY_MATRIX);
    900         }
    901         return mDrawMatrix;
    902     }
    903 
    904     /**
    905      * Adds a transformation {@link Matrix} that is applied
    906      * to the view's drawable when it is drawn.  Allows custom scaling,
    907      * translation, and perspective distortion.
    908      *
    909      * @param matrix The transformation parameters in matrix form.
    910      */
    911     public void setImageMatrix(Matrix matrix) {
    912         // collapse null and identity to just null
    913         if (matrix != null && matrix.isIdentity()) {
    914             matrix = null;
    915         }
    916 
    917         // don't invalidate unless we're actually changing our matrix
    918         if (matrix == null && !mMatrix.isIdentity() ||
    919                 matrix != null && !mMatrix.equals(matrix)) {
    920             mMatrix.set(matrix);
    921             configureBounds();
    922             invalidate();
    923         }
    924     }
    925 
    926     /**
    927      * Return whether this ImageView crops to padding.
    928      *
    929      * @return whether this ImageView crops to padding
    930      *
    931      * @see #setCropToPadding(boolean)
    932      *
    933      * @attr ref android.R.styleable#ImageView_cropToPadding
    934      */
    935     @InspectableProperty
    936     public boolean getCropToPadding() {
    937         return mCropToPadding;
    938     }
    939 
    940     /**
    941      * Sets whether this ImageView will crop to padding.
    942      *
    943      * @param cropToPadding whether this ImageView will crop to padding
    944      *
    945      * @see #getCropToPadding()
    946      *
    947      * @attr ref android.R.styleable#ImageView_cropToPadding
    948      */
    949     public void setCropToPadding(boolean cropToPadding) {
    950         if (mCropToPadding != cropToPadding) {
    951             mCropToPadding = cropToPadding;
    952             requestLayout();
    953             invalidate();
    954         }
    955     }
    956 
    957     @UnsupportedAppUsage
    958     private void resolveUri() {
    959         if (mDrawable != null) {
    960             return;
    961         }
    962 
    963         if (getResources() == null) {
    964             return;
    965         }
    966 
    967         Drawable d = null;
    968 
    969         if (mResource != 0) {
    970             try {
    971                 d = mContext.getDrawable(mResource);
    972             } catch (Exception e) {
    973                 Log.w(LOG_TAG, "Unable to find resource: " + mResource, e);
    974                 // Don't try again.
    975                 mResource = 0;
    976             }
    977         } else if (mUri != null) {
    978             d = getDrawableFromUri(mUri);
    979 
    980             if (d == null) {
    981                 Log.w(LOG_TAG, "resolveUri failed on bad bitmap uri: " + mUri);
    982                 // Don't try again.
    983                 mUri = null;
    984             }
    985         } else {
    986             return;
    987         }
    988 
    989         updateDrawable(d);
    990     }
    991 
    992     private Drawable getDrawableFromUri(Uri uri) {
    993         final String scheme = uri.getScheme();
    994         if (ContentResolver.SCHEME_ANDROID_RESOURCE.equals(scheme)) {
    995             try {
    996                 // Load drawable through Resources, to get the source density information
    997                 ContentResolver.OpenResourceIdResult r =
    998                         mContext.getContentResolver().getResourceId(uri);
    999                 return r.r.getDrawable(r.id, mContext.getTheme());
   1000             } catch (Exception e) {
   1001                 Log.w(LOG_TAG, "Unable to open content: " + uri, e);
   1002             }
   1003         } else if (ContentResolver.SCHEME_CONTENT.equals(scheme)
   1004                 || ContentResolver.SCHEME_FILE.equals(scheme)) {
   1005             try {
   1006                 Resources res = sCompatUseCorrectStreamDensity ? getResources() : null;
   1007                 ImageDecoder.Source src = ImageDecoder.createSource(mContext.getContentResolver(),
   1008                         uri, res);
   1009                 return ImageDecoder.decodeDrawable(src, (decoder, info, s) -> {
   1010                     decoder.setAllocator(ImageDecoder.ALLOCATOR_SOFTWARE);
   1011                 });
   1012             } catch (IOException e) {
   1013                 Log.w(LOG_TAG, "Unable to open content: " + uri, e);
   1014             }
   1015         } else {
   1016             return Drawable.createFromPath(uri.toString());
   1017         }
   1018         return null;
   1019     }
   1020 
   1021     @Override
   1022     public int[] onCreateDrawableState(int extraSpace) {
   1023         if (mState == null) {
   1024             return super.onCreateDrawableState(extraSpace);
   1025         } else if (!mMergeState) {
   1026             return mState;
   1027         } else {
   1028             return mergeDrawableStates(
   1029                     super.onCreateDrawableState(extraSpace + mState.length), mState);
   1030         }
   1031     }
   1032 
   1033     @UnsupportedAppUsage
   1034     private void updateDrawable(Drawable d) {
   1035         if (d != mRecycleableBitmapDrawable && mRecycleableBitmapDrawable != null) {
   1036             mRecycleableBitmapDrawable.setBitmap(null);
   1037         }
   1038 
   1039         boolean sameDrawable = false;
   1040 
   1041         if (mDrawable != null) {
   1042             sameDrawable = mDrawable == d;
   1043             mDrawable.setCallback(null);
   1044             unscheduleDrawable(mDrawable);
   1045             if (!sCompatDrawableVisibilityDispatch && !sameDrawable && isAttachedToWindow()) {
   1046                 mDrawable.setVisible(false, false);
   1047             }
   1048         }
   1049 
   1050         mDrawable = d;
   1051 
   1052         if (d != null) {
   1053             d.setCallback(this);
   1054             d.setLayoutDirection(getLayoutDirection());
   1055             if (d.isStateful()) {
   1056                 d.setState(getDrawableState());
   1057             }
   1058             if (!sameDrawable || sCompatDrawableVisibilityDispatch) {
   1059                 final boolean visible = sCompatDrawableVisibilityDispatch
   1060                         ? getVisibility() == VISIBLE
   1061                         : isAttachedToWindow() && getWindowVisibility() == VISIBLE && isShown();
   1062                 d.setVisible(visible, true);
   1063             }
   1064             d.setLevel(mLevel);
   1065             mDrawableWidth = d.getIntrinsicWidth();
   1066             mDrawableHeight = d.getIntrinsicHeight();
   1067             applyImageTint();
   1068             applyColorFilter();
   1069             applyAlpha();
   1070             applyXfermode();
   1071 
   1072             configureBounds();
   1073         } else {
   1074             mDrawableWidth = mDrawableHeight = -1;
   1075         }
   1076     }
   1077 
   1078     @UnsupportedAppUsage
   1079     private void resizeFromDrawable() {
   1080         final Drawable d = mDrawable;
   1081         if (d != null) {
   1082             int w = d.getIntrinsicWidth();
   1083             if (w < 0) w = mDrawableWidth;
   1084             int h = d.getIntrinsicHeight();
   1085             if (h < 0) h = mDrawableHeight;
   1086             if (w != mDrawableWidth || h != mDrawableHeight) {
   1087                 mDrawableWidth = w;
   1088                 mDrawableHeight = h;
   1089                 requestLayout();
   1090             }
   1091         }
   1092     }
   1093 
   1094     @Override
   1095     public void onRtlPropertiesChanged(int layoutDirection) {
   1096         super.onRtlPropertiesChanged(layoutDirection);
   1097 
   1098         if (mDrawable != null) {
   1099             mDrawable.setLayoutDirection(layoutDirection);
   1100         }
   1101     }
   1102 
   1103     private static final Matrix.ScaleToFit[] sS2FArray = {
   1104         Matrix.ScaleToFit.FILL,
   1105         Matrix.ScaleToFit.START,
   1106         Matrix.ScaleToFit.CENTER,
   1107         Matrix.ScaleToFit.END
   1108     };
   1109 
   1110     @UnsupportedAppUsage
   1111     private static Matrix.ScaleToFit scaleTypeToScaleToFit(ScaleType st)  {
   1112         // ScaleToFit enum to their corresponding Matrix.ScaleToFit values
   1113         return sS2FArray[st.nativeInt - 1];
   1114     }
   1115 
   1116     @Override
   1117     protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
   1118         resolveUri();
   1119         int w;
   1120         int h;
   1121 
   1122         // Desired aspect ratio of the view's contents (not including padding)
   1123         float desiredAspect = 0.0f;
   1124 
   1125         // We are allowed to change the view's width
   1126         boolean resizeWidth = false;
   1127 
   1128         // We are allowed to change the view's height
   1129         boolean resizeHeight = false;
   1130 
   1131         final int widthSpecMode = MeasureSpec.getMode(widthMeasureSpec);
   1132         final int heightSpecMode = MeasureSpec.getMode(heightMeasureSpec);
   1133 
   1134         if (mDrawable == null) {
   1135             // If no drawable, its intrinsic size is 0.
   1136             mDrawableWidth = -1;
   1137             mDrawableHeight = -1;
   1138             w = h = 0;
   1139         } else {
   1140             w = mDrawableWidth;
   1141             h = mDrawableHeight;
   1142             if (w <= 0) w = 1;
   1143             if (h <= 0) h = 1;
   1144 
   1145             // We are supposed to adjust view bounds to match the aspect
   1146             // ratio of our drawable. See if that is possible.
   1147             if (mAdjustViewBounds) {
   1148                 resizeWidth = widthSpecMode != MeasureSpec.EXACTLY;
   1149                 resizeHeight = heightSpecMode != MeasureSpec.EXACTLY;
   1150 
   1151                 desiredAspect = (float) w / (float) h;
   1152             }
   1153         }
   1154 
   1155         final int pleft = mPaddingLeft;
   1156         final int pright = mPaddingRight;
   1157         final int ptop = mPaddingTop;
   1158         final int pbottom = mPaddingBottom;
   1159 
   1160         int widthSize;
   1161         int heightSize;
   1162 
   1163         if (resizeWidth || resizeHeight) {
   1164             /* If we get here, it means we want to resize to match the
   1165                 drawables aspect ratio, and we have the freedom to change at
   1166                 least one dimension.
   1167             */
   1168 
   1169             // Get the max possible width given our constraints
   1170             widthSize = resolveAdjustedSize(w + pleft + pright, mMaxWidth, widthMeasureSpec);
   1171 
   1172             // Get the max possible height given our constraints
   1173             heightSize = resolveAdjustedSize(h + ptop + pbottom, mMaxHeight, heightMeasureSpec);
   1174 
   1175             if (desiredAspect != 0.0f) {
   1176                 // See what our actual aspect ratio is
   1177                 final float actualAspect = (float)(widthSize - pleft - pright) /
   1178                                         (heightSize - ptop - pbottom);
   1179 
   1180                 if (Math.abs(actualAspect - desiredAspect) > 0.0000001) {
   1181 
   1182                     boolean done = false;
   1183 
   1184                     // Try adjusting width to be proportional to height
   1185                     if (resizeWidth) {
   1186                         int newWidth = (int)(desiredAspect * (heightSize - ptop - pbottom)) +
   1187                                 pleft + pright;
   1188 
   1189                         // Allow the width to outgrow its original estimate if height is fixed.
   1190                         if (!resizeHeight && !sCompatAdjustViewBounds) {
   1191                             widthSize = resolveAdjustedSize(newWidth, mMaxWidth, widthMeasureSpec);
   1192                         }
   1193 
   1194                         if (newWidth <= widthSize) {
   1195                             widthSize = newWidth;
   1196                             done = true;
   1197                         }
   1198                     }
   1199 
   1200                     // Try adjusting height to be proportional to width
   1201                     if (!done && resizeHeight) {
   1202                         int newHeight = (int)((widthSize - pleft - pright) / desiredAspect) +
   1203                                 ptop + pbottom;
   1204 
   1205                         // Allow the height to outgrow its original estimate if width is fixed.
   1206                         if (!resizeWidth && !sCompatAdjustViewBounds) {
   1207                             heightSize = resolveAdjustedSize(newHeight, mMaxHeight,
   1208                                     heightMeasureSpec);
   1209                         }
   1210 
   1211                         if (newHeight <= heightSize) {
   1212                             heightSize = newHeight;
   1213                         }
   1214                     }
   1215                 }
   1216             }
   1217         } else {
   1218             /* We are either don't want to preserve the drawables aspect ratio,
   1219                or we are not allowed to change view dimensions. Just measure in
   1220                the normal way.
   1221             */
   1222             w += pleft + pright;
   1223             h += ptop + pbottom;
   1224 
   1225             w = Math.max(w, getSuggestedMinimumWidth());
   1226             h = Math.max(h, getSuggestedMinimumHeight());
   1227 
   1228             widthSize = resolveSizeAndState(w, widthMeasureSpec, 0);
   1229             heightSize = resolveSizeAndState(h, heightMeasureSpec, 0);
   1230         }
   1231 
   1232         setMeasuredDimension(widthSize, heightSize);
   1233     }
   1234 
   1235     private int resolveAdjustedSize(int desiredSize, int maxSize,
   1236                                    int measureSpec) {
   1237         int result = desiredSize;
   1238         final int specMode = MeasureSpec.getMode(measureSpec);
   1239         final int specSize =  MeasureSpec.getSize(measureSpec);
   1240         switch (specMode) {
   1241             case MeasureSpec.UNSPECIFIED:
   1242                 /* Parent says we can be as big as we want. Just don't be larger
   1243                    than max size imposed on ourselves.
   1244                 */
   1245                 result = Math.min(desiredSize, maxSize);
   1246                 break;
   1247             case MeasureSpec.AT_MOST:
   1248                 // Parent says we can be as big as we want, up to specSize.
   1249                 // Don't be larger than specSize, and don't be larger than
   1250                 // the max size imposed on ourselves.
   1251                 result = Math.min(Math.min(desiredSize, specSize), maxSize);
   1252                 break;
   1253             case MeasureSpec.EXACTLY:
   1254                 // No choice. Do what we are told.
   1255                 result = specSize;
   1256                 break;
   1257         }
   1258         return result;
   1259     }
   1260 
   1261     @Override
   1262     protected boolean setFrame(int l, int t, int r, int b) {
   1263         final boolean changed = super.setFrame(l, t, r, b);
   1264         mHaveFrame = true;
   1265         configureBounds();
   1266         return changed;
   1267     }
   1268 
   1269     private void configureBounds() {
   1270         if (mDrawable == null || !mHaveFrame) {
   1271             return;
   1272         }
   1273 
   1274         final int dwidth = mDrawableWidth;
   1275         final int dheight = mDrawableHeight;
   1276 
   1277         final int vwidth = getWidth() - mPaddingLeft - mPaddingRight;
   1278         final int vheight = getHeight() - mPaddingTop - mPaddingBottom;
   1279 
   1280         final boolean fits = (dwidth < 0 || vwidth == dwidth)
   1281                 && (dheight < 0 || vheight == dheight);
   1282 
   1283         if (dwidth <= 0 || dheight <= 0 || ScaleType.FIT_XY == mScaleType) {
   1284             /* If the drawable has no intrinsic size, or we're told to
   1285                 scaletofit, then we just fill our entire view.
   1286             */
   1287             mDrawable.setBounds(0, 0, vwidth, vheight);
   1288             mDrawMatrix = null;
   1289         } else {
   1290             // We need to do the scaling ourself, so have the drawable
   1291             // use its native size.
   1292             mDrawable.setBounds(0, 0, dwidth, dheight);
   1293 
   1294             if (ScaleType.MATRIX == mScaleType) {
   1295                 // Use the specified matrix as-is.
   1296                 if (mMatrix.isIdentity()) {
   1297                     mDrawMatrix = null;
   1298                 } else {
   1299                     mDrawMatrix = mMatrix;
   1300                 }
   1301             } else if (fits) {
   1302                 // The bitmap fits exactly, no transform needed.
   1303                 mDrawMatrix = null;
   1304             } else if (ScaleType.CENTER == mScaleType) {
   1305                 // Center bitmap in view, no scaling.
   1306                 mDrawMatrix = mMatrix;
   1307                 mDrawMatrix.setTranslate(Math.round((vwidth - dwidth) * 0.5f),
   1308                                          Math.round((vheight - dheight) * 0.5f));
   1309             } else if (ScaleType.CENTER_CROP == mScaleType) {
   1310                 mDrawMatrix = mMatrix;
   1311 
   1312                 float scale;
   1313                 float dx = 0, dy = 0;
   1314 
   1315                 if (dwidth * vheight > vwidth * dheight) {
   1316                     scale = (float) vheight / (float) dheight;
   1317                     dx = (vwidth - dwidth * scale) * 0.5f;
   1318                 } else {
   1319                     scale = (float) vwidth / (float) dwidth;
   1320                     dy = (vheight - dheight * scale) * 0.5f;
   1321                 }
   1322 
   1323                 mDrawMatrix.setScale(scale, scale);
   1324                 mDrawMatrix.postTranslate(Math.round(dx), Math.round(dy));
   1325             } else if (ScaleType.CENTER_INSIDE == mScaleType) {
   1326                 mDrawMatrix = mMatrix;
   1327                 float scale;
   1328                 float dx;
   1329                 float dy;
   1330 
   1331                 if (dwidth <= vwidth && dheight <= vheight) {
   1332                     scale = 1.0f;
   1333                 } else {
   1334                     scale = Math.min((float) vwidth / (float) dwidth,
   1335                             (float) vheight / (float) dheight);
   1336                 }
   1337 
   1338                 dx = Math.round((vwidth - dwidth * scale) * 0.5f);
   1339                 dy = Math.round((vheight - dheight * scale) * 0.5f);
   1340 
   1341                 mDrawMatrix.setScale(scale, scale);
   1342                 mDrawMatrix.postTranslate(dx, dy);
   1343             } else {
   1344                 // Generate the required transform.
   1345                 mTempSrc.set(0, 0, dwidth, dheight);
   1346                 mTempDst.set(0, 0, vwidth, vheight);
   1347 
   1348                 mDrawMatrix = mMatrix;
   1349                 mDrawMatrix.setRectToRect(mTempSrc, mTempDst, scaleTypeToScaleToFit(mScaleType));
   1350             }
   1351         }
   1352     }
   1353 
   1354     @Override
   1355     protected void drawableStateChanged() {
   1356         super.drawableStateChanged();
   1357 
   1358         final Drawable drawable = mDrawable;
   1359         if (drawable != null && drawable.isStateful()
   1360                 && drawable.setState(getDrawableState())) {
   1361             invalidateDrawable(drawable);
   1362         }
   1363     }
   1364 
   1365     @Override
   1366     public void drawableHotspotChanged(float x, float y) {
   1367         super.drawableHotspotChanged(x, y);
   1368 
   1369         if (mDrawable != null) {
   1370             mDrawable.setHotspot(x, y);
   1371         }
   1372     }
   1373 
   1374     /**
   1375      * Applies a temporary transformation {@link Matrix} to the view's drawable when it is drawn.
   1376      * Allows custom scaling, translation, and perspective distortion during an animation.
   1377      *
   1378      * This method is a lightweight analogue of {@link ImageView#setImageMatrix(Matrix)} to use
   1379      * only during animations as this matrix will be cleared after the next drawable
   1380      * update or view's bounds change.
   1381      *
   1382      * @param matrix The transformation parameters in matrix form.
   1383      */
   1384     public void animateTransform(@Nullable Matrix matrix) {
   1385         if (mDrawable == null) {
   1386             return;
   1387         }
   1388         if (matrix == null) {
   1389             final int vwidth = getWidth() - mPaddingLeft - mPaddingRight;
   1390             final int vheight = getHeight() - mPaddingTop - mPaddingBottom;
   1391             mDrawable.setBounds(0, 0, vwidth, vheight);
   1392             mDrawMatrix = null;
   1393         } else {
   1394             mDrawable.setBounds(0, 0, mDrawableWidth, mDrawableHeight);
   1395             if (mDrawMatrix == null) {
   1396                 mDrawMatrix = new Matrix();
   1397             }
   1398             mDrawMatrix.set(matrix);
   1399         }
   1400         invalidate();
   1401     }
   1402 
   1403     @Override
   1404     protected void onDraw(Canvas canvas) {
   1405         super.onDraw(canvas);
   1406 
   1407         if (mDrawable == null) {
   1408             return; // couldn't resolve the URI
   1409         }
   1410 
   1411         if (mDrawableWidth == 0 || mDrawableHeight == 0) {
   1412             return;     // nothing to draw (empty bounds)
   1413         }
   1414 
   1415         if (mDrawMatrix == null && mPaddingTop == 0 && mPaddingLeft == 0) {
   1416             mDrawable.draw(canvas);
   1417         } else {
   1418             final int saveCount = canvas.getSaveCount();
   1419             canvas.save();
   1420 
   1421             if (mCropToPadding) {
   1422                 final int scrollX = mScrollX;
   1423                 final int scrollY = mScrollY;
   1424                 canvas.clipRect(scrollX + mPaddingLeft, scrollY + mPaddingTop,
   1425                         scrollX + mRight - mLeft - mPaddingRight,
   1426                         scrollY + mBottom - mTop - mPaddingBottom);
   1427             }
   1428 
   1429             canvas.translate(mPaddingLeft, mPaddingTop);
   1430 
   1431             if (mDrawMatrix != null) {
   1432                 canvas.concat(mDrawMatrix);
   1433             }
   1434             mDrawable.draw(canvas);
   1435             canvas.restoreToCount(saveCount);
   1436         }
   1437     }
   1438 
   1439     /**
   1440      * <p>Return the offset of the widget's text baseline from the widget's top
   1441      * boundary. </p>
   1442      *
   1443      * @return the offset of the baseline within the widget's bounds or -1
   1444      *         if baseline alignment is not supported.
   1445      */
   1446     @Override
   1447     @InspectableProperty
   1448     @ViewDebug.ExportedProperty(category = "layout")
   1449     public int getBaseline() {
   1450         if (mBaselineAlignBottom) {
   1451             return getMeasuredHeight();
   1452         } else {
   1453             return mBaseline;
   1454         }
   1455     }
   1456 
   1457     /**
   1458      * <p>Set the offset of the widget's text baseline from the widget's top
   1459      * boundary.  This value is overridden by the {@link #setBaselineAlignBottom(boolean)}
   1460      * property.</p>
   1461      *
   1462      * @param baseline The baseline to use, or -1 if none is to be provided.
   1463      *
   1464      * @see #setBaseline(int)
   1465      * @attr ref android.R.styleable#ImageView_baseline
   1466      */
   1467     public void setBaseline(int baseline) {
   1468         if (mBaseline != baseline) {
   1469             mBaseline = baseline;
   1470             requestLayout();
   1471         }
   1472     }
   1473 
   1474     /**
   1475      * Sets whether the baseline of this view to the bottom of the view.
   1476      * Setting this value overrides any calls to setBaseline.
   1477      *
   1478      * @param aligned If true, the image view will be baseline aligned by its bottom edge.
   1479      *
   1480      * @attr ref android.R.styleable#ImageView_baselineAlignBottom
   1481      */
   1482     public void setBaselineAlignBottom(boolean aligned) {
   1483         if (mBaselineAlignBottom != aligned) {
   1484             mBaselineAlignBottom = aligned;
   1485             requestLayout();
   1486         }
   1487     }
   1488 
   1489     /**
   1490      * Checks whether this view's baseline is considered the bottom of the view.
   1491      *
   1492      * @return True if the ImageView's baseline is considered the bottom of the view, false if otherwise.
   1493      * @see #setBaselineAlignBottom(boolean)
   1494      */
   1495     @InspectableProperty
   1496     public boolean getBaselineAlignBottom() {
   1497         return mBaselineAlignBottom;
   1498     }
   1499 
   1500     /**
   1501      * Sets a tinting option for the image.
   1502      *
   1503      * @param color Color tint to apply.
   1504      * @param mode How to apply the color.  The standard mode is
   1505      * {@link PorterDuff.Mode#SRC_ATOP}
   1506      *
   1507      * @attr ref android.R.styleable#ImageView_tint
   1508      */
   1509     public final void setColorFilter(int color, PorterDuff.Mode mode) {
   1510         setColorFilter(new PorterDuffColorFilter(color, mode));
   1511     }
   1512 
   1513     /**
   1514      * Set a tinting option for the image. Assumes
   1515      * {@link PorterDuff.Mode#SRC_ATOP} blending mode.
   1516      *
   1517      * @param color Color tint to apply.
   1518      * @attr ref android.R.styleable#ImageView_tint
   1519      */
   1520     @RemotableViewMethod
   1521     public final void setColorFilter(int color) {
   1522         setColorFilter(color, PorterDuff.Mode.SRC_ATOP);
   1523     }
   1524 
   1525     /**
   1526      * Removes the image's {@link android.graphics.ColorFilter}.
   1527      *
   1528      * @see #setColorFilter(int)
   1529      * @see #getColorFilter()
   1530      */
   1531     public final void clearColorFilter() {
   1532         setColorFilter(null);
   1533     }
   1534 
   1535     /**
   1536      * @hide Candidate for future API inclusion
   1537      */
   1538     public final void setXfermode(Xfermode mode) {
   1539         if (mXfermode != mode) {
   1540             mXfermode = mode;
   1541             mHasXfermode = true;
   1542             applyXfermode();
   1543             invalidate();
   1544         }
   1545     }
   1546 
   1547     /**
   1548      * Returns the active color filter for this ImageView.
   1549      *
   1550      * @return the active color filter for this ImageView
   1551      *
   1552      * @see #setColorFilter(android.graphics.ColorFilter)
   1553      */
   1554     public ColorFilter getColorFilter() {
   1555         return mColorFilter;
   1556     }
   1557 
   1558     /**
   1559      * Apply an arbitrary colorfilter to the image.
   1560      *
   1561      * @param cf the colorfilter to apply (may be null)
   1562      *
   1563      * @see #getColorFilter()
   1564      */
   1565     public void setColorFilter(ColorFilter cf) {
   1566         if (mColorFilter != cf) {
   1567             mColorFilter = cf;
   1568             mHasColorFilter = true;
   1569             applyColorFilter();
   1570             invalidate();
   1571         }
   1572     }
   1573 
   1574     /**
   1575      * Returns the alpha that will be applied to the drawable of this ImageView.
   1576      *
   1577      * @return the alpha value that will be applied to the drawable of this
   1578      * ImageView (between 0 and 255 inclusive, with 0 being transparent and
   1579      * 255 being opaque)
   1580      *
   1581      * @see #setImageAlpha(int)
   1582      */
   1583     public int getImageAlpha() {
   1584         return mAlpha;
   1585     }
   1586 
   1587     /**
   1588      * Sets the alpha value that should be applied to the image.
   1589      *
   1590      * @param alpha the alpha value that should be applied to the image (between
   1591      * 0 and 255 inclusive, with 0 being transparent and 255 being opaque)
   1592      *
   1593      * @see #getImageAlpha()
   1594      */
   1595     @RemotableViewMethod
   1596     public void setImageAlpha(int alpha) {
   1597         setAlpha(alpha);
   1598     }
   1599 
   1600     /**
   1601      * Sets the alpha value that should be applied to the image.
   1602      *
   1603      * @param alpha the alpha value that should be applied to the image
   1604      *
   1605      * @deprecated use #setImageAlpha(int) instead
   1606      */
   1607     @Deprecated
   1608     @RemotableViewMethod
   1609     public void setAlpha(int alpha) {
   1610         alpha &= 0xFF;          // keep it legal
   1611         if (mAlpha != alpha) {
   1612             mAlpha = alpha;
   1613             mHasAlpha = true;
   1614             applyAlpha();
   1615             invalidate();
   1616         }
   1617     }
   1618 
   1619     private void applyXfermode() {
   1620         if (mDrawable != null && mHasXfermode) {
   1621             mDrawable = mDrawable.mutate();
   1622             mDrawable.setXfermode(mXfermode);
   1623         }
   1624     }
   1625 
   1626     private void applyColorFilter() {
   1627         if (mDrawable != null && mHasColorFilter) {
   1628             mDrawable = mDrawable.mutate();
   1629             mDrawable.setColorFilter(mColorFilter);
   1630         }
   1631     }
   1632 
   1633     private void applyAlpha() {
   1634         if (mDrawable != null && mHasAlpha) {
   1635             mDrawable = mDrawable.mutate();
   1636             mDrawable.setAlpha(mAlpha * mViewAlphaScale >> 8);
   1637         }
   1638     }
   1639 
   1640     @Override
   1641     public boolean isOpaque() {
   1642         return super.isOpaque() || mDrawable != null && mXfermode == null
   1643                 && mDrawable.getOpacity() == PixelFormat.OPAQUE
   1644                 && mAlpha * mViewAlphaScale >> 8 == 255
   1645                 && isFilledByImage();
   1646     }
   1647 
   1648     private boolean isFilledByImage() {
   1649         if (mDrawable == null) {
   1650             return false;
   1651         }
   1652 
   1653         final Rect bounds = mDrawable.getBounds();
   1654         final Matrix matrix = mDrawMatrix;
   1655         if (matrix == null) {
   1656             return bounds.left <= 0 && bounds.top <= 0 && bounds.right >= getWidth()
   1657                     && bounds.bottom >= getHeight();
   1658         } else if (matrix.rectStaysRect()) {
   1659             final RectF boundsSrc = mTempSrc;
   1660             final RectF boundsDst = mTempDst;
   1661             boundsSrc.set(bounds);
   1662             matrix.mapRect(boundsDst, boundsSrc);
   1663             return boundsDst.left <= 0 && boundsDst.top <= 0 && boundsDst.right >= getWidth()
   1664                     && boundsDst.bottom >= getHeight();
   1665         } else {
   1666             // If the matrix doesn't map to a rectangle, assume the worst.
   1667             return false;
   1668         }
   1669     }
   1670 
   1671     @Override
   1672     public void onVisibilityAggregated(boolean isVisible) {
   1673         super.onVisibilityAggregated(isVisible);
   1674         // Only do this for new apps post-Nougat
   1675         if (mDrawable != null && !sCompatDrawableVisibilityDispatch) {
   1676             mDrawable.setVisible(isVisible, false);
   1677         }
   1678     }
   1679 
   1680     @RemotableViewMethod
   1681     @Override
   1682     public void setVisibility(int visibility) {
   1683         super.setVisibility(visibility);
   1684         // Only do this for old apps pre-Nougat; new apps use onVisibilityAggregated
   1685         if (mDrawable != null && sCompatDrawableVisibilityDispatch) {
   1686             mDrawable.setVisible(visibility == VISIBLE, false);
   1687         }
   1688     }
   1689 
   1690     @Override
   1691     protected void onAttachedToWindow() {
   1692         super.onAttachedToWindow();
   1693         // Only do this for old apps pre-Nougat; new apps use onVisibilityAggregated
   1694         if (mDrawable != null && sCompatDrawableVisibilityDispatch) {
   1695             mDrawable.setVisible(getVisibility() == VISIBLE, false);
   1696         }
   1697     }
   1698 
   1699     @Override
   1700     protected void onDetachedFromWindow() {
   1701         super.onDetachedFromWindow();
   1702         // Only do this for old apps pre-Nougat; new apps use onVisibilityAggregated
   1703         if (mDrawable != null && sCompatDrawableVisibilityDispatch) {
   1704             mDrawable.setVisible(false, false);
   1705         }
   1706     }
   1707 
   1708     @Override
   1709     public CharSequence getAccessibilityClassName() {
   1710         return ImageView.class.getName();
   1711     }
   1712 
   1713     /** @hide */
   1714     @Override
   1715     protected void encodeProperties(@NonNull ViewHierarchyEncoder stream) {
   1716         super.encodeProperties(stream);
   1717         stream.addProperty("layout:baseline", getBaseline());
   1718     }
   1719 
   1720     /** @hide */
   1721     @Override
   1722     @TestApi
   1723     public boolean isDefaultFocusHighlightNeeded(Drawable background, Drawable foreground) {
   1724         final boolean lackFocusState = mDrawable == null || !mDrawable.isStateful()
   1725                 || !mDrawable.hasFocusStateSpecified();
   1726         return super.isDefaultFocusHighlightNeeded(background, foreground) && lackFocusState;
   1727     }
   1728 }
   1729