From 283ff44da96dfeebd560bfe693af6bd9eba12e55 Mon Sep 17 00:00:00 2001 From: Alex Hart Date: Tue, 1 Dec 2020 16:56:10 -0400 Subject: [PATCH] Cache conversation icon shortcuts. --- .../securesms/mms/SignalGlideModule.java | 2 + .../securesms/util/AvatarUtil.java | 34 +--- .../util/ConversationShortcutPhoto.java | 153 ++++++++++++++++++ 3 files changed, 161 insertions(+), 28 deletions(-) create mode 100644 app/src/main/java/org/thoughtcrime/securesms/util/ConversationShortcutPhoto.java diff --git a/app/src/main/java/org/thoughtcrime/securesms/mms/SignalGlideModule.java b/app/src/main/java/org/thoughtcrime/securesms/mms/SignalGlideModule.java index c95bcb078..b576b4b63 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/mms/SignalGlideModule.java +++ b/app/src/main/java/org/thoughtcrime/securesms/mms/SignalGlideModule.java @@ -45,6 +45,7 @@ import org.thoughtcrime.securesms.mms.AttachmentStreamUriLoader.AttachmentModel; import org.thoughtcrime.securesms.mms.DecryptableStreamUriLoader.DecryptableUri; import org.thoughtcrime.securesms.stickers.StickerRemoteUri; import org.thoughtcrime.securesms.stickers.StickerRemoteUriLoader; +import org.thoughtcrime.securesms.util.ConversationShortcutPhoto; import java.io.File; import java.io.InputStream; @@ -89,6 +90,7 @@ public class SignalGlideModule extends AppGlideModule { registry.prepend(BlurHash.class, Bitmap.class, new BlurHashResourceDecoder()); + registry.append(ConversationShortcutPhoto.class, Bitmap.class, new ConversationShortcutPhoto.Loader.Factory(context)); registry.append(ContactPhoto.class, InputStream.class, new ContactPhotoLoader.Factory(context)); registry.append(DecryptableUri.class, InputStream.class, new DecryptableStreamUriLoader.Factory(context)); registry.append(AttachmentModel.class, InputStream.class, new AttachmentStreamUriLoader.Factory()); diff --git a/app/src/main/java/org/thoughtcrime/securesms/util/AvatarUtil.java b/app/src/main/java/org/thoughtcrime/securesms/util/AvatarUtil.java index 63550dc7a..97f3a7732 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/util/AvatarUtil.java +++ b/app/src/main/java/org/thoughtcrime/securesms/util/AvatarUtil.java @@ -112,20 +112,20 @@ public final class AvatarUtil { @RequiresApi(ConversationUtil.CONVERSATION_SUPPORT_VERSION) @WorkerThread - public static Icon getIconForShortcut(@NonNull Context context, @NonNull Recipient recipient) { + public static @NonNull Icon getIconForShortcut(@NonNull Context context, @NonNull Recipient recipient) { try { - return Icon.createWithAdaptiveBitmap(getShortcutInfoBitmap(context, recipient)); + return Icon.createWithAdaptiveBitmap(GlideApp.with(context).asBitmap().load(new ConversationShortcutPhoto(recipient)).submit().get()); } catch (ExecutionException | InterruptedException e) { - return Icon.createWithAdaptiveBitmap(getFallbackForShortcut(context, recipient)); + throw new AssertionError("This call should not fail."); } } @WorkerThread - public static IconCompat getIconCompatForShortcut(@NonNull Context context, @NonNull Recipient recipient) { + public static @NonNull IconCompat getIconCompatForShortcut(@NonNull Context context, @NonNull Recipient recipient) { try { - return IconCompat.createWithAdaptiveBitmap(getShortcutInfoBitmap(context, recipient)); + return IconCompat.createWithAdaptiveBitmap(GlideApp.with(context).asBitmap().load(new ConversationShortcutPhoto(recipient)).submit().get()); } catch (ExecutionException | InterruptedException e) { - return IconCompat.createWithAdaptiveBitmap(getFallbackForShortcut(context, recipient)); + throw new AssertionError("This call should not fail."); } } @@ -138,10 +138,6 @@ public final class AvatarUtil { } } - private static @NonNull Bitmap getShortcutInfoBitmap(@NonNull Context context, @NonNull Recipient recipient) throws ExecutionException, InterruptedException { - return DrawableUtil.wrapBitmapForShortcutInfo(request(GlideApp.with(context).asBitmap(), context, recipient, false).circleCrop().submit().get()); - } - private static GlideRequest requestCircle(@NonNull GlideRequest glideRequest, @NonNull Context context, @NonNull Recipient recipient) { return request(glideRequest, context, recipient).circleCrop(); } @@ -167,24 +163,6 @@ public final class AvatarUtil { .diskCacheStrategy(DiskCacheStrategy.ALL); } - private static @NonNull Bitmap getFallbackForShortcut(@NonNull Context context, @NonNull Recipient recipient) { - @DrawableRes final int photoSource; - if (recipient.isSelf()) { - photoSource = R.drawable.ic_note_80; - } else if (recipient.isGroup()) { - photoSource = R.drawable.ic_group_80; - } else { - photoSource = R.drawable.ic_profile_80; - } - - Bitmap toWrap = DrawableUtil.toBitmap(new FallbackPhoto80dp(photoSource, recipient.getColor()).asDrawable(context, -1), ViewUtil.dpToPx(80), ViewUtil.dpToPx(80)); - Bitmap wrapped = DrawableUtil.wrapBitmapForShortcutInfo(toWrap); - - toWrap.recycle(); - - return wrapped; - } - private static Drawable getFallback(@NonNull Context context, @NonNull Recipient recipient) { String name = Optional.fromNullable(recipient.getDisplayName(context)).or(""); MaterialColor fallbackColor = recipient.getColor(); diff --git a/app/src/main/java/org/thoughtcrime/securesms/util/ConversationShortcutPhoto.java b/app/src/main/java/org/thoughtcrime/securesms/util/ConversationShortcutPhoto.java new file mode 100644 index 000000000..1a592af1d --- /dev/null +++ b/app/src/main/java/org/thoughtcrime/securesms/util/ConversationShortcutPhoto.java @@ -0,0 +1,153 @@ +package org.thoughtcrime.securesms.util; + +import android.content.Context; +import android.graphics.Bitmap; + +import androidx.annotation.DrawableRes; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.annotation.WorkerThread; + +import com.bumptech.glide.Priority; +import com.bumptech.glide.load.DataSource; +import com.bumptech.glide.load.Key; +import com.bumptech.glide.load.Options; +import com.bumptech.glide.load.data.DataFetcher; +import com.bumptech.glide.load.engine.DiskCacheStrategy; +import com.bumptech.glide.load.model.ModelLoader; +import com.bumptech.glide.load.model.ModelLoaderFactory; +import com.bumptech.glide.load.model.MultiModelLoaderFactory; + +import org.thoughtcrime.securesms.R; +import org.thoughtcrime.securesms.contacts.avatars.FallbackPhoto80dp; +import org.thoughtcrime.securesms.dependencies.ApplicationDependencies; +import org.thoughtcrime.securesms.mms.GlideApp; +import org.thoughtcrime.securesms.mms.GlideRequest; +import org.thoughtcrime.securesms.recipients.Recipient; + +import java.security.MessageDigest; +import java.util.concurrent.ExecutionException; + +public final class ConversationShortcutPhoto implements Key { + + private final Recipient recipient; + + @WorkerThread + public ConversationShortcutPhoto(@NonNull Recipient recipient) { + this.recipient = recipient.resolve(); + } + + @Override + public void updateDiskCacheKey(@NonNull MessageDigest messageDigest) { + messageDigest.update(recipient.getDisplayName(ApplicationDependencies.getApplication()).getBytes()); + + if (recipient.getProfileAvatar() != null) { + messageDigest.update(recipient.getProfileAvatar().getBytes()); + } + } + + public static final class Loader implements ModelLoader { + + private final Context context; + + private Loader(@NonNull Context context) { + this.context = context; + } + + @Override + public @Nullable LoadData buildLoadData(@NonNull ConversationShortcutPhoto conversationShortcutPhoto, int width, int height, @NonNull Options options) { + return new LoadData<>(conversationShortcutPhoto, new Fetcher(context, conversationShortcutPhoto)); + } + + @Override + public boolean handles(@NonNull ConversationShortcutPhoto conversationShortcutPhoto) { + return true; + } + + public static class Factory implements ModelLoaderFactory { + + private final Context context; + + public Factory(@NonNull Context context) { + this.context = context; + } + + @Override + public @NonNull ModelLoader build(@NonNull MultiModelLoaderFactory multiFactory) { + return new Loader(context); + } + + @Override + public void teardown() { + } + } + } + + static final class Fetcher implements DataFetcher { + + private final Context context; + private final ConversationShortcutPhoto photo; + + private Fetcher(@NonNull Context context, @NonNull ConversationShortcutPhoto photo) { + this.context = context; + this.photo = photo; + } + + @Override + public void loadData(@NonNull Priority priority, @NonNull DataCallback callback) { + Bitmap bitmap; + + try { + bitmap = getShortcutInfoBitmap(context); + } catch (ExecutionException | InterruptedException e) { + bitmap = getFallbackForShortcut(context); + } + + callback.onDataReady(bitmap); + } + + @Override + public void cleanup() { + } + + @Override + public void cancel() { + } + + @Override + public @NonNull Class getDataClass() { + return Bitmap.class; + } + + @Override + public @NonNull DataSource getDataSource() { + return DataSource.LOCAL; + } + + private @NonNull Bitmap getShortcutInfoBitmap(@NonNull Context context) throws ExecutionException, InterruptedException { + return DrawableUtil.wrapBitmapForShortcutInfo(request(GlideApp.with(context).asBitmap(), context, false).circleCrop().submit().get()); + } + + private @NonNull Bitmap getFallbackForShortcut(@NonNull Context context) { + @DrawableRes final int photoSource; + if (photo.recipient.isSelf()) { + photoSource = R.drawable.ic_note_80; + } else if (photo.recipient.isGroup()) { + photoSource = R.drawable.ic_group_80; + } else { + photoSource = R.drawable.ic_profile_80; + } + + Bitmap toWrap = DrawableUtil.toBitmap(new FallbackPhoto80dp(photoSource, photo.recipient.getColor()).asDrawable(context, -1), ViewUtil.dpToPx(80), ViewUtil.dpToPx(80)); + Bitmap wrapped = DrawableUtil.wrapBitmapForShortcutInfo(toWrap); + + toWrap.recycle(); + + return wrapped; + } + + private GlideRequest request(@NonNull GlideRequest glideRequest, @NonNull Context context, boolean loadSelf) { + return glideRequest.load(photo.recipient.getContactPhoto()).diskCacheStrategy(DiskCacheStrategy.ALL); + } + } +}