Added title to rename dialog; and some cleaning
[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
22 import com.owncloud.android.AccountUtils;
23 import com.owncloud.android.R;
24 import com.owncloud.android.datamodel.DataStorageManager;
25 import com.owncloud.android.datamodel.OCFile;
26 import com.owncloud.android.files.services.FileDownloader;
27 import com.owncloud.android.files.services.FileDownloader.FileDownloaderBinder;
28 import com.owncloud.android.files.services.FileUploader.FileUploaderBinder;
29 import com.owncloud.android.network.OwnCloudClientUtils;
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.ui.FragmentListView;
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
42 import eu.alefzero.webdav.WebdavClient;
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.util.Log;
53 import android.view.ContextMenu;
54 import android.view.MenuInflater;
55 import android.view.MenuItem;
56 import android.view.View;
57 import android.webkit.MimeTypeMap;
58 import android.widget.AdapterView;
59 import android.widget.Toast;
60 import android.widget.AdapterView.AdapterContextMenuInfo;
61
62 /**
63 * A Fragment that lists all files and folders in a given path.
64 *
65 * @author Bartek Przybylski
66 *
67 */
68 public class OCFileListFragment extends FragmentListView implements EditNameDialogListener, ConfirmationDialogFragmentListener {
69 private static final String TAG = "FileListFragment";
70 private static final String SAVED_LIST_POSITION = "LIST_POSITION";
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 /**
82 * {@inheritDoc}
83 */
84 @Override
85 public void onAttach(Activity activity) {
86 super.onAttach(activity);
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 Log.i(TAG, "onActivityCreated() start");
101
102 super.onActivityCreated(savedInstanceState);
103 mAdapter = new FileListListAdapter(mContainerActivity.getInitialDirectory(), mContainerActivity.getStorageManager(), getActivity(), mContainerActivity);
104 setListAdapter(mAdapter);
105
106 if (savedInstanceState != null) {
107 Log.i(TAG, "savedInstanceState is not null");
108 int position = savedInstanceState.getInt(SAVED_LIST_POSITION);
109 setReferencePosition(position);
110 }
111
112 registerForContextMenu(getListView());
113 getListView().setOnCreateContextMenuListener(this);
114
115 mHandler = new Handler();
116
117 Log.i(TAG, "onActivityCreated() stop");
118 }
119
120
121
122 @Override
123 public void onSaveInstanceState(Bundle savedInstanceState) {
124 Log.i(TAG, "onSaveInstanceState() start");
125
126 savedInstanceState.putInt(SAVED_LIST_POSITION, getReferencePosition());
127
128 Log.i(TAG, "onSaveInstanceState() stop");
129 }
130
131
132 @Override
133 public void onItemClick(AdapterView<?> l, View v, int position, long id) {
134 OCFile file = (OCFile) mAdapter.getItem(position);
135 if (file != null) {
136 /// Click on a directory
137 if (file.getMimetype().equals("DIR")) {
138 // just local updates
139 mFile = file;
140 listDirectory(file);
141 // any other updates are let to the container Activity
142 mContainerActivity.onDirectoryClick(file);
143
144 } else { /// Click on a file
145 mContainerActivity.onFileClick(file);
146 }
147
148 } else {
149 Log.d(TAG, "Null object in ListAdapter!!");
150 }
151
152 }
153
154 /**
155 * {@inheritDoc}
156 */
157 @Override
158 public void onCreateContextMenu (ContextMenu menu, View v, ContextMenu.ContextMenuInfo menuInfo) {
159 super.onCreateContextMenu(menu, v, menuInfo);
160 MenuInflater inflater = getActivity().getMenuInflater();
161 inflater.inflate(R.menu.file_context_menu, menu);
162 AdapterContextMenuInfo info = (AdapterContextMenuInfo) menuInfo;
163 OCFile targetFile = (OCFile) mAdapter.getItem(info.position);
164 MenuItem item = null;
165 int[] ids = null;
166 if (targetFile.isDirectory()) {
167 int[] theIds = {R.id.open_file_item, R.id.download_file_item, R.id.cancel_download_item, R.id.cancel_upload_item};
168 ids = theIds;
169
170 } else if ( mContainerActivity.getFileDownloaderBinder().isDownloading(AccountUtils.getCurrentOwnCloudAccount(getActivity()), targetFile)) {
171 int[] theIds = {R.id.open_file_item, R.id.download_file_item, R.id.cancel_upload_item};
172 ids = theIds;
173
174 } else if ( mContainerActivity.getFileUploaderBinder().isUploading(AccountUtils.getCurrentOwnCloudAccount(getActivity()), targetFile)) {
175 int[] theIds = {R.id.open_file_item, R.id.download_file_item, R.id.cancel_download_item};
176 ids = theIds;
177
178 } else if ( targetFile.isDown()) {
179 int[] theIds = {R.id.cancel_download_item, R.id.cancel_upload_item};
180 ids = theIds;
181
182 } else {
183 int[] theIds = {R.id.open_file_item, R.id.cancel_download_item, R.id.cancel_upload_item};
184 ids = theIds;
185 }
186
187 for (int i=0; i < ids.length; i++) {
188 item = menu.findItem(ids[i]);
189 if (item != null) {
190 item.setVisible(false);
191 item.setEnabled(false);
192 }
193 }
194 }
195
196
197 /**
198 * {@inhericDoc}
199 */
200 @Override
201 public boolean onContextItemSelected (MenuItem item) {
202 AdapterContextMenuInfo info = (AdapterContextMenuInfo) item.getMenuInfo();
203 mTargetFile = (OCFile) mAdapter.getItem(info.position);
204 switch (item.getItemId()) {
205 case R.id.rename_file_item: {
206 EditNameDialog dialog = EditNameDialog.newInstance(getString(R.string.rename_dialog_title), mTargetFile.getFileName(), this);
207 dialog.show(getFragmentManager(), EditNameDialog.TAG);
208 return true;
209 }
210 case R.id.remove_file_item: {
211 int messageStringId = R.string.confirmation_remove_alert;
212 int posBtnStringId = R.string.confirmation_remove_remote;
213 int neuBtnStringId = -1;
214 if (mTargetFile.isDirectory()) {
215 messageStringId = R.string.confirmation_remove_folder_alert;
216 posBtnStringId = R.string.confirmation_remove_remote_and_local;
217 neuBtnStringId = R.string.confirmation_remove_folder_local;
218 } else if (mTargetFile.isDown()) {
219 posBtnStringId = R.string.confirmation_remove_remote_and_local;
220 neuBtnStringId = R.string.confirmation_remove_local;
221 }
222 ConfirmationDialogFragment confDialog = ConfirmationDialogFragment.newInstance(
223 messageStringId,
224 new String[]{mTargetFile.getFileName()},
225 posBtnStringId,
226 neuBtnStringId,
227 R.string.common_cancel);
228 confDialog.setOnConfirmationListener(this);
229 confDialog.show(getFragmentManager(), FileDetailFragment.FTAG_CONFIRMATION);
230 return true;
231 }
232 case R.id.open_file_item: {
233 String storagePath = mTargetFile.getStoragePath();
234 String encodedStoragePath = WebdavUtils.encodePath(storagePath);
235 try {
236 Intent i = new Intent(Intent.ACTION_VIEW);
237 i.setDataAndType(Uri.parse("file://"+ encodedStoragePath), mTargetFile.getMimetype());
238 i.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION | Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
239 startActivity(i);
240
241 } catch (Throwable t) {
242 Log.e(TAG, "Fail when trying to open with the mimeType provided from the ownCloud server: " + mTargetFile.getMimetype());
243 boolean toastIt = true;
244 String mimeType = "";
245 try {
246 Intent i = new Intent(Intent.ACTION_VIEW);
247 mimeType = MimeTypeMap.getSingleton().getMimeTypeFromExtension(storagePath.substring(storagePath.lastIndexOf('.') + 1));
248 if (mimeType == null || !mimeType.equals(mTargetFile.getMimetype())) {
249 if (mimeType != null) {
250 i.setDataAndType(Uri.parse("file://"+ encodedStoragePath), mimeType);
251 } else {
252 // desperate try
253 i.setDataAndType(Uri.parse("file://"+ encodedStoragePath), "*/*");
254 }
255 i.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION | Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
256 startActivity(i);
257 toastIt = false;
258 }
259
260 } catch (IndexOutOfBoundsException e) {
261 Log.e(TAG, "Trying to find out MIME type of a file without extension: " + storagePath);
262
263 } catch (ActivityNotFoundException e) {
264 Log.e(TAG, "No activity found to handle: " + storagePath + " with MIME type " + mimeType + " obtained from extension");
265
266 } catch (Throwable th) {
267 Log.e(TAG, "Unexpected problem when opening: " + storagePath, th);
268
269 } finally {
270 if (toastIt) {
271 Toast.makeText(getActivity(), "There is no application to handle file " + mTargetFile.getFileName(), Toast.LENGTH_SHORT).show();
272 }
273 }
274
275 }
276 return true;
277 }
278 case R.id.download_file_item: {
279 Account account = AccountUtils.getCurrentOwnCloudAccount(getActivity());
280 Intent i = new Intent(getActivity(), FileDownloader.class);
281 i.putExtra(FileDownloader.EXTRA_ACCOUNT, account);
282 i.putExtra(FileDownloader.EXTRA_FILE, mTargetFile);
283 getActivity().startService(i);
284 listDirectory();
285 mContainerActivity.onTransferStateChanged(mTargetFile, true, false);
286 return true;
287 }
288 case R.id.cancel_download_item: {
289 FileDownloaderBinder downloaderBinder = mContainerActivity.getFileDownloaderBinder();
290 Account account = AccountUtils.getCurrentOwnCloudAccount(getActivity());
291 if (downloaderBinder != null && downloaderBinder.isDownloading(account, mTargetFile)) {
292 downloaderBinder.cancel(account, mTargetFile);
293 listDirectory();
294 mContainerActivity.onTransferStateChanged(mTargetFile, false, false);
295 }
296 return true;
297 }
298 case R.id.cancel_upload_item: {
299 FileUploaderBinder uploaderBinder = mContainerActivity.getFileUploaderBinder();
300 Account account = AccountUtils.getCurrentOwnCloudAccount(getActivity());
301 if (uploaderBinder != null && uploaderBinder.isUploading(account, mTargetFile)) {
302 uploaderBinder.cancel(account, mTargetFile);
303 listDirectory();
304 mContainerActivity.onTransferStateChanged(mTargetFile, false, false);
305 }
306 return true;
307 }
308 default:
309 return super.onContextItemSelected(item);
310 }
311 }
312
313
314 /**
315 * Call this, when the user presses the up button
316 */
317 public void onNavigateUp() {
318 OCFile parentDir = null;
319
320 if(mFile != null){
321 DataStorageManager storageManager = mContainerActivity.getStorageManager();
322 parentDir = storageManager.getFileById(mFile.getParentId());
323 mFile = parentDir;
324 }
325 listDirectory(parentDir);
326 }
327
328 /**
329 * Use this to query the {@link OCFile} that is currently
330 * being displayed by this fragment
331 * @return The currently viewed OCFile
332 */
333 public OCFile getCurrentFile(){
334 return mFile;
335 }
336
337 /**
338 * Calls {@link OCFileListFragment#listDirectory(OCFile)} with a null parameter
339 */
340 public void listDirectory(){
341 listDirectory(null);
342 }
343
344 /**
345 * Lists the given directory on the view. When the input parameter is null,
346 * it will either refresh the last known directory, or list the root
347 * if there never was a directory.
348 *
349 * @param directory File to be listed
350 */
351 public void listDirectory(OCFile directory) {
352 DataStorageManager storageManager = mContainerActivity.getStorageManager();
353 if (storageManager != null) {
354
355 // Check input parameters for null
356 if(directory == null){
357 if(mFile != null){
358 directory = mFile;
359 } else {
360 directory = storageManager.getFileByPath("/");
361 if (directory == null) return; // no files, wait for sync
362 }
363 }
364
365
366 // If that's not a directory -> List its parent
367 if(!directory.isDirectory()){
368 Log.w(TAG, "You see, that is not a directory -> " + directory.toString());
369 directory = storageManager.getFileById(directory.getParentId());
370 }
371
372 mAdapter.swapDirectory(directory, storageManager);
373 if (mFile == null || !mFile.equals(directory)) {
374 mList.setSelectionFromTop(0, 0);
375 }
376 mFile = directory;
377 }
378 }
379
380
381
382 /**
383 * Interface to implement by any Activity that includes some instance of FileListFragment
384 *
385 * @author David A. Velasco
386 */
387 public interface ContainerActivity extends TransferServiceGetter, OnRemoteOperationListener {
388
389 /**
390 * Callback method invoked when a directory is clicked by the user on the files list
391 *
392 * @param file
393 */
394 public void onDirectoryClick(OCFile file);
395
396 /**
397 * Callback method invoked when a file (non directory) is clicked by the user on the files list
398 *
399 * @param file
400 */
401 public void onFileClick(OCFile file);
402
403 /**
404 * Getter for the current DataStorageManager in the container activity
405 */
406 public DataStorageManager getStorageManager();
407
408
409 /**
410 * Callback method invoked when the parent activity is fully created to get the directory to list firstly.
411 *
412 * @return Directory to list firstly. Can be NULL.
413 */
414 public OCFile getInitialDirectory();
415
416
417 /**
418 * Callback method invoked when a the 'transfer state' of a file changes.
419 *
420 * This happens when a download or upload is started or ended for a file.
421 *
422 * This method is necessary by now to update the user interface of the double-pane layout in tablets
423 * because methods {@link FileDownloaderBinder#isDownloading(Account, OCFile)} and {@link FileUploaderBinder#isUploading(Account, OCFile)}
424 * won't provide the needed response before the method where this is called finishes.
425 *
426 * TODO Remove this when the transfer state of a file is kept in the database (other thing TODO)
427 *
428 * @param file OCFile which state changed.
429 * @param downloading Flag signaling if the file is now downloading.
430 * @param uploading Flag signaling if the file is now uploading.
431 */
432 public void onTransferStateChanged(OCFile file, boolean downloading, boolean uploading);
433
434 }
435
436
437 @Override
438 public void onDismiss(EditNameDialog dialog) {
439 if (dialog.getResult()) {
440 String newFilename = dialog.getNewFilename();
441 Log.d(TAG, "name edit dialog dismissed with new name " + newFilename);
442 RemoteOperation operation = new RenameFileOperation(mTargetFile,
443 AccountUtils.getCurrentOwnCloudAccount(getActivity()),
444 newFilename,
445 mContainerActivity.getStorageManager());
446 WebdavClient wc = OwnCloudClientUtils.createOwnCloudClient(AccountUtils.getCurrentOwnCloudAccount(getSherlockActivity()), getSherlockActivity().getApplicationContext());
447 operation.execute(wc, mContainerActivity, mHandler);
448 getActivity().showDialog(FileDisplayActivity.DIALOG_SHORT_WAIT);
449 }
450 }
451
452
453 @Override
454 public void onConfirmation(String callerTag) {
455 if (callerTag.equals(FileDetailFragment.FTAG_CONFIRMATION)) {
456 if (mContainerActivity.getStorageManager().getFileById(mTargetFile.getFileId()) != null) {
457 RemoteOperation operation = new RemoveFileOperation( mTargetFile,
458 true,
459 mContainerActivity.getStorageManager());
460 WebdavClient wc = OwnCloudClientUtils.createOwnCloudClient(AccountUtils.getCurrentOwnCloudAccount(getSherlockActivity()), getSherlockActivity().getApplicationContext());
461 operation.execute(wc, mContainerActivity, mHandler);
462
463 getActivity().showDialog(FileDisplayActivity.DIALOG_SHORT_WAIT);
464 }
465 }
466 }
467
468 @Override
469 public void onNeutral(String callerTag) {
470 File f = null;
471 if (mTargetFile.isDirectory()) {
472 // TODO run in a secondary thread?
473 mContainerActivity.getStorageManager().removeDirectory(mTargetFile, false, true);
474
475 } else if (mTargetFile.isDown() && (f = new File(mTargetFile.getStoragePath())).exists()) {
476 f.delete();
477 mTargetFile.setStoragePath(null);
478 mContainerActivity.getStorageManager().saveFile(mTargetFile);
479 }
480 listDirectory();
481 mContainerActivity.onTransferStateChanged(mTargetFile, false, false);
482 }
483
484 @Override
485 public void onCancel(String callerTag) {
486 Log.d(TAG, "REMOVAL CANCELED");
487 }
488
489
490 }