SlideshowModel.java revision 4ee7aa9aec45dd564c0e82928fa5a8378e856dcf
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 20 21import com.android.mms.ContentRestrictionException; 22import com.android.mms.R; 23import com.android.mms.dom.smil.parser.SmilXmlSerializer; 24import com.android.mms.drm.DrmWrapper; 25import com.android.mms.layout.LayoutManager; 26import com.google.android.mms.ContentType; 27import com.google.android.mms.MmsException; 28import com.google.android.mms.pdu.GenericPdu; 29import com.google.android.mms.pdu.MultimediaMessagePdu; 30import com.google.android.mms.pdu.PduBody; 31import com.google.android.mms.pdu.PduHeaders; 32import com.google.android.mms.pdu.PduPart; 33import com.google.android.mms.pdu.PduPersister; 34 35import org.w3c.dom.NodeList; 36import org.w3c.dom.events.EventTarget; 37import org.w3c.dom.smil.SMILDocument; 38import org.w3c.dom.smil.SMILElement; 39import org.w3c.dom.smil.SMILLayoutElement; 40import org.w3c.dom.smil.SMILMediaElement; 41import org.w3c.dom.smil.SMILParElement; 42import org.w3c.dom.smil.SMILRegionElement; 43import org.w3c.dom.smil.SMILRootLayoutElement; 44 45import android.content.ContentResolver; 46import android.content.Context; 47import android.drm.mobile1.DrmException; 48import android.net.Uri; 49import android.text.TextUtils; 50import android.util.Log; 51import android.widget.Toast; 52 53import java.io.ByteArrayOutputStream; 54import java.io.IOException; 55import java.util.ArrayList; 56import java.util.Collection; 57import java.util.Iterator; 58import java.util.List; 59import java.util.ListIterator; 60 61public class SlideshowModel extends Model 62 implements List<SlideModel>, IModelChangedObserver { 63 private static final String TAG = "Mms/slideshow"; 64 65 private final LayoutModel mLayout; 66 private final ArrayList<SlideModel> mSlides; 67 private SMILDocument mDocumentCache; 68 private PduBody mPduBodyCache; 69 private int mCurrentMessageSize; 70 private ContentResolver mContentResolver; 71 72 private SlideshowModel(ContentResolver contentResolver) { 73 mLayout = new LayoutModel(); 74 mSlides = new ArrayList<SlideModel>(); 75 mContentResolver = contentResolver; 76 } 77 78 private SlideshowModel ( 79 LayoutModel layouts, ArrayList<SlideModel> slides, 80 SMILDocument documentCache, PduBody pbCache, 81 ContentResolver contentResolver) { 82 mLayout = layouts; 83 mSlides = slides; 84 mContentResolver = contentResolver; 85 86 mDocumentCache = documentCache; 87 mPduBodyCache = pbCache; 88 for (SlideModel slide : mSlides) { 89 increaseMessageSize(slide.getSlideSize()); 90 slide.setParent(this); 91 } 92 } 93 94 public static SlideshowModel createNew(Context context) { 95 return new SlideshowModel(context.getContentResolver()); 96 } 97 98 public static SlideshowModel createFromMessageUri( 99 Context context, Uri uri) throws MmsException { 100 return createFromPduBody(context, getPduBody(context, uri)); 101 } 102 103 public static SlideshowModel createFromPduBody(Context context, PduBody pb) throws MmsException { 104 SMILDocument document = SmilHelper.getDocument(pb); 105 106 // Create root-layout model. 107 SMILLayoutElement sle = document.getLayout(); 108 SMILRootLayoutElement srle = sle.getRootLayout(); 109 int w = srle.getWidth(); 110 int h = srle.getHeight(); 111 if ((w == 0) || (h == 0)) { 112 w = LayoutManager.getInstance().getLayoutParameters().getWidth(); 113 h = LayoutManager.getInstance().getLayoutParameters().getHeight(); 114 srle.setWidth(w); 115 srle.setHeight(h); 116 } 117 RegionModel rootLayout = new RegionModel( 118 null, 0, 0, w, h); 119 120 // Create region models. 121 ArrayList<RegionModel> regions = new ArrayList<RegionModel>(); 122 NodeList nlRegions = sle.getRegions(); 123 int regionsNum = nlRegions.getLength(); 124 125 for (int i = 0; i < regionsNum; i++) { 126 SMILRegionElement sre = (SMILRegionElement) nlRegions.item(i); 127 RegionModel r = new RegionModel(sre.getId(), sre.getFit(), 128 sre.getLeft(), sre.getTop(), sre.getWidth(), sre.getHeight(), 129 sre.getBackgroundColor()); 130 regions.add(r); 131 } 132 LayoutModel layouts = new LayoutModel(rootLayout, regions); 133 134 // Create slide models. 135 SMILElement docBody = document.getBody(); 136 NodeList slideNodes = docBody.getChildNodes(); 137 int slidesNum = slideNodes.getLength(); 138 ArrayList<SlideModel> slides = new ArrayList<SlideModel>(slidesNum); 139 140 for (int i = 0; i < slidesNum; i++) { 141 // FIXME: This is NOT compatible with the SMILDocument which is 142 // generated by some other mobile phones. 143 SMILParElement par = (SMILParElement) slideNodes.item(i); 144 145 // Create media models for each slide. 146 NodeList mediaNodes = par.getChildNodes(); 147 int mediaNum = mediaNodes.getLength(); 148 ArrayList<MediaModel> mediaSet = new ArrayList<MediaModel>(mediaNum); 149 150 for (int j = 0; j < mediaNum; j++) { 151 SMILMediaElement sme = (SMILMediaElement) mediaNodes.item(j); 152 try { 153 MediaModel media = MediaModelFactory.getMediaModel( 154 context, sme, layouts, pb); 155 SmilHelper.addMediaElementEventListeners( 156 (EventTarget) sme, media); 157 mediaSet.add(media); 158 } catch (DrmException e) { 159 Log.e(TAG, e.getMessage(), e); 160 } catch (IOException e) { 161 Log.e(TAG, e.getMessage(), e); 162 } catch (IllegalArgumentException e) { 163 Log.e(TAG, e.getMessage(), e); 164 } 165 } 166 167 SlideModel slide = new SlideModel((int) (par.getDur() * 1000), mediaSet); 168 slide.setFill(par.getFill()); 169 SmilHelper.addParElementEventListeners((EventTarget) par, slide); 170 slides.add(slide); 171 } 172 173 SlideshowModel slideshow = new SlideshowModel(layouts, slides, document, pb, 174 context.getContentResolver()); 175 slideshow.registerModelChangedObserver(slideshow); 176 return slideshow; 177 } 178 179 public PduBody toPduBody() { 180 if (mPduBodyCache == null) { 181 mDocumentCache = SmilHelper.getDocument(this); 182 mPduBodyCache = makePduBody(mDocumentCache); 183 } 184 return mPduBodyCache; 185 } 186 187 private PduBody makePduBody(SMILDocument document) { 188 return makePduBody(null, document, false); 189 } 190 191 private PduBody makePduBody(Context context, SMILDocument document, boolean isMakingCopy) { 192 PduBody pb = new PduBody(); 193 194 boolean hasForwardLock = false; 195 for (SlideModel slide : mSlides) { 196 for (MediaModel media : slide) { 197 if (isMakingCopy) { 198 if (media.isDrmProtected() && !media.isAllowedToForward()) { 199 hasForwardLock = true; 200 continue; 201 } 202 } 203 204 PduPart part = new PduPart(); 205 206 if (media.isText()) { 207 TextModel text = (TextModel) media; 208 // Don't create empty text part. 209 if (TextUtils.isEmpty(text.getText())) { 210 continue; 211 } 212 // Set Charset if it's a text media. 213 part.setCharset(text.getCharset()); 214 } 215 216 // Set Content-Type. 217 part.setContentType(media.getContentType().getBytes()); 218 219 String src = media.getSrc(); 220 String location; 221 boolean startWithContentId = src.startsWith("cid:"); 222 if (startWithContentId) { 223 location = src.substring("cid:".length()); 224 } else { 225 location = src; 226 } 227 228 // Set Content-Location. 229 part.setContentLocation(location.getBytes()); 230 231 // Set Content-Id. 232 if (startWithContentId) { 233 //Keep the original Content-Id. 234 part.setContentId(location.getBytes()); 235 } 236 else { 237 int index = location.lastIndexOf("."); 238 String contentId = (index == -1) ? location 239 : location.substring(0, index); 240 part.setContentId(contentId.getBytes()); 241 } 242 243 if (media.isDrmProtected()) { 244 DrmWrapper wrapper = media.getDrmObject(); 245 part.setDataUri(wrapper.getOriginalUri()); 246 part.setData(wrapper.getOriginalData()); 247 } else if (media.isText()) { 248 part.setData(((TextModel) media).getText().getBytes()); 249 } else if (media.isImage() || media.isVideo() || media.isAudio()) { 250 part.setDataUri(media.getUri()); 251 } else { 252 Log.w(TAG, "Unsupport media: " + media); 253 } 254 255 pb.addPart(part); 256 } 257 } 258 259 if (hasForwardLock && isMakingCopy && context != null) { 260 Toast.makeText(context, 261 context.getString(R.string.cannot_forward_drm_obj), 262 Toast.LENGTH_LONG).show(); 263 document = SmilHelper.getDocument(pb); 264 } 265 266 // Create and insert SMIL part(as the first part) into the PduBody. 267 ByteArrayOutputStream out = new ByteArrayOutputStream(); 268 SmilXmlSerializer.serialize(document, out); 269 PduPart smilPart = new PduPart(); 270 smilPart.setContentId("smil".getBytes()); 271 smilPart.setContentLocation("smil.xml".getBytes()); 272 smilPart.setContentType(ContentType.APP_SMIL.getBytes()); 273 smilPart.setData(out.toByteArray()); 274 pb.addPart(0, smilPart); 275 276 return pb; 277 } 278 279 public PduBody makeCopy(Context context) { 280 return makePduBody(context, SmilHelper.getDocument(this), true); 281 } 282 283 public SMILDocument toSmilDocument() { 284 if (mDocumentCache == null) { 285 mDocumentCache = SmilHelper.getDocument(this); 286 } 287 return mDocumentCache; 288 } 289 290 public static PduBody getPduBody(Context context, Uri msg) throws MmsException { 291 PduPersister p = PduPersister.getPduPersister(context); 292 GenericPdu pdu = p.load(msg); 293 294 int msgType = pdu.getMessageType(); 295 if ((msgType == PduHeaders.MESSAGE_TYPE_SEND_REQ) 296 || (msgType == PduHeaders.MESSAGE_TYPE_RETRIEVE_CONF)) { 297 return ((MultimediaMessagePdu) pdu).getBody(); 298 } else { 299 throw new MmsException(); 300 } 301 } 302 303 public void setCurrentMessageSize(int size) { 304 mCurrentMessageSize = size; 305 } 306 307 public int getCurrentMessageSize() { 308 return mCurrentMessageSize; 309 } 310 311 public void increaseMessageSize(int increaseSize) { 312 if (increaseSize > 0) { 313 mCurrentMessageSize += increaseSize; 314 } 315 } 316 317 public void decreaseMessageSize(int decreaseSize) { 318 if (decreaseSize > 0) { 319 mCurrentMessageSize -= decreaseSize; 320 } 321 } 322 323 public LayoutModel getLayout() { 324 return mLayout; 325 } 326 327 // 328 // Implement List<E> interface. 329 // 330 public boolean add(SlideModel object) { 331 int increaseSize = object.getSlideSize(); 332 checkMessageSize(increaseSize); 333 334 if ((object != null) && mSlides.add(object)) { 335 increaseMessageSize(increaseSize); 336 object.registerModelChangedObserver(this); 337 for (IModelChangedObserver observer : mModelChangedObservers) { 338 object.registerModelChangedObserver(observer); 339 } 340 notifyModelChanged(true); 341 return true; 342 } 343 return false; 344 } 345 346 public boolean addAll(Collection<? extends SlideModel> collection) { 347 throw new UnsupportedOperationException("Operation not supported."); 348 } 349 350 public void clear() { 351 if (mSlides.size() > 0) { 352 for (SlideModel slide : mSlides) { 353 slide.unregisterModelChangedObserver(this); 354 for (IModelChangedObserver observer : mModelChangedObservers) { 355 slide.unregisterModelChangedObserver(observer); 356 } 357 } 358 mCurrentMessageSize = 0; 359 mSlides.clear(); 360 notifyModelChanged(true); 361 } 362 } 363 364 public boolean contains(Object object) { 365 return mSlides.contains(object); 366 } 367 368 public boolean containsAll(Collection<?> collection) { 369 return mSlides.containsAll(collection); 370 } 371 372 public boolean isEmpty() { 373 return mSlides.isEmpty(); 374 } 375 376 public Iterator<SlideModel> iterator() { 377 return mSlides.iterator(); 378 } 379 380 public boolean remove(Object object) { 381 if ((object != null) && mSlides.remove(object)) { 382 SlideModel slide = (SlideModel) object; 383 decreaseMessageSize(slide.getSlideSize()); 384 slide.unregisterAllModelChangedObservers(); 385 notifyModelChanged(true); 386 return true; 387 } 388 return false; 389 } 390 391 public boolean removeAll(Collection<?> collection) { 392 throw new UnsupportedOperationException("Operation not supported."); 393 } 394 395 public boolean retainAll(Collection<?> collection) { 396 throw new UnsupportedOperationException("Operation not supported."); 397 } 398 399 public int size() { 400 return mSlides.size(); 401 } 402 403 public Object[] toArray() { 404 return mSlides.toArray(); 405 } 406 407 public <T> T[] toArray(T[] array) { 408 return mSlides.toArray(array); 409 } 410 411 public void add(int location, SlideModel object) { 412 if (object != null) { 413 int increaseSize = object.getSlideSize(); 414 checkMessageSize(increaseSize); 415 416 mSlides.add(location, object); 417 increaseMessageSize(increaseSize); 418 object.registerModelChangedObserver(this); 419 for (IModelChangedObserver observer : mModelChangedObservers) { 420 object.registerModelChangedObserver(observer); 421 } 422 notifyModelChanged(true); 423 } 424 } 425 426 public boolean addAll(int location, 427 Collection<? extends SlideModel> collection) { 428 throw new UnsupportedOperationException("Operation not supported."); 429 } 430 431 public SlideModel get(int location) { 432 if (mSlides.size() == 0) { 433 return null; 434 } 435 436 return mSlides.get(location); 437 } 438 439 public int indexOf(Object object) { 440 return mSlides.indexOf(object); 441 } 442 443 public int lastIndexOf(Object object) { 444 return mSlides.lastIndexOf(object); 445 } 446 447 public ListIterator<SlideModel> listIterator() { 448 return mSlides.listIterator(); 449 } 450 451 public ListIterator<SlideModel> listIterator(int location) { 452 return mSlides.listIterator(location); 453 } 454 455 public SlideModel remove(int location) { 456 SlideModel slide = mSlides.remove(location); 457 if (slide != null) { 458 decreaseMessageSize(slide.getSlideSize()); 459 slide.unregisterAllModelChangedObservers(); 460 notifyModelChanged(true); 461 } 462 return slide; 463 } 464 465 public SlideModel set(int location, SlideModel object) { 466 SlideModel slide = mSlides.get(location); 467 if (null != object) { 468 int removeSize = 0; 469 int addSize = object.getSlideSize(); 470 if (null != slide) { 471 removeSize = slide.getSlideSize(); 472 } 473 if (addSize > removeSize) { 474 checkMessageSize(addSize - removeSize); 475 increaseMessageSize(addSize - removeSize); 476 } else { 477 decreaseMessageSize(removeSize - addSize); 478 } 479 } 480 481 slide = mSlides.set(location, object); 482 if (slide != null) { 483 slide.unregisterAllModelChangedObservers(); 484 } 485 486 if (object != null) { 487 object.registerModelChangedObserver(this); 488 for (IModelChangedObserver observer : mModelChangedObservers) { 489 object.registerModelChangedObserver(observer); 490 } 491 } 492 493 notifyModelChanged(true); 494 return slide; 495 } 496 497 public List<SlideModel> subList(int start, int end) { 498 return mSlides.subList(start, end); 499 } 500 501 @Override 502 protected void registerModelChangedObserverInDescendants( 503 IModelChangedObserver observer) { 504 mLayout.registerModelChangedObserver(observer); 505 506 for (SlideModel slide : mSlides) { 507 slide.registerModelChangedObserver(observer); 508 } 509 } 510 511 @Override 512 protected void unregisterModelChangedObserverInDescendants( 513 IModelChangedObserver observer) { 514 mLayout.unregisterModelChangedObserver(observer); 515 516 for (SlideModel slide : mSlides) { 517 slide.unregisterModelChangedObserver(observer); 518 } 519 } 520 521 @Override 522 protected void unregisterAllModelChangedObserversInDescendants() { 523 mLayout.unregisterAllModelChangedObservers(); 524 525 for (SlideModel slide : mSlides) { 526 slide.unregisterAllModelChangedObservers(); 527 } 528 } 529 530 public void onModelChanged(Model model, boolean dataChanged) { 531 if (dataChanged) { 532 mDocumentCache = null; 533 mPduBodyCache = null; 534 } 535 } 536 537 public void sync(PduBody pb) { 538 for (SlideModel slide : mSlides) { 539 for (MediaModel media : slide) { 540 PduPart part = pb.getPartByContentLocation(media.getSrc()); 541 if (part != null) { 542 media.setUri(part.getDataUri()); 543 } 544 } 545 } 546 } 547 548 public void checkMessageSize(int increaseSize) throws ContentRestrictionException { 549 ContentRestriction cr = ContentRestrictionFactory.getContentRestriction(); 550 cr.checkMessageSize(mCurrentMessageSize, increaseSize, mContentResolver); 551 } 552 553 /** 554 * Determines whether this is a "simple" slideshow. 555 * Criteria: 556 * - Exactly one slide 557 * - Exactly one multimedia attachment, but no audio 558 * - It can optionally have a caption 559 */ 560 public boolean isSimple() { 561 // There must be one (and only one) slide. 562 if (size() != 1) 563 return false; 564 565 SlideModel slide = get(0); 566 // The slide must have either an image or video, but not both. 567 if (!(slide.hasImage() ^ slide.hasVideo())) 568 return false; 569 570 // No audio allowed. 571 if (slide.hasAudio()) 572 return false; 573 574 return true; 575 } 576 577 /** 578 * Make sure the text in slide 0 is no longer holding onto a reference to the text 579 * in the message text box. 580 */ 581 public void prepareForSend() { 582 if (size() == 1) { 583 TextModel text = get(0).getText(); 584 if (text != null) { 585 text.cloneText(); 586 } 587 } 588 } 589 590} 591