package com.zulip.android.networking; import android.annotation.TargetApi; import android.content.Context; import android.content.res.Resources; import android.graphics.Bitmap; import android.graphics.BitmapFactory; import android.graphics.drawable.BitmapDrawable; import android.graphics.drawable.Drawable; import android.os.AsyncTask; import android.os.Build; import android.util.Log; import android.util.TypedValue; import android.widget.ImageView; import com.zulip.android.activities.ZulipActivity; import com.zulip.android.models.Person; import com.zulip.android.util.ZLog; import java.io.IOException; import java.io.InputStream; import java.lang.ref.WeakReference; import java.net.MalformedURLException; import java.net.URL; import java.net.URLConnection; /** * Retrieves a user avatar (which may be a Gravatar) from the relevant server */ public class GravatarAsyncFetchTask extends AsyncTask<URL, Void, Bitmap> { private static final String TAG = "GravatarAsyncFetchTask"; private final WeakReference<ImageView> imageViewReference; private Person person; private ZulipActivity context; private GravatarAsyncFetchTask(ZulipActivity context, ImageView imageView, Person person) { // Use a WeakReference to ensure the ImageView can be garbage collected imageViewReference = new WeakReference<>(imageView); this.person = person; this.context = context; } public static URL sizedURL(Context context, String url, float dpSize) { // From http://stackoverflow.com/questions/4605527/ Resources r = context.getResources(); float px = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dpSize, r.getDisplayMetrics()); try { return new URL(url + "&s=" + px); } catch (MalformedURLException e) { ZLog.logException(e); return null; } } public static Bitmap fetch(URL url) throws IOException { Log.i("GAFT.fetch", "Getting gravatar from url: " + url); URLConnection connection = url.openConnection(); connection.setUseCaches(true); Object response = connection.getContent(); if (response instanceof InputStream) { return BitmapFactory.decodeStream((InputStream) response); } return null; } private static boolean cancelPotentialWork(Person person, ImageView imageView) { final GravatarAsyncFetchTask bitmapWorkerTask = getBitmapWorkerTask(imageView); if (bitmapWorkerTask != null) { final Person bitmapData = bitmapWorkerTask.person; if (!bitmapData.equals(person)) { // Cancel previous task Log.i("GAFT.refetch", imageView + " was recycled, fetching new gravatar"); bitmapWorkerTask.cancel(true); } else { // The same work is already in progress return false; } } // No task associated with the ImageView, or an existing task was // cancelled return true; } private static GravatarAsyncFetchTask getBitmapWorkerTask( ImageView imageView) { if (imageView != null) { final Drawable drawable = imageView.getDrawable(); if (drawable instanceof AsyncDrawable) { final AsyncDrawable asyncDrawable = (AsyncDrawable) drawable; return asyncDrawable.getBitmapWorkerTask(); } } return null; } // Fetch and decode image in background. @Override protected Bitmap doInBackground(URL... urls) { try { if (urls[0] != null) { return fetch(urls[0]); } } catch (IOException e) { ZLog.logException(e); } return null; } // Once complete, see if ImageView is still around and set bitmap. @Override protected void onPostExecute(Bitmap bitmap) { if (isCancelled()) { bitmap = null; } if (imageViewReference != null && bitmap != null) { final ImageView imageView = imageViewReference.get(); final GravatarAsyncFetchTask bitmapWorkerTask = getBitmapWorkerTask(imageView); if (this == bitmapWorkerTask && imageView != null) { imageView.setImageBitmap(bitmap); context.getGravatars().put(person.getEmail(), bitmap); } } } // Called externally to initiate a new gravatar fetch task, only if there // already isn't one @TargetApi(Build.VERSION_CODES.HONEYCOMB) public void loadBitmap(ZulipActivity context, URL url, ImageView imageView, Person person) { if (cancelPotentialWork(person, imageView)) { Log.i("GAFT.init", "Starting new task for " + imageView); final GravatarAsyncFetchTask task = new GravatarAsyncFetchTask( context, imageView, person); final AsyncDrawable asyncDrawable = new AsyncDrawable( context.getResources(), BitmapFactory.decodeResource( context.getResources(), android.R.drawable.presence_online), task); imageView.setImageDrawable(asyncDrawable); try { task.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, url); } catch (NoSuchFieldError e) { Log.e(TAG, e.getMessage(), e); task.execute(url); } } } /** * This object is attached to the ImageView at the time of a gravatar * request to keep a reference back to the task that is fetching the image. * This is to prevent multiple tasks being initiated for the same ImageView. */ static class AsyncDrawable extends BitmapDrawable { private final WeakReference<GravatarAsyncFetchTask> taskReference; public AsyncDrawable(Resources res, Bitmap bitmap, GravatarAsyncFetchTask bitmapWorkerTask) { super(res, bitmap); taskReference = new WeakReference<>( bitmapWorkerTask); } public GravatarAsyncFetchTask getBitmapWorkerTask() { return taskReference.get(); } } }