Skip to content

Commit

Permalink
Cache conversation icon shortcuts.
Browse files Browse the repository at this point in the history
  • Loading branch information
alex-signal authored and greyson-signal committed Dec 1, 2020
1 parent adee104 commit 283ff44
Show file tree
Hide file tree
Showing 3 changed files with 161 additions and 28 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@
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;
Expand Down Expand Up @@ -89,6 +90,7 @@ public void registerComponents(@NonNull Context context, @NonNull Glide glide, @

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());
Expand Down
34 changes: 6 additions & 28 deletions app/src/main/java/org/thoughtcrime/securesms/util/AvatarUtil.java
Original file line number Diff line number Diff line change
Expand Up @@ -112,20 +112,20 @@ public static IconCompat getIconForNotification(@NonNull Context context, @NonNu

@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.");
}
}

Expand All @@ -138,10 +138,6 @@ public static Bitmap getBitmapForNotification(@NonNull Context context, @NonNull
}
}

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 <T> GlideRequest<T> requestCircle(@NonNull GlideRequest<T> glideRequest, @NonNull Context context, @NonNull Recipient recipient) {
return request(glideRequest, context, recipient).circleCrop();
}
Expand All @@ -167,24 +163,6 @@ private static <T> GlideRequest<T> request(@NonNull GlideRequest<T> glideRequest
.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();
Expand Down
Original file line number Diff line number Diff line change
@@ -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<ConversationShortcutPhoto, Bitmap> {

private final Context context;

private Loader(@NonNull Context context) {
this.context = context;
}

@Override
public @Nullable LoadData<Bitmap> 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<ConversationShortcutPhoto, Bitmap> {

private final Context context;

public Factory(@NonNull Context context) {
this.context = context;
}

@Override
public @NonNull ModelLoader<ConversationShortcutPhoto, Bitmap> build(@NonNull MultiModelLoaderFactory multiFactory) {
return new Loader(context);
}

@Override
public void teardown() {
}
}
}

static final class Fetcher implements DataFetcher<Bitmap> {

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<? super Bitmap> 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<Bitmap> 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 <T> GlideRequest<T> request(@NonNull GlideRequest<T> glideRequest, @NonNull Context context, boolean loadSelf) {
return glideRequest.load(photo.recipient.getContactPhoto()).diskCacheStrategy(DiskCacheStrategy.ALL);
}
}
}

0 comments on commit 283ff44

Please sign in to comment.