Home | History | Annotate | Download | only in model
      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.model;
     19 
     20 import com.android.mms.ContentRestrictionException;
     21 import com.android.mms.ExceedMessageSizeException;
     22 import com.android.mms.LogTag;
     23 import com.android.mms.MmsConfig;
     24 import com.android.mms.dom.smil.SmilMediaElementImpl;
     25 import android.drm.mobile1.DrmException;
     26 import com.android.mms.drm.DrmWrapper;
     27 import com.android.mms.ui.UriImage;
     28 import com.android.mms.ui.MessageUtils;
     29 
     30 import com.google.android.mms.ContentType;
     31 import com.google.android.mms.MmsException;
     32 import com.google.android.mms.pdu.PduPart;
     33 import com.google.android.mms.pdu.PduPersister;
     34 
     35 import org.w3c.dom.events.Event;
     36 import org.w3c.dom.smil.ElementTime;
     37 
     38 import android.content.Context;
     39 import android.graphics.Bitmap;
     40 import android.graphics.BitmapFactory;
     41 import android.net.Uri;
     42 import android.text.TextUtils;
     43 import android.util.Log;
     44 
     45 import java.io.FileNotFoundException;
     46 import java.io.IOException;
     47 import java.io.InputStream;
     48 import java.lang.ref.SoftReference;
     49 import java.util.Arrays;
     50 import java.util.HashSet;
     51 import java.util.Set;
     52 
     53 
     54 public class ImageModel extends RegionMediaModel {
     55     @SuppressWarnings("hiding")
     56     private static final String TAG = "Mms/image";
     57     private static final boolean DEBUG = false;
     58     private static final boolean LOCAL_LOGV = false;
     59 
     60     private static final int THUMBNAIL_BOUNDS_LIMIT = 480;
     61 
     62     /**
     63      * These are the image content types that MMS supports. Anything else needs to be transcoded
     64      * into one of these content types before being sent over MMS.
     65      */
     66     private static final Set<String> SUPPORTED_MMS_IMAGE_CONTENT_TYPES =
     67         new HashSet<String>(Arrays.asList(new String[] {
     68                 "image/jpeg",
     69             }));
     70 
     71     private int mWidth;
     72     private int mHeight;
     73     private SoftReference<Bitmap> mBitmapCache = new SoftReference<Bitmap>(null);
     74 
     75     public ImageModel(Context context, Uri uri, RegionModel region)
     76             throws MmsException {
     77         super(context, SmilHelper.ELEMENT_TAG_IMAGE, uri, region);
     78         initModelFromUri(uri);
     79         checkContentRestriction();
     80     }
     81 
     82     public ImageModel(Context context, String contentType, String src,
     83             Uri uri, RegionModel region) throws DrmException, MmsException {
     84         super(context, SmilHelper.ELEMENT_TAG_IMAGE,
     85                 contentType, src, uri, region);
     86         decodeImageBounds();
     87     }
     88 
     89     public ImageModel(Context context, String contentType, String src,
     90             DrmWrapper wrapper, RegionModel regionModel) throws IOException {
     91         super(context, SmilHelper.ELEMENT_TAG_IMAGE, contentType, src,
     92                 wrapper, regionModel);
     93     }
     94 
     95     private void initModelFromUri(Uri uri) throws MmsException {
     96         UriImage uriImage = new UriImage(mContext, uri);
     97 
     98         mContentType = uriImage.getContentType();
     99         if (TextUtils.isEmpty(mContentType)) {
    100             throw new MmsException("Type of media is unknown.");
    101         }
    102         mSrc = uriImage.getSrc();
    103         mWidth = uriImage.getWidth();
    104         mHeight = uriImage.getHeight();
    105 
    106         if (LOCAL_LOGV) {
    107             Log.v(TAG, "New ImageModel created:"
    108                     + " mSrc=" + mSrc
    109                     + " mContentType=" + mContentType
    110                     + " mUri=" + uri);
    111         }
    112     }
    113 
    114     private void decodeImageBounds() throws DrmException {
    115         UriImage uriImage = new UriImage(mContext, getUriWithDrmCheck());
    116         mWidth = uriImage.getWidth();
    117         mHeight = uriImage.getHeight();
    118 
    119         if (LOCAL_LOGV) {
    120             Log.v(TAG, "Image bounds: " + mWidth + "x" + mHeight);
    121         }
    122     }
    123 
    124     // EventListener Interface
    125     @Override
    126     public void handleEvent(Event evt) {
    127         if (evt.getType().equals(SmilMediaElementImpl.SMIL_MEDIA_START_EVENT)) {
    128             mVisible = true;
    129         } else if (mFill != ElementTime.FILL_FREEZE) {
    130             mVisible = false;
    131         }
    132 
    133         notifyModelChanged(false);
    134     }
    135 
    136     public int getWidth() {
    137         return mWidth;
    138     }
    139 
    140     public int getHeight() {
    141         return mHeight;
    142     }
    143 
    144     protected void checkContentRestriction() throws ContentRestrictionException {
    145         ContentRestriction cr = ContentRestrictionFactory.getContentRestriction();
    146         cr.checkImageContentType(mContentType);
    147     }
    148 
    149     public Bitmap getBitmap() {
    150         return internalGetBitmap(getUri());
    151     }
    152 
    153     public Bitmap getBitmapWithDrmCheck() throws DrmException {
    154         return internalGetBitmap(getUriWithDrmCheck());
    155     }
    156 
    157     private Bitmap internalGetBitmap(Uri uri) {
    158         Bitmap bm = mBitmapCache.get();
    159         if (bm == null) {
    160             try {
    161                 bm = createThumbnailBitmap(THUMBNAIL_BOUNDS_LIMIT, uri);
    162                 if (bm != null) {
    163                     mBitmapCache = new SoftReference<Bitmap>(bm);
    164                 }
    165             } catch (OutOfMemoryError ex) {
    166                 // fall through and return a null bitmap. The callers can handle a null
    167                 // result and show R.drawable.ic_missing_thumbnail_picture
    168             }
    169         }
    170         return bm;
    171     }
    172 
    173     private Bitmap createThumbnailBitmap(int thumbnailBoundsLimit, Uri uri) {
    174         int outWidth = mWidth;
    175         int outHeight = mHeight;
    176 
    177         int s = 1;
    178         while ((outWidth / s > thumbnailBoundsLimit)
    179                 || (outHeight / s > thumbnailBoundsLimit)) {
    180             s *= 2;
    181         }
    182         if (Log.isLoggable(LogTag.APP, Log.VERBOSE)) {
    183             Log.v(TAG, "createThumbnailBitmap: scale=" + s + ", w=" + outWidth / s
    184                     + ", h=" + outHeight / s);
    185         }
    186         BitmapFactory.Options options = new BitmapFactory.Options();
    187         options.inSampleSize = s;
    188 
    189         InputStream input = null;
    190         try {
    191             input = mContext.getContentResolver().openInputStream(uri);
    192             return BitmapFactory.decodeStream(input, null, options);
    193         } catch (FileNotFoundException e) {
    194             Log.e(TAG, e.getMessage(), e);
    195             return null;
    196         } catch (OutOfMemoryError ex) {
    197             if (DEBUG) {
    198                 MessageUtils.writeHprofDataToFile();
    199             }
    200             throw ex;
    201         } finally {
    202             if (input != null) {
    203                 try {
    204                     input.close();
    205                 } catch (IOException e) {
    206                     Log.e(TAG, e.getMessage(), e);
    207                 }
    208             }
    209         }
    210     }
    211 
    212     @Override
    213     public boolean getMediaResizable() {
    214         return true;
    215     }
    216 
    217     @Override
    218     protected void resizeMedia(int byteLimit, long messageId) throws MmsException {
    219         UriImage image = new UriImage(mContext, getUri());
    220 
    221         int widthLimit = MmsConfig.getMaxImageWidth();
    222         int heightLimit = MmsConfig.getMaxImageHeight();
    223         int size = getMediaSize();
    224         // In mms_config.xml, the max width has always been declared larger than the max height.
    225         // Swap the width and height limits if necessary so we scale the picture as little as
    226         // possible.
    227         if (image.getHeight() > image.getWidth()) {
    228             int temp = widthLimit;
    229             widthLimit = heightLimit;
    230             heightLimit = temp;
    231         }
    232 
    233         if (Log.isLoggable(LogTag.APP, Log.VERBOSE)) {
    234             Log.v(TAG, "resizeMedia size: " + size + " image.getWidth(): "
    235                     + image.getWidth() + " widthLimit: " + widthLimit
    236                     + " image.getHeight(): " + image.getHeight()
    237                     + " heightLimit: " + heightLimit
    238                     + " image.getContentType(): " + image.getContentType());
    239         }
    240 
    241         // Check if we're already within the limits - in which case we don't need to resize.
    242         // The size can be zero here, even when the media has content. See the comment in
    243         // MediaModel.initMediaSize. Sometimes it'll compute zero and it's costly to read the
    244         // whole stream to compute the size. When we call getResizedImageAsPart(), we'll correctly
    245         // set the size.
    246         if (size != 0 && size <= byteLimit &&
    247                 image.getWidth() <= widthLimit &&
    248                 image.getHeight() <= heightLimit &&
    249                 SUPPORTED_MMS_IMAGE_CONTENT_TYPES.contains(image.getContentType())) {
    250             if (Log.isLoggable(LogTag.APP, Log.VERBOSE)) {
    251                 Log.v(TAG, "resizeMedia - already sized");
    252             }
    253             return;
    254         }
    255 
    256         PduPart part = image.getResizedImageAsPart(
    257                 widthLimit,
    258                 heightLimit,
    259                 byteLimit);
    260 
    261         if (part == null) {
    262             throw new ExceedMessageSizeException("Not enough memory to turn image into part: " +
    263                     getUri());
    264         }
    265 
    266         // Update the content type because it may have changed due to resizing/recompressing
    267         mContentType = new String(part.getContentType());
    268 
    269         String src = getSrc();
    270         byte[] srcBytes = src.getBytes();
    271         part.setContentLocation(srcBytes);
    272         int period = src.lastIndexOf(".");
    273         byte[] contentId = period != -1 ? src.substring(0, period).getBytes() : srcBytes;
    274         part.setContentId(contentId);
    275 
    276         PduPersister persister = PduPersister.getPduPersister(mContext);
    277         this.mSize = part.getData().length;
    278 
    279         if (Log.isLoggable(LogTag.APP, Log.VERBOSE)) {
    280             Log.v(TAG, "resizeMedia mSize: " + mSize);
    281         }
    282 
    283         Uri newUri = persister.persistPart(part, messageId);
    284         setUri(newUri);
    285     }
    286 }
    287