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