OCFileListFragment.java 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522
  1. /* ownCloud Android client application
  2. * Copyright (C) 2011 Bartek Przybylski
  3. * Copyright (C) 2012-2013 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. import java.io.File;
  20. import java.util.ArrayList;
  21. import java.util.List;
  22. import com.owncloud.android.R;
  23. import com.owncloud.android.authentication.AccountUtils;
  24. import com.owncloud.android.datamodel.FileDataStorageManager;
  25. import com.owncloud.android.datamodel.OCFile;
  26. import com.owncloud.android.files.services.FileDownloader.FileDownloaderBinder;
  27. import com.owncloud.android.files.services.FileUploader.FileUploaderBinder;
  28. import com.owncloud.android.lib.operations.common.OnRemoteOperationListener;
  29. import com.owncloud.android.lib.operations.common.RemoteOperation;
  30. import com.owncloud.android.operations.RemoveFileOperation;
  31. import com.owncloud.android.operations.RenameFileOperation;
  32. import com.owncloud.android.operations.SynchronizeFileOperation;
  33. import com.owncloud.android.ui.activity.FileDisplayActivity;
  34. import com.owncloud.android.ui.activity.TransferServiceGetter;
  35. import com.owncloud.android.ui.adapter.FileListListAdapter;
  36. import com.owncloud.android.ui.dialog.EditNameDialog;
  37. import com.owncloud.android.ui.dialog.EditNameDialog.EditNameDialogListener;
  38. import com.owncloud.android.ui.fragment.ConfirmationDialogFragment.ConfirmationDialogFragmentListener;
  39. import com.owncloud.android.ui.preview.PreviewImageFragment;
  40. import com.owncloud.android.ui.preview.PreviewMediaFragment;
  41. import com.owncloud.android.utils.Log_OC;
  42. import android.accounts.Account;
  43. import android.app.Activity;
  44. import android.content.Intent;
  45. import android.net.Uri;
  46. import android.os.Bundle;
  47. import android.os.Handler;
  48. import android.view.ContextMenu;
  49. import android.view.MenuInflater;
  50. import android.view.MenuItem;
  51. import android.view.View;
  52. import android.widget.AdapterView;
  53. import android.widget.AdapterView.AdapterContextMenuInfo;
  54. /**
  55. * A Fragment that lists all files and folders in a given path.
  56. *
  57. * @author Bartek Przybylski
  58. *
  59. */
  60. public class OCFileListFragment extends ExtendedListFragment implements EditNameDialogListener, ConfirmationDialogFragmentListener {
  61. private static final String TAG = OCFileListFragment.class.getSimpleName();
  62. private static final String MY_PACKAGE = OCFileListFragment.class.getPackage() != null ? OCFileListFragment.class.getPackage().getName() : "com.owncloud.android.ui.fragment";
  63. private static final String EXTRA_FILE = MY_PACKAGE + ".extra.FILE";
  64. private OCFileListFragment.ContainerActivity mContainerActivity;
  65. private OCFile mFile = null;
  66. private FileListListAdapter mAdapter;
  67. private Handler mHandler;
  68. private OCFile mTargetFile;
  69. /**
  70. * {@inheritDoc}
  71. */
  72. @Override
  73. public void onAttach(Activity activity) {
  74. super.onAttach(activity);
  75. Log_OC.e(TAG, "onAttach");
  76. try {
  77. mContainerActivity = (ContainerActivity) activity;
  78. } catch (ClassCastException e) {
  79. throw new ClassCastException(activity.toString() + " must implement " + OCFileListFragment.ContainerActivity.class.getSimpleName());
  80. }
  81. }
  82. /**
  83. * {@inheritDoc}
  84. */
  85. @Override
  86. public void onActivityCreated(Bundle savedInstanceState) {
  87. super.onActivityCreated(savedInstanceState);
  88. Log_OC.e(TAG, "onActivityCreated() start");
  89. mAdapter = new FileListListAdapter(getActivity(), mContainerActivity);
  90. if (savedInstanceState != null) {
  91. mFile = savedInstanceState.getParcelable(EXTRA_FILE);
  92. }
  93. setListAdapter(mAdapter);
  94. registerForContextMenu(getListView());
  95. getListView().setOnCreateContextMenuListener(this);
  96. mHandler = new Handler();
  97. }
  98. /**
  99. * Saves the current listed folder.
  100. */
  101. @Override
  102. public void onSaveInstanceState (Bundle outState) {
  103. super.onSaveInstanceState(outState);
  104. outState.putParcelable(EXTRA_FILE, mFile);
  105. }
  106. /**
  107. * Call this, when the user presses the up button.
  108. *
  109. * Tries to move up the current folder one level. If the parent folder was removed from the database,
  110. * it continues browsing up until finding an existing folders.
  111. *
  112. * return Count of folder levels browsed up.
  113. */
  114. public int onBrowseUp() {
  115. OCFile parentDir = null;
  116. int moveCount = 0;
  117. if(mFile != null){
  118. FileDataStorageManager storageManager = mContainerActivity.getStorageManager();
  119. String parentPath = null;
  120. if (mFile.getParentId() != FileDataStorageManager.ROOT_PARENT_ID) {
  121. parentPath = new File(mFile.getRemotePath()).getParent();
  122. parentPath = parentPath.endsWith(OCFile.PATH_SEPARATOR) ? parentPath : parentPath + OCFile.PATH_SEPARATOR;
  123. parentDir = storageManager.getFileByPath(parentPath);
  124. moveCount++;
  125. } else {
  126. parentDir = storageManager.getFileByPath(OCFile.ROOT_PATH); // never returns null; keep the path in root folder
  127. }
  128. while (parentDir == null) {
  129. parentPath = new File(parentPath).getParent();
  130. parentPath = parentPath.endsWith(OCFile.PATH_SEPARATOR) ? parentPath : parentPath + OCFile.PATH_SEPARATOR;
  131. parentDir = storageManager.getFileByPath(parentPath);
  132. moveCount++;
  133. } // exit is granted because storageManager.getFileByPath("/") never returns null
  134. mFile = parentDir;
  135. }
  136. if (mFile != null) {
  137. listDirectory(mFile);
  138. mContainerActivity.startSyncFolderOperation(mFile);
  139. } // else - should never happen now
  140. return moveCount;
  141. }
  142. @Override
  143. public void onItemClick(AdapterView<?> l, View v, int position, long id) {
  144. OCFile file = (OCFile) mAdapter.getItem(position);
  145. if (file != null) {
  146. if (file.isFolder()) {
  147. // update state and view of this fragment
  148. listDirectory(file);
  149. // then, notify parent activity to let it update its state and view, and other fragments
  150. mContainerActivity.onBrowsedDownTo(file);
  151. } else { /// Click on a file
  152. if (PreviewImageFragment.canBePreviewed(file)) {
  153. // preview image - it handles the download, if needed
  154. mContainerActivity.startImagePreview(file);
  155. } else if (file.isDown()) {
  156. if (PreviewMediaFragment.canBePreviewed(file)) {
  157. // media preview
  158. mContainerActivity.startMediaPreview(file, 0, true);
  159. } else {
  160. FileDisplayActivity activity = (FileDisplayActivity) getSherlockActivity();
  161. activity.getFileOperationsHelper().openFile(file, activity);
  162. }
  163. } else {
  164. // automatic download, preview on finish
  165. mContainerActivity.startDownloadForPreview(file);
  166. }
  167. }
  168. } else {
  169. Log_OC.d(TAG, "Null object in ListAdapter!!");
  170. }
  171. }
  172. /**
  173. * {@inheritDoc}
  174. */
  175. @Override
  176. public void onCreateContextMenu (ContextMenu menu, View v, ContextMenu.ContextMenuInfo menuInfo) {
  177. super.onCreateContextMenu(menu, v, menuInfo);
  178. MenuInflater inflater = getActivity().getMenuInflater();
  179. inflater.inflate(R.menu.file_actions_menu, menu);
  180. AdapterContextMenuInfo info = (AdapterContextMenuInfo) menuInfo;
  181. OCFile targetFile = (OCFile) mAdapter.getItem(info.position);
  182. List<Integer> toHide = new ArrayList<Integer>();
  183. List<Integer> toDisable = new ArrayList<Integer>();
  184. MenuItem item = null;
  185. if (targetFile.isFolder()) {
  186. // contextual menu for folders
  187. toHide.add(R.id.action_open_file_with);
  188. toHide.add(R.id.action_download_file);
  189. toHide.add(R.id.action_cancel_download);
  190. toHide.add(R.id.action_cancel_upload);
  191. toHide.add(R.id.action_sync_file);
  192. toHide.add(R.id.action_see_details);
  193. toHide.add(R.id.action_send_file);
  194. if ( mContainerActivity.getFileDownloaderBinder().isDownloading(AccountUtils.getCurrentOwnCloudAccount(getActivity()), targetFile) ||
  195. mContainerActivity.getFileUploaderBinder().isUploading(AccountUtils.getCurrentOwnCloudAccount(getActivity()), targetFile) ) {
  196. toDisable.add(R.id.action_rename_file);
  197. toDisable.add(R.id.action_remove_file);
  198. }
  199. } else {
  200. // contextual menu for regular files
  201. // new design: 'download' and 'open with' won't be available anymore in context menu
  202. toHide.add(R.id.action_download_file);
  203. toHide.add(R.id.action_open_file_with);
  204. if (targetFile.isDown()) {
  205. toHide.add(R.id.action_cancel_download);
  206. toHide.add(R.id.action_cancel_upload);
  207. } else {
  208. toHide.add(R.id.action_sync_file);
  209. }
  210. if ( mContainerActivity.getFileDownloaderBinder().isDownloading(AccountUtils.getCurrentOwnCloudAccount(getActivity()), targetFile)) {
  211. toHide.add(R.id.action_cancel_upload);
  212. toDisable.add(R.id.action_rename_file);
  213. toDisable.add(R.id.action_remove_file);
  214. } else if ( mContainerActivity.getFileUploaderBinder().isUploading(AccountUtils.getCurrentOwnCloudAccount(getActivity()), targetFile)) {
  215. toHide.add(R.id.action_cancel_download);
  216. toDisable.add(R.id.action_rename_file);
  217. toDisable.add(R.id.action_remove_file);
  218. } else {
  219. toHide.add(R.id.action_cancel_download);
  220. toHide.add(R.id.action_cancel_upload);
  221. }
  222. }
  223. // Options shareLink
  224. if (!targetFile.isShareByLink()) {
  225. toHide.add(R.id.action_unshare_file);
  226. }
  227. for (int i : toHide) {
  228. item = menu.findItem(i);
  229. if (item != null) {
  230. item.setVisible(false);
  231. item.setEnabled(false);
  232. }
  233. }
  234. for (int i : toDisable) {
  235. item = menu.findItem(i);
  236. if (item != null) {
  237. item.setEnabled(false);
  238. }
  239. }
  240. }
  241. /**
  242. * {@inhericDoc}
  243. */
  244. @Override
  245. public boolean onContextItemSelected (MenuItem item) {
  246. AdapterContextMenuInfo info = (AdapterContextMenuInfo) item.getMenuInfo();
  247. mTargetFile = (OCFile) mAdapter.getItem(info.position);
  248. switch (item.getItemId()) {
  249. case R.id.action_share_file: {
  250. FileDisplayActivity activity = (FileDisplayActivity) getSherlockActivity();
  251. activity.getFileOperationsHelper().shareFileWithLink(mTargetFile, activity);
  252. return true;
  253. }
  254. case R.id.action_unshare_file: {
  255. FileDisplayActivity activity = (FileDisplayActivity) getSherlockActivity();
  256. activity.getFileOperationsHelper().unshareFileWithLink(mTargetFile, activity);
  257. return true;
  258. }
  259. case R.id.action_rename_file: {
  260. String fileName = mTargetFile.getFileName();
  261. int extensionStart = mTargetFile.isFolder() ? -1 : fileName.lastIndexOf(".");
  262. int selectionEnd = (extensionStart >= 0) ? extensionStart : fileName.length();
  263. EditNameDialog dialog = EditNameDialog.newInstance(getString(R.string.rename_dialog_title), fileName, 0, selectionEnd, this);
  264. dialog.show(getFragmentManager(), EditNameDialog.TAG);
  265. return true;
  266. }
  267. case R.id.action_remove_file: {
  268. int messageStringId = R.string.confirmation_remove_alert;
  269. int posBtnStringId = R.string.confirmation_remove_remote;
  270. int neuBtnStringId = -1;
  271. if (mTargetFile.isFolder()) {
  272. messageStringId = R.string.confirmation_remove_folder_alert;
  273. posBtnStringId = R.string.confirmation_remove_remote_and_local;
  274. neuBtnStringId = R.string.confirmation_remove_folder_local;
  275. } else if (mTargetFile.isDown()) {
  276. posBtnStringId = R.string.confirmation_remove_remote_and_local;
  277. neuBtnStringId = R.string.confirmation_remove_local;
  278. }
  279. ConfirmationDialogFragment confDialog = ConfirmationDialogFragment.newInstance(
  280. messageStringId,
  281. new String[]{mTargetFile.getFileName()},
  282. posBtnStringId,
  283. neuBtnStringId,
  284. R.string.common_cancel);
  285. confDialog.setOnConfirmationListener(this);
  286. confDialog.show(getFragmentManager(), FileDetailFragment.FTAG_CONFIRMATION);
  287. return true;
  288. }
  289. case R.id.action_sync_file: {
  290. Account account = AccountUtils.getCurrentOwnCloudAccount(getSherlockActivity());
  291. RemoteOperation operation = new SynchronizeFileOperation(mTargetFile, null, mContainerActivity.getStorageManager(), account, true, getSherlockActivity());
  292. operation.execute(account, getSherlockActivity(), mContainerActivity, mHandler, getSherlockActivity());
  293. ((FileDisplayActivity) getSherlockActivity()).showLoadingDialog();
  294. return true;
  295. }
  296. case R.id.action_cancel_download: {
  297. FileDownloaderBinder downloaderBinder = mContainerActivity.getFileDownloaderBinder();
  298. Account account = AccountUtils.getCurrentOwnCloudAccount(getActivity());
  299. if (downloaderBinder != null && downloaderBinder.isDownloading(account, mTargetFile)) {
  300. downloaderBinder.cancel(account, mTargetFile);
  301. listDirectory();
  302. mContainerActivity.onTransferStateChanged(mTargetFile, false, false);
  303. }
  304. return true;
  305. }
  306. case R.id.action_cancel_upload: {
  307. FileUploaderBinder uploaderBinder = mContainerActivity.getFileUploaderBinder();
  308. Account account = AccountUtils.getCurrentOwnCloudAccount(getActivity());
  309. if (uploaderBinder != null && uploaderBinder.isUploading(account, mTargetFile)) {
  310. uploaderBinder.cancel(account, mTargetFile);
  311. listDirectory();
  312. mContainerActivity.onTransferStateChanged(mTargetFile, false, false);
  313. }
  314. return true;
  315. }
  316. case R.id.action_see_details: {
  317. ((FileFragment.ContainerActivity)getActivity()).showDetails(mTargetFile);
  318. return true;
  319. }
  320. case R.id.action_send_file: {
  321. Intent sharingIntent = new Intent(android.content.Intent.ACTION_SEND);
  322. // set MimeType
  323. sharingIntent.setType(mTargetFile.getMimetype());
  324. sharingIntent.putExtra(Intent.EXTRA_STREAM, Uri.parse("file://" + mTargetFile.getStoragePath()));
  325. startActivity(Intent.createChooser(sharingIntent, getResources().getString(R.string.send_file_title_intent)));
  326. return true;
  327. }
  328. default:
  329. return super.onContextItemSelected(item);
  330. }
  331. }
  332. /**
  333. * Use this to query the {@link OCFile} that is currently
  334. * being displayed by this fragment
  335. * @return The currently viewed OCFile
  336. */
  337. public OCFile getCurrentFile(){
  338. return mFile;
  339. }
  340. /**
  341. * Calls {@link OCFileListFragment#listDirectory(OCFile)} with a null parameter
  342. */
  343. public void listDirectory(){
  344. listDirectory(null);
  345. }
  346. /**
  347. * Lists the given directory on the view. When the input parameter is null,
  348. * it will either refresh the last known directory. list the root
  349. * if there never was a directory.
  350. *
  351. * @param directory File to be listed
  352. */
  353. public void listDirectory(OCFile directory) {
  354. FileDataStorageManager storageManager = mContainerActivity.getStorageManager();
  355. if (storageManager != null) {
  356. // Check input parameters for null
  357. if(directory == null){
  358. if(mFile != null){
  359. directory = mFile;
  360. } else {
  361. directory = storageManager.getFileByPath("/");
  362. if (directory == null) return; // no files, wait for sync
  363. }
  364. }
  365. // If that's not a directory -> List its parent
  366. if(!directory.isFolder()){
  367. Log_OC.w(TAG, "You see, that is not a directory -> " + directory.toString());
  368. directory = storageManager.getFileById(directory.getParentId());
  369. }
  370. mAdapter.swapDirectory(directory, storageManager);
  371. if (mFile == null || !mFile.equals(directory)) {
  372. mList.setSelectionFromTop(0, 0);
  373. }
  374. mFile = directory;
  375. }
  376. }
  377. /**
  378. * Interface to implement by any Activity that includes some instance of FileListFragment
  379. *
  380. * @author David A. Velasco
  381. */
  382. public interface ContainerActivity extends TransferServiceGetter, OnRemoteOperationListener {
  383. /**
  384. * Callback method invoked when a the user browsed into a different folder through the list of files
  385. *
  386. * @param file
  387. */
  388. public void onBrowsedDownTo(OCFile folder);
  389. public void startDownloadForPreview(OCFile file);
  390. public void startMediaPreview(OCFile file, int i, boolean b);
  391. public void startImagePreview(OCFile file);
  392. public void startSyncFolderOperation(OCFile folder);
  393. /**
  394. * Getter for the current DataStorageManager in the container activity
  395. */
  396. public FileDataStorageManager getStorageManager();
  397. /**
  398. * Callback method invoked when a the 'transfer state' of a file changes.
  399. *
  400. * This happens when a download or upload is started or ended for a file.
  401. *
  402. * This method is necessary by now to update the user interface of the double-pane layout in tablets
  403. * because methods {@link FileDownloaderBinder#isDownloading(Account, OCFile)} and {@link FileUploaderBinder#isUploading(Account, OCFile)}
  404. * won't provide the needed response before the method where this is called finishes.
  405. *
  406. * TODO Remove this when the transfer state of a file is kept in the database (other thing TODO)
  407. *
  408. * @param file OCFile which state changed.
  409. * @param downloading Flag signaling if the file is now downloading.
  410. * @param uploading Flag signaling if the file is now uploading.
  411. */
  412. public void onTransferStateChanged(OCFile file, boolean downloading, boolean uploading);
  413. }
  414. @Override
  415. public void onDismiss(EditNameDialog dialog) {
  416. if (dialog.getResult()) {
  417. String newFilename = dialog.getNewFilename();
  418. Log_OC.d(TAG, "name edit dialog dismissed with new name " + newFilename);
  419. RemoteOperation operation = new RenameFileOperation(mTargetFile,
  420. AccountUtils.getCurrentOwnCloudAccount(getActivity()),
  421. newFilename,
  422. mContainerActivity.getStorageManager());
  423. operation.execute(AccountUtils.getCurrentOwnCloudAccount(getSherlockActivity()), getSherlockActivity(), mContainerActivity, mHandler, getSherlockActivity());
  424. ((FileDisplayActivity) getActivity()).showLoadingDialog();
  425. }
  426. }
  427. @Override
  428. public void onConfirmation(String callerTag) {
  429. if (callerTag.equals(FileDetailFragment.FTAG_CONFIRMATION)) {
  430. if (mContainerActivity.getStorageManager().getFileById(mTargetFile.getFileId()) != null) {
  431. RemoteOperation operation = new RemoveFileOperation( mTargetFile,
  432. true,
  433. mContainerActivity.getStorageManager());
  434. operation.execute(AccountUtils.getCurrentOwnCloudAccount(getSherlockActivity()), getSherlockActivity(), mContainerActivity, mHandler, getSherlockActivity());
  435. ((FileDisplayActivity) getActivity()).showLoadingDialog();
  436. }
  437. }
  438. }
  439. @Override
  440. public void onNeutral(String callerTag) {
  441. mContainerActivity.getStorageManager().removeFile(mTargetFile, false, true); // TODO perform in background task / new thread
  442. listDirectory();
  443. mContainerActivity.onTransferStateChanged(mTargetFile, false, false);
  444. }
  445. @Override
  446. public void onCancel(String callerTag) {
  447. Log_OC.d(TAG, "REMOVAL CANCELED");
  448. }
  449. }