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