2  *   ownCloud Android client application 
   4  *   @author David A. Velasco 
   5  *   Copyright (C) 2015 ownCloud Inc. 
   7  *   This program is free software: you can redistribute it and/or modify 
   8  *   it under the terms of the GNU General Public License version 2, 
   9  *   as published by the Free Software Foundation. 
  11  *   This program is distributed in the hope that it will be useful, 
  12  *   but WITHOUT ANY WARRANTY; without even the implied warranty of 
  13  *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the 
  14  *   GNU General Public License for more details. 
  16  *   You should have received a copy of the GNU General Public License 
  17  *   along with this program.  If not, see <http://www.gnu.org/licenses/>. 
  20 package com
.owncloud
.android
.media
; 
  22 import android
.content
.Context
; 
  23 import android
.media
.MediaPlayer
; 
  24 import android
.os
.Handler
; 
  25 import android
.os
.Message
; 
  26 import android
.util
.AttributeSet
; 
  27 import android
.view
.KeyEvent
; 
  28 import android
.view
.LayoutInflater
; 
  29 import android
.view
.View
; 
  30 import android
.view
.View
.OnClickListener
; 
  31 import android
.view
.ViewGroup
; 
  32 import android
.view
.accessibility
.AccessibilityEvent
; 
  33 import android
.view
.accessibility
.AccessibilityNodeInfo
; 
  34 import android
.widget
.FrameLayout
; 
  35 import android
.widget
.ImageButton
; 
  36 import android
.widget
.MediaController
.MediaPlayerControl
; 
  37 import android
.widget
.ProgressBar
; 
  38 import android
.widget
.SeekBar
; 
  39 import android
.widget
.SeekBar
.OnSeekBarChangeListener
; 
  40 import android
.widget
.TextView
; 
  42 import java
.util
.Formatter
; 
  43 import java
.util
.Locale
; 
  45 import com
.owncloud
.android
.R
; 
  46 import com
.owncloud
.android
.utils
.DisplayUtils
; 
  50  * View containing controls for a {@link MediaPlayer}.  
  52  * Holds buttons "play / pause", "rewind", "fast forward"  
  53  * and a progress slider.  
  55  * It synchronizes itself with the state of the  
  56  * {@link MediaPlayer}. 
  59 public class MediaControlView 
