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