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
18package com.android.mms.ui;
19
20import java.io.IOException;
21import java.util.Comparator;
22import java.util.Map;
23import java.util.TreeMap;
24
25import android.content.Context;
26import android.graphics.Bitmap;
27import android.graphics.BitmapFactory;
28import android.media.MediaPlayer;
29import android.net.Uri;
30import android.text.method.HideReturnsTransformationMethod;
31import android.util.AttributeSet;
32import android.util.Log;
33import android.view.Gravity;
34import android.view.LayoutInflater;
35import android.view.View;
36import android.widget.AbsoluteLayout;
37import android.widget.FrameLayout;
38import android.widget.ImageView;
39import android.widget.LinearLayout;
40import android.widget.MediaController;
41import android.widget.ScrollView;
42import android.widget.TextView;
43import android.widget.VideoView;
44
45import com.android.mms.R;
46import com.android.mms.layout.LayoutManager;
47
48/**
49 * A basic view to show the contents of a slide.
50 */
51public 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