From a860315587d6c64465c3cfbbc751acb73861cfe0 Mon Sep 17 00:00:00 2001 From: Alan Evans Date: Thu, 26 Mar 2020 11:00:17 -0300 Subject: [PATCH] GroupId class. --- .../securesms/GroupCreateActivity.java | 19 +-- .../securesms/contacts/ContactAccessor.java | 2 +- .../contacts/ContactSelectionListItem.java | 10 +- .../contacts/ContactsCursorLoader.java | 9 +- .../avatars/GroupRecordContactPhoto.java | 14 +- .../conversation/ConversationActivity.java | 2 +- .../securesms/database/GroupDatabase.java | 81 +++++------ .../securesms/database/RecipientDatabase.java | 38 +++-- .../securesms/database/SmsMigrator.java | 4 +- .../database/helpers/ClassicOpenHelper.java | 9 +- .../helpers/RecipientIdMigrationHelper.java | 4 +- .../database/helpers/SQLCipherOpenHelper.java | 6 +- .../securesms/groups/GroupId.java | 93 ++++++++++++ .../securesms/groups/GroupManager.java | 4 +- .../groups/GroupMessageProcessor.java | 22 ++- .../securesms/groups/V1GroupManager.java | 70 +++++---- .../securesms/jobs/AvatarDownloadJob.java | 22 ++- .../securesms/jobs/LeaveGroupJob.java | 19 ++- .../securesms/jobs/MmsDownloadJob.java | 12 +- .../jobs/MultiDeviceBlockedUpdateJob.java | 3 +- .../jobs/MultiDeviceGroupUpdateJob.java | 5 +- .../MultiDeviceMessageRequestResponseJob.java | 7 +- .../securesms/jobs/PushDecryptMessageJob.java | 4 +- .../securesms/jobs/PushGroupSendJob.java | 12 +- .../securesms/jobs/PushGroupUpdateJob.java | 32 ++--- .../securesms/jobs/PushProcessMessageJob.java | 48 +++---- .../securesms/jobs/ReactionSendJob.java | 3 +- .../securesms/jobs/RequestGroupInfoJob.java | 28 ++-- .../securesms/jobs/TypingSendJob.java | 3 +- .../mediasend/CameraContactsRepository.java | 2 +- .../migrations/AvatarMigrationJob.java | 5 +- .../securesms/mms/IncomingMediaMessage.java | 10 +- .../phonenumbers/PhoneNumberFormatter.java | 4 +- .../securesms/recipients/LiveRecipient.java | 3 +- .../securesms/recipients/Recipient.java | 32 ++--- .../recipients/RecipientDetails.java | 3 +- .../securesms/recipients/RecipientUtil.java | 7 +- .../securesms/sms/IncomingTextMessage.java | 37 ++--- .../storage/GroupV1ConflictMerger.java | 8 +- .../securesms/storage/StorageSyncModels.java | 3 +- .../securesms/util/GroupUtil.java | 44 +----- .../securesms/util/IdentityUtil.java | 6 +- .../securesms/groups/GroupIdTest.java | 135 ++++++++++++++++++ 43 files changed, 519 insertions(+), 365 deletions(-) create mode 100644 app/src/main/java/org/thoughtcrime/securesms/groups/GroupId.java create mode 100644 app/src/test/java/org/thoughtcrime/securesms/groups/GroupIdTest.java diff --git a/app/src/main/java/org/thoughtcrime/securesms/GroupCreateActivity.java b/app/src/main/java/org/thoughtcrime/securesms/GroupCreateActivity.java index bd5f68dba4..f5a827f64e 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/GroupCreateActivity.java +++ b/app/src/main/java/org/thoughtcrime/securesms/GroupCreateActivity.java @@ -54,6 +54,7 @@ import org.thoughtcrime.securesms.database.GroupDatabase; import org.thoughtcrime.securesms.database.GroupDatabase.GroupRecord; import org.thoughtcrime.securesms.database.RecipientDatabase; import org.thoughtcrime.securesms.database.ThreadDatabase; +import org.thoughtcrime.securesms.groups.GroupId; import org.thoughtcrime.securesms.groups.GroupManager; import org.thoughtcrime.securesms.groups.GroupManager.GroupActionResult; import org.thoughtcrime.securesms.logging.Log; @@ -208,7 +209,7 @@ public class GroupCreateActivity extends PassphraseRequiredActionBarActivity } private void initializeExistingGroup() { - final String groupId = getIntent().getStringExtra(GROUP_ID_EXTRA); + final GroupId groupId = GroupId.parseNullable(getIntent().getStringExtra(GROUP_ID_EXTRA)); if (groupId != null) { new FillExistingGroupInfoAsyncTask(this).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, groupId); @@ -361,7 +362,7 @@ public class GroupCreateActivity extends PassphraseRequiredActionBarActivity } memberAddresses.add(Recipient.self().getId()); - String groupId = DatabaseFactory.getGroupDatabase(activity).getOrCreateGroupForMembers(memberAddresses, true); + GroupId groupId = DatabaseFactory.getGroupDatabase(activity).getOrCreateGroupForMembers(memberAddresses, true); RecipientId groupRecipientId = DatabaseFactory.getRecipientDatabase(activity).getOrInsertFromGroupId(groupId); Recipient groupRecipient = Recipient.resolved(groupRecipientId); long threadId = DatabaseFactory.getThreadDatabase(activity).getThreadIdFor(groupRecipient, ThreadDatabase.DistributionTypes.DEFAULT); @@ -443,9 +444,9 @@ public class GroupCreateActivity extends PassphraseRequiredActionBarActivity } private static class UpdateSignalGroupTask extends SignalGroupTask { - private String groupId; + private final GroupId groupId; - public UpdateSignalGroupTask(GroupCreateActivity activity, String groupId, + public UpdateSignalGroupTask(GroupCreateActivity activity, GroupId groupId, Bitmap avatar, String name, Set members) { super(activity, avatar, name, members); @@ -467,7 +468,7 @@ public class GroupCreateActivity extends PassphraseRequiredActionBarActivity if (!activity.isFinishing()) { Intent intent = activity.getIntent(); intent.putExtra(GROUP_THREAD_EXTRA, result.get().getThreadId()); - intent.putExtra(GROUP_ID_EXTRA, result.get().getGroupRecipient().requireGroupId()); + intent.putExtra(GROUP_ID_EXTRA, result.get().getGroupRecipient().requireGroupId().toString()); activity.setResult(RESULT_OK, intent); activity.finish(); } @@ -534,7 +535,7 @@ public class GroupCreateActivity extends PassphraseRequiredActionBarActivity } } - private static class FillExistingGroupInfoAsyncTask extends ProgressDialogAsyncTask> { + private static class FillExistingGroupInfoAsyncTask extends ProgressDialogAsyncTask> { private GroupCreateActivity activity; public FillExistingGroupInfoAsyncTask(GroupCreateActivity activity) { @@ -545,7 +546,7 @@ public class GroupCreateActivity extends PassphraseRequiredActionBarActivity } @Override - protected Optional doInBackground(String... groupIds) { + protected Optional doInBackground(GroupId... groupIds) { final GroupDatabase db = DatabaseFactory.getGroupDatabase(activity); final List recipients = db.getGroupMembers(groupIds[0], false); final Optional group = db.getGroup(groupIds[0]); @@ -593,13 +594,13 @@ public class GroupCreateActivity extends PassphraseRequiredActionBarActivity } private static class GroupData { - String id; + GroupId id; Set recipients; Bitmap avatarBmp; byte[] avatarBytes; String name; - public GroupData(String id, Set recipients, Bitmap avatarBmp, byte[] avatarBytes, String name) { + GroupData(GroupId id, Set recipients, Bitmap avatarBmp, byte[] avatarBytes, String name) { this.id = id; this.recipients = recipients; this.avatarBmp = avatarBmp; diff --git a/app/src/main/java/org/thoughtcrime/securesms/contacts/ContactAccessor.java b/app/src/main/java/org/thoughtcrime/securesms/contacts/ContactAccessor.java index b9450e635e..973561cc95 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/contacts/ContactAccessor.java +++ b/app/src/main/java/org/thoughtcrime/securesms/contacts/ContactAccessor.java @@ -202,7 +202,7 @@ public class ContactAccessor { reader = DatabaseFactory.getGroupDatabase(context).getGroupsFilteredByTitle(constraint, true); while ((record = reader.getNext()) != null) { - numberList.add(record.getEncodedId()); + numberList.add(record.getId().toString()); } } finally { if (reader != null) diff --git a/app/src/main/java/org/thoughtcrime/securesms/contacts/ContactSelectionListItem.java b/app/src/main/java/org/thoughtcrime/securesms/contacts/ContactSelectionListItem.java index dbd2da39af..81bd8a83d3 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/contacts/ContactSelectionListItem.java +++ b/app/src/main/java/org/thoughtcrime/securesms/contacts/ContactSelectionListItem.java @@ -2,24 +2,24 @@ package org.thoughtcrime.securesms.contacts; import android.annotation.SuppressLint; import android.content.Context; -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; - import android.util.AttributeSet; import android.view.View; import android.widget.CheckBox; import android.widget.LinearLayout; import android.widget.TextView; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + import org.thoughtcrime.securesms.R; import org.thoughtcrime.securesms.components.AvatarImageView; import org.thoughtcrime.securesms.components.FromTextView; +import org.thoughtcrime.securesms.groups.GroupId; import org.thoughtcrime.securesms.mms.GlideRequests; import org.thoughtcrime.securesms.recipients.LiveRecipient; import org.thoughtcrime.securesms.recipients.Recipient; import org.thoughtcrime.securesms.recipients.RecipientForeverObserver; import org.thoughtcrime.securesms.recipients.RecipientId; -import org.thoughtcrime.securesms.util.GroupUtil; import org.thoughtcrime.securesms.util.ViewUtil; import org.whispersystems.libsignal.util.guava.Optional; @@ -106,7 +106,7 @@ public class ContactSelectionListItem extends LinearLayout implements RecipientF @SuppressLint("SetTextI18n") private void setText(@Nullable Recipient recipient, int type, String name, String number, String label) { - if (number == null || number.isEmpty() || GroupUtil.isEncodedGroup(number)) { + if (number == null || number.isEmpty() || GroupId.isEncodedGroup(number)) { this.nameView.setEnabled(false); this.numberView.setText(""); this.labelView.setVisibility(View.GONE); diff --git a/app/src/main/java/org/thoughtcrime/securesms/contacts/ContactsCursorLoader.java b/app/src/main/java/org/thoughtcrime/securesms/contacts/ContactsCursorLoader.java index 6a2f877cd5..ee5f530f2c 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/contacts/ContactsCursorLoader.java +++ b/app/src/main/java/org/thoughtcrime/securesms/contacts/ContactsCursorLoader.java @@ -22,10 +22,11 @@ import android.database.Cursor; import android.database.MatrixCursor; import android.database.MergeCursor; import android.provider.ContactsContract; +import android.text.TextUtils; + import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.loader.content.CursorLoader; -import android.text.TextUtils; import org.thoughtcrime.securesms.R; import org.thoughtcrime.securesms.database.DatabaseFactory; @@ -35,8 +36,8 @@ import org.thoughtcrime.securesms.database.ThreadDatabase; import org.thoughtcrime.securesms.database.model.ThreadRecord; import org.thoughtcrime.securesms.logging.Log; import org.thoughtcrime.securesms.permissions.Permissions; -import org.thoughtcrime.securesms.recipients.Recipient; import org.thoughtcrime.securesms.phonenumbers.NumberUtil; +import org.thoughtcrime.securesms.recipients.Recipient; import org.thoughtcrime.securesms.recipients.RecipientId; import org.thoughtcrime.securesms.util.FeatureFlags; import org.thoughtcrime.securesms.util.UsernameUtil; @@ -226,7 +227,7 @@ public class ContactsCursorLoader extends CursorLoader { ThreadRecord threadRecord; while ((threadRecord = reader.getNext()) != null) { Recipient recipient = threadRecord.getRecipient(); - String stringId = recipient.isGroup() ? recipient.requireGroupId() : recipient.getE164().or(recipient.getEmail()).or(""); + String stringId = recipient.isGroup() ? recipient.requireGroupId().toString() : recipient.getE164().or(recipient.getEmail()).or(""); recentConversations.addRow(new Object[] { recipient.getId().serialize(), recipient.toShortString(getContext()), @@ -265,7 +266,7 @@ public class ContactsCursorLoader extends CursorLoader { while ((groupRecord = reader.getNext()) != null) { groupContacts.addRow(new Object[] { groupRecord.getRecipientId().serialize(), groupRecord.getTitle(), - groupRecord.getEncodedId(), + groupRecord.getId(), ContactsContract.CommonDataKinds.Phone.TYPE_CUSTOM, "", ContactRepository.NORMAL_TYPE }); diff --git a/app/src/main/java/org/thoughtcrime/securesms/contacts/avatars/GroupRecordContactPhoto.java b/app/src/main/java/org/thoughtcrime/securesms/contacts/avatars/GroupRecordContactPhoto.java index db505e0f58..f360fba5c3 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/contacts/avatars/GroupRecordContactPhoto.java +++ b/app/src/main/java/org/thoughtcrime/securesms/contacts/avatars/GroupRecordContactPhoto.java @@ -3,11 +3,13 @@ package org.thoughtcrime.securesms.contacts.avatars; import android.content.Context; import android.net.Uri; + import androidx.annotation.NonNull; import androidx.annotation.Nullable; import org.thoughtcrime.securesms.database.DatabaseFactory; import org.thoughtcrime.securesms.database.GroupDatabase; +import org.thoughtcrime.securesms.groups.GroupId; import org.thoughtcrime.securesms.util.Conversions; import org.whispersystems.libsignal.util.guava.Optional; @@ -16,12 +18,12 @@ import java.io.IOException; import java.io.InputStream; import java.security.MessageDigest; -public class GroupRecordContactPhoto implements ContactPhoto { +public final class GroupRecordContactPhoto implements ContactPhoto { - private final String groupId; - private final long avatarId; + private final GroupId groupId; + private final long avatarId; - public GroupRecordContactPhoto(@NonNull String groupId, long avatarId) { + public GroupRecordContactPhoto(@NonNull GroupId groupId, long avatarId) { this.groupId = groupId; this.avatarId = avatarId; } @@ -50,13 +52,13 @@ public class GroupRecordContactPhoto implements ContactPhoto { @Override public void updateDiskCacheKey(@NonNull MessageDigest messageDigest) { - messageDigest.update(groupId.getBytes()); + messageDigest.update(groupId.toString().getBytes()); messageDigest.update(Conversions.longToByteArray(avatarId)); } @Override public boolean equals(Object other) { - if (other == null || !(other instanceof GroupRecordContactPhoto)) return false; + if (!(other instanceof GroupRecordContactPhoto)) return false; GroupRecordContactPhoto that = (GroupRecordContactPhoto)other; return this.groupId.equals(that.groupId) && this.avatarId == that.avatarId; diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationActivity.java b/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationActivity.java index 93ee4518c2..8c2e864ddb 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationActivity.java +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationActivity.java @@ -1140,7 +1140,7 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity private void handleEditPushGroup() { Intent intent = new Intent(ConversationActivity.this, GroupCreateActivity.class); - intent.putExtra(GroupCreateActivity.GROUP_ID_EXTRA, recipient.get().requireGroupId()); + intent.putExtra(GroupCreateActivity.GROUP_ID_EXTRA, recipient.get().requireGroupId().toString()); startActivityForResult(intent, GROUP_EDIT); } 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 2a61610090..ddc9296124 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/GroupDatabase.java +++ b/app/src/main/java/org/thoughtcrime/securesms/database/GroupDatabase.java @@ -6,25 +6,25 @@ import android.content.ContentValues; import android.content.Context; import android.database.Cursor; import android.graphics.Bitmap; +import android.text.TextUtils; + import androidx.annotation.NonNull; import androidx.annotation.Nullable; -import android.text.TextUtils; import com.annimon.stream.Stream; import net.sqlcipher.database.SQLiteDatabase; import org.thoughtcrime.securesms.database.helpers.SQLCipherOpenHelper; +import org.thoughtcrime.securesms.groups.GroupId; import org.thoughtcrime.securesms.recipients.Recipient; import org.thoughtcrime.securesms.recipients.RecipientId; import org.thoughtcrime.securesms.util.BitmapUtil; -import org.thoughtcrime.securesms.util.GroupUtil; import org.thoughtcrime.securesms.util.Util; import org.whispersystems.libsignal.util.guava.Optional; import org.whispersystems.signalservice.api.messages.SignalServiceAttachmentPointer; import java.io.Closeable; -import java.io.IOException; import java.security.SecureRandom; import java.util.ArrayList; import java.util.Collections; @@ -95,9 +95,9 @@ public class GroupDatabase extends Database { } } - public Optional getGroup(String groupId) { + public Optional getGroup(@NonNull GroupId groupId) { try (Cursor cursor = databaseHelper.getReadableDatabase().query(TABLE_NAME, null, GROUP_ID + " = ?", - new String[] {groupId}, + new String[] {groupId.toString()}, null, null, null)) { if (cursor != null && cursor.moveToNext()) { @@ -113,7 +113,7 @@ public class GroupDatabase extends Database { return Optional.fromNullable(reader.getCurrent()); } - public boolean isUnknownGroup(String groupId) { + public boolean isUnknownGroup(@NonNull GroupId groupId) { Optional group = getGroup(groupId); if (!group.isPresent()) { @@ -143,7 +143,7 @@ public class GroupDatabase extends Database { return new Reader(cursor); } - public String getOrCreateGroupForMembers(List members, boolean mms) { + public GroupId getOrCreateGroupForMembers(List members, boolean mms) { Collections.sort(members); Cursor cursor = databaseHelper.getReadableDatabase().query(TABLE_NAME, new String[] {GROUP_ID}, @@ -152,9 +152,9 @@ public class GroupDatabase extends Database { null, null, null); try { if (cursor != null && cursor.moveToNext()) { - return cursor.getString(cursor.getColumnIndexOrThrow(GROUP_ID)); + return GroupId.parse(cursor.getString(cursor.getColumnIndexOrThrow(GROUP_ID))); } else { - String groupId = GroupUtil.getEncodedId(allocateGroupId(), mms); + GroupId groupId = allocateGroupId(mms); create(groupId, null, members, null, null); return groupId; } @@ -197,7 +197,7 @@ public class GroupDatabase extends Database { return new Reader(cursor); } - public @NonNull List getGroupMembers(String groupId, boolean includeSelf) { + public @NonNull List getGroupMembers(@NonNull GroupId groupId, boolean includeSelf) { List members = getCurrentMembers(groupId); List recipients = new LinkedList<>(); @@ -212,14 +212,14 @@ public class GroupDatabase extends Database { return recipients; } - public void create(@NonNull String groupId, @Nullable String title, @NonNull List members, + public void create(@NonNull GroupId groupId, @Nullable String title, @NonNull List members, @Nullable SignalServiceAttachmentPointer avatar, @Nullable String relay) { Collections.sort(members); ContentValues contentValues = new ContentValues(); contentValues.put(RECIPIENT_ID, DatabaseFactory.getRecipientDatabase(context).getOrInsertFromGroupId(groupId).serialize()); - contentValues.put(GROUP_ID, groupId); + contentValues.put(GROUP_ID, groupId.toString()); contentValues.put(TITLE, title); contentValues.put(MEMBERS, RecipientId.toSerializedList(members)); @@ -233,7 +233,7 @@ public class GroupDatabase extends Database { contentValues.put(AVATAR_RELAY, relay); contentValues.put(TIMESTAMP, System.currentTimeMillis()); contentValues.put(ACTIVE, 1); - contentValues.put(MMS, GroupUtil.isMmsGroup(groupId)); + contentValues.put(MMS, groupId.isMmsGroup()); databaseHelper.getWritableDatabase().insert(TABLE_NAME, null, contentValues); @@ -243,7 +243,7 @@ public class GroupDatabase extends Database { notifyConversationListListeners(); } - public void update(String groupId, String title, SignalServiceAttachmentPointer avatar) { + public void update(@NonNull GroupId groupId, String title, SignalServiceAttachmentPointer avatar) { ContentValues contentValues = new ContentValues(); if (title != null) contentValues.put(TITLE, title); @@ -256,7 +256,7 @@ public class GroupDatabase extends Database { databaseHelper.getWritableDatabase().update(TABLE_NAME, contentValues, GROUP_ID + " = ?", - new String[] {groupId}); + new String[] {groupId.toString()}); RecipientId groupRecipient = DatabaseFactory.getRecipientDatabase(context).getOrInsertFromGroupId(groupId); Recipient.live(groupRecipient).refresh(); @@ -264,21 +264,21 @@ public class GroupDatabase extends Database { notifyConversationListListeners(); } - public void updateTitle(String groupId, String title) { + public void updateTitle(@NonNull GroupId groupId, String title) { ContentValues contentValues = new ContentValues(); contentValues.put(TITLE, title); databaseHelper.getWritableDatabase().update(TABLE_NAME, contentValues, GROUP_ID + " = ?", - new String[] {groupId}); + new String[] {groupId.toString()}); RecipientId groupRecipient = DatabaseFactory.getRecipientDatabase(context).getOrInsertFromGroupId(groupId); Recipient.live(groupRecipient).refresh(); } - public void updateAvatar(String groupId, Bitmap avatar) { + public void updateAvatar(@NonNull GroupId groupId, @Nullable Bitmap avatar) { updateAvatar(groupId, BitmapUtil.toByteArray(avatar)); } - public void updateAvatar(String groupId, byte[] avatar) { + public void updateAvatar(@NonNull GroupId groupId, @Nullable byte[] avatar) { long avatarId; if (avatar != null) avatarId = Math.abs(new SecureRandom().nextLong()); @@ -290,13 +290,13 @@ public class GroupDatabase extends Database { contentValues.put(AVATAR_ID, avatarId); databaseHelper.getWritableDatabase().update(TABLE_NAME, contentValues, GROUP_ID + " = ?", - new String[] {groupId}); + new String[] {groupId.toString()}); RecipientId groupRecipient = DatabaseFactory.getRecipientDatabase(context).getOrInsertFromGroupId(groupId); Recipient.live(groupRecipient).refresh(); } - public void updateMembers(String groupId, List members) { + public void updateMembers(@NonNull GroupId groupId, List members) { Collections.sort(members); ContentValues contents = new ContentValues(); @@ -304,13 +304,13 @@ public class GroupDatabase extends Database { contents.put(ACTIVE, 1); databaseHelper.getWritableDatabase().update(TABLE_NAME, contents, GROUP_ID + " = ?", - new String[] {groupId}); + new String[] {groupId.toString()}); RecipientId groupRecipient = DatabaseFactory.getRecipientDatabase(context).getOrInsertFromGroupId(groupId); Recipient.live(groupRecipient).refresh(); } - public void remove(String groupId, RecipientId source) { + public void remove(@NonNull GroupId groupId, RecipientId source) { List currentMembers = getCurrentMembers(groupId); currentMembers.remove(source); @@ -318,19 +318,19 @@ public class GroupDatabase extends Database { contents.put(MEMBERS, RecipientId.toSerializedList(currentMembers)); databaseHelper.getWritableDatabase().update(TABLE_NAME, contents, GROUP_ID + " = ?", - new String[] {groupId}); + new String[] {groupId.toString()}); RecipientId groupRecipient = DatabaseFactory.getRecipientDatabase(context).getOrInsertFromGroupId(groupId); Recipient.live(groupRecipient).refresh(); } - private List getCurrentMembers(String groupId) { + private List getCurrentMembers(@NonNull GroupId groupId) { Cursor cursor = null; try { cursor = databaseHelper.getReadableDatabase().query(TABLE_NAME, new String[] {MEMBERS}, GROUP_ID + " = ?", - new String[] {groupId}, + new String[] {groupId.toString()}, null, null, null); if (cursor != null && cursor.moveToFirst()) { @@ -345,23 +345,22 @@ public class GroupDatabase extends Database { } } - public boolean isActive(String groupId) { + public boolean isActive(@NonNull GroupId groupId) { Optional record = getGroup(groupId); return record.isPresent() && record.get().isActive(); } - public void setActive(String groupId, boolean active) { + public void setActive(@NonNull GroupId groupId, boolean active) { SQLiteDatabase database = databaseHelper.getWritableDatabase(); ContentValues values = new ContentValues(); values.put(ACTIVE, active ? 1 : 0); - database.update(TABLE_NAME, values, GROUP_ID + " = ?", new String[] {groupId}); + database.update(TABLE_NAME, values, GROUP_ID + " = ?", new String[] {groupId.toString()}); } - - public byte[] allocateGroupId() { + public static GroupId allocateGroupId(boolean mms) { byte[] groupId = new byte[16]; new SecureRandom().nextBytes(groupId); - return groupId; + return mms ? GroupId.mms(groupId) : GroupId.v1(groupId); } public static class Reader implements Closeable { @@ -385,7 +384,7 @@ public class GroupDatabase extends Database { return null; } - return new GroupRecord(cursor.getString(cursor.getColumnIndexOrThrow(GROUP_ID)), + return new GroupRecord(GroupId.parse(cursor.getString(cursor.getColumnIndexOrThrow(GROUP_ID))), RecipientId.from(cursor.getLong(cursor.getColumnIndexOrThrow(RECIPIENT_ID))), cursor.getString(cursor.getColumnIndexOrThrow(TITLE)), cursor.getString(cursor.getColumnIndexOrThrow(MEMBERS)), @@ -408,7 +407,7 @@ public class GroupDatabase extends Database { public static class GroupRecord { - private final String id; + private final GroupId id; private final RecipientId recipientId; private final String title; private final List members; @@ -421,7 +420,7 @@ public class GroupDatabase extends Database { private final boolean active; private final boolean mms; - public GroupRecord(String id, @NonNull RecipientId recipientId, String title, String members, byte[] avatar, + public GroupRecord(@NonNull GroupId id, @NonNull RecipientId recipientId, String title, String members, byte[] avatar, long avatarId, byte[] avatarKey, String avatarContentType, String relay, boolean active, byte[] avatarDigest, boolean mms) { @@ -441,22 +440,14 @@ public class GroupDatabase extends Database { else this.members = new LinkedList<>(); } - public byte[] getId() { - try { - return GroupUtil.getDecodedId(id); - } catch (IOException ioe) { - throw new AssertionError(ioe); - } + public GroupId getId() { + return id; } public @NonNull RecipientId getRecipientId() { return recipientId; } - public String getEncodedId() { - return id; - } - public String getTitle() { return title; } 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 d8c6c5aa41..ce8031b569 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/RecipientDatabase.java +++ b/app/src/main/java/org/thoughtcrime/securesms/database/RecipientDatabase.java @@ -17,22 +17,20 @@ import net.sqlcipher.database.SQLiteDatabase; import org.signal.zkgroup.profiles.ProfileKey; import org.signal.zkgroup.profiles.ProfileKeyCredential; import org.thoughtcrime.securesms.color.MaterialColor; -import org.thoughtcrime.securesms.storage.StorageSyncHelper; -import org.thoughtcrime.securesms.storage.StorageSyncHelper.RecordUpdate; -import org.thoughtcrime.securesms.storage.StorageSyncModels; import org.thoughtcrime.securesms.database.IdentityDatabase.IdentityRecord; import org.thoughtcrime.securesms.database.helpers.SQLCipherOpenHelper; import org.thoughtcrime.securesms.dependencies.ApplicationDependencies; -import org.thoughtcrime.securesms.jobs.StorageSyncJob; +import org.thoughtcrime.securesms.groups.GroupId; import org.thoughtcrime.securesms.logging.Log; import org.thoughtcrime.securesms.profiles.ProfileName; import org.thoughtcrime.securesms.recipients.Recipient; import org.thoughtcrime.securesms.recipients.RecipientId; +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.GroupUtil; import org.thoughtcrime.securesms.util.IdentityUtil; import org.thoughtcrime.securesms.util.SqlUtil; -import org.thoughtcrime.securesms.util.TextSecurePreferences; import org.thoughtcrime.securesms.util.Util; import org.whispersystems.libsignal.IdentityKey; import org.whispersystems.libsignal.InvalidKeyException; @@ -352,13 +350,13 @@ public class RecipientDatabase extends Database { return getOrInsertByColumn(EMAIL, email).recipientId; } - public @NonNull RecipientId getOrInsertFromGroupId(@NonNull String groupId) { - GetOrInsertResult result = getOrInsertByColumn(GROUP_ID, groupId); + public @NonNull RecipientId getOrInsertFromGroupId(@NonNull GroupId groupId) { + GetOrInsertResult result = getOrInsertByColumn(GROUP_ID, groupId.toString()); if (result.neededInsert) { ContentValues values = new ContentValues(); - if (GroupUtil.isMmsGroup(groupId)) { + if (groupId.isMmsGroup()) { values.put(GROUP_TYPE, GroupType.MMS.getId()); } else { values.put(GROUP_TYPE, GroupType.SIGNAL_V1.getId()); @@ -563,7 +561,7 @@ public class RecipientDatabase extends Database { for (SignalGroupV1Record insert : groupV1Inserts) { db.insertOrThrow(TABLE_NAME, null, getValuesForStorageGroupV1(insert)); - Recipient recipient = Recipient.externalGroup(context, GroupUtil.getEncodedId(insert.getGroupId(), false)); + Recipient recipient = Recipient.externalGroup(context, GroupId.v1(insert.getGroupId())); threadDatabase.setArchived(recipient.getId(), insert.isArchived()); recipient.live().refresh(); @@ -577,7 +575,7 @@ public class RecipientDatabase extends Database { throw new AssertionError("Had an update, but it didn't match any rows!"); } - Recipient recipient = Recipient.externalGroup(context, GroupUtil.getEncodedId(update.getOld().getGroupId(), false)); + Recipient recipient = Recipient.externalGroup(context, GroupId.v1(update.getOld().getGroupId())); threadDatabase.setArchived(recipient.getId(), update.getNew().isArchived()); recipient.live().refresh(); @@ -672,7 +670,7 @@ public class RecipientDatabase extends Database { private static @NonNull ContentValues getValuesForStorageGroupV1(@NonNull SignalGroupV1Record groupV1) { ContentValues values = new ContentValues(); - values.put(GROUP_ID, GroupUtil.getEncodedId(groupV1.getGroupId(), false)); + values.put(GROUP_ID, GroupId.v1(groupV1.getGroupId()).toString()); values.put(GROUP_TYPE, GroupType.SIGNAL_V1.getId()); values.put(PROFILE_SHARING, groupV1.isProfileSharingEnabled() ? "1" : "0"); values.put(BLOCKED, groupV1.isBlocked() ? "1" : "0"); @@ -729,13 +727,13 @@ public class RecipientDatabase extends Database { return out; } - private @NonNull RecipientSettings getRecipientSettings(@NonNull Cursor cursor) { + private static @NonNull RecipientSettings getRecipientSettings(@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)); - String groupId = cursor.getString(cursor.getColumnIndexOrThrow(GROUP_ID)); + GroupId groupId = GroupId.parseNullable(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)); @@ -1408,10 +1406,10 @@ public class RecipientDatabase extends Database { db.update(TABLE_NAME, setBlocked, UUID + " = ?", new String[] { uuid }); } - List groupIdStrings = Stream.of(groupIds).map(g -> GroupUtil.getEncodedId(g, false)).toList(); + List groupIdStrings = Stream.of(groupIds).map(GroupId::v1).toList(); - for (String groupId : groupIdStrings) { - db.update(TABLE_NAME, setBlocked, GROUP_ID + " = ?", new String[] { groupId }); + for (GroupId groupId : groupIdStrings) { + db.update(TABLE_NAME, setBlocked, GROUP_ID + " = ?", new String[] { groupId.toString() }); } db.setTransactionSuccessful(); @@ -1637,7 +1635,7 @@ public class RecipientDatabase extends Database { private final String username; private final String e164; private final String email; - private final String groupId; + private final GroupId groupId; private final GroupType groupType; private final boolean blocked; private final long muteUntil; @@ -1673,7 +1671,7 @@ public class RecipientDatabase extends Database { @Nullable String username, @Nullable String e164, @Nullable String email, - @Nullable String groupId, + @Nullable GroupId groupId, @NonNull GroupType groupType, boolean blocked, long muteUntil, @@ -1761,7 +1759,7 @@ public class RecipientDatabase extends Database { return email; } - public @Nullable String getGroupId() { + public @Nullable GroupId getGroupId() { return groupId; } diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/SmsMigrator.java b/app/src/main/java/org/thoughtcrime/securesms/database/SmsMigrator.java index 0ba794ab2f..01e47b2a0b 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/SmsMigrator.java +++ b/app/src/main/java/org/thoughtcrime/securesms/database/SmsMigrator.java @@ -20,6 +20,7 @@ import android.content.Context; import android.database.Cursor; import android.database.sqlite.SQLiteException; import android.net.Uri; + import androidx.annotation.Nullable; import com.annimon.stream.Stream; @@ -27,6 +28,7 @@ import com.annimon.stream.Stream; import net.sqlcipher.database.SQLiteDatabase; import net.sqlcipher.database.SQLiteStatement; +import org.thoughtcrime.securesms.groups.GroupId; import org.thoughtcrime.securesms.logging.Log; import org.thoughtcrime.securesms.recipients.Recipient; import org.thoughtcrime.securesms.recipients.RecipientId; @@ -231,7 +233,7 @@ public class SmsMigrator { List recipientIds = Stream.of(ourRecipients).map(Recipient::getId).toList(); - String ourGroupId = DatabaseFactory.getGroupDatabase(context).getOrCreateGroupForMembers(recipientIds, true); + GroupId ourGroupId = DatabaseFactory.getGroupDatabase(context).getOrCreateGroupForMembers(recipientIds, true); RecipientId ourGroupRecipientId = DatabaseFactory.getRecipientDatabase(context).getOrInsertFromGroupId(ourGroupId); Recipient ourGroupRecipient = Recipient.resolved(ourGroupRecipientId); long ourThreadId = threadDatabase.getThreadIdFor(ourGroupRecipient, ThreadDatabase.DistributionTypes.CONVERSATION); diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/helpers/ClassicOpenHelper.java b/app/src/main/java/org/thoughtcrime/securesms/database/helpers/ClassicOpenHelper.java index 1cd0784865..f0f91150fa 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/helpers/ClassicOpenHelper.java +++ b/app/src/main/java/org/thoughtcrime/securesms/database/helpers/ClassicOpenHelper.java @@ -10,9 +10,9 @@ import android.database.sqlite.SQLiteDatabase; import android.database.sqlite.SQLiteOpenHelper; import android.net.Uri; import android.provider.ContactsContract; -import androidx.annotation.Nullable; import android.text.TextUtils; -import org.thoughtcrime.securesms.logging.Log; + +import androidx.annotation.Nullable; import com.fasterxml.jackson.annotation.JsonProperty; import com.google.i18n.phonenumbers.NumberParseException; @@ -35,13 +35,14 @@ import org.thoughtcrime.securesms.database.PushDatabase; import org.thoughtcrime.securesms.database.RecipientDatabase; import org.thoughtcrime.securesms.database.SmsDatabase; import org.thoughtcrime.securesms.database.ThreadDatabase; +import org.thoughtcrime.securesms.groups.GroupId; +import org.thoughtcrime.securesms.logging.Log; import org.thoughtcrime.securesms.migrations.LegacyMigrationJob; import org.thoughtcrime.securesms.notifications.MessageNotifier; import org.thoughtcrime.securesms.permissions.Permissions; import org.thoughtcrime.securesms.phonenumbers.NumberUtil; import org.thoughtcrime.securesms.util.Base64; import org.thoughtcrime.securesms.util.DelimiterUtil; -import org.thoughtcrime.securesms.util.GroupUtil; import org.thoughtcrime.securesms.util.Hex; import org.thoughtcrime.securesms.util.JsonUtils; import org.thoughtcrime.securesms.util.MediaUtil; @@ -1274,7 +1275,7 @@ public class ClassicOpenHelper extends SQLiteOpenHelper { while (cursor != null && cursor.moveToNext()) { String address = cursor.getString(cursor.getColumnIndexOrThrow("recipient_ids")); - if (!TextUtils.isEmpty(address) && !GroupUtil.isEncodedGroup(address) && !NumberUtil.isValidEmail(address)) { + if (!TextUtils.isEmpty(address) && !GroupId.isEncodedGroup(address) && !NumberUtil.isValidEmail(address)) { Uri lookup = Uri.withAppendedPath(ContactsContract.PhoneLookup.CONTENT_FILTER_URI, Uri.encode(address)); try (Cursor contactCursor = context.getContentResolver().query(lookup, new String[] {ContactsContract.PhoneLookup.DISPLAY_NAME, diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/helpers/RecipientIdMigrationHelper.java b/app/src/main/java/org/thoughtcrime/securesms/database/helpers/RecipientIdMigrationHelper.java index a1e4e66101..eed375d656 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/helpers/RecipientIdMigrationHelper.java +++ b/app/src/main/java/org/thoughtcrime/securesms/database/helpers/RecipientIdMigrationHelper.java @@ -9,10 +9,10 @@ import androidx.annotation.Nullable; import net.sqlcipher.database.SQLiteDatabase; +import org.thoughtcrime.securesms.groups.GroupId; import org.thoughtcrime.securesms.logging.Log; import org.thoughtcrime.securesms.phonenumbers.NumberUtil; import org.thoughtcrime.securesms.util.DelimiterUtil; -import org.thoughtcrime.securesms.util.GroupUtil; import org.thoughtcrime.securesms.util.Util; import java.util.HashSet; @@ -153,7 +153,7 @@ public class RecipientIdMigrationHelper { try (Cursor cursor = db.query("recipient_preferences", null, null, null, null, null, null)) { while (cursor != null && cursor.moveToNext()) { String address = cursor.getString(cursor.getColumnIndexOrThrow("recipient_ids")); - boolean isGroup = GroupUtil.isEncodedGroup(address); + boolean isGroup = GroupId.isEncodedGroup(address); boolean isEmail = !isGroup && NumberUtil.isValidEmail(address); boolean isPhone = !isGroup && !isEmail; diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/helpers/SQLCipherOpenHelper.java b/app/src/main/java/org/thoughtcrime/securesms/database/helpers/SQLCipherOpenHelper.java index 498bcb1c9f..daf64ce39a 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/helpers/SQLCipherOpenHelper.java +++ b/app/src/main/java/org/thoughtcrime/securesms/database/helpers/SQLCipherOpenHelper.java @@ -21,7 +21,6 @@ import net.sqlcipher.database.SQLiteDatabase; import net.sqlcipher.database.SQLiteDatabaseHook; import net.sqlcipher.database.SQLiteOpenHelper; -import org.thoughtcrime.securesms.storage.StorageSyncHelper; import org.thoughtcrime.securesms.crypto.DatabaseSecret; import org.thoughtcrime.securesms.crypto.MasterSecret; import org.thoughtcrime.securesms.database.AttachmentDatabase; @@ -44,13 +43,14 @@ import org.thoughtcrime.securesms.database.StickerDatabase; import org.thoughtcrime.securesms.database.StorageKeyDatabase; import org.thoughtcrime.securesms.database.ThreadDatabase; import org.thoughtcrime.securesms.dependencies.ApplicationDependencies; +import org.thoughtcrime.securesms.groups.GroupId; import org.thoughtcrime.securesms.jobs.RefreshPreKeysJob; import org.thoughtcrime.securesms.logging.Log; import org.thoughtcrime.securesms.notifications.NotificationChannels; import org.thoughtcrime.securesms.phonenumbers.PhoneNumberFormatter; import org.thoughtcrime.securesms.service.KeyCachingService; +import org.thoughtcrime.securesms.storage.StorageSyncHelper; import org.thoughtcrime.securesms.util.Base64; -import org.thoughtcrime.securesms.util.GroupUtil; import org.thoughtcrime.securesms.util.ServiceUtil; import org.thoughtcrime.securesms.util.SqlUtil; import org.thoughtcrime.securesms.util.TextSecurePreferences; @@ -349,7 +349,7 @@ public class SQLCipherOpenHelper extends SQLiteOpenHelper { String displayName = NotificationChannels.getChannelDisplayNameFor(context, systemName, profileName, null, address); boolean vibrateEnabled = vibrateState == 0 ? TextSecurePreferences.isNotificationVibrateEnabled(context) : vibrateState == 1; - if (GroupUtil.isEncodedGroup(address)) { + if (GroupId.isEncodedGroup(address)) { try(Cursor groupCursor = db.rawQuery("SELECT title FROM groups WHERE group_id = ?", new String[] { address })) { if (groupCursor != null && groupCursor.moveToFirst()) { String title = groupCursor.getString(groupCursor.getColumnIndexOrThrow("title")); diff --git a/app/src/main/java/org/thoughtcrime/securesms/groups/GroupId.java b/app/src/main/java/org/thoughtcrime/securesms/groups/GroupId.java new file mode 100644 index 0000000000..769b4bf38c --- /dev/null +++ b/app/src/main/java/org/thoughtcrime/securesms/groups/GroupId.java @@ -0,0 +1,93 @@ +package org.thoughtcrime.securesms.groups; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + +import org.thoughtcrime.securesms.util.Hex; + +import java.io.IOException; + +public final class GroupId { + + private static final String ENCODED_SIGNAL_GROUP_PREFIX = "__textsecure_group__!"; + private static final String ENCODED_MMS_GROUP_PREFIX = "__signal_mms_group__!"; + + private final String encodedId; + + private GroupId(@NonNull String encodedId) { + this.encodedId = encodedId; + } + + public static @NonNull GroupId v1(byte[] gv1GroupIdBytes) { + return new GroupId(ENCODED_SIGNAL_GROUP_PREFIX + Hex.toStringCondensed(gv1GroupIdBytes)); + } + + public static @NonNull GroupId mms(byte[] mmsGroupIdBytes) { + return new GroupId(ENCODED_MMS_GROUP_PREFIX + Hex.toStringCondensed(mmsGroupIdBytes)); + } + + public static @NonNull GroupId parse(@NonNull String encodedGroupId) { + try { + if (!isEncodedGroup(encodedGroupId)) { + throw new IOException("Invalid encoding"); + } + + byte[] bytes = extractDecodedId(encodedGroupId); + return isMmsGroup(encodedGroupId) ? mms(bytes) : v1(bytes); + } catch (IOException e) { + throw new AssertionError(e); + } + } + + public static @Nullable GroupId parseNullable(@Nullable String encodedGroupId) { + if (encodedGroupId == null) { + return null; + } + + return parse(encodedGroupId); + } + + public static boolean isEncodedGroup(@NonNull String groupId) { + return groupId.startsWith(ENCODED_SIGNAL_GROUP_PREFIX) || groupId.startsWith(ENCODED_MMS_GROUP_PREFIX); + } + + private static byte[] extractDecodedId(@NonNull String encodedGroupId) throws IOException { + return Hex.fromStringCondensed(encodedGroupId.split("!", 2)[1]); + } + + private static boolean isMmsGroup(@NonNull String groupId) { + return groupId.startsWith(ENCODED_MMS_GROUP_PREFIX); + } + + public byte[] getDecodedId() { + try { + return extractDecodedId(encodedId); + } catch (IOException e) { + throw new AssertionError(e); + } + } + + public boolean isMmsGroup() { + return isMmsGroup(encodedId); + } + + @Override + public boolean equals(@Nullable Object obj) { + if (obj instanceof GroupId) { + return ((GroupId) obj).encodedId.equals(encodedId); + } + + return false; + } + + @Override + public int hashCode() { + return encodedId.hashCode(); + } + + @NonNull + @Override + public String toString() { + return encodedId; + } +} diff --git a/app/src/main/java/org/thoughtcrime/securesms/groups/GroupManager.java b/app/src/main/java/org/thoughtcrime/securesms/groups/GroupManager.java index 3cf40ebb78..2293513a13 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/groups/GroupManager.java +++ b/app/src/main/java/org/thoughtcrime/securesms/groups/GroupManager.java @@ -29,7 +29,7 @@ public final class GroupManager { } public static GroupActionResult updateGroup(@NonNull Context context, - @NonNull String groupId, + @NonNull GroupId groupId, @NonNull Set members, @Nullable Bitmap avatar, @Nullable String name) @@ -51,7 +51,7 @@ public final class GroupManager { @WorkerThread public static boolean leaveGroup(@NonNull Context context, @NonNull Recipient groupRecipient) { - String groupId = groupRecipient.requireGroupId(); + GroupId groupId = groupRecipient.requireGroupId(); return V1GroupManager.leaveGroup(context, groupId, groupRecipient); } diff --git a/app/src/main/java/org/thoughtcrime/securesms/groups/GroupMessageProcessor.java b/app/src/main/java/org/thoughtcrime/securesms/groups/GroupMessageProcessor.java index c4e32eb1fd..b257b6773c 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/groups/GroupMessageProcessor.java +++ b/app/src/main/java/org/thoughtcrime/securesms/groups/GroupMessageProcessor.java @@ -28,7 +28,6 @@ import org.thoughtcrime.securesms.sms.IncomingGroupMessage; import org.thoughtcrime.securesms.sms.IncomingTextMessage; import org.thoughtcrime.securesms.util.Base64; import org.thoughtcrime.securesms.util.FeatureFlags; -import org.thoughtcrime.securesms.util.GroupUtil; import org.whispersystems.libsignal.util.guava.Optional; import org.whispersystems.signalservice.api.messages.SignalServiceAttachment; import org.whispersystems.signalservice.api.messages.SignalServiceContent; @@ -63,7 +62,7 @@ public class GroupMessageProcessor { GroupDatabase database = DatabaseFactory.getGroupDatabase(context); SignalServiceGroup group = message.getGroupInfo().get(); - String id = GroupUtil.getEncodedId(group.getGroupId(), false); + GroupId id = GroupId.v1(group.getGroupId()); Optional record = database.getGroup(id); if (record.isPresent() && group.getType() == Type.UPDATE) { @@ -73,7 +72,7 @@ public class GroupMessageProcessor { } else if (record.isPresent() && group.getType() == Type.QUIT) { return handleGroupLeave(context, content, group, record.get(), outgoing); } else if (record.isPresent() && group.getType() == Type.REQUEST_INFO) { - return handleGroupInfoRequest(context, content, group, record.get()); + return handleGroupInfoRequest(context, content, record.get()); } else { Log.w(TAG, "Received unknown type, ignoring..."); return null; @@ -86,7 +85,7 @@ public class GroupMessageProcessor { boolean outgoing) { GroupDatabase database = DatabaseFactory.getGroupDatabase(context); - String id = GroupUtil.getEncodedId(group.getGroupId(), false); + GroupId id = GroupId.v1(group.getGroupId()); GroupContext.Builder builder = createGroupContext(group); builder.setType(GroupContext.Type.UPDATE); @@ -106,7 +105,7 @@ public class GroupMessageProcessor { if (FeatureFlags.messageRequests() && (sender.isSystemContact() || sender.isProfileSharing())) { Log.i(TAG, "Auto-enabling profile sharing because 'adder' is trusted. contact: " + sender.isSystemContact() + ", profileSharing: " + sender.isProfileSharing()); - DatabaseFactory.getRecipientDatabase(context).setProfileSharing(Recipient.external(context, id).getId(), true); + DatabaseFactory.getRecipientDatabase(context).setProfileSharing(Recipient.externalGroup(context, id).getId(), true); } return storeMessage(context, content, group, builder.build(), outgoing); @@ -120,7 +119,7 @@ public class GroupMessageProcessor { { GroupDatabase database = DatabaseFactory.getGroupDatabase(context); - String id = GroupUtil.getEncodedId(group.getGroupId(), false); + GroupId id = GroupId.v1(group.getGroupId()); Set recordMembers = new HashSet<>(groupRecord.getMembers()); Set messageMembers = new HashSet<>(); @@ -178,13 +177,12 @@ public class GroupMessageProcessor { private static Long handleGroupInfoRequest(@NonNull Context context, @NonNull SignalServiceContent content, - @NonNull SignalServiceGroup group, @NonNull GroupRecord record) { Recipient sender = Recipient.externalPush(context, content.getSender()); if (record.getMembers().contains(sender.getId())) { - ApplicationDependencies.getJobManager().add(new PushGroupUpdateJob(sender.getId(), group.getGroupId())); + ApplicationDependencies.getJobManager().add(new PushGroupUpdateJob(sender.getId(), record.getId())); } return null; @@ -197,7 +195,7 @@ public class GroupMessageProcessor { boolean outgoing) { GroupDatabase database = DatabaseFactory.getGroupDatabase(context); - String id = GroupUtil.getEncodedId(group.getGroupId(), false); + GroupId id = GroupId.v1(group.getGroupId()); List members = record.getMembers(); GroupContext.Builder builder = createGroupContext(group); @@ -222,13 +220,13 @@ public class GroupMessageProcessor { { if (group.getAvatar().isPresent()) { ApplicationDependencies.getJobManager() - .add(new AvatarDownloadJob(group.getGroupId())); + .add(new AvatarDownloadJob(GroupId.v1(group.getGroupId()))); } try { if (outgoing) { MmsDatabase mmsDatabase = DatabaseFactory.getMmsDatabase(context); - RecipientId recipientId = DatabaseFactory.getRecipientDatabase(context).getOrInsertFromGroupId(GroupUtil.getEncodedId(group.getGroupId(), false)); + RecipientId recipientId = DatabaseFactory.getRecipientDatabase(context).getOrInsertFromGroupId(GroupId.v1(group.getGroupId())); Recipient recipient = Recipient.resolved(recipientId); OutgoingGroupMediaMessage outgoingMessage = new OutgoingGroupMediaMessage(recipient, storage, null, content.getTimestamp(), 0, false, null, Collections.emptyList(), Collections.emptyList()); long threadId = DatabaseFactory.getThreadDatabase(context).getThreadIdFor(recipient); @@ -240,7 +238,7 @@ public class GroupMessageProcessor { } else { SmsDatabase smsDatabase = DatabaseFactory.getSmsDatabase(context); String body = Base64.encodeBytes(storage.toByteArray()); - IncomingTextMessage incoming = new IncomingTextMessage(Recipient.externalPush(context, content.getSender()).getId(), content.getSenderDevice(), content.getTimestamp(), body, Optional.of(GroupUtil.getEncodedId(group.getGroupId(), false)), 0, content.isNeedsReceipt()); + IncomingTextMessage incoming = new IncomingTextMessage(Recipient.externalPush(context, content.getSender()).getId(), content.getSenderDevice(), content.getTimestamp(), body, Optional.of(GroupId.v1(group.getGroupId())), 0, content.isNeedsReceipt()); IncomingGroupMessage groupMessage = new IncomingGroupMessage(incoming, storage, body); Optional insertResult = smsDatabase.insertMessageInbox(groupMessage); diff --git a/app/src/main/java/org/thoughtcrime/securesms/groups/V1GroupManager.java b/app/src/main/java/org/thoughtcrime/securesms/groups/V1GroupManager.java index ae01f6479e..451ea29a3e 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/groups/V1GroupManager.java +++ b/app/src/main/java/org/thoughtcrime/securesms/groups/V1GroupManager.java @@ -3,6 +3,7 @@ package org.thoughtcrime.securesms.groups; import android.content.Context; import android.graphics.Bitmap; import android.net.Uri; + import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.annotation.WorkerThread; @@ -11,7 +12,6 @@ import com.google.protobuf.ByteString; import org.thoughtcrime.securesms.attachments.Attachment; import org.thoughtcrime.securesms.attachments.UriAttachment; -import org.thoughtcrime.securesms.blurhash.BlurHash; import org.thoughtcrime.securesms.database.AttachmentDatabase; import org.thoughtcrime.securesms.database.DatabaseFactory; import org.thoughtcrime.securesms.database.GroupDatabase; @@ -28,12 +28,10 @@ import org.thoughtcrime.securesms.sms.MessageSender; import org.thoughtcrime.securesms.util.BitmapUtil; import org.thoughtcrime.securesms.util.GroupUtil; import org.thoughtcrime.securesms.util.MediaUtil; -import org.thoughtcrime.securesms.util.TextSecurePreferences; import org.whispersystems.libsignal.util.guava.Optional; import org.whispersystems.signalservice.api.util.InvalidNumberException; import org.whispersystems.signalservice.internal.push.SignalServiceProtos.GroupContext; -import java.io.IOException; import java.util.Collections; import java.util.LinkedList; import java.util.List; @@ -49,7 +47,7 @@ final class V1GroupManager { { final byte[] avatarBytes = BitmapUtil.toByteArray(avatar); final GroupDatabase groupDatabase = DatabaseFactory.getGroupDatabase(context); - final String groupId = GroupUtil.getEncodedId(groupDatabase.allocateGroupId(), mms); + final GroupId groupId = GroupDatabase.allocateGroupId(mms); final RecipientId groupRecipientId = DatabaseFactory.getRecipientDatabase(context).getOrInsertFromGroupId(groupId); final Recipient groupRecipient = Recipient.resolved(groupRecipientId); @@ -67,7 +65,7 @@ final class V1GroupManager { } static GroupActionResult updateGroup(@NonNull Context context, - @NonNull String groupId, + @NonNull GroupId groupId, @NonNull Set memberAddresses, @Nullable Bitmap avatar, @Nullable String name) @@ -81,7 +79,7 @@ final class V1GroupManager { groupDatabase.updateTitle(groupId, name); groupDatabase.updateAvatar(groupId, avatarBytes); - if (!GroupUtil.isMmsGroup(groupId)) { + if (!groupId.isMmsGroup()) { return sendGroupUpdate(context, groupId, memberAddresses, name, avatarBytes); } else { RecipientId groupRecipientId = DatabaseFactory.getRecipientDatabase(context).getOrInsertFromGroupId(groupId); @@ -92,48 +90,44 @@ final class V1GroupManager { } private static GroupActionResult sendGroupUpdate(@NonNull Context context, - @NonNull String groupId, + @NonNull GroupId groupId, @NonNull Set members, @Nullable String groupName, @Nullable byte[] avatar) { - try { - Attachment avatarAttachment = null; - RecipientId groupRecipientId = DatabaseFactory.getRecipientDatabase(context).getOrInsertFromGroupId(groupId); - Recipient groupRecipient = Recipient.resolved(groupRecipientId); + Attachment avatarAttachment = null; + RecipientId groupRecipientId = DatabaseFactory.getRecipientDatabase(context).getOrInsertFromGroupId(groupId); + Recipient groupRecipient = Recipient.resolved(groupRecipientId); - List uuidMembers = new LinkedList<>(); - List e164Members = new LinkedList<>(); + List uuidMembers = new LinkedList<>(); + List e164Members = new LinkedList<>(); - for (RecipientId member : members) { - Recipient recipient = Recipient.resolved(member); - uuidMembers.add(GroupMessageProcessor.createMember(RecipientUtil.toSignalServiceAddress(context, recipient))); - } - - GroupContext.Builder groupContextBuilder = GroupContext.newBuilder() - .setId(ByteString.copyFrom(GroupUtil.getDecodedId(groupId))) - .setType(GroupContext.Type.UPDATE) - .addAllMembersE164(e164Members) - .addAllMembers(uuidMembers); - if (groupName != null) groupContextBuilder.setName(groupName); - GroupContext groupContext = groupContextBuilder.build(); - - if (avatar != null) { - Uri avatarUri = BlobProvider.getInstance().forData(avatar).createForSingleUseInMemory(); - avatarAttachment = new UriAttachment(avatarUri, MediaUtil.IMAGE_PNG, AttachmentDatabase.TRANSFER_PROGRESS_DONE, avatar.length, null, false, false, null, null, null, null); - } - - OutgoingGroupMediaMessage outgoingMessage = new OutgoingGroupMediaMessage(groupRecipient, groupContext, avatarAttachment, System.currentTimeMillis(), 0, false, null, Collections.emptyList(), Collections.emptyList()); - long threadId = MessageSender.send(context, outgoingMessage, -1, false, null); - - return new GroupActionResult(groupRecipient, threadId); - } catch (IOException e) { - throw new AssertionError(e); + for (RecipientId member : members) { + Recipient recipient = Recipient.resolved(member); + uuidMembers.add(GroupMessageProcessor.createMember(RecipientUtil.toSignalServiceAddress(context, recipient))); } + + GroupContext.Builder groupContextBuilder = GroupContext.newBuilder() + .setId(ByteString.copyFrom(groupId.getDecodedId())) + .setType(GroupContext.Type.UPDATE) + .addAllMembersE164(e164Members) + .addAllMembers(uuidMembers); + if (groupName != null) groupContextBuilder.setName(groupName); + GroupContext groupContext = groupContextBuilder.build(); + + if (avatar != null) { + Uri avatarUri = BlobProvider.getInstance().forData(avatar).createForSingleUseInMemory(); + avatarAttachment = new UriAttachment(avatarUri, MediaUtil.IMAGE_PNG, AttachmentDatabase.TRANSFER_PROGRESS_DONE, avatar.length, null, false, false, null, null, null, null); + } + + OutgoingGroupMediaMessage outgoingMessage = new OutgoingGroupMediaMessage(groupRecipient, groupContext, avatarAttachment, System.currentTimeMillis(), 0, false, null, Collections.emptyList(), Collections.emptyList()); + long threadId = MessageSender.send(context, outgoingMessage, -1, false, null); + + return new GroupActionResult(groupRecipient, threadId); } @WorkerThread - static boolean leaveGroup(@NonNull Context context, @NonNull String groupId, @NonNull Recipient groupRecipient) { + static boolean leaveGroup(@NonNull Context context, @NonNull GroupId groupId, @NonNull Recipient groupRecipient) { long threadId = DatabaseFactory.getThreadDatabase(context).getThreadIdFor(groupRecipient); Optional leaveMessage = GroupUtil.createGroupLeaveMessage(context, groupRecipient); diff --git a/app/src/main/java/org/thoughtcrime/securesms/jobs/AvatarDownloadJob.java b/app/src/main/java/org/thoughtcrime/securesms/jobs/AvatarDownloadJob.java index aae85b7eb0..90b113d863 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/jobs/AvatarDownloadJob.java +++ b/app/src/main/java/org/thoughtcrime/securesms/jobs/AvatarDownloadJob.java @@ -1,12 +1,14 @@ package org.thoughtcrime.securesms.jobs; import android.graphics.Bitmap; + import androidx.annotation.NonNull; import org.thoughtcrime.securesms.database.DatabaseFactory; import org.thoughtcrime.securesms.database.GroupDatabase; import org.thoughtcrime.securesms.database.GroupDatabase.GroupRecord; import org.thoughtcrime.securesms.dependencies.ApplicationDependencies; +import org.thoughtcrime.securesms.groups.GroupId; import org.thoughtcrime.securesms.jobmanager.Data; import org.thoughtcrime.securesms.jobmanager.Job; import org.thoughtcrime.securesms.jobmanager.impl.NetworkConstraint; @@ -14,7 +16,6 @@ import org.thoughtcrime.securesms.logging.Log; import org.thoughtcrime.securesms.mms.AttachmentStreamUriLoader.AttachmentModel; import org.thoughtcrime.securesms.util.BitmapDecodingException; import org.thoughtcrime.securesms.util.BitmapUtil; -import org.thoughtcrime.securesms.util.GroupUtil; import org.thoughtcrime.securesms.util.Hex; import org.whispersystems.libsignal.InvalidMessageException; import org.whispersystems.libsignal.util.guava.Optional; @@ -36,9 +37,9 @@ public class AvatarDownloadJob extends BaseJob { private static final String KEY_GROUP_ID = "group_id"; - private byte[] groupId; + private @NonNull GroupId groupId; - public AvatarDownloadJob(@NonNull byte[] groupId) { + public AvatarDownloadJob(@NonNull GroupId groupId) { this(new Job.Parameters.Builder() .addConstraint(NetworkConstraint.KEY) .setMaxAttempts(10) @@ -46,14 +47,14 @@ public class AvatarDownloadJob extends BaseJob { groupId); } - private AvatarDownloadJob(@NonNull Job.Parameters parameters, @NonNull byte[] groupId) { + private AvatarDownloadJob(@NonNull Job.Parameters parameters, @NonNull GroupId groupId) { super(parameters); this.groupId = groupId; } @Override public @NonNull Data serialize() { - return new Data.Builder().putString(KEY_GROUP_ID, GroupUtil.getEncodedId(groupId, false)).build(); + return new Data.Builder().putString(KEY_GROUP_ID, groupId.toString()).build(); } @Override @@ -63,9 +64,8 @@ public class AvatarDownloadJob extends BaseJob { @Override public void onRun() throws IOException { - String encodeId = GroupUtil.getEncodedId(groupId, false); GroupDatabase database = DatabaseFactory.getGroupDatabase(context); - Optional record = database.getGroup(encodeId); + Optional record = database.getGroup(groupId); File attachment = null; try { @@ -93,7 +93,7 @@ public class AvatarDownloadJob extends BaseJob { InputStream inputStream = receiver.retrieveAttachment(pointer, attachment, MAX_AVATAR_SIZE); Bitmap avatar = BitmapUtil.createScaledBitmap(context, new AttachmentModel(attachment, key, 0, digest), 500, 500); - database.updateAvatar(encodeId, avatar); + database.updateAvatar(groupId, avatar); inputStream.close(); } } catch (BitmapDecodingException | NonSuccessfulResponseCodeException | InvalidMessageException e) { @@ -116,11 +116,7 @@ public class AvatarDownloadJob extends BaseJob { public static final class Factory implements Job.Factory { @Override public @NonNull AvatarDownloadJob create(@NonNull Parameters parameters, @NonNull Data data) { - try { - return new AvatarDownloadJob(parameters, GroupUtil.getDecodedId(data.getString(KEY_GROUP_ID))); - } catch (IOException e) { - throw new AssertionError(e); - } + return new AvatarDownloadJob(parameters, GroupId.parse(data.getString(KEY_GROUP_ID))); } } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/jobs/LeaveGroupJob.java b/app/src/main/java/org/thoughtcrime/securesms/jobs/LeaveGroupJob.java index 2d7d990639..be14b80868 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/jobs/LeaveGroupJob.java +++ b/app/src/main/java/org/thoughtcrime/securesms/jobs/LeaveGroupJob.java @@ -3,12 +3,12 @@ package org.thoughtcrime.securesms.jobs; import android.content.Context; import androidx.annotation.NonNull; -import androidx.annotation.WorkerThread; import com.annimon.stream.Stream; import org.thoughtcrime.securesms.crypto.UnidentifiedAccessUtil; import org.thoughtcrime.securesms.dependencies.ApplicationDependencies; +import org.thoughtcrime.securesms.groups.GroupId; import org.thoughtcrime.securesms.jobmanager.Data; import org.thoughtcrime.securesms.jobmanager.Job; import org.thoughtcrime.securesms.jobmanager.impl.NetworkConstraint; @@ -18,7 +18,6 @@ import org.thoughtcrime.securesms.recipients.RecipientId; import org.thoughtcrime.securesms.recipients.RecipientUtil; import org.thoughtcrime.securesms.transport.RetryLaterException; import org.thoughtcrime.securesms.util.Base64; -import org.thoughtcrime.securesms.util.GroupUtil; import org.whispersystems.libsignal.util.guava.Optional; import org.whispersystems.signalservice.api.SignalServiceMessageSender; import org.whispersystems.signalservice.api.crypto.UnidentifiedAccessPair; @@ -27,8 +26,6 @@ import org.whispersystems.signalservice.api.messages.SendMessageResult; import org.whispersystems.signalservice.api.messages.SignalServiceDataMessage; import org.whispersystems.signalservice.api.messages.SignalServiceGroup; import org.whispersystems.signalservice.api.push.SignalServiceAddress; -import org.whispersystems.signalservice.api.push.exceptions.PushNetworkException; -import org.whispersystems.signalservice.internal.push.SignalServiceProtos; import java.io.IOException; import java.util.Collections; @@ -54,7 +51,7 @@ public class LeaveGroupJob extends BaseJob { private static final String KEY_MEMBERS = "members"; private static final String KEY_RECIPIENTS = "recipients"; - private final byte[] groupId; + private final GroupId groupId; private final String name; private final List members; private final List recipients; @@ -63,7 +60,7 @@ public class LeaveGroupJob extends BaseJob { List members = Stream.of(group.resolve().getParticipants()).map(Recipient::getId).toList(); members.remove(Recipient.self().getId()); - return new LeaveGroupJob(GroupUtil.getDecodedIdOrThrow(group.getGroupId().get()), + return new LeaveGroupJob(group.getGroupId().get(), group.resolve().getDisplayName(ApplicationDependencies.getApplication()), members, members, @@ -75,7 +72,7 @@ public class LeaveGroupJob extends BaseJob { .build()); } - private LeaveGroupJob(@NonNull byte[] groupId, + private LeaveGroupJob(@NonNull GroupId groupId, @NonNull String name, @NonNull List members, @NonNull List recipients, @@ -90,7 +87,7 @@ public class LeaveGroupJob extends BaseJob { @Override public @NonNull Data serialize() { - return new Data.Builder().putString(KEY_GROUP_ID, Base64.encodeBytes(groupId)) + return new Data.Builder().putString(KEY_GROUP_ID, Base64.encodeBytes(groupId.getDecodedId())) .putString(KEY_GROUP_NAME, name) .putString(KEY_MEMBERS, RecipientId.toSerializedList(members)) .putString(KEY_RECIPIENTS, RecipientId.toSerializedList(recipients)) @@ -128,7 +125,7 @@ public class LeaveGroupJob extends BaseJob { } private static @NonNull List deliver(@NonNull Context context, - @NonNull byte[] groupId, + @NonNull GroupId groupId, @NonNull String name, @NonNull List members, @NonNull List destinations) @@ -138,7 +135,7 @@ public class LeaveGroupJob extends BaseJob { List addresses = Stream.of(destinations).map(Recipient::resolved).map(t -> RecipientUtil.toSignalServiceAddress(context, t)).toList(); List memberAddresses = Stream.of(members).map(Recipient::resolved).map(t -> RecipientUtil.toSignalServiceAddress(context, t)).toList(); List> unidentifiedAccess = Stream.of(destinations).map(Recipient::resolved).map(recipient -> UnidentifiedAccessUtil.getAccessFor(context, recipient)).toList(); - SignalServiceGroup serviceGroup = new SignalServiceGroup(SignalServiceGroup.Type.QUIT, groupId, name, memberAddresses, null); + SignalServiceGroup serviceGroup = new SignalServiceGroup(SignalServiceGroup.Type.QUIT, groupId.getDecodedId(), name, memberAddresses, null); SignalServiceDataMessage.Builder dataMessage = SignalServiceDataMessage.newBuilder() .withTimestamp(System.currentTimeMillis()) .asGroupMessage(serviceGroup); @@ -169,7 +166,7 @@ public class LeaveGroupJob extends BaseJob { public static class Factory implements Job.Factory { @Override public @NonNull LeaveGroupJob create(@NonNull Parameters parameters, @NonNull Data data) { - return new LeaveGroupJob(Base64.decodeOrThrow(data.getString(KEY_GROUP_ID)), + return new LeaveGroupJob(GroupId.v1(Base64.decodeOrThrow(data.getString(KEY_GROUP_ID))), data.getString(KEY_GROUP_NAME), RecipientId.fromSerializedList(data.getString(KEY_MEMBERS)), RecipientId.fromSerializedList(data.getString(KEY_RECIPIENTS)), diff --git a/app/src/main/java/org/thoughtcrime/securesms/jobs/MmsDownloadJob.java b/app/src/main/java/org/thoughtcrime/securesms/jobs/MmsDownloadJob.java index bb3bdf0fdd..5468db6daa 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/jobs/MmsDownloadJob.java +++ b/app/src/main/java/org/thoughtcrime/securesms/jobs/MmsDownloadJob.java @@ -1,6 +1,7 @@ package org.thoughtcrime.securesms.jobs; import android.net.Uri; + import androidx.annotation.NonNull; import androidx.annotation.Nullable; @@ -16,6 +17,7 @@ import org.thoughtcrime.securesms.database.AttachmentDatabase; import org.thoughtcrime.securesms.database.DatabaseFactory; import org.thoughtcrime.securesms.database.MessagingDatabase.InsertResult; import org.thoughtcrime.securesms.database.MmsDatabase; +import org.thoughtcrime.securesms.groups.GroupId; import org.thoughtcrime.securesms.jobmanager.Data; import org.thoughtcrime.securesms.jobmanager.Job; import org.thoughtcrime.securesms.logging.Log; @@ -177,11 +179,11 @@ public class MmsDownloadJob extends BaseJob { int subscriptionId, @Nullable RecipientId notificationFrom) throws MmsException { - MmsDatabase database = DatabaseFactory.getMmsDatabase(context); - Optional group = Optional.absent(); - Set members = new HashSet<>(); - String body = null; - List attachments = new LinkedList<>(); + MmsDatabase database = DatabaseFactory.getMmsDatabase(context); + Optional group = Optional.absent(); + Set members = new HashSet<>(); + String body = null; + List attachments = new LinkedList<>(); RecipientId from = null; diff --git a/app/src/main/java/org/thoughtcrime/securesms/jobs/MultiDeviceBlockedUpdateJob.java b/app/src/main/java/org/thoughtcrime/securesms/jobs/MultiDeviceBlockedUpdateJob.java index d848a647f7..cc70898eb1 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/jobs/MultiDeviceBlockedUpdateJob.java +++ b/app/src/main/java/org/thoughtcrime/securesms/jobs/MultiDeviceBlockedUpdateJob.java @@ -13,7 +13,6 @@ import org.thoughtcrime.securesms.jobmanager.impl.NetworkConstraint; import org.thoughtcrime.securesms.logging.Log; import org.thoughtcrime.securesms.recipients.Recipient; import org.thoughtcrime.securesms.recipients.RecipientUtil; -import org.thoughtcrime.securesms.util.GroupUtil; import org.thoughtcrime.securesms.util.TextSecurePreferences; import org.whispersystems.signalservice.api.SignalServiceMessageSender; import org.whispersystems.signalservice.api.crypto.UntrustedIdentityException; @@ -76,7 +75,7 @@ public class MultiDeviceBlockedUpdateJob extends BaseJob { while ((recipient = reader.getNext()) != null) { if (recipient.isPushGroup()) { - blockedGroups.add(GroupUtil.getDecodedId(recipient.requireGroupId())); + blockedGroups.add(recipient.requireGroupId().getDecodedId()); } else if (recipient.hasServiceIdentifier()) { blockedIndividuals.add(RecipientUtil.toSignalServiceAddress(context, recipient)); } diff --git a/app/src/main/java/org/thoughtcrime/securesms/jobs/MultiDeviceGroupUpdateJob.java b/app/src/main/java/org/thoughtcrime/securesms/jobs/MultiDeviceGroupUpdateJob.java index 3d292d69de..ca4405acc9 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/jobs/MultiDeviceGroupUpdateJob.java +++ b/app/src/main/java/org/thoughtcrime/securesms/jobs/MultiDeviceGroupUpdateJob.java @@ -14,7 +14,6 @@ import org.thoughtcrime.securesms.logging.Log; import org.thoughtcrime.securesms.recipients.Recipient; import org.thoughtcrime.securesms.recipients.RecipientId; import org.thoughtcrime.securesms.recipients.RecipientUtil; -import org.thoughtcrime.securesms.util.GroupUtil; import org.thoughtcrime.securesms.util.TextSecurePreferences; import org.whispersystems.libsignal.util.guava.Optional; import org.whispersystems.signalservice.api.SignalServiceMessageSender; @@ -92,13 +91,13 @@ public class MultiDeviceGroupUpdateJob extends BaseJob { members.add(RecipientUtil.toSignalServiceAddress(context, Recipient.resolved(member))); } - RecipientId recipientId = DatabaseFactory.getRecipientDatabase(context).getOrInsertFromGroupId(GroupUtil.getEncodedId(record.getId(), record.isMms())); + RecipientId recipientId = DatabaseFactory.getRecipientDatabase(context).getOrInsertFromGroupId(record.getId()); Recipient recipient = Recipient.resolved(recipientId); Optional expirationTimer = recipient.getExpireMessages() > 0 ? Optional.of(recipient.getExpireMessages()) : Optional.absent(); Map inboxPositions = DatabaseFactory.getThreadDatabase(context).getInboxPositions(); Set archived = DatabaseFactory.getThreadDatabase(context).getArchivedRecipients(); - out.write(new DeviceGroup(record.getId(), + out.write(new DeviceGroup(record.getId().getDecodedId(), Optional.fromNullable(record.getTitle()), members, getAvatar(record.getAvatar()), diff --git a/app/src/main/java/org/thoughtcrime/securesms/jobs/MultiDeviceMessageRequestResponseJob.java b/app/src/main/java/org/thoughtcrime/securesms/jobs/MultiDeviceMessageRequestResponseJob.java index f58b5dc7fa..1bbbf9d933 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/jobs/MultiDeviceMessageRequestResponseJob.java +++ b/app/src/main/java/org/thoughtcrime/securesms/jobs/MultiDeviceMessageRequestResponseJob.java @@ -8,18 +8,13 @@ import org.thoughtcrime.securesms.dependencies.ApplicationDependencies; import org.thoughtcrime.securesms.jobmanager.Data; import org.thoughtcrime.securesms.jobmanager.Job; import org.thoughtcrime.securesms.jobmanager.impl.NetworkConstraint; -import org.thoughtcrime.securesms.keyvalue.SignalStore; import org.thoughtcrime.securesms.logging.Log; import org.thoughtcrime.securesms.recipients.Recipient; import org.thoughtcrime.securesms.recipients.RecipientId; import org.thoughtcrime.securesms.recipients.RecipientUtil; -import org.thoughtcrime.securesms.util.GroupUtil; import org.thoughtcrime.securesms.util.TextSecurePreferences; -import org.whispersystems.libsignal.util.guava.Optional; import org.whispersystems.signalservice.api.SignalServiceMessageSender; import org.whispersystems.signalservice.api.crypto.UntrustedIdentityException; -import org.whispersystems.signalservice.api.kbs.MasterKey; -import org.whispersystems.signalservice.api.messages.multidevice.KeysMessage; import org.whispersystems.signalservice.api.messages.multidevice.MessageRequestResponseMessage; import org.whispersystems.signalservice.api.messages.multidevice.SignalServiceSyncMessage; import org.whispersystems.signalservice.api.push.exceptions.PushNetworkException; @@ -100,7 +95,7 @@ public class MultiDeviceMessageRequestResponseJob extends BaseJob { MessageRequestResponseMessage response; if (recipient.isGroup()) { - response = MessageRequestResponseMessage.forGroup(GroupUtil.getDecodedId(recipient.getGroupId().get()), localToRemoteType(type)); + response = MessageRequestResponseMessage.forGroup(recipient.getGroupId().get().getDecodedId(), localToRemoteType(type)); } else { response = MessageRequestResponseMessage.forIndividual(RecipientUtil.toSignalServiceAddress(context, recipient), localToRemoteType(type)); } diff --git a/app/src/main/java/org/thoughtcrime/securesms/jobs/PushDecryptMessageJob.java b/app/src/main/java/org/thoughtcrime/securesms/jobs/PushDecryptMessageJob.java index 4b7bbf72c4..670a87c705 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/jobs/PushDecryptMessageJob.java +++ b/app/src/main/java/org/thoughtcrime/securesms/jobs/PushDecryptMessageJob.java @@ -29,13 +29,13 @@ import org.thoughtcrime.securesms.database.DatabaseFactory; import org.thoughtcrime.securesms.database.NoSuchMessageException; import org.thoughtcrime.securesms.database.PushDatabase; import org.thoughtcrime.securesms.dependencies.ApplicationDependencies; +import org.thoughtcrime.securesms.groups.GroupId; import org.thoughtcrime.securesms.jobmanager.Data; import org.thoughtcrime.securesms.jobmanager.Job; import org.thoughtcrime.securesms.jobmanager.JobManager; import org.thoughtcrime.securesms.logging.Log; import org.thoughtcrime.securesms.notifications.NotificationChannels; import org.thoughtcrime.securesms.transport.RetryLaterException; -import org.thoughtcrime.securesms.util.GroupUtil; import org.thoughtcrime.securesms.util.TextSecurePreferences; import org.whispersystems.libsignal.state.SignalProtocolStore; import org.whispersystems.libsignal.util.guava.Optional; @@ -233,7 +233,7 @@ public final class PushDecryptMessageJob extends BaseJob { return new PushProcessMessageJob.ExceptionMetadata(sender, e.getSenderDevice(), - e.getGroup().transform(g -> GroupUtil.getEncodedId(g.getGroupId(), false)).orNull()); + e.getGroup().transform(g -> GroupId.v1(g.getGroupId())).orNull()); } private static PushProcessMessageJob.ExceptionMetadata toExceptionMetadata(@NonNull ProtocolException e) throws NoSenderException { diff --git a/app/src/main/java/org/thoughtcrime/securesms/jobs/PushGroupSendJob.java b/app/src/main/java/org/thoughtcrime/securesms/jobs/PushGroupSendJob.java index e58e3538d3..833659ac52 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/jobs/PushGroupSendJob.java +++ b/app/src/main/java/org/thoughtcrime/securesms/jobs/PushGroupSendJob.java @@ -1,6 +1,7 @@ package org.thoughtcrime.securesms.jobs; import android.content.Context; + import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.annotation.WorkerThread; @@ -18,6 +19,7 @@ import org.thoughtcrime.securesms.database.NoSuchMessageException; import org.thoughtcrime.securesms.database.documents.IdentityKeyMismatch; import org.thoughtcrime.securesms.database.documents.NetworkFailure; import org.thoughtcrime.securesms.dependencies.ApplicationDependencies; +import org.thoughtcrime.securesms.groups.GroupId; import org.thoughtcrime.securesms.jobmanager.Data; import org.thoughtcrime.securesms.jobmanager.Job; import org.thoughtcrime.securesms.jobmanager.JobManager; @@ -31,8 +33,6 @@ import org.thoughtcrime.securesms.recipients.RecipientId; import org.thoughtcrime.securesms.recipients.RecipientUtil; import org.thoughtcrime.securesms.transport.RetryLaterException; import org.thoughtcrime.securesms.transport.UndeliverableMessageException; -import org.thoughtcrime.securesms.util.FeatureFlags; -import org.thoughtcrime.securesms.util.GroupUtil; import org.whispersystems.libsignal.util.guava.Optional; import org.whispersystems.signalservice.api.SignalServiceMessageSender; import org.whispersystems.signalservice.api.crypto.UnidentifiedAccessPair; @@ -242,7 +242,7 @@ public class PushGroupSendJob extends PushSendJob { rotateSenderCertificateIfNecessary(); SignalServiceMessageSender messageSender = ApplicationDependencies.getSignalServiceMessageSender(); - String groupId = groupRecipient.requireGroupId(); + GroupId groupId = groupRecipient.requireGroupId(); Optional profileKey = getProfileKey(groupRecipient); Optional quote = getQuoteFor(message); Optional sticker = getStickerFor(message); @@ -266,7 +266,7 @@ public class PushGroupSendJob extends PushSendJob { List members = Stream.of(groupContext.getMembersList()) .map(m -> new SignalServiceAddress(UuidUtil.parseOrNull(m.getUuid()), m.getE164())) .toList(); - SignalServiceGroup group = new SignalServiceGroup(type, GroupUtil.getDecodedId(groupId), groupContext.getName(), members, avatar); + SignalServiceGroup group = new SignalServiceGroup(type, groupId.getDecodedId(), groupContext.getName(), members, avatar); SignalServiceDataMessage groupDataMessage = SignalServiceDataMessage.newBuilder() .withTimestamp(message.getSentTimeMillis()) .withExpiration(groupRecipient.getExpireMessages()) @@ -275,7 +275,7 @@ public class PushGroupSendJob extends PushSendJob { return messageSender.sendMessage(addresses, unidentifiedAccess, isRecipientUpdate, groupDataMessage); } else { - SignalServiceGroup group = new SignalServiceGroup(GroupUtil.getDecodedId(groupId)); + SignalServiceGroup group = new SignalServiceGroup(groupId.getDecodedId()); SignalServiceDataMessage groupMessage = SignalServiceDataMessage.newBuilder() .withTimestamp(message.getSentTimeMillis()) .asGroupMessage(group) @@ -295,7 +295,7 @@ public class PushGroupSendJob extends PushSendJob { } } - private @NonNull List getGroupMessageRecipients(String groupId, long messageId) { + private @NonNull List getGroupMessageRecipients(@NonNull GroupId groupId, long messageId) { List destinations = DatabaseFactory.getGroupReceiptDatabase(context).getGroupReceiptInfo(messageId); if (!destinations.isEmpty()) return Stream.of(destinations).map(GroupReceiptInfo::getRecipientId).toList(); diff --git a/app/src/main/java/org/thoughtcrime/securesms/jobs/PushGroupUpdateJob.java b/app/src/main/java/org/thoughtcrime/securesms/jobs/PushGroupUpdateJob.java index 949eea3891..54cb01b52f 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/jobs/PushGroupUpdateJob.java +++ b/app/src/main/java/org/thoughtcrime/securesms/jobs/PushGroupUpdateJob.java @@ -8,6 +8,7 @@ import org.thoughtcrime.securesms.database.DatabaseFactory; import org.thoughtcrime.securesms.database.GroupDatabase; import org.thoughtcrime.securesms.database.GroupDatabase.GroupRecord; import org.thoughtcrime.securesms.dependencies.ApplicationDependencies; +import org.thoughtcrime.securesms.groups.GroupId; import org.thoughtcrime.securesms.jobmanager.Data; import org.thoughtcrime.securesms.jobmanager.Job; import org.thoughtcrime.securesms.jobmanager.impl.NetworkConstraint; @@ -15,7 +16,6 @@ import org.thoughtcrime.securesms.logging.Log; import org.thoughtcrime.securesms.recipients.Recipient; import org.thoughtcrime.securesms.recipients.RecipientId; import org.thoughtcrime.securesms.recipients.RecipientUtil; -import org.thoughtcrime.securesms.util.GroupUtil; import org.whispersystems.libsignal.util.guava.Optional; import org.whispersystems.signalservice.api.SignalServiceMessageSender; import org.whispersystems.signalservice.api.crypto.UntrustedIdentityException; @@ -26,8 +26,6 @@ import org.whispersystems.signalservice.api.messages.SignalServiceGroup; import org.whispersystems.signalservice.api.messages.SignalServiceGroup.Type; import org.whispersystems.signalservice.api.push.SignalServiceAddress; import org.whispersystems.signalservice.api.push.exceptions.PushNetworkException; -import org.whispersystems.signalservice.internal.push.SignalServiceProtos; -import org.whispersystems.signalservice.internal.push.SignalServiceProtos.GroupContext; import java.io.ByteArrayInputStream; import java.io.IOException; @@ -44,10 +42,10 @@ public class PushGroupUpdateJob extends BaseJob { private static final String KEY_SOURCE = "source"; private static final String KEY_GROUP_ID = "group_id"; - private RecipientId source; - private byte[] groupId; + private final RecipientId source; + private final GroupId groupId; - public PushGroupUpdateJob(@NonNull RecipientId source, byte[] groupId) { + public PushGroupUpdateJob(@NonNull RecipientId source, @NonNull GroupId groupId) { this(new Job.Parameters.Builder() .addConstraint(NetworkConstraint.KEY) .setLifespan(TimeUnit.DAYS.toMillis(1)) @@ -57,7 +55,7 @@ public class PushGroupUpdateJob extends BaseJob { groupId); } - private PushGroupUpdateJob(@NonNull Job.Parameters parameters, RecipientId source, byte[] groupId) { + private PushGroupUpdateJob(@NonNull Job.Parameters parameters, RecipientId source, @NonNull GroupId groupId) { super(parameters); this.source = source; @@ -67,7 +65,7 @@ public class PushGroupUpdateJob extends BaseJob { @Override public @NonNull Data serialize() { return new Data.Builder().putString(KEY_SOURCE, source.serialize()) - .putString(KEY_GROUP_ID, GroupUtil.getEncodedId(groupId, false)) + .putString(KEY_GROUP_ID, groupId.toString()) .build(); } @@ -79,11 +77,11 @@ public class PushGroupUpdateJob extends BaseJob { @Override public void onRun() throws IOException, UntrustedIdentityException { GroupDatabase groupDatabase = DatabaseFactory.getGroupDatabase(context); - Optional record = groupDatabase.getGroup(GroupUtil.getEncodedId(groupId, false)); + Optional record = groupDatabase.getGroup(groupId); SignalServiceAttachment avatar = null; if (record == null) { - Log.w(TAG, "No information for group record info request: " + new String(groupId)); + Log.w(TAG, "No information for group record info request: " + groupId.toString()); return; } @@ -104,12 +102,12 @@ public class PushGroupUpdateJob extends BaseJob { SignalServiceGroup groupContext = SignalServiceGroup.newBuilder(Type.UPDATE) .withAvatar(avatar) - .withId(groupId) + .withId(groupId.getDecodedId()) .withMembers(members) .withName(record.get().getTitle()) .build(); - RecipientId groupRecipientId = DatabaseFactory.getRecipientDatabase(context).getOrInsertFromGroupId(GroupUtil.getEncodedId(groupId, false)); + RecipientId groupRecipientId = DatabaseFactory.getRecipientDatabase(context).getOrInsertFromGroupId(groupId); Recipient groupRecipient = Recipient.resolved(groupRecipientId); SignalServiceDataMessage message = SignalServiceDataMessage.newBuilder() @@ -139,13 +137,9 @@ public class PushGroupUpdateJob extends BaseJob { public static final class Factory implements Job.Factory { @Override public @NonNull PushGroupUpdateJob create(@NonNull Parameters parameters, @NonNull org.thoughtcrime.securesms.jobmanager.Data data) { - try { - return new PushGroupUpdateJob(parameters, - RecipientId.from(data.getString(KEY_SOURCE)), - GroupUtil.getDecodedId(data.getString(KEY_GROUP_ID))); - } catch (IOException e) { - throw new AssertionError(e); - } + return new PushGroupUpdateJob(parameters, + RecipientId.from(data.getString(KEY_SOURCE)), + GroupId.parse(data.getString(KEY_GROUP_ID))); } } } 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 84c240b175..26be5a3bbd 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/jobs/PushProcessMessageJob.java +++ b/app/src/main/java/org/thoughtcrime/securesms/jobs/PushProcessMessageJob.java @@ -43,6 +43,7 @@ import org.thoughtcrime.securesms.database.model.MmsMessageRecord; import org.thoughtcrime.securesms.database.model.ReactionRecord; import org.thoughtcrime.securesms.database.model.StickerRecord; import org.thoughtcrime.securesms.dependencies.ApplicationDependencies; +import org.thoughtcrime.securesms.groups.GroupId; import org.thoughtcrime.securesms.groups.GroupMessageProcessor; import org.thoughtcrime.securesms.jobmanager.Data; import org.thoughtcrime.securesms.jobmanager.Job; @@ -62,9 +63,9 @@ import org.thoughtcrime.securesms.mms.StickerSlide; import org.thoughtcrime.securesms.notifications.MessageNotifier; import org.thoughtcrime.securesms.recipients.Recipient; import org.thoughtcrime.securesms.recipients.RecipientId; -import org.thoughtcrime.securesms.service.WebRtcCallService; import org.thoughtcrime.securesms.ringrtc.IceCandidateParcel; import org.thoughtcrime.securesms.ringrtc.RemotePeer; +import org.thoughtcrime.securesms.service.WebRtcCallService; import org.thoughtcrime.securesms.sms.IncomingEncryptedMessage; import org.thoughtcrime.securesms.sms.IncomingEndSessionMessage; import org.thoughtcrime.securesms.sms.IncomingTextMessage; @@ -74,7 +75,6 @@ import org.thoughtcrime.securesms.sms.OutgoingTextMessage; import org.thoughtcrime.securesms.stickers.StickerLocator; import org.thoughtcrime.securesms.storage.StorageSyncHelper; import org.thoughtcrime.securesms.util.Base64; -import org.thoughtcrime.securesms.util.GroupUtil; import org.thoughtcrime.securesms.util.Hex; import org.thoughtcrime.securesms.util.IdentityUtil; import org.thoughtcrime.securesms.util.MediaUtil; @@ -217,7 +217,7 @@ public final class PushProcessMessageJob extends BaseJob { //noinspection ConstantConditions dataBuilder.putString(KEY_EXCEPTION_SENDER, exceptionMetadata.sender) .putInt(KEY_EXCEPTION_DEVICE, exceptionMetadata.senderDevice) - .putString(KEY_EXCEPTION_GROUP_ID, exceptionMetadata.groupId); + .putString(KEY_EXCEPTION_GROUP_ID, exceptionMetadata.groupId == null ? null : exceptionMetadata.groupId.toString()); } return dataBuilder.build(); @@ -272,7 +272,7 @@ public final class PushProcessMessageJob extends BaseJob { else if (isMediaMessage) handleMediaMessage(content, message, smsMessageId); else if (message.getBody().isPresent()) handleTextMessage(content, message, smsMessageId); - if (message.getGroupInfo().isPresent() && groupDatabase.isUnknownGroup(GroupUtil.getEncodedId(message.getGroupInfo().get().getGroupId(), false))) { + if (message.getGroupInfo().isPresent() && groupDatabase.isUnknownGroup(GroupId.v1(message.getGroupInfo().get().getGroupId()))) { handleUnknownGroupMessage(content, message.getGroupInfo().get()); } @@ -327,8 +327,8 @@ public final class PushProcessMessageJob extends BaseJob { } } - private static @NonNull Optional toEncodedId(@NonNull Optional groupInfo) { - return groupInfo.transform(g -> GroupUtil.getEncodedId(g.getGroupId(), false)); + private static @NonNull Optional toEncodedId(@NonNull Optional groupInfo) { + return groupInfo.transform(g -> GroupId.v1(g.getGroupId())); } private void handleExceptionMessage(@NonNull ExceptionMetadata e, @NonNull Optional smsMessageId) { @@ -546,7 +546,7 @@ public final class PushProcessMessageJob extends BaseJob { @NonNull SignalServiceGroup group) { if (group.getType() != SignalServiceGroup.Type.REQUEST_INFO) { - ApplicationDependencies.getJobManager().add(new RequestGroupInfoJob(Recipient.externalPush(context, content.getSender()).getId(), group.getGroupId())); + ApplicationDependencies.getJobManager().add(new RequestGroupInfoJob(Recipient.externalPush(context, content.getSender()).getId(), GroupId.v1(group.getGroupId()))); } else { Log.w(TAG, "Received a REQUEST_INFO message for a group we don't know about. Ignoring."); } @@ -682,7 +682,7 @@ public final class PushProcessMessageJob extends BaseJob { if (response.getPerson().isPresent()) { recipient = Recipient.externalPush(context, response.getPerson().get()); } else if (response.getGroupId().isPresent()) { - String groupId = GroupUtil.getEncodedId(response.getGroupId().get(), false); + GroupId groupId = GroupId.v1(response.getGroupId().get()); recipient = Recipient.externalGroup(context, groupId); } else { Log.w(TAG, "Message request response was missing a thread recipient! Skipping."); @@ -743,7 +743,7 @@ public final class PushProcessMessageJob extends BaseJob { threadId = handleSynchronizeSentTextMessage(message); } - if (message.getMessage().getGroupInfo().isPresent() && groupDatabase.isUnknownGroup(GroupUtil.getEncodedId(message.getMessage().getGroupInfo().get().getGroupId(), false))) { + if (message.getMessage().getGroupInfo().isPresent() && groupDatabase.isUnknownGroup(GroupId.v1(message.getMessage().getGroupInfo().get().getGroupId()))) { handleUnknownGroupMessage(content, message.getMessage().getGroupInfo().get()); } @@ -1017,7 +1017,7 @@ public final class PushProcessMessageJob extends BaseJob { updateGroupReceiptStatus(message, record.getId(), recipient.requireGroupId()); } - private void updateGroupReceiptStatus(@NonNull SentTranscriptMessage message, long messageId, @NonNull String groupString) { + private void updateGroupReceiptStatus(@NonNull SentTranscriptMessage message, long messageId, @NonNull GroupId groupString) { GroupReceiptDatabase receiptDatabase = DatabaseFactory.getGroupReceiptDatabase(context); List messageRecipients = Stream.of(message.getRecipients()).map(address -> Recipient.externalPush(context, address)).toList(); List members = DatabaseFactory.getGroupDatabase(context).getGroupMembers(groupString, false); @@ -1183,7 +1183,7 @@ public final class PushProcessMessageJob extends BaseJob { private void handleUnsupportedDataMessage(@NonNull String sender, int senderDevice, - @NonNull Optional groupId, + @NonNull Optional groupId, long timestamp, @NonNull Optional smsMessageId) { @@ -1203,7 +1203,7 @@ public final class PushProcessMessageJob extends BaseJob { private void handleInvalidMessage(@NonNull SignalServiceAddress sender, int senderDevice, - @NonNull Optional groupId, + @NonNull Optional groupId, long timestamp, @NonNull Optional smsMessageId) { @@ -1313,7 +1313,7 @@ public final class PushProcessMessageJob extends BaseJob { long threadId; if (typingMessage.getGroupId().isPresent()) { - RecipientId recipientId = DatabaseFactory.getRecipientDatabase(context).getOrInsertFromGroupId(GroupUtil.getEncodedId(typingMessage.getGroupId().get(), false)); + RecipientId recipientId = DatabaseFactory.getRecipientDatabase(context).getOrInsertFromGroupId(GroupId.v1(typingMessage.getGroupId().get())); Recipient groupRecipient = Recipient.resolved(recipientId); threadId = DatabaseFactory.getThreadDatabase(context).getThreadIdFor(groupRecipient); @@ -1478,7 +1478,7 @@ public final class PushProcessMessageJob extends BaseJob { return insertPlaceholder(sender, senderDevice, timestamp, Optional.absent()); } - private Optional insertPlaceholder(@NonNull String sender, int senderDevice, long timestamp, Optional groupId) { + private Optional insertPlaceholder(@NonNull String sender, int senderDevice, long timestamp, Optional groupId) { SmsDatabase database = DatabaseFactory.getSmsDatabase(context); IncomingTextMessage textMessage = new IncomingTextMessage(Recipient.external(context, sender).getId(), senderDevice, timestamp, "", @@ -1490,7 +1490,7 @@ public final class PushProcessMessageJob extends BaseJob { private Recipient getSyncMessageDestination(SentTranscriptMessage message) { if (message.getMessage().getGroupInfo().isPresent()) { - return Recipient.external(context, GroupUtil.getEncodedId(message.getMessage().getGroupInfo().get().getGroupId(), false)); + return Recipient.externalGroup(context, GroupId.v1(message.getMessage().getGroupInfo().get().getGroupId())); } else { return Recipient.externalPush(context, message.getDestination().get()); } @@ -1498,7 +1498,7 @@ public final class PushProcessMessageJob extends BaseJob { private Recipient getMessageDestination(SignalServiceContent content, SignalServiceDataMessage message) { if (message.getGroupInfo().isPresent()) { - return Recipient.external(context, GroupUtil.getEncodedId(message.getGroupInfo().get().getGroupId(), false)); + return Recipient.externalGroup(context, GroupId.v1(message.getGroupInfo().get().getGroupId())); } else { return Recipient.externalPush(context, content.getSender()); } @@ -1529,9 +1529,9 @@ public final class PushProcessMessageJob extends BaseJob { if (conversation.isGroup() && conversation.isBlocked()) { return true; } else if (conversation.isGroup()) { - GroupDatabase groupDatabase = DatabaseFactory.getGroupDatabase(context); - Optional groupId = message.getGroupInfo().isPresent() ? Optional.of(GroupUtil.getEncodedId(message.getGroupInfo().get().getGroupId(), false)) - : Optional.absent(); + GroupDatabase groupDatabase = DatabaseFactory.getGroupDatabase(context); + Optional groupId = message.getGroupInfo().isPresent() ? Optional.of(GroupId.v1(message.getGroupInfo().get().getGroupId())) + : Optional.absent(); if (groupId.isPresent() && groupDatabase.isUnknownGroup(groupId.get())) { return false; @@ -1616,7 +1616,7 @@ public final class PushProcessMessageJob extends BaseJob { } else { ExceptionMetadata exceptionMetadata = new ExceptionMetadata(data.getString(KEY_EXCEPTION_SENDER), data.getInt(KEY_EXCEPTION_DEVICE), - data.getStringOrDefault(KEY_EXCEPTION_GROUP_ID, null)); + GroupId.parseNullable(data.getStringOrDefault(KEY_EXCEPTION_GROUP_ID, null))); return new PushProcessMessageJob(parameters, state, @@ -1643,11 +1643,11 @@ public final class PushProcessMessageJob extends BaseJob { } static class ExceptionMetadata { - @NonNull private final String sender; - private final int senderDevice; - @Nullable private final String groupId; + @NonNull private final String sender; + private final int senderDevice; + @Nullable private final GroupId groupId; - ExceptionMetadata(@NonNull String sender, int senderDevice, @Nullable String groupId) { + ExceptionMetadata(@NonNull String sender, int senderDevice, @Nullable GroupId groupId) { this.sender = sender; this.senderDevice = senderDevice; this.groupId = groupId; diff --git a/app/src/main/java/org/thoughtcrime/securesms/jobs/ReactionSendJob.java b/app/src/main/java/org/thoughtcrime/securesms/jobs/ReactionSendJob.java index ca9a084990..51ff0af189 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/jobs/ReactionSendJob.java +++ b/app/src/main/java/org/thoughtcrime/securesms/jobs/ReactionSendJob.java @@ -21,7 +21,6 @@ import org.thoughtcrime.securesms.recipients.Recipient; import org.thoughtcrime.securesms.recipients.RecipientId; import org.thoughtcrime.securesms.recipients.RecipientUtil; import org.thoughtcrime.securesms.transport.RetryLaterException; -import org.thoughtcrime.securesms.util.GroupUtil; import org.whispersystems.libsignal.util.guava.Optional; import org.whispersystems.signalservice.api.SignalServiceMessageSender; import org.whispersystems.signalservice.api.crypto.UnidentifiedAccessPair; @@ -217,7 +216,7 @@ public class ReactionSendJob extends BaseJob { .withReaction(buildReaction(context, reaction, remove, targetAuthor, targetSentTimestamp)); if (conversationRecipient.isGroup()) { - dataMessage.asGroupMessage(new SignalServiceGroup(GroupUtil.getDecodedId(conversationRecipient.requireGroupId()))); + dataMessage.asGroupMessage(new SignalServiceGroup(conversationRecipient.requireGroupId().getDecodedId())); } diff --git a/app/src/main/java/org/thoughtcrime/securesms/jobs/RequestGroupInfoJob.java b/app/src/main/java/org/thoughtcrime/securesms/jobs/RequestGroupInfoJob.java index 0df50a2565..81cf689683 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/jobs/RequestGroupInfoJob.java +++ b/app/src/main/java/org/thoughtcrime/securesms/jobs/RequestGroupInfoJob.java @@ -4,20 +4,18 @@ import androidx.annotation.NonNull; import org.thoughtcrime.securesms.crypto.UnidentifiedAccessUtil; import org.thoughtcrime.securesms.dependencies.ApplicationDependencies; +import org.thoughtcrime.securesms.groups.GroupId; import org.thoughtcrime.securesms.jobmanager.Data; import org.thoughtcrime.securesms.jobmanager.Job; import org.thoughtcrime.securesms.jobmanager.impl.NetworkConstraint; +import org.thoughtcrime.securesms.recipients.Recipient; import org.thoughtcrime.securesms.recipients.RecipientId; import org.thoughtcrime.securesms.recipients.RecipientUtil; -import org.thoughtcrime.securesms.util.GroupUtil; -import org.thoughtcrime.securesms.recipients.Recipient; -import org.whispersystems.libsignal.util.guava.Optional; import org.whispersystems.signalservice.api.SignalServiceMessageSender; import org.whispersystems.signalservice.api.crypto.UntrustedIdentityException; import org.whispersystems.signalservice.api.messages.SignalServiceDataMessage; import org.whispersystems.signalservice.api.messages.SignalServiceGroup; import org.whispersystems.signalservice.api.messages.SignalServiceGroup.Type; -import org.whispersystems.signalservice.api.push.SignalServiceAddress; import org.whispersystems.signalservice.api.push.exceptions.PushNetworkException; import java.io.IOException; @@ -33,10 +31,10 @@ public class RequestGroupInfoJob extends BaseJob { private static final String KEY_SOURCE = "source"; private static final String KEY_GROUP_ID = "group_id"; - private RecipientId source; - private byte[] groupId; + private final RecipientId source; + private final GroupId groupId; - public RequestGroupInfoJob(@NonNull RecipientId source, @NonNull byte[] groupId) { + public RequestGroupInfoJob(@NonNull RecipientId source, @NonNull GroupId groupId) { this(new Job.Parameters.Builder() .addConstraint(NetworkConstraint.KEY) .setLifespan(TimeUnit.DAYS.toMillis(1)) @@ -47,7 +45,7 @@ public class RequestGroupInfoJob extends BaseJob { } - private RequestGroupInfoJob(@NonNull Job.Parameters parameters, @NonNull RecipientId source, @NonNull byte[] groupId) { + private RequestGroupInfoJob(@NonNull Job.Parameters parameters, @NonNull RecipientId source, @NonNull GroupId groupId) { super(parameters); this.source = source; @@ -57,7 +55,7 @@ public class RequestGroupInfoJob extends BaseJob { @Override public @NonNull Data serialize() { return new Data.Builder().putString(KEY_SOURCE, source.serialize()) - .putString(KEY_GROUP_ID, GroupUtil.getEncodedId(groupId, false)) + .putString(KEY_GROUP_ID, groupId.toString()) .build(); } @@ -69,7 +67,7 @@ public class RequestGroupInfoJob extends BaseJob { @Override public void onRun() throws IOException, UntrustedIdentityException { SignalServiceGroup group = SignalServiceGroup.newBuilder(Type.REQUEST_INFO) - .withId(groupId) + .withId(groupId.getDecodedId()) .build(); SignalServiceDataMessage message = SignalServiceDataMessage.newBuilder() @@ -99,13 +97,9 @@ public class RequestGroupInfoJob extends BaseJob { @Override public @NonNull RequestGroupInfoJob create(@NonNull Parameters parameters, @NonNull Data data) { - try { - return new RequestGroupInfoJob(parameters, - RecipientId.from(data.getString(KEY_SOURCE)), - GroupUtil.getDecodedId(data.getString(KEY_GROUP_ID))); - } catch (IOException e) { - throw new AssertionError(e); - } + return new RequestGroupInfoJob(parameters, + RecipientId.from(data.getString(KEY_SOURCE)), + GroupId.parse(data.getString(KEY_GROUP_ID))); } } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/jobs/TypingSendJob.java b/app/src/main/java/org/thoughtcrime/securesms/jobs/TypingSendJob.java index 242270de30..fa3cd4b90b 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/jobs/TypingSendJob.java +++ b/app/src/main/java/org/thoughtcrime/securesms/jobs/TypingSendJob.java @@ -12,7 +12,6 @@ import org.thoughtcrime.securesms.jobmanager.Job; import org.thoughtcrime.securesms.logging.Log; import org.thoughtcrime.securesms.recipients.Recipient; import org.thoughtcrime.securesms.recipients.RecipientUtil; -import org.thoughtcrime.securesms.util.GroupUtil; import org.thoughtcrime.securesms.util.TextSecurePreferences; import org.whispersystems.libsignal.util.guava.Optional; import org.whispersystems.signalservice.api.SignalServiceMessageSender; @@ -87,7 +86,7 @@ public class TypingSendJob extends BaseJob { if (recipient.isGroup()) { recipients = DatabaseFactory.getGroupDatabase(context).getGroupMembers(recipient.requireGroupId(), false); - groupId = Optional.of(GroupUtil.getDecodedId(recipient.requireGroupId())); + groupId = Optional.of(recipient.requireGroupId().getDecodedId()); } recipients = Stream.of(recipients).map(Recipient::resolve).toList(); diff --git a/app/src/main/java/org/thoughtcrime/securesms/mediasend/CameraContactsRepository.java b/app/src/main/java/org/thoughtcrime/securesms/mediasend/CameraContactsRepository.java index 74db0b495a..e22bd89230 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/mediasend/CameraContactsRepository.java +++ b/app/src/main/java/org/thoughtcrime/securesms/mediasend/CameraContactsRepository.java @@ -101,7 +101,7 @@ class CameraContactsRepository { try (GroupDatabase.Reader reader = groupDatabase.getGroupsFilteredByTitle(query, false)) { GroupDatabase.GroupRecord groupRecord; while ((groupRecord = reader.getNext()) != null) { - RecipientId recipientId = recipientDatabase.getOrInsertFromGroupId(groupRecord.getEncodedId()); + RecipientId recipientId = recipientDatabase.getOrInsertFromGroupId(groupRecord.getId()); recipients.add(Recipient.resolved(recipientId)); } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/migrations/AvatarMigrationJob.java b/app/src/main/java/org/thoughtcrime/securesms/migrations/AvatarMigrationJob.java index 20088c8ebd..e1966f70a1 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/migrations/AvatarMigrationJob.java +++ b/app/src/main/java/org/thoughtcrime/securesms/migrations/AvatarMigrationJob.java @@ -2,14 +2,13 @@ package org.thoughtcrime.securesms.migrations; import androidx.annotation.NonNull; -import org.thoughtcrime.securesms.database.DatabaseFactory; +import org.thoughtcrime.securesms.groups.GroupId; import org.thoughtcrime.securesms.jobmanager.Data; import org.thoughtcrime.securesms.jobmanager.Job; import org.thoughtcrime.securesms.logging.Log; import org.thoughtcrime.securesms.phonenumbers.NumberUtil; import org.thoughtcrime.securesms.profiles.AvatarHelper; import org.thoughtcrime.securesms.recipients.Recipient; -import org.thoughtcrime.securesms.util.GroupUtil; import org.thoughtcrime.securesms.util.Util; import java.io.File; @@ -83,7 +82,7 @@ public class AvatarMigrationJob extends MigrationJob { } private static boolean isValidFileName(@NonNull String name) { - return NUMBER_PATTERN.matcher(name).matches() || GroupUtil.isEncodedGroup(name) || NumberUtil.isValidEmail(name); + return NUMBER_PATTERN.matcher(name).matches() || GroupId.isEncodedGroup(name) || NumberUtil.isValidEmail(name); } public static class Factory implements Job.Factory { diff --git a/app/src/main/java/org/thoughtcrime/securesms/mms/IncomingMediaMessage.java b/app/src/main/java/org/thoughtcrime/securesms/mms/IncomingMediaMessage.java index 307d436ec2..c7905f0622 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/mms/IncomingMediaMessage.java +++ b/app/src/main/java/org/thoughtcrime/securesms/mms/IncomingMediaMessage.java @@ -5,9 +5,9 @@ import androidx.annotation.NonNull; import org.thoughtcrime.securesms.attachments.Attachment; import org.thoughtcrime.securesms.attachments.PointerAttachment; import org.thoughtcrime.securesms.contactshare.Contact; +import org.thoughtcrime.securesms.groups.GroupId; import org.thoughtcrime.securesms.linkpreview.LinkPreview; import org.thoughtcrime.securesms.recipients.RecipientId; -import org.thoughtcrime.securesms.util.GroupUtil; import org.whispersystems.libsignal.util.guava.Optional; import org.whispersystems.signalservice.api.messages.SignalServiceAttachment; import org.whispersystems.signalservice.api.messages.SignalServiceGroup; @@ -19,7 +19,7 @@ import java.util.List; public class IncomingMediaMessage { private final RecipientId from; - private final String groupId; + private final GroupId groupId; private final String body; private final boolean push; private final long sentTimeMillis; @@ -35,7 +35,7 @@ public class IncomingMediaMessage { private final List linkPreviews = new LinkedList<>(); public IncomingMediaMessage(@NonNull RecipientId from, - Optional groupId, + Optional groupId, String body, long sentTimeMillis, List attachments, @@ -86,7 +86,7 @@ public class IncomingMediaMessage { this.quote = quote.orNull(); this.unidentified = unidentified; - if (group.isPresent()) this.groupId = GroupUtil.getEncodedId(group.get().getGroupId(), false); + if (group.isPresent()) this.groupId = GroupId.v1(group.get().getGroupId()); else this.groupId = null; this.attachments.addAll(PointerAttachment.forPointers(attachments)); @@ -114,7 +114,7 @@ public class IncomingMediaMessage { return from; } - public String getGroupId() { + public GroupId getGroupId() { return groupId; } diff --git a/app/src/main/java/org/thoughtcrime/securesms/phonenumbers/PhoneNumberFormatter.java b/app/src/main/java/org/thoughtcrime/securesms/phonenumbers/PhoneNumberFormatter.java index 37e8ceb105..d3929c3392 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/phonenumbers/PhoneNumberFormatter.java +++ b/app/src/main/java/org/thoughtcrime/securesms/phonenumbers/PhoneNumberFormatter.java @@ -11,8 +11,8 @@ import com.google.i18n.phonenumbers.PhoneNumberUtil; import com.google.i18n.phonenumbers.Phonenumber; import com.google.i18n.phonenumbers.ShortNumberInfo; +import org.thoughtcrime.securesms.groups.GroupId; import org.thoughtcrime.securesms.logging.Log; -import org.thoughtcrime.securesms.util.GroupUtil; import org.thoughtcrime.securesms.util.TextSecurePreferences; import org.thoughtcrime.securesms.util.Util; import org.whispersystems.libsignal.util.Pair; @@ -83,7 +83,7 @@ public class PhoneNumberFormatter { public String format(@Nullable String number) { if (number == null) return "Unknown"; - if (GroupUtil.isEncodedGroup(number)) return number; + if (GroupId.isEncodedGroup(number)) return number; if (ALPHA_PATTERN.matcher(number).find()) return number.trim(); String bareNumber = number.replaceAll("[^0-9+]", ""); 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 d4d9b319b7..8b584f81be 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/recipients/LiveRecipient.java +++ b/app/src/main/java/org/thoughtcrime/securesms/recipients/LiveRecipient.java @@ -19,7 +19,6 @@ import org.thoughtcrime.securesms.database.GroupDatabase.GroupRecord; import org.thoughtcrime.securesms.database.RecipientDatabase; import org.thoughtcrime.securesms.database.RecipientDatabase.RecipientSettings; import org.thoughtcrime.securesms.logging.Log; -import org.thoughtcrime.securesms.util.GroupUtil; import org.thoughtcrime.securesms.util.TextSecurePreferences; import org.thoughtcrime.securesms.util.Util; import org.whispersystems.libsignal.util.guava.Optional; @@ -198,7 +197,7 @@ public final class LiveRecipient { List members = Stream.of(groupRecord.get().getMembers()).filterNot(RecipientId::isUnknown).map(this::fetchRecipientFromDisk).toList(); Optional avatarId = Optional.absent(); - if (settings.getGroupId() != null && !GroupUtil.isMmsGroup(settings.getGroupId()) && title == null) { + if (settings.getGroupId() != null && !settings.getGroupId().isMmsGroup() && title == null) { title = unnamedGroupName; } 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 d3db7c6f91..6cbe999371 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/recipients/Recipient.java +++ b/app/src/main/java/org/thoughtcrime/securesms/recipients/Recipient.java @@ -28,6 +28,7 @@ import org.thoughtcrime.securesms.database.RecipientDatabase.RegisteredState; import org.thoughtcrime.securesms.database.RecipientDatabase.UnidentifiedAccessMode; import org.thoughtcrime.securesms.database.RecipientDatabase.VibrateState; import org.thoughtcrime.securesms.dependencies.ApplicationDependencies; +import org.thoughtcrime.securesms.groups.GroupId; import org.thoughtcrime.securesms.jobs.DirectoryRefreshJob; import org.thoughtcrime.securesms.logging.Log; import org.thoughtcrime.securesms.notifications.NotificationChannels; @@ -35,7 +36,6 @@ import org.thoughtcrime.securesms.phonenumbers.NumberUtil; import org.thoughtcrime.securesms.phonenumbers.PhoneNumberFormatter; import org.thoughtcrime.securesms.profiles.ProfileName; import org.thoughtcrime.securesms.util.FeatureFlags; -import org.thoughtcrime.securesms.util.GroupUtil; import org.thoughtcrime.securesms.util.Util; import org.whispersystems.libsignal.util.guava.Optional; import org.whispersystems.libsignal.util.guava.Preconditions; @@ -64,7 +64,7 @@ public class Recipient { private final String username; private final String e164; private final String email; - private final String groupId; + private final GroupId groupId; private final List participants; private final Optional groupAvatarId; private final boolean localNumber; @@ -236,11 +236,7 @@ public class Recipient { * identifier is a groupId. */ @WorkerThread - public static @NonNull Recipient externalGroup(@NonNull Context context, @NonNull String groupId) { - if (!GroupUtil.isEncodedGroup(groupId)) { - throw new IllegalArgumentException("Invalid groupId!"); - } - + public static @NonNull Recipient externalGroup(@NonNull Context context, @NonNull GroupId groupId) { return Recipient.resolved(DatabaseFactory.getRecipientDatabase(context).getOrInsertFromGroupId(groupId)); } @@ -274,8 +270,8 @@ public class Recipient { throw new UuidRecipientError(); } } - } else if (GroupUtil.isEncodedGroup(identifier)) { - id = db.getOrInsertFromGroupId(identifier); + } else if (GroupId.isEncodedGroup(identifier)) { + id = db.getOrInsertFromGroupId(GroupId.parse(identifier)); } else if (NumberUtil.isValidEmail(identifier)) { id = db.getOrInsertFromEmail(identifier); } else { @@ -385,7 +381,7 @@ public class Recipient { } public @Nullable String getName(@NonNull Context context) { - if (this.name == null && groupId != null && GroupUtil.isMmsGroup(groupId)) { + if (this.name == null && groupId != null && groupId.isMmsGroup()) { List names = new LinkedList<>(); for (Recipient recipient : participants) { @@ -443,7 +439,7 @@ public class Recipient { return Optional.fromNullable(email); } - public @NonNull Optional getGroupId() { + public @NonNull Optional getGroupId() { return Optional.fromNullable(groupId); } @@ -495,8 +491,8 @@ public class Recipient { return getUuid().isPresent(); } - public @NonNull String requireGroupId() { - String resolved = resolving ? resolve().groupId : groupId; + public @NonNull GroupId requireGroupId() { + GroupId resolved = resolving ? resolve().groupId : groupId; if (resolved == null) { throw new MissingAddressError(); @@ -532,7 +528,7 @@ public class Recipient { Recipient resolved = resolving ? resolve() : this; if (resolved.isGroup()) { - return resolved.requireGroupId(); + return resolved.requireGroupId().toString(); } else if (resolved.getUuid().isPresent()) { return resolved.getUuid().get().toString(); } @@ -570,13 +566,13 @@ public class Recipient { } public boolean isMmsGroup() { - String groupId = resolve().groupId; - return groupId != null && GroupUtil.isMmsGroup(groupId); + GroupId groupId = resolve().groupId; + return groupId != null && groupId.isMmsGroup(); } public boolean isPushGroup() { - String groupId = resolve().groupId; - return groupId != null && !GroupUtil.isMmsGroup(groupId); + GroupId groupId = resolve().groupId; + return groupId != null && !groupId.isMmsGroup(); } public @NonNull List getParticipants() { 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 5a659a2692..e3736a3eba 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/recipients/RecipientDetails.java +++ b/app/src/main/java/org/thoughtcrime/securesms/recipients/RecipientDetails.java @@ -13,6 +13,7 @@ import org.thoughtcrime.securesms.database.RecipientDatabase.RecipientSettings; import org.thoughtcrime.securesms.database.RecipientDatabase.RegisteredState; import org.thoughtcrime.securesms.database.RecipientDatabase.UnidentifiedAccessMode; import org.thoughtcrime.securesms.database.RecipientDatabase.VibrateState; +import org.thoughtcrime.securesms.groups.GroupId; import org.thoughtcrime.securesms.profiles.ProfileName; import org.thoughtcrime.securesms.util.TextSecurePreferences; import org.thoughtcrime.securesms.util.Util; @@ -28,7 +29,7 @@ public class RecipientDetails { final String username; final String e164; final String email; - final String groupId; + final GroupId groupId; final String name; final String customLabel; final Uri systemContactPhoto; diff --git a/app/src/main/java/org/thoughtcrime/securesms/recipients/RecipientUtil.java b/app/src/main/java/org/thoughtcrime/securesms/recipients/RecipientUtil.java index c5d89aef57..256523a7b5 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/recipients/RecipientUtil.java +++ b/app/src/main/java/org/thoughtcrime/securesms/recipients/RecipientUtil.java @@ -14,8 +14,9 @@ import org.thoughtcrime.securesms.contacts.sync.DirectoryHelper; import org.thoughtcrime.securesms.database.DatabaseFactory; import org.thoughtcrime.securesms.database.GroupDatabase; import org.thoughtcrime.securesms.database.RecipientDatabase.RegisteredState; -import org.thoughtcrime.securesms.dependencies.ApplicationDependencies; import org.thoughtcrime.securesms.database.ThreadDatabase; +import org.thoughtcrime.securesms.dependencies.ApplicationDependencies; +import org.thoughtcrime.securesms.groups.GroupId; import org.thoughtcrime.securesms.jobs.DirectoryRefreshJob; import org.thoughtcrime.securesms.jobs.LeaveGroupJob; import org.thoughtcrime.securesms.jobs.MultiDeviceBlockedUpdateJob; @@ -23,9 +24,9 @@ import org.thoughtcrime.securesms.jobs.MultiDeviceMessageRequestResponseJob; import org.thoughtcrime.securesms.jobs.RotateProfileKeyJob; import org.thoughtcrime.securesms.keyvalue.SignalStore; import org.thoughtcrime.securesms.logging.Log; +import org.thoughtcrime.securesms.mms.OutgoingGroupMediaMessage; import org.thoughtcrime.securesms.storage.StorageSyncHelper; import org.thoughtcrime.securesms.util.FeatureFlags; -import org.thoughtcrime.securesms.mms.OutgoingGroupMediaMessage; import org.thoughtcrime.securesms.util.GroupUtil; import org.whispersystems.libsignal.util.guava.Optional; import org.whispersystems.signalservice.api.push.SignalServiceAddress; @@ -123,7 +124,7 @@ public class RecipientUtil { ApplicationDependencies.getJobManager().add(LeaveGroupJob.create(recipient)); GroupDatabase groupDatabase = DatabaseFactory.getGroupDatabase(context); - String groupId = resolved.requireGroupId(); + GroupId groupId = resolved.requireGroupId(); groupDatabase.setActive(groupId, false); groupDatabase.remove(groupId, Recipient.self().getId()); } else { diff --git a/app/src/main/java/org/thoughtcrime/securesms/sms/IncomingTextMessage.java b/app/src/main/java/org/thoughtcrime/securesms/sms/IncomingTextMessage.java index 89e7f3db8c..ebf3d6c3f0 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/sms/IncomingTextMessage.java +++ b/app/src/main/java/org/thoughtcrime/securesms/sms/IncomingTextMessage.java @@ -7,6 +7,7 @@ import android.telephony.SmsMessage; import androidx.annotation.NonNull; import androidx.annotation.Nullable; +import org.thoughtcrime.securesms.groups.GroupId; import org.thoughtcrime.securesms.recipients.RecipientId; import org.whispersystems.libsignal.util.guava.Optional; import org.whispersystems.signalservice.api.push.SignalServiceAddress; @@ -28,19 +29,19 @@ public class IncomingTextMessage implements Parcelable { }; private static final String TAG = IncomingTextMessage.class.getSimpleName(); - private final String message; - private RecipientId sender; - private final int senderDeviceId; - private final int protocol; - private final String serviceCenterAddress; - private final boolean replyPathPresent; - private final String pseudoSubject; - private final long sentTimestampMillis; - private final String groupId; - private final boolean push; - private final int subscriptionId; - private final long expiresInMillis; - private final boolean unidentified; + private final String message; + private final RecipientId sender; + private final int senderDeviceId; + private final int protocol; + private final String serviceCenterAddress; + private final boolean replyPathPresent; + private final String pseudoSubject; + private final long sentTimestampMillis; + @Nullable private final GroupId groupId; + private final boolean push; + private final int subscriptionId; + private final long expiresInMillis; + private final boolean unidentified; public IncomingTextMessage(@NonNull RecipientId sender, @NonNull SmsMessage message, int subscriptionId) { this.message = message.getDisplayMessageBody(); @@ -59,7 +60,7 @@ public class IncomingTextMessage implements Parcelable { } public IncomingTextMessage(@NonNull RecipientId sender, int senderDeviceId, long sentTimestampMillis, - String encodedBody, Optional groupId, + String encodedBody, Optional groupId, long expiresInMillis, boolean unidentified) { this.message = encodedBody; @@ -86,7 +87,7 @@ public class IncomingTextMessage implements Parcelable { this.replyPathPresent = (in.readInt() == 1); this.pseudoSubject = in.readString(); this.sentTimestampMillis = in.readLong(); - this.groupId = in.readString(); + this.groupId = GroupId.parseNullable(in.readString()); this.push = (in.readInt() == 1); this.subscriptionId = in.readInt(); this.expiresInMillis = in.readLong(); @@ -131,7 +132,7 @@ public class IncomingTextMessage implements Parcelable { this.unidentified = fragments.get(0).isUnidentified(); } - protected IncomingTextMessage(@NonNull RecipientId sender, @Nullable String groupId) + protected IncomingTextMessage(@NonNull RecipientId sender, @Nullable GroupId groupId) { this.message = ""; this.sender = sender; @@ -216,7 +217,7 @@ public class IncomingTextMessage implements Parcelable { return push; } - public @Nullable String getGroupId() { + public @Nullable GroupId getGroupId() { return groupId; } @@ -259,7 +260,7 @@ public class IncomingTextMessage implements Parcelable { out.writeInt(replyPathPresent ? 1 : 0); out.writeString(pseudoSubject); out.writeLong(sentTimestampMillis); - out.writeString(groupId); + out.writeString(groupId == null ? null : groupId.toString()); out.writeInt(push ? 1 : 0); out.writeInt(subscriptionId); out.writeLong(expiresInMillis); diff --git a/app/src/main/java/org/thoughtcrime/securesms/storage/GroupV1ConflictMerger.java b/app/src/main/java/org/thoughtcrime/securesms/storage/GroupV1ConflictMerger.java index 079a48a67e..6a8e6e662a 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/storage/GroupV1ConflictMerger.java +++ b/app/src/main/java/org/thoughtcrime/securesms/storage/GroupV1ConflictMerger.java @@ -5,7 +5,7 @@ import androidx.annotation.NonNull; import com.annimon.stream.Collectors; import com.annimon.stream.Stream; -import org.thoughtcrime.securesms.util.GroupUtil; +import org.thoughtcrime.securesms.groups.GroupId; import org.whispersystems.libsignal.util.guava.Optional; import org.whispersystems.signalservice.api.storage.SignalGroupV1Record; @@ -15,15 +15,15 @@ import java.util.Map; class GroupV1ConflictMerger implements StorageSyncHelper.ConflictMerger { - private final Map localByGroupId; + private final Map localByGroupId; GroupV1ConflictMerger(@NonNull Collection localOnly) { - localByGroupId = Stream.of(localOnly).collect(Collectors.toMap(g -> GroupUtil.getEncodedId(g.getGroupId(), false), g -> g)); + localByGroupId = Stream.of(localOnly).collect(Collectors.toMap(g -> GroupId.v1(g.getGroupId()), g -> g)); } @Override public @NonNull Optional getMatching(@NonNull SignalGroupV1Record record) { - return Optional.fromNullable(localByGroupId.get(GroupUtil.getEncodedId(record.getGroupId(), false))); + return Optional.fromNullable(localByGroupId.get(GroupId.v1(record.getGroupId()))); } @Override 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 f53797bd41..4ea3a416a4 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/storage/StorageSyncModels.java +++ b/app/src/main/java/org/thoughtcrime/securesms/storage/StorageSyncModels.java @@ -5,7 +5,6 @@ import androidx.annotation.NonNull; import org.thoughtcrime.securesms.database.IdentityDatabase; import org.thoughtcrime.securesms.database.RecipientDatabase.RecipientSettings; import org.thoughtcrime.securesms.recipients.RecipientId; -import org.thoughtcrime.securesms.util.GroupUtil; import org.whispersystems.signalservice.api.push.SignalServiceAddress; import org.whispersystems.signalservice.api.storage.SignalContactRecord; import org.whispersystems.signalservice.api.storage.SignalGroupV1Record; @@ -56,7 +55,7 @@ public final class StorageSyncModels { throw new AssertionError("Must have a groupId!"); } - return new SignalGroupV1Record.Builder(rawStorageId, GroupUtil.getDecodedIdOrThrow(recipient.getGroupId())) + return new SignalGroupV1Record.Builder(rawStorageId, recipient.getGroupId().getDecodedId()) .setBlocked(recipient.isBlocked()) .setProfileSharingEnabled(recipient.isProfileSharing()) .setArchived(archived.contains(recipient.getId())) 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 3f0b2abcc2..7104086906 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/util/GroupUtil.java +++ b/app/src/main/java/org/thoughtcrime/securesms/util/GroupUtil.java @@ -11,6 +11,7 @@ import com.google.protobuf.ByteString; import org.thoughtcrime.securesms.R; import org.thoughtcrime.securesms.database.DatabaseFactory; import org.thoughtcrime.securesms.database.GroupDatabase; +import org.thoughtcrime.securesms.groups.GroupId; import org.thoughtcrime.securesms.logging.Log; import org.thoughtcrime.securesms.mms.OutgoingGroupMediaMessage; import org.thoughtcrime.securesms.recipients.Recipient; @@ -26,43 +27,16 @@ import java.util.List; import static org.whispersystems.signalservice.internal.push.SignalServiceProtos.GroupContext; -public class GroupUtil { +public final class GroupUtil { - private static final String ENCODED_SIGNAL_GROUP_PREFIX = "__textsecure_group__!"; - private static final String ENCODED_MMS_GROUP_PREFIX = "__signal_mms_group__!"; - private static final String TAG = GroupUtil.class.getSimpleName(); - - public static String getEncodedId(byte[] groupId, boolean mms) { - return (mms ? ENCODED_MMS_GROUP_PREFIX : ENCODED_SIGNAL_GROUP_PREFIX) + Hex.toStringCondensed(groupId); + private GroupUtil() { } - public static byte[] getDecodedId(String groupId) throws IOException { - if (!isEncodedGroup(groupId)) { - throw new IOException("Invalid encoding"); - } - - return Hex.fromStringCondensed(groupId.split("!", 2)[1]); - } - - public static byte[] getDecodedIdOrThrow(String groupId) { - try { - return getDecodedId(groupId); - } catch (IOException e) { - throw new AssertionError(e); - } - } - - public static boolean isEncodedGroup(@NonNull String groupId) { - return groupId.startsWith(ENCODED_SIGNAL_GROUP_PREFIX) || groupId.startsWith(ENCODED_MMS_GROUP_PREFIX); - } - - public static boolean isMmsGroup(@NonNull String groupId) { - return groupId.startsWith(ENCODED_MMS_GROUP_PREFIX); - } + private static final String TAG = Log.tag(GroupUtil.class); @WorkerThread public static Optional createGroupLeaveMessage(@NonNull Context context, @NonNull Recipient groupRecipient) { - String encodedGroupId = groupRecipient.requireGroupId(); + GroupId encodedGroupId = groupRecipient.requireGroupId(); GroupDatabase groupDatabase = DatabaseFactory.getGroupDatabase(context); if (!groupDatabase.isActive(encodedGroupId)) { @@ -70,13 +44,7 @@ public class GroupUtil { return Optional.absent(); } - ByteString decodedGroupId; - try { - decodedGroupId = ByteString.copyFrom(getDecodedId(encodedGroupId)); - } catch (IOException e) { - Log.w(TAG, "Failed to decode group ID.", e); - return Optional.absent(); - } + ByteString decodedGroupId = ByteString.copyFrom(encodedGroupId.getDecodedId()); GroupContext groupContext = GroupContext.newBuilder() .setId(decodedGroupId) diff --git a/app/src/main/java/org/thoughtcrime/securesms/util/IdentityUtil.java b/app/src/main/java/org/thoughtcrime/securesms/util/IdentityUtil.java index 973f961501..7acfe7eaef 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/util/IdentityUtil.java +++ b/app/src/main/java/org/thoughtcrime/securesms/util/IdentityUtil.java @@ -78,14 +78,14 @@ public class IdentityUtil { if (groupRecord.getMembers().contains(recipient.getId()) && groupRecord.isActive() && !groupRecord.isMms()) { if (remote) { - IncomingTextMessage incoming = new IncomingTextMessage(recipient.getId(), 1, time, null, Optional.of(groupRecord.getEncodedId()), 0, false); + IncomingTextMessage incoming = new IncomingTextMessage(recipient.getId(), 1, time, null, Optional.of(groupRecord.getId()), 0, false); if (verified) incoming = new IncomingIdentityVerifiedMessage(incoming); else incoming = new IncomingIdentityDefaultMessage(incoming); smsDatabase.insertMessageInbox(incoming); } else { - RecipientId recipientId = DatabaseFactory.getRecipientDatabase(context).getOrInsertFromGroupId(groupRecord.getEncodedId()); + RecipientId recipientId = DatabaseFactory.getRecipientDatabase(context).getOrInsertFromGroupId(groupRecord.getId()); Recipient groupRecipient = Recipient.resolved(recipientId); long threadId = DatabaseFactory.getThreadDatabase(context).getThreadIdFor(groupRecipient); OutgoingTextMessage outgoing ; @@ -129,7 +129,7 @@ public class IdentityUtil { while ((groupRecord = reader.getNext()) != null) { if (groupRecord.getMembers().contains(recipient.getId()) && groupRecord.isActive()) { - IncomingTextMessage incoming = new IncomingTextMessage(recipient.getId(), 1, time, null, Optional.of(groupRecord.getEncodedId()), 0, false); + IncomingTextMessage incoming = new IncomingTextMessage(recipient.getId(), 1, time, null, Optional.of(groupRecord.getId()), 0, false); IncomingIdentityUpdateMessage groupUpdate = new IncomingIdentityUpdateMessage(incoming); smsDatabase.insertMessageInbox(groupUpdate); diff --git a/app/src/test/java/org/thoughtcrime/securesms/groups/GroupIdTest.java b/app/src/test/java/org/thoughtcrime/securesms/groups/GroupIdTest.java new file mode 100644 index 0000000000..2b6bcc0ca2 --- /dev/null +++ b/app/src/test/java/org/thoughtcrime/securesms/groups/GroupIdTest.java @@ -0,0 +1,135 @@ +package org.thoughtcrime.securesms.groups; + +import org.junit.Test; + +import static org.junit.Assert.assertArrayEquals; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotEquals; +import static org.junit.Assert.assertNotSame; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; + +public final class GroupIdTest { + + @Test + public void can_create_for_gv1() { + GroupId groupId = GroupId.v1(new byte[]{ 0, 1, 2, 3, 5, 6, 7, 8, 9, 11, 12, 13, 14, 15 }); + + assertEquals("__textsecure_group__!0001020305060708090b0c0d0e0f", groupId.toString()); + assertFalse(groupId.isMmsGroup()); + } + + @Test + public void can_parse_gv1() { + GroupId groupId = GroupId.parse("__textsecure_group__!0001020305060708090b0c0d0e0f"); + + assertEquals("__textsecure_group__!0001020305060708090b0c0d0e0f", groupId.toString()); + assertArrayEquals(new byte[]{ 0, 1, 2, 3, 5, 6, 7, 8, 9, 11, 12, 13, 14, 15 }, groupId.getDecodedId()); + assertFalse(groupId.isMmsGroup()); + } + + @Test + public void can_create_for_mms() { + GroupId groupId = GroupId.mms(new byte[]{ 0, 1, 2, 3, 5, 6, 7, 8, 9, 11, 12, 13, 14, 15 }); + + assertEquals("__signal_mms_group__!0001020305060708090b0c0d0e0f", groupId.toString()); + assertTrue(groupId.isMmsGroup()); + } + + @Test + public void can_parse_mms() { + GroupId groupId = GroupId.parse("__signal_mms_group__!0001020305060708090b0c0d0e0f"); + + assertEquals("__signal_mms_group__!0001020305060708090b0c0d0e0f", groupId.toString()); + assertArrayEquals(new byte[]{ 0, 1, 2, 3, 5, 6, 7, 8, 9, 11, 12, 13, 14, 15 }, groupId.getDecodedId()); + assertTrue(groupId.isMmsGroup()); + } + + @SuppressWarnings("ConstantConditions") + @Test + public void can_parse_null() { + GroupId groupId = GroupId.parseNullable(null); + + assertNull(groupId); + } + + @Test + public void can_parse_gv1_with_parseNullable() { + GroupId groupId = GroupId.parseNullable("__textsecure_group__!0001020305060708090b0c0d0e0f"); + + assertEquals("__textsecure_group__!0001020305060708090b0c0d0e0f", groupId.toString()); + assertArrayEquals(new byte[]{ 0, 1, 2, 3, 5, 6, 7, 8, 9, 11, 12, 13, 14, 15 }, groupId.getDecodedId()); + assertFalse(groupId.isMmsGroup()); + } + + @Test(expected = AssertionError.class) + public void bad_encoding__bad_prefix__parseNullable() { + GroupId.parseNullable("__BAD_PREFIX__!0001020305060708090b0c0d0e0f"); + } + + @Test(expected = AssertionError.class) + public void bad_encoding__empty__parseNullable() { + GroupId.parseNullable(""); + } + + @Test(expected = AssertionError.class) + public void bad_encoding__odd_hex__parseNullable() { + GroupId.parseNullable("__textsecure_group__!0001020305060708090bODD_HEX"); + } + + @Test(expected = AssertionError.class) + public void bad_encoding__bad_prefix__parse() { + GroupId.parse("__BAD_PREFIX__!0001020305060708090b0c0d0e0f"); + } + + @Test(expected = AssertionError.class) + public void bad_encoding__odd_hex__parse() { + GroupId.parse("__textsecure_group__!0001020305060708090b0c0d0e0fODD_HEX"); + } + + @Test + public void get_bytes() { + byte[] bytes = { 0, 1, 2, 3, 5, 6, 7, 8, 9, 11, 12, 13, 14, 15 }; + GroupId groupId = GroupId.v1(bytes); + + assertArrayEquals(bytes, groupId.getDecodedId()); + } + + @Test + public void equality() { + GroupId groupId1 = GroupId.v1(new byte[]{ 0, 1, 2, 3, 5, 6, 7, 8, 9, 11, 12, 13, 14, 15 }); + GroupId groupId2 = GroupId.v1(new byte[]{ 0, 1, 2, 3, 5, 6, 7, 8, 9, 11, 12, 13, 14, 15 }); + + assertNotSame(groupId1, groupId2); + assertEquals(groupId1, groupId2); + assertEquals(groupId1.hashCode(), groupId2.hashCode()); + } + + @Test + public void inequality_by_bytes() { + GroupId groupId1 = GroupId.v1(new byte[]{ 0, 1, 2, 3, 5, 6, 7, 8, 9, 11, 12, 13, 14, 15 }); + GroupId groupId2 = GroupId.v1(new byte[]{ 0, 1, 2, 3, 5, 6, 7, 8, 9, 11, 12, 13, 14, 16 }); + + assertNotSame(groupId1, groupId2); + assertNotEquals(groupId1, groupId2); + assertNotEquals(groupId1.hashCode(), groupId2.hashCode()); + } + + @Test + public void inequality_of_sms_and_mms() { + GroupId groupId1 = GroupId.v1(new byte[]{ 0, 1, 2, 3, 5, 6, 7, 8, 9, 11, 12, 13, 14, 15 }); + GroupId groupId2 = GroupId.mms(new byte[]{ 0, 1, 2, 3, 5, 6, 7, 8, 9, 11, 12, 13, 14, 15 }); + + assertNotSame(groupId1, groupId2); + assertNotEquals(groupId1, groupId2); + assertNotEquals(groupId1.hashCode(), groupId2.hashCode()); + } + + @Test + public void inequality_with_null() { + GroupId groupId = GroupId.v1(new byte[]{ 0, 1, 2, 3, 5, 6, 7, 8, 9, 11, 12, 13, 14, 15 }); + + assertNotEquals(groupId, null); + } +}