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