1 /* ownCloud Android client application
3 * @author David A. Velasco
4 * Copyright (C) 2012-2013 ownCloud Inc.
6 * This program is free software: you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License version 2,
8 * as published by the Free Software Foundation.
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
;
48 * View containing controls for a {@link MediaPlayer}.
50 * Holds buttons "play / pause", "rewind", "fast forward"
51 * and a progress slider.
53 * It synchronizes itself with the state of the
54 * {@link MediaPlayer}.
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());