62a772ef769070dbcb0aa518d3b69d5d25699c49
[pub/Android/ownCloud.git] / src / com / owncloud / android / ui / adapter / FileListListAdapter.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.adapter;
19
20
21 import java.io.File;
22 import java.util.Collections;
23 import java.util.Comparator;
24 import java.util.Vector;
25
26 import third_parties.daveKoeller.AlphanumComparator;
27 import android.accounts.Account;
28 import android.content.Context;
29 import android.content.SharedPreferences;
30 import android.graphics.Bitmap;
31 import android.graphics.BitmapFactory;
32 import android.media.ThumbnailUtils;
33 import android.preference.PreferenceManager;
34 import android.text.format.DateUtils;
35 import android.view.LayoutInflater;
36 import android.view.View;
37 import android.view.ViewGroup;
38 import android.widget.BaseAdapter;
39 import android.widget.Filter;
40 import android.widget.Filterable;
41 import android.widget.GridView;
42 import android.widget.ImageView;
43 import android.widget.ListAdapter;
44 import android.widget.TextView;
45
46 import com.owncloud.android.R;
47 import com.owncloud.android.authentication.AccountUtils;
48 import com.owncloud.android.datamodel.FileDataStorageManager;
49 import com.owncloud.android.datamodel.OCFile;
50 import com.owncloud.android.datamodel.ThumbnailsCacheManager;
51 import com.owncloud.android.files.services.FileDownloader.FileDownloaderBinder;
52 import com.owncloud.android.files.services.FileUploader.FileUploaderBinder;
53 import com.owncloud.android.ui.activity.ComponentsGetter;
54 import com.owncloud.android.utils.DisplayUtils;
55 import com.owncloud.android.utils.FileStorageUtils;
56
57
58 /**
59 * This Adapter populates a ListView with all files and folders in an ownCloud
60 * instance.
61 *
62 * @author Bartek Przybylski
63 * @author Tobias Kaminsky
64 * @author David A. Velasco
65 */
66 public class FileListListAdapter extends BaseAdapter implements ListAdapter {
67 private final static String PERMISSION_SHARED_WITH_ME = "S";
68
69 private Context mContext;
70 private OCFile mFile = null;
71 private Vector<OCFile> mFiles = null;
72 private Vector<OCFile> mFilesOrig = new Vector<OCFile>();
73 private boolean mJustFolders;
74
75 private FileDataStorageManager mStorageManager;
76 private Account mAccount;
77 private ComponentsGetter mTransferServiceGetter;
78 private enum ViewType {LIST_ITEM, GRID_IMAGE, GRID_ITEM };
79 private Integer mSortOrder;
80 public static final Integer SORT_NAME = 0;
81 public static final Integer SORT_DATE = 1;
82 public static final Integer SORT_SIZE = 2;
83 private Boolean mSortAscending;
84 private SharedPreferences mAppPreferences;
85
86 public FileListListAdapter(
87 boolean justFolders,
88 Context context,
89 ComponentsGetter transferServiceGetter
90 ) {
91
92 mJustFolders = justFolders;
93 mContext = context;
94 mAccount = AccountUtils.getCurrentOwnCloudAccount(mContext);
95 mTransferServiceGetter = transferServiceGetter;
96
97 mAppPreferences = PreferenceManager
98 .getDefaultSharedPreferences(mContext);
99
100 // Read sorting order, default to sort by name ascending
101 FileStorageUtils.mSortOrder = mAppPreferences.getInt("sortOrder", 0);
102 FileStorageUtils.mSortAscending = mAppPreferences.getBoolean("sortAscending", true);
103
104
105 // initialise thumbnails cache on background thread
106 new ThumbnailsCacheManager.InitDiskCacheTask().execute();
107 }
108
109 @Override
110 public boolean areAllItemsEnabled() {
111 return true;
112 }
113
114 @Override
115 public boolean isEnabled(int position) {
116 return true;
117 }
118
119 @Override
120 public int getCount() {
121 return mFiles != null ? mFiles.size() : 0;
122 }
123
124 @Override
125 public Object getItem(int position) {
126 if (mFiles == null || mFiles.size() <= position)
127 return null;
128 return mFiles.get(position);
129 }
130
131 @Override
132 public long getItemId(int position) {
133 if (mFiles == null || mFiles.size() <= position)
134 return 0;
135 return mFiles.get(position).getFileId();
136 }
137
138 @Override
139 public int getItemViewType(int position) {
140 return 0;
141 }
142
143 @Override
144 public View getView(int position, View convertView, ViewGroup parent) {
145 // decide image vs. file view
146 double countImages = 0;
147 double countFiles = 0;
148
149 for (OCFile file : mFiles){
150 if (!file.isFolder()){
151 countFiles++;
152
153 if (file.isImage()){
154 countImages++;
155 }
156 }
157 }
158
159 // TODO threshold as constant in Preferences
160 // > 50% Images --> image view
161 boolean fileView = true;
162 if ((countImages / countFiles) >= 0.5){
163 fileView = false;
164 } else {
165 fileView = true;
166 }
167
168 View view = convertView;
169 OCFile file = null;
170 LayoutInflater inflator = (LayoutInflater) mContext
171 .getSystemService(Context.LAYOUT_INFLATER_SERVICE);
172
173 if (mFiles != null && mFiles.size() > position) {
174 file = mFiles.get(position);
175 }
176
177 // Find out which layout should be displayed
178 ViewType viewType;
179 if (fileView){
180 viewType = ViewType.LIST_ITEM;
181 } else if (file.isImage()){
182 viewType = ViewType.GRID_IMAGE;
183 } else {
184 viewType = ViewType.GRID_ITEM;
185 }
186
187 // Create View
188 switch (viewType){
189 case GRID_IMAGE:
190 view = inflator.inflate(R.layout.grid_image, null);
191 break;
192 case GRID_ITEM:
193 view = inflator.inflate(R.layout.grid_item, null);
194 break;
195 case LIST_ITEM:
196 view = inflator.inflate(R.layout.list_item, null);
197 break;
198 }
199
200 view.invalidate();
201
202 if (file != null){
203
204 ImageView fileIcon = (ImageView) view.findViewById(R.id.thumbnail);
205 TextView fileName;
206 String name;
207
208 switch (viewType){
209 case LIST_ITEM:
210 fileName = (TextView) view.findViewById(R.id.Filename);
211 name = file.getFileName();
212 fileName.setText(name);
213
214 TextView fileSizeV = (TextView) view.findViewById(R.id.file_size);
215 TextView lastModV = (TextView) view.findViewById(R.id.last_mod);
216 ImageView checkBoxV = (ImageView) view.findViewById(R.id.custom_checkbox);
217
218 lastModV.setVisibility(View.VISIBLE);
219 lastModV.setText(showRelativeTimestamp(file));
220
221 checkBoxV.setVisibility(View.GONE);
222
223 fileSizeV.setVisibility(View.VISIBLE);
224 fileSizeV.setText(DisplayUtils.bytesToHumanReadable(file.getFileLength()));
225
226 ImageView sharedIconV = (ImageView) view.findViewById(R.id.sharedIcon);
227
228
229 if (file.isShareByLink()) {
230 sharedIconV.setVisibility(View.VISIBLE);
231 } else {
232 sharedIconV.setVisibility(View.GONE);
233 }
234
235 ImageView localStateView = (ImageView) view.findViewById(R.id.localFileIndicator);
236
237 if (!file.isFolder()) {
238 GridView parentList = (GridView)parent;
239 if (parentList.getChoiceMode() == GridView.CHOICE_MODE_NONE) {
240 checkBoxV.setVisibility(View.GONE);
241 } else {
242 if (parentList.isItemChecked(position)) {
243 checkBoxV.setImageResource(android.R.drawable.checkbox_on_background);
244 } else {
245 checkBoxV.setImageResource(android.R.drawable.checkbox_off_background);
246 }
247 checkBoxV.setVisibility(View.VISIBLE);
248 }
249
250 localStateView.bringToFront();
251 FileDownloaderBinder downloaderBinder = mTransferServiceGetter.getFileDownloaderBinder();
252 FileUploaderBinder uploaderBinder = mTransferServiceGetter.getFileUploaderBinder();
253 if (downloaderBinder != null && downloaderBinder.isDownloading(mAccount, file)) {
254 localStateView.setImageResource(R.drawable.downloading_file_indicator);
255 localStateView.setVisibility(View.VISIBLE);
256 } else if (uploaderBinder != null && uploaderBinder.isUploading(mAccount, file)) {
257 localStateView.setImageResource(R.drawable.uploading_file_indicator);
258 localStateView.setVisibility(View.VISIBLE);
259 } else if (file.isDown()) {
260 localStateView.setImageResource(R.drawable.local_file_indicator);
261 localStateView.setVisibility(View.VISIBLE);
262 } else {
263 localStateView.setVisibility(View.INVISIBLE);
264 }
265
266 ImageView sharedWithMeIconV = (ImageView) view.findViewById(R.id.sharedWithMeIcon);
267 if (checkIfFileIsSharedWithMe(file)) {
268 sharedWithMeIconV.setVisibility(View.VISIBLE);
269 } else {
270 sharedWithMeIconV.setVisibility(View.GONE);
271 }
272 } else {
273 localStateView.setVisibility(View.INVISIBLE);
274 }
275 break;
276 case GRID_ITEM:
277 fileName = (TextView) view.findViewById(R.id.Filename);
278 name = file.getFileName();
279 fileName.setText(name);
280 break;
281 case GRID_IMAGE:
282 break;
283 }
284
285 // For all Views
286
287 // this if-else is needed even though favorite icon is visible by default
288 // because android reuses views in listview
289 if (!file.keepInSync()) {
290 view.findViewById(R.id.favoriteIcon).setVisibility(View.GONE);
291 } else {
292 view.findViewById(R.id.favoriteIcon).setVisibility(View.VISIBLE);
293 }
294
295 // No Folder
296 if (!file.isFolder()) {
297 if (file.isImage() && file.getRemoteId() != null){
298 // Thumbnail in Cache?
299 Bitmap thumbnail = ThumbnailsCacheManager.getBitmapFromDiskCache(
300 String.valueOf(file.getRemoteId())
301 );
302 if (thumbnail != null && !file.needsUpdateThumbnail()){
303 fileIcon.setImageBitmap(thumbnail);
304 } else {
305 // generate new Thumbnail
306 if (ThumbnailsCacheManager.cancelPotentialWork(file, fileIcon)) {
307 final ThumbnailsCacheManager.ThumbnailGenerationTask task =
308 new ThumbnailsCacheManager.ThumbnailGenerationTask(
309 fileIcon, mStorageManager, mAccount
310 );
311 if (thumbnail == null) {
312 thumbnail = ThumbnailsCacheManager.mDefaultImg;
313 }
314 final ThumbnailsCacheManager.AsyncDrawable asyncDrawable =
315 new ThumbnailsCacheManager.AsyncDrawable(
316 mContext.getResources(),
317 thumbnail,
318 task
319 );
320 fileIcon.setImageDrawable(asyncDrawable);
321 task.execute(file);
322 }
323 }
324 } else {
325 fileIcon.setImageResource(DisplayUtils.getFileTypeIconId(file.getMimetype(), file.getFileName()));
326 }
327 } else {
328 // Folder
329 if (checkIfFileIsSharedWithMe(file)) {
330 fileIcon.setImageResource(R.drawable.shared_with_me_folder);
331 } else if (file.isShareByLink()) {
332 // If folder is sharedByLink, icon folder must be changed to
333 // folder-public one
334 fileIcon.setImageResource(R.drawable.folder_public);
335 } else {
336 fileIcon.setImageResource(
337 DisplayUtils.getFileTypeIconId(file.getMimetype(), file.getFileName())
338 );
339 }
340 }
341 }
342
343 return view;
344 }
345
346 /**
347 * Local Folder size in human readable format
348 *
349 * @param path
350 * String
351 * @return Size in human readable format
352 */
353 private String getFolderSizeHuman(String path) {
354
355 File dir = new File(path);
356
357 if (dir.exists()) {
358 long bytes = FileStorageUtils.getFolderSize(dir);
359 return DisplayUtils.bytesToHumanReadable(bytes);
360 }
361
362 return "0 B";
363 }
364
365 /**
366 * Local Folder size
367 * @param dir File
368 * @return Size in bytes
369 */
370 private long getFolderSize(File dir) {
371 if (dir.exists()) {
372 long result = 0;
373 File[] fileList = dir.listFiles();
374 for(int i = 0; i < fileList.length; i++) {
375 if(fileList[i].isDirectory()) {
376 result += getFolderSize(fileList[i]);
377 } else {
378 result += fileList[i].length();
379 }
380 }
381 return result;
382 }
383 return 0;
384 }
385
386 @Override
387 public int getViewTypeCount() {
388 return 1;
389 }
390
391 @Override
392 public boolean hasStableIds() {
393 return true;
394 }
395
396 @Override
397 public boolean isEmpty() {
398 return (mFiles == null || mFiles.isEmpty());
399 }
400
401 /**
402 * Change the adapted directory for a new one
403 * @param directory New file to adapt. Can be NULL, meaning
404 * "no content to adapt".
405 * @param updatedStorageManager Optional updated storage manager; used to replace
406 * mStorageManager if is different (and not NULL)
407 */
408 public void swapDirectory(OCFile directory, FileDataStorageManager updatedStorageManager) {
409 mFile = directory;
410 if (updatedStorageManager != null && updatedStorageManager != mStorageManager) {
411 mStorageManager = updatedStorageManager;
412 mAccount = AccountUtils.getCurrentOwnCloudAccount(mContext);
413 }
414 if (mStorageManager != null) {
415 mFiles = mStorageManager.getFolderContent(mFile);
416 mFilesOrig.clear();
417 mFilesOrig.addAll(mFiles);
418
419 if (mJustFolders) {
420 mFiles = getFolders(mFiles);
421 }
422 } else {
423 mFiles = null;
424 }
425
426 mFiles = FileStorageUtils.sortFolder(mFiles);
427 notifyDataSetChanged();
428 }
429
430
431 /**
432 * Filter for getting only the folders
433 * @param files
434 * @return Vector<OCFile>
435 */
436 public Vector<OCFile> getFolders(Vector<OCFile> files) {
437 Vector<OCFile> ret = new Vector<OCFile>();
438 OCFile current = null;
439 for (int i=0; i<files.size(); i++) {
440 current = files.get(i);
441 if (current.isFolder()) {
442 ret.add(current);
443 }
444 }
445 return ret;
446 }
447
448
449 /**
450 * Check if parent folder does not include 'S' permission and if file/folder
451 * is shared with me
452 *
453 * @param file: OCFile
454 * @return boolean: True if it is shared with me and false if it is not
455 */
456 private boolean checkIfFileIsSharedWithMe(OCFile file) {
457 return (mFile.getPermissions() != null
458 && !mFile.getPermissions().contains(PERMISSION_SHARED_WITH_ME)
459 && file.getPermissions() != null
460 && file.getPermissions().contains(PERMISSION_SHARED_WITH_ME));
461 }
462
463 /**
464 * Sorts list by Date
465 * @param sortAscending true: ascending, false: descending
466 */
467 private void sortByDate(boolean sortAscending){
468 final Integer val;
469 if (sortAscending){
470 val = 1;
471 } else {
472 val = -1;
473 }
474
475 Collections.sort(mFiles, new Comparator<OCFile>() {
476 public int compare(OCFile o1, OCFile o2) {
477 if (o1.isFolder() && o2.isFolder()) {
478 Long obj1 = o1.getModificationTimestamp();
479 return val * obj1.compareTo(o2.getModificationTimestamp());
480 }
481 else if (o1.isFolder()) {
482 return -1;
483 } else if (o2.isFolder()) {
484 return 1;
485 } else if (o1.getModificationTimestamp() == 0 || o2.getModificationTimestamp() == 0){
486 return 0;
487 } else {
488 Long obj1 = o1.getModificationTimestamp();
489 return val * obj1.compareTo(o2.getModificationTimestamp());
490 }
491 }
492 });
493 }
494
495 /**
496 * Sorts list by Size
497 * @param sortAscending true: ascending, false: descending
498 */
499 private void sortBySize(boolean sortAscending){
500 final Integer val;
501 if (sortAscending){
502 val = 1;
503 } else {
504 val = -1;
505 }
506
507 Collections.sort(mFiles, new Comparator<OCFile>() {
508 public int compare(OCFile o1, OCFile o2) {
509 if (o1.isFolder() && o2.isFolder()) {
510 Long obj1 = getFolderSize(new File(FileStorageUtils.getDefaultSavePathFor(mAccount.name, o1)));
511 return val * obj1.compareTo(getFolderSize(new File(FileStorageUtils.getDefaultSavePathFor(mAccount.name, o2))));
512 }
513 else if (o1.isFolder()) {
514 return -1;
515 } else if (o2.isFolder()) {
516 return 1;
517 } else if (o1.getFileLength() == 0 || o2.getFileLength() == 0){
518 return 0;
519 } else {
520 Long obj1 = o1.getFileLength();
521 return val * obj1.compareTo(o2.getFileLength());
522 }
523 }
524 });
525 }
526
527 /**
528 * Sorts list by Name
529 * @param sortAscending true: ascending, false: descending
530 */
531 private void sortByName(boolean sortAscending){
532 final Integer val;
533 if (sortAscending){
534 val = 1;
535 } else {
536 val = -1;
537 }
538
539 Collections.sort(mFiles, new Comparator<OCFile>() {
540 public int compare(OCFile o1, OCFile o2) {
541 if (o1.isFolder() && o2.isFolder()) {
542 return val * o1.getRemotePath().toLowerCase().compareTo(o2.getRemotePath().toLowerCase());
543 } else if (o1.isFolder()) {
544 return -1;
545 } else if (o2.isFolder()) {
546 return 1;
547 }
548 return val * new AlphanumComparator().compare(o1, o2);
549 }
550 });
551 }
552
553 public void setSortOrder(Integer order, boolean ascending) {
554 SharedPreferences.Editor editor = mAppPreferences.edit();
555 editor.putInt("sortOrder", order);
556 editor.putBoolean("sortAscending", ascending);
557 editor.commit();
558
559 FileStorageUtils.mSortOrder = order;
560 FileStorageUtils.mSortAscending = ascending;
561
562
563 mFiles = FileStorageUtils.sortFolder(mFiles);
564 notifyDataSetChanged();
565
566 }
567
568 private CharSequence showRelativeTimestamp(OCFile file){
569 return DisplayUtils.getRelativeDateTimeString(mContext, file.getModificationTimestamp(),
570 DateUtils.SECOND_IN_MILLIS, DateUtils.WEEK_IN_MILLIS, 0);
571 }
572 }