SlideshowModel.java revision 153ae99e0a7d626a24d61475eeb133249deb448c
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
18package com.android.mms.model;
19
20
21import com.android.mms.ContentRestrictionException;
22import com.android.mms.R;
23import com.android.mms.dom.smil.parser.SmilXmlSerializer;
24import com.android.mms.drm.DrmWrapper;
25import com.android.mms.layout.LayoutManager;
26import com.google.android.mms.ContentType;
27import com.google.android.mms.MmsException;
28import com.google.android.mms.pdu.GenericPdu;
29import com.google.android.mms.pdu.MultimediaMessagePdu;
30import com.google.android.mms.pdu.PduBody;
31import com.google.android.mms.pdu.PduHeaders;
32import com.google.android.mms.pdu.PduPart;
33import com.google.android.mms.pdu.PduPersister;
34
35import org.w3c.dom.NodeList;
36import org.w3c.dom.events.EventTarget;
37import org.w3c.dom.smil.SMILDocument;
38import org.w3c.dom.smil.SMILElement;
39import org.w3c.dom.smil.SMILLayoutElement;
40import org.w3c.dom.smil.SMILMediaElement;
41import org.w3c.dom.smil.SMILParElement;
42import org.w3c.dom.smil.SMILRegionElement;
43import org.w3c.dom.smil.SMILRootLayoutElement;
44
45import android.content.ContentResolver;
46import android.content.Context;
47import android.drm.mobile1.DrmException;
48import android.net.Uri;
49import android.text.TextUtils;
50import android.util.Log;
51import android.widget.Toast;
52
53import java.io.ByteArrayOutputStream;
54import java.io.IOException;
55import java.util.ArrayList;
56import java.util.Collection;
57import java.util.Iterator;
58import java.util.List;
59import java.util.ListIterator;
60
61public 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
72    private SlideshowModel(ContentResolver contentResolver) {
73        mLayout = new LayoutModel();
74        mSlides = new ArrayList<SlideModel>();
75        mContentResolver = contentResolver;
76    }
77
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
94    public static SlideshowModel createNew(Context context) {
95        return new SlideshowModel(context.getContentResolver());
96    }
97
98    public static SlideshowModel createFromMessageUri(
99            Context context, Uri uri) throws MmsException {
100        return createFromPduBody(context, getPduBody(context, uri));
101    }
102
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
179    public PduBody toPduBody() {
180        if (mPduBodyCache == null) {
181            mDocumentCache = SmilHelper.getDocument(this);
182            mPduBodyCache = makePduBody(mDocumentCache);
183        }
184        return mPduBodyCache;
185    }
186
187    private PduBody makePduBody(SMILDocument document) {
188        return makePduBody(null, document, false);
189    }
190
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
279    public PduBody makeCopy(Context context) {
280        return makePduBody(context, SmilHelper.getDocument(this), true);
281    }
282
283    public SMILDocument toSmilDocument() {
284        if (mDocumentCache == null) {
285            mDocumentCache = SmilHelper.getDocument(this);
286        }
287        return mDocumentCache;
288    }
289
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
303    public void setCurrentMessageSize(int size) {
304        mCurrentMessageSize = size;
305    }
306
307    public int getCurrentMessageSize() {
308        return mCurrentMessageSize;
309    }
310
311    public void increaseMessageSize(int increaseSize) {
312        if (increaseSize > 0) {
313            mCurrentMessageSize += increaseSize;
314        }
315    }
316
317    public void decreaseMessageSize(int decreaseSize) {
318        if (decreaseSize > 0) {
319            mCurrentMessageSize -= decreaseSize;
320        }
321    }
322
323    public LayoutModel getLayout() {
324        return mLayout;
325    }
326
327    //
328    // Implement List<E> interface.
329    //
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
346    public boolean addAll(Collection<? extends SlideModel> collection) {
347        throw new UnsupportedOperationException("Operation not supported.");
348    }
349
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
364    public boolean contains(Object object) {
365        return mSlides.contains(object);
366    }
367
368    public boolean containsAll(Collection<?> collection) {
369        return mSlides.containsAll(collection);
370    }
371
372    public boolean isEmpty() {
373        return mSlides.isEmpty();
374    }
375
376    public Iterator<SlideModel> iterator() {
377        return mSlides.iterator();
378    }
379
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
391    public boolean removeAll(Collection<?> collection) {
392        throw new UnsupportedOperationException("Operation not supported.");
393    }
394
395    public boolean retainAll(Collection<?> collection) {
396        throw new UnsupportedOperationException("Operation not supported.");
397    }
398
399    public int size() {
400        return mSlides.size();
401    }
402
403    public Object[] toArray() {
404        return mSlides.toArray();
405    }
406
407    public <T> T[] toArray(T[] array) {
408        return mSlides.toArray(array);
409    }
410
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
426    public boolean addAll(int location,
427            Collection<? extends SlideModel> collection) {
428        throw new UnsupportedOperationException("Operation not supported.");
429    }
430
431    public SlideModel get(int location) {
432        return mSlides.get(location);
433    }
434
435    public int indexOf(Object object) {
436        return mSlides.indexOf(object);
437    }
438
439    public int lastIndexOf(Object object) {
440        return mSlides.lastIndexOf(object);
441    }
442
443    public ListIterator<SlideModel> listIterator() {
444        return mSlides.listIterator();
445    }
446
447    public ListIterator<SlideModel> listIterator(int location) {
448        return mSlides.listIterator(location);
449    }
450
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
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
493    public List<SlideModel> subList(int start, int end) {
494        return mSlides.subList(start, end);
495    }
496
497    @Override
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
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
518    protected void unregisterAllModelChangedObserversInDescendants() {
519        mLayout.unregisterAllModelChangedObservers();
520
521        for (SlideModel slide : mSlides) {
522            slide.unregisterAllModelChangedObservers();
523        }
524    }
525
526    public void onModelChanged(Model model, boolean dataChanged) {
527        if (dataChanged) {
528            mDocumentCache = null;
529            mPduBodyCache = null;
530        }
531    }
532
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
544    public void checkMessageSize(int increaseSize) throws ContentRestrictionException {
545        ContentRestriction cr = ContentRestrictionFactory.getContentRestriction();
546        cr.checkMessageSize(mCurrentMessageSize, increaseSize, mContentResolver);
547    }
548}
549