dac32c8584c9c48379679d5bb5b1108f3504e5bf
[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 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.io.File;
21 import java.util.ArrayList;
22 import java.util.List;
23
24 import com.owncloud.android.AccountUtils;
25 import com.owncloud.android.Log_OC;
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.util.Log;
56 import android.view.ContextMenu;
57 import android.view.MenuInflater;
58 import android.view.MenuItem;
59 import android.view.View;
60 import android.webkit.MimeTypeMap;
61 import android.widget.AdapterView;
62 import android.widget.Toast;
63 import android.widget.AdapterView.AdapterContextMenuInfo;
64
65 /**
66 * A Fragment that lists all files and folders in a given path.
67 *
68 * @author Bartek Przybylski
69 *
70 */
71 public class OCFileListFragment extends FragmentListView implements EditNameDialogListener, ConfirmationDialogFragmentListener {
72 private static final String TAG = "FileListFragment";
73 private static final String SAVED_LIST_POSITION = "LIST_POSITION";
74
75 private OCFileListFragment.ContainerActivity mContainerActivity;
76
77 private OCFile mFile = null;
78 private FileListListAdapter mAdapter;
79
80 private Handler mHandler;
81 private OCFile mTargetFile;
82
83 /**
84 * {@inheritDoc}
85 */
86 @Override
87 public void onAttach(Activity activity) {
88 super.onAttach(activity);
89 try {
90 mContainerActivity = (ContainerActivity) activity;
91 } catch (ClassCastException e) {
92 throw new ClassCastException(activity.toString() + " must implement " + OCFileListFragment.ContainerActivity.class.getSimpleName());
93 }
94 }
95
96
97 /**
98 * {@inheritDoc}
99 */
100 @Override
101 public void onActivityCreated(Bundle savedInstanceState) {
102 Log_OC.i(TAG, "onActivityCreated() start");
103
104 super.onActivityCreated(savedInstanceState);
105 mAdapter = new FileListListAdapter(mContainerActivity.getInitialDirectory(), mContainerActivity.getStorageManager(), getActivity(), mContainerActivity);
106 setListAdapter(mAdapter);
107
108 if (savedInstanceState != null) {
109 Log_OC.i(TAG, "savedInstanceState is not null");
110 int position = savedInstanceState.getInt(SAVED_LIST_POSITION);
111 setReferencePosition(position);
112 }
113
114 registerForContextMenu(getListView());
115 getListView().setOnCreateContextMenuListener(this);
116
117 mHandler = new Handler();
118
119 Log_OC.i(TAG, "onActivityCreated() stop");
120 }
121
122
123
124 @Override
125 public void onSaveInstanceState(Bundle savedInstanceState) {
126 Log_OC.i(TAG, "onSaveInstanceState() start");
127
128 savedInstanceState.putInt(SAVED_LIST_POSITION, getReferencePosition());
129
130 Log_OC.i(TAG, "onSaveInstanceState() stop");
131 }
132
133
134 @Override
135 public void onItemClick(AdapterView<?> l, View v, int position, long id) {
136 OCFile file = (OCFile) mAdapter.getItem(position);
137 if (file != null) {
138 /// Click on a directory
139 if (file.getMimetype().equals("DIR")) {
140 // just local updates
141 mFile = file;
142 listDirectory(file);
143 // any other updates are let to the container Activity
144 mContainerActivity.onDirectoryClick(file);
145
146 } else { /// Click on a file
147 mContainerActivity.onFileClick(file);
148 }
149
150 } else {
151 Log_OC.d(TAG, "Null object in ListAdapter!!");
152 }
153
154 }
155
156 /**
157 * {@inheritDoc}
158 */
159 @Override
160 public void onCreateContextMenu (ContextMenu menu, View v, ContextMenu.ContextMenuInfo menuInfo) {
161 super.onCreateContextMenu(menu, v, menuInfo);
162 MenuInflater inflater = getActivity().getMenuInflater();
163 inflater.inflate(R.menu.file_actions_menu, menu);
164 AdapterContextMenuInfo info = (AdapterContextMenuInfo) menuInfo;
165 OCFile targetFile = (OCFile) mAdapter.getItem(info.position);
166 List<Integer> toHide = new ArrayList<Integer>();
167 List<Integer> toDisable = new ArrayList<Integer>();
168
169 MenuItem item = null;
170 if (targetFile.isDirectory()) {
171 // contextual menu for folders
172 toHide.add(R.id.action_open_file_with);
173 toHide.add(R.id.action_download_file);
174 toHide.add(R.id.action_cancel_download);
175 toHide.add(R.id.action_cancel_upload);
176 toHide.add(R.id.action_see_details);
177 if ( mContainerActivity.getFileDownloaderBinder().isDownloading(AccountUtils.getCurrentOwnCloudAccount(getActivity()), targetFile) ||
178 mContainerActivity.getFileUploaderBinder().isUploading(AccountUtils.getCurrentOwnCloudAccount(getActivity()), targetFile) ) {
179 toDisable.add(R.id.action_rename_file);
180 toDisable.add(R.id.action_remove_file);
181
182 }
183
184 } else {
185 // contextual menu for regular files
186 if (targetFile.isDown()) {
187 toHide.add(R.id.action_cancel_download);
188 toHide.add(R.id.action_cancel_upload);
189 item = menu.findItem(R.id.action_download_file);
190 if (item != null) {
191 item.setTitle(R.string.filedetails_sync_file);
192 }
193 } else {
194 toHide.add(R.id.action_open_file_with);
195 }
196 if ( mContainerActivity.getFileDownloaderBinder().isDownloading(AccountUtils.getCurrentOwnCloudAccount(getActivity()), targetFile)) {
197 toHide.add(R.id.action_download_file);
198 toHide.add(R.id.action_cancel_upload);
199 toDisable.add(R.id.action_open_file_with);
200 toDisable.add(R.id.action_rename_file);
201 toDisable.add(R.id.action_remove_file);
202
203 } else if ( mContainerActivity.getFileUploaderBinder().isUploading(AccountUtils.getCurrentOwnCloudAccount(getActivity()), targetFile)) {
204 toHide.add(R.id.action_download_file);
205 toHide.add(R.id.action_cancel_download);
206 toDisable.add(R.id.action_open_file_with);
207 toDisable.add(R.id.action_rename_file);
208 toDisable.add(R.id.action_remove_file);
209
210 } else {
211 toHide.add(R.id.action_cancel_download);
212 toHide.add(R.id.action_cancel_upload);
213 }
214 }
215
216 for (int i : toHide) {
217 item = menu.findItem(i);
218 if (item != null) {
219 item.setVisible(false);
220 item.setEnabled(false);
221 }
222 }
223
224 for (int i : toDisable) {
225 item = menu.findItem(i);
226 if (item != null) {
227 item.setEnabled(false);
228 }
229 }
230 }
231
232
233 /**
234 * {@inhericDoc}
235 */
236 @Override
237 public boolean onContextItemSelected (MenuItem item) {
238 AdapterContextMenuInfo info = (AdapterContextMenuInfo) item.getMenuInfo();
239 mTargetFile = (OCFile) mAdapter.getItem(info.position);
240 switch (item.getItemId()) {
241 case R.id.action_rename_file: {
242 String fileName = mTargetFile.getFileName();
243 int extensionStart = mTargetFile.isDirectory() ? -1 : fileName.lastIndexOf(".");
244 int selectionEnd = (extensionStart >= 0) ? extensionStart : fileName.length();
245 EditNameDialog dialog = EditNameDialog.newInstance(getString(R.string.rename_dialog_title), fileName, 0, selectionEnd, this);
246 dialog.show(getFragmentManager(), EditNameDialog.TAG);
247 return true;
248 }
249 case R.id.action_remove_file: {
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 confDialog.show(getFragmentManager(), FileDetailFragment.FTAG_CONFIRMATION);
269 return true;
270 }
271 case R.id.action_open_file_with: {
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_OC.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_OC.e(TAG, "Trying to find out MIME type of a file without extension: " + storagePath);
301
302 } catch (ActivityNotFoundException e) {
303 Log_OC.e(TAG, "No activity found to handle: " + storagePath + " with MIME type " + mimeType + " obtained from extension");
304
305 } catch (Throwable th) {
306 Log_OC.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.action_download_file: {
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.action_cancel_download: {
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.action_cancel_upload: {
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 case R.id.action_see_details: {
346 ((FileFragment.ContainerActivity)getActivity()).showFragmentWithDetails(mTargetFile);
347 return true;
348 }
349 default:
350 return super.onContextItemSelected(item);
351 }
352 }
353
354
355 /**
356 * Call this, when the user presses the up button
357 */
358 public void onNavigateUp() {
359 OCFile parentDir = null;
360
361 if(mFile != null){
362 DataStorageManager storageManager = mContainerActivity.getStorageManager();
363 parentDir = storageManager.getFileById(mFile.getParentId());
364 mFile = parentDir;
365 }
366 listDirectory(parentDir);
367 }
368
369 /**
370 * Use this to query the {@link OCFile} that is currently
371 * being displayed by this fragment
372 * @return The currently viewed OCFile
373 */
374 public OCFile getCurrentFile(){
375 return mFile;
376 }
377
378 /**
379 * Calls {@link OCFileListFragment#listDirectory(OCFile)} with a null parameter
380 */
381 public void listDirectory(){
382 listDirectory(null);
383 }
384
385 /**
386 * Lists the given directory on the view. When the input parameter is null,
387 * it will either refresh the last known directory. list the root
388 * if there never was a directory.
389 *
390 * @param directory File to be listed
391 */
392 public void listDirectory(OCFile directory) {
393 DataStorageManager storageManager = mContainerActivity.getStorageManager();
394 if (storageManager != null) {
395
396 // Check input parameters for null
397 if(directory == null){
398 if(mFile != null){
399 directory = mFile;
400 } else {
401 directory = storageManager.getFileByPath("/");
402 if (directory == null) return; // no files, wait for sync
403 }
404 }
405
406
407 // If that's not a directory -> List its parent
408 if(!directory.isDirectory()){
409 Log_OC.w(TAG, "You see, that is not a directory -> " + directory.toString());
410 directory = storageManager.getFileById(directory.getParentId());
411 }
412
413 mAdapter.swapDirectory(directory, storageManager);
414 if (mFile == null || !mFile.equals(directory)) {
415 mList.setSelectionFromTop(0, 0);
416 }
417 mFile = directory;
418 }
419 }
420
421
422
423 /**
424 * Interface to implement by any Activity that includes some instance of FileListFragment
425 *
426 * @author David A. Velasco
427 */
428 public interface ContainerActivity extends TransferServiceGetter, OnRemoteOperationListener {
429
430 /**
431 * Callback method invoked when a directory is clicked by the user on the files list
432 *
433 * @param file
434 */
435 public void onDirectoryClick(OCFile file);
436
437 /**
438 * Callback method invoked when a file (non directory) is clicked by the user on the files list
439 *
440 * @param file
441 */
442 public void onFileClick(OCFile file);
443
444 /**
445 * Getter for the current DataStorageManager in the container activity
446 */
447 public DataStorageManager getStorageManager();
448
449
450 /**
451 * Callback method invoked when the parent activity is fully created to get the directory to list firstly.
452 *
453 * @return Directory to list firstly. Can be NULL.
454 */
455 public OCFile getInitialDirectory();
456
457
458 /**
459 * Callback method invoked when a the 'transfer state' of a file changes.
460 *
461 * This happens when a download or upload is started or ended for a file.
462 *
463 * This method is necessary by now to update the user interface of the double-pane layout in tablets
464 * because methods {@link FileDownloaderBinder#isDownloading(Account, OCFile)} and {@link FileUploaderBinder#isUploading(Account, OCFile)}
465 * won't provide the needed response before the method where this is called finishes.
466 *
467 * TODO Remove this when the transfer state of a file is kept in the database (other thing TODO)
468 *
469 * @param file OCFile which state changed.
470 * @param downloading Flag signaling if the file is now downloading.
471 * @param uploading Flag signaling if the file is now uploading.
472 */
473 public void onTransferStateChanged(OCFile file, boolean downloading, boolean uploading);
474
475 }
476
477
478 @Override
479 public void onDismiss(EditNameDialog dialog) {
480 if (dialog.getResult()) {
481 String newFilename = dialog.getNewFilename();
482 Log_OC.d(TAG, "name edit dialog dismissed with new name " + newFilename);
483 RemoteOperation operation = new RenameFileOperation(mTargetFile,
484 AccountUtils.getCurrentOwnCloudAccount(getActivity()),
485 newFilename,
486 mContainerActivity.getStorageManager());
487 WebdavClient wc = OwnCloudClientUtils.createOwnCloudClient(AccountUtils.getCurrentOwnCloudAccount(getSherlockActivity()), getSherlockActivity().getApplicationContext());
488 operation.execute(wc, mContainerActivity, mHandler);
489 getActivity().showDialog(FileDisplayActivity.DIALOG_SHORT_WAIT);
490 }
491 }
492
493
494 @Override
495 public void onConfirmation(String callerTag) {
496 if (callerTag.equals(FileDetailFragment.FTAG_CONFIRMATION)) {
497 if (mContainerActivity.getStorageManager().getFileById(mTargetFile.getFileId()) != null) {
498 RemoteOperation operation = new RemoveFileOperation( mTargetFile,
499 true,
500 mContainerActivity.getStorageManager());
501 WebdavClient wc = OwnCloudClientUtils.createOwnCloudClient(AccountUtils.getCurrentOwnCloudAccount(getSherlockActivity()), getSherlockActivity().getApplicationContext());
502 operation.execute(wc, mContainerActivity, mHandler);
503
504 getActivity().showDialog(FileDisplayActivity.DIALOG_SHORT_WAIT);
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 listDirectory();
522 mContainerActivity.onTransferStateChanged(mTargetFile, false, false);
523 }
524
525 @Override
526 public void onCancel(String callerTag) {
527 Log_OC.d(TAG, "REMOVAL CANCELED");
528 }
529
530
531 }