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