Cleaned stuff , add a history delete button and renamed
[pub/Android/ownCloud.git] / src / com / owncloud / android / ui / fragment / OCFileListFragment.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.util.ArrayList;
23 import java.util.List;
24
25 import com.owncloud.android.AccountUtils;
26 import com.owncloud.android.Log_OC;
27 import com.owncloud.android.R;
28 import com.owncloud.android.datamodel.DataStorageManager;
29 import com.owncloud.android.datamodel.OCFile;
30 import com.owncloud.android.files.services.FileDownloader.FileDownloaderBinder;
31 import com.owncloud.android.files.services.FileUploader.FileUploaderBinder;
32 import com.owncloud.android.network.OwnCloudClientUtils;
33 import com.owncloud.android.operations.OnRemoteOperationListener;
34 import com.owncloud.android.operations.RemoteOperation;
35 import com.owncloud.android.operations.RemoveFileOperation;
36 import com.owncloud.android.operations.RenameFileOperation;
37 import com.owncloud.android.operations.SynchronizeFileOperation;
38 import com.owncloud.android.ui.FragmentListView;
39 import com.owncloud.android.ui.activity.FileDisplayActivity;
40 import com.owncloud.android.ui.activity.TransferServiceGetter;
41 import com.owncloud.android.ui.adapter.FileListListAdapter;
42 import com.owncloud.android.ui.dialog.EditNameDialog;
43 import com.owncloud.android.ui.dialog.EditNameDialog.EditNameDialogListener;
44 import com.owncloud.android.ui.fragment.ConfirmationDialogFragment.ConfirmationDialogFragmentListener;
45
46 import eu.alefzero.webdav.WebdavClient;
47 import eu.alefzero.webdav.WebdavUtils;
48
49 import android.accounts.Account;
50 import android.app.Activity;
51 import android.content.ActivityNotFoundException;
52 import android.content.Intent;
53 import android.net.Uri;
54 import android.os.Bundle;
55 import android.os.Handler;
56 import android.support.v4.app.DialogFragment;
57 import android.util.Log;
58 import android.view.ContextMenu;
59 import android.view.MenuInflater;
60 import android.view.MenuItem;
61 import android.view.View;
62 import android.webkit.MimeTypeMap;
63 import android.widget.AdapterView;
64 import android.widget.Toast;
65 import android.widget.AdapterView.AdapterContextMenuInfo;
66
67 /**
68 * A Fragment that lists all files and folders in a given path.
69 *
70 * @author Bartek Przybylski
71 *
72 */
73 public class OCFileListFragment extends FragmentListView implements EditNameDialogListener, ConfirmationDialogFragmentListener {
74 private static final String TAG = "FileListFragment";
75 private static final String SAVED_LIST_POSITION = "LIST_POSITION";
76
77 private OCFileListFragment.ContainerActivity mContainerActivity;
78
79 private OCFile mFile = null;
80 private FileListListAdapter mAdapter;
81
82 private Handler mHandler;
83 private OCFile mTargetFile;
84
85 private DialogFragment mCurrentDialog;
86
87 /**
88 * {@inheritDoc}
89 */
90 @Override
91 public void onAttach(Activity activity) {
92 super.onAttach(activity);
93 try {
94 mContainerActivity = (ContainerActivity) activity;
95 } catch (ClassCastException e) {
96 throw new ClassCastException(activity.toString() + " must implement " + OCFileListFragment.ContainerActivity.class.getSimpleName());
97 }
98 }
99
100
101 /**
102 * {@inheritDoc}
103 */
104 @Override
105 public void onActivityCreated(Bundle savedInstanceState) {
106 Log_OC.i(TAG, "onActivityCreated() start");
107
108 super.onActivityCreated(savedInstanceState);
109 mAdapter = new FileListListAdapter(mContainerActivity.getInitialDirectory(), mContainerActivity.getStorageManager(), getActivity(), mContainerActivity);
110 setListAdapter(mAdapter);
111
112 if (savedInstanceState != null) {
113 Log_OC.i(TAG, "savedInstanceState is not null");
114 int position = savedInstanceState.getInt(SAVED_LIST_POSITION);
115 setReferencePosition(position);
116 }
117
118 registerForContextMenu(getListView());
119 getListView().setOnCreateContextMenuListener(this);
120
121 mHandler = new Handler();
122
123 Log_OC.i(TAG, "onActivityCreated() stop");
124 }
125
126
127
128 @Override
129 public void onSaveInstanceState(Bundle savedInstanceState) {
130 Log_OC.i(TAG, "onSaveInstanceState() start");
131
132 savedInstanceState.putInt(SAVED_LIST_POSITION, getReferencePosition());
133
134 Log_OC.i(TAG, "onSaveInstanceState() stop");
135 }
136
137
138 @Override
139 public void onItemClick(AdapterView<?> l, View v, int position, long id) {
140 OCFile file = (OCFile) mAdapter.getItem(position);
141 if (file != null) {
142 /// Click on a directory
143 if (file.getMimetype().equals("DIR")) {
144 // just local updates
145 mFile = file;
146 listDirectory(file);
147 // any other updates are let to the container Activity
148 mContainerActivity.onDirectoryClick(file);
149
150 } else { /// Click on a file
151 mContainerActivity.onFileClick(file);
152 }
153
154 } else {
155 Log_OC.d(TAG, "Null object in ListAdapter!!");
156 }
157
158 }
159
160 /**
161 * {@inheritDoc}
162 */
163 @Override
164 public void onCreateContextMenu (ContextMenu menu, View v, ContextMenu.ContextMenuInfo menuInfo) {
165 super.onCreateContextMenu(menu, v, menuInfo);
166 MenuInflater inflater = getActivity().getMenuInflater();
167 inflater.inflate(R.menu.file_context_menu, menu);
168 AdapterContextMenuInfo info = (AdapterContextMenuInfo) menuInfo;
169 OCFile targetFile = (OCFile) mAdapter.getItem(info.position);
170 List<Integer> toHide = new ArrayList<Integer>();
171 List<Integer> toDisable = new ArrayList<Integer>();
172
173 MenuItem item = null;
174 if (targetFile.isDirectory()) {
175 // contextual menu for folders
176 toHide.add(R.id.open_file_item);
177 toHide.add(R.id.download_file_item);
178 toHide.add(R.id.cancel_download_item);
179 toHide.add(R.id.cancel_upload_item);
180 if ( mContainerActivity.getFileDownloaderBinder().isDownloading(AccountUtils.getCurrentOwnCloudAccount(getActivity()), targetFile) ||
181 mContainerActivity.getFileUploaderBinder().isUploading(AccountUtils.getCurrentOwnCloudAccount(getActivity()), targetFile) ) {
182 toDisable.add(R.id.rename_file_item);
183 toDisable.add(R.id.remove_file_item);
184
185 }
186
187 } else {
188 // contextual menu for regular files
189 if (targetFile.isDown()) {
190 toHide.add(R.id.cancel_download_item);
191 toHide.add(R.id.cancel_upload_item);
192 item = menu.findItem(R.id.download_file_item);
193 if (item != null) {
194 item.setTitle(R.string.filedetails_sync_file);
195 }
196 } else {
197 toHide.add(R.id.open_file_item);
198 }
199 if ( mContainerActivity.getFileDownloaderBinder().isDownloading(AccountUtils.getCurrentOwnCloudAccount(getActivity()), targetFile)) {
200 toHide.add(R.id.download_file_item);
201 toHide.add(R.id.cancel_upload_item);
202 toDisable.add(R.id.open_file_item);
203 toDisable.add(R.id.rename_file_item);
204 toDisable.add(R.id.remove_file_item);
205
206 } else if ( mContainerActivity.getFileUploaderBinder().isUploading(AccountUtils.getCurrentOwnCloudAccount(getActivity()), targetFile)) {
207 toHide.add(R.id.download_file_item);
208 toHide.add(R.id.cancel_download_item);
209 toDisable.add(R.id.open_file_item);
210 toDisable.add(R.id.rename_file_item);
211 toDisable.add(R.id.remove_file_item);
212
213 } else {
214 toHide.add(R.id.cancel_download_item);
215 toHide.add(R.id.cancel_upload_item);
216 }
217 }
218
219 for (int i : toHide) {
220 item = menu.findItem(i);
221 if (item != null) {
222 item.setVisible(false);
223 item.setEnabled(false);
224 }
225 }
226
227 for (int i : toDisable) {
228 item = menu.findItem(i);
229 if (item != null) {
230 item.setEnabled(false);
231 }
232 }
233 }
234
235
236 /**
237 * {@inhericDoc}
238 */
239 @Override
240 public boolean onContextItemSelected (MenuItem item) {
241 AdapterContextMenuInfo info = (AdapterContextMenuInfo) item.getMenuInfo();
242 mTargetFile = (OCFile) mAdapter.getItem(info.position);
243 switch (item.getItemId()) {
244 case R.id.rename_file_item: {
245 EditNameDialog dialog = EditNameDialog.newInstance(getString(R.string.rename_dialog_title), mTargetFile.getFileName(), this);
246 dialog.show(getFragmentManager(), EditNameDialog.TAG);
247 return true;
248 }
249 case R.id.remove_file_item: {
250 int messageStringId = R.string.confirmation_remove_alert;
251 int posBtnStringId = R.string.confirmation_remove_remote;
252 int neuBtnStringId = -1;
253 if (mTargetFile.isDirectory()) {
254 messageStringId = R.string.confirmation_remove_folder_alert;
255 posBtnStringId = R.string.confirmation_remove_remote_and_local;
256 neuBtnStringId = R.string.confirmation_remove_folder_local;
257 } else if (mTargetFile.isDown()) {
258 posBtnStringId = R.string.confirmation_remove_remote_and_local;
259 neuBtnStringId = R.string.confirmation_remove_local;
260 }
261 ConfirmationDialogFragment confDialog = ConfirmationDialogFragment.newInstance(
262 messageStringId,
263 new String[]{mTargetFile.getFileName()},
264 posBtnStringId,
265 neuBtnStringId,
266 R.string.common_cancel);
267 confDialog.setOnConfirmationListener(this);
268 mCurrentDialog = confDialog;
269 mCurrentDialog.show(getFragmentManager(), FileDetailFragment.FTAG_CONFIRMATION);
270 return true;
271 }
272 case R.id.open_file_item: {
273 String storagePath = mTargetFile.getStoragePath();
274 String encodedStoragePath = WebdavUtils.encodePath(storagePath);
275 try {
276 Intent i = new Intent(Intent.ACTION_VIEW);
277 i.setDataAndType(Uri.parse("file://"+ encodedStoragePath), mTargetFile.getMimetype());
278 i.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION | Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
279 startActivity(i);
280
281 } catch (Throwable t) {
282 Log_OC.e(TAG, "Fail when trying to open with the mimeType provided from the ownCloud server: " + mTargetFile.getMimetype());
283 boolean toastIt = true;
284 String mimeType = "";
285 try {
286 Intent i = new Intent(Intent.ACTION_VIEW);
287 mimeType = MimeTypeMap.getSingleton().getMimeTypeFromExtension(storagePath.substring(storagePath.lastIndexOf('.') + 1));
288 if (mimeType == null || !mimeType.equals(mTargetFile.getMimetype())) {
289 if (mimeType != null) {
290 i.setDataAndType(Uri.parse("file://"+ encodedStoragePath), mimeType);
291 } else {
292 // desperate try
293 i.setDataAndType(Uri.parse("file://"+ encodedStoragePath), "*/*");
294 }
295 i.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION | Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
296 startActivity(i);
297 toastIt = false;
298 }
299
300 } catch (IndexOutOfBoundsException e) {
301 Log_OC.e(TAG, "Trying to find out MIME type of a file without extension: " + storagePath);
302
303 } catch (ActivityNotFoundException e) {
304 Log_OC.e(TAG, "No activity found to handle: " + storagePath + " with MIME type " + mimeType + " obtained from extension");
305
306 } catch (Throwable th) {
307 Log_OC.e(TAG, "Unexpected problem when opening: " + storagePath, th);
308
309 } finally {
310 if (toastIt) {
311 Toast.makeText(getActivity(), "There is no application to handle file " + mTargetFile.getFileName(), Toast.LENGTH_SHORT).show();
312 }
313 }
314
315 }
316 return true;
317 }
318 case R.id.download_file_item: {
319 Account account = AccountUtils.getCurrentOwnCloudAccount(getSherlockActivity());
320 RemoteOperation operation = new SynchronizeFileOperation(mTargetFile, null, mContainerActivity.getStorageManager(), account, true, false, getSherlockActivity());
321 WebdavClient wc = OwnCloudClientUtils.createOwnCloudClient(account, getSherlockActivity().getApplicationContext());
322 operation.execute(wc, mContainerActivity, mHandler);
323 getSherlockActivity().showDialog(FileDisplayActivity.DIALOG_SHORT_WAIT);
324 return true;
325 }
326 case R.id.cancel_download_item: {
327 FileDownloaderBinder downloaderBinder = mContainerActivity.getFileDownloaderBinder();
328 Account account = AccountUtils.getCurrentOwnCloudAccount(getActivity());
329 if (downloaderBinder != null && downloaderBinder.isDownloading(account, mTargetFile)) {
330 downloaderBinder.cancel(account, mTargetFile);
331 listDirectory();
332 mContainerActivity.onTransferStateChanged(mTargetFile, false, false);
333 }
334 return true;
335 }
336 case R.id.cancel_upload_item: {
337 FileUploaderBinder uploaderBinder = mContainerActivity.getFileUploaderBinder();
338 Account account = AccountUtils.getCurrentOwnCloudAccount(getActivity());
339 if (uploaderBinder != null && uploaderBinder.isUploading(account, mTargetFile)) {
340 uploaderBinder.cancel(account, mTargetFile);
341 listDirectory();
342 mContainerActivity.onTransferStateChanged(mTargetFile, false, false);
343 }
344 return true;
345 }
346 default:
347 return super.onContextItemSelected(item);
348 }
349 }
350
351
352 /**
353 * Call this, when the user presses the up button
354 */
355 public void onNavigateUp() {
356 OCFile parentDir = null;
357
358 if(mFile != null){
359 DataStorageManager storageManager = mContainerActivity.getStorageManager();
360 parentDir = storageManager.getFileById(mFile.getParentId());
361 mFile = parentDir;
362 }
363 listDirectory(parentDir);
364 }
365
366 /**
367 * Use this to query the {@link OCFile} that is currently
368 * being displayed by this fragment
369 * @return The currently viewed OCFile
370 */
371 public OCFile getCurrentFile(){
372 return mFile;
373 }
374
375 /**
376 * Calls {@link OCFileListFragment#listDirectory(OCFile)} with a null parameter
377 */
378 public void listDirectory(){
379 listDirectory(null);
380 }
381
382 /**
383 * Lists the given directory on the view. When the input parameter is null,
384 * it will either refresh the last known directory, or list the root
385 * if there never was a directory.
386 *
387 * @param directory File to be listed
388 */
389 public void listDirectory(OCFile directory) {
390 DataStorageManager storageManager = mContainerActivity.getStorageManager();
391 if (storageManager != null) {
392
393 // Check input parameters for null
394 if(directory == null){
395 if(mFile != null){
396 directory = mFile;
397 } else {
398 directory = storageManager.getFileByPath("/");
399 if (directory == null) return; // no files, wait for sync
400 }
401 }
402
403
404 // If that's not a directory -> List its parent
405 if(!directory.isDirectory()){
406 Log_OC.w(TAG, "You see, that is not a directory -> " + directory.toString());
407 directory = storageManager.getFileById(directory.getParentId());
408 }
409
410 mAdapter.swapDirectory(directory, storageManager);
411 if (mFile == null || !mFile.equals(directory)) {
412 mList.setSelectionFromTop(0, 0);
413 }
414 mFile = directory;
415 }
416 }
417
418
419
420 /**
421 * Interface to implement by any Activity that includes some instance of FileListFragment
422 *
423 * @author David A. Velasco
424 */
425 public interface ContainerActivity extends TransferServiceGetter, OnRemoteOperationListener {
426
427 /**
428 * Callback method invoked when a directory is clicked by the user on the files list
429 *
430 * @param file
431 */
432 public void onDirectoryClick(OCFile file);
433
434 /**
435 * Callback method invoked when a file (non directory) is clicked by the user on the files list
436 *
437 * @param file
438 */
439 public void onFileClick(OCFile file);
440
441 /**
442 * Getter for the current DataStorageManager in the container activity
443 */
444 public DataStorageManager getStorageManager();
445
446
447 /**
448 * Callback method invoked when the parent activity is fully created to get the directory to list firstly.
449 *
450 * @return Directory to list firstly. Can be NULL.
451 */
452 public OCFile getInitialDirectory();
453
454
455 /**
456 * Callback method invoked when a the 'transfer state' of a file changes.
457 *
458 * This happens when a download or upload is started or ended for a file.
459 *
460 * This method is necessary by now to update the user interface of the double-pane layout in tablets
461 * because methods {@link FileDownloaderBinder#isDownloading(Account, OCFile)} and {@link FileUploaderBinder#isUploading(Account, OCFile)}
462 * won't provide the needed response before the method where this is called finishes.
463 *
464 * TODO Remove this when the transfer state of a file is kept in the database (other thing TODO)
465 *
466 * @param file OCFile which state changed.
467 * @param downloading Flag signaling if the file is now downloading.
468 * @param uploading Flag signaling if the file is now uploading.
469 */
470 public void onTransferStateChanged(OCFile file, boolean downloading, boolean uploading);
471
472 }
473
474
475 @Override
476 public void onDismiss(EditNameDialog dialog) {
477 if (dialog.getResult()) {
478 String newFilename = dialog.getNewFilename();
479 Log_OC.d(TAG, "name edit dialog dismissed with new name " + newFilename);
480 RemoteOperation operation = new RenameFileOperation(mTargetFile,
481 AccountUtils.getCurrentOwnCloudAccount(getActivity()),
482 newFilename,
483 mContainerActivity.getStorageManager());
484 WebdavClient wc = OwnCloudClientUtils.createOwnCloudClient(AccountUtils.getCurrentOwnCloudAccount(getSherlockActivity()), getSherlockActivity().getApplicationContext());
485 operation.execute(wc, mContainerActivity, mHandler);
486 getActivity().showDialog(FileDisplayActivity.DIALOG_SHORT_WAIT);
487 }
488 }
489
490
491 @Override
492 public void onConfirmation(String callerTag) {
493 if (callerTag.equals(FileDetailFragment.FTAG_CONFIRMATION)) {
494 if (mContainerActivity.getStorageManager().getFileById(mTargetFile.getFileId()) != null) {
495 RemoteOperation operation = new RemoveFileOperation( mTargetFile,
496 true,
497 mContainerActivity.getStorageManager());
498 WebdavClient wc = OwnCloudClientUtils.createOwnCloudClient(AccountUtils.getCurrentOwnCloudAccount(getSherlockActivity()), getSherlockActivity().getApplicationContext());
499 operation.execute(wc, mContainerActivity, mHandler);
500
501 getActivity().showDialog(FileDisplayActivity.DIALOG_SHORT_WAIT);
502 }
503 if (mCurrentDialog != null) {
504 mCurrentDialog.dismiss();
505 mCurrentDialog = null;
506 }
507 }
508 }
509
510 @Override
511 public void onNeutral(String callerTag) {
512 File f = null;
513 if (mTargetFile.isDirectory()) {
514 // TODO run in a secondary thread?
515 mContainerActivity.getStorageManager().removeDirectory(mTargetFile, false, true);
516
517 } else if (mTargetFile.isDown() && (f = new File(mTargetFile.getStoragePath())).exists()) {
518 f.delete();
519 mTargetFile.setStoragePath(null);
520 mContainerActivity.getStorageManager().saveFile(mTargetFile);
521 }
522 if (mCurrentDialog != null) {
523 mCurrentDialog.dismiss();
524 mCurrentDialog = null;
525 }
526 listDirectory();
527 mContainerActivity.onTransferStateChanged(mTargetFile, false, false);
528 }
529
530 @Override
531 public void onCancel(String callerTag) {
532 Log_OC.d(TAG, "REMOVAL CANCELED");
533 if (mCurrentDialog != null) {
534 mCurrentDialog.dismiss();
535 mCurrentDialog = null;
536 }
537 }
538
539
540 }