Home | History | Annotate | Download | only in ui
      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 
     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 
     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 
    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 
    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 
    163     public String getContentType() {
    164         return mContentType;
    165     }
    166 
    167     public String getSrc() {
    168         return mSrc;
    169     }
    170 
    171     public int getWidth() {
    172         return mWidth;
    173     }
    174 
    175     public int getHeight() {
    176         return mHeight;
    177     }
    178 
    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 
    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