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
18package com.android.mms.model;
19
20import java.io.IOException;
21
22import org.w3c.dom.smil.SMILMediaElement;
23import org.w3c.dom.smil.SMILRegionElement;
24import org.w3c.dom.smil.SMILRegionMediaElement;
25import org.w3c.dom.smil.Time;
26import org.w3c.dom.smil.TimeList;
27
28import android.content.Context;
29import android.util.Log;
30
31import com.android.mms.LogTag;
32import com.android.mms.MmsConfig;
33import com.google.android.mms.ContentType;
34import com.google.android.mms.MmsException;
35import com.google.android.mms.pdu.PduBody;
36import com.google.android.mms.pdu.PduPart;
37
38public class MediaModelFactory {
39    private static final String TAG = "Mms:media";
40
41    public static MediaModel getMediaModel(Context context,
42            SMILMediaElement sme, LayoutModel layouts, PduBody pb)
43            throws IOException, IllegalArgumentException, MmsException {
44        String tag = sme.getTagName();
45        String src = sme.getSrc();
46        PduPart part = findPart(pb, src);
47
48        if (sme instanceof SMILRegionMediaElement) {
49            return getRegionMediaModel(
50                    context, tag, src, (SMILRegionMediaElement) sme, layouts, part);
51        } else {
52            return getGenericMediaModel(
53                    context, tag, src, sme, part, null);
54        }
55    }
56
57    private static PduPart findPart(PduBody pb, String src) {
58        PduPart part = null;
59
60        if (src != null) {
61            src = unescapeXML(src);
62            if (src.startsWith("cid:")) {
63                part = pb.getPartByContentId("<" + src.substring("cid:".length()) + ">");
64            } else {
65                part = pb.getPartByName(src);
66                if (part == null) {
67                    part = pb.getPartByFileName(src);
68                    if (part == null) {
69                        part = pb.getPartByContentLocation(src);
70                    }
71                }
72            }
73        }
74
75        if (part != null) {
76            return part;
77        }
78
79        throw new IllegalArgumentException("No part found for the model.");
80    }
81
82    private static String unescapeXML(String str) {
83        return str.replaceAll("&lt;","<")
84            .replaceAll("&gt;", ">")
85            .replaceAll("&quot;","\"")
86            .replaceAll("&apos;","'")
87            .replaceAll("&amp;", "&");
88    }
89
90    private static MediaModel getRegionMediaModel(Context context,
91            String tag, String src, SMILRegionMediaElement srme,
92            LayoutModel layouts, PduPart part) throws IOException, MmsException {
93        SMILRegionElement sre = srme.getRegion();
94        if (sre != null) {
95            RegionModel region = layouts.findRegionById(sre.getId());
96            if (region != null) {
97                return getGenericMediaModel(context, tag, src, srme, part, region);
98            }
99        } else {
100            String rId = null;
101
102            if (tag.equals(SmilHelper.ELEMENT_TAG_TEXT)) {
103                rId = LayoutModel.TEXT_REGION_ID;
104            } else {
105                rId = LayoutModel.IMAGE_REGION_ID;
106            }
107
108            RegionModel region = layouts.findRegionById(rId);
109            if (region != null) {
110                return getGenericMediaModel(context, tag, src, srme, part, region);
111            }
112        }
113
114        throw new IllegalArgumentException("Region not found or bad region ID.");
115    }
116
117    // When we encounter a content type we can't handle, such as "application/vnd.smaf", instead
118    // of throwing an exception and crashing, insert an empty TextModel in its place.
119    private static MediaModel createEmptyTextModel(Context context,  RegionModel regionModel)
120            throws IOException {
121        return new TextModel(context, ContentType.TEXT_PLAIN, null, regionModel);
122    }
123
124    private static MediaModel getGenericMediaModel(Context context,
125            String tag, String src, SMILMediaElement sme, PduPart part,
126            RegionModel regionModel) throws IOException, MmsException {
127        byte[] bytes = part.getContentType();
128        if (bytes == null) {
129            throw new IllegalArgumentException(
130                    "Content-Type of the part may not be null.");
131        }
132
133        String contentType = new String(bytes);
134        MediaModel media = null;
135        if (tag.equals(SmilHelper.ELEMENT_TAG_TEXT)) {
136            media = new TextModel(context, contentType, src,
137                    part.getCharset(), part.getData(), regionModel);
138        } else if (tag.equals(SmilHelper.ELEMENT_TAG_IMAGE)) {
139            media = new ImageModel(context, contentType, src,
140                    part.getDataUri(), regionModel);
141        } else if (tag.equals(SmilHelper.ELEMENT_TAG_VIDEO)) {
142            media = new VideoModel(context, contentType, src,
143                    part.getDataUri(), regionModel);
144        } else if (tag.equals(SmilHelper.ELEMENT_TAG_AUDIO)) {
145            media = new AudioModel(context, contentType, src,
146                    part.getDataUri());
147        } else if (tag.equals(SmilHelper.ELEMENT_TAG_REF)) {
148            if (ContentType.isTextType(contentType)) {
149                media = new TextModel(context, contentType, src,
150                        part.getCharset(), part.getData(), regionModel);
151            } else if (ContentType.isImageType(contentType)) {
152                media = new ImageModel(context, contentType, src,
153                        part.getDataUri(), regionModel);
154            } else if (ContentType.isVideoType(contentType)) {
155                media = new VideoModel(context, contentType, src,
156                        part.getDataUri(), regionModel);
157            } else if (ContentType.isAudioType(contentType)) {
158                media = new AudioModel(context, contentType, src,
159                        part.getDataUri());
160            } else {
161                Log.d(TAG, "[MediaModelFactory] getGenericMediaModel Unsupported Content-Type: "
162                        + contentType);
163                media = createEmptyTextModel(context, regionModel);
164            }
165        } else {
166            throw new IllegalArgumentException("Unsupported TAG: " + tag);
167        }
168
169        // Set 'begin' property.
170        int begin = 0;
171        TimeList tl = sme.getBegin();
172        if ((tl != null) && (tl.getLength() > 0)) {
173            // We only support a single begin value.
174            Time t = tl.item(0);
175            begin = (int) (t.getResolvedOffset() * 1000);
176        }
177        media.setBegin(begin);
178
179        // Set 'duration' property.
180        int duration = (int) (sme.getDur() * 1000);
181        if (duration <= 0) {
182            tl = sme.getEnd();
183            if ((tl != null) && (tl.getLength() > 0)) {
184                // We only support a single end value.
185                Time t = tl.item(0);
186                if (t.getTimeType() != Time.SMIL_TIME_INDEFINITE) {
187                    duration = (int) (t.getResolvedOffset() * 1000) - begin;
188
189                    if (duration == 0 &&
190                            (media instanceof AudioModel || media instanceof VideoModel)) {
191                        duration = MmsConfig.getMinimumSlideElementDuration();
192                        if (Log.isLoggable(LogTag.APP, Log.VERBOSE)) {
193                            Log.d(TAG, "[MediaModelFactory] compute new duration for " + tag +
194                                    ", duration=" + duration);
195                        }
196                    }
197                }
198            }
199        }
200
201        media.setDuration(duration);
202
203        if (!MmsConfig.getSlideDurationEnabled()) {
204            /**
205             * Because The slide duration is not supported by mmsc,
206             * the device has to set fill type as FILL_FREEZE.
207             * If not, the media will disappear while rotating the screen
208             * in the slide show play view.
209             */
210            media.setFill(sme.FILL_FREEZE);
211        } else {
212            // Set 'fill' property.
213            media.setFill(sme.getFill());
214        }
215        return media;
216    }
217}
218