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