8762cbddfe5c30422a79747ead22877b0d09e7ea
[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.FileDataStorageManager;
49 import com.owncloud.android.datamodel.OCFile;
50 import com.owncloud.android.files.FileMenuFilter;
51 import com.owncloud.android.media.MediaControlView;
52 import com.owncloud.android.media.MediaService;
53 import com.owncloud.android.media.MediaServiceBinder;
54 import com.owncloud.android.ui.activity.FileActivity;
55 import com.owncloud.android.ui.dialog.ConfirmationDialogFragment;
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 ConfirmationDialogFragment.ConfirmationDialogFragmentListener {
72
73 public static final String EXTRA_FILE = "FILE";
74 public static final String EXTRA_ACCOUNT = "ACCOUNT";
75 private static final String EXTRA_PLAY_POSITION = "PLAY_POSITION";
76 private static final String EXTRA_PLAYING = "PLAYING";
77
78 private View mView;
79 private Account mAccount;
80 private ImageView mImagePreview;
81 private VideoView mVideoPreview;
82 private int mSavedPlaybackPosition;
83
84 private MediaServiceBinder mMediaServiceBinder = null;
85 private MediaControlView mMediaController = null;
86 private MediaServiceConnection mMediaServiceConnection = null;
87 private VideoHelper mVideoHelper;
88 private boolean mAutoplay;
89 public boolean mPrepared;
90
91 private static final String TAG = PreviewMediaFragment.class.getSimpleName();
92
93
94 /**
95 * Creates a fragment to preview a file.
96 *
97 * When 'fileToDetail' or 'ocAccount' are null
98 *
99 * @param fileToDetail An {@link OCFile} to preview in the fragment
100 * @param ocAccount An ownCloud account; needed to start downloads
101 */
102 public PreviewMediaFragment(OCFile fileToDetail, Account ocAccount, int startPlaybackPosition, boolean autoplay) {
103 super(fileToDetail);
104 mAccount = ocAccount;
105 mSavedPlaybackPosition = startPlaybackPosition;
106 mAutoplay = autoplay;
107 }
108
109
110 /**
111 * Creates an empty fragment for previews.
112 *
113 * MUST BE KEPT: the system uses it when tries to reinstantiate a fragment automatically (for instance, when the device is turned a aside).
114 *
115 * DO NOT CALL IT: an {@link OCFile} and {@link Account} must be provided for a successful construction
116 */
117 public PreviewMediaFragment() {
118 super();
119 mAccount = null;
120 mSavedPlaybackPosition = 0;
121 mAutoplay = true;
122 }
123
124
125 /**
126 * {@inheritDoc}
127 */
128 @Override
129 public void onCreate(Bundle savedInstanceState) {
130 super.onCreate(savedInstanceState);
131 setHasOptionsMenu(true);
132 }
133
134
135 /**
136 * {@inheritDoc}
137 */
138 @Override
139 public View onCreateView(LayoutInflater inflater, ViewGroup container,
140 Bundle savedInstanceState) {
141 super.onCreateView(inflater, container, savedInstanceState);
142 Log_OC.e(TAG, "onCreateView");
143
144
145 mView = inflater.inflate(R.layout.file_preview, container, false);
146
147 mImagePreview = (ImageView)mView.findViewById(R.id.image_preview);
148 mVideoPreview = (VideoView)mView.findViewById(R.id.video_preview);
149 mVideoPreview.setOnTouchListener(this);
150
151 mMediaController = (MediaControlView)mView.findViewById(R.id.media_controller);
152
153 return mView;
154 }
155
156
157 /**
158 * {@inheritDoc}
159 */
160 @Override
161 public void onActivityCreated(Bundle savedInstanceState) {
162 super.onActivityCreated(savedInstanceState);
163 Log_OC.e(TAG, "onActivityCreated");
164
165 if (savedInstanceState != null) {
166 setFile((OCFile)savedInstanceState.getParcelable(PreviewMediaFragment.EXTRA_FILE));
167 mAccount = savedInstanceState.getParcelable(PreviewMediaFragment.EXTRA_ACCOUNT);
168 mSavedPlaybackPosition = savedInstanceState.getInt(PreviewMediaFragment.EXTRA_PLAY_POSITION);
169 mAutoplay = savedInstanceState.getBoolean(PreviewMediaFragment.EXTRA_PLAYING);
170
171 }
172 OCFile file = getFile();
173 if (file == null) {
174 throw new IllegalStateException("Instanced with a NULL OCFile");
175 }
176 if (mAccount == null) {
177 throw new IllegalStateException("Instanced with a NULL ownCloud Account");
178 }
179 if (!file.isDown()) {
180 throw new IllegalStateException("There is no local file to preview");
181 }
182 if (file.isVideo()) {
183 mVideoPreview.setVisibility(View.VISIBLE);
184 mImagePreview.setVisibility(View.GONE);
185 prepareVideo();
186
187 } else {
188 mVideoPreview.setVisibility(View.GONE);
189 mImagePreview.setVisibility(View.VISIBLE);
190 }
191
192 }
193
194
195 /**
196 * {@inheritDoc}
197 */
198 @Override
199 public void onSaveInstanceState(Bundle outState) {
200 super.onSaveInstanceState(outState);
201 Log_OC.e(TAG, "onSaveInstanceState");
202
203 outState.putParcelable(PreviewMediaFragment.EXTRA_FILE, getFile());
204 outState.putParcelable(PreviewMediaFragment.EXTRA_ACCOUNT, mAccount);
205
206 if (getFile().isVideo()) {
207 mSavedPlaybackPosition = mVideoPreview.getCurrentPosition();
208 mAutoplay = mVideoPreview.isPlaying();
209 outState.putInt(PreviewMediaFragment.EXTRA_PLAY_POSITION , mSavedPlaybackPosition);
210 outState.putBoolean(PreviewMediaFragment.EXTRA_PLAYING , mAutoplay);
211 } else {
212 outState.putInt(PreviewMediaFragment.EXTRA_PLAY_POSITION , mMediaServiceBinder.getCurrentPosition());
213 outState.putBoolean(PreviewMediaFragment.EXTRA_PLAYING , mMediaServiceBinder.isPlaying());
214 }
215 }
216
217
218 @Override
219 public void onStart() {
220 super.onStart();
221 Log_OC.e(TAG, "onStart");
222
223 OCFile file = getFile();
224 if (file != null) {
225 if (file.isAudio()) {
226 bindMediaService();
227
228 } else if (file.isVideo()) {
229 stopAudio();
230 playVideo();
231 }
232 }
233 }
234
235
236 private void stopAudio() {
237 Intent i = new Intent(getSherlockActivity(), MediaService.class);
238 i.setAction(MediaService.ACTION_STOP_ALL);
239 getSherlockActivity().startService(i);
240 }
241
242
243 /**
244 * {@inheritDoc}
245 */
246 @Override
247 public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
248 super.onCreateOptionsMenu(menu, inflater);
249 inflater.inflate(R.menu.file_actions_menu, menu);
250 }
251
252
253 /**
254 * {@inheritDoc}
255 */
256 @Override
257 public void onPrepareOptionsMenu(Menu menu) {
258 super.onPrepareOptionsMenu(menu);
259
260 FileMenuFilter mf = new FileMenuFilter(
261 getFile(),
262 mContainerActivity.getStorageManager().getAccount(),
263 mContainerActivity,
264 getSherlockActivity()
265 );
266 mf.filter(menu);
267
268 // additional restriction for this fragment
269 // TODO allow renaming in PreviewImageFragment
270 MenuItem item = menu.findItem(R.id.action_rename_file);
271 if (item != null) {
272 item.setVisible(false);
273 item.setEnabled(false);
274 }
275 }
276
277
278 /**
279 * {@inheritDoc}
280 */
281 @Override
282 public boolean onOptionsItemSelected(MenuItem item) {
283 switch (item.getItemId()) {
284 case R.id.action_share_file: {
285 stopPreview(false);
286 mContainerActivity.getFileOperationsHelper().shareFileWithLink(getFile());
287 return true;
288 }
289 case R.id.action_unshare_file: {
290 stopPreview(false);
291 mContainerActivity.getFileOperationsHelper().unshareFileWithLink(getFile());
292 return true;
293 }
294 case R.id.action_open_file_with: {
295 openFile();
296 return true;
297 }
298 case R.id.action_remove_file: {
299 removeFile();
300 return true;
301 }
302 case R.id.action_see_details: {
303 seeDetails();
304 return true;
305 }
306 case R.id.action_send_file: {
307 sendFile();
308 }
309 case R.id.action_sync_file: {
310 mContainerActivity.getFileOperationsHelper().syncFile(getFile());
311 return true;
312 }
313
314 default:
315 return false;
316 }
317 }
318
319
320
321 /**
322 * Update the file of the fragment with file value
323 * @param file
324 */
325 public void updateFile(OCFile file){
326 setFile(file);
327 }
328
329 private void sendFile() {
330 stopPreview(false);
331 mContainerActivity.getFileOperationsHelper().sendDownloadedFile(getFile());
332
333 }
334
335 private void seeDetails() {
336 stopPreview(false);
337 mContainerActivity.showDetails(getFile());
338 }
339
340
341 private void prepareVideo() {
342 // create helper to get more control on the playback
343 mVideoHelper = new VideoHelper();
344 mVideoPreview.setOnPreparedListener(mVideoHelper);
345 mVideoPreview.setOnCompletionListener(mVideoHelper);
346 mVideoPreview.setOnErrorListener(mVideoHelper);
347 }
348
349 private void playVideo() {
350 // create and prepare control panel for the user
351 mMediaController.setMediaPlayer(mVideoPreview);
352
353 // load the video file in the video player ; when done, VideoHelper#onPrepared() will be called
354 mVideoPreview.setVideoPath(getFile().getStoragePath());
355 }
356
357
358 private class VideoHelper implements OnCompletionListener, OnPreparedListener, OnErrorListener {
359
360 /**
361 * Called when the file is ready to be played.
362 *
363 * Just starts the playback.
364 *
365 * @param mp {@link MediaPlayer} instance performing the playback.
366 */
367 @Override
368 public void onPrepared(MediaPlayer vp) {
369 Log_OC.e(TAG, "onPrepared");
370 mVideoPreview.seekTo(mSavedPlaybackPosition);
371 if (mAutoplay) {
372 mVideoPreview.start();
373 }
374 mMediaController.setEnabled(true);
375 mMediaController.updatePausePlay();
376 mPrepared = true;
377 }
378
379
380 /**
381 * Called when the file is finished playing.
382 *
383 * Finishes the activity.
384 *
385 * @param mp {@link MediaPlayer} instance performing the playback.
386 */
387 @Override
388 public void onCompletion(MediaPlayer mp) {
389 Log_OC.e(TAG, "completed");
390 if (mp != null) {
391 mVideoPreview.seekTo(0);
392 // next lines are necessary to work around undesired video loops
393 if (Build.VERSION.SDK_INT == Build.VERSION_CODES.GINGERBREAD) {
394 mVideoPreview.pause();
395
396 } else if (Build.VERSION.SDK_INT == Build.VERSION_CODES.GINGERBREAD_MR1) {
397 // mVideePreview.pause() is not enough
398
399 mMediaController.setEnabled(false);
400 mVideoPreview.stopPlayback();
401 mAutoplay = false;
402 mSavedPlaybackPosition = 0;
403 mVideoPreview.setVideoPath(getFile().getStoragePath());
404 }
405 } // else : called from onError()
406 mMediaController.updatePausePlay();
407 }
408
409
410 /**
411 * Called when an error in playback occurs.
412 *
413 * @param mp {@link MediaPlayer} instance performing the playback.
414 * @param what Type of error
415 * @param extra Extra code specific to the error
416 */
417 @Override
418 public boolean onError(MediaPlayer mp, int what, int extra) {
419 if (mVideoPreview.getWindowToken() != null) {
420 String message = MediaService.getMessageForMediaError(getSherlockActivity(), what, extra);
421 new AlertDialog.Builder(getSherlockActivity())
422 .setMessage(message)
423 .setPositiveButton(android.R.string.VideoView_error_button,
424 new DialogInterface.OnClickListener() {
425 public void onClick(DialogInterface dialog, int whichButton) {
426 dialog.dismiss();
427 VideoHelper.this.onCompletion(null);
428 }
429 })
430 .setCancelable(false)
431 .show();
432 }
433 return true;
434 }
435
436 }
437
438
439 @Override
440 public void onPause() {
441 super.onPause();
442 Log_OC.e(TAG, "onPause");
443 }
444
445 @Override
446 public void onResume() {
447 super.onResume();
448 Log_OC.e(TAG, "onResume");
449 }
450
451 @Override
452 public void onDestroy() {
453 super.onDestroy();
454 Log_OC.e(TAG, "onDestroy");
455 }
456
457 @Override
458 public void onStop() {
459 Log_OC.e(TAG, "onStop");
460 super.onStop();
461
462 mPrepared = false;
463 if (mMediaServiceConnection != null) {
464 Log_OC.d(TAG, "Unbinding from MediaService ...");
465 if (mMediaServiceBinder != null && mMediaController != null) {
466 mMediaServiceBinder.unregisterMediaController(mMediaController);
467 }
468 getSherlockActivity().unbindService(mMediaServiceConnection);
469 mMediaServiceConnection = null;
470 mMediaServiceBinder = null;
471 }
472 }
473
474 @Override
475 public boolean onTouch(View v, MotionEvent event) {
476 if (event.getAction() == MotionEvent.ACTION_DOWN && v == mVideoPreview) {
477 startFullScreenVideo();
478 return true;
479 }
480 return false;
481 }
482
483
484 private void startFullScreenVideo() {
485 Intent i = new Intent(getSherlockActivity(), PreviewVideoActivity.class);
486 i.putExtra(FileActivity.EXTRA_ACCOUNT, mAccount);
487 i.putExtra(FileActivity.EXTRA_FILE, getFile());
488 i.putExtra(PreviewVideoActivity.EXTRA_AUTOPLAY, mVideoPreview.isPlaying());
489 mVideoPreview.pause();
490 i.putExtra(PreviewVideoActivity.EXTRA_START_POSITION, mVideoPreview.getCurrentPosition());
491 startActivityForResult(i, 0);
492 }
493
494 @Override
495 public void onConfigurationChanged (Configuration newConfig) {
496 Log_OC.e(TAG, "onConfigurationChanged " + this);
497 }
498
499 @Override
500 public void onActivityResult (int requestCode, int resultCode, Intent data) {
501 Log_OC.e(TAG, "onActivityResult " + this);
502 super.onActivityResult(requestCode, resultCode, data);
503 if (resultCode == Activity.RESULT_OK) {
504 mSavedPlaybackPosition = data.getExtras().getInt(PreviewVideoActivity.EXTRA_START_POSITION);
505 mAutoplay = data.getExtras().getBoolean(PreviewVideoActivity.EXTRA_AUTOPLAY);
506 }
507 }
508
509
510 private void playAudio() {
511 OCFile file = getFile();
512 if (!mMediaServiceBinder.isPlaying(file)) {
513 Log_OC.d(TAG, "starting playback of " + file.getStoragePath());
514 mMediaServiceBinder.start(mAccount, file, mAutoplay, mSavedPlaybackPosition);
515
516 } else {
517 if (!mMediaServiceBinder.isPlaying() && mAutoplay) {
518 mMediaServiceBinder.start();
519 mMediaController.updatePausePlay();
520 }
521 }
522 }
523
524
525 private void bindMediaService() {
526 Log_OC.d(TAG, "Binding to MediaService...");
527 if (mMediaServiceConnection == null) {
528 mMediaServiceConnection = new MediaServiceConnection();
529 }
530 getSherlockActivity().bindService( new Intent(getSherlockActivity(),
531 MediaService.class),
532 mMediaServiceConnection,
533 Context.BIND_AUTO_CREATE);
534 // follow the flow in MediaServiceConnection#onServiceConnected(...)
535 }
536
537 /** Defines callbacks for service binding, passed to bindService() */
538 private class MediaServiceConnection implements ServiceConnection {
539
540 @Override
541 public void onServiceConnected(ComponentName component, IBinder service) {
542 if (getSherlockActivity() != null) {
543 if (component.equals(new ComponentName(getSherlockActivity(), MediaService.class))) {
544 Log_OC.d(TAG, "Media service connected");
545 mMediaServiceBinder = (MediaServiceBinder) service;
546 if (mMediaServiceBinder != null) {
547 prepareMediaController();
548 playAudio(); // do not wait for the touch of nobody to play audio
549
550 Log_OC.d(TAG, "Successfully bound to MediaService, MediaController ready");
551
552 } else {
553 Log_OC.e(TAG, "Unexpected response from MediaService while binding");
554 }
555 }
556 }
557 }
558
559 private void prepareMediaController() {
560 mMediaServiceBinder.registerMediaController(mMediaController);
561 if (mMediaController != null) {
562 mMediaController.setMediaPlayer(mMediaServiceBinder);
563 mMediaController.setEnabled(true);
564 mMediaController.updatePausePlay();
565 }
566 }
567
568 @Override
569 public void onServiceDisconnected(ComponentName component) {
570 if (component.equals(new ComponentName(getSherlockActivity(), MediaService.class))) {
571 Log_OC.e(TAG, "Media service suddenly disconnected");
572 if (mMediaController != null) {
573 mMediaController.setMediaPlayer(null);
574 } else {
575 Toast.makeText(getSherlockActivity(), "No media controller to release when disconnected from media service", Toast.LENGTH_SHORT).show();
576 }
577 mMediaServiceBinder = null;
578 mMediaServiceConnection = null;
579 }
580 }
581 }
582
583
584
585 /**
586 * Opens the previewed file with an external application.
587 */
588 private void openFile() {
589 stopPreview(true);
590 mContainerActivity.getFileOperationsHelper().openFile(getFile());
591 finish();
592 }
593
594 /**
595 * Starts a the removal of the previewed file.
596 *
597 * Shows a confirmation dialog. The action continues in {@link #onConfirmation(String)} , {@link #onNeutral(String)} or {@link #onCancel(String)},
598 * depending upon the user selection in the dialog.
599 */
600 private void removeFile() {
601 ConfirmationDialogFragment confDialog = ConfirmationDialogFragment.newInstance(
602 R.string.confirmation_remove_alert,
603 new String[]{getFile().getFileName()},
604 R.string.confirmation_remove_remote_and_local,
605 R.string.confirmation_remove_local,
606 R.string.common_cancel);
607 confDialog.setOnConfirmationListener(this);
608 confDialog.show(getFragmentManager(), ConfirmationDialogFragment.FTAG_CONFIRMATION);
609 }
610
611
612 /**
613 * Performs the removal of the previewed file, both locally and in the server.
614 */
615 @Override
616 public void onConfirmation(String callerTag) {
617 OCFile file = getFile();
618 FileDataStorageManager storageManager = mContainerActivity.getStorageManager();
619 if (storageManager.getFileById(file.getFileId()) != null) { // check that the file is still there;
620 stopPreview(true);
621 mContainerActivity.getFileOperationsHelper().removeFile(file, true);
622 }
623 }
624
625
626 /**
627 * Removes the file from local storage
628 */
629 @Override
630 public void onNeutral(String callerTag) {
631 OCFile file = getFile();
632 stopPreview(true);
633 mContainerActivity.getStorageManager().removeFile(file, false, true); // TODO perform in background task / new thread
634 finish();
635 }
636
637 /**
638 * User cancelled the removal action.
639 */
640 @Override
641 public void onCancel(String callerTag) {
642 // nothing to do here
643 }
644
645
646 /**
647 * Helper method to test if an {@link OCFile} can be passed to a {@link PreviewMediaFragment} to be previewed.
648 *
649 * @param file File to test if can be previewed.
650 * @return 'True' if the file can be handled by the fragment.
651 */
652 public static boolean canBePreviewed(OCFile file) {
653 return (file != null && (file.isAudio() || file.isVideo()));
654 }
655
656
657 private void stopPreview(boolean stopAudio) {
658 OCFile file = getFile();
659 if (file.isAudio() && stopAudio) {
660 mMediaServiceBinder.pause();
661
662 } else if (file.isVideo()) {
663 mVideoPreview.stopPlayback();
664 }
665 }
666
667
668
669 /**
670 * Finishes the preview
671 */
672 private void finish() {
673 getSherlockActivity().onBackPressed();
674 }
675
676
677 public int getPosition() {
678 if (mPrepared) {
679 mSavedPlaybackPosition = mVideoPreview.getCurrentPosition();
680 }
681 Log_OC.e(TAG, "getting position: " + mSavedPlaybackPosition);
682 return mSavedPlaybackPosition;
683 }
684
685 public boolean isPlaying() {
686 if (mPrepared) {
687 mAutoplay = mVideoPreview.isPlaying();
688 }
689 return mAutoplay;
690 }
691
692 }