1d62a616ebca5bfa4f9ec5517208e13f2d501b69aAurash Mahbod/** 2d62a616ebca5bfa4f9ec5517208e13f2d501b69aAurash Mahbod * Copyright (C) 2013 The Android Open Source Project 3d62a616ebca5bfa4f9ec5517208e13f2d501b69aAurash Mahbod * 4d62a616ebca5bfa4f9ec5517208e13f2d501b69aAurash Mahbod * Licensed under the Apache License, Version 2.0 (the "License"); 5d62a616ebca5bfa4f9ec5517208e13f2d501b69aAurash Mahbod * you may not use this file except in compliance with the License. 6d62a616ebca5bfa4f9ec5517208e13f2d501b69aAurash Mahbod * You may obtain a copy of the License at 7d62a616ebca5bfa4f9ec5517208e13f2d501b69aAurash Mahbod * 8d62a616ebca5bfa4f9ec5517208e13f2d501b69aAurash Mahbod * http://www.apache.org/licenses/LICENSE-2.0 9d62a616ebca5bfa4f9ec5517208e13f2d501b69aAurash Mahbod * 10d62a616ebca5bfa4f9ec5517208e13f2d501b69aAurash Mahbod * Unless required by applicable law or agreed to in writing, software 11d62a616ebca5bfa4f9ec5517208e13f2d501b69aAurash Mahbod * distributed under the License is distributed on an "AS IS" BASIS, 12d62a616ebca5bfa4f9ec5517208e13f2d501b69aAurash Mahbod * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13d62a616ebca5bfa4f9ec5517208e13f2d501b69aAurash Mahbod * See the License for the specific language governing permissions and 14d62a616ebca5bfa4f9ec5517208e13f2d501b69aAurash Mahbod * limitations under the License. 15d62a616ebca5bfa4f9ec5517208e13f2d501b69aAurash Mahbod */ 16d62a616ebca5bfa4f9ec5517208e13f2d501b69aAurash Mahbodpackage com.android.volley.toolbox; 17d62a616ebca5bfa4f9ec5517208e13f2d501b69aAurash Mahbod 18d62a616ebca5bfa4f9ec5517208e13f2d501b69aAurash Mahbodimport android.graphics.Bitmap; 19d62a616ebca5bfa4f9ec5517208e13f2d501b69aAurash Mahbodimport android.graphics.Bitmap.Config; 20d62a616ebca5bfa4f9ec5517208e13f2d501b69aAurash Mahbodimport android.os.Handler; 21d62a616ebca5bfa4f9ec5517208e13f2d501b69aAurash Mahbodimport android.os.Looper; 22d62a616ebca5bfa4f9ec5517208e13f2d501b69aAurash Mahbodimport android.widget.ImageView; 23d62a616ebca5bfa4f9ec5517208e13f2d501b69aAurash Mahbod 24d62a616ebca5bfa4f9ec5517208e13f2d501b69aAurash Mahbodimport com.android.volley.Request; 25d62a616ebca5bfa4f9ec5517208e13f2d501b69aAurash Mahbodimport com.android.volley.RequestQueue; 26d62a616ebca5bfa4f9ec5517208e13f2d501b69aAurash Mahbodimport com.android.volley.Response.ErrorListener; 27d62a616ebca5bfa4f9ec5517208e13f2d501b69aAurash Mahbodimport com.android.volley.Response.Listener; 28d62a616ebca5bfa4f9ec5517208e13f2d501b69aAurash Mahbodimport com.android.volley.VolleyError; 29d62a616ebca5bfa4f9ec5517208e13f2d501b69aAurash Mahbodimport com.android.volley.toolbox.ImageRequest; 30d62a616ebca5bfa4f9ec5517208e13f2d501b69aAurash Mahbod 31d62a616ebca5bfa4f9ec5517208e13f2d501b69aAurash Mahbodimport java.util.HashMap; 32d62a616ebca5bfa4f9ec5517208e13f2d501b69aAurash Mahbodimport java.util.LinkedList; 33d62a616ebca5bfa4f9ec5517208e13f2d501b69aAurash Mahbod 34d62a616ebca5bfa4f9ec5517208e13f2d501b69aAurash Mahbod/** 35d62a616ebca5bfa4f9ec5517208e13f2d501b69aAurash Mahbod * Helper that handles loading and caching images from remote URLs. 36d62a616ebca5bfa4f9ec5517208e13f2d501b69aAurash Mahbod * 37d62a616ebca5bfa4f9ec5517208e13f2d501b69aAurash Mahbod * The simple way to use this class is to call {@link ImageLoader#get(String, ImageListener)} 38d62a616ebca5bfa4f9ec5517208e13f2d501b69aAurash Mahbod * and to pass in the default image listener provided by 39d62a616ebca5bfa4f9ec5517208e13f2d501b69aAurash Mahbod * {@link ImageLoader#getImageListener(ImageView, int, int)}. Note that all function calls to 40d62a616ebca5bfa4f9ec5517208e13f2d501b69aAurash Mahbod * this class must be made from the main thead, and all responses will be delivered to the main 41d62a616ebca5bfa4f9ec5517208e13f2d501b69aAurash Mahbod * thread as well. 42d62a616ebca5bfa4f9ec5517208e13f2d501b69aAurash Mahbod */ 43d62a616ebca5bfa4f9ec5517208e13f2d501b69aAurash Mahbodpublic class ImageLoader { 44d62a616ebca5bfa4f9ec5517208e13f2d501b69aAurash Mahbod /** RequestQueue for dispatching ImageRequests onto. */ 45d62a616ebca5bfa4f9ec5517208e13f2d501b69aAurash Mahbod private final RequestQueue mRequestQueue; 46d62a616ebca5bfa4f9ec5517208e13f2d501b69aAurash Mahbod 47d62a616ebca5bfa4f9ec5517208e13f2d501b69aAurash Mahbod /** Amount of time to wait after first response arrives before delivering all responses. */ 48d62a616ebca5bfa4f9ec5517208e13f2d501b69aAurash Mahbod private int mBatchResponseDelayMs = 100; 49d62a616ebca5bfa4f9ec5517208e13f2d501b69aAurash Mahbod 50d62a616ebca5bfa4f9ec5517208e13f2d501b69aAurash Mahbod /** The cache implementation to be used as an L1 cache before calling into volley. */ 51d62a616ebca5bfa4f9ec5517208e13f2d501b69aAurash Mahbod private final ImageCache mCache; 52d62a616ebca5bfa4f9ec5517208e13f2d501b69aAurash Mahbod 53d62a616ebca5bfa4f9ec5517208e13f2d501b69aAurash Mahbod /** 54d62a616ebca5bfa4f9ec5517208e13f2d501b69aAurash Mahbod * HashMap of Cache keys -> BatchedImageRequest used to track in-flight requests so 55d62a616ebca5bfa4f9ec5517208e13f2d501b69aAurash Mahbod * that we can coalesce multiple requests to the same URL into a single network request. 56d62a616ebca5bfa4f9ec5517208e13f2d501b69aAurash Mahbod */ 57d62a616ebca5bfa4f9ec5517208e13f2d501b69aAurash Mahbod private final HashMap<String, BatchedImageRequest> mInFlightRequests = 58d62a616ebca5bfa4f9ec5517208e13f2d501b69aAurash Mahbod new HashMap<String, BatchedImageRequest>(); 59d62a616ebca5bfa4f9ec5517208e13f2d501b69aAurash Mahbod 60d62a616ebca5bfa4f9ec5517208e13f2d501b69aAurash Mahbod /** HashMap of the currently pending responses (waiting to be delivered). */ 61d62a616ebca5bfa4f9ec5517208e13f2d501b69aAurash Mahbod private final HashMap<String, BatchedImageRequest> mBatchedResponses = 62d62a616ebca5bfa4f9ec5517208e13f2d501b69aAurash Mahbod new HashMap<String, BatchedImageRequest>(); 63d62a616ebca5bfa4f9ec5517208e13f2d501b69aAurash Mahbod 64d62a616ebca5bfa4f9ec5517208e13f2d501b69aAurash Mahbod /** Handler to the main thread. */ 65d62a616ebca5bfa4f9ec5517208e13f2d501b69aAurash Mahbod private final Handler mHandler = new Handler(Looper.getMainLooper()); 66d62a616ebca5bfa4f9ec5517208e13f2d501b69aAurash Mahbod 67d62a616ebca5bfa4f9ec5517208e13f2d501b69aAurash Mahbod /** Runnable for in-flight response delivery. */ 68d62a616ebca5bfa4f9ec5517208e13f2d501b69aAurash Mahbod private Runnable mRunnable; 69d62a616ebca5bfa4f9ec5517208e13f2d501b69aAurash Mahbod 70d62a616ebca5bfa4f9ec5517208e13f2d501b69aAurash Mahbod /** 71d62a616ebca5bfa4f9ec5517208e13f2d501b69aAurash Mahbod * Simple cache adapter interface. If provided to the ImageLoader, it 72d62a616ebca5bfa4f9ec5517208e13f2d501b69aAurash Mahbod * will be used as an L1 cache before dispatch to Volley. Implementations 73d62a616ebca5bfa4f9ec5517208e13f2d501b69aAurash Mahbod * must not block. Implementation with an LruCache is recommended. 74d62a616ebca5bfa4f9ec5517208e13f2d501b69aAurash Mahbod */ 75d62a616ebca5bfa4f9ec5517208e13f2d501b69aAurash Mahbod public interface ImageCache { 76d62a616ebca5bfa4f9ec5517208e13f2d501b69aAurash Mahbod public Bitmap getBitmap(String url); 77d62a616ebca5bfa4f9ec5517208e13f2d501b69aAurash Mahbod public void putBitmap(String url, Bitmap bitmap); 78d62a616ebca5bfa4f9ec5517208e13f2d501b69aAurash Mahbod } 79d62a616ebca5bfa4f9ec5517208e13f2d501b69aAurash Mahbod 80d62a616ebca5bfa4f9ec5517208e13f2d501b69aAurash Mahbod /** 81d62a616ebca5bfa4f9ec5517208e13f2d501b69aAurash Mahbod * Constructs a new ImageLoader. 82d62a616ebca5bfa4f9ec5517208e13f2d501b69aAurash Mahbod * @param queue The RequestQueue to use for making image requests. 83d62a616ebca5bfa4f9ec5517208e13f2d501b69aAurash Mahbod * @param imageCache The cache to use as an L1 cache. 84d62a616ebca5bfa4f9ec5517208e13f2d501b69aAurash Mahbod */ 85d62a616ebca5bfa4f9ec5517208e13f2d501b69aAurash Mahbod public ImageLoader(RequestQueue queue, ImageCache imageCache) { 86d62a616ebca5bfa4f9ec5517208e13f2d501b69aAurash Mahbod mRequestQueue = queue; 87d62a616ebca5bfa4f9ec5517208e13f2d501b69aAurash Mahbod mCache = imageCache; 88d62a616ebca5bfa4f9ec5517208e13f2d501b69aAurash Mahbod } 89d62a616ebca5bfa4f9ec5517208e13f2d501b69aAurash Mahbod 90d62a616ebca5bfa4f9ec5517208e13f2d501b69aAurash Mahbod /** 91d62a616ebca5bfa4f9ec5517208e13f2d501b69aAurash Mahbod * The default implementation of ImageListener which handles basic functionality 92d62a616ebca5bfa4f9ec5517208e13f2d501b69aAurash Mahbod * of showing a default image until the network response is received, at which point 93d62a616ebca5bfa4f9ec5517208e13f2d501b69aAurash Mahbod * it will switch to either the actual image or the error image. 94d62a616ebca5bfa4f9ec5517208e13f2d501b69aAurash Mahbod * @param imageView The imageView that the listener is associated with. 95d62a616ebca5bfa4f9ec5517208e13f2d501b69aAurash Mahbod * @param defaultImageResId Default image resource ID to use, or 0 if it doesn't exist. 96d62a616ebca5bfa4f9ec5517208e13f2d501b69aAurash Mahbod * @param errorImageResId Error image resource ID to use, or 0 if it doesn't exist. 97d62a616ebca5bfa4f9ec5517208e13f2d501b69aAurash Mahbod */ 98d62a616ebca5bfa4f9ec5517208e13f2d501b69aAurash Mahbod public static ImageListener getImageListener(final ImageView view, 99d62a616ebca5bfa4f9ec5517208e13f2d501b69aAurash Mahbod final int defaultImageResId, final int errorImageResId) { 100d62a616ebca5bfa4f9ec5517208e13f2d501b69aAurash Mahbod return new ImageListener() { 101d62a616ebca5bfa4f9ec5517208e13f2d501b69aAurash Mahbod @Override 102d62a616ebca5bfa4f9ec5517208e13f2d501b69aAurash Mahbod public void onErrorResponse(VolleyError error) { 103d62a616ebca5bfa4f9ec5517208e13f2d501b69aAurash Mahbod if (errorImageResId != 0) { 104d62a616ebca5bfa4f9ec5517208e13f2d501b69aAurash Mahbod view.setImageResource(errorImageResId); 105d62a616ebca5bfa4f9ec5517208e13f2d501b69aAurash Mahbod } 106d62a616ebca5bfa4f9ec5517208e13f2d501b69aAurash Mahbod } 107d62a616ebca5bfa4f9ec5517208e13f2d501b69aAurash Mahbod 108d62a616ebca5bfa4f9ec5517208e13f2d501b69aAurash Mahbod @Override 109d62a616ebca5bfa4f9ec5517208e13f2d501b69aAurash Mahbod public void onResponse(ImageContainer response, boolean isImmediate) { 110d62a616ebca5bfa4f9ec5517208e13f2d501b69aAurash Mahbod if (response.getBitmap() != null) { 111d62a616ebca5bfa4f9ec5517208e13f2d501b69aAurash Mahbod view.setImageBitmap(response.getBitmap()); 112d62a616ebca5bfa4f9ec5517208e13f2d501b69aAurash Mahbod } else if (defaultImageResId != 0) { 113d62a616ebca5bfa4f9ec5517208e13f2d501b69aAurash Mahbod view.setImageResource(defaultImageResId); 114d62a616ebca5bfa4f9ec5517208e13f2d501b69aAurash Mahbod } 115d62a616ebca5bfa4f9ec5517208e13f2d501b69aAurash Mahbod } 116d62a616ebca5bfa4f9ec5517208e13f2d501b69aAurash Mahbod }; 117d62a616ebca5bfa4f9ec5517208e13f2d501b69aAurash Mahbod } 118d62a616ebca5bfa4f9ec5517208e13f2d501b69aAurash Mahbod 119d62a616ebca5bfa4f9ec5517208e13f2d501b69aAurash Mahbod /** 120d62a616ebca5bfa4f9ec5517208e13f2d501b69aAurash Mahbod * Interface for the response handlers on image requests. 121d62a616ebca5bfa4f9ec5517208e13f2d501b69aAurash Mahbod * 122d62a616ebca5bfa4f9ec5517208e13f2d501b69aAurash Mahbod * The call flow is this: 123d62a616ebca5bfa4f9ec5517208e13f2d501b69aAurash Mahbod * 1. Upon being attached to a request, onResponse(response, true) will 124d62a616ebca5bfa4f9ec5517208e13f2d501b69aAurash Mahbod * be invoked to reflect any cached data that was already available. If the 125d62a616ebca5bfa4f9ec5517208e13f2d501b69aAurash Mahbod * data was available, response.getBitmap() will be non-null. 126d62a616ebca5bfa4f9ec5517208e13f2d501b69aAurash Mahbod * 127d62a616ebca5bfa4f9ec5517208e13f2d501b69aAurash Mahbod * 2. After a network response returns, only one of the following cases will happen: 128d62a616ebca5bfa4f9ec5517208e13f2d501b69aAurash Mahbod * - onResponse(response, false) will be called if the image was loaded. 129d62a616ebca5bfa4f9ec5517208e13f2d501b69aAurash Mahbod * or 130d62a616ebca5bfa4f9ec5517208e13f2d501b69aAurash Mahbod * - onErrorResponse will be called if there was an error loading the image. 131d62a616ebca5bfa4f9ec5517208e13f2d501b69aAurash Mahbod */ 132d62a616ebca5bfa4f9ec5517208e13f2d501b69aAurash Mahbod public interface ImageListener extends ErrorListener { 133d62a616ebca5bfa4f9ec5517208e13f2d501b69aAurash Mahbod /** 134d62a616ebca5bfa4f9ec5517208e13f2d501b69aAurash Mahbod * Listens for non-error changes to the loading of the image request. 135d62a616ebca5bfa4f9ec5517208e13f2d501b69aAurash Mahbod * 136d62a616ebca5bfa4f9ec5517208e13f2d501b69aAurash Mahbod * @param response Holds all information pertaining to the request, as well 137d62a616ebca5bfa4f9ec5517208e13f2d501b69aAurash Mahbod * as the bitmap (if it is loaded). 138d62a616ebca5bfa4f9ec5517208e13f2d501b69aAurash Mahbod * @param isImmediate True if this was called during ImageLoader.get() variants. 139d62a616ebca5bfa4f9ec5517208e13f2d501b69aAurash Mahbod * This can be used to differentiate between a cached image loading and a network 140d62a616ebca5bfa4f9ec5517208e13f2d501b69aAurash Mahbod * image loading in order to, for example, run an animation to fade in network loaded 141d62a616ebca5bfa4f9ec5517208e13f2d501b69aAurash Mahbod * images. 142d62a616ebca5bfa4f9ec5517208e13f2d501b69aAurash Mahbod */ 143d62a616ebca5bfa4f9ec5517208e13f2d501b69aAurash Mahbod public void onResponse(ImageContainer response, boolean isImmediate); 144d62a616ebca5bfa4f9ec5517208e13f2d501b69aAurash Mahbod } 145d62a616ebca5bfa4f9ec5517208e13f2d501b69aAurash Mahbod 146d62a616ebca5bfa4f9ec5517208e13f2d501b69aAurash Mahbod /** 147285d4727a6c223e8f4c19b6b44078464605b7f8bandaagar * Checks if the item is available in the cache. 148285d4727a6c223e8f4c19b6b44078464605b7f8bandaagar * @param requestUrl The url of the remote image 149285d4727a6c223e8f4c19b6b44078464605b7f8bandaagar * @param maxWidth The maximum width of the returned image. 150285d4727a6c223e8f4c19b6b44078464605b7f8bandaagar * @param maxHeight The maximum height of the returned image. 151285d4727a6c223e8f4c19b6b44078464605b7f8bandaagar * @return True if the item exists in cache, false otherwise. 152285d4727a6c223e8f4c19b6b44078464605b7f8bandaagar */ 153285d4727a6c223e8f4c19b6b44078464605b7f8bandaagar public boolean isCached(String requestUrl, int maxWidth, int maxHeight) { 154285d4727a6c223e8f4c19b6b44078464605b7f8bandaagar throwIfNotOnMainThread(); 155285d4727a6c223e8f4c19b6b44078464605b7f8bandaagar 156285d4727a6c223e8f4c19b6b44078464605b7f8bandaagar String cacheKey = getCacheKey(requestUrl, maxWidth, maxHeight); 157285d4727a6c223e8f4c19b6b44078464605b7f8bandaagar return mCache.getBitmap(cacheKey) != null; 158285d4727a6c223e8f4c19b6b44078464605b7f8bandaagar } 159285d4727a6c223e8f4c19b6b44078464605b7f8bandaagar 160285d4727a6c223e8f4c19b6b44078464605b7f8bandaagar /** 161d62a616ebca5bfa4f9ec5517208e13f2d501b69aAurash Mahbod * Returns an ImageContainer for the requested URL. 162d62a616ebca5bfa4f9ec5517208e13f2d501b69aAurash Mahbod * 163d62a616ebca5bfa4f9ec5517208e13f2d501b69aAurash Mahbod * The ImageContainer will contain either the specified default bitmap or the loaded bitmap. 164d62a616ebca5bfa4f9ec5517208e13f2d501b69aAurash Mahbod * If the default was returned, the {@link ImageLoader} will be invoked when the 165d62a616ebca5bfa4f9ec5517208e13f2d501b69aAurash Mahbod * request is fulfilled. 166d62a616ebca5bfa4f9ec5517208e13f2d501b69aAurash Mahbod * 167d62a616ebca5bfa4f9ec5517208e13f2d501b69aAurash Mahbod * @param requestUrl The URL of the image to be loaded. 168d62a616ebca5bfa4f9ec5517208e13f2d501b69aAurash Mahbod * @param defaultImage Optional default image to return until the actual image is loaded. 169d62a616ebca5bfa4f9ec5517208e13f2d501b69aAurash Mahbod */ 170d62a616ebca5bfa4f9ec5517208e13f2d501b69aAurash Mahbod public ImageContainer get(String requestUrl, final ImageListener listener) { 171d62a616ebca5bfa4f9ec5517208e13f2d501b69aAurash Mahbod return get(requestUrl, listener, 0, 0); 172d62a616ebca5bfa4f9ec5517208e13f2d501b69aAurash Mahbod } 173d62a616ebca5bfa4f9ec5517208e13f2d501b69aAurash Mahbod 174d62a616ebca5bfa4f9ec5517208e13f2d501b69aAurash Mahbod /** 175d62a616ebca5bfa4f9ec5517208e13f2d501b69aAurash Mahbod * Issues a bitmap request with the given URL if that image is not available 176d62a616ebca5bfa4f9ec5517208e13f2d501b69aAurash Mahbod * in the cache, and returns a bitmap container that contains all of the data 177d62a616ebca5bfa4f9ec5517208e13f2d501b69aAurash Mahbod * relating to the request (as well as the default image if the requested 178d62a616ebca5bfa4f9ec5517208e13f2d501b69aAurash Mahbod * image is not available). 179d62a616ebca5bfa4f9ec5517208e13f2d501b69aAurash Mahbod * @param requestUrl The url of the remote image 180d62a616ebca5bfa4f9ec5517208e13f2d501b69aAurash Mahbod * @param imageListener The listener to call when the remote image is loaded 181d62a616ebca5bfa4f9ec5517208e13f2d501b69aAurash Mahbod * @param maxWidth The maximum width of the returned image. 182d62a616ebca5bfa4f9ec5517208e13f2d501b69aAurash Mahbod * @param maxHeight The maximum height of the returned image. 183d62a616ebca5bfa4f9ec5517208e13f2d501b69aAurash Mahbod * @return A container object that contains all of the properties of the request, as well as 184d62a616ebca5bfa4f9ec5517208e13f2d501b69aAurash Mahbod * the currently available image (default if remote is not loaded). 185d62a616ebca5bfa4f9ec5517208e13f2d501b69aAurash Mahbod */ 186d62a616ebca5bfa4f9ec5517208e13f2d501b69aAurash Mahbod public ImageContainer get(String requestUrl, ImageListener imageListener, 187d62a616ebca5bfa4f9ec5517208e13f2d501b69aAurash Mahbod int maxWidth, int maxHeight) { 188d62a616ebca5bfa4f9ec5517208e13f2d501b69aAurash Mahbod // only fulfill requests that were initiated from the main thread. 189d62a616ebca5bfa4f9ec5517208e13f2d501b69aAurash Mahbod throwIfNotOnMainThread(); 190d62a616ebca5bfa4f9ec5517208e13f2d501b69aAurash Mahbod 191d62a616ebca5bfa4f9ec5517208e13f2d501b69aAurash Mahbod final String cacheKey = getCacheKey(requestUrl, maxWidth, maxHeight); 192d62a616ebca5bfa4f9ec5517208e13f2d501b69aAurash Mahbod 193d62a616ebca5bfa4f9ec5517208e13f2d501b69aAurash Mahbod // Try to look up the request in the cache of remote images. 194d62a616ebca5bfa4f9ec5517208e13f2d501b69aAurash Mahbod Bitmap cachedBitmap = mCache.getBitmap(cacheKey); 195d62a616ebca5bfa4f9ec5517208e13f2d501b69aAurash Mahbod if (cachedBitmap != null) { 196d62a616ebca5bfa4f9ec5517208e13f2d501b69aAurash Mahbod // Return the cached bitmap. 197d62a616ebca5bfa4f9ec5517208e13f2d501b69aAurash Mahbod ImageContainer container = new ImageContainer(cachedBitmap, requestUrl, null, null); 198d62a616ebca5bfa4f9ec5517208e13f2d501b69aAurash Mahbod imageListener.onResponse(container, true); 199d62a616ebca5bfa4f9ec5517208e13f2d501b69aAurash Mahbod return container; 200d62a616ebca5bfa4f9ec5517208e13f2d501b69aAurash Mahbod } 201d62a616ebca5bfa4f9ec5517208e13f2d501b69aAurash Mahbod 202d62a616ebca5bfa4f9ec5517208e13f2d501b69aAurash Mahbod // The bitmap did not exist in the cache, fetch it! 203d62a616ebca5bfa4f9ec5517208e13f2d501b69aAurash Mahbod ImageContainer imageContainer = 204d62a616ebca5bfa4f9ec5517208e13f2d501b69aAurash Mahbod new ImageContainer(null, requestUrl, cacheKey, imageListener); 205d62a616ebca5bfa4f9ec5517208e13f2d501b69aAurash Mahbod 206d62a616ebca5bfa4f9ec5517208e13f2d501b69aAurash Mahbod // Update the caller to let them know that they should use the default bitmap. 207d62a616ebca5bfa4f9ec5517208e13f2d501b69aAurash Mahbod imageListener.onResponse(imageContainer, true); 208d62a616ebca5bfa4f9ec5517208e13f2d501b69aAurash Mahbod 209d62a616ebca5bfa4f9ec5517208e13f2d501b69aAurash Mahbod // Check to see if a request is already in-flight. 210d62a616ebca5bfa4f9ec5517208e13f2d501b69aAurash Mahbod BatchedImageRequest request = mInFlightRequests.get(cacheKey); 211d62a616ebca5bfa4f9ec5517208e13f2d501b69aAurash Mahbod if (request != null) { 212d62a616ebca5bfa4f9ec5517208e13f2d501b69aAurash Mahbod // If it is, add this request to the list of listeners. 213d62a616ebca5bfa4f9ec5517208e13f2d501b69aAurash Mahbod request.addContainer(imageContainer); 214d62a616ebca5bfa4f9ec5517208e13f2d501b69aAurash Mahbod return imageContainer; 215d62a616ebca5bfa4f9ec5517208e13f2d501b69aAurash Mahbod } 216d62a616ebca5bfa4f9ec5517208e13f2d501b69aAurash Mahbod 217d62a616ebca5bfa4f9ec5517208e13f2d501b69aAurash Mahbod // The request is not already in flight. Send the new request to the network and 218d62a616ebca5bfa4f9ec5517208e13f2d501b69aAurash Mahbod // track it. 219d62a616ebca5bfa4f9ec5517208e13f2d501b69aAurash Mahbod Request<?> newRequest = 220d62a616ebca5bfa4f9ec5517208e13f2d501b69aAurash Mahbod new ImageRequest(requestUrl, new Listener<Bitmap>() { 221d62a616ebca5bfa4f9ec5517208e13f2d501b69aAurash Mahbod @Override 222d62a616ebca5bfa4f9ec5517208e13f2d501b69aAurash Mahbod public void onResponse(Bitmap response) { 223d62a616ebca5bfa4f9ec5517208e13f2d501b69aAurash Mahbod onGetImageSuccess(cacheKey, response); 224d62a616ebca5bfa4f9ec5517208e13f2d501b69aAurash Mahbod } 225d62a616ebca5bfa4f9ec5517208e13f2d501b69aAurash Mahbod }, maxWidth, maxHeight, 226d62a616ebca5bfa4f9ec5517208e13f2d501b69aAurash Mahbod Config.RGB_565, new ErrorListener() { 227d62a616ebca5bfa4f9ec5517208e13f2d501b69aAurash Mahbod @Override 228d62a616ebca5bfa4f9ec5517208e13f2d501b69aAurash Mahbod public void onErrorResponse(VolleyError error) { 229d62a616ebca5bfa4f9ec5517208e13f2d501b69aAurash Mahbod onGetImageError(cacheKey, error); 230d62a616ebca5bfa4f9ec5517208e13f2d501b69aAurash Mahbod } 231d62a616ebca5bfa4f9ec5517208e13f2d501b69aAurash Mahbod }); 232d62a616ebca5bfa4f9ec5517208e13f2d501b69aAurash Mahbod 233d62a616ebca5bfa4f9ec5517208e13f2d501b69aAurash Mahbod mRequestQueue.add(newRequest); 234d62a616ebca5bfa4f9ec5517208e13f2d501b69aAurash Mahbod mInFlightRequests.put(cacheKey, 235d62a616ebca5bfa4f9ec5517208e13f2d501b69aAurash Mahbod new BatchedImageRequest(newRequest, imageContainer)); 236d62a616ebca5bfa4f9ec5517208e13f2d501b69aAurash Mahbod return imageContainer; 237d62a616ebca5bfa4f9ec5517208e13f2d501b69aAurash Mahbod } 238d62a616ebca5bfa4f9ec5517208e13f2d501b69aAurash Mahbod 239d62a616ebca5bfa4f9ec5517208e13f2d501b69aAurash Mahbod /** 240d62a616ebca5bfa4f9ec5517208e13f2d501b69aAurash Mahbod * Sets the amount of time to wait after the first response arrives before delivering all 241d62a616ebca5bfa4f9ec5517208e13f2d501b69aAurash Mahbod * responses. Batching can be disabled entirely by passing in 0. 242d62a616ebca5bfa4f9ec5517208e13f2d501b69aAurash Mahbod * @param newBatchedResponseDelayMs The time in milliseconds to wait. 243d62a616ebca5bfa4f9ec5517208e13f2d501b69aAurash Mahbod */ 244d62a616ebca5bfa4f9ec5517208e13f2d501b69aAurash Mahbod public void setBatchedResponseDelay(int newBatchedResponseDelayMs) { 245d62a616ebca5bfa4f9ec5517208e13f2d501b69aAurash Mahbod mBatchResponseDelayMs = newBatchedResponseDelayMs; 246d62a616ebca5bfa4f9ec5517208e13f2d501b69aAurash Mahbod } 247d62a616ebca5bfa4f9ec5517208e13f2d501b69aAurash Mahbod 248d62a616ebca5bfa4f9ec5517208e13f2d501b69aAurash Mahbod /** 249d62a616ebca5bfa4f9ec5517208e13f2d501b69aAurash Mahbod * Handler for when an image was successfully loaded. 250d62a616ebca5bfa4f9ec5517208e13f2d501b69aAurash Mahbod * @param cacheKey The cache key that is associated with the image request. 251d62a616ebca5bfa4f9ec5517208e13f2d501b69aAurash Mahbod * @param response The bitmap that was returned from the network. 252d62a616ebca5bfa4f9ec5517208e13f2d501b69aAurash Mahbod */ 253d62a616ebca5bfa4f9ec5517208e13f2d501b69aAurash Mahbod private void onGetImageSuccess(String cacheKey, Bitmap response) { 254d62a616ebca5bfa4f9ec5517208e13f2d501b69aAurash Mahbod // cache the image that was fetched. 255d62a616ebca5bfa4f9ec5517208e13f2d501b69aAurash Mahbod mCache.putBitmap(cacheKey, response); 256d62a616ebca5bfa4f9ec5517208e13f2d501b69aAurash Mahbod 257d62a616ebca5bfa4f9ec5517208e13f2d501b69aAurash Mahbod // remove the request from the list of in-flight requests. 258d62a616ebca5bfa4f9ec5517208e13f2d501b69aAurash Mahbod BatchedImageRequest request = mInFlightRequests.remove(cacheKey); 259d62a616ebca5bfa4f9ec5517208e13f2d501b69aAurash Mahbod 260d62a616ebca5bfa4f9ec5517208e13f2d501b69aAurash Mahbod if (request != null) { 261d62a616ebca5bfa4f9ec5517208e13f2d501b69aAurash Mahbod // Update the response bitmap. 262d62a616ebca5bfa4f9ec5517208e13f2d501b69aAurash Mahbod request.mResponseBitmap = response; 263d62a616ebca5bfa4f9ec5517208e13f2d501b69aAurash Mahbod 264d62a616ebca5bfa4f9ec5517208e13f2d501b69aAurash Mahbod // Send the batched response 265dc5355251132a447e384e273127b1a8bdaf32f5aCameron Ketcham batchResponse(cacheKey, request); 266d62a616ebca5bfa4f9ec5517208e13f2d501b69aAurash Mahbod } 267d62a616ebca5bfa4f9ec5517208e13f2d501b69aAurash Mahbod } 268d62a616ebca5bfa4f9ec5517208e13f2d501b69aAurash Mahbod 269d62a616ebca5bfa4f9ec5517208e13f2d501b69aAurash Mahbod /** 270d62a616ebca5bfa4f9ec5517208e13f2d501b69aAurash Mahbod * Handler for when an image failed to load. 271d62a616ebca5bfa4f9ec5517208e13f2d501b69aAurash Mahbod * @param cacheKey The cache key that is associated with the image request. 272d62a616ebca5bfa4f9ec5517208e13f2d501b69aAurash Mahbod */ 273d62a616ebca5bfa4f9ec5517208e13f2d501b69aAurash Mahbod private void onGetImageError(String cacheKey, VolleyError error) { 274d62a616ebca5bfa4f9ec5517208e13f2d501b69aAurash Mahbod // Notify the requesters that something failed via a null result. 275d62a616ebca5bfa4f9ec5517208e13f2d501b69aAurash Mahbod // Remove this request from the list of in-flight requests. 276d62a616ebca5bfa4f9ec5517208e13f2d501b69aAurash Mahbod BatchedImageRequest request = mInFlightRequests.remove(cacheKey); 277d62a616ebca5bfa4f9ec5517208e13f2d501b69aAurash Mahbod 278d62a616ebca5bfa4f9ec5517208e13f2d501b69aAurash Mahbod if (request != null) { 279ee9d4500c337d5d2371651764dfcbbf8d5502757Andrew Sutherland // Set the error for this request 280ee9d4500c337d5d2371651764dfcbbf8d5502757Andrew Sutherland request.setError(error); 281ee9d4500c337d5d2371651764dfcbbf8d5502757Andrew Sutherland 282d62a616ebca5bfa4f9ec5517208e13f2d501b69aAurash Mahbod // Send the batched response 283dc5355251132a447e384e273127b1a8bdaf32f5aCameron Ketcham batchResponse(cacheKey, request); 284d62a616ebca5bfa4f9ec5517208e13f2d501b69aAurash Mahbod } 285d62a616ebca5bfa4f9ec5517208e13f2d501b69aAurash Mahbod } 286d62a616ebca5bfa4f9ec5517208e13f2d501b69aAurash Mahbod 287d62a616ebca5bfa4f9ec5517208e13f2d501b69aAurash Mahbod /** 288d62a616ebca5bfa4f9ec5517208e13f2d501b69aAurash Mahbod * Container object for all of the data surrounding an image request. 289d62a616ebca5bfa4f9ec5517208e13f2d501b69aAurash Mahbod */ 290d62a616ebca5bfa4f9ec5517208e13f2d501b69aAurash Mahbod public class ImageContainer { 291d62a616ebca5bfa4f9ec5517208e13f2d501b69aAurash Mahbod /** 292d62a616ebca5bfa4f9ec5517208e13f2d501b69aAurash Mahbod * The most relevant bitmap for the container. If the image was in cache, the 293d62a616ebca5bfa4f9ec5517208e13f2d501b69aAurash Mahbod * Holder to use for the final bitmap (the one that pairs to the requested URL). 294d62a616ebca5bfa4f9ec5517208e13f2d501b69aAurash Mahbod */ 295d62a616ebca5bfa4f9ec5517208e13f2d501b69aAurash Mahbod private Bitmap mBitmap; 296d62a616ebca5bfa4f9ec5517208e13f2d501b69aAurash Mahbod 297d62a616ebca5bfa4f9ec5517208e13f2d501b69aAurash Mahbod private final ImageListener mListener; 298d62a616ebca5bfa4f9ec5517208e13f2d501b69aAurash Mahbod 299d62a616ebca5bfa4f9ec5517208e13f2d501b69aAurash Mahbod /** The cache key that was associated with the request */ 300d62a616ebca5bfa4f9ec5517208e13f2d501b69aAurash Mahbod private final String mCacheKey; 301d62a616ebca5bfa4f9ec5517208e13f2d501b69aAurash Mahbod 302d62a616ebca5bfa4f9ec5517208e13f2d501b69aAurash Mahbod /** The request URL that was specified */ 303d62a616ebca5bfa4f9ec5517208e13f2d501b69aAurash Mahbod private final String mRequestUrl; 304d62a616ebca5bfa4f9ec5517208e13f2d501b69aAurash Mahbod 305d62a616ebca5bfa4f9ec5517208e13f2d501b69aAurash Mahbod /** 306d62a616ebca5bfa4f9ec5517208e13f2d501b69aAurash Mahbod * Constructs a BitmapContainer object. 307d62a616ebca5bfa4f9ec5517208e13f2d501b69aAurash Mahbod * @param bitmap The final bitmap (if it exists). 308d62a616ebca5bfa4f9ec5517208e13f2d501b69aAurash Mahbod * @param requestUrl The requested URL for this container. 309d62a616ebca5bfa4f9ec5517208e13f2d501b69aAurash Mahbod * @param cacheKey The cache key that identifies the requested URL for this container. 310d62a616ebca5bfa4f9ec5517208e13f2d501b69aAurash Mahbod */ 311d62a616ebca5bfa4f9ec5517208e13f2d501b69aAurash Mahbod public ImageContainer(Bitmap bitmap, String requestUrl, 312d62a616ebca5bfa4f9ec5517208e13f2d501b69aAurash Mahbod String cacheKey, ImageListener listener) { 313d62a616ebca5bfa4f9ec5517208e13f2d501b69aAurash Mahbod mBitmap = bitmap; 314d62a616ebca5bfa4f9ec5517208e13f2d501b69aAurash Mahbod mRequestUrl = requestUrl; 315d62a616ebca5bfa4f9ec5517208e13f2d501b69aAurash Mahbod mCacheKey = cacheKey; 316d62a616ebca5bfa4f9ec5517208e13f2d501b69aAurash Mahbod mListener = listener; 317d62a616ebca5bfa4f9ec5517208e13f2d501b69aAurash Mahbod } 318d62a616ebca5bfa4f9ec5517208e13f2d501b69aAurash Mahbod 319d62a616ebca5bfa4f9ec5517208e13f2d501b69aAurash Mahbod /** 320d62a616ebca5bfa4f9ec5517208e13f2d501b69aAurash Mahbod * Releases interest in the in-flight request (and cancels it if no one else is listening). 321d62a616ebca5bfa4f9ec5517208e13f2d501b69aAurash Mahbod */ 322d62a616ebca5bfa4f9ec5517208e13f2d501b69aAurash Mahbod public void cancelRequest() { 323d62a616ebca5bfa4f9ec5517208e13f2d501b69aAurash Mahbod if (mListener == null) { 324d62a616ebca5bfa4f9ec5517208e13f2d501b69aAurash Mahbod return; 325d62a616ebca5bfa4f9ec5517208e13f2d501b69aAurash Mahbod } 326d62a616ebca5bfa4f9ec5517208e13f2d501b69aAurash Mahbod 327d62a616ebca5bfa4f9ec5517208e13f2d501b69aAurash Mahbod BatchedImageRequest request = mInFlightRequests.get(mCacheKey); 328d62a616ebca5bfa4f9ec5517208e13f2d501b69aAurash Mahbod if (request != null) { 329d62a616ebca5bfa4f9ec5517208e13f2d501b69aAurash Mahbod boolean canceled = request.removeContainerAndCancelIfNecessary(this); 330d62a616ebca5bfa4f9ec5517208e13f2d501b69aAurash Mahbod if (canceled) { 331d62a616ebca5bfa4f9ec5517208e13f2d501b69aAurash Mahbod mInFlightRequests.remove(mCacheKey); 332d62a616ebca5bfa4f9ec5517208e13f2d501b69aAurash Mahbod } 333d62a616ebca5bfa4f9ec5517208e13f2d501b69aAurash Mahbod } else { 334d62a616ebca5bfa4f9ec5517208e13f2d501b69aAurash Mahbod // check to see if it is already batched for delivery. 335d62a616ebca5bfa4f9ec5517208e13f2d501b69aAurash Mahbod request = mBatchedResponses.get(mCacheKey); 336d62a616ebca5bfa4f9ec5517208e13f2d501b69aAurash Mahbod if (request != null) { 337d62a616ebca5bfa4f9ec5517208e13f2d501b69aAurash Mahbod request.removeContainerAndCancelIfNecessary(this); 338d62a616ebca5bfa4f9ec5517208e13f2d501b69aAurash Mahbod if (request.mContainers.size() == 0) { 339d62a616ebca5bfa4f9ec5517208e13f2d501b69aAurash Mahbod mBatchedResponses.remove(mCacheKey); 340d62a616ebca5bfa4f9ec5517208e13f2d501b69aAurash Mahbod } 341d62a616ebca5bfa4f9ec5517208e13f2d501b69aAurash Mahbod } 342d62a616ebca5bfa4f9ec5517208e13f2d501b69aAurash Mahbod } 343d62a616ebca5bfa4f9ec5517208e13f2d501b69aAurash Mahbod } 344d62a616ebca5bfa4f9ec5517208e13f2d501b69aAurash Mahbod 345d62a616ebca5bfa4f9ec5517208e13f2d501b69aAurash Mahbod /** 346d62a616ebca5bfa4f9ec5517208e13f2d501b69aAurash Mahbod * Returns the bitmap associated with the request URL if it has been loaded, null otherwise. 347d62a616ebca5bfa4f9ec5517208e13f2d501b69aAurash Mahbod */ 348d62a616ebca5bfa4f9ec5517208e13f2d501b69aAurash Mahbod public Bitmap getBitmap() { 349d62a616ebca5bfa4f9ec5517208e13f2d501b69aAurash Mahbod return mBitmap; 350d62a616ebca5bfa4f9ec5517208e13f2d501b69aAurash Mahbod } 351d62a616ebca5bfa4f9ec5517208e13f2d501b69aAurash Mahbod 352d62a616ebca5bfa4f9ec5517208e13f2d501b69aAurash Mahbod /** 353d62a616ebca5bfa4f9ec5517208e13f2d501b69aAurash Mahbod * Returns the requested URL for this container. 354d62a616ebca5bfa4f9ec5517208e13f2d501b69aAurash Mahbod */ 355d62a616ebca5bfa4f9ec5517208e13f2d501b69aAurash Mahbod public String getRequestUrl() { 356d62a616ebca5bfa4f9ec5517208e13f2d501b69aAurash Mahbod return mRequestUrl; 357d62a616ebca5bfa4f9ec5517208e13f2d501b69aAurash Mahbod } 358d62a616ebca5bfa4f9ec5517208e13f2d501b69aAurash Mahbod } 359d62a616ebca5bfa4f9ec5517208e13f2d501b69aAurash Mahbod 360d62a616ebca5bfa4f9ec5517208e13f2d501b69aAurash Mahbod /** 361d62a616ebca5bfa4f9ec5517208e13f2d501b69aAurash Mahbod * Wrapper class used to map a Request to the set of active ImageContainer objects that are 362d62a616ebca5bfa4f9ec5517208e13f2d501b69aAurash Mahbod * interested in its results. 363d62a616ebca5bfa4f9ec5517208e13f2d501b69aAurash Mahbod */ 364d62a616ebca5bfa4f9ec5517208e13f2d501b69aAurash Mahbod private class BatchedImageRequest { 365d62a616ebca5bfa4f9ec5517208e13f2d501b69aAurash Mahbod /** The request being tracked */ 366d62a616ebca5bfa4f9ec5517208e13f2d501b69aAurash Mahbod private final Request<?> mRequest; 367d62a616ebca5bfa4f9ec5517208e13f2d501b69aAurash Mahbod 368d62a616ebca5bfa4f9ec5517208e13f2d501b69aAurash Mahbod /** The result of the request being tracked by this item */ 369d62a616ebca5bfa4f9ec5517208e13f2d501b69aAurash Mahbod private Bitmap mResponseBitmap; 370d62a616ebca5bfa4f9ec5517208e13f2d501b69aAurash Mahbod 371dc5355251132a447e384e273127b1a8bdaf32f5aCameron Ketcham /** Error if one occurred for this response */ 372dc5355251132a447e384e273127b1a8bdaf32f5aCameron Ketcham private VolleyError mError; 373dc5355251132a447e384e273127b1a8bdaf32f5aCameron Ketcham 374d62a616ebca5bfa4f9ec5517208e13f2d501b69aAurash Mahbod /** List of all of the active ImageContainers that are interested in the request */ 375d62a616ebca5bfa4f9ec5517208e13f2d501b69aAurash Mahbod private final LinkedList<ImageContainer> mContainers = new LinkedList<ImageContainer>(); 376d62a616ebca5bfa4f9ec5517208e13f2d501b69aAurash Mahbod 377d62a616ebca5bfa4f9ec5517208e13f2d501b69aAurash Mahbod /** 378d62a616ebca5bfa4f9ec5517208e13f2d501b69aAurash Mahbod * Constructs a new BatchedImageRequest object 379d62a616ebca5bfa4f9ec5517208e13f2d501b69aAurash Mahbod * @param request The request being tracked 380d62a616ebca5bfa4f9ec5517208e13f2d501b69aAurash Mahbod * @param container The ImageContainer of the person who initiated the request. 381d62a616ebca5bfa4f9ec5517208e13f2d501b69aAurash Mahbod */ 382d62a616ebca5bfa4f9ec5517208e13f2d501b69aAurash Mahbod public BatchedImageRequest(Request<?> request, ImageContainer container) { 383d62a616ebca5bfa4f9ec5517208e13f2d501b69aAurash Mahbod mRequest = request; 384d62a616ebca5bfa4f9ec5517208e13f2d501b69aAurash Mahbod mContainers.add(container); 385d62a616ebca5bfa4f9ec5517208e13f2d501b69aAurash Mahbod } 386d62a616ebca5bfa4f9ec5517208e13f2d501b69aAurash Mahbod 387d62a616ebca5bfa4f9ec5517208e13f2d501b69aAurash Mahbod /** 388dc5355251132a447e384e273127b1a8bdaf32f5aCameron Ketcham * Set the error for this response 389dc5355251132a447e384e273127b1a8bdaf32f5aCameron Ketcham */ 390dc5355251132a447e384e273127b1a8bdaf32f5aCameron Ketcham public void setError(VolleyError error) { 391dc5355251132a447e384e273127b1a8bdaf32f5aCameron Ketcham mError = error; 392dc5355251132a447e384e273127b1a8bdaf32f5aCameron Ketcham } 393dc5355251132a447e384e273127b1a8bdaf32f5aCameron Ketcham 394dc5355251132a447e384e273127b1a8bdaf32f5aCameron Ketcham /** 395dc5355251132a447e384e273127b1a8bdaf32f5aCameron Ketcham * Get the error for this response 396dc5355251132a447e384e273127b1a8bdaf32f5aCameron Ketcham */ 397dc5355251132a447e384e273127b1a8bdaf32f5aCameron Ketcham public VolleyError getError() { 398dc5355251132a447e384e273127b1a8bdaf32f5aCameron Ketcham return mError; 399dc5355251132a447e384e273127b1a8bdaf32f5aCameron Ketcham } 400dc5355251132a447e384e273127b1a8bdaf32f5aCameron Ketcham 401dc5355251132a447e384e273127b1a8bdaf32f5aCameron Ketcham /** 402d62a616ebca5bfa4f9ec5517208e13f2d501b69aAurash Mahbod * Adds another ImageContainer to the list of those interested in the results of 403d62a616ebca5bfa4f9ec5517208e13f2d501b69aAurash Mahbod * the request. 404d62a616ebca5bfa4f9ec5517208e13f2d501b69aAurash Mahbod */ 405d62a616ebca5bfa4f9ec5517208e13f2d501b69aAurash Mahbod public void addContainer(ImageContainer container) { 406d62a616ebca5bfa4f9ec5517208e13f2d501b69aAurash Mahbod mContainers.add(container); 407d62a616ebca5bfa4f9ec5517208e13f2d501b69aAurash Mahbod } 408d62a616ebca5bfa4f9ec5517208e13f2d501b69aAurash Mahbod 409d62a616ebca5bfa4f9ec5517208e13f2d501b69aAurash Mahbod /** 410d62a616ebca5bfa4f9ec5517208e13f2d501b69aAurash Mahbod * Detatches the bitmap container from the request and cancels the request if no one is 411d62a616ebca5bfa4f9ec5517208e13f2d501b69aAurash Mahbod * left listening. 412d62a616ebca5bfa4f9ec5517208e13f2d501b69aAurash Mahbod * @param container The container to remove from the list 413d62a616ebca5bfa4f9ec5517208e13f2d501b69aAurash Mahbod * @return True if the request was canceled, false otherwise. 414d62a616ebca5bfa4f9ec5517208e13f2d501b69aAurash Mahbod */ 415d62a616ebca5bfa4f9ec5517208e13f2d501b69aAurash Mahbod public boolean removeContainerAndCancelIfNecessary(ImageContainer container) { 416d62a616ebca5bfa4f9ec5517208e13f2d501b69aAurash Mahbod mContainers.remove(container); 417d62a616ebca5bfa4f9ec5517208e13f2d501b69aAurash Mahbod if (mContainers.size() == 0) { 418d62a616ebca5bfa4f9ec5517208e13f2d501b69aAurash Mahbod mRequest.cancel(); 419d62a616ebca5bfa4f9ec5517208e13f2d501b69aAurash Mahbod return true; 420d62a616ebca5bfa4f9ec5517208e13f2d501b69aAurash Mahbod } 421d62a616ebca5bfa4f9ec5517208e13f2d501b69aAurash Mahbod return false; 422d62a616ebca5bfa4f9ec5517208e13f2d501b69aAurash Mahbod } 423d62a616ebca5bfa4f9ec5517208e13f2d501b69aAurash Mahbod } 424d62a616ebca5bfa4f9ec5517208e13f2d501b69aAurash Mahbod 425d62a616ebca5bfa4f9ec5517208e13f2d501b69aAurash Mahbod /** 426d62a616ebca5bfa4f9ec5517208e13f2d501b69aAurash Mahbod * Starts the runnable for batched delivery of responses if it is not already started. 427d62a616ebca5bfa4f9ec5517208e13f2d501b69aAurash Mahbod * @param cacheKey The cacheKey of the response being delivered. 428d62a616ebca5bfa4f9ec5517208e13f2d501b69aAurash Mahbod * @param request The BatchedImageRequest to be delivered. 429d62a616ebca5bfa4f9ec5517208e13f2d501b69aAurash Mahbod * @param error The volley error associated with the request (if applicable). 430d62a616ebca5bfa4f9ec5517208e13f2d501b69aAurash Mahbod */ 431dc5355251132a447e384e273127b1a8bdaf32f5aCameron Ketcham private void batchResponse(String cacheKey, BatchedImageRequest request) { 432d62a616ebca5bfa4f9ec5517208e13f2d501b69aAurash Mahbod mBatchedResponses.put(cacheKey, request); 433d62a616ebca5bfa4f9ec5517208e13f2d501b69aAurash Mahbod // If we don't already have a batch delivery runnable in flight, make a new one. 434d62a616ebca5bfa4f9ec5517208e13f2d501b69aAurash Mahbod // Note that this will be used to deliver responses to all callers in mBatchedResponses. 435d62a616ebca5bfa4f9ec5517208e13f2d501b69aAurash Mahbod if (mRunnable == null) { 436d62a616ebca5bfa4f9ec5517208e13f2d501b69aAurash Mahbod mRunnable = new Runnable() { 437d62a616ebca5bfa4f9ec5517208e13f2d501b69aAurash Mahbod @Override 438d62a616ebca5bfa4f9ec5517208e13f2d501b69aAurash Mahbod public void run() { 439d62a616ebca5bfa4f9ec5517208e13f2d501b69aAurash Mahbod for (BatchedImageRequest bir : mBatchedResponses.values()) { 440d62a616ebca5bfa4f9ec5517208e13f2d501b69aAurash Mahbod for (ImageContainer container : bir.mContainers) { 441d62a616ebca5bfa4f9ec5517208e13f2d501b69aAurash Mahbod // If one of the callers in the batched request canceled the request 442d62a616ebca5bfa4f9ec5517208e13f2d501b69aAurash Mahbod // after the response was received but before it was delivered, 443d62a616ebca5bfa4f9ec5517208e13f2d501b69aAurash Mahbod // skip them. 444d62a616ebca5bfa4f9ec5517208e13f2d501b69aAurash Mahbod if (container.mListener == null) { 445d62a616ebca5bfa4f9ec5517208e13f2d501b69aAurash Mahbod continue; 446d62a616ebca5bfa4f9ec5517208e13f2d501b69aAurash Mahbod } 447dc5355251132a447e384e273127b1a8bdaf32f5aCameron Ketcham if (bir.getError() == null) { 448d62a616ebca5bfa4f9ec5517208e13f2d501b69aAurash Mahbod container.mBitmap = bir.mResponseBitmap; 449d62a616ebca5bfa4f9ec5517208e13f2d501b69aAurash Mahbod container.mListener.onResponse(container, false); 450d62a616ebca5bfa4f9ec5517208e13f2d501b69aAurash Mahbod } else { 451dc5355251132a447e384e273127b1a8bdaf32f5aCameron Ketcham container.mListener.onErrorResponse(bir.getError()); 452d62a616ebca5bfa4f9ec5517208e13f2d501b69aAurash Mahbod } 453d62a616ebca5bfa4f9ec5517208e13f2d501b69aAurash Mahbod } 454d62a616ebca5bfa4f9ec5517208e13f2d501b69aAurash Mahbod } 455d62a616ebca5bfa4f9ec5517208e13f2d501b69aAurash Mahbod mBatchedResponses.clear(); 456d62a616ebca5bfa4f9ec5517208e13f2d501b69aAurash Mahbod mRunnable = null; 457d62a616ebca5bfa4f9ec5517208e13f2d501b69aAurash Mahbod } 458d62a616ebca5bfa4f9ec5517208e13f2d501b69aAurash Mahbod 459d62a616ebca5bfa4f9ec5517208e13f2d501b69aAurash Mahbod }; 460d62a616ebca5bfa4f9ec5517208e13f2d501b69aAurash Mahbod // Post the runnable. 461d62a616ebca5bfa4f9ec5517208e13f2d501b69aAurash Mahbod mHandler.postDelayed(mRunnable, mBatchResponseDelayMs); 462d62a616ebca5bfa4f9ec5517208e13f2d501b69aAurash Mahbod } 463d62a616ebca5bfa4f9ec5517208e13f2d501b69aAurash Mahbod } 464d62a616ebca5bfa4f9ec5517208e13f2d501b69aAurash Mahbod 465d62a616ebca5bfa4f9ec5517208e13f2d501b69aAurash Mahbod private void throwIfNotOnMainThread() { 466d62a616ebca5bfa4f9ec5517208e13f2d501b69aAurash Mahbod if (Looper.myLooper() != Looper.getMainLooper()) { 467d62a616ebca5bfa4f9ec5517208e13f2d501b69aAurash Mahbod throw new IllegalStateException("ImageLoader must be invoked from the main thread."); 468d62a616ebca5bfa4f9ec5517208e13f2d501b69aAurash Mahbod } 469d62a616ebca5bfa4f9ec5517208e13f2d501b69aAurash Mahbod } 470d62a616ebca5bfa4f9ec5517208e13f2d501b69aAurash Mahbod /** 471d62a616ebca5bfa4f9ec5517208e13f2d501b69aAurash Mahbod * Creates a cache key for use with the L1 cache. 472d62a616ebca5bfa4f9ec5517208e13f2d501b69aAurash Mahbod * @param url The URL of the request. 473d62a616ebca5bfa4f9ec5517208e13f2d501b69aAurash Mahbod * @param maxWidth The max-width of the output. 474d62a616ebca5bfa4f9ec5517208e13f2d501b69aAurash Mahbod * @param maxHeight The max-height of the output. 475d62a616ebca5bfa4f9ec5517208e13f2d501b69aAurash Mahbod */ 476d62a616ebca5bfa4f9ec5517208e13f2d501b69aAurash Mahbod private static String getCacheKey(String url, int maxWidth, int maxHeight) { 477d62a616ebca5bfa4f9ec5517208e13f2d501b69aAurash Mahbod return new StringBuilder(url.length() + 12).append("#W").append(maxWidth) 478d62a616ebca5bfa4f9ec5517208e13f2d501b69aAurash Mahbod .append("#H").append(maxHeight).append(url).toString(); 479d62a616ebca5bfa4f9ec5517208e13f2d501b69aAurash Mahbod } 480d62a616ebca5bfa4f9ec5517208e13f2d501b69aAurash Mahbod} 481