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 com.android.mms.R;
     21 import com.android.mms.layout.LayoutManager;
     22 
     23 import android.content.Context;
     24 import android.graphics.Bitmap;
     25 import android.graphics.BitmapFactory;
     26 import android.media.MediaPlayer;
     27 import android.net.Uri;
     28 import android.text.method.HideReturnsTransformationMethod;
     29 import android.util.AttributeSet;
     30 import android.util.Log;
     31 import android.view.Gravity;
     32 import android.view.LayoutInflater;
     33 import android.view.View;
     34 import android.widget.AbsoluteLayout;
     35 import android.widget.FrameLayout;
     36 import android.widget.ImageView;
     37 import android.widget.LinearLayout;
     38 import android.widget.MediaController;
     39 import android.widget.ScrollView;
     40 import android.widget.TextView;
     41 import android.widget.VideoView;
     42 
     43 import java.io.IOException;
     44 import java.util.Comparator;
     45 import java.util.Map;
     46 import java.util.TreeMap;
     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.WRAP_CONTENT, LayoutParams.WRAP_CONTENT, 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     }
    249 
    250     public void setTextRegion(int left, int top, int width, int height) {
    251         // Ignore any requirement of layout change once we are in MMS conformance mode.
    252         if (mScrollText != null && !mConformanceMode) {
    253             mScrollText.setLayoutParams(new LayoutParams(width, height, left, top));
    254         }
    255     }
    256 
    257     public void setVideoRegion(int left, int top, int width, int height) {
    258         if (mVideoView != null && !mConformanceMode) {
    259             mVideoView.setLayoutParams(new LayoutParams(width, height, left, top));
    260         }
    261     }
    262 
    263     public void setImageVisibility(boolean visible) {
    264         if (mImageView != null) {
    265             if (mConformanceMode) {
    266                 mImageView.setVisibility(visible ? View.VISIBLE : View.GONE);
    267             } else {
    268                 mImageView.setVisibility(visible ? View.VISIBLE : View.INVISIBLE);
    269             }
    270         }
    271     }
    272 
    273     public void setTextVisibility(boolean visible) {
    274         if (mConformanceMode) {
    275             if (mTextView != null) {
    276                 mTextView.setVisibility(visible ? View.VISIBLE : View.GONE);
    277             }
    278         } else if (mScrollText != null) {
    279             mScrollText.setVisibility(visible ? View.VISIBLE : View.INVISIBLE);
    280         }
    281     }
    282 
    283     public void setVideoVisibility(boolean visible) {
    284         if (mVideoView != null) {
    285             if (mConformanceMode) {
    286                 mVideoView.setVisibility(visible ? View.VISIBLE : View.GONE);
    287             } else {
    288                 mVideoView.setVisibility(visible ? View.VISIBLE : View.INVISIBLE);
    289             }
    290         }
    291     }
    292 
    293     public void startAudio() {
    294         if ((mAudioPlayer != null) && mIsPrepared) {
    295             mAudioPlayer.start();
    296             mStartWhenPrepared = false;
    297             displayAudioInfo();
    298         } else {
    299             mStartWhenPrepared = true;
    300         }
    301     }
    302 
    303     public void stopAudio() {
    304         if ((mAudioPlayer != null) && mIsPrepared) {
    305             mAudioPlayer.stop();
    306             mAudioPlayer.release();
    307             mAudioPlayer = null;
    308             hideAudioInfo();
    309         } else {
    310             mStopWhenPrepared = true;
    311         }
    312     }
    313 
    314     public void pauseAudio() {
    315         if ((mAudioPlayer != null) && mIsPrepared) {
    316             if (mAudioPlayer.isPlaying()) {
    317                 mAudioPlayer.pause();
    318             }
    319         }
    320         mStartWhenPrepared = false;
    321     }
    322 
    323     public void seekAudio(int seekTo) {
    324         if ((mAudioPlayer != null) && mIsPrepared) {
    325             mAudioPlayer.seekTo(seekTo);
    326         } else {
    327             mSeekWhenPrepared = seekTo;
    328         }
    329     }
    330 
    331     public void startVideo() {
    332         if (mVideoView != null) {
    333             if (LOCAL_LOGV) {
    334                 Log.v(TAG, "Starting video playback.");
    335             }
    336             mVideoView.start();
    337         }
    338     }
    339 
    340     public void stopVideo() {
    341         if ((mVideoView != null)) {
    342             if (LOCAL_LOGV) {
    343                 Log.v(TAG, "Stopping video playback.");
    344             }
    345             mVideoView.stopPlayback();
    346         }
    347     }
    348 
    349     public void pauseVideo() {
    350         if (mVideoView != null) {
    351             if (LOCAL_LOGV) {
    352                 Log.v(TAG, "Pausing video playback.");
    353             }
    354             mVideoView.pause();
    355         }
    356     }
    357 
    358     public void seekVideo(int seekTo) {
    359         if (mVideoView != null) {
    360             if (seekTo > 0) {
    361                 if (LOCAL_LOGV) {
    362                     Log.v(TAG, "Seeking video playback to " + seekTo);
    363                 }
    364                 mVideoView.seekTo(seekTo);
    365             }
    366         }
    367     }
    368 
    369     public void reset() {
    370         if (null != mScrollText) {
    371             mScrollText.setVisibility(View.GONE);
    372         }
    373 
    374         if (null != mImageView) {
    375             mImageView.setVisibility(View.GONE);
    376         }
    377 
    378         if (null != mAudioPlayer) {
    379             stopAudio();
    380         }
    381 
    382         if (null != mVideoView) {
    383             stopVideo();
    384             mVideoView.setVisibility(View.GONE);
    385         }
    386 
    387         if (null != mTextView) {
    388             mTextView.setVisibility(View.GONE);
    389         }
    390 
    391         if (mScrollViewPort != null) {
    392             mScrollViewPort.scrollTo(0, 0);
    393             mScrollViewPort.setLayoutParams(
    394                     new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT, 0, 0));
    395         }
    396 
    397     }
    398 
    399     public void setVisibility(boolean visible) {
    400         // TODO Auto-generated method stub
    401     }
    402 
    403     @Override
    404     protected void onSizeChanged(int w, int h, int oldw, int oldh) {
    405         super.onSizeChanged(w, h, oldw, oldh);
    406 
    407         if (mSizeChangedListener != null) {
    408             if (LOCAL_LOGV) {
    409                 Log.v(TAG, "new size=" + w + "x" + h);
    410             }
    411             mSizeChangedListener.onSizeChanged(w, h - AUDIO_INFO_HEIGHT);
    412         }
    413     }
    414 
    415     public void setOnSizeChangedListener(OnSizeChangedListener l) {
    416         mSizeChangedListener = l;
    417     }
    418 
    419     private class Position {
    420         public Position(int left, int top) {
    421             mTop = top;
    422             mLeft = left;
    423         }
    424         public int mTop;
    425         public int mLeft;
    426     }
    427 
    428     /**
    429      * Makes the SlideView working on  MMSConformance Mode. The view will be
    430      * re-layout to the linear view.
    431      * <p>
    432      * This is Chinese requirement about mms conformance.
    433      * The most popular Mms service in China is newspaper which is MMS conformance,
    434      * normally it mixes the image and text and has a number of slides. The
    435      * AbsoluteLayout doesn't have good user experience for this kind of message,
    436      * for example,
    437      *
    438      * 1. AbsoluteLayout exactly follows the smil's layout which is not optimized,
    439      * and actually, no other MMS applications follow the smil's layout, they adjust
    440      * the layout according their screen size. MMS conformance doc also allows the
    441      * implementation to adjust the layout.
    442      *
    443      * 2. The TextView is fixed in the small area of screen, and other part of screen
    444      * is empty once there is no image in the current slide.
    445      *
    446      * 3. The TextView is scrollable in a small area of screen and the font size is
    447      * small which make the user experience bad.
    448      *
    449      * The better UI for the MMS conformance message could be putting the image/video
    450      * and text in a linear layout view and making them scrollable together.
    451      *
    452      * Another reason for only applying the LinearLayout to the MMS conformance message
    453      * is that the AbsoluteLayout has ability to play image and video in a same screen.
    454      * which shouldn't be broken.
    455      */
    456     public void enableMMSConformanceMode(int textLeft, int textTop,
    457             int imageLeft, int imageTop) {
    458         mConformanceMode = true;
    459         if (mScrollViewPort == null) {
    460             mScrollViewPort = new ScrollView(mContext) {
    461                 private int mBottomY;
    462                 @Override
    463                 protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
    464                     super.onLayout(changed, left, top, right, bottom);
    465                     if (getChildCount() > 0) {
    466                         int childHeight = getChildAt(0).getHeight();
    467                         int height = getHeight();
    468                         mBottomY = height < childHeight ? childHeight - height : 0;
    469                     }
    470                 }
    471                 @Override
    472                 protected void onScrollChanged(int l, int t, int oldl, int oldt) {
    473                     // Shows MediaController when the view is scrolled to the top/bottom of itself.
    474                     if (t == 0 || t >= mBottomY){
    475                         if (mMediaController != null) {
    476                             mMediaController.show();
    477                         }
    478                     }
    479                 }
    480             };
    481             mScrollViewPort.setScrollBarStyle(SCROLLBARS_OUTSIDE_INSET);
    482             mViewPort = new LinearLayout(mContext);
    483             mViewPort.setOrientation(LinearLayout.VERTICAL);
    484             mViewPort.setGravity(Gravity.CENTER);
    485             mViewPort.setOnClickListener(new OnClickListener() {
    486                 public void onClick(View v) {
    487                     if (mMediaController != null) {
    488                         mMediaController.show();
    489                     }
    490                 }
    491             });
    492             mScrollViewPort.addView(mViewPort, new FrameLayout.LayoutParams(LayoutParams.MATCH_PARENT,
    493                     LayoutParams.WRAP_CONTENT));
    494             addView(mScrollViewPort);
    495         }
    496         // Layout views to fit the LinearLayout from left to right, then top to
    497         // bottom.
    498         TreeMap<Position, View> viewsByPosition = new TreeMap<Position, View>(new Comparator<Position>() {
    499             public int compare(Position p1, Position p2) {
    500                 int l1 = p1.mLeft;
    501                 int t1 = p1.mTop;
    502                 int l2 = p2.mLeft;
    503                 int t2 = p2.mTop;
    504                 int res = t1 - t2;
    505                 if (res == 0) {
    506                     res = l1 - l2;
    507                 }
    508                 if (res == 0) {
    509                     // A view will be lost if return 0.
    510                     return -1;
    511                 }
    512                 return res;
    513             }
    514         });
    515         if (textLeft >=0 && textTop >=0) {
    516             mTextView = new TextView(mContext);
    517             mTextView.setTransformationMethod(HideReturnsTransformationMethod.getInstance());
    518             mTextView.setTextSize(18);
    519             mTextView.setPadding(5, 5, 5, 5);
    520             viewsByPosition.put(new Position(textLeft, textTop), mTextView);
    521         }
    522 
    523         if (imageLeft >=0 && imageTop >=0) {
    524             mImageView = new ImageView(mContext);
    525             mImageView.setPadding(0, 5, 0, 5);
    526             viewsByPosition.put(new Position(imageLeft, imageTop), mImageView);
    527             // According MMS Conformance Document, the image and video should use the same
    528             // region. So, put the VideoView below the ImageView.
    529             mVideoView = new VideoView(mContext);
    530             viewsByPosition.put(new Position(imageLeft + 1, imageTop), mVideoView);
    531         }
    532         for (View view : viewsByPosition.values()) {
    533             if (view instanceof VideoView) {
    534                 mViewPort.addView(view, new LinearLayout.LayoutParams(LayoutParams.MATCH_PARENT,
    535                         LayoutManager.getInstance().getLayoutParameters().getHeight()));
    536             } else {
    537                 mViewPort.addView(view, new LinearLayout.LayoutParams(LayoutParams.MATCH_PARENT,
    538                         LayoutParams.WRAP_CONTENT));
    539             }
    540             view.setVisibility(View.GONE);
    541         }
    542     }
    543 }
    544