add jar to libs folder for ant to find...
[pub/Android/ownCloud.git] / src / com / owncloud / android / ui / fragment / OCFileListFragment.java
1 /**
2 * ownCloud Android client application
3 *
4 * @author Bartek Przybylski
5 * @author masensio
6 * @author David A. Velasco
7 * Copyright (C) 2011 Bartek Przybylski
8 * Copyright (C) 2015 ownCloud Inc.
9 *
10 * This program is free software: you can redistribute it and/or modify
11 * it under the terms of the GNU General Public License version 2,
12 * as published by the Free Software Foundation.
13 *
14 * This program is distributed in the hope that it will be useful,
15 * but WITHOUT ANY WARRANTY; without even the implied warranty of
16 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 * GNU General Public License for more details.
18 *
19 * You should have received a copy of the GNU General Public License
20 * along with this program. If not, see <http://www.gnu.org/licenses/>.
21 *
22 */
23 package com.owncloud.android.ui.fragment;
24
25 import java.io.File;
26
27 import android.app.Activity;
28 import android.content.Intent;
29 import android.os.Bundle;
30 import android.support.v4.widget.SwipeRefreshLayout;
31 import android.view.ContextMenu;
32 import android.view.LayoutInflater;
33 import android.view.Menu;
34 import android.view.MenuInflater;
35 import android.view.MenuItem;
36 import android.view.View;
37 import android.view.ViewGroup;
38 import android.widget.AdapterView;
39 import android.widget.AdapterView.AdapterContextMenuInfo;
40 import android.widget.ListView;
41 import android.widget.PopupMenu;
42 import android.widget.Toast;
43
44 import com.getbase.floatingactionbutton.FloatingActionsMenu;
45 import com.owncloud.android.R;
46 import com.owncloud.android.authentication.AccountUtils;
47 import com.owncloud.android.datamodel.FileDataStorageManager;
48 import com.owncloud.android.datamodel.OCFile;
49 import com.owncloud.android.files.FileMenuFilter;
50 import com.owncloud.android.lib.common.utils.Log_OC;
51 import com.owncloud.android.lib.resources.status.OwnCloudVersion;
52 import com.owncloud.android.ui.activity.FileActivity;
53 import com.owncloud.android.ui.activity.FileDisplayActivity;
54 import com.owncloud.android.ui.activity.FolderPickerActivity;
55 import com.owncloud.android.ui.activity.OnEnforceableRefreshListener;
56 import com.owncloud.android.ui.adapter.FileListListAdapter;
57 import com.owncloud.android.ui.dialog.ConfirmationDialogFragment;
58 import com.owncloud.android.ui.dialog.FileActionsDialogFragment;
59 import com.owncloud.android.ui.dialog.RemoveFileDialogFragment;
60 import com.owncloud.android.ui.dialog.RenameFileDialogFragment;
61 import com.owncloud.android.ui.preview.PreviewImageFragment;
62 import com.owncloud.android.ui.preview.PreviewMediaFragment;
63 import com.owncloud.android.utils.DialogMenuItem;
64 import com.owncloud.android.utils.FileStorageUtils;
65
66 /**
67 * A Fragment that lists all files and folders in a given path.
68 *
69 * TODO refactor to get rid of direct dependency on FileDisplayActivity
70 */
71 public class OCFileListFragment extends ExtendedListFragment implements FileActionsDialogFragment.FileActionsDialogFragmentListener {
72
73 private static final String TAG = OCFileListFragment.class.getSimpleName();
74
75 private static final String MY_PACKAGE = OCFileListFragment.class.getPackage() != null ?
76 OCFileListFragment.class.getPackage().getName() : "com.owncloud.android.ui.fragment";
77
78 public final static String ARG_JUST_FOLDERS = MY_PACKAGE + ".JUST_FOLDERS";
79 public final static String ARG_ALLOW_CONTEXTUAL_ACTIONS = MY_PACKAGE + ".ALLOW_CONTEXTUAL";
80
81 private static final String KEY_FILE = MY_PACKAGE + ".extra.FILE";
82
83 private FileFragment.ContainerActivity mContainerActivity;
84
85 private OCFile mFile = null;
86 private FileListListAdapter mAdapter;
87 private boolean mJustFolders;
88
89 private OCFile mTargetFile;
90
91
92
93 /**
94 * {@inheritDoc}
95 */
96 @Override
97 public void onAttach(Activity activity) {
98 super.onAttach(activity);
99 Log_OC.e(TAG, "onAttach");
100 try {
101 mContainerActivity = (FileFragment.ContainerActivity) activity;
102
103 } catch (ClassCastException e) {
104 throw new ClassCastException(activity.toString() + " must implement " +
105 FileFragment.ContainerActivity.class.getSimpleName());
106 }
107 try {
108 setOnRefreshListener((OnEnforceableRefreshListener) activity);
109
110 } catch (ClassCastException e) {
111 throw new ClassCastException(activity.toString() + " must implement " +
112 SwipeRefreshLayout.OnRefreshListener.class.getSimpleName());
113 }
114 }
115
116 /**
117 * {@inheritDoc}
118 */
119 @Override
120 public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
121 Log_OC.i(TAG, "onCreateView() start");
122 View v = super.onCreateView(inflater, container, savedInstanceState);
123
124 // Setup FAB listeners
125 getFabUpload().setOnClickListener(new View.OnClickListener() {
126 @Override
127 public void onClick(View v) {
128 ((FileDisplayActivity)getActivity()).uploadLocalFilesSelected();
129 getFabMain().collapse();
130 }
131 });
132
133 getFabMkdir().setOnClickListener(new View.OnClickListener() {
134 @Override
135 public void onClick(View v) {
136 ((FileDisplayActivity) getActivity()).createFolder();
137 getFabMain().collapse();
138 }
139 });
140
141 getFabUploadFromApp().setOnClickListener(new View.OnClickListener() {
142 @Override
143 public void onClick(View v) {
144 ((FileDisplayActivity) getActivity()).uploadFromOtherAppsSelected();
145 getFabMain().collapse();
146 }
147 });
148
149 Log_OC.i(TAG, "onCreateView() end");
150 return v;
151 }
152
153
154 @Override
155 public void onDetach() {
156 setOnRefreshListener(null);
157 mContainerActivity = null;
158 super.onDetach();
159 }
160
161 /**
162 * {@inheritDoc}
163 */
164 @Override
165 public void onActivityCreated(Bundle savedInstanceState) {
166 super.onActivityCreated(savedInstanceState);
167 Log_OC.e(TAG, "onActivityCreated() start");
168
169 if (savedInstanceState != null) {
170 mFile = savedInstanceState.getParcelable(KEY_FILE);
171 }
172
173 if (mJustFolders) {
174 setFooterEnabled(false);
175 } else {
176 setFooterEnabled(true);
177 }
178
179 Bundle args = getArguments();
180 mJustFolders = (args == null) ? false : args.getBoolean(ARG_JUST_FOLDERS, false);
181 mAdapter = new FileListListAdapter(
182 mJustFolders,
183 getActivity(),
184 mContainerActivity
185 );
186 setListAdapter(mAdapter);
187
188 registerLongClickListener();
189 }
190
191 private void registerLongClickListener() {
192 getListView().setOnItemLongClickListener(new AdapterView.OnItemLongClickListener() {
193 public boolean onItemLongClick(AdapterView<?> arg0, View v,
194 int index, long arg3) {
195 showFileAction(index);
196 return true;
197 }
198 });
199 }
200
201
202 private void showFileAction(int fileIndex) {
203 Bundle args = getArguments();
204 PopupMenu pm = new PopupMenu(getActivity(),null);
205 Menu menu = pm.getMenu();
206
207 boolean allowContextualActions =
208 (args == null) ? true : args.getBoolean(ARG_ALLOW_CONTEXTUAL_ACTIONS, true);
209
210 if (allowContextualActions) {
211 MenuInflater inflater = getActivity().getMenuInflater();
212
213 inflater.inflate(R.menu.file_actions_menu, menu);
214 OCFile targetFile = (OCFile) mAdapter.getItem(fileIndex);
215
216 if (mContainerActivity.getStorageManager() != null) {
217 FileMenuFilter mf = new FileMenuFilter(
218 targetFile,
219 mContainerActivity.getStorageManager().getAccount(),
220 mContainerActivity,
221 getActivity()
222 );
223 mf.filter(menu);
224 }
225
226 /// TODO break this direct dependency on FileDisplayActivity... if possible
227 MenuItem item = menu.findItem(R.id.action_open_file_with);
228 FileFragment frag = ((FileDisplayActivity)getActivity()).getSecondFragment();
229 if (frag != null && frag instanceof FileDetailFragment &&
230 frag.getFile().getFileId() == targetFile.getFileId()) {
231 item = menu.findItem(R.id.action_see_details);
232 if (item != null) {
233 item.setVisible(false);
234 item.setEnabled(false);
235 }
236 }
237
238 MenuItem menuItem = new DialogMenuItem(item.getItemId());
239 FileActionsDialogFragment dialog = FileActionsDialogFragment.newInstance(menu, fileIndex);
240 dialog.setTargetFragment(this, 0);
241 dialog.show(getFragmentManager(), FileActionsDialogFragment.FTAG_FILE_ACTIONS);
242 }
243 }
244
245 /**
246 * Saves the current listed folder.
247 */
248 @Override
249 public void onSaveInstanceState (Bundle outState) {
250 super.onSaveInstanceState(outState);
251 outState.putParcelable(KEY_FILE, mFile);
252 }
253
254 /**
255 * Call this, when the user presses the up button.
256 *
257 * Tries to move up the current folder one level. If the parent folder was removed from the
258 * database, it continues browsing up until finding an existing folders.
259 *
260 * return Count of folder levels browsed up.
261 */
262 public int onBrowseUp() {
263 OCFile parentDir = null;
264 int moveCount = 0;
265
266 if(mFile != null){
267 FileDataStorageManager storageManager = mContainerActivity.getStorageManager();
268
269 String parentPath = null;
270 if (mFile.getParentId() != FileDataStorageManager.ROOT_PARENT_ID) {
271 parentPath = new File(mFile.getRemotePath()).getParent();
272 parentPath = parentPath.endsWith(OCFile.PATH_SEPARATOR) ? parentPath :
273 parentPath + OCFile.PATH_SEPARATOR;
274 parentDir = storageManager.getFileByPath(parentPath);
275 moveCount++;
276 } else {
277 parentDir = storageManager.getFileByPath(OCFile.ROOT_PATH);
278 }
279 while (parentDir == null) {
280 parentPath = new File(parentPath).getParent();
281 parentPath = parentPath.endsWith(OCFile.PATH_SEPARATOR) ? parentPath :
282 parentPath + OCFile.PATH_SEPARATOR;
283 parentDir = storageManager.getFileByPath(parentPath);
284 moveCount++;
285 } // exit is granted because storageManager.getFileByPath("/") never returns null
286 mFile = parentDir;
287
288 // TODO Enable when "On Device" is recovered ?
289 listDirectory(mFile /*, MainApp.getOnlyOnDevice()*/);
290
291 onRefresh(false);
292
293 // restore index and top position
294 restoreIndexAndTopPosition();
295
296 } // else - should never happen now
297
298 return moveCount;
299 }
300
301 @Override
302 public void onItemClick(AdapterView<?> l, View v, int position, long id) {
303 OCFile file = (OCFile) mAdapter.getItem(position);
304 if (file != null) {
305 if (file.isFolder()) {
306 // update state and view of this fragment
307 // TODO Enable when "On Device" is recovered ?
308 listDirectory(file/*, MainApp.getOnlyOnDevice()*/);
309 // then, notify parent activity to let it update its state and view
310 mContainerActivity.onBrowsedDownTo(file);
311 // save index and top position
312 saveIndexAndTopPosition(position);
313
314 } else { /// Click on a file
315 if (PreviewImageFragment.canBePreviewed(file)) {
316 // preview image - it handles the download, if needed
317 ((FileDisplayActivity)mContainerActivity).startImagePreview(file);
318
319 } else if (file.isDown()) {
320 if (PreviewMediaFragment.canBePreviewed(file)) {
321 // media preview
322 ((FileDisplayActivity)mContainerActivity).startMediaPreview(file, 0, true);
323 } else {
324 mContainerActivity.getFileOperationsHelper().openFile(file);
325 }
326
327 } else {
328 // automatic download, preview on finish
329 ((FileDisplayActivity)mContainerActivity).startDownloadForPreview(file);
330 }
331
332 }
333
334 } else {
335 Log_OC.d(TAG, "Null object in ListAdapter!!");
336 }
337
338 }
339
340 /**
341 * {@inheritDoc}
342 */
343 @Override
344 public void onCreateContextMenu (
345 ContextMenu menu, View v, ContextMenu.ContextMenuInfo menuInfo) {
346 Bundle args = getArguments();
347 boolean allowContextualActions =
348 (args == null) ? true : args.getBoolean(ARG_ALLOW_CONTEXTUAL_ACTIONS, true);
349 if (allowContextualActions) {
350 MenuInflater inflater = getActivity().getMenuInflater();
351 inflater.inflate(R.menu.file_actions_menu, menu);
352 AdapterContextMenuInfo info = (AdapterContextMenuInfo) menuInfo;
353 OCFile targetFile = (OCFile) mAdapter.getItem(info.position);
354
355 if (mContainerActivity.getStorageManager() != null) {
356 FileMenuFilter mf = new FileMenuFilter(
357 targetFile,
358 mContainerActivity.getStorageManager().getAccount(),
359 mContainerActivity,
360 getActivity()
361 );
362 mf.filter(menu);
363 }
364
365 /// TODO break this direct dependency on FileDisplayActivity... if possible
366 MenuItem item = menu.findItem(R.id.action_open_file_with);
367 FileFragment frag = ((FileDisplayActivity)getActivity()).getSecondFragment();
368 if (frag != null && frag instanceof FileDetailFragment &&
369 frag.getFile().getFileId() == targetFile.getFileId()) {
370 item = menu.findItem(R.id.action_see_details);
371 if (item != null) {
372 item.setVisible(false);
373 item.setEnabled(false);
374 }
375 }
376 }
377 }
378
379 /**
380 * {@inheritDoc}
381 */
382 @Override
383 public boolean onFileActionChosen(int menuId, int filePosition) {
384 mTargetFile = (OCFile) mAdapter.getItem(filePosition);
385 switch (menuId) {
386 case R.id.action_share_file: {
387 mContainerActivity.getFileOperationsHelper().shareFileWithLink(mTargetFile);
388 return true;
389 }
390 case R.id.action_open_file_with: {
391 mContainerActivity.getFileOperationsHelper().openFile(mTargetFile);
392 return true;
393 }
394 case R.id.action_unshare_file: {
395 mContainerActivity.getFileOperationsHelper().unshareFileWithLink(mTargetFile);
396 return true;
397 }
398 case R.id.action_rename_file: {
399 RenameFileDialogFragment dialog = RenameFileDialogFragment.newInstance(mTargetFile);
400 dialog.show(getFragmentManager(), FileDetailFragment.FTAG_RENAME_FILE);
401 return true;
402 }
403 case R.id.action_remove_file: {
404 RemoveFileDialogFragment dialog = RemoveFileDialogFragment.newInstance(mTargetFile);
405 dialog.show(getFragmentManager(), ConfirmationDialogFragment.FTAG_CONFIRMATION);
406 return true;
407 }
408 case R.id.action_download_file:
409 case R.id.action_sync_file: {
410 mContainerActivity.getFileOperationsHelper().syncFile(mTargetFile);
411 return true;
412 }
413 case R.id.action_cancel_download:
414 case R.id.action_cancel_upload: {
415 ((FileDisplayActivity) mContainerActivity).cancelTransference(mTargetFile);
416 return true;
417 }
418 case R.id.action_see_details: {
419 mContainerActivity.showDetails(mTargetFile);
420 return true;
421 }
422 case R.id.action_send_file: {
423 // Obtain the file
424 if (!mTargetFile.isDown()) { // Download the file
425 Log_OC.d(TAG, mTargetFile.getRemotePath() + " : File must be downloaded");
426 ((FileDisplayActivity) mContainerActivity).startDownloadForSending(mTargetFile);
427
428 } else {
429 mContainerActivity.getFileOperationsHelper().sendDownloadedFile(mTargetFile);
430 }
431 return true;
432 }
433 case R.id.action_move: {
434 Intent action = new Intent(getActivity(), FolderPickerActivity.class);
435
436 // Pass mTargetFile that contains info of selected file/folder
437 action.putExtra(FolderPickerActivity.EXTRA_FILE, mTargetFile);
438 getActivity().startActivityForResult(action, FileDisplayActivity.ACTION_MOVE_FILES);
439 return true;
440 }
441 case R.id.action_favorite_file: {
442 mContainerActivity.getFileOperationsHelper().toggleFavorite(mTargetFile, true);
443 return true;
444 }
445 case R.id.action_unfavorite_file: {
446 mContainerActivity.getFileOperationsHelper().toggleFavorite(mTargetFile, false);
447 return true;
448 }
449 default:
450 return false;
451 }
452 }
453
454 /**
455 * {@inhericDoc}
456 */
457 @Override
458 public boolean onContextItemSelected (MenuItem item) {
459 AdapterContextMenuInfo info = (AdapterContextMenuInfo) item.getMenuInfo();
460 boolean matched = onFileActionChosen(item.getItemId(), ((AdapterContextMenuInfo) item.getMenuInfo()).position);
461 if(!matched) {
462 return super.onContextItemSelected(item);
463 } else {
464 return matched;
465 }
466 }
467
468
469 /**
470 * Use this to query the {@link OCFile} that is currently
471 * being displayed by this fragment
472 * @return The currently viewed OCFile
473 */
474 public OCFile getCurrentFile(){
475 return mFile;
476 }
477
478 /**
479 * Calls {@link OCFileListFragment#listDirectory(OCFile)} with a null parameter
480 */
481 public void listDirectory(/*boolean onlyOnDevice*/){
482 listDirectory(null);
483 // TODO Enable when "On Device" is recovered ?
484 // listDirectory(null, onlyOnDevice);
485 }
486
487 public void refreshDirectory(){
488 // TODO Enable when "On Device" is recovered ?
489 listDirectory(getCurrentFile()/*, MainApp.getOnlyOnDevice()*/);
490 }
491
492 /**
493 * Lists the given directory on the view. When the input parameter is null,
494 * it will either refresh the last known directory. list the root
495 * if there never was a directory.
496 *
497 * @param directory File to be listed
498 */
499 public void listDirectory(OCFile directory/*, boolean onlyOnDevice*/) {
500 FileDataStorageManager storageManager = mContainerActivity.getStorageManager();
501 if (storageManager != null) {
502
503 // Check input parameters for null
504 if(directory == null){
505 if(mFile != null){
506 directory = mFile;
507 } else {
508 directory = storageManager.getFileByPath("/");
509 if (directory == null) return; // no files, wait for sync
510 }
511 }
512
513
514 // If that's not a directory -> List its parent
515 if(!directory.isFolder()){
516 Log_OC.w(TAG, "You see, that is not a directory -> " + directory.toString());
517 directory = storageManager.getFileById(directory.getParentId());
518 }
519
520 // TODO Enable when "On Device" is recovered ?
521 mAdapter.swapDirectory(directory, storageManager/*, onlyOnDevice*/);
522 if (mFile == null || !mFile.equals(directory)) {
523 mCurrentListView.setSelection(0);
524 }
525 mFile = directory;
526
527 updateLayout();
528
529 }
530 }
531
532 private void updateLayout() {
533 if (!mJustFolders) {
534 int filesCount = 0, foldersCount = 0, imagesCount = 0;
535 int count = mAdapter.getCount();
536 OCFile file;
537 for (int i=0; i < count ; i++) {
538 file = (OCFile) mAdapter.getItem(i);
539 if (file.isFolder()) {
540 foldersCount++;
541 } else {
542 filesCount++;
543 if (file.isImage()){
544 imagesCount++;
545 }
546 }
547 }
548 // set footer text
549 setFooterText(generateFooterText(filesCount, foldersCount));
550
551 // decide grid vs list view
552 OwnCloudVersion version = AccountUtils.getServerVersion(
553 ((FileActivity)mContainerActivity).getAccount());
554 if (version != null && version.supportsRemoteThumbnails() &&
555 imagesCount > 0 && imagesCount == filesCount) {
556 switchToGridView();
557 registerLongClickListener();
558 } else {
559 switchToListView();
560 }
561 }
562 }
563
564 private String generateFooterText(int filesCount, int foldersCount) {
565 String output;
566 if (filesCount <= 0) {
567 if (foldersCount <= 0) {
568 output = "";
569
570 } else if (foldersCount == 1) {
571 output = getResources().getString(R.string.file_list__footer__folder);
572
573 } else { // foldersCount > 1
574 output = getResources().getString(R.string.file_list__footer__folders, foldersCount);
575 }
576
577 } else if (filesCount == 1) {
578 if (foldersCount <= 0) {
579 output = getResources().getString(R.string.file_list__footer__file);
580
581 } else if (foldersCount == 1) {
582 output = getResources().getString(R.string.file_list__footer__file_and_folder);
583
584 } else { // foldersCount > 1
585 output = getResources().getString(R.string.file_list__footer__file_and_folders, foldersCount);
586 }
587 } else { // filesCount > 1
588 if (foldersCount <= 0) {
589 output = getResources().getString(R.string.file_list__footer__files, filesCount);
590
591 } else if (foldersCount == 1) {
592 output = getResources().getString(R.string.file_list__footer__files_and_folder, filesCount);
593
594 } else { // foldersCount > 1
595 output = getResources().getString(
596 R.string.file_list__footer__files_and_folders, filesCount, foldersCount
597 );
598
599 }
600 }
601 return output;
602 }
603
604 public void sortByName(boolean descending) {
605 mAdapter.setSortOrder(FileStorageUtils.SORT_NAME, descending);
606 }
607
608 public void sortByDate(boolean descending) {
609 mAdapter.setSortOrder(FileStorageUtils.SORT_DATE, descending);
610 }
611
612 public void sortBySize(boolean descending) {
613 mAdapter.setSortOrder(FileStorageUtils.SORT_SIZE, descending);
614 }
615 }