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
17package com.android.calendar;
18
19import com.android.calendar.event.EditEventHelper.AttendeeItem;
20
21import android.content.Context;
22import android.graphics.drawable.Drawable;
23import android.net.Uri;
24import android.os.Handler;
25import android.os.HandlerThread;
26import android.os.Looper;
27import android.os.Message;
28import android.provider.ContactsContract.Contacts;
29import android.util.Log;
30import android.view.View;
31import android.widget.ImageView;
32
33import java.io.InputStream;
34
35/**
36 * Helper class for async access of images.
37 */
38public 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         */
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 {
81        public WorkerHandler(Looper looper) {
82            super(looper);
83        }
84
85        @Override
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     */
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     */
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     */
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
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