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