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