1 /* 2 * Copyright (C) 2006 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package android.widget; 18 19 import android.content.Context; 20 import android.content.res.Resources; 21 import android.graphics.PixelFormat; 22 import android.media.AudioManager; 23 import android.os.Handler; 24 import android.os.Message; 25 import android.util.AttributeSet; 26 import android.util.Log; 27 import android.view.Gravity; 28 import android.view.KeyEvent; 29 import android.view.LayoutInflater; 30 import android.view.MotionEvent; 31 import android.view.View; 32 import android.view.ViewGroup; 33 import android.view.Window; 34 import android.view.WindowManager; 35 import android.view.accessibility.AccessibilityEvent; 36 import android.view.accessibility.AccessibilityNodeInfo; 37 import android.widget.SeekBar.OnSeekBarChangeListener; 38 39 import com.android.internal.policy.PolicyManager; 40 41 import java.util.Formatter; 42 import java.util.Locale; 43 44 /** 45 * A view containing controls for a MediaPlayer. Typically contains the 46 * buttons like "Play/Pause", "Rewind", "Fast Forward" and a progress 47 * slider. It takes care of synchronizing the controls with the state 48 * of the MediaPlayer. 49 * <p> 50 * The way to use this class is to instantiate it programatically. 51 * The MediaController will create a default set of controls 52 * and put them in a window floating above your application. Specifically, 53 * the controls will float above the view specified with setAnchorView(). 54 * The window will disappear if left idle for three seconds and reappear 55 * when the user touches the anchor view. 56 * <p> 57 * Functions like show() and hide() have no effect when MediaController 58 * is created in an xml layout. 59 * 60 * MediaController will hide and 61 * show the buttons according to these rules: 62 * <ul> 63 * <li> The "previous" and "next" buttons are hidden until setPrevNextListeners() 64 * has been called 65 * <li> The "previous" and "next" buttons are visible but disabled if 66 * setPrevNextListeners() was called with null listeners 67 * <li> The "rewind" and "fastforward" buttons are shown unless requested 68 * otherwise by using the MediaController(Context, boolean) constructor 69 * with the boolean set to false 70 * </ul> 71 */ 72 public class MediaController extends FrameLayout { 73 74 private MediaPlayerControl mPlayer; 75 private Context mContext; 76 private View mAnchor; 77 private View mRoot; 78 private WindowManager mWindowManager; 79 private Window mWindow; 80 private View mDecor; 81 private WindowManager.LayoutParams mDecorLayoutParams; 82 private ProgressBar mProgress; 83 private TextView mEndTime, mCurrentTime; 84 private boolean mShowing; 85 private boolean mDragging; 86 private static final int sDefaultTimeout = 3000; 87 private static final int FADE_OUT = 1; 88 private static final int SHOW_PROGRESS = 2; 89 private boolean mUseFastForward; 90 private boolean mFromXml; 91 private boolean mListenersSet; 92 private View.OnClickListener mNextListener, mPrevListener; 93 StringBuilder mFormatBuilder; 94 Formatter mFormatter; 95 private ImageButton mPauseButton; 96 private ImageButton mFfwdButton; 97 private ImageButton mRewButton; 98 private ImageButton mNextButton; 99 private ImageButton mPrevButton; 100 private CharSequence mPlayDescription; 101 private CharSequence mPauseDescription; 102 MediaController(Context context, AttributeSet attrs)103 public MediaController(Context context, AttributeSet attrs) { 104 super(context, attrs); 105 mRoot = this; 106 mContext = context; 107 mUseFastForward = true; 108 mFromXml = true; 109 } 110 111 @Override onFinishInflate()112 public void onFinishInflate() { 113 if (mRoot != null) 114 initControllerView(mRoot); 115 } 116 MediaController(Context context, boolean useFastForward)117 public MediaController(Context context, boolean useFastForward) { 118 super(context); 119 mContext = context; 120 mUseFastForward = useFastForward; 121 initFloatingWindowLayout(); 122 initFloatingWindow(); 123 } 124 MediaController(Context context)125 public MediaController(Context context) { 126 this(context, true); 127 } 128 initFloatingWindow()129 private void initFloatingWindow() { 130 mWindowManager = (WindowManager)mContext.getSystemService(Context.WINDOW_SERVICE); 131 mWindow = PolicyManager.makeNewWindow(mContext); 132 mWindow.setWindowManager(mWindowManager, null, null); 133 mWindow.requestFeature(Window.FEATURE_NO_TITLE); 134 mDecor = mWindow.getDecorView(); 135 mDecor.setOnTouchListener(mTouchListener); 136 mWindow.setContentView(this); 137 mWindow.setBackgroundDrawableResource(android.R.color.transparent); 138 139 // While the media controller is up, the volume control keys should 140 // affect the media stream type 141 mWindow.setVolumeControlStream(AudioManager.STREAM_MUSIC); 142 143 setFocusable(true); 144 setFocusableInTouchMode(true); 145 setDescendantFocusability(ViewGroup.FOCUS_AFTER_DESCENDANTS); 146 requestFocus(); 147 } 148 149 // Allocate and initialize the static parts of mDecorLayoutParams. Must 150 // also call updateFloatingWindowLayout() to fill in the dynamic parts 151 // (y and width) before mDecorLayoutParams can be used. initFloatingWindowLayout()152 private void initFloatingWindowLayout() { 153 mDecorLayoutParams = new WindowManager.LayoutParams(); 154 WindowManager.LayoutParams p = mDecorLayoutParams; 155 p.gravity = Gravity.TOP | Gravity.LEFT; 156 p.height = LayoutParams.WRAP_CONTENT; 157 p.x = 0; 158 p.format = PixelFormat.TRANSLUCENT; 159 p.type = WindowManager.LayoutParams.TYPE_APPLICATION_PANEL; 160 p.flags |= WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM 161 | WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL 162 | WindowManager.LayoutParams.FLAG_SPLIT_TOUCH; 163 p.token = null; 164 p.windowAnimations = 0; // android.R.style.DropDownAnimationDown; 165 } 166 167 // Update the dynamic parts of mDecorLayoutParams 168 // Must be called with mAnchor != NULL. updateFloatingWindowLayout()169 private void updateFloatingWindowLayout() { 170 int [] anchorPos = new int[2]; 171 mAnchor.getLocationOnScreen(anchorPos); 172 173 // we need to know the size of the controller so we can properly position it 174 // within its space 175 mDecor.measure(MeasureSpec.makeMeasureSpec(mAnchor.getWidth(), MeasureSpec.AT_MOST), 176 MeasureSpec.makeMeasureSpec(mAnchor.getHeight(), MeasureSpec.AT_MOST)); 177 178 WindowManager.LayoutParams p = mDecorLayoutParams; 179 p.width = mAnchor.getWidth(); 180 p.x = anchorPos[0] + (mAnchor.getWidth() - p.width) / 2; 181 p.y = anchorPos[1] + mAnchor.getHeight() - mDecor.getMeasuredHeight(); 182 } 183 184 // This is called whenever mAnchor's layout bound changes 185 private OnLayoutChangeListener mLayoutChangeListener = 186 new OnLayoutChangeListener() { 187 public void onLayoutChange(View v, int left, int top, int right, 188 int bottom, int oldLeft, int oldTop, int oldRight, 189 int oldBottom) { 190 updateFloatingWindowLayout(); 191 if (mShowing) { 192 mWindowManager.updateViewLayout(mDecor, mDecorLayoutParams); 193 } 194 } 195 }; 196 197 private OnTouchListener mTouchListener = new OnTouchListener() { 198 public boolean onTouch(View v, MotionEvent event) { 199 if (event.getAction() == MotionEvent.ACTION_DOWN) { 200 if (mShowing) { 201 hide(); 202 } 203 } 204 return false; 205 } 206 }; 207 setMediaPlayer(MediaPlayerControl player)208 public void setMediaPlayer(MediaPlayerControl player) { 209 mPlayer = player; 210 updatePausePlay(); 211 } 212 213 /** 214 * Set the view that acts as the anchor for the control view. 215 * This can for example be a VideoView, or your Activity's main view. 216 * When VideoView calls this method, it will use the VideoView's parent 217 * as the anchor. 218 * @param view The view to which to anchor the controller when it is visible. 219 */ setAnchorView(View view)220 public void setAnchorView(View view) { 221 if (mAnchor != null) { 222 mAnchor.removeOnLayoutChangeListener(mLayoutChangeListener); 223 } 224 mAnchor = view; 225 if (mAnchor != null) { 226 mAnchor.addOnLayoutChangeListener(mLayoutChangeListener); 227 } 228 229 FrameLayout.LayoutParams frameParams = new FrameLayout.LayoutParams( 230 ViewGroup.LayoutParams.MATCH_PARENT, 231 ViewGroup.LayoutParams.MATCH_PARENT 232 ); 233 234 removeAllViews(); 235 View v = makeControllerView(); 236 addView(v, frameParams); 237 } 238 239 /** 240 * Create the view that holds the widgets that control playback. 241 * Derived classes can override this to create their own. 242 * @return The controller view. 243 * @hide This doesn't work as advertised 244 */ makeControllerView()245 protected View makeControllerView() { 246 LayoutInflater inflate = (LayoutInflater) mContext.getSystemService(Context.LAYOUT_INFLATER_SERVICE); 247 mRoot = inflate.inflate(com.android.internal.R.layout.media_controller, null); 248 249 initControllerView(mRoot); 250 251 return mRoot; 252 } 253 initControllerView(View v)254 private void initControllerView(View v) { 255 Resources res = mContext.getResources(); 256 mPlayDescription = res 257 .getText(com.android.internal.R.string.lockscreen_transport_play_description); 258 mPauseDescription = res 259 .getText(com.android.internal.R.string.lockscreen_transport_pause_description); 260 mPauseButton = (ImageButton) v.findViewById(com.android.internal.R.id.pause); 261 if (mPauseButton != null) { 262 mPauseButton.requestFocus(); 263 mPauseButton.setOnClickListener(mPauseListener); 264 } 265 266 mFfwdButton = (ImageButton) v.findViewById(com.android.internal.R.id.ffwd); 267 if (mFfwdButton != null) { 268 mFfwdButton.setOnClickListener(mFfwdListener); 269 if (!mFromXml) { 270 mFfwdButton.setVisibility(mUseFastForward ? View.VISIBLE : View.GONE); 271 } 272 } 273 274 mRewButton = (ImageButton) v.findViewById(com.android.internal.R.id.rew); 275 if (mRewButton != null) { 276 mRewButton.setOnClickListener(mRewListener); 277 if (!mFromXml) { 278 mRewButton.setVisibility(mUseFastForward ? View.VISIBLE : View.GONE); 279 } 280 } 281 282 // By default these are hidden. They will be enabled when setPrevNextListeners() is called 283 mNextButton = (ImageButton) v.findViewById(com.android.internal.R.id.next); 284 if (mNextButton != null && !mFromXml && !mListenersSet) { 285 mNextButton.setVisibility(View.GONE); 286 } 287 mPrevButton = (ImageButton) v.findViewById(com.android.internal.R.id.prev); 288 if (mPrevButton != null && !mFromXml && !mListenersSet) { 289 mPrevButton.setVisibility(View.GONE); 290 } 291 292 mProgress = (ProgressBar) v.findViewById(com.android.internal.R.id.mediacontroller_progress); 293 if (mProgress != null) { 294 if (mProgress instanceof SeekBar) { 295 SeekBar seeker = (SeekBar) mProgress; 296 seeker.setOnSeekBarChangeListener(mSeekListener); 297 } 298 mProgress.setMax(1000); 299 } 300 301 mEndTime = (TextView) v.findViewById(com.android.internal.R.id.time); 302 mCurrentTime = (TextView) v.findViewById(com.android.internal.R.id.time_current); 303 mFormatBuilder = new StringBuilder(); 304 mFormatter = new Formatter(mFormatBuilder, Locale.getDefault()); 305 306 installPrevNextListeners(); 307 } 308 309 /** 310 * Show the controller on screen. It will go away 311 * automatically after 3 seconds of inactivity. 312 */ show()313 public void show() { 314 show(sDefaultTimeout); 315 } 316 317 /** 318 * Disable pause or seek buttons if the stream cannot be paused or seeked. 319 * This requires the control interface to be a MediaPlayerControlExt 320 */ disableUnsupportedButtons()321 private void disableUnsupportedButtons() { 322 try { 323 if (mPauseButton != null && !mPlayer.canPause()) { 324 mPauseButton.setEnabled(false); 325 } 326 if (mRewButton != null && !mPlayer.canSeekBackward()) { 327 mRewButton.setEnabled(false); 328 } 329 if (mFfwdButton != null && !mPlayer.canSeekForward()) { 330 mFfwdButton.setEnabled(false); 331 } 332 } catch (IncompatibleClassChangeError ex) { 333 // We were given an old version of the interface, that doesn't have 334 // the canPause/canSeekXYZ methods. This is OK, it just means we 335 // assume the media can be paused and seeked, and so we don't disable 336 // the buttons. 337 } 338 } 339 340 /** 341 * Show the controller on screen. It will go away 342 * automatically after 'timeout' milliseconds of inactivity. 343 * @param timeout The timeout in milliseconds. Use 0 to show 344 * the controller until hide() is called. 345 */ show(int timeout)346 public void show(int timeout) { 347 if (!mShowing && mAnchor != null) { 348 setProgress(); 349 if (mPauseButton != null) { 350 mPauseButton.requestFocus(); 351 } 352 disableUnsupportedButtons(); 353 updateFloatingWindowLayout(); 354 mWindowManager.addView(mDecor, mDecorLayoutParams); 355 mShowing = true; 356 } 357 updatePausePlay(); 358 359 // cause the progress bar to be updated even if mShowing 360 // was already true. This happens, for example, if we're 361 // paused with the progress bar showing the user hits play. 362 mHandler.sendEmptyMessage(SHOW_PROGRESS); 363 364 Message msg = mHandler.obtainMessage(FADE_OUT); 365 if (timeout != 0) { 366 mHandler.removeMessages(FADE_OUT); 367 mHandler.sendMessageDelayed(msg, timeout); 368 } 369 } 370 isShowing()371 public boolean isShowing() { 372 return mShowing; 373 } 374 375 /** 376 * Remove the controller from the screen. 377 */ hide()378 public void hide() { 379 if (mAnchor == null) 380 return; 381 382 if (mShowing) { 383 try { 384 mHandler.removeMessages(SHOW_PROGRESS); 385 mWindowManager.removeView(mDecor); 386 } catch (IllegalArgumentException ex) { 387 Log.w("MediaController", "already removed"); 388 } 389 mShowing = false; 390 } 391 } 392 393 private Handler mHandler = new Handler() { 394 @Override 395 public void handleMessage(Message msg) { 396 int pos; 397 switch (msg.what) { 398 case FADE_OUT: 399 hide(); 400 break; 401 case SHOW_PROGRESS: 402 pos = setProgress(); 403 if (!mDragging && mShowing && mPlayer.isPlaying()) { 404 msg = obtainMessage(SHOW_PROGRESS); 405 sendMessageDelayed(msg, 1000 - (pos % 1000)); 406 } 407 break; 408 } 409 } 410 }; 411 stringForTime(int timeMs)412 private String stringForTime(int timeMs) { 413 int totalSeconds = timeMs / 1000; 414 415 int seconds = totalSeconds % 60; 416 int minutes = (totalSeconds / 60) % 60; 417 int hours = totalSeconds / 3600; 418 419 mFormatBuilder.setLength(0); 420 if (hours > 0) { 421 return mFormatter.format("%d:%02d:%02d", hours, minutes, seconds).toString(); 422 } else { 423 return mFormatter.format("%02d:%02d", minutes, seconds).toString(); 424 } 425 } 426 setProgress()427 private int setProgress() { 428 if (mPlayer == null || mDragging) { 429 return 0; 430 } 431 int position = mPlayer.getCurrentPosition(); 432 int duration = mPlayer.getDuration(); 433 if (mProgress != null) { 434 if (duration > 0) { 435 // use long to avoid overflow 436 long pos = 1000L * position / duration; 437 mProgress.setProgress( (int) pos); 438 } 439 int percent = mPlayer.getBufferPercentage(); 440 mProgress.setSecondaryProgress(percent * 10); 441 } 442 443 if (mEndTime != null) 444 mEndTime.setText(stringForTime(duration)); 445 if (mCurrentTime != null) 446 mCurrentTime.setText(stringForTime(position)); 447 448 return position; 449 } 450 451 @Override onTouchEvent(MotionEvent event)452 public boolean onTouchEvent(MotionEvent event) { 453 switch (event.getAction()) { 454 case MotionEvent.ACTION_DOWN: 455 show(0); // show until hide is called 456 break; 457 case MotionEvent.ACTION_UP: 458 show(sDefaultTimeout); // start timeout 459 break; 460 case MotionEvent.ACTION_CANCEL: 461 hide(); 462 break; 463 default: 464 break; 465 } 466 return true; 467 } 468 469 @Override onTrackballEvent(MotionEvent ev)470 public boolean onTrackballEvent(MotionEvent ev) { 471 show(sDefaultTimeout); 472 return false; 473 } 474 475 @Override dispatchKeyEvent(KeyEvent event)476 public boolean dispatchKeyEvent(KeyEvent event) { 477 int keyCode = event.getKeyCode(); 478 final boolean uniqueDown = event.getRepeatCount() == 0 479 && event.getAction() == KeyEvent.ACTION_DOWN; 480 if (keyCode == KeyEvent.KEYCODE_HEADSETHOOK 481 || keyCode == KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE 482 || keyCode == KeyEvent.KEYCODE_SPACE) { 483 if (uniqueDown) { 484 doPauseResume(); 485 show(sDefaultTimeout); 486 if (mPauseButton != null) { 487 mPauseButton.requestFocus(); 488 } 489 } 490 return true; 491 } else if (keyCode == KeyEvent.KEYCODE_MEDIA_PLAY) { 492 if (uniqueDown && !mPlayer.isPlaying()) { 493 mPlayer.start(); 494 updatePausePlay(); 495 show(sDefaultTimeout); 496 } 497 return true; 498 } else if (keyCode == KeyEvent.KEYCODE_MEDIA_STOP 499 || keyCode == KeyEvent.KEYCODE_MEDIA_PAUSE) { 500 if (uniqueDown && mPlayer.isPlaying()) { 501 mPlayer.pause(); 502 updatePausePlay(); 503 show(sDefaultTimeout); 504 } 505 return true; 506 } else if (keyCode == KeyEvent.KEYCODE_VOLUME_DOWN 507 || keyCode == KeyEvent.KEYCODE_VOLUME_UP 508 || keyCode == KeyEvent.KEYCODE_VOLUME_MUTE 509 || keyCode == KeyEvent.KEYCODE_CAMERA) { 510 // don't show the controls for volume adjustment 511 return super.dispatchKeyEvent(event); 512 } else if (keyCode == KeyEvent.KEYCODE_BACK || keyCode == KeyEvent.KEYCODE_MENU) { 513 if (uniqueDown) { 514 hide(); 515 } 516 return true; 517 } 518 519 show(sDefaultTimeout); 520 return super.dispatchKeyEvent(event); 521 } 522 523 private View.OnClickListener mPauseListener = new View.OnClickListener() { 524 public void onClick(View v) { 525 doPauseResume(); 526 show(sDefaultTimeout); 527 } 528 }; 529 updatePausePlay()530 private void updatePausePlay() { 531 if (mRoot == null || mPauseButton == null) 532 return; 533 534 if (mPlayer.isPlaying()) { 535 mPauseButton.setImageResource(com.android.internal.R.drawable.ic_media_pause); 536 mPauseButton.setContentDescription(mPauseDescription); 537 } else { 538 mPauseButton.setImageResource(com.android.internal.R.drawable.ic_media_play); 539 mPauseButton.setContentDescription(mPlayDescription); 540 } 541 } 542 doPauseResume()543 private void doPauseResume() { 544 if (mPlayer.isPlaying()) { 545 mPlayer.pause(); 546 } else { 547 mPlayer.start(); 548 } 549 updatePausePlay(); 550 } 551 552 // There are two scenarios that can trigger the seekbar listener to trigger: 553 // 554 // The first is the user using the touchpad to adjust the posititon of the 555 // seekbar's thumb. In this case onStartTrackingTouch is called followed by 556 // a number of onProgressChanged notifications, concluded by onStopTrackingTouch. 557 // We're setting the field "mDragging" to true for the duration of the dragging 558 // session to avoid jumps in the position in case of ongoing playback. 559 // 560 // The second scenario involves the user operating the scroll ball, in this 561 // case there WON'T BE onStartTrackingTouch/onStopTrackingTouch notifications, 562 // we will simply apply the updated position without suspending regular updates. 563 private OnSeekBarChangeListener mSeekListener = new OnSeekBarChangeListener() { 564 public void onStartTrackingTouch(SeekBar bar) { 565 show(3600000); 566 567 mDragging = true; 568 569 // By removing these pending progress messages we make sure 570 // that a) we won't update the progress while the user adjusts 571 // the seekbar and b) once the user is done dragging the thumb 572 // we will post one of these messages to the queue again and 573 // this ensures that there will be exactly one message queued up. 574 mHandler.removeMessages(SHOW_PROGRESS); 575 } 576 577 public void onProgressChanged(SeekBar bar, int progress, boolean fromuser) { 578 if (!fromuser) { 579 // We're not interested in programmatically generated changes to 580 // the progress bar's position. 581 return; 582 } 583 584 long duration = mPlayer.getDuration(); 585 long newposition = (duration * progress) / 1000L; 586 mPlayer.seekTo( (int) newposition); 587 if (mCurrentTime != null) 588 mCurrentTime.setText(stringForTime( (int) newposition)); 589 } 590 591 public void onStopTrackingTouch(SeekBar bar) { 592 mDragging = false; 593 setProgress(); 594 updatePausePlay(); 595 show(sDefaultTimeout); 596 597 // Ensure that progress is properly updated in the future, 598 // the call to show() does not guarantee this because it is a 599 // no-op if we are already showing. 600 mHandler.sendEmptyMessage(SHOW_PROGRESS); 601 } 602 }; 603 604 @Override setEnabled(boolean enabled)605 public void setEnabled(boolean enabled) { 606 if (mPauseButton != null) { 607 mPauseButton.setEnabled(enabled); 608 } 609 if (mFfwdButton != null) { 610 mFfwdButton.setEnabled(enabled); 611 } 612 if (mRewButton != null) { 613 mRewButton.setEnabled(enabled); 614 } 615 if (mNextButton != null) { 616 mNextButton.setEnabled(enabled && mNextListener != null); 617 } 618 if (mPrevButton != null) { 619 mPrevButton.setEnabled(enabled && mPrevListener != null); 620 } 621 if (mProgress != null) { 622 mProgress.setEnabled(enabled); 623 } 624 disableUnsupportedButtons(); 625 super.setEnabled(enabled); 626 } 627 628 @Override onInitializeAccessibilityEvent(AccessibilityEvent event)629 public void onInitializeAccessibilityEvent(AccessibilityEvent event) { 630 super.onInitializeAccessibilityEvent(event); 631 event.setClassName(MediaController.class.getName()); 632 } 633 634 @Override onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info)635 public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) { 636 super.onInitializeAccessibilityNodeInfo(info); 637 info.setClassName(MediaController.class.getName()); 638 } 639 640 private View.OnClickListener mRewListener = new View.OnClickListener() { 641 public void onClick(View v) { 642 int pos = mPlayer.getCurrentPosition(); 643 pos -= 5000; // milliseconds 644 mPlayer.seekTo(pos); 645 setProgress(); 646 647 show(sDefaultTimeout); 648 } 649 }; 650 651 private View.OnClickListener mFfwdListener = new View.OnClickListener() { 652 public void onClick(View v) { 653 int pos = mPlayer.getCurrentPosition(); 654 pos += 15000; // milliseconds 655 mPlayer.seekTo(pos); 656 setProgress(); 657 658 show(sDefaultTimeout); 659 } 660 }; 661 installPrevNextListeners()662 private void installPrevNextListeners() { 663 if (mNextButton != null) { 664 mNextButton.setOnClickListener(mNextListener); 665 mNextButton.setEnabled(mNextListener != null); 666 } 667 668 if (mPrevButton != null) { 669 mPrevButton.setOnClickListener(mPrevListener); 670 mPrevButton.setEnabled(mPrevListener != null); 671 } 672 } 673 setPrevNextListeners(View.OnClickListener next, View.OnClickListener prev)674 public void setPrevNextListeners(View.OnClickListener next, View.OnClickListener prev) { 675 mNextListener = next; 676 mPrevListener = prev; 677 mListenersSet = true; 678 679 if (mRoot != null) { 680 installPrevNextListeners(); 681 682 if (mNextButton != null && !mFromXml) { 683 mNextButton.setVisibility(View.VISIBLE); 684 } 685 if (mPrevButton != null && !mFromXml) { 686 mPrevButton.setVisibility(View.VISIBLE); 687 } 688 } 689 } 690 691 public interface MediaPlayerControl { start()692 void start(); pause()693 void pause(); getDuration()694 int getDuration(); getCurrentPosition()695 int getCurrentPosition(); seekTo(int pos)696 void seekTo(int pos); isPlaying()697 boolean isPlaying(); getBufferPercentage()698 int getBufferPercentage(); canPause()699 boolean canPause(); canSeekBackward()700 boolean canSeekBackward(); canSeekForward()701 boolean canSeekForward(); 702 703 /** 704 * Get the audio session id for the player used by this VideoView. This can be used to 705 * apply audio effects to the audio track of a video. 706 * @return The audio session, or 0 if there was an error. 707 */ getAudioSessionId()708 int getAudioSessionId(); 709 } 710 } 711