Merge remote-tracking branch 'origin/oauth_login' into oauth_login
[pub/Android/ownCloud.git] / src / com / owncloud / android / ui / fragment / OCFileListFragment.java
1 /* ownCloud Android client application
2 * Copyright (C) 2011 Bartek Przybylski
3 *
4 * This program is free software: you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License as published by
6 * the Free Software Foundation, either version 3 of the License, or
7 * (at your option) any later version.
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.AccountUtils;
25 import com.owncloud.android.R;
26 import com.owncloud.android.datamodel.DataStorageManager;
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.operations.OnRemoteOperationListener;
31 import com.owncloud.android.operations.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.FragmentListView;
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
43 import eu.alefzero.webdav.WebdavUtils;
44
45 import android.accounts.Account;
46 import android.app.Activity;
47 import android.content.ActivityNotFoundException;
48 import android.content.Intent;
49 import android.net.Uri;
50 import android.os.Bundle;
51 import android.os.Handler;
52 import android.support.v4.app.DialogFragment;
53 import android.util.Log;
54 import android.view.ContextMenu;
55 import android.view.MenuInflater;
56 import android.view.MenuItem;
57 import android.view.View;
58 import android.webkit.MimeTypeMap;
59 import android.widget.AdapterView;
60 import android.widget.Toast;
61 import android.widget.AdapterView.AdapterContextMenuInfo;
62
63 /**
64 * A Fragment that lists all files and folders in a given path.
65 *
66 * @author Bartek Przybylski
67 *
68 */
69 public class OCFileListFragment extends FragmentListView implements EditNameDialogListener, ConfirmationDialogFragmentListener {
70 private static final String TAG = "FileListFragment";
71 private static final String SAVED_LIST_POSITION = "LIST_POSITION";
72
73 private OCFileListFragment.ContainerActivity mContainerActivity;
74
75 private OCFile mFile = null;
76 private FileListListAdapter mAdapter;
77
78 private Handler mHandler;
79 private OCFile mTargetFile;
80
81 private DialogFragment mCurrentDialog;
82
83 /**
84 * {@inheritDoc}
85 */
86 @Override
87 public void onAttach(Activity activity) {
88 super.onAttach(activity);
89 try {
90 mContainerActivity = (ContainerActivity) activity;
91 } catch (ClassCastException e) {
92 throw new ClassCastException(activity.toString() + " must implement " + OCFileListFragment.ContainerActivity.class.getSimpleName());
93 }
94 }
95
96
97 /**
98 * {@inheritDoc}
99 */
100 @Override
101 public void onActivityCreated(Bundle savedInstanceState) {
102 Log.i(TAG, "onActivityCreated() start");
103
104 super.onActivityCreated(savedInstanceState);
105 mAdapter = new FileListListAdapter(mContainerActivity.getInitialDirectory(), mContainerActivity.getStorageManager(), getActivity(), mContainerActivity);
106 setListAdapter(mAdapter);
107
108 if (savedInstanceState != null) {
109 Log.i(TAG, "savedInstanceState is not null");
110 int position = savedInstanceState.getInt(SAVED_LIST_POSITION);
111 setReferencePosition(position);
112 }
113
114 registerForContextMenu(getListView());
115 getListView().setOnCreateContextMenuListener(this);
116
117 mHandler = new Handler();
118
119 Log.i(TAG, "onActivityCreated() stop");
120 }
121
122
123
124 @Override
125 public void onSaveInstanceState(Bundle savedInstanceState) {
126 Log.i(TAG, "onSaveInstanceState() start");
127
128 savedInstanceState.putInt(SAVED_LIST_POSITION, getReferencePosition());
129
130 Log.i(TAG, "onSaveInstanceState() stop");
131 }
132
133
134 @Override
135 public void onItemClick(AdapterView<?> l, View v, int position, long id) {
136 OCFile file = (OCFile) mAdapter.getItem(position);
137 if (file != null) {
138 /// Click on a directory
139 if (file.getMimetype().equals("DIR")) {
140 // just local updates
141 mFile = file;
142 listDirectory(file);
143 // any other updates are let to the container Activity
144 mContainerActivity.onDirectoryClick(file);
145
146 } else { /// Click on a file
147 mContainerActivity.onFileClick(file);
148 }
149
150 } else {
151 Log.d(TAG, "Null object in ListAdapter!!");
152 }
153
154 }
155
156 /**
157 * {@inheritDoc}
158 */
159 @Override
160 public void onCreateContextMenu (ContextMenu menu, View v, ContextMenu.ContextMenuInfo menuInfo) {
161 super.onCreateContextMenu(menu, v, menuInfo);
162 MenuInflater inflater = getActivity().getMenuInflater();
163 inflater.inflate(R.menu.file_context_menu, menu);
164 AdapterContextMenuInfo info = (AdapterContextMenuInfo) menuInfo;
165 OCFile targetFile = (OCFile) mAdapter.getItem(info.position);
166 List<Integer> toHide = new ArrayList<Integer>();
167 List<Integer> toDisable = new ArrayList<Integer>();
168
169 MenuItem item = null;
170 if (targetFile.isDirectory()) {
171 // contextual menu for folders
172 toHide.add(R.id.open_file_item);
173 toHide.add(R.id.download_file_item);
174 toHide.add(R.id.cancel_download_item);
175 toHide.add(R.id.cancel_upload_item);
176 if ( mContainerActivity.getFileDownloaderBinder().isDownloading(AccountUtils.getCurrentOwnCloudAccount(getActivity()), targetFile) ||
177 mContainerActivity.getFileUploaderBinder().isUploading(AccountUtils.getCurrentOwnCloudAccount(getActivity()), targetFile) ) {
178 toDisable.add(R.id.rename_file_item);
179 toDisable.add(R.id.remove_file_item);
180
181 }
182
183 } else {
184 // contextual menu for regular files
185 if (targetFile.isDown()) {
186 toHide.add(R.id.cancel_download_item);
187 toHide.add(R.id.cancel_upload_item);
188 item = menu.findItem(R.id.download_file_item);
189 if (item != null) {
190 item.setTitle(R.string.filedetails_sync_file);
191 }
192 } else {
193 toHide.add(R.id.open_file_item);
194 }
195 if ( mContainerActivity.getFileDownloaderBinder().isDownloading(AccountUtils.getCurrentOwnCloudAccount(getActivity()), targetFile)) {
196 toHide.add(R.id.download_file_item);
197 toHide.add(R.id.cancel_upload_item);
198 toDisable.add(R.id.open_file_item);
199 toDisable.add(R.id.rename_file_item);
200 toDisable.add(R.id.remove_file_item);
201
202 } else if ( mContainerActivity.getFileUploaderBinder().isUploading(AccountUtils.getCurrentOwnCloudAccount(getActivity()), targetFile)) {
203 toHide.add(R.id.download_file_item);
204 toHide.add(R.id.cancel_download_item);
205 toDisable.add(R.id.open_file_item);
206 toDisable.add(R.id.rename_file_item);
207 toDisable.add(R.id.remove_file_item);
208
209 } else {
210 toHide.add(R.id.cancel_download_item);
211 toHide.add(R.id.cancel_upload_item);
212 }
213 }
214
215 for (int i : toHide) {
216 item = menu.findItem(i);
217 if (item != null) {
218 item.setVisible(false);
219 item.setEnabled(false);
220 }
221 }
222
223 for (int i : toDisable) {
224 item = menu.findItem(i);
225 if (item != null) {
226 item.setEnabled(false);
227 }
228 }
229 }
230
231
232 /**
233 * {@inhericDoc}
234 */
235 @Override
236 public boolean onContextItemSelected (MenuItem item) {
237 AdapterContextMenuInfo info = (AdapterContextMenuInfo) item.getMenuInfo();
238 mTargetFile = (OCFile) mAdapter.getItem(info.position);
239 switch (item.getItemId()) {
240 case R.id.rename_file_item: {
241 EditNameDialog dialog = EditNameDialog.newInstance(getString(R.string.rename_dialog_title), mTargetFile.getFileName(), this);
242 dialog.show(getFragmentManager(), EditNameDialog.TAG);
243 return true;
244 }
245 case R.id.remove_file_item: {
246 int messageStringId = R.string.confirmation_remove_alert;
247 int posBtnStringId = R.string.confirmation_remove_remote;
248 int neuBtnStringId = -1;
249 if (mTargetFile.isDirectory()) {
250 messageStringId = R.string.confirmation_remove_folder_alert;
251 posBtnStringId = R.string.confirmation_remove_remote_and_local;
252 neuBtnStringId = R.string.confirmation_remove_folder_local;
253 } else if (mTargetFile.isDown()) {
254 posBtnStringId = R.string.confirmation_remove_remote_and_local;
255 neuBtnStringId = R.string.confirmation_remove_local;
256 }
257 ConfirmationDialogFragment confDialog = ConfirmationDialogFragment.newInstance(
258 messageStringId,
259 new String[]{mTargetFile.getFileName()},
260 posBtnStringId,
261 neuBtnStringId,
262 R.string.common_cancel);
263 confDialog.setOnConfirmationListener(this);
264 mCurrentDialog = confDialog;
265 mCurrentDialog.show(getFragmentManager(), FileDetailFragment.FTAG_CONFIRMATION);
266 return true;
267 }
268 case R.id.open_file_item: {
269 String storagePath = mTargetFile.getStoragePath();
270 String encodedStoragePath = WebdavUtils.encodePath(storagePath);
271 try {
272 Intent i = new Intent(Intent.ACTION_VIEW);
273 i.setDataAndType(Uri.parse("file://"+ encodedStoragePath), mTargetFile.getMimetype());
274 i.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION | Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
275 startActivity(i);
276
277 } catch (Throwable t) {
278 Log.e(TAG, "Fail when trying to open with the mimeType provided from the ownCloud server: " + mTargetFile.getMimetype());
279 boolean toastIt = true;
280 String mimeType = "";
281 try {
282 Intent i = new Intent(Intent.ACTION_VIEW);
283 mimeType = MimeTypeMap.getSingleton().getMimeTypeFromExtension(storagePath.substring(storagePath.lastIndexOf('.') + 1));
284 if (mimeType == null || !mimeType.equals(mTargetFile.getMimetype())) {
285 if (mimeType != null) {
286 i.setDataAndType(Uri.parse("file://"+ encodedStoragePath), mimeType);
287 } else {
288 // desperate try
289 i.setDataAndType(Uri.parse("file://"+ encodedStoragePath), "*/*");
290 }
291 i.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION | Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
292 startActivity(i);
293 toastIt = false;
294 }
295
296 } catch (IndexOutOfBoundsException e) {
297 Log.e(TAG, "Trying to find out MIME type of a file without extension: " + storagePath);
298
299 } catch (ActivityNotFoundException e) {
300 Log.e(TAG, "No activity found to handle: " + storagePath + " with MIME type " + mimeType + " obtained from extension");
301
302 } catch (Throwable th) {
303 Log.e(TAG, "Unexpected problem when opening: " + storagePath, th);
304
305 } finally {
306 if (toastIt) {
307 Toast.makeText(getActivity(), "There is no application to handle file " + mTargetFile.getFileName(), Toast.LENGTH_SHORT).show();
308 }
309 }
310
311 }
312 return true;
313 }
314 case R.id.download_file_item: {
315 Account account = AccountUtils.getCurrentOwnCloudAccount(getSherlockActivity());
316 RemoteOperation operation = new SynchronizeFileOperation(mTargetFile, null, mContainerActivity.getStorageManager(), account, true, false, getSherlockActivity());
317 operation.execute(account, getSherlockActivity(), mContainerActivity, mHandler, getSherlockActivity());
318 getSherlockActivity().showDialog(FileDisplayActivity.DIALOG_SHORT_WAIT);
319 return true;
320 }
321 case R.id.cancel_download_item: {
322 FileDownloaderBinder downloaderBinder = mContainerActivity.getFileDownloaderBinder();
323 Account account = AccountUtils.getCurrentOwnCloudAccount(getActivity());
324 if (downloaderBinder != null && downloaderBinder.isDownloading(account, mTargetFile)) {
325 downloaderBinder.cancel(account, mTargetFile);
326 listDirectory();
327 mContainerActivity.onTransferStateChanged(mTargetFile, false, false);
328 }
329 return true;
330 }
331 case R.id.cancel_upload_item: {
332 FileUploaderBinder uploaderBinder = mContainerActivity.getFileUploaderBinder();
333 Account account = AccountUtils.getCurrentOwnCloudAccount(getActivity());
334 if (uploaderBinder != null && uploaderBinder.isUploading(account, mTargetFile)) {
335 uploaderBinder.cancel(account, mTargetFile);
336 listDirectory();
337 mContainerActivity.onTransferStateChanged(mTargetFile, false, false);
338 }
339 return true;
340 }
341 default:
342 return super.onContextItemSelected(item);
343 }
344 }
345
346
347 /**
348 * Call this, when the user presses the up button
349 */
350 public void onNavigateUp() {
351 OCFile parentDir = null;
352
353 if(mFile != null){
354 DataStorageManager storageManager = mContainerActivity.getStorageManager();
355 parentDir = storageManager.getFileById(mFile.getParentId());
356 mFile = parentDir;
357 }
358 listDirectory(parentDir);
359 }
360
361 /**
362 * Use this to query the {@link OCFile} that is currently
363 * being displayed by this fragment
364 * @return The currently viewed OCFile
365 */
366 public OCFile getCurrentFile(){
367 return mFile;
368 }
369
370 /**
371 * Calls {@link OCFileListFragment#listDirectory(OCFile)} with a null parameter
372 */
373 public void listDirectory(){
374 listDirectory(null);
375 }
376
377 /**
378 * Lists the given directory on the view. When the input parameter is null,
379 * it will either refresh the last known directory, or list the root
380 * if there never was a directory.
381 *
382 * @param directory File to be listed
383 */
384 public void listDirectory(OCFile directory) {
385 DataStorageManager storageManager = mContainerActivity.getStorageManager();
386 if (storageManager != null) {
387
388 // Check input parameters for null
389 if(directory == null){
390 if(mFile != null){
391 directory = mFile;
392 } else {
393 directory = storageManager.getFileByPath("/");
394 if (directory == null) return; // no files, wait for sync
395 }
396 }
397
398
399 // If that's not a directory -> List its parent
400 if(!directory.isDirectory()){
401 Log.w(TAG, "You see, that is not a directory -> " + directory.toString());
402 directory = storageManager.getFileById(directory.getParentId());
403 }
404
405 mAdapter.swapDirectory(directory, storageManager);
406 if (mFile == null || !mFile.equals(directory)) {
407 mList.setSelectionFromTop(0, 0);
408 }
409 mFile = directory;
410 }
411 }
412
413
414
415 /**
416 * Interface to implement by any Activity that includes some instance of FileListFragment
417 *
418 * @author David A. Velasco
419 */
420 public interface ContainerActivity extends TransferServiceGetter, OnRemoteOperationListener {
421
422 /**
423 * Callback method invoked when a directory is clicked by the user on the files list
424 *
425 * @param file
426 */
427 public void onDirectoryClick(OCFile file);
428
429 /**
430 * Callback method invoked when a file (non directory) is clicked by the user on the files list
431 *
432 * @param file
433 */
434 public void onFileClick(OCFile file);
435
436 /**
437 * Getter for the current DataStorageManager in the container activity
438 */
439 public DataStorageManager getStorageManager();
440
441
442 /**
443 * Callback method invoked when the parent activity is fully created to get the directory to list firstly.
444 *
445 * @return Directory to list firstly. Can be NULL.
446 */
447 public OCFile getInitialDirectory();
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.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 getActivity().showDialog(FileDisplayActivity.DIALOG_SHORT_WAIT);
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 getActivity().showDialog(FileDisplayActivity.DIALOG_SHORT_WAIT);
495 }
496 if (mCurrentDialog != null) {
497 mCurrentDialog.dismiss();
498 mCurrentDialog = null;
499 }
500 }
501 }
502
503 @Override
504 public void onNeutral(String callerTag) {
505 File f = null;
506 if (mTargetFile.isDirectory()) {
507 // TODO run in a secondary thread?
508 mContainerActivity.getStorageManager().removeDirectory(mTargetFile, false, true);
509
510 } else if (mTargetFile.isDown() && (f = new File(mTargetFile.getStoragePath())).exists()) {
511 f.delete();
512 mTargetFile.setStoragePath(null);
513 mContainerActivity.getStorageManager().saveFile(mTargetFile);
514 }
515 if (mCurrentDialog != null) {
516 mCurrentDialog.dismiss();
517 mCurrentDialog = null;
518 }
519 listDirectory();
520 mContainerActivity.onTransferStateChanged(mTargetFile, false, false);
521 }
522
523 @Override
524 public void onCancel(String callerTag) {
525 Log.d(TAG, "REMOVAL CANCELED");
526 if (mCurrentDialog != null) {
527 mCurrentDialog.dismiss();
528 mCurrentDialog = null;
529 }
530 }
531
532
533 }