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