Various refactorings to prepare proper big picture loading

 - Added a function to load a picture with ideal sampleSize
 - Renamed xmls/java files for tiles
 - ContactTileView is now abstract and has subclasses for each use-case
 - Added mechanism to estimate the image-size in a tile before layout
 - Changed the cross-fade in ContactPhotoManager to apply the fade to the
   current picture instead of the default avatar. Reduces flickering

Bug:6202229

Change-Id: Ic8636d1b3349473163fb2424b5f74476fd435fb5
diff --git a/src/com/android/contacts/ContactPhotoManager.java b/src/com/android/contacts/ContactPhotoManager.java
index 35585a6..8688426 100644
--- a/src/com/android/contacts/ContactPhotoManager.java
+++ b/src/com/android/contacts/ContactPhotoManager.java
@@ -17,6 +17,7 @@
 package com.android.contacts;
 
 import com.android.contacts.model.AccountTypeManager;
+import com.android.contacts.util.BitmapUtil;
 import com.android.contacts.util.MemoryUtils;
 import com.android.contacts.util.UriUtils;
 import com.google.android.collect.Lists;
@@ -27,9 +28,13 @@
 import android.content.ContentUris;
 import android.content.Context;
 import android.content.res.Configuration;
+import android.content.res.Resources;
 import android.database.Cursor;
 import android.graphics.Bitmap;
-import android.graphics.BitmapFactory;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.Paint;
+import android.graphics.Paint.Style;
 import android.graphics.drawable.BitmapDrawable;
 import android.graphics.drawable.ColorDrawable;
 import android.graphics.drawable.Drawable;
@@ -47,6 +52,7 @@
 import android.text.TextUtils;
 import android.util.Log;
 import android.util.LruCache;
+import android.util.TypedValue;
 import android.widget.ImageView;
 
 import java.io.ByteArrayOutputStream;
