Merge branch 'pr/311' into pull_request_311_with_develop
[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 import android.accounts.Account;
46 import android.app.Activity;
47 import android.content.Intent;
48 import android.net.Uri;
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 * Tries to move up the current folder one level. If the parent folder was removed from the database,
128 * it continues browsing up until finding an existing folders.
129 *
130 * return Count of folder levels browsed up.
131 */
132 public int onBrowseUp() {
133 OCFile parentDir = null;
134 int moveCount = 0;
135
136 if(mFile != null){
137 FileDataStorageManager storageManager = mContainerActivity.getStorageManager();
138
139 String parentPath = null;
140 if (mFile.getParentId() != FileDataStorageManager.ROOT_PARENT_ID) {
141 parentPath = new File(mFile.getRemotePath()).getParent();
142 parentPath = parentPath.endsWith(OCFile.PATH_SEPARATOR) ? parentPath : parentPath + OCFile.PATH_SEPARATOR;
143 parentDir = storageManager.getFileByPath(parentPath);
144 moveCount++;
145 } else {
146 parentDir = storageManager.getFileByPath(OCFile.ROOT_PATH); // never returns null; keep the path in root folder
147 }
148 while (parentDir == null) {
149 parentPath = new File(parentPath).getParent();
150 parentPath = parentPath.endsWith(OCFile.PATH_SEPARATOR) ? parentPath : parentPath + OCFile.PATH_SEPARATOR;
151 parentDir = storageManager.getFileByPath(parentPath);
152 moveCount++;
153 } // exit is granted because storageManager.getFileByPath("/") never returns null
154 mFile = parentDir;
155 }
156
157 if (mFile != null) {
158 listDirectory(mFile);
159
160 mContainerActivity.startSyncFolderOperation(mFile);
161 } // else - should never happen now
162
163 return moveCount;
164 }
165
166 @Override
167 public void onItemClick(AdapterView<?> l, View v, int position, long id) {
168 OCFile file = (OCFile) mAdapter.getItem(position);
169 if (file != null) {
170 if (file.isFolder()) {
171 // update state and view of this fragment
172 listDirectory(file);
173 // then, notify parent activity to let it update its state and view, and other fragments
174 mContainerActivity.onBrowsedDownTo(file);
175
176 } else { /// Click on a file
177 if (PreviewImageFragment.canBePreviewed(file)) {
178 // preview image - it handles the download, if needed
179 mContainerActivity.startImagePreview(file);
180
181 } else if (file.isDown()) {
182 if (PreviewMediaFragment.canBePreviewed(file)) {
183 // media preview
184 mContainerActivity.startMediaPreview(file, 0, true);
185 } else {
186 FileDisplayActivity activity = (FileDisplayActivity) getSherlockActivity();
187 activity.getFileOperationsHelper().openFile(file, activity);
188 }
189
190 } else {
191 // automatic download, preview on finish
192 mContainerActivity.startDownloadForPreview(file);
193 }
194
195 }
196
197 } else {
198 Log_OC.d(TAG, "Null object in ListAdapter!!");
199 }
200
201 }
202
203 /**
204 * {@inheritDoc}
205 */
206 @Override
207 public void onCreateContextMenu (ContextMenu menu, View v, ContextMenu.ContextMenuInfo menuInfo) {
208 super.onCreateContextMenu(menu, v, menuInfo);
209 MenuInflater inflater = getActivity().getMenuInflater();
210 inflater.inflate(R.menu.file_actions_menu, menu);
211 AdapterContextMenuInfo info = (AdapterContextMenuInfo) menuInfo;
212 OCFile targetFile = (OCFile) mAdapter.getItem(info.position);
213 List<Integer> toHide = new ArrayList<Integer>();
214 List<Integer> toDisable = new ArrayList<Integer>();
215
216 MenuItem item = null;
217 if (targetFile.isFolder()) {
218 // contextual menu for folders
219 toHide.add(R.id.action_open_file_with);
220 toHide.add(R.id.action_download_file);
221 toHide.add(R.id.action_cancel_download);
222 toHide.add(R.id.action_cancel_upload);
223 toHide.add(R.id.action_sync_file);
224 toHide.add(R.id.action_see_details);
225 toHide.add(R.id.action_share_file);
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_share_file: {
289 FileDisplayActivity activity = (FileDisplayActivity) getSherlockActivity();
290 activity.getFileOperationsHelper().shareFileWithLink(mTargetFile, activity);
291 return true;
292 }
293 case R.id.action_rename_file: {
294 String fileName = mTargetFile.getFileName();
295 int extensionStart = mTargetFile.isFolder() ? -1 : fileName.lastIndexOf(".");
296 int selectionEnd = (extensionStart >= 0) ? extensionStart : fileName.length();
297 EditNameDialog dialog = EditNameDialog.newInstance(getString(R.string.rename_dialog_title), fileName, 0, selectionEnd, this);
298 dialog.show(getFragmentManager(), EditNameDialog.TAG);
299 return true;
300 }
301 case R.id.action_remove_file: {
302 int messageStringId = R.string.confirmation_remove_alert;
303 int posBtnStringId = R.string.confirmation_remove_remote;
304 int neuBtnStringId = -1;
305 if (mTargetFile.isFolder()) {
306 messageStringId = R.string.confirmation_remove_folder_alert;
307 posBtnStringId = R.string.confirmation_remove_remote_and_local;
308 neuBtnStringId = R.string.confirmation_remove_folder_local;
309 } else if (mTargetFile.isDown()) {
310 posBtnStringId = R.string.confirmation_remove_remote_and_local;
311 neuBtnStringId = R.string.confirmation_remove_local;
312 }
313 ConfirmationDialogFragment confDialog = ConfirmationDialogFragment.newInstance(
314 messageStringId,
315 new String[]{mTargetFile.getFileName()},
316 posBtnStringId,
317 neuBtnStringId,
318 R.string.common_cancel);
319 confDialog.setOnConfirmationListener(this);
320 confDialog.show(getFragmentManager(), FileDetailFragment.FTAG_CONFIRMATION);
321 return true;
322 }
323 case R.id.action_sync_file: {
324 Account account = AccountUtils.getCurrentOwnCloudAccount(getSherlockActivity());
325 RemoteOperation operation = new SynchronizeFileOperation(mTargetFile, null, mContainerActivity.getStorageManager(), account, true, getSherlockActivity());
326 operation.execute(account, getSherlockActivity(), mContainerActivity, mHandler, getSherlockActivity());
327 ((FileDisplayActivity) getSherlockActivity()).showLoadingDialog();
328 return true;
329 }
330 case R.id.action_cancel_download: {
331 FileDownloaderBinder downloaderBinder = mContainerActivity.getFileDownloaderBinder();
332 Account account = AccountUtils.getCurrentOwnCloudAccount(getActivity());
333 if (downloaderBinder != null && downloaderBinder.isDownloading(account, mTargetFile)) {
334 downloaderBinder.cancel(account, mTargetFile);
335 listDirectory();
336 mContainerActivity.onTransferStateChanged(mTargetFile, false, false);
337 }
338 return true;
339 }
340 case R.id.action_cancel_upload: {
341 FileUploaderBinder uploaderBinder = mContainerActivity.getFileUploaderBinder();
342 Account account = AccountUtils.getCurrentOwnCloudAccount(getActivity());
343 if (uploaderBinder != null && uploaderBinder.isUploading(account, mTargetFile)) {
344 uploaderBinder.cancel(account, mTargetFile);
345 listDirectory();
346 mContainerActivity.onTransferStateChanged(mTargetFile, false, false);
347 }
348 return true;
349 }
350 case R.id.action_see_details: {
351 ((FileFragment.ContainerActivity)getActivity()).showDetails(mTargetFile);
352 return true;
353 }
354 case R.id.action_share_file: {
355 Intent sharingIntent = new Intent(android.content.Intent.ACTION_SEND);
356 // set MimeType
357 sharingIntent.setType(mTargetFile.getMimetype());
358 sharingIntent.putExtra(Intent.EXTRA_STREAM, Uri.parse("file://"+mTargetFile.getStoragePath()));
359 startActivity(Intent.createChooser(sharingIntent, "Share via"));
360 return true;
361 }
362 default:
363 return super.onContextItemSelected(item);
364 }
365 }
366
367
368 /**
369 * Use this to query the {@link OCFile} that is currently
370 * being displayed by this fragment
371 * @return The currently viewed OCFile
372 */
373 public OCFile getCurrentFile(){
374 return mFile;
375 }
376
377 /**
378 * Calls {@link OCFileListFragment#listDirectory(OCFile)} with a null parameter
379 */
380 public void listDirectory(){
381 listDirectory(null);
382 }
383
384 /**
385 * Lists the given directory on the view. When the input parameter is null,
386 * it will either refresh the last known directory. list the root
387 * if there never was a directory.
388 *
389 * @param directory File to be listed
390 */
391 public void listDirectory(OCFile directory) {
392 FileDataStorageManager storageManager = mContainerActivity.getStorageManager();
393 if (storageManager != null) {
394
395 // Check input parameters for null
396 if(directory == null){
397 if(mFile != null){
398 directory = mFile;
399 } else {
400 directory = storageManager.getFileByPath("/");
401 if (directory == null) return; // no files, wait for sync
402 }
403 }
404
405
406 // If that's not a directory -> List its parent
407 if(!directory.isFolder()){
408 Log_OC.w(TAG, "You see, that is not a directory -> " + directory.toString());
409 directory = storageManager.getFileById(directory.getParentId());
410 }
411
412 mAdapter.swapDirectory(directory, storageManager);
413 if (mFile == null || !mFile.equals(directory)) {
414 mList.setSelectionFromTop(0, 0);
415 }
416 mFile = directory;
417 }
418 }
419
420
421
422 /**
423 * Interface to implement by any Activity that includes some instance of FileListFragment
424 *
425 * @author David A. Velasco
426 */
427 public interface ContainerActivity extends TransferServiceGetter, OnRemoteOperationListener {
428
429 /**
430 * Callback method invoked when a the user browsed into a different folder through the list of files
431 *
432 * @param file
433 */
434 public void onBrowsedDownTo(OCFile folder);
435
436 public void startDownloadForPreview(OCFile file);
437
438 public void startMediaPreview(OCFile file, int i, boolean b);
439
440 public void startImagePreview(OCFile file);
441
442 public void startSyncFolderOperation(OCFile folder);
443
444 /**
445 * Getter for the current DataStorageManager in the container activity
446 */
447 public FileDataStorageManager getStorageManager();
448
449
450 /**
451 * Callback method invoked when a the 'transfer state' of a file changes.
452 *
453 * This happens when a download or upload is started or ended for a file.
454 *
455 * This method is necessary by now to update the user interface of the double-pane layout in tablets
456 * because methods {@link FileDownloaderBinder#isDownloading(Account, OCFile)} and {@link FileUploaderBinder#isUploading(Account, OCFile)}
457 * won't provide the needed response before the method where this is called finishes.
458 *
459 * TODO Remove this when the transfer state of a file is kept in the database (other thing TODO)
460 *
461 * @param file OCFile which state changed.
462 * @param downloading Flag signaling if the file is now downloading.
463 * @param uploading Flag signaling if the file is now uploading.
464 */
465 public void onTransferStateChanged(OCFile file, boolean downloading, boolean uploading);
466
467 }
468
469
470 @Override
471 public void onDismiss(EditNameDialog dialog) {
472 if (dialog.getResult()) {
473 String newFilename = dialog.getNewFilename();
474 Log_OC.d(TAG, "name edit dialog dismissed with new name " + newFilename);
475 RemoteOperation operation = new RenameFileOperation(mTargetFile,
476 AccountUtils.getCurrentOwnCloudAccount(getActivity()),
477 newFilename,
478 mContainerActivity.getStorageManager());
479 operation.execute(AccountUtils.getCurrentOwnCloudAccount(getSherlockActivity()), getSherlockActivity(), mContainerActivity, mHandler, getSherlockActivity());
480 ((FileDisplayActivity) getActivity()).showLoadingDialog();
481 }
482 }
483
484
485 @Override
486 public void onConfirmation(String callerTag) {
487 if (callerTag.equals(FileDetailFragment.FTAG_CONFIRMATION)) {
488 if (mContainerActivity.getStorageManager().getFileById(mTargetFile.getFileId()) != null) {
489 RemoteOperation operation = new RemoveFileOperation( mTargetFile,
490 true,
491 mContainerActivity.getStorageManager());
492 operation.execute(AccountUtils.getCurrentOwnCloudAccount(getSherlockActivity()), getSherlockActivity(), mContainerActivity, mHandler, getSherlockActivity());
493
494 ((FileDisplayActivity) getActivity()).showLoadingDialog();
495 }
496 }
497 }
498
499 @Override
500 public void onNeutral(String callerTag) {
501 mContainerActivity.getStorageManager().removeFile(mTargetFile, false, true); // TODO perform in background task / new thread
502 listDirectory();
503 mContainerActivity.onTransferStateChanged(mTargetFile, false, false);
504 }
505
506 @Override
507 public void onCancel(String callerTag) {
508 Log_OC.d(TAG, "REMOVAL CANCELED");
509 }
510
511
512 }