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