Copyright note fixes
[pub/Android/ownCloud.git] / src / com / owncloud / android / media / MediaControlView.java
1 /* ownCloud Android client application
2 *
3 * Copyright (C) 2012-2013 ownCloud Inc.
4 *
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.
8 *
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.
13 *
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/>.
16 *
17 */
18 package com.owncloud.android.media;
19
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;
39
40 import java.util.Formatter;
41 import java.util.Locale;
42
43 import com.owncloud.android.R;
44
45 /**
46 * View containing controls for a {@link MediaPlayer}.
47 *
48 * Holds buttons "play / pause", "rewind", "fast forward"
49 * and a progress slider.
50 *
51 * It synchronizes itself with the state of the
52 * {@link MediaPlayer}.
53 *
54 * @author David A. Velasco
55 */
56
57 public class MediaControlView extends FrameLayout /* implements OnLayoutChangeListener, OnTouchListener */ implements OnClickListener, OnSeekBarChangeListener {
58
59 private MediaPlayerControl mPlayer;
60 private Context mContext;
61 private View mRoot;
62 private ProgressBar mProgress;
63 private TextView mEndTime, mCurrentTime;
64 private boolean mDragging;
65 private static final int SHOW_PROGRESS = 1;
66 StringBuilder mFormatBuilder;
67 Formatter mFormatter;
68 private ImageButton mPauseButton;
69 private ImageButton mFfwdButton;
70 private ImageButton mRewButton;
71
72 public MediaControlView(Context context, AttributeSet attrs) {
73 super(context, attrs);
74 mContext = context;
75
76 FrameLayout.LayoutParams frameParams = new FrameLayout.LayoutParams(
77 ViewGroup.LayoutParams.MATCH_PARENT,
78 ViewGroup.LayoutParams.MATCH_PARENT
79 );
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);
84
85 setFocusable(true);
86 setFocusableInTouchMode(true);
87 setDescendantFocusability(ViewGroup.FOCUS_AFTER_DESCENDANTS);
88 requestFocus();
89 }
90
91 @Override
92 public void onFinishInflate() {
93 /*
94 if (mRoot != null)
95 initControllerView(mRoot);
96 */
97 }
98
99 /* TODO REMOVE
100 public MediaControlView(Context context, boolean useFastForward) {
101 super(context);
102 mContext = context;
103 mUseFastForward = useFastForward;
104 initFloatingWindowLayout();
105 //initFloatingWindow();
106 }
107 */
108
109 /* TODO REMOVE
110 public MediaControlView(Context context) {
111 this(context, true);
112 }
113 */
114
115 /* T
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);
125
126 // While the media controller is up, the volume control keys should
127 // affect the media stream type
128 mWindow.setVolumeControlStream(AudioManager.STREAM_MUSIC);
129
130 setFocusable(true);
131 setFocusableInTouchMode(true);
132 setDescendantFocusability(ViewGroup.FOCUS_AFTER_DESCENDANTS);
133 requestFocus();
134 }
135 */
136
137 /*
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;
146 p.x = 0;
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;
152 p.token = null;
153 p.windowAnimations = 0; // android.R.style.DropDownAnimationDown;
154 }
155 */
156
157 // Update the dynamic parts of mDecorLayoutParams
158 // Must be called with mAnchor != NULL.
159 /*
160 private void updateFloatingWindowLayout() {
161 int [] anchorPos = new int[2];
162 mAnchor.getLocationOnScreen(anchorPos);
163
164 WindowManager.LayoutParams p = mDecorLayoutParams;
165 p.width = mAnchor.getWidth();
166 p.y = anchorPos[1] + mAnchor.getHeight();
167 }
168 */
169
170 /*
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,
174 int oldBottom) {
175 //updateFloatingWindowLayout();
176 if (mShowing) {
177 mWindowManager.updateViewLayout(mDecor, mDecorLayoutParams);
178 }
179 }
180 */
181
182 /*
183 public boolean onTouch(View v, MotionEvent event) {
184 if (event.getAction() == MotionEvent.ACTION_DOWN) {
185 if (mShowing) {
186 hide();
187 }
188 }
189 return false;
190 }
191 */
192
193
194 public void setMediaPlayer(MediaPlayerControl player) {
195 mPlayer = player;
196 mHandler.sendEmptyMessage(SHOW_PROGRESS);
197 updatePausePlay();
198 }
199
200
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);
206 }
207
208 mFfwdButton = (ImageButton) v.findViewById(R.id.forwardBtn);
209 if (mFfwdButton != null) {
210 mFfwdButton.setOnClickListener(this);
211 }
212
213 mRewButton = (ImageButton) v.findViewById(R.id.rewindBtn);
214 if (mRewButton != null) {
215 mRewButton.setOnClickListener(this);
216 }
217
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);
223 }
224 mProgress.setMax(1000);
225 }
226
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());
231
232 }
233
234
235 /**
236 * Disable pause or seek buttons if the stream cannot be paused or seeked.
237 * This requires the control interface to be a MediaPlayerControlExt
238 */
239 private void disableUnsupportedButtons() {
240 try {
241 if (mPauseButton != null && !mPlayer.canPause()) {
242 mPauseButton.setEnabled(false);
243 }
244 if (mRewButton != null && !mPlayer.canSeekBackward()) {
245 mRewButton.setEnabled(false);
246 }
247 if (mFfwdButton != null && !mPlayer.canSeekForward()) {
248 mFfwdButton.setEnabled(false);
249 }
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
254 // the buttons.
255 }
256 }
257
258
259 private Handler mHandler = new Handler() {
260 @Override
261 public void handleMessage(Message msg) {
262 int pos;
263 switch (msg.what) {
264 case SHOW_PROGRESS:
265 pos = setProgress();
266 if (!mDragging) {
267 msg = obtainMessage(SHOW_PROGRESS);
268 sendMessageDelayed(msg, 1000 - (pos % 1000));
269 }
270 break;
271 }
272 }
273 };
274
275 private String stringForTime(int timeMs) {
276 int totalSeconds = timeMs / 1000;
277
278 int seconds = totalSeconds % 60;
279 int minutes = (totalSeconds / 60) % 60;
280 int hours = totalSeconds / 3600;
281
282 mFormatBuilder.setLength(0);
283 if (hours > 0) {
284 return mFormatter.format("%d:%02d:%02d", hours, minutes, seconds).toString();
285 } else {
286 return mFormatter.format("%02d:%02d", minutes, seconds).toString();
287 }
288 }
289
290 private int setProgress() {
291 if (mPlayer == null || mDragging) {
292 return 0;
293 }
294 int position = mPlayer.getCurrentPosition();
295 int duration = mPlayer.getDuration();
296 if (mProgress != null) {
297 if (duration > 0) {
298 // use long to avoid overflow
299 long pos = 1000L * position / duration;
300 mProgress.setProgress( (int) pos);
301 }
302 int percent = mPlayer.getBufferPercentage();
303 mProgress.setSecondaryProgress(percent * 10);
304 }
305
306 if (mEndTime != null)
307 mEndTime.setText(stringForTime(duration));
308 if (mCurrentTime != null)
309 mCurrentTime.setText(stringForTime(position));
310
311 return position;
312 }
313
314
315 @Override
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) {
323 if (uniqueDown) {
324 doPauseResume();
325 //show(sDefaultTimeout);
326 if (mPauseButton != null) {
327 mPauseButton.requestFocus();
328 }
329 }
330 return true;
331 } else if (keyCode == KeyEvent.KEYCODE_MEDIA_PLAY) {
332 if (uniqueDown && !mPlayer.isPlaying()) {
333 mPlayer.start();
334 updatePausePlay();
335 //show(sDefaultTimeout);
336 }
337 return true;
338 } else if (keyCode == KeyEvent.KEYCODE_MEDIA_STOP
339 || keyCode == KeyEvent.KEYCODE_MEDIA_PAUSE) {
340 if (uniqueDown && mPlayer.isPlaying()) {
341 mPlayer.pause();
342 updatePausePlay();
343 //show(sDefaultTimeout);
344 }
345 return true;
346 }
347
348 //show(sDefaultTimeout);
349 return super.dispatchKeyEvent(event);
350 }
351
352 public void updatePausePlay() {
353 if (mRoot == null || mPauseButton == null)
354 return;
355
356 if (mPlayer.isPlaying()) {
357 mPauseButton.setImageResource(android.R.drawable.ic_media_pause);
358 } else {
359 mPauseButton.setImageResource(android.R.drawable.ic_media_play);
360 }
361 }
362
363 private void doPauseResume() {
364 if (mPlayer.isPlaying()) {
365 mPlayer.pause();
366 } else {
367 mPlayer.start();
368 }
369 updatePausePlay();
370 }
371
372 @Override
373 public void setEnabled(boolean enabled) {
374 if (mPauseButton != null) {
375 mPauseButton.setEnabled(enabled);
376 }
377 if (mFfwdButton != null) {
378 mFfwdButton.setEnabled(enabled);
379 }
380 if (mRewButton != null) {
381 mRewButton.setEnabled(enabled);
382 }
383 if (mProgress != null) {
384 mProgress.setEnabled(enabled);
385 }
386 disableUnsupportedButtons();
387 super.setEnabled(enabled);
388 }
389
390 @Override
391 public void onClick(View v) {
392 int pos;
393 boolean playing = mPlayer.isPlaying();
394 switch (v.getId()) {
395
396 case R.id.playBtn:
397 doPauseResume();
398 break;
399
400 case R.id.rewindBtn:
401 pos = mPlayer.getCurrentPosition();
402 pos -= 5000;
403 mPlayer.seekTo(pos);
404 if (!playing) mPlayer.pause(); // necessary in some 2.3.x devices
405 setProgress();
406 break;
407
408 case R.id.forwardBtn:
409 pos = mPlayer.getCurrentPosition();
410 pos += 15000;
411 mPlayer.seekTo(pos);
412 if (!playing) mPlayer.pause(); // necessary in some 2.3.x devices
413 setProgress();
414 break;
415
416 }
417 }
418
419
420 @Override
421 public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
422 if (!fromUser) {
423 // We're not interested in programmatically generated changes to
424 // the progress bar's position.
425 return;
426 }
427
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));
433 }
434
435 /**
436 * Called in devices with touchpad when the user starts to adjust the
437 * position of the seekbar's thumb.
438 *
439 * Will be followed by several onProgressChanged notifications.
440 */
441 @Override
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
445 }
446
447
448 /**
449 * Called in devices with touchpad when the user finishes the
450 * adjusting of the seekbar.
451 */
452 @Override
453 public void onStopTrackingTouch(SeekBar seekBar) {
454 mDragging = false;
455 setProgress();
456 updatePausePlay();
457 mHandler.sendEmptyMessage(SHOW_PROGRESS); // grants future updates with media player progress
458 }
459
460 @Override
461 public void onInitializeAccessibilityEvent(AccessibilityEvent event) {
462 super.onInitializeAccessibilityEvent(event);
463 event.setClassName(MediaControlView.class.getName());
464 }
465
466 @Override
467 public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
468 super.onInitializeAccessibilityNodeInfo(info);
469 info.setClassName(MediaControlView.class.getName());
470 }
471
472 }