/* * Copyright (C) 2008 Esmertec AG. * Copyright (C) 2008 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.mms.ui; import java.io.IOException; import java.io.InputStream; import java.util.ArrayList; import java.util.Comparator; import java.util.Map; import java.util.TreeMap; import android.app.ProgressDialog; import android.content.Context; import android.graphics.Bitmap; import android.graphics.BitmapFactory; import android.graphics.Color; import android.media.MediaPlayer; import android.net.Uri; import android.os.Handler; import android.os.Message; import android.text.method.HideReturnsTransformationMethod; import android.util.AttributeSet; import android.util.Config; import android.util.Log; import android.view.Gravity; import android.view.LayoutInflater; import android.view.View; import android.widget.AbsoluteLayout; import android.widget.FrameLayout; import android.widget.GifDecodeInterface; import android.widget.ImageView; import android.widget.LinearLayout; import android.widget.MediaController; import android.widget.ScrollView; import android.widget.TextView; import android.widget.VideoView; import com.android.mms.R; import com.android.mms.dom.smil.SmilPlayer; import com.android.mms.layout.LayoutManager; /** * A basic view to show the contents of a slide. */ public class SlideView extends AbsoluteLayout implements AdaptableSlideViewInterface ,GifDecodeInterface{ private static final String TAG = "SlideView"; private static final boolean DEBUG = false; private static final boolean LOCAL_LOGV = DEBUG ? Config.LOGD : Config.LOGV; // FIXME: Need getHeight from mAudioInfoView instead of constant AUDIO_INFO_HEIGHT. private static final int AUDIO_INFO_HEIGHT = 82; private View mAudioInfoView; private ImageView mImageView; private VideoView mVideoView; private ScrollView mScrollText; private TextView mTextView; private OnSizeChangedListener mSizeChangedListener; private MediaPlayer mAudioPlayer; private boolean mIsPrepared; private boolean mStartWhenPrepared; private int mSeekWhenPrepared; private boolean mStopWhenPrepared; private ScrollView mScrollViewPort; private LinearLayout mViewPort; // Indicates whether the view is in MMS conformance mode. private boolean mConformanceMode; private MediaController mMediaController; // ======fixed CR<NEWMS00110179> by luning at 11-08-12 begin====== private Context mContext; // ======fixed CR<NEWMS00110179> by luning at 11-08-12 end====== MediaPlayer.OnPreparedListener mPreparedListener = new MediaPlayer.OnPreparedListener() { public void onPrepared(MediaPlayer mp) { synchronized(SlideView.this) { mIsPrepared = true; if (mSeekWhenPrepared > 0) { mAudioPlayer.seekTo(mSeekWhenPrepared); mSeekWhenPrepared = 0; } if (mStartWhenPrepared) { mAudioPlayer.start(); mStartWhenPrepared = false; displayAudioInfo(); } if (mStopWhenPrepared) { mAudioPlayer.stop(); mAudioPlayer.release(); mAudioPlayer = null; mStopWhenPrepared = false; hideAudioInfo(); } } } }; public SlideView(Context context) { super(context); // ======fixed CR<NEWMS00110179> by luning at 11-08-12 begin====== mContext = context; // ======fixed CR<NEWMS00110179> by luning at 11-08-12 end====== } public SlideView(Context context, AttributeSet attrs) { super(context, attrs); // ======fixed CR<NEWMS00110179> by luning at 11-08-12 begin====== mContext = context; // ======fixed CR<NEWMS00110179> by luning at 11-08-12 end====== } // ======fixed CR<NEWMS00110179> by luning at 11-08-12 begin====== private Handler notifyShowGif; private Runnable nextFrame; private GifHandler gifHandler; public void initHandler(){ if(null == gifHandler){ gifHandler = new GifHandler(); } if (null == notifyShowGif) { notifyShowGif = new Handler(){ public void handleMessage(Message msg) { if (msg.what == 101) {//success nextFrame(); }else if(msg.what == -101){//fail recycleGif(); Bitmap bitmap = BitmapFactory.decodeResource(getResources(), R.drawable.ic_missing_thumbnail_picture); mImageView.setImageBitmap(bitmap); } } }; } if (null == nextFrame) { nextFrame = new Runnable() { public void run() { nextFrame(); } }; } } //set next frame from gif data public void nextFrame() { if (null != notifyShowGif && null != gifHandler) { Log.d(TAG, "nextFrame"); mImageView.setImageBitmap(gifHandler.nextFrameBitmap()); notifyShowGif.postDelayed(nextFrame, gifHandler.getDelay()); } } public void recycleGif() { if(null != notifyShowGif) { notifyShowGif.removeCallbacks(nextFrame); } if(null != gifHandler){ gifHandler.recycleDecode(); } } // ======fixed CR<NEWMS00110179> by luning at 11-08-12 end====== public void setImage(String name, Bitmap bitmap) { // ======fixed CR<NEWMS00127016> by luning at 11-09-28 begin====== recycleGif(); // ======fixed CR<NEWMS00127016> by luning at 11-09-28 end====== if (mImageView == null) { mImageView = new ImageView(mContext); mImageView.setPadding(0, 5, 0, 5); addView(mImageView, new LayoutParams( LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT, 0, 0)); if (DEBUG) { mImageView.setBackgroundColor(0xFFFF0000); } } try { if (null == bitmap) { bitmap = BitmapFactory.decodeResource(getResources(), R.drawable.ic_missing_thumbnail_picture); } mImageView.setVisibility(View.VISIBLE); mImageView.setImageBitmap(bitmap); } catch (java.lang.OutOfMemoryError e) { Log.e(TAG, "setImage: out of memory: ", e); } } // ======fixed CR<NEWMS00110179> by luning at 11-08-12 begin====== //Slide show gif data need more time to prepare,we should show progress to user. public void setImage(final Uri uri, final Bitmap bitmap, boolean isGifImage) { setImage(null, bitmap);//show first frame at once if (null != uri) { initHandler();//init handler /*modify by luning at 2011.12.20 begin*/ // new Thread(new Runnable() { // public void run() {//use a new thread to show gif data // try { // InputStream data = mContext.getContentResolver().openInputStream(uri); // gifHandler.initGifData(data);//init gif data // notifyShowGif.sendEmptyMessage(101);//notify show gif image // } catch (OutOfMemoryError error) { // notifyShowGif.sendEmptyMessage(-101);//recyle gif data // } catch (Exception exception) { // notifyShowGif.sendEmptyMessage(-101); // } // } // }).start(); new Thread(new Runnable() { public void run() {//use a new thread to show gif data try { InputStream data = mContext.getContentResolver().openInputStream(uri); if (null != gifHandler.initGifData(data)) { notifyShowGif.sendEmptyMessage(101); } } catch (OutOfMemoryError error) { if (null != bitmap) { //we do nothing here,because of we have show first frame at once. } else { notifyShowGif.sendEmptyMessage(-101); } } catch (Exception exception) { if (null != bitmap) { //we do nothing here,because of we have show first frame at once. } else { notifyShowGif.sendEmptyMessage(-101); } } } }).start(); /*modify by luning at 2011.12.20 end*/ } } // private ProgressDialog mScanningDialog; // // private SmilPlayer mPlayer; // // public void setSmilPlayer(SmilPlayer player) { // mPlayer = player; // } // // public void slideWait() { // mScanningDialog = ProgressDialog.show(mContext, null, mContext // .getResources().getString(R.string.wait_message), true, true); // if (mPlayer != null) { // mPlayer.pause(); // } // } // // public void slideStart() { // mScanningDialog.cancel(); // if (mPlayer != null) { // mPlayer.start(); // } // } // ======fixed CR<NEWMS00110179> by luning at 11-08-12 end====== public void setImageRegion(int left, int top, int width, int height) { // Ignore any requirement of layout change once we are in MMS conformance mode. if (mImageView != null && !mConformanceMode) { mImageView.setLayoutParams(new LayoutParams(width, height, left, top)); } } public void setImageRegionFit(String fit) { // TODO Auto-generated method stub } public void setVideo(String name, Uri video) { if (mVideoView == null) { mVideoView = new VideoView(mContext); addView(mVideoView, new LayoutParams( LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT, 0, 0)); if (DEBUG) { mVideoView.setBackgroundColor(0xFFFF0000); } } if (LOCAL_LOGV) { Log.v(TAG, "Changing video source to " + video); } mVideoView.setVisibility(View.VISIBLE); mVideoView.setVideoURI(video); } public void setMediaController(MediaController mediaController) { mMediaController = mediaController; } private void initAudioInfoView(String name) { if (null == mAudioInfoView) { LayoutInflater factory = LayoutInflater.from(getContext()); mAudioInfoView = factory.inflate(R.layout.playing_audio_info, null); int height = mAudioInfoView.getHeight(); if (mConformanceMode) { mViewPort.addView(mAudioInfoView, new LinearLayout.LayoutParams(LayoutParams.MATCH_PARENT, AUDIO_INFO_HEIGHT)); } else { addView(mAudioInfoView, new LayoutParams( LayoutParams.MATCH_PARENT, AUDIO_INFO_HEIGHT, 0, getHeight() - AUDIO_INFO_HEIGHT)); if (DEBUG) { mAudioInfoView.setBackgroundColor(0xFFFF0000); } } } // ======fixed CR<NEWMS00117791> by luning at 11-08-25 begin====== if(null != mAudioInfoView){ TextView audioName = (TextView) mAudioInfoView.findViewById(R.id.name); audioName.setText(name); } // ======fixed CR<NEWMS00117791> by luning at 11-08-25 end====== mAudioInfoView.setVisibility(View.GONE); } private void displayAudioInfo() { if (null != mAudioInfoView) { mAudioInfoView.setVisibility(View.VISIBLE); } } private void hideAudioInfo() { if (null != mAudioInfoView) { mAudioInfoView.setVisibility(View.GONE); } } synchronized public void setAudio(Uri audio, String name, Map<String, ?> extras) { if (audio == null) { throw new IllegalArgumentException("Audio URI may not be null."); } if (LOCAL_LOGV) { Log.v(TAG, "Changing audio source to " + audio); } if (mAudioPlayer != null) { mAudioPlayer.reset(); mAudioPlayer.release(); mAudioPlayer = null; } mIsPrepared = false; try { mAudioPlayer = new MediaPlayer(); mAudioPlayer.setOnPreparedListener(mPreparedListener); mAudioPlayer.setDataSource(mContext, audio); mAudioPlayer.prepareAsync(); } catch (IOException e) { Log.e(TAG, "Unexpected IOException.", e); mAudioPlayer.release(); mAudioPlayer = null; } initAudioInfoView(name); } public void setText(String name, String text) { if (!mConformanceMode) { if (null == mScrollText) { mScrollText = new ScrollView(mContext); mScrollText.setScrollBarStyle(SCROLLBARS_OUTSIDE_INSET); addView(mScrollText, new LayoutParams( LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT, 0, 0)); if (DEBUG) { mScrollText.setBackgroundColor(0xFF00FF00); } } if (null == mTextView) { mTextView = new TextView(mContext); mTextView.setTransformationMethod(HideReturnsTransformationMethod.getInstance()); mScrollText.addView(mTextView); } mScrollText.requestFocus(); } mTextView.setVisibility(View.VISIBLE); mTextView.setText(text); } public void setTextRegion(int left, int top, int width, int height) { // Ignore any requirement of layout change once we are in MMS conformance mode. if (mScrollText != null && !mConformanceMode) { mScrollText.setLayoutParams(new LayoutParams(width, height, left, top)); } } public void setVideoRegion(int left, int top, int width, int height) { if (mVideoView != null && !mConformanceMode) { mVideoView.setLayoutParams(new LayoutParams(width, height, left, top)); } } public void setImageVisibility(boolean visible) { if (mImageView != null) { if (mConformanceMode) { mImageView.setVisibility(visible ? View.VISIBLE : View.GONE); } else { mImageView.setVisibility(visible ? View.VISIBLE : View.INVISIBLE); } } } public void setTextVisibility(boolean visible) { if (mScrollText != null) { if (mConformanceMode) { mTextView.setVisibility(visible ? View.VISIBLE : View.GONE); } else { mScrollText.setVisibility(visible ? View.VISIBLE : View.INVISIBLE); } } } public void setVideoVisibility(boolean visible) { if (mVideoView != null) { if (mConformanceMode) { mVideoView.setVisibility(visible ? View.VISIBLE : View.GONE); } else { mVideoView.setVisibility(visible ? View.VISIBLE : View.INVISIBLE); } } } synchronized public void startAudio() { if ((mAudioPlayer != null) && mIsPrepared) { mAudioPlayer.start(); mStartWhenPrepared = false; displayAudioInfo(); } else { mStartWhenPrepared = true; mStopWhenPrepared = false; } } synchronized public void stopAudio() { if ((mAudioPlayer != null) && mIsPrepared) { mAudioPlayer.stop(); mAudioPlayer.release(); mAudioPlayer = null; hideAudioInfo(); } else { mStopWhenPrepared = true; mStartWhenPrepared = false; } } synchronized public void pauseAudio() { if ((mAudioPlayer != null) && mIsPrepared) { if (mAudioPlayer.isPlaying()) { mAudioPlayer.pause(); } } mStartWhenPrepared = false; } synchronized public void seekAudio(int seekTo) { if ((mAudioPlayer != null) && mIsPrepared) { mAudioPlayer.seekTo(seekTo); } else { mSeekWhenPrepared = seekTo; } } public void startVideo() { if (mVideoView != null) { if (LOCAL_LOGV) { Log.v(TAG, "Starting video playback."); } mVideoView.start(); } } public void stopVideo() { if ((mVideoView != null)) { if (LOCAL_LOGV) { Log.v(TAG, "Stopping video playback."); } mVideoView.stopPlayback(); } } public void pauseVideo() { if (mVideoView != null) { if (LOCAL_LOGV) { Log.v(TAG, "Pausing video playback."); } mVideoView.pause(); } } public void seekVideo(int seekTo) { if (mVideoView != null) { if (seekTo > 0) { if (LOCAL_LOGV) { Log.v(TAG, "Seeking video playback to " + seekTo); } mVideoView.seekTo(seekTo); } } } synchronized public void reset() { if (null != mScrollText) { mScrollText.setVisibility(View.GONE); } if (null != mImageView) { mImageView.setVisibility(View.GONE); } if (null != mAudioPlayer) { stopAudio(); } if (null != mVideoView) { stopVideo(); mVideoView.setVisibility(View.GONE); } if (null != mTextView) { mTextView.setVisibility(View.GONE); } if (mScrollViewPort != null) { mScrollViewPort.scrollTo(0, 0); mScrollViewPort.setLayoutParams( new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT, 0, 0)); } } public void setVisibility(boolean visible) { // TODO Auto-generated method stub } @Override protected void onSizeChanged(int w, int h, int oldw, int oldh) { super.onSizeChanged(w, h, oldw, oldh); if (mSizeChangedListener != null) { if (LOCAL_LOGV) { Log.v(TAG, "new size=" + w + "x" + h); } mSizeChangedListener.onSizeChanged(w, h - AUDIO_INFO_HEIGHT); } } public void setOnSizeChangedListener(OnSizeChangedListener l) { mSizeChangedListener = l; } private class Position { public Position(int left, int top) { mTop = top; mLeft = left; } public int mTop; public int mLeft; } /** * Makes the SlideView working on MMSConformance Mode. The view will be * re-layout to the linear view. * <p> * This is Chinese requirement about mms conformance. * The most popular Mms service in China is newspaper which is MMS conformance, * normally it mixes the image and text and has a number of slides. The * AbsoluteLayout doesn't have good user experience for this kind of message, * for example, * * 1. AbsoluteLayout exactly follows the smil's layout which is not optimized, * and actually, no other MMS applications follow the smil's layout, they adjust * the layout according their screen size. MMS conformance doc also allows the * implementation to adjust the layout. * * 2. The TextView is fixed in the small area of screen, and other part of screen * is empty once there is no image in the current slide. * * 3. The TextView is scrollable in a small area of screen and the font size is * small which make the user experience bad. * * The better UI for the MMS conformance message could be putting the image/video * and text in a linear layout view and making them scrollable together. * * Another reason for only applying the LinearLayout to the MMS conformance message * is that the AbsoluteLayout has ability to play image and video in a same screen. * which shouldn't be broken. */ public void enableMMSConformanceMode(int textLeft, int textTop, int imageLeft, int imageTop) { mConformanceMode = true; if (mScrollViewPort == null) { mScrollViewPort = new ScrollView(mContext) { private int mBottomY; @Override protected void onLayout(boolean changed, int left, int top, int right, int bottom) { super.onLayout(changed, left, top, right, bottom); if (getChildCount() > 0) { int childHeight = getChildAt(0).getHeight(); int height = getHeight(); mBottomY = height < childHeight ? childHeight - height : 0; } } @Override protected void onScrollChanged(int l, int t, int oldl, int oldt) { // Shows MediaController when the view is scrolled to the top/bottom of itself. if (t == 0 || t >= mBottomY){ if (mMediaController != null) { mMediaController.show(); } } } }; mScrollViewPort.setScrollBarStyle(SCROLLBARS_OUTSIDE_INSET); mViewPort = new LinearLayout(mContext); mViewPort.setOrientation(LinearLayout.VERTICAL); mViewPort.setGravity(Gravity.CENTER); mViewPort.setOnClickListener(new OnClickListener() { public void onClick(View v) { if (mMediaController != null) { mMediaController.show(); } } }); mScrollViewPort.addView(mViewPort, new FrameLayout.LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT)); addView(mScrollViewPort); } // Layout views to fit the LinearLayout from left to right, then top to // bottom. TreeMap<Position, View> viewsByPosition = new TreeMap<Position, View>(new Comparator<Position>() { public int compare(Position p1, Position p2) { int l1 = p1.mLeft; int t1 = p1.mTop; int l2 = p2.mLeft; int t2 = p2.mTop; int res = t1 - t2; if (res == 0) { res = l1 - l2; } if (res == 0) { // A view will be lost if return 0. return -1; } return res; } }); if (textLeft >=0 && textTop >=0) { mTextView = new TextView(mContext); mTextView.setTransformationMethod(HideReturnsTransformationMethod.getInstance()); mTextView.setTextSize(18); mTextView.setPadding(5, 5, 5, 5); viewsByPosition.put(new Position(textLeft, textTop), mTextView); } if (imageLeft >=0 && imageTop >=0) { mImageView = new ImageView(mContext); mImageView.setPadding(0, 5, 0, 5); viewsByPosition.put(new Position(imageLeft, imageTop), mImageView); // According MMS Conformance Document, the image and video should use the same // region. So, put the VideoView below the ImageView. mVideoView = new VideoView(mContext); mVideoView.setBackgroundColor(Color.TRANSPARENT); viewsByPosition.put(new Position(imageLeft + 1, imageTop), mVideoView); } for (View view : viewsByPosition.values()) { if (view instanceof VideoView) { mViewPort.addView(view, new LinearLayout.LayoutParams(LayoutParams.MATCH_PARENT, LayoutManager.getInstance().getLayoutParameters().getHeight())); } else { mViewPort.addView(view, new LinearLayout.LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT)); } view.setVisibility(View.GONE); } } public void setSize(String size) { // TODO Auto-generated method stub } public void destroy(){ } public void setVcard(Uri uri, String name) { setImage(name, BitmapFactory.decodeResource(getResources(), R.drawable.vcf)); setText(name, getResources().getString(R.string.slide_show_vcard)); } @Override public void setVcard(ArrayList vCards) { // TODO Auto-generated method stub } @Override public void setFile(ArrayList files) { // TODO Auto-generated method stub } @Override public void setFile(Uri uri, String name) { setImage(name, BitmapFactory.decodeResource(getResources(), MessageUtils.getFileIconId(uri.toString()))); setText(name, name); } }