Fix leak in Message Details for disappearing messages.

fork-5.53.8
Cody Henthorne 2022-06-28 19:34:52 -04:00
rodzic 96ea4c0cc2
commit 4215b0391d
3 zmienionych plików z 56 dodań i 104 usunięć

Wyświetl plik

@ -16,17 +16,12 @@ import org.thoughtcrime.securesms.conversation.colors.Colorizer;
import org.thoughtcrime.securesms.database.model.MessageRecord; import org.thoughtcrime.securesms.database.model.MessageRecord;
import org.thoughtcrime.securesms.mms.GlideRequests; import org.thoughtcrime.securesms.mms.GlideRequests;
import java.util.List;
final class MessageDetailsAdapter extends ListAdapter<MessageDetailsAdapter.MessageDetailsViewState<?>, RecyclerView.ViewHolder> { final class MessageDetailsAdapter extends ListAdapter<MessageDetailsAdapter.MessageDetailsViewState<?>, RecyclerView.ViewHolder> {
private static final Object EXPIRATION_TIMER_CHANGE_PAYLOAD = new Object();
private final LifecycleOwner lifecycleOwner; private final LifecycleOwner lifecycleOwner;
private final GlideRequests glideRequests; private final GlideRequests glideRequests;
private final Colorizer colorizer; private final Colorizer colorizer;
private Callbacks callbacks; private final Callbacks callbacks;
private boolean running;
MessageDetailsAdapter(@NonNull LifecycleOwner lifecycleOwner, @NonNull GlideRequests glideRequests, @NonNull Colorizer colorizer, @NonNull Callbacks callbacks) { MessageDetailsAdapter(@NonNull LifecycleOwner lifecycleOwner, @NonNull GlideRequests glideRequests, @NonNull Colorizer colorizer, @NonNull Callbacks callbacks) {
super(new MessageDetailsDiffer()); super(new MessageDetailsDiffer());
@ -34,7 +29,6 @@ final class MessageDetailsAdapter extends ListAdapter<MessageDetailsAdapter.Mess
this.glideRequests = glideRequests; this.glideRequests = glideRequests;
this.colorizer = colorizer; this.colorizer = colorizer;
this.callbacks = callbacks; this.callbacks = callbacks;
this.running = true;
} }
@Override @Override
@ -54,7 +48,7 @@ final class MessageDetailsAdapter extends ListAdapter<MessageDetailsAdapter.Mess
@Override @Override
public void onBindViewHolder(@NonNull RecyclerView.ViewHolder holder, int position) { public void onBindViewHolder(@NonNull RecyclerView.ViewHolder holder, int position) {
if (holder instanceof MessageHeaderViewHolder) { if (holder instanceof MessageHeaderViewHolder) {
((MessageHeaderViewHolder) holder).bind(lifecycleOwner, (ConversationMessage) getItem(position).data, running); ((MessageHeaderViewHolder) holder).bind(lifecycleOwner, (ConversationMessage) getItem(position).data);
} else if (holder instanceof RecipientHeaderViewHolder) { } else if (holder instanceof RecipientHeaderViewHolder) {
((RecipientHeaderViewHolder) holder).bind((RecipientHeader) getItem(position).data); ((RecipientHeaderViewHolder) holder).bind((RecipientHeader) getItem(position).data);
} else if (holder instanceof RecipientViewHolder) { } else if (holder instanceof RecipientViewHolder) {
@ -64,34 +58,11 @@ final class MessageDetailsAdapter extends ListAdapter<MessageDetailsAdapter.Mess
} }
} }
@Override
public void onBindViewHolder(@NonNull RecyclerView.ViewHolder holder, int position, @NonNull List<Object> payloads) {
if (payloads.isEmpty()) {
super.onBindViewHolder(holder, position, payloads);
} else if (holder instanceof MessageHeaderViewHolder) {
((MessageHeaderViewHolder) holder).partialBind((ConversationMessage) getItem(position).data, running);
}
}
@Override @Override
public int getItemViewType(int position) { public int getItemViewType(int position) {
return getItem(position).itemType; return getItem(position).itemType;
} }
void resumeMessageExpirationTimer() {
running = true;
if (getItemCount() > 0) {
notifyItemChanged(0, EXPIRATION_TIMER_CHANGE_PAYLOAD);
}
}
void pauseMessageExpirationTimer() {
running = false;
if (getItemCount() > 0) {
notifyItemChanged(0, EXPIRATION_TIMER_CHANGE_PAYLOAD);
}
}
private static class MessageDetailsDiffer extends DiffUtil.ItemCallback<MessageDetailsViewState<?>> { private static class MessageDetailsDiffer extends DiffUtil.ItemCallback<MessageDetailsViewState<?>> {
@Override @Override
public boolean areItemsTheSame(@NonNull MessageDetailsViewState<?> oldItem, @NonNull MessageDetailsViewState<?> newItem) { public boolean areItemsTheSame(@NonNull MessageDetailsViewState<?> oldItem, @NonNull MessageDetailsViewState<?> newItem) {

Wyświetl plik

@ -76,18 +76,6 @@ public final class MessageDetailsFragment extends FullScreenDialogFragment {
initializeVideoPlayer(view); initializeVideoPlayer(view);
} }
@Override
public void onResume() {
super.onResume();
adapter.resumeMessageExpirationTimer();
}
@Override
public void onPause() {
super.onPause();
adapter.pauseMessageExpirationTimer();
}
@Override @Override
public void onDismiss(@NonNull DialogInterface dialog) { public void onDismiss(@NonNull DialogInterface dialog) {
super.onDismiss(dialog); super.onDismiss(dialog);
@ -104,7 +92,7 @@ public final class MessageDetailsFragment extends FullScreenDialogFragment {
View toolbarShadow = view.findViewById(R.id.toolbar_shadow); View toolbarShadow = view.findViewById(R.id.toolbar_shadow);
colorizer = new Colorizer(); colorizer = new Colorizer();
adapter = new MessageDetailsAdapter(this, glideRequests, colorizer, this::onErrorClicked); adapter = new MessageDetailsAdapter(getViewLifecycleOwner(), glideRequests, colorizer, this::onErrorClicked);
recyclerViewColorizer = new RecyclerViewColorizer(list); recyclerViewColorizer = new RecyclerViewColorizer(list);
list.setAdapter(adapter); list.setAdapter(adapter);

Wyświetl plik

@ -3,6 +3,7 @@ package org.thoughtcrime.securesms.messagedetails;
import android.content.ClipData; import android.content.ClipData;
import android.content.ClipboardManager; import android.content.ClipboardManager;
import android.content.Context; import android.content.Context;
import android.os.CountDownTimer;
import android.text.SpannableStringBuilder; import android.text.SpannableStringBuilder;
import android.text.Spanned; import android.text.Spanned;
import android.text.style.StyleSpan; import android.text.style.StyleSpan;
@ -13,12 +14,12 @@ import android.widget.TextView;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import androidx.lifecycle.DefaultLifecycleObserver;
import androidx.lifecycle.LifecycleOwner; import androidx.lifecycle.LifecycleOwner;
import androidx.recyclerview.widget.RecyclerView; import androidx.recyclerview.widget.RecyclerView;
import com.google.android.exoplayer2.MediaItem; import com.google.android.exoplayer2.MediaItem;
import org.signal.core.util.ThreadUtil;
import org.signal.core.util.concurrent.SignalExecutors; import org.signal.core.util.concurrent.SignalExecutors;
import org.thoughtcrime.securesms.R; import org.thoughtcrime.securesms.R;
import org.thoughtcrime.securesms.conversation.ConversationItem; import org.thoughtcrime.securesms.conversation.ConversationItem;
@ -40,6 +41,7 @@ import java.text.SimpleDateFormat;
import java.util.HashSet; import java.util.HashSet;
import java.util.Locale; import java.util.Locale;
import java.util.Optional; import java.util.Optional;
import java.util.concurrent.TimeUnit;
final class MessageHeaderViewHolder extends RecyclerView.ViewHolder implements GiphyMp4Playable, Colorizable { final class MessageHeaderViewHolder extends RecyclerView.ViewHolder implements GiphyMp4Playable, Colorizable {
private final TextView sentDate; private final TextView sentDate;
@ -53,10 +55,10 @@ final class MessageHeaderViewHolder extends RecyclerView.ViewHolder implements G
private final ViewStub sentStub; private final ViewStub sentStub;
private final ViewStub receivedStub; private final ViewStub receivedStub;
private final Colorizer colorizer; private final Colorizer colorizer;
private final GlideRequests glideRequests;
private GlideRequests glideRequests;
private ConversationItem conversationItem; private ConversationItem conversationItem;
private ExpiresUpdater expiresUpdater; private CountDownTimer expiresUpdater;
MessageHeaderViewHolder(@NonNull View itemView, GlideRequests glideRequests, @NonNull Colorizer colorizer) { MessageHeaderViewHolder(@NonNull View itemView, GlideRequests glideRequests, @NonNull Colorizer colorizer) {
super(itemView); super(itemView);
@ -75,20 +77,16 @@ final class MessageHeaderViewHolder extends RecyclerView.ViewHolder implements G
receivedStub = itemView.findViewById(R.id.message_details_header_message_view_received_multimedia); receivedStub = itemView.findViewById(R.id.message_details_header_message_view_received_multimedia);
} }
void bind(@NonNull LifecycleOwner lifecycleOwner, @Nullable ConversationMessage conversationMessage, boolean running) { void bind(@NonNull LifecycleOwner lifecycleOwner, @NonNull ConversationMessage conversationMessage) {
MessageRecord messageRecord = conversationMessage.getMessageRecord(); MessageRecord messageRecord = conversationMessage.getMessageRecord();
bindMessageView(lifecycleOwner, conversationMessage); bindMessageView(lifecycleOwner, conversationMessage);
bindErrorState(messageRecord); bindErrorState(messageRecord);
bindSentReceivedDates(messageRecord); bindSentReceivedDates(messageRecord);
bindExpirationTime(messageRecord, running); bindExpirationTime(lifecycleOwner, messageRecord);
bindTransport(messageRecord); bindTransport(messageRecord);
} }
void partialBind(ConversationMessage conversationMessage, boolean running) { private void bindMessageView(@NonNull LifecycleOwner lifecycleOwner, @NonNull ConversationMessage conversationMessage) {
bindExpirationTime(conversationMessage.getMessageRecord(), running);
}
private void bindMessageView(@NonNull LifecycleOwner lifecycleOwner, @Nullable ConversationMessage conversationMessage) {
if (conversationItem == null) { if (conversationItem == null) {
if (conversationMessage.getMessageRecord().isGroupAction()) { if (conversationMessage.getMessageRecord().isGroupAction()) {
conversationItem = (ConversationItem) updateStub.inflate(); conversationItem = (ConversationItem) updateStub.inflate();
@ -166,9 +164,9 @@ final class MessageHeaderViewHolder extends RecyclerView.ViewHolder implements G
} }
} }
private void bindExpirationTime(final MessageRecord messageRecord, boolean running) { private void bindExpirationTime(@NonNull LifecycleOwner lifecycleOwner, @NonNull MessageRecord messageRecord) {
if (expiresUpdater != null) { if (expiresUpdater != null) {
expiresUpdater.stop(); expiresUpdater.cancel();
expiresUpdater = null; expiresUpdater = null;
} }
@ -178,10 +176,36 @@ final class MessageHeaderViewHolder extends RecyclerView.ViewHolder implements G
} }
expiresIn.setVisibility(View.VISIBLE); expiresIn.setVisibility(View.VISIBLE);
if (running) {
expiresUpdater = new ExpiresUpdater(messageRecord); lifecycleOwner.getLifecycle().addObserver(new DefaultLifecycleObserver() {
ThreadUtil.runOnMain(expiresUpdater); @Override
public void onResume(@NonNull LifecycleOwner owner) {
if (expiresUpdater != null) {
expiresUpdater.cancel();
} }
expiresUpdater = new CountDownTimer(messageRecord.getExpiresIn(), TimeUnit.SECONDS.toMillis(1)) {
@Override
public void onTick(long millisUntilFinished) {
int expirationTime = Math.max((int) (TimeUnit.MILLISECONDS.toSeconds(millisUntilFinished)), 1);
String duration = ExpirationUtil.getExpirationDisplayValue(itemView.getContext(), expirationTime);
expiresIn.setText(formatBoldString(R.string.message_details_header_disappears, duration));
}
@Override
public void onFinish() {}
};
expiresUpdater.start();
}
@Override
public void onPause(@NonNull LifecycleOwner owner) {
if (expiresUpdater != null) {
expiresUpdater.cancel();
expiresUpdater = null;
}
}
});
} }
private void bindTransport(MessageRecord messageRecord) { private void bindTransport(MessageRecord messageRecord) {
@ -255,35 +279,4 @@ final class MessageHeaderViewHolder extends RecyclerView.ViewHolder implements G
public @NonNull ProjectionList getColorizerProjections(@NonNull ViewGroup coordinateRoot) { public @NonNull ProjectionList getColorizerProjections(@NonNull ViewGroup coordinateRoot) {
return conversationItem.getColorizerProjections(coordinateRoot); return conversationItem.getColorizerProjections(coordinateRoot);
} }
private class ExpiresUpdater implements Runnable {
private final long expireStartedTimestamp;
private final long expiresInTimestamp;
private boolean running;
ExpiresUpdater(MessageRecord messageRecord) {
expireStartedTimestamp = messageRecord.getExpireStarted();
expiresInTimestamp = messageRecord.getExpiresIn();
running = true;
}
@Override
public void run() {
long elapsed = System.currentTimeMillis() - expireStartedTimestamp;
long remaining = expiresInTimestamp - elapsed;
int expirationTime = Math.max((int) (remaining / 1000), 1);
String duration = ExpirationUtil.getExpirationDisplayValue(itemView.getContext(), expirationTime);
expiresIn.setText(formatBoldString(R.string.message_details_header_disappears, duration));
if (running && expirationTime > 1) {
ThreadUtil.runOnMainDelayed(this, 500);
}
}
void stop() {
running = false;
}
}
} }