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
20import com.android.mms.ContentRestrictionException;
21import com.android.mms.dom.smil.SmilParElementImpl;
22import com.google.android.mms.ContentType;
23
24import org.w3c.dom.events.Event;
25import org.w3c.dom.events.EventListener;
26import org.w3c.dom.smil.ElementTime;
27
28import android.util.Config;
29import android.util.Log;
30import android.text.TextUtils;
31
32import java.util.ArrayList;
33import java.util.Collection;
34import java.util.Iterator;
35import java.util.List;
36import java.util.ListIterator;
37
38public class SlideModel extends Model implements List<MediaModel>, EventListener {
39    public static final String TAG = "Mms/slideshow";
40    private static final boolean DEBUG = false;
41    private static final boolean LOCAL_LOGV = DEBUG ? Config.LOGD : Config.LOGV;
42    private static final int DEFAULT_SLIDE_DURATION = 5000;
43
44    private final ArrayList<MediaModel> mMedia = new ArrayList<MediaModel>();
45
46    private MediaModel mText;
47    private MediaModel mImage;
48    private MediaModel mAudio;
49    private MediaModel mVideo;
50
51    private boolean mCanAddImage = true;
52    private boolean mCanAddAudio = true;
53    private boolean mCanAddVideo = true;
54
55    private int mDuration;
56    private boolean mVisible = true;
57    private short mFill;
58    private int mSlideSize;
59    private SlideshowModel mParent;
60
61    public SlideModel(SlideshowModel slideshow) {
62        this(DEFAULT_SLIDE_DURATION, slideshow);
63    }
64
65    public SlideModel(int duration, SlideshowModel slideshow) {
66        mDuration = duration;
67        mParent = slideshow;
68    }
69
70    /**
71     * Create a SlideModel with exist media collection.
72     *
73     * @param duration The duration of the slide.
74     * @param mediaList The exist media collection.
75     *
76     * @throws IllegalStateException One or more media in the mediaList cannot
77     *         be added into the slide due to a slide cannot contain image
78     *         and video or audio and video at the same time.
79     */
80    public SlideModel(int duration, ArrayList<MediaModel> mediaList) {
81        mDuration = duration;
82
83        int maxDur = 0;
84        for (MediaModel media : mediaList) {
85            internalAdd(media);
86
87            int mediaDur = media.getDuration();
88            if (mediaDur > maxDur) {
89                maxDur = mediaDur;
90            }
91        }
92
93        updateDuration(maxDur);
94    }
95
96    private void internalAdd(MediaModel media) throws IllegalStateException {
97        if (media == null) {
98            // Don't add null value into the list.
99            return;
100        }
101
102        if (media.isText()) {
103            String contentType = media.getContentType();
104            if (TextUtils.isEmpty(contentType) || ContentType.TEXT_PLAIN.equals(contentType)
105                    || ContentType.TEXT_HTML.equals(contentType)) {
106                internalAddOrReplace(mText, media);
107                mText = media;
108            } else {
109                Log.w(TAG, "[SlideModel] content type " + media.getContentType() +
110                        " isn't supported (as text)");
111            }
112        } else if (media.isImage()) {
113            if (mCanAddImage) {
114                internalAddOrReplace(mImage, media);
115                mImage = media;
116                mCanAddVideo = false;
117            } else {
118                throw new IllegalStateException();
119            }
120        } else if (media.isAudio()) {
121            if (mCanAddAudio) {
122                internalAddOrReplace(mAudio, media);
123                mAudio = media;
124                mCanAddVideo = false;
125            } else {
126                throw new IllegalStateException();
127            }
128        } else if (media.isVideo()) {
129            if (mCanAddVideo) {
130                internalAddOrReplace(mVideo, media);
131                mVideo = media;
132                mCanAddImage = false;
133                mCanAddAudio = false;
134            } else {
135                throw new IllegalStateException();
136            }
137        }
138    }
139
140    private void internalAddOrReplace(MediaModel old, MediaModel media) {
141        // If the media is resizable, at this point consider it to be zero length.
142        // Just before we send the slideshow, we take the remaining space in the
143        // slideshow and equally allocate it to all the resizeable media items and resize them.
144        int addSize = media.getMediaResizable() ? 0 : media.getMediaSize();
145        int removeSize;
146        if (old == null) {
147            if (null != mParent) {
148                mParent.checkMessageSize(addSize);
149            }
150            mMedia.add(media);
151            increaseSlideSize(addSize);
152            increaseMessageSize(addSize);
153        } else {
154            removeSize = old.getMediaSize();
155            if (addSize > removeSize) {
156                if (null != mParent) {
157                    mParent.checkMessageSize(addSize - removeSize);
158                }
159                increaseSlideSize(addSize - removeSize);
160                increaseMessageSize(addSize - removeSize);
161            } else {
162                decreaseSlideSize(removeSize - addSize);
163                decreaseMessageSize(removeSize - addSize);
164            }
165            mMedia.set(mMedia.indexOf(old), media);
166            old.unregisterAllModelChangedObservers();
167        }
168
169        for (IModelChangedObserver observer : mModelChangedObservers) {
170            media.registerModelChangedObserver(observer);
171        }
172    }
173
174    private boolean internalRemove(Object object) {
175        if (mMedia.remove(object)) {
176            if (object instanceof TextModel) {
177                mText = null;
178            } else if (object instanceof ImageModel) {
179                mImage = null;
180                mCanAddVideo = true;
181            } else if (object instanceof AudioModel) {
182                mAudio = null;
183                mCanAddVideo = true;
184            } else if (object instanceof VideoModel) {
185                mVideo = null;
186                mCanAddImage = true;
187                mCanAddAudio = true;
188            }
189            // If the media is resizable, at this point consider it to be zero length.
190            // Just before we send the slideshow, we take the remaining space in the
191            // slideshow and equally allocate it to all the resizeable media items and resize them.
192            int decreaseSize = ((MediaModel) object).getMediaResizable() ? 0
193                                        : ((MediaModel) object).getMediaSize();
194            decreaseSlideSize(decreaseSize);
195            decreaseMessageSize(decreaseSize);
196
197            ((Model) object).unregisterAllModelChangedObservers();
198
199            return true;
200        }
201
202        return false;
203    }
204
205    /**
206     * @return the mDuration
207     */
208    public int getDuration() {
209        return mDuration;
210    }
211
212    /**
213     * @param duration the mDuration to set
214     */
215    public void setDuration(int duration) {
216        mDuration = duration;
217        notifyModelChanged(true);
218    }
219
220    public int getSlideSize() {
221        return mSlideSize;
222    }
223
224    public void increaseSlideSize(int increaseSize) {
225        if (increaseSize > 0) {
226            mSlideSize += increaseSize;
227        }
228    }
229
230    public void decreaseSlideSize(int decreaseSize) {
231        if (decreaseSize > 0) {
232            mSlideSize -= decreaseSize;
233        }
234    }
235
236    public void setParent(SlideshowModel parent) {
237        mParent = parent;
238    }
239
240    public void increaseMessageSize(int increaseSize) {
241        if ((increaseSize > 0) && (null != mParent)) {
242            int size = mParent.getCurrentMessageSize();
243            size += increaseSize;
244            mParent.setCurrentMessageSize(size);
245        }
246    }
247
248    public void decreaseMessageSize(int decreaseSize) {
249        if ((decreaseSize > 0) && (null != mParent)) {
250            int size = mParent.getCurrentMessageSize();
251            size -= decreaseSize;
252            mParent.setCurrentMessageSize(size);
253        }
254    }
255
256    //
257    // Implement List<E> interface.
258    //
259
260    /**
261     * Add a MediaModel to the slide. If the slide has already contained
262     * a media object in the same type, the media object will be replaced by
263     * the new one.
264     *
265     * @param object A media object to be added into the slide.
266     * @return true
267     * @throws IllegalStateException One or more media in the mediaList cannot
268     *         be added into the slide due to a slide cannot contain image
269     *         and video or audio and video at the same time.
270     * @throws ContentRestrictionException when can not add this object.
271     *
272     */
273    public boolean add(MediaModel object) {
274        internalAdd(object);
275        notifyModelChanged(true);
276        return true;
277    }
278
279    public boolean addAll(Collection<? extends MediaModel> collection) {
280        throw new UnsupportedOperationException("Operation not supported.");
281    }
282
283    public void clear() {
284        if (mMedia.size() > 0) {
285            for (MediaModel media : mMedia) {
286                media.unregisterAllModelChangedObservers();
287                int decreaseSize = media.getMediaSize();
288                decreaseSlideSize(decreaseSize);
289                decreaseMessageSize(decreaseSize);
290            }
291            mMedia.clear();
292
293            mText = null;
294            mImage = null;
295            mAudio = null;
296            mVideo = null;
297
298            mCanAddImage = true;
299            mCanAddAudio = true;
300            mCanAddVideo = true;
301
302            notifyModelChanged(true);
303        }
304    }
305
306    public boolean contains(Object object) {
307        return mMedia.contains(object);
308    }
309
310    public boolean containsAll(Collection<?> collection) {
311        return mMedia.containsAll(collection);
312    }
313
314    public boolean isEmpty() {
315        return mMedia.isEmpty();
316    }
317
318    public Iterator<MediaModel> iterator() {
319        return mMedia.iterator();
320    }
321
322    public boolean remove(Object object) {
323        if ((object != null) && (object instanceof MediaModel)
324                && internalRemove(object)) {
325            notifyModelChanged(true);
326            return true;
327        }
328        return false;
329    }
330
331    public boolean removeAll(Collection<?> collection) {
332        throw new UnsupportedOperationException("Operation not supported.");
333    }
334
335    public boolean retainAll(Collection<?> collection) {
336        throw new UnsupportedOperationException("Operation not supported.");
337    }
338
339    public int size() {
340        return mMedia.size();
341    }
342
343    public Object[] toArray() {
344        return mMedia.toArray();
345    }
346
347    public <T> T[] toArray(T[] array) {
348        return mMedia.toArray(array);
349    }
350
351    public void add(int location, MediaModel object) {
352        throw new UnsupportedOperationException("Operation not supported.");
353    }
354
355    public boolean addAll(int location,
356            Collection<? extends MediaModel> collection) {
357        throw new UnsupportedOperationException("Operation not supported.");
358    }
359
360    public MediaModel get(int location) {
361        if (mMedia.size() == 0) {
362            return null;
363        }
364
365        return mMedia.get(location);
366    }
367
368    public int indexOf(Object object) {
369        return mMedia.indexOf(object);
370    }
371
372    public int lastIndexOf(Object object) {
373        return mMedia.lastIndexOf(object);
374    }
375
376    public ListIterator<MediaModel> listIterator() {
377        return mMedia.listIterator();
378    }
379
380    public ListIterator<MediaModel> listIterator(int location) {
381        return mMedia.listIterator(location);
382    }
383
384    public MediaModel remove(int location) {
385        MediaModel media = mMedia.get(location);
386        if ((media != null) && internalRemove(media)) {
387            notifyModelChanged(true);
388        }
389        return media;
390    }
391
392    public MediaModel set(int location, MediaModel object) {
393        throw new UnsupportedOperationException("Operation not supported.");
394    }
395
396    public List<MediaModel> subList(int start, int end) {
397        return mMedia.subList(start, end);
398    }
399
400    /**
401     * @return the mVisible
402     */
403    public boolean isVisible() {
404        return mVisible;
405    }
406
407    /**
408     * @param visible the mVisible to set
409     */
410    public void setVisible(boolean visible) {
411        mVisible = visible;
412        notifyModelChanged(true);
413    }
414
415    /**
416     * @return the mFill
417     */
418    public short getFill() {
419        return mFill;
420    }
421
422    /**
423     * @param fill the mFill to set
424     */
425    public void setFill(short fill) {
426        mFill = fill;
427        notifyModelChanged(true);
428    }
429
430    @Override
431    protected void registerModelChangedObserverInDescendants(
432            IModelChangedObserver observer) {
433        for (MediaModel media : mMedia) {
434            media.registerModelChangedObserver(observer);
435        }
436    }
437
438    @Override
439    protected void unregisterModelChangedObserverInDescendants(
440            IModelChangedObserver observer) {
441        for (MediaModel media : mMedia) {
442            media.unregisterModelChangedObserver(observer);
443        }
444    }
445
446    @Override
447    protected void unregisterAllModelChangedObserversInDescendants() {
448        for (MediaModel media : mMedia) {
449            media.unregisterAllModelChangedObservers();
450        }
451    }
452
453    // EventListener Interface
454    public void handleEvent(Event evt) {
455        if (evt.getType().equals(SmilParElementImpl.SMIL_SLIDE_START_EVENT)) {
456            if (LOCAL_LOGV) {
457                Log.v(TAG, "Start to play slide: " + this);
458            }
459            mVisible = true;
460        } else if (mFill != ElementTime.FILL_FREEZE) {
461            if (LOCAL_LOGV) {
462                Log.v(TAG, "Stop playing slide: " + this);
463            }
464            mVisible = false;
465        }
466
467        notifyModelChanged(false);
468    }
469
470    public boolean hasText() {
471        return mText != null;
472    }
473
474    public boolean hasImage() {
475        return mImage != null;
476    }
477
478    public boolean hasAudio() {
479        return mAudio != null;
480    }
481
482    public boolean hasVideo() {
483        return mVideo != null;
484    }
485
486    public boolean removeText() {
487        return remove(mText);
488    }
489
490    public boolean removeImage() {
491        return remove(mImage);
492    }
493
494    public boolean removeAudio() {
495        return remove(mAudio);
496    }
497
498    public boolean removeVideo() {
499        return remove(mVideo);
500    }
501
502    public TextModel getText() {
503        return (TextModel) mText;
504    }
505
506    public ImageModel getImage() {
507        return (ImageModel) mImage;
508    }
509
510    public AudioModel getAudio() {
511        return (AudioModel) mAudio;
512    }
513
514    public VideoModel getVideo() {
515        return (VideoModel) mVideo;
516    }
517
518    public void updateDuration(int duration) {
519        if (duration <= 0) {
520            return;
521        }
522
523        if ((duration > mDuration)
524                || (mDuration == DEFAULT_SLIDE_DURATION)) {
525            mDuration = duration;
526        }
527    }
528}
529