diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversationlist/ConversationListDataSource.java b/app/src/main/java/org/thoughtcrime/securesms/conversationlist/ConversationListDataSource.java index bd087a83b8..a8e35902c6 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversationlist/ConversationListDataSource.java +++ b/app/src/main/java/org/thoughtcrime/securesms/conversationlist/ConversationListDataSource.java @@ -13,13 +13,16 @@ import org.thoughtcrime.securesms.database.DatabaseContentProviders; import org.thoughtcrime.securesms.database.DatabaseFactory; import org.thoughtcrime.securesms.database.ThreadDatabase; import org.thoughtcrime.securesms.database.model.ThreadRecord; +import org.thoughtcrime.securesms.dependencies.ApplicationDependencies; import org.thoughtcrime.securesms.logging.Log; +import org.thoughtcrime.securesms.recipients.Recipient; import org.thoughtcrime.securesms.util.ThrottledDebouncer; import org.thoughtcrime.securesms.util.concurrent.SignalExecutors; import org.thoughtcrime.securesms.util.paging.Invalidator; import org.thoughtcrime.securesms.util.paging.SizeFixResult; import java.util.ArrayList; +import java.util.LinkedList; import java.util.List; import java.util.concurrent.Executor; @@ -66,15 +69,19 @@ abstract class ConversationListDataSource extends PositionalDataSource conversations = new ArrayList<>(params.requestedLoadSize); int totalCount = getTotalCount(); int effectiveCount = params.requestedStartPosition; + List recipients = new LinkedList<>(); try (ThreadDatabase.Reader reader = threadDatabase.readerFor(getCursor(params.requestedStartPosition, params.requestedLoadSize))) { ThreadRecord record; while ((record = reader.getNext()) != null && effectiveCount < totalCount && !isInvalid()) { conversations.add(new Conversation(record)); + recipients.add(record.getRecipient()); effectiveCount++; } } + ApplicationDependencies.getRecipientCache().addToCache(recipients); + if (!isInvalid()) { SizeFixResult result = SizeFixResult.ensureMultipleOfPageSize(conversations, params.requestedStartPosition, params.pageSize, totalCount); @@ -89,14 +96,18 @@ abstract class ConversationListDataSource extends PositionalDataSource conversations = new ArrayList<>(params.loadSize); + List recipients = new LinkedList<>(); try (ThreadDatabase.Reader reader = threadDatabase.readerFor(getCursor(params.startPosition, params.loadSize))) { ThreadRecord record; while ((record = reader.getNext()) != null && !isInvalid()) { conversations.add(new Conversation(record)); + recipients.add(record.getRecipient()); } } + ApplicationDependencies.getRecipientCache().addToCache(recipients); + callback.onResult(conversations); Log.d(TAG, "[Update] " + (System.currentTimeMillis() - start) + " ms | start: " + params.startPosition + ", size: " + params.loadSize + ", class: " + getClass().getSimpleName() + (isInvalid() ? " -- invalidated" : "")); diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/GroupDatabase.java b/app/src/main/java/org/thoughtcrime/securesms/database/GroupDatabase.java index a2e0daf6ca..82bca9494e 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/GroupDatabase.java +++ b/app/src/main/java/org/thoughtcrime/securesms/database/GroupDatabase.java @@ -96,7 +96,7 @@ public final class GroupDatabase extends Database { private static final String[] GROUP_PROJECTION = { 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 TYPED_GROUP_PROJECTION = Stream.of(GROUP_PROJECTION).map(columnName -> TABLE_NAME + "." + columnName).toList(); diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/RecipientDatabase.java b/app/src/main/java/org/thoughtcrime/securesms/database/RecipientDatabase.java index 4786f2b69d..1c6363933a 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/RecipientDatabase.java +++ b/app/src/main/java/org/thoughtcrime/securesms/database/RecipientDatabase.java @@ -37,6 +37,7 @@ import org.thoughtcrime.securesms.storage.StorageSyncHelper; import org.thoughtcrime.securesms.storage.StorageSyncHelper.RecordUpdate; import org.thoughtcrime.securesms.storage.StorageSyncModels; import org.thoughtcrime.securesms.util.Base64; +import org.thoughtcrime.securesms.util.CursorUtil; import org.thoughtcrime.securesms.util.FeatureFlags; import org.thoughtcrime.securesms.util.IdentityUtil; import org.thoughtcrime.securesms.util.SqlUtil; @@ -827,44 +828,45 @@ public class RecipientDatabase extends Database { return out; } - private static @NonNull RecipientSettings getRecipientSettings(@NonNull Context context, @NonNull Cursor cursor) { - long id = cursor.getLong(cursor.getColumnIndexOrThrow(ID)); - UUID uuid = UuidUtil.parseOrNull(cursor.getString(cursor.getColumnIndexOrThrow(UUID))); - String username = cursor.getString(cursor.getColumnIndexOrThrow(USERNAME)); - String e164 = cursor.getString(cursor.getColumnIndexOrThrow(PHONE)); - String email = cursor.getString(cursor.getColumnIndexOrThrow(EMAIL)); - GroupId groupId = GroupId.parseNullableOrThrow(cursor.getString(cursor.getColumnIndexOrThrow(GROUP_ID))); - int groupType = cursor.getInt(cursor.getColumnIndexOrThrow(GROUP_TYPE)); - boolean blocked = cursor.getInt(cursor.getColumnIndexOrThrow(BLOCKED)) == 1; - String messageRingtone = cursor.getString(cursor.getColumnIndexOrThrow(MESSAGE_RINGTONE)); - String callRingtone = cursor.getString(cursor.getColumnIndexOrThrow(CALL_RINGTONE)); - int messageVibrateState = cursor.getInt(cursor.getColumnIndexOrThrow(MESSAGE_VIBRATE)); - int callVibrateState = cursor.getInt(cursor.getColumnIndexOrThrow(CALL_VIBRATE)); + static @NonNull RecipientSettings getRecipientSettings(@NonNull Context context, @NonNull Cursor cursor) { + long id = CursorUtil.requireLong(cursor, ID); + UUID uuid = UuidUtil.parseOrNull(CursorUtil.requireString(cursor, UUID)); + String username = CursorUtil.requireString(cursor, USERNAME); + String e164 = CursorUtil.requireString(cursor, PHONE); + String email = CursorUtil.requireString(cursor, EMAIL); + GroupId groupId = GroupId.parseNullableOrThrow(CursorUtil.requireString(cursor, GROUP_ID)); + int groupType = CursorUtil.requireInt(cursor, GROUP_TYPE); + boolean blocked = CursorUtil.requireBoolean(cursor, BLOCKED); + String messageRingtone = CursorUtil.requireString(cursor, MESSAGE_RINGTONE); + String callRingtone = CursorUtil.requireString(cursor, CALL_RINGTONE); + int messageVibrateState = CursorUtil.requireInt(cursor, MESSAGE_VIBRATE); + int callVibrateState = CursorUtil.requireInt(cursor, CALL_VIBRATE); long muteUntil = cursor.getLong(cursor.getColumnIndexOrThrow(MUTE_UNTIL)); - String serializedColor = cursor.getString(cursor.getColumnIndexOrThrow(COLOR)); - int insightsBannerTier = cursor.getInt(cursor.getColumnIndexOrThrow(SEEN_INVITE_REMINDER)); - int defaultSubscriptionId = cursor.getInt(cursor.getColumnIndexOrThrow(DEFAULT_SUBSCRIPTION_ID)); - int expireMessages = cursor.getInt(cursor.getColumnIndexOrThrow(MESSAGE_EXPIRATION_TIME)); - int registeredState = cursor.getInt(cursor.getColumnIndexOrThrow(REGISTERED)); - String profileKeyString = cursor.getString(cursor.getColumnIndexOrThrow(PROFILE_KEY)); - String profileKeyCredentialString = cursor.getString(cursor.getColumnIndexOrThrow(PROFILE_KEY_CREDENTIAL)); - String systemDisplayName = cursor.getString(cursor.getColumnIndexOrThrow(SYSTEM_DISPLAY_NAME)); - String systemContactPhoto = cursor.getString(cursor.getColumnIndexOrThrow(SYSTEM_PHOTO_URI)); - String systemPhoneLabel = cursor.getString(cursor.getColumnIndexOrThrow(SYSTEM_PHONE_LABEL)); - String systemContactUri = cursor.getString(cursor.getColumnIndexOrThrow(SYSTEM_CONTACT_URI)); - String profileGivenName = cursor.getString(cursor.getColumnIndexOrThrow(PROFILE_GIVEN_NAME)); - String profileFamilyName = cursor.getString(cursor.getColumnIndexOrThrow(PROFILE_FAMILY_NAME)); - String signalProfileAvatar = cursor.getString(cursor.getColumnIndexOrThrow(SIGNAL_PROFILE_AVATAR)); - boolean profileSharing = cursor.getInt(cursor.getColumnIndexOrThrow(PROFILE_SHARING)) == 1; + String serializedColor = CursorUtil.requireString(cursor, COLOR); + int insightsBannerTier = CursorUtil.requireInt(cursor, SEEN_INVITE_REMINDER); + int defaultSubscriptionId = CursorUtil.requireInt(cursor, DEFAULT_SUBSCRIPTION_ID); + int expireMessages = CursorUtil.requireInt(cursor, MESSAGE_EXPIRATION_TIME); + int registeredState = CursorUtil.requireInt(cursor, REGISTERED); + String profileKeyString = CursorUtil.requireString(cursor, PROFILE_KEY); + String profileKeyCredentialString = CursorUtil.requireString(cursor, PROFILE_KEY_CREDENTIAL); + String systemDisplayName = CursorUtil.requireString(cursor, SYSTEM_DISPLAY_NAME); + String systemContactPhoto = CursorUtil.requireString(cursor, SYSTEM_PHOTO_URI); + String systemPhoneLabel = CursorUtil.requireString(cursor, SYSTEM_PHONE_LABEL); + String systemContactUri = CursorUtil.requireString(cursor, SYSTEM_CONTACT_URI); + String profileGivenName = CursorUtil.requireString(cursor, PROFILE_GIVEN_NAME); + String profileFamilyName = CursorUtil.requireString(cursor, PROFILE_FAMILY_NAME); + String signalProfileAvatar = CursorUtil.requireString(cursor, SIGNAL_PROFILE_AVATAR); + boolean profileSharing = CursorUtil.requireBoolean(cursor, PROFILE_SHARING); long lastProfileFetch = cursor.getLong(cursor.getColumnIndexOrThrow(LAST_PROFILE_FETCH)); - String notificationChannel = cursor.getString(cursor.getColumnIndexOrThrow(NOTIFICATION_CHANNEL)); - int unidentifiedAccessMode = cursor.getInt(cursor.getColumnIndexOrThrow(UNIDENTIFIED_ACCESS_MODE)); - boolean forceSmsSelection = cursor.getInt(cursor.getColumnIndexOrThrow(FORCE_SMS_SELECTION)) == 1; - int uuidCapabilityValue = cursor.getInt(cursor.getColumnIndexOrThrow(UUID_CAPABILITY)); - int groupsV2CapabilityValue = cursor.getInt(cursor.getColumnIndexOrThrow(GROUPS_V2_CAPABILITY)); - String storageKeyRaw = cursor.getString(cursor.getColumnIndexOrThrow(STORAGE_SERVICE_ID)); - String identityKeyRaw = cursor.getString(cursor.getColumnIndexOrThrow(IDENTITY_KEY)); - int identityStatusRaw = cursor.getInt(cursor.getColumnIndexOrThrow(IDENTITY_STATUS)); + String notificationChannel = CursorUtil.requireString(cursor, NOTIFICATION_CHANNEL); + int unidentifiedAccessMode = CursorUtil.requireInt(cursor, UNIDENTIFIED_ACCESS_MODE); + boolean forceSmsSelection = CursorUtil.requireBoolean(cursor, FORCE_SMS_SELECTION); + int uuidCapabilityValue = CursorUtil.requireInt(cursor, UUID_CAPABILITY); + int groupsV2CapabilityValue = CursorUtil.requireInt(cursor, GROUPS_V2_CAPABILITY); + String storageKeyRaw = CursorUtil.requireString(cursor, STORAGE_SERVICE_ID); + + Optional identityKeyRaw = CursorUtil.getString(cursor, IDENTITY_KEY); + Optional identityStatusRaw = CursorUtil.getInt(cursor, IDENTITY_STATUS); int masterKeyIndex = cursor.getColumnIndex(GroupDatabase.V2_MASTER_KEY); GroupMasterKey groupMasterKey = null; @@ -909,9 +911,9 @@ public class RecipientDatabase extends Database { } 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, VibrateState.fromId(messageVibrateState), diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/ThreadDatabase.java b/app/src/main/java/org/thoughtcrime/securesms/database/ThreadDatabase.java index d4c303e305..313ee6f789 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/ThreadDatabase.java +++ b/app/src/main/java/org/thoughtcrime/securesms/database/ThreadDatabase.java @@ -33,6 +33,7 @@ import net.sqlcipher.database.SQLiteDatabase; import org.signal.storageservice.protos.groups.local.DecryptedGroup; 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.model.MediaMmsMessageRecord; 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.SlideDeck; import org.thoughtcrime.securesms.recipients.Recipient; +import org.thoughtcrime.securesms.recipients.RecipientDetails; import org.thoughtcrime.securesms.recipients.RecipientId; import org.thoughtcrime.securesms.recipients.RecipientUtil; import org.thoughtcrime.securesms.storage.StorageSyncHelper; +import org.thoughtcrime.securesms.util.CursorUtil; import org.thoughtcrime.securesms.util.JsonUtils; import org.thoughtcrime.securesms.util.TextSecurePreferences; import org.thoughtcrime.securesms.util.Util; @@ -928,9 +931,29 @@ public class ThreadDatabase extends Database { } public ThreadRecord getCurrent() { - RecipientId recipientId = RecipientId.from(cursor.getLong(cursor.getColumnIndexOrThrow(ThreadDatabase.RECIPIENT_ID))); - Recipient recipient = Recipient.live(recipientId).get(); + RecipientId recipientId = RecipientId.from(CursorUtil.requireLong(cursor, ThreadDatabase.RECIPIENT_ID)); + 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)) : 0; diff --git a/app/src/main/java/org/thoughtcrime/securesms/recipients/LiveRecipient.java b/app/src/main/java/org/thoughtcrime/securesms/recipients/LiveRecipient.java index a216167bfc..588e45d509 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/recipients/LiveRecipient.java +++ b/app/src/main/java/org/thoughtcrime/securesms/recipients/LiveRecipient.java @@ -40,7 +40,6 @@ public final class LiveRecipient { private final AtomicReference recipient; private final RecipientDatabase recipientDatabase; private final GroupDatabase groupDatabase; - private final String unnamedGroupName; LiveRecipient(@NonNull Context context, @NonNull MutableLiveData liveData, @NonNull Recipient defaultRecipient) { this.context = context.getApplicationContext(); @@ -48,7 +47,6 @@ public final class LiveRecipient { this.recipient = new AtomicReference<>(defaultRecipient); this.recipientDatabase = DatabaseFactory.getRecipientDatabase(context); this.groupDatabase = DatabaseFactory.getGroupDatabase(context); - this.unnamedGroupName = context.getString(R.string.RecipientProvider_unnamed_group); this.observers = new CopyOnWriteArraySet<>(); this.foreverObserver = recipient -> { for (RecipientForeverObserver o : observers) { @@ -175,21 +173,13 @@ public final class LiveRecipient { private @NonNull Recipient fetchAndCacheRecipientFromDisk(@NonNull RecipientId id) { RecipientSettings settings = recipientDatabase.getRecipientSettings(id); 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); 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 private @NonNull RecipientDetails getGroupRecipientDetails(@NonNull RecipientSettings settings) { Optional groupRecord = groupDatabase.getGroup(settings.getId()); @@ -199,21 +189,17 @@ public final class LiveRecipient { List members = Stream.of(groupRecord.get().getMembers()).filterNot(RecipientId::isUnknown).map(this::fetchAndCacheRecipientFromDisk).toList(); Optional avatarId = Optional.absent(); - if (settings.getGroupId() != null && settings.getGroupId().isPush() && title == null) { - title = unnamedGroupName; - } - if (groupRecord.get().hasAvatar()) { 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.liveData.postValue(recipient); } diff --git a/app/src/main/java/org/thoughtcrime/securesms/recipients/LiveRecipientCache.java b/app/src/main/java/org/thoughtcrime/securesms/recipients/LiveRecipientCache.java index 7040d39944..267198cb63 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/recipients/LiveRecipientCache.java +++ b/app/src/main/java/org/thoughtcrime/securesms/recipients/LiveRecipientCache.java @@ -20,6 +20,7 @@ import org.thoughtcrime.securesms.util.TextSecurePreferences; import org.thoughtcrime.securesms.util.concurrent.SignalExecutors; import java.util.ArrayList; +import java.util.Collection; import java.util.Collections; import java.util.List; import java.util.Map; @@ -75,6 +76,40 @@ public final class LiveRecipientCache { 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 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() { synchronized (this) { if (localRecipientId == null) { @@ -107,23 +142,22 @@ public final class LiveRecipientCache { } SignalExecutors.BOUNDED.execute(() -> { - ThreadDatabase threadDatabase = DatabaseFactory.getThreadDatabase(context); + ThreadDatabase threadDatabase = DatabaseFactory.getThreadDatabase(context); + List recipients = new ArrayList<>(); try (ThreadDatabase.Reader reader = threadDatabase.readerFor(threadDatabase.getConversationList())) { - int i = 0; - ThreadRecord record = null; - List recipients = new ArrayList<>(); + int i = 0; + ThreadRecord record = null; while ((record = reader.getNext()) != null && i < CACHE_WARM_MAX) { recipients.add(record.getRecipient()); 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); }); } diff --git a/app/src/main/java/org/thoughtcrime/securesms/recipients/Recipient.java b/app/src/main/java/org/thoughtcrime/securesms/recipients/Recipient.java index 431879ea46..a9446cf181 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/recipients/Recipient.java +++ b/app/src/main/java/org/thoughtcrime/securesms/recipients/Recipient.java @@ -54,7 +54,7 @@ import static org.thoughtcrime.securesms.database.RecipientDatabase.InsightsBann 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 String TAG = Log.tag(Recipient.class); @@ -344,9 +344,9 @@ public class Recipient { 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.resolving = false; + this.resolving = !resolved; this.uuid = details.uuid; this.username = details.username; this.e164 = details.e164; @@ -408,9 +408,11 @@ public class Recipient { } 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) { if (localNumber) return fallbackPhotoProvider.getPhotoForLocalNumber(); - if (isResolving()) return fallbackPhotoProvider.getPhotoForResolvingRecipient(); + else if (isResolving()) return fallbackPhotoProvider.getPhotoForResolvingRecipient(); else if (isGroupInternal()) return fallbackPhotoProvider.getPhotoForGroup(); else if (isGroup()) return fallbackPhotoProvider.getPhotoForGroup(); else if (!TextUtils.isEmpty(name)) return fallbackPhotoProvider.getPhotoForRecipientWithName(name); diff --git a/app/src/main/java/org/thoughtcrime/securesms/recipients/RecipientDetails.java b/app/src/main/java/org/thoughtcrime/securesms/recipients/RecipientDetails.java index d116c6d964..4c08f66228 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/recipients/RecipientDetails.java +++ b/app/src/main/java/org/thoughtcrime/securesms/recipients/RecipientDetails.java @@ -2,6 +2,7 @@ package org.thoughtcrime.securesms.recipients; import android.content.Context; import android.net.Uri; +import android.text.TextUtils; import androidx.annotation.NonNull; import androidx.annotation.Nullable; @@ -66,13 +67,12 @@ public class RecipientDetails { final byte[] identityKey; final VerifiedStatus identityStatus; - RecipientDetails(@NonNull Context context, - @Nullable String name, - @NonNull Optional groupAvatarId, - boolean systemContact, - boolean isLocalNumber, - @NonNull RecipientSettings settings, - @Nullable List participants) + public RecipientDetails(@Nullable String name, + @NonNull Optional groupAvatarId, + boolean systemContact, + boolean isLocalNumber, + @NonNull RecipientSettings settings, + @Nullable List participants) { this.groupAvatarId = groupAvatarId; this.systemContactPhoto = Util.uri(settings.getSystemContactPhotoUri()); @@ -161,4 +161,12 @@ public class RecipientDetails { this.identityKey = null; 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); + } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/util/CursorUtil.java b/app/src/main/java/org/thoughtcrime/securesms/util/CursorUtil.java new file mode 100644 index 0000000000..698ee19370 --- /dev/null +++ b/app/src/main/java/org/thoughtcrime/securesms/util/CursorUtil.java @@ -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 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 getInt(@NonNull Cursor cursor, @NonNull String column) { + if (cursor.getColumnIndex(column) < 0) { + return Optional.absent(); + } else { + return Optional.of(requireInt(cursor, column)); + } + } +}