d427bfdac44f85c2de2890044205a63c60f11ea6
[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 as published by
6 * the Free Software Foundation, either version 2 of the License, or
7 * (at your option) any later version.
8 *
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
13 *
14 * You should have received a copy of the GNU General Public License
15 * along with this program. If not, see <http://www.gnu.org/licenses/>.
16 *
17 */
18 package com.owncloud.android.ui.preview;
19
20 import java.io.File;
21 import java.util.ArrayList;
22 import java.util.List;
23
24 import android.accounts.Account;
25 import android.app.Activity;
26 import android.app.AlertDialog;
27 import android.content.ActivityNotFoundException;
28 import android.content.ComponentName;
29 import android.content.Context;
30 import android.content.DialogInterface;
31 import android.content.Intent;
32 import android.content.ServiceConnection;
33 import android.media.MediaPlayer;
34 import android.media.MediaPlayer.OnCompletionListener;
35 import android.media.MediaPlayer.OnErrorListener;
36 import android.media.MediaPlayer.OnPreparedListener;
37 import android.net.Uri;
38 import android.os.Bundle;
39 import android.os.Handler;
40 import android.os.IBinder;
41 import android.support.v4.app.FragmentTransaction;
42 import android.util.Log;
43 import android.view.LayoutInflater;
44 import android.view.MotionEvent;
45 import android.view.View;
46 import android.view.View.OnTouchListener;
47 import android.view.ViewGroup;
48 import android.webkit.MimeTypeMap;
49 import android.widget.ImageView;
50 import android.widget.MediaController;
51 import android.widget.Toast;
52 import android.widget.VideoView;
53
54 import com.actionbarsherlock.app.SherlockFragment;
55 import com.actionbarsherlock.view.Menu;
56 import com.actionbarsherlock.view.MenuInflater;
57 import com.actionbarsherlock.view.MenuItem;
58 import com.owncloud.android.datamodel.FileDataStorageManager;
59 import com.owncloud.android.datamodel.OCFile;
60 import com.owncloud.android.media.MediaService;
61 import com.owncloud.android.media.MediaServiceBinder;
62 import com.owncloud.android.network.OwnCloudClientUtils;
63 import com.owncloud.android.operations.OnRemoteOperationListener;
64 import com.owncloud.android.operations.RemoteOperation;
65 import com.owncloud.android.operations.RemoteOperationResult;
66 import com.owncloud.android.operations.RemoveFileOperation;
67 import com.owncloud.android.ui.activity.FileDetailActivity;
68 import com.owncloud.android.ui.activity.FileDisplayActivity;
69 import com.owncloud.android.ui.activity.TransferServiceGetter;
70 import com.owncloud.android.ui.fragment.ConfirmationDialogFragment;
71 import com.owncloud.android.ui.fragment.FileDetailFragment;
72 import com.owncloud.android.ui.fragment.FileFragment;
73
74 import com.owncloud.android.R;
75 import eu.alefzero.webdav.WebdavClient;
76 import eu.alefzero.webdav.WebdavUtils;
77
78 /**
79 * This fragment shows a preview of a downloaded media file (audio or video).
80 *
81 * Trying to get an instance with NULL {@link OCFile} or ownCloud {@link Account} values will produce an {@link IllegalStateException}.
82 *
83 * By now, if the {@link OCFile} passed is not downloaded, an {@link IllegalStateException} is generated on instantiation too.
84 *
85 * @author David A. Velasco
86 */
87 public class PreviewMediaFragment extends SherlockFragment implements
88 OnTouchListener , FileFragment,
89 ConfirmationDialogFragment.ConfirmationDialogFragmentListener, OnRemoteOperationListener {
90
91 public static final String EXTRA_FILE = "FILE";
92 public static final String EXTRA_ACCOUNT = "ACCOUNT";
93 private static final String EXTRA_PLAY_POSITION = "PLAY_POSITION";
94
95 private View mView;
96 private OCFile mFile;
97 private Account mAccount;
98 private FileDataStorageManager mStorageManager;
99 private ImageView mImagePreview;
100 private VideoView mVideoPreview;
101 private int mSavedPlaybackPosition;
102
103 private Handler mHandler;
104 private RemoteOperation mLastRemoteOperation;
105
106 private MediaServiceBinder mMediaServiceBinder = null;
107 private MediaController mMediaController = null;
108 private MediaServiceConnection mMediaServiceConnection = null;
109 private VideoHelper mVideoHelper;
110
111 private static final String TAG = PreviewMediaFragment.class.getSimpleName();
112
113
114 /**
115 * Creates a fragment to preview a file.
116 *
117 * When 'fileToDetail' or 'ocAccount' are null
118 *
119 * @param fileToDetail An {@link OCFile} to preview in the fragment
120 * @param ocAccount An ownCloud account; needed to start downloads
121 */
122 public PreviewMediaFragment(OCFile fileToDetail, Account ocAccount) {
123 mFile = fileToDetail;
124 mAccount = ocAccount;
125 mSavedPlaybackPosition = 0;
126 mStorageManager = null; // we need a context to init this; the container activity is not available yet at this moment
127 }
128
129
130 /**
131 * Creates an empty fragment for previews.
132 *
133 * MUST BE KEPT: the system uses it when tries to reinstantiate a fragment automatically (for instance, when the device is turned a aside).
134 *
135 * DO NOT CALL IT: an {@link OCFile} and {@link Account} must be provided for a successful construction
136 */
137 public PreviewMediaFragment() {
138 mFile = null;
139 mAccount = null;
140 mSavedPlaybackPosition = 0;
141 mStorageManager = null;
142 }
143
144
145 /**
146 * {@inheritDoc}
147 */
148 @Override
149 public void onCreate(Bundle savedInstanceState) {
150 super.onCreate(savedInstanceState);
151 mHandler = new Handler();
152 setHasOptionsMenu(true);
153 }
154
155
156 /**
157 * {@inheritDoc}
158 */
159 @Override
160 public View onCreateView(LayoutInflater inflater, ViewGroup container,
161 Bundle savedInstanceState) {
162 super.onCreateView(inflater, container, savedInstanceState);
163
164 mView = inflater.inflate(R.layout.file_preview, container, false);
165
166 mImagePreview = (ImageView)mView.findViewById(R.id.image_preview);
167 mImagePreview.setOnTouchListener(this);
168 mVideoPreview = (VideoView)mView.findViewById(R.id.video_preview);
169
170 //updateFileDetails(false);
171 return mView;
172 }
173
174
175 /**
176 * {@inheritDoc}
177 */
178 @Override
179 public void onAttach(Activity activity) {
180 super.onAttach(activity);
181 if (!(activity instanceof FileFragment.ContainerActivity))
182 throw new ClassCastException(activity.toString() + " must implement " + FileFragment.ContainerActivity.class.getSimpleName());
183 }
184
185
186 /**
187 * {@inheritDoc}
188 */
189 @Override
190 public void onActivityCreated(Bundle savedInstanceState) {
191 super.onActivityCreated(savedInstanceState);
192
193 mStorageManager = new FileDataStorageManager(mAccount, getActivity().getApplicationContext().getContentResolver());
194 if (savedInstanceState != null) {
195 mFile = savedInstanceState.getParcelable(PreviewMediaFragment.EXTRA_FILE);
196 mAccount = savedInstanceState.getParcelable(PreviewMediaFragment.EXTRA_ACCOUNT);
197 mSavedPlaybackPosition = savedInstanceState.getInt(PreviewMediaFragment.EXTRA_PLAY_POSITION);
198
199 }
200 if (mFile == null) {
201 throw new IllegalStateException("Instanced with a NULL OCFile");
202 }
203 if (mAccount == null) {
204 throw new IllegalStateException("Instanced with a NULL ownCloud Account");
205 }
206 if (!mFile.isDown()) {
207 throw new IllegalStateException("There is no local file to preview");
208 }
209 if (mFile.isVideo()) {
210 mVideoPreview.setVisibility(View.VISIBLE);
211 mImagePreview.setVisibility(View.GONE);
212 prepareVideo();
213
214 } else {
215 mVideoPreview.setVisibility(View.GONE);
216 mImagePreview.setVisibility(View.VISIBLE);
217 }
218
219 }
220
221
222 /**
223 * {@inheritDoc}
224 */
225 @Override
226 public void onSaveInstanceState(Bundle outState) {
227 super.onSaveInstanceState(outState);
228
229 outState.putParcelable(PreviewMediaFragment.EXTRA_FILE, mFile);
230 outState.putParcelable(PreviewMediaFragment.EXTRA_ACCOUNT, mAccount);
231
232 if (mVideoPreview.isPlaying()) {
233 outState.putInt(PreviewMediaFragment.EXTRA_PLAY_POSITION , mVideoPreview.getCurrentPosition());
234 }
235 }
236
237
238 @Override
239 public void onStart() {
240 super.onStart();
241
242 if (mFile != null) {
243 if (mFile.isAudio()) {
244 bindMediaService();
245
246 } else if (mFile.isVideo()) {
247 stopAudio();
248 playVideo();
249 }
250 }
251 }
252
253
254 private void stopAudio() {
255 Intent i = new Intent(getSherlockActivity(), MediaService.class);
256 i.setAction(MediaService.ACTION_STOP_ALL);
257 getSherlockActivity().startService(i);
258 }
259
260
261 /**
262 * {@inheritDoc}
263 */
264 @Override
265 public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
266 super.onCreateOptionsMenu(menu, inflater);
267
268 inflater.inflate(R.menu.file_actions_menu, menu);
269 List<Integer> toHide = new ArrayList<Integer>();
270
271 MenuItem item = null;
272 toHide.add(R.id.action_cancel_download);
273 toHide.add(R.id.action_cancel_upload);
274 toHide.add(R.id.action_download_file);
275 toHide.add(R.id.action_rename_file); // by now
276
277 for (int i : toHide) {
278 item = menu.findItem(i);
279 if (item != null) {
280 item.setVisible(false);
281 item.setEnabled(false);
282 }
283 }
284
285 }
286
287
288 /**
289 * {@inheritDoc}
290 */
291 @Override
292 public boolean onOptionsItemSelected(MenuItem item) {
293 switch (item.getItemId()) {
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
307 default:
308 return false;
309 }
310 }
311
312
313 private void seeDetails() {
314 stopPreview(false);
315 ((FileFragment.ContainerActivity)getActivity()).showFragmentWithDetails(mFile);
316 }
317
318
319 private void prepareVideo() {
320 // create helper to get more control on the playback
321 mVideoHelper = new VideoHelper();
322 mVideoPreview.setOnPreparedListener(mVideoHelper);
323 mVideoPreview.setOnCompletionListener(mVideoHelper);
324 mVideoPreview.setOnErrorListener(mVideoHelper);
325 }
326
327 private void playVideo() {
328 // load the video file in the video player ; when done, VideoHelper#onPrepared() will be called
329 mVideoPreview.setVideoPath(mFile.getStoragePath());
330
331 // create and prepare control panel for the user
332 mMediaController = new MediaController(getActivity());
333 mMediaController.setMediaPlayer(mVideoPreview);
334 mMediaController.setAnchorView(mVideoPreview);
335 mVideoPreview.setMediaController(mMediaController);
336 }
337
338
339 private class VideoHelper implements OnCompletionListener, OnPreparedListener, OnErrorListener {
340
341 /**
342 * Called when the file is ready to be played.
343 *
344 * Just starts the playback.
345 *
346 * @param mp {@link MediaPlayer} instance performing the playback.
347 */
348 @Override
349 public void onPrepared(MediaPlayer vp) {
350 mVideoPreview.seekTo(mSavedPlaybackPosition);
351 mVideoPreview.start();
352 mMediaController.show(MediaService.MEDIA_CONTROL_SHORT_LIFE);
353 }
354
355
356 /**
357 * Called when the file is finished playing.
358 *
359 * Finishes the activity.
360 *
361 * @param mp {@link MediaPlayer} instance performing the playback.
362 */
363 @Override
364 public void onCompletion(MediaPlayer mp) {
365 // nothing, right now
366 }
367
368
369 /**
370 * Called when an error in playback occurs.
371 *
372 * @param mp {@link MediaPlayer} instance performing the playback.
373 * @param what Type of error
374 * @param extra Extra code specific to the error
375 */
376 @Override
377 public boolean onError(MediaPlayer mp, int what, int extra) {
378 Log.e(TAG, "Error in video playback, what = " + what + ", extra = " + extra);
379
380 if (mMediaController != null) {
381 mMediaController.hide();
382 }
383
384 if (mVideoPreview.getWindowToken() != null) {
385 String message = MediaService.getMessageForMediaError(getActivity(), what, extra);
386 new AlertDialog.Builder(getActivity())
387 .setMessage(message)
388 .setPositiveButton(android.R.string.VideoView_error_button,
389 new DialogInterface.OnClickListener() {
390 public void onClick(DialogInterface dialog, int whichButton) {
391 dialog.dismiss();
392 VideoHelper.this.onCompletion(null);
393 }
394 })
395 .setCancelable(false)
396 .show();
397 }
398 return true;
399 }
400
401 }
402
403
404 @Override
405 public void onResume() {
406 super.onResume();
407 /*
408 mDownloadFinishReceiver = new DownloadFinishReceiver();
409 IntentFilter filter = new IntentFilter(
410 FileDownloader.DOWNLOAD_FINISH_MESSAGE);
411 getActivity().registerReceiver(mDownloadFinishReceiver, filter);
412
413 mUploadFinishReceiver = new UploadFinishReceiver();
414 filter = new IntentFilter(FileUploader.UPLOAD_FINISH_MESSAGE);
415 getActivity().registerReceiver(mUploadFinishReceiver, filter);
416 */
417
418 }
419
420
421 @Override
422 public void onPause() {
423 super.onPause();
424 /*
425 if (mVideoPreview.getVisibility() == View.VISIBLE) {
426 mSavedPlaybackPosition = mVideoPreview.getCurrentPosition();
427 }*/
428 /*
429 getActivity().unregisterReceiver(mDownloadFinishReceiver);
430 mDownloadFinishReceiver = null;
431
432 getActivity().unregisterReceiver(mUploadFinishReceiver);
433 mUploadFinishReceiver = null;
434 */
435 }
436
437
438 @Override
439 public void onStop() {
440 super.onStop();
441 if (mMediaServiceConnection != null) {
442 Log.d(TAG, "Unbinding from MediaService ...");
443 if (mMediaServiceBinder != null && mMediaController != null) {
444 mMediaServiceBinder.unregisterMediaController(mMediaController);
445 }
446 getActivity().unbindService(mMediaServiceConnection);
447 mMediaServiceConnection = null;
448 mMediaServiceBinder = null;
449 if (mMediaController != null) {
450 mMediaController.hide();
451 mMediaController = null;
452 }
453 }
454 }
455
456 @Override
457 public void onDestroy() {
458 super.onDestroy();
459 }
460
461
462 @Override
463 public boolean onTouch(View v, MotionEvent event) {
464 if (event.getAction() == MotionEvent.ACTION_DOWN) {
465 if (v == mImagePreview &&
466 mMediaServiceBinder != null && mFile.isAudio() && mMediaServiceBinder.isPlaying(mFile)) {
467 toggleMediaController(MediaService.MEDIA_CONTROL_PERMANENT);
468 return true;
469
470 } else if (v == mVideoPreview) {
471 toggleMediaController(MediaService.MEDIA_CONTROL_SHORT_LIFE);
472 return true;
473 }
474 }
475 return false;
476 }
477
478
479 private void toggleMediaController(int time) {
480 if (mMediaController.isShowing()) {
481 mMediaController.hide();
482 } else {
483 mMediaController.show(time);
484 }
485 }
486
487
488 private void playAudio() {
489 if (!mMediaServiceBinder.isPlaying(mFile)) {
490 Log.d(TAG, "starting playback of " + mFile.getStoragePath());
491 mMediaServiceBinder.start(mAccount, mFile);
492
493 } else {
494 if (!mMediaServiceBinder.isPlaying()) {
495 mMediaServiceBinder.start();
496 }
497 if (!mMediaController.isShowing() && isVisible()) {
498 mMediaController.show(MediaService.MEDIA_CONTROL_PERMANENT);
499 // TODO - fix strange bug; steps to trigger :
500 // 1. remove the "isVisible()" control
501 // 2. start the app and preview an audio file
502 // 3. exit from the app (home button, for instance) while the audio file is still being played
503 // 4. go to notification bar and click on the "ownCloud music app" notification
504 // PUM!
505 }
506 }
507 }
508
509
510 private void bindMediaService() {
511 Log.d(TAG, "Binding to MediaService...");
512 if (mMediaServiceConnection == null) {
513 mMediaServiceConnection = new MediaServiceConnection();
514 }
515 getActivity().bindService( new Intent(getActivity(),
516 MediaService.class),
517 mMediaServiceConnection,
518 Context.BIND_AUTO_CREATE);
519 // follow the flow in MediaServiceConnection#onServiceConnected(...)
520 }
521
522 /** Defines callbacks for service binding, passed to bindService() */
523 private class MediaServiceConnection implements ServiceConnection {
524
525 @Override
526 public void onServiceConnected(ComponentName component, IBinder service) {
527 if (component.equals(new ComponentName(getActivity(), MediaService.class))) {
528 Log.d(TAG, "Media service connected");
529 mMediaServiceBinder = (MediaServiceBinder) service;
530 if (mMediaServiceBinder != null) {
531 if (mMediaController == null) {
532 mMediaController = new MediaController(getSherlockActivity());
533 }
534 prepareMediaController();
535 playAudio(); // do not wait for the touch of nobody to play audio
536
537 Log.d(TAG, "Successfully bound to MediaService, MediaController ready");
538
539 } else {
540 Log.e(TAG, "Unexpected response from MediaService while binding");
541 }
542 }
543 }
544
545 private void prepareMediaController() {
546 mMediaServiceBinder.registerMediaController(mMediaController);
547 mMediaController.setMediaPlayer(mMediaServiceBinder);
548 mMediaController.setAnchorView(getView());
549 mMediaController.setEnabled(mMediaServiceBinder.isInPlaybackState());
550 }
551
552 @Override
553 public void onServiceDisconnected(ComponentName component) {
554 if (component.equals(new ComponentName(getActivity(), MediaService.class))) {
555 Log.e(TAG, "Media service suddenly disconnected");
556 if (mMediaController != null) {
557 mMediaController.hide();
558 mMediaController.setMediaPlayer(null);
559 mMediaController = null;
560 }
561 mMediaServiceBinder = null;
562 mMediaServiceConnection = null;
563 }
564 }
565 }
566
567
568
569 /**
570 * Opens the previewed file with an external application.
571 *
572 * TODO - improve this; instead of prioritize the actions available for the MIME type in the server,
573 * we should get a list of available apps for MIME tpye in the server and join it with the list of
574 * available apps for the MIME type known from the file extension, to let the user choose
575 */
576 private void openFile() {
577 stopPreview(true);
578 String storagePath = mFile.getStoragePath();
579 String encodedStoragePath = WebdavUtils.encodePath(storagePath);
580 try {
581 Intent i = new Intent(Intent.ACTION_VIEW);
582 i.setDataAndType(Uri.parse("file://"+ encodedStoragePath), mFile.getMimetype());
583 i.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION | Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
584 startActivity(i);
585
586 } catch (Throwable t) {
587 Log.e(TAG, "Fail when trying to open with the mimeType provided from the ownCloud server: " + mFile.getMimetype());
588 boolean toastIt = true;
589 String mimeType = "";
590 try {
591 Intent i = new Intent(Intent.ACTION_VIEW);
592 mimeType = MimeTypeMap.getSingleton().getMimeTypeFromExtension(storagePath.substring(storagePath.lastIndexOf('.') + 1));
593 if (mimeType == null || !mimeType.equals(mFile.getMimetype())) {
594 if (mimeType != null) {
595 i.setDataAndType(Uri.parse("file://"+ encodedStoragePath), mimeType);
596 } else {
597 // desperate try
598 i.setDataAndType(Uri.parse("file://"+ encodedStoragePath), "*-/*");
599 }
600 i.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION | Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
601 startActivity(i);
602 toastIt = false;
603 }
604
605 } catch (IndexOutOfBoundsException e) {
606 Log.e(TAG, "Trying to find out MIME type of a file without extension: " + storagePath);
607
608 } catch (ActivityNotFoundException e) {
609 Log.e(TAG, "No activity found to handle: " + storagePath + " with MIME type " + mimeType + " obtained from extension");
610
611 } catch (Throwable th) {
612 Log.e(TAG, "Unexpected problem when opening: " + storagePath, th);
613
614 } finally {
615 if (toastIt) {
616 Toast.makeText(getActivity(), "There is no application to handle file " + mFile.getFileName(), Toast.LENGTH_SHORT).show();
617 }
618 }
619
620 }
621 finish();
622 }
623
624 /**
625 * Starts a the removal of the previewed file.
626 *
627 * Shows a confirmation dialog. The action continues in {@link #onConfirmation(String)} , {@link #onNeutral(String)} or {@link #onCancel(String)},
628 * depending upon the user selection in the dialog.
629 */
630 private void removeFile() {
631 ConfirmationDialogFragment confDialog = ConfirmationDialogFragment.newInstance(
632 R.string.confirmation_remove_alert,
633 new String[]{mFile.getFileName()},
634 R.string.confirmation_remove_remote_and_local,
635 R.string.confirmation_remove_local,
636 R.string.common_cancel);
637 confDialog.setOnConfirmationListener(this);
638 confDialog.show(getFragmentManager(), ConfirmationDialogFragment.FTAG_CONFIRMATION);
639 }
640
641
642 /**
643 * Performs the removal of the previewed file, both locally and in the server.
644 */
645 @Override
646 public void onConfirmation(String callerTag) {
647 if (mStorageManager.getFileById(mFile.getFileId()) != null) { // check that the file is still there;
648 stopPreview(true);
649 mLastRemoteOperation = new RemoveFileOperation( mFile, // TODO we need to review the interface with RemoteOperations, and use OCFile IDs instead of OCFile objects as parameters
650 true,
651 mStorageManager);
652 WebdavClient wc = OwnCloudClientUtils.createOwnCloudClient(mAccount, getSherlockActivity().getApplicationContext());
653 mLastRemoteOperation.execute(wc, this, mHandler);
654
655 boolean inDisplayActivity = getActivity() instanceof FileDisplayActivity;
656 getActivity().showDialog((inDisplayActivity)? FileDisplayActivity.DIALOG_SHORT_WAIT : FileDetailActivity.DIALOG_SHORT_WAIT);
657 }
658 }
659
660
661 /**
662 * Removes the file from local storage
663 */
664 @Override
665 public void onNeutral(String callerTag) {
666 // TODO this code should be made in a secondary thread,
667 if (mFile.isDown()) { // checks it is still there
668 stopPreview(true);
669 File f = new File(mFile.getStoragePath());
670 f.delete();
671 mFile.setStoragePath(null);
672 mStorageManager.saveFile(mFile);
673 finish();
674 }
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 * {@inheritDoc}
688 */
689 public OCFile getFile(){
690 return mFile;
691 }
692
693 /*
694 /**
695 * Use this method to signal this Activity that it shall update its view.
696 *
697 * @param file : An {@link OCFile}
698 *-/
699 public void updateFileDetails(OCFile file, Account ocAccount) {
700 mFile = file;
701 if (ocAccount != null && (
702 mStorageManager == null ||
703 (mAccount != null && !mAccount.equals(ocAccount))
704 )) {
705 mStorageManager = new FileDataStorageManager(ocAccount, getActivity().getApplicationContext().getContentResolver());
706 }
707 mAccount = ocAccount;
708 updateFileDetails(false);
709 }
710 */
711
712
713 /**
714 * Interface to implement by any Activity that includes some instance of FileDetailFragment
715 *
716 * @author David A. Velasco
717 */
718 public interface ContainerActivity extends TransferServiceGetter {
719
720 /**
721 * Callback method invoked when the detail fragment wants to notice its container
722 * activity about a relevant state the file shown by the fragment.
723 *
724 * Added to notify to FileDisplayActivity about the need of refresh the files list.
725 *
726 * Currently called when:
727 * - a download is started;
728 * - a rename is completed;
729 * - a deletion is completed;
730 * - the 'inSync' flag is changed;
731 */
732 public void onFileStateChanged();
733
734 }
735
736 /**
737 * Helper method to test if an {@link OCFile} can be passed to a {@link PreviewMediaFragment} to be previewed.
738 *
739 * @param file File to test if can be previewed.
740 * @return 'True' if the file can be handled by the fragment.
741 */
742 public static boolean canBePreviewed(OCFile file) {
743 return (file != null && (file.isAudio() || file.isVideo()));
744 }
745
746 /**
747 * {@inheritDoc}
748 */
749 @Override
750 public void onRemoteOperationFinish(RemoteOperation operation, RemoteOperationResult result) {
751 if (operation.equals(mLastRemoteOperation)) {
752 if (operation instanceof RemoveFileOperation) {
753 onRemoveFileOperationFinish((RemoveFileOperation)operation, result);
754 }
755 }
756 }
757
758 private void onRemoveFileOperationFinish(RemoveFileOperation operation, RemoteOperationResult result) {
759 boolean inDisplayActivity = getActivity() instanceof FileDisplayActivity;
760 getActivity().dismissDialog((inDisplayActivity)? FileDisplayActivity.DIALOG_SHORT_WAIT : FileDetailActivity.DIALOG_SHORT_WAIT);
761
762 if (result.isSuccess()) {
763 Toast msg = Toast.makeText(getActivity().getApplicationContext(), R.string.remove_success_msg, Toast.LENGTH_LONG);
764 msg.show();
765 finish();
766
767 } else {
768 Toast msg = Toast.makeText(getActivity(), R.string.remove_fail_msg, Toast.LENGTH_LONG);
769 msg.show();
770 if (result.isSslRecoverableException()) {
771 // TODO show the SSL warning dialog
772 }
773 }
774 }
775
776 private void stopPreview(boolean stopAudio) {
777 if (mMediaController != null) {
778 mMediaController.hide();
779 }
780 if (mFile.isAudio() && stopAudio) {
781 mMediaServiceBinder.pause();
782
783 } else if (mFile.isVideo()) {
784 mVideoPreview.stopPlayback();
785 }
786 }
787
788
789
790 /**
791 * Finishes the preview
792 */
793 private void finish() {
794 Activity container = getActivity();
795 if (container instanceof FileDisplayActivity) {
796 // double pane
797 FragmentTransaction transaction = getActivity().getSupportFragmentManager().beginTransaction();
798 transaction.replace(R.id.file_details_container, new FileDetailFragment(null, null), FileDetailFragment.FTAG); // empty FileDetailFragment
799 transaction.commit();
800 ((FileFragment.ContainerActivity)container).onFileStateChanged();
801 } else {
802 container.finish();
803 }
804 }
805
806 }