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