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