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 import android.widget.SeekBar;
     46 
     47 import com.android.mms.R;
     48 import com.android.mms.dom.AttrImpl;
     49 import com.android.mms.dom.smil.SmilDocumentImpl;
     50 import com.android.mms.dom.smil.SmilPlayer;
     51 import com.android.mms.dom.smil.parser.SmilXmlSerializer;
     52 import com.android.mms.model.LayoutModel;
     53 import com.android.mms.model.RegionModel;
     54 import com.android.mms.model.SlideshowModel;
     55 import com.android.mms.model.SmilHelper;
     56 import com.google.android.mms.MmsException;
     57 
     58 /**
     59  * Plays the given slideshow in full-screen mode with a common controller.
     60  */
     61 public 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 = false;
     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     private int mSlideCount;
     75 
     76     /**
     77      * @return whether the Smil has MMS conformance layout.
     78      * Refer to MMS Conformance Document OMA-MMS-CONF-v1_2-20050301-A
     79      */
     80     private static final boolean isMMSConformance(SMILDocument smilDoc) {
     81         SMILElement head = smilDoc.getHead();
     82         if (head == null) {
     83             // No 'head' element
     84             return false;
     85         }
     86         NodeList children = head.getChildNodes();
     87         if (children == null || children.getLength() != 1) {
     88             // The 'head' element should have only one child.
     89             return false;
     90         }
     91         Node layout = children.item(0);
     92         if (layout == null || !"layout".equals(layout.getNodeName())) {
     93             // The child is not layout element
     94             return false;
     95         }
     96         NodeList layoutChildren = layout.getChildNodes();
     97         if (layoutChildren == null) {
     98             // The 'layout' element has no child.
     99             return false;
    100         }
    101         int num = layoutChildren.getLength();
    102         if (num <= 0) {
    103             // The 'layout' element has no child.
    104             return false;
    105         }
    106         for (int i = 0; i < num; i++) {
    107             Node layoutChild = layoutChildren.item(i);
    108             if (layoutChild == null) {
    109                 // The 'layout' child is null.
    110                 return false;
    111             }
    112             String name = layoutChild.getNodeName();
    113             if ("root-layout".equals(name)) {
    114                 continue;
    115             } else if ("region".equals(name)) {
    116                 NamedNodeMap map = layoutChild.getAttributes();
    117                 for (int j = 0; j < map.getLength(); j++) {
    118                     Node node = map.item(j);
    119                     if (node == null) {
    120                         return false;
    121                     }
    122                     String attrName = node.getNodeName();
    123                     // The attr should be one of left, top, height, width, fit and id
    124                     if ("left".equals(attrName) || "top".equals(attrName) ||
    125                             "height".equals(attrName) || "width".equals(attrName) ||
    126                             "fit".equals(attrName)) {
    127                         continue;
    128                     } else if ("id".equals(attrName)) {
    129                         String value;
    130                         if (node instanceof AttrImpl) {
    131                             value = ((AttrImpl)node).getValue();
    132                         } else {
    133                             return false;
    134                         }
    135                         if ("Text".equals(value) || "Image".equals(value)) {
    136                             continue;
    137                         } else {
    138                             // The id attr is not 'Text' or 'Image'
    139                             return false;
    140                         }
    141                     } else {
    142                         return false;
    143                     }
    144                 }
    145             } else {
    146                 // The 'layout' element has the child other than 'root-layout' or 'region'
    147                 return false;
    148             }
    149         }
    150         return true;
    151     }
    152 
    153     @Override
    154     public void onCreate(Bundle icicle) {
    155         super.onCreate(icicle);
    156         mHandler = new Handler();
    157 
    158         // Play slide-show in full-screen mode.
    159         requestWindowFeature(Window.FEATURE_NO_TITLE);
    160         getWindow().setFormat(PixelFormat.TRANSLUCENT);
    161         setContentView(R.layout.slideshow);
    162 
    163         Intent intent = getIntent();
    164         Uri msg = intent.getData();
    165         final SlideshowModel model;
    166 
    167         try {
    168             model = SlideshowModel.createFromMessageUri(this, msg);
    169             mSlideCount = model.size();
    170         } catch (MmsException e) {
    171             Log.e(TAG, "Cannot present the slide show.", e);
    172             finish();
    173             return;
    174         }
    175 
    176         mSlideView = (SlideView) findViewById(R.id.slide_view);
    177         PresenterFactory.getPresenter("SlideshowPresenter", this, mSlideView, model);
    178 
    179         mHandler.post(new Runnable() {
    180             private boolean isRotating() {
    181                 return mSmilPlayer.isPausedState()
    182                         || mSmilPlayer.isPlayingState()
    183                         || mSmilPlayer.isPlayedState();
    184             }
    185 
    186             public void run() {
    187                 mSmilPlayer = SmilPlayer.getPlayer();
    188                 if (mSlideCount > 1) {
    189                     // Only show the slideshow controller if we have more than a single slide.
    190                     // Otherwise, when we play a sound on a single slide, it appears like
    191                     // the slide controller should control the sound (seeking, ff'ing, etc).
    192                     initMediaController();
    193                     mSlideView.setMediaController(mMediaController);
    194                 }
    195                 // Use SmilHelper.getDocument() to ensure rebuilding the
    196                 // entire SMIL document.
    197                 mSmilDoc = SmilHelper.getDocument(model);
    198                 if (isMMSConformance(mSmilDoc)) {
    199                     int imageLeft = 0;
    200                     int imageTop = 0;
    201                     int textLeft = 0;
    202                     int textTop = 0;
    203                     LayoutModel layout = model.getLayout();
    204                     if (layout != null) {
    205                         RegionModel imageRegion = layout.getImageRegion();
    206                         if (imageRegion != null) {
    207                             imageLeft = imageRegion.getLeft();
    208                             imageTop = imageRegion.getTop();
    209                         }
    210                         RegionModel textRegion = layout.getTextRegion();
    211                         if (textRegion != null) {
    212                             textLeft = textRegion.getLeft();
    213                             textTop = textRegion.getTop();
    214                         }
    215                     }
    216                     mSlideView.enableMMSConformanceMode(textLeft, textTop, imageLeft, imageTop);
    217                 }
    218                 if (DEBUG) {
    219                     ByteArrayOutputStream ostream = new ByteArrayOutputStream();
    220                     SmilXmlSerializer.serialize(mSmilDoc, ostream);
    221                     if (LOCAL_LOGV) {
    222                         Log.v(TAG, ostream.toString());
    223                     }
    224                 }
    225 
    226                 // Add event listener.
    227                 ((EventTarget) mSmilDoc).addEventListener(
    228                         SmilDocumentImpl.SMIL_DOCUMENT_END_EVENT,
    229                         SlideshowActivity.this, false);
    230 
    231                 mSmilPlayer.init(mSmilDoc);
    232                 if (isRotating()) {
    233                     mSmilPlayer.reload();
    234                 } else {
    235                     mSmilPlayer.play();
    236                 }
    237             }
    238         });
    239     }
    240 
    241     private void initMediaController() {
    242         mMediaController = new MediaController(SlideshowActivity.this, false);
    243         mMediaController.setMediaPlayer(new SmilPlayerController(mSmilPlayer));
    244         mMediaController.setAnchorView(findViewById(R.id.slide_view));
    245         mMediaController.setPrevNextListeners(
    246             new OnClickListener() {
    247               public void onClick(View v) {
    248                   mSmilPlayer.next();
    249               }
    250             },
    251             new OnClickListener() {
    252               public void onClick(View v) {
    253                   mSmilPlayer.prev();
    254               }
    255             });
    256     }
    257 
    258     @Override
    259     public boolean onTouchEvent(MotionEvent ev) {
    260         if ((mSmilPlayer != null) && (mMediaController != null)) {
    261             mMediaController.show();
    262         }
    263         return false;
    264     }
    265 
    266     @Override
    267     protected void onPause() {
    268         super.onPause();
    269         if (mSmilDoc != null) {
    270             ((EventTarget) mSmilDoc).removeEventListener(
    271                     SmilDocumentImpl.SMIL_DOCUMENT_END_EVENT, this, false);
    272         }
    273         if (mSmilPlayer != null) {
    274             mSmilPlayer.pause();
    275         }
    276     }
    277 
    278     @Override
    279     protected void onStop() {
    280         super.onStop();
    281         if ((null != mSmilPlayer)) {
    282             if (isFinishing()) {
    283                 mSmilPlayer.stop();
    284             } else {
    285                 mSmilPlayer.stopWhenReload();
    286             }
    287             if (mMediaController != null) {
    288                 // Must set the seek bar change listener null, otherwise if we rotate it
    289                 // while tapping progress bar continuously, window will leak.
    290                 View seekBar = mMediaController
    291                         .findViewById(com.android.internal.R.id.mediacontroller_progress);
    292                 if (seekBar instanceof SeekBar) {
    293                     ((SeekBar)seekBar).setOnSeekBarChangeListener(null);
    294                 }
    295                 // Must do this so we don't leak a window.
    296                 mMediaController.hide();
    297             }
    298         }
    299     }
    300 
    301     @Override
    302     protected void onDestroy() {
    303         if (mSlideView != null) {
    304             mSlideView.setMediaController(null);
    305         }
    306         super.onDestroy();
    307     }
    308 
    309     @Override
    310     public boolean onKeyDown(int keyCode, KeyEvent event) {
    311         switch (keyCode) {
    312             case KeyEvent.KEYCODE_VOLUME_DOWN:
    313             case KeyEvent.KEYCODE_VOLUME_UP:
    314             case KeyEvent.KEYCODE_VOLUME_MUTE:
    315             case KeyEvent.KEYCODE_DPAD_UP:
    316             case KeyEvent.KEYCODE_DPAD_DOWN:
    317             case KeyEvent.KEYCODE_DPAD_LEFT:
    318             case KeyEvent.KEYCODE_DPAD_RIGHT:
    319                 break;
    320             case KeyEvent.KEYCODE_BACK:
    321             case KeyEvent.KEYCODE_MENU:
    322                 if ((mSmilPlayer != null) &&
    323                         (mSmilPlayer.isPausedState()
    324                         || mSmilPlayer.isPlayingState()
    325                         || mSmilPlayer.isPlayedState())) {
    326                     mSmilPlayer.stop();
    327                 }
    328                 break;
    329             default:
    330                 if ((mSmilPlayer != null) && (mMediaController != null)) {
    331                     mMediaController.show();
    332                 }
    333         }
    334         return super.onKeyDown(keyCode, event);
    335     }
    336 
    337     private class SmilPlayerController implements MediaPlayerControl {
    338         private final SmilPlayer mPlayer;
    339         /**
    340          * We need to cache the playback state because when the MediaController issues a play or
    341          * pause command, it expects subsequent calls to {@link #isPlaying()} to return the right
    342          * value immediately. However, the SmilPlayer executes play and pause asynchronously, so
    343          * {@link #isPlaying()} will return the wrong value for some time. That's why we keep our
    344          * own version of the state of whether the player is playing.
    345          *
    346          * Initialized to true because we always programatically start the SmilPlayer upon creation
    347          */
    348         private boolean mCachedIsPlaying = true;
    349 
    350         public SmilPlayerController(SmilPlayer player) {
    351             mPlayer = player;
    352         }
    353 
    354         public int getBufferPercentage() {
    355             // We don't need to buffer data, always return 100%.
    356             return 100;
    357         }
    358 
    359         public int getCurrentPosition() {
    360             return mPlayer.getCurrentPosition();
    361         }
    362 
    363         public int getDuration() {
    364             return mPlayer.getDuration();
    365         }
    366 
    367         public boolean isPlaying() {
    368             return mCachedIsPlaying;
    369         }
    370 
    371         public void pause() {
    372             mPlayer.pause();
    373             mCachedIsPlaying = false;
    374         }
    375 
    376         public void seekTo(int pos) {
    377             // Don't need to support.
    378         }
    379 
    380         public void start() {
    381             mPlayer.start();
    382             mCachedIsPlaying = true;
    383         }
    384 
    385         public boolean canPause() {
    386             return true;
    387         }
    388 
    389         public boolean canSeekBackward() {
    390             return true;
    391         }
    392 
    393         public boolean canSeekForward() {
    394             return true;
    395         }
    396 
    397         @Override
    398         public int getAudioSessionId() {
    399             return 0;
    400         }
    401     }
    402 
    403     public void handleEvent(Event evt) {
    404         final Event event = evt;
    405         mHandler.post(new Runnable() {
    406             public void run() {
    407                 String type = event.getType();
    408                 if(type.equals(SmilDocumentImpl.SMIL_DOCUMENT_END_EVENT)) {
    409                     finish();
    410                 }
    411             }
    412         });
    413     }
    414 }
    415