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