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