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