• 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 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 
ImageModel(Context context, Uri uri, RegionModel region)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 
ImageModel(Context context, String contentType, String src, Uri uri, RegionModel region)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 
initModelFromUri(Uri uri)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 
decodeImageBounds(Uri uri)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
handleEvent(Event evt)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 
getWidth()126     public int getWidth() {
127         return mWidth;
128     }
129 
getHeight()130     public int getHeight() {
131         return mHeight;
132     }
133 
checkContentRestriction()134     protected void checkContentRestriction() throws ContentRestrictionException {
135         ContentRestriction cr = ContentRestrictionFactory.getContentRestriction();
136         cr.checkImageContentType(mContentType);
137     }
138 
loadThumbnailBitmap(ItemLoadedCallback callback)139     public ItemLoadedFuture loadThumbnailBitmap(ItemLoadedCallback callback) {
140         ThumbnailManager thumbnailManager = MmsApp.getApplication().getThumbnailManager();
141         mItemLoadedFuture = thumbnailManager.getThumbnail(getUri(), callback);
142         return mItemLoadedFuture;
143     }
144 
cancelThumbnailLoading()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 
createBitmap(int thumbnailBoundsLimit, Uri uri)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 
getBitmap(int width, int height)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
getMediaResizable()181     public boolean getMediaResizable() {
182         return true;
183     }
184 
185     @Override
resizeMedia(int byteLimit, long messageId)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