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