Fixed inconsistencies in file actions shown in menus according to the target file...
[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 version 2,
7 * as published by the Free Software Foundation.
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.fragment;
19
20 import java.lang.ref.WeakReference;
21
22 import android.accounts.Account;
23 import android.content.Intent;
24 import android.os.Bundle;
25 import android.view.LayoutInflater;
26 import android.view.View;
27 import android.view.View.OnClickListener;
28 import android.view.ViewGroup;
29 import android.widget.CheckBox;
30 import android.widget.ImageView;
31 import android.widget.ProgressBar;
32 import android.widget.TextView;
33
34 import com.actionbarsherlock.view.Menu;
35 import com.actionbarsherlock.view.MenuInflater;
36 import com.actionbarsherlock.view.MenuItem;
37 import com.owncloud.android.R;
38 import com.owncloud.android.datamodel.FileDataStorageManager;
39 import com.owncloud.android.datamodel.OCFile;
40 import com.owncloud.android.files.FileMenuFilter;
41 import com.owncloud.android.files.services.FileObserverService;
42 import com.owncloud.android.files.services.FileDownloader.FileDownloaderBinder;
43 import com.owncloud.android.files.services.FileUploader.FileUploaderBinder;
44 import com.owncloud.android.lib.common.network.OnDatatransferProgressListener;
45 import com.owncloud.android.ui.activity.FileActivity;
46 import com.owncloud.android.ui.activity.FileDisplayActivity;
47 import com.owncloud.android.ui.dialog.ConfirmationDialogFragment;
48 import com.owncloud.android.ui.dialog.EditNameDialog;
49 import com.owncloud.android.ui.dialog.EditNameDialog.EditNameDialogListener;
50 import com.owncloud.android.utils.DisplayUtils;
51 import com.owncloud.android.utils.Log_OC;
52
53
54 /**
55 * This Fragment is used to display the details about a file.
56 *
57 * @author Bartek Przybylski
58 * @author David A. Velasco
59 */
60 public class FileDetailFragment extends FileFragment implements
61 OnClickListener,
62 ConfirmationDialogFragment.ConfirmationDialogFragmentListener, EditNameDialogListener {
63
64 private int mLayout;
65 private View mView;
66 private Account mAccount;
67
68 public ProgressListener mProgressListener;
69
70 private static final String TAG = FileDetailFragment.class.getSimpleName();
71 public static final String FTAG_CONFIRMATION = "REMOVE_CONFIRMATION_FRAGMENT";
72 public static final String FTAG_RENAME_FILE = "RENAME_FILE_FRAGMENT";
73
74
75 /**
76 * Creates an empty details fragment.
77 *
78 * It's necessary to keep a public constructor without parameters; the system uses it when tries to reinstantiate a fragment automatically.
79 */
80 public FileDetailFragment() {
81 super();
82 mAccount = null;
83 mLayout = R.layout.file_details_empty;
84 mProgressListener = null;
85 }
86
87 /**
88 * Creates a details fragment.
89 *
90 * When 'fileToDetail' or 'ocAccount' are null, creates a dummy layout (to use when a file wasn't tapped before).
91 *
92 * @param fileToDetail An {@link OCFile} to show in the fragment
93 * @param ocAccount An ownCloud account; needed to start downloads
94 */
95 public FileDetailFragment(OCFile fileToDetail, Account ocAccount) {
96 super(fileToDetail);
97 mAccount = ocAccount;
98 mLayout = R.layout.file_details_empty;
99 mProgressListener = null;
100 }
101
102
103 @Override
104 public void onCreate(Bundle savedInstanceState) {
105 super.onCreate(savedInstanceState);
106 setHasOptionsMenu(true);
107 }
108
109
110 @Override
111 public View onCreateView(LayoutInflater inflater, ViewGroup container,
112 Bundle savedInstanceState) {
113
114 if (savedInstanceState != null) {
115 setFile((OCFile)savedInstanceState.getParcelable(FileActivity.EXTRA_FILE));
116 mAccount = savedInstanceState.getParcelable(FileActivity.EXTRA_ACCOUNT);
117 }
118
119 if(getFile() != null && mAccount != null) {
120 mLayout = R.layout.file_details_fragment;
121 }
122
123 View view = null;
124 view = inflater.inflate(mLayout, null);
125 mView = view;
126
127 if (mLayout == R.layout.file_details_fragment) {
128 mView.findViewById(R.id.fdKeepInSync).setOnClickListener(this);
129 ProgressBar progressBar = (ProgressBar)mView.findViewById(R.id.fdProgressBar);
130 mProgressListener = new ProgressListener(progressBar);
131 mView.findViewById(R.id.fdCancelBtn).setOnClickListener(this);
132 }
133
134 updateFileDetails(false, false);
135 return view;
136 }
137
138 @Override
139 public void onSaveInstanceState(Bundle outState) {
140 super.onSaveInstanceState(outState);
141 outState.putParcelable(FileActivity.EXTRA_FILE, getFile());
142 outState.putParcelable(FileActivity.EXTRA_ACCOUNT, mAccount);
143 }
144
145 @Override
146 public void onStart() {
147 super.onStart();
148 listenForTransferProgress();
149 }
150
151 @Override
152 public void onStop() {
153 super.onStop();
154 leaveTransferProgress();
155 }
156
157
158 @Override
159 public View getView() {
160 return super.getView() == null ? mView : super.getView();
161 }
162
163
164 /**
165 * {@inheritDoc}
166 */
167 @Override
168 public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
169 super.onCreateOptionsMenu(menu, inflater);
170 inflater.inflate(R.menu.file_actions_menu, menu);
171 }
172
173
174 /**
175 * {@inheritDoc}
176 */
177 @Override
178 public void onPrepareOptionsMenu (Menu menu) {
179 super.onPrepareOptionsMenu(menu);
180
181 FileMenuFilter mf = new FileMenuFilter(
182 getFile(),
183 mContainerActivity.getStorageManager().getAccount(),
184 mContainerActivity,
185 getSherlockActivity()
186 );
187 mf.filter(menu);
188
189 // additional restriction for this fragment
190 MenuItem item = menu.findItem(R.id.action_see_details);
191 if (item != null) {
192 item.setVisible(false);
193 item.setEnabled(false);
194 }
195 }
196
197
198 /**
199 * {@inheritDoc}
200 */
201 @Override
202 public boolean onOptionsItemSelected(MenuItem item) {
203 switch (item.getItemId()) {
204 case R.id.action_share_file: {
205 mContainerActivity.getFileOperationsHelper().shareFileWithLink(getFile());
206 return true;
207 }
208 case R.id.action_unshare_file: {
209 mContainerActivity.getFileOperationsHelper().unshareFileWithLink(getFile());
210 return true;
211 }
212 case R.id.action_open_file_with: {
213 mContainerActivity.getFileOperationsHelper().openFile(getFile());
214 return true;
215 }
216 case R.id.action_remove_file: {
217 showDialogToRemoveFile();
218 return true;
219 }
220 case R.id.action_rename_file: {
221 showDialogToRenameFile();
222 return true;
223 }
224 case R.id.action_cancel_download:
225 case R.id.action_cancel_upload: {
226 ((FileDisplayActivity)mContainerActivity).cancelTransference(getFile());
227 return true;
228 }
229 case R.id.action_download_file:
230 case R.id.action_sync_file: {
231 mContainerActivity.getFileOperationsHelper().syncFile(getFile());
232 return true;
233 }
234 case R.id.action_send_file: {
235 // Obtain the file
236 if (!getFile().isDown()) { // Download the file
237 Log_OC.d(TAG, getFile().getRemotePath() + " : File must be downloaded");
238 ((FileDisplayActivity)mContainerActivity).startDownloadForSending(getFile());
239
240 } else {
241 mContainerActivity.getFileOperationsHelper().sendDownloadedFile(getFile());
242 }
243 return true;
244 }
245 default:
246 return false;
247 }
248 }
249
250 @Override
251 public void onClick(View v) {
252 switch (v.getId()) {
253 case R.id.fdKeepInSync: {
254 toggleKeepInSync();
255 break;
256 }
257 case R.id.fdCancelBtn: {
258 ((FileDisplayActivity)mContainerActivity).cancelTransference(getFile());
259 break;
260 }
261 default:
262 Log_OC.e(TAG, "Incorrect view clicked!");
263 }
264 }
265
266
267 private void toggleKeepInSync() {
268 CheckBox cb = (CheckBox) getView().findViewById(R.id.fdKeepInSync);
269 OCFile file = getFile();
270 file.setKeepInSync(cb.isChecked());
271 mContainerActivity.getStorageManager().saveFile(file);
272
273 /// register the OCFile instance in the observer service to monitor local updates;
274 /// if necessary, the file is download
275 Intent intent = new Intent(getActivity().getApplicationContext(),
276 FileObserverService.class);
277 intent.putExtra(FileObserverService.KEY_FILE_CMD,
278 (cb.isChecked()?
279 FileObserverService.CMD_ADD_OBSERVED_FILE:
280 FileObserverService.CMD_DEL_OBSERVED_FILE));
281 intent.putExtra(FileObserverService.KEY_CMD_ARG_FILE, file);
282 intent.putExtra(FileObserverService.KEY_CMD_ARG_ACCOUNT, mAccount);
283 getActivity().startService(intent);
284
285 if (file.keepInSync()) {
286 mContainerActivity.getFileOperationsHelper().syncFile(getFile());
287 }
288 }
289
290 private void showDialogToRemoveFile() {
291 OCFile file = getFile();
292 ConfirmationDialogFragment confDialog = ConfirmationDialogFragment.newInstance(
293 R.string.confirmation_remove_alert,
294 new String[]{file.getFileName()},
295 file.isDown() ? R.string.confirmation_remove_remote_and_local : R.string.confirmation_remove_remote,
296 file.isDown() ? R.string.confirmation_remove_local : -1,
297 R.string.common_cancel);
298 confDialog.setOnConfirmationListener(this);
299 confDialog.show(getFragmentManager(), FTAG_CONFIRMATION);
300 }
301
302
303 private void showDialogToRenameFile() {
304 OCFile file = getFile();
305 String fileName = file.getFileName();
306 int extensionStart = file.isFolder() ? -1 : fileName.lastIndexOf(".");
307 int selectionEnd = (extensionStart >= 0) ? extensionStart : fileName.length();
308 EditNameDialog dialog = EditNameDialog.newInstance(getString(R.string.rename_dialog_title), fileName, 0, selectionEnd, this);
309 dialog.show(getFragmentManager(), FTAG_RENAME_FILE);
310 }
311
312
313 @Override
314 public void onConfirmation(String callerTag) {
315 OCFile file = getFile();
316 if (callerTag.equals(FTAG_CONFIRMATION)) {
317 if (mContainerActivity.getStorageManager().getFileById(file.getFileId()) != null) {
318 mContainerActivity.getFileOperationsHelper().removeFile(file, true);
319 }
320 }
321 }
322
323 @Override
324 public void onNeutral(String callerTag) {
325 OCFile file = getFile();
326 mContainerActivity.getStorageManager().removeFile(file, false, true); // TODO perform in background task / new thread
327 if (file.getStoragePath() != null) {
328 file.setStoragePath(null);
329 updateFileDetails(file, mAccount);
330 }
331 }
332
333 @Override
334 public void onCancel(String callerTag) {
335 Log_OC.d(TAG, "REMOVAL CANCELED");
336 }
337
338
339 /**
340 * Check if the fragment was created with an empty layout. An empty fragment can't show file details, must be replaced.
341 *
342 * @return True when the fragment was created with the empty layout.
343 */
344 public boolean isEmpty() {
345 return (mLayout == R.layout.file_details_empty || getFile() == null || mAccount == null);
346 }
347
348
349 /**
350 * Use this method to signal this Activity that it shall update its view.
351 *
352 * @param file : An {@link OCFile}
353 */
354 public void updateFileDetails(OCFile file, Account ocAccount) {
355 setFile(file);
356 mAccount = ocAccount;
357 updateFileDetails(false, false);
358 }
359
360 /**
361 * Updates the view with all relevant details about that file.
362 *
363 * TODO Remove parameter when the transferring state of files is kept in database.
364 *
365 * @param transferring Flag signaling if the file should be considered as downloading or uploading,
366 * although {@link FileDownloaderBinder#isDownloading(Account, OCFile)} and
367 * {@link FileUploaderBinder#isUploading(Account, OCFile)} return false.
368 *
369 * @param refresh If 'true', try to refresh the whole file from the database
370 */
371 public void updateFileDetails(boolean transferring, boolean refresh) {
372 if (readyToShow()) {
373 FileDataStorageManager storageManager = mContainerActivity.getStorageManager();
374 if (refresh && storageManager != null) {
375 setFile(storageManager.getFileByPath(getFile().getRemotePath()));
376 }
377 OCFile file = getFile();
378
379 // set file details
380 setFilename(file.getFileName());
381 setFiletype(file.getMimetype());
382 setFilesize(file.getFileLength());
383 if(ocVersionSupportsTimeCreated()){
384 setTimeCreated(file.getCreationTimestamp());
385 }
386
387 setTimeModified(file.getModificationTimestamp());
388
389 CheckBox cb = (CheckBox)getView().findViewById(R.id.fdKeepInSync);
390 cb.setChecked(file.keepInSync());
391
392 // configure UI for depending upon local state of the file
393 FileDownloaderBinder downloaderBinder = mContainerActivity.getFileDownloaderBinder();
394 FileUploaderBinder uploaderBinder = mContainerActivity.getFileUploaderBinder();
395 if (transferring || (downloaderBinder != null && downloaderBinder.isDownloading(mAccount, file)) || (uploaderBinder != null && uploaderBinder.isUploading(mAccount, file))) {
396 setButtonsForTransferring();
397
398 } else if (file.isDown()) {
399
400 setButtonsForDown();
401
402 } else {
403 // TODO load default preview image; when the local file is removed, the preview remains there
404 setButtonsForRemote();
405 }
406 }
407 getView().invalidate();
408 }
409
410 /**
411 * Checks if the fragment is ready to show details of a OCFile
412 *
413 * @return 'True' when the fragment is ready to show details of a file
414 */
415 private boolean readyToShow() {
416 return (getFile() != null && mAccount != null && mLayout == R.layout.file_details_fragment);
417 }
418
419
420 /**
421 * Updates the filename in view
422 * @param filename to set
423 */
424 private void setFilename(String filename) {
425 TextView tv = (TextView) getView().findViewById(R.id.fdFilename);
426 if (tv != null)
427 tv.setText(filename);
428 }
429
430 /**
431 * Updates the MIME type in view
432 * @param mimetype to set
433 */
434 private void setFiletype(String mimetype) {
435 TextView tv = (TextView) getView().findViewById(R.id.fdType);
436 if (tv != null) {
437 String printableMimetype = DisplayUtils.convertMIMEtoPrettyPrint(mimetype);;
438 tv.setText(printableMimetype);
439 }
440 ImageView iv = (ImageView) getView().findViewById(R.id.fdIcon);
441 if (iv != null) {
442 iv.setImageResource(DisplayUtils.getResourceId(mimetype));
443 }
444 }
445
446 /**
447 * Updates the file size in view
448 * @param filesize in bytes to set
449 */
450 private void setFilesize(long filesize) {
451 TextView tv = (TextView) getView().findViewById(R.id.fdSize);
452 if (tv != null)
453 tv.setText(DisplayUtils.bytesToHumanReadable(filesize));
454 }
455
456 /**
457 * Updates the time that the file was created in view
458 * @param milliseconds Unix time to set
459 */
460 private void setTimeCreated(long milliseconds){
461 TextView tv = (TextView) getView().findViewById(R.id.fdCreated);
462 TextView tvLabel = (TextView) getView().findViewById(R.id.fdCreatedLabel);
463 if(tv != null){
464 tv.setText(DisplayUtils.unixTimeToHumanReadable(milliseconds));
465 tv.setVisibility(View.VISIBLE);
466 tvLabel.setVisibility(View.VISIBLE);
467 }
468 }
469
470 /**
471 * Updates the time that the file was last modified
472 * @param milliseconds Unix time to set
473 */
474 private void setTimeModified(long milliseconds){
475 TextView tv = (TextView) getView().findViewById(R.id.fdModified);
476 if(tv != null){
477 tv.setText(DisplayUtils.unixTimeToHumanReadable(milliseconds));
478 }
479 }
480
481 /**
482 * Enables or disables buttons for a file being downloaded
483 */
484 private void setButtonsForTransferring() {
485 if (!isEmpty()) {
486 // let's protect the user from himself ;)
487 getView().findViewById(R.id.fdKeepInSync).setEnabled(false);
488
489 // show the progress bar for the transfer
490 getView().findViewById(R.id.fdProgressBlock).setVisibility(View.VISIBLE);
491 TextView progressText = (TextView)getView().findViewById(R.id.fdProgressText);
492 progressText.setVisibility(View.VISIBLE);
493 FileDownloaderBinder downloaderBinder = mContainerActivity.getFileDownloaderBinder();
494 FileUploaderBinder uploaderBinder = mContainerActivity.getFileUploaderBinder();
495 if (downloaderBinder != null && downloaderBinder.isDownloading(mAccount, getFile())) {
496 progressText.setText(R.string.downloader_download_in_progress_ticker);
497 } else if (uploaderBinder != null && uploaderBinder.isUploading(mAccount, getFile())) {
498 progressText.setText(R.string.uploader_upload_in_progress_ticker);
499 }
500 }
501 }
502
503 /**
504 * Enables or disables buttons for a file locally available
505 */
506 private void setButtonsForDown() {
507 if (!isEmpty()) {
508 getView().findViewById(R.id.fdKeepInSync).setEnabled(true);
509
510 // hides the progress bar
511 getView().findViewById(R.id.fdProgressBlock).setVisibility(View.GONE);
512 TextView progressText = (TextView)getView().findViewById(R.id.fdProgressText);
513 progressText.setVisibility(View.GONE);
514 }
515 }
516
517 /**
518 * Enables or disables buttons for a file not locally available
519 */
520 private void setButtonsForRemote() {
521 if (!isEmpty()) {
522 getView().findViewById(R.id.fdKeepInSync).setEnabled(true);
523
524 // hides the progress bar
525 getView().findViewById(R.id.fdProgressBlock).setVisibility(View.GONE);
526 TextView progressText = (TextView)getView().findViewById(R.id.fdProgressText);
527 progressText.setVisibility(View.GONE);
528 }
529 }
530
531
532 /**
533 * In ownCloud 3.X.X and 4.X.X there is a bug that SabreDAV does not return
534 * the time that the file was created. There is a chance that this will
535 * be fixed in future versions. Use this method to check if this version of
536 * ownCloud has this fix.
537 * @return True, if ownCloud the ownCloud version is supporting creation time
538 */
539 private boolean ocVersionSupportsTimeCreated(){
540 /*if(mAccount != null){
541 AccountManager accManager = (AccountManager) getActivity().getSystemService(Context.ACCOUNT_SERVICE);
542 OwnCloudVersion ocVersion = new OwnCloudVersion(accManager
543 .getUserData(mAccount, AccountAuthenticator.KEY_OC_VERSION));
544 if(ocVersion.compareTo(new OwnCloudVersion(0x030000)) < 0) {
545 return true;
546 }
547 }*/
548 return false;
549 }
550
551
552 public void onDismiss(EditNameDialog dialog) {
553 if (dialog.getResult()) {
554 String newFilename = dialog.getNewFilename();
555 Log_OC.d(TAG, "name edit dialog dismissed with new name " + newFilename);
556 mContainerActivity.getFileOperationsHelper().renameFile(getFile(), newFilename);
557 }
558 }
559
560
561 public void listenForTransferProgress() {
562 if (mProgressListener != null) {
563 if (mContainerActivity.getFileDownloaderBinder() != null) {
564 mContainerActivity.getFileDownloaderBinder().addDatatransferProgressListener(mProgressListener, mAccount, getFile());
565 }
566 if (mContainerActivity.getFileUploaderBinder() != null) {
567 mContainerActivity.getFileUploaderBinder().addDatatransferProgressListener(mProgressListener, mAccount, getFile());
568 }
569 }
570 }
571
572
573 public void leaveTransferProgress() {
574 if (mProgressListener != null) {
575 if (mContainerActivity.getFileDownloaderBinder() != null) {
576 mContainerActivity.getFileDownloaderBinder().removeDatatransferProgressListener(mProgressListener, mAccount, getFile());
577 }
578 if (mContainerActivity.getFileUploaderBinder() != null) {
579 mContainerActivity.getFileUploaderBinder().removeDatatransferProgressListener(mProgressListener, mAccount, getFile());
580 }
581 }
582 }
583
584
585
586 /**
587 * Helper class responsible for updating the progress bar shown for file uploading or downloading
588 *
589 * @author David A. Velasco
590 */
591 private class ProgressListener implements OnDatatransferProgressListener {
592 int mLastPercent = 0;
593 WeakReference<ProgressBar> mProgressBar = null;
594
595 ProgressListener(ProgressBar progressBar) {
596 mProgressBar = new WeakReference<ProgressBar>(progressBar);
597 }
598
599 @Override
600 public void onTransferProgress(long progressRate, long totalTransferredSoFar, long totalToTransfer, String filename) {
601 int percent = (int)(100.0*((double)totalTransferredSoFar)/((double)totalToTransfer));
602 if (percent != mLastPercent) {
603 ProgressBar pb = mProgressBar.get();
604 if (pb != null) {
605 pb.setProgress(percent);
606 pb.postInvalidate();
607 }
608 }
609 mLastPercent = percent;
610 }
611
612 };
613
614 }