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 android.content.res.Resources;
20import android.graphics.Bitmap;
21import android.graphics.BitmapFactory;
22import android.graphics.drawable.BitmapDrawable;
23import android.graphics.drawable.Drawable;
24import android.graphics.drawable.TransitionDrawable;
25import android.media.ThumbnailUtils;
26import android.text.TextUtils;
27import android.widget.ImageView;
28
29import com.android.contacts.ContactPhotoManager;
30import com.android.contacts.ContactPhotoManager.DefaultImageRequest;
31import com.android.contacts.lettertiles.LetterTileDrawable;
32import com.android.contacts.model.Contact;
33
34import java.util.Arrays;
35
36/**
37 * Initialized with a target ImageView. When provided with a compressed image
38 * (i.e. a byte[]), it appropriately updates the ImageView's Drawable.
39 */
40public class ImageViewDrawableSetter {
41    private ImageView mTarget;
42    private byte[] mCompressed;
43    private Drawable mPreviousDrawable;
44    private int mDurationInMillis = 0;
45    private Contact mContact;
46    private static final String TAG = "ImageViewDrawableSetter";
47
48    public ImageViewDrawableSetter() {
49    }
50
51    public ImageViewDrawableSetter(ImageView target) {
52        mTarget = target;
53    }
54
55    public Bitmap setupContactPhoto(Contact contactData, ImageView photoView) {
56        mContact = contactData;
57        setTarget(photoView);
58        return setCompressedImage(contactData.getPhotoBinaryData());
59    }
60
61    public void setTransitionDuration(int durationInMillis) {
62        mDurationInMillis = durationInMillis;
63    }
64
65    public ImageView getTarget() {
66        return mTarget;
67    }
68
69    /**
70     * Re-initialize to use new target. As a result, the next time a new image
71     * is set, it will immediately be applied to the target (there will be no
72     * fade transition).
73     */
74    protected void setTarget(ImageView target) {
75        if (mTarget != target) {
76            mTarget = target;
77            mCompressed = null;
78            mPreviousDrawable = null;
79        }
80    }
81
82    protected byte[] getCompressedImage() {
83        return mCompressed;
84    }
85
86    protected Bitmap setCompressedImage(byte[] compressed) {
87        if (mPreviousDrawable == null) {
88            // If we don't already have a drawable, skip the exit-early test
89            // below; otherwise we might not end up setting the default image.
90        } else if (mPreviousDrawable != null
91                && mPreviousDrawable instanceof BitmapDrawable
92                && Arrays.equals(mCompressed, compressed)) {
93            // TODO: the worst case is when the arrays are equal but not
94            // identical. This takes about 1ms (more with high-res photos). A
95            // possible optimization is to sparsely sample chunks of the arrays
96            // to compare.
97            return previousBitmap();
98        }
99
100        Drawable newDrawable = decodedBitmapDrawable(compressed);
101        if (newDrawable == null) {
102            newDrawable = defaultDrawable();
103        }
104
105        // Remember this for next time, so that we can check if it changed.
106        mCompressed = compressed;
107
108        // If we don't have a new Drawable, something went wrong... bail out.
109        if (newDrawable == null) return previousBitmap();
110
111        if (mPreviousDrawable == null || mDurationInMillis == 0) {
112            // Set the new one immediately.
113            mTarget.setImageDrawable(newDrawable);
114        } else {
115            // Set up a transition from the previous Drawable to the new one.
116            final Drawable[] beforeAndAfter = new Drawable[2];
117            beforeAndAfter[0] = mPreviousDrawable;
118            beforeAndAfter[1] = newDrawable;
119            final TransitionDrawable transition = new TransitionDrawable(beforeAndAfter);
120            mTarget.setImageDrawable(transition);
121            transition.startTransition(mDurationInMillis);
122        }
123
124        // Remember this for next time, so that we can transition from it to the
125        // new one.
126        mPreviousDrawable = newDrawable;
127
128        return previousBitmap();
129    }
130
131    private Bitmap previousBitmap() {
132        return (mPreviousDrawable == null) ? null
133                : mPreviousDrawable instanceof LetterTileDrawable ? null
134                : ((BitmapDrawable) mPreviousDrawable).getBitmap();
135    }
136
137    /**
138     * Obtain the default drawable for a contact when no photo is available. If this is a local
139     * contact, then use the contact's display name and lookup key (as a unique identifier) to
140     * retrieve a default drawable for this contact. If not, then use the name as the contact
141     * identifier instead.
142     */
143    private Drawable defaultDrawable() {
144        Resources resources = mTarget.getResources();
145        DefaultImageRequest request;
146        int contactType = ContactPhotoManager.TYPE_DEFAULT;
147
148        if (mContact.isDisplayNameFromOrganization()) {
149            contactType = ContactPhotoManager.TYPE_BUSINESS;
150        }
151
152        if (TextUtils.isEmpty(mContact.getLookupKey())) {
153            request = new DefaultImageRequest(null, mContact.getDisplayName(), contactType,
154                    false /* isCircular */);
155        } else {
156            request = new DefaultImageRequest(mContact.getDisplayName(), mContact.getLookupKey(),
157                    contactType, false /* isCircular */);
158        }
159        return ContactPhotoManager.getDefaultAvatarDrawableForContact(resources, true, request);
160    }
161
162    private BitmapDrawable decodedBitmapDrawable(byte[] compressed) {
163        if (compressed == null) {
164            return null;
165        }
166        final Resources rsrc = mTarget.getResources();
167        Bitmap bitmap = BitmapFactory.decodeByteArray(compressed, 0, compressed.length);
168        if (bitmap == null) {
169            return null;
170        }
171        if (bitmap.getHeight() != bitmap.getWidth()) {
172            // Crop the bitmap into a square.
173            final int size = Math.min(bitmap.getWidth(), bitmap.getHeight());
174            bitmap = ThumbnailUtils.extractThumbnail(bitmap, size, size);
175        }
176        return new BitmapDrawable(rsrc, bitmap);
177    }
178}
179