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