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