Update group update messages faster.

This commit is contained in:
Greyson Parrelli 2022-03-17 14:24:17 -04:00 committed by Cody Henthorne
parent f91494f813
commit 945c308cf5
7 changed files with 50 additions and 55 deletions

View file

@ -22,17 +22,23 @@ import org.thoughtcrime.securesms.database.model.MessageId;
import org.thoughtcrime.securesms.database.model.MessageRecord;
import org.thoughtcrime.securesms.database.model.ReactionRecord;
import org.thoughtcrime.securesms.database.model.SmsMessageRecord;
import org.thoughtcrime.securesms.database.model.UpdateDescription;
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies;
import org.thoughtcrime.securesms.recipients.Recipient;
import org.thoughtcrime.securesms.recipients.RecipientId;
import org.thoughtcrime.securesms.util.Stopwatch;
import org.thoughtcrime.securesms.util.Util;
import org.whispersystems.signalservice.api.push.ServiceId;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;
/**
@ -90,6 +96,7 @@ class ConversationDataSource implements PagedDataSource<MessageId, ConversationM
MentionHelper mentionHelper = new MentionHelper();
AttachmentHelper attachmentHelper = new AttachmentHelper();
ReactionHelper reactionHelper = new ReactionHelper();
Set<ServiceId> referencedIds = new HashSet<>();
try (MmsSmsDatabase.Reader reader = MmsSmsDatabase.readerFor(db.getConversation(threadId, start, length))) {
MessageRecord record;
@ -98,6 +105,11 @@ class ConversationDataSource implements PagedDataSource<MessageId, ConversationM
mentionHelper.add(record);
reactionHelper.add(record);
attachmentHelper.add(record);
UpdateDescription description = record.getUpdateDisplayBody(context);
if (description != null) {
referencedIds.addAll(description.getMentioned());
}
}
}
@ -126,6 +138,11 @@ class ConversationDataSource implements PagedDataSource<MessageId, ConversationM
records = attachmentHelper.buildUpdatedModels(context, records);
stopwatch.split("attachment-models");
for (ServiceId serviceId : referencedIds) {
Recipient.resolved(RecipientId.from(serviceId, null));
}
stopwatch.split("recipient-resolves");
List<ConversationMessage> messages = Stream.of(records)
.map(m -> ConversationMessageFactory.createWithUnresolvedData(context, m, mentionHelper.getMentions(m.getId())))
.toList();

View file

@ -73,13 +73,12 @@ public final class ConversationUpdateItem extends FrameLayout
private Stub<CardView> donateButtonStub;
private View background;
private ConversationMessage conversationMessage;
private Recipient conversationRecipient;
private Optional<MessageRecord> nextMessageRecord;
private MessageRecord messageRecord;
private Recipient conversationRecipient;
private Optional<MessageRecord> nextMessageRecord;
private MessageRecord messageRecord;
private boolean isMessageRequestAccepted;
private LiveData<SpannableString> displayBody;
private EventListener eventListener;
private boolean hasWallpaper;
private final UpdateObserver updateObserver = new UpdateObserver();
@ -146,7 +145,6 @@ public final class ConversationUpdateItem extends FrameLayout
boolean hasWallpaper,
boolean isMessageRequestAccepted)
{
this.hasWallpaper = hasWallpaper;
this.conversationMessage = conversationMessage;
this.messageRecord = conversationMessage.getMessageRecord();
this.nextMessageRecord = nextMessageRecord;

View file

@ -14,15 +14,22 @@ import org.signal.paging.PagedDataSource;
import org.thoughtcrime.securesms.conversationlist.model.Conversation;
import org.thoughtcrime.securesms.conversationlist.model.ConversationReader;
import org.thoughtcrime.securesms.database.SignalDatabase;
import org.thoughtcrime.securesms.database.SmsDatabase;
import org.thoughtcrime.securesms.database.ThreadDatabase;
import org.thoughtcrime.securesms.database.model.MessageRecord;
import org.thoughtcrime.securesms.database.model.ThreadRecord;
import org.thoughtcrime.securesms.database.model.UpdateDescription;
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies;
import org.thoughtcrime.securesms.recipients.Recipient;
import org.thoughtcrime.securesms.recipients.RecipientId;
import org.thoughtcrime.securesms.util.Stopwatch;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;
abstract class ConversationListDataSource implements PagedDataSource<Long, Conversation> {
@ -53,22 +60,32 @@ abstract class ConversationListDataSource implements PagedDataSource<Long, Conve
Stopwatch stopwatch = new Stopwatch("load(" + start + ", " + length + "), " + getClass().getSimpleName());
List<Conversation> conversations = new ArrayList<>(length);
List<Recipient> recipients = new LinkedList<>();
List<Recipient> recipients = new LinkedList<>();
Set<RecipientId> needsResolve = new HashSet<>();
try (ConversationReader reader = new ConversationReader(getCursor(start, length))) {
ThreadRecord record;
while ((record = reader.getNext()) != null && !cancellationSignal.isCanceled()) {
conversations.add(new Conversation(record));
recipients.add(record.getRecipient());
if (!record.getRecipient().isPushV2Group()) {
needsResolve.add(record.getRecipient().getId());
} else if (SmsDatabase.Types.isGroupUpdate(record.getType())) {
UpdateDescription description = MessageRecord.getGv2ChangeDescription(ApplicationDependencies.getApplication(), record.getBody());
needsResolve.addAll(description.getMentioned().stream().map(sid -> RecipientId.from(sid, null)).collect(Collectors.toList()));
}
}
}
stopwatch.split("cursor");
ApplicationDependencies.getRecipientCache().addToCache(recipients);
stopwatch.split("cache-recipients");
Recipient.resolvedList(needsResolve);
stopwatch.split("recipient-resolve");
stopwatch.stop(TAG);
return conversations;

View file

@ -3,6 +3,7 @@ package org.thoughtcrime.securesms.database.model;
import android.content.Context;
import androidx.annotation.DrawableRes;
import androidx.annotation.MainThread;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.WorkerThread;
@ -763,11 +764,9 @@ final class GroupsV2UpdateMessageProducer {
interface DescribeMemberStrategy {
/**
* Map an ACI to a string that describes the group member.
* @param serviceId
* Map a ServiceId to a string that describes the group member.
*/
@NonNull
@WorkerThread
String describe(@NonNull ServiceId serviceId);
}

