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
31bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwoodimport com.android.ex.photo.PhotoViewController;
32ca8fdcd23fcc53e0429abf8d4ef80e8d6d8fe6faMartin Hibdonimport com.android.ex.photo.loaders.PhotoBitmapLoaderInterface.BitmapResult;
33bd8b47b147f29f196f37d66ccb561d40414ab5b6Adam Copp
34237ded1a59b1fc148a3ffcf78d6f9d34af5c170cPaul Westbrookimport java.io.ByteArrayInputStream;
35bd8b47b147f29f196f37d66ccb561d40414ab5b6Adam Coppimport java.io.FileNotFoundException;
36bd8b47b147f29f196f37d66ccb561d40414ab5b6Adam Coppimport java.io.IOException;
37bd8b47b147f29f196f37d66ccb561d40414ab5b6Adam Coppimport java.io.InputStream;
38dccd6d91ca82225395af470595e4ffb8734dac1aMark Brophyimport java.util.regex.Pattern;
39bd8b47b147f29f196f37d66ccb561d40414ab5b6Adam Copp
409323b13fc9bc79ce38ce7c851a2aa894ab988ed0Paul Westbrook
41bd8b47b147f29f196f37d66ccb561d40414ab5b6Adam Copp/**
42bd8b47b147f29f196f37d66ccb561d40414ab5b6Adam Copp * Image utilities
43bd8b47b147f29f196f37d66ccb561d40414ab5b6Adam Copp */
44bd8b47b147f29f196f37d66ccb561d40414ab5b6Adam Copppublic class ImageUtils {
45bd8b47b147f29f196f37d66ccb561d40414ab5b6Adam Copp    // Logging
46bd8b47b147f29f196f37d66ccb561d40414ab5b6Adam Copp    private static final String TAG = "ImageUtils";
47bd8b47b147f29f196f37d66ccb561d40414ab5b6Adam Copp
48bd8b47b147f29f196f37d66ccb561d40414ab5b6Adam Copp    /** Minimum class memory class to use full-res photos */
49bd8b47b147f29f196f37d66ccb561d40414ab5b6Adam Copp    private final static long MIN_NORMAL_CLASS = 32;
50bd8b47b147f29f196f37d66ccb561d40414ab5b6Adam Copp    /** Minimum class memory class to use small photos */
51bd8b47b147f29f196f37d66ccb561d40414ab5b6Adam Copp    private final static long MIN_SMALL_CLASS = 24;
52bd8b47b147f29f196f37d66ccb561d40414ab5b6Adam Copp
53dccd6d91ca82225395af470595e4ffb8734dac1aMark Brophy    private static final String BASE64_URI_PREFIX = "base64,";
54dccd6d91ca82225395af470595e4ffb8734dac1aMark Brophy    private static final Pattern BASE64_IMAGE_URI_PATTERN = Pattern.compile("^(?:.*;)?base64,.*");
55dccd6d91ca82225395af470595e4ffb8734dac1aMark Brophy
56bd8b47b147f29f196f37d66ccb561d40414ab5b6Adam Copp    public static enum ImageSize {
57bd8b47b147f29f196f37d66ccb561d40414ab5b6Adam Copp        EXTRA_SMALL,
58bd8b47b147f29f196f37d66ccb561d40414ab5b6Adam Copp        SMALL,
59bd8b47b147f29f196f37d66ccb561d40414ab5b6Adam Copp        NORMAL,
60bd8b47b147f29f196f37d66ccb561d40414ab5b6Adam Copp    }
61bd8b47b147f29f196f37d66ccb561d40414ab5b6Adam Copp
62bd8b47b147f29f196f37d66ccb561d40414ab5b6Adam Copp    public static final ImageSize sUseImageSize;
63bd8b47b147f29f196f37d66ccb561d40414ab5b6Adam Copp    static {
64bd8b47b147f29f196f37d66ccb561d40414ab5b6Adam Copp        // On HC and beyond, assume devices are more capable
65bd8b47b147f29f196f37d66ccb561d40414ab5b6Adam Copp        if (Build.VERSION.SDK_INT >= 11) {
66bd8b47b147f29f196f37d66ccb561d40414ab5b6Adam Copp            sUseImageSize = ImageSize.NORMAL;
67bd8b47b147f29f196f37d66ccb561d40414ab5b6Adam Copp        } else {
68bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood            if (PhotoViewController.sMemoryClass >= MIN_NORMAL_CLASS) {
69bd8b47b147f29f196f37d66ccb561d40414ab5b6Adam Copp                // We have plenty of memory; use full sized photos
70bd8b47b147f29f196f37d66ccb561d40414ab5b6Adam Copp                sUseImageSize = ImageSize.NORMAL;
71bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood            } else if (PhotoViewController.sMemoryClass >= MIN_SMALL_CLASS) {
72bd8b47b147f29f196f37d66ccb561d40414ab5b6Adam Copp                // We have slight less memory; use smaller sized photos
73bd8b47b147f29f196f37d66ccb561d40414ab5b6Adam Copp                sUseImageSize = ImageSize.SMALL;
74bd8b47b147f29f196f37d66ccb561d40414ab5b6Adam Copp            } else {
75bd8b47b147f29f196f37d66ccb561d40414ab5b6Adam Copp                // We have little memory; use very small sized photos
76bd8b47b147f29f196f37d66ccb561d40414ab5b6Adam Copp                sUseImageSize = ImageSize.EXTRA_SMALL;
77bd8b47b147f29f196f37d66ccb561d40414ab5b6Adam Copp            }
78bd8b47b147f29f196f37d66ccb561d40414ab5b6Adam Copp        }
79bd8b47b147f29f196f37d66ccb561d40414ab5b6Adam Copp    }
80bd8b47b147f29f196f37d66ccb561d40414ab5b6Adam Copp
81bd8b47b147f29f196f37d66ccb561d40414ab5b6Adam Copp    /**
82bd8b47b147f29f196f37d66ccb561d40414ab5b6Adam Copp     * @return true if the MimeType type is image
83bd8b47b147f29f196f37d66ccb561d40414ab5b6Adam Copp     */
84bd8b47b147f29f196f37d66ccb561d40414ab5b6Adam Copp    public static boolean isImageMimeType(String mimeType) {
85bd8b47b147f29f196f37d66ccb561d40414ab5b6Adam Copp        return mimeType != null && mimeType.startsWith("image/");
86bd8b47b147f29f196f37d66ccb561d40414ab5b6Adam Copp    }
87bd8b47b147f29f196f37d66ccb561d40414ab5b6Adam Copp
88bd8b47b147f29f196f37d66ccb561d40414ab5b6Adam Copp    /**
89bd8b47b147f29f196f37d66ccb561d40414ab5b6Adam Copp     * Create a bitmap from a local URI
90bd8b47b147f29f196f37d66ccb561d40414ab5b6Adam Copp     *
91bd8b47b147f29f196f37d66ccb561d40414ab5b6Adam Copp     * @param resolver The ContentResolver
92c631b5a4b1f19f84a70b772bc879fae7c92fd4a8Mark Wei     * @param uri      The local URI
93c631b5a4b1f19f84a70b772bc879fae7c92fd4a8Mark Wei     * @param maxSize  The maximum size (either width or height)
94f24e7099f9688518d705116c1b4279fc6279d3d6Mark Wei     * @return The new bitmap or null
95bd8b47b147f29f196f37d66ccb561d40414ab5b6Adam Copp     */
96c631b5a4b1f19f84a70b772bc879fae7c92fd4a8Mark Wei    public static BitmapResult createLocalBitmap(final ContentResolver resolver, final Uri uri,
97c631b5a4b1f19f84a70b772bc879fae7c92fd4a8Mark Wei            final int maxSize) {
98c631b5a4b1f19f84a70b772bc879fae7c92fd4a8Mark Wei        final BitmapResult result = new BitmapResult();
99c631b5a4b1f19f84a70b772bc879fae7c92fd4a8Mark Wei        final InputStreamFactory factory = createInputStreamFactory(resolver, uri);
100bd8b47b147f29f196f37d66ccb561d40414ab5b6Adam Copp        try {
101c631b5a4b1f19f84a70b772bc879fae7c92fd4a8Mark Wei            final Point bounds = getImageBounds(factory);
102c631b5a4b1f19f84a70b772bc879fae7c92fd4a8Mark Wei            if (bounds == null) {
103112958e7a0493f2e692bbc40d113e98715caaab8Adam Copp                result.status = BitmapResult.STATUS_EXCEPTION;
104112958e7a0493f2e692bbc40d113e98715caaab8Adam Copp                return result;
1057aabd796bbcb149db9ac183c1d0aec09fccd42a4Adam Copp            }
106bd8b47b147f29f196f37d66ccb561d40414ab5b6Adam Copp
107c631b5a4b1f19f84a70b772bc879fae7c92fd4a8Mark Wei            final BitmapFactory.Options opts = new BitmapFactory.Options();
108c631b5a4b1f19f84a70b772bc879fae7c92fd4a8Mark Wei            opts.inSampleSize = Math.max(bounds.x / maxSize, bounds.y / maxSize);
109c631b5a4b1f19f84a70b772bc879fae7c92fd4a8Mark Wei            result.bitmap = decodeStream(factory, null, opts);
110112958e7a0493f2e692bbc40d113e98715caaab8Adam Copp            result.status = BitmapResult.STATUS_SUCCESS;
111112958e7a0493f2e692bbc40d113e98715caaab8Adam Copp            return result;
112bd8b47b147f29f196f37d66ccb561d40414ab5b6Adam Copp
113bd8b47b147f29f196f37d66ccb561d40414ab5b6Adam Copp        } catch (FileNotFoundException exception) {
114bd8b47b147f29f196f37d66ccb561d40414ab5b6Adam Copp            // Do nothing - the photo will appear to be missing
115bd8b47b147f29f196f37d66ccb561d40414ab5b6Adam Copp        } catch (IOException exception) {
116112958e7a0493f2e692bbc40d113e98715caaab8Adam Copp            result.status = BitmapResult.STATUS_EXCEPTION;
1171f00d9d7c4beb007d491fc766f7a0c8fc4977a50The Android Open Source Project        } catch (IllegalArgumentException exception) {
1181f00d9d7c4beb007d491fc766f7a0c8fc4977a50The Android Open Source Project            // Do nothing - the photo will appear to be missing
1192de1aa6bc9a936ff3f2f654db770a091904533d7Mark Wei        } catch (SecurityException exception) {
120112958e7a0493f2e692bbc40d113e98715caaab8Adam Copp            result.status = BitmapResult.STATUS_EXCEPTION;
121bd8b47b147f29f196f37d66ccb561d40414ab5b6Adam Copp        }
122112958e7a0493f2e692bbc40d113e98715caaab8Adam Copp        return result;
123bd8b47b147f29f196f37d66ccb561d40414ab5b6Adam Copp    }
124bd8b47b147f29f196f37d66ccb561d40414ab5b6Adam Copp
125bd8b47b147f29f196f37d66ccb561d40414ab5b6Adam Copp    /**
126bd8b47b147f29f196f37d66ccb561d40414ab5b6Adam Copp     * Wrapper around {@link BitmapFactory#decodeStream(InputStream, Rect,
127bd8b47b147f29f196f37d66ccb561d40414ab5b6Adam Copp     * BitmapFactory.Options)} that returns {@code null} on {@link
128bd8b47b147f29f196f37d66ccb561d40414ab5b6Adam Copp     * OutOfMemoryError}.
129bd8b47b147f29f196f37d66ccb561d40414ab5b6Adam Copp     *
130c631b5a4b1f19f84a70b772bc879fae7c92fd4a8Mark Wei     * @param factory    Used to create input streams that holds the raw data to be decoded into a
131c631b5a4b1f19f84a70b772bc879fae7c92fd4a8Mark Wei     *                   bitmap.
132bd8b47b147f29f196f37d66ccb561d40414ab5b6Adam Copp     * @param outPadding If not null, return the padding rect for the bitmap if
133bd8b47b147f29f196f37d66ccb561d40414ab5b6Adam Copp     *                   it exists, otherwise set padding to [-1,-1,-1,-1]. If
134bd8b47b147f29f196f37d66ccb561d40414ab5b6Adam Copp     *                   no bitmap is returned (null) then padding is
135bd8b47b147f29f196f37d66ccb561d40414ab5b6Adam Copp     *                   unchanged.
136c631b5a4b1f19f84a70b772bc879fae7c92fd4a8Mark Wei     * @param opts       null-ok; Options that control downsampling and whether the
137c631b5a4b1f19f84a70b772bc879fae7c92fd4a8Mark Wei     *                   image should be completely decoded, or just is size returned.
138bd8b47b147f29f196f37d66ccb561d40414ab5b6Adam Copp     * @return The decoded bitmap, or null if the image data could not be
139c631b5a4b1f19f84a70b772bc879fae7c92fd4a8Mark Wei     * decoded, or, if opts is non-null, if opts requested only the
140c631b5a4b1f19f84a70b772bc879fae7c92fd4a8Mark Wei     * size be returned (in opts.outWidth and opts.outHeight)
141bd8b47b147f29f196f37d66ccb561d40414ab5b6Adam Copp     */
142c631b5a4b1f19f84a70b772bc879fae7c92fd4a8Mark Wei    public static Bitmap decodeStream(final InputStreamFactory factory, final Rect outPadding,
143c631b5a4b1f19f84a70b772bc879fae7c92fd4a8Mark Wei            final BitmapFactory.Options opts) throws FileNotFoundException {
144c631b5a4b1f19f84a70b772bc879fae7c92fd4a8Mark Wei        InputStream is = null;
145bd8b47b147f29f196f37d66ccb561d40414ab5b6Adam Copp        try {
1469323b13fc9bc79ce38ce7c851a2aa894ab988ed0Paul Westbrook            // Determine the orientation for this image
147c631b5a4b1f19f84a70b772bc879fae7c92fd4a8Mark Wei            is = factory.createInputStream();
148c631b5a4b1f19f84a70b772bc879fae7c92fd4a8Mark Wei            final int orientation = Exif.getOrientation(is, -1);
149274afea9b667b551d9582a3088fc27012d0e9a29Hao Pan            if (is != null) {
150274afea9b667b551d9582a3088fc27012d0e9a29Hao Pan                is.close();
151274afea9b667b551d9582a3088fc27012d0e9a29Hao Pan            }
152237ded1a59b1fc148a3ffcf78d6f9d34af5c170cPaul Westbrook
153c631b5a4b1f19f84a70b772bc879fae7c92fd4a8Mark Wei            // Decode the bitmap
154c631b5a4b1f19f84a70b772bc879fae7c92fd4a8Mark Wei            is = factory.createInputStream();
155c631b5a4b1f19f84a70b772bc879fae7c92fd4a8Mark Wei            final Bitmap originalBitmap = BitmapFactory.decodeStream(is, outPadding, opts);
1569323b13fc9bc79ce38ce7c851a2aa894ab988ed0Paul Westbrook
157c631b5a4b1f19f84a70b772bc879fae7c92fd4a8Mark Wei            if (is != null && originalBitmap == null && !opts.inJustDecodeBounds) {
15806d8b5e82b9b75588051a492152fda0b63f70484Mark Wei                Log.w(TAG, "ImageUtils#decodeStream(InputStream, Rect, Options): "
15906d8b5e82b9b75588051a492152fda0b63f70484Mark Wei                        + "Image bytes cannot be decoded into a Bitmap");
16006d8b5e82b9b75588051a492152fda0b63f70484Mark Wei                throw new UnsupportedOperationException(
16106d8b5e82b9b75588051a492152fda0b63f70484Mark Wei                        "Image bytes cannot be decoded into a Bitmap.");
16206d8b5e82b9b75588051a492152fda0b63f70484Mark Wei            }
163c631b5a4b1f19f84a70b772bc879fae7c92fd4a8Mark Wei
164c631b5a4b1f19f84a70b772bc879fae7c92fd4a8Mark Wei            // Rotate the Bitmap based on the orientation
1659323b13fc9bc79ce38ce7c851a2aa894ab988ed0Paul Westbrook            if (originalBitmap != null && orientation != 0) {
1669323b13fc9bc79ce38ce7c851a2aa894ab988ed0Paul Westbrook                final Matrix matrix = new Matrix();
1679323b13fc9bc79ce38ce7c851a2aa894ab988ed0Paul Westbrook                matrix.postRotate(orientation);
1689323b13fc9bc79ce38ce7c851a2aa894ab988ed0Paul Westbrook                return Bitmap.createBitmap(originalBitmap, 0, 0, originalBitmap.getWidth(),
1699323b13fc9bc79ce38ce7c851a2aa894ab988ed0Paul Westbrook                        originalBitmap.getHeight(), matrix, true);
1709323b13fc9bc79ce38ce7c851a2aa894ab988ed0Paul Westbrook            }
1719323b13fc9bc79ce38ce7c851a2aa894ab988ed0Paul Westbrook            return originalBitmap;
172bd8b47b147f29f196f37d66ccb561d40414ab5b6Adam Copp        } catch (OutOfMemoryError oome) {
173bd8b47b147f29f196f37d66ccb561d40414ab5b6Adam Copp            Log.e(TAG, "ImageUtils#decodeStream(InputStream, Rect, Options) threw an OOME", oome);
174bd8b47b147f29f196f37d66ccb561d40414ab5b6Adam Copp            return null;
1759323b13fc9bc79ce38ce7c851a2aa894ab988ed0Paul Westbrook        } catch (IOException ioe) {
1769323b13fc9bc79ce38ce7c851a2aa894ab988ed0Paul Westbrook            Log.e(TAG, "ImageUtils#decodeStream(InputStream, Rect, Options) threw an IOE", ioe);
1779323b13fc9bc79ce38ce7c851a2aa894ab988ed0Paul Westbrook            return null;
1789323b13fc9bc79ce38ce7c851a2aa894ab988ed0Paul Westbrook        } finally {
179c631b5a4b1f19f84a70b772bc879fae7c92fd4a8Mark Wei            if (is != null) {
1809323b13fc9bc79ce38ce7c851a2aa894ab988ed0Paul Westbrook                try {
181c631b5a4b1f19f84a70b772bc879fae7c92fd4a8Mark Wei                    is.close();
182237ded1a59b1fc148a3ffcf78d6f9d34af5c170cPaul Westbrook                } catch (IOException e) {
183237ded1a59b1fc148a3ffcf78d6f9d34af5c170cPaul Westbrook                    // Do nothing
184237ded1a59b1fc148a3ffcf78d6f9d34af5c170cPaul Westbrook                }
185237ded1a59b1fc148a3ffcf78d6f9d34af5c170cPaul Westbrook            }
186bd8b47b147f29f196f37d66ccb561d40414ab5b6Adam Copp        }
187bd8b47b147f29f196f37d66ccb561d40414ab5b6Adam Copp    }
188bd8b47b147f29f196f37d66ccb561d40414ab5b6Adam Copp
189bd8b47b147f29f196f37d66ccb561d40414ab5b6Adam Copp    /**
190bd8b47b147f29f196f37d66ccb561d40414ab5b6Adam Copp     * Gets the image bounds
191bd8b47b147f29f196f37d66ccb561d40414ab5b6Adam Copp     *
192c631b5a4b1f19f84a70b772bc879fae7c92fd4a8Mark Wei     * @param factory Used to create the InputStream.
193bd8b47b147f29f196f37d66ccb561d40414ab5b6Adam Copp     *
194bd8b47b147f29f196f37d66ccb561d40414ab5b6Adam Copp     * @return The image bounds
195bd8b47b147f29f196f37d66ccb561d40414ab5b6Adam Copp     */
196c631b5a4b1f19f84a70b772bc879fae7c92fd4a8Mark Wei    private static Point getImageBounds(final InputStreamFactory factory)
197bd8b47b147f29f196f37d66ccb561d40414ab5b6Adam Copp            throws IOException {
198bd8b47b147f29f196f37d66ccb561d40414ab5b6Adam Copp        final BitmapFactory.Options opts = new BitmapFactory.Options();
199c631b5a4b1f19f84a70b772bc879fae7c92fd4a8Mark Wei        opts.inJustDecodeBounds = true;
200c631b5a4b1f19f84a70b772bc879fae7c92fd4a8Mark Wei        decodeStream(factory, null, opts);
201bd8b47b147f29f196f37d66ccb561d40414ab5b6Adam Copp
202c631b5a4b1f19f84a70b772bc879fae7c92fd4a8Mark Wei        return new Point(opts.outWidth, opts.outHeight);
203c631b5a4b1f19f84a70b772bc879fae7c92fd4a8Mark Wei    }
204c631b5a4b1f19f84a70b772bc879fae7c92fd4a8Mark Wei
205c631b5a4b1f19f84a70b772bc879fae7c92fd4a8Mark Wei    private static InputStreamFactory createInputStreamFactory(final ContentResolver resolver,
206c631b5a4b1f19f84a70b772bc879fae7c92fd4a8Mark Wei            final Uri uri) {
207c631b5a4b1f19f84a70b772bc879fae7c92fd4a8Mark Wei        final String scheme = uri.getScheme();
2087a75fba57ed82c4b0fea362ce739ab253eac909fAdam Copp        if ("data".equals(scheme)) {
209c631b5a4b1f19f84a70b772bc879fae7c92fd4a8Mark Wei            return new DataInputStreamFactory(resolver, uri);
210c631b5a4b1f19f84a70b772bc879fae7c92fd4a8Mark Wei        }
211c631b5a4b1f19f84a70b772bc879fae7c92fd4a8Mark Wei        return new BaseInputStreamFactory(resolver, uri);
212c631b5a4b1f19f84a70b772bc879fae7c92fd4a8Mark Wei    }
213c631b5a4b1f19f84a70b772bc879fae7c92fd4a8Mark Wei
214c631b5a4b1f19f84a70b772bc879fae7c92fd4a8Mark Wei    /**
215c631b5a4b1f19f84a70b772bc879fae7c92fd4a8Mark Wei     * Utility class for when an InputStream needs to be read multiple times. For example, one pass
216c631b5a4b1f19f84a70b772bc879fae7c92fd4a8Mark Wei     * may load EXIF orientation, and the second pass may do the actual Bitmap decode.
217c631b5a4b1f19f84a70b772bc879fae7c92fd4a8Mark Wei     */
218c631b5a4b1f19f84a70b772bc879fae7c92fd4a8Mark Wei    public interface InputStreamFactory {
219c631b5a4b1f19f84a70b772bc879fae7c92fd4a8Mark Wei
220c631b5a4b1f19f84a70b772bc879fae7c92fd4a8Mark Wei        /**
221c631b5a4b1f19f84a70b772bc879fae7c92fd4a8Mark Wei         * Create a new InputStream. The caller of this method must be able to read the input
222c631b5a4b1f19f84a70b772bc879fae7c92fd4a8Mark Wei         * stream starting from the beginning.
223c631b5a4b1f19f84a70b772bc879fae7c92fd4a8Mark Wei         * @return
224c631b5a4b1f19f84a70b772bc879fae7c92fd4a8Mark Wei         */
225c631b5a4b1f19f84a70b772bc879fae7c92fd4a8Mark Wei        InputStream createInputStream() throws FileNotFoundException;
226c631b5a4b1f19f84a70b772bc879fae7c92fd4a8Mark Wei    }
227c631b5a4b1f19f84a70b772bc879fae7c92fd4a8Mark Wei
228c631b5a4b1f19f84a70b772bc879fae7c92fd4a8Mark Wei    private static class BaseInputStreamFactory implements InputStreamFactory {
229c631b5a4b1f19f84a70b772bc879fae7c92fd4a8Mark Wei        protected final ContentResolver mResolver;
230c631b5a4b1f19f84a70b772bc879fae7c92fd4a8Mark Wei        protected final Uri mUri;
231c631b5a4b1f19f84a70b772bc879fae7c92fd4a8Mark Wei
232c631b5a4b1f19f84a70b772bc879fae7c92fd4a8Mark Wei        public BaseInputStreamFactory(final ContentResolver resolver, final Uri uri) {
233c631b5a4b1f19f84a70b772bc879fae7c92fd4a8Mark Wei            mResolver = resolver;
234c631b5a4b1f19f84a70b772bc879fae7c92fd4a8Mark Wei            mUri = uri;
235c631b5a4b1f19f84a70b772bc879fae7c92fd4a8Mark Wei        }
236c631b5a4b1f19f84a70b772bc879fae7c92fd4a8Mark Wei
237c631b5a4b1f19f84a70b772bc879fae7c92fd4a8Mark Wei        @Override
238c631b5a4b1f19f84a70b772bc879fae7c92fd4a8Mark Wei        public InputStream createInputStream() throws FileNotFoundException {
239c631b5a4b1f19f84a70b772bc879fae7c92fd4a8Mark Wei            return mResolver.openInputStream(mUri);
240c631b5a4b1f19f84a70b772bc879fae7c92fd4a8Mark Wei        }
241c631b5a4b1f19f84a70b772bc879fae7c92fd4a8Mark Wei    }
242c631b5a4b1f19f84a70b772bc879fae7c92fd4a8Mark Wei
243c631b5a4b1f19f84a70b772bc879fae7c92fd4a8Mark Wei    private static class DataInputStreamFactory extends BaseInputStreamFactory {
244c631b5a4b1f19f84a70b772bc879fae7c92fd4a8Mark Wei        private byte[] mData;
245c631b5a4b1f19f84a70b772bc879fae7c92fd4a8Mark Wei
246c631b5a4b1f19f84a70b772bc879fae7c92fd4a8Mark Wei        public DataInputStreamFactory(final ContentResolver resolver, final Uri uri) {
247c631b5a4b1f19f84a70b772bc879fae7c92fd4a8Mark Wei            super(resolver, uri);
248c631b5a4b1f19f84a70b772bc879fae7c92fd4a8Mark Wei        }
249c631b5a4b1f19f84a70b772bc879fae7c92fd4a8Mark Wei
250c631b5a4b1f19f84a70b772bc879fae7c92fd4a8Mark Wei        @Override
251c631b5a4b1f19f84a70b772bc879fae7c92fd4a8Mark Wei        public InputStream createInputStream() throws FileNotFoundException {
252c631b5a4b1f19f84a70b772bc879fae7c92fd4a8Mark Wei            if (mData == null) {
253c631b5a4b1f19f84a70b772bc879fae7c92fd4a8Mark Wei                mData = parseDataUri(mUri);
254c631b5a4b1f19f84a70b772bc879fae7c92fd4a8Mark Wei                if (mData == null) {
255c631b5a4b1f19f84a70b772bc879fae7c92fd4a8Mark Wei                    return super.createInputStream();
256bd8b47b147f29f196f37d66ccb561d40414ab5b6Adam Copp                }
257bd8b47b147f29f196f37d66ccb561d40414ab5b6Adam Copp            }
258c631b5a4b1f19f84a70b772bc879fae7c92fd4a8Mark Wei            return new ByteArrayInputStream(mData);
259bd8b47b147f29f196f37d66ccb561d40414ab5b6Adam Copp        }
260bd8b47b147f29f196f37d66ccb561d40414ab5b6Adam Copp
261c631b5a4b1f19f84a70b772bc879fae7c92fd4a8Mark Wei        private byte[] parseDataUri(final Uri uri) {
262c631b5a4b1f19f84a70b772bc879fae7c92fd4a8Mark Wei            final String ssp = uri.getSchemeSpecificPart();
263bd8b47b147f29f196f37d66ccb561d40414ab5b6Adam Copp            try {
264c631b5a4b1f19f84a70b772bc879fae7c92fd4a8Mark Wei                if (ssp.startsWith(BASE64_URI_PREFIX)) {
265c631b5a4b1f19f84a70b772bc879fae7c92fd4a8Mark Wei                    final String base64 = ssp.substring(BASE64_URI_PREFIX.length());
266c631b5a4b1f19f84a70b772bc879fae7c92fd4a8Mark Wei                    return Base64.decode(base64, Base64.URL_SAFE);
267c631b5a4b1f19f84a70b772bc879fae7c92fd4a8Mark Wei                } else if (BASE64_IMAGE_URI_PATTERN.matcher(ssp).matches()){
268c631b5a4b1f19f84a70b772bc879fae7c92fd4a8Mark Wei                    final String base64 = ssp.substring(
269c631b5a4b1f19f84a70b772bc879fae7c92fd4a8Mark Wei                            ssp.indexOf(BASE64_URI_PREFIX) + BASE64_URI_PREFIX.length());
270c631b5a4b1f19f84a70b772bc879fae7c92fd4a8Mark Wei                    return Base64.decode(base64, Base64.DEFAULT);
271c631b5a4b1f19f84a70b772bc879fae7c92fd4a8Mark Wei                } else {
272c631b5a4b1f19f84a70b772bc879fae7c92fd4a8Mark Wei                    return null;
273c631b5a4b1f19f84a70b772bc879fae7c92fd4a8Mark Wei                }
274c631b5a4b1f19f84a70b772bc879fae7c92fd4a8Mark Wei            } catch (IllegalArgumentException ex) {
275c631b5a4b1f19f84a70b772bc879fae7c92fd4a8Mark Wei                Log.e(TAG, "Mailformed data URI: " + ex);
276bd8b47b147f29f196f37d66ccb561d40414ab5b6Adam Copp                return null;
277bd8b47b147f29f196f37d66ccb561d40414ab5b6Adam Copp            }
278bd8b47b147f29f196f37d66ccb561d40414ab5b6Adam Copp        }
279bd8b47b147f29f196f37d66ccb561d40414ab5b6Adam Copp    }
280bd8b47b147f29f196f37d66ccb561d40414ab5b6Adam Copp}
281