• 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 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 
SmilHelper()70     private SmilHelper() {
71         // Never instantiate this class.
72     }
73 
getDocument(PduBody pb)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 
getDocument(SlideshowModel model)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      */
findSmilPart(PduBody body)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 
validate(SMILDocument in)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      */
getSmilDocument(PduPart smilPart)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 
addPar(SMILDocument document)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 
createMediaElement( String tag, SMILDocument document, String src)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 
escapeXML(String str)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 
createSmilDocument(PduBody pb)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 
createSmilDocument(SlideshowModel slideshow)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 
findRegionElementById( ArrayList<SMILRegionElement> smilRegions, String rId)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 
setRegion(SMILRegionMediaElement srme, ArrayList<SMILRegionElement> smilRegions, SMILLayoutElement smilLayout, String regionId, boolean regionPresentInLayout)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 
addMediaElementEventListeners( EventTarget target, MediaModel media)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 
addParElementEventListeners( EventTarget target, SlideModel slide)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