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