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 as published by
7 * the Free Software Foundation, either version 3 of the License, or
8 * (at your option) any later version.
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
15 * You should have received a copy of the GNU General Public License
16 * along with this program. If not, see <http://www.gnu.org/licenses/>.
19 package com
.owncloud
.android
.media
;
21 import android
.content
.Context
;
22 import android
.media
.MediaPlayer
;
23 import android
.os
.Handler
;
24 import android
.os
.Message
;
25 import android
.util
.AttributeSet
;
26 import android
.view
.KeyEvent
;
27 import android
.view
.LayoutInflater
;
28 import android
.view
.View
;
29 import android
.view
.View
.OnClickListener
;
30 import android
.view
.ViewGroup
;
31 import android
.view
.accessibility
.AccessibilityEvent
;
32 import android
.view
.accessibility
.AccessibilityNodeInfo
;
33 import android
.widget
.FrameLayout
;
34 import android
.widget
.ImageButton
;
35 import android
.widget
.MediaController
.MediaPlayerControl
;
36 import android
.widget
.ProgressBar
;
37 import android
.widget
.SeekBar
;
38 import android
.widget
.SeekBar
.OnSeekBarChangeListener
;
39 import android
.widget
.TextView
;
41 import java
.util
.Formatter
;
42 import java
.util
.Locale
;
44 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
) {
401 pos
= mPlayer
.getCurrentPosition();
407 case R
.id
.forwardBtn
:
408 pos
= mPlayer
.getCurrentPosition();
419 public void onProgressChanged(SeekBar seekBar
, int progress
, boolean fromUser
) {
421 // We're not interested in programmatically generated changes to
422 // the progress bar's position.
426 long duration
= mPlayer
.getDuration();
427 long newposition
= (duration
* progress
) / 1000L;
428 mPlayer
.seekTo( (int) newposition
);
429 if (mCurrentTime
!= null
)
430 mCurrentTime
.setText(stringForTime( (int) newposition
));
434 * Called in devices with touchpad when the user starts to adjust the
435 * position of the seekbar's thumb.
437 * Will be followed by several onProgressChanged notifications.
440 public void onStartTrackingTouch(SeekBar seekBar
) {
441 mDragging
= true
; // monitors the duration of dragging
442 mHandler
.removeMessages(SHOW_PROGRESS
); // grants no more updates with media player progress while dragging
447 * Called in devices with touchpad when the user finishes the
448 * adjusting of the seekbar.
451 public void onStopTrackingTouch(SeekBar seekBar
) {
455 mHandler
.sendEmptyMessage(SHOW_PROGRESS
); // grants future updates with media player progress
459 public void onInitializeAccessibilityEvent(AccessibilityEvent event
) {
460 super.onInitializeAccessibilityEvent(event
);
461 event
.setClassName(MediaControlView
.class.getName());
465 public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info
) {
466 super.onInitializeAccessibilityNodeInfo(info
);
467 info
.setClassName(MediaControlView
.class.getName());