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