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