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