From 3dfd1c98badc951f80d02228eb34ceaa310df926 Mon Sep 17 00:00:00 2001 From: Cody Henthorne Date: Wed, 4 Oct 2023 15:00:52 -0400 Subject: [PATCH] Re-download profile avatars if they fail to load. --- .../securesms/components/AvatarImageView.java | 25 +++++++++- .../contacts/avatars/ProfileContactPhoto.java | 4 ++ .../jobs/RetrieveProfileAvatarJob.java | 47 ++++++++++++++----- 3 files changed, 63 insertions(+), 13 deletions(-) diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/AvatarImageView.java b/app/src/main/java/org/thoughtcrime/securesms/components/AvatarImageView.java index a77125e4cd..3ee4a09db6 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/components/AvatarImageView.java +++ b/app/src/main/java/org/thoughtcrime/securesms/components/AvatarImageView.java @@ -15,12 +15,16 @@ import androidx.annotation.Px; import androidx.appcompat.widget.AppCompatImageView; import androidx.fragment.app.FragmentActivity; +import com.bumptech.glide.load.DataSource; import com.bumptech.glide.load.MultiTransformation; import com.bumptech.glide.load.Transformation; import com.bumptech.glide.load.engine.DiskCacheStrategy; +import com.bumptech.glide.load.engine.GlideException; import com.bumptech.glide.load.resource.bitmap.CircleCrop; import com.bumptech.glide.load.resource.bitmap.DownsampleStrategy; +import com.bumptech.glide.request.RequestListener; import com.bumptech.glide.request.target.SimpleTarget; +import com.bumptech.glide.request.target.Target; import com.bumptech.glide.request.transition.Transition; import org.signal.core.util.logging.Log; @@ -32,6 +36,7 @@ import org.thoughtcrime.securesms.contacts.avatars.ResourceContactPhoto; import org.thoughtcrime.securesms.conversation.colors.AvatarColor; import org.thoughtcrime.securesms.conversation.colors.ChatColors; import org.thoughtcrime.securesms.dependencies.ApplicationDependencies; +import org.thoughtcrime.securesms.jobs.RetrieveProfileAvatarJob; import org.thoughtcrime.securesms.mms.GlideApp; import org.thoughtcrime.securesms.mms.GlideRequest; import org.thoughtcrime.securesms.mms.GlideRequests; @@ -54,6 +59,8 @@ public final class AvatarImageView extends AppCompatImageView { @SuppressWarnings("unused") private static final String TAG = Log.tag(AvatarImageView.class); + private final RequestListener redownloadRequestListener = new RedownloadRequestListener(); + private int size; private boolean inverted; private OnClickListener listener; @@ -198,7 +205,8 @@ public final class AvatarImageView extends AppCompatImageView { .error(fallbackContactPhotoDrawable) .diskCacheStrategy(DiskCacheStrategy.ALL) .downsample(DownsampleStrategy.CENTER_INSIDE) - .transform(new MultiTransformation<>(transforms)); + .transform(new MultiTransformation<>(transforms)) + .addListener(redownloadRequestListener); if (avatarOptions.fixedSize > 0) { fixedSizeTarget = new FixedSizeTarget(avatarOptions.fixedSize); @@ -363,4 +371,19 @@ public final class AvatarImageView extends AppCompatImageView { } } } + + private static class RedownloadRequestListener implements RequestListener { + @Override + public boolean onLoadFailed(@Nullable GlideException e, Object model, Target target, boolean isFirstResource) { + if (model instanceof ProfileContactPhoto) { + RetrieveProfileAvatarJob.enqueueForceUpdate(((ProfileContactPhoto) model).getRecipient()); + } + return false; + } + + @Override + public boolean onResourceReady(Drawable resource, Object model, Target target, DataSource dataSource, boolean isFirstResource) { + return false; + } + } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/contacts/avatars/ProfileContactPhoto.java b/app/src/main/java/org/thoughtcrime/securesms/contacts/avatars/ProfileContactPhoto.java index 0dd78d2f1f..e2183bfc12 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/contacts/avatars/ProfileContactPhoto.java +++ b/app/src/main/java/org/thoughtcrime/securesms/contacts/avatars/ProfileContactPhoto.java @@ -66,4 +66,8 @@ public class ProfileContactPhoto implements ContactPhoto { public int hashCode() { return Objects.hash(recipient, avatarObject, profileAvatarFileDetails); } + + public @NonNull Recipient getRecipient() { + return recipient; + } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/jobs/RetrieveProfileAvatarJob.java b/app/src/main/java/org/thoughtcrime/securesms/jobs/RetrieveProfileAvatarJob.java index c2943cd67a..39addaa8c4 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/jobs/RetrieveProfileAvatarJob.java +++ b/app/src/main/java/org/thoughtcrime/securesms/jobs/RetrieveProfileAvatarJob.java @@ -6,11 +6,13 @@ import android.text.TextUtils; import androidx.annotation.NonNull; import androidx.annotation.Nullable; +import org.signal.core.util.concurrent.SignalExecutors; import org.signal.core.util.logging.Log; import org.signal.libsignal.zkgroup.profiles.ProfileKey; import org.thoughtcrime.securesms.crypto.ProfileKeyUtil; import org.thoughtcrime.securesms.database.RecipientTable; import org.thoughtcrime.securesms.database.SignalDatabase; +import org.thoughtcrime.securesms.database.model.ProfileAvatarFileDetails; import org.thoughtcrime.securesms.dependencies.ApplicationDependencies; import org.thoughtcrime.securesms.jobmanager.JsonJobData; import org.thoughtcrime.securesms.jobmanager.Job; @@ -34,35 +36,47 @@ public class RetrieveProfileAvatarJob extends BaseJob { private static final String TAG = Log.tag(RetrieveProfileAvatarJob.class); - private static final int MAX_PROFILE_SIZE_BYTES = 20 * 1024 * 1024; + private static final long MIN_TIME_BETWEEN_FORCE_RETRY = TimeUnit.DAYS.toMillis(1); private static final String KEY_PROFILE_AVATAR = "profile_avatar"; private static final String KEY_RECIPIENT = "recipient"; + private static final String KEY_FORCE_UPDATE = "force"; private final String profileAvatar; private final Recipient recipient; + private final boolean forceUpdate; - public RetrieveProfileAvatarJob(Recipient recipient, String profileAvatar) { - this(new Job.Parameters.Builder() - .setQueue("RetrieveProfileAvatarJob::" + recipient.getId().toQueueKey()) - .addConstraint(NetworkConstraint.KEY) - .setLifespan(TimeUnit.HOURS.toMillis(1)) - .build(), - recipient, - profileAvatar); + public static void enqueueForceUpdate(Recipient recipient) { + SignalExecutors.BOUNDED.execute(() -> ApplicationDependencies.getJobManager().add(new RetrieveProfileAvatarJob(recipient, recipient.resolve().getProfileAvatar(), true))); } - private RetrieveProfileAvatarJob(@NonNull Job.Parameters parameters, @NonNull Recipient recipient, String profileAvatar) { + public RetrieveProfileAvatarJob(Recipient recipient, String profileAvatar) { + this(recipient, profileAvatar, false); + } + + public RetrieveProfileAvatarJob(Recipient recipient, String profileAvatar, boolean forceUpdate) { + this(new Job.Parameters.Builder().setQueue("RetrieveProfileAvatarJob::" + recipient.getId().toQueueKey()) + .addConstraint(NetworkConstraint.KEY) + .setLifespan(TimeUnit.HOURS.toMillis(1)) + .build(), + recipient, + profileAvatar, + forceUpdate); + } + + private RetrieveProfileAvatarJob(@NonNull Job.Parameters parameters, @NonNull Recipient recipient, String profileAvatar, boolean forceUpdate) { super(parameters); this.recipient = recipient; this.profileAvatar = profileAvatar; + this.forceUpdate = forceUpdate; } @Override public @Nullable byte[] serialize() { return new JsonJobData.Builder().putString(KEY_PROFILE_AVATAR, profileAvatar) .putString(KEY_RECIPIENT, recipient.getId().serialize()) + .putBoolean(KEY_FORCE_UPDATE, forceUpdate) .serialize(); } @@ -81,7 +95,15 @@ public class RetrieveProfileAvatarJob extends BaseJob { return; } - if (profileAvatar != null && profileAvatar.equals(recipient.resolve().getProfileAvatar())) { + if (forceUpdate) { + ProfileAvatarFileDetails details = recipient.getProfileAvatarFileDetails(); + if (!details.hasFile() || (details.getLastModified() > System.currentTimeMillis() || details.getLastModified() + MIN_TIME_BETWEEN_FORCE_RETRY < System.currentTimeMillis())) { + Log.i(TAG, "Forcing re-download of avatar."); + } else { + Log.i(TAG, "Too early to force re-download avatar."); + return; + } + } else if (profileAvatar != null && profileAvatar.equals(recipient.resolve().getProfileAvatar())) { Log.w(TAG, "Already retrieved profile avatar: " + profileAvatar); return; } @@ -142,7 +164,8 @@ public class RetrieveProfileAvatarJob extends BaseJob { return new RetrieveProfileAvatarJob(parameters, Recipient.resolved(RecipientId.from(data.getString(KEY_RECIPIENT))), - data.getString(KEY_PROFILE_AVATAR)); + data.getString(KEY_PROFILE_AVATAR), + data.getBooleanOrDefault(KEY_FORCE_UPDATE, false)); } } }