Browse Source

Merge pull request #1658 from nextcloud/avatarETags

Avatar Etags
Tobias Kaminsky 7 years ago
parent
commit
8d63ef5d47

+ 96 - 65
src/main/java/com/owncloud/android/datamodel/ThumbnailsCacheManager.java

@@ -38,6 +38,7 @@ import android.media.ThumbnailUtils;
 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.OwnCloudClientManagerFactory;
 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<File, Void, Void> {
-
         @Override
         protected Void doInBackground(File... params) {
             synchronized (mThumbnailsDiskCacheLock) {
@@ -759,27 +755,32 @@ public class ThumbnailsCacheManager {
         }
     }
 
-    public static class AvatarGenerationTask extends AsyncTask<String, Void, Bitmap> {
+    public static class AvatarGenerationTask extends AsyncTask<String, Void, Drawable> {
         private final WeakReference<AvatarGenerationListener> 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 @@ public class ThumbnailsCacheManager {
             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 @@ public class ThumbnailsCacheManager {
             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 class ThumbnailsCacheManager {
     public static class AsyncAvatarDrawable extends BitmapDrawable {
         private final WeakReference<AvatarGenerationTask> avatarWorkerTaskReference;
 
-        public AsyncAvatarDrawable(
-                Resources res, Bitmap bitmap, AvatarGenerationTask avatarWorkerTask
-        ) {
-
-            super(res, bitmap);
-            avatarWorkerTaskReference =
-                    new WeakReference<AvatarGenerationTask>(avatarWorkerTask);
+        public AsyncAvatarDrawable(Resources res, Drawable bitmap, AvatarGenerationTask avatarWorkerTask) {
+            super(res, BitmapUtils.drawableToBitmap(bitmap));
+            avatarWorkerTaskReference = new WeakReference<>(avatarWorkerTask);
         }
 
         public AvatarGenerationTask getAvatarWorkerTask() {

+ 2 - 0
src/main/java/com/owncloud/android/ui/TextDrawable.java

@@ -30,6 +30,7 @@ import android.support.annotation.NonNull;
 
 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 class TextDrawable extends Drawable {
      * @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);

+ 13 - 7
src/main/java/com/owncloud/android/ui/activity/DrawerActivity.java

@@ -641,9 +641,11 @@ public abstract class DrawerActivity extends ToolbarActivity implements DisplayU
 
                 // 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 @@ public abstract class DrawerActivity extends ToolbarActivity implements DisplayU
 
                 // 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 @@ public abstract class DrawerActivity extends ToolbarActivity implements DisplayU
                 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();

+ 3 - 2
src/main/java/com/owncloud/android/ui/activity/UserInfoActivity.java

@@ -262,8 +262,9 @@ public class UserInfoActivity extends FileActivity {
 
     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);
 

+ 4 - 2
src/main/java/com/owncloud/android/ui/adapter/AccountListAdapter.java

@@ -150,8 +150,10 @@ public class AccountListAdapter extends ArrayAdapter<AccountListItem> implements
 
     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

+ 26 - 0
src/main/java/com/owncloud/android/utils/BitmapUtils.java

@@ -22,7 +22,10 @@ import android.content.res.Resources;
 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 class BitmapUtils {
         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;
+    }
 }

+ 30 - 27
src/main/java/com/owncloud/android/utils/DisplayUtils.java

@@ -60,6 +60,7 @@ import com.caverock.androidsvg.SVG;
 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 class DisplayUtils {
      * @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);
+            }
         }
     }
 

+ 19 - 0
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();
+}

+ 1 - 0
src/main/java/com/owncloud/android/utils/ThemeUtils.java

@@ -108,6 +108,7 @@ public class ThemeUtils {
         return elementColor(null);
     }
 
+    @NextcloudServer(max = 12)
     public static int elementColor(Account account) {
         OCCapability capability = getCapability(account);