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