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