diff --git a/src/main/java/com/owncloud/android/datamodel/ThumbnailsCacheManager.java b/src/main/java/com/owncloud/android/datamodel/ThumbnailsCacheManager.java index df398fd8df6d..a9332ae42f59 100644 --- a/src/main/java/com/owncloud/android/datamodel/ThumbnailsCacheManager.java +++ b/src/main/java/com/owncloud/android/datamodel/ThumbnailsCacheManager.java @@ -38,6 +38,7 @@ import android.net.Uri; import android.os.AsyncTask; import android.provider.MediaStore; +import android.support.annotation.Nullable; import android.text.TextUtils; import android.view.Display; import android.view.MenuItem; @@ -53,6 +54,7 @@ import com.owncloud.android.lib.common.operations.RemoteOperation; import com.owncloud.android.lib.common.utils.Log_OC; import com.owncloud.android.lib.resources.status.OwnCloudVersion; +import com.owncloud.android.ui.TextDrawable; import com.owncloud.android.ui.adapter.DiskLruImageCache; import com.owncloud.android.ui.preview.PreviewImageFragment; import com.owncloud.android.utils.BitmapUtils; @@ -84,31 +86,25 @@ public class ThumbnailsCacheManager { private static final String TAG = ThumbnailsCacheManager.class.getSimpleName(); private static final String PNG_MIMETYPE = "image/png"; private static final String CACHE_FOLDER = "thumbnailCache"; + public static final String AVATAR = "avatar"; + private static final String ETAG = "ETag"; private static final Object mThumbnailsDiskCacheLock = new Object(); private static DiskLruImageCache mThumbnailCache = null; private static boolean mThumbnailCacheStarting = true; - + private static final int DISK_CACHE_SIZE = 1024 * 1024 * 200; // 200MB private static final CompressFormat mCompressFormat = CompressFormat.JPEG; private static final int mCompressQuality = 70; private static OwnCloudClient mClient = null; - public static final Bitmap mDefaultImg = - BitmapFactory.decodeResource( - MainApp.getAppContext().getResources(), - R.drawable.file_image - ); - - public static final Bitmap mDefaultVideo = - BitmapFactory.decodeResource( - MainApp.getAppContext().getResources(), - R.drawable.file_movie - ); + public static final Bitmap mDefaultImg = BitmapFactory.decodeResource(MainApp.getAppContext().getResources(), + R.drawable.file_image); + public static final Bitmap mDefaultVideo = BitmapFactory.decodeResource(MainApp.getAppContext().getResources(), + R.drawable.file_movie); public static class InitDiskCacheTask extends AsyncTask { - @Override protected Void doInBackground(File... params) { synchronized (mThumbnailsDiskCacheLock) { @@ -759,27 +755,32 @@ private Bitmap doFileInBackground(File file, Type type) { } } - public static class AvatarGenerationTask extends AsyncTask { + public static class AvatarGenerationTask extends AsyncTask { private final WeakReference mAvatarGenerationListener; private final Object mCallContext; + private final Resources mResources; + private final float mAvatarRadius; private Account mAccount; private String mUsername; public AvatarGenerationTask(AvatarGenerationListener avatarGenerationListener, Object callContext, - FileDataStorageManager storageManager, Account account) { + FileDataStorageManager storageManager, Account account, Resources resources, + float avatarRadius) { mAvatarGenerationListener = new WeakReference<>(avatarGenerationListener); mCallContext = callContext; if (storageManager == null) { throw new IllegalArgumentException("storageManager must not be NULL"); } mAccount = account; + mResources = resources; + mAvatarRadius = avatarRadius; } @SuppressFBWarnings("Dm") @Override - protected Bitmap doInBackground(String... params) { - Bitmap thumbnail = null; + protected Drawable doInBackground(String... params) { + Drawable thumbnail = null; try { if (mAccount != null) { @@ -802,14 +803,14 @@ protected Bitmap doInBackground(String... params) { return thumbnail; } - protected void onPostExecute(Bitmap bitmap) { - if (bitmap != null) { + protected void onPostExecute(Drawable drawable) { + if (drawable != null) { AvatarGenerationListener listener = mAvatarGenerationListener.get(); AvatarGenerationTask avatarWorkerTask = getAvatarWorkerTask(mCallContext); - if (this == avatarWorkerTask - && listener.shouldCallGeneratedCallback(mUsername, mCallContext)) { - listener.avatarGenerated(new BitmapDrawable(bitmap), mCallContext); - } + + if (this == avatarWorkerTask && listener.shouldCallGeneratedCallback(mUsername, mCallContext)) { + listener.avatarGenerated(drawable, mCallContext); + } } } @@ -823,63 +824,97 @@ private int getAvatarDimension(){ return Math.round(r.getDimension(R.dimen.file_avatar_size)); } - private Bitmap doAvatarInBackground() { + private @Nullable + Drawable doAvatarInBackground() { + Bitmap avatar = null; String username = mUsername; - final String imageKey = "a_" + username; + ArbitraryDataProvider arbitraryDataProvider = new ArbitraryDataProvider( + MainApp.getAppContext().getContentResolver()); - // Check disk cache in background thread - Bitmap avatar = getBitmapFromDiskCache(imageKey); + String eTag = arbitraryDataProvider.getValue(mAccount, AVATAR); - // Not found in disk cache - if (avatar == null) { + final String imageKey = "a_" + username + "_" + eTag; - int px = getAvatarDimension(); + try { + Thread.sleep(3000); + } catch (InterruptedException e) { + e.printStackTrace(); + } - // Download avatar from server - OwnCloudVersion serverOCVersion = AccountUtils.getServerVersion(mAccount); - if (mClient != null && serverOCVersion != null) { - if (serverOCVersion.supportsRemoteThumbnails()) { - GetMethod get = null; - try { + int px = getAvatarDimension(); + + // Download avatar from server + OwnCloudVersion serverOCVersion = AccountUtils.getServerVersion(mAccount); + if (mClient != null && serverOCVersion != null) { + if (serverOCVersion.supportsRemoteThumbnails()) { + GetMethod get = null; + try { + String userId = AccountManager.get(MainApp.getAppContext()).getUserData(mAccount, + com.owncloud.android.lib.common.accounts.AccountUtils.Constants.KEY_USER_ID); - String userId = AccountManager.get(MainApp.getAppContext()).getUserData(mAccount, - com.owncloud.android.lib.common.accounts.AccountUtils.Constants.KEY_USER_ID); + if (TextUtils.isEmpty(userId)) { + userId = AccountUtils.getAccountUsername(username); + } - if (TextUtils.isEmpty(userId)) { - userId = AccountUtils.getAccountUsername(username); - } + String uri = mClient.getBaseUri() + "/index.php/avatar/" + Uri.encode(userId) + "/" + px; + Log_OC.d("Avatar", "URI: " + uri); + get = new GetMethod(uri); + + if (!eTag.isEmpty()) { + get.setRequestHeader("If-None-Match", eTag); + } - String uri = mClient.getBaseUri() + "/index.php/avatar/" + Uri.encode(userId) + "/" + px; - Log_OC.d("Avatar", "URI: " + uri); - get = new GetMethod(uri); - int status = mClient.executeMethod(get); - if (status == HttpStatus.SC_OK) { + int status = mClient.executeMethod(get); + + // we are using eTag to download a new avatar only if it changed + switch (status) { + case HttpStatus.SC_OK: + // new avatar InputStream inputStream = get.getResponseBodyAsStream(); + + if (get.getResponseHeader(ETAG) != null) { + eTag = get.getResponseHeader(ETAG).getValue().replace("\"", ""); + arbitraryDataProvider.storeOrUpdateKeyValue(mAccount.name, AVATAR, eTag); + } + Bitmap bitmap = BitmapFactory.decodeStream(inputStream); avatar = ThumbnailUtils.extractThumbnail(bitmap, px, px); // Add avatar to cache if (avatar != null) { avatar = handlePNG(avatar, px); - addBitmapToCache(imageKey, avatar); + String newImageKey = "a_" + username + "_" + eTag; + addBitmapToCache(newImageKey, avatar); + } else { + return TextDrawable.createAvatar(mAccount.name, mAvatarRadius); } - } else { + break; + + case HttpStatus.SC_NOT_MODIFIED: + // old avatar + avatar = getBitmapFromDiskCache(imageKey); mClient.exhaustResponse(get.getResponseBodyAsStream()); - } - } catch (Exception e) { - Log_OC.e(TAG, "Error downloading avatar", e); - } finally { - if (get != null) { - get.releaseConnection(); - } + break; + + default: + // everything else + mClient.exhaustResponse(get.getResponseBodyAsStream()); + break; + + } + } catch (Exception e) { + Log_OC.e(TAG, "Error downloading avatar", e); + } finally { + if (get != null) { + get.releaseConnection(); } - } else { - Log_OC.d(TAG, "Server too old"); } + } else { + Log_OC.d(TAG, "Server too old"); } } - return avatar; + return BitmapUtils.bitmapToCircularBitmapDrawable(mResources, avatar); } } @@ -1077,13 +1112,9 @@ public AsyncMediaThumbnailDrawable(Resources res, Bitmap bitmap, public static class AsyncAvatarDrawable extends BitmapDrawable { private final WeakReference avatarWorkerTaskReference; - public AsyncAvatarDrawable( - Resources res, Bitmap bitmap, AvatarGenerationTask avatarWorkerTask - ) { - - super(res, bitmap); - avatarWorkerTaskReference = - new WeakReference(avatarWorkerTask); + public AsyncAvatarDrawable(Resources res, Drawable bitmap, AvatarGenerationTask avatarWorkerTask) { + super(res, BitmapUtils.drawableToBitmap(bitmap)); + avatarWorkerTaskReference = new WeakReference<>(avatarWorkerTask); } public AvatarGenerationTask getAvatarWorkerTask() { diff --git a/src/main/java/com/owncloud/android/ui/TextDrawable.java b/src/main/java/com/owncloud/android/ui/TextDrawable.java index 7dfe71f69639..5e6fb6e49a1b 100644 --- a/src/main/java/com/owncloud/android/ui/TextDrawable.java +++ b/src/main/java/com/owncloud/android/ui/TextDrawable.java @@ -30,6 +30,7 @@ import com.owncloud.android.authentication.AccountUtils; import com.owncloud.android.utils.BitmapUtils; +import com.owncloud.android.utils.NextcloudServer; import java.io.UnsupportedEncodingException; import java.security.NoSuchAlgorithmException; @@ -95,6 +96,7 @@ public TextDrawable(String text, int r, int g, int b, float radius) { * @throws NoSuchAlgorithmException if the specified algorithm is not available when calculating the color values */ @NonNull + @NextcloudServer(max = 12) public static TextDrawable createAvatar(String accountName, float radiusInDp) throws UnsupportedEncodingException, NoSuchAlgorithmException { String username = AccountUtils.getAccountUsername(accountName); diff --git a/src/main/java/com/owncloud/android/ui/activity/DrawerActivity.java b/src/main/java/com/owncloud/android/ui/activity/DrawerActivity.java index 69860aefaa95..8ea0734dbf67 100644 --- a/src/main/java/com/owncloud/android/ui/activity/DrawerActivity.java +++ b/src/main/java/com/owncloud/android/ui/activity/DrawerActivity.java @@ -641,9 +641,11 @@ public void updateAccountList() { // activate second/end account avatar if (mAvatars[1] != null) { + View accountEndView = findNavigationViewChildById(R.id.drawer_account_end); + accountEndView.setTag(mAvatars[1].name); + DisplayUtils.setAvatar(mAvatars[1], this, - mOtherAccountAvatarRadiusDimension, getResources(), getStorageManager(), - findNavigationViewChildById(R.id.drawer_account_end)); + mOtherAccountAvatarRadiusDimension, getResources(), getStorageManager(), accountEndView); mAccountEndAccountAvatar.setVisibility(View.VISIBLE); } else { mAccountEndAccountAvatar.setVisibility(View.GONE); @@ -651,9 +653,11 @@ mOtherAccountAvatarRadiusDimension, getResources(), getStorageManager(), // activate third/middle account avatar if (mAvatars[2] != null) { + View accountMiddleView = findNavigationViewChildById(R.id.drawer_account_middle); + accountMiddleView.setTag(mAvatars[2].name); + DisplayUtils.setAvatar(mAvatars[2], this, - mOtherAccountAvatarRadiusDimension, getResources(), getStorageManager(), - findNavigationViewChildById(R.id.drawer_account_middle)); + mOtherAccountAvatarRadiusDimension, getResources(), getStorageManager(), accountMiddleView); mAccountMiddleAccountAvatar.setVisibility(View.VISIBLE); } else { mAccountMiddleAccountAvatar.setVisibility(View.GONE); @@ -749,9 +753,11 @@ protected void setAccountInDrawer(Account account) { username.setText(AccountUtils.getAccountUsername(account.name)); } - DisplayUtils.setAvatar(account, this, - mCurrentAccountAvatarRadiusDimension, getResources(), getStorageManager(), - findNavigationViewChildById(R.id.drawer_current_account)); + View currentAccountView = findNavigationViewChildById(R.id.drawer_current_account); + currentAccountView.setTag(account.name); + + DisplayUtils.setAvatar(account, this, mCurrentAccountAvatarRadiusDimension, getResources(), + getStorageManager(), currentAccountView); // check and show quota info if available getAndDisplayUserQuota(); diff --git a/src/main/java/com/owncloud/android/ui/activity/UserInfoActivity.java b/src/main/java/com/owncloud/android/ui/activity/UserInfoActivity.java index 8e01ef9c5673..88bb99599cc4 100644 --- a/src/main/java/com/owncloud/android/ui/activity/UserInfoActivity.java +++ b/src/main/java/com/owncloud/android/ui/activity/UserInfoActivity.java @@ -262,8 +262,9 @@ public void onLoadFailed(Exception e, Drawable errorDrawable) { private void populateUserInfoUi(UserInfo userInfo) { userName.setText(account.name); - DisplayUtils.setAvatar(account, UserInfoActivity.this, - mCurrentAccountAvatarRadiusDimension, getResources(), getStorageManager(), avatar); + avatar.setTag(account.name); + DisplayUtils.setAvatar(account, UserInfoActivity.this, mCurrentAccountAvatarRadiusDimension, getResources(), + getStorageManager(), avatar); int tint = ThemeUtils.primaryColor(account); diff --git a/src/main/java/com/owncloud/android/ui/adapter/AccountListAdapter.java b/src/main/java/com/owncloud/android/ui/adapter/AccountListAdapter.java index f48900df5127..b280fdbded4e 100644 --- a/src/main/java/com/owncloud/android/ui/adapter/AccountListAdapter.java +++ b/src/main/java/com/owncloud/android/ui/adapter/AccountListAdapter.java @@ -150,8 +150,10 @@ private void setCurrentlyActiveState(AccountViewHolderItem viewHolder, Account a private void setAvatar(AccountViewHolderItem viewHolder, Account account) { try { - DisplayUtils.setAvatar(account, this, mAccountAvatarRadiusDimension, - mContext.getResources(), mContext.getStorageManager(), viewHolder.imageViewItem); + View viewItem = viewHolder.imageViewItem; + viewItem.setTag(account.name); + DisplayUtils.setAvatar(account, this, mAccountAvatarRadiusDimension, mContext.getResources(), + mContext.getStorageManager(), viewItem); } catch (Exception e) { Log_OC.e(TAG, "Error calculating RGB value for account list item.", e); // use user icon as a fallback diff --git a/src/main/java/com/owncloud/android/utils/BitmapUtils.java b/src/main/java/com/owncloud/android/utils/BitmapUtils.java index 89e44a1096b7..982b4034c42a 100644 --- a/src/main/java/com/owncloud/android/utils/BitmapUtils.java +++ b/src/main/java/com/owncloud/android/utils/BitmapUtils.java @@ -22,7 +22,10 @@ import android.graphics.Bitmap; import android.graphics.BitmapFactory; import android.graphics.BitmapFactory.Options; +import android.graphics.Canvas; import android.graphics.Matrix; +import android.graphics.drawable.BitmapDrawable; +import android.graphics.drawable.Drawable; import android.support.media.ExifInterface; import android.support.v4.graphics.drawable.RoundedBitmapDrawable; import android.support.v4.graphics.drawable.RoundedBitmapDrawableFactory; @@ -376,4 +379,27 @@ public static RoundedBitmapDrawable bitmapToCircularBitmapDrawable(Resources res roundedBitmap.setCircular(true); return roundedBitmap; } + + public static Bitmap drawableToBitmap(Drawable drawable) { + Bitmap bitmap; + + if (drawable instanceof BitmapDrawable) { + BitmapDrawable bitmapDrawable = (BitmapDrawable) drawable; + if (bitmapDrawable.getBitmap() != null) { + return bitmapDrawable.getBitmap(); + } + } + + if (drawable.getIntrinsicWidth() <= 0 || drawable.getIntrinsicHeight() <= 0) { + bitmap = Bitmap.createBitmap(1, 1, Bitmap.Config.ARGB_8888); + } else { + bitmap = Bitmap.createBitmap(drawable.getIntrinsicWidth(), drawable.getIntrinsicHeight(), + Bitmap.Config.ARGB_8888); + } + + Canvas canvas = new Canvas(bitmap); + drawable.setBounds(0, 0, canvas.getWidth(), canvas.getHeight()); + drawable.draw(canvas); + return bitmap; + } } diff --git a/src/main/java/com/owncloud/android/utils/DisplayUtils.java b/src/main/java/com/owncloud/android/utils/DisplayUtils.java index d36cb408e524..b6034ce6e99c 100644 --- a/src/main/java/com/owncloud/android/utils/DisplayUtils.java +++ b/src/main/java/com/owncloud/android/utils/DisplayUtils.java @@ -60,6 +60,7 @@ import com.owncloud.android.MainApp; import com.owncloud.android.R; import com.owncloud.android.authentication.AccountUtils; +import com.owncloud.android.datamodel.ArbitraryDataProvider; import com.owncloud.android.datamodel.FileDataStorageManager; import com.owncloud.android.datamodel.OCFile; import com.owncloud.android.datamodel.ThumbnailsCacheManager; @@ -426,41 +427,43 @@ public interface AvatarGenerationListener { * @param resources reference for density information * @param storageManager reference for caching purposes */ - public static void setAvatar(Account account, AvatarGenerationListener listener, float avatarRadius, Resources resources, - FileDataStorageManager storageManager, Object callContext) { + public static void setAvatar(Account account, AvatarGenerationListener listener, float avatarRadius, + Resources resources, FileDataStorageManager storageManager, Object callContext) { if (account != null) { if (callContext instanceof View) { ((View) callContext).setContentDescription(account.name); } - // Thumbnail in Cache? - Bitmap thumbnail = ThumbnailsCacheManager.getBitmapFromDiskCache("a_" + account.name); + ArbitraryDataProvider arbitraryDataProvider = new ArbitraryDataProvider( + MainApp.getAppContext().getContentResolver()); - if (thumbnail != null) { - listener.avatarGenerated( - BitmapUtils.bitmapToCircularBitmapDrawable(MainApp.getAppContext().getResources(), thumbnail), - callContext); - } else { - // generate new avatar - if (ThumbnailsCacheManager.cancelPotentialAvatarWork(account.name, callContext)) { - final ThumbnailsCacheManager.AvatarGenerationTask task = - new ThumbnailsCacheManager.AvatarGenerationTask(listener, callContext, storageManager, account); - if (thumbnail == null) { - try { - listener.avatarGenerated(TextDrawable.createAvatar(account.name, avatarRadius), callContext); - } catch (Exception e) { - Log_OC.e(TAG, "Error calculating RGB value for active account icon.", e); - listener.avatarGenerated(resources.getDrawable(R.drawable.ic_account_circle), callContext); - } - } else { - final ThumbnailsCacheManager.AsyncAvatarDrawable asyncDrawable = - new ThumbnailsCacheManager.AsyncAvatarDrawable(resources, thumbnail, task); - listener.avatarGenerated(BitmapUtils.bitmapToCircularBitmapDrawable( - resources, asyncDrawable.getBitmap()), callContext); - } - task.execute(account.name); + String eTag = arbitraryDataProvider.getValue(account, ThumbnailsCacheManager.AVATAR); + + // first show old one + Drawable avatar = BitmapUtils.bitmapToCircularBitmapDrawable(resources, + ThumbnailsCacheManager.getBitmapFromDiskCache("a_" + account.name + "_" + eTag)); + + // if no one exists, show colored icon with initial char + if (avatar == null) { + try { + avatar = TextDrawable.createAvatar(account.name, avatarRadius); + } catch (Exception e) { + Log_OC.e(TAG, "Error calculating RGB value for active account icon.", e); + avatar = resources.getDrawable(R.drawable.ic_account_circle); } } + + // check for new avatar, eTag is compared, so only new one is downloaded + if (ThumbnailsCacheManager.cancelPotentialAvatarWork(account.name, callContext)) { + final ThumbnailsCacheManager.AvatarGenerationTask task = + new ThumbnailsCacheManager.AvatarGenerationTask(listener, callContext, storageManager, + account, resources, avatarRadius); + + final ThumbnailsCacheManager.AsyncAvatarDrawable asyncDrawable = + new ThumbnailsCacheManager.AsyncAvatarDrawable(resources, avatar, task); + listener.avatarGenerated(asyncDrawable, callContext); + task.execute(account.name); + } } } diff --git a/src/main/java/com/owncloud/android/utils/NextcloudServer.java b/src/main/java/com/owncloud/android/utils/NextcloudServer.java new file mode 100644 index 000000000000..cf72c345af1b --- /dev/null +++ b/src/main/java/com/owncloud/android/utils/NextcloudServer.java @@ -0,0 +1,19 @@ +package com.owncloud.android.utils; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Defines min and max server version. Useful to find not needed code, e.g. if annotated max=12 and last supported + * version is 13 the code can be removed. + */ + +@Retention(RetentionPolicy.SOURCE) +@Target(ElementType.METHOD) +public @interface NextcloudServer { + int min() default -1; + + int max(); +} \ No newline at end of file diff --git a/src/main/java/com/owncloud/android/utils/ThemeUtils.java b/src/main/java/com/owncloud/android/utils/ThemeUtils.java index 1d1849750b57..ac5b48689dcf 100644 --- a/src/main/java/com/owncloud/android/utils/ThemeUtils.java +++ b/src/main/java/com/owncloud/android/utils/ThemeUtils.java @@ -108,6 +108,7 @@ public static int elementColor() { return elementColor(null); } + @NextcloudServer(max = 12) public static int elementColor(Account account) { OCCapability capability = getCapability(account);