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
; 
  47  * View containing controls for a {@link MediaPlayer}.  
  49  * Holds buttons "play / pause", "rewind", "fast forward"  
  50  * and a progress slider.  
  52  * It synchronizes itself with the state of the  
  53  * {@link MediaPlayer}. 
  55  * @author David A. Velasco 
  58 public class MediaControlView 
extends FrameLayout 
/* implements OnLayoutChangeListener, OnTouchListener */ implements OnClickListener
, OnSeekBarChangeListener 
{ 
  60     private MediaPlayerControl  mPlayer
; 
  61     private Context             mContext
; 
  63     private ProgressBar         mProgress
; 
  64     private TextView            mEndTime
, mCurrentTime
; 
  65     private boolean             mDragging
; 
  66     private static final int    SHOW_PROGRESS 
= 1; 
  67     StringBuilder               mFormatBuilder
; 
  69     private ImageButton         mPauseButton
; 
  70     private ImageButton         mFfwdButton
; 
  71     private ImageButton         mRewButton
; 
  73     public MediaControlView(Context context
, AttributeSet attrs
) { 
  74         super(context
, attrs
); 
  77         FrameLayout
.LayoutParams frameParams 
= new FrameLayout
.LayoutParams( 
  78                 ViewGroup
.LayoutParams
.MATCH_PARENT
, 
  79                 ViewGroup
.LayoutParams
.MATCH_PARENT
 
  81         LayoutInflater inflate 
= (LayoutInflater
) mContext
.getSystemService(Context
.LAYOUT_INFLATER_SERVICE
); 
  82         mRoot 
= inflate
.inflate(R
.layout
.media_control
, null
); 
  83         initControllerView(mRoot
); 
  84         addView(mRoot
, frameParams
); 
  87         setFocusableInTouchMode(true
); 
  88         setDescendantFocusability(ViewGroup
.FOCUS_AFTER_DESCENDANTS
); 
  93     public void onFinishInflate() { 
  96             initControllerView(mRoot); 
 101     public MediaControlView(Context context, boolean useFastForward) { 
 104         mUseFastForward = useFastForward; 
 105         initFloatingWindowLayout(); 
 106         //initFloatingWindow(); 
 111     public MediaControlView(Context context) { 
 117     private void initFloatingWindow() { 
 118         mWindowManager = (WindowManager)mContext.getSystemService(Context.WINDOW_SERVICE); 
 119         mWindow = PolicyManager.makeNewWindow(mContext); 
 120         mWindow.setWindowManager(mWindowManager, null, null); 
 121         mWindow.requestFeature(Window.FEATURE_NO_TITLE); 
 122         mDecor = mWindow.getDecorView(); 
 123         mDecor.setOnTouchListener(mTouchListener); 
 124         mWindow.setContentView(this); 
 125         mWindow.setBackgroundDrawableResource(android.R.color.transparent); 
 127         // While the media controller is up, the volume control keys should 
 128         // affect the media stream type 
 129         mWindow.setVolumeControlStream(AudioManager.STREAM_MUSIC); 
 132         setFocusableInTouchMode(true); 
 133         setDescendantFocusability(ViewGroup.FOCUS_AFTER_DESCENDANTS); 
 139     // Allocate and initialize the static parts of mDecorLayoutParams. Must 
 140     // also call updateFloatingWindowLayout() to fill in the dynamic parts 
 141     // (y and width) before mDecorLayoutParams can be used. 
 142     private void initFloatingWindowLayout() { 
 143         mDecorLayoutParams = new WindowManager.LayoutParams(); 
 144         WindowManager.LayoutParams p = mDecorLayoutParams; 
 145         p.gravity = Gravity.TOP; 
 146         p.height = LayoutParams.WRAP_CONTENT; 
 148         p.format = PixelFormat.TRANSLUCENT; 
 149         p.type = WindowManager.LayoutParams.TYPE_APPLICATION_PANEL; 
 150         p.flags |= WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM 
 151                 | WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL 
 152                 | WindowManager.LayoutParams.FLAG_SPLIT_TOUCH; 
 154         p.windowAnimations = 0; // android.R.style.DropDownAnimationDown; 
 158     // Update the dynamic parts of mDecorLayoutParams 
 159     // Must be called with mAnchor != NULL. 
 161     private void updateFloatingWindowLayout() { 
 162         int [] anchorPos = new int[2]; 
 163         mAnchor.getLocationOnScreen(anchorPos); 
 165         WindowManager.LayoutParams p = mDecorLayoutParams; 
 166         p.width = mAnchor.getWidth(); 
 167         p.y = anchorPos[1] + mAnchor.getHeight(); 
 172     // This is called whenever mAnchor's layout bound changes 
 173     public void onLayoutChange(View v, int left, int top, int right, 
 174             int bottom, int oldLeft, int oldTop, int oldRight, 
 176         //updateFloatingWindowLayout(); 
 178             mWindowManager.updateViewLayout(mDecor, mDecorLayoutParams); 
 184     public boolean onTouch(View v, MotionEvent event) { 
 185         if (event.getAction() == MotionEvent.ACTION_DOWN) { 
 195     public void setMediaPlayer(MediaPlayerControl player
) { 
 197         mHandler
.sendEmptyMessage(SHOW_PROGRESS
); 
 202     private void initControllerView(View v
) { 
 203         mPauseButton 
= (ImageButton
) v
.findViewById(R
.id
.playBtn
); 
 204         if (mPauseButton 
!= null
) { 
 205             mPauseButton
.requestFocus(); 
 206             mPauseButton
.setOnClickListener(this); 
 209         mFfwdButton 
= (ImageButton
) v
.findViewById(R
.id
.forwardBtn
); 
 210         if (mFfwdButton 
!= null
) { 
 211             mFfwdButton
.setOnClickListener(this); 
 214         mRewButton 
= (ImageButton
) v
.findViewById(R
.id
.rewindBtn
); 
 215         if (mRewButton 
!= null
) { 
 216             mRewButton
.setOnClickListener(this); 
 219         mProgress 
= (ProgressBar
) v
.findViewById(R
.id
.progressBar
); 
 220         if (mProgress 
!= null
) { 
 221             if (mProgress 
instanceof SeekBar
) { 
 222                 SeekBar seeker 
= (SeekBar
) mProgress
; 
 223                 seeker
.setOnSeekBarChangeListener(this); 
 225             mProgress
.setMax(1000); 
 228         mEndTime 
= (TextView
) v
.findViewById(R
.id
.totalTimeText
); 
 229         mCurrentTime 
= (TextView
) v
.findViewById(R
.id
.currentTimeText
); 
 230         mFormatBuilder 
= new StringBuilder(); 
 231         mFormatter 
= new Formatter(mFormatBuilder
, Locale
.getDefault()); 
 237      * Disable pause or seek buttons if the stream cannot be paused or seeked. 
 238      * This requires the control interface to be a MediaPlayerControlExt 
 240     private void disableUnsupportedButtons() { 
 242             if (mPauseButton 
!= null 
&& !mPlayer
.canPause()) { 
 243                 mPauseButton
.setEnabled(false
); 
 245             if (mRewButton 
!= null 
&& !mPlayer
.canSeekBackward()) { 
 246                 mRewButton
.setEnabled(false
); 
 248             if (mFfwdButton 
!= null 
&& !mPlayer
.canSeekForward()) { 
 249                 mFfwdButton
.setEnabled(false
); 
 251         } catch (IncompatibleClassChangeError ex
) { 
 252             // We were given an old version of the interface, that doesn't have 
 253             // the canPause/canSeekXYZ methods. This is OK, it just means we 
 254             // assume the media can be paused and seeked, and so we don't disable 
 260     private Handler mHandler 
= new Handler() { 
 262         public void handleMessage(Message msg
) { 
 268                         msg 
= obtainMessage(SHOW_PROGRESS
); 
 269                         sendMessageDelayed(msg
, 1000 - (pos 
% 1000)); 
 276     private String 
stringForTime(int timeMs
) { 
 277         int totalSeconds 
= timeMs 
/ 1000; 
 279         int seconds 
= totalSeconds 
% 60; 
 280         int minutes 
= (totalSeconds 
/ 60) % 60; 
 281         int hours   
= totalSeconds 
/ 3600; 
 283         mFormatBuilder
.setLength(0); 
 285             return mFormatter
.format("%d:%02d:%02d", hours
, minutes
, seconds
).toString(); 
 287             return mFormatter
.format("%02d:%02d", minutes
, seconds
).toString(); 
 291     private int setProgress() { 
 292         if (mPlayer 
== null 
|| mDragging
) { 
 295         int position 
= mPlayer
.getCurrentPosition(); 
 296         int duration 
= mPlayer
.getDuration(); 
 297         if (mProgress 
!= null
) { 
 299                 // use long to avoid overflow 
 300                 long pos 
= 1000L * position 
/ duration
; 
 301                 mProgress
.setProgress( (int) pos
); 
 303             int percent 
= mPlayer
.getBufferPercentage(); 
 304             mProgress
.setSecondaryProgress(percent 
* 10); 
 307         if (mEndTime 
!= null
) 
 308             mEndTime
.setText(stringForTime(duration
)); 
 309         if (mCurrentTime 
!= null
) 
 310             mCurrentTime
.setText(stringForTime(position
)); 
 317     public boolean dispatchKeyEvent(KeyEvent event
) { 
 318         int keyCode 
= event
.getKeyCode(); 
 319         final boolean uniqueDown 
= event
.getRepeatCount() == 0 
 320                 && event
.getAction() == KeyEvent
.ACTION_DOWN
; 
 321         if (keyCode 
==  KeyEvent
.KEYCODE_HEADSETHOOK
 
 322                 || keyCode 
== KeyEvent
.KEYCODE_MEDIA_PLAY_PAUSE
 
 323                 || keyCode 
== KeyEvent
.KEYCODE_SPACE
) { 
 326                 //show(sDefaultTimeout); 
 327                 if (mPauseButton 
!= null
) { 
 328                     mPauseButton
.requestFocus(); 
 332         } else if (keyCode 
== KeyEvent
.KEYCODE_MEDIA_PLAY
) { 
 333             if (uniqueDown 
&& !mPlayer
.isPlaying()) { 
 336                 //show(sDefaultTimeout); 
 339         } else if (keyCode 
== KeyEvent
.KEYCODE_MEDIA_STOP
 
 340                 || keyCode 
== KeyEvent
.KEYCODE_MEDIA_PAUSE
) { 
 341             if (uniqueDown 
&& mPlayer
.isPlaying()) { 
 344                 //show(sDefaultTimeout); 
 349         //show(sDefaultTimeout); 
 350         return super.dispatchKeyEvent(event
); 
 353     public void updatePausePlay() { 
 354         if (mRoot 
== null 
|| mPauseButton 
== null
) 
 357         if (mPlayer
.isPlaying()) { 
 358             mPauseButton
.setImageResource(android
.R
.drawable
.ic_media_pause
); 
 360             mPauseButton
.setImageResource(android
.R
.drawable
.ic_media_play
); 
 364     private void doPauseResume() { 
 365         if (mPlayer
.isPlaying()) { 
 374     public void setEnabled(boolean enabled
) { 
 375         if (mPauseButton 
!= null
) { 
 376             mPauseButton
.setEnabled(enabled
); 
 378         if (mFfwdButton 
!= null
) { 
 379             mFfwdButton
.setEnabled(enabled
); 
 381         if (mRewButton 
!= null
) { 
 382             mRewButton
.setEnabled(enabled
); 
 384         if (mProgress 
!= null
) { 
 385             mProgress
.setEnabled(enabled
); 
 387         disableUnsupportedButtons(); 
 388         super.setEnabled(enabled
); 
 392     public void onClick(View v
) { 
 394         boolean playing 
= mPlayer
.isPlaying(); 
 402             pos 
= mPlayer
.getCurrentPosition(); 
 405             if (!playing
) mPlayer
.pause();  // necessary in some 2.3.x devices  
 409         case R
.id
.forwardBtn
: 
 410             pos 
= mPlayer
.getCurrentPosition(); 
 413             if (!playing
) mPlayer
.pause(); // necessary in some 2.3.x devices 
 422     public void onProgressChanged(SeekBar seekBar
, int progress
, boolean fromUser
) { 
 424             // We're not interested in programmatically generated changes to 
 425             // the progress bar's position. 
 429         long duration 
= mPlayer
.getDuration(); 
 430         long newposition 
= (duration 
* progress
) / 1000L; 
 431         mPlayer
.seekTo( (int) newposition
); 
 432         if (mCurrentTime 
!= null
) 
 433             mCurrentTime
.setText(stringForTime( (int) newposition
)); 
 437      * Called in devices with touchpad when the user starts to adjust the  
 438      * position of the seekbar's thumb. 
 440      * Will be followed by several onProgressChanged notifications. 
 443     public void onStartTrackingTouch(SeekBar seekBar
) { 
 444         mDragging 
= true
;                           // monitors the duration of dragging  
 445         mHandler
.removeMessages(SHOW_PROGRESS
);     // grants no more updates with media player progress while dragging  
 450      * Called in devices with touchpad when the user finishes the 
 451      * adjusting of the seekbar. 
 454     public void onStopTrackingTouch(SeekBar seekBar
) { 
 458         mHandler
.sendEmptyMessage(SHOW_PROGRESS
);    // grants future updates with media player progress  
 462     public void onInitializeAccessibilityEvent(AccessibilityEvent event
) { 
 463         super.onInitializeAccessibilityEvent(event
); 
 464         event
.setClassName(MediaControlView
.class.getName()); 
 468     public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info
) { 
 469         super.onInitializeAccessibilityNodeInfo(info
); 
 470         info
.setClassName(MediaControlView
.class.getName());