Use resolved recipients in the conversation list.

This commit is contained in:
Greyson Parrelli 2020-07-02 08:19:52 -07:00
parent 70e33518a9
commit c877aba09f
9 changed files with 192 additions and 82 deletions

View File

@ -13,13 +13,16 @@ import org.thoughtcrime.securesms.database.DatabaseContentProviders;
import org.thoughtcrime.securesms.database.DatabaseFactory; import org.thoughtcrime.securesms.database.DatabaseFactory;
import org.thoughtcrime.securesms.database.ThreadDatabase; import org.thoughtcrime.securesms.database.ThreadDatabase;
import org.thoughtcrime.securesms.database.model.ThreadRecord; import org.thoughtcrime.securesms.database.model.ThreadRecord;
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies;
import org.thoughtcrime.securesms.logging.Log; import org.thoughtcrime.securesms.logging.Log;
import org.thoughtcrime.securesms.recipients.Recipient;
import org.thoughtcrime.securesms.util.ThrottledDebouncer; import org.thoughtcrime.securesms.util.ThrottledDebouncer;
import org.thoughtcrime.securesms.util.concurrent.SignalExecutors; import org.thoughtcrime.securesms.util.concurrent.SignalExecutors;
import org.thoughtcrime.securesms.util.paging.Invalidator; import org.thoughtcrime.securesms.util.paging.Invalidator;
import org.thoughtcrime.securesms.util.paging.SizeFixResult; import org.thoughtcrime.securesms.util.paging.SizeFixResult;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List; import java.util.List;
import java.util.concurrent.Executor; import java.util.concurrent.Executor;
@ -66,15 +69,19 @@ abstract class ConversationListDataSource extends PositionalDataSource<Conversat
List<Conversation> conversations = new ArrayList<>(params.requestedLoadSize); List<Conversation> conversations = new ArrayList<>(params.requestedLoadSize);
int totalCount = getTotalCount(); int totalCount = getTotalCount();
int effectiveCount = params.requestedStartPosition; int effectiveCount = params.requestedStartPosition;
List<Recipient> recipients = new LinkedList<>();
try (ThreadDatabase.Reader reader = threadDatabase.readerFor(getCursor(params.requestedStartPosition, params.requestedLoadSize))) { try (ThreadDatabase.Reader reader = threadDatabase.readerFor(getCursor(params.requestedStartPosition, params.requestedLoadSize))) {
ThreadRecord record; ThreadRecord record;
while ((record = reader.getNext()) != null && effectiveCount < totalCount && !isInvalid()) { while ((record = reader.getNext()) != null && effectiveCount < totalCount && !isInvalid()) {
conversations.add(new Conversation(record)); conversations.add(new Conversation(record));
recipients.add(record.getRecipient());
effectiveCount++; effectiveCount++;
} }
} }
ApplicationDependencies.getRecipientCache().addToCache(recipients);
if (!isInvalid()) { if (!isInvalid()) {
SizeFixResult<Conversation> result = SizeFixResult.ensureMultipleOfPageSize(conversations, params.requestedStartPosition, params.pageSize, totalCount); SizeFixResult<Conversation> result = SizeFixResult.ensureMultipleOfPageSize(conversations, params.requestedStartPosition, params.pageSize, totalCount);
@ -89,14 +96,18 @@ abstract class ConversationListDataSource extends PositionalDataSource<Conversat
long start = System.currentTimeMillis(); long start = System.currentTimeMillis();
List<Conversation> conversations = new ArrayList<>(params.loadSize); List<Conversation> conversations = new ArrayList<>(params.loadSize);
List<Recipient> recipients = new LinkedList<>();
try (ThreadDatabase.Reader reader = threadDatabase.readerFor(getCursor(params.startPosition, params.loadSize))) { try (ThreadDatabase.Reader reader = threadDatabase.readerFor(getCursor(params.startPosition, params.loadSize))) {
ThreadRecord record; ThreadRecord record;
while ((record = reader.getNext()) != null && !isInvalid()) { while ((record = reader.getNext()) != null && !isInvalid()) {
conversations.add(new Conversation(record)); conversations.add(new Conversation(record));
recipients.add(record.getRecipient());
} }
} }
ApplicationDependencies.getRecipientCache().addToCache(recipients);
callback.onResult(conversations); callback.onResult(conversations);
Log.d(TAG, "[Update] " + (System.currentTimeMillis() - start) + " ms | start: " + params.startPosition + ", size: " + params.loadSize + ", class: " + getClass().getSimpleName() + (isInvalid() ? " -- invalidated" : "")); Log.d(TAG, "[Update] " + (System.currentTimeMillis() - start) + " ms | start: " + params.startPosition + ", size: " + params.loadSize + ", class: " + getClass().getSimpleName() + (isInvalid() ? " -- invalidated" : ""));

View File

@ -96,7 +96,7 @@ public final class GroupDatabase extends Database {
private static final String[] GROUP_PROJECTION = { private static final String[] GROUP_PROJECTION = {
GROUP_ID, RECIPIENT_ID, TITLE, MEMBERS, AVATAR_ID, AVATAR_KEY, AVATAR_CONTENT_TYPE, AVATAR_RELAY, AVATAR_DIGEST, GROUP_ID, RECIPIENT_ID, TITLE, MEMBERS, AVATAR_ID, AVATAR_KEY, AVATAR_CONTENT_TYPE, AVATAR_RELAY, AVATAR_DIGEST,
TIMESTAMP, ACTIVE, MMS TIMESTAMP, ACTIVE, MMS, V2_MASTER_KEY, V2_REVISION, V2_DECRYPTED_GROUP
}; };
static final List<String> TYPED_GROUP_PROJECTION = Stream.of(GROUP_PROJECTION).map(columnName -> TABLE_NAME + "." + columnName).toList(); static final List<String> TYPED_GROUP_PROJECTION = Stream.of(GROUP_PROJECTION).map(columnName -> TABLE_NAME + "." + columnName).toList();

View File

@ -37,6 +37,7 @@ import org.thoughtcrime.securesms.storage.StorageSyncHelper;
import org.thoughtcrime.securesms.storage.StorageSyncHelper.RecordUpdate; import org.thoughtcrime.securesms.storage.StorageSyncHelper.RecordUpdate;
import org.thoughtcrime.securesms.storage.StorageSyncModels; import org.thoughtcrime.securesms.storage.StorageSyncModels;
import org.thoughtcrime.securesms.util.Base64; import org.thoughtcrime.securesms.util.Base64;
import org.thoughtcrime.securesms.util.CursorUtil;
import org.thoughtcrime.securesms.util.FeatureFlags; import org.thoughtcrime.securesms.util.FeatureFlags;
import org.thoughtcrime.securesms.util.IdentityUtil; import org.thoughtcrime.securesms.util.IdentityUtil;
import org.thoughtcrime.securesms.util.SqlUtil; import org.thoughtcrime.securesms.util.SqlUtil;
@ -827,44 +828,45 @@ public class RecipientDatabase extends Database {
return out; return out;
} }
private static @NonNull RecipientSettings getRecipientSettings(@NonNull Context context, @NonNull Cursor cursor) { static @NonNull RecipientSettings getRecipientSettings(@NonNull Context context, @NonNull Cursor cursor) {
long id = cursor.getLong(cursor.getColumnIndexOrThrow(ID)); long id = CursorUtil.requireLong(cursor, ID);
UUID uuid = UuidUtil.parseOrNull(cursor.getString(cursor.getColumnIndexOrThrow(UUID))); UUID uuid = UuidUtil.parseOrNull(CursorUtil.requireString(cursor, UUID));
String username = cursor.getString(cursor.getColumnIndexOrThrow(USERNAME)); String username = CursorUtil.requireString(cursor, USERNAME);
String e164 = cursor.getString(cursor.getColumnIndexOrThrow(PHONE)); String e164 = CursorUtil.requireString(cursor, PHONE);
String email = cursor.getString(cursor.getColumnIndexOrThrow(EMAIL)); String email = CursorUtil.requireString(cursor, EMAIL);
GroupId groupId = GroupId.parseNullableOrThrow(cursor.getString(cursor.getColumnIndexOrThrow(GROUP_ID))); GroupId groupId = GroupId.parseNullableOrThrow(CursorUtil.requireString(cursor, GROUP_ID));
int groupType = cursor.getInt(cursor.getColumnIndexOrThrow(GROUP_TYPE)); int groupType = CursorUtil.requireInt(cursor, GROUP_TYPE);
boolean blocked = cursor.getInt(cursor.getColumnIndexOrThrow(BLOCKED)) == 1; boolean blocked = CursorUtil.requireBoolean(cursor, BLOCKED);
String messageRingtone = cursor.getString(cursor.getColumnIndexOrThrow(MESSAGE_RINGTONE)); String messageRingtone = CursorUtil.requireString(cursor, MESSAGE_RINGTONE);
String callRingtone = cursor.getString(cursor.getColumnIndexOrThrow(CALL_RINGTONE)); String callRingtone = CursorUtil.requireString(cursor, CALL_RINGTONE);
int messageVibrateState = cursor.getInt(cursor.getColumnIndexOrThrow(MESSAGE_VIBRATE)); int messageVibrateState = CursorUtil.requireInt(cursor, MESSAGE_VIBRATE);
int callVibrateState = cursor.getInt(cursor.getColumnIndexOrThrow(CALL_VIBRATE)); int callVibrateState = CursorUtil.requireInt(cursor, CALL_VIBRATE);
long muteUntil = cursor.getLong(cursor.getColumnIndexOrThrow(MUTE_UNTIL)); long muteUntil = cursor.getLong(cursor.getColumnIndexOrThrow(MUTE_UNTIL));
String serializedColor = cursor.getString(cursor.getColumnIndexOrThrow(COLOR)); String serializedColor = CursorUtil.requireString(cursor, COLOR);
int insightsBannerTier = cursor.getInt(cursor.getColumnIndexOrThrow(SEEN_INVITE_REMINDER)); int insightsBannerTier = CursorUtil.requireInt(cursor, SEEN_INVITE_REMINDER);
int defaultSubscriptionId = cursor.getInt(cursor.getColumnIndexOrThrow(DEFAULT_SUBSCRIPTION_ID)); int defaultSubscriptionId = CursorUtil.requireInt(cursor, DEFAULT_SUBSCRIPTION_ID);
int expireMessages = cursor.getInt(cursor.getColumnIndexOrThrow(MESSAGE_EXPIRATION_TIME)); int expireMessages = CursorUtil.requireInt(cursor, MESSAGE_EXPIRATION_TIME);
int registeredState = cursor.getInt(cursor.getColumnIndexOrThrow(REGISTERED)); int registeredState = CursorUtil.requireInt(cursor, REGISTERED);
String profileKeyString = cursor.getString(cursor.getColumnIndexOrThrow(PROFILE_KEY)); String profileKeyString = CursorUtil.requireString(cursor, PROFILE_KEY);
String profileKeyCredentialString = cursor.getString(cursor.getColumnIndexOrThrow(PROFILE_KEY_CREDENTIAL)); String profileKeyCredentialString = CursorUtil.requireString(cursor, PROFILE_KEY_CREDENTIAL);
String systemDisplayName = cursor.getString(cursor.getColumnIndexOrThrow(SYSTEM_DISPLAY_NAME)); String systemDisplayName = CursorUtil.requireString(cursor, SYSTEM_DISPLAY_NAME);
String systemContactPhoto = cursor.getString(cursor.getColumnIndexOrThrow(SYSTEM_PHOTO_URI)); String systemContactPhoto = CursorUtil.requireString(cursor, SYSTEM_PHOTO_URI);
String systemPhoneLabel = cursor.getString(cursor.getColumnIndexOrThrow(SYSTEM_PHONE_LABEL)); String systemPhoneLabel = CursorUtil.requireString(cursor, SYSTEM_PHONE_LABEL);
String systemContactUri = cursor.getString(cursor.getColumnIndexOrThrow(SYSTEM_CONTACT_URI)); String systemContactUri = CursorUtil.requireString(cursor, SYSTEM_CONTACT_URI);
String profileGivenName = cursor.getString(cursor.getColumnIndexOrThrow(PROFILE_GIVEN_NAME)); String profileGivenName = CursorUtil.requireString(cursor, PROFILE_GIVEN_NAME);
String profileFamilyName = cursor.getString(cursor.getColumnIndexOrThrow(PROFILE_FAMILY_NAME)); String profileFamilyName = CursorUtil.requireString(cursor, PROFILE_FAMILY_NAME);
String signalProfileAvatar = cursor.getString(cursor.getColumnIndexOrThrow(SIGNAL_PROFILE_AVATAR)); String signalProfileAvatar = CursorUtil.requireString(cursor, SIGNAL_PROFILE_AVATAR);
boolean profileSharing = cursor.getInt(cursor.getColumnIndexOrThrow(PROFILE_SHARING)) == 1; boolean profileSharing = CursorUtil.requireBoolean(cursor, PROFILE_SHARING);
long lastProfileFetch = cursor.getLong(cursor.getColumnIndexOrThrow(LAST_PROFILE_FETCH)); long lastProfileFetch = cursor.getLong(cursor.getColumnIndexOrThrow(LAST_PROFILE_FETCH));
String notificationChannel = cursor.getString(cursor.getColumnIndexOrThrow(NOTIFICATION_CHANNEL)); String notificationChannel = CursorUtil.requireString(cursor, NOTIFICATION_CHANNEL);
int unidentifiedAccessMode = cursor.getInt(cursor.getColumnIndexOrThrow(UNIDENTIFIED_ACCESS_MODE)); int unidentifiedAccessMode = CursorUtil.requireInt(cursor, UNIDENTIFIED_ACCESS_MODE);
boolean forceSmsSelection = cursor.getInt(cursor.getColumnIndexOrThrow(FORCE_SMS_SELECTION)) == 1; boolean forceSmsSelection = CursorUtil.requireBoolean(cursor, FORCE_SMS_SELECTION);
int uuidCapabilityValue = cursor.getInt(cursor.getColumnIndexOrThrow(UUID_CAPABILITY)); int uuidCapabilityValue = CursorUtil.requireInt(cursor, UUID_CAPABILITY);
int groupsV2CapabilityValue = cursor.getInt(cursor.getColumnIndexOrThrow(GROUPS_V2_CAPABILITY)); int groupsV2CapabilityValue = CursorUtil.requireInt(cursor, GROUPS_V2_CAPABILITY);
String storageKeyRaw = cursor.getString(cursor.getColumnIndexOrThrow(STORAGE_SERVICE_ID)); String storageKeyRaw = CursorUtil.requireString(cursor, STORAGE_SERVICE_ID);
String identityKeyRaw = cursor.getString(cursor.getColumnIndexOrThrow(IDENTITY_KEY));
int identityStatusRaw = cursor.getInt(cursor.getColumnIndexOrThrow(IDENTITY_STATUS)); Optional<String> identityKeyRaw = CursorUtil.getString(cursor, IDENTITY_KEY);
Optional<Integer> identityStatusRaw = CursorUtil.getInt(cursor, IDENTITY_STATUS);
int masterKeyIndex = cursor.getColumnIndex(GroupDatabase.V2_MASTER_KEY); int masterKeyIndex = cursor.getColumnIndex(GroupDatabase.V2_MASTER_KEY);
GroupMasterKey groupMasterKey = null; GroupMasterKey groupMasterKey = null;
@ -909,9 +911,9 @@ public class RecipientDatabase extends Database {
} }
byte[] storageKey = storageKeyRaw != null ? Base64.decodeOrThrow(storageKeyRaw) : null; byte[] storageKey = storageKeyRaw != null ? Base64.decodeOrThrow(storageKeyRaw) : null;
byte[] identityKey = identityKeyRaw != null ? Base64.decodeOrThrow(identityKeyRaw) : null; byte[] identityKey = identityKeyRaw.transform(Base64::decodeOrThrow).orNull();;
IdentityDatabase.VerifiedStatus identityStatus = IdentityDatabase.VerifiedStatus.forState(identityStatusRaw); IdentityDatabase.VerifiedStatus identityStatus = identityStatusRaw.transform(IdentityDatabase.VerifiedStatus::forState).or(IdentityDatabase.VerifiedStatus.DEFAULT);
return new RecipientSettings(RecipientId.from(id), uuid, username, e164, email, groupId, groupMasterKey, GroupType.fromId(groupType), blocked, muteUntil, return new RecipientSettings(RecipientId.from(id), uuid, username, e164, email, groupId, groupMasterKey, GroupType.fromId(groupType), blocked, muteUntil,
VibrateState.fromId(messageVibrateState), VibrateState.fromId(messageVibrateState),

View File

@ -33,6 +33,7 @@ import net.sqlcipher.database.SQLiteDatabase;
import org.signal.storageservice.protos.groups.local.DecryptedGroup; import org.signal.storageservice.protos.groups.local.DecryptedGroup;
import org.thoughtcrime.securesms.database.MessagingDatabase.MarkedMessageInfo; import org.thoughtcrime.securesms.database.MessagingDatabase.MarkedMessageInfo;
import org.thoughtcrime.securesms.database.RecipientDatabase.RecipientSettings;
import org.thoughtcrime.securesms.database.helpers.SQLCipherOpenHelper; import org.thoughtcrime.securesms.database.helpers.SQLCipherOpenHelper;
import org.thoughtcrime.securesms.database.model.MediaMmsMessageRecord; import org.thoughtcrime.securesms.database.model.MediaMmsMessageRecord;
import org.thoughtcrime.securesms.database.model.MessageRecord; import org.thoughtcrime.securesms.database.model.MessageRecord;
@ -42,9 +43,11 @@ import org.thoughtcrime.securesms.logging.Log;
import org.thoughtcrime.securesms.mms.Slide; import org.thoughtcrime.securesms.mms.Slide;
import org.thoughtcrime.securesms.mms.SlideDeck; import org.thoughtcrime.securesms.mms.SlideDeck;
import org.thoughtcrime.securesms.recipients.Recipient; import org.thoughtcrime.securesms.recipients.Recipient;
import org.thoughtcrime.securesms.recipients.RecipientDetails;
import org.thoughtcrime.securesms.recipients.RecipientId; import org.thoughtcrime.securesms.recipients.RecipientId;
import org.thoughtcrime.securesms.recipients.RecipientUtil; import org.thoughtcrime.securesms.recipients.RecipientUtil;
import org.thoughtcrime.securesms.storage.StorageSyncHelper; import org.thoughtcrime.securesms.storage.StorageSyncHelper;
import org.thoughtcrime.securesms.util.CursorUtil;
import org.thoughtcrime.securesms.util.JsonUtils; import org.thoughtcrime.securesms.util.JsonUtils;
import org.thoughtcrime.securesms.util.TextSecurePreferences; import org.thoughtcrime.securesms.util.TextSecurePreferences;
import org.thoughtcrime.securesms.util.Util; import org.thoughtcrime.securesms.util.Util;
@ -928,9 +931,29 @@ public class ThreadDatabase extends Database {
} }
public ThreadRecord getCurrent() { public ThreadRecord getCurrent() {
RecipientId recipientId = RecipientId.from(cursor.getLong(cursor.getColumnIndexOrThrow(ThreadDatabase.RECIPIENT_ID))); RecipientId recipientId = RecipientId.from(CursorUtil.requireLong(cursor, ThreadDatabase.RECIPIENT_ID));
Recipient recipient = Recipient.live(recipientId).get(); RecipientSettings recipientSettings = RecipientDatabase.getRecipientSettings(context, cursor);
Recipient recipient;
if (recipientSettings.getGroupId() != null) {
GroupDatabase.GroupRecord group = new GroupDatabase.Reader(cursor).getCurrent();
if (group != null) {
RecipientDetails details = new RecipientDetails(group.getTitle(),
group.hasAvatar() ? Optional.of(group.getAvatarId()) : Optional.absent(),
false,
false,
recipientSettings,
null);
recipient = new Recipient(recipientId, details, false);
} else {
recipient = Recipient.live(recipientId).get();
}
} else {
RecipientDetails details = RecipientDetails.forIndividual(context, recipientSettings);
recipient = new Recipient(recipientId, details, false);
}
int readReceiptCount = TextSecurePreferences.isReadReceiptsEnabled(context) ? cursor.getInt(cursor.getColumnIndexOrThrow(ThreadDatabase.READ_RECEIPT_COUNT)) int readReceiptCount = TextSecurePreferences.isReadReceiptsEnabled(context) ? cursor.getInt(cursor.getColumnIndexOrThrow(ThreadDatabase.READ_RECEIPT_COUNT))
: 0; : 0;

View File

@ -40,7 +40,6 @@ public final class LiveRecipient {
private final AtomicReference<Recipient> recipient; private final AtomicReference<Recipient> recipient;
private final RecipientDatabase recipientDatabase; private final RecipientDatabase recipientDatabase;
private final GroupDatabase groupDatabase; private final GroupDatabase groupDatabase;
private final String unnamedGroupName;
LiveRecipient(@NonNull Context context, @NonNull MutableLiveData<Recipient> liveData, @NonNull Recipient defaultRecipient) { LiveRecipient(@NonNull Context context, @NonNull MutableLiveData<Recipient> liveData, @NonNull Recipient defaultRecipient) {
this.context = context.getApplicationContext(); this.context = context.getApplicationContext();
@ -48,7 +47,6 @@ public final class LiveRecipient {
this.recipient = new AtomicReference<>(defaultRecipient); this.recipient = new AtomicReference<>(defaultRecipient);
this.recipientDatabase = DatabaseFactory.getRecipientDatabase(context); this.recipientDatabase = DatabaseFactory.getRecipientDatabase(context);
this.groupDatabase = DatabaseFactory.getGroupDatabase(context); this.groupDatabase = DatabaseFactory.getGroupDatabase(context);
this.unnamedGroupName = context.getString(R.string.RecipientProvider_unnamed_group);
this.observers = new CopyOnWriteArraySet<>(); this.observers = new CopyOnWriteArraySet<>();
this.foreverObserver = recipient -> { this.foreverObserver = recipient -> {
for (RecipientForeverObserver o : observers) { for (RecipientForeverObserver o : observers) {
@ -175,21 +173,13 @@ public final class LiveRecipient {
private @NonNull Recipient fetchAndCacheRecipientFromDisk(@NonNull RecipientId id) { private @NonNull Recipient fetchAndCacheRecipientFromDisk(@NonNull RecipientId id) {
RecipientSettings settings = recipientDatabase.getRecipientSettings(id); RecipientSettings settings = recipientDatabase.getRecipientSettings(id);
RecipientDetails details = settings.getGroupId() != null ? getGroupRecipientDetails(settings) RecipientDetails details = settings.getGroupId() != null ? getGroupRecipientDetails(settings)
: getIndividualRecipientDetails(settings); : RecipientDetails.forIndividual(context, settings);
Recipient recipient = new Recipient(id, details); Recipient recipient = new Recipient(id, details, true);
RecipientIdCache.INSTANCE.put(recipient); RecipientIdCache.INSTANCE.put(recipient);
return recipient; return recipient;
} }
private @NonNull RecipientDetails getIndividualRecipientDetails(RecipientSettings settings) {
boolean systemContact = !TextUtils.isEmpty(settings.getSystemDisplayName());
boolean isLocalNumber = (settings.getE164() != null && settings.getE164().equals(TextSecurePreferences.getLocalNumber(context))) ||
(settings.getUuid() != null && settings.getUuid().equals(TextSecurePreferences.getLocalUuid(context)));
return new RecipientDetails(context, null, Optional.absent(), systemContact, isLocalNumber, settings, null);
}
@WorkerThread @WorkerThread
private @NonNull RecipientDetails getGroupRecipientDetails(@NonNull RecipientSettings settings) { private @NonNull RecipientDetails getGroupRecipientDetails(@NonNull RecipientSettings settings) {
Optional<GroupRecord> groupRecord = groupDatabase.getGroup(settings.getId()); Optional<GroupRecord> groupRecord = groupDatabase.getGroup(settings.getId());
@ -199,21 +189,17 @@ public final class LiveRecipient {
List<Recipient> members = Stream.of(groupRecord.get().getMembers()).filterNot(RecipientId::isUnknown).map(this::fetchAndCacheRecipientFromDisk).toList(); List<Recipient> members = Stream.of(groupRecord.get().getMembers()).filterNot(RecipientId::isUnknown).map(this::fetchAndCacheRecipientFromDisk).toList();
Optional<Long> avatarId = Optional.absent(); Optional<Long> avatarId = Optional.absent();
if (settings.getGroupId() != null && settings.getGroupId().isPush() && title == null) {
title = unnamedGroupName;
}
if (groupRecord.get().hasAvatar()) { if (groupRecord.get().hasAvatar()) {
avatarId = Optional.of(groupRecord.get().getAvatarId()); avatarId = Optional.of(groupRecord.get().getAvatarId());
} }
return new RecipientDetails(context, title, avatarId, false, false, settings, members); return new RecipientDetails(title, avatarId, false, false, settings, members);
} }
return new RecipientDetails(context, unnamedGroupName, Optional.absent(), false, false, settings, null); return new RecipientDetails(null, Optional.absent(), false, false, settings, null);
} }
private synchronized void set(@NonNull Recipient recipient) { synchronized void set(@NonNull Recipient recipient) {
this.recipient.set(recipient); this.recipient.set(recipient);
this.liveData.postValue(recipient); this.liveData.postValue(recipient);
} }

View File

@ -20,6 +20,7 @@ import org.thoughtcrime.securesms.util.TextSecurePreferences;
import org.thoughtcrime.securesms.util.concurrent.SignalExecutors; import org.thoughtcrime.securesms.util.concurrent.SignalExecutors;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections; import java.util.Collections;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
@ -75,6 +76,40 @@ public final class LiveRecipientCache {
return live; return live;
} }
/**
* Adds a recipient to the cache if we don't have an entry. This will also update a cache entry
* if the provided recipient is resolved, or if the existing cache entry is unresolved.
*
* If the recipient you add is unresolved, this will enqueue a resolve on a background thread.
*/
@AnyThread
public synchronized void addToCache(@NonNull Collection<Recipient> newRecipients) {
for (Recipient recipient : newRecipients) {
LiveRecipient live = recipients.get(recipient.getId());
boolean needsResolve = false;
if (live == null) {
live = new LiveRecipient(context, new MutableLiveData<>(), recipient);
recipients.put(recipient.getId(), live);
needsResolve = recipient.isResolving();
} else if (live.get().isResolving() || !recipient.isResolving()) {
live.set(recipient);
needsResolve = recipient.isResolving();
}
if (needsResolve) {
MissingRecipientException prettyStackTraceError = new MissingRecipientException(recipient.getId());
SignalExecutors.BOUNDED.execute(() -> {
try {
recipient.resolve();
} catch (MissingRecipientException e) {
throw prettyStackTraceError;
}
});
}
}
}
@NonNull Recipient getSelf() { @NonNull Recipient getSelf() {
synchronized (this) { synchronized (this) {
if (localRecipientId == null) { if (localRecipientId == null) {
@ -107,23 +142,22 @@ public final class LiveRecipientCache {
} }
SignalExecutors.BOUNDED.execute(() -> { SignalExecutors.BOUNDED.execute(() -> {
ThreadDatabase threadDatabase = DatabaseFactory.getThreadDatabase(context); ThreadDatabase threadDatabase = DatabaseFactory.getThreadDatabase(context);
List<Recipient> recipients = new ArrayList<>();
try (ThreadDatabase.Reader reader = threadDatabase.readerFor(threadDatabase.getConversationList())) { try (ThreadDatabase.Reader reader = threadDatabase.readerFor(threadDatabase.getConversationList())) {
int i = 0; int i = 0;
ThreadRecord record = null; ThreadRecord record = null;
List<Recipient> recipients = new ArrayList<>();
while ((record = reader.getNext()) != null && i < CACHE_WARM_MAX) { while ((record = reader.getNext()) != null && i < CACHE_WARM_MAX) {
recipients.add(record.getRecipient()); recipients.add(record.getRecipient());
i++; i++;
} }
Log.d(TAG, "Warming up " + recipients.size() + " recipients.");
Collections.reverse(recipients);
Stream.of(recipients).map(Recipient::getId).forEach(this::getLive);
} }
Log.d(TAG, "Warming up " + recipients.size() + " recipients.");
Collections.reverse(recipients);
Stream.of(recipients).map(Recipient::getId).forEach(this::getLive);
}); });
} }

View File

@ -54,7 +54,7 @@ import static org.thoughtcrime.securesms.database.RecipientDatabase.InsightsBann
public class Recipient { public class Recipient {
public static final Recipient UNKNOWN = new Recipient(RecipientId.UNKNOWN, new RecipientDetails()); public static final Recipient UNKNOWN = new Recipient(RecipientId.UNKNOWN, new RecipientDetails(), true);
private static final FallbackPhotoProvider DEFAULT_FALLBACK_PHOTO_PROVIDER = new FallbackPhotoProvider(); private static final FallbackPhotoProvider DEFAULT_FALLBACK_PHOTO_PROVIDER = new FallbackPhotoProvider();
private static final String TAG = Log.tag(Recipient.class); private static final String TAG = Log.tag(Recipient.class);
@ -344,9 +344,9 @@ public class Recipient {
this.identityStatus = VerifiedStatus.DEFAULT; this.identityStatus = VerifiedStatus.DEFAULT;
} }
Recipient(@NonNull RecipientId id, @NonNull RecipientDetails details) { public Recipient(@NonNull RecipientId id, @NonNull RecipientDetails details, boolean resolved) {
this.id = id; this.id = id;
this.resolving = false; this.resolving = !resolved;
this.uuid = details.uuid; this.uuid = details.uuid;
this.username = details.username; this.username = details.username;
this.e164 = details.e164; this.e164 = details.e164;
@ -408,9 +408,11 @@ public class Recipient {
} }
return Util.join(names, ", "); return Util.join(names, ", ");
} else if (name == null && groupId != null && groupId.isPush()) {
return context.getString(R.string.RecipientProvider_unnamed_group);
} else {
return this.name;
} }
return this.name;
} }
/** /**
@ -657,7 +659,7 @@ public class Recipient {
public @NonNull FallbackContactPhoto getFallbackContactPhoto(@NonNull FallbackPhotoProvider fallbackPhotoProvider) { public @NonNull FallbackContactPhoto getFallbackContactPhoto(@NonNull FallbackPhotoProvider fallbackPhotoProvider) {
if (localNumber) return fallbackPhotoProvider.getPhotoForLocalNumber(); if (localNumber) return fallbackPhotoProvider.getPhotoForLocalNumber();
if (isResolving()) return fallbackPhotoProvider.getPhotoForResolvingRecipient(); else if (isResolving()) return fallbackPhotoProvider.getPhotoForResolvingRecipient();
else if (isGroupInternal()) return fallbackPhotoProvider.getPhotoForGroup(); else if (isGroupInternal()) return fallbackPhotoProvider.getPhotoForGroup();
else if (isGroup()) return fallbackPhotoProvider.getPhotoForGroup(); else if (isGroup()) return fallbackPhotoProvider.getPhotoForGroup();
else if (!TextUtils.isEmpty(name)) return fallbackPhotoProvider.getPhotoForRecipientWithName(name); else if (!TextUtils.isEmpty(name)) return fallbackPhotoProvider.getPhotoForRecipientWithName(name);

View File

@ -2,6 +2,7 @@ package org.thoughtcrime.securesms.recipients;
import android.content.Context; import android.content.Context;
import android.net.Uri; import android.net.Uri;
import android.text.TextUtils;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
@ -66,13 +67,12 @@ public class RecipientDetails {
final byte[] identityKey; final byte[] identityKey;
final VerifiedStatus identityStatus; final VerifiedStatus identityStatus;
RecipientDetails(@NonNull Context context, public RecipientDetails(@Nullable String name,
@Nullable String name, @NonNull Optional<Long> groupAvatarId,
@NonNull Optional<Long> groupAvatarId, boolean systemContact,
boolean systemContact, boolean isLocalNumber,
boolean isLocalNumber, @NonNull RecipientSettings settings,
@NonNull RecipientSettings settings, @Nullable List<Recipient> participants)
@Nullable List<Recipient> participants)
{ {
this.groupAvatarId = groupAvatarId; this.groupAvatarId = groupAvatarId;
this.systemContactPhoto = Util.uri(settings.getSystemContactPhotoUri()); this.systemContactPhoto = Util.uri(settings.getSystemContactPhotoUri());
@ -161,4 +161,12 @@ public class RecipientDetails {
this.identityKey = null; this.identityKey = null;
this.identityStatus = VerifiedStatus.DEFAULT; this.identityStatus = VerifiedStatus.DEFAULT;
} }
public static @NonNull RecipientDetails forIndividual(@NonNull Context context, @NonNull RecipientSettings settings) {
boolean systemContact = !TextUtils.isEmpty(settings.getSystemDisplayName());
boolean isLocalNumber = (settings.getE164() != null && settings.getE164().equals(TextSecurePreferences.getLocalNumber(context))) ||
(settings.getUuid() != null && settings.getUuid().equals(TextSecurePreferences.getLocalUuid(context)));
return new RecipientDetails(null, Optional.absent(), systemContact, isLocalNumber, settings, null);
}
} }

View File

@ -0,0 +1,44 @@
package org.thoughtcrime.securesms.util;
import android.database.Cursor;
import androidx.annotation.NonNull;
import org.whispersystems.libsignal.util.guava.Optional;
public final class CursorUtil {
private CursorUtil() {}
public static String requireString(@NonNull Cursor cursor, @NonNull String column) {
return cursor.getString(cursor.getColumnIndexOrThrow(column));
}
public static int requireInt(@NonNull Cursor cursor, @NonNull String column) {
return cursor.getInt(cursor.getColumnIndexOrThrow(column));
}
public static long requireLong(@NonNull Cursor cursor, @NonNull String column) {
return cursor.getLong(cursor.getColumnIndexOrThrow(column));
}
public static boolean requireBoolean(@NonNull Cursor cursor, @NonNull String column) {
return requireInt(cursor, column) != 0;
}
public static Optional<String> getString(@NonNull Cursor cursor, @NonNull String column) {
if (cursor.getColumnIndex(column) < 0) {
return Optional.absent();
} else {
return Optional.fromNullable(requireString(cursor, column));
}
}
public static Optional<Integer> getInt(@NonNull Cursor cursor, @NonNull String column) {
if (cursor.getColumnIndex(column) < 0) {
return Optional.absent();
} else {
return Optional.of(requireInt(cursor, column));
}
}
}