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 
17 package com.android.contacts.util;
18 
19 import android.content.res.Resources;
20 import android.graphics.Bitmap;
21 import android.graphics.BitmapFactory;
22 import android.graphics.drawable.BitmapDrawable;
23 import android.graphics.drawable.Drawable;
24 import android.graphics.drawable.TransitionDrawable;
25 import android.media.ThumbnailUtils;
26 import android.text.TextUtils;
27 import android.widget.ImageView;
28 
29 import com.android.contacts.ContactPhotoManager;
30 import com.android.contacts.ContactPhotoManager.DefaultImageRequest;
31 import com.android.contacts.lettertiles.LetterTileDrawable;
32 import com.android.contacts.model.Contact;
33 
34 import 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  */
40 public 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 
ImageViewDrawableSetter()48     public ImageViewDrawableSetter() {
49     }
50 
ImageViewDrawableSetter(ImageView target)51     public ImageViewDrawableSetter(ImageView target) {
52         mTarget = target;
53     }
54 
setupContactPhoto(Contact contactData, ImageView photoView)55     public Bitmap setupContactPhoto(Contact contactData, ImageView photoView) {
56         mContact = contactData;
57         setTarget(photoView);
58         return setCompressedImage(contactData.getPhotoBinaryData());
59     }
60 
setTransitionDuration(int durationInMillis)61     public void setTransitionDuration(int durationInMillis) {
62         mDurationInMillis = durationInMillis;
63     }
64 
getTarget()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      */
setTarget(ImageView target)74     protected void setTarget(ImageView target) {
75         if (mTarget != target) {
76             mTarget = target;
77             mCompressed = null;
78             mPreviousDrawable = null;
79         }
80     }
81 
getCompressedImage()82     protected byte[] getCompressedImage() {
83         return mCompressed;
84     }
85 
setCompressedImage(byte[] compressed)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 
previousBitmap()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      */
defaultDrawable()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 
decodedBitmapDrawable(byte[] compressed)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