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