Fixed NULL pointers and wrong handling of dialogs
[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.network.OwnCloudClientUtils;
31 import com.owncloud.android.operations.OnRemoteOperationListener;
32 import com.owncloud.android.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.FragmentListView;
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
44 import eu.alefzero.webdav.WebdavClient;
45 import eu.alefzero.webdav.WebdavUtils;
46
47 import android.accounts.Account;
48 import android.app.Activity;
49 import android.content.ActivityNotFoundException;
50 import android.content.Intent;
51 import android.net.Uri;
52 import android.os.Bundle;
53 import android.os.Handler;
54 import android.support.v4.app.DialogFragment;
55 import android.util.Log;
56 import android.view.ContextMenu;
57 import android.view.MenuInflater;
58 import android.view.MenuItem;
59 import android.view.View;
60 import android.webkit.MimeTypeMap;
61 import android.widget.AdapterView;
62 import android.widget.Toast;
63 import android.widget.AdapterView.AdapterContextMenuInfo;
64
65 /**
66 * A Fragment that lists all files and folders in a given path.
67 *
68 * @author Bartek Przybylski
69 *
70 */
71 public class OCFileListFragment extends FragmentListView implements EditNameDialogListener, ConfirmationDialogFragmentListener {
72 private static final String TAG = "FileListFragment";
73 private static final String SAVED_LIST_POSITION = "LIST_POSITION";
74
75 private OCFileListFragment.ContainerActivity mContainerActivity;
76
77 private OCFile mFile = null;
78 private FileListListAdapter mAdapter;
79
80 private Handler mHandler;
81 private OCFile mTargetFile;
82
83 private DialogFragment mCurrentDialog;
84
85 /**
86 * {@inheritDoc}
87 */
88 @Override
89 public void onAttach(Activity activity) {
90 super.onAttach(activity);
91 try {
92 mContainerActivity = (ContainerActivity) activity;
93 } catch (ClassCastException e) {
94 throw new ClassCastException(activity.toString() + " must implement " + OCFileListFragment.ContainerActivity.class.getSimpleName());
95 }
96 }
97
98
99 /**
100 * {@inheritDoc}
101 */
102 @Override
103 public void onActivityCreated(Bundle savedInstanceState) {
104 Log.i(TAG, "onActivityCreated() start");
105
106 super.onActivityCreated(savedInstanceState);
107 mAdapter = new FileListListAdapter(mContainerActivity.getInitialDirectory(), mContainerActivity.getStorageManager(), getActivity(), mContainerActivity);
108 setListAdapter(mAdapter);
109
110 if (savedInstanceState != null) {
111 Log.i(TAG, "savedInstanceState is not null");
112 int position = savedInstanceState.getInt(SAVED_LIST_POSITION);
113 setReferencePosition(position);
114 }
115
116 registerForContextMenu(getListView());
117 getListView().setOnCreateContextMenuListener(this);
118
119 mHandler = new Handler();
120
121 Log.i(TAG, "onActivityCreated() stop");
122 }
123
124
125
126 @Override
127 public void onSaveInstanceState(Bundle savedInstanceState) {
128 Log.i(TAG, "onSaveInstanceState() start");
129
130 savedInstanceState.putInt(SAVED_LIST_POSITION, getReferencePosition());
131
132 Log.i(TAG, "onSaveInstanceState() stop");
133 }
134
135
136 @Override
137 public void onItemClick(AdapterView<?> l, View v, int position, long id) {
138 OCFile file = (OCFile) mAdapter.getItem(position);
139 if (file != null) {
140 /// Click on a directory
141 if (file.getMimetype().equals("DIR")) {
142 // just local updates
143 mFile = file;
144 listDirectory(file);
145 // any other updates are let to the container Activity
146 mContainerActivity.onDirectoryClick(file);
147
148 } else { /// Click on a file
149 mContainerActivity.onFileClick(file);
150 }
151
152 } else {
153 Log.d(TAG, "Null object in ListAdapter!!");
154 }
155
156 }
157
158 /**
159 * {@inheritDoc}
160 */
161 @Override
162 public void onCreateContextMenu (ContextMenu menu, View v, ContextMenu.ContextMenuInfo menuInfo) {
163 super.onCreateContextMenu(menu, v, menuInfo);
164 MenuInflater inflater = getActivity().getMenuInflater();
165 inflater.inflate(R.menu.file_context_menu, menu);
166 AdapterContextMenuInfo info = (AdapterContextMenuInfo) menuInfo;
167 OCFile targetFile = (OCFile) mAdapter.getItem(info.position);
168 List<Integer> toHide = new ArrayList<Integer>();
169 List<Integer> toDisable = new ArrayList<Integer>();
170
171 MenuItem item = null;
172 if (targetFile.isDirectory()) {
173 // contextual menu for folders
174 toHide.add(R.id.open_file_item);
175 toHide.add(R.id.download_file_item);
176 toHide.add(R.id.cancel_download_item);
177 toHide.add(R.id.cancel_upload_item);
178 if ( mContainerActivity.getFileDownloaderBinder().isDownloading(AccountUtils.getCurrentOwnCloudAccount(getActivity()), targetFile) ||
179 mContainerActivity.getFileUploaderBinder().isUploading(AccountUtils.getCurrentOwnCloudAccount(getActivity()), targetFile) ) {
180 toDisable.add(R.id.rename_file_item);
181 toDisable.add(R.id.remove_file_item);
182
183 }
184
185 } else {
186 // contextual menu for regular files
187 if (targetFile.isDown()) {
188 toHide.add(R.id.cancel_download_item);
189 toHide.add(R.id.cancel_upload_item);
190 item = menu.findItem(R.id.download_file_item);
191 if (item != null) {
192 item.setTitle(R.string.filedetails_sync_file);
193 }
194 } else {
195 toHide.add(R.id.open_file_item);
196 }
197 if ( mContainerActivity.getFileDownloaderBinder().isDownloading(AccountUtils.getCurrentOwnCloudAccount(getActivity()), targetFile)) {
198 toHide.add(R.id.download_file_item);
199 toHide.add(R.id.cancel_upload_item);
200 toDisable.add(R.id.open_file_item);
201 toDisable.add(R.id.rename_file_item);
202 toDisable.add(R.id.remove_file_item);
203
204 } else if ( mContainerActivity.getFileUploaderBinder().isUploading(AccountUtils.getCurrentOwnCloudAccount(getActivity()), targetFile)) {
205 toHide.add(R.id.download_file_item);
206 toHide.add(R.id.cancel_download_item);
207 toDisable.add(R.id.open_file_item);
208 toDisable.add(R.id.rename_file_item);
209 toDisable.add(R.id.remove_file_item);
210
211 } else {
212 toHide.add(R.id.cancel_download_item);
213 toHide.add(R.id.cancel_upload_item);
214 }
215 }
216
217 for (int i : toHide) {
218 item = menu.findItem(i);
219 if (item != null) {
220 item.setVisible(false);
221 item.setEnabled(false);
222 }
223 }
224
225 for (int i : toDisable) {
226 item = menu.findItem(i);
227 if (item != null) {
228 item.setEnabled(false);
229 }
230 }
231 }
232
233
234 /**
235 * {@inhericDoc}
236 */
237 @Override
238 public boolean onContextItemSelected (MenuItem item) {
239 AdapterContextMenuInfo info = (AdapterContextMenuInfo) item.getMenuInfo();
240 mTargetFile = (OCFile) mAdapter.getItem(info.position);
241 switch (item.getItemId()) {
242 case R.id.rename_file_item: {
243 EditNameDialog dialog = EditNameDialog.newInstance(getString(R.string.rename_dialog_title), mTargetFile.getFileName(), this);
244 dialog.show(getFragmentManager(), EditNameDialog.TAG);
245 return true;
246 }
247 case R.id.remove_file_item: {
248 int messageStringId = R.string.confirmation_remove_alert;
249 int posBtnStringId = R.string.confirmation_remove_remote;
250 int neuBtnStringId = -1;
251 if (mTargetFile.isDirectory()) {
252 messageStringId = R.string.confirmation_remove_folder_alert;
253 posBtnStringId = R.string.confirmation_remove_remote_and_local;
254 neuBtnStringId = R.string.confirmation_remove_folder_local;
255 } else if (mTargetFile.isDown()) {
256 posBtnStringId = R.string.confirmation_remove_remote_and_local;
257 neuBtnStringId = R.string.confirmation_remove_local;
258 }
259 ConfirmationDialogFragment confDialog = ConfirmationDialogFragment.newInstance(
260 messageStringId,
261 new String[]{mTargetFile.getFileName()},
262 posBtnStringId,
263 neuBtnStringId,
264 R.string.common_cancel);
265 confDialog.setOnConfirmationListener(this);
266 mCurrentDialog = confDialog;
267 mCurrentDialog.show(getFragmentManager(), FileDetailFragment.FTAG_CONFIRMATION);
268 return true;
269 }
270 case R.id.open_file_item: {
271 String storagePath = mTargetFile.getStoragePath();
272 String encodedStoragePath = WebdavUtils.encodePath(storagePath);
273 try {
274 Intent i = new Intent(Intent.ACTION_VIEW);
275 i.setDataAndType(Uri.parse("file://"+ encodedStoragePath), mTargetFile.getMimetype());
276 i.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION | Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
277 startActivity(i);
278
279 } catch (Throwable t) {
280 Log.e(TAG, "Fail when trying to open with the mimeType provided from the ownCloud server: " + mTargetFile.getMimetype());
281 boolean toastIt = true;
282 String mimeType = "";
283 try {
284 Intent i = new Intent(Intent.ACTION_VIEW);
285 mimeType = MimeTypeMap.getSingleton().getMimeTypeFromExtension(storagePath.substring(storagePath.lastIndexOf('.') + 1));
286 if (mimeType == null || !mimeType.equals(mTargetFile.getMimetype())) {
287 if (mimeType != null) {
288 i.setDataAndType(Uri.parse("file://"+ encodedStoragePath), mimeType);
289 } else {
290 // desperate try
291 i.setDataAndType(Uri.parse("file://"+ encodedStoragePath), "*/*");
292 }
293 i.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION | Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
294 startActivity(i);
295 toastIt = false;
296 }
297
298 } catch (IndexOutOfBoundsException e) {
299 Log.e(TAG, "Trying to find out MIME type of a file without extension: " + storagePath);
300
301 } catch (ActivityNotFoundException e) {
302 Log.e(TAG, "No activity found to handle: " + storagePath + " with MIME type " + mimeType + " obtained from extension");
303
304 } catch (Throwable th) {
305 Log.e(TAG, "Unexpected problem when opening: " + storagePath, th);
306
307 } finally {
308 if (toastIt) {
309 Toast.makeText(getActivity(), "There is no application to handle file " + mTargetFile.getFileName(), Toast.LENGTH_SHORT).show();
310 }
311 }
312
313 }
314 return true;
315 }
316 case R.id.download_file_item: {
317 Account account = AccountUtils.getCurrentOwnCloudAccount(getSherlockActivity());
318 RemoteOperation operation = new SynchronizeFileOperation(mTargetFile, null, mContainerActivity.getStorageManager(), account, true, false, getSherlockActivity());
319 WebdavClient wc = OwnCloudClientUtils.createOwnCloudClient(account, getSherlockActivity().getApplicationContext());
320 operation.execute(wc, mContainerActivity, mHandler);
321 getSherlockActivity().showDialog(FileDisplayActivity.DIALOG_SHORT_WAIT);
322 return true;
323 }
324 case R.id.cancel_download_item: {
325 FileDownloaderBinder downloaderBinder = mContainerActivity.getFileDownloaderBinder();
326 Account account = AccountUtils.getCurrentOwnCloudAccount(getActivity());
327 if (downloaderBinder != null && downloaderBinder.isDownloading(account, mTargetFile)) {
328 downloaderBinder.cancel(account, mTargetFile);
329 listDirectory();
330 mContainerActivity.onTransferStateChanged(mTargetFile, false, false);
331 }
332 return true;
333 }
334 case R.id.cancel_upload_item: {
335 FileUploaderBinder uploaderBinder = mContainerActivity.getFileUploaderBinder();
336 Account account = AccountUtils.getCurrentOwnCloudAccount(getActivity());
337 if (uploaderBinder != null && uploaderBinder.isUploading(account, mTargetFile)) {
338 uploaderBinder.cancel(account, mTargetFile);
339 listDirectory();
340 mContainerActivity.onTransferStateChanged(mTargetFile, false, false);
341 }
342 return true;
343 }
344 default:
345 return super.onContextItemSelected(item);
346 }
347 }
348
349
350 /**
351 * Call this, when the user presses the up button
352 */
353 public void onNavigateUp() {
354 OCFile parentDir = null;
355
356 if(mFile != null){
357 DataStorageManager storageManager = mContainerActivity.getStorageManager();
358 parentDir = storageManager.getFileById(mFile.getParentId());
359 mFile = parentDir;
360 }
361 listDirectory(parentDir);
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, or 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 DataStorageManager 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.isDirectory()){
404 Log.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 {
424
425 /**
426 * Callback method invoked when a directory is clicked by the user on the files list
427 *
428 * @param file
429 */
430 public void onDirectoryClick(OCFile file);
431
432 /**
433 * Callback method invoked when a file (non directory) is clicked by the user on the files list
434 *
435 * @param file
436 */
437 public void onFileClick(OCFile file);
438
439 /**
440 * Getter for the current DataStorageManager in the container activity
441 */
442 public DataStorageManager getStorageManager();
443
444
445 /**
446 * Callback method invoked when the parent activity is fully created to get the directory to list firstly.
447 *
448 * @return Directory to list firstly. Can be NULL.
449 */
450 public OCFile getInitialDirectory();
451
452
453 /**
454 * Callback method invoked when a the 'transfer state' of a file changes.
455 *
456 * This happens when a download or upload is started or ended for a file.
457 *
458 * This method is necessary by now to update the user interface of the double-pane layout in tablets
459 * because methods {@link FileDownloaderBinder#isDownloading(Account, OCFile)} and {@link FileUploaderBinder#isUploading(Account, OCFile)}
460 * won't provide the needed response before the method where this is called finishes.
461 *
462 * TODO Remove this when the transfer state of a file is kept in the database (other thing TODO)
463 *
464 * @param file OCFile which state changed.
465 * @param downloading Flag signaling if the file is now downloading.
466 * @param uploading Flag signaling if the file is now uploading.
467 */
468 public void onTransferStateChanged(OCFile file, boolean downloading, boolean uploading);
469
470 }
471
472
473 @Override
474 public void onDismiss(EditNameDialog dialog) {
475 if (dialog.getResult()) {
476 String newFilename = dialog.getNewFilename();
477 Log.d(TAG, "name edit dialog dismissed with new name " + newFilename);
478 RemoteOperation operation = new RenameFileOperation(mTargetFile,
479 AccountUtils.getCurrentOwnCloudAccount(getActivity()),
480 newFilename,
481 mContainerActivity.getStorageManager());
482 WebdavClient wc = OwnCloudClientUtils.createOwnCloudClient(AccountUtils.getCurrentOwnCloudAccount(getSherlockActivity()), getSherlockActivity().getApplicationContext());
483 operation.execute(wc, mContainerActivity, mHandler);
484 getActivity().showDialog(FileDisplayActivity.DIALOG_SHORT_WAIT);
485 }
486 }
487
488
489 @Override
490 public void onConfirmation(String callerTag) {
491 if (callerTag.equals(FileDetailFragment.FTAG_CONFIRMATION)) {
492 if (mContainerActivity.getStorageManager().getFileById(mTargetFile.getFileId()) != null) {
493 RemoteOperation operation = new RemoveFileOperation( mTargetFile,
494 true,
495 mContainerActivity.getStorageManager());
496 WebdavClient wc = OwnCloudClientUtils.createOwnCloudClient(AccountUtils.getCurrentOwnCloudAccount(getSherlockActivity()), getSherlockActivity().getApplicationContext());
497 operation.execute(wc, mContainerActivity, mHandler);
498
499 getActivity().showDialog(FileDisplayActivity.DIALOG_SHORT_WAIT);
500 }
501 mCurrentDialog.dismiss();
502 mCurrentDialog = null;
503 }
504 }
505
506 @Override
507 public void onNeutral(String callerTag) {
508 File f = null;
509 if (mTargetFile.isDirectory()) {
510 // TODO run in a secondary thread?
511 mContainerActivity.getStorageManager().removeDirectory(mTargetFile, false, true);
512
513 } else if (mTargetFile.isDown() && (f = new File(mTargetFile.getStoragePath())).exists()) {
514 f.delete();
515 mTargetFile.setStoragePath(null);
516 mContainerActivity.getStorageManager().saveFile(mTargetFile);
517 }
518 mCurrentDialog.dismiss();
519 mCurrentDialog = null;
520 listDirectory();
521 mContainerActivity.onTransferStateChanged(mTargetFile, false, false);
522 }
523
524 @Override
525 public void onCancel(String callerTag) {
526 Log.d(TAG, "REMOVAL CANCELED");
527 mCurrentDialog.dismiss();
528 mCurrentDialog = null;
529 }
530
531
532 }