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