summaryrefslogtreecommitdiff
path: root/java/com/android/incallui/ContactsAsyncHelper.java
diff options
context:
space:
mode:
Diffstat (limited to 'java/com/android/incallui/ContactsAsyncHelper.java')
-rw-r--r--java/com/android/incallui/ContactsAsyncHelper.java269
1 files changed, 269 insertions, 0 deletions
diff --git a/java/com/android/incallui/ContactsAsyncHelper.java b/java/com/android/incallui/ContactsAsyncHelper.java
new file mode 100644
index 000000000..08ff74d0e
--- /dev/null
+++ b/java/com/android/incallui/ContactsAsyncHelper.java
@@ -0,0 +1,269 @@
+/*
+ * 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.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.support.annotation.MainThread;
+import android.support.annotation.WorkerThread;
+import java.io.IOException;
+import java.io.InputStream;
+
+/** Helper class for loading contacts photo asynchronously. */
+public class ContactsAsyncHelper {
+
+ /** Interface for a WorkerHandler result return. */
+ public interface OnImageLoadCompleteListener {
+
+ /**
+ * Called when the image load is complete. Must be called in main thread.
+ *
+ * @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.
+ */
+ @MainThread
+ void onImageLoadComplete(int token, Drawable photo, Bitmap photoIcon, Object cookie);
+
+ /** Called when image is loaded to udpate data. Must be called in worker thread. */
+ @WorkerThread
+ void onImageLoaded(int token, Drawable photo, Bitmap photoIcon, Object cookie);
+ }
+
+ // constants
+ private static final int EVENT_LOAD_IMAGE = 1;
+ /** 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 final Handler mResultHandler =
+ /** A handler that handles message to call listener notifying UI change on main thread. */
+ new Handler(Looper.getMainLooper()) {
+ @Override
+ public void handleMessage(Message msg) {
+ WorkerArgs args = (WorkerArgs) msg.obj;
+ switch (msg.arg1) {
+ case EVENT_LOAD_IMAGE:
+ if (args.listener != null) {
+ Log.d(
+ this,
+ "Notifying listener: "
+ + args.listener.toString()
+ + " image: "
+ + args.displayPhotoUri
+ + " completed");
+ args.listener.onImageLoadComplete(
+ msg.what, args.photo, args.photoIcon, args.cookie);
+ }
+ break;
+ default:
+ }
+ }
+ };
+
+ /** 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 displayPhotoUri 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 displayPhotoUri,
+ 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 (displayPhotoUri == null) {
+ Log.e("startObjectPhotoAsync", "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.displayPhotoUri = displayPhotoUri;
+ args.listener = listener;
+
+ // setup message arguments
+ Message msg = sThreadHandler.obtainMessage(token);
+ msg.arg1 = EVENT_LOAD_IMAGE;
+ msg.obj = args;
+
+ Log.d(
+ "startObjectPhotoAsync",
+ "Begin loading image: " + args.displayPhotoUri + ", displaying default image for now.");
+
+ // notify the thread to begin working
+ sThreadHandler.sendMessage(msg);
+ }
+
+ private static final class WorkerArgs {
+
+ public Context context;
+ public Uri displayPhotoUri;
+ public Drawable photo;
+ public Bitmap photoIcon;
+ public Object cookie;
+ public OnImageLoadCompleteListener listener;
+ }
+
+ /** 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 = args.context.getContentResolver().openInputStream(args.displayPhotoUri);
+ } catch (Exception e) {
+ Log.e(this, "Error opening photo input stream", e);
+ }
+
+ if (inputStream != null) {
+ args.photo = Drawable.createFromStream(inputStream, args.displayPhotoUri.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);
+
+ Log.d(
+ ContactsAsyncHelper.this,
+ "Loading image: "
+ + msg.arg1
+ + " token: "
+ + msg.what
+ + " image URI: "
+ + args.displayPhotoUri);
+ } else {
+ args.photo = null;
+ args.photoIcon = null;
+ Log.d(
+ ContactsAsyncHelper.this,
+ "Problem with image: "
+ + msg.arg1
+ + " token: "
+ + msg.what
+ + " image URI: "
+ + args.displayPhotoUri
+ + ", using default image.");
+ }
+ if (args.listener != null) {
+ args.listener.onImageLoaded(msg.what, args.photo, args.photoIcon, args.cookie);
+ }
+ } finally {
+ if (inputStream != null) {
+ try {
+ inputStream.close();
+ } catch (IOException e) {
+ Log.e(this, "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(this, "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;
+ }
+ }
+ }
+}