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