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.content.ContentResolver;
20import android.content.Context;
21import android.content.res.Resources;
22import android.content.res.TypedArray;
23import android.graphics.Bitmap;
24import android.graphics.Canvas;
25import android.graphics.ColorFilter;
26import android.graphics.Matrix;
27import android.graphics.PorterDuff;
28import android.graphics.PorterDuffColorFilter;
29import android.graphics.RectF;
30import android.graphics.drawable.BitmapDrawable;
31import android.graphics.drawable.Drawable;
32import android.net.Uri;
33import android.text.TextUtils;
34import android.util.AttributeSet;
35import android.util.Log;
36import android.view.RemotableViewMethod;
37import android.view.View;
38import android.view.ViewDebug;
39import android.view.accessibility.AccessibilityEvent;
40import android.view.accessibility.AccessibilityNodeInfo;
41import android.widget.RemoteViews.RemoteView;
42
43/**
44 * Displays an arbitrary image, such as an icon.  The ImageView class
45 * can load images from various sources (such as resources or content
46 * providers), takes care of computing its measurement from the image so that
47 * it can be used in any layout manager, and provides various display options
48 * such as scaling and tinting.
49 *
50 * @attr ref android.R.styleable#ImageView_adjustViewBounds
51 * @attr ref android.R.styleable#ImageView_src
52 * @attr ref android.R.styleable#ImageView_maxWidth
53 * @attr ref android.R.styleable#ImageView_maxHeight
54 * @attr ref android.R.styleable#ImageView_tint
55 * @attr ref android.R.styleable#ImageView_scaleType
56 * @attr ref android.R.styleable#ImageView_cropToPadding
57 */
58@RemoteView
59public class ImageView extends View {
60    // settable by the client
61    private Uri mUri;
62    private int mResource = 0;
63    private Matrix mMatrix;
64    private ScaleType mScaleType;
65    private boolean mHaveFrame = false;
66    private boolean mAdjustViewBounds = false;
67    private int mMaxWidth = Integer.MAX_VALUE;
68    private int mMaxHeight = Integer.MAX_VALUE;
69
70    // these are applied to the drawable
71    private ColorFilter mColorFilter;
72    private int mAlpha = 255;
73    private int mViewAlphaScale = 256;
74    private boolean mColorMod = false;
75
76    private Drawable mDrawable = null;
77    private int[] mState = null;
78    private boolean mMergeState = false;
79    private int mLevel = 0;
80    private int mDrawableWidth;
81    private int mDrawableHeight;
82    private Matrix mDrawMatrix = null;
83
84    // Avoid allocations...
85    private RectF mTempSrc = new RectF();
86    private RectF mTempDst = new RectF();
87
88    private boolean mCropToPadding;
89
90    private int mBaseline = -1;
91    private boolean mBaselineAlignBottom = false;
92
93    private static final ScaleType[] sScaleTypeArray = {
94        ScaleType.MATRIX,
95        ScaleType.FIT_XY,
96        ScaleType.FIT_START,
97        ScaleType.FIT_CENTER,
98        ScaleType.FIT_END,
99        ScaleType.CENTER,
100        ScaleType.CENTER_CROP,
101        ScaleType.CENTER_INSIDE
102    };
103
104    public ImageView(Context context) {
105        super(context);
106        initImageView();
107    }
108
109    public ImageView(Context context, AttributeSet attrs) {
110        this(context, attrs, 0);
111    }
112
113    public ImageView(Context context, AttributeSet attrs, int defStyle) {
114        super(context, attrs, defStyle);
115        initImageView();
116
117        TypedArray a = context.obtainStyledAttributes(attrs,
118                com.android.internal.R.styleable.ImageView, defStyle, 0);
119
120        Drawable d = a.getDrawable(com.android.internal.R.styleable.ImageView_src);
121        if (d != null) {
122            setImageDrawable(d);
123        }
124
125        mBaselineAlignBottom = a.getBoolean(
126                com.android.internal.R.styleable.ImageView_baselineAlignBottom, false);
127
128        mBaseline = a.getDimensionPixelSize(
129                com.android.internal.R.styleable.ImageView_baseline, -1);
130
131        setAdjustViewBounds(
132            a.getBoolean(com.android.internal.R.styleable.ImageView_adjustViewBounds,
133            false));
134
135        setMaxWidth(a.getDimensionPixelSize(
136                com.android.internal.R.styleable.ImageView_maxWidth, Integer.MAX_VALUE));
137
138        setMaxHeight(a.getDimensionPixelSize(
139                com.android.internal.R.styleable.ImageView_maxHeight, Integer.MAX_VALUE));
140
141        int index = a.getInt(com.android.internal.R.styleable.ImageView_scaleType, -1);
142        if (index >= 0) {
143            setScaleType(sScaleTypeArray[index]);
144        }
145
146        int tint = a.getInt(com.android.internal.R.styleable.ImageView_tint, 0);
147        if (tint != 0) {
148            setColorFilter(tint);
149        }
150
151        int alpha = a.getInt(com.android.internal.R.styleable.ImageView_drawableAlpha, 255);
152        if (alpha != 255) {
153            setAlpha(alpha);
154        }
155
156        mCropToPadding = a.getBoolean(
157                com.android.internal.R.styleable.ImageView_cropToPadding, false);
158
159        a.recycle();
160
161        //need inflate syntax/reader for matrix
162    }
163
164    private void initImageView() {
165        mMatrix     = new Matrix();
166        mScaleType  = ScaleType.FIT_CENTER;
167    }
168
169    @Override
170    protected boolean verifyDrawable(Drawable dr) {
171        return mDrawable == dr || super.verifyDrawable(dr);
172    }
173
174    @Override
175    public void jumpDrawablesToCurrentState() {
176        super.jumpDrawablesToCurrentState();
177        if (mDrawable != null) mDrawable.jumpToCurrentState();
178    }
179
180    @Override
181    public void invalidateDrawable(Drawable dr) {
182        if (dr == mDrawable) {
183            /* we invalidate the whole view in this case because it's very
184             * hard to know where the drawable actually is. This is made
185             * complicated because of the offsets and transformations that
186             * can be applied. In theory we could get the drawable's bounds
187             * and run them through the transformation and offsets, but this
188             * is probably not worth the effort.
189             */
190            invalidate();
191        } else {
192            super.invalidateDrawable(dr);
193        }
194    }
195
196    @Override
197    public boolean hasOverlappingRendering() {
198        return (getBackground() != null);
199    }
200
201    @Override
202    public void onPopulateAccessibilityEvent(AccessibilityEvent event) {
203        super.onPopulateAccessibilityEvent(event);
204        CharSequence contentDescription = getContentDescription();
205        if (!TextUtils.isEmpty(contentDescription)) {
206            event.getText().add(contentDescription);
207        }
208    }
209
210    /**
211     * True when ImageView is adjusting its bounds
212     * to preserve the aspect ratio of its drawable
213     *
214     * @return whether to adjust the bounds of this view
215     * to presrve the original aspect ratio of the drawable
216     *
217     * @see #setAdjustViewBounds(boolean)
218     *
219     * @attr ref android.R.styleable#ImageView_adjustViewBounds
220     */
221    public boolean getAdjustViewBounds() {
222        return mAdjustViewBounds;
223    }
224
225    /**
226     * Set this to true if you want the ImageView to adjust its bounds
227     * to preserve the aspect ratio of its drawable.
228     * @param adjustViewBounds Whether to adjust the bounds of this view
229     * to presrve the original aspect ratio of the drawable
230     *
231     * @see #getAdjustViewBounds()
232     *
233     * @attr ref android.R.styleable#ImageView_adjustViewBounds
234     */
235    @android.view.RemotableViewMethod
236    public void setAdjustViewBounds(boolean adjustViewBounds) {
237        mAdjustViewBounds = adjustViewBounds;
238        if (adjustViewBounds) {
239            setScaleType(ScaleType.FIT_CENTER);
240        }
241    }
242
243    /**
244     * The maximum width of this view.
245     *
246     * @return The maximum width of this view
247     *
248     * @see #setMaxWidth(int)
249     *
250     * @attr ref android.R.styleable#ImageView_maxWidth
251     */
252    public int getMaxWidth() {
253        return mMaxWidth;
254    }
255
256    /**
257     * An optional argument to supply a maximum width for this view. Only valid if
258     * {@link #setAdjustViewBounds(boolean)} has been set to true. To set an image to be a maximum
259     * of 100 x 100 while preserving the original aspect ratio, do the following: 1) set
260     * adjustViewBounds to true 2) set maxWidth and maxHeight to 100 3) set the height and width
261     * layout params to WRAP_CONTENT.
262     *
263     * <p>
264     * Note that this view could be still smaller than 100 x 100 using this approach if the original
265     * image is small. To set an image to a fixed size, specify that size in the layout params and
266     * then use {@link #setScaleType(android.widget.ImageView.ScaleType)} to determine how to fit
267     * the image within the bounds.
268     * </p>
269     *
270     * @param maxWidth maximum width for this view
271     *
272     * @see #getMaxWidth()
273     *
274     * @attr ref android.R.styleable#ImageView_maxWidth
275     */
276    @android.view.RemotableViewMethod
277    public void setMaxWidth(int maxWidth) {
278        mMaxWidth = maxWidth;
279    }
280
281    /**
282     * The maximum height of this view.
283     *
284     * @return The maximum height of this view
285     *
286     * @see #setMaxHeight(int)
287     *
288     * @attr ref android.R.styleable#ImageView_maxHeight
289     */
290    public int getMaxHeight() {
291        return mMaxHeight;
292    }
293
294    /**
295     * An optional argument to supply a maximum height for this view. Only valid if
296     * {@link #setAdjustViewBounds(boolean)} has been set to true. To set an image to be a
297     * maximum of 100 x 100 while preserving the original aspect ratio, do the following: 1) set
298     * adjustViewBounds to true 2) set maxWidth and maxHeight to 100 3) set the height and width
299     * layout params to WRAP_CONTENT.
300     *
301     * <p>
302     * Note that this view could be still smaller than 100 x 100 using this approach if the original
303     * image is small. To set an image to a fixed size, specify that size in the layout params and
304     * then use {@link #setScaleType(android.widget.ImageView.ScaleType)} to determine how to fit
305     * the image within the bounds.
306     * </p>
307     *
308     * @param maxHeight maximum height for this view
309     *
310     * @see #getMaxHeight()
311     *
312     * @attr ref android.R.styleable#ImageView_maxHeight
313     */
314    @android.view.RemotableViewMethod
315    public void setMaxHeight(int maxHeight) {
316        mMaxHeight = maxHeight;
317    }
318
319    /** Return the view's drawable, or null if no drawable has been
320        assigned.
321    */
322    public Drawable getDrawable() {
323        return mDrawable;
324    }
325
326    /**
327     * Sets a drawable as the content of this ImageView.
328     *
329     * <p class="note">This does Bitmap reading and decoding on the UI
330     * thread, which can cause a latency hiccup.  If that's a concern,
331     * consider using {@link #setImageDrawable(android.graphics.drawable.Drawable)} or
332     * {@link #setImageBitmap(android.graphics.Bitmap)} and
333     * {@link android.graphics.BitmapFactory} instead.</p>
334     *
335     * @param resId the resource identifier of the the drawable
336     *
337     * @attr ref android.R.styleable#ImageView_src
338     */
339    @android.view.RemotableViewMethod
340    public void setImageResource(int resId) {
341        if (mUri != null || mResource != resId) {
342            updateDrawable(null);
343            mResource = resId;
344            mUri = null;
345
346            final int oldWidth = mDrawableWidth;
347            final int oldHeight = mDrawableHeight;
348
349            resolveUri();
350
351            if (oldWidth != mDrawableWidth || oldHeight != mDrawableHeight) {
352                requestLayout();
353            }
354            invalidate();
355        }
356    }
357
358    /**
359     * Sets the content of this ImageView to the specified Uri.
360     *
361     * <p class="note">This does Bitmap reading and decoding on the UI
362     * thread, which can cause a latency hiccup.  If that's a concern,
363     * consider using {@link #setImageDrawable(android.graphics.drawable.Drawable)} or
364     * {@link #setImageBitmap(android.graphics.Bitmap)} and
365     * {@link android.graphics.BitmapFactory} instead.</p>
366     *
367     * @param uri The Uri of an image
368     */
369    @android.view.RemotableViewMethod
370    public void setImageURI(Uri uri) {
371        if (mResource != 0 ||
372                (mUri != uri &&
373                 (uri == null || mUri == null || !uri.equals(mUri)))) {
374            updateDrawable(null);
375            mResource = 0;
376            mUri = uri;
377
378            final int oldWidth = mDrawableWidth;
379            final int oldHeight = mDrawableHeight;
380
381            resolveUri();
382
383            if (oldWidth != mDrawableWidth || oldHeight != mDrawableHeight) {
384                requestLayout();
385            }
386            invalidate();
387        }
388    }
389
390    /**
391     * Sets a drawable as the content of this ImageView.
392     *
393     * @param drawable The drawable to set
394     */
395    public void setImageDrawable(Drawable drawable) {
396        if (mDrawable != drawable) {
397            mResource = 0;
398            mUri = null;
399
400            final int oldWidth = mDrawableWidth;
401            final int oldHeight = mDrawableHeight;
402
403            updateDrawable(drawable);
404
405            if (oldWidth != mDrawableWidth || oldHeight != mDrawableHeight) {
406                requestLayout();
407            }
408            invalidate();
409        }
410    }
411
412    /**
413     * Sets a Bitmap as the content of this ImageView.
414     *
415     * @param bm The bitmap to set
416     */
417    @android.view.RemotableViewMethod
418    public void setImageBitmap(Bitmap bm) {
419        // if this is used frequently, may handle bitmaps explicitly
420        // to reduce the intermediate drawable object
421        setImageDrawable(new BitmapDrawable(mContext.getResources(), bm));
422    }
423
424    public void setImageState(int[] state, boolean merge) {
425        mState = state;
426        mMergeState = merge;
427        if (mDrawable != null) {
428            refreshDrawableState();
429            resizeFromDrawable();
430        }
431    }
432
433    @Override
434    public void setSelected(boolean selected) {
435        super.setSelected(selected);
436        resizeFromDrawable();
437    }
438
439    /**
440     * Sets the image level, when it is constructed from a
441     * {@link android.graphics.drawable.LevelListDrawable}.
442     *
443     * @param level The new level for the image.
444     */
445    @android.view.RemotableViewMethod
446    public void setImageLevel(int level) {
447        mLevel = level;
448        if (mDrawable != null) {
449            mDrawable.setLevel(level);
450            resizeFromDrawable();
451        }
452    }
453
454    /**
455     * Options for scaling the bounds of an image to the bounds of this view.
456     */
457    public enum ScaleType {
458        /**
459         * Scale using the image matrix when drawing. The image matrix can be set using
460         * {@link ImageView#setImageMatrix(Matrix)}. From XML, use this syntax:
461         * <code>android:scaleType="matrix"</code>.
462         */
463        MATRIX      (0),
464        /**
465         * Scale the image using {@link Matrix.ScaleToFit#FILL}.
466         * From XML, use this syntax: <code>android:scaleType="fitXY"</code>.
467         */
468        FIT_XY      (1),
469        /**
470         * Scale the image using {@link Matrix.ScaleToFit#START}.
471         * From XML, use this syntax: <code>android:scaleType="fitStart"</code>.
472         */
473        FIT_START   (2),
474        /**
475         * Scale the image using {@link Matrix.ScaleToFit#CENTER}.
476         * From XML, use this syntax:
477         * <code>android:scaleType="fitCenter"</code>.
478         */
479        FIT_CENTER  (3),
480        /**
481         * Scale the image using {@link Matrix.ScaleToFit#END}.
482         * From XML, use this syntax: <code>android:scaleType="fitEnd"</code>.
483         */
484        FIT_END     (4),
485        /**
486         * Center the image in the view, but perform no scaling.
487         * From XML, use this syntax: <code>android:scaleType="center"</code>.
488         */
489        CENTER      (5),
490        /**
491         * Scale the image uniformly (maintain the image's aspect ratio) so
492         * that both dimensions (width and height) of the image will be equal
493         * to or larger than the corresponding dimension of the view
494         * (minus padding). The image is then centered in the view.
495         * From XML, use this syntax: <code>android:scaleType="centerCrop"</code>.
496         */
497        CENTER_CROP (6),
498        /**
499         * Scale the image uniformly (maintain the image's aspect ratio) so
500         * that both dimensions (width and height) of the image will be equal
501         * to or less than the corresponding dimension of the view
502         * (minus padding). The image is then centered in the view.
503         * From XML, use this syntax: <code>android:scaleType="centerInside"</code>.
504         */
505        CENTER_INSIDE (7);
506
507        ScaleType(int ni) {
508            nativeInt = ni;
509        }
510        final int nativeInt;
511    }
512
513    /**
514     * Controls how the image should be resized or moved to match the size
515     * of this ImageView.
516     *
517     * @param scaleType The desired scaling mode.
518     *
519     * @attr ref android.R.styleable#ImageView_scaleType
520     */
521    public void setScaleType(ScaleType scaleType) {
522        if (scaleType == null) {
523            throw new NullPointerException();
524        }
525
526        if (mScaleType != scaleType) {
527            mScaleType = scaleType;
528
529            setWillNotCacheDrawing(mScaleType == ScaleType.CENTER);
530
531            requestLayout();
532            invalidate();
533        }
534    }
535
536    /**
537     * Return the current scale type in use by this ImageView.
538     *
539     * @see ImageView.ScaleType
540     *
541     * @attr ref android.R.styleable#ImageView_scaleType
542     */
543    public ScaleType getScaleType() {
544        return mScaleType;
545    }
546
547    /** Return the view's optional matrix. This is applied to the
548        view's drawable when it is drawn. If there is not matrix,
549        this method will return null.
550        Do not change this matrix in place. If you want a different matrix
551        applied to the drawable, be sure to call setImageMatrix().
552    */
553    public Matrix getImageMatrix() {
554        return mMatrix;
555    }
556
557    public void setImageMatrix(Matrix matrix) {
558        // collaps null and identity to just null
559        if (matrix != null && matrix.isIdentity()) {
560            matrix = null;
561        }
562
563        // don't invalidate unless we're actually changing our matrix
564        if (matrix == null && !mMatrix.isIdentity() ||
565                matrix != null && !mMatrix.equals(matrix)) {
566            mMatrix.set(matrix);
567            configureBounds();
568            invalidate();
569        }
570    }
571
572    /**
573     * Return whether this ImageView crops to padding.
574     *
575     * @return whether this ImageView crops to padding
576     *
577     * @see #setCropToPadding(boolean)
578     *
579     * @attr ref android.R.styleable#ImageView_cropToPadding
580     */
581    public boolean getCropToPadding() {
582        return mCropToPadding;
583    }
584
585    /**
586     * Sets whether this ImageView will crop to padding.
587     *
588     * @param cropToPadding whether this ImageView will crop to padding
589     *
590     * @see #getCropToPadding()
591     *
592     * @attr ref android.R.styleable#ImageView_cropToPadding
593     */
594    public void setCropToPadding(boolean cropToPadding) {
595        if (mCropToPadding != cropToPadding) {
596            mCropToPadding = cropToPadding;
597            requestLayout();
598            invalidate();
599        }
600    }
601
602    private void resolveUri() {
603        if (mDrawable != null) {
604            return;
605        }
606
607        Resources rsrc = getResources();
608        if (rsrc == null) {
609            return;
610        }
611
612        Drawable d = null;
613
614        if (mResource != 0) {
615            try {
616                d = rsrc.getDrawable(mResource);
617            } catch (Exception e) {
618                Log.w("ImageView", "Unable to find resource: " + mResource, e);
619                // Don't try again.
620                mUri = null;
621            }
622        } else if (mUri != null) {
623            String scheme = mUri.getScheme();
624            if (ContentResolver.SCHEME_ANDROID_RESOURCE.equals(scheme)) {
625                try {
626                    // Load drawable through Resources, to get the source density information
627                    ContentResolver.OpenResourceIdResult r =
628                            mContext.getContentResolver().getResourceId(mUri);
629                    d = r.r.getDrawable(r.id);
630                } catch (Exception e) {
631                    Log.w("ImageView", "Unable to open content: " + mUri, e);
632                }
633            } else if (ContentResolver.SCHEME_CONTENT.equals(scheme)
634                    || ContentResolver.SCHEME_FILE.equals(scheme)) {
635                try {
636                    d = Drawable.createFromStream(
637                        mContext.getContentResolver().openInputStream(mUri),
638                        null);
639                } catch (Exception e) {
640                    Log.w("ImageView", "Unable to open content: " + mUri, e);
641                }
642            } else {
643                d = Drawable.createFromPath(mUri.toString());
644            }
645
646            if (d == null) {
647                System.out.println("resolveUri failed on bad bitmap uri: "
648                                   + mUri);
649                // Don't try again.
650                mUri = null;
651            }
652        } else {
653            return;
654        }
655
656        updateDrawable(d);
657    }
658
659    @Override
660    public int[] onCreateDrawableState(int extraSpace) {
661        if (mState == null) {
662            return super.onCreateDrawableState(extraSpace);
663        } else if (!mMergeState) {
664            return mState;
665        } else {
666            return mergeDrawableStates(
667                    super.onCreateDrawableState(extraSpace + mState.length), mState);
668        }
669    }
670
671    private void updateDrawable(Drawable d) {
672        if (mDrawable != null) {
673            mDrawable.setCallback(null);
674            unscheduleDrawable(mDrawable);
675        }
676        mDrawable = d;
677        if (d != null) {
678            d.setCallback(this);
679            if (d.isStateful()) {
680                d.setState(getDrawableState());
681            }
682            d.setLevel(mLevel);
683            d.setLayoutDirection(getLayoutDirection());
684            mDrawableWidth = d.getIntrinsicWidth();
685            mDrawableHeight = d.getIntrinsicHeight();
686            applyColorMod();
687            configureBounds();
688        } else {
689            mDrawableWidth = mDrawableHeight = -1;
690        }
691    }
692
693    private void resizeFromDrawable() {
694        Drawable d = mDrawable;
695        if (d != null) {
696            int w = d.getIntrinsicWidth();
697            if (w < 0) w = mDrawableWidth;
698            int h = d.getIntrinsicHeight();
699            if (h < 0) h = mDrawableHeight;
700            if (w != mDrawableWidth || h != mDrawableHeight) {
701                mDrawableWidth = w;
702                mDrawableHeight = h;
703                requestLayout();
704            }
705        }
706    }
707
708    private static final Matrix.ScaleToFit[] sS2FArray = {
709        Matrix.ScaleToFit.FILL,
710        Matrix.ScaleToFit.START,
711        Matrix.ScaleToFit.CENTER,
712        Matrix.ScaleToFit.END
713    };
714
715    private static Matrix.ScaleToFit scaleTypeToScaleToFit(ScaleType st)  {
716        // ScaleToFit enum to their corresponding Matrix.ScaleToFit values
717        return sS2FArray[st.nativeInt - 1];
718    }
719
720    @Override
721    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
722        resolveUri();
723        int w;
724        int h;
725
726        // Desired aspect ratio of the view's contents (not including padding)
727        float desiredAspect = 0.0f;
728
729        // We are allowed to change the view's width
730        boolean resizeWidth = false;
731
732        // We are allowed to change the view's height
733        boolean resizeHeight = false;
734
735        final int widthSpecMode = MeasureSpec.getMode(widthMeasureSpec);
736        final int heightSpecMode = MeasureSpec.getMode(heightMeasureSpec);
737
738        if (mDrawable == null) {
739            // If no drawable, its intrinsic size is 0.
740            mDrawableWidth = -1;
741            mDrawableHeight = -1;
742            w = h = 0;
743        } else {
744            w = mDrawableWidth;
745            h = mDrawableHeight;
746            if (w <= 0) w = 1;
747            if (h <= 0) h = 1;
748
749            // We are supposed to adjust view bounds to match the aspect
750            // ratio of our drawable. See if that is possible.
751            if (mAdjustViewBounds) {
752                resizeWidth = widthSpecMode != MeasureSpec.EXACTLY;
753                resizeHeight = heightSpecMode != MeasureSpec.EXACTLY;
754
755                desiredAspect = (float) w / (float) h;
756            }
757        }
758
759        int pleft = mPaddingLeft;
760        int pright = mPaddingRight;
761        int ptop = mPaddingTop;
762        int pbottom = mPaddingBottom;
763
764        int widthSize;
765        int heightSize;
766
767        if (resizeWidth || resizeHeight) {
768            /* If we get here, it means we want to resize to match the
769                drawables aspect ratio, and we have the freedom to change at
770                least one dimension.
771            */
772
773            // Get the max possible width given our constraints
774            widthSize = resolveAdjustedSize(w + pleft + pright, mMaxWidth, widthMeasureSpec);
775
776            // Get the max possible height given our constraints
777            heightSize = resolveAdjustedSize(h + ptop + pbottom, mMaxHeight, heightMeasureSpec);
778
779            if (desiredAspect != 0.0f) {
780                // See what our actual aspect ratio is
781                float actualAspect = (float)(widthSize - pleft - pright) /
782                                        (heightSize - ptop - pbottom);
783
784                if (Math.abs(actualAspect - desiredAspect) > 0.0000001) {
785
786                    boolean done = false;
787
788                    // Try adjusting width to be proportional to height
789                    if (resizeWidth) {
790                        int newWidth = (int)(desiredAspect * (heightSize - ptop - pbottom)) +
791                                pleft + pright;
792                        if (newWidth <= widthSize) {
793                            widthSize = newWidth;
794                            done = true;
795                        }
796                    }
797
798                    // Try adjusting height to be proportional to width
799                    if (!done && resizeHeight) {
800                        int newHeight = (int)((widthSize - pleft - pright) / desiredAspect) +
801                                ptop + pbottom;
802                        if (newHeight <= heightSize) {
803                            heightSize = newHeight;
804                        }
805                    }
806                }
807            }
808        } else {
809            /* We are either don't want to preserve the drawables aspect ratio,
810               or we are not allowed to change view dimensions. Just measure in
811               the normal way.
812            */
813            w += pleft + pright;
814            h += ptop + pbottom;
815
816            w = Math.max(w, getSuggestedMinimumWidth());
817            h = Math.max(h, getSuggestedMinimumHeight());
818
819            widthSize = resolveSizeAndState(w, widthMeasureSpec, 0);
820            heightSize = resolveSizeAndState(h, heightMeasureSpec, 0);
821        }
822
823        setMeasuredDimension(widthSize, heightSize);
824    }
825
826    private int resolveAdjustedSize(int desiredSize, int maxSize,
827                                   int measureSpec) {
828        int result = desiredSize;
829        int specMode = MeasureSpec.getMode(measureSpec);
830        int specSize =  MeasureSpec.getSize(measureSpec);
831        switch (specMode) {
832            case MeasureSpec.UNSPECIFIED:
833                /* Parent says we can be as big as we want. Just don't be larger
834                   than max size imposed on ourselves.
835                */
836                result = Math.min(desiredSize, maxSize);
837                break;
838            case MeasureSpec.AT_MOST:
839                // Parent says we can be as big as we want, up to specSize.
840                // Don't be larger than specSize, and don't be larger than
841                // the max size imposed on ourselves.
842                result = Math.min(Math.min(desiredSize, specSize), maxSize);
843                break;
844            case MeasureSpec.EXACTLY:
845                // No choice. Do what we are told.
846                result = specSize;
847                break;
848        }
849        return result;
850    }
851
852    @Override
853    protected boolean setFrame(int l, int t, int r, int b) {
854        boolean changed = super.setFrame(l, t, r, b);
855        mHaveFrame = true;
856        configureBounds();
857        return changed;
858    }
859
860    private void configureBounds() {
861        if (mDrawable == null || !mHaveFrame) {
862            return;
863        }
864
865        int dwidth = mDrawableWidth;
866        int dheight = mDrawableHeight;
867
868        int vwidth = getWidth() - mPaddingLeft - mPaddingRight;
869        int vheight = getHeight() - mPaddingTop - mPaddingBottom;
870
871        boolean fits = (dwidth < 0 || vwidth == dwidth) &&
872                       (dheight < 0 || vheight == dheight);
873
874        if (dwidth <= 0 || dheight <= 0 || ScaleType.FIT_XY == mScaleType) {
875            /* If the drawable has no intrinsic size, or we're told to
876                scaletofit, then we just fill our entire view.
877            */
878            mDrawable.setBounds(0, 0, vwidth, vheight);
879            mDrawMatrix = null;
880        } else {
881            // We need to do the scaling ourself, so have the drawable
882            // use its native size.
883            mDrawable.setBounds(0, 0, dwidth, dheight);
884
885            if (ScaleType.MATRIX == mScaleType) {
886                // Use the specified matrix as-is.
887                if (mMatrix.isIdentity()) {
888                    mDrawMatrix = null;
889                } else {
890                    mDrawMatrix = mMatrix;
891                }
892            } else if (fits) {
893                // The bitmap fits exactly, no transform needed.
894                mDrawMatrix = null;
895            } else if (ScaleType.CENTER == mScaleType) {
896                // Center bitmap in view, no scaling.
897                mDrawMatrix = mMatrix;
898                mDrawMatrix.setTranslate((int) ((vwidth - dwidth) * 0.5f + 0.5f),
899                                         (int) ((vheight - dheight) * 0.5f + 0.5f));
900            } else if (ScaleType.CENTER_CROP == mScaleType) {
901                mDrawMatrix = mMatrix;
902
903                float scale;
904                float dx = 0, dy = 0;
905
906                if (dwidth * vheight > vwidth * dheight) {
907                    scale = (float) vheight / (float) dheight;
908                    dx = (vwidth - dwidth * scale) * 0.5f;
909                } else {
910                    scale = (float) vwidth / (float) dwidth;
911                    dy = (vheight - dheight * scale) * 0.5f;
912                }
913
914                mDrawMatrix.setScale(scale, scale);
915                mDrawMatrix.postTranslate((int) (dx + 0.5f), (int) (dy + 0.5f));
916            } else if (ScaleType.CENTER_INSIDE == mScaleType) {
917                mDrawMatrix = mMatrix;
918                float scale;
919                float dx;
920                float dy;
921
922                if (dwidth <= vwidth && dheight <= vheight) {
923                    scale = 1.0f;
924                } else {
925                    scale = Math.min((float) vwidth / (float) dwidth,
926                            (float) vheight / (float) dheight);
927                }
928
929                dx = (int) ((vwidth - dwidth * scale) * 0.5f + 0.5f);
930                dy = (int) ((vheight - dheight * scale) * 0.5f + 0.5f);
931
932                mDrawMatrix.setScale(scale, scale);
933                mDrawMatrix.postTranslate(dx, dy);
934            } else {
935                // Generate the required transform.
936                mTempSrc.set(0, 0, dwidth, dheight);
937                mTempDst.set(0, 0, vwidth, vheight);
938
939                mDrawMatrix = mMatrix;
940                mDrawMatrix.setRectToRect(mTempSrc, mTempDst, scaleTypeToScaleToFit(mScaleType));
941            }
942        }
943    }
944
945    @Override
946    protected void drawableStateChanged() {
947        super.drawableStateChanged();
948        Drawable d = mDrawable;
949        if (d != null && d.isStateful()) {
950            d.setState(getDrawableState());
951        }
952    }
953
954    @Override
955    protected void onDraw(Canvas canvas) {
956        super.onDraw(canvas);
957
958        if (mDrawable == null) {
959            return; // couldn't resolve the URI
960        }
961
962        if (mDrawableWidth == 0 || mDrawableHeight == 0) {
963            return;     // nothing to draw (empty bounds)
964        }
965
966        if (mDrawMatrix == null && mPaddingTop == 0 && mPaddingLeft == 0) {
967            mDrawable.draw(canvas);
968        } else {
969            int saveCount = canvas.getSaveCount();
970            canvas.save();
971
972            if (mCropToPadding) {
973                final int scrollX = mScrollX;
974                final int scrollY = mScrollY;
975                canvas.clipRect(scrollX + mPaddingLeft, scrollY + mPaddingTop,
976                        scrollX + mRight - mLeft - mPaddingRight,
977                        scrollY + mBottom - mTop - mPaddingBottom);
978            }
979
980            canvas.translate(mPaddingLeft, mPaddingTop);
981
982            if (mDrawMatrix != null) {
983                canvas.concat(mDrawMatrix);
984            }
985            mDrawable.draw(canvas);
986            canvas.restoreToCount(saveCount);
987        }
988    }
989
990    /**
991     * <p>Return the offset of the widget's text baseline from the widget's top
992     * boundary. </p>
993     *
994     * @return the offset of the baseline within the widget's bounds or -1
995     *         if baseline alignment is not supported.
996     */
997    @Override
998    @ViewDebug.ExportedProperty(category = "layout")
999    public int getBaseline() {
1000        if (mBaselineAlignBottom) {
1001            return getMeasuredHeight();
1002        } else {
1003            return mBaseline;
1004        }
1005    }
1006
1007    /**
1008     * <p>Set the offset of the widget's text baseline from the widget's top
1009     * boundary.  This value is overridden by the {@link #setBaselineAlignBottom(boolean)}
1010     * property.</p>
1011     *
1012     * @param baseline The baseline to use, or -1 if none is to be provided.
1013     *
1014     * @see #setBaseline(int)
1015     * @attr ref android.R.styleable#ImageView_baseline
1016     */
1017    public void setBaseline(int baseline) {
1018        if (mBaseline != baseline) {
1019            mBaseline = baseline;
1020            requestLayout();
1021        }
1022    }
1023
1024    /**
1025     * Set whether to set the baseline of this view to the bottom of the view.
1026     * Setting this value overrides any calls to setBaseline.
1027     *
1028     * @param aligned If true, the image view will be baseline aligned with
1029     *      based on its bottom edge.
1030     *
1031     * @attr ref android.R.styleable#ImageView_baselineAlignBottom
1032     */
1033    public void setBaselineAlignBottom(boolean aligned) {
1034        if (mBaselineAlignBottom != aligned) {
1035            mBaselineAlignBottom = aligned;
1036            requestLayout();
1037        }
1038    }
1039
1040    /**
1041     * Return whether this view's baseline will be considered the bottom of the view.
1042     *
1043     * @see #setBaselineAlignBottom(boolean)
1044     */
1045    public boolean getBaselineAlignBottom() {
1046        return mBaselineAlignBottom;
1047    }
1048
1049    /**
1050     * Set a tinting option for the image.
1051     *
1052     * @param color Color tint to apply.
1053     * @param mode How to apply the color.  The standard mode is
1054     * {@link PorterDuff.Mode#SRC_ATOP}
1055     *
1056     * @attr ref android.R.styleable#ImageView_tint
1057     */
1058    public final void setColorFilter(int color, PorterDuff.Mode mode) {
1059        setColorFilter(new PorterDuffColorFilter(color, mode));
1060    }
1061
1062    /**
1063     * Set a tinting option for the image. Assumes
1064     * {@link PorterDuff.Mode#SRC_ATOP} blending mode.
1065     *
1066     * @param color Color tint to apply.
1067     * @attr ref android.R.styleable#ImageView_tint
1068     */
1069    @RemotableViewMethod
1070    public final void setColorFilter(int color) {
1071        setColorFilter(color, PorterDuff.Mode.SRC_ATOP);
1072    }
1073
1074    public final void clearColorFilter() {
1075        setColorFilter(null);
1076    }
1077
1078    /**
1079     * Returns the active color filter for this ImageView.
1080     *
1081     * @return the active color filter for this ImageView
1082     *
1083     * @see #setColorFilter(android.graphics.ColorFilter)
1084     */
1085    public ColorFilter getColorFilter() {
1086        return mColorFilter;
1087    }
1088
1089    /**
1090     * Apply an arbitrary colorfilter to the image.
1091     *
1092     * @param cf the colorfilter to apply (may be null)
1093     *
1094     * @see #getColorFilter()
1095     */
1096    public void setColorFilter(ColorFilter cf) {
1097        if (mColorFilter != cf) {
1098            mColorFilter = cf;
1099            mColorMod = true;
1100            applyColorMod();
1101            invalidate();
1102        }
1103    }
1104
1105    /**
1106     * Returns the alpha that will be applied to the drawable of this ImageView.
1107     *
1108     * @return the alpha that will be applied to the drawable of this ImageView
1109     *
1110     * @see #setImageAlpha(int)
1111     */
1112    public int getImageAlpha() {
1113        return mAlpha;
1114    }
1115
1116    /**
1117     * Sets the alpha value that should be applied to the image.
1118     *
1119     * @param alpha the alpha value that should be applied to the image
1120     *
1121     * @see #getImageAlpha()
1122     */
1123    @RemotableViewMethod
1124    public void setImageAlpha(int alpha) {
1125        setAlpha(alpha);
1126    }
1127
1128    /**
1129     * Sets the alpha value that should be applied to the image.
1130     *
1131     * @param alpha the alpha value that should be applied to the image
1132     *
1133     * @deprecated use #setImageAlpha(int) instead
1134     */
1135    @Deprecated
1136    @RemotableViewMethod
1137    public void setAlpha(int alpha) {
1138        alpha &= 0xFF;          // keep it legal
1139        if (mAlpha != alpha) {
1140            mAlpha = alpha;
1141            mColorMod = true;
1142            applyColorMod();
1143            invalidate();
1144        }
1145    }
1146
1147    private void applyColorMod() {
1148        // Only mutate and apply when modifications have occurred. This should
1149        // not reset the mColorMod flag, since these filters need to be
1150        // re-applied if the Drawable is changed.
1151        if (mDrawable != null && mColorMod) {
1152            mDrawable = mDrawable.mutate();
1153            mDrawable.setColorFilter(mColorFilter);
1154            mDrawable.setAlpha(mAlpha * mViewAlphaScale >> 8);
1155        }
1156    }
1157
1158    @RemotableViewMethod
1159    @Override
1160    public void setVisibility(int visibility) {
1161        super.setVisibility(visibility);
1162        if (mDrawable != null) {
1163            mDrawable.setVisible(visibility == VISIBLE, false);
1164        }
1165    }
1166
1167    @Override
1168    protected void onAttachedToWindow() {
1169        super.onAttachedToWindow();
1170        if (mDrawable != null) {
1171            mDrawable.setVisible(getVisibility() == VISIBLE, false);
1172        }
1173    }
1174
1175    @Override
1176    protected void onDetachedFromWindow() {
1177        super.onDetachedFromWindow();
1178        if (mDrawable != null) {
1179            mDrawable.setVisible(false, false);
1180        }
1181    }
1182
1183    @Override
1184    public void onInitializeAccessibilityEvent(AccessibilityEvent event) {
1185        super.onInitializeAccessibilityEvent(event);
1186        event.setClassName(ImageView.class.getName());
1187    }
1188
1189    @Override
1190    public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
1191        super.onInitializeAccessibilityNodeInfo(info);
1192        info.setClassName(ImageView.class.getName());
1193    }
1194}
1195