ad399762903c3238864b7d7ca31c713cd67498de
[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
306 // generate new Thumbnail
307 if (ThumbnailsCacheManager.cancelPotentialWork(file, fileIcon)) {
308 final ThumbnailsCacheManager.ThumbnailGenerationTask task =
309 new ThumbnailsCacheManager.ThumbnailGenerationTask(
310 fileIcon, mStorageManager, mAccount
311 );
312 if (thumbnail == null) {
313 thumbnail = ThumbnailsCacheManager.mDefaultImg;
314 }
315 final ThumbnailsCacheManager.AsyncDrawable asyncDrawable =
316 new ThumbnailsCacheManager.AsyncDrawable(
317 mContext.getResources(),
318 thumbnail,
319 task
320 );
321 fileIcon.setImageDrawable(asyncDrawable);
322 task.execute(file);
323
324 }
325 }
326 } else {
327 fileIcon.setImageResource(DisplayUtils.getFileTypeIconId(file.getMimetype(), file.getFileName()));
328 }
329 else {
330 fileIcon.setImageResource(DisplayUtils.getResourceId(file.getMimetype(), file.getFileName()));
331 }
332 } else {
333 // Folder
334 if (checkIfFileIsSharedWithMe(file)) {
335 fileIcon.setImageResource(R.drawable.shared_with_me_folder);
336 } else if (file.isShareByLink()) {
337 // If folder is sharedByLink, icon folder must be changed to
338 // folder-public one
339 fileIcon.setImageResource(R.drawable.folder_public);
340 } else {
341 fileIcon.setImageResource(
342 DisplayUtils.getFileTypeIconId(file.getMimetype(), file.getFileName())
343 );
344 }
345 }
346 }
347
348 return view;
349 }
350
351 /**
352 * Local Folder size in human readable format
353 *
354 * @param path
355 * String
356 * @return Size in human readable format
357 */
358 private String getFolderSizeHuman(String path) {
359
360 File dir = new File(path);
361
362 if (dir.exists()) {
363 long bytes = FileStorageUtils.getFolderSize(dir);
364 return DisplayUtils.bytesToHumanReadable(bytes);
365 }
366
367 return "0 B";
368 }
369
370 /**
371 * Local Folder size
372 * @param dir File
373 * @return Size in bytes
374 */
375 private long getFolderSize(File dir) {
376 if (dir.exists()) {
377 long result = 0;
378 File[] fileList = dir.listFiles();
379 for(int i = 0; i < fileList.length; i++) {
380 if(fileList[i].isDirectory()) {
381 result += getFolderSize(fileList[i]);
382 } else {
383 result += fileList[i].length();
384 }
385 }
386 return result;
387 }
388 return 0;
389 }
390
391 @Override
392 public int getViewTypeCount() {
393 return 1;
394 }
395
396 @Override
397 public boolean hasStableIds() {
398 return true;
399 }
400
401 @Override
402 public boolean isEmpty() {
403 return (mFiles == null || mFiles.isEmpty());
404 }
405
406 /**
407 * Change the adapted directory for a new one
408 * @param directory New file to adapt. Can be NULL, meaning
409 * "no content to adapt".
410 * @param updatedStorageManager Optional updated storage manager; used to replace
411 * mStorageManager if is different (and not NULL)
412 */
413 public void swapDirectory(OCFile directory, FileDataStorageManager updatedStorageManager) {
414 mFile = directory;
415 if (updatedStorageManager != null && updatedStorageManager != mStorageManager) {
416 mStorageManager = updatedStorageManager;
417 mAccount = AccountUtils.getCurrentOwnCloudAccount(mContext);
418 }
419 if (mStorageManager != null) {
420 mFiles = mStorageManager.getFolderContent(mFile);
421 mFilesOrig.clear();
422 mFilesOrig.addAll(mFiles);
423
424 if (mJustFolders) {
425 mFiles = getFolders(mFiles);
426 }
427 } else {
428 mFiles = null;
429 }
430
431 mFiles = FileStorageUtils.sortFolder(mFiles);
432 notifyDataSetChanged();
433 }
434
435
436 /**
437 * Filter for getting only the folders
438 * @param files
439 * @return Vector<OCFile>
440 */
441 public Vector<OCFile> getFolders(Vector<OCFile> files) {
442 Vector<OCFile> ret = new Vector<OCFile>();
443 OCFile current = null;
444 for (int i=0; i<files.size(); i++) {
445 current = files.get(i);
446 if (current.isFolder()) {
447 ret.add(current);
448 }
449 }
450 return ret;
451 }
452
453
454 /**
455 * Check if parent folder does not include 'S' permission and if file/folder
456 * is shared with me
457 *
458 * @param file: OCFile
459 * @return boolean: True if it is shared with me and false if it is not
460 */
461 private boolean checkIfFileIsSharedWithMe(OCFile file) {
462 return (mFile.getPermissions() != null
463 && !mFile.getPermissions().contains(PERMISSION_SHARED_WITH_ME)
464 && file.getPermissions() != null
465 && file.getPermissions().contains(PERMISSION_SHARED_WITH_ME));
466 }
467
468 /**
469 * Sorts list by Date
470 * @param sortAscending true: ascending, false: descending
471 */
472 private void sortByDate(boolean sortAscending){
473 final Integer val;
474 if (sortAscending){
475 val = 1;
476 } else {
477 val = -1;
478 }
479
480 Collections.sort(mFiles, new Comparator<OCFile>() {
481 public int compare(OCFile o1, OCFile o2) {
482 if (o1.isFolder() && o2.isFolder()) {
483 Long obj1 = o1.getModificationTimestamp();
484 return val * obj1.compareTo(o2.getModificationTimestamp());
485 }
486 else if (o1.isFolder()) {
487 return -1;
488 } else if (o2.isFolder()) {
489 return 1;
490 } else if (o1.getModificationTimestamp() == 0 || o2.getModificationTimestamp() == 0){
491 return 0;
492 } else {
493 Long obj1 = o1.getModificationTimestamp();
494 return val * obj1.compareTo(o2.getModificationTimestamp());
495 }
496 }
497 });
498 }
499
500 /**
501 * Sorts list by Size
502 * @param sortAscending true: ascending, false: descending
503 */
504 private void sortBySize(boolean sortAscending){
505 final Integer val;
506 if (sortAscending){
507 val = 1;
508 } else {
509 val = -1;
510 }
511
512 Collections.sort(mFiles, new Comparator<OCFile>() {
513 public int compare(OCFile o1, OCFile o2) {
514 if (o1.isFolder() && o2.isFolder()) {
515 Long obj1 = getFolderSize(new File(FileStorageUtils.getDefaultSavePathFor(mAccount.name, o1)));
516 return val * obj1.compareTo(getFolderSize(new File(FileStorageUtils.getDefaultSavePathFor(mAccount.name, o2))));
517 }
518 else if (o1.isFolder()) {
519 return -1;
520 } else if (o2.isFolder()) {
521 return 1;
522 } else if (o1.getFileLength() == 0 || o2.getFileLength() == 0){
523 return 0;
524 } else {
525 Long obj1 = o1.getFileLength();
526 return val * obj1.compareTo(o2.getFileLength());
527 }
528 }
529 });
530 }
531
532 /**
533 * Sorts list by Name
534 * @param sortAscending true: ascending, false: descending
535 */
536 private void sortByName(boolean sortAscending){
537 final Integer val;
538 if (sortAscending){
539 val = 1;
540 } else {
541 val = -1;
542 }
543
544 Collections.sort(mFiles, new Comparator<OCFile>() {
545 public int compare(OCFile o1, OCFile o2) {
546 if (o1.isFolder() && o2.isFolder()) {
547 return val * o1.getRemotePath().toLowerCase().compareTo(o2.getRemotePath().toLowerCase());
548 } else if (o1.isFolder()) {
549 return -1;
550 } else if (o2.isFolder()) {
551 return 1;
552 }
553 return val * new AlphanumComparator().compare(o1, o2);
554 }
555 });
556 }
557
558 public void setSortOrder(Integer order, boolean ascending) {
559 SharedPreferences.Editor editor = mAppPreferences.edit();
560 editor.putInt("sortOrder", order);
561 editor.putBoolean("sortAscending", ascending);
562 editor.commit();
563
564 FileStorageUtils.mSortOrder = order;
565 FileStorageUtils.mSortAscending = ascending;
566
567
568 mFiles = FileStorageUtils.sortFolder(mFiles);
569 notifyDataSetChanged();
570
571 }
572 sortDirectory();
573 }
574
575 private CharSequence showRelativeTimestamp(OCFile file){
576 return DisplayUtils.getRelativeDateTimeString(mContext, file.getModificationTimestamp(),
577 DateUtils.SECOND_IN_MILLIS, DateUtils.WEEK_IN_MILLIS, 0);
578 }
579 }