/** * Copyright (C) 2010-2012 Regis Montoya (aka r3gis - www.r3gis.fr) * This file is part of CSipSimple. * * CSipSimple is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * If you own a pjsip commercial license you can also redistribute it * and/or modify it under the terms of the GNU Lesser General Public License * as an android library. * * CSipSimple is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with CSipSimple. If not, see <http://www.gnu.org/licenses/>. */ /** * This file contains relicensed code from Apache copyright of * Copyright (C) 2008 The Android Open Source Project */ package com.csipsimple.utils; import android.content.Context; import android.graphics.Bitmap; import android.graphics.BitmapFactory; import android.net.Uri; import android.os.Handler; import android.os.HandlerThread; import android.os.Looper; import android.os.Message; import android.support.v4.util.LruCache; import android.text.TextUtils; import android.view.View; import android.widget.ImageView; import com.csipsimple.R; import com.csipsimple.models.CallerInfo; import com.csipsimple.utils.contacts.ContactsWrapper; import java.io.ByteArrayOutputStream; import java.io.InputStream; public class ContactsAsyncHelper extends Handler { private static final String THIS_FILE = "ContactsAsyncHelper"; // TODO : use LRUCache for bitmaps. LruCache<Uri, Bitmap> photoCache = new LruCache<Uri, Bitmap>(5 * 1024 * 1024 /* 5MiB */) { protected int sizeOf(Uri key, Bitmap value) { return value.getRowBytes() * value.getWidth(); } }; /** * Interface for a WorkerHandler result return. */ public interface OnImageLoadCompleteListener { /** * Called when the image load is complete. * * @param imagePresent true if an image was found */ public void onImageLoadComplete(int token, Object cookie, ImageView iView, boolean imagePresent); } // constants private static final int EVENT_LOAD_IMAGE = 1; private static final int EVENT_LOAD_IMAGE_URI = 2; private static final int EVENT_LOAD_CONTACT_URI = 3; private static final int DEFAULT_TOKEN = -1; private static final int TAG_PHOTO_INFOS = R.id.icon; private static ContactsWrapper contactsWrapper; // static objects private static Handler sThreadHandler; private static final class WorkerArgs { public Context context; public ImageView view; public int defaultResource; public Object result; public Uri loadedUri; public Object cookie; public OnImageLoadCompleteListener listener; } private static class PhotoViewTag { public Uri uri; } public static final String HIGH_RES_URI_PARAM = "hiRes"; /** * Thread worker class that handles the task of opening the stream and * loading the images. */ private class WorkerHandler extends Handler { public WorkerHandler(Looper looper) { super(looper); } public void handleMessage(Message msg) { WorkerArgs args = (WorkerArgs) msg.obj; Uri uri = null; if (msg.arg1 == EVENT_LOAD_IMAGE) { PhotoViewTag photoTag = (PhotoViewTag) args.view.getTag(TAG_PHOTO_INFOS); if (photoTag != null && photoTag.uri != null) { uri = photoTag.uri; boolean hiRes = false; String p = uri.getQueryParameter(HIGH_RES_URI_PARAM); if(!TextUtils.isEmpty(p) && p.equalsIgnoreCase("1")) { hiRes = true; } Log.v(THIS_FILE, "get : " + uri); Bitmap img = null; synchronized (photoCache) { img = photoCache.get(uri); } if(img == null) { img = contactsWrapper.getContactPhoto(args.context, uri, hiRes, args.defaultResource); synchronized (photoCache) { photoCache.put(uri, img); } } if (img != null) { args.result = img; } else { args.result = null; } } } else if (msg.arg1 == EVENT_LOAD_IMAGE_URI || msg.arg1 == EVENT_LOAD_CONTACT_URI) { PhotoViewTag photoTag = (PhotoViewTag) args.view.getTag(TAG_PHOTO_INFOS); if (photoTag != null && photoTag.uri != null) { uri = photoTag.uri; Log.v(THIS_FILE, "get : " + uri); Bitmap img = null; synchronized (photoCache) { img = photoCache.get(uri); } if (img == null) { if (msg.arg1 == EVENT_LOAD_IMAGE_URI) { try { byte[] buffer = new byte[1024 * 16]; InputStream is = args.context.getContentResolver().openInputStream( uri); if (is != null) { ByteArrayOutputStream baos = new ByteArrayOutputStream(); try { int size; while ((size = is.read(buffer)) != -1) { baos.write(buffer, 0, size); } } finally { is.close(); } byte[] boasBytes = baos.toByteArray(); img = BitmapFactory.decodeByteArray(boasBytes, 0, boasBytes.length, null); } } catch (Exception ex) { Log.v(THIS_FILE, "Cannot load photo " + uri, ex); } } else if (msg.arg1 == EVENT_LOAD_CONTACT_URI) { img = ContactsWrapper.getInstance().getContactPhoto(args.context, uri, false, null); } } if (img != null) { args.result = img; synchronized (photoCache) { photoCache.put(uri, img); } } else { args.result = null; } } } args.loadedUri = uri; // send the reply to the enclosing class. Message reply = ContactsAsyncHelper.this.obtainMessage(msg.what); reply.arg1 = msg.arg1; reply.obj = msg.obj; reply.sendToTarget(); } } /** * Private constructor for static class */ private ContactsAsyncHelper() { HandlerThread thread = new HandlerThread("ContactsAsyncWorker"); thread.start(); sThreadHandler = new WorkerHandler(thread.getLooper()); contactsWrapper = ContactsWrapper.getInstance(); } /** * Convenience method for calls that do not want to deal with listeners and * tokens. */ public static final void updateImageViewWithContactPhotoAsync(Context context, ImageView imageView, CallerInfo person, int placeholderImageResource) { // Added additional Cookie field in the callee. updateImageViewWithContactPhotoAsync(DEFAULT_TOKEN, null, null, context, imageView, person, placeholderImageResource); } /** * Start an image load, attach the result to the specified CallerInfo * object. Note, when the query is started, we make the ImageView INVISIBLE * if the placeholderImageResource value is -1. When we're given a valid (!= * -1) placeholderImageResource value, we make sure the image is visible. */ public static final void updateImageViewWithContactPhotoAsync(int token, OnImageLoadCompleteListener listener, Object cookie, Context context, ImageView imageView, CallerInfo callerInfo, int placeholderImageResource) { if (sThreadHandler == null) { new ContactsAsyncHelper(); } // in case the source caller info is null, the URI will be null as well. // just update using the placeholder image in this case. if (callerInfo == null || callerInfo.contactContentUri == null) { defaultImage(imageView, placeholderImageResource); return; } // Check that the view is not already loading for same uri if (isAlreadyProcessed(imageView, callerInfo.contactContentUri)) { return; } // Added additional Cookie field in the callee to handle arguments // sent to the callback function. // setup arguments WorkerArgs args = new WorkerArgs(); args.cookie = cookie; args.context = context; args.view = imageView; PhotoViewTag photoTag = new PhotoViewTag(); photoTag.uri = callerInfo.contactContentUri; args.view.setTag(TAG_PHOTO_INFOS, photoTag); args.defaultResource = placeholderImageResource; args.listener = listener; // setup message arguments Message msg = sThreadHandler.obtainMessage(token); msg.arg1 = EVENT_LOAD_IMAGE; msg.obj = args; preloadImage(imageView, placeholderImageResource, msg); } public static void updateImageViewWithContactPhotoAsync(Context context, ImageView imageView, Uri photoUri, int placeholderImageResource) { updateImageViewWithUriAsync(context, imageView, photoUri, placeholderImageResource, EVENT_LOAD_IMAGE_URI); } public static void updateImageViewWithContactAsync(Context context, ImageView imageView, Uri contactUri, int placeholderImageResource) { updateImageViewWithUriAsync(context, imageView, contactUri, placeholderImageResource, EVENT_LOAD_CONTACT_URI); } private static void updateImageViewWithUriAsync(Context context, ImageView imageView, Uri photoUri, int placeholderImageResource, int eventType) { if (sThreadHandler == null) { Log.v(THIS_FILE, "Update image view with contact async"); new ContactsAsyncHelper(); } // in case the source caller info is null, the URI will be null as well. // just update using the placeholder image in this case. if (photoUri == null) { defaultImage(imageView, placeholderImageResource); return; } if (isAlreadyProcessed(imageView, photoUri)) { return; } // Added additional Cookie field in the callee to handle arguments // sent to the callback function. // setup arguments WorkerArgs args = new WorkerArgs(); args.context = context; args.view = imageView; PhotoViewTag photoTag = new PhotoViewTag(); photoTag.uri = photoUri; args.view.setTag(TAG_PHOTO_INFOS, photoTag); args.defaultResource = placeholderImageResource; // setup message arguments Message msg = sThreadHandler.obtainMessage(); msg.arg1 = eventType; msg.obj = args; preloadImage(imageView, placeholderImageResource, msg); } private static void defaultImage(ImageView imageView, int placeholderImageResource) { Log.v(THIS_FILE, "No uri, just display placeholder."); PhotoViewTag photoTag = new PhotoViewTag(); photoTag.uri = null; imageView.setTag(TAG_PHOTO_INFOS, photoTag); imageView.setVisibility(View.VISIBLE); imageView.setImageResource(placeholderImageResource); } private static void preloadImage(ImageView imageView, int placeholderImageResource, Message msg) { // set the default image first, when the query is complete, we will // replace the image with the correct one. if (placeholderImageResource != -1) { imageView.setVisibility(View.VISIBLE); imageView.setImageResource(placeholderImageResource); } else { imageView.setVisibility(View.INVISIBLE); } // notify the thread to begin working sThreadHandler.sendMessage(msg); } private static boolean isAlreadyProcessed(ImageView imageView, Uri uri) { if(imageView != null) { PhotoViewTag vt = (PhotoViewTag) imageView.getTag(TAG_PHOTO_INFOS); return (vt != null && UriUtils.areEqual(uri, vt.uri)); } return true; } /** * Called when loading is done. */ @Override public void handleMessage(Message msg) { WorkerArgs args = (WorkerArgs) msg.obj; if (msg.arg1 == EVENT_LOAD_IMAGE || msg.arg1 == EVENT_LOAD_IMAGE_URI || msg.arg1 == EVENT_LOAD_CONTACT_URI) { boolean imagePresent = false; // Sanity check on image view PhotoViewTag photoTag = (PhotoViewTag) args.view.getTag(TAG_PHOTO_INFOS); if (photoTag == null) { Log.w(THIS_FILE, "Tag has been removed meanwhile"); return; } if (!UriUtils.areEqual(args.loadedUri, photoTag.uri)) { Log.w(THIS_FILE, "Image view has changed uri meanwhile"); return; } // if the image has been loaded then display it, otherwise set // default. // in either case, make sure the image is visible. if (args.result != null) { args.view.setVisibility(View.VISIBLE); args.view.setImageBitmap((Bitmap) args.result); imagePresent = true; } else if (args.defaultResource != -1) { args.view.setVisibility(View.VISIBLE); args.view.setImageResource(args.defaultResource); } // notify the listener if it is there. if (args.listener != null) { Log.v(THIS_FILE, "Notifying listener: " + args.listener.toString() + " image: " + args.loadedUri + " completed"); args.listener.onImageLoadComplete(msg.what, args.cookie, args.view, imagePresent); } } } }