extends FrameLayout 
/* implements OnLayoutChangeListener, OnTouchListener */ implements OnClickListener
, OnSeekBarChangeListener 
{ 
  61     private MediaPlayerControl  mPlayer
; 
  62     private Context             mContext
; 
  64     private ProgressBar         mProgress
; 
  65     private TextView            mEndTime
, mCurrentTime
; 
  66     private boolean             mDragging
; 
  67     private static final int    SHOW_PROGRESS 
= 1; 
  68     StringBuilder               mFormatBuilder
; 
  70     private ImageButton         mPauseButton
; 
  71     private ImageButton         mFfwdButton
; 
  72     private ImageButton         mRewButton
; 
  74     public MediaControlView(Context context
, AttributeSet attrs
) { 
  75         super(context
, attrs
); 
  78         FrameLayout
.LayoutParams frameParams 
= new FrameLayout
.LayoutParams( 
  79                 ViewGroup
.LayoutParams
.MATCH_PARENT
, 
  80                 ViewGroup
.LayoutParams
.MATCH_PARENT
 
  82         LayoutInflater inflate 
= (LayoutInflater
) mContext
.getSystemService(Context
.LAYOUT_INFLATER_SERVICE
); 
  83         mRoot 
= inflate
.inflate(R
.layout
.media_control
, null
); 
  84         initControllerView(mRoot
); 
  85         addView(mRoot
, frameParams
); 
  88         setFocusableInTouchMode(true
); 
  89         setDescendantFocusability(ViewGroup
.FOCUS_AFTER_DESCENDANTS
); 
  94     public void onFinishInflate() { 
  97             initControllerView(mRoot); 
 102     public MediaControlView(Context context, boolean useFastForward) { 
 105         mUseFastForward = useFastForward; 
 106         initFloatingWindowLayout(); 
 107         //initFloatingWindow(); 
 112     public MediaControlView(Context context) { 
 118     private void initFloatingWindow() { 
 119         mWindowManager = (WindowManager)mContext.getSystemService(Context.WINDOW_SERVICE); 
 120         mWindow = PolicyManager.makeNewWindow(mContext); 
 121         mWindow.setWindowManager(mWindowManager, null, null); 
 122         mWindow.requestFeature(Window.FEATURE_NO_TITLE); 
 123         mDecor = mWindow.getDecorView(); 
 124         mDecor.setOnTouchListener(mTouchListener); 
 125         mWindow.setContentView(this); 
 126         mWindow.setBackgroundDrawableResource(android.R.color.transparent); 
 128         // While the media controller is up, the volume control keys should 
 129         // affect the media stream type 
 130         mWindow.setVolumeControlStream(AudioManager.STREAM_MUSIC); 
 133         setFocusableInTouchMode(true); 
 134         setDescendantFocusability(ViewGroup.FOCUS_AFTER_DESCENDANTS); 
 140     // Allocate and initialize the static parts of mDecorLayoutParams. Must 
 141     // also call updateFloatingWindowLayout() to fill in the dynamic parts 
 142     // (y and width) before mDecorLayoutParams can be used. 
 143     private void initFloatingWindowLayout() { 
 144         mDecorLayoutParams = new WindowManager.LayoutParams(); 
 145         WindowManager.LayoutParams p = mDecorLayoutParams; 
 146         p.gravity = Gravity.TOP; 
 147         p.height = LayoutParams.WRAP_CONTENT; 
 149         p.format = PixelFormat.TRANSLUCENT; 
 150         p.type = WindowManager.LayoutParams.TYPE_APPLICATION_PANEL; 
 151         p.flags |= WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM 
 152                 | WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL 
 153                 | WindowManager.LayoutParams.FLAG_SPLIT_TOUCH; 
 155         p.windowAnimations = 0; // android.R.style.DropDownAnimationDown; 
 159     // Update the dynamic parts of mDecorLayoutParams 
 160     // Must be called with mAnchor != NULL. 
 162     private void updateFloatingWindowLayout() { 
 163         int [] anchorPos = new int[2]; 
 164         mAnchor.getLocationOnScreen(anchorPos); 
 166         WindowManager.LayoutParams p = mDecorLayoutParams; 
 167         p.width = mAnchor.getWidth(); 
 168         p.y = anchorPos[1] + mAnchor.getHeight(); 
 173     // This is called whenever mAnchor's layout bound changes 
 174     public void onLayoutChange(View v, int left, int top, int right, 
 175             int bottom, int oldLeft, int oldTop, int oldRight, 
 177         //updateFloatingWindowLayout(); 
 179             mWindowManager.updateViewLayout(mDecor, mDecorLayoutParams); 
 185     public boolean onTouch(View v, MotionEvent event) { 
 186         if (event.getAction() == MotionEvent.ACTION_DOWN) { 
 196     public void setMediaPlayer(MediaPlayerControl player
) { 
 198         mHandler
.sendEmptyMessage(SHOW_PROGRESS
); 
 203     private void initControllerView(View v
) { 
 204         mPauseButton 
= (ImageButton
) v
.findViewById(R
.id
.playBtn
); 
 205         if (mPauseButton 
!= null
) { 
 206             mPauseButton
.requestFocus(); 
 207             mPauseButton
.setOnClickListener(this); 
 210         mFfwdButton 
= (ImageButton
) v
.findViewById(R
.id
.forwardBtn
); 
 211         if (mFfwdButton 
!= null
) { 
 212             mFfwdButton
.setOnClickListener(this); 
 215         mRewButton 
= (ImageButton
) v
.findViewById(R
.id
.rewindBtn
); 
 216         if (mRewButton 
!= null
) { 
 217             mRewButton
.setOnClickListener(this); 
 220         mProgress 
= (ProgressBar
) v
.findViewById(R
.id
.progressBar
); 
 221         if (mProgress 
!= null
) { 
 222             if (mProgress 
instanceof SeekBar
) { 
 223                 SeekBar seeker 
= (SeekBar
) mProgress
; 
 224                 DisplayUtils
.colorPreLollipopHorizontalSeekBar(seeker
); 
 225                 seeker
.setOnSeekBarChangeListener(this); 
 227                 DisplayUtils
.colorPreLollipopHorizontalProgressBar(mProgress
); 
 229             mProgress
.setMax(1000); 
 232         mEndTime 
= (TextView
) v
.findViewById(R
.id
.totalTimeText
); 
 233         mCurrentTime 
= (TextView
) v
.findViewById(R
.id
.currentTimeText
); 
 234         mFormatBuilder 
= new StringBuilder(); 
 235         mFormatter 
= new Formatter(mFormatBuilder
, Locale
.getDefault()); 
 241      * Disable pause or seek buttons if the stream cannot be paused or seeked. 
 242      * This requires the control interface to be a MediaPlayerControlExt 
 244     private void disableUnsupportedButtons() { 
 246             if (mPauseButton 
!= null 
&& !mPlayer
.canPause()) { 
 247                 mPauseButton
.setEnabled(false
); 
 249             if (mRewButton 
!= null 
&& !mPlayer
.canSeekBackward()) { 
 250                 mRewButton
.setEnabled(false
); 
 252             if (mFfwdButton 
!= null 
&& !mPlayer
.canSeekForward()) { 
 253                 mFfwdButton
.setEnabled(false
); 
 255         } catch (IncompatibleClassChangeError ex
) { 
 256             // We were given an old version of the interface, that doesn't have 
 257             // the canPause/canSeekXYZ methods. This is OK, it just means we 
 258             // assume the media can be paused and seeked, and so we don't disable 
 264     private Handler mHandler 
= new Handler() { 
 266         public void handleMessage(Message msg
) { 
 272                         msg 
= obtainMessage(SHOW_PROGRESS
); 
 273                         sendMessageDelayed(msg
, 1000 - (pos 
% 1000)); 
 280     private String 
stringForTime(int timeMs
) { 
 281         int totalSeconds 
= timeMs 
/ 1000; 
 283         int seconds 
= totalSeconds 
% 60; 
 284         int minutes 
= (totalSeconds 
/ 60) % 60; 
 285         int hours   
= totalSeconds 
/ 3600; 
 287         mFormatBuilder
.setLength(0); 
 289             return mFormatter
.format("%d:%02d:%02d", hours
, minutes
, seconds
).toString(); 
 291             return mFormatter
.format("%02d:%02d", minutes
, seconds
).toString(); 
 295     private int setProgress() { 
 296         if (mPlayer 
== null 
|| mDragging
) { 
 299         int position 
= mPlayer
.getCurrentPosition(); 
 300         int duration 
= mPlayer
.getDuration(); 
 301         if (mProgress 
!= null
) { 
 303                 // use long to avoid overflow 
 304                 long pos 
= 1000L * position 
/ duration
; 
 305                 mProgress
.setProgress( (int) pos
); 
 307             int percent 
= mPlayer
.getBufferPercentage(); 
 308             mProgress
.setSecondaryProgress(percent 
* 10); 
 311         if (mEndTime 
!= null
) 
 312             mEndTime
.setText(stringForTime(duration
)); 
 313         if (mCurrentTime 
!= null
) 
 314             mCurrentTime
.setText(stringForTime(position
)); 
 321     public boolean dispatchKeyEvent(KeyEvent event
) { 
 322         int keyCode 
= event
.getKeyCode(); 
 323         final boolean uniqueDown 
= event
.getRepeatCount() == 0 
 324                 && event
.getAction() == KeyEvent
.ACTION_DOWN
; 
 325         if (keyCode 
==  KeyEvent
.KEYCODE_HEADSETHOOK
 
 326                 || keyCode 
== KeyEvent
.KEYCODE_MEDIA_PLAY_PAUSE
 
 327                 || keyCode 
== KeyEvent
.KEYCODE_SPACE
) { 
 330                 //show(sDefaultTimeout); 
 331                 if (mPauseButton 
!= null
) { 
 332                     mPauseButton
.requestFocus(); 
 336         } else if (keyCode 
== KeyEvent
.KEYCODE_MEDIA_PLAY
) { 
 337             if (uniqueDown 
&& !mPlayer
.isPlaying()) { 
 340                 //show(sDefaultTimeout); 
 343         } else if (keyCode 
== KeyEvent
.KEYCODE_MEDIA_STOP
 
 344                 || keyCode 
== KeyEvent
.KEYCODE_MEDIA_PAUSE
) { 
 345             if (uniqueDown 
&& mPlayer
.isPlaying()) { 
 348                 //show(sDefaultTimeout); 
 353         //show(sDefaultTimeout); 
 354         return super.dispatchKeyEvent(event
); 
 357     public void updatePausePlay() { 
 358         if (mRoot 
== null 
|| mPauseButton 
== null
) 
 361         if (mPlayer
.isPlaying()) { 
 362             mPauseButton
.setImageResource(android
.R
.drawable
.ic_media_pause
); 
 364             mPauseButton
.setImageResource(android
.R
.drawable
.ic_media_play
); 
 368     private void doPauseResume() { 
 369         if (mPlayer
.isPlaying()) { 
 378     public void setEnabled(boolean enabled
) { 
 379         if (mPauseButton 
!= null
) { 
 380             mPauseButton
.setEnabled(enabled
); 
 382         if (mFfwdButton 
!= null
) { 
 383             mFfwdButton
.setEnabled(enabled
); 
 385         if (mRewButton 
!= null
) { 
 386             mRewButton
.setEnabled(enabled
); 
 388         if (mProgress 
!= null
) { 
 389             mProgress
.setEnabled(enabled
); 
 391         disableUnsupportedButtons(); 
 392         super.setEnabled(enabled
); 
 396     public void onClick(View v
) { 
 398         boolean playing 
= mPlayer
.isPlaying(); 
 406             pos 
= mPlayer
.getCurrentPosition(); 
 409             if (!playing
) mPlayer
.pause();  // necessary in some 2.3.x devices  
 413         case R
.id
.forwardBtn
: 
 414             pos 
= mPlayer
.getCurrentPosition(); 
 417             if (!playing
) mPlayer
.pause(); // necessary in some 2.3.x devices 
 426     public void onProgressChanged(SeekBar seekBar
, int progress
, boolean fromUser
) { 
 428             // We're not interested in programmatically generated changes to 
 429             // the progress bar's position. 
 433         long duration 
= mPlayer
.getDuration(); 
 434         long newposition 
= (duration 
* progress
) / 1000L; 
 435         mPlayer
.seekTo( (int) newposition
); 
 436         if (mCurrentTime 
!= null
) 
 437             mCurrentTime
.setText(stringForTime( (int) newposition
)); 
 441      * Called in devices with touchpad when the user starts to adjust the  
 442      * position of the seekbar's thumb. 
 444      * Will be followed by several onProgressChanged notifications. 
 447     public void onStartTrackingTouch(SeekBar seekBar
) { 
 448         mDragging 
= true
;                           // monitors the duration of dragging  
 449         mHandler
.removeMessages(SHOW_PROGRESS
);     // grants no more updates with media player progress while dragging  
 454      * Called in devices with touchpad when the user finishes the 
 455      * adjusting of the seekbar. 
 458     public void onStopTrackingTouch(SeekBar seekBar
) { 
 462         mHandler
.sendEmptyMessage(SHOW_PROGRESS
);    // grants future updates with media player progress  
 466     public void onInitializeAccessibilityEvent(AccessibilityEvent event
) { 
 467         super.onInitializeAccessibilityEvent(event
); 
 468         event
.setClassName(MediaControlView
.class.getName()); 
 472     public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info
) { 
 473         super.onInitializeAccessibilityNodeInfo(info
); 
 474         info
.setClassName(MediaControlView
.class.getName());