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 */ 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