• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
18 package com.android.mms.ui;
19 
20 import com.android.mms.R;
21 import com.android.mms.dom.AttrImpl;
22 import com.android.mms.dom.smil.SmilDocumentImpl;
23 import com.android.mms.dom.smil.SmilPlayer;
24 import com.android.mms.dom.smil.parser.SmilXmlSerializer;
25 import com.android.mms.model.LayoutModel;
26 import com.android.mms.model.RegionModel;
27 import com.android.mms.model.SlideshowModel;
28 import com.android.mms.model.SmilHelper;
29 import com.google.android.mms.MmsException;
30 
31 import org.w3c.dom.NamedNodeMap;
32 import org.w3c.dom.Node;
33 import org.w3c.dom.NodeList;
34 import org.w3c.dom.events.Event;
35 import org.w3c.dom.events.EventListener;
36 import org.w3c.dom.events.EventTarget;
37 import org.w3c.dom.smil.SMILDocument;
38 import org.w3c.dom.smil.SMILElement;
39 
40 import android.app.Activity;
41 import android.content.Intent;
42 import android.graphics.PixelFormat;
43 import android.net.Uri;
44 import android.os.Bundle;
45 import android.os.Handler;
46 import android.util.Log;
47 import android.view.KeyEvent;
48 import android.view.MotionEvent;
49 import android.view.View;
50 import android.view.Window;
51 import android.view.View.OnClickListener;
52 import android.widget.MediaController;
53 import android.widget.MediaController.MediaPlayerControl;
54 
55 import java.io.ByteArrayOutputStream;
56 
57 /**
58  * Plays the given slideshow in full-screen mode with a common controller.
59  */
60 public class SlideshowActivity extends Activity implements EventListener {
61     private static final String TAG = "SlideshowActivity";
62     private static final boolean DEBUG = false;
63     private static final boolean LOCAL_LOGV = false;
64 
65     private MediaController mMediaController;
66     private SmilPlayer mSmilPlayer;
67 
68     private Handler mHandler;
69 
70     private SMILDocument mSmilDoc;
71 
72     private SlideView mSlideView;
73     private int mSlideCount;
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      */
isMMSConformance(SMILDocument smilDoc)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
onCreate(Bundle icicle)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             mSlideCount = model.size();
169         } catch (MmsException e) {
170             Log.e(TAG, "Cannot present the slide show.", e);
171             finish();
172             return;
173         }
174 
175         mSlideView = (SlideView) findViewById(R.id.slide_view);
176         PresenterFactory.getPresenter("SlideshowPresenter", this, mSlideView, model);
177 
178         mHandler.post(new Runnable() {
179             private boolean isRotating() {
180                 return mSmilPlayer.isPausedState()
181                         || mSmilPlayer.isPlayingState()
182                         || mSmilPlayer.isPlayedState();
183             }
184 
185             public void run() {
186                 mSmilPlayer = SmilPlayer.getPlayer();
187                 if (mSlideCount > 1) {
188                     // Only show the slideshow controller if we have more than a single slide.
189                     // Otherwise, when we play a sound on a single slide, it appears like
190                     // the slide controller should control the sound (seeking, ff'ing, etc).
191                     initMediaController();
192                     mSlideView.setMediaController(mMediaController);
193                 }
194                 // Use SmilHelper.getDocument() to ensure rebuilding the
195                 // entire SMIL document.
196                 mSmilDoc = SmilHelper.getDocument(model);
197                 if (isMMSConformance(mSmilDoc)) {
198                     int imageLeft = 0;
199                     int imageTop = 0;
200                     int textLeft = 0;
201                     int textTop = 0;
202                     LayoutModel layout = model.getLayout();
203                     if (layout != null) {
204                         RegionModel imageRegion = layout.getImageRegion();
205                         if (imageRegion != null) {
206                             imageLeft = imageRegion.getLeft();
207                             imageTop = imageRegion.getTop();
208                         }
209                         RegionModel textRegion = layout.getTextRegion();
210                         if (textRegion != null) {
211                             textLeft = textRegion.getLeft();
212                             textTop = textRegion.getTop();
213                         }
214                     }
215                     mSlideView.enableMMSConformanceMode(textLeft, textTop, imageLeft, imageTop);
216                 }
217                 if (DEBUG) {
218                     ByteArrayOutputStream ostream = new ByteArrayOutputStream();
219                     SmilXmlSerializer.serialize(mSmilDoc, ostream);
220                     if (LOCAL_LOGV) {
221                         Log.v(TAG, ostream.toString());
222                     }
223                 }
224 
225                 // Add event listener.
226                 ((EventTarget) mSmilDoc).addEventListener(
227                         SmilDocumentImpl.SMIL_DOCUMENT_END_EVENT,
228                         SlideshowActivity.this, false);
229 
230                 mSmilPlayer.init(mSmilDoc);
231                 if (isRotating()) {
232                     mSmilPlayer.reload();
233                 } else {
234                     mSmilPlayer.play();
235                 }
236             }
237         });
238     }
239 
initMediaController()240     private void initMediaController() {
241         mMediaController = new MediaController(SlideshowActivity.this, false);
242         mMediaController.setMediaPlayer(new SmilPlayerController(mSmilPlayer));
243         mMediaController.setAnchorView(findViewById(R.id.slide_view));
244         mMediaController.setPrevNextListeners(
245             new OnClickListener() {
246               public void onClick(View v) {
247                   mSmilPlayer.next();
248               }
249             },
250             new OnClickListener() {
251               public void onClick(View v) {
252                   mSmilPlayer.prev();
253               }
254             });
255     }
256 
257     @Override
onTouchEvent(MotionEvent ev)258     public boolean onTouchEvent(MotionEvent ev) {
259         if ((mSmilPlayer != null) && (mMediaController != null)) {
260             mMediaController.show();
261         }
262         return false;
263     }
264 
265     @Override
onPause()266     protected void onPause() {
267         super.onPause();
268         if (mSmilDoc != null) {
269             ((EventTarget) mSmilDoc).removeEventListener(
270                     SmilDocumentImpl.SMIL_DOCUMENT_END_EVENT, this, false);
271         }
272         if (mSmilPlayer != null) {
273             mSmilPlayer.pause();
274         }
275     }
276 
277     @Override
onStop()278     protected void onStop() {
279         super.onStop();
280         if ((null != mSmilPlayer)) {
281             if (isFinishing()) {
282                 mSmilPlayer.stop();
283             } else {
284                 mSmilPlayer.stopWhenReload();
285             }
286             if (mMediaController != null) {
287                 // Must do this so we don't leak a window.
288                 mMediaController.hide();
289             }
290         }
291     }
292 
293     @Override
onKeyDown(int keyCode, KeyEvent event)294     public boolean onKeyDown(int keyCode, KeyEvent event) {
295         switch (keyCode) {
296             case KeyEvent.KEYCODE_VOLUME_DOWN:
297             case KeyEvent.KEYCODE_VOLUME_UP:
298             case KeyEvent.KEYCODE_VOLUME_MUTE:
299             case KeyEvent.KEYCODE_DPAD_UP:
300             case KeyEvent.KEYCODE_DPAD_DOWN:
301             case KeyEvent.KEYCODE_DPAD_LEFT:
302             case KeyEvent.KEYCODE_DPAD_RIGHT:
303                 break;
304             case KeyEvent.KEYCODE_BACK:
305             case KeyEvent.KEYCODE_MENU:
306                 if ((mSmilPlayer != null) &&
307                         (mSmilPlayer.isPausedState()
308                         || mSmilPlayer.isPlayingState()
309                         || mSmilPlayer.isPlayedState())) {
310                     mSmilPlayer.stop();
311                 }
312                 break;
313             default:
314                 if ((mSmilPlayer != null) && (mMediaController != null)) {
315                     mMediaController.show();
316                 }
317         }
318         return super.onKeyDown(keyCode, event);
319     }
320 
321     private class SmilPlayerController implements MediaPlayerControl {
322         private final SmilPlayer mPlayer;
323         /**
324          * We need to cache the playback state because when the MediaController issues a play or
325          * pause command, it expects subsequent calls to {@link #isPlaying()} to return the right
326          * value immediately. However, the SmilPlayer executes play and pause asynchronously, so
327          * {@link #isPlaying()} will return the wrong value for some time. That's why we keep our
328          * own version of the state of whether the player is playing.
329          *
330          * Initialized to true because we always programatically start the SmilPlayer upon creation
331          */
332         private boolean mCachedIsPlaying = true;
333 
SmilPlayerController(SmilPlayer player)334         public SmilPlayerController(SmilPlayer player) {
335             mPlayer = player;
336         }
337 
getBufferPercentage()338         public int getBufferPercentage() {
339             // We don't need to buffer data, always return 100%.
340             return 100;
341         }
342 
getCurrentPosition()343         public int getCurrentPosition() {
344             return mPlayer.getCurrentPosition();
345         }
346 
getDuration()347         public int getDuration() {
348             return mPlayer.getDuration();
349         }
350 
isPlaying()351         public boolean isPlaying() {
352             return mCachedIsPlaying;
353         }
354 
pause()355         public void pause() {
356             mPlayer.pause();
357             mCachedIsPlaying = false;
358         }
359 
seekTo(int pos)360         public void seekTo(int pos) {
361             // Don't need to support.
362         }
363 
start()364         public void start() {
365             mPlayer.start();
366             mCachedIsPlaying = true;
367         }
368 
canPause()369         public boolean canPause() {
370             return true;
371         }
372 
canSeekBackward()373         public boolean canSeekBackward() {
374             return true;
375         }
376 
canSeekForward()377         public boolean canSeekForward() {
378             return true;
379         }
380     }
381 
handleEvent(Event evt)382     public void handleEvent(Event evt) {
383         final Event event = evt;
384         mHandler.post(new Runnable() {
385             public void run() {
386                 String type = event.getType();
387                 if(type.equals(SmilDocumentImpl.SMIL_DOCUMENT_END_EVENT)) {
388                     finish();
389                 }
390             }
391         });
392     }
393 }
394