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