UriImage.java revision b0664c5822ee07642617a73c932b02a2636a2223
1/* 2 * Copyright (C) 2008 Esmertec AG. 3 * Copyright (C) 2008 The Android Open Source Project 4 * 5 * Licensed under the Apache License, Version 2.0 (the "License"); 6 * you may not use this file except in compliance with the License. 7 * You may obtain a copy of the License at 8 * 9 * http://www.apache.org/licenses/LICENSE-2.0 10 * 11 * Unless required by applicable law or agreed to in writing, software 12 * distributed under the License is distributed on an "AS IS" BASIS, 13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 * See the License for the specific language governing permissions and 15 * limitations under the License. 16 */ 17 18package com.android.mms.ui; 19 20import com.android.mms.model.ImageModel; 21import com.android.mms.LogTag; 22import com.google.android.mms.pdu.PduPart; 23import android.database.sqlite.SqliteWrapper; 24 25import android.content.Context; 26import android.database.Cursor; 27import android.graphics.Bitmap; 28import android.graphics.BitmapFactory; 29import android.graphics.Bitmap.CompressFormat; 30import android.net.Uri; 31import android.provider.MediaStore.Images; 32import android.provider.Telephony.Mms.Part; 33import android.text.TextUtils; 34import android.util.Log; 35import android.webkit.MimeTypeMap; 36 37import java.io.ByteArrayOutputStream; 38import java.io.FileNotFoundException; 39import java.io.IOException; 40import java.io.InputStream; 41 42public class UriImage { 43 private static final String TAG = "Mms/image"; 44 private static final boolean DEBUG = false; 45 private static final boolean LOCAL_LOGV = false; 46 47 private final Context mContext; 48 private final Uri mUri; 49 private String mContentType; 50 private String mPath; 51 private String mSrc; 52 private int mWidth; 53 private int mHeight; 54 55 public UriImage(Context context, Uri uri) { 56 if ((null == context) || (null == uri)) { 57 throw new IllegalArgumentException(); 58 } 59 60 String scheme = uri.getScheme(); 61 if (scheme.equals("content")) { 62 initFromContentUri(context, uri); 63 } else if (uri.getScheme().equals("file")) { 64 initFromFile(context, uri); 65 } 66 67 mSrc = mPath.substring(mPath.lastIndexOf('/') + 1); 68 69 if(mSrc.startsWith(".") && mSrc.length() > 1) { 70 mSrc = mSrc.substring(1); 71 } 72 73 // Some MMSCs appear to have problems with filenames 74 // containing a space. So just replace them with 75 // underscores in the name, which is typically not 76 // visible to the user anyway. 77 mSrc = mSrc.replace(' ', '_'); 78 79 mContext = context; 80 mUri = uri; 81 82 decodeBoundsInfo(); 83 84 if (LOCAL_LOGV) { 85 Log.v(TAG, "UriImage uri: " + uri + " mPath: " + mPath + " mWidth: " + mWidth + 86 " mHeight: " + mHeight); 87 } 88 } 89 90 private void initFromFile(Context context, Uri uri) { 91 mPath = uri.getPath(); 92 MimeTypeMap mimeTypeMap = MimeTypeMap.getSingleton(); 93 String extension = MimeTypeMap.getFileExtensionFromUrl(mPath); 94 if (TextUtils.isEmpty(extension)) { 95 // getMimeTypeFromExtension() doesn't handle spaces in filenames nor can it handle 96 // urlEncoded strings. Let's try one last time at finding the extension. 97 int dotPos = mPath.lastIndexOf('.'); 98 if (0 <= dotPos) { 99 extension = mPath.substring(dotPos + 1); 100 } 101 } 102 mContentType = mimeTypeMap.getMimeTypeFromExtension(extension); 103 // It's ok if mContentType is null. Eventually we'll show a toast telling the 104 // user the picture couldn't be attached. 105 } 106 107 private void initFromContentUri(Context context, Uri uri) { 108 Cursor c = SqliteWrapper.query(context, context.getContentResolver(), 109 uri, null, null, null, null); 110 111 if (c == null) { 112 throw new IllegalArgumentException( 113 "Query on " + uri + " returns null result."); 114 } 115 116 try { 117 if ((c.getCount() != 1) || !c.moveToFirst()) { 118 throw new IllegalArgumentException( 119 "Query on " + uri + " returns 0 or multiple rows."); 120 } 121 122 String filePath; 123 if (ImageModel.isMmsUri(uri)) { 124 filePath = c.getString(c.getColumnIndexOrThrow(Part.FILENAME)); 125 if (TextUtils.isEmpty(filePath)) { 126 filePath = c.getString( 127 c.getColumnIndexOrThrow(Part._DATA)); 128 } 129 mContentType = c.getString( 130 c.getColumnIndexOrThrow(Part.CONTENT_TYPE)); 131 } else { 132 filePath = uri.getPath(); 133 mContentType = c.getString( 134 c.getColumnIndexOrThrow(Images.Media.MIME_TYPE)); 135 } 136 mPath = filePath; 137 } finally { 138 c.close(); 139 } 140 } 141 142 private void decodeBoundsInfo() { 143 InputStream input = null; 144 try { 145 input = mContext.getContentResolver().openInputStream(mUri); 146 BitmapFactory.Options opt = new BitmapFactory.Options(); 147 opt.inJustDecodeBounds = true; 148 BitmapFactory.decodeStream(input, null, opt); 149 mWidth = opt.outWidth; 150 mHeight = opt.outHeight; 151 } catch (FileNotFoundException e) { 152 // Ignore 153 Log.e(TAG, "IOException caught while opening stream", e); 154 } finally { 155 if (null != input) { 156 try { 157 input.close(); 158 } catch (IOException e) { 159 // Ignore 160 Log.e(TAG, "IOException caught while closing stream", e); 161 } 162 } 163 } 164 } 165 166 public String getContentType() { 167 return mContentType; 168 } 169 170 public String getSrc() { 171 return mSrc; 172 } 173 174 public int getWidth() { 175 return mWidth; 176 } 177 178 public int getHeight() { 179 return mHeight; 180 } 181 182 public PduPart getResizedImageAsPart(int widthLimit, int heightLimit, int byteLimit) { 183 PduPart part = new PduPart(); 184 185 byte[] data = getResizedImageData(widthLimit, heightLimit, byteLimit); 186 if (data == null) { 187 if (LOCAL_LOGV) { 188 Log.v(TAG, "Resize image failed."); 189 } 190 return null; 191 } 192 193 part.setData(data); 194 part.setContentType(getContentType().getBytes()); 195 196 return part; 197 } 198 199 private static final int NUMBER_OF_RESIZE_ATTEMPTS = 4; 200 201 private byte[] getResizedImageData(int widthLimit, int heightLimit, int byteLimit) { 202 int outWidth = mWidth; 203 int outHeight = mHeight; 204 205 int scaleFactor = 1; 206 while ((outWidth / scaleFactor > widthLimit) || (outHeight / scaleFactor > heightLimit)) { 207 scaleFactor *= 2; 208 } 209 210 if (Log.isLoggable(LogTag.APP, Log.VERBOSE)) { 211 Log.v(TAG, "getResizedImageData: wlimit=" + widthLimit + 212 ", hlimit=" + heightLimit + ", sizeLimit=" + byteLimit + 213 ", mWidth=" + mWidth + ", mHeight=" + mHeight + 214 ", initialScaleFactor=" + scaleFactor + 215 ", mUri=" + mUri); 216 } 217 218 InputStream input = null; 219 try { 220 ByteArrayOutputStream os = null; 221 int attempts = 1; 222 223 do { 224 BitmapFactory.Options options = new BitmapFactory.Options(); 225 options.inSampleSize = scaleFactor; 226 input = mContext.getContentResolver().openInputStream(mUri); 227 int quality = MessageUtils.IMAGE_COMPRESSION_QUALITY; 228 try { 229 Bitmap b = BitmapFactory.decodeStream(input, null, options); 230 if (b == null) { 231 return null; 232 } 233 if (options.outWidth > widthLimit || options.outHeight > heightLimit) { 234 // The decoder does not support the inSampleSize option. 235 // Scale the bitmap using Bitmap library. 236 int scaledWidth = outWidth / scaleFactor; 237 int scaledHeight = outHeight / scaleFactor; 238 239 if (Log.isLoggable(LogTag.APP, Log.VERBOSE)) { 240 Log.v(TAG, "getResizedImageData: retry scaling using " + 241 "Bitmap.createScaledBitmap: w=" + scaledWidth + 242 ", h=" + scaledHeight); 243 } 244 245 b = Bitmap.createScaledBitmap(b, outWidth / scaleFactor, 246 outHeight / scaleFactor, false); 247 if (b == null) { 248 return null; 249 } 250 } 251 252 // Compress the image into a JPG. Start with MessageUtils.IMAGE_COMPRESSION_QUALITY. 253 // In case that the image byte size is still too large reduce the quality in 254 // proportion to the desired byte size. Should the quality fall below 255 // MINIMUM_IMAGE_COMPRESSION_QUALITY skip a compression attempt and we will enter 256 // the next round with a smaller image to start with. 257 os = new ByteArrayOutputStream(); 258 b.compress(CompressFormat.JPEG, quality, os); 259 int jpgFileSize = os.size(); 260 if (jpgFileSize > byteLimit) { 261 int reducedQuality = quality * byteLimit / jpgFileSize; 262 if (reducedQuality >= MessageUtils.MINIMUM_IMAGE_COMPRESSION_QUALITY) { 263 quality = reducedQuality; 264 265 if (Log.isLoggable(LogTag.APP, Log.VERBOSE)) { 266 Log.v(TAG, "getResizedImageData: compress(2) w/ quality=" + quality); 267 } 268 269 os = new ByteArrayOutputStream(); 270 b.compress(CompressFormat.JPEG, quality, os); 271 } 272 } 273 b.recycle(); // done with the bitmap, release the memory 274 } catch (java.lang.OutOfMemoryError e) { 275 Log.w(TAG, "getResizedImageData - image too big (OutOfMemoryError), will try " 276 + " with smaller scale factor, cur scale factor: " + scaleFactor); 277 // fall through and keep trying with a smaller scale factor. 278 } 279 if (Log.isLoggable(LogTag.APP, Log.VERBOSE)) { 280 Log.v(TAG, "attempt=" + attempts 281 + " size=" + (os == null ? 0 : os.size()) 282 + " width=" + outWidth / scaleFactor 283 + " height=" + outHeight / scaleFactor 284 + " scaleFactor=" + scaleFactor 285 + " quality=" + quality); 286 } 287 scaleFactor *= 2; 288 attempts++; 289 } while ((os == null || os.size() > byteLimit) && attempts < NUMBER_OF_RESIZE_ATTEMPTS); 290 291 return os == null ? null : os.toByteArray(); 292 } catch (FileNotFoundException e) { 293 Log.e(TAG, e.getMessage(), e); 294 return null; 295 } catch (java.lang.OutOfMemoryError e) { 296 Log.e(TAG, e.getMessage(), e); 297 return null; 298 } finally { 299 if (input != null) { 300 try { 301 input.close(); 302 } catch (IOException e) { 303 Log.e(TAG, e.getMessage(), e); 304 } 305 } 306 } 307 } 308} 309