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