Re-download profile avatars if they fail to load.

This commit is contained in:
Cody Henthorne
2023-10-04 15:00:52 -04:00
committed by GitHub
parent 9a249b0dec
commit 3dfd1c98ba
3 changed files with 63 additions and 13 deletions

View File

@@ -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<Drawable> 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<Drawable> {
@Override
public boolean onLoadFailed(@Nullable GlideException e, Object model, Target<Drawable> target, boolean isFirstResource) {
if (model instanceof ProfileContactPhoto) {
RetrieveProfileAvatarJob.enqueueForceUpdate(((ProfileContactPhoto) model).getRecipient());
}
return false;
}
@Override
public boolean onResourceReady(Drawable resource, Object model, Target<Drawable> target, DataSource dataSource, boolean isFirstResource) {
return false;
}
}
}

View File

@@ -66,4 +66,8 @@ public class ProfileContactPhoto implements ContactPhoto {
public int hashCode() {
return Objects.hash(recipient, avatarObject, profileAvatarFileDetails);
}
public @NonNull Recipient getRecipient() {
return recipient;
}
}

View File

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