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