• 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 
21 import com.android.mms.ContentRestrictionException;
22 import com.android.mms.R;
23 import com.android.mms.dom.smil.parser.SmilXmlSerializer;
24 import com.android.mms.drm.DrmWrapper;
25 import com.android.mms.layout.LayoutManager;
26 import com.google.android.mms.ContentType;
27 import com.google.android.mms.MmsException;
28 import com.google.android.mms.pdu.GenericPdu;
29 import com.google.android.mms.pdu.MultimediaMessagePdu;
30 import com.google.android.mms.pdu.PduBody;
31 import com.google.android.mms.pdu.PduHeaders;
32 import com.google.android.mms.pdu.PduPart;
33 import com.google.android.mms.pdu.PduPersister;
34 
35 import org.w3c.dom.NodeList;
36 import org.w3c.dom.events.EventTarget;
37 import org.w3c.dom.smil.SMILDocument;
38 import org.w3c.dom.smil.SMILElement;
39 import org.w3c.dom.smil.SMILLayoutElement;
40 import org.w3c.dom.smil.SMILMediaElement;
41 import org.w3c.dom.smil.SMILParElement;
42 import org.w3c.dom.smil.SMILRegionElement;
43 import org.w3c.dom.smil.SMILRootLayoutElement;
44 
45 import android.content.ContentResolver;
46 import android.content.Context;
47 import android.drm.mobile1.DrmException;
48 import android.net.Uri;
49 import android.text.TextUtils;
50 import android.util.Log;
51 import android.widget.Toast;
52 
53 import java.io.ByteArrayOutputStream;
54 import java.io.IOException;
55 import java.util.ArrayList;
56 import java.util.Collection;
57 import java.util.Iterator;
58 import java.util.List;
59 import java.util.ListIterator;
60 
61 public class SlideshowModel extends Model
62         implements List<SlideModel>, IModelChangedObserver {
63     private static final String TAG = "SlideshowModel";
64 
65     private final LayoutModel mLayout;
66     private final ArrayList<SlideModel> mSlides;
67     private SMILDocument mDocumentCache;
68     private PduBody mPduBodyCache;
69     private int mCurrentMessageSize;
70     private ContentResolver mContentResolver;
71 
SlideshowModel(ContentResolver contentResolver)72     private SlideshowModel(ContentResolver contentResolver) {
73         mLayout = new LayoutModel();
74         mSlides = new ArrayList<SlideModel>();
75         mContentResolver = contentResolver;
76     }
77 
SlideshowModel( LayoutModel layouts, ArrayList<SlideModel> slides, SMILDocument documentCache, PduBody pbCache, ContentResolver contentResolver)78     private SlideshowModel (
79             LayoutModel layouts, ArrayList<SlideModel> slides,
80             SMILDocument documentCache, PduBody pbCache,
81             ContentResolver contentResolver) {
82         mLayout = layouts;
83         mSlides = slides;
84         mContentResolver = contentResolver;
85 
86         mDocumentCache = documentCache;
87         mPduBodyCache = pbCache;
88         for (SlideModel slide : mSlides) {
89             increaseMessageSize(slide.getSlideSize());
90             slide.setParent(this);
91         }
92     }
93 
createNew(Context context)94     public static SlideshowModel createNew(Context context) {
95         return new SlideshowModel(context.getContentResolver());
96     }
97 
createFromMessageUri( Context context, Uri uri)98     public static SlideshowModel createFromMessageUri(
99             Context context, Uri uri) throws MmsException {
100         return createFromPduBody(context, getPduBody(context, uri));
101     }
102 
createFromPduBody(Context context, PduBody pb)103     public static SlideshowModel createFromPduBody(Context context, PduBody pb) throws MmsException {
104         SMILDocument document = SmilHelper.getDocument(pb);
105 
106         // Create root-layout model.
107         SMILLayoutElement sle = document.getLayout();
108         SMILRootLayoutElement srle = sle.getRootLayout();
109         int w = srle.getWidth();
110         int h = srle.getHeight();
111         if ((w == 0) || (h == 0)) {
112             w = LayoutManager.getInstance().getLayoutParameters().getWidth();
113             h = LayoutManager.getInstance().getLayoutParameters().getHeight();
114             srle.setWidth(w);
115             srle.setHeight(h);
116         }
117         RegionModel rootLayout = new RegionModel(
118                 null, 0, 0, w, h);
119 
120         // Create region models.
121         ArrayList<RegionModel> regions = new ArrayList<RegionModel>();
122         NodeList nlRegions = sle.getRegions();
123         int regionsNum = nlRegions.getLength();
124 
125         for (int i = 0; i < regionsNum; i++) {
126             SMILRegionElement sre = (SMILRegionElement) nlRegions.item(i);
127             RegionModel r = new RegionModel(sre.getId(), sre.getFit(),
128                     sre.getLeft(), sre.getTop(), sre.getWidth(), sre.getHeight(),
129                     sre.getBackgroundColor());
130             regions.add(r);
131         }
132         LayoutModel layouts = new LayoutModel(rootLayout, regions);
133 
134         // Create slide models.
135         SMILElement docBody = document.getBody();
136         NodeList slideNodes = docBody.getChildNodes();
137         int slidesNum = slideNodes.getLength();
138         ArrayList<SlideModel> slides = new ArrayList<SlideModel>(slidesNum);
139 
140         for (int i = 0; i < slidesNum; i++) {
141             // FIXME: This is NOT compatible with the SMILDocument which is
142             // generated by some other mobile phones.
143             SMILParElement par = (SMILParElement) slideNodes.item(i);
144 
145             // Create media models for each slide.
146             NodeList mediaNodes = par.getChildNodes();
147             int mediaNum = mediaNodes.getLength();
148             ArrayList<MediaModel> mediaSet = new ArrayList<MediaModel>(mediaNum);
149 
150             for (int j = 0; j < mediaNum; j++) {
151                 SMILMediaElement sme = (SMILMediaElement) mediaNodes.item(j);
152                 try {
153                     MediaModel media = MediaModelFactory.getMediaModel(
154                             context, sme, layouts, pb);
155                     SmilHelper.addMediaElementEventListeners(
156                             (EventTarget) sme, media);
157                     mediaSet.add(media);
158                 } catch (DrmException e) {
159                     Log.e(TAG, e.getMessage(), e);
160                 } catch (IOException e) {
161                     Log.e(TAG, e.getMessage(), e);
162                 } catch (IllegalArgumentException e) {
163                     Log.e(TAG, e.getMessage(), e);
164                 }
165             }
166 
167             SlideModel slide = new SlideModel((int) (par.getDur() * 1000), mediaSet);
168             slide.setFill(par.getFill());
169             SmilHelper.addParElementEventListeners((EventTarget) par, slide);
170             slides.add(slide);
171         }
172 
173         SlideshowModel slideshow = new SlideshowModel(layouts, slides, document, pb,
174                 context.getContentResolver());
175         slideshow.registerModelChangedObserver(slideshow);
176         return slideshow;
177     }
178 
toPduBody()179     public PduBody toPduBody() {
180         if (mPduBodyCache == null) {
181             mDocumentCache = SmilHelper.getDocument(this);
182             mPduBodyCache = makePduBody(mDocumentCache);
183         }
184         return mPduBodyCache;
185     }
186 
makePduBody(SMILDocument document)187     private PduBody makePduBody(SMILDocument document) {
188         return makePduBody(null, document, false);
189     }
190 
makePduBody(Context context, SMILDocument document, boolean isMakingCopy)191     private PduBody makePduBody(Context context, SMILDocument document, boolean isMakingCopy) {
192         PduBody pb = new PduBody();
193 
194         boolean hasForwardLock = false;
195         for (SlideModel slide : mSlides) {
196             for (MediaModel media : slide) {
197                 if (isMakingCopy) {
198                     if (media.isDrmProtected() && !media.isAllowedToForward()) {
199                         hasForwardLock = true;
200                         continue;
201                     }
202                 }
203 
204                 PduPart part = new PduPart();
205 
206                 if (media.isText()) {
207                     TextModel text = (TextModel) media;
208                     // Don't create empty text part.
209                     if (TextUtils.isEmpty(text.getText())) {
210                         continue;
211                     }
212                     // Set Charset if it's a text media.
213                     part.setCharset(text.getCharset());
214                 }
215 
216                 // Set Content-Type.
217                 part.setContentType(media.getContentType().getBytes());
218 
219                 String src = media.getSrc();
220                 String location;
221                 boolean startWithContentId = src.startsWith("cid:");
222                 if (startWithContentId) {
223                     location = src.substring("cid:".length());
224                 } else {
225                     location = src;
226                 }
227 
228                 // Set Content-Location.
229                 part.setContentLocation(location.getBytes());
230 
231                 // Set Content-Id.
232                 if (startWithContentId) {
233                     //Keep the original Content-Id.
234                     part.setContentId(location.getBytes());
235                 }
236                 else {
237                     int index = location.lastIndexOf(".");
238                     String contentId = (index == -1) ? location
239                             : location.substring(0, index);
240                     part.setContentId(contentId.getBytes());
241                 }
242 
243                 if (media.isDrmProtected()) {
244                     DrmWrapper wrapper = media.getDrmObject();
245                     part.setDataUri(wrapper.getOriginalUri());
246                     part.setData(wrapper.getOriginalData());
247                 } else if (media.isText()) {
248                     part.setData(((TextModel) media).getText().getBytes());
249                 } else if (media.isImage() || media.isVideo() || media.isAudio()) {
250                     part.setDataUri(media.getUri());
251                 } else {
252                     Log.w(TAG, "Unsupport media: " + media);
253                 }
254 
255                 pb.addPart(part);
256             }
257         }
258 
259         if (hasForwardLock && isMakingCopy && context != null) {
260             Toast.makeText(context,
261                     context.getString(R.string.cannot_forward_drm_obj),
262                     Toast.LENGTH_LONG).show();
263             document = SmilHelper.getDocument(pb);
264         }
265 
266         // Create and insert SMIL part(as the first part) into the PduBody.
267         ByteArrayOutputStream out = new ByteArrayOutputStream();
268         SmilXmlSerializer.serialize(document, out);
269         PduPart smilPart = new PduPart();
270         smilPart.setContentId("smil".getBytes());
271         smilPart.setContentLocation("smil.xml".getBytes());
272         smilPart.setContentType(ContentType.APP_SMIL.getBytes());
273         smilPart.setData(out.toByteArray());
274         pb.addPart(0, smilPart);
275 
276         return pb;
277     }
278 
makeCopy(Context context)279     public PduBody makeCopy(Context context) {
280         return makePduBody(context, SmilHelper.getDocument(this), true);
281     }
282 
toSmilDocument()283     public SMILDocument toSmilDocument() {
284         if (mDocumentCache == null) {
285             mDocumentCache = SmilHelper.getDocument(this);
286         }
287         return mDocumentCache;
288     }
289 
getPduBody(Context context, Uri msg)290     public static PduBody getPduBody(Context context, Uri msg) throws MmsException {
291         PduPersister p = PduPersister.getPduPersister(context);
292         GenericPdu pdu = p.load(msg);
293 
294         int msgType = pdu.getMessageType();
295         if ((msgType == PduHeaders.MESSAGE_TYPE_SEND_REQ)
296                 || (msgType == PduHeaders.MESSAGE_TYPE_RETRIEVE_CONF)) {
297             return ((MultimediaMessagePdu) pdu).getBody();
298         } else {
299             throw new MmsException();
300         }
301     }
302 
setCurrentMessageSize(int size)303     public void setCurrentMessageSize(int size) {
304         mCurrentMessageSize = size;
305     }
306 
getCurrentMessageSize()307     public int getCurrentMessageSize() {
308         return mCurrentMessageSize;
309     }
310 
increaseMessageSize(int increaseSize)311     public void increaseMessageSize(int increaseSize) {
312         if (increaseSize > 0) {
313             mCurrentMessageSize += increaseSize;
314         }
315     }
316 
decreaseMessageSize(int decreaseSize)317     public void decreaseMessageSize(int decreaseSize) {
318         if (decreaseSize > 0) {
319             mCurrentMessageSize -= decreaseSize;
320         }
321     }
322 
getLayout()323     public LayoutModel getLayout() {
324         return mLayout;
325     }
326 
327     //
328     // Implement List<E> interface.
329     //
add(SlideModel object)330     public boolean add(SlideModel object) {
331         int increaseSize = object.getSlideSize();
332         checkMessageSize(increaseSize);
333 
334         if ((object != null) && mSlides.add(object)) {
335             increaseMessageSize(increaseSize);
336             object.registerModelChangedObserver(this);
337             for (IModelChangedObserver observer : mModelChangedObservers) {
338                 object.registerModelChangedObserver(observer);
339             }
340             notifyModelChanged(true);
341             return true;
342         }
343         return false;
344     }
345 
addAll(Collection<? extends SlideModel> collection)346     public boolean addAll(Collection<? extends SlideModel> collection) {
347         throw new UnsupportedOperationException("Operation not supported.");
348     }
349 
clear()350     public void clear() {
351         if (mSlides.size() > 0) {
352             for (SlideModel slide : mSlides) {
353                 slide.unregisterModelChangedObserver(this);
354                 for (IModelChangedObserver observer : mModelChangedObservers) {
355                     slide.unregisterModelChangedObserver(observer);
356                 }
357             }
358             mCurrentMessageSize = 0;
359             mSlides.clear();
360             notifyModelChanged(true);
361         }
362     }
363 
contains(Object object)364     public boolean contains(Object object) {
365         return mSlides.contains(object);
366     }
367 
containsAll(Collection<?> collection)368     public boolean containsAll(Collection<?> collection) {
369         return mSlides.containsAll(collection);
370     }
371 
isEmpty()372     public boolean isEmpty() {
373         return mSlides.isEmpty();
374     }
375 
iterator()376     public Iterator<SlideModel> iterator() {
377         return mSlides.iterator();
378     }
379 
remove(Object object)380     public boolean remove(Object object) {
381         if ((object != null) && mSlides.remove(object)) {
382             SlideModel slide = (SlideModel) object;
383             decreaseMessageSize(slide.getSlideSize());
384             slide.unregisterAllModelChangedObservers();
385             notifyModelChanged(true);
386             return true;
387         }
388         return false;
389     }
390 
removeAll(Collection<?> collection)391     public boolean removeAll(Collection<?> collection) {
392         throw new UnsupportedOperationException("Operation not supported.");
393     }
394 
retainAll(Collection<?> collection)395     public boolean retainAll(Collection<?> collection) {
396         throw new UnsupportedOperationException("Operation not supported.");
397     }
398 
size()399     public int size() {
400         return mSlides.size();
401     }
402 
toArray()403     public Object[] toArray() {
404         return mSlides.toArray();
405     }
406 
toArray(T[] array)407     public <T> T[] toArray(T[] array) {
408         return mSlides.toArray(array);
409     }
410 
add(int location, SlideModel object)411     public void add(int location, SlideModel object) {
412         if (object != null) {
413             int increaseSize = object.getSlideSize();
414             checkMessageSize(increaseSize);
415 
416             mSlides.add(location, object);
417             increaseMessageSize(increaseSize);
418             object.registerModelChangedObserver(this);
419             for (IModelChangedObserver observer : mModelChangedObservers) {
420                 object.registerModelChangedObserver(observer);
421             }
422             notifyModelChanged(true);
423         }
424     }
425 
addAll(int location, Collection<? extends SlideModel> collection)426     public boolean addAll(int location,
427             Collection<? extends SlideModel> collection) {
428         throw new UnsupportedOperationException("Operation not supported.");
429     }
430 
get(int location)431     public SlideModel get(int location) {
432         return mSlides.get(location);
433     }
434 
indexOf(Object object)435     public int indexOf(Object object) {
436         return mSlides.indexOf(object);
437     }
438 
lastIndexOf(Object object)439     public int lastIndexOf(Object object) {
440         return mSlides.lastIndexOf(object);
441     }
442 
listIterator()443     public ListIterator<SlideModel> listIterator() {
444         return mSlides.listIterator();
445     }
446 
listIterator(int location)447     public ListIterator<SlideModel> listIterator(int location) {
448         return mSlides.listIterator(location);
449     }
450 
remove(int location)451     public SlideModel remove(int location) {
452         SlideModel slide = mSlides.remove(location);
453         if (slide != null) {
454             decreaseMessageSize(slide.getSlideSize());
455             slide.unregisterAllModelChangedObservers();
456             notifyModelChanged(true);
457         }
458         return slide;
459     }
460 
set(int location, SlideModel object)461     public SlideModel set(int location, SlideModel object) {
462         SlideModel slide = mSlides.get(location);
463         if (null != object) {
464             int removeSize = 0;
465             int addSize = object.getSlideSize();
466             if (null != slide) {
467                 removeSize = slide.getSlideSize();
468             }
469             if (addSize > removeSize) {
470                 checkMessageSize(addSize - removeSize);
471                 increaseMessageSize(addSize - removeSize);
472             } else {
473                 decreaseMessageSize(removeSize - addSize);
474             }
475         }
476 
477         slide =  mSlides.set(location, object);
478         if (slide != null) {
479             slide.unregisterAllModelChangedObservers();
480         }
481 
482         if (object != null) {
483             object.registerModelChangedObserver(this);
484             for (IModelChangedObserver observer : mModelChangedObservers) {
485                 object.registerModelChangedObserver(observer);
486             }
487         }
488 
489         notifyModelChanged(true);
490         return slide;
491     }
492 
subList(int start, int end)493     public List<SlideModel> subList(int start, int end) {
494         return mSlides.subList(start, end);
495     }
496 
497     @Override
registerModelChangedObserverInDescendants( IModelChangedObserver observer)498     protected void registerModelChangedObserverInDescendants(
499             IModelChangedObserver observer) {
500         mLayout.registerModelChangedObserver(observer);
501 
502         for (SlideModel slide : mSlides) {
503             slide.registerModelChangedObserver(observer);
504         }
505     }
506 
507     @Override
unregisterModelChangedObserverInDescendants( IModelChangedObserver observer)508     protected void unregisterModelChangedObserverInDescendants(
509             IModelChangedObserver observer) {
510         mLayout.unregisterModelChangedObserver(observer);
511 
512         for (SlideModel slide : mSlides) {
513             slide.unregisterModelChangedObserver(observer);
514         }
515     }
516 
517     @Override
unregisterAllModelChangedObserversInDescendants()518     protected void unregisterAllModelChangedObserversInDescendants() {
519         mLayout.unregisterAllModelChangedObservers();
520 
521         for (SlideModel slide : mSlides) {
522             slide.unregisterAllModelChangedObservers();
523         }
524     }
525 
onModelChanged(Model model, boolean dataChanged)526     public void onModelChanged(Model model, boolean dataChanged) {
527         if (dataChanged) {
528             mDocumentCache = null;
529             mPduBodyCache = null;
530         }
531     }
532 
sync(PduBody pb)533     public void sync(PduBody pb) {
534         for (SlideModel slide : mSlides) {
535             for (MediaModel media : slide) {
536                 PduPart part = pb.getPartByContentLocation(media.getSrc());
537                 if (part != null) {
538                     media.setUri(part.getDataUri());
539                 }
540             }
541         }
542     }
543 
checkMessageSize(int increaseSize)544     public void checkMessageSize(int increaseSize) throws ContentRestrictionException {
545         ContentRestriction cr = ContentRestrictionFactory.getContentRestriction();
546         cr.checkMessageSize(mCurrentMessageSize, increaseSize, mContentResolver);
547     }
548 
549     /**
550      * Determines whether this is a "simple" slideshow.
551      * Criteria:
552      * - Exactly one slide
553      * - Exactly one multimedia attachment, but no audio
554      * - It can optionally have a caption
555     */
isSimple()556     public boolean isSimple() {
557         // There must be one (and only one) slide.
558         if (size() != 1)
559             return false;
560 
561         SlideModel slide = get(0);
562         // The slide must have either an image or video, but not both.
563         if (!(slide.hasImage() ^ slide.hasVideo()))
564             return false;
565 
566         // No audio allowed.
567         if (slide.hasAudio())
568             return false;
569 
570         return true;
571     }
572 
573     /**
574      * Make sure the text in slide 0 is no longer holding onto a reference to the text
575      * in the message text box.
576     */
prepareForSend()577     public void prepareForSend() {
578         if (size() == 1) {
579             TextModel text = get(0).getText();
580             if (text != null) {
581                 text.cloneText();
582             }
583         }
584     }
585 
586 }
587