/* * Copyright (C) 2008 Esmertec AG. * Copyright (C) 2008 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.mms.model; import java.lang.ref.SoftReference; import java.util.Arrays; import java.util.HashSet; import java.util.Set; import org.w3c.dom.events.Event; import org.w3c.dom.smil.ElementTime; import android.content.Context; import android.graphics.Bitmap; import android.graphics.BitmapFactory; import android.net.Uri; import android.text.TextUtils; import android.util.Log; import com.android.mms.ContentRestrictionException; import com.android.mms.ExceedMessageSizeException; import com.android.mms.LogTag; import com.android.mms.MmsApp; import com.android.mms.MmsConfig; import com.android.mms.dom.smil.SmilMediaElementImpl; import com.android.mms.ui.UriImage; import com.android.mms.util.ItemLoadedCallback; import com.android.mms.util.ItemLoadedFuture; import com.android.mms.util.ThumbnailManager; import com.google.android.mms.MmsException; import com.google.android.mms.pdu.PduPart; import com.google.android.mms.pdu.PduPersister; public class ImageModel extends RegionMediaModel { private static final String TAG = "Mms/image"; private static final boolean DEBUG = false; private static final boolean LOCAL_LOGV = false; private static final int PICTURE_SIZE_LIMIT = 100 * 1024; /** * These are the image content types that MMS supports. Anything else needs to be transcoded * into one of these content types before being sent over MMS. */ private static final Set<String> SUPPORTED_MMS_IMAGE_CONTENT_TYPES = new HashSet<String>(Arrays.asList(new String[] { "image/jpeg", })); private int mWidth; private int mHeight; private SoftReference<Bitmap> mFullSizeBitmapCache = new SoftReference<Bitmap>(null); private ItemLoadedFuture mItemLoadedFuture; public ImageModel(Context context, Uri uri, RegionModel region) throws MmsException { super(context, SmilHelper.ELEMENT_TAG_IMAGE, uri, region); initModelFromUri(uri); checkContentRestriction(); } public ImageModel(Context context, String contentType, String src, Uri uri, RegionModel region) throws MmsException { super(context, SmilHelper.ELEMENT_TAG_IMAGE, contentType, src, uri, region); decodeImageBounds(uri); } private void initModelFromUri(Uri uri) throws MmsException { UriImage uriImage = new UriImage(mContext, uri); mContentType = uriImage.getContentType(); if (TextUtils.isEmpty(mContentType)) { throw new MmsException("Type of media is unknown."); } mSrc = uriImage.getSrc(); mWidth = uriImage.getWidth(); mHeight = uriImage.getHeight(); if (LOCAL_LOGV) { Log.v(TAG, "New ImageModel created:" + " mSrc=" + mSrc + " mContentType=" + mContentType + " mUri=" + uri); } } private void decodeImageBounds(Uri uri) { UriImage uriImage = new UriImage(mContext, uri); mWidth = uriImage.getWidth(); mHeight = uriImage.getHeight(); if (LOCAL_LOGV) { Log.v(TAG, "Image bounds: " + mWidth + "x" + mHeight); } } // EventListener Interface @Override public void handleEvent(Event evt) { if (evt.getType().equals(SmilMediaElementImpl.SMIL_MEDIA_START_EVENT)) { mVisible = true; } else if (mFill != ElementTime.FILL_FREEZE) { mVisible = false; } notifyModelChanged(false); } public int getWidth() { return mWidth; } public int getHeight() { return mHeight; } protected void checkContentRestriction() throws ContentRestrictionException { ContentRestriction cr = ContentRestrictionFactory.getContentRestriction(); cr.checkImageContentType(mContentType); } public ItemLoadedFuture loadThumbnailBitmap(ItemLoadedCallback callback) { ThumbnailManager thumbnailManager = MmsApp.getApplication().getThumbnailManager(); mItemLoadedFuture = thumbnailManager.getThumbnail(getUri(), callback); return mItemLoadedFuture; } public void cancelThumbnailLoading() { if (mItemLoadedFuture != null && !mItemLoadedFuture.isDone()) { if (Log.isLoggable(LogTag.APP, Log.DEBUG)) { Log.v(TAG, "cancelThumbnailLoading for: " + this); } mItemLoadedFuture.cancel(getUri()); mItemLoadedFuture = null; } } private Bitmap createBitmap(int thumbnailBoundsLimit, Uri uri) { UriImage image = new UriImage(mContext, uri); byte[] data = image.getResizedImageData(mWidth, mHeight, image.getOrientation(), thumbnailBoundsLimit, thumbnailBoundsLimit, PICTURE_SIZE_LIMIT, uri, mContext); if (LOCAL_LOGV) { Log.v(TAG, "createBitmap size: " + (data == null ? data : data.length)); } return data == null ? null : BitmapFactory.decodeByteArray(data, 0, data.length); } public Bitmap getBitmap(int width, int height) { Bitmap bm = mFullSizeBitmapCache.get(); if (bm == null) { try { bm = createBitmap(Math.max(width, height), getUri()); if (bm != null) { mFullSizeBitmapCache = new SoftReference<Bitmap>(bm); } } catch (OutOfMemoryError ex) { // fall through and return a null bitmap. The callers can handle a null // result and show R.drawable.ic_missing_thumbnail_picture } } return bm; } @Override public boolean getMediaResizable() { return true; } @Override protected void resizeMedia(int byteLimit, long messageId) throws MmsException { UriImage image = new UriImage(mContext, getUri()); int widthLimit = MmsConfig.getMaxImageWidth(); int heightLimit = MmsConfig.getMaxImageHeight(); int size = getMediaSize(); // In mms_config.xml, the max width has always been declared larger than the max height. // Swap the width and height limits if necessary so we scale the picture as little as // possible. if (image.getHeight() > image.getWidth()) { int temp = widthLimit; widthLimit = heightLimit; heightLimit = temp; } if (Log.isLoggable(LogTag.APP, Log.VERBOSE)) { Log.v(TAG, "resizeMedia size: " + size + " image.getWidth(): " + image.getWidth() + " widthLimit: " + widthLimit + " image.getHeight(): " + image.getHeight() + " heightLimit: " + heightLimit + " image.getContentType(): " + image.getContentType()); } // Check if we're already within the limits - in which case we don't need to resize. // The size can be zero here, even when the media has content. See the comment in // MediaModel.initMediaSize. Sometimes it'll compute zero and it's costly to read the // whole stream to compute the size. When we call getResizedImageAsPart(), we'll correctly // set the size. if (size != 0 && size <= byteLimit && image.getOrientation() == 0 && image.getWidth() <= widthLimit && image.getHeight() <= heightLimit && SUPPORTED_MMS_IMAGE_CONTENT_TYPES.contains(image.getContentType())) { if (Log.isLoggable(LogTag.APP, Log.VERBOSE)) { Log.v(TAG, "resizeMedia - already sized"); } return; } PduPart part = image.getResizedImageAsPart( widthLimit, heightLimit, byteLimit); if (part == null) { throw new ExceedMessageSizeException("Not enough memory to turn image into part: " + getUri()); } // Update the content type because it may have changed due to resizing/recompressing mContentType = new String(part.getContentType()); String src = getSrc(); byte[] srcBytes = src.getBytes(); part.setContentLocation(srcBytes); int period = src.lastIndexOf("."); byte[] contentId = period != -1 ? src.substring(0, period).getBytes() : srcBytes; part.setContentId(contentId); PduPersister persister = PduPersister.getPduPersister(mContext); this.mSize = part.getData().length; if (Log.isLoggable(LogTag.APP, Log.VERBOSE)) { Log.v(TAG, "resizeMedia mSize: " + mSize); } Uri newUri = persister.persistPart(part, messageId, null); setUri(newUri); } }