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.R; 46 import com.android.mms.layout.LayoutManager; 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.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