Code refactored to 'open file' in a single point
[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.AccountUtils;
25 import com.owncloud.android.Log_OC;
26 import com.owncloud.android.R;
27 import com.owncloud.android.datamodel.DataStorageManager;
28 import com.owncloud.android.datamodel.OCFile;
29 import com.owncloud.android.files.FileHandler;
30 import com.owncloud.android.files.services.FileDownloader.FileDownloaderBinder;
31 import com.owncloud.android.files.services.FileUploader.FileUploaderBinder;
32 import com.owncloud.android.operations.OnRemoteOperationListener;
33 import com.owncloud.android.operations.RemoteOperation;
34 import com.owncloud.android.operations.RemoveFileOperation;
35 import com.owncloud.android.operations.RenameFileOperation;
36 import com.owncloud.android.operations.SynchronizeFileOperation;
37 import com.owncloud.android.ui.FragmentListView;
38 import com.owncloud.android.ui.activity.FileDisplayActivity;
39 import com.owncloud.android.ui.activity.TransferServiceGetter;
40 import com.owncloud.android.ui.adapter.FileListListAdapter;
41 import com.owncloud.android.ui.dialog.EditNameDialog;
42 import com.owncloud.android.ui.dialog.EditNameDialog.EditNameDialogListener;
43 import com.owncloud.android.ui.fragment.ConfirmationDialogFragment.ConfirmationDialogFragmentListener;
44
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.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 /**
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_OC.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_OC.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_OC.i(TAG, "onActivityCreated() stop");
118 }
119
120
121
122 @Override
123 public void onSaveInstanceState(Bundle savedInstanceState) {
124 Log_OC.i(TAG, "onSaveInstanceState() start");
125
126 savedInstanceState.putInt(SAVED_LIST_POSITION, getReferencePosition());
127
128 Log_OC.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, false);
146 }
147
148 } else {
149 Log_OC.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_actions_menu, menu);
162 AdapterContextMenuInfo info = (AdapterContextMenuInfo) menuInfo;
163 OCFile targetFile = (OCFile) mAdapter.getItem(info.position);
164 List<Integer> toHide = new ArrayList<Integer>();
165 List<Integer> toDisable = new ArrayList<Integer>();
166
167 MenuItem item = null;
168 if (targetFile.isDirectory()) {
169 // contextual menu for folders
170 toHide.add(R.id.action_open_file_with);
171 toHide.add(R.id.action_download_file);
172 toHide.add(R.id.action_cancel_download);
173 toHide.add(R.id.action_cancel_upload);
174 toHide.add(R.id.action_sync_file);
175 toHide.add(R.id.action_see_details);
176 if ( mContainerActivity.getFileDownloaderBinder().isDownloading(AccountUtils.getCurrentOwnCloudAccount(getActivity()), targetFile) ||
177 mContainerActivity.getFileUploaderBinder().isUploading(AccountUtils.getCurrentOwnCloudAccount(getActivity()), targetFile) ) {
178 toDisable.add(R.id.action_rename_file);
179 toDisable.add(R.id.action_remove_file);
180
181 }
182
183 } else {
184 // contextual menu for regular files
185 if (targetFile.isDown()) {
186 toHide.add(R.id.action_cancel_download);
187 toHide.add(R.id.action_cancel_upload);
188 toHide.add(R.id.action_download_file);
189
190 } else {
191 toHide.add(R.id.action_open_file_with);
192 toHide.add(R.id.action_sync_file);
193 }
194 if ( mContainerActivity.getFileDownloaderBinder().isDownloading(AccountUtils.getCurrentOwnCloudAccount(getActivity()), targetFile)) {
195 toHide.add(R.id.action_download_file);
196 toHide.add(R.id.action_cancel_upload);
197 toDisable.add(R.id.action_open_file_with);
198 toDisable.add(R.id.action_rename_file);
199 toDisable.add(R.id.action_remove_file);
200
201 } else if ( mContainerActivity.getFileUploaderBinder().isUploading(AccountUtils.getCurrentOwnCloudAccount(getActivity()), targetFile)) {
202 toHide.add(R.id.action_download_file);
203 toHide.add(R.id.action_cancel_download);
204 toDisable.add(R.id.action_open_file_with);
205 toDisable.add(R.id.action_rename_file);
206 toDisable.add(R.id.action_remove_file);
207
208 } else {
209 toHide.add(R.id.action_cancel_download);
210 toHide.add(R.id.action_cancel_upload);
211 }
212 }
213
214 for (int i : toHide) {
215 item = menu.findItem(i);
216 if (item != null) {
217 item.setVisible(false);
218 item.setEnabled(false);
219 }
220 }
221
222 for (int i : toDisable) {
223 item = menu.findItem(i);
224 if (item != null) {
225 item.setEnabled(false);
226 }
227 }
228 }
229
230
231 /**
232 * {@inhericDoc}
233 */
234 @Override
235 public boolean onContextItemSelected (MenuItem item) {
236 AdapterContextMenuInfo info = (AdapterContextMenuInfo) item.getMenuInfo();
237 mTargetFile = (OCFile) mAdapter.getItem(info.position);
238 switch (item.getItemId()) {
239 case R.id.action_rename_file: {
240 String fileName = mTargetFile.getFileName();
241 int extensionStart = mTargetFile.isDirectory() ? -1 : fileName.lastIndexOf(".");
242 int selectionEnd = (extensionStart >= 0) ? extensionStart : fileName.length();
243 EditNameDialog dialog = EditNameDialog.newInstance(getString(R.string.rename_dialog_title), fileName, 0, selectionEnd, this);
244 dialog.show(getFragmentManager(), EditNameDialog.TAG);
245 return true;
246 }
247 case R.id.action_remove_file: {
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 confDialog.show(getFragmentManager(), FileDetailFragment.FTAG_CONFIRMATION);
267 return true;
268 }
269 case R.id.action_open_file_with: {
270 mContainerActivity.openFile(mTargetFile);
271 return true;
272 }
273 case R.id.action_download_file:
274 case R.id.action_sync_file: {
275 Account account = AccountUtils.getCurrentOwnCloudAccount(getSherlockActivity());
276 RemoteOperation operation = new SynchronizeFileOperation(mTargetFile, null, mContainerActivity.getStorageManager(), account, true, false, getSherlockActivity());
277 operation.execute(account, getSherlockActivity(), mContainerActivity, mHandler, getSherlockActivity());
278 getSherlockActivity().showDialog(FileDisplayActivity.DIALOG_SHORT_WAIT);
279 return true;
280 }
281 case R.id.action_cancel_download: {
282 FileDownloaderBinder downloaderBinder = mContainerActivity.getFileDownloaderBinder();
283 Account account = AccountUtils.getCurrentOwnCloudAccount(getActivity());
284 if (downloaderBinder != null && downloaderBinder.isDownloading(account, mTargetFile)) {
285 downloaderBinder.cancel(account, mTargetFile);
286 listDirectory();
287 mContainerActivity.onTransferStateChanged(mTargetFile, false, false);
288 }
289 return true;
290 }
291 case R.id.action_cancel_upload: {
292 FileUploaderBinder uploaderBinder = mContainerActivity.getFileUploaderBinder();
293 Account account = AccountUtils.getCurrentOwnCloudAccount(getActivity());
294 if (uploaderBinder != null && uploaderBinder.isUploading(account, mTargetFile)) {
295 uploaderBinder.cancel(account, mTargetFile);
296 listDirectory();
297 mContainerActivity.onTransferStateChanged(mTargetFile, false, false);
298 }
299 return true;
300 }
301 case R.id.action_see_details: {
302 ((FileFragment.ContainerActivity)getActivity()).showFragmentWithDetails(mTargetFile);
303 return true;
304 }
305 default:
306 return super.onContextItemSelected(item);
307 }
308 }
309
310
311 /**
312 * Call this, when the user presses the up button
313 */
314 public void onNavigateUp() {
315 OCFile parentDir = null;
316
317 if(mFile != null){
318 DataStorageManager storageManager = mContainerActivity.getStorageManager();
319 parentDir = storageManager.getFileById(mFile.getParentId());
320 mFile = parentDir;
321 }
322 listDirectory(parentDir);
323 }
324
325 /**
326 * Use this to query the {@link OCFile} that is currently
327 * being displayed by this fragment
328 * @return The currently viewed OCFile
329 */
330 public OCFile getCurrentFile(){
331 return mFile;
332 }
333
334 /**
335 * Calls {@link OCFileListFragment#listDirectory(OCFile)} with a null parameter
336 */
337 public void listDirectory(){
338 listDirectory(null);
339 }
340
341 /**
342 * Lists the given directory on the view. When the input parameter is null,
343 * it will either refresh the last known directory. list the root
344 * if there never was a directory.
345 *
346 * @param directory File to be listed
347 */
348 public void listDirectory(OCFile directory) {
349 DataStorageManager storageManager = mContainerActivity.getStorageManager();
350 if (storageManager != null) {
351
352 // Check input parameters for null
353 if(directory == null){
354 if(mFile != null){
355 directory = mFile;
356 } else {
357 directory = storageManager.getFileByPath("/");
358 if (directory == null) return; // no files, wait for sync
359 }
360 }
361
362
363 // If that's not a directory -> List its parent
364 if(!directory.isDirectory()){
365 Log_OC.w(TAG, "You see, that is not a directory -> " + directory.toString());
366 directory = storageManager.getFileById(directory.getParentId());
367 }
368
369 mAdapter.swapDirectory(directory, storageManager);
370 if (mFile == null || !mFile.equals(directory)) {
371 mList.setSelectionFromTop(0, 0);
372 }
373 mFile = directory;
374 }
375 }
376
377
378
379 /**
380 * Interface to implement by any Activity that includes some instance of FileListFragment
381 *
382 * @author David A. Velasco
383 */
384 public interface ContainerActivity extends TransferServiceGetter, OnRemoteOperationListener, FileHandler {
385
386 /**
387 * Callback method invoked when a directory is clicked by the user on the files list
388 *
389 * @param file
390 */
391 public void onDirectoryClick(OCFile file);
392
393 /**
394 * Callback method invoked when a file (non directory) is clicked by the user on the files list
395 *
396 * @param file
397 */
398 public void onFileClick(OCFile file, boolean realClick);
399
400 /**
401 * Getter for the current DataStorageManager in the container activity
402 */
403 public DataStorageManager getStorageManager();
404
405
406 /**
407 * Callback method invoked when the parent activity is fully created to get the directory to list firstly.
408 *
409 * @return Directory to list firstly. Can be NULL.
410 */
411 public OCFile getInitialDirectory();
412
413
414 /**
415 * Callback method invoked when a the 'transfer state' of a file changes.
416 *
417 * This happens when a download or upload is started or ended for a file.
418 *
419 * This method is necessary by now to update the user interface of the double-pane layout in tablets
420 * because methods {@link FileDownloaderBinder#isDownloading(Account, OCFile)} and {@link FileUploaderBinder#isUploading(Account, OCFile)}
421 * won't provide the needed response before the method where this is called finishes.
422 *
423 * TODO Remove this when the transfer state of a file is kept in the database (other thing TODO)
424 *
425 * @param file OCFile which state changed.
426 * @param downloading Flag signaling if the file is now downloading.
427 * @param uploading Flag signaling if the file is now uploading.
428 */
429 public void onTransferStateChanged(OCFile file, boolean downloading, boolean uploading);
430
431 }
432
433
434 @Override
435 public void onDismiss(EditNameDialog dialog) {
436 if (dialog.getResult()) {
437 String newFilename = dialog.getNewFilename();
438 Log_OC.d(TAG, "name edit dialog dismissed with new name " + newFilename);
439 RemoteOperation operation = new RenameFileOperation(mTargetFile,
440 AccountUtils.getCurrentOwnCloudAccount(getActivity()),
441 newFilename,
442 mContainerActivity.getStorageManager());
443 operation.execute(AccountUtils.getCurrentOwnCloudAccount(getSherlockActivity()), getSherlockActivity(), mContainerActivity, mHandler, getSherlockActivity());
444 getActivity().showDialog(FileDisplayActivity.DIALOG_SHORT_WAIT);
445 }
446 }
447
448
449 @Override
450 public void onConfirmation(String callerTag) {
451 if (callerTag.equals(FileDetailFragment.FTAG_CONFIRMATION)) {
452 if (mContainerActivity.getStorageManager().getFileById(mTargetFile.getFileId()) != null) {
453 RemoteOperation operation = new RemoveFileOperation( mTargetFile,
454 true,
455 mContainerActivity.getStorageManager());
456 operation.execute(AccountUtils.getCurrentOwnCloudAccount(getSherlockActivity()), getSherlockActivity(), mContainerActivity, mHandler, getSherlockActivity());
457
458 getActivity().showDialog(FileDisplayActivity.DIALOG_SHORT_WAIT);
459 }
460 }
461 }
462
463 @Override
464 public void onNeutral(String callerTag) {
465 File f = null;
466 if (mTargetFile.isDirectory()) {
467 // TODO run in a secondary thread?
468 mContainerActivity.getStorageManager().removeDirectory(mTargetFile, false, true);
469
470 } else if (mTargetFile.isDown() && (f = new File(mTargetFile.getStoragePath())).exists()) {
471 f.delete();
472 mTargetFile.setStoragePath(null);
473 mContainerActivity.getStorageManager().saveFile(mTargetFile);
474 }
475 listDirectory();
476 mContainerActivity.onTransferStateChanged(mTargetFile, false, false);
477 }
478
479 @Override
480 public void onCancel(String callerTag) {
481 Log_OC.d(TAG, "REMOVAL CANCELED");
482 }
483
484
485 }