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