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.ExceedMessageSizeException; 23 import com.android.mms.LogTag; 24 import com.android.mms.MmsConfig; 25 import com.android.mms.R; 26 import com.android.mms.dom.smil.parser.SmilXmlSerializer; 27 import com.android.mms.layout.LayoutManager; 28 import com.google.android.mms.ContentType; 29 import com.google.android.mms.MmsException; 30 import com.google.android.mms.pdu.GenericPdu; 31 import com.google.android.mms.pdu.MultimediaMessagePdu; 32 import com.google.android.mms.pdu.PduBody; 33 import com.google.android.mms.pdu.PduHeaders; 34 import com.google.android.mms.pdu.PduPart; 35 import com.google.android.mms.pdu.PduPersister; 36 37 import org.w3c.dom.NodeList; 38 import org.w3c.dom.events.EventTarget; 39 import org.w3c.dom.smil.SMILDocument; 40 import org.w3c.dom.smil.SMILElement; 41 import org.w3c.dom.smil.SMILLayoutElement; 42 import org.w3c.dom.smil.SMILMediaElement; 43 import org.w3c.dom.smil.SMILParElement; 44 import org.w3c.dom.smil.SMILRegionElement; 45 import org.w3c.dom.smil.SMILRootLayoutElement; 46 47 import android.content.ContentUris; 48 import android.content.Context; 49 import android.net.Uri; 50 import android.text.TextUtils; 51 import android.util.Log; 52 import android.widget.Toast; 53 54 import java.io.ByteArrayOutputStream; 55 import java.io.IOException; 56 import java.util.ArrayList; 57 import java.util.Collection; 58 import java.util.Iterator; 59 import java.util.List; 60 import java.util.ListIterator; 61 62 public class SlideshowModel extends Model 63 implements List<SlideModel>, IModelChangedObserver { 64 private static final String TAG = "Mms/slideshow"; 65 66 private final LayoutModel mLayout; 67 private final ArrayList<SlideModel> mSlides; 68 private SMILDocument mDocumentCache; 69 private PduBody mPduBodyCache; 70 private int mCurrentMessageSize; // This is the current message size, not including 71 // attachments that can be resized (such as photos) 72 private int mTotalMessageSize; // This is the computed total message size 73 private Context mContext; 74 75 // amount of space to leave in a slideshow for text and overhead. 76 public static final int SLIDESHOW_SLOP = 1024; 77 SlideshowModel(Context context)78 private SlideshowModel(Context context) { 79 mLayout = new LayoutModel(); 80 mSlides = new ArrayList<SlideModel>(); 81 mContext = context; 82 } 83 SlideshowModel( LayoutModel layouts, ArrayList<SlideModel> slides, SMILDocument documentCache, PduBody pbCache, Context context)84 private SlideshowModel ( 85 LayoutModel layouts, ArrayList<SlideModel> slides, 86 SMILDocument documentCache, PduBody pbCache, 87 Context context) { 88 mLayout = layouts; 89 mSlides = slides; 90 mContext = context; 91 92 mDocumentCache = documentCache; 93 mPduBodyCache = pbCache; 94 for (SlideModel slide : mSlides) { 95 increaseMessageSize(slide.getSlideSize()); 96 slide.setParent(this); 97 } 98 } 99 createNew(Context context)100 public static SlideshowModel createNew(Context context) { 101 return new SlideshowModel(context); 102 } 103 createFromMessageUri( Context context, Uri uri)104 public static SlideshowModel createFromMessageUri( 105 Context context, Uri uri) throws MmsException { 106 return createFromPduBody(context, getPduBody(context, uri)); 107 } 108 createFromPduBody(Context context, PduBody pb)109 public static SlideshowModel createFromPduBody(Context context, PduBody pb) throws MmsException { 110 SMILDocument document = SmilHelper.getDocument(pb); 111 112 // Create root-layout model. 113 SMILLayoutElement sle = document.getLayout(); 114 SMILRootLayoutElement srle = sle.getRootLayout(); 115 int w = srle.getWidth(); 116 int h = srle.getHeight(); 117 if ((w == 0) || (h == 0)) { 118 w = LayoutManager.getInstance().getLayoutParameters().getWidth(); 119 h = LayoutManager.getInstance().getLayoutParameters().getHeight(); 120 srle.setWidth(w); 121 srle.setHeight(h); 122 } 123 RegionModel rootLayout = new RegionModel( 124 null, 0, 0, w, h); 125 126 // Create region models. 127 ArrayList<RegionModel> regions = new ArrayList<RegionModel>(); 128 NodeList nlRegions = sle.getRegions(); 129 int regionsNum = nlRegions.getLength(); 130 131 for (int i = 0; i < regionsNum; i++) { 132 SMILRegionElement sre = (SMILRegionElement) nlRegions.item(i); 133 RegionModel r = new RegionModel(sre.getId(), sre.getFit(), 134 sre.getLeft(), sre.getTop(), sre.getWidth(), sre.getHeight(), 135 sre.getBackgroundColor()); 136 regions.add(r); 137 } 138 LayoutModel layouts = new LayoutModel(rootLayout, regions); 139 140 // Create slide models. 141 SMILElement docBody = document.getBody(); 142 NodeList slideNodes = docBody.getChildNodes(); 143 int slidesNum = slideNodes.getLength(); 144 ArrayList<SlideModel> slides = new ArrayList<SlideModel>(slidesNum); 145 int totalMessageSize = 0; 146 147 for (int i = 0; i < slidesNum; i++) { 148 // FIXME: This is NOT compatible with the SMILDocument which is 149 // generated by some other mobile phones. 150 SMILParElement par = (SMILParElement) slideNodes.item(i); 151 152 // Create media models for each slide. 153 NodeList mediaNodes = par.getChildNodes(); 154 int mediaNum = mediaNodes.getLength(); 155 ArrayList<MediaModel> mediaSet = new ArrayList<MediaModel>(mediaNum); 156 157 for (int j = 0; j < mediaNum; j++) { 158 SMILMediaElement sme = (SMILMediaElement) mediaNodes.item(j); 159 try { 160 MediaModel media = MediaModelFactory.getMediaModel( 161 context, sme, layouts, pb); 162 163 /* 164 * This is for slide duration value set. 165 * If mms server does not support slide duration. 166 */ 167 if (!MmsConfig.getSlideDurationEnabled()) { 168 int mediadur = media.getDuration(); 169 float dur = par.getDur(); 170 if (dur == 0) { 171 mediadur = MmsConfig.getMinimumSlideElementDuration() * 1000; 172 media.setDuration(mediadur); 173 } 174 175 if ((int)mediadur / 1000 != dur) { 176 String tag = sme.getTagName(); 177 178 if (ContentType.isVideoType(media.mContentType) 179 || tag.equals(SmilHelper.ELEMENT_TAG_VIDEO) 180 || ContentType.isAudioType(media.mContentType) 181 || tag.equals(SmilHelper.ELEMENT_TAG_AUDIO)) { 182 /* 183 * add 1 sec to release and close audio/video 184 * for guaranteeing the audio/video playing. 185 * because the mmsc does not support the slide duration. 186 */ 187 par.setDur((float)mediadur / 1000 + 1); 188 } else { 189 /* 190 * If a slide has an image and an audio/video element 191 * and the audio/video element has longer duration than the image, 192 * The Image disappear before the slide play done. so have to match 193 * an image duration to the slide duration. 194 */ 195 if ((int)mediadur / 1000 < dur) { 196 media.setDuration((int)dur * 1000); 197 } else { 198 if ((int)dur != 0) { 199 media.setDuration((int)dur * 1000); 200 } else { 201 par.setDur((float)mediadur / 1000); 202 } 203 } 204 } 205 } 206 } 207 SmilHelper.addMediaElementEventListeners( 208 (EventTarget) sme, media); 209 mediaSet.add(media); 210 totalMessageSize += media.getMediaSize(); 211 } catch (IOException e) { 212 Log.e(TAG, e.getMessage(), e); 213 } catch (IllegalArgumentException e) { 214 Log.e(TAG, e.getMessage(), e); 215 } 216 } 217 218 SlideModel slide = new SlideModel((int) (par.getDur() * 1000), mediaSet); 219 slide.setFill(par.getFill()); 220 SmilHelper.addParElementEventListeners((EventTarget) par, slide); 221 slides.add(slide); 222 } 223 224 SlideshowModel slideshow = new SlideshowModel(layouts, slides, document, pb, context); 225 slideshow.mTotalMessageSize = totalMessageSize; 226 slideshow.registerModelChangedObserver(slideshow); 227 return slideshow; 228 } 229 toPduBody()230 public PduBody toPduBody() { 231 if (mPduBodyCache == null) { 232 mDocumentCache = SmilHelper.getDocument(this); 233 mPduBodyCache = makePduBody(mDocumentCache); 234 } 235 return mPduBodyCache; 236 } 237 makePduBody(SMILDocument document)238 private PduBody makePduBody(SMILDocument document) { 239 PduBody pb = new PduBody(); 240 241 boolean hasForwardLock = false; 242 for (SlideModel slide : mSlides) { 243 for (MediaModel media : slide) { 244 PduPart part = new PduPart(); 245 246 if (media.isText()) { 247 TextModel text = (TextModel) media; 248 // Don't create empty text part. 249 if (TextUtils.isEmpty(text.getText())) { 250 continue; 251 } 252 // Set Charset if it's a text media. 253 part.setCharset(text.getCharset()); 254 } 255 256 // Set Content-Type. 257 part.setContentType(media.getContentType().getBytes()); 258 259 String src = media.getSrc(); 260 String location; 261 boolean startWithContentId = src.startsWith("cid:"); 262 if (startWithContentId) { 263 location = src.substring("cid:".length()); 264 } else { 265 location = src; 266 } 267 268 // Set Content-Location. 269 part.setContentLocation(location.getBytes()); 270 271 // Set Content-Id. 272 if (startWithContentId) { 273 //Keep the original Content-Id. 274 part.setContentId(location.getBytes()); 275 } 276 else { 277 int index = location.lastIndexOf("."); 278 String contentId = (index == -1) ? location 279 : location.substring(0, index); 280 part.setContentId(contentId.getBytes()); 281 } 282 283 if (media.isText()) { 284 part.setData(((TextModel) media).getText().getBytes()); 285 } else if (media.isImage() || media.isVideo() || media.isAudio()) { 286 part.setDataUri(media.getUri()); 287 } else { 288 Log.w(TAG, "Unsupport media: " + media); 289 } 290 291 pb.addPart(part); 292 } 293 } 294 295 // Create and insert SMIL part(as the first part) into the PduBody. 296 ByteArrayOutputStream out = new ByteArrayOutputStream(); 297 SmilXmlSerializer.serialize(document, out); 298 PduPart smilPart = new PduPart(); 299 smilPart.setContentId("smil".getBytes()); 300 smilPart.setContentLocation("smil.xml".getBytes()); 301 smilPart.setContentType(ContentType.APP_SMIL.getBytes()); 302 smilPart.setData(out.toByteArray()); 303 pb.addPart(0, smilPart); 304 305 return pb; 306 } 307 makeCopy()308 public PduBody makeCopy() { 309 return makePduBody(SmilHelper.getDocument(this)); 310 } 311 toSmilDocument()312 public SMILDocument toSmilDocument() { 313 if (mDocumentCache == null) { 314 mDocumentCache = SmilHelper.getDocument(this); 315 } 316 return mDocumentCache; 317 } 318 getPduBody(Context context, Uri msg)319 public static PduBody getPduBody(Context context, Uri msg) throws MmsException { 320 PduPersister p = PduPersister.getPduPersister(context); 321 GenericPdu pdu = p.load(msg); 322 323 int msgType = pdu.getMessageType(); 324 if ((msgType == PduHeaders.MESSAGE_TYPE_SEND_REQ) 325 || (msgType == PduHeaders.MESSAGE_TYPE_RETRIEVE_CONF)) { 326 return ((MultimediaMessagePdu) pdu).getBody(); 327 } else { 328 throw new MmsException(); 329 } 330 } 331 setCurrentMessageSize(int size)332 public void setCurrentMessageSize(int size) { 333 mCurrentMessageSize = size; 334 } 335 336 // getCurrentMessageSize returns the size of the message, not including resizable attachments 337 // such as photos. mCurrentMessageSize is used when adding/deleting/replacing non-resizable 338 // attachments (movies, sounds, etc) in order to compute how much size is left in the message. 339 // The difference between mCurrentMessageSize and the maxSize allowed for a message is then 340 // divided up between the remaining resizable attachments. While this function is public, 341 // it is only used internally between various MMS classes. If the UI wants to know the 342 // size of a MMS message, it should call getTotalMessageSize() instead. getCurrentMessageSize()343 public int getCurrentMessageSize() { 344 return mCurrentMessageSize; 345 } 346 347 // getTotalMessageSize returns the total size of the message, including resizable attachments 348 // such as photos. This function is intended to be used by the UI for displaying the size of the 349 // MMS message. getTotalMessageSize()350 public int getTotalMessageSize() { 351 return mTotalMessageSize; 352 } 353 increaseMessageSize(int increaseSize)354 public void increaseMessageSize(int increaseSize) { 355 if (increaseSize > 0) { 356 mCurrentMessageSize += increaseSize; 357 } 358 } 359 decreaseMessageSize(int decreaseSize)360 public void decreaseMessageSize(int decreaseSize) { 361 if (decreaseSize > 0) { 362 mCurrentMessageSize -= decreaseSize; 363 } 364 } 365 getLayout()366 public LayoutModel getLayout() { 367 return mLayout; 368 } 369 370 // 371 // Implement List<E> interface. 372 // add(SlideModel object)373 public boolean add(SlideModel object) { 374 int increaseSize = object.getSlideSize(); 375 checkMessageSize(increaseSize); 376 377 if ((object != null) && mSlides.add(object)) { 378 increaseMessageSize(increaseSize); 379 object.registerModelChangedObserver(this); 380 for (IModelChangedObserver observer : mModelChangedObservers) { 381 object.registerModelChangedObserver(observer); 382 } 383 notifyModelChanged(true); 384 return true; 385 } 386 return false; 387 } 388 addAll(Collection<? extends SlideModel> collection)389 public boolean addAll(Collection<? extends SlideModel> collection) { 390 throw new UnsupportedOperationException("Operation not supported."); 391 } 392 clear()393 public void clear() { 394 if (mSlides.size() > 0) { 395 for (SlideModel slide : mSlides) { 396 slide.unregisterModelChangedObserver(this); 397 for (IModelChangedObserver observer : mModelChangedObservers) { 398 slide.unregisterModelChangedObserver(observer); 399 } 400 } 401 mCurrentMessageSize = 0; 402 mSlides.clear(); 403 notifyModelChanged(true); 404 } 405 } 406 contains(Object object)407 public boolean contains(Object object) { 408 return mSlides.contains(object); 409 } 410 containsAll(Collection<?> collection)411 public boolean containsAll(Collection<?> collection) { 412 return mSlides.containsAll(collection); 413 } 414 isEmpty()415 public boolean isEmpty() { 416 return mSlides.isEmpty(); 417 } 418 iterator()419 public Iterator<SlideModel> iterator() { 420 return mSlides.iterator(); 421 } 422 remove(Object object)423 public boolean remove(Object object) { 424 if ((object != null) && mSlides.remove(object)) { 425 SlideModel slide = (SlideModel) object; 426 decreaseMessageSize(slide.getSlideSize()); 427 slide.unregisterAllModelChangedObservers(); 428 notifyModelChanged(true); 429 return true; 430 } 431 return false; 432 } 433 removeAll(Collection<?> collection)434 public boolean removeAll(Collection<?> collection) { 435 throw new UnsupportedOperationException("Operation not supported."); 436 } 437 retainAll(Collection<?> collection)438 public boolean retainAll(Collection<?> collection) { 439 throw new UnsupportedOperationException("Operation not supported."); 440 } 441 size()442 public int size() { 443 return mSlides.size(); 444 } 445 toArray()446 public Object[] toArray() { 447 return mSlides.toArray(); 448 } 449 toArray(T[] array)450 public <T> T[] toArray(T[] array) { 451 return mSlides.toArray(array); 452 } 453 add(int location, SlideModel object)454 public void add(int location, SlideModel object) { 455 if (object != null) { 456 int increaseSize = object.getSlideSize(); 457 checkMessageSize(increaseSize); 458 459 mSlides.add(location, object); 460 increaseMessageSize(increaseSize); 461 object.registerModelChangedObserver(this); 462 for (IModelChangedObserver observer : mModelChangedObservers) { 463 object.registerModelChangedObserver(observer); 464 } 465 notifyModelChanged(true); 466 } 467 } 468 addAll(int location, Collection<? extends SlideModel> collection)469 public boolean addAll(int location, 470 Collection<? extends SlideModel> collection) { 471 throw new UnsupportedOperationException("Operation not supported."); 472 } 473 get(int location)474 public SlideModel get(int location) { 475 return (location >= 0 && location < mSlides.size()) ? mSlides.get(location) : null; 476 } 477 indexOf(Object object)478 public int indexOf(Object object) { 479 return mSlides.indexOf(object); 480 } 481 lastIndexOf(Object object)482 public int lastIndexOf(Object object) { 483 return mSlides.lastIndexOf(object); 484 } 485 listIterator()486 public ListIterator<SlideModel> listIterator() { 487 return mSlides.listIterator(); 488 } 489 listIterator(int location)490 public ListIterator<SlideModel> listIterator(int location) { 491 return mSlides.listIterator(location); 492 } 493 remove(int location)494 public SlideModel remove(int location) { 495 SlideModel slide = mSlides.remove(location); 496 if (slide != null) { 497 decreaseMessageSize(slide.getSlideSize()); 498 slide.unregisterAllModelChangedObservers(); 499 notifyModelChanged(true); 500 } 501 return slide; 502 } 503 set(int location, SlideModel object)504 public SlideModel set(int location, SlideModel object) { 505 SlideModel slide = mSlides.get(location); 506 if (null != object) { 507 int removeSize = 0; 508 int addSize = object.getSlideSize(); 509 if (null != slide) { 510 removeSize = slide.getSlideSize(); 511 } 512 if (addSize > removeSize) { 513 checkMessageSize(addSize - removeSize); 514 increaseMessageSize(addSize - removeSize); 515 } else { 516 decreaseMessageSize(removeSize - addSize); 517 } 518 } 519 520 slide = mSlides.set(location, object); 521 if (slide != null) { 522 slide.unregisterAllModelChangedObservers(); 523 } 524 525 if (object != null) { 526 object.registerModelChangedObserver(this); 527 for (IModelChangedObserver observer : mModelChangedObservers) { 528 object.registerModelChangedObserver(observer); 529 } 530 } 531 532 notifyModelChanged(true); 533 return slide; 534 } 535 subList(int start, int end)536 public List<SlideModel> subList(int start, int end) { 537 return mSlides.subList(start, end); 538 } 539 540 @Override registerModelChangedObserverInDescendants( IModelChangedObserver observer)541 protected void registerModelChangedObserverInDescendants( 542 IModelChangedObserver observer) { 543 mLayout.registerModelChangedObserver(observer); 544 545 for (SlideModel slide : mSlides) { 546 slide.registerModelChangedObserver(observer); 547 } 548 } 549 550 @Override unregisterModelChangedObserverInDescendants( IModelChangedObserver observer)551 protected void unregisterModelChangedObserverInDescendants( 552 IModelChangedObserver observer) { 553 mLayout.unregisterModelChangedObserver(observer); 554 555 for (SlideModel slide : mSlides) { 556 slide.unregisterModelChangedObserver(observer); 557 } 558 } 559 560 @Override unregisterAllModelChangedObserversInDescendants()561 protected void unregisterAllModelChangedObserversInDescendants() { 562 mLayout.unregisterAllModelChangedObservers(); 563 564 for (SlideModel slide : mSlides) { 565 slide.unregisterAllModelChangedObservers(); 566 } 567 } 568 onModelChanged(Model model, boolean dataChanged)569 public void onModelChanged(Model model, boolean dataChanged) { 570 if (dataChanged) { 571 mDocumentCache = null; 572 mPduBodyCache = null; 573 } 574 } 575 sync(PduBody pb)576 public void sync(PduBody pb) { 577 for (SlideModel slide : mSlides) { 578 for (MediaModel media : slide) { 579 PduPart part = pb.getPartByContentLocation(media.getSrc()); 580 if (part != null) { 581 media.setUri(part.getDataUri()); 582 } 583 } 584 } 585 } 586 checkMessageSize(int increaseSize)587 public void checkMessageSize(int increaseSize) throws ContentRestrictionException { 588 ContentRestriction cr = ContentRestrictionFactory.getContentRestriction(); 589 cr.checkMessageSize(mCurrentMessageSize, increaseSize, mContext.getContentResolver()); 590 } 591 592 /** 593 * Determines whether this is a "simple" slideshow. 594 * Criteria: 595 * - Exactly one slide 596 * - Exactly one multimedia attachment, but no audio 597 * - It can optionally have a caption 598 */ isSimple()599 public boolean isSimple() { 600 // There must be one (and only one) slide. 601 if (size() != 1) 602 return false; 603 604 SlideModel slide = get(0); 605 // The slide must have either an image or video, but not both. 606 if (!(slide.hasImage() ^ slide.hasVideo())) 607 return false; 608 609 // No audio allowed. 610 if (slide.hasAudio()) 611 return false; 612 613 return true; 614 } 615 616 /** 617 * Make sure the text in slide 0 is no longer holding onto a reference to the text 618 * in the message text box. 619 */ prepareForSend()620 public void prepareForSend() { 621 if (size() == 1) { 622 TextModel text = get(0).getText(); 623 if (text != null) { 624 text.cloneText(); 625 } 626 } 627 } 628 629 /** 630 * Resize all the resizeable media objects to fit in the remaining size of the slideshow. 631 * This should be called off of the UI thread. 632 * 633 * @throws MmsException, ExceedMessageSizeException 634 */ finalResize(Uri messageUri)635 public void finalResize(Uri messageUri) throws MmsException, ExceedMessageSizeException { 636 637 // Figure out if we have any media items that need to be resized and total up the 638 // sizes of the items that can't be resized. 639 int resizableCnt = 0; 640 int fixedSizeTotal = 0; 641 for (SlideModel slide : mSlides) { 642 for (MediaModel media : slide) { 643 if (media.getMediaResizable()) { 644 ++resizableCnt; 645 } else { 646 fixedSizeTotal += media.getMediaSize(); 647 } 648 } 649 } 650 if (Log.isLoggable(LogTag.APP, Log.VERBOSE)) { 651 Log.v(TAG, "finalResize: original message size: " + getCurrentMessageSize() + 652 " getMaxMessageSize: " + MmsConfig.getMaxMessageSize() + 653 " fixedSizeTotal: " + fixedSizeTotal); 654 } 655 if (resizableCnt > 0) { 656 int remainingSize = MmsConfig.getMaxMessageSize() - fixedSizeTotal - SLIDESHOW_SLOP; 657 if (remainingSize <= 0) { 658 throw new ExceedMessageSizeException("No room for pictures"); 659 } 660 long messageId = ContentUris.parseId(messageUri); 661 int bytesPerMediaItem = remainingSize / resizableCnt; 662 // Resize the resizable media items to fit within their byte limit. 663 for (SlideModel slide : mSlides) { 664 for (MediaModel media : slide) { 665 if (media.getMediaResizable()) { 666 media.resizeMedia(bytesPerMediaItem, messageId); 667 } 668 } 669 } 670 // One last time through to calc the real message size. 671 int totalSize = 0; 672 for (SlideModel slide : mSlides) { 673 for (MediaModel media : slide) { 674 totalSize += media.getMediaSize(); 675 } 676 } 677 if (Log.isLoggable(LogTag.APP, Log.VERBOSE)) { 678 Log.v(TAG, "finalResize: new message size: " + totalSize); 679 } 680 681 if (totalSize > MmsConfig.getMaxMessageSize()) { 682 throw new ExceedMessageSizeException("After compressing pictures, message too big"); 683 } 684 setCurrentMessageSize(totalSize); 685 686 onModelChanged(this, true); // clear the cached pdu body 687 PduBody pb = toPduBody(); 688 // This will write out all the new parts to: 689 // /data/data/com.android.providers.telephony/app_parts 690 // and at the same time delete the old parts. 691 PduPersister.getPduPersister(mContext).updateParts(messageUri, pb); 692 } 693 } 694 695 } 696