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