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