• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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.Config;
50 import android.util.Log;
51 
52 import java.io.ByteArrayInputStream;
53 import java.io.ByteArrayOutputStream;
54 import java.io.IOException;
55 import java.util.ArrayList;
56 import java.util.Arrays;
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 
SmilHelper()69     private SmilHelper() {
70         // Never instantiate this class.
71     }
72 
getDocument(PduBody pb)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 
getDocument(SlideshowModel model)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      */
findSmilPart(PduBody body)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 
validate(SMILDocument in)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      */
getSmilDocument(PduPart smilPart)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 
addPar(SMILDocument document)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 
createMediaElement( String tag, SMILDocument document, String src)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 
escapeXML(String str)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 
createSmilDocument(PduBody pb)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         boolean hasText = false;
202         boolean hasMedia = false;
203         for (int i = 0; i < partsNum; i++) {
204             // Create new <par> element.
205             if ((par == null) || (hasMedia && hasText)) {
206                 par = addPar(document);
207                 hasText = false;
208                 hasMedia = false;
209             }
210 
211             PduPart part = pb.getPart(i);
212             String contentType = new String(part.getContentType());
213             if (ContentType.isDrmType(contentType)) {
214                 DrmWrapper dw;
215                 try {
216                     dw = new DrmWrapper(contentType, part.getDataUri(),
217                                         part.getData());
218                     contentType = dw.getContentType();
219                 } catch (DrmException e) {
220                     Log.e(TAG, e.getMessage(), e);
221                 } catch (IOException e) {
222                     Log.e(TAG, e.getMessage(), e);
223                 }
224             }
225 
226             if (contentType.equals(ContentType.TEXT_PLAIN)
227                     || contentType.equalsIgnoreCase(ContentType.APP_WAP_XHTML)
228                     || contentType.equals(ContentType.TEXT_HTML)) {
229                 SMILMediaElement textElement = createMediaElement(
230                         ELEMENT_TAG_TEXT, document, part.generateLocation());
231                 par.appendChild(textElement);
232                 hasText = true;
233             } else if (ContentType.isImageType(contentType)) {
234                 SMILMediaElement imageElement = createMediaElement(
235                         ELEMENT_TAG_IMAGE, document, part.generateLocation());
236                 par.appendChild(imageElement);
237                 hasMedia = true;
238             } else if (ContentType.isVideoType(contentType)) {
239                 SMILMediaElement videoElement = createMediaElement(
240                         ELEMENT_TAG_VIDEO, document, part.generateLocation());
241                 par.appendChild(videoElement);
242                 hasMedia = true;
243             } else if (ContentType.isAudioType(contentType)) {
244                 SMILMediaElement audioElement = createMediaElement(
245                         ELEMENT_TAG_AUDIO, document, part.generateLocation());
246                 par.appendChild(audioElement);
247                 hasMedia = true;
248             } else {
249                 // TODO: handle other media types.
250                 Log.w(TAG, "unsupport media type");
251             }
252         }
253 
254         return document;
255     }
256 
createSmilDocument(SlideshowModel slideshow)257     private static SMILDocument createSmilDocument(SlideshowModel slideshow) {
258         if (Config.LOGV) {
259             Log.v(TAG, "Creating SMIL document from SlideshowModel.");
260         }
261 
262         SMILDocument document = new SmilDocumentImpl();
263 
264         // Create SMIL and append it to document
265         SMILElement smilElement = (SMILElement) document.createElement("smil");
266         document.appendChild(smilElement);
267 
268         // Create HEAD and append it to SMIL
269         SMILElement headElement = (SMILElement) document.createElement("head");
270         smilElement.appendChild(headElement);
271 
272         // Create LAYOUT and append it to HEAD
273         SMILLayoutElement layoutElement = (SMILLayoutElement)
274                 document.createElement("layout");
275         headElement.appendChild(layoutElement);
276 
277         // Create ROOT-LAYOUT and append it to LAYOUT
278         SMILRootLayoutElement rootLayoutElement =
279                 (SMILRootLayoutElement) document.createElement("root-layout");
280         LayoutModel layouts = slideshow.getLayout();
281         rootLayoutElement.setWidth(layouts.getLayoutWidth());
282         rootLayoutElement.setHeight(layouts.getLayoutHeight());
283         String bgColor = layouts.getBackgroundColor();
284         if (!TextUtils.isEmpty(bgColor)) {
285             rootLayoutElement.setBackgroundColor(bgColor);
286         }
287         layoutElement.appendChild(rootLayoutElement);
288 
289         // Create REGIONs and append them to LAYOUT
290         ArrayList<RegionModel> regions = layouts.getRegions();
291         ArrayList<SMILRegionElement> smilRegions = new ArrayList<SMILRegionElement>();
292         for (RegionModel r : regions) {
293             SMILRegionElement smilRegion = (SMILRegionElement) document.createElement("region");
294             smilRegion.setId(r.getRegionId());
295             smilRegion.setLeft(r.getLeft());
296             smilRegion.setTop(r.getTop());
297             smilRegion.setWidth(r.getWidth());
298             smilRegion.setHeight(r.getHeight());
299             smilRegion.setFit(r.getFit());
300             smilRegions.add(smilRegion);
301         }
302 
303         // Create BODY and append it to the document.
304         SMILElement bodyElement = (SMILElement) document.createElement("body");
305         smilElement.appendChild(bodyElement);
306 
307         boolean txtRegionPresentInLayout = false;
308         boolean imgRegionPresentInLayout = false;
309         for (SlideModel slide : slideshow) {
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 
findRegionElementById( ArrayList<SMILRegionElement> smilRegions, String rId)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 
setRegion(SMILRegionMediaElement srme, ArrayList<SMILRegionElement> smilRegions, SMILLayoutElement smilLayout, String regionId, boolean regionPresentInLayout)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 
addMediaElementEventListeners( EventTarget target, MediaModel media)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 
addParElementEventListeners( EventTarget target, SlideModel slide)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