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