Update group update messages faster.
This commit is contained in:
parent
f91494f813
commit
945c308cf5
7 changed files with 50 additions and 55 deletions
|
@ -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();
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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());
|
||||
|
|
Loading…
Add table
Reference in a new issue