Merge branch 'develop' into feature_previews
[pub/Android/ownCloud.git] / src / com / owncloud / android / ui / fragment / FileDetailFragment.java
1 /* ownCloud Android client application
2 * Copyright (C) 2011 Bartek Przybylski
3 * Copyright (C) 2012-2013 ownCloud Inc.
4 *
5 * This program is free software: you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation, either version 2 of the License, or
8 * (at your option) any later version.
9 *
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
14 *
15 * You should have received a copy of the GNU General Public License
16 * along with this program. If not, see <http://www.gnu.org/licenses/>.
17 *
18 */
19 package com.owncloud.android.ui.fragment;
20
21 import java.io.File;
22 import java.lang.ref.WeakReference;
23 import java.util.ArrayList;
24 import java.util.List;
25
26 import org.apache.commons.httpclient.methods.GetMethod;
27 import org.apache.commons.httpclient.methods.PostMethod;
28 import org.apache.commons.httpclient.methods.StringRequestEntity;
29 import org.apache.commons.httpclient.params.HttpConnectionManagerParams;
30 import org.apache.http.HttpStatus;
31 import org.apache.http.NameValuePair;
32 import org.apache.http.client.utils.URLEncodedUtils;
33 import org.apache.http.message.BasicNameValuePair;
34 import org.apache.http.protocol.HTTP;
35 import org.apache.jackrabbit.webdav.client.methods.PropFindMethod;
36 import org.json.JSONObject;
37
38 import android.accounts.Account;
39 import android.accounts.AccountManager;
40 import android.app.Activity;
41 import android.content.ActivityNotFoundException;
42 import android.content.BroadcastReceiver;
43 import android.content.Context;
44 import android.content.Intent;
45 import android.content.IntentFilter;
46 import android.net.Uri;
47 import android.os.Bundle;
48 import android.os.Handler;
49 import android.support.v4.app.FragmentTransaction;
50 import android.util.Log;
51 import android.view.LayoutInflater;
52 import android.view.View;
53 import android.view.View.OnClickListener;
54 import android.view.ViewGroup;
55 import android.webkit.MimeTypeMap;
56 import android.widget.Button;
57 import android.widget.CheckBox;
58 import android.widget.ImageView;
59 import android.widget.ProgressBar;
60 import android.widget.TextView;
61 import android.widget.Toast;
62
63 import com.actionbarsherlock.app.SherlockFragment;
64 import com.owncloud.android.AccountUtils;
65 import com.owncloud.android.DisplayUtils;
66 import com.owncloud.android.authenticator.AccountAuthenticator;
67 import com.owncloud.android.datamodel.FileDataStorageManager;
68 import com.owncloud.android.datamodel.OCFile;
69 import com.owncloud.android.files.services.FileObserverService;
70 import com.owncloud.android.files.services.FileUploader;
71 import com.owncloud.android.files.services.FileDownloader.FileDownloaderBinder;
72 import com.owncloud.android.files.services.FileUploader.FileUploaderBinder;
73 import com.owncloud.android.network.OwnCloudClientUtils;
74 import com.owncloud.android.operations.OnRemoteOperationListener;
75 import com.owncloud.android.operations.RemoteOperation;
76 import com.owncloud.android.operations.RemoteOperationResult;
77 import com.owncloud.android.operations.RemoteOperationResult.ResultCode;
78 import com.owncloud.android.operations.RemoveFileOperation;
79 import com.owncloud.android.operations.RenameFileOperation;
80 import com.owncloud.android.operations.SynchronizeFileOperation;
81 import com.owncloud.android.ui.activity.ConflictsResolveActivity;
82 import com.owncloud.android.ui.activity.FileDetailActivity;
83 import com.owncloud.android.ui.activity.FileDisplayActivity;
84 import com.owncloud.android.ui.dialog.EditNameDialog;
85 import com.owncloud.android.ui.dialog.EditNameDialog.EditNameDialogListener;
86 import com.owncloud.android.utils.OwnCloudVersion;
87
88 import com.owncloud.android.R;
89
90 import eu.alefzero.webdav.OnDatatransferProgressListener;
91 import eu.alefzero.webdav.WebdavClient;
92 import eu.alefzero.webdav.WebdavUtils;
93
94 /**
95 * This Fragment is used to display the details about a file.
96 *
97 * @author Bartek Przybylski
98 * @author David A. Velasco
99 */
100 public class FileDetailFragment extends SherlockFragment implements
101 OnClickListener,
102 ConfirmationDialogFragment.ConfirmationDialogFragmentListener, OnRemoteOperationListener, EditNameDialogListener,
103 FileFragment {
104
105 public static final String EXTRA_FILE = "FILE";
106 public static final String EXTRA_ACCOUNT = "ACCOUNT";
107
108 private FileFragment.ContainerActivity mContainerActivity;
109
110 private int mLayout;
111 private View mView;
112 private OCFile mFile;
113 private Account mAccount;
114 private FileDataStorageManager mStorageManager;
115
116 private UploadFinishReceiver mUploadFinishReceiver;
117 public ProgressListener mProgressListener;
118
119 private Handler mHandler;
120 private RemoteOperation mLastRemoteOperation;
121
122 private static final String TAG = FileDetailFragment.class.getSimpleName();
123 public static final String FTAG = "FileDetails";
124 public static final String FTAG_CONFIRMATION = "REMOVE_CONFIRMATION_FRAGMENT";
125
126
127 /**
128 * Creates an empty details fragment.
129 *
130 * It's necessary to keep a public constructor without parameters; the system uses it when tries to reinstantiate a fragment automatically.
131 */
132 public FileDetailFragment() {
133 mFile = null;
134 mAccount = null;
135 mStorageManager = null;
136 mLayout = R.layout.file_details_empty;
137 mProgressListener = null;
138 }
139
140
141 /**
142 * Creates a details fragment.
143 *
144 * When 'fileToDetail' or 'ocAccount' are null, creates a dummy layout (to use when a file wasn't tapped before).
145 *
146 * @param fileToDetail An {@link OCFile} to show in the fragment
147 * @param ocAccount An ownCloud account; needed to start downloads
148 */
149 public FileDetailFragment(OCFile fileToDetail, Account ocAccount) {
150 mFile = fileToDetail;
151 mAccount = ocAccount;
152 mStorageManager = null; // we need a context to init this; the container activity is not available yet at this moment
153 mLayout = R.layout.file_details_empty;
154 mProgressListener = null;
155 }
156
157
158 @Override
159 public void onCreate(Bundle savedInstanceState) {
160 super.onCreate(savedInstanceState);
161 mHandler = new Handler();
162 }
163
164
165 @Override
166 public View onCreateView(LayoutInflater inflater, ViewGroup container,
167 Bundle savedInstanceState) {
168 super.onCreateView(inflater, container, savedInstanceState);
169
170 if (savedInstanceState != null) {
171 mFile = savedInstanceState.getParcelable(FileDetailFragment.EXTRA_FILE);
172 mAccount = savedInstanceState.getParcelable(FileDetailFragment.EXTRA_ACCOUNT);
173 }
174
175 if(mFile != null && mAccount != null) {
176 mLayout = R.layout.file_details_fragment;
177 }
178
179 View view = null;
180 view = inflater.inflate(mLayout, container, false);
181 mView = view;
182
183 if (mLayout == R.layout.file_details_fragment) {
184 mView.findViewById(R.id.fdKeepInSync).setOnClickListener(this);
185 mView.findViewById(R.id.fdRenameBtn).setOnClickListener(this);
186 mView.findViewById(R.id.fdDownloadBtn).setOnClickListener(this);
187 mView.findViewById(R.id.fdOpenBtn).setOnClickListener(this);
188 mView.findViewById(R.id.fdRemoveBtn).setOnClickListener(this);
189 //mView.findViewById(R.id.fdShareBtn).setOnClickListener(this);
190 ProgressBar progressBar = (ProgressBar)mView.findViewById(R.id.fdProgressBar);
191 mProgressListener = new ProgressListener(progressBar);
192 }
193
194 updateFileDetails(false, false);
195 return view;
196 }
197
198
199 /**
200 * {@inheritDoc}
201 */
202 @Override
203 public void onAttach(Activity activity) {
204 super.onAttach(activity);
205 try {
206 mContainerActivity = (ContainerActivity) activity;
207
208 } catch (ClassCastException e) {
209 throw new ClassCastException(activity.toString() + " must implement " + FileDetailFragment.ContainerActivity.class.getSimpleName());
210 }
211 }
212
213
214 /**
215 * {@inheritDoc}
216 */
217 @Override
218 public void onActivityCreated(Bundle savedInstanceState) {
219 super.onActivityCreated(savedInstanceState);
220 if (mAccount != null) {
221 mStorageManager = new FileDataStorageManager(mAccount, getActivity().getApplicationContext().getContentResolver());
222 }
223 }
224
225
226 @Override
227 public void onSaveInstanceState(Bundle outState) {
228 super.onSaveInstanceState(outState);
229 outState.putParcelable(FileDetailFragment.EXTRA_FILE, mFile);
230 outState.putParcelable(FileDetailFragment.EXTRA_ACCOUNT, mAccount);
231 }
232
233 @Override
234 public void onStart() {
235 super.onStart();
236 listenForTransferProgress();
237 }
238
239 @Override
240 public void onResume() {
241 super.onResume();
242 mUploadFinishReceiver = new UploadFinishReceiver();
243 IntentFilter filter = new IntentFilter(FileUploader.UPLOAD_FINISH_MESSAGE);
244 getActivity().registerReceiver(mUploadFinishReceiver, filter);
245
246 }
247
248
249 @Override
250 public void onPause() {
251 super.onPause();
252 if (mUploadFinishReceiver != null) {
253 getActivity().unregisterReceiver(mUploadFinishReceiver);
254 mUploadFinishReceiver = null;
255 }
256 }
257
258
259 @Override
260 public void onStop() {
261 super.onStop();
262 leaveTransferProgress();
263 }
264
265
266 @Override
267 public View getView() {
268 return super.getView() == null ? mView : super.getView();
269 }
270
271
272 @Override
273 public void onClick(View v) {
274 switch (v.getId()) {
275 case R.id.fdDownloadBtn: {
276 FileDownloaderBinder downloaderBinder = mContainerActivity.getFileDownloaderBinder();
277 FileUploaderBinder uploaderBinder = mContainerActivity.getFileUploaderBinder();
278 if (downloaderBinder != null && downloaderBinder.isDownloading(mAccount, mFile)) {
279 downloaderBinder.cancel(mAccount, mFile);
280 if (mFile.isDown()) {
281 setButtonsForDown();
282 } else {
283 setButtonsForRemote();
284 }
285
286 } else if (uploaderBinder != null && uploaderBinder.isUploading(mAccount, mFile)) {
287 uploaderBinder.cancel(mAccount, mFile);
288 if (!mFile.fileExists()) {
289 // TODO make something better
290 if (getActivity() instanceof FileDisplayActivity) {
291 // double pane
292 FragmentTransaction transaction = getActivity().getSupportFragmentManager().beginTransaction();
293 transaction.replace(R.id.file_details_container, new FileDetailFragment(null, null), FTAG); // empty FileDetailFragment
294 transaction.commit();
295 mContainerActivity.onFileStateChanged();
296 } else {
297 getActivity().finish();
298 }
299
300 } else if (mFile.isDown()) {
301 setButtonsForDown();
302 } else {
303 setButtonsForRemote();
304 }
305
306 } else {
307 mLastRemoteOperation = new SynchronizeFileOperation(mFile, null, mStorageManager, mAccount, true, false, getActivity());
308 WebdavClient wc = OwnCloudClientUtils.createOwnCloudClient(mAccount, getSherlockActivity().getApplicationContext());
309 mLastRemoteOperation.execute(wc, this, mHandler);
310
311 // update ui
312 boolean inDisplayActivity = getActivity() instanceof FileDisplayActivity;
313 getActivity().showDialog((inDisplayActivity)? FileDisplayActivity.DIALOG_SHORT_WAIT : FileDetailActivity.DIALOG_SHORT_WAIT);
314
315 }
316 break;
317 }
318 case R.id.fdKeepInSync: {
319 CheckBox cb = (CheckBox) getView().findViewById(R.id.fdKeepInSync);
320 mFile.setKeepInSync(cb.isChecked());
321 mStorageManager.saveFile(mFile);
322
323 /// register the OCFile instance in the observer service to monitor local updates;
324 /// if necessary, the file is download
325 Intent intent = new Intent(getActivity().getApplicationContext(),
326 FileObserverService.class);
327 intent.putExtra(FileObserverService.KEY_FILE_CMD,
328 (cb.isChecked()?
329 FileObserverService.CMD_ADD_OBSERVED_FILE:
330 FileObserverService.CMD_DEL_OBSERVED_FILE));
331 intent.putExtra(FileObserverService.KEY_CMD_ARG_FILE, mFile);
332 intent.putExtra(FileObserverService.KEY_CMD_ARG_ACCOUNT, mAccount);
333 getActivity().startService(intent);
334
335 if (mFile.keepInSync()) {
336 onClick(getView().findViewById(R.id.fdDownloadBtn)); // force an immediate synchronization
337 }
338 break;
339 }
340 case R.id.fdRenameBtn: {
341 EditNameDialog dialog = EditNameDialog.newInstance(getString(R.string.rename_dialog_title), mFile.getFileName(), this);
342 dialog.show(getFragmentManager(), "nameeditdialog");
343 break;
344 }
345 case R.id.fdRemoveBtn: {
346 ConfirmationDialogFragment confDialog = ConfirmationDialogFragment.newInstance(
347 R.string.confirmation_remove_alert,
348 new String[]{mFile.getFileName()},
349 mFile.isDown() ? R.string.confirmation_remove_remote_and_local : R.string.confirmation_remove_remote,
350 mFile.isDown() ? R.string.confirmation_remove_local : -1,
351 R.string.common_cancel);
352 confDialog.setOnConfirmationListener(this);
353 confDialog.show(getFragmentManager(), FTAG_CONFIRMATION);
354 break;
355 }
356 case R.id.fdOpenBtn: {
357 openFile();
358 break;
359 }
360 default:
361 Log.e(TAG, "Incorrect view clicked!");
362 }
363
364 /* else if (v.getId() == R.id.fdShareBtn) {
365 Thread t = new Thread(new ShareRunnable(mFile.getRemotePath()));
366 t.start();
367 }*/
368 }
369
370
371 /**
372 * Opens mFile.
373 */
374 private void openFile() {
375
376 String storagePath = mFile.getStoragePath();
377 String encodedStoragePath = WebdavUtils.encodePath(storagePath);
378 try {
379 Intent i = new Intent(Intent.ACTION_VIEW);
380 i.setDataAndType(Uri.parse("file://"+ encodedStoragePath), mFile.getMimetype());
381 i.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION | Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
382 startActivity(i);
383
384 } catch (Throwable t) {
385 Log.e(TAG, "Fail when trying to open with the mimeType provided from the ownCloud server: " + mFile.getMimetype());
386 boolean toastIt = true;
387 String mimeType = "";
388 try {
389 Intent i = new Intent(Intent.ACTION_VIEW);
390 mimeType = MimeTypeMap.getSingleton().getMimeTypeFromExtension(storagePath.substring(storagePath.lastIndexOf('.') + 1));
391 if (mimeType == null || !mimeType.equals(mFile.getMimetype())) {
392 if (mimeType != null) {
393 i.setDataAndType(Uri.parse("file://"+ encodedStoragePath), mimeType);
394 } else {
395 // desperate try
396 i.setDataAndType(Uri.parse("file://"+ encodedStoragePath), "*/*");
397 }
398 i.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION | Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
399 startActivity(i);
400 toastIt = false;
401 }
402
403 } catch (IndexOutOfBoundsException e) {
404 Log.e(TAG, "Trying to find out MIME type of a file without extension: " + storagePath);
405
406 } catch (ActivityNotFoundException e) {
407 Log.e(TAG, "No activity found to handle: " + storagePath + " with MIME type " + mimeType + " obtained from extension");
408
409 } catch (Throwable th) {
410 Log.e(TAG, "Unexpected problem when opening: " + storagePath, th);
411
412 } finally {
413 if (toastIt) {
414 Toast.makeText(getActivity(), "There is no application to handle file " + mFile.getFileName(), Toast.LENGTH_SHORT).show();
415 }
416 }
417
418 }
419 }
420
421
422 @Override
423 public void onConfirmation(String callerTag) {
424 if (callerTag.equals(FTAG_CONFIRMATION)) {
425 if (mStorageManager.getFileById(mFile.getFileId()) != null) {
426 mLastRemoteOperation = new RemoveFileOperation( mFile,
427 true,
428 mStorageManager);
429 WebdavClient wc = OwnCloudClientUtils.createOwnCloudClient(mAccount, getSherlockActivity().getApplicationContext());
430 mLastRemoteOperation.execute(wc, this, mHandler);
431
432 boolean inDisplayActivity = getActivity() instanceof FileDisplayActivity;
433 getActivity().showDialog((inDisplayActivity)? FileDisplayActivity.DIALOG_SHORT_WAIT : FileDetailActivity.DIALOG_SHORT_WAIT);
434 }
435 }
436 }
437
438 @Override
439 public void onNeutral(String callerTag) {
440 File f = null;
441 if (mFile.isDown() && (f = new File(mFile.getStoragePath())).exists()) {
442 f.delete();
443 mFile.setStoragePath(null);
444 mStorageManager.saveFile(mFile);
445 updateFileDetails(mFile, mAccount);
446 }
447 }
448
449 @Override
450 public void onCancel(String callerTag) {
451 Log.d(TAG, "REMOVAL CANCELED");
452 }
453
454
455 /**
456 * Check if the fragment was created with an empty layout. An empty fragment can't show file details, must be replaced.
457 *
458 * @return True when the fragment was created with the empty layout.
459 */
460 public boolean isEmpty() {
461 return (mLayout == R.layout.file_details_empty || mFile == null || mAccount == null);
462 }
463
464
465 /**
466 * {@inheritDoc}
467 */
468 public OCFile getFile(){
469 return mFile;
470 }
471
472 /**
473 * Use this method to signal this Activity that it shall update its view.
474 *
475 * @param file : An {@link OCFile}
476 */
477 public void updateFileDetails(OCFile file, Account ocAccount) {
478 mFile = file;
479 if (ocAccount != null && (
480 mStorageManager == null ||
481 (mAccount != null && !mAccount.equals(ocAccount))
482 )) {
483 mStorageManager = new FileDataStorageManager(ocAccount, getActivity().getApplicationContext().getContentResolver());
484 }
485 mAccount = ocAccount;
486 updateFileDetails(false, false);
487 }
488
489
490 /**
491 * Updates the view with all relevant details about that file.
492 *
493 * TODO Remove parameter when the transferring state of files is kept in database.
494 *
495 * TODO REFACTORING! this method called 5 times before every time the fragment is shown!
496 *
497 * @param transferring Flag signaling if the file should be considered as downloading or uploading,
498 * although {@link FileDownloaderBinder#isDownloading(Account, OCFile)} and
499 * {@link FileUploaderBinder#isUploading(Account, OCFile)} return false.
500 *
501 * @param refresh If 'true', try to refresh the hold file from the database
502 */
503 public void updateFileDetails(boolean transferring, boolean refresh) {
504
505 if (readyToShow()) {
506
507 if (refresh && mStorageManager != null) {
508 mFile = mStorageManager.getFileByPath(mFile.getRemotePath());
509 }
510
511 // set file details
512 setFilename(mFile.getFileName());
513 setFiletype(mFile.getMimetype());
514 setFilesize(mFile.getFileLength());
515 if(ocVersionSupportsTimeCreated()){
516 setTimeCreated(mFile.getCreationTimestamp());
517 }
518
519 setTimeModified(mFile.getModificationTimestamp());
520
521 CheckBox cb = (CheckBox)getView().findViewById(R.id.fdKeepInSync);
522 cb.setChecked(mFile.keepInSync());
523
524 // configure UI for depending upon local state of the file
525 //if (FileDownloader.isDownloading(mAccount, mFile.getRemotePath()) || FileUploader.isUploading(mAccount, mFile.getRemotePath())) {
526 FileDownloaderBinder downloaderBinder = mContainerActivity.getFileDownloaderBinder();
527 FileUploaderBinder uploaderBinder = mContainerActivity.getFileUploaderBinder();
528 if (transferring || (downloaderBinder != null && downloaderBinder.isDownloading(mAccount, mFile)) || (uploaderBinder != null && uploaderBinder.isUploading(mAccount, mFile))) {
529 setButtonsForTransferring();
530
531 } else if (mFile.isDown()) {
532
533 setButtonsForDown();
534
535 } else {
536 // TODO load default preview image; when the local file is removed, the preview remains there
537 setButtonsForRemote();
538 }
539 }
540 getView().invalidate();
541 }
542
543
544 /**
545 * Checks if the fragment is ready to show details of a OCFile
546 *
547 * @return 'True' when the fragment is ready to show details of a file
548 */
549 private boolean readyToShow() {
550 return (mFile != null && mAccount != null && mLayout == R.layout.file_details_fragment);
551 }
552
553
554
555 /**
556 * Updates the filename in view
557 * @param filename to set
558 */
559 private void setFilename(String filename) {
560 TextView tv = (TextView) getView().findViewById(R.id.fdFilename);
561 if (tv != null)
562 tv.setText(filename);
563 }
564
565 /**
566 * Updates the MIME type in view
567 * @param mimetype to set
568 */
569 private void setFiletype(String mimetype) {
570 TextView tv = (TextView) getView().findViewById(R.id.fdType);
571 if (tv != null) {
572 String printableMimetype = DisplayUtils.convertMIMEtoPrettyPrint(mimetype);;
573 tv.setText(printableMimetype);
574 }
575 ImageView iv = (ImageView) getView().findViewById(R.id.fdIcon);
576 if (iv != null) {
577 iv.setImageResource(DisplayUtils.getResourceId(mimetype));
578 }
579 }
580
581 /**
582 * Updates the file size in view
583 * @param filesize in bytes to set
584 */
585 private void setFilesize(long filesize) {
586 TextView tv = (TextView) getView().findViewById(R.id.fdSize);
587 if (tv != null)
588 tv.setText(DisplayUtils.bytesToHumanReadable(filesize));
589 }
590
591 /**
592 * Updates the time that the file was created in view
593 * @param milliseconds Unix time to set
594 */
595 private void setTimeCreated(long milliseconds){
596 TextView tv = (TextView) getView().findViewById(R.id.fdCreated);
597 TextView tvLabel = (TextView) getView().findViewById(R.id.fdCreatedLabel);
598 if(tv != null){
599 tv.setText(DisplayUtils.unixTimeToHumanReadable(milliseconds));
600 tv.setVisibility(View.VISIBLE);
601 tvLabel.setVisibility(View.VISIBLE);
602 }
603 }
604
605 /**
606 * Updates the time that the file was last modified
607 * @param milliseconds Unix time to set
608 */
609 private void setTimeModified(long milliseconds){
610 TextView tv = (TextView) getView().findViewById(R.id.fdModified);
611 if(tv != null){
612 tv.setText(DisplayUtils.unixTimeToHumanReadable(milliseconds));
613 }
614 }
615
616 /**
617 * Enables or disables buttons for a file being downloaded
618 */
619 private void setButtonsForTransferring() {
620 if (!isEmpty()) {
621 Button downloadButton = (Button) getView().findViewById(R.id.fdDownloadBtn);
622 downloadButton.setText(R.string.common_cancel);
623 //downloadButton.setEnabled(false);
624
625 // let's protect the user from himself ;)
626 ((Button) getView().findViewById(R.id.fdOpenBtn)).setEnabled(false);
627 ((Button) getView().findViewById(R.id.fdRenameBtn)).setEnabled(false);
628 ((Button) getView().findViewById(R.id.fdRemoveBtn)).setEnabled(false);
629 getView().findViewById(R.id.fdKeepInSync).setEnabled(false);
630
631 // show the progress bar for the transfer
632 ProgressBar progressBar = (ProgressBar)getView().findViewById(R.id.fdProgressBar);
633 progressBar.setVisibility(View.VISIBLE);
634 TextView progressText = (TextView)getView().findViewById(R.id.fdProgressText);
635 progressText.setVisibility(View.VISIBLE);
636 FileDownloaderBinder downloaderBinder = mContainerActivity.getFileDownloaderBinder();
637 FileUploaderBinder uploaderBinder = mContainerActivity.getFileUploaderBinder();
638 if (downloaderBinder != null && downloaderBinder.isDownloading(mAccount, mFile)) {
639 progressText.setText(R.string.downloader_download_in_progress_ticker);
640 } else if (uploaderBinder != null && uploaderBinder.isUploading(mAccount, mFile)) {
641 progressText.setText(R.string.uploader_upload_in_progress_ticker);
642 }
643 }
644 }
645
646
647 /**
648 * Enables or disables buttons for a file locally available
649 */
650 private void setButtonsForDown() {
651 if (!isEmpty()) {
652 Button downloadButton = (Button) getView().findViewById(R.id.fdDownloadBtn);
653 downloadButton.setText(R.string.filedetails_sync_file);
654
655 ((Button) getView().findViewById(R.id.fdOpenBtn)).setEnabled(true);
656 ((Button) getView().findViewById(R.id.fdRenameBtn)).setEnabled(true);
657 ((Button) getView().findViewById(R.id.fdRemoveBtn)).setEnabled(true);
658 getView().findViewById(R.id.fdKeepInSync).setEnabled(true);
659
660 // hides the progress bar
661 ProgressBar progressBar = (ProgressBar)getView().findViewById(R.id.fdProgressBar);
662 progressBar.setVisibility(View.GONE);
663 TextView progressText = (TextView)getView().findViewById(R.id.fdProgressText);
664 progressText.setVisibility(View.GONE);
665 }
666 }
667
668 /**
669 * Enables or disables buttons for a file not locally available
670 */
671 private void setButtonsForRemote() {
672 if (!isEmpty()) {
673 Button downloadButton = (Button) getView().findViewById(R.id.fdDownloadBtn);
674 downloadButton.setText(R.string.filedetails_download);
675
676 ((Button) getView().findViewById(R.id.fdOpenBtn)).setEnabled(false);
677 ((Button) getView().findViewById(R.id.fdRenameBtn)).setEnabled(true);
678 ((Button) getView().findViewById(R.id.fdRemoveBtn)).setEnabled(true);
679 getView().findViewById(R.id.fdKeepInSync).setEnabled(true);
680
681 // hides the progress bar
682 ProgressBar progressBar = (ProgressBar)getView().findViewById(R.id.fdProgressBar);
683 progressBar.setVisibility(View.GONE);
684 TextView progressText = (TextView)getView().findViewById(R.id.fdProgressText);
685 progressText.setVisibility(View.GONE);
686 }
687 }
688
689
690 /**
691 * In ownCloud 3.X.X and 4.X.X there is a bug that SabreDAV does not return
692 * the time that the file was created. There is a chance that this will
693 * be fixed in future versions. Use this method to check if this version of
694 * ownCloud has this fix.
695 * @return True, if ownCloud the ownCloud version is supporting creation time
696 */
697 private boolean ocVersionSupportsTimeCreated(){
698 /*if(mAccount != null){
699 AccountManager accManager = (AccountManager) getActivity().getSystemService(Context.ACCOUNT_SERVICE);
700 OwnCloudVersion ocVersion = new OwnCloudVersion(accManager
701 .getUserData(mAccount, AccountAuthenticator.KEY_OC_VERSION));
702 if(ocVersion.compareTo(new OwnCloudVersion(0x030000)) < 0) {
703 return true;
704 }
705 }*/
706 return false;
707 }
708
709
710 /**
711 * Once the file upload has finished -> update view
712 *
713 * Being notified about the finish of an upload is necessary for the next sequence:
714 * 1. Upload a big file.
715 * 2. Force a synchronization; if it finished before the upload, the file in transfer will be included in the local database and in the file list
716 * of its containing folder; the the server includes it in the PROPFIND requests although it's not fully upload.
717 * 3. Click the file in the list to see its details.
718 * 4. Wait for the upload finishes; at this moment, the details view must be refreshed to enable the action buttons.
719 */
720 private class UploadFinishReceiver extends BroadcastReceiver {
721 @Override
722 public void onReceive(Context context, Intent intent) {
723 String accountName = intent.getStringExtra(FileUploader.ACCOUNT_NAME);
724
725 if (!isEmpty() && accountName.equals(mAccount.name)) {
726 boolean uploadWasFine = intent.getBooleanExtra(FileUploader.EXTRA_UPLOAD_RESULT, false);
727 String uploadRemotePath = intent.getStringExtra(FileUploader.EXTRA_REMOTE_PATH);
728 boolean renamedInUpload = mFile.getRemotePath().equals(intent.getStringExtra(FileUploader.EXTRA_OLD_REMOTE_PATH));
729 if (mFile.getRemotePath().equals(uploadRemotePath) ||
730 renamedInUpload) {
731 if (uploadWasFine) {
732 mFile = mStorageManager.getFileByPath(uploadRemotePath);
733 }
734 if (renamedInUpload) {
735 String newName = (new File(uploadRemotePath)).getName();
736 Toast msg = Toast.makeText(getActivity().getApplicationContext(), String.format(getString(R.string.filedetails_renamed_in_upload_msg), newName), Toast.LENGTH_LONG);
737 msg.show();
738 }
739 getSherlockActivity().removeStickyBroadcast(intent); // not the best place to do this; a small refactorization of BroadcastReceivers should be done
740 updateFileDetails(false, false); // it updates the buttons; must be called although !uploadWasFine; interrupted uploads still leave an incomplete file in the server
741 }
742 }
743 }
744 }
745
746
747 // this is a temporary class for sharing purposes, it need to be replaced in transfer service
748 @SuppressWarnings("unused")
749 private class ShareRunnable implements Runnable {
750 private String mPath;
751
752 public ShareRunnable(String path) {
753 mPath = path;
754 }
755
756 public void run() {
757 AccountManager am = AccountManager.get(getActivity());
758 Account account = AccountUtils.getCurrentOwnCloudAccount(getActivity());
759 OwnCloudVersion ocv = new OwnCloudVersion(am.getUserData(account, AccountAuthenticator.KEY_OC_VERSION));
760 String url = am.getUserData(account, AccountAuthenticator.KEY_OC_BASE_URL) + AccountUtils.getWebdavPath(ocv);
761
762 Log.d("share", "sharing for version " + ocv.toString());
763
764 if (ocv.compareTo(new OwnCloudVersion(0x040000)) >= 0) {
765 String APPS_PATH = "/apps/files_sharing/";
766 String SHARE_PATH = "ajax/share.php";
767
768 String SHARED_PATH = "/apps/files_sharing/get.php?token=";
769
770 final String WEBDAV_SCRIPT = "webdav.php";
771 final String WEBDAV_FILES_LOCATION = "/files/";
772
773 WebdavClient wc = OwnCloudClientUtils.createOwnCloudClient(account, getActivity().getApplicationContext());
774 HttpConnectionManagerParams params = new HttpConnectionManagerParams();
775 params.setMaxConnectionsPerHost(wc.getHostConfiguration(), 5);
776
777 //wc.getParams().setParameter("http.protocol.single-cookie-header", true);
778 //wc.getParams().setCookiePolicy(CookiePolicy.BROWSER_COMPATIBILITY);
779
780 PostMethod post = new PostMethod(am.getUserData(account, AccountAuthenticator.KEY_OC_BASE_URL) + APPS_PATH + SHARE_PATH);
781
782 post.addRequestHeader("Content-type","application/x-www-form-urlencoded; charset=UTF-8" );
783 post.addRequestHeader("Referer", am.getUserData(account, AccountAuthenticator.KEY_OC_BASE_URL));
784 List<NameValuePair> formparams = new ArrayList<NameValuePair>();
785 Log.d("share", mPath+"");
786 formparams.add(new BasicNameValuePair("sources",mPath));
787 formparams.add(new BasicNameValuePair("uid_shared_with", "public"));
788 formparams.add(new BasicNameValuePair("permissions", "0"));
789 post.setRequestEntity(new StringRequestEntity(URLEncodedUtils.format(formparams, HTTP.UTF_8)));
790
791 int status;
792 try {
793 PropFindMethod find = new PropFindMethod(url+"/");
794 find.addRequestHeader("Referer", am.getUserData(account, AccountAuthenticator.KEY_OC_BASE_URL));
795 Log.d("sharer", ""+ url+"/");
796
797 for (org.apache.commons.httpclient.Header a : find.getRequestHeaders()) {
798 Log.d("sharer-h", a.getName() + ":"+a.getValue());
799 }
800
801 int status2 = wc.executeMethod(find);
802
803 Log.d("sharer", "propstatus "+status2);
804
805 GetMethod get = new GetMethod(am.getUserData(account, AccountAuthenticator.KEY_OC_BASE_URL) + "/");
806 get.addRequestHeader("Referer", am.getUserData(account, AccountAuthenticator.KEY_OC_BASE_URL));
807
808 status2 = wc.executeMethod(get);
809
810 Log.d("sharer", "getstatus "+status2);
811 Log.d("sharer", "" + get.getResponseBodyAsString());
812
813 for (org.apache.commons.httpclient.Header a : get.getResponseHeaders()) {
814 Log.d("sharer", a.getName() + ":"+a.getValue());
815 }
816
817 status = wc.executeMethod(post);
818 for (org.apache.commons.httpclient.Header a : post.getRequestHeaders()) {
819 Log.d("sharer-h", a.getName() + ":"+a.getValue());
820 }
821 for (org.apache.commons.httpclient.Header a : post.getResponseHeaders()) {
822 Log.d("sharer", a.getName() + ":"+a.getValue());
823 }
824 String resp = post.getResponseBodyAsString();
825 Log.d("share", ""+post.getURI().toString());
826 Log.d("share", "returned status " + status);
827 Log.d("share", " " +resp);
828
829 if(status != HttpStatus.SC_OK ||resp == null || resp.equals("") || resp.startsWith("false")) {
830 return;
831 }
832
833 JSONObject jsonObject = new JSONObject (resp);
834 String jsonStatus = jsonObject.getString("status");
835 if(!jsonStatus.equals("success")) throw new Exception("Error while sharing file status != success");
836
837 String token = jsonObject.getString("data");
838 String uri = am.getUserData(account, AccountAuthenticator.KEY_OC_BASE_URL) + SHARED_PATH + token;
839 Log.d("Actions:shareFile ok", "url: " + uri);
840
841 } catch (Exception e) {
842 e.printStackTrace();
843 }
844
845 } else if (ocv.compareTo(new OwnCloudVersion(0x030000)) >= 0) {
846
847 }
848 }
849 }
850
851 public void onDismiss(EditNameDialog dialog) {
852 if (dialog.getResult()) {
853 String newFilename = dialog.getNewFilename();
854 Log.d(TAG, "name edit dialog dismissed with new name " + newFilename);
855 mLastRemoteOperation = new RenameFileOperation( mFile,
856 mAccount,
857 newFilename,
858 new FileDataStorageManager(mAccount, getActivity().getContentResolver()));
859 WebdavClient wc = OwnCloudClientUtils.createOwnCloudClient(mAccount, getSherlockActivity().getApplicationContext());
860 mLastRemoteOperation.execute(wc, this, mHandler);
861 boolean inDisplayActivity = getActivity() instanceof FileDisplayActivity;
862 getActivity().showDialog((inDisplayActivity)? FileDisplayActivity.DIALOG_SHORT_WAIT : FileDetailActivity.DIALOG_SHORT_WAIT);
863 }
864 }
865
866
867 /**
868 * {@inheritDoc}
869 */
870 @Override
871 public void onRemoteOperationFinish(RemoteOperation operation, RemoteOperationResult result) {
872 if (operation.equals(mLastRemoteOperation)) {
873 if (operation instanceof RemoveFileOperation) {
874 onRemoveFileOperationFinish((RemoveFileOperation)operation, result);
875
876 } else if (operation instanceof RenameFileOperation) {
877 onRenameFileOperationFinish((RenameFileOperation)operation, result);
878
879 } else if (operation instanceof SynchronizeFileOperation) {
880 onSynchronizeFileOperationFinish((SynchronizeFileOperation)operation, result);
881 }
882 }
883 }
884
885
886 private void onRemoveFileOperationFinish(RemoveFileOperation operation, RemoteOperationResult result) {
887 boolean inDisplayActivity = getActivity() instanceof FileDisplayActivity;
888 getActivity().dismissDialog((inDisplayActivity)? FileDisplayActivity.DIALOG_SHORT_WAIT : FileDetailActivity.DIALOG_SHORT_WAIT);
889
890 if (result.isSuccess()) {
891 Toast msg = Toast.makeText(getActivity().getApplicationContext(), R.string.remove_success_msg, Toast.LENGTH_LONG);
892 msg.show();
893 if (inDisplayActivity) {
894 // double pane
895 FragmentTransaction transaction = getActivity().getSupportFragmentManager().beginTransaction();
896 transaction.replace(R.id.file_details_container, new FileDetailFragment(null, null)); // empty FileDetailFragment
897 transaction.commit();
898 mContainerActivity.onFileStateChanged();
899 } else {
900 getActivity().finish();
901 }
902
903 } else {
904 Toast msg = Toast.makeText(getActivity(), R.string.remove_fail_msg, Toast.LENGTH_LONG);
905 msg.show();
906 if (result.isSslRecoverableException()) {
907 // TODO show the SSL warning dialog
908 }
909 }
910 }
911
912 private void onRenameFileOperationFinish(RenameFileOperation operation, RemoteOperationResult result) {
913 boolean inDisplayActivity = getActivity() instanceof FileDisplayActivity;
914 getActivity().dismissDialog((inDisplayActivity)? FileDisplayActivity.DIALOG_SHORT_WAIT : FileDetailActivity.DIALOG_SHORT_WAIT);
915
916 if (result.isSuccess()) {
917 updateFileDetails(((RenameFileOperation)operation).getFile(), mAccount);
918 mContainerActivity.onFileStateChanged();
919
920 } else {
921 if (result.getCode().equals(ResultCode.INVALID_LOCAL_FILE_NAME)) {
922 Toast msg = Toast.makeText(getActivity(), R.string.rename_local_fail_msg, Toast.LENGTH_LONG);
923 msg.show();
924 // TODO throw again the new rename dialog
925 } else {
926 Toast msg = Toast.makeText(getActivity(), R.string.rename_server_fail_msg, Toast.LENGTH_LONG);
927 msg.show();
928 if (result.isSslRecoverableException()) {
929 // TODO show the SSL warning dialog
930 }
931 }
932 }
933 }
934
935 private void onSynchronizeFileOperationFinish(SynchronizeFileOperation operation, RemoteOperationResult result) {
936 boolean inDisplayActivity = getActivity() instanceof FileDisplayActivity;
937 getActivity().dismissDialog((inDisplayActivity)? FileDisplayActivity.DIALOG_SHORT_WAIT : FileDetailActivity.DIALOG_SHORT_WAIT);
938
939 if (!result.isSuccess()) {
940 if (result.getCode() == ResultCode.SYNC_CONFLICT) {
941 Intent i = new Intent(getActivity(), ConflictsResolveActivity.class);
942 i.putExtra(ConflictsResolveActivity.EXTRA_FILE, mFile);
943 i.putExtra(ConflictsResolveActivity.EXTRA_ACCOUNT, mAccount);
944 startActivity(i);
945
946 } else {
947 Toast msg = Toast.makeText(getActivity(), R.string.sync_file_fail_msg, Toast.LENGTH_LONG);
948 msg.show();
949 }
950
951 if (mFile.isDown()) {
952 setButtonsForDown();
953
954 } else {
955 setButtonsForRemote();
956 }
957
958 } else {
959 if (operation.transferWasRequested()) {
960 setButtonsForTransferring();
961 mContainerActivity.onFileStateChanged(); // this is not working; FileDownloader won't do NOTHING at all until this method finishes, so
962 // checking the service to see if the file is downloading results in FALSE
963 } else {
964 Toast msg = Toast.makeText(getActivity(), R.string.sync_file_nothing_to_do_msg, Toast.LENGTH_LONG);
965 msg.show();
966 if (mFile.isDown()) {
967 setButtonsForDown();
968
969 } else {
970 setButtonsForRemote();
971 }
972 }
973 }
974 }
975
976
977 public void listenForTransferProgress() {
978 if (mProgressListener != null) {
979 if (mContainerActivity.getFileDownloaderBinder() != null) {
980 mContainerActivity.getFileDownloaderBinder().addDatatransferProgressListener(mProgressListener, mAccount, mFile);
981 }
982 if (mContainerActivity.getFileUploaderBinder() != null) {
983 mContainerActivity.getFileUploaderBinder().addDatatransferProgressListener(mProgressListener, mAccount, mFile);
984 }
985 }
986 }
987
988
989 public void leaveTransferProgress() {
990 if (mProgressListener != null) {
991 if (mContainerActivity.getFileDownloaderBinder() != null) {
992 mContainerActivity.getFileDownloaderBinder().removeDatatransferProgressListener(mProgressListener, mAccount, mFile);
993 }
994 if (mContainerActivity.getFileUploaderBinder() != null) {
995 mContainerActivity.getFileUploaderBinder().removeDatatransferProgressListener(mProgressListener, mAccount, mFile);
996 }
997 }
998 }
999
1000
1001
1002 /**
1003 * Helper class responsible for updating the progress bar shown for file uploading or downloading
1004 *
1005 * @author David A. Velasco
1006 */
1007 private class ProgressListener implements OnDatatransferProgressListener {
1008 int mLastPercent = 0;
1009 WeakReference<ProgressBar> mProgressBar = null;
1010
1011 ProgressListener(ProgressBar progressBar) {
1012 mProgressBar = new WeakReference<ProgressBar>(progressBar);
1013 }
1014
1015 @Override
1016 public void onTransferProgress(long progressRate) {
1017 // old method, nothing here
1018 };
1019
1020 @Override
1021 public void onTransferProgress(long progressRate, long totalTransferredSoFar, long totalToTransfer, String filename) {
1022 int percent = (int)(100.0*((double)totalTransferredSoFar)/((double)totalToTransfer));
1023 if (percent != mLastPercent) {
1024 ProgressBar pb = mProgressBar.get();
1025 if (pb != null) {
1026 pb.setProgress(percent);
1027 pb.postInvalidate();
1028 }
1029 }
1030 mLastPercent = percent;
1031 }
1032
1033 };
1034
1035
1036
1037 }