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