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