OCFileListFragment.java 22 KB

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