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