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