Added support for sharing files:
[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.FileHandler;
29 import com.owncloud.android.files.services.FileDownloader.FileDownloaderBinder;
30 import com.owncloud.android.files.services.FileUploader.FileUploaderBinder;
31 import com.owncloud.android.oc_framework.operations.OnRemoteOperationListener;
32 import com.owncloud.android.oc_framework.operations.RemoteOperation;
33 import com.owncloud.android.operations.RemoveFileOperation;
34 import com.owncloud.android.operations.RenameFileOperation;
35 import com.owncloud.android.operations.SynchronizeFileOperation;
36 import com.owncloud.android.ui.activity.FileDisplayActivity;
37 import com.owncloud.android.ui.activity.TransferServiceGetter;
38 import com.owncloud.android.ui.adapter.FileListListAdapter;
39 import com.owncloud.android.ui.dialog.EditNameDialog;
40 import com.owncloud.android.ui.dialog.EditNameDialog.EditNameDialogListener;
41 import com.owncloud.android.ui.fragment.ConfirmationDialogFragment.ConfirmationDialogFragmentListener;
42 import com.owncloud.android.ui.preview.PreviewImageFragment;
43 import com.owncloud.android.ui.preview.PreviewMediaFragment;
44 import com.owncloud.android.utils.Log_OC;
45
46 import android.accounts.Account;
47 import android.app.Activity;
48 import android.content.Intent;
49 import android.net.Uri;
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 toHide.add(R.id.action_share_file);
227 if ( mContainerActivity.getFileDownloaderBinder().isDownloading(AccountUtils.getCurrentOwnCloudAccount(getActivity()), targetFile) ||
228 mContainerActivity.getFileUploaderBinder().isUploading(AccountUtils.getCurrentOwnCloudAccount(getActivity()), targetFile) ) {
229 toDisable.add(R.id.action_rename_file);
230 toDisable.add(R.id.action_remove_file);
231
232 }
233
234 } else {
235 // contextual menu for regular files
236
237 // new design: 'download' and 'open with' won't be available anymore in context menu
238 toHide.add(R.id.action_download_file);
239 toHide.add(R.id.action_open_file_with);
240
241 if (targetFile.isDown()) {
242 toHide.add(R.id.action_cancel_download);
243 toHide.add(R.id.action_cancel_upload);
244
245 } else {
246 toHide.add(R.id.action_sync_file);
247 }
248 if ( mContainerActivity.getFileDownloaderBinder().isDownloading(AccountUtils.getCurrentOwnCloudAccount(getActivity()), targetFile)) {
249 toHide.add(R.id.action_cancel_upload);
250 toDisable.add(R.id.action_rename_file);
251 toDisable.add(R.id.action_remove_file);
252
253 } else if ( mContainerActivity.getFileUploaderBinder().isUploading(AccountUtils.getCurrentOwnCloudAccount(getActivity()), targetFile)) {
254 toHide.add(R.id.action_cancel_download);
255 toDisable.add(R.id.action_rename_file);
256 toDisable.add(R.id.action_remove_file);
257
258 } else {
259 toHide.add(R.id.action_cancel_download);
260 toHide.add(R.id.action_cancel_upload);
261 }
262 }
263
264 for (int i : toHide) {
265 item = menu.findItem(i);
266 if (item != null) {
267 item.setVisible(false);
268 item.setEnabled(false);
269 }
270 }
271
272 for (int i : toDisable) {
273 item = menu.findItem(i);
274 if (item != null) {
275 item.setEnabled(false);
276 }
277 }
278 }
279
280
281 /**
282 * {@inhericDoc}
283 */
284 @Override
285 public boolean onContextItemSelected (MenuItem item) {
286 AdapterContextMenuInfo info = (AdapterContextMenuInfo) item.getMenuInfo();
287 mTargetFile = (OCFile) mAdapter.getItem(info.position);
288 switch (item.getItemId()) {
289 case R.id.action_rename_file: {
290 String fileName = mTargetFile.getFileName();
291 int extensionStart = mTargetFile.isFolder() ? -1 : fileName.lastIndexOf(".");
292 int selectionEnd = (extensionStart >= 0) ? extensionStart : fileName.length();
293 EditNameDialog dialog = EditNameDialog.newInstance(getString(R.string.rename_dialog_title), fileName, 0, selectionEnd, this);
294 dialog.show(getFragmentManager(), EditNameDialog.TAG);
295 return true;
296 }
297 case R.id.action_remove_file: {
298 int messageStringId = R.string.confirmation_remove_alert;
299 int posBtnStringId = R.string.confirmation_remove_remote;
300 int neuBtnStringId = -1;
301 if (mTargetFile.isFolder()) {
302 messageStringId = R.string.confirmation_remove_folder_alert;
303 posBtnStringId = R.string.confirmation_remove_remote_and_local;
304 neuBtnStringId = R.string.confirmation_remove_folder_local;
305 } else if (mTargetFile.isDown()) {
306 posBtnStringId = R.string.confirmation_remove_remote_and_local;
307 neuBtnStringId = R.string.confirmation_remove_local;
308 }
309 ConfirmationDialogFragment confDialog = ConfirmationDialogFragment.newInstance(
310 messageStringId,
311 new String[]{mTargetFile.getFileName()},
312 posBtnStringId,
313 neuBtnStringId,
314 R.string.common_cancel);
315 confDialog.setOnConfirmationListener(this);
316 confDialog.show(getFragmentManager(), FileDetailFragment.FTAG_CONFIRMATION);
317 return true;
318 }
319 case R.id.action_sync_file: {
320 Account account = AccountUtils.getCurrentOwnCloudAccount(getSherlockActivity());
321 RemoteOperation operation = new SynchronizeFileOperation(mTargetFile, null, mContainerActivity.getStorageManager(), account, true, getSherlockActivity());
322 operation.execute(account, getSherlockActivity(), mContainerActivity, mHandler, getSherlockActivity());
323 ((FileDisplayActivity) getSherlockActivity()).showLoadingDialog();
324 return true;
325 }
326 case R.id.action_cancel_download: {
327 FileDownloaderBinder downloaderBinder = mContainerActivity.getFileDownloaderBinder();
328 Account account = AccountUtils.getCurrentOwnCloudAccount(getActivity());
329 if (downloaderBinder != null && downloaderBinder.isDownloading(account, mTargetFile)) {
330 downloaderBinder.cancel(account, mTargetFile);
331 listDirectory();
332 mContainerActivity.onTransferStateChanged(mTargetFile, false, false);
333 }
334 return true;
335 }
336 case R.id.action_cancel_upload: {
337 FileUploaderBinder uploaderBinder = mContainerActivity.getFileUploaderBinder();
338 Account account = AccountUtils.getCurrentOwnCloudAccount(getActivity());
339 if (uploaderBinder != null && uploaderBinder.isUploading(account, mTargetFile)) {
340 uploaderBinder.cancel(account, mTargetFile);
341 listDirectory();
342 mContainerActivity.onTransferStateChanged(mTargetFile, false, false);
343 }
344 return true;
345 }
346 case R.id.action_see_details: {
347 ((FileFragment.ContainerActivity)getActivity()).showDetails(mTargetFile);
348 return true;
349 }
350 case R.id.action_share_file: {
351 Intent sharingIntent = new Intent(android.content.Intent.ACTION_SEND);
352 // set MimeType
353 sharingIntent.setType(mTargetFile.getMimetype());
354 sharingIntent.putExtra(Intent.EXTRA_STREAM, Uri.parse("file://"+mTargetFile.getStoragePath()));
355 startActivity(Intent.createChooser(sharingIntent, "Share via"));
356 return true;
357 }
358 default:
359 return super.onContextItemSelected(item);
360 }
361 }
362
363
364 /**
365 * Use this to query the {@link OCFile} that is currently
366 * being displayed by this fragment
367 * @return The currently viewed OCFile
368 */
369 public OCFile getCurrentFile(){
370 return mFile;
371 }
372
373 /**
374 * Calls {@link OCFileListFragment#listDirectory(OCFile)} with a null parameter
375 */
376 public void listDirectory(){
377 listDirectory(null);
378 }
379
380 /**
381 * Lists the given directory on the view. When the input parameter is null,
382 * it will either refresh the last known directory. list the root
383 * if there never was a directory.
384 *
385 * @param directory File to be listed
386 */
387 public void listDirectory(OCFile directory) {
388 FileDataStorageManager storageManager = mContainerActivity.getStorageManager();
389 if (storageManager != null) {
390
391 // Check input parameters for null
392 if(directory == null){
393 if(mFile != null){
394 directory = mFile;
395 } else {
396 directory = storageManager.getFileByPath("/");
397 if (directory == null) return; // no files, wait for sync
398 }
399 }
400
401
402 // If that's not a directory -> List its parent
403 if(!directory.isFolder()){
404 Log_OC.w(TAG, "You see, that is not a directory -> " + directory.toString());
405 directory = storageManager.getFileById(directory.getParentId());
406 }
407
408 mAdapter.swapDirectory(directory, storageManager);
409 if (mFile == null || !mFile.equals(directory)) {
410 mList.setSelectionFromTop(0, 0);
411 }
412 mFile = directory;
413 }
414 }
415
416
417
418 /**
419 * Interface to implement by any Activity that includes some instance of FileListFragment
420 *
421 * @author David A. Velasco
422 */
423 public interface ContainerActivity extends TransferServiceGetter, OnRemoteOperationListener, FileHandler {
424
425 /**
426 * Callback method invoked when a the user browsed into a different folder through the list of files
427 *
428 * @param file
429 */
430 public void onBrowsedDownTo(OCFile folder);
431
432 public void startDownloadForPreview(OCFile file);
433
434 public void startMediaPreview(OCFile file, int i, boolean b);
435
436 public void startImagePreview(OCFile file);
437
438 public void startSyncFolderOperation(OCFile folder);
439
440 /**
441 * Getter for the current DataStorageManager in the container activity
442 */
443 public FileDataStorageManager getStorageManager();
444
445
446 /**
447 * Callback method invoked when a the 'transfer state' of a file changes.
448 *
449 * This happens when a download or upload is started or ended for a file.
450 *
451 * This method is necessary by now to update the user interface of the double-pane layout in tablets
452 * because methods {@link FileDownloaderBinder#isDownloading(Account, OCFile)} and {@link FileUploaderBinder#isUploading(Account, OCFile)}
453 * won't provide the needed response before the method where this is called finishes.
454 *
455 * TODO Remove this when the transfer state of a file is kept in the database (other thing TODO)
456 *
457 * @param file OCFile which state changed.
458 * @param downloading Flag signaling if the file is now downloading.
459 * @param uploading Flag signaling if the file is now uploading.
460 */
461 public void onTransferStateChanged(OCFile file, boolean downloading, boolean uploading);
462
463 }
464
465
466 @Override
467 public void onDismiss(EditNameDialog dialog) {
468 if (dialog.getResult()) {
469 String newFilename = dialog.getNewFilename();
470 Log_OC.d(TAG, "name edit dialog dismissed with new name " + newFilename);
471 RemoteOperation operation = new RenameFileOperation(mTargetFile,
472 AccountUtils.getCurrentOwnCloudAccount(getActivity()),
473 newFilename,
474 mContainerActivity.getStorageManager());
475 operation.execute(AccountUtils.getCurrentOwnCloudAccount(getSherlockActivity()), getSherlockActivity(), mContainerActivity, mHandler, getSherlockActivity());
476 ((FileDisplayActivity) getActivity()).showLoadingDialog();
477 }
478 }
479
480
481 @Override
482 public void onConfirmation(String callerTag) {
483 if (callerTag.equals(FileDetailFragment.FTAG_CONFIRMATION)) {
484 if (mContainerActivity.getStorageManager().getFileById(mTargetFile.getFileId()) != null) {
485 RemoteOperation operation = new RemoveFileOperation( mTargetFile,
486 true,
487 mContainerActivity.getStorageManager());
488 operation.execute(AccountUtils.getCurrentOwnCloudAccount(getSherlockActivity()), getSherlockActivity(), mContainerActivity, mHandler, getSherlockActivity());
489
490 ((FileDisplayActivity) getActivity()).showLoadingDialog();
491 }
492 }
493 }
494
495 @Override
496 public void onNeutral(String callerTag) {
497 mContainerActivity.getStorageManager().removeFile(mTargetFile, false, true); // TODO perform in background task / new thread
498 listDirectory();
499 mContainerActivity.onTransferStateChanged(mTargetFile, false, false);
500 }
501
502 @Override
503 public void onCancel(String callerTag) {
504 Log_OC.d(TAG, "REMOVAL CANCELED");
505 }
506
507
508 }