OCFileListFragment.java 22 KB

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