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 62 public SlideModel(SlideshowModel slideshow) { 63 this(DEFAULT_SLIDE_DURATION, slideshow); 64 } 65 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 */ 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 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 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 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 */ 212 public int getDuration() { 213 return mDuration; 214 } 215 216 /** 217 * @param duration the mDuration to set 218 */ 219 public void setDuration(int duration) { 220 mDuration = duration; 221 notifyModelChanged(true); 222 } 223 224 public int getSlideSize() { 225 return mSlideSize; 226 } 227 228 public void increaseSlideSize(int increaseSize) { 229 if (increaseSize > 0) { 230 mSlideSize += increaseSize; 231 } 232 } 233 234 public void decreaseSlideSize(int decreaseSize) { 235 if (decreaseSize > 0) { 236 mSlideSize -= decreaseSize; 237 if (mSlideSize < 0) { 238 mSlideSize = 0; 239 } 240 } 241 } 242 243 public void setParent(SlideshowModel parent) { 244 mParent = parent; 245 } 246 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 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 */ 283 public boolean add(MediaModel object) { 284 internalAdd(object); 285 notifyModelChanged(true); 286 return true; 287 } 288 289 public boolean addAll(Collection<? extends MediaModel> collection) { 290 throw new UnsupportedOperationException("Operation not supported."); 291 } 292 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 316 public boolean contains(Object object) { 317 return mMedia.contains(object); 318 } 319 320 public boolean containsAll(Collection<?> collection) { 321 return mMedia.containsAll(collection); 322 } 323 324 public boolean isEmpty() { 325 return mMedia.isEmpty(); 326 } 327 328 public Iterator<MediaModel> iterator() { 329 return mMedia.iterator(); 330 } 331 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 341 public boolean removeAll(Collection<?> collection) { 342 throw new UnsupportedOperationException("Operation not supported."); 343 } 344 345 public boolean retainAll(Collection<?> collection) { 346 throw new UnsupportedOperationException("Operation not supported."); 347 } 348 349 public int size() { 350 return mMedia.size(); 351 } 352 353 public Object[] toArray() { 354 return mMedia.toArray(); 355 } 356 357 public <T> T[] toArray(T[] array) { 358 return mMedia.toArray(array); 359 } 360 361 public void add(int location, MediaModel object) { 362 throw new UnsupportedOperationException("Operation not supported."); 363 } 364 365 public boolean addAll(int location, 366 Collection<? extends MediaModel> collection) { 367 throw new UnsupportedOperationException("Operation not supported."); 368 } 369 370 public MediaModel get(int location) { 371 if (mMedia.size() == 0) { 372 return null; 373 } 374 375 return mMedia.get(location); 376 } 377 378 public int indexOf(Object object) { 379 return mMedia.indexOf(object); 380 } 381 382 public int lastIndexOf(Object object) { 383 return mMedia.lastIndexOf(object); 384 } 385 386 public ListIterator<MediaModel> listIterator() { 387 return mMedia.listIterator(); 388 } 389 390 public ListIterator<MediaModel> listIterator(int location) { 391 return mMedia.listIterator(location); 392 } 393 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 402 public MediaModel set(int location, MediaModel object) { 403 throw new UnsupportedOperationException("Operation not supported."); 404 } 405 406 public List<MediaModel> subList(int start, int end) { 407 return mMedia.subList(start, end); 408 } 409 410 /** 411 * @return the mVisible 412 */ 413 public boolean isVisible() { 414 return mVisible; 415 } 416 417 /** 418 * @param visible the mVisible to set 419 */ 420 public void setVisible(boolean visible) { 421 mVisible = visible; 422 notifyModelChanged(true); 423 } 424 425 /** 426 * @return the mFill 427 */ 428 public short getFill() { 429 return mFill; 430 } 431 432 /** 433 * @param fill the mFill to set 434 */ 435 public void setFill(short fill) { 436 mFill = fill; 437 notifyModelChanged(true); 438 } 439 440 @Override 441 protected void registerModelChangedObserverInDescendants( 442 IModelChangedObserver observer) { 443 for (MediaModel media : mMedia) { 444 media.registerModelChangedObserver(observer); 445 } 446 } 447 448 @Override 449 protected void unregisterModelChangedObserverInDescendants( 450 IModelChangedObserver observer) { 451 for (MediaModel media : mMedia) { 452 media.unregisterModelChangedObserver(observer); 453 } 454 } 455 456 @Override 457 protected void unregisterAllModelChangedObserversInDescendants() { 458 for (MediaModel media : mMedia) { 459 media.unregisterAllModelChangedObservers(); 460 } 461 } 462 463 // EventListener Interface 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 480 public boolean hasText() { 481 return mText != null; 482 } 483 484 public boolean hasImage() { 485 return mImage != null; 486 } 487 488 public boolean hasAudio() { 489 return mAudio != null; 490 } 491 492 public boolean hasVideo() { 493 return mVideo != null; 494 } 495 496 public boolean removeText() { 497 return remove(mText); 498 } 499 500 public boolean removeImage() { 501 return remove(mImage); 502 } 503 504 public boolean removeAudio() { 505 boolean result = remove(mAudio); 506 resetDuration(); 507 return result; 508 } 509 510 public boolean removeVideo() { 511 boolean result = remove(mVideo); 512 resetDuration(); 513 return result; 514 } 515 516 public TextModel getText() { 517 return (TextModel) mText; 518 } 519 520 public ImageModel getImage() { 521 return (ImageModel) mImage; 522 } 523 524 public AudioModel getAudio() { 525 return (AudioModel) mAudio; 526 } 527 528 public VideoModel getVideo() { 529 return (VideoModel) mVideo; 530 } 531 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 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