From e00397620aa334a6ad1dc11f9249e5e6a3d87be3 Mon Sep 17 00:00:00 2001 From: Greyson Parrelli Date: Tue, 6 Oct 2020 10:24:14 -0400 Subject: [PATCH] Simplify storing storage-service-specific recipient values. This gives us the ability to separate things we need for the Recipient class from things we only need for storage syncing. Not only does this simplify the storage service model building code (i.e. we no longer need to pass around a set of archived recipients), but it also eliminates a join on the Identity table for building regular recipients, which should help perf. --- .../securesms/database/RecipientDatabase.java | 203 ++++++++++-------- .../securesms/database/ThreadDatabase.java | 2 +- .../securesms/jobs/PushProcessMessageJob.java | 4 +- .../securesms/jobs/StorageForcePushJob.java | 3 +- .../securesms/jobs/StorageSyncJob.java | 13 +- .../securesms/recipients/Recipient.java | 14 -- .../recipients/RecipientDetails.java | 8 +- .../securesms/storage/StorageSyncHelper.java | 11 +- .../securesms/storage/StorageSyncModels.java | 36 ++-- .../securesms/util/CursorUtil.java | 20 ++ .../securesms/util/GroupUtil.java | 10 + 11 files changed, 181 insertions(+), 143 deletions(-) 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 550955be5c..f9a8d3e38b 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/RecipientDatabase.java +++ b/app/src/main/java/org/thoughtcrime/securesms/database/RecipientDatabase.java @@ -15,7 +15,6 @@ import net.sqlcipher.database.SQLiteConstraintException; import net.sqlcipher.database.SQLiteDatabase; import org.signal.storageservice.protos.groups.local.DecryptedGroup; -import org.signal.zkgroup.InvalidInputException; import org.signal.zkgroup.groups.GroupMasterKey; import org.signal.zkgroup.profiles.ProfileKey; import org.signal.zkgroup.profiles.ProfileKeyCredential; @@ -23,6 +22,7 @@ import org.thoughtcrime.securesms.color.MaterialColor; import org.thoughtcrime.securesms.contacts.avatars.ContactColors; import org.thoughtcrime.securesms.crypto.ProfileKeyUtil; import org.thoughtcrime.securesms.database.IdentityDatabase.IdentityRecord; +import org.thoughtcrime.securesms.database.IdentityDatabase.VerifiedStatus; import org.thoughtcrime.securesms.database.helpers.SQLCipherOpenHelper; import org.thoughtcrime.securesms.database.model.ThreadRecord; import org.thoughtcrime.securesms.dependencies.ApplicationDependencies; @@ -41,6 +41,7 @@ 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.GroupUtil; import org.thoughtcrime.securesms.util.IdentityUtil; import org.thoughtcrime.securesms.util.SqlUtil; import org.thoughtcrime.securesms.util.StringUtil; @@ -61,6 +62,7 @@ import org.whispersystems.signalservice.api.util.UuidUtil; import java.io.Closeable; import java.io.IOException; import java.util.ArrayList; +import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.HashMap; @@ -128,7 +130,7 @@ public class RecipientDatabase extends Database { private static final String IDENTITY_KEY = "identity_key"; private static final String[] RECIPIENT_PROJECTION = new String[] { - UUID, USERNAME, PHONE, EMAIL, GROUP_ID, GROUP_TYPE, + ID, UUID, USERNAME, PHONE, EMAIL, GROUP_ID, GROUP_TYPE, BLOCKED, MESSAGE_RINGTONE, CALL_RINGTONE, MESSAGE_VIBRATE, CALL_VIBRATE, MUTE_UNTIL, COLOR, SEEN_INVITE_REMINDER, DEFAULT_SUBSCRIPTION_ID, MESSAGE_EXPIRATION_TIME, REGISTERED, PROFILE_KEY, PROFILE_KEY_CREDENTIAL, SYSTEM_DISPLAY_NAME, SYSTEM_PHOTO_URI, SYSTEM_PHONE_LABEL, SYSTEM_PHONE_TYPE, SYSTEM_CONTACT_URI, @@ -144,20 +146,13 @@ public class RecipientDatabase extends Database { private static final String[] ID_PROJECTION = new String[]{ID}; private static final String[] SEARCH_PROJECTION = new String[]{ID, SYSTEM_DISPLAY_NAME, PHONE, EMAIL, SYSTEM_PHONE_LABEL, SYSTEM_PHONE_TYPE, REGISTERED, "COALESCE(" + nullIfEmpty(PROFILE_JOINED_NAME) + ", " + nullIfEmpty(PROFILE_GIVEN_NAME) + ") AS " + SEARCH_PROFILE_NAME, "COALESCE(" + nullIfEmpty(SYSTEM_DISPLAY_NAME) + ", " + nullIfEmpty(PROFILE_JOINED_NAME) + ", " + nullIfEmpty(PROFILE_GIVEN_NAME) + ", " + nullIfEmpty(USERNAME) + ") AS " + SORT_NAME}; public static final String[] SEARCH_PROJECTION_NAMES = new String[]{ID, SYSTEM_DISPLAY_NAME, PHONE, EMAIL, SYSTEM_PHONE_LABEL, SYSTEM_PHONE_TYPE, REGISTERED, SEARCH_PROFILE_NAME, SORT_NAME}; - static final String[] TYPED_RECIPIENT_PROJECTION = Stream.of(RECIPIENT_PROJECTION) + private static final String[] TYPED_RECIPIENT_PROJECTION = Stream.of(RECIPIENT_PROJECTION) .map(columnName -> TABLE_NAME + "." + columnName) .toList().toArray(new String[0]); - private static final String[] MENTION_SEARCH_PROJECTION = new String[]{ID, removeWhitespace("COALESCE(" + nullIfEmpty(SYSTEM_DISPLAY_NAME) + ", " + nullIfEmpty(PROFILE_JOINED_NAME) + ", " + nullIfEmpty(PROFILE_GIVEN_NAME) + ", " + nullIfEmpty(USERNAME) + ", " + nullIfEmpty(PHONE) + ")") + " AS " + SORT_NAME}; + static final String[] TYPED_RECIPIENT_PROJECTION_NO_ID = Arrays.copyOfRange(TYPED_RECIPIENT_PROJECTION, 1, TYPED_RECIPIENT_PROJECTION.length); - private static final String[] RECIPIENT_FULL_PROJECTION = Stream.of( - new String[] { TABLE_NAME + "." + ID, - TABLE_NAME + "." + STORAGE_PROTO }, - TYPED_RECIPIENT_PROJECTION, - new String[] { - IdentityDatabase.TABLE_NAME + "." + IdentityDatabase.VERIFIED + " AS " + IDENTITY_STATUS, - IdentityDatabase.TABLE_NAME + "." + IdentityDatabase.IDENTITY_KEY + " AS " + IDENTITY_KEY - }).flatMap(Stream::of).toArray(String[]::new); + private static final String[] MENTION_SEARCH_PROJECTION = new String[]{ID, removeWhitespace("COALESCE(" + nullIfEmpty(SYSTEM_DISPLAY_NAME) + ", " + nullIfEmpty(PROFILE_JOINED_NAME) + ", " + nullIfEmpty(PROFILE_GIVEN_NAME) + ", " + nullIfEmpty(USERNAME) + ", " + nullIfEmpty(PHONE) + ")") + " AS " + SORT_NAME}; public static final String[] CREATE_INDEXS = new String[] { "CREATE INDEX IF NOT EXISTS recipient_dirty_index ON " + TABLE_NAME + " (" + DIRTY + ");", @@ -595,11 +590,10 @@ public class RecipientDatabase extends Database { public @NonNull RecipientSettings getRecipientSettings(@NonNull RecipientId id) { SQLiteDatabase database = databaseHelper.getReadableDatabase(); - String table = TABLE_NAME + " LEFT OUTER JOIN " + IdentityDatabase.TABLE_NAME + " ON " + TABLE_NAME + "." + ID + " = " + IdentityDatabase.TABLE_NAME + "." + IdentityDatabase.RECIPIENT_ID; - String query = TABLE_NAME + "." + ID + " = ?"; + String query = ID + " = ?"; String[] args = new String[] { id.serialize() }; - try (Cursor cursor = database.query(table, RECIPIENT_FULL_PROJECTION, query, args, null, null, null)) { + try (Cursor cursor = database.query(TABLE_NAME, RECIPIENT_PROJECTION, query, args, null, null, null)) { if (cursor != null && cursor.moveToNext()) { return getRecipientSettings(context, cursor); } else { @@ -814,12 +808,12 @@ public class RecipientDatabase extends Database { Optional newIdentityRecord = identityDatabase.getIdentity(recipientId); - if ((newIdentityRecord.isPresent() && newIdentityRecord.get().getVerifiedStatus() == IdentityDatabase.VerifiedStatus.VERIFIED) && - (!oldIdentityRecord.isPresent() || oldIdentityRecord.get().getVerifiedStatus() != IdentityDatabase.VerifiedStatus.VERIFIED)) + if ((newIdentityRecord.isPresent() && newIdentityRecord.get().getVerifiedStatus() == VerifiedStatus.VERIFIED) && + (!oldIdentityRecord.isPresent() || oldIdentityRecord.get().getVerifiedStatus() != VerifiedStatus.VERIFIED)) { IdentityUtil.markIdentityVerified(context, Recipient.resolved(recipientId), true, true); - } else if ((newIdentityRecord.isPresent() && newIdentityRecord.get().getVerifiedStatus() != IdentityDatabase.VerifiedStatus.VERIFIED) && - (oldIdentityRecord.isPresent() && oldIdentityRecord.get().getVerifiedStatus() == IdentityDatabase.VerifiedStatus.VERIFIED)) + } else if ((newIdentityRecord.isPresent() && newIdentityRecord.get().getVerifiedStatus() != VerifiedStatus.VERIFIED) && + (oldIdentityRecord.isPresent() && oldIdentityRecord.get().getVerifiedStatus() == VerifiedStatus.VERIFIED)) { IdentityUtil.markIdentityVerified(context, Recipient.resolved(recipientId), false, true); } @@ -1054,12 +1048,20 @@ public class RecipientDatabase extends Database { private List getRecipientSettingsForSync(@Nullable String query, @Nullable String[] args) { SQLiteDatabase db = databaseHelper.getReadableDatabase(); - String table = TABLE_NAME + " LEFT OUTER JOIN " + IdentityDatabase.TABLE_NAME + " ON " + TABLE_NAME + "." + ID + " = " + IdentityDatabase.TABLE_NAME + "." + IdentityDatabase.RECIPIENT_ID - + " LEFT OUTER JOIN " + GroupDatabase.TABLE_NAME + " ON " + TABLE_NAME + "." + GROUP_ID + " = " + GroupDatabase.TABLE_NAME + "." + GroupDatabase.GROUP_ID; + String table = TABLE_NAME + " LEFT OUTER JOIN " + IdentityDatabase.TABLE_NAME + " ON " + TABLE_NAME + "." + ID + " = " + IdentityDatabase.TABLE_NAME + "." + IdentityDatabase.RECIPIENT_ID + + " LEFT OUTER JOIN " + GroupDatabase.TABLE_NAME + " ON " + TABLE_NAME + "." + GROUP_ID + " = " + GroupDatabase.TABLE_NAME + "." + GroupDatabase.GROUP_ID + + " LEFT OUTER JOIN " + ThreadDatabase.TABLE_NAME + " ON " + TABLE_NAME + "." + ID + " = " + ThreadDatabase.TABLE_NAME + "." + ThreadDatabase.RECIPIENT_ID; List out = new ArrayList<>(); - String[] columns = Stream.of(RECIPIENT_FULL_PROJECTION, - new String[]{GroupDatabase.TABLE_NAME + "." + GroupDatabase.V2_MASTER_KEY }).flatMap(Stream::of).toArray(String[]::new); + String[] columns = Stream.of(TYPED_RECIPIENT_PROJECTION, + new String[]{ RecipientDatabase.TABLE_NAME + "." + STORAGE_PROTO, + GroupDatabase.TABLE_NAME + "." + GroupDatabase.V2_MASTER_KEY, + ThreadDatabase.TABLE_NAME + "." + ThreadDatabase.ARCHIVED, + ThreadDatabase.TABLE_NAME + "." + ThreadDatabase.READ, + IdentityDatabase.TABLE_NAME + "." + IdentityDatabase.VERIFIED + " AS " + IDENTITY_STATUS, + IdentityDatabase.TABLE_NAME + "." + IdentityDatabase.IDENTITY_KEY + " AS " + IDENTITY_KEY }) + .flatMap(Stream::of) + .toArray(String[]::new); try (Cursor cursor = db.query(table, columns, query, args, null, null, null)) { while (cursor != null && cursor.moveToNext()) { @@ -1159,23 +1161,6 @@ public class RecipientDatabase extends Database { int groupsV2CapabilityValue = CursorUtil.requireInt(cursor, GROUPS_V2_CAPABILITY); String storageKeyRaw = CursorUtil.requireString(cursor, STORAGE_SERVICE_ID); int mentionSettingId = CursorUtil.requireInt(cursor, MENTION_SETTING); - String storageProtoRaw = CursorUtil.getString(cursor, STORAGE_PROTO).orNull(); - - 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; - try { - if (masterKeyIndex != -1) { - byte[] blob = cursor.getBlob(masterKeyIndex); - if (blob != null) { - groupMasterKey = new GroupMasterKey(blob); - } - } - } catch (InvalidInputException e) { - throw new AssertionError(e); - } MaterialColor color; byte[] profileKey = null; @@ -1206,30 +1191,57 @@ public class RecipientDatabase extends Database { } } - byte[] storageKey = storageKeyRaw != null ? Base64.decodeOrThrow(storageKeyRaw) : null; - byte[] identityKey = identityKeyRaw.transform(Base64::decodeOrThrow).orNull(); - byte[] storageProto = storageProtoRaw != null ? Base64.decodeOrThrow(storageProtoRaw) : null; + byte[] storageKey = storageKeyRaw != null ? Base64.decodeOrThrow(storageKeyRaw) : null; - 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, + GroupType.fromId(groupType), + blocked, + muteUntil, VibrateState.fromId(messageVibrateState), VibrateState.fromId(callVibrateState), - Util.uri(messageRingtone), Util.uri(callRingtone), - color, defaultSubscriptionId, expireMessages, + Util.uri(messageRingtone), + Util.uri(callRingtone), + color, + defaultSubscriptionId, + expireMessages, RegisteredState.fromId(registeredState), - profileKey, profileKeyCredential, - systemDisplayName, systemContactPhoto, - systemPhoneLabel, systemContactUri, - ProfileName.fromParts(profileGivenName, profileFamilyName), signalProfileAvatar, - AvatarHelper.hasAvatar(context, RecipientId.from(id)), profileSharing, lastProfileFetch, - notificationChannel, UnidentifiedAccessMode.fromMode(unidentifiedAccessMode), + profileKey, + profileKeyCredential, + systemDisplayName, + systemContactPhoto, + systemPhoneLabel, + systemContactUri, + ProfileName.fromParts(profileGivenName, profileFamilyName), + signalProfileAvatar, + AvatarHelper.hasAvatar(context, RecipientId.from(id)), + profileSharing, + lastProfileFetch, + notificationChannel, + UnidentifiedAccessMode.fromMode(unidentifiedAccessMode), forceSmsSelection, Recipient.Capability.deserialize(uuidCapabilityValue), Recipient.Capability.deserialize(groupsV2CapabilityValue), InsightsBannerTier.fromId(insightsBannerTier), - storageKey, identityKey, identityStatus, MentionSetting.fromId(mentionSettingId), - storageProto); + storageKey, + MentionSetting.fromId(mentionSettingId), + getSyncExtras(cursor)); + } + + private static @NonNull RecipientSettings.SyncExtras getSyncExtras(@NonNull Cursor cursor) { + String storageProtoRaw = CursorUtil.getString(cursor, STORAGE_PROTO).orNull(); + byte[] storageProto = storageProtoRaw != null ? Base64.decodeOrThrow(storageProtoRaw) : null; + boolean archived = CursorUtil.getBoolean(cursor, ThreadDatabase.ARCHIVED).or(false); + GroupMasterKey groupMasterKey = CursorUtil.getBlob(cursor, GroupDatabase.V2_MASTER_KEY).transform(GroupUtil::requireMasterKey).orNull(); + byte[] identityKey = CursorUtil.getString(cursor, IDENTITY_KEY).transform(Base64::decodeOrThrow).orNull(); + VerifiedStatus identityStatus = CursorUtil.getInt(cursor, IDENTITY_STATUS).transform(VerifiedStatus::forState).or(VerifiedStatus.DEFAULT); + + + return new RecipientSettings.SyncExtras(storageProto, groupMasterKey, identityKey, identityStatus, archived); } public BulkOperationsHandle beginBulkSystemContactUpdate() { @@ -2537,7 +2549,6 @@ public class RecipientDatabase extends Database { private final String e164; private final String email; private final GroupId groupId; - private final GroupMasterKey groupMasterKey; private final GroupType groupType; private final boolean blocked; private final long muteUntil; @@ -2567,10 +2578,8 @@ public class RecipientDatabase extends Database { private final Recipient.Capability groupsV2Capability; private final InsightsBannerTier insightsBannerTier; private final byte[] storageId; - private final byte[] identityKey; - private final IdentityDatabase.VerifiedStatus identityStatus; private final MentionSetting mentionSetting; - private final byte[] storageProto; + private final SyncExtras syncExtras; RecipientSettings(@NonNull RecipientId id, @Nullable UUID uuid, @@ -2578,7 +2587,6 @@ public class RecipientDatabase extends Database { @Nullable String e164, @Nullable String email, @Nullable GroupId groupId, - @Nullable GroupMasterKey groupMasterKey, @NonNull GroupType groupType, boolean blocked, long muteUntil, @@ -2608,10 +2616,8 @@ public class RecipientDatabase extends Database { Recipient.Capability groupsV2Capability, @NonNull InsightsBannerTier insightsBannerTier, @Nullable byte[] storageId, - @Nullable byte[] identityKey, - @NonNull IdentityDatabase.VerifiedStatus identityStatus, @NonNull MentionSetting mentionSetting, - @Nullable byte[] storageProto) + @NonNull SyncExtras syncExtras) { this.id = id; this.uuid = uuid; @@ -2619,7 +2625,6 @@ public class RecipientDatabase extends Database { this.e164 = e164; this.email = email; this.groupId = groupId; - this.groupMasterKey = groupMasterKey; this.groupType = groupType; this.blocked = blocked; this.muteUntil = muteUntil; @@ -2649,10 +2654,8 @@ public class RecipientDatabase extends Database { this.groupsV2Capability = groupsV2Capability; this.insightsBannerTier = insightsBannerTier; this.storageId = storageId; - this.identityKey = identityKey; - this.identityStatus = identityStatus; this.mentionSetting = mentionSetting; - this.storageProto = storageProto; + this.syncExtras = syncExtras; } public RecipientId getId() { @@ -2679,13 +2682,6 @@ public class RecipientDatabase extends Database { return groupId; } - /** - * Only read populated for sync. - */ - public @Nullable GroupMasterKey getGroupMasterKey() { - return groupMasterKey; - } - public @NonNull GroupType getGroupType() { return groupType; } @@ -2802,20 +2798,57 @@ public class RecipientDatabase extends Database { return storageId; } - public @Nullable byte[] getIdentityKey() { - return identityKey; - } - - public @NonNull IdentityDatabase.VerifiedStatus getIdentityStatus() { - return identityStatus; - } - public @NonNull MentionSetting getMentionSetting() { return mentionSetting; } - public @Nullable byte[] getStorageProto() { - return storageProto; + public @NonNull SyncExtras getSyncExtras() { + return syncExtras; + } + + /** + * A bundle of data that's only necessary when syncing to storage service, not for a + * {@link Recipient}. + */ + public static class SyncExtras { + private final byte[] storageProto; + private final GroupMasterKey groupMasterKey; + private final byte[] identityKey; + private final VerifiedStatus identityStatus; + private final boolean archived; + + public SyncExtras(@Nullable byte[] storageProto, + @Nullable GroupMasterKey groupMasterKey, + @Nullable byte[] identityKey, + @NonNull VerifiedStatus identityStatus, + boolean archived) + { + this.storageProto = storageProto; + this.groupMasterKey = groupMasterKey; + this.identityKey = identityKey; + this.identityStatus = identityStatus; + this.archived = archived; + } + + public @Nullable byte[] getStorageProto() { + return storageProto; + } + + public @Nullable GroupMasterKey getGroupMasterKey() { + return groupMasterKey; + } + + public boolean isArchived() { + return archived; + } + + public @Nullable byte[] getIdentityKey() { + return identityKey; + } + + public @NonNull VerifiedStatus getIdentityStatus() { + return identityStatus; + } } } 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 d19c3f8108..8bbe567f86 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/ThreadDatabase.java +++ b/app/src/main/java/org/thoughtcrime/securesms/database/ThreadDatabase.java @@ -144,7 +144,7 @@ public class ThreadDatabase extends Database { .toList(); private static final List COMBINED_THREAD_RECIPIENT_GROUP_PROJECTION = Stream.concat(Stream.concat(Stream.of(TYPED_THREAD_PROJECTION), - Stream.of(RecipientDatabase.TYPED_RECIPIENT_PROJECTION)), + Stream.of(RecipientDatabase.TYPED_RECIPIENT_PROJECTION_NO_ID)), Stream.of(GroupDatabase.TYPED_GROUP_PROJECTION)) .toList(); diff --git a/app/src/main/java/org/thoughtcrime/securesms/jobs/PushProcessMessageJob.java b/app/src/main/java/org/thoughtcrime/securesms/jobs/PushProcessMessageJob.java index 9379a97d14..a47a0728ad 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/jobs/PushProcessMessageJob.java +++ b/app/src/main/java/org/thoughtcrime/securesms/jobs/PushProcessMessageJob.java @@ -512,7 +512,7 @@ public final class PushProcessMessageJob extends BaseJob { Intent intent = new Intent(context, WebRtcCallService.class); Recipient recipient = Recipient.externalHighTrustPush(context, content.getSender()); RemotePeer remotePeer = new RemotePeer(recipient.getId()); - byte[] remoteIdentityKey = recipient.getIdentityKey(); + byte[] remoteIdentityKey = DatabaseFactory.getIdentityDatabase(context).getIdentity(recipient.getId()).transform(record -> record.getIdentityKey().serialize()).orNull(); intent.setAction(WebRtcCallService.ACTION_RECEIVE_OFFER) .putExtra(WebRtcCallService.EXTRA_CALL_ID, message.getId()) @@ -538,7 +538,7 @@ public final class PushProcessMessageJob extends BaseJob { Intent intent = new Intent(context, WebRtcCallService.class); Recipient recipient = Recipient.externalHighTrustPush(context, content.getSender()); RemotePeer remotePeer = new RemotePeer(recipient.getId()); - byte[] remoteIdentityKey = recipient.getIdentityKey(); + byte[] remoteIdentityKey = DatabaseFactory.getIdentityDatabase(context).getIdentity(recipient.getId()).transform(record -> record.getIdentityKey().serialize()).orNull(); intent.setAction(WebRtcCallService.ACTION_RECEIVE_ANSWER) .putExtra(WebRtcCallService.EXTRA_CALL_ID, message.getId()) diff --git a/app/src/main/java/org/thoughtcrime/securesms/jobs/StorageForcePushJob.java b/app/src/main/java/org/thoughtcrime/securesms/jobs/StorageForcePushJob.java index 362ffa903c..32d155fa08 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/jobs/StorageForcePushJob.java +++ b/app/src/main/java/org/thoughtcrime/securesms/jobs/StorageForcePushJob.java @@ -83,11 +83,10 @@ public class StorageForcePushJob extends BaseJob { long newVersion = currentVersion + 1; Map newContactStorageIds = generateContactStorageIds(oldContactStorageIds); - Set archivedRecipients = DatabaseFactory.getThreadDatabase(context).getArchivedRecipients(); List inserts = Stream.of(oldContactStorageIds.keySet()) .map(recipientDatabase::getRecipientSettingsForSync) .withoutNulls() - .map(s -> StorageSyncModels.localToRemoteRecord(s, Objects.requireNonNull(newContactStorageIds.get(s.getId())).getRaw(), archivedRecipients)) + .map(s -> StorageSyncModels.localToRemoteRecord(s, Objects.requireNonNull(newContactStorageIds.get(s.getId())).getRaw())) .toList(); SignalStorageRecord accountRecord = StorageSyncHelper.buildAccountRecord(context, Recipient.self().fresh()); diff --git a/app/src/main/java/org/thoughtcrime/securesms/jobs/StorageSyncJob.java b/app/src/main/java/org/thoughtcrime/securesms/jobs/StorageSyncJob.java index 969aa536e0..0829ed13b8 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/jobs/StorageSyncJob.java +++ b/app/src/main/java/org/thoughtcrime/securesms/jobs/StorageSyncJob.java @@ -152,8 +152,7 @@ public class StorageSyncJob extends BaseJob { if (!keyDifference.isEmpty()) { Log.i(TAG, "[Remote Newer] There's a difference in keys. Local-only: " + keyDifference.getLocalOnlyKeys().size() + ", Remote-only: " + keyDifference.getRemoteOnlyKeys().size()); - Set archivedRecipients = DatabaseFactory.getThreadDatabase(context).getArchivedRecipients(); - List localOnly = buildLocalStorageRecords(context, keyDifference.getLocalOnlyKeys(), archivedRecipients); + List localOnly = buildLocalStorageRecords(context, keyDifference.getLocalOnlyKeys()); List remoteOnly = accountManager.readStorageRecords(storageServiceKey, keyDifference.getRemoteOnlyKeys()); MergeResult mergeResult = StorageSyncHelper.resolveConflict(remoteOnly, localOnly); WriteOperationResult writeOperationResult = StorageSyncHelper.createWriteOperation(remoteManifest.get().getVersion(), allLocalStorageKeys, mergeResult); @@ -212,15 +211,13 @@ public class StorageSyncJob extends BaseJob { List pendingDeletions = recipientDatabase.getPendingRecipientSyncDeletions(); Optional pendingAccountInsert = StorageSyncHelper.getPendingAccountSyncInsert(context, self); Optional pendingAccountUpdate = StorageSyncHelper.getPendingAccountSyncUpdate(context, self); - Set archivedRecipients = DatabaseFactory.getThreadDatabase(context).getArchivedRecipients(); Optional localWriteResult = StorageSyncHelper.buildStorageUpdatesForLocal(localManifestVersion, allLocalStorageKeys, pendingUpdates, pendingInsertions, pendingDeletions, pendingAccountUpdate, - pendingAccountInsert, - archivedRecipients); + pendingAccountInsert); if (localWriteResult.isPresent()) { Log.i(TAG, String.format(Locale.ENGLISH, "[Local Changes] Local changes present. %d updates, %d inserts, %d deletes, account update: %b, account insert: %b.", pendingUpdates.size(), pendingInsertions.size(), pendingDeletions.size(), pendingAccountUpdate.isPresent(), pendingAccountInsert.isPresent())); @@ -273,7 +270,7 @@ public class StorageSyncJob extends BaseJob { DatabaseFactory.getStorageKeyDatabase(context).getAllKeys()); } - private static @NonNull List buildLocalStorageRecords(@NonNull Context context, @NonNull List ids, @NonNull Set archivedRecipients) { + private static @NonNull List buildLocalStorageRecords(@NonNull Context context, @NonNull List ids) { Recipient self = Recipient.self().fresh(); RecipientDatabase recipientDatabase = DatabaseFactory.getRecipientDatabase(context); StorageKeyDatabase storageKeyDatabase = DatabaseFactory.getStorageKeyDatabase(context); @@ -287,10 +284,10 @@ public class StorageSyncJob extends BaseJob { case ManifestRecord.Identifier.Type.GROUPV2_VALUE: RecipientSettings settings = recipientDatabase.getByStorageId(id.getRaw()); if (settings != null) { - if (settings.getGroupType() == RecipientDatabase.GroupType.SIGNAL_V2 && settings.getGroupMasterKey() == null) { + if (settings.getGroupType() == RecipientDatabase.GroupType.SIGNAL_V2 && settings.getSyncExtras().getGroupMasterKey() == null) { Log.w(TAG, "Missing master key on gv2 recipient"); } else { - records.add(StorageSyncModels.localToRemoteRecord(settings, archivedRecipients)); + records.add(StorageSyncModels.localToRemoteRecord(settings)); } } else { Log.w(TAG, "Missing local recipient model! Type: " + id.getType()); 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 9ee867d496..d97ad07ddf 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/recipients/Recipient.java +++ b/app/src/main/java/org/thoughtcrime/securesms/recipients/Recipient.java @@ -102,8 +102,6 @@ public class Recipient { private final Capability groupsV2Capability; private final InsightsBannerTier insightsBannerTier; private final byte[] storageId; - private final byte[] identityKey; - private final VerifiedStatus identityStatus; private final MentionSetting mentionSetting; @@ -317,8 +315,6 @@ public class Recipient { this.uuidCapability = Capability.UNKNOWN; this.groupsV2Capability = Capability.UNKNOWN; this.storageId = null; - this.identityKey = null; - this.identityStatus = VerifiedStatus.DEFAULT; this.mentionSetting = MentionSetting.ALWAYS_NOTIFY; } @@ -361,8 +357,6 @@ public class Recipient { this.uuidCapability = details.uuidCapability; this.groupsV2Capability = details.groupsV2Capability; this.storageId = details.storageId; - this.identityKey = details.identityKey; - this.identityStatus = details.identityStatus; this.mentionSetting = details.mentionSetting; } @@ -782,14 +776,6 @@ public class Recipient { return storageId; } - public @NonNull VerifiedStatus getIdentityVerifiedStatus() { - return identityStatus; - } - - public @Nullable byte[] getIdentityKey() { - return identityKey; - } - public @NonNull UnidentifiedAccessMode getUnidentifiedAccessMode() { return unidentifiedAccessMode; } 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 010f12ce2b..f23bac94b8 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/recipients/RecipientDetails.java +++ b/app/src/main/java/org/thoughtcrime/securesms/recipients/RecipientDetails.java @@ -65,9 +65,7 @@ public class RecipientDetails { final Recipient.Capability uuidCapability; final Recipient.Capability groupsV2Capability; final InsightsBannerTier insightsBannerTier; - final byte[] storageId; - final byte[] identityKey; - final VerifiedStatus identityStatus; + final byte[] storageId; final MentionSetting mentionSetting; public RecipientDetails(@Nullable String name, @@ -113,8 +111,6 @@ public class RecipientDetails { this.groupsV2Capability = settings.getGroupsV2Capability(); this.insightsBannerTier = settings.getInsightsBannerTier(); this.storageId = settings.getStorageId(); - this.identityKey = settings.getIdentityKey(); - this.identityStatus = settings.getIdentityStatus(); this.mentionSetting = settings.getMentionSetting(); if (name == null) this.name = settings.getSystemDisplayName(); @@ -162,8 +158,6 @@ public class RecipientDetails { this.uuidCapability = Recipient.Capability.UNKNOWN; this.groupsV2Capability = Recipient.Capability.UNKNOWN; this.storageId = null; - this.identityKey = null; - this.identityStatus = VerifiedStatus.DEFAULT; this.mentionSetting = MentionSetting.ALWAYS_NOTIFY; } diff --git a/app/src/main/java/org/thoughtcrime/securesms/storage/StorageSyncHelper.java b/app/src/main/java/org/thoughtcrime/securesms/storage/StorageSyncHelper.java index 134fcd8a72..29c72ee99c 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/storage/StorageSyncHelper.java +++ b/app/src/main/java/org/thoughtcrime/securesms/storage/StorageSyncHelper.java @@ -83,8 +83,7 @@ public final class StorageSyncHelper { @NonNull List inserts, @NonNull List deletes, @NonNull Optional accountUpdate, - @NonNull Optional accountInsert, - @NonNull Set archivedRecipients) + @NonNull Optional accountInsert) { int accountCount = Stream.of(currentLocalKeys) .filter(id -> id.getType() == ManifestRecord.Identifier.Type.ACCOUNT_VALUE) @@ -119,12 +118,12 @@ public final class StorageSyncHelper { Map storageKeyUpdates = new HashMap<>(); for (RecipientSettings insert : inserts) { - if (insert.getGroupType() == RecipientDatabase.GroupType.SIGNAL_V2 && insert.getGroupMasterKey() == null) { + if (insert.getGroupType() == RecipientDatabase.GroupType.SIGNAL_V2 && insert.getSyncExtras().getGroupMasterKey() == null) { Log.w(TAG, "Missing master key on gv2 recipient"); continue; } - storageInserts.add(StorageSyncModels.localToRemoteRecord(insert, archivedRecipients)); + storageInserts.add(StorageSyncModels.localToRemoteRecord(insert)); switch (insert.getGroupType()) { case NONE: @@ -173,7 +172,7 @@ public final class StorageSyncHelper { throw new AssertionError("Unsupported type!"); } - storageInserts.add(StorageSyncModels.localToRemoteRecord(update, newId.getRaw(), archivedRecipients)); + storageInserts.add(StorageSyncModels.localToRemoteRecord(update, newId.getRaw())); storageDeletes.add(ByteBuffer.wrap(oldId.getRaw())); completeIds.remove(oldId); completeIds.add(newId); @@ -413,7 +412,7 @@ public final class StorageSyncHelper { RecipientSettings settings = DatabaseFactory.getRecipientDatabase(context).getRecipientSettingsForSync(self.getId()); SignalAccountRecord account = new SignalAccountRecord.Builder(self.getStorageServiceId()) - .setUnknownFields(settings != null ? settings.getStorageProto() : null) + .setUnknownFields(settings != null ? settings.getSyncExtras().getStorageProto() : null) .setProfileKey(self.getProfileKey()) .setGivenName(self.getProfileName().getGivenName()) .setFamilyName(self.getProfileName().getFamilyName()) diff --git a/app/src/main/java/org/thoughtcrime/securesms/storage/StorageSyncModels.java b/app/src/main/java/org/thoughtcrime/securesms/storage/StorageSyncModels.java index 6c43296b1f..6b8874f803 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/storage/StorageSyncModels.java +++ b/app/src/main/java/org/thoughtcrime/securesms/storage/StorageSyncModels.java @@ -20,42 +20,42 @@ public final class StorageSyncModels { private StorageSyncModels() {} - public static @NonNull SignalStorageRecord localToRemoteRecord(@NonNull RecipientSettings settings, @NonNull Set archived) { + public static @NonNull SignalStorageRecord localToRemoteRecord(@NonNull RecipientSettings settings) { if (settings.getStorageId() == null) { throw new AssertionError("Must have a storage key!"); } - return localToRemoteRecord(settings, settings.getStorageId(), archived); + return localToRemoteRecord(settings, settings.getStorageId()); } - public static @NonNull SignalStorageRecord localToRemoteRecord(@NonNull RecipientSettings settings, @NonNull byte[] rawStorageId, @NonNull Set archived) { + public static @NonNull SignalStorageRecord localToRemoteRecord(@NonNull RecipientSettings settings, @NonNull byte[] rawStorageId) { switch (settings.getGroupType()) { - case NONE: return SignalStorageRecord.forContact(localToRemoteContact(settings, rawStorageId, archived)); - case SIGNAL_V1: return SignalStorageRecord.forGroupV1(localToRemoteGroupV1(settings, rawStorageId, archived)); - case SIGNAL_V2: return SignalStorageRecord.forGroupV2(localToRemoteGroupV2(settings, rawStorageId, archived)); + case NONE: return SignalStorageRecord.forContact(localToRemoteContact(settings, rawStorageId)); + case SIGNAL_V1: return SignalStorageRecord.forGroupV1(localToRemoteGroupV1(settings, rawStorageId)); + case SIGNAL_V2: return SignalStorageRecord.forGroupV2(localToRemoteGroupV2(settings, rawStorageId)); default: throw new AssertionError("Unsupported type!"); } } - private static @NonNull SignalContactRecord localToRemoteContact(@NonNull RecipientSettings recipient, byte[] rawStorageId, @NonNull Set archived) { + private static @NonNull SignalContactRecord localToRemoteContact(@NonNull RecipientSettings recipient, byte[] rawStorageId) { if (recipient.getUuid() == null && recipient.getE164() == null) { throw new AssertionError("Must have either a UUID or a phone number!"); } return new SignalContactRecord.Builder(rawStorageId, new SignalServiceAddress(recipient.getUuid(), recipient.getE164())) - .setUnknownFields(recipient.getStorageProto()) + .setUnknownFields(recipient.getSyncExtras().getStorageProto()) .setProfileKey(recipient.getProfileKey()) .setGivenName(recipient.getProfileName().getGivenName()) .setFamilyName(recipient.getProfileName().getFamilyName()) .setBlocked(recipient.isBlocked()) .setProfileSharingEnabled(recipient.isProfileSharing() || recipient.getSystemContactUri() != null) - .setIdentityKey(recipient.getIdentityKey()) - .setIdentityState(localToRemoteIdentityState(recipient.getIdentityStatus())) - .setArchived(archived.contains(recipient.getId())) + .setIdentityKey(recipient.getSyncExtras().getIdentityKey()) + .setIdentityState(localToRemoteIdentityState(recipient.getSyncExtras().getIdentityStatus())) + .setArchived(recipient.getSyncExtras().isArchived()) .build(); } - private static @NonNull SignalGroupV1Record localToRemoteGroupV1(@NonNull RecipientSettings recipient, byte[] rawStorageId, @NonNull Set archived) { + private static @NonNull SignalGroupV1Record localToRemoteGroupV1(@NonNull RecipientSettings recipient, byte[] rawStorageId) { GroupId groupId = recipient.getGroupId(); if (groupId == null) { @@ -67,14 +67,14 @@ public final class StorageSyncModels { } return new SignalGroupV1Record.Builder(rawStorageId, groupId.getDecodedId()) - .setUnknownFields(recipient.getStorageProto()) + .setUnknownFields(recipient.getSyncExtras().getStorageProto()) .setBlocked(recipient.isBlocked()) .setProfileSharingEnabled(recipient.isProfileSharing()) - .setArchived(archived.contains(recipient.getId())) + .setArchived(recipient.getSyncExtras().isArchived()) .build(); } - private static @NonNull SignalGroupV2Record localToRemoteGroupV2(@NonNull RecipientSettings recipient, byte[] rawStorageId, @NonNull Set archived) { + private static @NonNull SignalGroupV2Record localToRemoteGroupV2(@NonNull RecipientSettings recipient, byte[] rawStorageId) { GroupId groupId = recipient.getGroupId(); if (groupId == null) { @@ -85,17 +85,17 @@ public final class StorageSyncModels { throw new AssertionError("Group is not V2"); } - GroupMasterKey groupMasterKey = recipient.getGroupMasterKey(); + GroupMasterKey groupMasterKey = recipient.getSyncExtras().getGroupMasterKey(); if (groupMasterKey == null) { throw new AssertionError("Group master key not on recipient record"); } return new SignalGroupV2Record.Builder(rawStorageId, groupMasterKey) - .setUnknownFields(recipient.getStorageProto()) + .setUnknownFields(recipient.getSyncExtras().getStorageProto()) .setBlocked(recipient.isBlocked()) .setProfileSharingEnabled(recipient.isProfileSharing()) - .setArchived(archived.contains(recipient.getId())) + .setArchived(recipient.getSyncExtras().isArchived()) .build(); } diff --git a/app/src/main/java/org/thoughtcrime/securesms/util/CursorUtil.java b/app/src/main/java/org/thoughtcrime/securesms/util/CursorUtil.java index 698ee19370..c5a87af8ae 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/util/CursorUtil.java +++ b/app/src/main/java/org/thoughtcrime/securesms/util/CursorUtil.java @@ -26,6 +26,10 @@ public final class CursorUtil { return requireInt(cursor, column) != 0; } + public static byte[] requireBlob(@NonNull Cursor cursor, @NonNull String column) { + return cursor.getBlob(cursor.getColumnIndexOrThrow(column)); + } + public static Optional getString(@NonNull Cursor cursor, @NonNull String column) { if (cursor.getColumnIndex(column) < 0) { return Optional.absent(); @@ -41,4 +45,20 @@ public final class CursorUtil { return Optional.of(requireInt(cursor, column)); } } + + public static Optional getBoolean(@NonNull Cursor cursor, @NonNull String column) { + if (cursor.getColumnIndex(column) < 0) { + return Optional.absent(); + } else { + return Optional.of(requireBoolean(cursor, column)); + } + } + + public static Optional getBlob(@NonNull Cursor cursor, @NonNull String column) { + if (cursor.getColumnIndex(column) < 0) { + return Optional.absent(); + } else { + return Optional.fromNullable(requireBlob(cursor, column)); + } + } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/util/GroupUtil.java b/app/src/main/java/org/thoughtcrime/securesms/util/GroupUtil.java index 93a7cc7ef0..7a48332f75 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/util/GroupUtil.java +++ b/app/src/main/java/org/thoughtcrime/securesms/util/GroupUtil.java @@ -6,6 +6,8 @@ import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.annotation.WorkerThread; +import org.signal.zkgroup.InvalidInputException; +import org.signal.zkgroup.groups.GroupMasterKey; import org.thoughtcrime.securesms.R; import org.thoughtcrime.securesms.database.DatabaseFactory; import org.thoughtcrime.securesms.database.GroupDatabase; @@ -66,6 +68,14 @@ public final class GroupUtil { return Optional.absent(); } + public static @NonNull GroupMasterKey requireMasterKey(@NonNull byte[] masterKey) { + try { + return new GroupMasterKey(masterKey); + } catch (InvalidInputException e) { + throw new AssertionError(e); + } + } + public static @NonNull GroupDescription getNonV2GroupDescription(@NonNull Context context, @Nullable String encodedGroup) { if (encodedGroup == null) { return new GroupDescription(context, null);