• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2008 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 android.pim;
18 
19 import com.android.internal.telephony.CallerInfo;
20 import com.android.internal.telephony.Connection;
21 
22 import android.content.ContentUris;
23 import android.content.Context;
24 import android.graphics.drawable.Drawable;
25 import android.net.Uri;
26 import android.os.Handler;
27 import android.os.HandlerThread;
28 import android.os.Looper;
29 import android.os.Message;
30 import android.provider.ContactsContract.Contacts;
31 import android.util.Log;
32 import android.view.View;
33 import android.widget.ImageView;
34 
35 import java.io.InputStream;
36 
37 /**
38  * Helper class for async access of images.
39  */
40 public class ContactsAsyncHelper extends Handler {
41 
42     private static final boolean DBG = false;
43     private static final String LOG_TAG = "ContactsAsyncHelper";
44 
45     /**
46      * Interface for a WorkerHandler result return.
47      */
48     public interface OnImageLoadCompleteListener {
49         /**
50          * Called when the image load is complete.
51          *
52          * @param imagePresent true if an image was found
53          */
onImageLoadComplete(int token, Object cookie, ImageView iView, boolean imagePresent)54         public void onImageLoadComplete(int token, Object cookie, ImageView iView,
55                 boolean imagePresent);
56     }
57 
58     // constants
59     private static final int EVENT_LOAD_IMAGE = 1;
60     private static final int DEFAULT_TOKEN = -1;
61 
62     // static objects
63     private static Handler sThreadHandler;
64     private static ContactsAsyncHelper sInstance;
65 
66     static {
67         sInstance = new ContactsAsyncHelper();
68     }
69 
70     private static final class WorkerArgs {
71         public Context context;
72         public ImageView view;
73         public Uri uri;
74         public int defaultResource;
75         public Object result;
76         public Object cookie;
77         public OnImageLoadCompleteListener listener;
78         public CallerInfo info;
79     }
80 
81     /**
82      * public inner class to help out the ContactsAsyncHelper callers
83      * with tracking the state of the CallerInfo Queries and image
84      * loading.
85      *
86      * Logic contained herein is used to remove the race conditions
87      * that exist as the CallerInfo queries run and mix with the image
88      * loads, which then mix with the Phone state changes.
89      */
90     public static class ImageTracker {
91 
92         // Image display states
93         public static final int DISPLAY_UNDEFINED = 0;
94         public static final int DISPLAY_IMAGE = -1;
95         public static final int DISPLAY_DEFAULT = -2;
96 
97         // State of the image on the imageview.
98         private CallerInfo mCurrentCallerInfo;
99         private int displayMode;
100 
ImageTracker()101         public ImageTracker() {
102             mCurrentCallerInfo = null;
103             displayMode = DISPLAY_UNDEFINED;
104         }
105 
106         /**
107          * Used to see if the requested call / connection has a
108          * different caller attached to it than the one we currently
109          * have in the CallCard.
110          */
isDifferentImageRequest(CallerInfo ci)111         public boolean isDifferentImageRequest(CallerInfo ci) {
112             // note, since the connections are around for the lifetime of the
113             // call, and the CallerInfo-related items as well, we can
114             // definitely use a simple != comparison.
115             return (mCurrentCallerInfo != ci);
116         }
117 
isDifferentImageRequest(Connection connection)118         public boolean isDifferentImageRequest(Connection connection) {
119             // if the connection does not exist, see if the
120             // mCurrentCallerInfo is also null to match.
121             if (connection == null) {
122                 if (DBG) Log.d(LOG_TAG, "isDifferentImageRequest: connection is null");
123                 return (mCurrentCallerInfo != null);
124             }
125             Object o = connection.getUserData();
126 
127             // if the call does NOT have a callerInfo attached
128             // then it is ok to query.
129             boolean runQuery = true;
130             if (o instanceof CallerInfo) {
131                 runQuery = isDifferentImageRequest((CallerInfo) o);
132             }
133             return runQuery;
134         }
135 
136         /**
137          * Simple setter for the CallerInfo object.
138          */
setPhotoRequest(CallerInfo ci)139         public void setPhotoRequest(CallerInfo ci) {
140             mCurrentCallerInfo = ci;
141         }
142 
143         /**
144          * Convenience method used to retrieve the URI
145          * representing the Photo file recorded in the attached
146          * CallerInfo Object.
147          */
getPhotoUri()148         public Uri getPhotoUri() {
149             if (mCurrentCallerInfo != null) {
150                 return ContentUris.withAppendedId(Contacts.CONTENT_URI,
151                         mCurrentCallerInfo.person_id);
152             }
153             return null;
154         }
155 
156         /**
157          * Simple setter for the Photo state.
158          */
setPhotoState(int state)159         public void setPhotoState(int state) {
160             displayMode = state;
161         }
162 
163         /**
164          * Simple getter for the Photo state.
165          */
getPhotoState()166         public int getPhotoState() {
167             return displayMode;
168         }
169     }
170 
171     /**
172      * Thread worker class that handles the task of opening the stream and loading
173      * the images.
174      */
175     private class WorkerHandler extends Handler {
WorkerHandler(Looper looper)176         public WorkerHandler(Looper looper) {
177             super(looper);
178         }
179 
180         @Override
handleMessage(Message msg)181         public void handleMessage(Message msg) {
182             WorkerArgs args = (WorkerArgs) msg.obj;
183 
184             switch (msg.arg1) {
185                 case EVENT_LOAD_IMAGE:
186                     InputStream inputStream = null;
187                     try {
188                         inputStream = Contacts.openContactPhotoInputStream(
189                                 args.context.getContentResolver(), args.uri);
190                     } catch (Exception e) {
191                         Log.e(LOG_TAG, "Error opening photo input stream", e);
192                     }
193 
194                     if (inputStream != null) {
195                         args.result = Drawable.createFromStream(inputStream, args.uri.toString());
196 
197                         if (DBG) Log.d(LOG_TAG, "Loading image: " + msg.arg1 +
198                                 " token: " + msg.what + " image URI: " + args.uri);
199                     } else {
200                         args.result = null;
201                         if (DBG) Log.d(LOG_TAG, "Problem with image: " + msg.arg1 +
202                                 " token: " + msg.what + " image URI: " + args.uri +
203                                 ", using default image.");
204                     }
205                     break;
206                 default:
207             }
208 
209             // send the reply to the enclosing class.
210             Message reply = ContactsAsyncHelper.this.obtainMessage(msg.what);
211             reply.arg1 = msg.arg1;
212             reply.obj = msg.obj;
213             reply.sendToTarget();
214         }
215     }
216 
217     /**
218      * Private constructor for static class
219      */
ContactsAsyncHelper()220     private ContactsAsyncHelper() {
221         HandlerThread thread = new HandlerThread("ContactsAsyncWorker");
222         thread.start();
223         sThreadHandler = new WorkerHandler(thread.getLooper());
224     }
225 
226     /**
227      * Convenience method for calls that do not want to deal with listeners and tokens.
228      */
updateImageViewWithContactPhotoAsync(Context context, ImageView imageView, Uri person, int placeholderImageResource)229     public static final void updateImageViewWithContactPhotoAsync(Context context,
230             ImageView imageView, Uri person, int placeholderImageResource) {
231         // Added additional Cookie field in the callee.
232         updateImageViewWithContactPhotoAsync (null, DEFAULT_TOKEN, null, null, context,
233                 imageView, person, placeholderImageResource);
234     }
235 
236     /**
237      * Convenience method for calls that do not want to deal with listeners and tokens, but have
238      * a CallerInfo object to cache the image to.
239      */
updateImageViewWithContactPhotoAsync(CallerInfo info, Context context, ImageView imageView, Uri person, int placeholderImageResource)240     public static final void updateImageViewWithContactPhotoAsync(CallerInfo info, Context context,
241             ImageView imageView, Uri person, int placeholderImageResource) {
242         // Added additional Cookie field in the callee.
243         updateImageViewWithContactPhotoAsync (info, DEFAULT_TOKEN, null, null, context,
244                 imageView, person, placeholderImageResource);
245     }
246 
247 
248     /**
249      * Start an image load, attach the result to the specified CallerInfo object.
250      * Note, when the query is started, we make the ImageView INVISIBLE if the
251      * placeholderImageResource value is -1.  When we're given a valid (!= -1)
252      * placeholderImageResource value, we make sure the image is visible.
253      */
updateImageViewWithContactPhotoAsync(CallerInfo info, int token, OnImageLoadCompleteListener listener, Object cookie, Context context, ImageView imageView, Uri person, int placeholderImageResource)254     public static final void updateImageViewWithContactPhotoAsync(CallerInfo info, int token,
255             OnImageLoadCompleteListener listener, Object cookie, Context context,
256             ImageView imageView, Uri person, int placeholderImageResource) {
257 
258         // in case the source caller info is null, the URI will be null as well.
259         // just update using the placeholder image in this case.
260         if (person == null) {
261             if (DBG) Log.d(LOG_TAG, "target image is null, just display placeholder.");
262             imageView.setVisibility(View.VISIBLE);
263             imageView.setImageResource(placeholderImageResource);
264             return;
265         }
266 
267         // Added additional Cookie field in the callee to handle arguments
268         // sent to the callback function.
269 
270         // setup arguments
271         WorkerArgs args = new WorkerArgs();
272         args.cookie = cookie;
273         args.context = context;
274         args.view = imageView;
275         args.uri = person;
276         args.defaultResource = placeholderImageResource;
277         args.listener = listener;
278         args.info = info;
279 
280         // setup message arguments
281         Message msg = sThreadHandler.obtainMessage(token);
282         msg.arg1 = EVENT_LOAD_IMAGE;
283         msg.obj = args;
284 
285         if (DBG) Log.d(LOG_TAG, "Begin loading image: " + args.uri +
286                 ", displaying default image for now.");
287 
288         // set the default image first, when the query is complete, we will
289         // replace the image with the correct one.
290         if (placeholderImageResource != -1) {
291             imageView.setVisibility(View.VISIBLE);
292             imageView.setImageResource(placeholderImageResource);
293         } else {
294             imageView.setVisibility(View.INVISIBLE);
295         }
296 
297         // notify the thread to begin working
298         sThreadHandler.sendMessage(msg);
299     }
300 
301     /**
302      * Called when loading is done.
303      */
304     @Override
handleMessage(Message msg)305     public void handleMessage(Message msg) {
306         WorkerArgs args = (WorkerArgs) msg.obj;
307         switch (msg.arg1) {
308             case EVENT_LOAD_IMAGE:
309                 boolean imagePresent = false;
310 
311                 // if the image has been loaded then display it, otherwise set default.
312                 // in either case, make sure the image is visible.
313                 if (args.result != null) {
314                     args.view.setVisibility(View.VISIBLE);
315                     args.view.setImageDrawable((Drawable) args.result);
316                     // make sure the cached photo data is updated.
317                     if (args.info != null) {
318                         args.info.cachedPhoto = (Drawable) args.result;
319                     }
320                     imagePresent = true;
321                 } else if (args.defaultResource != -1) {
322                     args.view.setVisibility(View.VISIBLE);
323                     args.view.setImageResource(args.defaultResource);
324                 }
325 
326                 // Note that the data is cached.
327                 if (args.info != null) {
328                     args.info.isCachedPhotoCurrent = true;
329                 }
330 
331                 // notify the listener if it is there.
332                 if (args.listener != null) {
333                     if (DBG) Log.d(LOG_TAG, "Notifying listener: " + args.listener.toString() +
334                             " image: " + args.uri + " completed");
335                     args.listener.onImageLoadComplete(msg.what, args.cookie, args.view,
336                             imagePresent);
337                 }
338                 break;
339             default:
340         }
341     }
342 }
343