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());