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.Log; 31 import android.view.Gravity; 32 import android.view.LayoutInflater; 33 import android.view.View; 34 import android.widget.AbsoluteLayout; 35 import android.widget.FrameLayout; 36 import android.widget.ImageView; 37 import android.widget.LinearLayout; 38 import android.widget.MediaController; 39 import android.widget.ScrollView; 40 import android.widget.TextView; 41 import android.widget.VideoView; 42 43 import java.io.IOException; 44 import java.util.Comparator; 45 import java.util.Map; 46 import java.util.TreeMap; 47 48 /** 49 * A basic view to show the contents of a slide. 50 */ 51 public 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.WRAP_CONTENT, LayoutParams.WRAP_CONTENT, 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 } 249 250 public void setTextRegion(int left, int top, int width, int height) { 251 // Ignore any requirement of layout change once we are in MMS conformance mode. 252 if (mScrollText != null && !mConformanceMode) { 253 mScrollText.setLayoutParams(new LayoutParams(width, height, left, top)); 254 } 255 } 256 257 public void setVideoRegion(int left, int top, int width, int height) { 258 if (mVideoView != null && !mConformanceMode) { 259 mVideoView.setLayoutParams(new LayoutParams(width, height, left, top)); 260 } 261 } 262 263 public void setImageVisibility(boolean visible) { 264 if (mImageView != null) { 265 if (mConformanceMode) { 266 mImageView.setVisibility(visible ? View.VISIBLE : View.GONE); 267 } else { 268 mImageView.setVisibility(visible ? View.VISIBLE : View.INVISIBLE); 269 } 270 } 271 } 272 273 public void setTextVisibility(boolean visible) { 274 if (mConformanceMode) { 275 if (mTextView != null) { 276 mTextView.setVisibility(visible ? View.VISIBLE : View.GONE); 277 } 278 } else if (mScrollText != null) { 279 mScrollText.setVisibility(visible ? View.VISIBLE : View.INVISIBLE); 280 } 281 } 282 283 public void setVideoVisibility(boolean visible) { 284 if (mVideoView != null) { 285 if (mConformanceMode) { 286 mVideoView.setVisibility(visible ? View.VISIBLE : View.GONE); 287 } else { 288 mVideoView.setVisibility(visible ? View.VISIBLE : View.INVISIBLE); 289 } 290 } 291 } 292 293 public void startAudio() { 294 if ((mAudioPlayer != null) && mIsPrepared) { 295 mAudioPlayer.start(); 296 mStartWhenPrepared = false; 297 displayAudioInfo(); 298 } else { 299 mStartWhenPrepared = true; 300 } 301 } 302 303 public void stopAudio() { 304 if ((mAudioPlayer != null) && mIsPrepared) { 305 mAudioPlayer.stop(); 306 mAudioPlayer.release(); 307 mAudioPlayer = null; 308 hideAudioInfo(); 309 } else { 310 mStopWhenPrepared = true; 311 } 312 } 313 314 public void pauseAudio() { 315 if ((mAudioPlayer != null) && mIsPrepared) { 316 if (mAudioPlayer.isPlaying()) { 317 mAudioPlayer.pause(); 318 } 319 } 320 mStartWhenPrepared = false; 321 } 322 323 public void seekAudio(int seekTo) { 324 if ((mAudioPlayer != null) && mIsPrepared) { 325 mAudioPlayer.seekTo(seekTo); 326 } else { 327 mSeekWhenPrepared = seekTo; 328 } 329 } 330 331 public void startVideo() { 332 if (mVideoView != null) { 333 if (LOCAL_LOGV) { 334 Log.v(TAG, "Starting video playback."); 335 } 336 mVideoView.start(); 337 } 338 } 339 340 public void stopVideo() { 341 if ((mVideoView != null)) { 342 if (LOCAL_LOGV) { 343 Log.v(TAG, "Stopping video playback."); 344 } 345 mVideoView.stopPlayback(); 346 } 347 } 348 349 public void pauseVideo() { 350 if (mVideoView != null) { 351 if (LOCAL_LOGV) { 352 Log.v(TAG, "Pausing video playback."); 353 } 354 mVideoView.pause(); 355 } 356 } 357 358 public void seekVideo(int seekTo) { 359 if (mVideoView != null) { 360 if (seekTo > 0) { 361 if (LOCAL_LOGV) { 362 Log.v(TAG, "Seeking video playback to " + seekTo); 363 } 364 mVideoView.seekTo(seekTo); 365 } 366 } 367 } 368 369 public void reset() { 370 if (null != mScrollText) { 371 mScrollText.setVisibility(View.GONE); 372 } 373 374 if (null != mImageView) { 375 mImageView.setVisibility(View.GONE); 376 } 377 378 if (null != mAudioPlayer) { 379 stopAudio(); 380 } 381 382 if (null != mVideoView) { 383 stopVideo(); 384 mVideoView.setVisibility(View.GONE); 385 } 386 387 if (null != mTextView) { 388 mTextView.setVisibility(View.GONE); 389 } 390 391 if (mScrollViewPort != null) { 392 mScrollViewPort.scrollTo(0, 0); 393 mScrollViewPort.setLayoutParams( 394 new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT, 0, 0)); 395 } 396 397 } 398 399 public void setVisibility(boolean visible) { 400 // TODO Auto-generated method stub 401 } 402 403 @Override 404 protected void onSizeChanged(int w, int h, int oldw, int oldh) { 405 super.onSizeChanged(w, h, oldw, oldh); 406 407 if (mSizeChangedListener != null) { 408 if (LOCAL_LOGV) { 409 Log.v(TAG, "new size=" + w + "x" + h); 410 } 411 mSizeChangedListener.onSizeChanged(w, h - AUDIO_INFO_HEIGHT); 412 } 413 } 414 415 public void setOnSizeChangedListener(OnSizeChangedListener l) { 416 mSizeChangedListener = l; 417 } 418 419 private class Position { 420 public Position(int left, int top) { 421 mTop = top; 422 mLeft = left; 423 } 424 public int mTop; 425 public int mLeft; 426 } 427 428 /** 429 * Makes the SlideView working on MMSConformance Mode. The view will be 430 * re-layout to the linear view. 431 * <p> 432 * This is Chinese requirement about mms conformance. 433 * The most popular Mms service in China is newspaper which is MMS conformance, 434 * normally it mixes the image and text and has a number of slides. The 435 * AbsoluteLayout doesn't have good user experience for this kind of message, 436 * for example, 437 * 438 * 1. AbsoluteLayout exactly follows the smil's layout which is not optimized, 439 * and actually, no other MMS applications follow the smil's layout, they adjust 440 * the layout according their screen size. MMS conformance doc also allows the 441 * implementation to adjust the layout. 442 * 443 * 2. The TextView is fixed in the small area of screen, and other part of screen 444 * is empty once there is no image in the current slide. 445 * 446 * 3. The TextView is scrollable in a small area of screen and the font size is 447 * small which make the user experience bad. 448 * 449 * The better UI for the MMS conformance message could be putting the image/video 450 * and text in a linear layout view and making them scrollable together. 451 * 452 * Another reason for only applying the LinearLayout to the MMS conformance message 453 * is that the AbsoluteLayout has ability to play image and video in a same screen. 454 * which shouldn't be broken. 455 */ 456 public void enableMMSConformanceMode(int textLeft, int textTop, 457 int imageLeft, int imageTop) { 458 mConformanceMode = true; 459 if (mScrollViewPort == null) { 460 mScrollViewPort = new ScrollView(mContext) { 461 private int mBottomY; 462 @Override 463 protected void onLayout(boolean changed, int left, int top, int right, int bottom) { 464 super.onLayout(changed, left, top, right, bottom); 465 if (getChildCount() > 0) { 466 int childHeight = getChildAt(0).getHeight(); 467 int height = getHeight(); 468 mBottomY = height < childHeight ? childHeight - height : 0; 469 } 470 } 471 @Override 472 protected void onScrollChanged(int l, int t, int oldl, int oldt) { 473 // Shows MediaController when the view is scrolled to the top/bottom of itself. 474 if (t == 0 || t >= mBottomY){ 475 if (mMediaController != null) { 476 mMediaController.show(); 477 } 478 } 479 } 480 }; 481 mScrollViewPort.setScrollBarStyle(SCROLLBARS_OUTSIDE_INSET); 482 mViewPort = new LinearLayout(mContext); 483 mViewPort.setOrientation(LinearLayout.VERTICAL); 484 mViewPort.setGravity(Gravity.CENTER); 485 mViewPort.setOnClickListener(new OnClickListener() { 486 public void onClick(View v) { 487 if (mMediaController != null) { 488 mMediaController.show(); 489 } 490 } 491 }); 492 mScrollViewPort.addView(mViewPort, new FrameLayout.LayoutParams(LayoutParams.MATCH_PARENT, 493 LayoutParams.WRAP_CONTENT)); 494 addView(mScrollViewPort); 495 } 496 // Layout views to fit the LinearLayout from left to right, then top to 497 // bottom. 498 TreeMap<Position, View> viewsByPosition = new TreeMap<Position, View>(new Comparator<Position>() { 499 public int compare(Position p1, Position p2) { 500 int l1 = p1.mLeft; 501 int t1 = p1.mTop; 502 int l2 = p2.mLeft; 503 int t2 = p2.mTop; 504 int res = t1 - t2; 505 if (res == 0) { 506 res = l1 - l2; 507 } 508 if (res == 0) { 509 // A view will be lost if return 0. 510 return -1; 511 } 512 return res; 513 } 514 }); 515 if (textLeft >=0 && textTop >=0) { 516 mTextView = new TextView(mContext); 517 mTextView.setTransformationMethod(HideReturnsTransformationMethod.getInstance()); 518 mTextView.setTextSize(18); 519 mTextView.setPadding(5, 5, 5, 5); 520 viewsByPosition.put(new Position(textLeft, textTop), mTextView); 521 } 522 523 if (imageLeft >=0 && imageTop >=0) { 524 mImageView = new ImageView(mContext); 525 mImageView.setPadding(0, 5, 0, 5); 526 viewsByPosition.put(new Position(imageLeft, imageTop), mImageView); 527 // According MMS Conformance Document, the image and video should use the same 528 // region. So, put the VideoView below the ImageView. 529 mVideoView = new VideoView(mContext); 530 viewsByPosition.put(new Position(imageLeft + 1, imageTop), mVideoView); 531 } 532 for (View view : viewsByPosition.values()) { 533 if (view instanceof VideoView) { 534 mViewPort.addView(view, new LinearLayout.LayoutParams(LayoutParams.MATCH_PARENT, 535 LayoutManager.getInstance().getLayoutParameters().getHeight())); 536 } else { 537 mViewPort.addView(view, new LinearLayout.LayoutParams(LayoutParams.MATCH_PARENT, 538 LayoutParams.WRAP_CONTENT)); 539 } 540 view.setVisibility(View.GONE); 541 } 542 } 543 } 544