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