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