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