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.IOException;
     21 import java.util.Comparator;
     22 import java.util.Map;
     23 import java.util.TreeMap;
     24 
     25 import android.content.Context;
     26 import android.graphics.Bitmap;
     27 import android.graphics.BitmapFactory;
     28 import android.media.MediaPlayer;
     29 import android.net.Uri;
     30 import android.text.method.HideReturnsTransformationMethod;
     31 import android.util.AttributeSet;
     32 import android.util.Log;
     33 import android.view.Gravity;
     34 import android.view.LayoutInflater;
     35 import android.view.View;
     36 import android.widget.AbsoluteLayout;
     37 import android.widget.FrameLayout;
     38 import android.widget.ImageView;
     39 import android.widget.LinearLayout;
     40 import android.widget.MediaController;
     41 import android.widget.ScrollView;
     42 import android.widget.TextView;
     43 import android.widget.VideoView;
     44 
     45 import com.android.mms.R;
     46 import com.android.mms.layout.LayoutManager;
     47 
     48 /**
     49  * A basic view to show the contents of a slide.
     50  */
     51 public class SlideView extends AbsoluteLayout implements
     52         AdaptableSlideViewInterface {
     53     private static final String TAG = "SlideView";
     54     private static final boolean DEBUG = false;
     55     private static final boolean LOCAL_LOGV = false;
     56     // FIXME: Need getHeight from mAudioInfoView instead of constant AUDIO_INFO_HEIGHT.
     57     private static final int AUDIO_INFO_HEIGHT = 82;
     58 
     59     private View mAudioInfoView;
     60     private ImageView mImageView;
     61     private VideoView mVideoView;
     62     private ScrollView mScrollText;
     63     private TextView mTextView;
     64     private OnSizeChangedListener mSizeChangedListener;
     65     private MediaPlayer mAudioPlayer;
     66     private boolean mIsPrepared;
     67     private boolean mStartWhenPrepared;
     68     private int     mSeekWhenPrepared;
     69     private boolean mStopWhenPrepared;
     70     private ScrollView mScrollViewPort;
     71     private LinearLayout mViewPort;
     72     // Indicates whether the view is in MMS conformance mode.
     73     private boolean mConformanceMode;
     74     private MediaController mMediaController;
     75 
     76     MediaPlayer.OnPreparedListener mPreparedListener = new MediaPlayer.OnPreparedListener() {
     77         public void onPrepared(MediaPlayer mp) {
     78             mIsPrepared = true;
     79             if (mSeekWhenPrepared > 0) {
     80                 mAudioPlayer.seekTo(mSeekWhenPrepared);
     81                 mSeekWhenPrepared = 0;
     82             }
     83             if (mStartWhenPrepared) {
     84                 mAudioPlayer.start();
     85                 mStartWhenPrepared = false;
     86                 displayAudioInfo();
     87             }
     88             if (mStopWhenPrepared) {
     89                 mAudioPlayer.stop();
     90                 mAudioPlayer.release();
     91                 mAudioPlayer = null;
     92                 mStopWhenPrepared = false;
     93                 hideAudioInfo();
     94             }
     95         }
     96     };
     97 
     98     public SlideView(Context context) {
     99         super(context);
    100     }
    101 
    102     public SlideView(Context context, AttributeSet attrs) {
    103         super(context, attrs);
    104     }
    105 
    106     public void setImage(String name, Bitmap bitmap) {
    107         if (mImageView == null) {
    108             mImageView = new ImageView(mContext);
    109             mImageView.setPadding(0, 5, 0, 5);
    110             addView(mImageView, new LayoutParams(
    111                     LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT, 0, 0));
    112             if (DEBUG) {
    113                 mImageView.setBackgroundColor(0xFFFF0000);
    114             }
    115         }
    116         try {
    117             if (null == bitmap) {
    118                 bitmap = BitmapFactory.decodeResource(getResources(),
    119                         R.drawable.ic_missing_thumbnail_picture);
    120             }
    121             mImageView.setVisibility(View.VISIBLE);
    122             mImageView.setImageBitmap(bitmap);
    123         } catch (java.lang.OutOfMemoryError e) {
    124             Log.e(TAG, "setImage: out of memory: ", e);
    125         }
    126     }
    127 
    128     public void setImageRegion(int left, int top, int width, int height) {
    129         // Ignore any requirement of layout change once we are in MMS conformance mode.
    130         if (mImageView != null && !mConformanceMode) {
    131             mImageView.setLayoutParams(new LayoutParams(width, height, left, top));
    132         }
    133     }
    134 
    135     public void setImageRegionFit(String fit) {
    136         // TODO Auto-generated method stub
    137     }
    138 
    139     public void setVideo(String name, Uri video) {
    140         if (mVideoView == null) {
    141             mVideoView = new VideoView(mContext);
    142             addView(mVideoView, new LayoutParams(
    143                     LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT, 0, 0));
    144             if (DEBUG) {
    145                 mVideoView.setBackgroundColor(0xFFFF0000);
    146             }
    147         }
    148 
    149         if (LOCAL_LOGV) {
    150             Log.v(TAG, "Changing video source to " + video);
    151         }
    152         mVideoView.setVisibility(View.VISIBLE);
    153         mVideoView.setVideoURI(video);
    154     }
    155 
    156     public void setMediaController(MediaController mediaController) {
    157         mMediaController = mediaController;
    158     }
    159 
    160     private void initAudioInfoView(String name) {
    161         if (null == mAudioInfoView) {
    162             LayoutInflater factory = LayoutInflater.from(getContext());
    163             mAudioInfoView = factory.inflate(R.layout.playing_audio_info, null);
    164             int height = mAudioInfoView.getHeight();
    165             if (mConformanceMode) {
    166                 mViewPort.addView(mAudioInfoView, new LinearLayout.LayoutParams(LayoutParams.MATCH_PARENT,
    167                         AUDIO_INFO_HEIGHT));
    168             } else {
    169                 addView(mAudioInfoView, new LayoutParams(
    170                         LayoutParams.MATCH_PARENT, AUDIO_INFO_HEIGHT,
    171                         0, getHeight() - AUDIO_INFO_HEIGHT));
    172                 if (DEBUG) {
    173                     mAudioInfoView.setBackgroundColor(0xFFFF0000);
    174                 }
    175             }
    176         }
    177         TextView audioName = (TextView) mAudioInfoView.findViewById(R.id.name);
    178         audioName.setText(name);
    179         mAudioInfoView.setVisibility(View.GONE);
    180     }
    181 
    182     private void displayAudioInfo() {
    183         if (null != mAudioInfoView) {
    184             mAudioInfoView.setVisibility(View.VISIBLE);
    185         }
    186     }
    187 
    188     private void hideAudioInfo() {
    189         if (null != mAudioInfoView) {
    190             mAudioInfoView.setVisibility(View.GONE);
    191         }
    192     }
    193 
    194     public void setAudio(Uri audio, String name, Map<String, ?> extras) {
    195         if (audio == null) {
    196             throw new IllegalArgumentException("Audio URI may not be null.");
    197         }
    198 
    199         if (LOCAL_LOGV) {
    200             Log.v(TAG, "Changing audio source to " + audio);
    201         }
    202 
    203         if (mAudioPlayer != null) {
    204             mAudioPlayer.reset();
    205             mAudioPlayer.release();
    206             mAudioPlayer = null;
    207         }
    208 
    209         // Reset state variables
    210         mIsPrepared = false;
    211         mStartWhenPrepared = false;
    212         mSeekWhenPrepared = 0;
    213         mStopWhenPrepared = false;
    214 
    215         try {
    216             mAudioPlayer = new MediaPlayer();
    217             mAudioPlayer.setOnPreparedListener(mPreparedListener);
    218             mAudioPlayer.setDataSource(mContext, audio);
    219             mAudioPlayer.prepareAsync();
    220         } catch (IOException e) {
    221             Log.e(TAG, "Unexpected IOException.", e);
    222             mAudioPlayer.release();
    223             mAudioPlayer = null;
    224         }
    225         initAudioInfoView(name);
    226     }
    227 
    228     public void setText(String name, String text) {
    229         if (!mConformanceMode) {
    230             if (null == mScrollText) {
    231                 mScrollText = new ScrollView(mContext);
    232                 mScrollText.setScrollBarStyle(SCROLLBARS_OUTSIDE_INSET);
    233                 addView(mScrollText, new LayoutParams(
    234                         LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT, 0, 0));
    235                 if (DEBUG) {
    236                     mScrollText.setBackgroundColor(0xFF00FF00);
    237                 }
    238             }
    239             if (null == mTextView) {
    240                 mTextView = new TextView(mContext);
    241                 mTextView.setTransformationMethod(HideReturnsTransformationMethod.getInstance());
    242                 mScrollText.addView(mTextView);
    243             }
    244             mScrollText.requestFocus();
    245         }
    246         mTextView.setVisibility(View.VISIBLE);
    247         mTextView.setText(text);
    248         // Let the text in Mms can be selected.
    249         mTextView.setTextIsSelectable(true);
    250     }
    251 
    252     public void setTextRegion(int left, int top, int width, int height) {
    253         // Ignore any requirement of layout change once we are in MMS conformance mode.
    254         if (mScrollText != null && !mConformanceMode) {
    255             mScrollText.setLayoutParams(new LayoutParams(width, height, left, top));
    256         }
    257     }
    258 
    259     public void setVideoRegion(int left, int top, int width, int height) {
    260         if (mVideoView != null && !mConformanceMode) {
    261             mVideoView.setLayoutParams(new LayoutParams(width, height, left, top));
    262         }
    263     }
    264 
    265     public void setImageVisibility(boolean visible) {
    266         if (mImageView != null) {
    267             if (mConformanceMode) {
    268                 mImageView.setVisibility(visible ? View.VISIBLE : View.GONE);
    269             } else {
    270                 mImageView.setVisibility(visible ? View.VISIBLE : View.INVISIBLE);
    271             }
    272         }
    273     }
    274 
    275     public void setTextVisibility(boolean visible) {
    276         if (mConformanceMode) {
    277             if (mTextView != null) {
    278                 mTextView.setVisibility(visible ? View.VISIBLE : View.GONE);
    279             }
    280         } else if (mScrollText != null) {
    281             mScrollText.setVisibility(visible ? View.VISIBLE : View.INVISIBLE);
    282         }
    283     }
    284 
    285     public void setVideoVisibility(boolean visible) {
    286         if (mVideoView != null) {
    287             if (mConformanceMode) {
    288                 mVideoView.setVisibility(visible ? View.VISIBLE : View.GONE);
    289             } else {
    290                 mVideoView.setVisibility(visible ? View.VISIBLE : View.INVISIBLE);
    291             }
    292         }
    293     }
    294 
    295     public void startAudio() {
    296         if ((mAudioPlayer != null) && mIsPrepared) {
    297             mAudioPlayer.start();
    298             mStartWhenPrepared = false;
    299             displayAudioInfo();
    300         } else {
    301             mStartWhenPrepared = true;
    302         }
    303     }
    304 
    305     public void stopAudio() {
    306         if ((mAudioPlayer != null) && mIsPrepared) {
    307             mAudioPlayer.stop();
    308             mAudioPlayer.release();
    309             mAudioPlayer = null;
    310             hideAudioInfo();
    311         } else {
    312             mStopWhenPrepared = true;
    313         }
    314     }
    315 
    316     public void pauseAudio() {
    317         if ((mAudioPlayer != null) && mIsPrepared) {
    318             if (mAudioPlayer.isPlaying()) {
    319                 mAudioPlayer.pause();
    320             }
    321         }
    322         mStartWhenPrepared = false;
    323     }
    324 
    325     public void seekAudio(int seekTo) {
    326         if ((mAudioPlayer != null) && mIsPrepared) {
    327             mAudioPlayer.seekTo(seekTo);
    328         } else {
    329             mSeekWhenPrepared = seekTo;
    330         }
    331     }
    332 
    333     public void startVideo() {
    334         if (mVideoView != null) {
    335             if (LOCAL_LOGV) {
    336                 Log.v(TAG, "Starting video playback.");
    337             }
    338             mVideoView.start();
    339         }
    340     }
    341 
    342     public void stopVideo() {
    343         if ((mVideoView != null)) {
    344             if (LOCAL_LOGV) {
    345                 Log.v(TAG, "Stopping video playback.");
    346             }
    347             mVideoView.stopPlayback();
    348         }
    349     }
    350 
    351     public void pauseVideo() {
    352         if (mVideoView != null) {
    353             if (LOCAL_LOGV) {
    354                 Log.v(TAG, "Pausing video playback.");
    355             }
    356             mVideoView.pause();
    357         }
    358     }
    359 
    360     public void seekVideo(int seekTo) {
    361         if (mVideoView != null) {
    362             if (seekTo > 0) {
    363                 if (LOCAL_LOGV) {
    364                     Log.v(TAG, "Seeking video playback to " + seekTo);
    365                 }
    366                 mVideoView.seekTo(seekTo);
    367             }
    368         }
    369     }
    370 
    371     public void reset() {
    372         if (null != mScrollText) {
    373             mScrollText.setVisibility(View.GONE);
    374         }
    375 
    376         if (null != mImageView) {
    377             mImageView.setVisibility(View.GONE);
    378         }
    379 
    380         if (null != mAudioPlayer) {
    381             stopAudio();
    382         }
    383 
    384         if (null != mVideoView) {
    385             stopVideo();
    386             mVideoView.setVisibility(View.GONE);
    387         }
    388 
    389         if (null != mTextView) {
    390             mTextView.setVisibility(View.GONE);
    391         }
    392 
    393         if (mScrollViewPort != null) {
    394             mScrollViewPort.scrollTo(0, 0);
    395             mScrollViewPort.setLayoutParams(
    396                     new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT, 0, 0));
    397         }
    398 
    399     }
    400 
    401     public void setVisibility(boolean visible) {
    402         // TODO Auto-generated method stub
    403     }
    404 
    405     @Override
    406     protected void onSizeChanged(int w, int h, int oldw, int oldh) {
    407         super.onSizeChanged(w, h, oldw, oldh);
    408 
    409         if (mSizeChangedListener != null) {
    410             if (LOCAL_LOGV) {
    411                 Log.v(TAG, "new size=" + w + "x" + h);
    412             }
    413             mSizeChangedListener.onSizeChanged(w, h - AUDIO_INFO_HEIGHT);
    414         }
    415     }
    416 
    417     public void setOnSizeChangedListener(OnSizeChangedListener l) {
    418         mSizeChangedListener = l;
    419     }
    420 
    421     private class Position {
    422         public Position(int left, int top) {
    423             mTop = top;
    424             mLeft = left;
    425         }
    426         public int mTop;
    427         public int mLeft;
    428     }
    429 
    430     /**
    431      * Makes the SlideView working on  MMSConformance Mode. The view will be
    432      * re-layout to the linear view.
    433      * <p>
    434      * This is Chinese requirement about mms conformance.
    435      * The most popular Mms service in China is newspaper which is MMS conformance,
    436      * normally it mixes the image and text and has a number of slides. The
    437      * AbsoluteLayout doesn't have good user experience for this kind of message,
    438      * for example,
    439      *
    440      * 1. AbsoluteLayout exactly follows the smil's layout which is not optimized,
    441      * and actually, no other MMS applications follow the smil's layout, they adjust
    442      * the layout according their screen size. MMS conformance doc also allows the
    443      * implementation to adjust the layout.
    444      *
    445      * 2. The TextView is fixed in the small area of screen, and other part of screen
    446      * is empty once there is no image in the current slide.
    447      *
    448      * 3. The TextView is scrollable in a small area of screen and the font size is
    449      * small which make the user experience bad.
    450      *
    451      * The better UI for the MMS conformance message could be putting the image/video
    452      * and text in a linear layout view and making them scrollable together.
    453      *
    454      * Another reason for only applying the LinearLayout to the MMS conformance message
    455      * is that the AbsoluteLayout has ability to play image and video in a same screen.
    456      * which shouldn't be broken.
    457      */
    458     public void enableMMSConformanceMode(int textLeft, int textTop,
    459             int imageLeft, int imageTop) {
    460         mConformanceMode = true;
    461         if (mScrollViewPort == null) {
    462             mScrollViewPort = new ScrollView(mContext) {
    463                 private int mBottomY;
    464                 @Override
    465                 protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
    466                     super.onLayout(changed, left, top, right, bottom);
    467                     if (getChildCount() > 0) {
    468                         int childHeight = getChildAt(0).getHeight();
    469                         int height = getHeight();
    470                         mBottomY = height < childHeight ? childHeight - height : 0;
    471                     }
    472                 }
    473                 @Override
    474                 protected void onScrollChanged(int l, int t, int oldl, int oldt) {
    475                     // Shows MediaController when the view is scrolled to the top/bottom of itself.
    476                     if (t == 0 || t >= mBottomY){
    477                         if (mMediaController != null
    478                                 && !((SlideshowActivity) mContext).isFinishing()) {
    479                             mMediaController.show();
    480                         }
    481                     }
    482                 }
    483             };
    484             mScrollViewPort.setScrollBarStyle(SCROLLBARS_INSIDE_OVERLAY);
    485             mViewPort = new LinearLayout(mContext);
    486             mViewPort.setOrientation(LinearLayout.VERTICAL);
    487             mViewPort.setGravity(Gravity.CENTER);
    488             mViewPort.setOnClickListener(new OnClickListener() {
    489                 public void onClick(View v) {
    490                     if (mMediaController != null) {
    491                         mMediaController.show();
    492                     }
    493                 }
    494             });
    495             mScrollViewPort.addView(mViewPort, new FrameLayout.LayoutParams(LayoutParams.MATCH_PARENT,
    496                     LayoutParams.WRAP_CONTENT));
    497             addView(mScrollViewPort);
    498         }
    499         // Layout views to fit the LinearLayout from left to right, then top to
    500         // bottom.
    501         TreeMap<Position, View> viewsByPosition = new TreeMap<Position, View>(new Comparator<Position>() {
    502             public int compare(Position p1, Position p2) {
    503                 int l1 = p1.mLeft;
    504                 int t1 = p1.mTop;
    505                 int l2 = p2.mLeft;
    506                 int t2 = p2.mTop;
    507                 int res = t1 - t2;
    508                 if (res == 0) {
    509                     res = l1 - l2;
    510                 }
    511                 if (res == 0) {
    512                     // A view will be lost if return 0.
    513                     return -1;
    514                 }
    515                 return res;
    516             }
    517         });
    518         if (textLeft >=0 && textTop >=0) {
    519             mTextView = new TextView(mContext);
    520             mTextView.setTransformationMethod(HideReturnsTransformationMethod.getInstance());
    521             mTextView.setTextSize(18);
    522             mTextView.setPadding(5, 5, 5, 5);
    523             viewsByPosition.put(new Position(textLeft, textTop), mTextView);
    524         }
    525 
    526         if (imageLeft >=0 && imageTop >=0) {
    527             mImageView = new ImageView(mContext);
    528             mImageView.setPadding(0, 5, 0, 5);
    529             viewsByPosition.put(new Position(imageLeft, imageTop), mImageView);
    530             // According MMS Conformance Document, the image and video should use the same
    531             // region. So, put the VideoView below the ImageView.
    532             mVideoView = new VideoView(mContext);
    533             viewsByPosition.put(new Position(imageLeft + 1, imageTop), mVideoView);
    534         }
    535         for (View view : viewsByPosition.values()) {
    536             if (view instanceof VideoView) {
    537                 mViewPort.addView(view, new LinearLayout.LayoutParams(LayoutParams.MATCH_PARENT,
    538                         LayoutManager.getInstance().getLayoutParameters().getHeight()));
    539             } else {
    540                 mViewPort.addView(view, new LinearLayout.LayoutParams(LayoutParams.MATCH_PARENT,
    541                         LayoutParams.WRAP_CONTENT));
    542             }
    543             view.setVisibility(View.GONE);
    544         }
    545     }
    546 
    547     public void setVideoThumbnail(String name, Bitmap bitmap) {
    548     }
    549 }
    550