Fixed wrong field override breaking options menu in PreviewMediaFragment and PreviewI...
[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 mAdapter = new FileListListAdapter(getSherlockActivity(), mContainerActivity);
115
116 if (savedInstanceState != null) {
117 mFile = savedInstanceState.getParcelable(EXTRA_FILE);
118 mIndexes = savedInstanceState.getIntegerArrayList(KEY_INDEXES);
119 mFirstPositions = savedInstanceState.getIntegerArrayList(KEY_FIRST_POSITIONS);
120 mTops = savedInstanceState.getIntegerArrayList(KEY_TOPS);
121 mHeightCell = savedInstanceState.getInt(KEY_HEIGHT_CELL);
122
123 } else {
124 mIndexes = new ArrayList<Integer>();
125 mFirstPositions = new ArrayList<Integer>();
126 mTops = new ArrayList<Integer>();
127 mHeightCell = 0;
128
129 }
130
131 mAdapter = new FileListListAdapter(getSherlockActivity(), mContainerActivity);
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 = (OCFile) mAdapter.getItem(position);
255 if (file != null) {
256 if (file.isFolder()) {
257 // update state and view of this fragment
258 listDirectory(file);
259 // then, notify parent activity to let it update its state and view, and other fragments
260 mContainerActivity.onBrowsedDownTo(file);
261 // save index and top position
262 saveIndexAndTopPosition(position);
263
264 } else { /// Click on a file
265 if (PreviewImageFragment.canBePreviewed(file)) {
266 // preview image - it handles the download, if needed
267 ((FileDisplayActivity)mContainerActivity).startImagePreview(file);
268
269 } else if (file.isDown()) {
270 if (PreviewMediaFragment.canBePreviewed(file)) {
271 // media preview
272 ((FileDisplayActivity)mContainerActivity).startMediaPreview(file, 0, true);
273 } else {
274 ((FileDisplayActivity)mContainerActivity).getFileOperationsHelper().openFile(file);
275 }
276
277 } else {
278 // automatic download, preview on finish
279 ((FileDisplayActivity)mContainerActivity).startDownloadForPreview(file);
280 }
281
282 }
283
284 } else {
285 Log_OC.d(TAG, "Null object in ListAdapter!!");
286 }
287
288 }
289
290 /**
291 * {@inheritDoc}
292 */
293 @Override
294 public void onCreateContextMenu (ContextMenu menu, View v, ContextMenu.ContextMenuInfo menuInfo) {
295 super.onCreateContextMenu(menu, v, menuInfo);
296 MenuInflater inflater = getSherlockActivity().getMenuInflater();
297 inflater.inflate(R.menu.file_actions_menu, menu);
298 AdapterContextMenuInfo info = (AdapterContextMenuInfo) menuInfo;
299 OCFile targetFile = (OCFile) mAdapter.getItem(info.position);
300 List<Integer> toHide = new ArrayList<Integer>();
301 List<Integer> toDisable = new ArrayList<Integer>();
302
303 MenuItem item = null;
304 if (targetFile.isFolder()) {
305 // contextual menu for folders
306 toHide.add(R.id.action_open_file_with);
307 toHide.add(R.id.action_download_file);
308 toHide.add(R.id.action_cancel_download);
309 toHide.add(R.id.action_cancel_upload);
310 toHide.add(R.id.action_sync_file);
311 toHide.add(R.id.action_see_details);
312 toHide.add(R.id.action_send_file);
313 if ( mContainerActivity.getFileDownloaderBinder().isDownloading(AccountUtils.getCurrentOwnCloudAccount(getSherlockActivity()), targetFile) ||
314 mContainerActivity.getFileUploaderBinder().isUploading(AccountUtils.getCurrentOwnCloudAccount(getSherlockActivity()), targetFile) ) {
315 toDisable.add(R.id.action_rename_file);
316 toDisable.add(R.id.action_remove_file);
317
318 }
319
320 } else {
321 // contextual menu for regular files
322
323 // new design: 'download' and 'open with' won't be available anymore in context menu
324 toHide.add(R.id.action_download_file);
325 toHide.add(R.id.action_open_file_with);
326
327 if (targetFile.isDown()) {
328 toHide.add(R.id.action_cancel_download);
329 toHide.add(R.id.action_cancel_upload);
330
331 } else {
332 toHide.add(R.id.action_sync_file);
333 }
334 if ( mContainerActivity.getFileDownloaderBinder().isDownloading(AccountUtils.getCurrentOwnCloudAccount(getSherlockActivity()), targetFile)) {
335 toHide.add(R.id.action_cancel_upload);
336 toDisable.add(R.id.action_rename_file);
337 toDisable.add(R.id.action_remove_file);
338
339 } else if ( mContainerActivity.getFileUploaderBinder().isUploading(AccountUtils.getCurrentOwnCloudAccount(getSherlockActivity()), targetFile)) {
340 toHide.add(R.id.action_cancel_download);
341 toDisable.add(R.id.action_rename_file);
342 toDisable.add(R.id.action_remove_file);
343
344 } else {
345 toHide.add(R.id.action_cancel_download);
346 toHide.add(R.id.action_cancel_upload);
347 }
348 }
349
350 // Options shareLink
351 if (!targetFile.isShareByLink()) {
352 toHide.add(R.id.action_unshare_file);
353 }
354
355 // Send file
356 boolean sendEnabled = getString(R.string.send_files_to_other_apps).equalsIgnoreCase("on");
357 if (!sendEnabled) {
358 toHide.add(R.id.action_send_file);
359 }
360
361 for (int i : toHide) {
362 item = menu.findItem(i);
363 if (item != null) {
364 item.setVisible(false);
365 item.setEnabled(false);
366 }
367 }
368
369 for (int i : toDisable) {
370 item = menu.findItem(i);
371 if (item != null) {
372 item.setEnabled(false);
373 }
374 }
375 }
376
377
378 /**
379 * {@inhericDoc}
380 */
381 @Override
382 public boolean onContextItemSelected (MenuItem item) {
383 AdapterContextMenuInfo info = (AdapterContextMenuInfo) item.getMenuInfo();
384 mTargetFile = (OCFile) mAdapter.getItem(info.position);
385 switch (item.getItemId()) {
386 case R.id.action_share_file: {
387 mContainerActivity.getFileOperationsHelper().shareFileWithLink(mTargetFile);
388 return true;
389 }
390 case R.id.action_unshare_file: {
391 mContainerActivity.getFileOperationsHelper().unshareFileWithLink(mTargetFile);
392 return true;
393 }
394 case R.id.action_rename_file: {
395 String fileName = mTargetFile.getFileName();
396 int extensionStart = mTargetFile.isFolder() ? -1 : fileName.lastIndexOf(".");
397 int selectionEnd = (extensionStart >= 0) ? extensionStart : fileName.length();
398 EditNameDialog dialog = EditNameDialog.newInstance(getString(R.string.rename_dialog_title), fileName, 0, selectionEnd, this);
399 dialog.show(getFragmentManager(), EditNameDialog.TAG);
400 return true;
401 }
402 case R.id.action_remove_file: {
403 int messageStringId = R.string.confirmation_remove_alert;
404 int posBtnStringId = R.string.confirmation_remove_remote;
405 int neuBtnStringId = -1;
406 if (mTargetFile.isFolder()) {
407 messageStringId = R.string.confirmation_remove_folder_alert;
408 posBtnStringId = R.string.confirmation_remove_remote_and_local;
409 neuBtnStringId = R.string.confirmation_remove_folder_local;
410 } else if (mTargetFile.isDown()) {
411 posBtnStringId = R.string.confirmation_remove_remote_and_local;
412 neuBtnStringId = R.string.confirmation_remove_local;
413 }
414 ConfirmationDialogFragment confDialog = ConfirmationDialogFragment.newInstance(
415 messageStringId,
416 new String[]{mTargetFile.getFileName()},
417 posBtnStringId,
418 neuBtnStringId,
419 R.string.common_cancel);
420 confDialog.setOnConfirmationListener(this);
421 confDialog.show(getFragmentManager(), FileDetailFragment.FTAG_CONFIRMATION);
422 return true;
423 }
424 case R.id.action_sync_file: {
425 mContainerActivity.getFileOperationsHelper().syncFile(mTargetFile);
426 return true;
427 }
428 case R.id.action_cancel_download: {
429 FileDownloaderBinder downloaderBinder = mContainerActivity.getFileDownloaderBinder();
430 Account account = AccountUtils.getCurrentOwnCloudAccount(getSherlockActivity());
431 if (downloaderBinder != null && downloaderBinder.isDownloading(account, mTargetFile)) {
432 downloaderBinder.cancel(account, mTargetFile);
433 listDirectory();
434 mContainerActivity.onTransferStateChanged(mTargetFile, false, false);
435 }
436 return true;
437 }
438 case R.id.action_cancel_upload: {
439 FileUploaderBinder uploaderBinder = mContainerActivity.getFileUploaderBinder();
440 Account account = AccountUtils.getCurrentOwnCloudAccount(getSherlockActivity());
441 if (uploaderBinder != null && uploaderBinder.isUploading(account, mTargetFile)) {
442 uploaderBinder.cancel(account, mTargetFile);
443 listDirectory();
444 mContainerActivity.onTransferStateChanged(mTargetFile, false, false);
445 }
446 return true;
447 }
448 case R.id.action_see_details: {
449 mContainerActivity.showDetails(mTargetFile);
450 return true;
451 }
452 case R.id.action_send_file: {
453 // Obtain the file
454 if (!mTargetFile.isDown()) { // Download the file
455 Log_OC.d(TAG, mTargetFile.getRemotePath() + " : File must be downloaded");
456 ((FileDisplayActivity)mContainerActivity).startDownloadForSending(mTargetFile);
457
458 } else {
459 ((FileDisplayActivity)mContainerActivity).getFileOperationsHelper().sendDownloadedFile(mTargetFile);
460 }
461 return true;
462 }
463 default:
464 return super.onContextItemSelected(item);
465 }
466 }
467
468
469 /**
470 * Use this to query the {@link OCFile} that is currently
471 * being displayed by this fragment
472 * @return The currently viewed OCFile
473 */
474 public OCFile getCurrentFile(){
475 return mFile;
476 }
477
478 /**
479 * Calls {@link OCFileListFragment#listDirectory(OCFile)} with a null parameter
480 */
481 public void listDirectory(){
482 listDirectory(null);
483 }
484
485 /**
486 * Lists the given directory on the view. When the input parameter is null,
487 * it will either refresh the last known directory. list the root
488 * if there never was a directory.
489 *
490 * @param directory File to be listed
491 */
492 public void listDirectory(OCFile directory) {
493 FileDataStorageManager storageManager = mContainerActivity.getStorageManager();
494 if (storageManager != null) {
495
496 // Check input parameters for null
497 if(directory == null){
498 if(mFile != null){
499 directory = mFile;
500 } else {
501 directory = storageManager.getFileByPath("/");
502 if (directory == null) return; // no files, wait for sync
503 }
504 }
505
506
507 // If that's not a directory -> List its parent
508 if(!directory.isFolder()){
509 Log_OC.w(TAG, "You see, that is not a directory -> " + directory.toString());
510 directory = storageManager.getFileById(directory.getParentId());
511 }
512
513 mAdapter.swapDirectory(directory, storageManager);
514 if (mFile == null || !mFile.equals(directory)) {
515 mList.setSelectionFromTop(0, 0);
516 }
517 mFile = directory;
518 }
519 }
520
521
522
523 @Override
524 public void onDismiss(EditNameDialog dialog) {
525 if (dialog.getResult()) {
526 String newFilename = dialog.getNewFilename();
527 Log_OC.d(TAG, "name edit dialog dismissed with new name " + newFilename);
528 mContainerActivity.getFileOperationsHelper().renameFile(mTargetFile, newFilename);
529 }
530 }
531
532
533 @Override
534 public void onConfirmation(String callerTag) {
535 if (callerTag.equals(FileDetailFragment.FTAG_CONFIRMATION)) {
536 FileDataStorageManager storageManager = mContainerActivity.getStorageManager();
537 if (storageManager.getFileById(mTargetFile.getFileId()) != null) {
538 mContainerActivity.getFileOperationsHelper().removeFile(mTargetFile, true);
539 }
540 }
541 }
542
543 @Override
544 public void onNeutral(String callerTag) {
545 mContainerActivity.getStorageManager().removeFile(mTargetFile, false, true); // TODO perform in background task / new thread
546 listDirectory();
547 mContainerActivity.onTransferStateChanged(mTargetFile, false, false);
548 }
549
550 @Override
551 public void onCancel(String callerTag) {
552 Log_OC.d(TAG, "REMOVAL CANCELED");
553 }
554
555 }