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