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