@@ -65,9 +71,31 @@
 public abstract class ContactPhotoManager implements ComponentCallbacks2 {
     static final String TAG = "ContactPhotoManager";
     static final boolean DEBUG = false; // Don't submit with true
+    static final boolean DEBUG_SIZES = false; // Don't submit with true
+
+    /** Caches 180dip in pixel. This is used to detect whether to show the hires or lores version
+     * of the default avatar */
+    private static int s180DipInPixel = -1;
 
     public static final String CONTACT_PHOTO_SERVICE = "contactPhotos";
 
+    /**
+     * Returns the resource id of the default avatar. Tries to find a resource that is bigger
+     * than the given extent (width or height). If extent=-1, a thumbnail avatar is returned
+     */
+    public static int getDefaultAvatarResId(Context context, int extent, boolean darkTheme) {
+        // TODO: Is it worth finding a nicer way to do hires/lores here? In practice, the
+        // default avatar doesn't look too different when stretched
+        if (s180DipInPixel == -1) {
+            Resources r = context.getResources();
+            s180DipInPixel = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 180,
+                    r.getDisplayMetrics());
+        }
+
+        final boolean hires = (extent != -1) && (extent > s180DipInPixel);
+        return getDefaultAvatarResId(hires, darkTheme);
+    }
+
     public static int getDefaultAvatarResId(boolean hires, boolean darkTheme) {
         if (hires && darkTheme) return R.drawable.ic_contact_picture_180_holo_dark;
         if (hires) return R.drawable.ic_contact_picture_180_holo_light;
@@ -76,13 +104,17 @@
     }
 
     public static abstract class DefaultImageProvider {
-        public abstract void applyDefaultImage(ImageView view, boolean hires, boolean darkTheme);
+        /**
+         * Applies the default avatar to the ImageView. Extent is an indicator for the size (width
+         * or height). If darkTheme is set, the avatar is one that looks better on dark background
+         */
+        public abstract void applyDefaultImage(ImageView view, int extent, boolean darkTheme);
     }
 
     private static class AvatarDefaultImageProvider extends DefaultImageProvider {
         @Override
-        public void applyDefaultImage(ImageView view, boolean hires, boolean darkTheme) {
-            view.setImageResource(getDefaultAvatarResId(hires, darkTheme));
+        public void applyDefaultImage(ImageView view, int extent, boolean darkTheme) {
+            view.setImageResource(getDefaultAvatarResId(view.getContext(), extent, darkTheme));
         }
     }
 
@@ -90,7 +122,7 @@
         private static Drawable sDrawable;
 
         @Override
-        public void applyDefaultImage(ImageView view, boolean hires, boolean darkTheme) {
+        public void applyDefaultImage(ImageView view, int extent, boolean darkTheme) {
             if (sDrawable == null) {
                 Context context = view.getContext();
                 sDrawable = new ColorDrawable(context.getResources().getColor(
@@ -124,35 +156,53 @@
     }
 
     /**
-     * Load photo into the supplied image view.  If the photo is already cached,
+     * Load thumbnail image into the supplied image view. If the photo is already cached,
      * it is displayed immediately.  Otherwise a request is sent to load the photo
      * from the database.
      */
-    public abstract void loadPhoto(ImageView view, long photoId, boolean hires, boolean darkTheme,
+    public abstract void loadThumbnail(ImageView view, long photoId, boolean darkTheme,
             DefaultImageProvider defaultProvider);
 
     /**
-     * Calls {@link #loadPhoto(ImageView, long, boolean, boolean, DefaultImageProvider)} with
+     * Calls {@link #loadThumbnail(ImageView, long, boolean, DefaultImageProvider)} with
      * {@link #DEFAULT_AVATAR}.
      */
-    public final void loadPhoto(ImageView view, long photoId, boolean hires, boolean darkTheme) {
-        loadPhoto(view, photoId, hires, darkTheme, DEFAULT_AVATAR);
+    public final void loadThumbnail(ImageView view, long photoId, boolean darkTheme) {
+        loadThumbnail(view, photoId, darkTheme, DEFAULT_AVATAR);
     }
 
     /**
-     * Load photo into the supplied image view.  If the photo is already cached,
-     * it is displayed immediately.  Otherwise a request is sent to load the photo
+     * Load photo into the supplied image view. If the photo is already cached,
+     * it is displayed immediately. Otherwise a request is sent to load the photo
      * from the location specified by the URI.
+     * @param view The target view
+     * @param photoUri The uri of the photo to load
+     * @param requestedExtent Specifies an approximate Max(width, height) of the targetView.
+     * This is useful if the source image can be a lot bigger that the target, so that the decoding
+     * is done using efficient sampling. If requestedExtent is specified, no sampling of the image
+     * is performed
+     * @param darkTheme Whether the background is dark. This is used for default avatars
+     * @param defaultProvider The provider of default avatars (this is used if photoUri doesn't
+     * refer to an existing image)
      */
-    public abstract void loadPhoto(ImageView view, Uri photoUri, boolean hires, boolean darkTheme,
-            DefaultImageProvider defaultProvider);
+    public abstract void loadPhoto(ImageView view, Uri photoUri, int requestedExtent,
+            boolean darkTheme, DefaultImageProvider defaultProvider);
 
     /**
      * Calls {@link #loadPhoto(ImageView, Uri, boolean, boolean, DefaultImageProvider)} with
      * {@link #DEFAULT_AVATAR}.
      */
-    public final void loadPhoto(ImageView view, Uri photoUri, boolean hires, boolean darkTheme) {
-        loadPhoto(view, photoUri, hires, darkTheme, DEFAULT_AVATAR);
+    public final void loadPhoto(ImageView view, Uri photoUri, int requestedExtent,
+            boolean darkTheme) {
+        loadPhoto(view, photoUri, requestedExtent, darkTheme, DEFAULT_AVATAR);
+    }
+
+    /**
+     * Calls {@link #loadPhoto(ImageView, Uri, boolean, boolean, DefaultImageProvider)} with
+     * {@link #DEFAULT_AVATAR} and with the assumption, that the image is a thumbnail
+     */
+    public final void loadDirectoryPhoto(ImageView view, Uri photoUri, boolean darkTheme) {
+        loadPhoto(view, photoUri, -1, darkTheme, DEFAULT_AVATAR);
     }
 
     /**
@@ -234,14 +284,17 @@
      */
     private static class BitmapHolder {
         final byte[] bytes;
+        final int originalSmallerExtent;
 
         volatile boolean fresh;
         Bitmap bitmap;
         Reference<Bitmap> bitmapRef;
+        int decodedSampleSize;
 
-        public BitmapHolder(byte[] bytes) {
+        public BitmapHolder(byte[] bytes, int originalSmallerExtent) {
             this.bytes = bytes;
             this.fresh = true;
+            this.originalSmallerExtent = originalSmallerExtent;
         }
     }
 
@@ -417,29 +470,29 @@
     }
 
     @Override
-    public void loadPhoto(ImageView view, long photoId, boolean hires, boolean darkTheme,
+    public void loadThumbnail(ImageView view, long photoId, boolean darkTheme,
             DefaultImageProvider defaultProvider) {
         if (photoId == 0) {
             // No photo is needed
-            defaultProvider.applyDefaultImage(view, hires, darkTheme);
+            defaultProvider.applyDefaultImage(view, -1, darkTheme);
             mPendingRequests.remove(view);
         } else {
             if (DEBUG) Log.d(TAG, "loadPhoto request: " + photoId);
-            loadPhotoByIdOrUri(view, Request.createFromId(photoId, hires, darkTheme,
+            loadPhotoByIdOrUri(view, Request.createFromThumbnailId(photoId, darkTheme,
                     defaultProvider));
         }
     }
 
     @Override
-    public void loadPhoto(ImageView view, Uri photoUri, boolean hires, boolean darkTheme,
+    public void loadPhoto(ImageView view, Uri photoUri, int requestedExtent, boolean darkTheme,
             DefaultImageProvider defaultProvider) {
         if (photoUri == null) {
             // No photo is needed
-            defaultProvider.applyDefaultImage(view, hires, darkTheme);
+            defaultProvider.applyDefaultImage(view, requestedExtent, darkTheme);
             mPendingRequests.remove(view);
         } else {
             if (DEBUG) Log.d(TAG, "loadPhoto request: " + photoUri);
-            loadPhotoByIdOrUri(view, Request.createFromUri(photoUri, hires, darkTheme,
+            loadPhotoByIdOrUri(view, Request.createFromUri(photoUri, requestedExtent, darkTheme,
                     defaultProvider));
         }
     }
@@ -482,12 +535,6 @@
      * @return false if the photo needs to be (re)loaded from the provider.
      */
     private boolean loadCachedPhoto(ImageView view, Request request, boolean fadeIn) {
-        Bitmap bitmap = mBitmapCache.get(request.getKey());
-        if (bitmap != null) {
-            view.setImageBitmap(bitmap);
-            return true;
-        }
-
         BitmapHolder holder = mBitmapHolderCache.get(request.getKey());
         if (holder == null) {
             // The bitmap has not been loaded - should display the placeholder image.
@@ -501,12 +548,25 @@
         }
 
         // Optionally decode bytes into a bitmap
-        inflateBitmap(holder);
+        final long inflateStart = System.currentTimeMillis();
+        final boolean decodingPicture = holder.bitmap == null;
+        inflateBitmap(holder, request.getRequestedExtent());
+        if (decodingPicture && DEBUG) {
+            Log.d(TAG, "Inflated a picture on the foreground thread. Took " +
+                    (System.currentTimeMillis() - inflateStart) + "ms");
+        }
 
-        if (fadeIn) {
-            Drawable[] layers = new Drawable[2];
-            layers[0] = mContext.getResources().getDrawable(
-                    getDefaultAvatarResId(request.mHires, request.mDarkTheme));
+        final Drawable previousDrawable = view.getDrawable();
+        if (fadeIn && previousDrawable != null) {
+            final Drawable[] layers = new Drawable[2];
+            // Prevent cascade of TransitionDrawables.
+            if (previousDrawable instanceof TransitionDrawable) {
+                final TransitionDrawable transitionDrawable = (TransitionDrawable) previousDrawable;
+                layers[0] =
+                        transitionDrawable.getDrawable(transitionDrawable.getNumberOfLayers() - 1);
+            } else {
+                layers[0] = previousDrawable;
+            }
             layers[1] = new BitmapDrawable(mContext.getResources(), holder.bitmap);
             TransitionDrawable drawable = new TransitionDrawable(layers);
             view.setImageDrawable(drawable);
@@ -531,23 +591,45 @@
      * bitmap is held either by {@link #mBitmapCache} or by a soft reference in
      * the holder, it will not be necessary to decode the bitmap.
      */
-    private static void inflateBitmap(BitmapHolder holder) {
+    private static void inflateBitmap(BitmapHolder holder, int requestedExtent) {
+        final int sampleSize =
+                BitmapUtil.findOptimalSampleSize(holder.originalSmallerExtent, requestedExtent);
         byte[] bytes = holder.bytes;
         if (bytes == null || bytes.length == 0) {
             return;
         }
 
-        // Check the soft reference.  If will be retained if the bitmap is also
-        // in the LRU cache, so we don't need to check the LRU cache explicitly.
-        if (holder.bitmapRef != null) {
-            holder.bitmap = holder.bitmapRef.get();
-            if (holder.bitmap != null) {
-                return;
+        if (sampleSize == holder.decodedSampleSize) {
+            // Check the soft reference.  If will be retained if the bitmap is also
+            // in the LRU cache, so we don't need to check the LRU cache explicitly.
+            if (holder.bitmapRef != null) {
+                holder.bitmap = holder.bitmapRef.get();
+                if (holder.bitmap != null) {
+                    return;
+                }
             }
         }
 
         try {
-            Bitmap bitmap = BitmapFactory.decodeByteArray(bytes, 0, bytes.length, null);
+            Bitmap bitmap = BitmapUtil.decodeBitmapFromBytes(bytes, sampleSize);
+
+            // make bitmap mutable and draw size onto it
+            if (DEBUG_SIZES) {
+                Bitmap original = bitmap;
+                bitmap = bitmap.copy(bitmap.getConfig(), true);
+                original.recycle();
+                Canvas canvas = new Canvas(bitmap);
+                Paint paint = new Paint();
+                paint.setTextSize(16);
+                paint.setColor(Color.BLUE);
+                paint.setStyle(Style.FILL);
+                canvas.drawRect(0.0f, 0.0f, 50.0f, 20.0f, paint);
+                paint.setColor(Color.WHITE);
+                paint.setAntiAlias(true);
+                canvas.drawText(bitmap.getWidth() + "/" + sampleSize, 0, 15, paint);
+            }
+
+            holder.decodedSampleSize = sampleSize;
             holder.bitmap = bitmap;
             holder.bitmapRef = new SoftReference<Bitmap>(bitmap);
             if (DEBUG) {
@@ -662,7 +744,7 @@
     /**
      * Stores the supplied bitmap in cache.
      */
-    private void cacheBitmap(Object key, byte[] bytes, boolean preloading) {
+    private void cacheBitmap(Object key, byte[] bytes, boolean preloading, int requestedExtent) {
         if (DEBUG) {
             BitmapHolder prev = mBitmapHolderCache.get(key);
             if (prev != null && prev.bytes != null) {
@@ -673,14 +755,16 @@
                     mStaleCacheOverwrite.incrementAndGet();
                 }
             }
-            Log.d(TAG, "Caching data: key=" + key + ", " + btk(bytes.length));
+            Log.d(TAG, "Caching data: key=" + key + ", " +
+                    (bytes == null ? "<null>" : btk(bytes.length)));
         }
-        BitmapHolder holder = new BitmapHolder(bytes);
+        BitmapHolder holder = new BitmapHolder(bytes,
+                bytes == null ? -1 : BitmapUtil.getSmallerExtentFromBytes(bytes));
 
         // Unless this image is being preloaded, decode it right away while
         // we are still on the background thread.
         if (!preloading) {
-            inflateBitmap(holder);
+            inflateBitmap(holder, requestedExtent);
         }
 
         mBitmapHolderCache.put(key, holder);
@@ -689,8 +773,11 @@
 
     @Override
     public void cacheBitmap(Uri photoUri, Bitmap bitmap, byte[] photoBytes) {
-        Request request = Request.createFromUri(photoUri, true, false, DEFAULT_AVATAR);
-        BitmapHolder holder = new BitmapHolder(photoBytes);
+        final int smallerExtent = Math.min(bitmap.getWidth(), bitmap.getHeight());
+        // We can pretend here that the extent of the photo was the size that we originally
+        // requested
+        Request request = Request.createFromUri(photoUri, smallerExtent, false, DEFAULT_AVATAR);
+        BitmapHolder holder = new BitmapHolder(photoBytes, smallerExtent);
         mBitmapHolderCache.put(request.getKey(), holder);
         mBitmapHolderCacheAllUnfresh = false;
         mBitmapCache.put(request.getKey(), bitmap);
@@ -700,7 +787,7 @@
      * Populates an array of photo IDs that need to be loaded.
      */
     private void obtainPhotoIdsAndUrisToLoad(Set<Long> photoIds,
-            Set<String> photoIdsAsStrings, Set<Uri> uris) {
+            Set<String> photoIdsAsStrings, Set<Request> uris) {
         photoIds.clear();
         photoIdsAsStrings.clear();
         uris.clear();
@@ -719,9 +806,9 @@
             BitmapHolder holder = mBitmapHolderCache.get(request);
             if (holder == null || !holder.fresh) {
                 if (request.isUriRequest()) {
-                    uris.add(request.mUri);
+                    uris.add(request);
                 } else {
-                    photoIds.add(request.mId);
+                    photoIds.add(request.getId());
                     photoIdsAsStrings.add(String.valueOf(request.mId));
                 }
             }
@@ -756,7 +843,7 @@
         private final StringBuilder mStringBuilder = new StringBuilder();
         private final Set<Long> mPhotoIds = Sets.newHashSet();
         private final Set<String> mPhotoIdsAsStrings = Sets.newHashSet();
-        private final Set<Uri> mPhotoUris = Sets.newHashSet();
+        private final Set<Request> mPhotoUris = Sets.newHashSet();
         private final List<Long> mPreloadPhotoIds = Lists.newArrayList();
 
         private Handler mLoaderThreadHandler;
@@ -869,7 +956,7 @@
                 mPreloadPhotoIds.remove(preloadSize);
             }
 
-            loadPhotosFromDatabase(true);
+            loadThumbnails(true);
 
             if (preloadSize == 0) {
                 mPreloadStatus = PRELOAD_STATUS_DONE;
@@ -910,12 +997,13 @@
 
         private void loadPhotosInBackground() {
             obtainPhotoIdsAndUrisToLoad(mPhotoIds, mPhotoIdsAsStrings, mPhotoUris);
-            loadPhotosFromDatabase(false);
-            loadRemotePhotos();
+            loadThumbnails(false);
+            loadUriBasedPhotos();
             requestPreloading();
         }
 
-        private void loadPhotosFromDatabase(boolean preloading) {
+        /** Loads thumbnail photos with ids */
+        private void loadThumbnails(boolean preloading) {
             if (mPhotoIds.isEmpty()) {
                 return;
             }
@@ -954,7 +1042,7 @@
                     while (cursor.moveToNext()) {
                         Long id = cursor.getLong(0);
                         byte[] bytes = cursor.getBlob(1);
-                        cacheBitmap(id, bytes, preloading);
+                        cacheBitmap(id, bytes, preloading, -1);
                         mPhotoIds.remove(id);
                     }
                 }
@@ -974,10 +1062,10 @@
                                 COLUMNS, null, null, null);
                         if (profileCursor != null && profileCursor.moveToFirst()) {
                             cacheBitmap(profileCursor.getLong(0), profileCursor.getBlob(1),
-                                    preloading);
+                                    preloading, -1);
                         } else {
                             // Couldn't load a photo this way either.
-                            cacheBitmap(id, null, preloading);
+                            cacheBitmap(id, null, preloading, -1);
                         }
                     } finally {
                         if (profileCursor != null) {
@@ -986,15 +1074,20 @@
                     }
                 } else {
                     // Not a profile photo and not found - mark the cache accordingly
-                    cacheBitmap(id, null, preloading);
+                    cacheBitmap(id, null, preloading, -1);
                 }
             }
 
             mMainThreadHandler.sendEmptyMessage(MESSAGE_PHOTOS_LOADED);
         }
 
-        private void loadRemotePhotos() {
-            for (Uri uri : mPhotoUris) {
+        /**
+         * Loads photos referenced with Uris. Those can be remote thumbnails
+         * (from directory searches), display photos etc
+         */
+        private void loadUriBasedPhotos() {
+            for (Request uriRequest : mPhotoUris) {
+                Uri uri = uriRequest.getUri();
                 if (mBuffer == null) {
                     mBuffer = new byte[BUFFER_SIZE];
                 }
@@ -1011,15 +1104,16 @@
                         } finally {
                             is.close();
                         }
-                        cacheBitmap(uri, baos.toByteArray(), false);
+                        cacheBitmap(uri, baos.toByteArray(), false,
+                                uriRequest.getRequestedExtent());
                         mMainThreadHandler.sendEmptyMessage(MESSAGE_PHOTOS_LOADED);
                     } else {
                         Log.v(TAG, "Cannot load photo " + uri);
-                        cacheBitmap(uri, null, false);
+                        cacheBitmap(uri, null, false, uriRequest.getRequestedExtent());
                     }
                 } catch (Exception ex) {
                     Log.v(TAG, "Cannot load photo " + uri, ex);
-                    cacheBitmap(uri, null, false);
+                    cacheBitmap(uri, null, false, uriRequest.getRequestedExtent());
                 }
             }
         }
@@ -1033,57 +1127,68 @@
         private final long mId;
         private final Uri mUri;
         private final boolean mDarkTheme;
-        private final boolean mHires;
+        private final int mRequestedExtent;
         private final DefaultImageProvider mDefaultProvider;
 
-        private Request(long id, Uri uri, boolean hires, boolean darkTheme,
+        private Request(long id, Uri uri, int requestedExtent, boolean darkTheme,
                 DefaultImageProvider defaultProvider) {
             mId = id;
             mUri = uri;
             mDarkTheme = darkTheme;
-            mHires = hires;
+            mRequestedExtent = requestedExtent;
             mDefaultProvider = defaultProvider;
         }
 
-        public static Request createFromId(long id, boolean hires, boolean darkTheme,
+        public static Request createFromThumbnailId(long id, boolean darkTheme,
                 DefaultImageProvider defaultProvider) {
-            return new Request(id, null /* no URI */, hires, darkTheme, defaultProvider);
+            return new Request(id, null /* no URI */, -1, darkTheme, defaultProvider);
         }
 
-        public static Request createFromUri(Uri uri, boolean hires, boolean darkTheme,
+        public static Request createFromUri(Uri uri, int requestedExtent, boolean darkTheme,
                 DefaultImageProvider defaultProvider) {
-            return new Request(0 /* no ID */, uri, hires, darkTheme, defaultProvider);
-        }
-
-        public boolean isDarkTheme() {
-            return mDarkTheme;
-        }
-
-        public boolean isHires() {
-            return mHires;
+            return new Request(0 /* no ID */, uri, requestedExtent, darkTheme, defaultProvider);
         }
 
         public boolean isUriRequest() {
             return mUri != null;
         }
 
-        @Override
-        public int hashCode() {
-            if (mUri != null) return mUri.hashCode();
+        public Uri getUri() {
+            return mUri;
+        }
 
-            // copied over from Long.hashCode()
-            return (int) (mId ^ (mId >>> 32));
+        public long getId() {
+            return mId;
+        }
+
+        public int getRequestedExtent() {
+            return mRequestedExtent;
         }
 
         @Override
-        public boolean equals(Object o) {
-            if (!(o instanceof Request)) return false;
-            final Request that = (Request) o;
-            // Don't compare equality of mHires and mDarkTheme fields because these are only used
-            // in the default contact photo case. When the contact does have a photo, the contact
-            // photo is the same regardless of mHires and mDarkTheme, so we shouldn't need to put
-            // the photo request on the queue twice.
-            return mId == that.mId && UriUtils.areEqual(mUri, that.mUri);
+        public int hashCode() {
+            final int prime = 31;
+            int result = 1;
+            result = prime * result + (int) (mId ^ (mId >>> 32));
+            result = prime * result + mRequestedExtent;
+            result = prime * result + ((mUri == null) ? 0 : mUri.hashCode());
+            return result;
+        }
+
+        @Override
+        public boolean equals(Object obj) {
+            if (this == obj) return true;
+            if (obj == null) return false;
+            if (getClass() != obj.getClass()) return false;
+            final Request that = (Request) obj;
+            if (mId != that.mId) return false;
+            if (mRequestedExtent != that.mRequestedExtent) return false;
+            if (!UriUtils.areEqual(mUri, that.mUri)) return false;
+            // Don't compare equality of mDarkTheme because it is only used in the default contact
+            // photo case. When the contact does have a photo, the contact photo is the same
+            // regardless of mDarkTheme, so we shouldn't need to put the photo request on the queue
+            // twice.
+            return true;
         }
 
         public Object getKey() {
@@ -1091,7 +1196,7 @@
         }
 
         public void applyDefaultImage(ImageView view) {
-            mDefaultProvider.applyDefaultImage(view, mHires, mDarkTheme);
+            mDefaultProvider.applyDefaultImage(view, mRequestedExtent, mDarkTheme);
         }
     }
 }