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.ui;
19
20import com.android.mms.R;
21import com.android.mms.dom.AttrImpl;
22import com.android.mms.dom.smil.SmilDocumentImpl;
23import com.android.mms.dom.smil.SmilPlayer;
24import com.android.mms.dom.smil.parser.SmilXmlSerializer;
25import com.android.mms.model.LayoutModel;
26import com.android.mms.model.RegionModel;
27import com.android.mms.model.SlideshowModel;
28import com.android.mms.model.SmilHelper;
29import com.google.android.mms.MmsException;
30
31import org.w3c.dom.NamedNodeMap;
32import org.w3c.dom.Node;
33import org.w3c.dom.NodeList;
34import org.w3c.dom.events.Event;
35import org.w3c.dom.events.EventListener;
36import org.w3c.dom.events.EventTarget;
37import org.w3c.dom.smil.SMILDocument;
38import org.w3c.dom.smil.SMILElement;
39
40import android.app.Activity;
41import android.content.Intent;
42import android.graphics.PixelFormat;
43import android.net.Uri;
44import android.os.Bundle;
45import android.os.Handler;
46import android.util.Config;
47import android.util.Log;
48import android.view.KeyEvent;
49import android.view.MotionEvent;
50import android.view.View;
51import android.view.Window;
52import android.view.View.OnClickListener;
53import android.widget.MediaController;
54import android.widget.MediaController.MediaPlayerControl;
55
56import java.io.ByteArrayOutputStream;
57
58/**
59 * Plays the given slideshow in full-screen mode with a common controller.
60 */
61public class SlideshowActivity extends Activity implements EventListener {
62    private static final String TAG = "SlideshowActivity";
63    private static final boolean DEBUG = false;
64    private static final boolean LOCAL_LOGV = DEBUG ? Config.LOGD : Config.LOGV;
65
66    private MediaController mMediaController;
67    private SmilPlayer mSmilPlayer;
68
69    private Handler mHandler;
70
71    private SMILDocument mSmilDoc;
72
73    private SlideView mSlideView;
74
75    /**
76     * @return whether the Smil has MMS conformance layout.
77     * Refer to MMS Conformance Document OMA-MMS-CONF-v1_2-20050301-A
78     */
79    private static final boolean isMMSConformance(SMILDocument smilDoc) {
80        SMILElement head = smilDoc.getHead();
81        if (head == null) {
82            // No 'head' element
83            return false;
84        }
85        NodeList children = head.getChildNodes();
86        if (children == null || children.getLength() != 1) {
87            // The 'head' element should have only one child.
88            return false;
89        }
90        Node layout = children.item(0);
91        if (layout == null || !"layout".equals(layout.getNodeName())) {
92            // The child is not layout element
93            return false;
94        }
95        NodeList layoutChildren = layout.getChildNodes();
96        if (layoutChildren == null) {
97            // The 'layout' element has no child.
98            return false;
99        }
100        int num = layoutChildren.getLength();
101        if (num <= 0) {
102            // The 'layout' element has no child.
103            return false;
104        }
105        for (int i = 0; i < num; i++) {
106            Node layoutChild = layoutChildren.item(i);
107            if (layoutChild == null) {
108                // The 'layout' child is null.
109                return false;
110            }
111            String name = layoutChild.getNodeName();
112            if ("root-layout".equals(name)) {
113                continue;
114            } else if ("region".equals(name)) {
115                NamedNodeMap map = layoutChild.getAttributes();
116                for (int j = 0; j < map.getLength(); j++) {
117                    Node node = map.item(j);
118                    if (node == null) {
119                        return false;
120                    }
121                    String attrName = node.getNodeName();
122                    // The attr should be one of left, top, height, width, fit and id
123                    if ("left".equals(attrName) || "top".equals(attrName) ||
124                            "height".equals(attrName) || "width".equals(attrName) ||
125                            "fit".equals(attrName)) {
126                        continue;
127                    } else if ("id".equals(attrName)) {
128                        String value;
129                        if (node instanceof AttrImpl) {
130                            value = ((AttrImpl)node).getValue();
131                        } else {
132                            return false;
133                        }
134                        if ("Text".equals(value) || "Image".equals(value)) {
135                            continue;
136                        } else {
137                            // The id attr is not 'Text' or 'Image'
138                            return false;
139                        }
140                    } else {
141                        return false;
142                    }
143                }
144            } else {
145                // The 'layout' element has the child other than 'root-layout' or 'region'
146                return false;
147            }
148        }
149        return true;
150    }
151
152    @Override
153    public void onCreate(Bundle icicle) {
154        super.onCreate(icicle);
155        mHandler = new Handler();
156
157        // Play slide-show in full-screen mode.
158        requestWindowFeature(Window.FEATURE_NO_TITLE);
159        getWindow().setFormat(PixelFormat.TRANSLUCENT);
160        setContentView(R.layout.slideshow);
161
162        Intent intent = getIntent();
163        Uri msg = intent.getData();
164        final SlideshowModel model;
165
166        try {
167            model = SlideshowModel.createFromMessageUri(this, msg);
168        } catch (MmsException e) {
169            Log.e(TAG, "Cannot present the slide show.", e);
170            finish();
171            return;
172        }
173
174        mSlideView = (SlideView) findViewById(R.id.slide_view);
175        PresenterFactory.getPresenter("SlideshowPresenter", this, mSlideView, model);
176
177        mHandler.post(new Runnable() {
178            private boolean isRotating() {
179                return mSmilPlayer.isPausedState()
180                        || mSmilPlayer.isPlayingState()
181                        || mSmilPlayer.isPlayedState();
182            }
183
184            public void run() {
185                mSmilPlayer = SmilPlayer.getPlayer();
186                initMediaController();
187                mSlideView.setMediaController(mMediaController);
188                // Use SmilHelper.getDocument() to ensure rebuilding the
189                // entire SMIL document.
190                mSmilDoc = SmilHelper.getDocument(model);
191                if (isMMSConformance(mSmilDoc)) {
192                    int imageLeft = 0;
193                    int imageTop = 0;
194                    int textLeft = 0;
195                    int textTop = 0;
196                    LayoutModel layout = model.getLayout();
197                    if (layout != null) {
198                        RegionModel imageRegion = layout.getImageRegion();
199                        if (imageRegion != null) {
200                            imageLeft = imageRegion.getLeft();
201                            imageTop = imageRegion.getTop();
202                        }
203                        RegionModel textRegion = layout.getTextRegion();
204                        if (textRegion != null) {
205                            textLeft = textRegion.getLeft();
206                            textTop = textRegion.getTop();
207                        }
208                    }
209                    mSlideView.enableMMSConformanceMode(textLeft, textTop, imageLeft, imageTop);
210                }
211                if (DEBUG) {
212                    ByteArrayOutputStream ostream = new ByteArrayOutputStream();
213                    SmilXmlSerializer.serialize(mSmilDoc, ostream);
214                    if (LOCAL_LOGV) {
215                        Log.v(TAG, ostream.toString());
216                    }
217                }
218
219                // Add event listener.
220                ((EventTarget) mSmilDoc).addEventListener(
221                        SmilDocumentImpl.SMIL_DOCUMENT_END_EVENT,
222                        SlideshowActivity.this, false);
223
224                mSmilPlayer.init(mSmilDoc);
225                if (isRotating()) {
226                    mSmilPlayer.reload();
227                } else {
228                    mSmilPlayer.play();
229                }
230            }
231        });
232    }
233
234    private void initMediaController() {
235        mMediaController = new MediaController(SlideshowActivity.this, false);
236        mMediaController.setMediaPlayer(new SmilPlayerController(mSmilPlayer));
237        mMediaController.setAnchorView(findViewById(R.id.slide_view));
238        mMediaController.setPrevNextListeners(
239            new OnClickListener() {
240              public void onClick(View v) {
241                  mSmilPlayer.next();
242              }
243            },
244            new OnClickListener() {
245              public void onClick(View v) {
246                  mSmilPlayer.prev();
247              }
248            });
249    }
250
251    @Override
252    public boolean onTouchEvent(MotionEvent ev) {
253        if ((mSmilPlayer != null) && (mMediaController != null)) {
254            mMediaController.show();
255        }
256        return false;
257    }
258
259    @Override
260    protected void onPause() {
261        super.onPause();
262        if (mSmilDoc != null) {
263            ((EventTarget) mSmilDoc).removeEventListener(
264                    SmilDocumentImpl.SMIL_DOCUMENT_END_EVENT, this, false);
265        }
266    }
267
268    @Override
269    protected void onStop() {
270        super.onStop();
271        if ((null != mSmilPlayer)) {
272            if (isFinishing()) {
273                mSmilPlayer.stop();
274            } else {
275                mSmilPlayer.stopWhenReload();
276            }
277            if (mMediaController != null) {
278                // Must do this so we don't leak a window.
279                mMediaController.hide();
280            }
281        }
282    }
283
284    @Override
285    public boolean onKeyDown(int keyCode, KeyEvent event) {
286        switch (keyCode) {
287            case KeyEvent.KEYCODE_VOLUME_DOWN:
288            case KeyEvent.KEYCODE_VOLUME_UP:
289            case KeyEvent.KEYCODE_DPAD_UP:
290            case KeyEvent.KEYCODE_DPAD_DOWN:
291            case KeyEvent.KEYCODE_DPAD_LEFT:
292            case KeyEvent.KEYCODE_DPAD_RIGHT:
293                break;
294            case KeyEvent.KEYCODE_BACK:
295            case KeyEvent.KEYCODE_MENU:
296                if ((mSmilPlayer != null) &&
297                        (mSmilPlayer.isPausedState()
298                        || mSmilPlayer.isPlayingState()
299                        || mSmilPlayer.isPlayedState())) {
300                    mSmilPlayer.stop();
301                }
302                break;
303            default:
304                if ((mSmilPlayer != null) && (mMediaController != null)) {
305                    mMediaController.show();
306                }
307        }
308        return super.onKeyDown(keyCode, event);
309    }
310
311    private class SmilPlayerController implements MediaPlayerControl {
312        private final SmilPlayer mPlayer;
313
314        public SmilPlayerController(SmilPlayer player) {
315            mPlayer = player;
316        }
317
318        public int getBufferPercentage() {
319            // We don't need to buffer data, always return 100%.
320            return 100;
321        }
322
323        public int getCurrentPosition() {
324            return mPlayer.getCurrentPosition();
325        }
326
327        public int getDuration() {
328            return mPlayer.getDuration();
329        }
330
331        public boolean isPlaying() {
332            return mPlayer != null ? mPlayer.isPlayingState() : false;
333        }
334
335        public void pause() {
336            if (mPlayer != null) {
337                mPlayer.pause();
338            }
339        }
340
341        public void seekTo(int pos) {
342            // Don't need to support.
343        }
344
345        public void start() {
346            if (mPlayer != null) {
347                mPlayer.start();
348            }
349        }
350
351        public boolean canPause() {
352            return true;
353        }
354
355        public boolean canSeekBackward() {
356            return true;
357        }
358
359        public boolean canSeekForward() {
360            return true;
361        }
362    }
363
364    public void handleEvent(Event evt) {
365        final Event event = evt;
366        mHandler.post(new Runnable() {
367            public void run() {
368                String type = event.getType();
369                if(type.equals(SmilDocumentImpl.SMIL_DOCUMENT_END_EVENT)) {
370                    finish();
371                }
372            }
373        });
374    }
375}
376