/*
* Copyright (C) 2008 Esmertec AG.
* Copyright (C) 2008 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.mms.model;
import static com.android.mms.dom.smil.SmilMediaElementImpl.SMIL_MEDIA_END_EVENT;
import static com.android.mms.dom.smil.SmilMediaElementImpl.SMIL_MEDIA_PAUSE_EVENT;
import static com.android.mms.dom.smil.SmilMediaElementImpl.SMIL_MEDIA_SEEK_EVENT;
import static com.android.mms.dom.smil.SmilMediaElementImpl.SMIL_MEDIA_START_EVENT;
import static com.android.mms.dom.smil.SmilParElementImpl.SMIL_SLIDE_END_EVENT;
import static com.android.mms.dom.smil.SmilParElementImpl.SMIL_SLIDE_START_EVENT;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import org.w3c.dom.events.EventTarget;
import org.w3c.dom.smil.SMILDocument;
import org.w3c.dom.smil.SMILElement;
import org.w3c.dom.smil.SMILLayoutElement;
import org.w3c.dom.smil.SMILMediaElement;
import org.w3c.dom.smil.SMILParElement;
import org.w3c.dom.smil.SMILRegionElement;
import org.w3c.dom.smil.SMILRegionMediaElement;
import org.w3c.dom.smil.SMILRootLayoutElement;
import org.xml.sax.SAXException;
import android.drm.DrmManagerClient;
import android.text.TextUtils;
import android.util.Config;
import android.util.Log;
import com.android.mms.MmsApp;
import com.android.mms.dom.smil.SmilDocumentImpl;
import com.android.mms.dom.smil.parser.SmilXmlParser;
import com.android.mms.dom.smil.parser.SmilXmlSerializer;
import com.google.android.mms.ContentType;
import com.google.android.mms.MmsException;
import com.google.android.mms.pdu.PduBody;
import com.google.android.mms.pdu.PduPart;
public class SmilHelper {
private static final String TAG = "Mms/smil";
private static final boolean DEBUG = false;
private static final boolean LOCAL_LOGV = DEBUG ? Config.LOGD : Config.LOGV;
public static final String ELEMENT_TAG_TEXT = "text";
public static final String ELEMENT_TAG_IMAGE = "img";
public static final String ELEMENT_TAG_AUDIO = "audio";
public static final String ELEMENT_TAG_VIDEO = "video";
public static final String ELEMENT_TAG_REF = "ref";
private SmilHelper() {
// Never instantiate this class.
}
public static SMILDocument getDocument(PduBody pb) {
// Find SMIL part in the message.
PduPart smilPart = findSmilPart(pb);
SMILDocument document = null;
// Try to load SMIL document from existing part.
if (smilPart != null) {
document = getSmilDocument(smilPart);
}
if (document == null) {
// Create a new SMIL document.
document = createSmilDocument(pb);
}
return document;
}
public static SMILDocument getDocument(SlideshowModel model) {
return createSmilDocument(model);
}
/**
* Find a SMIL part in the MM.
*
* @return The existing SMIL part or null if no SMIL part was found.
*/
private static PduPart findSmilPart(PduBody body) {
int partNum = body.getPartsNum();
for(int i = 0; i < partNum; i++) {
PduPart part = body.getPart(i);
if (Arrays.equals(part.getContentType(),
ContentType.APP_SMIL.getBytes())) {
// Sure only one SMIL part.
return part;
}
}
return null;
}
private static SMILDocument validate(SMILDocument in) {
// TODO: add more validating facilities.
return in;
}
/**
* Parse SMIL message and retrieve SMILDocument.
*
* @return A SMILDocument or null if parsing failed.
*/
private static SMILDocument getSmilDocument(PduPart smilPart) {
try {
byte[] data = smilPart.getData();
if (data != null) {
if (LOCAL_LOGV) {
Log.v(TAG, "Parsing SMIL document.");
Log.v(TAG, new String(data));
}
ByteArrayInputStream bais = new ByteArrayInputStream(data);
SMILDocument document = new SmilXmlParser().parse(bais);
return validate(document);
}
} catch (IOException e) {
Log.e(TAG, "Failed to parse SMIL document.", e);
} catch (SAXException e) {
Log.e(TAG, "Failed to parse SMIL document.", e);
} catch (MmsException e) {
Log.e(TAG, "Failed to parse SMIL document.", e);
}
return null;
}
public static SMILParElement addPar(SMILDocument document) {
SMILParElement par = (SMILParElement) document.createElement("par");
// Set duration to eight seconds by default.
par.setDur(8.0f);
document.getBody().appendChild(par);
return par;
}
public static SMILMediaElement createMediaElement(
String tag, SMILDocument document, String src) {
SMILMediaElement mediaElement =
(SMILMediaElement) document.createElement(tag);
mediaElement.setSrc(escapeXML(src));
return mediaElement;
}
static public String escapeXML(String str) {
return str.replaceAll("&","&")
.replaceAll("<", "<")
.replaceAll(">", ">")
.replaceAll("\"", """)
.replaceAll("'", "'");
}
private static SMILDocument createSmilDocument(PduBody pb) {
if (Config.LOGV) {
Log.v(TAG, "Creating default SMIL document.");
}
SMILDocument document = new SmilDocumentImpl();
// Create root element.
// FIXME: Should we create root element in the constructor of document?
SMILElement smil = (SMILElement) document.createElement("smil");
smil.setAttribute("xmlns", "http://www.w3.org/2001/SMIL20/Language");
document.appendChild(smil);
// Create <head> and <layout> element.
SMILElement head = (SMILElement) document.createElement("head");
smil.appendChild(head);
SMILLayoutElement layout = (SMILLayoutElement) document.createElement("layout");
head.appendChild(layout);
// Create <body> element and add a empty <par>.
SMILElement body = (SMILElement) document.createElement("body");
smil.appendChild(body);
SMILParElement par = addPar(document);
// Create media objects for the parts in PDU.
int partsNum = pb.getPartsNum();
if (partsNum == 0) {
return document;
}
DrmManagerClient drmManagerClient = MmsApp.getApplication().getDrmManagerClient();
boolean hasText = false;
boolean hasMedia = false;
for (int i = 0; i < partsNum; i++) {
// Create new <par> element.
if ((par == null) || (hasMedia && hasText)) {
par = addPar(document);
hasText = false;
hasMedia = false;
}
PduPart part = pb.getPart(i);
String contentType = new String(part.getContentType());
if (ContentType.isDrmType(contentType)) {
contentType = drmManagerClient.getOriginalMimeType(part.getDataUri());
}
if (contentType.equals(ContentType.TEXT_PLAIN)
|| contentType.equalsIgnoreCase(ContentType.APP_WAP_XHTML)
|| contentType.equals(ContentType.TEXT_HTML)) {
SMILMediaElement textElement = createMediaElement(
ELEMENT_TAG_TEXT, document, part.generateLocation());
par.appendChild(textElement);
hasText = true;
} else if (ContentType.isImageType(contentType)) {
SMILMediaElement imageElement = createMediaElement(
ELEMENT_TAG_IMAGE, document, part.generateLocation());
par.appendChild(imageElement);
hasMedia = true;
} else if (ContentType.isVideoType(contentType)) {
SMILMediaElement videoElement = createMediaElement(
ELEMENT_TAG_VIDEO, document, part.generateLocation());
par.appendChild(videoElement);
hasMedia = true;
} else if (ContentType.isAudioType(contentType)) {
SMILMediaElement audioElement = createMediaElement(
ELEMENT_TAG_AUDIO, document, part.generateLocation());
par.appendChild(audioElement);
hasMedia = true;
} else {
// TODO: handle other media types.
Log.w(TAG, "unsupport media type");
}
}
return document;
}
private static SMILDocument createSmilDocument(SlideshowModel slideshow) {
if (Config.LOGV) {
Log.v(TAG, "Creating SMIL document from SlideshowModel.");
}
SMILDocument document = new SmilDocumentImpl();
// Create SMIL and append it to document
SMILElement smilElement = (SMILElement) document.createElement("smil");
document.appendChild(smilElement);
// Create HEAD and append it to SMIL
SMILElement headElement = (SMILElement) document.createElement("head");
smilElement.appendChild(headElement);
// Create LAYOUT and append it to HEAD
SMILLayoutElement layoutElement = (SMILLayoutElement)
document.createElement("layout");
headElement.appendChild(layoutElement);
// Create ROOT-LAYOUT and append it to LAYOUT
SMILRootLayoutElement rootLayoutElement =
(SMILRootLayoutElement) document.createElement("root-layout");
LayoutModel layouts = slideshow.getLayout();
rootLayoutElement.setWidth(layouts.getLayoutWidth());
rootLayoutElement.setHeight(layouts.getLayoutHeight());
String bgColor = layouts.getBackgroundColor();
if (!TextUtils.isEmpty(bgColor)) {
rootLayoutElement.setBackgroundColor(bgColor);
}
layoutElement.appendChild(rootLayoutElement);
// Create REGIONs and append them to LAYOUT
ArrayList<RegionModel> regions = layouts.getRegions();
ArrayList<SMILRegionElement> smilRegions = new ArrayList<SMILRegionElement>();
for (RegionModel r : regions) {
SMILRegionElement smilRegion = (SMILRegionElement) document.createElement("region");
smilRegion.setId(r.getRegionId());
smilRegion.setLeft(r.getLeft());
smilRegion.setTop(r.getTop());
smilRegion.setWidth(r.getWidth());
smilRegion.setHeight(r.getHeight());
smilRegion.setFit(r.getFit());
smilRegions.add(smilRegion);
}
// Create BODY and append it to the document.
SMILElement bodyElement = (SMILElement) document.createElement("body");
smilElement.appendChild(bodyElement);
for (SlideModel slide : slideshow) {
boolean txtRegionPresentInLayout = false;
boolean imgRegionPresentInLayout = false;
// Create PAR element.
SMILParElement par = addPar(document);
par.setDur(slide.getDuration() / 1000f);
addParElementEventListeners((EventTarget) par, slide);
// Add all media elements.
for (MediaModel media : slide) {
SMILMediaElement sme = null;
String src = media.getSrc();
if (media instanceof TextModel) {
TextModel text = (TextModel) media;
if (TextUtils.isEmpty(text.getText())) {
if (LOCAL_LOGV) {
Log.v(TAG, "Empty text part ignored: " + text.getSrc());
}
continue;
}
sme = SmilHelper.createMediaElement(SmilHelper.ELEMENT_TAG_TEXT, document, src);
txtRegionPresentInLayout = setRegion((SMILRegionMediaElement) sme,
smilRegions,
layoutElement,
LayoutModel.TEXT_REGION_ID,
txtRegionPresentInLayout);
} else if (media instanceof ImageModel) {
sme = SmilHelper.createMediaElement(SmilHelper.ELEMENT_TAG_IMAGE, document, src);
imgRegionPresentInLayout = setRegion((SMILRegionMediaElement) sme,
smilRegions,
layoutElement,
LayoutModel.IMAGE_REGION_ID,
imgRegionPresentInLayout);
} else if (media instanceof VideoModel) {
sme = SmilHelper.createMediaElement(SmilHelper.ELEMENT_TAG_VIDEO, document, src);
imgRegionPresentInLayout = setRegion((SMILRegionMediaElement) sme,
smilRegions,
layoutElement,
LayoutModel.IMAGE_REGION_ID,
imgRegionPresentInLayout);
} else if (media instanceof AudioModel) {
sme = SmilHelper.createMediaElement(SmilHelper.ELEMENT_TAG_AUDIO, document, src);
} else {
Log.w(TAG, "Unsupport media: " + media);
continue;
}
// Set timing information.
int begin = media.getBegin();
if (begin != 0) {
sme.setAttribute("begin", String.valueOf(begin / 1000));
}
int duration = media.getDuration();
if (duration != 0) {
sme.setDur((float) duration / 1000);
}
par.appendChild(sme);
addMediaElementEventListeners((EventTarget) sme, media);
}
}
if (LOCAL_LOGV) {
ByteArrayOutputStream out = new ByteArrayOutputStream();
SmilXmlSerializer.serialize(document, out);
Log.v(TAG, out.toString());
}
return document;
}
private static SMILRegionElement findRegionElementById(
ArrayList<SMILRegionElement> smilRegions, String rId) {
for (SMILRegionElement smilRegion : smilRegions) {
if (smilRegion.getId().equals(rId)) {
return smilRegion;
}
}
return null;
}
private static boolean setRegion(SMILRegionMediaElement srme,
ArrayList<SMILRegionElement> smilRegions,
SMILLayoutElement smilLayout,
String regionId,
boolean regionPresentInLayout) {
SMILRegionElement smilRegion = findRegionElementById(smilRegions, regionId);
if (!regionPresentInLayout && smilRegion != null) {
srme.setRegion(smilRegion);
smilLayout.appendChild(smilRegion);
return true;
}
return false;
}
static void addMediaElementEventListeners(
EventTarget target, MediaModel media) {
// To play the media with SmilPlayer, we should add them
// as EventListener into an EventTarget.
target.addEventListener(SMIL_MEDIA_START_EVENT, media, false);
target.addEventListener(SMIL_MEDIA_END_EVENT, media, false);
target.addEventListener(SMIL_MEDIA_PAUSE_EVENT, media, false);
target.addEventListener(SMIL_MEDIA_SEEK_EVENT, media, false);
}
static void addParElementEventListeners(
EventTarget target, SlideModel slide) {
// To play the slide with SmilPlayer, we should add it
// as EventListener into an EventTarget.
target.addEventListener(SMIL_SLIDE_START_EVENT, slide, false);
target.addEventListener(SMIL_SLIDE_END_EVENT, slide, false);
}
}