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