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