Fix: Download is started over and over when rotating the device
[pub/Android/ownCloud.git] / src / com / owncloud / android / ui / preview / PreviewMediaFragment.java
1 /* ownCloud Android client application
2 * Copyright (C) 2012-2013 ownCloud Inc.
3 *
4 * This program is free software: you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License version 2,
6 * as published by the Free Software Foundation.
7 *
8 * This program is distributed in the hope that it will be useful,
9 * but WITHOUT ANY WARRANTY; without even the implied warranty of
10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 * GNU General Public License for more details.
12 *
13 * You should have received a copy of the GNU General Public License
14 * along with this program. If not, see <http://www.gnu.org/licenses/>.
15 *
16 */
17 package com.owncloud.android.ui.preview;
18
19 import android.accounts.Account;
20 import android.app.Activity;
21 import android.app.AlertDialog;
22 import android.content.ComponentName;
23 import android.content.Context;
24 import android.content.DialogInterface;
25 import android.content.Intent;
26 import android.content.ServiceConnection;
27 import android.content.res.Configuration;
28 import android.media.MediaPlayer;
29 import android.media.MediaPlayer.OnCompletionListener;
30 import android.media.MediaPlayer.OnErrorListener;
31 import android.media.MediaPlayer.OnPreparedListener;
32 import android.os.Build;
33 import android.os.Bundle;
34 import android.os.IBinder;
35 import android.view.LayoutInflater;
36 import android.view.MotionEvent;
37 import android.view.View;
38 import android.view.View.OnTouchListener;
39 import android.view.ViewGroup;
40 import android.widget.ImageView;
41 import android.widget.Toast;
42 import android.widget.VideoView;
43
44 import com.actionbarsherlock.view.Menu;
45 import com.actionbarsherlock.view.MenuInflater;
46 import com.actionbarsherlock.view.MenuItem;
47 import com.owncloud.android.R;
48 import com.owncloud.android.datamodel.OCFile;
49 import com.owncloud.android.files.FileMenuFilter;
50 import com.owncloud.android.media.MediaControlView;
51 import com.owncloud.android.media.MediaService;
52 import com.owncloud.android.media.MediaServiceBinder;
53 import com.owncloud.android.ui.activity.FileActivity;
54 import com.owncloud.android.ui.dialog.ConfirmationDialogFragment;
55 import com.owncloud.android.ui.dialog.RemoveFileDialogFragment;
56 import com.owncloud.android.ui.fragment.FileFragment;
57 import com.owncloud.android.utils.Log_OC;
58
59
60 /**
61 * This fragment shows a preview of a downloaded media file (audio or video).
62 *
63 * Trying to get an instance with NULL {@link OCFile} or ownCloud {@link Account} values will produce an {@link IllegalStateException}.
64 *
65 * By now, if the {@link OCFile} passed is not downloaded, an {@link IllegalStateException} is generated on instantiation too.
66 *
67 * @author David A. Velasco
68 */
69 public class PreviewMediaFragment extends FileFragment implements
70 OnTouchListener {
71
72 public static final String EXTRA_FILE = "FILE";
73 public static final String EXTRA_ACCOUNT = "ACCOUNT";
74 private static final String EXTRA_PLAY_POSITION = "PLAY_POSITION";
75 private static final String EXTRA_PLAYING = "PLAYING";
76
77 private View mView;
78 private Account mAccount;
79 private ImageView mImagePreview;
80 private VideoView mVideoPreview;
81 private int mSavedPlaybackPosition;
82
83 private MediaServiceBinder mMediaServiceBinder = null;
84 private MediaControlView mMediaController = null;
85 private MediaServiceConnection mMediaServiceConnection = null;
86 private VideoHelper mVideoHelper;
87 private boolean mAutoplay;
88 public boolean mPrepared;
89
90 private static final String TAG = PreviewMediaFragment.class.getSimpleName();
91
92
93 /**
94 * Creates a fragment to preview a file.
95 *
96 * When 'fileToDetail' or 'ocAccount' are null
97 *
98 * @param fileToDetail An {@link OCFile} to preview in the fragment
99 * @param ocAccount An ownCloud account; needed to start downloads
100 */
101 public PreviewMediaFragment(OCFile fileToDetail, Account ocAccount, int startPlaybackPosition, boolean autoplay) {
102 super(fileToDetail);
103 mAccount = ocAccount;
104 mSavedPlaybackPosition = startPlaybackPosition;
105 mAutoplay = autoplay;
106 }
107
108
109 /**
110 * Creates an empty fragment for previews.
111 *
112 * MUST BE KEPT: the system uses it when tries to reinstantiate a fragment automatically (for instance, when the device is turned a aside).
113 *
114 * DO NOT CALL IT: an {@link OCFile} and {@link Account} must be provided for a successful construction
115 */
116 public PreviewMediaFragment() {
117 super();
118 mAccount = null;
119 mSavedPlaybackPosition = 0;
120 mAutoplay = true;
121 }
122
123
124 /**
125 * {@inheritDoc}
126 */
127 @Override
128 public void onCreate(Bundle savedInstanceState) {
129 super.onCreate(savedInstanceState);
130 setHasOptionsMenu(true);
131 }
132
133
134 /**
135 * {@inheritDoc}
136 */
137 @Override
138 public View onCreateView(LayoutInflater inflater, ViewGroup container,
139 Bundle savedInstanceState) {
140 super.onCreateView(inflater, container, savedInstanceState);
141 Log_OC.e(TAG, "onCreateView");
142
143
144 mView = inflater.inflate(R.layout.file_preview, container, false);
145
146 mImagePreview = (ImageView)mView.findViewById(R.id.image_preview);
147 mVideoPreview = (VideoView)mView.findViewById(R.id.video_preview);
148 mVideoPreview.setOnTouchListener(this);
149
150 mMediaController = (MediaControlView)mView.findViewById(R.id.media_controller);
151
152 return mView;
153 }
154
155
156 /**
157 * {@inheritDoc}
158 */
159 @Override
160 public void onActivityCreated(Bundle savedInstanceState) {
161 super.onActivityCreated(savedInstanceState);
162 Log_OC.e(TAG, "onActivityCreated");
163
164 if (savedInstanceState != null) {
165 setFile((OCFile)savedInstanceState.getParcelable(PreviewMediaFragment.EXTRA_FILE));
166 mAccount = savedInstanceState.getParcelable(PreviewMediaFragment.EXTRA_ACCOUNT);
167 mSavedPlaybackPosition = savedInstanceState.getInt(PreviewMediaFragment.EXTRA_PLAY_POSITION);
168 mAutoplay = savedInstanceState.getBoolean(PreviewMediaFragment.EXTRA_PLAYING);
169
170 }
171 OCFile file = getFile();
172 if (file == null) {
173 throw new IllegalStateException("Instanced with a NULL OCFile");
174 }
175 if (mAccount == null) {
176 throw new IllegalStateException("Instanced with a NULL ownCloud Account");
177 }
178 if (!file.isDown()) {
179 throw new IllegalStateException("There is no local file to preview");
180 }
181 if (file.isVideo()) {
182 mVideoPreview.setVisibility(View.VISIBLE);
183 mImagePreview.setVisibility(View.GONE);
184 prepareVideo();
185
186 } else {
187 mVideoPreview.setVisibility(View.GONE);
188 mImagePreview.setVisibility(View.VISIBLE);
189 }
190
191 }
192
193
194 /**
195 * {@inheritDoc}
196 */
197 @Override
198 public void onSaveInstanceState(Bundle outState) {
199 super.onSaveInstanceState(outState);
200 Log_OC.e(TAG, "onSaveInstanceState");
201
202 outState.putParcelable(PreviewMediaFragment.EXTRA_FILE, getFile());
203 outState.putParcelable(PreviewMediaFragment.EXTRA_ACCOUNT, mAccount);
204
205 if (getFile().isVideo()) {
206 mSavedPlaybackPosition = mVideoPreview.getCurrentPosition();
207 mAutoplay = mVideoPreview.isPlaying();
208 outState.putInt(PreviewMediaFragment.EXTRA_PLAY_POSITION , mSavedPlaybackPosition);
209 outState.putBoolean(PreviewMediaFragment.EXTRA_PLAYING , mAutoplay);
210 } else {
211 outState.putInt(PreviewMediaFragment.EXTRA_PLAY_POSITION , mMediaServiceBinder.getCurrentPosition());
212 outState.putBoolean(PreviewMediaFragment.EXTRA_PLAYING , mMediaServiceBinder.isPlaying());
213 }
214 }
215
216
217 @Override
218 public void onStart() {
219 super.onStart();
220 Log_OC.e(TAG, "onStart");
221
222 OCFile file = getFile();
223 if (file != null) {
224 if (file.isAudio()) {
225 bindMediaService();
226
227 } else if (file.isVideo()) {
228 stopAudio();
229 playVideo();
230 }
231 }
232 }
233
234
235 private void stopAudio() {
236 Intent i = new Intent(getSherlockActivity(), MediaService.class);
237 i.setAction(MediaService.ACTION_STOP_ALL);
238 getSherlockActivity().startService(i);
239 }
240
241
242 /**
243 * {@inheritDoc}
244 */
245 @Override
246 public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
247 super.onCreateOptionsMenu(menu, inflater);
248 inflater.inflate(R.menu.file_actions_menu, menu);
249 }
250
251
252 /**
253 * {@inheritDoc}
254 */
255 @Override
256 public void onPrepareOptionsMenu(Menu menu) {
257 super.onPrepareOptionsMenu(menu);
258
259 if (mContainerActivity.getStorageManager() != null) {
260 FileMenuFilter mf = new FileMenuFilter(
261 getFile(),
262 mContainerActivity.getStorageManager().getAccount(),
263 mContainerActivity,
264 getSherlockActivity()
265 );
266 mf.filter(menu);
267 }
268
269 // additional restriction for this fragment
270 // TODO allow renaming in PreviewImageFragment
271 MenuItem item = menu.findItem(R.id.action_rename_file);
272 if (item != null) {
273 item.setVisible(false);
274 item.setEnabled(false);
275 }
276 }
277
278
279 /**
280 * {@inheritDoc}
281 */
282 @Override
283 public boolean onOptionsItemSelected(MenuItem item) {
284 switch (item.getItemId()) {
285 case R.id.action_share_file: {
286 stopPreview(false);
287 mContainerActivity.getFileOperationsHelper().shareFileWithLink(getFile());
288 return true;
289 }
290 case R.id.action_unshare_file: {
291 stopPreview(false);
292 mContainerActivity.getFileOperationsHelper().unshareFileWithLink(getFile());
293 return true;
294 }
295 case R.id.action_open_file_with: {
296 openFile();
297 return true;
298 }
299 case R.id.action_remove_file: {
300 RemoveFileDialogFragment dialog = RemoveFileDialogFragment.newInstance(getFile());
301 dialog.show(getFragmentManager(), ConfirmationDialogFragment.FTAG_CONFIRMATION);
302 return true;
303 }
304 case R.id.action_see_details: {
305 seeDetails();
306 return true;
307 }
308 case R.id.action_send_file: {
309 sendFile();
310 }
311 case R.id.action_sync_file: {
312 mContainerActivity.getFileOperationsHelper().syncFile(getFile());
313 return true;
314 }
315
316 default:
317 return false;
318 }
319 }
320
321
322
323 /**
324 * Update the file of the fragment with file value
325 * @param file
326 */
327 public void updateFile(OCFile file){
328 setFile(file);
329 }
330
331 private void sendFile() {
332 stopPreview(false);
333 mContainerActivity.getFileOperationsHelper().sendDownloadedFile(getFile());
334
335 }
336
337 private void seeDetails() {
338 stopPreview(false);
339 mContainerActivity.showDetails(getFile());
340 }
341
342
343 private void prepareVideo() {
344 // create helper to get more control on the playback
345 mVideoHelper = new VideoHelper();
346 mVideoPreview.setOnPreparedListener(mVideoHelper);
347 mVideoPreview.setOnCompletionListener(mVideoHelper);
348 mVideoPreview.setOnErrorListener(mVideoHelper);
349 }
350
351 private void playVideo() {
352 // create and prepare control panel for the user
353 mMediaController.setMediaPlayer(mVideoPreview);
354
355 // load the video file in the video player ; when done, VideoHelper#onPrepared() will be called
356 mVideoPreview.setVideoPath(getFile().getStoragePath());
357 }
358
359
360 private class VideoHelper implements OnCompletionListener, OnPreparedListener, OnErrorListener {
361
362 /**
363 * Called when the file is ready to be played.
364 *
365 * Just starts the playback.
366 *
367 * @param mp {@link MediaPlayer} instance performing the playback.
368 */
369 @Override
370 public void onPrepared(MediaPlayer vp) {
371 Log_OC.e(TAG, "onPrepared");
372 mVideoPreview.seekTo(mSavedPlaybackPosition);
373 if (mAutoplay) {
374 mVideoPreview.start();
375 }
376 mMediaController.setEnabled(true);
377 mMediaController.updatePausePlay();
378 mPrepared = true;
379 }
380
381
382 /**
383 * Called when the file is finished playing.
384 *
385 * Finishes the activity.
386 *
387 * @param mp {@link MediaPlayer} instance performing the playback.
388 */
389 @Override
390 public void onCompletion(MediaPlayer mp) {
391 Log_OC.e(TAG, "completed");
392 if (mp != null) {
393 mVideoPreview.seekTo(0);
394 // next lines are necessary to work around undesired video loops
395 if (Build.VERSION.SDK_INT == Build.VERSION_CODES.GINGERBREAD) {
396 mVideoPreview.pause();
397
398 } else if (Build.VERSION.SDK_INT == Build.VERSION_CODES.GINGERBREAD_MR1) {
399 // mVideePreview.pause() is not enough
400
401 mMediaController.setEnabled(false);
402 mVideoPreview.stopPlayback();
403 mAutoplay = false;
404 mSavedPlaybackPosition = 0;
405 mVideoPreview.setVideoPath(getFile().getStoragePath());
406 }
407 } // else : called from onError()
408 mMediaController.updatePausePlay();
409 }
410
411
412 /**
413 * Called when an error in playback occurs.
414 *
415 * @param mp {@link MediaPlayer} instance performing the playback.
416 * @param what Type of error
417 * @param extra Extra code specific to the error
418 */
419 @Override
420 public boolean onError(MediaPlayer mp, int what, int extra) {
421 if (mVideoPreview.getWindowToken() != null) {
422 String message = MediaService.getMessageForMediaError(getSherlockActivity(), what, extra);
423 new AlertDialog.Builder(getSherlockActivity())
424 .setMessage(message)
425 .setPositiveButton(android.R.string.VideoView_error_button,
426 new DialogInterface.OnClickListener() {
427 public void onClick(DialogInterface dialog, int whichButton) {
428 dialog.dismiss();
429 VideoHelper.this.onCompletion(null);
430 }
431 })
432 .setCancelable(false)
433 .show();
434 }
435 return true;
436 }
437
438 }
439
440
441 @Override
442 public void onPause() {
443 super.onPause();
444 Log_OC.e(TAG, "onPause");
445 }
446
447 @Override
448 public void onResume() {
449 super.onResume();
450 Log_OC.e(TAG, "onResume");
451 }
452
453 @Override
454 public void onDestroy() {
455 super.onDestroy();
456 Log_OC.e(TAG, "onDestroy");
457 }
458
459 @Override
460 public void onStop() {
461 Log_OC.e(TAG, "onStop");
462 super.onStop();
463
464 mPrepared = false;
465 if (mMediaServiceConnection != null) {
466 Log_OC.d(TAG, "Unbinding from MediaService ...");
467 if (mMediaServiceBinder != null && mMediaController != null) {
468 mMediaServiceBinder.unregisterMediaController(mMediaController);
469 }
470 getSherlockActivity().unbindService(mMediaServiceConnection);
471 mMediaServiceConnection = null;
472 mMediaServiceBinder = null;
473 }
474 }
475
476 @Override
477 public boolean onTouch(View v, MotionEvent event) {
478 if (event.getAction() == MotionEvent.ACTION_DOWN && v == mVideoPreview) {
479 startFullScreenVideo();
480 return true;
481 }
482 return false;
483 }
484
485
486 private void startFullScreenVideo() {
487 Intent i = new Intent(getSherlockActivity(), PreviewVideoActivity.class);
488 i.putExtra(FileActivity.EXTRA_ACCOUNT, mAccount);
489 i.putExtra(FileActivity.EXTRA_FILE, getFile());
490 i.putExtra(PreviewVideoActivity.EXTRA_AUTOPLAY, mVideoPreview.isPlaying());
491 mVideoPreview.pause();
492 i.putExtra(PreviewVideoActivity.EXTRA_START_POSITION, mVideoPreview.getCurrentPosition());
493 startActivityForResult(i, 0);
494 }
495
496 @Override
497 public void onConfigurationChanged (Configuration newConfig) {
498 Log_OC.e(TAG, "onConfigurationChanged " + this);
499 }
500
501 @Override
502 public void onActivityResult (int requestCode, int resultCode, Intent data) {
503 Log_OC.e(TAG, "onActivityResult " + this);
504 super.onActivityResult(requestCode, resultCode, data);
505 if (resultCode == Activity.RESULT_OK) {
506 mSavedPlaybackPosition = data.getExtras().getInt(PreviewVideoActivity.EXTRA_START_POSITION);
507 mAutoplay = data.getExtras().getBoolean(PreviewVideoActivity.EXTRA_AUTOPLAY);
508 }
509 }
510
511
512 private void playAudio() {
513 OCFile file = getFile();
514 if (!mMediaServiceBinder.isPlaying(file)) {
515 Log_OC.d(TAG, "starting playback of " + file.getStoragePath());
516 mMediaServiceBinder.start(mAccount, file, mAutoplay, mSavedPlaybackPosition);
517
518 } else {
519 if (!mMediaServiceBinder.isPlaying() && mAutoplay) {
520 mMediaServiceBinder.start();
521 mMediaController.updatePausePlay();
522 }
523 }
524 }
525
526
527 private void bindMediaService() {
528 Log_OC.d(TAG, "Binding to MediaService...");
529 if (mMediaServiceConnection == null) {
530 mMediaServiceConnection = new MediaServiceConnection();
531 }
532 getSherlockActivity().bindService( new Intent(getSherlockActivity(),
533 MediaService.class),
534 mMediaServiceConnection,
535 Context.BIND_AUTO_CREATE);
536 // follow the flow in MediaServiceConnection#onServiceConnected(...)
537 }
538
539 /** Defines callbacks for service binding, passed to bindService() */
540 private class MediaServiceConnection implements ServiceConnection {
541
542 @Override
543 public void onServiceConnected(ComponentName component, IBinder service) {
544 if (getSherlockActivity() != null) {
545 if (component.equals(new ComponentName(getSherlockActivity(), MediaService.class))) {
546 Log_OC.d(TAG, "Media service connected");
547 mMediaServiceBinder = (MediaServiceBinder) service;
548 if (mMediaServiceBinder != null) {
549 prepareMediaController();
550 playAudio(); // do not wait for the touch of nobody to play audio
551
552 Log_OC.d(TAG, "Successfully bound to MediaService, MediaController ready");
553
554 } else {
555 Log_OC.e(TAG, "Unexpected response from MediaService while binding");
556 }
557 }
558 }
559 }
560
561 private void prepareMediaController() {
562 mMediaServiceBinder.registerMediaController(mMediaController);
563 if (mMediaController != null) {
564 mMediaController.setMediaPlayer(mMediaServiceBinder);
565 mMediaController.setEnabled(true);
566 mMediaController.updatePausePlay();
567 }
568 }
569
570 @Override
571 public void onServiceDisconnected(ComponentName component) {
572 if (component.equals(new ComponentName(getSherlockActivity(), MediaService.class))) {
573 Log_OC.e(TAG, "Media service suddenly disconnected");
574 if (mMediaController != null) {
575 mMediaController.setMediaPlayer(null);
576 } else {
577 Toast.makeText(getSherlockActivity(), "No media controller to release when disconnected from media service", Toast.LENGTH_SHORT).show();
578 }
579 mMediaServiceBinder = null;
580 mMediaServiceConnection = null;
581 }
582 }
583 }
584
585
586
587 /**
588 * Opens the previewed file with an external application.
589 */
590 private void openFile() {
591 stopPreview(true);
592 mContainerActivity.getFileOperationsHelper().openFile(getFile());
593 finish();
594 }
595
596 /**
597 * Helper method to test if an {@link OCFile} can be passed to a {@link PreviewMediaFragment} to be previewed.
598 *
599 * @param file File to test if can be previewed.
600 * @return 'True' if the file can be handled by the fragment.
601 */
602 public static boolean canBePreviewed(OCFile file) {
603 return (file != null && (file.isAudio() || file.isVideo()));
604 }
605
606
607 public void stopPreview(boolean stopAudio) {
608 OCFile file = getFile();
609 if (file.isAudio() && stopAudio) {
610 mMediaServiceBinder.pause();
611
612 } else if (file.isVideo()) {
613 mVideoPreview.stopPlayback();
614 }
615 }
616
617
618
619 /**
620 * Finishes the preview
621 */
622 private void finish() {
623 getSherlockActivity().onBackPressed();
624 }
625
626
627 public int getPosition() {
628 if (mPrepared) {
629 mSavedPlaybackPosition = mVideoPreview.getCurrentPosition();
630 }
631 Log_OC.e(TAG, "getting position: " + mSavedPlaybackPosition);
632 return mSavedPlaybackPosition;
633 }
634
635 public boolean isPlaying() {
636 if (mPrepared) {
637 mAutoplay = mVideoPreview.isPlaying();
638 }
639 return mAutoplay;
640 }
641
642 }