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 550955be5..f9a8d3e38 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 d19c3f810..8bbe567f8 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 9379a97d1..a47a0728a 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 362ffa903..32d155fa0 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 969aa536e..0829ed13b 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 9ee867d49..d97ad07dd 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 010f12ce2..f23bac94b 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 134fcd8a7..29c72ee99 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 6c43296b1..6b8874f80 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 698ee1937..c5a87af8a 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 93a7cc7ef..7a48332f7 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);