diff options
Diffstat (limited to 'InCallUI/src/com/android/incallui/ContactsAsyncHelper.java')
-rw-r--r-- | InCallUI/src/com/android/incallui/ContactsAsyncHelper.java | 337 |
1 files changed, 337 insertions, 0 deletions
diff --git a/InCallUI/src/com/android/incallui/ContactsAsyncHelper.java b/InCallUI/src/com/android/incallui/ContactsAsyncHelper.java new file mode 100644 index 000000000..c9a331771 --- /dev/null +++ b/InCallUI/src/com/android/incallui/ContactsAsyncHelper.java @@ -0,0 +1,337 @@ +/* + * Copyright (C) 2008 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.incallui; + +import android.app.Notification; +import android.content.ContentUris; +import android.content.Context; +import android.graphics.Bitmap; +import android.graphics.drawable.BitmapDrawable; +import android.graphics.drawable.Drawable; +import android.net.Uri; +import android.os.Handler; +import android.os.HandlerThread; +import android.os.Looper; +import android.os.Message; +import android.provider.ContactsContract.Contacts; +import android.util.Log; + +import java.io.IOException; +import java.io.InputStream; + +/** + * Helper class for loading contacts photo asynchronously. + */ +public class ContactsAsyncHelper { + + private static final boolean DBG = false; + private static final String LOG_TAG = "ContactsAsyncHelper"; + + /** + * Interface for a WorkerHandler result return. + */ + public interface OnImageLoadCompleteListener { + /** + * Called when the image load is complete. + * + * @param token Integer passed in {@link ContactsAsyncHelper#startObtainPhotoAsync(int, + * Context, Uri, OnImageLoadCompleteListener, Object)}. + * @param photo Drawable object obtained by the async load. + * @param photoIcon Bitmap object obtained by the async load. + * @param cookie Object passed in {@link ContactsAsyncHelper#startObtainPhotoAsync(int, + * Context, Uri, OnImageLoadCompleteListener, Object)}. Can be null iff. the original + * cookie is null. + */ + public void onImageLoadComplete(int token, Drawable photo, Bitmap photoIcon, + Object cookie); + } + + // constants + private static final int EVENT_LOAD_IMAGE = 1; + + private final Handler mResultHandler = new Handler() { + /** Called when loading is done. */ + @Override + public void handleMessage(Message msg) { + WorkerArgs args = (WorkerArgs) msg.obj; + switch (msg.arg1) { + case EVENT_LOAD_IMAGE: + if (args.listener != null) { + if (DBG) { + Log.d(LOG_TAG, "Notifying listener: " + args.listener.toString() + + " image: " + args.uri + " completed"); + } + args.listener.onImageLoadComplete(msg.what, args.photo, args.photoIcon, + args.cookie); + } + break; + default: + } + } + }; + + /** Handler run on a worker thread to load photo asynchronously. */ + private static Handler sThreadHandler; + + /** For forcing the system to call its constructor */ + @SuppressWarnings("unused") + private static ContactsAsyncHelper sInstance; + + static { + sInstance = new ContactsAsyncHelper(); + } + + private static final class WorkerArgs { + public Context context; + public Uri uri; + public Drawable photo; + public Bitmap photoIcon; + public Object cookie; + public OnImageLoadCompleteListener listener; + } + + /** + * public inner class to help out the ContactsAsyncHelper callers + * with tracking the state of the CallerInfo Queries and image + * loading. + * + * Logic contained herein is used to remove the race conditions + * that exist as the CallerInfo queries run and mix with the image + * loads, which then mix with the Phone state changes. + */ + public static class ImageTracker { + + // Image display states + public static final int DISPLAY_UNDEFINED = 0; + public static final int DISPLAY_IMAGE = -1; + public static final int DISPLAY_DEFAULT = -2; + + // State of the image on the imageview. + private CallerInfo mCurrentCallerInfo; + private int displayMode; + + public ImageTracker() { + mCurrentCallerInfo = null; + displayMode = DISPLAY_UNDEFINED; + } + + /** + * Used to see if the requested call / connection has a + * different caller attached to it than the one we currently + * have in the CallCard. + */ + public boolean isDifferentImageRequest(CallerInfo ci) { + // note, since the connections are around for the lifetime of the + // call, and the CallerInfo-related items as well, we can + // definitely use a simple != comparison. + return (mCurrentCallerInfo != ci); + } + + /** + * Simple setter for the CallerInfo object. + */ + public void setPhotoRequest(CallerInfo info) { + mCurrentCallerInfo = info; + } + + /** + * Convenience method used to retrieve the URI + * representing the Photo file recorded in the attached + * CallerInfo Object. + */ + public Uri getPhotoUri() { + if (mCurrentCallerInfo != null) { + return ContentUris.withAppendedId(Contacts.CONTENT_URI, + mCurrentCallerInfo.person_id); + } + return null; + } + + /** + * Simple setter for the Photo state. + */ + public void setPhotoState(int state) { + displayMode = state; + } + + /** + * Simple getter for the Photo state. + */ + public int getPhotoState() { + return displayMode; + } + } + + /** + * 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); + } + + @Override + public void handleMessage(Message msg) { + WorkerArgs args = (WorkerArgs) msg.obj; + + switch (msg.arg1) { + case EVENT_LOAD_IMAGE: + InputStream inputStream = null; + try { + try { + inputStream = Contacts.openContactPhotoInputStream( + args.context.getContentResolver(), args.uri, true); + } catch (Exception e) { + Log.e(LOG_TAG, "Error opening photo input stream", e); + } + + if (inputStream != null) { + args.photo = Drawable.createFromStream(inputStream, + args.uri.toString()); + + // This assumes Drawable coming from contact database is usually + // BitmapDrawable and thus we can have (down)scaled version of it. + args.photoIcon = getPhotoIconWhenAppropriate(args.context, args.photo); + + if (DBG) { + Log.d(LOG_TAG, "Loading image: " + msg.arg1 + + " token: " + msg.what + " image URI: " + args.uri); + } + } else { + args.photo = null; + args.photoIcon = null; + if (DBG) { + Log.d(LOG_TAG, "Problem with image: " + msg.arg1 + + " token: " + msg.what + " image URI: " + args.uri + + ", using default image."); + } + } + } finally { + if (inputStream != null) { + try { + inputStream.close(); + } catch (IOException e) { + Log.e(LOG_TAG, "Unable to close input stream.", e); + } + } + } + break; + default: + } + + // send the reply to the enclosing class. + Message reply = ContactsAsyncHelper.this.mResultHandler.obtainMessage(msg.what); + reply.arg1 = msg.arg1; + reply.obj = msg.obj; + reply.sendToTarget(); + } + + /** + * Returns a Bitmap object suitable for {@link Notification}'s large icon. This might + * return null when the given Drawable isn't BitmapDrawable, or if the system fails to + * create a scaled Bitmap for the Drawable. + */ + private Bitmap getPhotoIconWhenAppropriate(Context context, Drawable photo) { + if (!(photo instanceof BitmapDrawable)) { + return null; + } + int iconSize = context.getResources() + .getDimensionPixelSize(R.dimen.notification_icon_size); + Bitmap orgBitmap = ((BitmapDrawable) photo).getBitmap(); + int orgWidth = orgBitmap.getWidth(); + int orgHeight = orgBitmap.getHeight(); + int longerEdge = orgWidth > orgHeight ? orgWidth : orgHeight; + // We want downscaled one only when the original icon is too big. + if (longerEdge > iconSize) { + float ratio = ((float) longerEdge) / iconSize; + int newWidth = (int) (orgWidth / ratio); + int newHeight = (int) (orgHeight / ratio); + // If the longer edge is much longer than the shorter edge, the latter may + // become 0 which will cause a crash. + if (newWidth <= 0 || newHeight <= 0) { + Log.w(LOG_TAG, "Photo icon's width or height become 0."); + return null; + } + + // It is sure ratio >= 1.0f in any case and thus the newly created Bitmap + // should be smaller than the original. + return Bitmap.createScaledBitmap(orgBitmap, newWidth, newHeight, true); + } else { + return orgBitmap; + } + } + } + + /** + * Private constructor for static class + */ + private ContactsAsyncHelper() { + HandlerThread thread = new HandlerThread("ContactsAsyncWorker"); + thread.start(); + sThreadHandler = new WorkerHandler(thread.getLooper()); + } + + /** + * Starts an asynchronous image load. After finishing the load, + * {@link OnImageLoadCompleteListener#onImageLoadComplete(int, Drawable, Bitmap, Object)} + * will be called. + * + * @param token Arbitrary integer which will be returned as the first argument of + * {@link OnImageLoadCompleteListener#onImageLoadComplete(int, Drawable, Bitmap, Object)} + * @param context Context object used to do the time-consuming operation. + * @param personUri Uri to be used to fetch the photo + * @param listener Callback object which will be used when the asynchronous load is done. + * Can be null, which means only the asynchronous load is done while there's no way to + * obtain the loaded photos. + * @param cookie Arbitrary object the caller wants to remember, which will become the + * fourth argument of {@link OnImageLoadCompleteListener#onImageLoadComplete(int, Drawable, + * Bitmap, Object)}. Can be null, at which the callback will also has null for the argument. + */ + public static final void startObtainPhotoAsync(int token, Context context, Uri personUri, + OnImageLoadCompleteListener listener, Object cookie) { + // 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 (personUri == null) { + Log.wtf(LOG_TAG, "Uri is missing"); + 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.uri = personUri; + args.listener = listener; + + // setup message arguments + Message msg = sThreadHandler.obtainMessage(token); + msg.arg1 = EVENT_LOAD_IMAGE; + msg.obj = args; + + if (DBG) Log.d(LOG_TAG, "Begin loading image: " + args.uri + + ", displaying default image for now."); + + // notify the thread to begin working + sThreadHandler.sendMessage(msg); + } + + +} |