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