ImageUtils.java revision c631b5a4b1f19f84a70b772bc879fae7c92fd4a8
1bd8b47b147f29f196f37d66ccb561d40414ab5b6Adam Copp/*
2bd8b47b147f29f196f37d66ccb561d40414ab5b6Adam Copp * Copyright (C) 2011 Google Inc.
3bd8b47b147f29f196f37d66ccb561d40414ab5b6Adam Copp * Licensed to The Android Open Source Project.
4bd8b47b147f29f196f37d66ccb561d40414ab5b6Adam Copp *
5bd8b47b147f29f196f37d66ccb561d40414ab5b6Adam Copp * Licensed under the Apache License, Version 2.0 (the "License");
6bd8b47b147f29f196f37d66ccb561d40414ab5b6Adam Copp * you may not use this file except in compliance with the License.
7bd8b47b147f29f196f37d66ccb561d40414ab5b6Adam Copp * You may obtain a copy of the License at
8bd8b47b147f29f196f37d66ccb561d40414ab5b6Adam Copp *
9bd8b47b147f29f196f37d66ccb561d40414ab5b6Adam Copp *      http://www.apache.org/licenses/LICENSE-2.0
10bd8b47b147f29f196f37d66ccb561d40414ab5b6Adam Copp *
11bd8b47b147f29f196f37d66ccb561d40414ab5b6Adam Copp * Unless required by applicable law or agreed to in writing, software
12bd8b47b147f29f196f37d66ccb561d40414ab5b6Adam Copp * distributed under the License is distributed on an "AS IS" BASIS,
13bd8b47b147f29f196f37d66ccb561d40414ab5b6Adam Copp * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14bd8b47b147f29f196f37d66ccb561d40414ab5b6Adam Copp * See the License for the specific language governing permissions and
15bd8b47b147f29f196f37d66ccb561d40414ab5b6Adam Copp * limitations under the License.
16bd8b47b147f29f196f37d66ccb561d40414ab5b6Adam Copp */
17bd8b47b147f29f196f37d66ccb561d40414ab5b6Adam Copp
18bd8b47b147f29f196f37d66ccb561d40414ab5b6Adam Copppackage com.android.ex.photo.util;
19bd8b47b147f29f196f37d66ccb561d40414ab5b6Adam Copp
20bd8b47b147f29f196f37d66ccb561d40414ab5b6Adam Coppimport android.content.ContentResolver;
21bd8b47b147f29f196f37d66ccb561d40414ab5b6Adam Coppimport android.graphics.Bitmap;
22bd8b47b147f29f196f37d66ccb561d40414ab5b6Adam Coppimport android.graphics.BitmapFactory;
239323b13fc9bc79ce38ce7c851a2aa894ab988ed0Paul Westbrookimport android.graphics.Matrix;
24bd8b47b147f29f196f37d66ccb561d40414ab5b6Adam Coppimport android.graphics.Point;
25bd8b47b147f29f196f37d66ccb561d40414ab5b6Adam Coppimport android.graphics.Rect;
26bd8b47b147f29f196f37d66ccb561d40414ab5b6Adam Coppimport android.net.Uri;
27bd8b47b147f29f196f37d66ccb561d40414ab5b6Adam Coppimport android.os.Build;
28dccd6d91ca82225395af470595e4ffb8734dac1aMark Brophyimport android.util.Base64;
29bd8b47b147f29f196f37d66ccb561d40414ab5b6Adam Coppimport android.util.Log;
30bd8b47b147f29f196f37d66ccb561d40414ab5b6Adam Copp
31bd8b47b147f29f196f37d66ccb561d40414ab5b6Adam Coppimport com.android.ex.photo.PhotoViewActivity;
32112958e7a0493f2e692bbc40d113e98715caaab8Adam Coppimport com.android.ex.photo.loaders.PhotoBitmapLoader.BitmapResult;
33bd8b47b147f29f196f37d66ccb561d40414ab5b6Adam Copp
34237ded1a59b1fc148a3ffcf78d6f9d34af5c170cPaul Westbrookimport java.io.ByteArrayInputStream;
359323b13fc9bc79ce38ce7c851a2aa894ab988ed0Paul Westbrookimport java.io.ByteArrayOutputStream;
36bd8b47b147f29f196f37d66ccb561d40414ab5b6Adam Coppimport java.io.FileNotFoundException;
37bd8b47b147f29f196f37d66ccb561d40414ab5b6Adam Coppimport java.io.IOException;
38bd8b47b147f29f196f37d66ccb561d40414ab5b6Adam Coppimport java.io.InputStream;
39bd8b47b147f29f196f37d66ccb561d40414ab5b6Adam Coppimport java.net.MalformedURLException;
40bd8b47b147f29f196f37d66ccb561d40414ab5b6Adam Coppimport java.net.URL;
41dccd6d91ca82225395af470595e4ffb8734dac1aMark Brophyimport java.util.regex.Pattern;
42bd8b47b147f29f196f37d66ccb561d40414ab5b6Adam Copp
439323b13fc9bc79ce38ce7c851a2aa894ab988ed0Paul Westbrook
44bd8b47b147f29f196f37d66ccb561d40414ab5b6Adam Copp/**
45bd8b47b147f29f196f37d66ccb561d40414ab5b6Adam Copp * Image utilities
46bd8b47b147f29f196f37d66ccb561d40414ab5b6Adam Copp */
47bd8b47b147f29f196f37d66ccb561d40414ab5b6Adam Copppublic class ImageUtils {
48bd8b47b147f29f196f37d66ccb561d40414ab5b6Adam Copp    // Logging
49bd8b47b147f29f196f37d66ccb561d40414ab5b6Adam Copp    private static final String TAG = "ImageUtils";
50bd8b47b147f29f196f37d66ccb561d40414ab5b6Adam Copp
51bd8b47b147f29f196f37d66ccb561d40414ab5b6Adam Copp    /** Minimum class memory class to use full-res photos */
52bd8b47b147f29f196f37d66ccb561d40414ab5b6Adam Copp    private final static long MIN_NORMAL_CLASS = 32;
53bd8b47b147f29f196f37d66ccb561d40414ab5b6Adam Copp    /** Minimum class memory class to use small photos */
54bd8b47b147f29f196f37d66ccb561d40414ab5b6Adam Copp    private final static long MIN_SMALL_CLASS = 24;
55bd8b47b147f29f196f37d66ccb561d40414ab5b6Adam Copp
56dccd6d91ca82225395af470595e4ffb8734dac1aMark Brophy    private static final String BASE64_URI_PREFIX = "base64,";
57dccd6d91ca82225395af470595e4ffb8734dac1aMark Brophy    private static final Pattern BASE64_IMAGE_URI_PATTERN = Pattern.compile("^(?:.*;)?base64,.*");
58dccd6d91ca82225395af470595e4ffb8734dac1aMark Brophy
59bd8b47b147f29f196f37d66ccb561d40414ab5b6Adam Copp    public static enum ImageSize {
60bd8b47b147f29f196f37d66ccb561d40414ab5b6Adam Copp        EXTRA_SMALL,
61bd8b47b147f29f196f37d66ccb561d40414ab5b6Adam Copp        SMALL,
62bd8b47b147f29f196f37d66ccb561d40414ab5b6Adam Copp        NORMAL,
63bd8b47b147f29f196f37d66ccb561d40414ab5b6Adam Copp    }
64bd8b47b147f29f196f37d66ccb561d40414ab5b6Adam Copp
65bd8b47b147f29f196f37d66ccb561d40414ab5b6Adam Copp    public static final ImageSize sUseImageSize;
66bd8b47b147f29f196f37d66ccb561d40414ab5b6Adam Copp    static {
67bd8b47b147f29f196f37d66ccb561d40414ab5b6Adam Copp        // On HC and beyond, assume devices are more capable
68bd8b47b147f29f196f37d66ccb561d40414ab5b6Adam Copp        if (Build.VERSION.SDK_INT >= 11) {
69bd8b47b147f29f196f37d66ccb561d40414ab5b6Adam Copp            sUseImageSize = ImageSize.NORMAL;
70bd8b47b147f29f196f37d66ccb561d40414ab5b6Adam Copp        } else {
71bd8b47b147f29f196f37d66ccb561d40414ab5b6Adam Copp            if (PhotoViewActivity.sMemoryClass >= MIN_NORMAL_CLASS) {
72bd8b47b147f29f196f37d66ccb561d40414ab5b6Adam Copp                // We have plenty of memory; use full sized photos
73bd8b47b147f29f196f37d66ccb561d40414ab5b6Adam Copp                sUseImageSize = ImageSize.NORMAL;
74bd8b47b147f29f196f37d66ccb561d40414ab5b6Adam Copp            } else if (PhotoViewActivity.sMemoryClass >= MIN_SMALL_CLASS) {
75bd8b47b147f29f196f37d66ccb561d40414ab5b6Adam Copp                // We have slight less memory; use smaller sized photos
76bd8b47b147f29f196f37d66ccb561d40414ab5b6Adam Copp                sUseImageSize = ImageSize.SMALL;
77bd8b47b147f29f196f37d66ccb561d40414ab5b6Adam Copp            } else {
78bd8b47b147f29f196f37d66ccb561d40414ab5b6Adam Copp                // We have little memory; use very small sized photos
79bd8b47b147f29f196f37d66ccb561d40414ab5b6Adam Copp                sUseImageSize = ImageSize.EXTRA_SMALL;
80bd8b47b147f29f196f37d66ccb561d40414ab5b6Adam Copp            }
81bd8b47b147f29f196f37d66ccb561d40414ab5b6Adam Copp        }
82bd8b47b147f29f196f37d66ccb561d40414ab5b6Adam Copp    }
83bd8b47b147f29f196f37d66ccb561d40414ab5b6Adam Copp
84bd8b47b147f29f196f37d66ccb561d40414ab5b6Adam Copp    /**
85bd8b47b147f29f196f37d66ccb561d40414ab5b6Adam Copp     * @return true if the MimeType type is image
86bd8b47b147f29f196f37d66ccb561d40414ab5b6Adam Copp     */
87bd8b47b147f29f196f37d66ccb561d40414ab5b6Adam Copp    public static boolean isImageMimeType(String mimeType) {
88bd8b47b147f29f196f37d66ccb561d40414ab5b6Adam Copp        return mimeType != null && mimeType.startsWith("image/");
89bd8b47b147f29f196f37d66ccb561d40414ab5b6Adam Copp    }
90bd8b47b147f29f196f37d66ccb561d40414ab5b6Adam Copp
91bd8b47b147f29f196f37d66ccb561d40414ab5b6Adam Copp    /**
92bd8b47b147f29f196f37d66ccb561d40414ab5b6Adam Copp     * Create a bitmap from a local URI
93bd8b47b147f29f196f37d66ccb561d40414ab5b6Adam Copp     *
94bd8b47b147f29f196f37d66ccb561d40414ab5b6Adam Copp     * @param resolver The ContentResolver
95c631b5a4b1f19f84a70b772bc879fae7c92fd4a8Mark Wei     * @param uri      The local URI
96c631b5a4b1f19f84a70b772bc879fae7c92fd4a8Mark Wei     * @param maxSize  The maximum size (either width or height)
97f24e7099f9688518d705116c1b4279fc6279d3d6Mark Wei     * @return The new bitmap or null
98bd8b47b147f29f196f37d66ccb561d40414ab5b6Adam Copp     */
99c631b5a4b1f19f84a70b772bc879fae7c92fd4a8Mark Wei    public static BitmapResult createLocalBitmap(final ContentResolver resolver, final Uri uri,
100c631b5a4b1f19f84a70b772bc879fae7c92fd4a8Mark Wei            final int maxSize) {
101c631b5a4b1f19f84a70b772bc879fae7c92fd4a8Mark Wei        final BitmapResult result = new BitmapResult();
102c631b5a4b1f19f84a70b772bc879fae7c92fd4a8Mark Wei        final InputStreamFactory factory = createInputStreamFactory(resolver, uri);
103bd8b47b147f29f196f37d66ccb561d40414ab5b6Adam Copp        try {
104c631b5a4b1f19f84a70b772bc879fae7c92fd4a8Mark Wei            final Point bounds = getImageBounds(factory);
105c631b5a4b1f19f84a70b772bc879fae7c92fd4a8Mark Wei            if (bounds == null) {
106112958e7a0493f2e692bbc40d113e98715caaab8Adam Copp                result.status = BitmapResult.STATUS_EXCEPTION;
107112958e7a0493f2e692bbc40d113e98715caaab8Adam Copp                return result;
1087aabd796bbcb149db9ac183c1d0aec09fccd42a4Adam Copp            }
109bd8b47b147f29f196f37d66ccb561d40414ab5b6Adam Copp
110c631b5a4b1f19f84a70b772bc879fae7c92fd4a8Mark Wei            final BitmapFactory.Options opts = new BitmapFactory.Options();
111c631b5a4b1f19f84a70b772bc879fae7c92fd4a8Mark Wei            opts.inSampleSize = Math.max(bounds.x / maxSize, bounds.y / maxSize);
112c631b5a4b1f19f84a70b772bc879fae7c92fd4a8Mark Wei            result.bitmap = decodeStream(factory, null, opts);
113112958e7a0493f2e692bbc40d113e98715caaab8Adam Copp            result.status = BitmapResult.STATUS_SUCCESS;
114112958e7a0493f2e692bbc40d113e98715caaab8Adam Copp            return result;
115bd8b47b147f29f196f37d66ccb561d40414ab5b6Adam Copp
116bd8b47b147f29f196f37d66ccb561d40414ab5b6Adam Copp        } catch (FileNotFoundException exception) {
117bd8b47b147f29f196f37d66ccb561d40414ab5b6Adam Copp            // Do nothing - the photo will appear to be missing
118bd8b47b147f29f196f37d66ccb561d40414ab5b6Adam Copp        } catch (IOException exception) {
119112958e7a0493f2e692bbc40d113e98715caaab8Adam Copp            result.status = BitmapResult.STATUS_EXCEPTION;
1201f00d9d7c4beb007d491fc766f7a0c8fc4977a50The Android Open Source Project        } catch (IllegalArgumentException exception) {
1211f00d9d7c4beb007d491fc766f7a0c8fc4977a50The Android Open Source Project            // Do nothing - the photo will appear to be missing
1222de1aa6bc9a936ff3f2f654db770a091904533d7Mark Wei        } catch (SecurityException exception) {
123112958e7a0493f2e692bbc40d113e98715caaab8Adam Copp            result.status = BitmapResult.STATUS_EXCEPTION;
124bd8b47b147f29f196f37d66ccb561d40414ab5b6Adam Copp        }
125112958e7a0493f2e692bbc40d113e98715caaab8Adam Copp        return result;
126bd8b47b147f29f196f37d66ccb561d40414ab5b6Adam Copp    }
127bd8b47b147f29f196f37d66ccb561d40414ab5b6Adam Copp
128bd8b47b147f29f196f37d66ccb561d40414ab5b6Adam Copp    /**
129bd8b47b147f29f196f37d66ccb561d40414ab5b6Adam Copp     * Wrapper around {@link BitmapFactory#decodeStream(InputStream, Rect,
130bd8b47b147f29f196f37d66ccb561d40414ab5b6Adam Copp     * BitmapFactory.Options)} that returns {@code null} on {@link
131bd8b47b147f29f196f37d66ccb561d40414ab5b6Adam Copp     * OutOfMemoryError}.
132bd8b47b147f29f196f37d66ccb561d40414ab5b6Adam Copp     *
133c631b5a4b1f19f84a70b772bc879fae7c92fd4a8Mark Wei     * @param factory    Used to create input streams that holds the raw data to be decoded into a
134c631b5a4b1f19f84a70b772bc879fae7c92fd4a8Mark Wei     *                   bitmap.
135bd8b47b147f29f196f37d66ccb561d40414ab5b6Adam Copp     * @param outPadding If not null, return the padding rect for the bitmap if
136bd8b47b147f29f196f37d66ccb561d40414ab5b6Adam Copp     *                   it exists, otherwise set padding to [-1,-1,-1,-1]. If
137bd8b47b147f29f196f37d66ccb561d40414ab5b6Adam Copp     *                   no bitmap is returned (null) then padding is
138bd8b47b147f29f196f37d66ccb561d40414ab5b6Adam Copp     *                   unchanged.
139c631b5a4b1f19f84a70b772bc879fae7c92fd4a8Mark Wei     * @param opts       null-ok; Options that control downsampling and whether the
140c631b5a4b1f19f84a70b772bc879fae7c92fd4a8Mark Wei     *                   image should be completely decoded, or just is size returned.
141bd8b47b147f29f196f37d66ccb561d40414ab5b6Adam Copp     * @return The decoded bitmap, or null if the image data could not be
142c631b5a4b1f19f84a70b772bc879fae7c92fd4a8Mark Wei     * decoded, or, if opts is non-null, if opts requested only the
143c631b5a4b1f19f84a70b772bc879fae7c92fd4a8Mark Wei     * size be returned (in opts.outWidth and opts.outHeight)
144bd8b47b147f29f196f37d66ccb561d40414ab5b6Adam Copp     */
145c631b5a4b1f19f84a70b772bc879fae7c92fd4a8Mark Wei    public static Bitmap decodeStream(final InputStreamFactory factory, final Rect outPadding,
146c631b5a4b1f19f84a70b772bc879fae7c92fd4a8Mark Wei            final BitmapFactory.Options opts) throws FileNotFoundException {
147c631b5a4b1f19f84a70b772bc879fae7c92fd4a8Mark Wei        InputStream is = null;
148bd8b47b147f29f196f37d66ccb561d40414ab5b6Adam Copp        try {
1499323b13fc9bc79ce38ce7c851a2aa894ab988ed0Paul Westbrook            // Determine the orientation for this image
150c631b5a4b1f19f84a70b772bc879fae7c92fd4a8Mark Wei            is = factory.createInputStream();
151c631b5a4b1f19f84a70b772bc879fae7c92fd4a8Mark Wei            final int orientation = Exif.getOrientation(is, -1);
152c631b5a4b1f19f84a70b772bc879fae7c92fd4a8Mark Wei            is.close();
153237ded1a59b1fc148a3ffcf78d6f9d34af5c170cPaul Westbrook
154c631b5a4b1f19f84a70b772bc879fae7c92fd4a8Mark Wei            // Decode the bitmap
155c631b5a4b1f19f84a70b772bc879fae7c92fd4a8Mark Wei            is = factory.createInputStream();
156c631b5a4b1f19f84a70b772bc879fae7c92fd4a8Mark Wei            final Bitmap originalBitmap = BitmapFactory.decodeStream(is, outPadding, opts);
1579323b13fc9bc79ce38ce7c851a2aa894ab988ed0Paul Westbrook
158c631b5a4b1f19f84a70b772bc879fae7c92fd4a8Mark Wei            if (is != null && originalBitmap == null && !opts.inJustDecodeBounds) {
15906d8b5e82b9b75588051a492152fda0b63f70484Mark Wei                Log.w(TAG, "ImageUtils#decodeStream(InputStream, Rect, Options): "
16006d8b5e82b9b75588051a492152fda0b63f70484Mark Wei                        + "Image bytes cannot be decoded into a Bitmap");
16106d8b5e82b9b75588051a492152fda0b63f70484Mark Wei                throw new UnsupportedOperationException(
16206d8b5e82b9b75588051a492152fda0b63f70484Mark Wei                        "Image bytes cannot be decoded into a Bitmap.");
16306d8b5e82b9b75588051a492152fda0b63f70484Mark Wei            }
164c631b5a4b1f19f84a70b772bc879fae7c92fd4a8Mark Wei
165c631b5a4b1f19f84a70b772bc879fae7c92fd4a8Mark Wei            // Rotate the Bitmap based on the orientation
1669323b13fc9bc79ce38ce7c851a2aa894ab988ed0Paul Westbrook            if (originalBitmap != null && orientation != 0) {
1679323b13fc9bc79ce38ce7c851a2aa894ab988ed0Paul Westbrook                final Matrix matrix = new Matrix();
1689323b13fc9bc79ce38ce7c851a2aa894ab988ed0Paul Westbrook                matrix.postRotate(orientation);
1699323b13fc9bc79ce38ce7c851a2aa894ab988ed0Paul Westbrook                return Bitmap.createBitmap(originalBitmap, 0, 0, originalBitmap.getWidth(),
1709323b13fc9bc79ce38ce7c851a2aa894ab988ed0Paul Westbrook                        originalBitmap.getHeight(), matrix, true);
1719323b13fc9bc79ce38ce7c851a2aa894ab988ed0Paul Westbrook            }
1729323b13fc9bc79ce38ce7c851a2aa894ab988ed0Paul Westbrook            return originalBitmap;
173bd8b47b147f29f196f37d66ccb561d40414ab5b6Adam Copp        } catch (OutOfMemoryError oome) {
174bd8b47b147f29f196f37d66ccb561d40414ab5b6Adam Copp            Log.e(TAG, "ImageUtils#decodeStream(InputStream, Rect, Options) threw an OOME", oome);
175bd8b47b147f29f196f37d66ccb561d40414ab5b6Adam Copp            return null;
1769323b13fc9bc79ce38ce7c851a2aa894ab988ed0Paul Westbrook        } catch (IOException ioe) {
1779323b13fc9bc79ce38ce7c851a2aa894ab988ed0Paul Westbrook            Log.e(TAG, "ImageUtils#decodeStream(InputStream, Rect, Options) threw an IOE", ioe);
1789323b13fc9bc79ce38ce7c851a2aa894ab988ed0Paul Westbrook            return null;
1799323b13fc9bc79ce38ce7c851a2aa894ab988ed0Paul Westbrook        } finally {
180c631b5a4b1f19f84a70b772bc879fae7c92fd4a8Mark Wei            if (is != null) {
1819323b13fc9bc79ce38ce7c851a2aa894ab988ed0Paul Westbrook                try {
182c631b5a4b1f19f84a70b772bc879fae7c92fd4a8Mark Wei                    is.close();
183237ded1a59b1fc148a3ffcf78d6f9d34af5c170cPaul Westbrook                } catch (IOException e) {
184237ded1a59b1fc148a3ffcf78d6f9d34af5c170cPaul Westbrook                    // Do nothing
185237ded1a59b1fc148a3ffcf78d6f9d34af5c170cPaul Westbrook                }
186237ded1a59b1fc148a3ffcf78d6f9d34af5c170cPaul Westbrook            }
187bd8b47b147f29f196f37d66ccb561d40414ab5b6Adam Copp        }
188bd8b47b147f29f196f37d66ccb561d40414ab5b6Adam Copp    }
189bd8b47b147f29f196f37d66ccb561d40414ab5b6Adam Copp
190bd8b47b147f29f196f37d66ccb561d40414ab5b6Adam Copp    /**
191bd8b47b147f29f196f37d66ccb561d40414ab5b6Adam Copp     * Gets the image bounds
192bd8b47b147f29f196f37d66ccb561d40414ab5b6Adam Copp     *
193c631b5a4b1f19f84a70b772bc879fae7c92fd4a8Mark Wei     * @param factory Used to create the InputStream.
194bd8b47b147f29f196f37d66ccb561d40414ab5b6Adam Copp     *
195bd8b47b147f29f196f37d66ccb561d40414ab5b6Adam Copp     * @return The image bounds
196bd8b47b147f29f196f37d66ccb561d40414ab5b6Adam Copp     */
197c631b5a4b1f19f84a70b772bc879fae7c92fd4a8Mark Wei    private static Point getImageBounds(final InputStreamFactory factory)
198bd8b47b147f29f196f37d66ccb561d40414ab5b6Adam Copp            throws IOException {
199bd8b47b147f29f196f37d66ccb561d40414ab5b6Adam Copp        final BitmapFactory.Options opts = new BitmapFactory.Options();
200c631b5a4b1f19f84a70b772bc879fae7c92fd4a8Mark Wei        opts.inJustDecodeBounds = true;
201c631b5a4b1f19f84a70b772bc879fae7c92fd4a8Mark Wei        decodeStream(factory, null, opts);
202bd8b47b147f29f196f37d66ccb561d40414ab5b6Adam Copp
203c631b5a4b1f19f84a70b772bc879fae7c92fd4a8Mark Wei        return new Point(opts.outWidth, opts.outHeight);
204c631b5a4b1f19f84a70b772bc879fae7c92fd4a8Mark Wei    }
205c631b5a4b1f19f84a70b772bc879fae7c92fd4a8Mark Wei
206c631b5a4b1f19f84a70b772bc879fae7c92fd4a8Mark Wei    private static InputStreamFactory createInputStreamFactory(final ContentResolver resolver,
207c631b5a4b1f19f84a70b772bc879fae7c92fd4a8Mark Wei            final Uri uri) {
208c631b5a4b1f19f84a70b772bc879fae7c92fd4a8Mark Wei        final String scheme = uri.getScheme();
209c631b5a4b1f19f84a70b772bc879fae7c92fd4a8Mark Wei        if ("http".equals(scheme) || "https".equals(scheme)) {
210c631b5a4b1f19f84a70b772bc879fae7c92fd4a8Mark Wei            return new HttpInputStreamFactory(resolver, uri);
211c631b5a4b1f19f84a70b772bc879fae7c92fd4a8Mark Wei        } else if ("data".equals(scheme)) {
212c631b5a4b1f19f84a70b772bc879fae7c92fd4a8Mark Wei            return new DataInputStreamFactory(resolver, uri);
213c631b5a4b1f19f84a70b772bc879fae7c92fd4a8Mark Wei        }
214c631b5a4b1f19f84a70b772bc879fae7c92fd4a8Mark Wei        return new BaseInputStreamFactory(resolver, uri);
215c631b5a4b1f19f84a70b772bc879fae7c92fd4a8Mark Wei    }
216c631b5a4b1f19f84a70b772bc879fae7c92fd4a8Mark Wei
217c631b5a4b1f19f84a70b772bc879fae7c92fd4a8Mark Wei    /**
218c631b5a4b1f19f84a70b772bc879fae7c92fd4a8Mark Wei     * Utility class for when an InputStream needs to be read multiple times. For example, one pass
219c631b5a4b1f19f84a70b772bc879fae7c92fd4a8Mark Wei     * may load EXIF orientation, and the second pass may do the actual Bitmap decode.
220c631b5a4b1f19f84a70b772bc879fae7c92fd4a8Mark Wei     */
221c631b5a4b1f19f84a70b772bc879fae7c92fd4a8Mark Wei    public interface InputStreamFactory {
222c631b5a4b1f19f84a70b772bc879fae7c92fd4a8Mark Wei
223c631b5a4b1f19f84a70b772bc879fae7c92fd4a8Mark Wei        /**
224c631b5a4b1f19f84a70b772bc879fae7c92fd4a8Mark Wei         * Create a new InputStream. The caller of this method must be able to read the input
225c631b5a4b1f19f84a70b772bc879fae7c92fd4a8Mark Wei         * stream starting from the beginning.
226c631b5a4b1f19f84a70b772bc879fae7c92fd4a8Mark Wei         * @return
227c631b5a4b1f19f84a70b772bc879fae7c92fd4a8Mark Wei         */
228c631b5a4b1f19f84a70b772bc879fae7c92fd4a8Mark Wei        InputStream createInputStream() throws FileNotFoundException;
229c631b5a4b1f19f84a70b772bc879fae7c92fd4a8Mark Wei    }
230c631b5a4b1f19f84a70b772bc879fae7c92fd4a8Mark Wei
231c631b5a4b1f19f84a70b772bc879fae7c92fd4a8Mark Wei    private static class BaseInputStreamFactory implements InputStreamFactory {
232c631b5a4b1f19f84a70b772bc879fae7c92fd4a8Mark Wei        protected final ContentResolver mResolver;
233c631b5a4b1f19f84a70b772bc879fae7c92fd4a8Mark Wei        protected final Uri mUri;
234c631b5a4b1f19f84a70b772bc879fae7c92fd4a8Mark Wei
235c631b5a4b1f19f84a70b772bc879fae7c92fd4a8Mark Wei        public BaseInputStreamFactory(final ContentResolver resolver, final Uri uri) {
236c631b5a4b1f19f84a70b772bc879fae7c92fd4a8Mark Wei            mResolver = resolver;
237c631b5a4b1f19f84a70b772bc879fae7c92fd4a8Mark Wei            mUri = uri;
238c631b5a4b1f19f84a70b772bc879fae7c92fd4a8Mark Wei        }
239c631b5a4b1f19f84a70b772bc879fae7c92fd4a8Mark Wei
240c631b5a4b1f19f84a70b772bc879fae7c92fd4a8Mark Wei        @Override
241c631b5a4b1f19f84a70b772bc879fae7c92fd4a8Mark Wei        public InputStream createInputStream() throws FileNotFoundException {
242c631b5a4b1f19f84a70b772bc879fae7c92fd4a8Mark Wei            return mResolver.openInputStream(mUri);
243c631b5a4b1f19f84a70b772bc879fae7c92fd4a8Mark Wei        }
244c631b5a4b1f19f84a70b772bc879fae7c92fd4a8Mark Wei    }
245c631b5a4b1f19f84a70b772bc879fae7c92fd4a8Mark Wei
246c631b5a4b1f19f84a70b772bc879fae7c92fd4a8Mark Wei    private static class DataInputStreamFactory extends BaseInputStreamFactory {
247c631b5a4b1f19f84a70b772bc879fae7c92fd4a8Mark Wei        private byte[] mData;
248c631b5a4b1f19f84a70b772bc879fae7c92fd4a8Mark Wei
249c631b5a4b1f19f84a70b772bc879fae7c92fd4a8Mark Wei        public DataInputStreamFactory(final ContentResolver resolver, final Uri uri) {
250c631b5a4b1f19f84a70b772bc879fae7c92fd4a8Mark Wei            super(resolver, uri);
251c631b5a4b1f19f84a70b772bc879fae7c92fd4a8Mark Wei        }
252c631b5a4b1f19f84a70b772bc879fae7c92fd4a8Mark Wei
253c631b5a4b1f19f84a70b772bc879fae7c92fd4a8Mark Wei        @Override
254c631b5a4b1f19f84a70b772bc879fae7c92fd4a8Mark Wei        public InputStream createInputStream() throws FileNotFoundException {
255c631b5a4b1f19f84a70b772bc879fae7c92fd4a8Mark Wei            if (mData == null) {
256c631b5a4b1f19f84a70b772bc879fae7c92fd4a8Mark Wei                mData = parseDataUri(mUri);
257c631b5a4b1f19f84a70b772bc879fae7c92fd4a8Mark Wei                if (mData == null) {
258c631b5a4b1f19f84a70b772bc879fae7c92fd4a8Mark Wei                    return super.createInputStream();
259bd8b47b147f29f196f37d66ccb561d40414ab5b6Adam Copp                }
260bd8b47b147f29f196f37d66ccb561d40414ab5b6Adam Copp            }
261c631b5a4b1f19f84a70b772bc879fae7c92fd4a8Mark Wei            return new ByteArrayInputStream(mData);
262bd8b47b147f29f196f37d66ccb561d40414ab5b6Adam Copp        }
263bd8b47b147f29f196f37d66ccb561d40414ab5b6Adam Copp
264c631b5a4b1f19f84a70b772bc879fae7c92fd4a8Mark Wei        private byte[] parseDataUri(final Uri uri) {
265c631b5a4b1f19f84a70b772bc879fae7c92fd4a8Mark Wei            final String ssp = uri.getSchemeSpecificPart();
266bd8b47b147f29f196f37d66ccb561d40414ab5b6Adam Copp            try {
267c631b5a4b1f19f84a70b772bc879fae7c92fd4a8Mark Wei                if (ssp.startsWith(BASE64_URI_PREFIX)) {
268c631b5a4b1f19f84a70b772bc879fae7c92fd4a8Mark Wei                    final String base64 = ssp.substring(BASE64_URI_PREFIX.length());
269c631b5a4b1f19f84a70b772bc879fae7c92fd4a8Mark Wei                    return Base64.decode(base64, Base64.URL_SAFE);
270c631b5a4b1f19f84a70b772bc879fae7c92fd4a8Mark Wei                } else if (BASE64_IMAGE_URI_PATTERN.matcher(ssp).matches()){
271c631b5a4b1f19f84a70b772bc879fae7c92fd4a8Mark Wei                    final String base64 = ssp.substring(
272c631b5a4b1f19f84a70b772bc879fae7c92fd4a8Mark Wei                            ssp.indexOf(BASE64_URI_PREFIX) + BASE64_URI_PREFIX.length());
273c631b5a4b1f19f84a70b772bc879fae7c92fd4a8Mark Wei                    return Base64.decode(base64, Base64.DEFAULT);
274c631b5a4b1f19f84a70b772bc879fae7c92fd4a8Mark Wei                } else {
275c631b5a4b1f19f84a70b772bc879fae7c92fd4a8Mark Wei                    return null;
276c631b5a4b1f19f84a70b772bc879fae7c92fd4a8Mark Wei                }
277c631b5a4b1f19f84a70b772bc879fae7c92fd4a8Mark Wei            } catch (IllegalArgumentException ex) {
278c631b5a4b1f19f84a70b772bc879fae7c92fd4a8Mark Wei                Log.e(TAG, "Mailformed data URI: " + ex);
279bd8b47b147f29f196f37d66ccb561d40414ab5b6Adam Copp                return null;
280bd8b47b147f29f196f37d66ccb561d40414ab5b6Adam Copp            }
281bd8b47b147f29f196f37d66ccb561d40414ab5b6Adam Copp        }
282bd8b47b147f29f196f37d66ccb561d40414ab5b6Adam Copp    }
283dccd6d91ca82225395af470595e4ffb8734dac1aMark Brophy
284c631b5a4b1f19f84a70b772bc879fae7c92fd4a8Mark Wei    private static class HttpInputStreamFactory extends BaseInputStreamFactory {
285c631b5a4b1f19f84a70b772bc879fae7c92fd4a8Mark Wei        private byte[] mData;
286c631b5a4b1f19f84a70b772bc879fae7c92fd4a8Mark Wei
287c631b5a4b1f19f84a70b772bc879fae7c92fd4a8Mark Wei        public HttpInputStreamFactory(final ContentResolver resolver, final Uri uri) {
288c631b5a4b1f19f84a70b772bc879fae7c92fd4a8Mark Wei            super(resolver, uri);
289c631b5a4b1f19f84a70b772bc879fae7c92fd4a8Mark Wei        }
290c631b5a4b1f19f84a70b772bc879fae7c92fd4a8Mark Wei
291c631b5a4b1f19f84a70b772bc879fae7c92fd4a8Mark Wei        @Override
292c631b5a4b1f19f84a70b772bc879fae7c92fd4a8Mark Wei        public InputStream createInputStream() throws FileNotFoundException {
293c631b5a4b1f19f84a70b772bc879fae7c92fd4a8Mark Wei            if (mData == null) {
294c631b5a4b1f19f84a70b772bc879fae7c92fd4a8Mark Wei                mData = downloadBytes();
295c631b5a4b1f19f84a70b772bc879fae7c92fd4a8Mark Wei                if (mData == null) {
296c631b5a4b1f19f84a70b772bc879fae7c92fd4a8Mark Wei                    return super.createInputStream();
297c631b5a4b1f19f84a70b772bc879fae7c92fd4a8Mark Wei                }
298c631b5a4b1f19f84a70b772bc879fae7c92fd4a8Mark Wei            }
299c631b5a4b1f19f84a70b772bc879fae7c92fd4a8Mark Wei            return new ByteArrayInputStream(mData);
300c631b5a4b1f19f84a70b772bc879fae7c92fd4a8Mark Wei        }
301c631b5a4b1f19f84a70b772bc879fae7c92fd4a8Mark Wei
302c631b5a4b1f19f84a70b772bc879fae7c92fd4a8Mark Wei        private byte[] downloadBytes() throws FileNotFoundException {
303c631b5a4b1f19f84a70b772bc879fae7c92fd4a8Mark Wei            InputStream is = null;
304c631b5a4b1f19f84a70b772bc879fae7c92fd4a8Mark Wei            ByteArrayOutputStream out = null;
305c631b5a4b1f19f84a70b772bc879fae7c92fd4a8Mark Wei            try {
306c631b5a4b1f19f84a70b772bc879fae7c92fd4a8Mark Wei                try {
307c631b5a4b1f19f84a70b772bc879fae7c92fd4a8Mark Wei                    is = new URL(mUri.toString()).openStream();
308c631b5a4b1f19f84a70b772bc879fae7c92fd4a8Mark Wei                } catch (MalformedURLException e) {
309c631b5a4b1f19f84a70b772bc879fae7c92fd4a8Mark Wei                    return null;
310c631b5a4b1f19f84a70b772bc879fae7c92fd4a8Mark Wei                }
311c631b5a4b1f19f84a70b772bc879fae7c92fd4a8Mark Wei                out = new ByteArrayOutputStream();
312c631b5a4b1f19f84a70b772bc879fae7c92fd4a8Mark Wei                final byte[] buffer = new byte[4096];
313c631b5a4b1f19f84a70b772bc879fae7c92fd4a8Mark Wei                int n = is.read(buffer);
314c631b5a4b1f19f84a70b772bc879fae7c92fd4a8Mark Wei                while (n >= 0) {
315c631b5a4b1f19f84a70b772bc879fae7c92fd4a8Mark Wei                    out.write(buffer, 0, n);
316c631b5a4b1f19f84a70b772bc879fae7c92fd4a8Mark Wei                    n = is.read(buffer);
317c631b5a4b1f19f84a70b772bc879fae7c92fd4a8Mark Wei                }
318c631b5a4b1f19f84a70b772bc879fae7c92fd4a8Mark Wei
319c631b5a4b1f19f84a70b772bc879fae7c92fd4a8Mark Wei                return out.toByteArray();
320c631b5a4b1f19f84a70b772bc879fae7c92fd4a8Mark Wei            } catch (IOException ignored) {
321c631b5a4b1f19f84a70b772bc879fae7c92fd4a8Mark Wei            } finally {
322c631b5a4b1f19f84a70b772bc879fae7c92fd4a8Mark Wei                if (is != null) {
323c631b5a4b1f19f84a70b772bc879fae7c92fd4a8Mark Wei                    try {
324c631b5a4b1f19f84a70b772bc879fae7c92fd4a8Mark Wei                        is.close();
325c631b5a4b1f19f84a70b772bc879fae7c92fd4a8Mark Wei                    } catch (IOException ignored) {
326c631b5a4b1f19f84a70b772bc879fae7c92fd4a8Mark Wei                    }
327c631b5a4b1f19f84a70b772bc879fae7c92fd4a8Mark Wei                }
328c631b5a4b1f19f84a70b772bc879fae7c92fd4a8Mark Wei                if (out != null) {
329c631b5a4b1f19f84a70b772bc879fae7c92fd4a8Mark Wei                    try {
330c631b5a4b1f19f84a70b772bc879fae7c92fd4a8Mark Wei                        out.close();
331c631b5a4b1f19f84a70b772bc879fae7c92fd4a8Mark Wei                    } catch (IOException ignored) {
332c631b5a4b1f19f84a70b772bc879fae7c92fd4a8Mark Wei                    }
333c631b5a4b1f19f84a70b772bc879fae7c92fd4a8Mark Wei                }
334dccd6d91ca82225395af470595e4ffb8734dac1aMark Brophy            }
335dccd6d91ca82225395af470595e4ffb8734dac1aMark Brophy            return null;
336dccd6d91ca82225395af470595e4ffb8734dac1aMark Brophy        }
337dccd6d91ca82225395af470595e4ffb8734dac1aMark Brophy    }
338bd8b47b147f29f196f37d66ccb561d40414ab5b6Adam Copp}
339