1 /* ownCloud Android client application 
   3  *   Copyright (C) 2012-2013  ownCloud Inc. 
   5  *   This program is free software: you can redistribute it and/or modify 
   6  *   it under the terms of the GNU General Public License version 2, 
   7  *   as published by the Free Software Foundation. 
   9  *   This program is distributed in the hope that it will be useful, 
  10  *   but WITHOUT ANY WARRANTY; without even the implied warranty of 
  11  *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the 
  12  *   GNU General Public License for more details. 
  14  *   You should have received a copy of the GNU General Public License 
  15  *   along with this program.  If not, see <http://www.gnu.org/licenses/>. 
  18 package com
.owncloud
.android
.media
; 
  20 import android
.content
.Context
; 
  21 import android
.media
.MediaPlayer
; 
  22 import android
.os
.Handler
; 
  23 import android
.os
.Message
; 
  24 import android
.util
.AttributeSet
; 
  25 import android
.view
.KeyEvent
; 
  26 import android
.view
.LayoutInflater
; 
  27 import android
.view
.View
; 
  28 import android
.view
.View
.OnClickListener
; 
  29 import android
.view
.ViewGroup
; 
  30 import android
.view
.accessibility
.AccessibilityEvent
; 
  31 import android
.view
.accessibility
.AccessibilityNodeInfo
; 
  32 import android
.widget
.FrameLayout
; 
  33 import android
.widget
.ImageButton
; 
  34 import android
.widget
.MediaController
.MediaPlayerControl
; 
  35 import android
.widget
.ProgressBar
; 
  36 import android
.widget
.SeekBar
; 
  37 import android
.widget
.SeekBar
.OnSeekBarChangeListener
; 
  38 import android
.widget
.TextView
; 
  40 import java
.util
.Formatter
; 
  41 import java
.util
.Locale
; 
  43 import com
.owncloud
.android
.R
; 
  46  * View containing controls for a {@link MediaPlayer}.  
  48  * Holds buttons "play / pause", "rewind", "fast forward"  
  49  * and a progress slider.  
  51  * It synchronizes itself with the state of the  
  52  * {@link MediaPlayer}. 
  54  * @author David A. Velasco 
  57 public class MediaControlView 
extends FrameLayout 
/* implements OnLayoutChangeListener, OnTouchListener */ implements OnClickListener
, OnSeekBarChangeListener 
{ 
  59     private MediaPlayerControl  mPlayer
; 
  60     private Context             mContext
; 
  62     private ProgressBar         mProgress
; 
  63     private TextView            mEndTime
, mCurrentTime
; 
  64     private boolean             mDragging
; 
  65     private static final int    SHOW_PROGRESS 
= 1; 
  66     StringBuilder               mFormatBuilder
; 
  68     private ImageButton         mPauseButton
; 
  69     private ImageButton         mFfwdButton
; 
  70     private ImageButton         mRewButton
; 
  72     public MediaControlView(Context context
, AttributeSet attrs
) { 
  73         super(context
, attrs
); 
  76         FrameLayout
.LayoutParams frameParams 
= new FrameLayout
.LayoutParams( 
  77                 ViewGroup
.LayoutParams
.MATCH_PARENT
, 
  78                 ViewGroup
.LayoutParams
.MATCH_PARENT
 
  80         LayoutInflater inflate 
= (LayoutInflater
) mContext
.getSystemService(Context
.LAYOUT_INFLATER_SERVICE
); 
  81         mRoot 
= inflate
.inflate(R
.layout
.media_control
, null
); 
  82         initControllerView(mRoot
); 
  83         addView(mRoot
, frameParams
); 
  86         setFocusableInTouchMode(true
); 
  87         setDescendantFocusability(ViewGroup
.FOCUS_AFTER_DESCENDANTS
); 
  92     public void onFinishInflate() { 
  95             initControllerView(mRoot); 
 100     public MediaControlView(Context context, boolean useFastForward) { 
 103         mUseFastForward = useFastForward; 
 104         initFloatingWindowLayout(); 
 105         //initFloatingWindow(); 
 110     public MediaControlView(Context context) { 
 116     private void initFloatingWindow() { 
 117         mWindowManager = (WindowManager)mContext.getSystemService(Context.WINDOW_SERVICE); 
 118         mWindow = PolicyManager.makeNewWindow(mContext); 
 119         mWindow.setWindowManager(mWindowManager, null, null); 
 120         mWindow.requestFeature(Window.FEATURE_NO_TITLE); 
 121         mDecor = mWindow.getDecorView(); 
 122         mDecor.setOnTouchListener(mTouchListener); 
 123         mWindow.setContentView(this); 
 124         mWindow.setBackgroundDrawableResource(android.R.color.transparent); 
 126         // While the media controller is up, the volume control keys should 
 127         // affect the media stream type 
 128         mWindow.setVolumeControlStream(AudioManager.STREAM_MUSIC); 
 131         setFocusableInTouchMode(true); 
 132         setDescendantFocusability(ViewGroup.FOCUS_AFTER_DESCENDANTS); 
 138     // Allocate and initialize the static parts of mDecorLayoutParams. Must 
 139     // also call updateFloatingWindowLayout() to fill in the dynamic parts 
 140     // (y and width) before mDecorLayoutParams can be used. 
 141     private void initFloatingWindowLayout() { 
 142         mDecorLayoutParams = new WindowManager.LayoutParams(); 
 143         WindowManager.LayoutParams p = mDecorLayoutParams; 
 144         p.gravity = Gravity.TOP; 
 145         p.height = LayoutParams.WRAP_CONTENT; 
 147         p.format = PixelFormat.TRANSLUCENT; 
 148         p.type = WindowManager.LayoutParams.TYPE_APPLICATION_PANEL; 
 149         p.flags |= WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM 
 150                 | WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL 
 151                 | WindowManager.LayoutParams.FLAG_SPLIT_TOUCH; 
 153         p.windowAnimations = 0; // android.R.style.DropDownAnimationDown; 
 157     // Update the dynamic parts of mDecorLayoutParams 
 158     // Must be called with mAnchor != NULL. 
 160     private void updateFloatingWindowLayout() { 
 161         int [] anchorPos = new int[2]; 
 162         mAnchor.getLocationOnScreen(anchorPos); 
 164         WindowManager.LayoutParams p = mDecorLayoutParams; 
 165         p.width = mAnchor.getWidth(); 
 166         p.y = anchorPos[1] + mAnchor.getHeight(); 
 171     // This is called whenever mAnchor's layout bound changes 
 172     public void onLayoutChange(View v, int left, int top, int right, 
 173             int bottom, int oldLeft, int oldTop, int oldRight, 
 175         //updateFloatingWindowLayout(); 
 177             mWindowManager.updateViewLayout(mDecor, mDecorLayoutParams); 
 183     public boolean onTouch(View v, MotionEvent event) { 
 184         if (event.getAction() == MotionEvent.ACTION_DOWN) { 
 194     public void setMediaPlayer(MediaPlayerControl player
) { 
 196         mHandler
.sendEmptyMessage(SHOW_PROGRESS
); 
 201     private void initControllerView(View v
) { 
 202         mPauseButton 
= (ImageButton
) v
.findViewById(R
.id
.playBtn
); 
 203         if (mPauseButton 
!= null
) { 
 204             mPauseButton
.requestFocus(); 
 205             mPauseButton
.setOnClickListener(this); 
 208         mFfwdButton 
= (ImageButton
) v
.findViewById(R
.id
.forwardBtn
); 
 209         if (mFfwdButton 
!= null
) { 
 210             mFfwdButton
.setOnClickListener(this); 
 213         mRewButton 
= (ImageButton
) v
.findViewById(R
.id
.rewindBtn
); 
 214         if (mRewButton 
!= null
) { 
 215             mRewButton
.setOnClickListener(this); 
 218         mProgress 
= (ProgressBar
) v
.findViewById(R
.id
.progressBar
); 
 219         if (mProgress 
!= null
) { 
 220             if (mProgress 
instanceof SeekBar
) { 
 221                 SeekBar seeker 
= (SeekBar
) mProgress
; 
 222                 seeker
.setOnSeekBarChangeListener(this); 
 224             mProgress
.setMax(1000); 
 227         mEndTime 
= (TextView
) v
.findViewById(R
.id
.totalTimeText
); 
 228         mCurrentTime 
= (TextView
) v
.findViewById(R
.id
.currentTimeText
); 
 229         mFormatBuilder 
= new StringBuilder(); 
 230         mFormatter 
= new Formatter(mFormatBuilder
, Locale
.getDefault()); 
 236      * Disable pause or seek buttons if the stream cannot be paused or seeked. 
 237      * This requires the control interface to be a MediaPlayerControlExt 
 239     private void disableUnsupportedButtons() { 
 241             if (mPauseButton 
!= null 
&& !mPlayer
.canPause()) { 
 242                 mPauseButton
.setEnabled(false
); 
 244             if (mRewButton 
!= null 
&& !mPlayer
.canSeekBackward()) { 
 245                 mRewButton
.setEnabled(false
); 
 247             if (mFfwdButton 
!= null 
&& !mPlayer
.canSeekForward()) { 
 248                 mFfwdButton
.setEnabled(false
); 
 250         } catch (IncompatibleClassChangeError ex
) { 
 251             // We were given an old version of the interface, that doesn't have 
 252             // the canPause/canSeekXYZ methods. This is OK, it just means we 
 253             // assume the media can be paused and seeked, and so we don't disable 
 259     private Handler mHandler 
= new Handler() { 
 261         public void handleMessage(Message msg
) { 
 267                         msg 
= obtainMessage(SHOW_PROGRESS
); 
 268                         sendMessageDelayed(msg
, 1000 - (pos 
% 1000)); 
 275     private String 
stringForTime(int timeMs
) { 
 276         int totalSeconds 
= timeMs 
/ 1000; 
 278         int seconds 
= totalSeconds 
% 60; 
 279         int minutes 
= (totalSeconds 
/ 60) % 60; 
 280         int hours   
= totalSeconds 
/ 3600; 
 282         mFormatBuilder
.setLength(0); 
 284             return mFormatter
.format("%d:%02d:%02d", hours
, minutes
, seconds
).toString(); 
 286             return mFormatter
.format("%02d:%02d", minutes
, seconds
).toString(); 
 290     private int setProgress() { 
 291         if (mPlayer 
== null 
|| mDragging
) { 
 294         int position 
= mPlayer
.getCurrentPosition(); 
 295         int duration 
= mPlayer
.getDuration(); 
 296         if (mProgress 
!= null
) { 
 298                 // use long to avoid overflow 
 299                 long pos 
= 1000L * position 
/ duration
; 
 300                 mProgress
.setProgress( (int) pos
); 
 302             int percent 
= mPlayer
.getBufferPercentage(); 
 303             mProgress
.setSecondaryProgress(percent 
* 10); 
 306         if (mEndTime 
!= null
) 
 307             mEndTime
.setText(stringForTime(duration
)); 
 308         if (mCurrentTime 
!= null
) 
 309             mCurrentTime
.setText(stringForTime(position
)); 
 316     public boolean dispatchKeyEvent(KeyEvent event
) { 
 317         int keyCode 
= event
.getKeyCode(); 
 318         final boolean uniqueDown 
= event
.getRepeatCount() == 0 
 319                 && event
.getAction() == KeyEvent
.ACTION_DOWN
; 
 320         if (keyCode 
==  KeyEvent
.KEYCODE_HEADSETHOOK
 
 321                 || keyCode 
== KeyEvent
.KEYCODE_MEDIA_PLAY_PAUSE
 
 322                 || keyCode 
== KeyEvent
.KEYCODE_SPACE
) { 
 325                 //show(sDefaultTimeout); 
 326                 if (mPauseButton 
!= null
) { 
 327                     mPauseButton
.requestFocus(); 
 331         } else if (keyCode 
== KeyEvent
.KEYCODE_MEDIA_PLAY
) { 
 332             if (uniqueDown 
&& !mPlayer
.isPlaying()) { 
 335                 //show(sDefaultTimeout); 
 338         } else if (keyCode 
== KeyEvent
.KEYCODE_MEDIA_STOP
 
 339                 || keyCode 
== KeyEvent
.KEYCODE_MEDIA_PAUSE
) { 
 340             if (uniqueDown 
&& mPlayer
.isPlaying()) { 
 343                 //show(sDefaultTimeout); 
 348         //show(sDefaultTimeout); 
 349         return super.dispatchKeyEvent(event
); 
 352     public void updatePausePlay() { 
 353         if (mRoot 
== null 
|| mPauseButton 
== null
) 
 356         if (mPlayer
.isPlaying()) { 
 357             mPauseButton
.setImageResource(android
.R
.drawable
.ic_media_pause
); 
 359             mPauseButton
.setImageResource(android
.R
.drawable
.ic_media_play
); 
 363     private void doPauseResume() { 
 364         if (mPlayer
.isPlaying()) { 
 373     public void setEnabled(boolean enabled
) { 
 374         if (mPauseButton 
!= null
) { 
 375             mPauseButton
.setEnabled(enabled
); 
 377         if (mFfwdButton 
!= null
) { 
 378             mFfwdButton
.setEnabled(enabled
); 
 380         if (mRewButton 
!= null
) { 
 381             mRewButton
.setEnabled(enabled
); 
 383         if (mProgress 
!= null
) { 
 384             mProgress
.setEnabled(enabled
); 
 386         disableUnsupportedButtons(); 
 387         super.setEnabled(enabled
); 
 391     public void onClick(View v
) { 
 393         boolean playing 
= mPlayer
.isPlaying(); 
 401             pos 
= mPlayer
.getCurrentPosition(); 
 404             if (!playing
) mPlayer
.pause();  // necessary in some 2.3.x devices  
 408         case R
.id
.forwardBtn
: 
 409             pos 
= mPlayer
.getCurrentPosition(); 
 412             if (!playing
) mPlayer
.pause(); // necessary in some 2.3.x devices 
 421     public void onProgressChanged(SeekBar seekBar
, int progress
, boolean fromUser
) { 
 423             // We're not interested in programmatically generated changes to 
 424             // the progress bar's position. 
 428         long duration 
= mPlayer
.getDuration(); 
 429         long newposition 
= (duration 
* progress
) / 1000L; 
 430         mPlayer
.seekTo( (int) newposition
); 
 431         if (mCurrentTime 
!= null
) 
 432             mCurrentTime
.setText(stringForTime( (int) newposition
)); 
 436      * Called in devices with touchpad when the user starts to adjust the  
 437      * position of the seekbar's thumb. 
 439      * Will be followed by several onProgressChanged notifications. 
 442     public void onStartTrackingTouch(SeekBar seekBar
) { 
 443         mDragging 
= true
;                           // monitors the duration of dragging  
 444         mHandler
.removeMessages(SHOW_PROGRESS
);     // grants no more updates with media player progress while dragging  
 449      * Called in devices with touchpad when the user finishes the 
 450      * adjusting of the seekbar. 
 453     public void onStopTrackingTouch(SeekBar seekBar
) { 
 457         mHandler
.sendEmptyMessage(SHOW_PROGRESS
);    // grants future updates with media player progress  
 461     public void onInitializeAccessibilityEvent(AccessibilityEvent event
) { 
 462         super.onInitializeAccessibilityEvent(event
); 
 463         event
.setClassName(MediaControlView
.class.getName()); 
 467     public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info
) { 
 468         super.onInitializeAccessibilityNodeInfo(info
); 
 469         info
.setClassName(MediaControlView
.class.getName());