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