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