• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
ImageModel(Context context, Uri uri, RegionModel region)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 
ImageModel(Context context, String contentType, String src, Uri uri, RegionModel region)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 
ImageModel(Context context, String contentType, String src, DrmWrapper wrapper, RegionModel regionModel)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 
initModelFromUri(Uri uri)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 
decodeImageBounds()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
handleEvent(Event evt)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 
getWidth()136     public int getWidth() {
137         return mWidth;
138     }
139 
getHeight()140     public int getHeight() {
141         return mHeight;
142     }
143 
checkContentRestriction()144     protected void checkContentRestriction() throws ContentRestrictionException {
145         ContentRestriction cr = ContentRestrictionFactory.getContentRestriction();
146         cr.checkImageContentType(mContentType);
147     }
148 
getBitmap()149     public Bitmap getBitmap() {
150         return internalGetBitmap(getUri());
151     }
152 
getBitmapWithDrmCheck()153     public Bitmap getBitmapWithDrmCheck() throws DrmException {
154         return internalGetBitmap(getUriWithDrmCheck());
155     }
156 
internalGetBitmap(Uri uri)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 
createThumbnailBitmap(int thumbnailBoundsLimit, Uri uri)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
getMediaResizable()213     public boolean getMediaResizable() {
214         return true;
215     }
216 
217     @Override
resizeMedia(int byteLimit, long messageId)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