1/*
2 * Copyright (C) 2007-2008 Esmertec AG.
3 * Copyright (C) 2007-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.dom.smil;
19
20import java.util.ArrayList;
21
22import org.w3c.dom.DOMException;
23import org.w3c.dom.smil.ElementTime;
24import org.w3c.dom.smil.SMILElement;
25import org.w3c.dom.smil.Time;
26import org.w3c.dom.smil.TimeList;
27
28import android.util.Log;
29
30public abstract class ElementTimeImpl implements ElementTime {
31    private static final String TAG = "ElementTimeImpl";
32
33    private static final String FILL_REMOVE_ATTRIBUTE = "remove";
34    private static final String FILL_FREEZE_ATTRIBUTE = "freeze";
35    private static final String FILL_HOLD_ATTRIBUTE = "hold";
36    private static final String FILL_TRANSITION_ATTRIBUTE = "transition";
37    private static final String FILL_AUTO_ATTRIBUTE   = "auto";
38    private static final String FILL_ATTRIBUTE_NAME   = "fill";
39    private static final String FILLDEFAULT_ATTRIBUTE_NAME   = "fillDefault";
40
41    final SMILElement mSmilElement;
42
43    /*
44     * Internal Interface
45     */
46    ElementTimeImpl(SMILElement element) {
47        mSmilElement = element;
48    }
49
50    // Default implementation. Override if required.
51    int getBeginConstraints() {
52        return TimeImpl.ALLOW_ALL;
53    }
54
55    // Default implementation. Override if required
56    int getEndConstraints() {
57        return TimeImpl.ALLOW_ALL;
58    }
59
60    /**
61     * To get the parent node on the ElementTime tree. It is in opposition to getTimeChildren.
62     * @return the parent ElementTime. Returns <code>null</code> if there is no parent.
63     */
64    abstract ElementTime getParentElementTime();
65
66    /*
67     * ElementTime Interface
68     */
69
70    public TimeList getBegin() {
71        String[] beginTimeStringList = mSmilElement.getAttribute("begin").split(";");
72
73        // TODO: Check other constraints on parsed values, e.g., "single, non-negative offset values
74        ArrayList<Time> beginTimeList = new ArrayList<Time>();
75        // Initialize Time instances and add them to Vector
76        for (int i = 0; i < beginTimeStringList.length; i++) {
77            try {
78                beginTimeList.add(new TimeImpl(beginTimeStringList[i], getBeginConstraints()));
79            } catch (IllegalArgumentException e) {
80                // Ignore badly formatted times
81            }
82        }
83        if (beginTimeList.size() == 0) {
84            /*
85             * What is the right default value?
86             *
87             * In MMS SMIL, this method may be called either on an instance of:
88             *
89             * 1 - ElementSequentialTimeContainer (The SMILDocument)
90             * 2 - ElementParallelTimeContainer (A Time-Child of the SMILDocument, which is a seq)
91             * 3 - ElementTime (A SMILMediaElement).
92             *
93             * 1 - In the first case, the default start time is obviously 0.
94             * 2 - In the second case, the specifications mentions that
95             *      "For children of a sequence, the only legal value for begin is
96             *      a (single) non-negative offset value. The default begin value is 0."
97             * 3 - In the third case, the specification mentions that
98             *      "The default value of begin for children of a par is 0."
99             *
100             * In short, if no value is specified, the default is always 0.
101             */
102
103            beginTimeList.add(new TimeImpl("0", TimeImpl.ALLOW_ALL));
104        }
105        return new TimeListImpl(beginTimeList);
106    }
107
108    public float getDur() {
109        float dur = 0;
110        try {
111            String durString = mSmilElement.getAttribute("dur");
112            if (durString != null) {
113                dur = TimeImpl.parseClockValue(durString) / 1000f;
114            }
115        } catch (IllegalArgumentException e) {
116            // Do nothing and return the minimum value
117        }
118
119        return dur;
120    }
121
122    public TimeList getEnd() {
123        ArrayList<Time> endTimeList = new ArrayList<Time>();
124
125        String[] endTimeStringList = mSmilElement.getAttribute("end").split(";");
126        int len = endTimeStringList.length;
127        if (!((len == 1) && (endTimeStringList[0].length() == 0))) {  // Ensure the end field is set.
128            // Initialize Time instances and add them to Vector
129            for (int i = 0; i < len; i++) {
130                try {
131                    endTimeList.add(new TimeImpl(endTimeStringList[i],
132                            getEndConstraints()));
133                } catch (IllegalArgumentException e) {
134                    // Ignore badly formatted times
135                    Log.e(TAG, "Malformed time value.", e);
136                }
137            }
138        }
139
140        // "end" time is not specified
141        if (endTimeList.size() == 0) {
142            // Get duration
143            float duration = getDur();
144
145            if (duration < 0) {
146                endTimeList.add(new TimeImpl("indefinite", getEndConstraints()));
147            } else {
148                // Get begin
149                TimeList begin = getBegin();
150                for (int i = 0; i < begin.getLength(); i++) {
151                    endTimeList.add(new TimeImpl(
152                            // end = begin + dur
153                            begin.item(i).getResolvedOffset() + duration + "s",
154                            getEndConstraints()));
155                }
156            }
157        }
158
159        return new TimeListImpl(endTimeList);
160    }
161
162    private boolean beginAndEndAreZero() {
163        TimeList begin = getBegin();
164        TimeList end = getEnd();
165        if (begin.getLength() == 1 && end.getLength() == 1) {
166            Time beginTime = begin.item(0);
167            Time endTime = end.item(0);
168            return beginTime.getOffset() == 0. && endTime.getOffset() == 0.;
169        }
170        return false;
171    }
172
173    public short getFill() {
174        String fill = mSmilElement.getAttribute(FILL_ATTRIBUTE_NAME);
175        if (fill.equalsIgnoreCase(FILL_FREEZE_ATTRIBUTE)) {
176            return FILL_FREEZE;
177        } else if (fill.equalsIgnoreCase(FILL_REMOVE_ATTRIBUTE)) {
178            return FILL_REMOVE;
179        } else if (fill.equalsIgnoreCase(FILL_HOLD_ATTRIBUTE)) {
180            // FIXME handle it as freeze for now
181            return FILL_FREEZE;
182        } else if (fill.equalsIgnoreCase(FILL_TRANSITION_ATTRIBUTE)) {
183            // FIXME handle it as freeze for now
184            return FILL_FREEZE;
185        } else if (!fill.equalsIgnoreCase(FILL_AUTO_ATTRIBUTE)) {
186            /*
187             * fill = default
188             * The fill behavior for the element is determined by the value of the fillDefault
189             * attribute.  This is the default value.
190             */
191            short fillDefault = getFillDefault();
192            if (fillDefault != FILL_AUTO) {
193                return fillDefault;
194            }
195        }
196
197        /*
198         * fill = auto
199         * The fill behavior for this element depends on whether the element specifies any of
200         * the attributes that define the simple or active duration:
201         *  - If none of the attributes dur, end, repeatCount or repeatDur are specified on
202         *    the element, then the element will have a fill behavior identical to that if it were
203         *    specified as "freeze".
204         *  - Otherwise, the element will have a fill behavior identical to that if it were
205         *    specified as "remove".
206         */
207        if (((mSmilElement.getAttribute("dur").length() == 0) &&
208                (mSmilElement.getAttribute("end").length() == 0) &&
209                (mSmilElement.getAttribute("repeatCount").length() == 0) &&
210                (mSmilElement.getAttribute("repeatDur").length() == 0)) ||
211                beginAndEndAreZero()) {
212            return FILL_FREEZE;
213        } else {
214            return FILL_REMOVE;
215        }
216    }
217
218    public short getFillDefault() {
219        String fillDefault = mSmilElement.getAttribute(FILLDEFAULT_ATTRIBUTE_NAME);
220        if (fillDefault.equalsIgnoreCase(FILL_REMOVE_ATTRIBUTE)) {
221            return FILL_REMOVE;
222        } else if (fillDefault.equalsIgnoreCase(FILL_FREEZE_ATTRIBUTE)) {
223            return FILL_FREEZE;
224        } else if (fillDefault.equalsIgnoreCase(FILL_AUTO_ATTRIBUTE)) {
225            return FILL_AUTO;
226        } else if (fillDefault.equalsIgnoreCase(FILL_HOLD_ATTRIBUTE)) {
227            // FIXME handle it as freeze for now
228            return FILL_FREEZE;
229        } else if (fillDefault.equalsIgnoreCase(FILL_TRANSITION_ATTRIBUTE)) {
230            // FIXME handle it as freeze for now
231            return FILL_FREEZE;
232        } else {
233            /*
234             * fillDefault = inherit
235             * Specifies that the value of this attribute (and of the fill behavior) are
236             * inherited from the fillDefault value of the parent element.
237             * This is the default value.
238             */
239            ElementTime parent = getParentElementTime();
240            if (parent == null) {
241                /*
242                 * fillDefault = auto
243                 * If there is no parent element, the value is "auto".
244                 */
245                return FILL_AUTO;
246            } else {
247                return ((ElementTimeImpl) parent).getFillDefault();
248            }
249        }
250    }
251
252    public float getRepeatCount() {
253        String repeatCount = mSmilElement.getAttribute("repeatCount");
254        try {
255            float value = Float.parseFloat(repeatCount);
256            if (value > 0) {
257                return value;
258            } else {
259                return 0; // default
260            }
261        } catch (NumberFormatException e) {
262            return 0; // default
263        }
264    }
265
266    public float getRepeatDur() {
267        try {
268            float repeatDur =
269                TimeImpl.parseClockValue(mSmilElement.getAttribute("repeatDur"));
270            if (repeatDur > 0) {
271                return repeatDur;
272            } else {
273                return 0; // default
274            }
275        } catch (IllegalArgumentException e) {
276            return 0; // default
277        }
278    }
279
280    public short getRestart() {
281        String restart = mSmilElement.getAttribute("restart");
282        if (restart.equalsIgnoreCase("never")) {
283            return RESTART_NEVER;
284        } else if (restart.equalsIgnoreCase("whenNotActive")) {
285            return RESTART_WHEN_NOT_ACTIVE;
286        } else {
287            return RESTART_ALWAYS; // default
288        }
289    }
290
291    public void setBegin(TimeList begin) throws DOMException {
292        // TODO Implement this
293        mSmilElement.setAttribute("begin", "indefinite");
294    }
295
296    public void setDur(float dur) throws DOMException {
297        // In SMIL 3.0, the dur could be a timecount-value which may contain fractions.
298        // However, in MMS 1.3, the dur SHALL be expressed in integer milliseconds.
299        mSmilElement.setAttribute("dur", Integer.toString((int)(dur * 1000)) + "ms");
300    }
301
302    public void setEnd(TimeList end) throws DOMException {
303        // TODO Implement this
304        mSmilElement.setAttribute("end", "indefinite");
305    }
306
307    public void setFill(short fill) throws DOMException {
308        if (fill == FILL_FREEZE) {
309            mSmilElement.setAttribute(FILL_ATTRIBUTE_NAME, FILL_FREEZE_ATTRIBUTE);
310        } else {
311            mSmilElement.setAttribute(FILL_ATTRIBUTE_NAME, FILL_REMOVE_ATTRIBUTE); // default
312        }
313    }
314
315    public void setFillDefault(short fillDefault) throws DOMException {
316        if (fillDefault == FILL_FREEZE) {
317            mSmilElement.setAttribute(FILLDEFAULT_ATTRIBUTE_NAME, FILL_FREEZE_ATTRIBUTE);
318        } else {
319            mSmilElement.setAttribute(FILLDEFAULT_ATTRIBUTE_NAME, FILL_REMOVE_ATTRIBUTE);
320        }
321    }
322
323    public void setRepeatCount(float repeatCount) throws DOMException {
324        String repeatCountString = "indefinite";
325        if (repeatCount > 0) {
326            repeatCountString = Float.toString(repeatCount);
327        }
328        mSmilElement.setAttribute("repeatCount", repeatCountString);
329    }
330
331    public void setRepeatDur(float repeatDur) throws DOMException {
332        String repeatDurString = "indefinite";
333        if (repeatDur > 0) {
334            repeatDurString = Float.toString(repeatDur) + "ms";
335        }
336        mSmilElement.setAttribute("repeatDur", repeatDurString);
337    }
338
339    public void setRestart(short restart) throws DOMException {
340        if (restart == RESTART_NEVER) {
341            mSmilElement.setAttribute("restart", "never");
342        } else if (restart == RESTART_WHEN_NOT_ACTIVE) {
343            mSmilElement.setAttribute("restart", "whenNotActive");
344        } else {
345            mSmilElement.setAttribute("restart", "always");
346        }
347    }
348}
349