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