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