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 21 import com.android.mms.ContentRestrictionException; 22 import com.android.mms.R; 23 import com.android.mms.dom.smil.parser.SmilXmlSerializer; 24 import com.android.mms.drm.DrmWrapper; 25 import com.android.mms.layout.LayoutManager; 26 import com.google.android.mms.ContentType; 27 import com.google.android.mms.MmsException; 28 import com.google.android.mms.pdu.GenericPdu; 29 import com.google.android.mms.pdu.MultimediaMessagePdu; 30 import com.google.android.mms.pdu.PduBody; 31 import com.google.android.mms.pdu.PduHeaders; 32 import com.google.android.mms.pdu.PduPart; 33 import com.google.android.mms.pdu.PduPersister; 34 35 import org.w3c.dom.NodeList; 36 import org.w3c.dom.events.EventTarget; 37 import org.w3c.dom.smil.SMILDocument; 38 import org.w3c.dom.smil.SMILElement; 39 import org.w3c.dom.smil.SMILLayoutElement; 40 import org.w3c.dom.smil.SMILMediaElement; 41 import org.w3c.dom.smil.SMILParElement; 42 import org.w3c.dom.smil.SMILRegionElement; 43 import org.w3c.dom.smil.SMILRootLayoutElement; 44 45 import android.content.ContentResolver; 46 import android.content.Context; 47 import android.drm.mobile1.DrmException; 48 import android.net.Uri; 49 import android.text.TextUtils; 50 import android.util.Log; 51 import android.widget.Toast; 52 53 import java.io.ByteArrayOutputStream; 54 import java.io.IOException; 55 import java.util.ArrayList; 56 import java.util.Collection; 57 import java.util.Iterator; 58 import java.util.List; 59 import java.util.ListIterator; 60 61 public class SlideshowModel extends Model 62 implements List<SlideModel>, IModelChangedObserver { 63 private static final String TAG = "SlideshowModel"; 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 SlideshowModel(ContentResolver contentResolver)72 private SlideshowModel(ContentResolver contentResolver) { 73 mLayout = new LayoutModel(); 74 mSlides = new ArrayList<SlideModel>(); 75 mContentResolver = contentResolver; 76 } 77 SlideshowModel( LayoutModel layouts, ArrayList<SlideModel> slides, SMILDocument documentCache, PduBody pbCache, ContentResolver contentResolver)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 createNew(Context context)94 public static SlideshowModel createNew(Context context) { 95 return new SlideshowModel(context.getContentResolver()); 96 } 97 createFromMessageUri( Context context, Uri uri)98 public static SlideshowModel createFromMessageUri( 99 Context context, Uri uri) throws MmsException { 100 return createFromPduBody(context, getPduBody(context, uri)); 101 } 102 createFromPduBody(Context context, PduBody pb)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 toPduBody()179 public PduBody toPduBody() { 180 if (mPduBodyCache == null) { 181 mDocumentCache = SmilHelper.getDocument(this); 182 mPduBodyCache = makePduBody(mDocumentCache); 183 } 184 return mPduBodyCache; 185 } 186 makePduBody(SMILDocument document)187 private PduBody makePduBody(SMILDocument document) { 188 return makePduBody(null, document, false); 189 } 190 makePduBody(Context context, SMILDocument document, boolean isMakingCopy)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 makeCopy(Context context)279 public PduBody makeCopy(Context context) { 280 return makePduBody(context, SmilHelper.getDocument(this), true); 281 } 282 toSmilDocument()283 public SMILDocument toSmilDocument() { 284 if (mDocumentCache == null) { 285 mDocumentCache = SmilHelper.getDocument(this); 286 } 287 return mDocumentCache; 288 } 289 getPduBody(Context context, Uri msg)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 setCurrentMessageSize(int size)303 public void setCurrentMessageSize(int size) { 304 mCurrentMessageSize = size; 305 } 306 getCurrentMessageSize()307 public int getCurrentMessageSize() { 308 return mCurrentMessageSize; 309 } 310 increaseMessageSize(int increaseSize)311 public void increaseMessageSize(int increaseSize) { 312 if (increaseSize > 0) { 313 mCurrentMessageSize += increaseSize; 314 } 315 } 316 decreaseMessageSize(int decreaseSize)317 public void decreaseMessageSize(int decreaseSize) { 318 if (decreaseSize > 0) { 319 mCurrentMessageSize -= decreaseSize; 320 } 321 } 322 getLayout()323 public LayoutModel getLayout() { 324 return mLayout; 325 } 326 327 // 328 // Implement List<E> interface. 329 // add(SlideModel object)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 addAll(Collection<? extends SlideModel> collection)346 public boolean addAll(Collection<? extends SlideModel> collection) { 347 throw new UnsupportedOperationException("Operation not supported."); 348 } 349 clear()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 contains(Object object)364 public boolean contains(Object object) { 365 return mSlides.contains(object); 366 } 367 containsAll(Collection<?> collection)368 public boolean containsAll(Collection<?> collection) { 369 return mSlides.containsAll(collection); 370 } 371 isEmpty()372 public boolean isEmpty() { 373 return mSlides.isEmpty(); 374 } 375 iterator()376 public Iterator<SlideModel> iterator() { 377 return mSlides.iterator(); 378 } 379 remove(Object object)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 removeAll(Collection<?> collection)391 public boolean removeAll(Collection<?> collection) { 392 throw new UnsupportedOperationException("Operation not supported."); 393 } 394 retainAll(Collection<?> collection)395 public boolean retainAll(Collection<?> collection) { 396 throw new UnsupportedOperationException("Operation not supported."); 397 } 398 size()399 public int size() { 400 return mSlides.size(); 401 } 402 toArray()403 public Object[] toArray() { 404 return mSlides.toArray(); 405 } 406 toArray(T[] array)407 public <T> T[] toArray(T[] array) { 408 return mSlides.toArray(array); 409 } 410 add(int location, SlideModel object)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 addAll(int location, Collection<? extends SlideModel> collection)426 public boolean addAll(int location, 427 Collection<? extends SlideModel> collection) { 428 throw new UnsupportedOperationException("Operation not supported."); 429 } 430 get(int location)431 public SlideModel get(int location) { 432 return mSlides.get(location); 433 } 434 indexOf(Object object)435 public int indexOf(Object object) { 436 return mSlides.indexOf(object); 437 } 438 lastIndexOf(Object object)439 public int lastIndexOf(Object object) { 440 return mSlides.lastIndexOf(object); 441 } 442 listIterator()443 public ListIterator<SlideModel> listIterator() { 444 return mSlides.listIterator(); 445 } 446 listIterator(int location)447 public ListIterator<SlideModel> listIterator(int location) { 448 return mSlides.listIterator(location); 449 } 450 remove(int location)451 public SlideModel remove(int location) { 452 SlideModel slide = mSlides.remove(location); 453 if (slide != null) { 454 decreaseMessageSize(slide.getSlideSize()); 455 slide.unregisterAllModelChangedObservers(); 456 notifyModelChanged(true); 457 } 458 return slide; 459 } 460 set(int location, SlideModel object)461 public SlideModel set(int location, SlideModel object) { 462 SlideModel slide = mSlides.get(location); 463 if (null != object) { 464 int removeSize = 0; 465 int addSize = object.getSlideSize(); 466 if (null != slide) { 467 removeSize = slide.getSlideSize(); 468 } 469 if (addSize > removeSize) { 470 checkMessageSize(addSize - removeSize); 471 increaseMessageSize(addSize - removeSize); 472 } else { 473 decreaseMessageSize(removeSize - addSize); 474 } 475 } 476 477 slide = mSlides.set(location, object); 478 if (slide != null) { 479 slide.unregisterAllModelChangedObservers(); 480 } 481 482 if (object != null) { 483 object.registerModelChangedObserver(this); 484 for (IModelChangedObserver observer : mModelChangedObservers) { 485 object.registerModelChangedObserver(observer); 486 } 487 } 488 489 notifyModelChanged(true); 490 return slide; 491 } 492 subList(int start, int end)493 public List<SlideModel> subList(int start, int end) { 494 return mSlides.subList(start, end); 495 } 496 497 @Override registerModelChangedObserverInDescendants( IModelChangedObserver observer)498 protected void registerModelChangedObserverInDescendants( 499 IModelChangedObserver observer) { 500 mLayout.registerModelChangedObserver(observer); 501 502 for (SlideModel slide : mSlides) { 503 slide.registerModelChangedObserver(observer); 504 } 505 } 506 507 @Override unregisterModelChangedObserverInDescendants( IModelChangedObserver observer)508 protected void unregisterModelChangedObserverInDescendants( 509 IModelChangedObserver observer) { 510 mLayout.unregisterModelChangedObserver(observer); 511 512 for (SlideModel slide : mSlides) { 513 slide.unregisterModelChangedObserver(observer); 514 } 515 } 516 517 @Override unregisterAllModelChangedObserversInDescendants()518 protected void unregisterAllModelChangedObserversInDescendants() { 519 mLayout.unregisterAllModelChangedObservers(); 520 521 for (SlideModel slide : mSlides) { 522 slide.unregisterAllModelChangedObservers(); 523 } 524 } 525 onModelChanged(Model model, boolean dataChanged)526 public void onModelChanged(Model model, boolean dataChanged) { 527 if (dataChanged) { 528 mDocumentCache = null; 529 mPduBodyCache = null; 530 } 531 } 532 sync(PduBody pb)533 public void sync(PduBody pb) { 534 for (SlideModel slide : mSlides) { 535 for (MediaModel media : slide) { 536 PduPart part = pb.getPartByContentLocation(media.getSrc()); 537 if (part != null) { 538 media.setUri(part.getDataUri()); 539 } 540 } 541 } 542 } 543 checkMessageSize(int increaseSize)544 public void checkMessageSize(int increaseSize) throws ContentRestrictionException { 545 ContentRestriction cr = ContentRestrictionFactory.getContentRestriction(); 546 cr.checkMessageSize(mCurrentMessageSize, increaseSize, mContentResolver); 547 } 548 549 /** 550 * Determines whether this is a "simple" slideshow. 551 * Criteria: 552 * - Exactly one slide 553 * - Exactly one multimedia attachment, but no audio 554 * - It can optionally have a caption 555 */ isSimple()556 public boolean isSimple() { 557 // There must be one (and only one) slide. 558 if (size() != 1) 559 return false; 560 561 SlideModel slide = get(0); 562 // The slide must have either an image or video, but not both. 563 if (!(slide.hasImage() ^ slide.hasVideo())) 564 return false; 565 566 // No audio allowed. 567 if (slide.hasAudio()) 568 return false; 569 570 return true; 571 } 572 573 /** 574 * Make sure the text in slide 0 is no longer holding onto a reference to the text 575 * in the message text box. 576 */ prepareForSend()577 public void prepareForSend() { 578 if (size() == 1) { 579 TextModel text = get(0).getText(); 580 if (text != null) { 581 text.cloneText(); 582 } 583 } 584 } 585 586 } 587