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