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 
21 import java.io.ByteArrayOutputStream;
22 import java.io.FileNotFoundException;
23 import java.io.IOException;
24 import java.io.InputStream;
25 import java.util.ArrayList;
26 import java.util.Collection;
27 import java.util.HashMap;
28 import java.util.Iterator;
29 import java.util.List;
30 import java.util.ListIterator;
31 
32 import org.w3c.dom.NodeList;
33 import org.w3c.dom.events.EventTarget;
34 import org.w3c.dom.smil.SMILDocument;
35 import org.w3c.dom.smil.SMILElement;
36 import org.w3c.dom.smil.SMILLayoutElement;
37 import org.w3c.dom.smil.SMILMediaElement;
38 import org.w3c.dom.smil.SMILParElement;
39 import org.w3c.dom.smil.SMILRegionElement;
40 import org.w3c.dom.smil.SMILRootLayoutElement;
41 
42 import android.content.ContentResolver;
43 import android.content.ContentUris;
44 import android.content.Context;
45 import android.net.Uri;
46 import android.text.TextUtils;
47 import android.util.Log;
48 
49 import com.android.mms.ContentRestrictionException;
50 import com.android.mms.ExceedMessageSizeException;
51 import com.android.mms.LogTag;
52 import com.android.mms.MmsConfig;
53 import com.android.mms.dom.smil.parser.SmilXmlSerializer;
54 import com.android.mms.layout.LayoutManager;
55 import com.google.android.mms.ContentType;
56 import com.google.android.mms.MmsException;
57 import com.google.android.mms.pdu.GenericPdu;
58 import com.google.android.mms.pdu.MultimediaMessagePdu;
59 import com.google.android.mms.pdu.PduBody;
60 import com.google.android.mms.pdu.PduHeaders;
61 import com.google.android.mms.pdu.PduPart;
62 import com.google.android.mms.pdu.PduPersister;
63 import com.android.mms.UnsupportContentTypeException;
64 
65 public class SlideshowModel extends Model
66         implements List<SlideModel>, IModelChangedObserver {
67     private static final String TAG = LogTag.TAG;
68 
69     private final LayoutModel mLayout;
70     private final ArrayList<SlideModel> mSlides;
71     private SMILDocument mDocumentCache;
72     private PduBody mPduBodyCache;
73     private int mCurrentMessageSize;    // This is the current message size, not including
74                                         // attachments that can be resized (such as photos)
75     private int mTotalMessageSize;      // This is the computed total message size
76     private Context mContext;
77 
78     // amount of space to leave in a slideshow for text and overhead.
79     public static final int SLIDESHOW_SLOP = 1024;
80 
SlideshowModel(Context context)81     private SlideshowModel(Context context) {
82         mLayout = new LayoutModel();
83         mSlides = new ArrayList<SlideModel>();
84         mContext = context;
85     }
86 
SlideshowModel( LayoutModel layouts, ArrayList<SlideModel> slides, SMILDocument documentCache, PduBody pbCache, Context context)87     private SlideshowModel (
88             LayoutModel layouts, ArrayList<SlideModel> slides,
89             SMILDocument documentCache, PduBody pbCache,
90             Context context) {
91         mLayout = layouts;
92         mSlides = slides;
93         mContext = context;
94 
95         mDocumentCache = documentCache;
96         mPduBodyCache = pbCache;
97         for (SlideModel slide : mSlides) {
98             increaseMessageSize(slide.getSlideSize());
99             slide.setParent(this);
100         }
101     }
102 
createNew(Context context)103     public static SlideshowModel createNew(Context context) {
104         return new SlideshowModel(context);
105     }
106 
createFromMessageUri( Context context, Uri uri)107     public static SlideshowModel createFromMessageUri(
108             Context context, Uri uri) throws MmsException {
109         return createFromPduBody(context, getPduBody(context, uri));
110     }
111 
createFromPduBody(Context context, PduBody pb)112     public static SlideshowModel createFromPduBody(Context context, PduBody pb) throws MmsException {
113         SMILDocument document = SmilHelper.getDocument(pb);
114 
115         // Create root-layout model.
116         SMILLayoutElement sle = document.getLayout();
117         SMILRootLayoutElement srle = sle.getRootLayout();
118         int w = srle.getWidth();
119         int h = srle.getHeight();
120         if ((w == 0) || (h == 0)) {
121             w = LayoutManager.getInstance().getLayoutParameters().getWidth();
122             h = LayoutManager.getInstance().getLayoutParameters().getHeight();
123             srle.setWidth(w);
124             srle.setHeight(h);
125         }
126         RegionModel rootLayout = new RegionModel(
127                 null, 0, 0, w, h);
128 
129         // Create region models.
130         ArrayList<RegionModel> regions = new ArrayList<RegionModel>();
131         NodeList nlRegions = sle.getRegions();
132         int regionsNum = nlRegions.getLength();
133 
134         for (int i = 0; i < regionsNum; i++) {
135             SMILRegionElement sre = (SMILRegionElement) nlRegions.item(i);
136             RegionModel r = new RegionModel(sre.getId(), sre.getFit(),
137                     sre.getLeft(), sre.getTop(), sre.getWidth(), sre.getHeight(),
138                     sre.getBackgroundColor());
139             regions.add(r);
140         }
141         LayoutModel layouts = new LayoutModel(rootLayout, regions);
142 
143         // Create slide models.
144         SMILElement docBody = document.getBody();
145         NodeList slideNodes = docBody.getChildNodes();
146         int slidesNum = slideNodes.getLength();
147         ArrayList<SlideModel> slides = new ArrayList<SlideModel>(slidesNum);
148         int totalMessageSize = 0;
149 
150         for (int i = 0; i < slidesNum; i++) {
151             // FIXME: This is NOT compatible with the SMILDocument which is
152             // generated by some other mobile phones.
153             SMILParElement par = (SMILParElement) slideNodes.item(i);
154 
155             // Create media models for each slide.
156             NodeList mediaNodes = par.getChildNodes();
157             int mediaNum = mediaNodes.getLength();
158             ArrayList<MediaModel> mediaSet = new ArrayList<MediaModel>(mediaNum);
159 
160             for (int j = 0; j < mediaNum; j++) {
161                 SMILMediaElement sme = (SMILMediaElement) mediaNodes.item(j);
162                 try {
163                     MediaModel media = MediaModelFactory.getMediaModel(
164                             context, sme, layouts, pb);
165 
166                     /*
167                     * This is for slide duration value set.
168                     * If mms server does not support slide duration.
169                     */
170                     if (!MmsConfig.getSlideDurationEnabled()) {
171                         int mediadur = media.getDuration();
172                         float dur = par.getDur();
173                         if (dur == 0) {
174                             mediadur = MmsConfig.getMinimumSlideElementDuration() * 1000;
175                             media.setDuration(mediadur);
176                         }
177 
178                         if ((int)mediadur / 1000 != dur) {
179                             String tag = sme.getTagName();
180 
181                             if (ContentType.isVideoType(media.mContentType)
182                               || tag.equals(SmilHelper.ELEMENT_TAG_VIDEO)
183                               || ContentType.isAudioType(media.mContentType)
184                               || tag.equals(SmilHelper.ELEMENT_TAG_AUDIO)) {
185                                 /*
186                                 * add 1 sec to release and close audio/video
187                                 * for guaranteeing the audio/video playing.
188                                 * because the mmsc does not support the slide duration.
189                                 */
190                                 par.setDur((float)mediadur / 1000 + 1);
191                             } else {
192                                 /*
193                                 * If a slide has an image and an audio/video element
194                                 * and the audio/video element has longer duration than the image,
195                                 * The Image disappear before the slide play done. so have to match
196                                 * an image duration to the slide duration.
197                                 */
198                                 if ((int)mediadur / 1000 < dur) {
199                                     media.setDuration((int)dur * 1000);
200                                 } else {
201                                     if ((int)dur != 0) {
202                                         media.setDuration((int)dur * 1000);
203                                     } else {
204                                         par.setDur((float)mediadur / 1000);
205                                     }
206                                 }
207                             }
208                         }
209                     }
210                     SmilHelper.addMediaElementEventListeners(
211                             (EventTarget) sme, media);
212                     mediaSet.add(media);
213                     totalMessageSize += media.getMediaSize();
214                 } catch (IOException e) {
215                     Log.e(TAG, e.getMessage(), e);
216                 } catch (IllegalArgumentException e) {
217                     Log.e(TAG, e.getMessage(), e);
218                 } catch (UnsupportContentTypeException e) {
219                     Log.e(TAG, e.getMessage(), e);
220                 }
221             }
222 
223             SlideModel slide = new SlideModel((int) (par.getDur() * 1000), mediaSet);
224             slide.setFill(par.getFill());
225             SmilHelper.addParElementEventListeners((EventTarget) par, slide);
226             slides.add(slide);
227         }
228 
229         SlideshowModel slideshow = new SlideshowModel(layouts, slides, document, pb, context);
230         slideshow.mTotalMessageSize = totalMessageSize;
231         slideshow.registerModelChangedObserver(slideshow);
232         return slideshow;
233     }
234 
toPduBody()235     public PduBody toPduBody() {
236         if (mPduBodyCache == null) {
237             mDocumentCache = SmilHelper.getDocument(this);
238             mPduBodyCache = makePduBody(mDocumentCache);
239         }
240         return mPduBodyCache;
241     }
242 
makePduBody(SMILDocument document)243     private PduBody makePduBody(SMILDocument document) {
244         PduBody pb = new PduBody();
245 
246         boolean hasForwardLock = false;
247         for (SlideModel slide : mSlides) {
248             for (MediaModel media : slide) {
249                 PduPart part = new PduPart();
250 
251                 if (media.isText()) {
252                     TextModel text = (TextModel) media;
253                     // Don't create empty text part.
254                     if (TextUtils.isEmpty(text.getText())) {
255                         continue;
256                     }
257                     // Set Charset if it's a text media.
258                     part.setCharset(text.getCharset());
259                 }
260 
261                 // Set Content-Type.
262                 part.setContentType(media.getContentType().getBytes());
263 
264                 String src = media.getSrc();
265                 String location;
266                 boolean startWithContentId = src.startsWith("cid:");
267                 if (startWithContentId) {
268                     location = src.substring("cid:".length());
269                 } else {
270                     location = src;
271                 }
272 
273                 // Set Content-Location.
274                 part.setContentLocation(location.getBytes());
275 
276                 // Set Content-Id.
277                 if (startWithContentId) {
278                     //Keep the original Content-Id.
279                     part.setContentId(location.getBytes());
280                 }
281                 else {
282                     int index = location.lastIndexOf(".");
283                     String contentId = (index == -1) ? location
284                             : location.substring(0, index);
285                     part.setContentId(contentId.getBytes());
286                 }
287 
288                 if (media.isText()) {
289                     part.setData(((TextModel) media).getText().getBytes());
290                 } else if (media.isImage() || media.isVideo() || media.isAudio()) {
291                     part.setDataUri(media.getUri());
292                 } else {
293                     Log.w(TAG, "Unsupport media: " + media);
294                 }
295 
296                 pb.addPart(part);
297             }
298         }
299 
300         // Create and insert SMIL part(as the first part) into the PduBody.
301         ByteArrayOutputStream out = new ByteArrayOutputStream();
302         SmilXmlSerializer.serialize(document, out);
303         PduPart smilPart = new PduPart();
304         smilPart.setContentId("smil".getBytes());
305         smilPart.setContentLocation("smil.xml".getBytes());
306         smilPart.setContentType(ContentType.APP_SMIL.getBytes());
307         smilPart.setData(out.toByteArray());
308         pb.addPart(0, smilPart);
309 
310         return pb;
311     }
312 
openPartFiles(ContentResolver cr)313     public HashMap<Uri, InputStream> openPartFiles(ContentResolver cr) {
314         HashMap<Uri, InputStream> openedFiles = null;     // Don't create unless we have to
315 
316         for (SlideModel slide : mSlides) {
317             for (MediaModel media : slide) {
318                 if (media.isText()) {
319                     continue;
320                 }
321                 Uri uri = media.getUri();
322                 InputStream is;
323                 try {
324                     is = cr.openInputStream(uri);
325                     if (is != null) {
326                         if (openedFiles == null) {
327                             openedFiles = new HashMap<Uri, InputStream>();
328                         }
329                         openedFiles.put(uri, is);
330                     }
331                 } catch (FileNotFoundException e) {
332                     Log.e(TAG, "openPartFiles couldn't open: " + uri, e);
333                 }
334             }
335         }
336         return openedFiles;
337     }
338 
makeCopy()339     public PduBody makeCopy() {
340         return makePduBody(SmilHelper.getDocument(this));
341     }
342 
toSmilDocument()343     public SMILDocument toSmilDocument() {
344         if (mDocumentCache == null) {
345             mDocumentCache = SmilHelper.getDocument(this);
346         }
347         return mDocumentCache;
348     }
349 
getPduBody(Context context, Uri msg)350     public static PduBody getPduBody(Context context, Uri msg) throws MmsException {
351         PduPersister p = PduPersister.getPduPersister(context);
352         GenericPdu pdu = p.load(msg);
353 
354         int msgType = pdu.getMessageType();
355         if ((msgType == PduHeaders.MESSAGE_TYPE_SEND_REQ)
356                 || (msgType == PduHeaders.MESSAGE_TYPE_RETRIEVE_CONF)) {
357             return ((MultimediaMessagePdu) pdu).getBody();
358         } else {
359             throw new MmsException();
360         }
361     }
362 
setCurrentMessageSize(int size)363     public void setCurrentMessageSize(int size) {
364         mCurrentMessageSize = size;
365     }
366 
367     // getCurrentMessageSize returns the size of the message, not including resizable attachments
368     // such as photos. mCurrentMessageSize is used when adding/deleting/replacing non-resizable
369     // attachments (movies, sounds, etc) in order to compute how much size is left in the message.
370     // The difference between mCurrentMessageSize and the maxSize allowed for a message is then
371     // divided up between the remaining resizable attachments. While this function is public,
372     // it is only used internally between various MMS classes. If the UI wants to know the
373     // size of a MMS message, it should call getTotalMessageSize() instead.
getCurrentMessageSize()374     public int getCurrentMessageSize() {
375         return mCurrentMessageSize;
376     }
377 
378     // getTotalMessageSize returns the total size of the message, including resizable attachments
379     // such as photos. This function is intended to be used by the UI for displaying the size of the
380     // MMS message.
getTotalMessageSize()381     public int getTotalMessageSize() {
382         return mTotalMessageSize;
383     }
384 
increaseMessageSize(int increaseSize)385     public void increaseMessageSize(int increaseSize) {
386         if (increaseSize > 0) {
387             mCurrentMessageSize += increaseSize;
388         }
389     }
390 
decreaseMessageSize(int decreaseSize)391     public void decreaseMessageSize(int decreaseSize) {
392         if (decreaseSize > 0) {
393             mCurrentMessageSize -= decreaseSize;
394         }
395     }
396 
getLayout()397     public LayoutModel getLayout() {
398         return mLayout;
399     }
400 
401     //
402     // Implement List<E> interface.
403     //
add(SlideModel object)404     public boolean add(SlideModel object) {
405         int increaseSize = object.getSlideSize();
406         checkMessageSize(increaseSize);
407 
408         if ((object != null) && mSlides.add(object)) {
409             increaseMessageSize(increaseSize);
410             object.registerModelChangedObserver(this);
411             for (IModelChangedObserver observer : mModelChangedObservers) {
412                 object.registerModelChangedObserver(observer);
413             }
414             notifyModelChanged(true);
415             return true;
416         }
417         return false;
418     }
419 
addAll(Collection<? extends SlideModel> collection)420     public boolean addAll(Collection<? extends SlideModel> collection) {
421         throw new UnsupportedOperationException("Operation not supported.");
422     }
423 
clear()424     public void clear() {
425         if (mSlides.size() > 0) {
426             for (SlideModel slide : mSlides) {
427                 slide.unregisterModelChangedObserver(this);
428                 for (IModelChangedObserver observer : mModelChangedObservers) {
429                     slide.unregisterModelChangedObserver(observer);
430                 }
431             }
432             mCurrentMessageSize = 0;
433             mSlides.clear();
434             notifyModelChanged(true);
435         }
436     }
437 
contains(Object object)438     public boolean contains(Object object) {
439         return mSlides.contains(object);
440     }
441 
containsAll(Collection<?> collection)442     public boolean containsAll(Collection<?> collection) {
443         return mSlides.containsAll(collection);
444     }
445 
isEmpty()446     public boolean isEmpty() {
447         return mSlides.isEmpty();
448     }
449 
iterator()450     public Iterator<SlideModel> iterator() {
451         return mSlides.iterator();
452     }
453 
remove(Object object)454     public boolean remove(Object object) {
455         if ((object != null) && mSlides.remove(object)) {
456             SlideModel slide = (SlideModel) object;
457             decreaseMessageSize(slide.getSlideSize());
458             slide.unregisterAllModelChangedObservers();
459             notifyModelChanged(true);
460             return true;
461         }
462         return false;
463     }
464 
removeAll(Collection<?> collection)465     public boolean removeAll(Collection<?> collection) {
466         throw new UnsupportedOperationException("Operation not supported.");
467     }
468 
retainAll(Collection<?> collection)469     public boolean retainAll(Collection<?> collection) {
470         throw new UnsupportedOperationException("Operation not supported.");
471     }
472 
size()473     public int size() {
474         return mSlides.size();
475     }
476 
toArray()477     public Object[] toArray() {
478         return mSlides.toArray();
479     }
480 
toArray(T[] array)481     public <T> T[] toArray(T[] array) {
482         return mSlides.toArray(array);
483     }
484 
add(int location, SlideModel object)485     public void add(int location, SlideModel object) {
486         if (object != null) {
487             int increaseSize = object.getSlideSize();
488             checkMessageSize(increaseSize);
489 
490             mSlides.add(location, object);
491             increaseMessageSize(increaseSize);
492             object.registerModelChangedObserver(this);
493             for (IModelChangedObserver observer : mModelChangedObservers) {
494                 object.registerModelChangedObserver(observer);
495             }
496             notifyModelChanged(true);
497         }
498     }
499 
addAll(int location, Collection<? extends SlideModel> collection)500     public boolean addAll(int location,
501             Collection<? extends SlideModel> collection) {
502         throw new UnsupportedOperationException("Operation not supported.");
503     }
504 
get(int location)505     public SlideModel get(int location) {
506         return (location >= 0 && location < mSlides.size()) ? mSlides.get(location) : null;
507     }
508 
indexOf(Object object)509     public int indexOf(Object object) {
510         return mSlides.indexOf(object);
511     }
512 
lastIndexOf(Object object)513     public int lastIndexOf(Object object) {
514         return mSlides.lastIndexOf(object);
515     }
516 
listIterator()517     public ListIterator<SlideModel> listIterator() {
518         return mSlides.listIterator();
519     }
520 
listIterator(int location)521     public ListIterator<SlideModel> listIterator(int location) {
522         return mSlides.listIterator(location);
523     }
524 
remove(int location)525     public SlideModel remove(int location) {
526         SlideModel slide = mSlides.remove(location);
527         if (slide != null) {
528             decreaseMessageSize(slide.getSlideSize());
529             slide.unregisterAllModelChangedObservers();
530             notifyModelChanged(true);
531         }
532         return slide;
533     }
534 
set(int location, SlideModel object)535     public SlideModel set(int location, SlideModel object) {
536         SlideModel slide = mSlides.get(location);
537         if (null != object) {
538             int removeSize = 0;
539             int addSize = object.getSlideSize();
540             if (null != slide) {
541                 removeSize = slide.getSlideSize();
542             }
543             if (addSize > removeSize) {
544                 checkMessageSize(addSize - removeSize);
545                 increaseMessageSize(addSize - removeSize);
546             } else {
547                 decreaseMessageSize(removeSize - addSize);
548             }
549         }
550 
551         slide =  mSlides.set(location, object);
552         if (slide != null) {
553             slide.unregisterAllModelChangedObservers();
554         }
555 
556         if (object != null) {
557             object.registerModelChangedObserver(this);
558             for (IModelChangedObserver observer : mModelChangedObservers) {
559                 object.registerModelChangedObserver(observer);
560             }
561         }
562 
563         notifyModelChanged(true);
564         return slide;
565     }
566 
subList(int start, int end)567     public List<SlideModel> subList(int start, int end) {
568         return mSlides.subList(start, end);
569     }
570 
571     @Override
registerModelChangedObserverInDescendants( IModelChangedObserver observer)572     protected void registerModelChangedObserverInDescendants(
573             IModelChangedObserver observer) {
574         mLayout.registerModelChangedObserver(observer);
575 
576         for (SlideModel slide : mSlides) {
577             slide.registerModelChangedObserver(observer);
578         }
579     }
580 
581     @Override
unregisterModelChangedObserverInDescendants( IModelChangedObserver observer)582     protected void unregisterModelChangedObserverInDescendants(
583             IModelChangedObserver observer) {
584         mLayout.unregisterModelChangedObserver(observer);
585 
586         for (SlideModel slide : mSlides) {
587             slide.unregisterModelChangedObserver(observer);
588         }
589     }
590 
591     @Override
unregisterAllModelChangedObserversInDescendants()592     protected void unregisterAllModelChangedObserversInDescendants() {
593         mLayout.unregisterAllModelChangedObservers();
594 
595         for (SlideModel slide : mSlides) {
596             slide.unregisterAllModelChangedObservers();
597         }
598     }
599 
onModelChanged(Model model, boolean dataChanged)600     public void onModelChanged(Model model, boolean dataChanged) {
601         if (dataChanged) {
602             mDocumentCache = null;
603             mPduBodyCache = null;
604         }
605     }
606 
sync(PduBody pb)607     public void sync(PduBody pb) {
608         for (SlideModel slide : mSlides) {
609             for (MediaModel media : slide) {
610                 PduPart part = pb.getPartByContentLocation(media.getSrc());
611                 if (part != null) {
612                     media.setUri(part.getDataUri());
613                 }
614             }
615         }
616     }
617 
checkMessageSize(int increaseSize)618     public void checkMessageSize(int increaseSize) throws ContentRestrictionException {
619         ContentRestriction cr = ContentRestrictionFactory.getContentRestriction();
620         cr.checkMessageSize(mCurrentMessageSize, increaseSize, mContext.getContentResolver());
621     }
622 
623     /**
624      * Determines whether this is a "simple" slideshow.
625      * Criteria:
626      * - Exactly one slide
627      * - Exactly one multimedia attachment, but no audio
628      * - It can optionally have a caption
629     */
isSimple()630     public boolean isSimple() {
631         // There must be one (and only one) slide.
632         if (size() != 1)
633             return false;
634 
635         SlideModel slide = get(0);
636         // The slide must have either an image or video, but not both.
637         if (!(slide.hasImage() ^ slide.hasVideo()))
638             return false;
639 
640         // No audio allowed.
641         if (slide.hasAudio())
642             return false;
643 
644         return true;
645     }
646 
647     /**
648      * Make sure the text in slide 0 is no longer holding onto a reference to the text
649      * in the message text box.
650      */
prepareForSend()651     public void prepareForSend() {
652         if (size() == 1) {
653             TextModel text = get(0).getText();
654             if (text != null) {
655                 text.cloneText();
656             }
657         }
658     }
659 
660     /**
661      * Resize all the resizeable media objects to fit in the remaining size of the slideshow.
662      * This should be called off of the UI thread.
663      *
664      * @throws MmsException, ExceedMessageSizeException
665      */
finalResize(Uri messageUri)666     public void finalResize(Uri messageUri) throws MmsException, ExceedMessageSizeException {
667 
668         // Figure out if we have any media items that need to be resized and total up the
669         // sizes of the items that can't be resized.
670         int resizableCnt = 0;
671         int fixedSizeTotal = 0;
672         for (SlideModel slide : mSlides) {
673             for (MediaModel media : slide) {
674                 if (media.getMediaResizable()) {
675                     ++resizableCnt;
676                 } else {
677                     fixedSizeTotal += media.getMediaSize();
678                 }
679             }
680         }
681         if (Log.isLoggable(LogTag.APP, Log.VERBOSE)) {
682             Log.v(TAG, "finalResize: original message size: " + getCurrentMessageSize() +
683                     " getMaxMessageSize: " + MmsConfig.getMaxMessageSize() +
684                     " fixedSizeTotal: " + fixedSizeTotal);
685         }
686         if (resizableCnt > 0) {
687             int remainingSize = MmsConfig.getMaxMessageSize() - fixedSizeTotal - SLIDESHOW_SLOP;
688             if (remainingSize <= 0) {
689                 throw new ExceedMessageSizeException("No room for pictures");
690             }
691             long messageId = ContentUris.parseId(messageUri);
692             int bytesPerMediaItem = remainingSize / resizableCnt;
693             // Resize the resizable media items to fit within their byte limit.
694             for (SlideModel slide : mSlides) {
695                 for (MediaModel media : slide) {
696                     if (media.getMediaResizable()) {
697                         media.resizeMedia(bytesPerMediaItem, messageId);
698                     }
699                 }
700             }
701             // One last time through to calc the real message size.
702             int totalSize = 0;
703             for (SlideModel slide : mSlides) {
704                 for (MediaModel media : slide) {
705                     totalSize += media.getMediaSize();
706                 }
707             }
708             if (Log.isLoggable(LogTag.APP, Log.VERBOSE)) {
709                 Log.v(TAG, "finalResize: new message size: " + totalSize);
710             }
711 
712             if (totalSize > MmsConfig.getMaxMessageSize()) {
713                 throw new ExceedMessageSizeException("After compressing pictures, message too big");
714             }
715             setCurrentMessageSize(totalSize);
716 
717             onModelChanged(this, true);     // clear the cached pdu body
718             PduBody pb = toPduBody();
719             // This will write out all the new parts to:
720             //      /data/data/com.android.providers.telephony/app_parts
721             // and at the same time delete the old parts.
722             PduPersister.getPduPersister(mContext).updateParts(messageUri, pb, null);
723         }
724     }
725 
726 }
727