172341c49fd4b9fba2380b1ea067cdd56b949a0f
[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 import java.io.File;
21 import java.io.IOException;
22 import java.lang.ref.WeakReference;
23 //import java.net.URLEncoder;
24 import java.util.Vector;
25
26 import android.accounts.Account;
27 import android.accounts.AuthenticatorException;
28 import android.accounts.OperationCanceledException;
29 import android.content.Context;
30 import android.content.res.Resources;
31 import android.graphics.Bitmap;
32 import android.graphics.Bitmap.CompressFormat;
33 import android.graphics.BitmapFactory;
34 import android.graphics.drawable.BitmapDrawable;
35 import android.graphics.drawable.Drawable;
36 import android.media.ThumbnailUtils;
37 import android.os.AsyncTask;
38 import android.util.TypedValue;
39 import android.view.LayoutInflater;
40 import android.view.View;
41 import android.view.ViewGroup;
42 import android.widget.BaseAdapter;
43 import android.widget.ImageView;
44 import android.widget.ListAdapter;
45 import android.widget.ListView;
46 import android.widget.TextView;
47
48 import com.owncloud.android.R;
49 import com.owncloud.android.authentication.AccountUtils;
50 import com.owncloud.android.datamodel.FileDataStorageManager;
51 import com.owncloud.android.datamodel.OCFile;
52 import com.owncloud.android.files.services.FileDownloader.FileDownloaderBinder;
53 import com.owncloud.android.files.services.FileUploader.FileUploaderBinder;
54 import com.owncloud.android.lib.common.OwnCloudAccount;
55 import com.owncloud.android.lib.common.OwnCloudClient;
56 import com.owncloud.android.lib.common.OwnCloudClientManagerFactory;
57 import com.owncloud.android.lib.common.accounts.AccountUtils.AccountNotFoundException;
58 import com.owncloud.android.ui.activity.ComponentsGetter;
59 import com.owncloud.android.utils.DisplayUtils;
60
61 /*
62 import org.apache.http.HttpEntity;
63 import org.apache.http.HttpResponse;
64 import org.apache.http.auth.AuthScope;
65 import org.apache.http.auth.UsernamePasswordCredentials;
66 import org.apache.http.client.methods.HttpGet;
67 import org.apache.http.impl.client.DefaultHttpClient;
68 import org.apache.http.util.EntityUtils;
69 */
70
71
72 /**
73 * This Adapter populates a ListView with all files and folders in an ownCloud
74 * instance.
75 *
76 * @author Bartek Przybylski
77 * @Author Tobias Kaminsky
78 *
79 */
80 public class FileListListAdapter extends BaseAdapter implements ListAdapter {
81 private final static String PERMISSION_SHARED_WITH_ME = "S";
82
83 private Context mContext;
84 private OCFile mFile = null;
85 private Vector<OCFile> mFiles = null;
86 private boolean mJustFolders;
87
88 private FileDataStorageManager mStorageManager;
89 private Account mAccount;
90 private ComponentsGetter mTransferServiceGetter;
91
92 private final Object thumbnailDiskCacheLock = new Object();
93 private DiskLruImageCache mThumbnailCache;
94 private boolean mThumbnailCacheStarting = true;
95 private static final int DISK_CACHE_SIZE = 1024 * 1024 * 10; // 10MB
96 private static final CompressFormat mCompressFormat = CompressFormat.JPEG;
97 private static final int mCompressQuality = 70;
98 private OwnCloudClient mClient;
99 private Bitmap defaultImg;
100
101 public FileListListAdapter(
102 boolean justFolders,
103 Context context,
104 ComponentsGetter transferServiceGetter
105 ) {
106
107 mJustFolders = justFolders;
108 mContext = context;
109 mAccount = AccountUtils.getCurrentOwnCloudAccount(mContext);
110 mTransferServiceGetter = transferServiceGetter;
111 defaultImg = BitmapFactory.decodeResource(mContext.getResources(),
112 DisplayUtils.getResourceId("image/png", "default.png"));
113
114 // Initialise disk cache on background thread
115 new InitDiskCacheTask().execute();
116 }
117
118 class InitDiskCacheTask extends AsyncTask<File, Void, Void> {
119 @Override
120 protected Void doInBackground(File... params) {
121 synchronized (thumbnailDiskCacheLock) {
122 mThumbnailCache = new DiskLruImageCache(mContext, "thumbnailCache",
123 DISK_CACHE_SIZE, mCompressFormat, mCompressQuality);
124
125 try {
126 OwnCloudAccount ocAccount = new OwnCloudAccount(mAccount, mContext);
127 mClient = OwnCloudClientManagerFactory.getDefaultSingleton().
128 getClientFor(ocAccount, mContext);
129 } catch (AccountNotFoundException e) {
130 // TODO Auto-generated catch block
131 e.printStackTrace();
132 } catch (AuthenticatorException e) {
133 // TODO Auto-generated catch block
134 e.printStackTrace();
135 } catch (OperationCanceledException e) {
136 // TODO Auto-generated catch block
137 e.printStackTrace();
138 } catch (IOException e) {
139 // TODO Auto-generated catch block
140 e.printStackTrace();
141 }
142
143 mThumbnailCacheStarting = false; // Finished initialization
144 thumbnailDiskCacheLock.notifyAll(); // Wake any waiting threads
145 }
146 return null;
147 }
148 }
149
150 static class AsyncDrawable extends BitmapDrawable {
151 private final WeakReference<ThumbnailGenerationTask> bitmapWorkerTaskReference;
152
153 public AsyncDrawable(Resources res, Bitmap bitmap,
154 ThumbnailGenerationTask bitmapWorkerTask) {
155 super(res, bitmap);
156 bitmapWorkerTaskReference =
157 new WeakReference<ThumbnailGenerationTask>(bitmapWorkerTask);
158 }
159
160 public ThumbnailGenerationTask getBitmapWorkerTask() {
161 return bitmapWorkerTaskReference.get();
162 }
163 }
164
165 class ThumbnailGenerationTask extends AsyncTask<OCFile, Void, Bitmap> {
166 private final WeakReference<ImageView> imageViewReference;
167 private OCFile file;
168
169
170 public ThumbnailGenerationTask(ImageView imageView) {
171 // Use a WeakReference to ensure the ImageView can be garbage collected
172 imageViewReference = new WeakReference<ImageView>(imageView);
173 }
174
175 // Decode image in background.
176 @Override
177 protected Bitmap doInBackground(OCFile... params) {
178 file = params[0];
179 final String imageKey = String.valueOf(file.getRemoteId());
180
181 // Check disk cache in background thread
182 Bitmap thumbnail = getBitmapFromDiskCache(imageKey);
183
184 // Not found in disk cache
185 if (thumbnail == null) {
186 // Converts dp to pixel
187 Resources r = mContext.getResources();
188 int px = (int) Math.round(TypedValue.applyDimension(
189 TypedValue.COMPLEX_UNIT_DIP, 150, r.getDisplayMetrics()
190 ));
191
192 if (file.isDown()){
193 Bitmap bitmap = BitmapFactory.decodeFile(file.getStoragePath());
194 thumbnail = ThumbnailUtils.extractThumbnail(bitmap, px, px);
195
196 // Add thumbnail to cache
197 addBitmapToCache(imageKey, thumbnail);
198
199 } else {
200 // Download thumbnail from server
201 // Commented out as maybe changes to client library are needed
202 // DefaultHttpClient httpclient = new DefaultHttpClient();
203 // try {
204 // httpclient.getCredentialsProvider().setCredentials(
205 // new AuthScope(mClient.getBaseUri().toString().replace("https://", ""), 443),
206 // new UsernamePasswordCredentials(mClient.getCredentials().getUsername(), mClient.getCredentials().getAuthToken()));
207 //
208 //
209 // HttpGet httpget = new HttpGet(mClient.getBaseUri() + "/ocs/v1.php/thumbnail?x=50&y=50&path=" + URLEncoder.encode(file.getRemotePath(), "UTF-8"));
210 // HttpResponse response = httpclient.execute(httpget);
211 // HttpEntity entity = response.getEntity();
212 //
213 // if (entity != null) {
214 // byte[] bytes = EntityUtils.toByteArray(entity);
215 // Bitmap bitmap = BitmapFactory.decodeByteArray(bytes, 0, bytes.length);
216 // thumbnail = ThumbnailUtils.extractThumbnail(bitmap, px, px);
217 //
218 // // Add thumbnail to cache
219 // if (thumbnail != null){
220 // addBitmapToCache(imageKey, thumbnail);
221 // }
222 // }
223 // } catch(Exception e){
224 // e.printStackTrace();
225 // }finally {
226 // httpclient.getConnectionManager().shutdown();
227 // }
228 }
229 }
230 return thumbnail;
231 }
232
233 protected void onPostExecute(Bitmap bitmap){
234 if (isCancelled()) {
235 bitmap = null;
236 }
237
238 if (imageViewReference != null && bitmap != null) {
239 final ImageView imageView = imageViewReference.get();
240 final ThumbnailGenerationTask bitmapWorkerTask =
241 getBitmapWorkerTask(imageView);
242 if (this == bitmapWorkerTask && imageView != null) {
243 imageView.setImageBitmap(bitmap);
244 }
245 }
246 }
247 }
248
249 public void addBitmapToCache(String key, Bitmap bitmap) {
250 synchronized (thumbnailDiskCacheLock) {
251 if (mThumbnailCache != null && mThumbnailCache.getBitmap(key) == null) {
252 mThumbnailCache.put(key, bitmap);
253 }
254 }
255 }
256
257 public Bitmap getBitmapFromDiskCache(String key) {
258 synchronized (thumbnailDiskCacheLock) {
259 // Wait while disk cache is started from background thread
260 while (mThumbnailCacheStarting) {
261 try {
262 thumbnailDiskCacheLock.wait();
263 } catch (InterruptedException e) {}
264 }
265 if (mThumbnailCache != null) {
266 return (Bitmap) mThumbnailCache.getBitmap(key);
267 }
268 }
269 return null;
270 }
271
272 @Override
273 public boolean areAllItemsEnabled() {
274 return true;
275 }
276
277 @Override
278 public boolean isEnabled(int position) {
279 return true;
280 }
281
282 @Override
283 public int getCount() {
284 return mFiles != null ? mFiles.size() : 0;
285 }
286
287 @Override
288 public Object getItem(int position) {
289 if (mFiles == null || mFiles.size() <= position)
290 return null;
291 return mFiles.get(position);
292 }
293
294 @Override
295 public long getItemId(int position) {
296 if (mFiles == null || mFiles.size() <= position)
297 return 0;
298 return mFiles.get(position).getFileId();
299 }
300
301 @Override
302 public int getItemViewType(int position) {
303 return 0;
304 }
305
306 @Override
307 public View getView(int position, View convertView, ViewGroup parent) {
308 View view = convertView;
309 if (view == null) {
310 LayoutInflater inflator = (LayoutInflater) mContext
311 .getSystemService(Context.LAYOUT_INFLATER_SERVICE);
312 view = inflator.inflate(R.layout.list_item, null);
313 }
314
315 if (mFiles != null && mFiles.size() > position) {
316 OCFile file = mFiles.get(position);
317 TextView fileName = (TextView) view.findViewById(R.id.Filename);
318 String name = file.getFileName();
319
320 fileName.setText(name);
321 ImageView fileIcon = (ImageView) view.findViewById(R.id.imageView1);
322 ImageView sharedIconV = (ImageView) view.findViewById(R.id.sharedIcon);
323 ImageView sharedWithMeIconV = (ImageView) view.findViewById(R.id.sharedWithMeIcon);
324 sharedWithMeIconV.setVisibility(View.GONE);
325
326 ImageView localStateView = (ImageView) view.findViewById(R.id.imageView2);
327 localStateView.bringToFront();
328 FileDownloaderBinder downloaderBinder =
329 mTransferServiceGetter.getFileDownloaderBinder();
330 FileUploaderBinder uploaderBinder = mTransferServiceGetter.getFileUploaderBinder();
331 if (downloaderBinder != null && downloaderBinder.isDownloading(mAccount, file)) {
332 localStateView.setImageResource(R.drawable.downloading_file_indicator);
333 localStateView.setVisibility(View.VISIBLE);
334 } else if (uploaderBinder != null && uploaderBinder.isUploading(mAccount, file)) {
335 localStateView.setImageResource(R.drawable.uploading_file_indicator);
336 localStateView.setVisibility(View.VISIBLE);
337 } else if (file.isDown()) {
338 localStateView.setImageResource(R.drawable.local_file_indicator);
339 localStateView.setVisibility(View.VISIBLE);
340 } else {
341 localStateView.setVisibility(View.INVISIBLE);
342 }
343
344 TextView fileSizeV = (TextView) view.findViewById(R.id.file_size);
345 TextView lastModV = (TextView) view.findViewById(R.id.last_mod);
346 ImageView checkBoxV = (ImageView) view.findViewById(R.id.custom_checkbox);
347
348 if (!file.isFolder()) {
349 fileSizeV.setVisibility(View.VISIBLE);
350 fileSizeV.setText(DisplayUtils.bytesToHumanReadable(file.getFileLength()));
351 lastModV.setVisibility(View.VISIBLE);
352 lastModV.setText(
353 DisplayUtils.unixTimeToHumanReadable(file.getModificationTimestamp())
354 );
355 // this if-else is needed even thoe fav icon is visible by default
356 // because android reuses views in listview
357 if (!file.keepInSync()) {
358 view.findViewById(R.id.imageView3).setVisibility(View.GONE);
359 } else {
360 view.findViewById(R.id.imageView3).setVisibility(View.VISIBLE);
361 }
362
363 ListView parentList = (ListView)parent;
364 if (parentList.getChoiceMode() == ListView.CHOICE_MODE_NONE) {
365 checkBoxV.setVisibility(View.GONE);
366 } else {
367 if (parentList.isItemChecked(position)) {
368 checkBoxV.setImageResource(android.R.drawable.checkbox_on_background);
369 } else {
370 checkBoxV.setImageResource(android.R.drawable.checkbox_off_background);
371 }
372 checkBoxV.setVisibility(View.VISIBLE);
373 }
374
375 // get Thumbnail if file is image
376 if (file.isImage()){
377 // Thumbnail in Cache?
378 Bitmap thumbnail = getBitmapFromDiskCache(String.valueOf(file.getRemoteId()));
379 if (thumbnail != null){
380 fileIcon.setImageBitmap(thumbnail);
381 } else {
382 // generate new Thumbnail
383 if (cancelPotentialWork(file, fileIcon)) {
384 final ThumbnailGenerationTask task =
385 new ThumbnailGenerationTask(fileIcon);
386 final AsyncDrawable asyncDrawable =
387 new AsyncDrawable(mContext.getResources(), defaultImg, task);
388 fileIcon.setImageDrawable(asyncDrawable);
389 task.execute(file);
390 }
391 }
392 } else {
393 fileIcon.setImageResource(
394 DisplayUtils.getResourceId(file.getMimetype(), file.getFileName())
395 );
396 }
397
398 if (checkIfFileIsSharedWithMe(file)) {
399 sharedWithMeIconV.setVisibility(View.VISIBLE);
400 }
401 }
402 else {
403 fileSizeV.setVisibility(View.INVISIBLE);
404 //fileSizeV.setText(DisplayUtils.bytesToHumanReadable(file.getFileLength()));
405 lastModV.setVisibility(View.VISIBLE);
406 lastModV.setText(
407 DisplayUtils.unixTimeToHumanReadable(file.getModificationTimestamp())
408 );
409 checkBoxV.setVisibility(View.GONE);
410 view.findViewById(R.id.imageView3).setVisibility(View.GONE);
411
412 if (checkIfFileIsSharedWithMe(file)) {
413 fileIcon.setImageResource(R.drawable.shared_with_me_folder);
414 sharedWithMeIconV.setVisibility(View.VISIBLE);
415 } else {
416 fileIcon.setImageResource(
417 DisplayUtils.getResourceId(file.getMimetype(), file.getFileName())
418 );
419 }
420
421 // If folder is sharedByLink, icon folder must be changed to
422 // folder-public one
423 if (file.isShareByLink()) {
424 fileIcon.setImageResource(R.drawable.folder_public);
425 }
426 }
427
428 if (file.isShareByLink()) {
429 sharedIconV.setVisibility(View.VISIBLE);
430 } else {
431 sharedIconV.setVisibility(View.GONE);
432 }
433 }
434
435 return view;
436 }
437
438 public static boolean cancelPotentialWork(OCFile file, ImageView imageView) {
439 final ThumbnailGenerationTask bitmapWorkerTask = getBitmapWorkerTask(imageView);
440
441 if (bitmapWorkerTask != null) {
442 final OCFile bitmapData = bitmapWorkerTask.file;
443 // If bitmapData is not yet set or it differs from the new data
444 if (bitmapData == null || bitmapData != file) {
445 // Cancel previous task
446 bitmapWorkerTask.cancel(true);
447 } else {
448 // The same work is already in progress
449 return false;
450 }
451 }
452 // No task associated with the ImageView, or an existing task was cancelled
453 return true;
454 }
455
456 private static ThumbnailGenerationTask getBitmapWorkerTask(ImageView imageView) {
457 if (imageView != null) {
458 final Drawable drawable = imageView.getDrawable();
459 if (drawable instanceof AsyncDrawable) {
460 final AsyncDrawable asyncDrawable = (AsyncDrawable) drawable;
461 return asyncDrawable.getBitmapWorkerTask();
462 }
463 }
464 return null;
465 }
466
467 @Override
468 public int getViewTypeCount() {
469 return 1;
470 }
471
472 @Override
473 public boolean hasStableIds() {
474 return true;
475 }
476
477 @Override
478 public boolean isEmpty() {
479 return (mFiles == null || mFiles.isEmpty());
480 }
481
482 /**
483 * Change the adapted directory for a new one
484 * @param directory New file to adapt. Can be NULL, meaning
485 * "no content to adapt".
486 * @param updatedStorageManager Optional updated storage manager; used to replace
487 * mStorageManager if is different (and not NULL)
488 */
489 public void swapDirectory(OCFile directory, FileDataStorageManager updatedStorageManager) {
490 mFile = directory;
491 if (updatedStorageManager != null && updatedStorageManager != mStorageManager) {
492 mStorageManager = updatedStorageManager;
493 mAccount = AccountUtils.getCurrentOwnCloudAccount(mContext);
494 }
495 if (mStorageManager != null) {
496 mFiles = mStorageManager.getFolderContent(mFile);
497 if (mJustFolders) {
498 mFiles = getFolders(mFiles);
499 }
500 } else {
501 mFiles = null;
502 }
503 notifyDataSetChanged();
504 }
505
506
507 /**
508 * Filter for getting only the folders
509 * @param files
510 * @return Vector<OCFile>
511 */
512 public Vector<OCFile> getFolders(Vector<OCFile> files) {
513 Vector<OCFile> ret = new Vector<OCFile>();
514 OCFile current = null;
515 for (int i=0; i<files.size(); i++) {
516 current = files.get(i);
517 if (current.isFolder()) {
518 ret.add(current);
519 }
520 }
521 return ret;
522 }
523
524
525 /**
526 * Check if parent folder does not include 'S' permission and if file/folder
527 * is shared with me
528 *
529 * @param file: OCFile
530 * @return boolean: True if it is shared with me and false if it is not
531 */
532 private boolean checkIfFileIsSharedWithMe(OCFile file) {
533 return (mFile.getPermissions() != null
534 && !mFile.getPermissions().contains(PERMISSION_SHARED_WITH_ME)
535 && file.getPermissions() != null
536 && file.getPermissions().contains(PERMISSION_SHARED_WITH_ME));
537 }
538 }