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