c9a5ecf086100680d636aaeb36782783249763e3
[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-2014 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
23 import com.owncloud.android.R;
24 import com.owncloud.android.authentication.AccountUtils;
25 import com.owncloud.android.datamodel.FileDataStorageManager;
26 import com.owncloud.android.datamodel.OCFile;
27 import com.owncloud.android.files.FileMenuFilter;
28 import com.owncloud.android.files.services.FileDownloader.FileDownloaderBinder;
29 import com.owncloud.android.files.services.FileUploader.FileUploaderBinder;
30 import com.owncloud.android.ui.adapter.FileListListAdapter;
31 import com.owncloud.android.ui.activity.FileDisplayActivity;
32 import com.owncloud.android.ui.dialog.ConfirmationDialogFragment;
33 import com.owncloud.android.ui.dialog.EditNameDialog;
34 import com.owncloud.android.ui.dialog.ConfirmationDialogFragment.ConfirmationDialogFragmentListener;
35 import com.owncloud.android.ui.dialog.EditNameDialog.EditNameDialogListener;
36 import com.owncloud.android.ui.preview.PreviewImageFragment;
37 import com.owncloud.android.ui.preview.PreviewMediaFragment;
38 import com.owncloud.android.utils.Log_OC;
39
40 import android.accounts.Account;
41 import android.app.Activity;
42 import android.os.Bundle;
43 import android.view.ContextMenu;
44 import android.view.MenuInflater;
45 import android.view.MenuItem;
46 import android.view.View;
47 import android.widget.AdapterView;
48 import android.widget.AdapterView.AdapterContextMenuInfo;
49
50 /**
51 * A Fragment that lists all files and folders in a given path.
52 *
53 * TODO refactorize to get rid of direct dependency on FileDisplayActivity
54 *
55 * @author Bartek Przybylski
56 * @author masensio
57 * @author David A. Velasco
58 */
59 public class OCFileListFragment extends ExtendedListFragment
60 implements EditNameDialogListener, ConfirmationDialogFragmentListener {
61
62 private static final String TAG = OCFileListFragment.class.getSimpleName();
63
64 private static final String MY_PACKAGE = OCFileListFragment.class.getPackage() != null ? OCFileListFragment.class.getPackage().getName() : "com.owncloud.android.ui.fragment";
65 private static final String EXTRA_FILE = MY_PACKAGE + ".extra.FILE";
66
67 private static final String KEY_INDEXES = "INDEXES";
68 private static final String KEY_FIRST_POSITIONS= "FIRST_POSITIONS";
69 private static final String KEY_TOPS = "TOPS";
70 private static final String KEY_HEIGHT_CELL = "HEIGHT_CELL";
71
72 private FileFragment.ContainerActivity mContainerActivity;
73
74 private OCFile mFile = null;
75 private FileListListAdapter mAdapter;
76
77 private OCFile mTargetFile;
78
79 // Save the state of the scroll in browsing
80 private ArrayList<Integer> mIndexes;
81 private ArrayList<Integer> mFirstPositions;
82 private ArrayList<Integer> mTops;
83
84 private int mHeightCell = 0;
85
86 /**
87 * {@inheritDoc}
88 */
89 @Override
90 public void onAttach(Activity activity) {
91 super.onAttach(activity);
92 Log_OC.e(TAG, "onAttach");
93 try {
94 mContainerActivity = (FileFragment.ContainerActivity) activity;
95 } catch (ClassCastException e) {
96 throw new ClassCastException(activity.toString() + " must implement " +
97 FileFragment.ContainerActivity.class.getSimpleName());
98 }
99 }
100
101
102 @Override
103 public void onDetach() {
104 mContainerActivity = null;
105 super.onDetach();
106 }
107
108 /**
109 * {@inheritDoc}
110 */
111 @Override
112 public void onActivityCreated(Bundle savedInstanceState) {
113 super.onActivityCreated(savedInstanceState);
114 Log_OC.e(TAG, "onActivityCreated() start");
115 mAdapter = new FileListListAdapter(getSherlockActivity(), mContainerActivity);
116
117 if (savedInstanceState != null) {
118 mFile = savedInstanceState.getParcelable(EXTRA_FILE);
119 mIndexes = savedInstanceState.getIntegerArrayList(KEY_INDEXES);
120 mFirstPositions = savedInstanceState.getIntegerArrayList(KEY_FIRST_POSITIONS);
121 mTops = savedInstanceState.getIntegerArrayList(KEY_TOPS);
122 mHeightCell = savedInstanceState.getInt(KEY_HEIGHT_CELL);
123
124 } else {
125 mIndexes = new ArrayList<Integer>();
126 mFirstPositions = new ArrayList<Integer>();
127 mTops = new ArrayList<Integer>();
128 mHeightCell = 0;
129
130 }
131
132 mAdapter = new FileListListAdapter(getSherlockActivity(), mContainerActivity);
133
134 setListAdapter(mAdapter);
135
136 registerForContextMenu(getListView());
137 getListView().setOnCreateContextMenuListener(this);
138
139 // mHandler = new Handler();
140 }
141
142 /**
143 * Saves the current listed folder.
144 */
145 @Override
146 public void onSaveInstanceState (Bundle outState) {
147 super.onSaveInstanceState(outState);
148 outState.putParcelable(EXTRA_FILE, mFile);
149 outState.putIntegerArrayList(KEY_INDEXES, mIndexes);
150 outState.putIntegerArrayList(KEY_FIRST_POSITIONS, mFirstPositions);
151 outState.putIntegerArrayList(KEY_TOPS, mTops);
152 outState.putInt(KEY_HEIGHT_CELL, mHeightCell);
153 }
154
155 /**
156 * Call this, when the user presses the up button.
157 *
158 * Tries to move up the current folder one level. If the parent folder was removed from the database,
159 * it continues browsing up until finding an existing folders.
160 *
161 * return Count of folder levels browsed up.
162 */
163 public int onBrowseUp() {
164 OCFile parentDir = null;
165 int moveCount = 0;
166
167 if(mFile != null){
168 FileDataStorageManager storageManager = mContainerActivity.getStorageManager();
169
170 String parentPath = null;
171 if (mFile.getParentId() != FileDataStorageManager.ROOT_PARENT_ID) {
172 parentPath = new File(mFile.getRemotePath()).getParent();
173 parentPath = parentPath.endsWith(OCFile.PATH_SEPARATOR) ? parentPath : parentPath + OCFile.PATH_SEPARATOR;
174 parentDir = storageManager.getFileByPath(parentPath);
175 moveCount++;
176 } else {
177 parentDir = storageManager.getFileByPath(OCFile.ROOT_PATH); // never returns null; keep the path in root folder
178 }
179 while (parentDir == null) {
180 parentPath = new File(parentPath).getParent();
181 parentPath = parentPath.endsWith(OCFile.PATH_SEPARATOR) ? parentPath : parentPath + OCFile.PATH_SEPARATOR;
182 parentDir = storageManager.getFileByPath(parentPath);
183 moveCount++;
184 } // exit is granted because storageManager.getFileByPath("/") never returns null
185 mFile = parentDir;
186 }
187
188 if (mFile != null) {
189 listDirectory(mFile);
190
191 ((FileDisplayActivity)mContainerActivity).startSyncFolderOperation(mFile);
192
193 // restore index and top position
194 restoreIndexAndTopPosition();
195
196 } // else - should never happen now
197
198 return moveCount;
199 }
200
201 /*
202 * Restore index and position
203 */
204 private void restoreIndexAndTopPosition() {
205 if (mIndexes.size() > 0) {
206 // needs to be checked; not every browse-up had a browse-down before
207
208 int index = mIndexes.remove(mIndexes.size() - 1);
209
210 int firstPosition = mFirstPositions.remove(mFirstPositions.size() -1);
211
212 int top = mTops.remove(mTops.size() - 1);
213
214 mList.setSelectionFromTop(firstPosition, top);
215
216 // Move the scroll if the selection is not visible
217 int indexPosition = mHeightCell*index;
218 int height = mList.getHeight();
219
220 if (indexPosition > height) {
221 if (android.os.Build.VERSION.SDK_INT >= 11)
222 {
223 mList.smoothScrollToPosition(index);
224 }
225 else if (android.os.Build.VERSION.SDK_INT >= 8)
226 {
227 mList.setSelectionFromTop(index, 0);
228 }
229
230 }
231 }
232 }
233
234 /*
235 * Save index and top position
236 */
237 private void saveIndexAndTopPosition(int index) {
238
239 mIndexes.add(index);
240
241 int firstPosition = mList.getFirstVisiblePosition();
242 mFirstPositions.add(firstPosition);
243
244 View view = mList.getChildAt(0);
245 int top = (view == null) ? 0 : view.getTop() ;
246
247 mTops.add(top);
248
249 // Save the height of a cell
250 mHeightCell = (view == null || mHeightCell != 0) ? mHeightCell : view.getHeight();
251 }
252
253 @Override
254 public void onItemClick(AdapterView<?> l, View v, int position, long id) {
255 OCFile file = (OCFile) mAdapter.getItem(position);
256 if (file != null) {
257 if (file.isFolder()) {
258 // update state and view of this fragment
259 listDirectory(file);
260 // then, notify parent activity to let it update its state and view, and other fragments
261 mContainerActivity.onBrowsedDownTo(file);
262 // save index and top position
263 saveIndexAndTopPosition(position);
264
265 } else { /// Click on a file
266 if (PreviewImageFragment.canBePreviewed(file)) {
267 // preview image - it handles the download, if needed
268 ((FileDisplayActivity)mContainerActivity).startImagePreview(file);
269
270 } else if (file.isDown()) {
271 if (PreviewMediaFragment.canBePreviewed(file)) {
272 // media preview
273 ((FileDisplayActivity)mContainerActivity).startMediaPreview(file, 0, true);
274 } else {
275 mContainerActivity.getFileOperationsHelper().openFile(file);
276 }
277
278 } else {
279 // automatic download, preview on finish
280 ((FileDisplayActivity)mContainerActivity).startDownloadForPreview(file);
281 }
282
283 }
284
285 } else {
286 Log_OC.d(TAG, "Null object in ListAdapter!!");
287 }
288
289 }
290
291 /**
292 * {@inheritDoc}
293 */
294 @Override
295 public void onCreateContextMenu (ContextMenu menu, View v, ContextMenu.ContextMenuInfo menuInfo) {
296 super.onCreateContextMenu(menu, v, menuInfo);
297 MenuInflater inflater = getSherlockActivity().getMenuInflater();
298 inflater.inflate(R.menu.file_actions_menu, menu);
299 AdapterContextMenuInfo info = (AdapterContextMenuInfo) menuInfo;
300 OCFile targetFile = (OCFile) mAdapter.getItem(info.position);
301
302 FileMenuFilter mf = new FileMenuFilter();
303 mf.setFile(targetFile);
304 mf.setComponentGetter(mContainerActivity);
305 mf.setAccount(mContainerActivity.getStorageManager().getAccount());
306 mf.setContext(getSherlockActivity());
307 mf.setFragment(this);
308 mf.filter(menu);
309 }
310
311
312 /**
313 * {@inhericDoc}
314 */
315 @Override
316 public boolean onContextItemSelected (MenuItem item) {
317 AdapterContextMenuInfo info = (AdapterContextMenuInfo) item.getMenuInfo();
318 mTargetFile = (OCFile) mAdapter.getItem(info.position);
319 switch (item.getItemId()) {
320 case R.id.action_share_file: {
321 mContainerActivity.getFileOperationsHelper().shareFileWithLink(mTargetFile);
322 return true;
323 }
324 case R.id.action_unshare_file: {
325 mContainerActivity.getFileOperationsHelper().unshareFileWithLink(mTargetFile);
326 return true;
327 }
328 case R.id.action_rename_file: {
329 String fileName = mTargetFile.getFileName();
330 int extensionStart = mTargetFile.isFolder() ? -1 : fileName.lastIndexOf(".");
331 int selectionEnd = (extensionStart >= 0) ? extensionStart : fileName.length();
332 EditNameDialog dialog = EditNameDialog.newInstance(getString(R.string.rename_dialog_title), fileName, 0, selectionEnd, this);
333 dialog.show(getFragmentManager(), EditNameDialog.TAG);
334 return true;
335 }
336 case R.id.action_remove_file: {
337 int messageStringId = R.string.confirmation_remove_alert;
338 int posBtnStringId = R.string.confirmation_remove_remote;
339 int neuBtnStringId = -1;
340 if (mTargetFile.isFolder()) {
341 messageStringId = R.string.confirmation_remove_folder_alert;
342 posBtnStringId = R.string.confirmation_remove_remote_and_local;
343 neuBtnStringId = R.string.confirmation_remove_folder_local;
344 } else if (mTargetFile.isDown()) {
345 posBtnStringId = R.string.confirmation_remove_remote_and_local;
346 neuBtnStringId = R.string.confirmation_remove_local;
347 }
348 ConfirmationDialogFragment confDialog = ConfirmationDialogFragment.newInstance(
349 messageStringId,
350 new String[]{mTargetFile.getFileName()},
351 posBtnStringId,
352 neuBtnStringId,
353 R.string.common_cancel);
354 confDialog.setOnConfirmationListener(this);
355 confDialog.show(getFragmentManager(), FileDetailFragment.FTAG_CONFIRMATION);
356 return true;
357 }
358 case R.id.action_sync_file: {
359 mContainerActivity.getFileOperationsHelper().syncFile(mTargetFile);
360 return true;
361 }
362 case R.id.action_cancel_download: {
363 FileDownloaderBinder downloaderBinder = mContainerActivity.getFileDownloaderBinder();
364 Account account = AccountUtils.getCurrentOwnCloudAccount(getSherlockActivity());
365 if (downloaderBinder != null && downloaderBinder.isDownloading(account, mTargetFile)) {
366 downloaderBinder.cancel(account, mTargetFile);
367 listDirectory();
368 mContainerActivity.onTransferStateChanged(mTargetFile, false, false);
369 }
370 return true;
371 }
372 case R.id.action_cancel_upload: {
373 FileUploaderBinder uploaderBinder = mContainerActivity.getFileUploaderBinder();
374 Account account = AccountUtils.getCurrentOwnCloudAccount(getSherlockActivity());
375 if (uploaderBinder != null && uploaderBinder.isUploading(account, mTargetFile)) {
376 uploaderBinder.cancel(account, mTargetFile);
377 listDirectory();
378 mContainerActivity.onTransferStateChanged(mTargetFile, false, false);
379 }
380 return true;
381 }
382 case R.id.action_see_details: {
383 mContainerActivity.showDetails(mTargetFile);
384 return true;
385 }
386 case R.id.action_send_file: {
387 // Obtain the file
388 if (!mTargetFile.isDown()) { // Download the file
389 Log_OC.d(TAG, mTargetFile.getRemotePath() + " : File must be downloaded");
390 ((FileDisplayActivity)mContainerActivity).startDownloadForSending(mTargetFile);
391
392 } else {
393 mContainerActivity.getFileOperationsHelper().sendDownloadedFile(mTargetFile);
394 }
395 return true;
396 }
397 default:
398 return super.onContextItemSelected(item);
399 }
400 }
401
402
403 /**
404 * Use this to query the {@link OCFile} that is currently
405 * being displayed by this fragment
406 * @return The currently viewed OCFile
407 */
408 public OCFile getCurrentFile(){
409 return mFile;
410 }
411
412 /**
413 * Calls {@link OCFileListFragment#listDirectory(OCFile)} with a null parameter
414 */
415 public void listDirectory(){
416 listDirectory(null);
417 }
418
419 /**
420 * Lists the given directory on the view. When the input parameter is null,
421 * it will either refresh the last known directory. list the root
422 * if there never was a directory.
423 *
424 * @param directory File to be listed
425 */
426 public void listDirectory(OCFile directory) {
427 FileDataStorageManager storageManager = mContainerActivity.getStorageManager();
428 if (storageManager != null) {
429
430 // Check input parameters for null
431 if(directory == null){
432 if(mFile != null){
433 directory = mFile;
434 } else {
435 directory = storageManager.getFileByPath("/");
436 if (directory == null) return; // no files, wait for sync
437 }
438 }
439
440
441 // If that's not a directory -> List its parent
442 if(!directory.isFolder()){
443 Log_OC.w(TAG, "You see, that is not a directory -> " + directory.toString());
444 directory = storageManager.getFileById(directory.getParentId());
445 }
446
447 mAdapter.swapDirectory(directory, storageManager);
448 if (mFile == null || !mFile.equals(directory)) {
449 mList.setSelectionFromTop(0, 0);
450 }
451 mFile = directory;
452 }
453 }
454
455
456
457 @Override
458 public void onDismiss(EditNameDialog dialog) {
459 if (dialog.getResult()) {
460 String newFilename = dialog.getNewFilename();
461 Log_OC.d(TAG, "name edit dialog dismissed with new name " + newFilename);
462 mContainerActivity.getFileOperationsHelper().renameFile(mTargetFile, newFilename);
463 }
464 }
465
466
467 @Override
468 public void onConfirmation(String callerTag) {
469 if (callerTag.equals(FileDetailFragment.FTAG_CONFIRMATION)) {
470 FileDataStorageManager storageManager = mContainerActivity.getStorageManager();
471 if (storageManager.getFileById(mTargetFile.getFileId()) != null) {
472 mContainerActivity.getFileOperationsHelper().removeFile(mTargetFile, true);
473 }
474 }
475 }
476
477 @Override
478 public void onNeutral(String callerTag) {
479 mContainerActivity.getStorageManager().removeFile(mTargetFile, false, true); // TODO perform in background task / new thread
480 listDirectory();
481 mContainerActivity.onTransferStateChanged(mTargetFile, false, false);
482 }
483
484 @Override
485 public void onCancel(String callerTag) {
486 Log_OC.d(TAG, "REMOVAL CANCELED");
487 }
488
489 }