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