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