70432486ad22a27b57ab6d0ceb66e22480f99d60
[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.R;
25 import com.owncloud.android.authentication.AccountUtils;
26 import com.owncloud.android.datamodel.FileDataStorageManager;
27 import com.owncloud.android.datamodel.OCFile;
28 import com.owncloud.android.files.services.FileDownloader.FileDownloaderBinder;
29 import com.owncloud.android.files.services.FileUploader.FileUploaderBinder;
30 import com.owncloud.android.lib.common.operations.OnRemoteOperationListener;
31 import com.owncloud.android.lib.common.operations.RemoteOperation;
32 import com.owncloud.android.operations.RemoveFileOperation;
33 import com.owncloud.android.operations.RenameFileOperation;
34 import com.owncloud.android.operations.SynchronizeFileOperation;
35 import com.owncloud.android.ui.activity.FileDisplayActivity;
36 import com.owncloud.android.ui.activity.TransferServiceGetter;
37 import com.owncloud.android.ui.adapter.FileListListAdapter;
38 import com.owncloud.android.ui.dialog.EditNameDialog;
39 import com.owncloud.android.ui.dialog.EditNameDialog.EditNameDialogListener;
40 import com.owncloud.android.ui.fragment.ConfirmationDialogFragment.ConfirmationDialogFragmentListener;
41 import com.owncloud.android.ui.preview.PreviewImageFragment;
42 import com.owncloud.android.ui.preview.PreviewMediaFragment;
43 import com.owncloud.android.utils.Log_OC;
44
45 import android.accounts.Account;
46 import android.app.Activity;
47 import android.os.Bundle;
48 import android.os.Handler;
49 import android.view.ContextMenu;
50 import android.view.MenuInflater;
51 import android.view.MenuItem;
52 import android.view.View;
53 import android.widget.AdapterView;
54 import android.widget.AdapterView.AdapterContextMenuInfo;
55
56 /**
57 * A Fragment that lists all files and folders in a given path.
58 *
59 * @author Bartek Przybylski
60 *
61 */
62 public class OCFileListFragment extends ExtendedListFragment implements EditNameDialogListener, ConfirmationDialogFragmentListener {
63
64 private static final String TAG = OCFileListFragment.class.getSimpleName();
65
66 private static final String MY_PACKAGE = OCFileListFragment.class.getPackage() != null ? OCFileListFragment.class.getPackage().getName() : "com.owncloud.android.ui.fragment";
67 private static final String EXTRA_FILE = MY_PACKAGE + ".extra.FILE";
68
69 private static final String KEY_INDEXES = "INDEXES";
70 private static final String KEY_TOPS = "TOPS";
71
72 private OCFileListFragment.ContainerActivity mContainerActivity;
73
74 private OCFile mFile = null;
75 private FileListListAdapter mAdapter;
76
77 private Handler mHandler;
78 private OCFile mTargetFile;
79
80 private ArrayList<Integer> mIndexes;
81 private ArrayList<Integer> mTops;
82
83
84 /**
85 * {@inheritDoc}
86 */
87 @Override
88 public void onAttach(Activity activity) {
89 super.onAttach(activity);
90 Log_OC.e(TAG, "onAttach");
91 try {
92 mContainerActivity = (ContainerActivity) activity;
93 } catch (ClassCastException e) {
94 throw new ClassCastException(activity.toString() + " must implement " + OCFileListFragment.ContainerActivity.class.getSimpleName());
95 }
96 }
97
98
99 /**
100 * {@inheritDoc}
101 */
102 @Override
103 public void onActivityCreated(Bundle savedInstanceState) {
104 super.onActivityCreated(savedInstanceState);
105 Log_OC.e(TAG, "onActivityCreated() start");
106 mAdapter = new FileListListAdapter(getActivity(), mContainerActivity);
107 if (savedInstanceState != null) {
108 mFile = savedInstanceState.getParcelable(EXTRA_FILE);
109 mIndexes = savedInstanceState.getIntegerArrayList(KEY_INDEXES);
110 mTops = savedInstanceState.getIntegerArrayList(KEY_TOPS);
111
112 } else {
113 mIndexes = new ArrayList<Integer>();
114 mTops = new ArrayList<Integer>();
115
116 }
117
118 setListAdapter(mAdapter);
119
120 registerForContextMenu(getListView());
121 getListView().setOnCreateContextMenuListener(this);
122
123 mHandler = new Handler();
124
125 }
126
127 /**
128 * Saves the current listed folder.
129 */
130 @Override
131 public void onSaveInstanceState (Bundle outState) {
132 super.onSaveInstanceState(outState);
133 outState.putParcelable(EXTRA_FILE, mFile);
134 outState.putIntegerArrayList(KEY_INDEXES, mIndexes);
135 outState.putIntegerArrayList(KEY_TOPS, mTops);
136 }
137
138 /**
139 * Call this, when the user presses the up button.
140 *
141 * Tries to move up the current folder one level. If the parent folder was removed from the database,
142 * it continues browsing up until finding an existing folders.
143 *
144 * return Count of folder levels browsed up.
145 */
146 public int onBrowseUp() {
147 OCFile parentDir = null;
148 int moveCount = 0;
149
150 if(mFile != null){
151 FileDataStorageManager storageManager = mContainerActivity.getStorageManager();
152
153 String parentPath = null;
154 if (mFile.getParentId() != FileDataStorageManager.ROOT_PARENT_ID) {
155 parentPath = new File(mFile.getRemotePath()).getParent();
156 parentPath = parentPath.endsWith(OCFile.PATH_SEPARATOR) ? parentPath : parentPath + OCFile.PATH_SEPARATOR;
157 parentDir = storageManager.getFileByPath(parentPath);
158 moveCount++;
159 } else {
160 parentDir = storageManager.getFileByPath(OCFile.ROOT_PATH); // never returns null; keep the path in root folder
161 }
162 while (parentDir == null) {
163 parentPath = new File(parentPath).getParent();
164 parentPath = parentPath.endsWith(OCFile.PATH_SEPARATOR) ? parentPath : parentPath + OCFile.PATH_SEPARATOR;
165 parentDir = storageManager.getFileByPath(parentPath);
166 moveCount++;
167 } // exit is granted because storageManager.getFileByPath("/") never returns null
168 mFile = parentDir;
169 }
170
171 if (mFile != null) {
172 listDirectory(mFile);
173
174 mContainerActivity.startSyncFolderOperation(mFile);
175
176 // restore index and position
177 restoreIndexAndPosition();
178
179 } // else - should never happen now
180
181 return moveCount;
182 }
183
184 /*
185 * Restore index and position
186 */
187 private void restoreIndexAndPosition() {
188 int index = mIndexes.get(mIndexes.size() - 1);
189 mIndexes.remove(mIndexes.size() - 1);
190 int top = mTops.get(mTops.size() - 1);
191 mTops.remove(mTops.size() - 1);
192 mList.setSelectionFromTop(index, top);
193 }
194
195 /*
196 * Save index and top position
197 */
198 private void saveIndexAndPosition(int index) {
199
200 mIndexes.add(index);
201 View view = mList.getChildAt(0);
202 mTops.add( (view == null) ? 0 : view.getTop() );
203 }
204
205 @Override
206 public void onItemClick(AdapterView<?> l, View v, int position, long id) {
207 OCFile file = (OCFile) mAdapter.getItem(position);
208 if (file != null) {
209 if (file.isFolder()) {
210 // update state and view of this fragment
211 listDirectory(file);
212 // then, notify parent activity to let it update its state and view, and other fragments
213 mContainerActivity.onBrowsedDownTo(file);
214
215 // save index and top position
216 saveIndexAndPosition(position);
217
218 } else { /// Click on a file
219 if (PreviewImageFragment.canBePreviewed(file)) {
220 // preview image - it handles the download, if needed
221 mContainerActivity.startImagePreview(file);
222
223 } else if (file.isDown()) {
224 if (PreviewMediaFragment.canBePreviewed(file)) {
225 // media preview
226 mContainerActivity.startMediaPreview(file, 0, true);
227 } else {
228 FileDisplayActivity activity = (FileDisplayActivity) getSherlockActivity();
229 activity.getFileOperationsHelper().openFile(file, activity);
230 }
231
232 } else {
233 // automatic download, preview on finish
234 mContainerActivity.startDownloadForPreview(file);
235 }
236
237 }
238
239 } else {
240 Log_OC.d(TAG, "Null object in ListAdapter!!");
241 }
242
243 }
244
245 /**
246 * {@inheritDoc}
247 */
248 @Override
249 public void onCreateContextMenu (ContextMenu menu, View v, ContextMenu.ContextMenuInfo menuInfo) {
250 super.onCreateContextMenu(menu, v, menuInfo);
251 MenuInflater inflater = getActivity().getMenuInflater();
252 inflater.inflate(R.menu.file_actions_menu, menu);
253 AdapterContextMenuInfo info = (AdapterContextMenuInfo) menuInfo;
254 OCFile targetFile = (OCFile) mAdapter.getItem(info.position);
255 List<Integer> toHide = new ArrayList<Integer>();
256 List<Integer> toDisable = new ArrayList<Integer>();
257
258 MenuItem item = null;
259 if (targetFile.isFolder()) {
260 // contextual menu for folders
261 toHide.add(R.id.action_open_file_with);
262 toHide.add(R.id.action_download_file);
263 toHide.add(R.id.action_cancel_download);
264 toHide.add(R.id.action_cancel_upload);
265 toHide.add(R.id.action_sync_file);
266 toHide.add(R.id.action_see_details);
267 toHide.add(R.id.action_send_file);
268 if ( mContainerActivity.getFileDownloaderBinder().isDownloading(AccountUtils.getCurrentOwnCloudAccount(getActivity()), targetFile) ||
269 mContainerActivity.getFileUploaderBinder().isUploading(AccountUtils.getCurrentOwnCloudAccount(getActivity()), targetFile) ) {
270 toDisable.add(R.id.action_rename_file);
271 toDisable.add(R.id.action_remove_file);
272
273 }
274
275 } else {
276 // contextual menu for regular files
277
278 // new design: 'download' and 'open with' won't be available anymore in context menu
279 toHide.add(R.id.action_download_file);
280 toHide.add(R.id.action_open_file_with);
281
282 if (targetFile.isDown()) {
283 toHide.add(R.id.action_cancel_download);
284 toHide.add(R.id.action_cancel_upload);
285
286 } else {
287 toHide.add(R.id.action_sync_file);
288 }
289 if ( mContainerActivity.getFileDownloaderBinder().isDownloading(AccountUtils.getCurrentOwnCloudAccount(getActivity()), targetFile)) {
290 toHide.add(R.id.action_cancel_upload);
291 toDisable.add(R.id.action_rename_file);
292 toDisable.add(R.id.action_remove_file);
293
294 } else if ( mContainerActivity.getFileUploaderBinder().isUploading(AccountUtils.getCurrentOwnCloudAccount(getActivity()), targetFile)) {
295 toHide.add(R.id.action_cancel_download);
296 toDisable.add(R.id.action_rename_file);
297 toDisable.add(R.id.action_remove_file);
298
299 } else {
300 toHide.add(R.id.action_cancel_download);
301 toHide.add(R.id.action_cancel_upload);
302 }
303 }
304
305 // Options shareLink
306 if (!targetFile.isShareByLink()) {
307 toHide.add(R.id.action_unshare_file);
308 }
309
310 // Send file
311 boolean sendEnabled = getString(R.string.send_files_to_other_apps).equalsIgnoreCase("on");
312 if (!sendEnabled) {
313 toHide.add(R.id.action_send_file);
314 }
315
316 for (int i : toHide) {
317 item = menu.findItem(i);
318 if (item != null) {
319 item.setVisible(false);
320 item.setEnabled(false);
321 }
322 }
323
324 for (int i : toDisable) {
325 item = menu.findItem(i);
326 if (item != null) {
327 item.setEnabled(false);
328 }
329 }
330 }
331
332
333 /**
334 * {@inhericDoc}
335 */
336 @Override
337 public boolean onContextItemSelected (MenuItem item) {
338 AdapterContextMenuInfo info = (AdapterContextMenuInfo) item.getMenuInfo();
339 mTargetFile = (OCFile) mAdapter.getItem(info.position);
340 switch (item.getItemId()) {
341 case R.id.action_share_file: {
342 FileDisplayActivity activity = (FileDisplayActivity) getSherlockActivity();
343 activity.getFileOperationsHelper().shareFileWithLink(mTargetFile, activity);
344 return true;
345 }
346 case R.id.action_unshare_file: {
347 FileDisplayActivity activity = (FileDisplayActivity) getSherlockActivity();
348 activity.getFileOperationsHelper().unshareFileWithLink(mTargetFile, activity);
349 return true;
350 }
351 case R.id.action_rename_file: {
352 String fileName = mTargetFile.getFileName();
353 int extensionStart = mTargetFile.isFolder() ? -1 : fileName.lastIndexOf(".");
354 int selectionEnd = (extensionStart >= 0) ? extensionStart : fileName.length();
355 EditNameDialog dialog = EditNameDialog.newInstance(getString(R.string.rename_dialog_title), fileName, 0, selectionEnd, this);
356 dialog.show(getFragmentManager(), EditNameDialog.TAG);
357 return true;
358 }
359 case R.id.action_remove_file: {
360 int messageStringId = R.string.confirmation_remove_alert;
361 int posBtnStringId = R.string.confirmation_remove_remote;
362 int neuBtnStringId = -1;
363 if (mTargetFile.isFolder()) {
364 messageStringId = R.string.confirmation_remove_folder_alert;
365 posBtnStringId = R.string.confirmation_remove_remote_and_local;
366 neuBtnStringId = R.string.confirmation_remove_folder_local;
367 } else if (mTargetFile.isDown()) {
368 posBtnStringId = R.string.confirmation_remove_remote_and_local;
369 neuBtnStringId = R.string.confirmation_remove_local;
370 }
371 ConfirmationDialogFragment confDialog = ConfirmationDialogFragment.newInstance(
372 messageStringId,
373 new String[]{mTargetFile.getFileName()},
374 posBtnStringId,
375 neuBtnStringId,
376 R.string.common_cancel);
377 confDialog.setOnConfirmationListener(this);
378 confDialog.show(getFragmentManager(), FileDetailFragment.FTAG_CONFIRMATION);
379 return true;
380 }
381 case R.id.action_sync_file: {
382 Account account = AccountUtils.getCurrentOwnCloudAccount(getSherlockActivity());
383 RemoteOperation operation = new SynchronizeFileOperation(mTargetFile, null, mContainerActivity.getStorageManager(), account, true, getSherlockActivity());
384 operation.execute(account, getSherlockActivity(), mContainerActivity, mHandler, getSherlockActivity());
385 ((FileDisplayActivity) getSherlockActivity()).showLoadingDialog();
386 return true;
387 }
388 case R.id.action_cancel_download: {
389 FileDownloaderBinder downloaderBinder = mContainerActivity.getFileDownloaderBinder();
390 Account account = AccountUtils.getCurrentOwnCloudAccount(getActivity());
391 if (downloaderBinder != null && downloaderBinder.isDownloading(account, mTargetFile)) {
392 downloaderBinder.cancel(account, mTargetFile);
393 listDirectory();
394 mContainerActivity.onTransferStateChanged(mTargetFile, false, false);
395 }
396 return true;
397 }
398 case R.id.action_cancel_upload: {
399 FileUploaderBinder uploaderBinder = mContainerActivity.getFileUploaderBinder();
400 Account account = AccountUtils.getCurrentOwnCloudAccount(getActivity());
401 if (uploaderBinder != null && uploaderBinder.isUploading(account, mTargetFile)) {
402 uploaderBinder.cancel(account, mTargetFile);
403 listDirectory();
404 mContainerActivity.onTransferStateChanged(mTargetFile, false, false);
405 }
406 return true;
407 }
408 case R.id.action_see_details: {
409 ((FileFragment.ContainerActivity)getActivity()).showDetails(mTargetFile);
410 return true;
411 }
412 case R.id.action_send_file: {
413 // Obtain the file
414 if (!mTargetFile.isDown()) { // Download the file
415 Log_OC.d(TAG, mTargetFile.getRemotePath() + " : File must be downloaded");
416 mContainerActivity.startDownloadForSending(mTargetFile);
417
418 } else {
419
420 FileDisplayActivity activity = (FileDisplayActivity) getSherlockActivity();
421 activity.getFileOperationsHelper().sendDownloadedFile(mTargetFile, activity);
422 }
423 return true;
424 }
425 default:
426 return super.onContextItemSelected(item);
427 }
428 }
429
430
431 /**
432 * Use this to query the {@link OCFile} that is currently
433 * being displayed by this fragment
434 * @return The currently viewed OCFile
435 */
436 public OCFile getCurrentFile(){
437 return mFile;
438 }
439
440 /**
441 * Calls {@link OCFileListFragment#listDirectory(OCFile)} with a null parameter
442 */
443 public void listDirectory(){
444 listDirectory(null);
445 }
446
447 /**
448 * Lists the given directory on the view. When the input parameter is null,
449 * it will either refresh the last known directory. list the root
450 * if there never was a directory.
451 *
452 * @param directory File to be listed
453 */
454 public void listDirectory(OCFile directory) {
455 FileDataStorageManager storageManager = mContainerActivity.getStorageManager();
456 if (storageManager != null) {
457
458 // Check input parameters for null
459 if(directory == null){
460 if(mFile != null){
461 directory = mFile;
462 } else {
463 directory = storageManager.getFileByPath("/");
464 if (directory == null) return; // no files, wait for sync
465 }
466 }
467
468
469 // If that's not a directory -> List its parent
470 if(!directory.isFolder()){
471 Log_OC.w(TAG, "You see, that is not a directory -> " + directory.toString());
472 directory = storageManager.getFileById(directory.getParentId());
473 }
474
475 mAdapter.swapDirectory(directory, storageManager);
476 if (mFile == null || !mFile.equals(directory)) {
477 mList.setSelectionFromTop(0, 0);
478 }
479 mFile = directory;
480 }
481 }
482
483
484
485 /**
486 * Interface to implement by any Activity that includes some instance of FileListFragment
487 *
488 * @author David A. Velasco
489 */
490 public interface ContainerActivity extends TransferServiceGetter, OnRemoteOperationListener {
491
492 /**
493 * Callback method invoked when a the user browsed into a different folder through the list of files
494 *
495 * @param file
496 */
497 public void onBrowsedDownTo(OCFile folder);
498
499 public void startDownloadForPreview(OCFile file);
500
501 public void startMediaPreview(OCFile file, int i, boolean b);
502
503 public void startImagePreview(OCFile file);
504
505 public void startSyncFolderOperation(OCFile folder);
506
507 /**
508 * Getter for the current DataStorageManager in the container activity
509 */
510 public FileDataStorageManager getStorageManager();
511
512
513 /**
514 * Callback method invoked when a the 'transfer state' of a file changes.
515 *
516 * This happens when a download or upload is started or ended for a file.
517 *
518 * This method is necessary by now to update the user interface of the double-pane layout in tablets
519 * because methods {@link FileDownloaderBinder#isDownloading(Account, OCFile)} and {@link FileUploaderBinder#isUploading(Account, OCFile)}
520 * won't provide the needed response before the method where this is called finishes.
521 *
522 * TODO Remove this when the transfer state of a file is kept in the database (other thing TODO)
523 *
524 * @param file OCFile which state changed.
525 * @param downloading Flag signaling if the file is now downloading.
526 * @param uploading Flag signaling if the file is now uploading.
527 */
528 public void onTransferStateChanged(OCFile file, boolean downloading, boolean uploading);
529
530 void startDownloadForSending(OCFile file);
531
532 }
533
534
535 @Override
536 public void onDismiss(EditNameDialog dialog) {
537 if (dialog.getResult()) {
538 String newFilename = dialog.getNewFilename();
539 Log_OC.d(TAG, "name edit dialog dismissed with new name " + newFilename);
540 RemoteOperation operation = new RenameFileOperation(mTargetFile,
541 AccountUtils.getCurrentOwnCloudAccount(getActivity()),
542 newFilename,
543 mContainerActivity.getStorageManager());
544 operation.execute(AccountUtils.getCurrentOwnCloudAccount(getSherlockActivity()), getSherlockActivity(), mContainerActivity, mHandler, getSherlockActivity());
545 ((FileDisplayActivity) getActivity()).showLoadingDialog();
546 }
547 }
548
549
550 @Override
551 public void onConfirmation(String callerTag) {
552 if (callerTag.equals(FileDetailFragment.FTAG_CONFIRMATION)) {
553 if (mContainerActivity.getStorageManager().getFileById(mTargetFile.getFileId()) != null) {
554 RemoteOperation operation = new RemoveFileOperation( mTargetFile,
555 true,
556 mContainerActivity.getStorageManager());
557 operation.execute(AccountUtils.getCurrentOwnCloudAccount(getSherlockActivity()), getSherlockActivity(), mContainerActivity, mHandler, getSherlockActivity());
558
559 ((FileDisplayActivity) getActivity()).showLoadingDialog();
560 }
561 }
562 }
563
564 @Override
565 public void onNeutral(String callerTag) {
566 mContainerActivity.getStorageManager().removeFile(mTargetFile, false, true); // TODO perform in background task / new thread
567 listDirectory();
568 mContainerActivity.onTransferStateChanged(mTargetFile, false, false);
569 }
570
571 @Override
572 public void onCancel(String callerTag) {
573 Log_OC.d(TAG, "REMOVAL CANCELED");
574 }
575
576
577 }