/* * 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 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.android.mms.drm.DrmUtils; 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; 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.content.ContentResolver; import android.database.Cursor; import android.database.sqlite.SQLiteException; import android.drm.DrmManagerClient; import android.net.Uri; import android.provider.MediaStore; import android.text.TextUtils; import android.util.Config; import android.util.Log; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.File; import java.io.IOException; import java.util.ArrayList; import java.util.Arrays; 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 and element. SMILElement head = (SMILElement) document.createElement("head"); smil.appendChild(head); SMILLayoutElement layout = (SMILLayoutElement) document.createElement("layout"); head.appendChild(layout); // Create element and add a empty . 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 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 regions = layouts.getRegions(); ArrayList smilRegions = new ArrayList(); 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 smilRegions, String rId) { for (SMILRegionElement smilRegion : smilRegions) { if (smilRegion.getId().equals(rId)) { return smilRegion; } } return null; } private static boolean setRegion(SMILRegionMediaElement srme, ArrayList 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); } }