Refactored menu filtering in a single class FileMenuFilter
[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
250 inflater.inflate(R.menu.file_actions_menu, menu);
251 /*
252 List<Integer> toHide = new ArrayList<Integer>();
253
254 MenuItem item = null;
255 toHide.add(R.id.action_cancel_download);
256 toHide.add(R.id.action_cancel_upload);
257 toHide.add(R.id.action_download_file);
258 toHide.add(R.id.action_sync_file);
259 toHide.add(R.id.action_rename_file); // by now
260
261 // Options shareLink
262 if (!getFile().isShareByLink()) {
263 toHide.add(R.id.action_unshare_file);
264 }
265
266 // Send file
267 boolean sendEnabled = getString(R.string.send_files_to_other_apps).equalsIgnoreCase("on");
268 if (!sendEnabled) {
269 toHide.add(R.id.action_send_file);
270 }
271
272 for (int i : toHide) {
273 item = menu.findItem(i);
274 if (item != null) {
275 item.setVisible(false);
276 item.setEnabled(false);
277 }
278 }
279 */
280
281 }
282
283
284 /**
285 * {@inheritDoc}
286 */
287 @Override
288 public void onPrepareOptionsMenu(Menu menu) {
289 super.onPrepareOptionsMenu(menu);
290
291 FileMenuFilter mf = new FileMenuFilter();
292 mf.setFile(getFile());
293 mf.setComponentGetter(mContainerActivity);
294 mf.setAccount(mContainerActivity.getStorageManager().getAccount());
295 mf.setContext(getSherlockActivity());
296 mf.setFragment(this);
297 mf.filter(menu);
298
299 /*
300 MenuItem item = menu.findItem(R.id.action_unshare_file);
301 // Options shareLink
302 if (!getFile().isShareByLink()) {
303 item.setVisible(false);
304 item.setEnabled(false);
305 } else {
306 item.setVisible(true);
307 item.setEnabled(true);
308 }
309 */
310 }
311
312
313 /**
314 * {@inheritDoc}
315 */
316 @Override
317 public boolean onOptionsItemSelected(MenuItem item) {
318 switch (item.getItemId()) {
319 case R.id.action_share_file: {
320 shareFileWithLink();
321 return true;
322 }
323 case R.id.action_unshare_file: {
324 unshareFileWithLink();
325 return true;
326 }
327 case R.id.action_open_file_with: {
328 openFile();
329 return true;
330 }
331 case R.id.action_remove_file: {
332 removeFile();
333 return true;
334 }
335 case R.id.action_see_details: {
336 seeDetails();
337 return true;
338 }
339 case R.id.action_send_file: {
340 sendFile();
341 }
342
343 default:
344 return false;
345 }
346 }
347
348
349
350 /**
351 * Update the file of the fragment with file value
352 * @param file
353 */
354 public void updateFile(OCFile file){
355 setFile(file);
356 }
357
358 private void unshareFileWithLink() {
359 stopPreview(false);
360 mContainerActivity.getFileOperationsHelper().unshareFileWithLink(getFile());
361 }
362
363 private void shareFileWithLink() {
364 stopPreview(false);
365 mContainerActivity.getFileOperationsHelper().shareFileWithLink(getFile());
366
367 }
368
369 private void sendFile() {
370 stopPreview(false);
371 mContainerActivity.getFileOperationsHelper().sendDownloadedFile(getFile());
372
373 }
374
375 private void seeDetails() {
376 stopPreview(false);
377 mContainerActivity.showDetails(getFile());
378 }
379
380
381 private void prepareVideo() {
382 // create helper to get more control on the playback
383 mVideoHelper = new VideoHelper();
384 mVideoPreview.setOnPreparedListener(mVideoHelper);
385 mVideoPreview.setOnCompletionListener(mVideoHelper);
386 mVideoPreview.setOnErrorListener(mVideoHelper);
387 }
388
389 private void playVideo() {
390 // create and prepare control panel for the user
391 mMediaController.setMediaPlayer(mVideoPreview);
392
393 // load the video file in the video player ; when done, VideoHelper#onPrepared() will be called
394 mVideoPreview.setVideoPath(getFile().getStoragePath());
395 }
396
397
398 private class VideoHelper implements OnCompletionListener, OnPreparedListener, OnErrorListener {
399
400 /**
401 * Called when the file is ready to be played.
402 *
403 * Just starts the playback.
404 *
405 * @param mp {@link MediaPlayer} instance performing the playback.
406 */
407 @Override
408 public void onPrepared(MediaPlayer vp) {
409 Log_OC.e(TAG, "onPrepared");
410 mVideoPreview.seekTo(mSavedPlaybackPosition);
411 if (mAutoplay) {
412 mVideoPreview.start();
413 }
414 mMediaController.setEnabled(true);
415 mMediaController.updatePausePlay();
416 mPrepared = true;
417 }
418
419
420 /**
421 * Called when the file is finished playing.
422 *
423 * Finishes the activity.
424 *
425 * @param mp {@link MediaPlayer} instance performing the playback.
426 */
427 @Override
428 public void onCompletion(MediaPlayer mp) {
429 Log_OC.e(TAG, "completed");
430 if (mp != null) {
431 mVideoPreview.seekTo(0);
432 // next lines are necessary to work around undesired video loops
433 if (Build.VERSION.SDK_INT == Build.VERSION_CODES.GINGERBREAD) {
434 mVideoPreview.pause();
435
436 } else if (Build.VERSION.SDK_INT == Build.VERSION_CODES.GINGERBREAD_MR1) {
437 // mVideePreview.pause() is not enough
438
439 mMediaController.setEnabled(false);
440 mVideoPreview.stopPlayback();
441 mAutoplay = false;
442 mSavedPlaybackPosition = 0;
443 mVideoPreview.setVideoPath(getFile().getStoragePath());
444 }
445 } // else : called from onError()
446 mMediaController.updatePausePlay();
447 }
448
449
450 /**
451 * Called when an error in playback occurs.
452 *
453 * @param mp {@link MediaPlayer} instance performing the playback.
454 * @param what Type of error
455 * @param extra Extra code specific to the error
456 */
457 @Override
458 public boolean onError(MediaPlayer mp, int what, int extra) {
459 if (mVideoPreview.getWindowToken() != null) {
460 String message = MediaService.getMessageForMediaError(getSherlockActivity(), what, extra);
461 new AlertDialog.Builder(getSherlockActivity())
462 .setMessage(message)
463 .setPositiveButton(android.R.string.VideoView_error_button,
464 new DialogInterface.OnClickListener() {
465 public void onClick(DialogInterface dialog, int whichButton) {
466 dialog.dismiss();
467 VideoHelper.this.onCompletion(null);
468 }
469 })
470 .setCancelable(false)
471 .show();
472 }
473 return true;
474 }
475
476 }
477
478
479 @Override
480 public void onPause() {
481 super.onPause();
482 Log_OC.e(TAG, "onPause");
483 }
484
485 @Override
486 public void onResume() {
487 super.onResume();
488 Log_OC.e(TAG, "onResume");
489 }
490
491 @Override
492 public void onDestroy() {
493 super.onDestroy();
494 Log_OC.e(TAG, "onDestroy");
495 }
496
497 @Override
498 public void onStop() {
499 Log_OC.e(TAG, "onStop");
500 super.onStop();
501
502 mPrepared = false;
503 if (mMediaServiceConnection != null) {
504 Log_OC.d(TAG, "Unbinding from MediaService ...");
505 if (mMediaServiceBinder != null && mMediaController != null) {
506 mMediaServiceBinder.unregisterMediaController(mMediaController);
507 }
508 getSherlockActivity().unbindService(mMediaServiceConnection);
509 mMediaServiceConnection = null;
510 mMediaServiceBinder = null;
511 }
512 }
513
514 @Override
515 public boolean onTouch(View v, MotionEvent event) {
516 if (event.getAction() == MotionEvent.ACTION_DOWN && v == mVideoPreview) {
517 startFullScreenVideo();
518 return true;
519 }
520 return false;
521 }
522
523
524 private void startFullScreenVideo() {
525 Intent i = new Intent(getSherlockActivity(), PreviewVideoActivity.class);
526 i.putExtra(FileActivity.EXTRA_ACCOUNT, mAccount);
527 i.putExtra(FileActivity.EXTRA_FILE, getFile());
528 i.putExtra(PreviewVideoActivity.EXTRA_AUTOPLAY, mVideoPreview.isPlaying());
529 mVideoPreview.pause();
530 i.putExtra(PreviewVideoActivity.EXTRA_START_POSITION, mVideoPreview.getCurrentPosition());
531 startActivityForResult(i, 0);
532 }
533
534 @Override
535 public void onConfigurationChanged (Configuration newConfig) {
536 Log_OC.e(TAG, "onConfigurationChanged " + this);
537 }
538
539 @Override
540 public void onActivityResult (int requestCode, int resultCode, Intent data) {
541 Log_OC.e(TAG, "onActivityResult " + this);
542 super.onActivityResult(requestCode, resultCode, data);
543 if (resultCode == Activity.RESULT_OK) {
544 mSavedPlaybackPosition = data.getExtras().getInt(PreviewVideoActivity.EXTRA_START_POSITION);
545 mAutoplay = data.getExtras().getBoolean(PreviewVideoActivity.EXTRA_AUTOPLAY);
546 }
547 }
548
549
550 private void playAudio() {
551 OCFile file = getFile();
552 if (!mMediaServiceBinder.isPlaying(file)) {
553 Log_OC.d(TAG, "starting playback of " + file.getStoragePath());
554 mMediaServiceBinder.start(mAccount, file, mAutoplay, mSavedPlaybackPosition);
555
556 } else {
557 if (!mMediaServiceBinder.isPlaying() && mAutoplay) {
558 mMediaServiceBinder.start();
559 mMediaController.updatePausePlay();
560 }
561 }
562 }
563
564
565 private void bindMediaService() {
566 Log_OC.d(TAG, "Binding to MediaService...");
567 if (mMediaServiceConnection == null) {
568 mMediaServiceConnection = new MediaServiceConnection();
569 }
570 getSherlockActivity().bindService( new Intent(getSherlockActivity(),
571 MediaService.class),
572 mMediaServiceConnection,
573 Context.BIND_AUTO_CREATE);
574 // follow the flow in MediaServiceConnection#onServiceConnected(...)
575 }
576
577 /** Defines callbacks for service binding, passed to bindService() */
578 private class MediaServiceConnection implements ServiceConnection {
579
580 @Override
581 public void onServiceConnected(ComponentName component, IBinder service) {
582 if (getSherlockActivity() != null) {
583 if (component.equals(new ComponentName(getSherlockActivity(), MediaService.class))) {
584 Log_OC.d(TAG, "Media service connected");
585 mMediaServiceBinder = (MediaServiceBinder) service;
586 if (mMediaServiceBinder != null) {
587 prepareMediaController();
588 playAudio(); // do not wait for the touch of nobody to play audio
589
590 Log_OC.d(TAG, "Successfully bound to MediaService, MediaController ready");
591
592 } else {
593 Log_OC.e(TAG, "Unexpected response from MediaService while binding");
594 }
595 }
596 }
597 }
598
599 private void prepareMediaController() {
600 mMediaServiceBinder.registerMediaController(mMediaController);
601 if (mMediaController != null) {
602 mMediaController.setMediaPlayer(mMediaServiceBinder);
603 mMediaController.setEnabled(true);
604 mMediaController.updatePausePlay();
605 }
606 }
607
608 @Override
609 public void onServiceDisconnected(ComponentName component) {
610 if (component.equals(new ComponentName(getSherlockActivity(), MediaService.class))) {
611 Log_OC.e(TAG, "Media service suddenly disconnected");
612 if (mMediaController != null) {
613 mMediaController.setMediaPlayer(null);
614 } else {
615 Toast.makeText(getSherlockActivity(), "No media controller to release when disconnected from media service", Toast.LENGTH_SHORT).show();
616 }
617 mMediaServiceBinder = null;
618 mMediaServiceConnection = null;
619 }
620 }
621 }
622
623
624
625 /**
626 * Opens the previewed file with an external application.
627 */
628 private void openFile() {
629 stopPreview(true);
630 mContainerActivity.getFileOperationsHelper().openFile(getFile());
631 finish();
632 }
633
634 /**
635 * Starts a the removal of the previewed file.
636 *
637 * Shows a confirmation dialog. The action continues in {@link #onConfirmation(String)} , {@link #onNeutral(String)} or {@link #onCancel(String)},
638 * depending upon the user selection in the dialog.
639 */
640 private void removeFile() {
641 ConfirmationDialogFragment confDialog = ConfirmationDialogFragment.newInstance(
642 R.string.confirmation_remove_alert,
643 new String[]{getFile().getFileName()},
644 R.string.confirmation_remove_remote_and_local,
645 R.string.confirmation_remove_local,
646 R.string.common_cancel);
647 confDialog.setOnConfirmationListener(this);
648 confDialog.show(getFragmentManager(), ConfirmationDialogFragment.FTAG_CONFIRMATION);
649 }
650
651
652 /**
653 * Performs the removal of the previewed file, both locally and in the server.
654 */
655 @Override
656 public void onConfirmation(String callerTag) {
657 OCFile file = getFile();
658 FileDataStorageManager storageManager = mContainerActivity.getStorageManager();
659 if (storageManager.getFileById(file.getFileId()) != null) { // check that the file is still there;
660 stopPreview(true);
661 mContainerActivity.getFileOperationsHelper().removeFile(file, true);
662 }
663 }
664
665
666 /**
667 * Removes the file from local storage
668 */
669 @Override
670 public void onNeutral(String callerTag) {
671 OCFile file = getFile();
672 stopPreview(true);
673 mContainerActivity.getStorageManager().removeFile(file, false, true); // TODO perform in background task / new thread
674 finish();
675 }
676
677 /**
678 * User cancelled the removal action.
679 */
680 @Override
681 public void onCancel(String callerTag) {
682 // nothing to do here
683 }
684
685
686 /**
687 * Helper method to test if an {@link OCFile} can be passed to a {@link PreviewMediaFragment} to be previewed.
688 *
689 * @param file File to test if can be previewed.
690 * @return 'True' if the file can be handled by the fragment.
691 */
692 public static boolean canBePreviewed(OCFile file) {
693 return (file != null && (file.isAudio() || file.isVideo()));
694 }
695
696
697 private void stopPreview(boolean stopAudio) {
698 OCFile file = getFile();
699 if (file.isAudio() && stopAudio) {
700 mMediaServiceBinder.pause();
701
702 } else if (file.isVideo()) {
703 mVideoPreview.stopPlayback();
704 }
705 }
706
707
708
709 /**
710 * Finishes the preview
711 */
712 private void finish() {
713 getSherlockActivity().onBackPressed();
714 }
715
716
717 public int getPosition() {
718 if (mPrepared) {
719 mSavedPlaybackPosition = mVideoPreview.getCurrentPosition();
720 }
721 Log_OC.e(TAG, "getting position: " + mSavedPlaybackPosition);
722 return mSavedPlaybackPosition;
723 }
724
725 public boolean isPlaying() {
726 if (mPrepared) {
727 mAutoplay = mVideoPreview.isPlaying();
728 }
729 return mAutoplay;
730 }
731
732 }