Clean up of synchronization code
[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.Log_OC;
25 import com.owncloud.android.R;
26 import com.owncloud.android.authentication.AccountUtils;
27 import com.owncloud.android.datamodel.DataStorageManager;
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.operations.OnRemoteOperationListener;
33 import com.owncloud.android.operations.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
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 public void onBrowseUp() {
127 OCFile parentDir = null;
128
129 if(mFile != null){
130 DataStorageManager storageManager = mContainerActivity.getStorageManager();
131 if (mFile.getParentId() == 0) {
132 parentDir = storageManager.getFileById(1);
133 }
134 else {
135 parentDir = storageManager.getFileById(mFile.getParentId());
136 }
137
138 mFile = parentDir;
139 }
140
141 if (mFile != null) {
142 listDirectory(mFile);
143
144 mContainerActivity.startSyncFolderOperation(mFile);
145 }
146
147 }
148
149 @Override
150 public void onItemClick(AdapterView<?> l, View v, int position, long id) {
151 OCFile file = (OCFile) mAdapter.getItem(position);
152 if (file != null) {
153 if (file.isDirectory()) {
154 // update state and view of this fragment
155 listDirectory(file);
156 // then, notify parent activity to let it update its state and view, and other fragments
157 mContainerActivity.onBrowsedDownTo(file);
158
159 } else { /// Click on a file
160 if (PreviewImageFragment.canBePreviewed(file)) {
161 // preview image - it handles the download, if needed
162 mContainerActivity.startImagePreview(file);
163
164 } else if (file.isDown()) {
165 if (PreviewMediaFragment.canBePreviewed(file)) {
166 // media preview
167 mContainerActivity.startMediaPreview(file, 0, true);
168 } else {
169 // open with
170 mContainerActivity.openFile(file);
171 }
172
173 } else {
174 // automatic download, preview on finish
175 mContainerActivity.startDownloadForPreview(file);
176 }
177
178 }
179
180 } else {
181 Log_OC.d(TAG, "Null object in ListAdapter!!");
182 }
183
184 }
185
186 /**
187 * {@inheritDoc}
188 */
189 @Override
190 public void onCreateContextMenu (ContextMenu menu, View v, ContextMenu.ContextMenuInfo menuInfo) {
191 super.onCreateContextMenu(menu, v, menuInfo);
192 MenuInflater inflater = getActivity().getMenuInflater();
193 inflater.inflate(R.menu.file_actions_menu, menu);
194 AdapterContextMenuInfo info = (AdapterContextMenuInfo) menuInfo;
195 OCFile targetFile = (OCFile) mAdapter.getItem(info.position);
196 List<Integer> toHide = new ArrayList<Integer>();
197 List<Integer> toDisable = new ArrayList<Integer>();
198
199 MenuItem item = null;
200 if (targetFile.isDirectory()) {
201 // contextual menu for folders
202 toHide.add(R.id.action_open_file_with);
203 toHide.add(R.id.action_download_file);
204 toHide.add(R.id.action_cancel_download);
205 toHide.add(R.id.action_cancel_upload);
206 toHide.add(R.id.action_sync_file);
207 toHide.add(R.id.action_see_details);
208 if ( mContainerActivity.getFileDownloaderBinder().isDownloading(AccountUtils.getCurrentOwnCloudAccount(getActivity()), targetFile) ||
209 mContainerActivity.getFileUploaderBinder().isUploading(AccountUtils.getCurrentOwnCloudAccount(getActivity()), targetFile) ) {
210 toDisable.add(R.id.action_rename_file);
211 toDisable.add(R.id.action_remove_file);
212
213 }
214
215 } else {
216 // contextual menu for regular files
217
218 // new design: 'download' and 'open with' won't be available anymore in context menu
219 toHide.add(R.id.action_download_file);
220 toHide.add(R.id.action_open_file_with);
221
222 if (targetFile.isDown()) {
223 toHide.add(R.id.action_cancel_download);
224 toHide.add(R.id.action_cancel_upload);
225
226 } else {
227 toHide.add(R.id.action_sync_file);
228 }
229 if ( mContainerActivity.getFileDownloaderBinder().isDownloading(AccountUtils.getCurrentOwnCloudAccount(getActivity()), targetFile)) {
230 toHide.add(R.id.action_cancel_upload);
231 toDisable.add(R.id.action_rename_file);
232 toDisable.add(R.id.action_remove_file);
233
234 } else if ( mContainerActivity.getFileUploaderBinder().isUploading(AccountUtils.getCurrentOwnCloudAccount(getActivity()), targetFile)) {
235 toHide.add(R.id.action_cancel_download);
236 toDisable.add(R.id.action_rename_file);
237 toDisable.add(R.id.action_remove_file);
238
239 } else {
240 toHide.add(R.id.action_cancel_download);
241 toHide.add(R.id.action_cancel_upload);
242 }
243 }
244
245 for (int i : toHide) {
246 item = menu.findItem(i);
247 if (item != null) {
248 item.setVisible(false);
249 item.setEnabled(false);
250 }
251 }
252
253 for (int i : toDisable) {
254 item = menu.findItem(i);
255 if (item != null) {
256 item.setEnabled(false);
257 }
258 }
259 }
260
261
262 /**
263 * {@inhericDoc}
264 */
265 @Override
266 public boolean onContextItemSelected (MenuItem item) {
267 AdapterContextMenuInfo info = (AdapterContextMenuInfo) item.getMenuInfo();
268 mTargetFile = (OCFile) mAdapter.getItem(info.position);
269 switch (item.getItemId()) {
270 case R.id.action_rename_file: {
271 String fileName = mTargetFile.getFileName();
272 int extensionStart = mTargetFile.isDirectory() ? -1 : fileName.lastIndexOf(".");
273 int selectionEnd = (extensionStart >= 0) ? extensionStart : fileName.length();
274 EditNameDialog dialog = EditNameDialog.newInstance(getString(R.string.rename_dialog_title), fileName, 0, selectionEnd, this);
275 dialog.show(getFragmentManager(), EditNameDialog.TAG);
276 return true;
277 }
278 case R.id.action_remove_file: {
279 int messageStringId = R.string.confirmation_remove_alert;
280 int posBtnStringId = R.string.confirmation_remove_remote;
281 int neuBtnStringId = -1;
282 if (mTargetFile.isDirectory()) {
283 messageStringId = R.string.confirmation_remove_folder_alert;
284 posBtnStringId = R.string.confirmation_remove_remote_and_local;
285 neuBtnStringId = R.string.confirmation_remove_folder_local;
286 } else if (mTargetFile.isDown()) {
287 posBtnStringId = R.string.confirmation_remove_remote_and_local;
288 neuBtnStringId = R.string.confirmation_remove_local;
289 }
290 ConfirmationDialogFragment confDialog = ConfirmationDialogFragment.newInstance(
291 messageStringId,
292 new String[]{mTargetFile.getFileName()},
293 posBtnStringId,
294 neuBtnStringId,
295 R.string.common_cancel);
296 confDialog.setOnConfirmationListener(this);
297 confDialog.show(getFragmentManager(), FileDetailFragment.FTAG_CONFIRMATION);
298 return true;
299 }
300 case R.id.action_sync_file: {
301 Account account = AccountUtils.getCurrentOwnCloudAccount(getSherlockActivity());
302 RemoteOperation operation = new SynchronizeFileOperation(mTargetFile, null, mContainerActivity.getStorageManager(), account, true, false, getSherlockActivity());
303 operation.execute(account, getSherlockActivity(), mContainerActivity, mHandler, getSherlockActivity());
304 ((FileDisplayActivity) getSherlockActivity()).showLoadingDialog();
305 return true;
306 }
307 case R.id.action_cancel_download: {
308 FileDownloaderBinder downloaderBinder = mContainerActivity.getFileDownloaderBinder();
309 Account account = AccountUtils.getCurrentOwnCloudAccount(getActivity());
310 if (downloaderBinder != null && downloaderBinder.isDownloading(account, mTargetFile)) {
311 downloaderBinder.cancel(account, mTargetFile);
312 listDirectory();
313 mContainerActivity.onTransferStateChanged(mTargetFile, false, false);
314 }
315 return true;
316 }
317 case R.id.action_cancel_upload: {
318 FileUploaderBinder uploaderBinder = mContainerActivity.getFileUploaderBinder();
319 Account account = AccountUtils.getCurrentOwnCloudAccount(getActivity());
320 if (uploaderBinder != null && uploaderBinder.isUploading(account, mTargetFile)) {
321 uploaderBinder.cancel(account, mTargetFile);
322 listDirectory();
323 mContainerActivity.onTransferStateChanged(mTargetFile, false, false);
324 }
325 return true;
326 }
327 case R.id.action_see_details: {
328 ((FileFragment.ContainerActivity)getActivity()).showDetails(mTargetFile);
329 return true;
330 }
331 default:
332 return super.onContextItemSelected(item);
333 }
334 }
335
336
337 /**
338 * Use this to query the {@link OCFile} that is currently
339 * being displayed by this fragment
340 * @return The currently viewed OCFile
341 */
342 public OCFile getCurrentFile(){
343 return mFile;
344 }
345
346 /**
347 * Calls {@link OCFileListFragment#listDirectory(OCFile)} with a null parameter
348 */
349 public void listDirectory(){
350 listDirectory(null);
351 }
352
353 /**
354 * Lists the given directory on the view. When the input parameter is null,
355 * it will either refresh the last known directory. list the root
356 * if there never was a directory.
357 *
358 * @param directory File to be listed
359 */
360 public void listDirectory(OCFile directory) {
361 DataStorageManager storageManager = mContainerActivity.getStorageManager();
362 if (storageManager != null) {
363
364 // Check input parameters for null
365 if(directory == null){
366 if(mFile != null){
367 directory = mFile;
368 } else {
369 directory = storageManager.getFileByPath("/");
370 if (directory == null) return; // no files, wait for sync
371 }
372 }
373
374
375 // If that's not a directory -> List its parent
376 if(!directory.isDirectory()){
377 Log_OC.w(TAG, "You see, that is not a directory -> " + directory.toString());
378 directory = storageManager.getFileById(directory.getParentId());
379 }
380
381 mAdapter.swapDirectory(directory, storageManager);
382 if (mFile == null || !mFile.equals(directory)) {
383 mList.setSelectionFromTop(0, 0);
384 }
385 mFile = directory;
386 }
387 }
388
389
390
391 /**
392 * Interface to implement by any Activity that includes some instance of FileListFragment
393 *
394 * @author David A. Velasco
395 */
396 public interface ContainerActivity extends TransferServiceGetter, OnRemoteOperationListener, FileHandler {
397
398 /**
399 * Callback method invoked when a the user browsed into a different folder through the list of files
400 *
401 * @param file
402 */
403 public void onBrowsedDownTo(OCFile folder);
404
405 public void startDownloadForPreview(OCFile file);
406
407 public void startMediaPreview(OCFile file, int i, boolean b);
408
409 public void startImagePreview(OCFile file);
410
411 public void startSyncFolderOperation(OCFile folder);
412
413 /**
414 * Getter for the current DataStorageManager in the container activity
415 */
416 public DataStorageManager getStorageManager();
417
418
419 /**
420 * Callback method invoked when a the 'transfer state' of a file changes.
421 *
422 * This happens when a download or upload is started or ended for a file.
423 *
424 * This method is necessary by now to update the user interface of the double-pane layout in tablets
425 * because methods {@link FileDownloaderBinder#isDownloading(Account, OCFile)} and {@link FileUploaderBinder#isUploading(Account, OCFile)}
426 * won't provide the needed response before the method where this is called finishes.
427 *
428 * TODO Remove this when the transfer state of a file is kept in the database (other thing TODO)
429 *
430 * @param file OCFile which state changed.
431 * @param downloading Flag signaling if the file is now downloading.
432 * @param uploading Flag signaling if the file is now uploading.
433 */
434 public void onTransferStateChanged(OCFile file, boolean downloading, boolean uploading);
435
436 }
437
438
439 @Override
440 public void onDismiss(EditNameDialog dialog) {
441 if (dialog.getResult()) {
442 String newFilename = dialog.getNewFilename();
443 Log_OC.d(TAG, "name edit dialog dismissed with new name " + newFilename);
444 RemoteOperation operation = new RenameFileOperation(mTargetFile,
445 AccountUtils.getCurrentOwnCloudAccount(getActivity()),
446 newFilename,
447 mContainerActivity.getStorageManager());
448 operation.execute(AccountUtils.getCurrentOwnCloudAccount(getSherlockActivity()), getSherlockActivity(), mContainerActivity, mHandler, getSherlockActivity());
449 ((FileDisplayActivity) getActivity()).showLoadingDialog();
450 }
451 }
452
453
454 @Override
455 public void onConfirmation(String callerTag) {
456 if (callerTag.equals(FileDetailFragment.FTAG_CONFIRMATION)) {
457 if (mContainerActivity.getStorageManager().getFileById(mTargetFile.getFileId()) != null) {
458 RemoteOperation operation = new RemoveFileOperation( mTargetFile,
459 true,
460 mContainerActivity.getStorageManager());
461 operation.execute(AccountUtils.getCurrentOwnCloudAccount(getSherlockActivity()), getSherlockActivity(), mContainerActivity, mHandler, getSherlockActivity());
462
463 ((FileDisplayActivity) getActivity()).showLoadingDialog();
464 }
465 }
466 }
467
468 @Override
469 public void onNeutral(String callerTag) {
470 File f = null;
471 if (mTargetFile.isDirectory()) {
472 // TODO run in a secondary thread?
473 mContainerActivity.getStorageManager().removeDirectory(mTargetFile, false, true);
474
475 } else if (mTargetFile.isDown() && (f = new File(mTargetFile.getStoragePath())).exists()) {
476 f.delete();
477 mTargetFile.setStoragePath(null);
478 mContainerActivity.getStorageManager().saveFile(mTargetFile);
479 }
480 listDirectory();
481 mContainerActivity.onTransferStateChanged(mTargetFile, false, false);
482 }
483
484 @Override
485 public void onCancel(String callerTag) {
486 Log_OC.d(TAG, "REMOVAL CANCELED");
487 }
488
489
490 }