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