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