Home | History | Annotate | Download | only in model
      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 static com.android.mms.dom.smil.SmilMediaElementImpl.SMIL_MEDIA_END_EVENT;
     21 import static com.android.mms.dom.smil.SmilMediaElementImpl.SMIL_MEDIA_PAUSE_EVENT;
     22 import static com.android.mms.dom.smil.SmilMediaElementImpl.SMIL_MEDIA_SEEK_EVENT;
     23 import static com.android.mms.dom.smil.SmilMediaElementImpl.SMIL_MEDIA_START_EVENT;
     24 import static com.android.mms.dom.smil.SmilParElementImpl.SMIL_SLIDE_END_EVENT;
     25 import static com.android.mms.dom.smil.SmilParElementImpl.SMIL_SLIDE_START_EVENT;
     26 
     27 import com.android.mms.MmsApp;
     28 import com.android.mms.dom.smil.SmilDocumentImpl;
     29 import com.android.mms.dom.smil.parser.SmilXmlParser;
     30 import com.android.mms.dom.smil.parser.SmilXmlSerializer;
     31 import com.android.mms.drm.DrmUtils;
     32 import com.google.android.mms.ContentType;
     33 import com.google.android.mms.MmsException;
     34 import com.google.android.mms.pdu.PduBody;
     35 import com.google.android.mms.pdu.PduPart;
     36 
     37 import org.w3c.dom.events.EventTarget;
     38 import org.w3c.dom.smil.SMILDocument;
     39 import org.w3c.dom.smil.SMILElement;
     40 import org.w3c.dom.smil.SMILLayoutElement;
     41 import org.w3c.dom.smil.SMILMediaElement;
     42 import org.w3c.dom.smil.SMILParElement;
     43 import org.w3c.dom.smil.SMILRegionElement;
     44 import org.w3c.dom.smil.SMILRegionMediaElement;
     45 import org.w3c.dom.smil.SMILRootLayoutElement;
     46 import org.xml.sax.SAXException;
     47 
     48 import android.content.ContentResolver;
     49 import android.database.Cursor;
     50 import android.database.sqlite.SQLiteException;
     51 import android.drm.DrmManagerClient;
     52 import android.net.Uri;
     53 import android.provider.MediaStore;
     54 import android.text.TextUtils;
     55 import android.util.Config;
     56 import android.util.Log;
     57 
     58 import java.io.ByteArrayInputStream;
     59 import java.io.ByteArrayOutputStream;
     60 import java.io.File;
     61 import java.io.IOException;
     62 import java.util.ArrayList;
     63 import java.util.Arrays;
     64 
     65 public class SmilHelper {
     66     private static final String TAG = "Mms/smil";
     67     private static final boolean DEBUG = false;
     68     private static final boolean LOCAL_LOGV = DEBUG ? Config.LOGD : Config.LOGV;
     69 
     70     public static final String ELEMENT_TAG_TEXT = "text";
     71     public static final String ELEMENT_TAG_IMAGE = "img";
     72     public static final String ELEMENT_TAG_AUDIO = "audio";
     73     public static final String ELEMENT_TAG_VIDEO = "video";
     74     public static final String ELEMENT_TAG_REF = "ref";
     75 
     76     private SmilHelper() {
     77         // Never instantiate this class.
     78     }
     79 
     80     public static SMILDocument getDocument(PduBody pb) {
     81         // Find SMIL part in the message.
     82         PduPart smilPart = findSmilPart(pb);
     83         SMILDocument document = null;
     84 
     85         // Try to load SMIL document from existing part.
     86         if (smilPart != null) {
     87             document = getSmilDocument(smilPart);
     88         }
     89 
     90         if (document == null) {
     91             // Create a new SMIL document.
     92             document = createSmilDocument(pb);
     93         }
     94 
     95         return document;
     96     }
     97 
     98     public static SMILDocument getDocument(SlideshowModel model) {
     99         return createSmilDocument(model);
    100     }
    101 
    102     /**
    103      * Find a SMIL part in the MM.
    104      *
    105      * @return The existing SMIL part or null if no SMIL part was found.
    106      */
    107     private static PduPart findSmilPart(PduBody body) {
    108         int partNum = body.getPartsNum();
    109         for(int i = 0; i < partNum; i++) {
    110             PduPart part = body.getPart(i);
    111             if (Arrays.equals(part.getContentType(),
    112                             ContentType.APP_SMIL.getBytes())) {
    113                 // Sure only one SMIL part.
    114                 return part;
    115             }
    116         }
    117         return null;
    118     }
    119 
    120     private static SMILDocument validate(SMILDocument in) {
    121         // TODO: add more validating facilities.
    122         return in;
    123     }
    124 
    125     /**
    126      * Parse SMIL message and retrieve SMILDocument.
    127      *
    128      * @return A SMILDocument or null if parsing failed.
    129      */
    130     private static SMILDocument getSmilDocument(PduPart smilPart) {
    131         try {
    132             byte[] data = smilPart.getData();
    133             if (data != null) {
    134                 if (LOCAL_LOGV) {
    135                     Log.v(TAG, "Parsing SMIL document.");
    136                     Log.v(TAG, new String(data));
    137                 }
    138 
    139                 ByteArrayInputStream bais = new ByteArrayInputStream(data);
    140                 SMILDocument document = new SmilXmlParser().parse(bais);
    141                 return validate(document);
    142             }
    143         } catch (IOException e) {
    144             Log.e(TAG, "Failed to parse SMIL document.", e);
    145         } catch (SAXException e) {
    146             Log.e(TAG, "Failed to parse SMIL document.", e);
    147         } catch (MmsException e) {
    148             Log.e(TAG, "Failed to parse SMIL document.", e);
    149         }
    150         return null;
    151     }
    152 
    153     public static SMILParElement addPar(SMILDocument document) {
    154         SMILParElement par = (SMILParElement) document.createElement("par");
    155         // Set duration to eight seconds by default.
    156         par.setDur(8.0f);
    157         document.getBody().appendChild(par);
    158         return par;
    159     }
    160 
    161     public static SMILMediaElement createMediaElement(
    162             String tag, SMILDocument document, String src) {
    163         SMILMediaElement mediaElement =
    164                 (SMILMediaElement) document.createElement(tag);
    165         mediaElement.setSrc(escapeXML(src));
    166         return mediaElement;
    167     }
    168 
    169     static public String escapeXML(String str) {
    170         return str.replaceAll("&","&amp;")
    171                   .replaceAll("<", "&lt;")
    172                   .replaceAll(">", "&gt;")
    173                   .replaceAll("\"", "&quot;")
    174                   .replaceAll("'", "&apos;");
    175     }
    176 
    177     private static SMILDocument createSmilDocument(PduBody pb) {
    178         if (Config.LOGV) {
    179             Log.v(TAG, "Creating default SMIL document.");
    180         }
    181 
    182         SMILDocument document = new SmilDocumentImpl();
    183 
    184         // Create root element.
    185         // FIXME: Should we create root element in the constructor of document?
    186         SMILElement smil = (SMILElement) document.createElement("smil");
    187         smil.setAttribute("xmlns", "http://www.w3.org/2001/SMIL20/Language");
    188         document.appendChild(smil);
    189 
    190         // Create <head> and <layout> element.
    191         SMILElement head = (SMILElement) document.createElement("head");
    192         smil.appendChild(head);
    193 
    194         SMILLayoutElement layout = (SMILLayoutElement) document.createElement("layout");
    195         head.appendChild(layout);
    196 
    197         // Create <body> element and add a empty <par>.
    198         SMILElement body = (SMILElement) document.createElement("body");
    199         smil.appendChild(body);
    200         SMILParElement par = addPar(document);
    201 
    202         // Create media objects for the parts in PDU.
    203         int partsNum = pb.getPartsNum();
    204         if (partsNum == 0) {
    205             return document;
    206         }
    207 
    208         DrmManagerClient drmManagerClient = MmsApp.getApplication().getDrmManagerClient();
    209 
    210         boolean hasText = false;
    211         boolean hasMedia = false;
    212         for (int i = 0; i < partsNum; i++) {
    213             // Create new <par> element.
    214             if ((par == null) || (hasMedia && hasText)) {
    215                 par = addPar(document);
    216                 hasText = false;
    217                 hasMedia = false;
    218             }
    219 
    220             PduPart part = pb.getPart(i);
    221             String contentType = new String(part.getContentType());
    222 
    223             if (ContentType.isDrmType(contentType)) {
    224                 contentType = drmManagerClient.getOriginalMimeType(part.getDataUri());
    225             }
    226 
    227             if (contentType.equals(ContentType.TEXT_PLAIN)
    228                     || contentType.equalsIgnoreCase(ContentType.APP_WAP_XHTML)
    229                     || contentType.equals(ContentType.TEXT_HTML)) {
    230                 SMILMediaElement textElement = createMediaElement(
    231                         ELEMENT_TAG_TEXT, document, part.generateLocation());
    232                 par.appendChild(textElement);
    233                 hasText = true;
    234             } else if (ContentType.isImageType(contentType)) {
    235                 SMILMediaElement imageElement = createMediaElement(
    236                         ELEMENT_TAG_IMAGE, document, part.generateLocation());
    237                 par.appendChild(imageElement);
    238                 hasMedia = true;
    239             } else if (ContentType.isVideoType(contentType)) {
    240                 SMILMediaElement videoElement = createMediaElement(
    241                         ELEMENT_TAG_VIDEO, document, part.generateLocation());
    242                 par.appendChild(videoElement);
    243                 hasMedia = true;
    244             } else if (ContentType.isAudioType(contentType)) {
    245                 SMILMediaElement audioElement = createMediaElement(
    246                         ELEMENT_TAG_AUDIO, document, part.generateLocation());
    247                 par.appendChild(audioElement);
    248                 hasMedia = true;
    249             } else {
    250                 // TODO: handle other media types.
    251                 Log.w(TAG, "unsupport media type");
    252             }
    253         }
    254 
    255         return document;
    256     }
    257 
    258     private static SMILDocument createSmilDocument(SlideshowModel slideshow) {
    259         if (Config.LOGV) {
    260             Log.v(TAG, "Creating SMIL document from SlideshowModel.");
    261         }
    262 
    263         SMILDocument document = new SmilDocumentImpl();
    264 
    265         // Create SMIL and append it to document
    266         SMILElement smilElement = (SMILElement) document.createElement("smil");
    267         document.appendChild(smilElement);
    268 
    269         // Create HEAD and append it to SMIL
    270         SMILElement headElement = (SMILElement) document.createElement("head");
    271         smilElement.appendChild(headElement);
    272 
    273         // Create LAYOUT and append it to HEAD
    274         SMILLayoutElement layoutElement = (SMILLayoutElement)
    275                 document.createElement("layout");
    276         headElement.appendChild(layoutElement);
    277 
    278         // Create ROOT-LAYOUT and append it to LAYOUT
    279         SMILRootLayoutElement rootLayoutElement =
    280                 (SMILRootLayoutElement) document.createElement("root-layout");
    281         LayoutModel layouts = slideshow.getLayout();
    282         rootLayoutElement.setWidth(layouts.getLayoutWidth());
    283         rootLayoutElement.setHeight(layouts.getLayoutHeight());
    284         String bgColor = layouts.getBackgroundColor();
    285         if (!TextUtils.isEmpty(bgColor)) {
    286             rootLayoutElement.setBackgroundColor(bgColor);
    287         }
    288         layoutElement.appendChild(rootLayoutElement);
    289 
    290         // Create REGIONs and append them to LAYOUT
    291         ArrayList<RegionModel> regions = layouts.getRegions();
    292         ArrayList<SMILRegionElement> smilRegions = new ArrayList<SMILRegionElement>();
    293         for (RegionModel r : regions) {
    294             SMILRegionElement smilRegion = (SMILRegionElement) document.createElement("region");
    295             smilRegion.setId(r.getRegionId());
    296             smilRegion.setLeft(r.getLeft());
    297             smilRegion.setTop(r.getTop());
    298             smilRegion.setWidth(r.getWidth());
    299             smilRegion.setHeight(r.getHeight());
    300             smilRegion.setFit(r.getFit());
    301             smilRegions.add(smilRegion);
    302         }
    303 
    304         // Create BODY and append it to the document.
    305         SMILElement bodyElement = (SMILElement) document.createElement("body");
    306         smilElement.appendChild(bodyElement);
    307 
    308         for (SlideModel slide : slideshow) {
    309             boolean txtRegionPresentInLayout = false;
    310             boolean imgRegionPresentInLayout = false;
    311 
    312             // Create PAR element.
    313             SMILParElement par = addPar(document);
    314             par.setDur(slide.getDuration() / 1000f);
    315 
    316             addParElementEventListeners((EventTarget) par, slide);
    317 
    318             // Add all media elements.
    319             for (MediaModel media : slide) {
    320                 SMILMediaElement sme = null;
    321                 String src = media.getSrc();
    322                 if (media instanceof TextModel) {
    323                     TextModel text = (TextModel) media;
    324                     if (TextUtils.isEmpty(text.getText())) {
    325                         if (LOCAL_LOGV) {
    326                             Log.v(TAG, "Empty text part ignored: " + text.getSrc());
    327                         }
    328                         continue;
    329                     }
    330                     sme = SmilHelper.createMediaElement(SmilHelper.ELEMENT_TAG_TEXT, document, src);
    331                     txtRegionPresentInLayout = setRegion((SMILRegionMediaElement) sme,
    332                                                          smilRegions,
    333                                                          layoutElement,
    334                                                          LayoutModel.TEXT_REGION_ID,
    335                                                          txtRegionPresentInLayout);
    336                 } else if (media instanceof ImageModel) {
    337                     sme = SmilHelper.createMediaElement(SmilHelper.ELEMENT_TAG_IMAGE, document, src);
    338                     imgRegionPresentInLayout = setRegion((SMILRegionMediaElement) sme,
    339                                                          smilRegions,
    340                                                          layoutElement,
    341                                                          LayoutModel.IMAGE_REGION_ID,
    342                                                          imgRegionPresentInLayout);
    343                 } else if (media instanceof VideoModel) {
    344                     sme = SmilHelper.createMediaElement(SmilHelper.ELEMENT_TAG_VIDEO, document, src);
    345                     imgRegionPresentInLayout = setRegion((SMILRegionMediaElement) sme,
    346                                                          smilRegions,
    347                                                          layoutElement,
    348                                                          LayoutModel.IMAGE_REGION_ID,
    349                                                          imgRegionPresentInLayout);
    350                 } else if (media instanceof AudioModel) {
    351                     sme = SmilHelper.createMediaElement(SmilHelper.ELEMENT_TAG_AUDIO, document, src);
    352                 } else {
    353                     Log.w(TAG, "Unsupport media: " + media);
    354                     continue;
    355                 }
    356 
    357                 // Set timing information.
    358                 int begin = media.getBegin();
    359                 if (begin != 0) {
    360                     sme.setAttribute("begin", String.valueOf(begin / 1000));
    361                 }
    362                 int duration = media.getDuration();
    363                 if (duration != 0) {
    364                     sme.setDur((float) duration / 1000);
    365                 }
    366                 par.appendChild(sme);
    367 
    368                 addMediaElementEventListeners((EventTarget) sme, media);
    369             }
    370         }
    371 
    372         if (LOCAL_LOGV) {
    373             ByteArrayOutputStream out = new ByteArrayOutputStream();
    374             SmilXmlSerializer.serialize(document, out);
    375             Log.v(TAG, out.toString());
    376         }
    377 
    378         return document;
    379     }
    380 
    381     private static SMILRegionElement findRegionElementById(
    382             ArrayList<SMILRegionElement> smilRegions, String rId) {
    383         for (SMILRegionElement smilRegion : smilRegions) {
    384             if (smilRegion.getId().equals(rId)) {
    385                 return smilRegion;
    386             }
    387         }
    388         return null;
    389     }
    390 
    391     private static boolean setRegion(SMILRegionMediaElement srme,
    392                                      ArrayList<SMILRegionElement> smilRegions,
    393                                      SMILLayoutElement smilLayout,
    394                                      String regionId,
    395                                      boolean regionPresentInLayout) {
    396         SMILRegionElement smilRegion = findRegionElementById(smilRegions, regionId);
    397         if (!regionPresentInLayout && smilRegion != null) {
    398             srme.setRegion(smilRegion);
    399             smilLayout.appendChild(smilRegion);
    400             return true;
    401         }
    402         return false;
    403     }
    404 
    405     static void addMediaElementEventListeners(
    406             EventTarget target, MediaModel media) {
    407         // To play the media with SmilPlayer, we should add them
    408         // as EventListener into an EventTarget.
    409         target.addEventListener(SMIL_MEDIA_START_EVENT, media, false);
    410         target.addEventListener(SMIL_MEDIA_END_EVENT, media, false);
    411         target.addEventListener(SMIL_MEDIA_PAUSE_EVENT, media, false);
    412         target.addEventListener(SMIL_MEDIA_SEEK_EVENT, media, false);
    413     }
    414 
    415     static void addParElementEventListeners(
    416             EventTarget target, SlideModel slide) {
    417         // To play the slide with SmilPlayer, we should add it
    418         // as EventListener into an EventTarget.
    419         target.addEventListener(SMIL_SLIDE_START_EVENT, slide, false);
    420         target.addEventListener(SMIL_SLIDE_END_EVENT, slide, false);
    421     }
    422 }
    423