1/*
2 * Copyright (C) 2012 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 com.android.contacts.util;
18
19import com.android.contacts.ContactLoader.Result;
20import com.android.contacts.ContactPhotoManager;
21
22import android.content.res.Resources;
23import android.content.res.Resources.NotFoundException;
24import android.graphics.Bitmap;
25import android.graphics.BitmapFactory;
26import android.graphics.drawable.BitmapDrawable;
27import android.graphics.drawable.Drawable;
28import android.graphics.drawable.TransitionDrawable;
29import android.util.Log;
30import android.widget.ImageView;
31
32import java.util.Arrays;
33
34/**
35 * Initialized with a target ImageView. When provided with a compressed image
36 * (i.e. a byte[]), it appropriately updates the ImageView's Drawable.
37 */
38public class ImageViewDrawableSetter {
39    private ImageView mTarget;
40    private byte[] mCompressed;
41    private Drawable mPreviousDrawable;
42    private int mDurationInMillis = 0;
43    private static final String TAG = "ImageViewDrawableSetter";
44
45    public ImageViewDrawableSetter() {
46    }
47
48    public ImageViewDrawableSetter(ImageView target) {
49        mTarget = target;
50    }
51
52    public void setupContactPhoto(Result contactData, ImageView photoView) {
53        setTarget(photoView);
54        setCompressedImage(contactData.getPhotoBinaryData());
55    }
56
57    public void setTransitionDuration(int durationInMillis) {
58        mDurationInMillis = durationInMillis;
59    }
60
61    public ImageView getTarget() {
62        return mTarget;
63    }
64
65    /**
66     * Re-initialize to use new target. As a result, the next time a new image
67     * is set, it will immediately be applied to the target (there will be no
68     * fade transition).
69     */
70    protected void setTarget(ImageView target) {
71        if (mTarget != target) {
72            mTarget = target;
73            mCompressed = null;
74            mPreviousDrawable = null;
75        }
76    }
77
78    protected byte[] getCompressedImage() {
79        return mCompressed;
80    }
81
82    protected Bitmap setCompressedImage(byte[] compressed) {
83        if (mPreviousDrawable == null) {
84            // If we don't already have a drawable, skip the exit-early test
85            // below; otherwise we might not end up setting the default image.
86        } else if (mPreviousDrawable != null && Arrays.equals(mCompressed, compressed)) {
87            // TODO: the worst case is when the arrays are equal but not
88            // identical. This takes about 1ms (more with high-res photos). A
89            // possible optimization is to sparsely sample chunks of the arrays
90            // to compare.
91            return previousBitmap();
92        }
93
94        final Drawable newDrawable = (compressed == null)
95                ? defaultDrawable()
96                : decodedBitmapDrawable(compressed);
97
98        // Remember this for next time, so that we can check if it changed.
99        mCompressed = compressed;
100
101        // If we don't have a new Drawable, something went wrong... bail out.
102        if (newDrawable == null) return previousBitmap();
103
104        if (mPreviousDrawable == null || mDurationInMillis == 0) {
105            // Set the new one immediately.
106            mTarget.setImageDrawable(newDrawable);
107        } else {
108            // Set up a transition from the previous Drawable to the new one.
109            final Drawable[] beforeAndAfter = new Drawable[2];
110            beforeAndAfter[0] = mPreviousDrawable;
111            beforeAndAfter[1] = newDrawable;
112            final TransitionDrawable transition = new TransitionDrawable(beforeAndAfter);
113            mTarget.setImageDrawable(transition);
114            transition.startTransition(mDurationInMillis);
115        }
116
117        // Remember this for next time, so that we can transition from it to the
118        // new one.
119        mPreviousDrawable = newDrawable;
120
121        return previousBitmap();
122    }
123
124    private Bitmap previousBitmap() {
125        return (mPreviousDrawable == null)
126                ? null
127                : ((BitmapDrawable) mPreviousDrawable).getBitmap();
128    }
129
130    /**
131     * Obtain the default drawable for a contact when no photo is available.
132     */
133    private Drawable defaultDrawable() {
134        Resources resources = mTarget.getResources();
135        final int resId = ContactPhotoManager.getDefaultAvatarResId(true, false);
136        try {
137            return resources.getDrawable(resId);
138        } catch (NotFoundException e) {
139            Log.wtf(TAG, "Cannot load default avatar resource.");
140            return null;
141        }
142    }
143
144    private BitmapDrawable decodedBitmapDrawable(byte[] compressed) {
145        Resources rsrc = mTarget.getResources();
146        Bitmap bitmap = BitmapFactory.decodeByteArray(compressed, 0, compressed.length);
147        return new BitmapDrawable(rsrc, bitmap);
148    }
149
150}
151