1d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru/*
2d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru * Copyright (C) 2011 The Android Open Source Project
3d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru *
4d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru * Licensed under the Apache License, Version 2.0 (the "License");
5d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru * you may not use this file except in compliance with the License.
6d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru * You may obtain a copy of the License at
7d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru *
8d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru *      http://www.apache.org/licenses/LICENSE-2.0
9d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru *
10d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru * Unless required by applicable law or agreed to in writing, software
11d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru * distributed under the License is distributed on an "AS IS" BASIS,
12d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru * See the License for the specific language governing permissions and
14d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru * limitations under the License.
15d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru */
16d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru
17d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Querupackage com.android.volley.toolbox;
18d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru
19d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queruimport com.android.volley.DefaultRetryPolicy;
20d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queruimport com.android.volley.NetworkResponse;
21d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queruimport com.android.volley.ParseError;
22d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queruimport com.android.volley.Request;
23d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queruimport com.android.volley.Response;
24d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queruimport com.android.volley.VolleyLog;
25e48f4430bfd3030350aa5ba827b449c37e2fadc9Jean-Baptiste Queru
26d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queruimport android.graphics.Bitmap;
27d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queruimport android.graphics.Bitmap.Config;
28d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queruimport android.graphics.BitmapFactory;
29d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru
30d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru/**
31d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru * A canned request for getting an image at a given URL and calling
32d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru * back with a decoded Bitmap.
33d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru */
34d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Querupublic class ImageRequest extends Request<Bitmap> {
35d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru    /** Socket timeout in milliseconds for image requests */
36d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru    private static final int IMAGE_TIMEOUT_MS = 1000;
37d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru
38d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru    /** Default number of retries for image requests */
39d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru    private static final int IMAGE_MAX_RETRIES = 2;
40d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru
41d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru    /** Default backoff multiplier for image requests */
42d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru    private static final float IMAGE_BACKOFF_MULT = 2f;
43d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru
44d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru    private final Response.Listener<Bitmap> mListener;
45d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru    private final Config mDecodeConfig;
46d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru    private final int mMaxWidth;
47d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru    private final int mMaxHeight;
48d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru
49d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru    /** Decoding lock so that we don't decode more than one image at a time (to avoid OOM's) */
50d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru    private static final Object sDecodeLock = new Object();
51d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru
52d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru    /**
53d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru     * Creates a new image request, decoding to a maximum specified width and
54d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru     * height. If both width and height are zero, the image will be decoded to
55d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru     * its natural size. If one of the two is nonzero, that dimension will be
56d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru     * clamped and the other one will be set to preserve the image's aspect
57d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru     * ratio. If both width and height are nonzero, the image will be decoded to
58d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru     * be fit in the rectangle of dimensions width x height while keeping its
59d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru     * aspect ratio.
60d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru     *
61d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru     * @param url URL of the image
62d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru     * @param listener Listener to receive the decoded bitmap
63d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru     * @param maxWidth Maximum width to decode this bitmap to, or zero for none
64d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru     * @param maxHeight Maximum height to decode this bitmap to, or zero for
65d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru     *            none
66d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru     * @param decodeConfig Format to decode the bitmap to
67d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru     * @param errorListener Error listener, or null to ignore errors
68d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru     */
69d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru    public ImageRequest(String url, Response.Listener<Bitmap> listener, int maxWidth, int maxHeight,
70d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru            Config decodeConfig, Response.ErrorListener errorListener) {
71e48f4430bfd3030350aa5ba827b449c37e2fadc9Jean-Baptiste Queru        super(Method.GET, url, errorListener);
72d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru        setRetryPolicy(
73d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru                new DefaultRetryPolicy(IMAGE_TIMEOUT_MS, IMAGE_MAX_RETRIES, IMAGE_BACKOFF_MULT));
74d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru        mListener = listener;
75d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru        mDecodeConfig = decodeConfig;
76d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru        mMaxWidth = maxWidth;
77d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru        mMaxHeight = maxHeight;
78d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru    }
79d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru
80d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru    @Override
81d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru    public Priority getPriority() {
82d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru        return Priority.LOW;
83d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru    }
84d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru
85d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru    /**
86d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru     * Scales one side of a rectangle to fit aspect ratio.
87d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru     *
88d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru     * @param maxPrimary Maximum size of the primary dimension (i.e. width for
89d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru     *        max width), or zero to maintain aspect ratio with secondary
90d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru     *        dimension
91d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru     * @param maxSecondary Maximum size of the secondary dimension, or zero to
92d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru     *        maintain aspect ratio with primary dimension
93d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru     * @param actualPrimary Actual size of the primary dimension
94d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru     * @param actualSecondary Actual size of the secondary dimension
95d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru     */
96d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru    private static int getResizedDimension(int maxPrimary, int maxSecondary, int actualPrimary,
97d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru            int actualSecondary) {
98d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru        // If no dominant value at all, just return the actual.
99d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru        if (maxPrimary == 0 && maxSecondary == 0) {
100d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru            return actualPrimary;
101d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru        }
102d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru
103d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru        // If primary is unspecified, scale primary to match secondary's scaling ratio.
104d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru        if (maxPrimary == 0) {
105d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru            double ratio = (double) maxSecondary / (double) actualSecondary;
106d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru            return (int) (actualPrimary * ratio);
107d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru        }
108d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru
109d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru        if (maxSecondary == 0) {
110d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru            return maxPrimary;
111d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru        }
112d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru
113d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru        double ratio = (double) actualSecondary / (double) actualPrimary;
114d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru        int resized = maxPrimary;
115d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru        if (resized * ratio > maxSecondary) {
116d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru            resized = (int) (maxSecondary / ratio);
117d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru        }
118d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru        return resized;
119d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru    }
120d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru
121d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru    @Override
122d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru    protected Response<Bitmap> parseNetworkResponse(NetworkResponse response) {
123d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru        // Serialize all decode on a global lock to reduce concurrent heap usage.
124d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru        synchronized (sDecodeLock) {
125d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru            try {
126d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru                return doParse(response);
127d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru            } catch (OutOfMemoryError e) {
128d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru                VolleyLog.e("Caught OOM for %d byte image, url=%s", response.data.length, getUrl());
129d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru                return Response.error(new ParseError(e));
130d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru            }
131d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru        }
132d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru    }
133d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru
134d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru    /**
135d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru     * The real guts of parseNetworkResponse. Broken out for readability.
136d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru     */
137d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru    private Response<Bitmap> doParse(NetworkResponse response) {
138d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru        byte[] data = response.data;
139d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru        BitmapFactory.Options decodeOptions = new BitmapFactory.Options();
140d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru        Bitmap bitmap = null;
141d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru        if (mMaxWidth == 0 && mMaxHeight == 0) {
142d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru            decodeOptions.inPreferredConfig = mDecodeConfig;
143d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru            bitmap = BitmapFactory.decodeByteArray(data, 0, data.length, decodeOptions);
144d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru        } else {
145d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru            // If we have to resize this image, first get the natural bounds.
146d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru            decodeOptions.inJustDecodeBounds = true;
147d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru            BitmapFactory.decodeByteArray(data, 0, data.length, decodeOptions);
148d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru            int actualWidth = decodeOptions.outWidth;
149d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru            int actualHeight = decodeOptions.outHeight;
150d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru
151d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru            // Then compute the dimensions we would ideally like to decode to.
152d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru            int desiredWidth = getResizedDimension(mMaxWidth, mMaxHeight,
153d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru                    actualWidth, actualHeight);
154d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru            int desiredHeight = getResizedDimension(mMaxHeight, mMaxWidth,
155d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru                    actualHeight, actualWidth);
156d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru
157d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru            // Decode to the nearest power of two scaling factor.
158d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru            decodeOptions.inJustDecodeBounds = false;
159d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru            // TODO(ficus): Do we need this or is it okay since API 8 doesn't support it?
160d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru            // decodeOptions.inPreferQualityOverSpeed = PREFER_QUALITY_OVER_SPEED;
161d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru            decodeOptions.inSampleSize =
162d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru                findBestSampleSize(actualWidth, actualHeight, desiredWidth, desiredHeight);
163d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru            Bitmap tempBitmap =
164d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru                BitmapFactory.decodeByteArray(data, 0, data.length, decodeOptions);
165d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru
1666772bce3d3322ccbcf6481545ffe895d5d401b39Jean-Baptiste Queru            // If necessary, scale down to the maximal acceptable size.
1676772bce3d3322ccbcf6481545ffe895d5d401b39Jean-Baptiste Queru            if (tempBitmap != null && (tempBitmap.getWidth() > desiredWidth ||
1686772bce3d3322ccbcf6481545ffe895d5d401b39Jean-Baptiste Queru                    tempBitmap.getHeight() > desiredHeight)) {
169d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru                bitmap = Bitmap.createScaledBitmap(tempBitmap,
170d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru                        desiredWidth, desiredHeight, true);
171d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru                tempBitmap.recycle();
172d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru            } else {
173d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru                bitmap = tempBitmap;
174d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru            }
175d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru        }
176d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru
177d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru        if (bitmap == null) {
1787c8e29e4cc2219275042a228abea0657bb7633cdEmmanuel Rodriguez            return Response.error(new ParseError(response));
179d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru        } else {
180d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru            return Response.success(bitmap, HttpHeaderParser.parseCacheHeaders(response));
181d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru        }
182d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru    }
183d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru
184d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru    @Override
185d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru    protected void deliverResponse(Bitmap response) {
186d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru        mListener.onResponse(response);
187d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru    }
188d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru
189d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru    /**
190d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru     * Returns the largest power-of-two divisor for use in downscaling a bitmap
191d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru     * that will not result in the scaling past the desired dimensions.
192d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru     *
193d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru     * @param actualWidth Actual width of the bitmap
194d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru     * @param actualHeight Actual height of the bitmap
195d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru     * @param desiredWidth Desired width of the bitmap
196d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru     * @param desiredHeight Desired height of the bitmap
197d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru     */
198d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru    // Visible for testing.
199d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru    static int findBestSampleSize(
200d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru            int actualWidth, int actualHeight, int desiredWidth, int desiredHeight) {
201d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru        double wr = (double) actualWidth / desiredWidth;
202d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru        double hr = (double) actualHeight / desiredHeight;
203d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru        double ratio = Math.min(wr, hr);
204d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru        float n = 1.0f;
205d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru        while ((n * 2) <= ratio) {
206d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru            n *= 2;
207d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru        }
208d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru
209d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru        return (int) n;
210d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru    }
211d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru}
212