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 com.android.calendar;
18 
19 import com.android.calendar.event.EditEventHelper.AttendeeItem;
20 
21 import android.content.Context;
22 import android.graphics.drawable.Drawable;
23 import android.net.Uri;
24 import android.os.Handler;
25 import android.os.HandlerThread;
26 import android.os.Looper;
27 import android.os.Message;
28 import android.provider.ContactsContract.Contacts;
29 import android.util.Log;
30 import android.view.View;
31 import android.widget.ImageView;
32 
33 import java.io.InputStream;
34 
35 /**
36  * Helper class for async access of images.
37  */
38 public class ContactsAsyncHelper extends Handler {
39 
40     private static final boolean DBG = false;
41     private static final String LOG_TAG = "ContactsAsyncHelper";
42 
43     private static ContactsAsyncHelper mInstance = null;
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 EVENT_LOAD_DRAWABLE = 2;
61     private static final int DEFAULT_TOKEN = -1;
62 
63     // static objects
64     private static Handler sThreadHandler;
65 
66     private static final class WorkerArgs {
67         public Context context;
68         public ImageView view;
69         public Uri uri;
70         public int defaultResource;
71         public Object result;
72         public AttendeeItem item;
73         public Runnable callback;
74     }
75 
76     /**
77      * Thread worker class that handles the task of opening the stream and loading
78      * the images.
79      */
80     private class WorkerHandler extends Handler {
WorkerHandler(Looper looper)81         public WorkerHandler(Looper looper) {
82             super(looper);
83         }
84 
85         @Override
handleMessage(Message msg)86         public void handleMessage(Message msg) {
87             WorkerArgs args = (WorkerArgs) msg.obj;
88 
89             switch (msg.arg1) {
90                 case EVENT_LOAD_DRAWABLE:
91                 case EVENT_LOAD_IMAGE:
92                     InputStream inputStream = null;
93                     try {
94                         inputStream = Contacts.openContactPhotoInputStream(
95                                 args.context.getContentResolver(), args.uri);
96                     } catch (Exception e) {
97                         Log.e(LOG_TAG, "Error opening photo input stream", e);
98                     }
99 
100                     if (inputStream != null) {
101                         args.result = Drawable.createFromStream(inputStream, args.uri.toString());
102 
103                         if (DBG) Log.d(LOG_TAG, "Loading image: " + msg.arg1 +
104                                 " token: " + msg.what + " image URI: " + args.uri);
105                     } else {
106                         args.result = null;
107                         if (DBG) Log.d(LOG_TAG, "Problem with image: " + msg.arg1 +
108                                 " token: " + msg.what + " image URI: " + args.uri +
109                                 ", using default image.");
110                     }
111                     break;
112                 default:
113             }
114 
115             // send the reply to the enclosing class.
116             Message reply = ContactsAsyncHelper.this.obtainMessage(msg.what);
117             reply.arg1 = msg.arg1;
118             reply.obj = msg.obj;
119             reply.sendToTarget();
120         }
121     }
122 
123     /**
124      * Private constructor for static class
125      */
ContactsAsyncHelper()126     private ContactsAsyncHelper() {
127         HandlerThread thread = new HandlerThread("ContactsAsyncWorker");
128         thread.start();
129         sThreadHandler = new WorkerHandler(thread.getLooper());
130     }
131 
132     /**
133      * Start an image load, attach the result to the specified CallerInfo object.
134      * Note, when the query is started, we make the ImageView INVISIBLE if the
135      * placeholderImageResource value is -1.  When we're given a valid (!= -1)
136      * placeholderImageResource value, we make sure the image is visible.
137      */
updateImageViewWithContactPhotoAsync(Context context, ImageView imageView, Uri contact, int placeholderImageResource)138     public static final void updateImageViewWithContactPhotoAsync(Context context,
139             ImageView imageView, Uri contact, int placeholderImageResource) {
140 
141         // in case the source caller info is null, the URI will be null as well.
142         // just update using the placeholder image in this case.
143         if (contact == null) {
144             if (DBG) Log.d(LOG_TAG, "target image is null, just display placeholder.");
145             imageView.setVisibility(View.VISIBLE);
146             imageView.setImageResource(placeholderImageResource);
147             return;
148         }
149 
150         // Added additional Cookie field in the callee to handle arguments
151         // sent to the callback function.
152 
153         // setup arguments
154         WorkerArgs args = new WorkerArgs();
155         args.context = context;
156         args.view = imageView;
157         args.uri = contact;
158         args.defaultResource = placeholderImageResource;
159 
160         if (mInstance == null) {
161             mInstance = new ContactsAsyncHelper();
162         }
163         // setup message arguments
164         Message msg = sThreadHandler.obtainMessage(DEFAULT_TOKEN);
165         msg.arg1 = EVENT_LOAD_IMAGE;
166         msg.obj = args;
167 
168         if (DBG) Log.d(LOG_TAG, "Begin loading image: " + args.uri +
169                 ", displaying default image for now.");
170 
171         // set the default image first, when the query is complete, we will
172         // replace the image with the correct one.
173         if (placeholderImageResource != -1) {
174             imageView.setVisibility(View.VISIBLE);
175             imageView.setImageResource(placeholderImageResource);
176         } else {
177             imageView.setVisibility(View.INVISIBLE);
178         }
179 
180         // notify the thread to begin working
181         sThreadHandler.sendMessage(msg);
182     }
183 
184     /**
185      * Start an image load, attach the result to the specified CallerInfo object.
186      * Note, when the query is started, we make the ImageView INVISIBLE if the
187      * placeholderImageResource value is -1.  When we're given a valid (!= -1)
188      * placeholderImageResource value, we make sure the image is visible.
189      */
retrieveContactPhotoAsync(Context context, AttendeeItem item, Runnable run, Uri photoUri)190     public static final void retrieveContactPhotoAsync(Context context,
191             AttendeeItem item, Runnable run, Uri photoUri) {
192 
193         // in case the source caller info is null, the URI will be null as well.
194         // just return as there's nothing to do.
195         if (photoUri == null) {
196             return;
197         }
198 
199         // Added additional Cookie field in the callee to handle arguments
200         // sent to the callback function.
201 
202         // setup arguments
203         WorkerArgs args = new WorkerArgs();
204         args.context = context;
205         args.item = item;
206         args.uri = photoUri;
207         args.callback = run;
208 
209         if (mInstance == null) {
210             mInstance = new ContactsAsyncHelper();
211         }
212         // setup message arguments
213         Message msg = sThreadHandler.obtainMessage(DEFAULT_TOKEN);
214         msg.arg1 = EVENT_LOAD_DRAWABLE;
215         msg.obj = args;
216 
217         if (DBG) Log.d(LOG_TAG, "Begin loading drawable: " + args.uri);
218 
219 
220         // notify the thread to begin working
221         sThreadHandler.sendMessage(msg);
222     }
223 
224     /**
225      * Called when loading is done.
226      */
227     @Override
handleMessage(Message msg)228     public void handleMessage(Message msg) {
229         WorkerArgs args = (WorkerArgs) msg.obj;
230         switch (msg.arg1) {
231             case EVENT_LOAD_IMAGE:
232                 // if the image has been loaded then display it, otherwise set default.
233                 // in either case, make sure the image is visible.
234                 if (args.result != null) {
235                     args.view.setVisibility(View.VISIBLE);
236                     args.view.setImageDrawable((Drawable) args.result);
237                 } else if (args.defaultResource != -1) {
238                     args.view.setVisibility(View.VISIBLE);
239                     args.view.setImageResource(args.defaultResource);
240                 }
241                 break;
242             case EVENT_LOAD_DRAWABLE:
243                 if (args.result != null) {
244                     args.item.mBadge = (Drawable) args.result;
245                     if (args.callback != null) {
246                         args.callback.run();
247                     }
248                 }
249                 break;
250             default:
251         }
252     }
253 }
254