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