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