Home | History | Annotate | Download | only in ui
      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 java.io.ByteArrayOutputStream;
     21 
     22 import org.w3c.dom.NamedNodeMap;
     23 import org.w3c.dom.Node;
     24 import org.w3c.dom.NodeList;
     25 import org.w3c.dom.events.Event;
     26 import org.w3c.dom.events.EventListener;
     27 import org.w3c.dom.events.EventTarget;
     28 import org.w3c.dom.smil.SMILDocument;
     29 import org.w3c.dom.smil.SMILElement;
     30 
     31 import android.app.Activity;
     32 import android.content.Intent;
     33 import android.graphics.PixelFormat;
     34 import android.net.Uri;
     35 import android.os.Bundle;
     36 import android.os.Handler;
     37 import android.util.Log;
     38 import android.view.KeyEvent;
     39 import android.view.MotionEvent;
     40 import android.view.View;
     41 import android.view.View.OnClickListener;
     42 import android.view.Window;
     43 import android.widget.MediaController;
     44 import android.widget.MediaController.MediaPlayerControl;
     45 
     46 import com.android.mms.R;
     47 import com.android.mms.dom.AttrImpl;
     48 import com.android.mms.dom.smil.SmilDocumentImpl;
     49 import com.android.mms.dom.smil.SmilPlayer;
     50 import com.android.mms.dom.smil.parser.SmilXmlSerializer;
     51 import com.android.mms.model.LayoutModel;
     52 import com.android.mms.model.RegionModel;
     53 import com.android.mms.model.SlideshowModel;
     54 import com.android.mms.model.SmilHelper;
     55 import com.google.android.mms.MmsException;
     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      */
     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             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 
    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
    258     public boolean onTouchEvent(MotionEvent ev) {
    259         if ((mSmilPlayer != null) && (mMediaController != null)) {
    260             mMediaController.show();
    261         }
    262         return false;
    263     }
    264 
    265     @Override
    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
    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
    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 
    334         public SmilPlayerController(SmilPlayer player) {
    335             mPlayer = player;
    336         }
    337 
    338         public int getBufferPercentage() {
    339             // We don't need to buffer data, always return 100%.
    340             return 100;
    341         }
    342 
    343         public int getCurrentPosition() {
    344             return mPlayer.getCurrentPosition();
    345         }
    346 
    347         public int getDuration() {
    348             return mPlayer.getDuration();
    349         }
    350 
    351         public boolean isPlaying() {
    352             return mCachedIsPlaying;
    353         }
    354 
    355         public void pause() {
    356             mPlayer.pause();
    357             mCachedIsPlaying = false;
    358         }
    359 
    360         public void seekTo(int pos) {
    361             // Don't need to support.
    362         }
    363 
    364         public void start() {
    365             mPlayer.start();
    366             mCachedIsPlaying = true;
    367         }
    368 
    369         public boolean canPause() {
    370             return true;
    371         }
    372 
    373         public boolean canSeekBackward() {
    374             return true;
    375         }
    376 
    377         public boolean canSeekForward() {
    378             return true;
    379         }
    380 
    381         @Override
    382         public int getAudioSessionId() {
    383             return 0;
    384         }
    385     }
    386 
    387     public void handleEvent(Event evt) {
    388         final Event event = evt;
    389         mHandler.post(new Runnable() {
    390             public void run() {
    391                 String type = event.getType();
    392                 if(type.equals(SmilDocumentImpl.SMIL_DOCUMENT_END_EVENT)) {
    393                     finish();
    394                 }
    395             }
    396         });
    397     }
    398 }
    399