View file

@ -12,8 +12,10 @@ import android.text.SpannableStringBuilder;
import androidx.annotation.AnyThread;
import androidx.annotation.ColorInt;
import androidx.annotation.MainThread;
import androidx.annotation.NonNull;
import androidx.lifecycle.LiveData;
import androidx.lifecycle.Transformations;
import com.annimon.stream.Stream;
@ -34,11 +36,12 @@ public final class LiveUpdateMessage {
* Creates a live data that observes the recipients mentioned in the {@link UpdateDescription} and
* recreates the string asynchronously when they change.
*/
@AnyThread
@MainThread
public static LiveData<SpannableString> fromMessageDescription(@NonNull Context context,
@NonNull UpdateDescription updateDescription,
@ColorInt int defaultTint,
boolean adjustPosition) {
boolean adjustPosition)
{
if (updateDescription.isStringStatic()) {
return LiveDataUtil.just(toSpannable(context, updateDescription, updateDescription.getStaticString(), defaultTint, adjustPosition));
}
@ -50,16 +53,17 @@ public final class LiveUpdateMessage {
LiveData<?> mentionedRecipientChangeStream = allMentionedRecipients.isEmpty() ? LiveDataUtil.just(new Object())
: LiveDataUtil.merge(allMentionedRecipients);
return LiveDataUtil.mapAsync(mentionedRecipientChangeStream, event -> toSpannable(context, updateDescription, updateDescription.getString(), defaultTint, adjustPosition));
return Transformations.map(mentionedRecipientChangeStream, event -> toSpannable(context, updateDescription, updateDescription.getString(), defaultTint, adjustPosition));
}
/**
* Observes a single recipient and recreates the string asynchronously when they change.
*/
@MainThread
public static LiveData<SpannableString> recipientToStringAsync(@NonNull RecipientId recipientId,
@NonNull Function<Recipient, SpannableString> createStringInBackground)
{
return LiveDataUtil.mapAsync(Recipient.live(recipientId).getLiveDataResolved(), createStringInBackground);
return Transformations.map(Recipient.live(recipientId).getLiveDataResolved(), createStringInBackground::apply);
}
private static @NonNull SpannableString toSpannable(@NonNull Context context, @NonNull UpdateDescription updateDescription, @NonNull String string, @ColorInt int defaultTint, boolean adjustPosition) {

View file

@ -22,7 +22,6 @@ import java.util.Set;
public final class UpdateDescription {
public interface StringFactory {
@WorkerThread
String create();
}
@ -109,14 +108,12 @@ public final class UpdateDescription {
return staticString;
}
ThreadUtil.assertNotMainThread();
//noinspection ConstantConditions
return stringFactory.create();
}
@AnyThread
public Collection<ServiceId> getMentioned() {
public @NonNull Collection<ServiceId> getMentioned() {
return mentioned;
}

View file

@ -1,13 +1,6 @@
package org.thoughtcrime.securesms.database.model;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.mockito.Mock;
import org.mockito.MockedStatic;
import org.mockito.junit.MockitoJUnit;
import org.mockito.junit.MockitoRule;
import org.signal.core.util.ThreadUtil;
import org.whispersystems.signalservice.api.push.ServiceId;
import java.util.Arrays;
@ -22,19 +15,6 @@ import static org.junit.Assert.assertTrue;
public final class UpdateDescriptionTest {
@Rule
public MockitoRule rule = MockitoJUnit.rule();
@Mock
private MockedStatic<ThreadUtil> threadUtilMockedStatic;
@Before
public void setup() {
threadUtilMockedStatic.when(ThreadUtil::isMainThread).thenReturn(true);
threadUtilMockedStatic.when(ThreadUtil::assertMainThread).thenCallRealMethod();
threadUtilMockedStatic.when(ThreadUtil::assertNotMainThread).thenCallRealMethod();
}
@Test
public void staticDescription_byGetStaticString() {
UpdateDescription description = UpdateDescription.staticDescription("update", 0);
@ -56,15 +36,6 @@ public final class UpdateDescriptionTest {
assertEquals("update", description.getString());
}
@Test(expected = AssertionError.class)
public void stringFactory_cannot_run_on_main_thread() {
UpdateDescription description = UpdateDescription.mentioning(Collections.singletonList(ServiceId.from(UUID.randomUUID())), () -> "update", 0);
threadUtilMockedStatic.when(ThreadUtil::isMainThread).thenReturn(true);
description.getString();
}
@Test(expected = UnsupportedOperationException.class)
public void stringFactory_cannot_call_static_string() {
UpdateDescription description = UpdateDescription.mentioning(Collections.singletonList(ServiceId.from(UUID.randomUUID())), () -> "update", 0);
@ -85,8 +56,6 @@ public final class UpdateDescriptionTest {
assertEquals(0, factoryCalls.get());
threadUtilMockedStatic.when(ThreadUtil::isMainThread).thenReturn(false);
String string = description.getString();
assertEquals("update", string);
@ -99,8 +68,6 @@ public final class UpdateDescriptionTest {
UpdateDescription.StringFactory stringFactory = () -> "call" + factoryCalls.incrementAndGet();
UpdateDescription description = UpdateDescription.mentioning(Collections.singletonList(ServiceId.from(UUID.randomUUID())), stringFactory, 0);
threadUtilMockedStatic.when(ThreadUtil::isMainThread).thenReturn(false);
assertEquals("call1", description.getString());
assertEquals("call2", description.getString());
assertEquals("call3", description.getString());
@ -143,8 +110,6 @@ public final class UpdateDescriptionTest {
assertFalse(description.isStringStatic());
threadUtilMockedStatic.when(ThreadUtil::isMainThread).thenReturn(false);
assertEquals("update.11\nupdate.21", description.getString());
assertEquals("update.12\nupdate.22", description.getString());
assertEquals("update.13\nupdate.23", description.getString());
@ -167,8 +132,6 @@ public final class UpdateDescriptionTest {
assertFalse(description.isStringStatic());
threadUtilMockedStatic.when(ThreadUtil::isMainThread).thenReturn(false);
assertEquals("update.101\nstatic\nupdate.201", description.getString());
assertEquals("update.102\nstatic\nupdate.202", description.getString());
assertEquals("update.103\nstatic\nupdate.203", description.getString());