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