From ffa280bc1b79ffe9fcf403425d2bb97e3e3ae271 Mon Sep 17 00:00:00 2001 From: charles Date: Mon, 3 Oct 2022 13:15:14 +1100 Subject: [PATCH 01/47] fix: Disable typing and message requests in read-only open groups --- .../conversation/v2/ConversationActivityV2.kt | 1 + .../conversation/v2/messages/VisibleMessageView.kt | 5 +++-- .../securesms/groups/OpenGroupManager.kt | 2 +- .../libsession/messaging/open_groups/OpenGroup.kt | 13 ++++++++----- .../sending_receiving/pollers/OpenGroupPoller.kt | 1 + 5 files changed, 14 insertions(+), 8 deletions(-) diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationActivityV2.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationActivityV2.kt index 467be2c0b..d4e09b6b6 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationActivityV2.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationActivityV2.kt @@ -466,6 +466,7 @@ class ConversationActivityV2 : PassphraseRequiredActionBarActivity(), InputBarDe // called from onCreate private fun setUpInputBar() { + binding!!.inputBar.isVisible = viewModel.openGroup == null || viewModel.openGroup?.canWrite == true binding!!.inputBar.delegate = this binding!!.inputBarRecordingView.delegate = this // GIF button diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/messages/VisibleMessageView.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/messages/VisibleMessageView.kt index bd6869300..0507394db 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/messages/VisibleMessageView.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/messages/VisibleMessageView.kt @@ -153,12 +153,13 @@ class VisibleMessageView : LinearLayout { if (isGroupThread && !message.isOutgoing) { if (isEndOfMessageCluster) { + val openGroup = lokiThreadDb.getOpenGroupChat(threadID) binding.profilePictureView.root.publicKey = senderSessionID binding.profilePictureView.root.glide = glide binding.profilePictureView.root.update(message.individualRecipient) binding.profilePictureView.root.setOnClickListener { if (thread.isOpenGroupRecipient) { - if (IdPrefix.fromValue(senderSessionID) == IdPrefix.BLINDED) { + if (IdPrefix.fromValue(senderSessionID) == IdPrefix.BLINDED && openGroup?.canWrite == true) { val intent = Intent(context, ConversationActivityV2::class.java) intent.putExtra(ConversationActivityV2.FROM_GROUP_THREAD_ID, threadID) intent.putExtra(ConversationActivityV2.ADDRESS, Address.fromSerialized(senderSessionID)) @@ -169,7 +170,7 @@ class VisibleMessageView : LinearLayout { } } if (thread.isOpenGroupRecipient) { - val openGroup = lokiThreadDb.getOpenGroupChat(threadID) ?: return + openGroup ?: return var standardPublicKey = "" var blindedPublicKey: String? = null if (IdPrefix.fromValue(senderSessionID) == IdPrefix.BLINDED) { diff --git a/app/src/main/java/org/thoughtcrime/securesms/groups/OpenGroupManager.kt b/app/src/main/java/org/thoughtcrime/securesms/groups/OpenGroupManager.kt index d39ba709d..ee83427ab 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/groups/OpenGroupManager.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/groups/OpenGroupManager.kt @@ -83,7 +83,7 @@ object OpenGroupManager { if (threadID < 0) { threadID = GroupManager.createOpenGroup(openGroupID, context, null, info.name).threadId } - val openGroup = OpenGroup(server, room, info.name, info.infoUpdates, publicKey) + val openGroup = OpenGroup(server, room, info.name, info.infoUpdates, publicKey, info.write) threadDB.setOpenGroupChat(openGroup, threadID) } diff --git a/libsession/src/main/java/org/session/libsession/messaging/open_groups/OpenGroup.kt b/libsession/src/main/java/org/session/libsession/messaging/open_groups/OpenGroup.kt index 9efeaf15d..5a31e374d 100644 --- a/libsession/src/main/java/org/session/libsession/messaging/open_groups/OpenGroup.kt +++ b/libsession/src/main/java/org/session/libsession/messaging/open_groups/OpenGroup.kt @@ -12,15 +12,17 @@ data class OpenGroup( val name: String, val publicKey: String, val infoUpdates: Int, + val canWrite: Boolean, ) { - constructor(server: String, room: String, name: String, infoUpdates: Int, publicKey: String) : this( + constructor(server: String, room: String, name: String, infoUpdates: Int, publicKey: String, canWrite: Boolean) : this( server = server, room = room, id = "$server.$room", name = name, publicKey = publicKey, infoUpdates = infoUpdates, + canWrite = canWrite ) companion object { @@ -29,13 +31,13 @@ data class OpenGroup( return try { val json = JsonUtil.fromJson(jsonAsString) if (!json.has("room")) return null - val room = json.get("room").asText().toLowerCase(Locale.US) - val server = json.get("server").asText().toLowerCase(Locale.US) + val room = json.get("room").asText().lowercase(Locale.US) + val server = json.get("server").asText().lowercase(Locale.US) val displayName = json.get("displayName").asText() val publicKey = json.get("publicKey").asText() val infoUpdates = json.get("infoUpdates")?.asText()?.toIntOrNull() ?: 0 - val capabilities = json.get("capabilities")?.asText()?.split(",") ?: emptyList() - OpenGroup(server, room, displayName, infoUpdates, publicKey) + val canWrite = json.get("canWrite")?.asText()?.toBoolean() ?: true + OpenGroup(server, room, displayName, infoUpdates, publicKey, canWrite) } catch (e: Exception) { Log.w("Loki", "Couldn't parse open group from JSON: $jsonAsString.", e); null @@ -59,6 +61,7 @@ data class OpenGroup( "displayName" to name, "publicKey" to publicKey, "infoUpdates" to infoUpdates.toString(), + "canWrite" to canWrite.toString() ) val joinURL: String get() = "$server/$room?public_key=$publicKey" diff --git a/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/pollers/OpenGroupPoller.kt b/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/pollers/OpenGroupPoller.kt index 7bb00f8f3..fba701371 100644 --- a/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/pollers/OpenGroupPoller.kt +++ b/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/pollers/OpenGroupPoller.kt @@ -126,6 +126,7 @@ class OpenGroupPoller(private val server: String, private val executorService: S name = pollInfo.details?.name ?: "", infoUpdates = pollInfo.details?.infoUpdates ?: 0, publicKey = publicKey, + canWrite = pollInfo.write ) // - Open Group changes storage.updateOpenGroup(openGroup) From 99f70e5c216b110a62993bd8fdd8c5ec7dd5e52e Mon Sep 17 00:00:00 2001 From: charles Date: Mon, 3 Oct 2022 13:42:47 +1100 Subject: [PATCH 02/47] Move open group query into click handler --- .../securesms/conversation/v2/messages/VisibleMessageView.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/messages/VisibleMessageView.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/messages/VisibleMessageView.kt index 0507394db..834f00ca9 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/messages/VisibleMessageView.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/messages/VisibleMessageView.kt @@ -153,12 +153,12 @@ class VisibleMessageView : LinearLayout { if (isGroupThread && !message.isOutgoing) { if (isEndOfMessageCluster) { - val openGroup = lokiThreadDb.getOpenGroupChat(threadID) binding.profilePictureView.root.publicKey = senderSessionID binding.profilePictureView.root.glide = glide binding.profilePictureView.root.update(message.individualRecipient) binding.profilePictureView.root.setOnClickListener { if (thread.isOpenGroupRecipient) { + val openGroup = lokiThreadDb.getOpenGroupChat(threadID) if (IdPrefix.fromValue(senderSessionID) == IdPrefix.BLINDED && openGroup?.canWrite == true) { val intent = Intent(context, ConversationActivityV2::class.java) intent.putExtra(ConversationActivityV2.FROM_GROUP_THREAD_ID, threadID) @@ -170,7 +170,7 @@ class VisibleMessageView : LinearLayout { } } if (thread.isOpenGroupRecipient) { - openGroup ?: return + val openGroup = lokiThreadDb.getOpenGroupChat(threadID) ?: return var standardPublicKey = "" var blindedPublicKey: String? = null if (IdPrefix.fromValue(senderSessionID) == IdPrefix.BLINDED) { From c537da6acd3047fa93f7e8ef7036463075d57ab0 Mon Sep 17 00:00:00 2001 From: charles Date: Mon, 3 Oct 2022 16:31:29 +1100 Subject: [PATCH 03/47] fix: Show message sender in push notifications for groups --- .../MultipleRecipientNotificationBuilder.java | 15 ++++++++------- .../SingleRecipientNotificationBuilder.java | 17 +++++++++-------- 2 files changed, 17 insertions(+), 15 deletions(-) diff --git a/app/src/main/java/org/thoughtcrime/securesms/notifications/MultipleRecipientNotificationBuilder.java b/app/src/main/java/org/thoughtcrime/securesms/notifications/MultipleRecipientNotificationBuilder.java index 81332e87d..3425162c2 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/notifications/MultipleRecipientNotificationBuilder.java +++ b/app/src/main/java/org/thoughtcrime/securesms/notifications/MultipleRecipientNotificationBuilder.java @@ -52,8 +52,8 @@ public class MultipleRecipientNotificationBuilder extends AbstractNotificationBu public void setMostRecentSender(Recipient recipient, Recipient threadRecipient) { String displayName = recipient.toShortString(); - if (threadRecipient.isOpenGroupRecipient()) { - displayName = getOpenGroupDisplayName(recipient); + if (threadRecipient.isGroupRecipient()) { + displayName = getGroupDisplayName(recipient, threadRecipient.isOpenGroupRecipient()); } if (privacy.isDisplayContact()) { setContentText(context.getString(R.string.MessageNotifier_most_recent_from_s, displayName)); @@ -78,8 +78,8 @@ public class MultipleRecipientNotificationBuilder extends AbstractNotificationBu public void addMessageBody(@NonNull Recipient sender, Recipient threadRecipient, @Nullable CharSequence body) { String displayName = sender.toShortString(); - if (threadRecipient.isOpenGroupRecipient()) { - displayName = getOpenGroupDisplayName(sender); + if (threadRecipient.isGroupRecipient()) { + displayName = getGroupDisplayName(sender, threadRecipient.isOpenGroupRecipient()); } if (privacy.isDisplayMessage()) { SpannableStringBuilder builder = new SpannableStringBuilder(); @@ -113,14 +113,15 @@ public class MultipleRecipientNotificationBuilder extends AbstractNotificationBu } /** - * @param recipient the * individual * recipient for which to get the open group display name. + * @param recipient the * individual * recipient for which to get the display name. + * @param openGroupRecipient whether in an open group context */ - private String getOpenGroupDisplayName(Recipient recipient) { + private String getGroupDisplayName(Recipient recipient, boolean openGroupRecipient) { SessionContactDatabase contactDB = DatabaseComponent.get(context).sessionContactDatabase(); String sessionID = recipient.getAddress().serialize(); Contact contact = contactDB.getContactWithSessionID(sessionID); if (contact == null) { return sessionID; } - String displayName = contact.displayName(Contact.ContactContext.OPEN_GROUP); + String displayName = contact.displayName(openGroupRecipient ? Contact.ContactContext.OPEN_GROUP : Contact.ContactContext.REGULAR); if (displayName == null) { return sessionID; } return displayName; } diff --git a/app/src/main/java/org/thoughtcrime/securesms/notifications/SingleRecipientNotificationBuilder.java b/app/src/main/java/org/thoughtcrime/securesms/notifications/SingleRecipientNotificationBuilder.java index b461081cf..ea547379d 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/notifications/SingleRecipientNotificationBuilder.java +++ b/app/src/main/java/org/thoughtcrime/securesms/notifications/SingleRecipientNotificationBuilder.java @@ -117,15 +117,15 @@ public class SingleRecipientNotificationBuilder extends AbstractNotificationBuil setNumber(messageCount); } - public void setPrimaryMessageBody(@NonNull Recipient threadRecipients, + public void setPrimaryMessageBody(@NonNull Recipient threadRecipient, @NonNull Recipient individualRecipient, @NonNull CharSequence message, @Nullable SlideDeck slideDeck) { SpannableStringBuilder stringBuilder = new SpannableStringBuilder(); - if (privacy.isDisplayContact() && threadRecipients.isOpenGroupRecipient()) { - String displayName = getOpenGroupDisplayName(individualRecipient); + if (privacy.isDisplayContact() && threadRecipient.isGroupRecipient()) { + String displayName = getGroupDisplayName(individualRecipient, threadRecipient.isOpenGroupRecipient()); stringBuilder.append(Util.getBoldedString(displayName + ": ")); } @@ -214,8 +214,8 @@ public class SingleRecipientNotificationBuilder extends AbstractNotificationBuil { SpannableStringBuilder stringBuilder = new SpannableStringBuilder(); - if (privacy.isDisplayContact() && threadRecipient.isOpenGroupRecipient()) { - String displayName = getOpenGroupDisplayName(individualRecipient); + if (privacy.isDisplayContact() && threadRecipient.isGroupRecipient()) { + String displayName = getGroupDisplayName(individualRecipient, threadRecipient.isOpenGroupRecipient()); stringBuilder.append(Util.getBoldedString(displayName + ": ")); } @@ -334,14 +334,15 @@ public class SingleRecipientNotificationBuilder extends AbstractNotificationBuil } /** - * @param recipient the * individual * recipient for which to get the open group display name. + * @param recipient the * individual * recipient for which to get the display name. + * @param openGroupRecipient whether in an open group context */ - private String getOpenGroupDisplayName(Recipient recipient) { + private String getGroupDisplayName(Recipient recipient, boolean openGroupRecipient) { SessionContactDatabase contactDB = DatabaseComponent.get(context).sessionContactDatabase(); String sessionID = recipient.getAddress().serialize(); Contact contact = contactDB.getContactWithSessionID(sessionID); if (contact == null) { return sessionID; } - String displayName = contact.displayName(Contact.ContactContext.OPEN_GROUP); + String displayName = contact.displayName(openGroupRecipient ? Contact.ContactContext.OPEN_GROUP : Contact.ContactContext.REGULAR); if (displayName == null) { return sessionID; } return displayName; } From 1f7edadc5982feb670140fa6eb59319a8d8d4fe0 Mon Sep 17 00:00:00 2001 From: charles Date: Tue, 4 Oct 2022 15:23:58 +1100 Subject: [PATCH 04/47] Add profile data to message request responses --- .../control/MessageRequestResponse.kt | 17 +- libsignal/protobuf/SignalService.proto | 4 +- .../libsignal/protos/SignalServiceProtos.java | 316 ++++++++++++++++-- 3 files changed, 315 insertions(+), 22 deletions(-) diff --git a/libsession/src/main/java/org/session/libsession/messaging/messages/control/MessageRequestResponse.kt b/libsession/src/main/java/org/session/libsession/messaging/messages/control/MessageRequestResponse.kt index f5a65e4ca..5722f707b 100644 --- a/libsession/src/main/java/org/session/libsession/messaging/messages/control/MessageRequestResponse.kt +++ b/libsession/src/main/java/org/session/libsession/messaging/messages/control/MessageRequestResponse.kt @@ -1,15 +1,22 @@ package org.session.libsession.messaging.messages.control +import com.google.protobuf.ByteString +import org.session.libsession.messaging.messages.visible.Profile import org.session.libsignal.protos.SignalServiceProtos import org.session.libsignal.utilities.Log -class MessageRequestResponse(val isApproved: Boolean) : ControlMessage() { +class MessageRequestResponse(val isApproved: Boolean, val profile: Profile? = null) : ControlMessage() { override val isSelfSendValid: Boolean = true override fun toProto(): SignalServiceProtos.Content? { + val profileProto = SignalServiceProtos.DataMessage.LokiProfile.newBuilder() + profile?.displayName?.let { profileProto.displayName = it } + profile?.profilePictureURL?.let { profileProto.profilePicture = it } val messageRequestResponseProto = SignalServiceProtos.MessageRequestResponse.newBuilder() .setIsApproved(isApproved) + .setProfile(profileProto.build()) + profile?.profileKey?.let { messageRequestResponseProto.profileKey = ByteString.copyFrom(it) } return try { SignalServiceProtos.Content.newBuilder() .setMessageRequestResponse(messageRequestResponseProto.build()) @@ -26,7 +33,13 @@ class MessageRequestResponse(val isApproved: Boolean) : ControlMessage() { fun fromProto(proto: SignalServiceProtos.Content): MessageRequestResponse? { val messageRequestResponseProto = if (proto.hasMessageRequestResponse()) proto.messageRequestResponse else return null val isApproved = messageRequestResponseProto.isApproved - return MessageRequestResponse(isApproved) + val profileProto = messageRequestResponseProto.profile + val profile = Profile().apply { + displayName = profileProto.displayName + profileKey = messageRequestResponseProto.profileKey.toByteArray() + profilePictureURL = profileProto.profilePicture + } + return MessageRequestResponse(isApproved, profile) } } diff --git a/libsignal/protobuf/SignalService.proto b/libsignal/protobuf/SignalService.proto index e1c1c856d..50c521833 100644 --- a/libsignal/protobuf/SignalService.proto +++ b/libsignal/protobuf/SignalService.proto @@ -233,7 +233,9 @@ message ConfigurationMessage { message MessageRequestResponse { // @required - required bool isApproved = 1; // Whether the request was approved + required bool isApproved = 1; + optional bytes profileKey = 2; + optional DataMessage.LokiProfile profile = 3; } message ReceiptMessage { diff --git a/libsignal/src/main/java/org/session/libsignal/protos/SignalServiceProtos.java b/libsignal/src/main/java/org/session/libsignal/protos/SignalServiceProtos.java index ead1b6255..7c44087f8 100644 --- a/libsignal/src/main/java/org/session/libsignal/protos/SignalServiceProtos.java +++ b/libsignal/src/main/java/org/session/libsignal/protos/SignalServiceProtos.java @@ -21504,6 +21504,30 @@ public final class SignalServiceProtos { * */ boolean getIsApproved(); + + // optional bytes profileKey = 2; + /** + * optional bytes profileKey = 2; + */ + boolean hasProfileKey(); + /** + * optional bytes profileKey = 2; + */ + com.google.protobuf.ByteString getProfileKey(); + + // optional .signalservice.DataMessage.LokiProfile profile = 3; + /** + * optional .signalservice.DataMessage.LokiProfile profile = 3; + */ + boolean hasProfile(); + /** + * optional .signalservice.DataMessage.LokiProfile profile = 3; + */ + org.session.libsignal.protos.SignalServiceProtos.DataMessage.LokiProfile getProfile(); + /** + * optional .signalservice.DataMessage.LokiProfile profile = 3; + */ + org.session.libsignal.protos.SignalServiceProtos.DataMessage.LokiProfileOrBuilder getProfileOrBuilder(); } /** * Protobuf type {@code signalservice.MessageRequestResponse} @@ -21561,6 +21585,24 @@ public final class SignalServiceProtos { isApproved_ = input.readBool(); break; } + case 18: { + bitField0_ |= 0x00000002; + profileKey_ = input.readBytes(); + break; + } + case 26: { + org.session.libsignal.protos.SignalServiceProtos.DataMessage.LokiProfile.Builder subBuilder = null; + if (((bitField0_ & 0x00000004) == 0x00000004)) { + subBuilder = profile_.toBuilder(); + } + profile_ = input.readMessage(org.session.libsignal.protos.SignalServiceProtos.DataMessage.LokiProfile.PARSER, extensionRegistry); + if (subBuilder != null) { + subBuilder.mergeFrom(profile_); + profile_ = subBuilder.buildPartial(); + } + bitField0_ |= 0x00000004; + break; + } } } } catch (com.google.protobuf.InvalidProtocolBufferException e) { @@ -21625,8 +21667,48 @@ public final class SignalServiceProtos { return isApproved_; } + // optional bytes profileKey = 2; + public static final int PROFILEKEY_FIELD_NUMBER = 2; + private com.google.protobuf.ByteString profileKey_; + /** + * optional bytes profileKey = 2; + */ + public boolean hasProfileKey() { + return ((bitField0_ & 0x00000002) == 0x00000002); + } + /** + * optional bytes profileKey = 2; + */ + public com.google.protobuf.ByteString getProfileKey() { + return profileKey_; + } + + // optional .signalservice.DataMessage.LokiProfile profile = 3; + public static final int PROFILE_FIELD_NUMBER = 3; + private org.session.libsignal.protos.SignalServiceProtos.DataMessage.LokiProfile profile_; + /** + * optional .signalservice.DataMessage.LokiProfile profile = 3; + */ + public boolean hasProfile() { + return ((bitField0_ & 0x00000004) == 0x00000004); + } + /** + * optional .signalservice.DataMessage.LokiProfile profile = 3; + */ + public org.session.libsignal.protos.SignalServiceProtos.DataMessage.LokiProfile getProfile() { + return profile_; + } + /** + * optional .signalservice.DataMessage.LokiProfile profile = 3; + */ + public org.session.libsignal.protos.SignalServiceProtos.DataMessage.LokiProfileOrBuilder getProfileOrBuilder() { + return profile_; + } + private void initFields() { isApproved_ = false; + profileKey_ = com.google.protobuf.ByteString.EMPTY; + profile_ = org.session.libsignal.protos.SignalServiceProtos.DataMessage.LokiProfile.getDefaultInstance(); } private byte memoizedIsInitialized = -1; public final boolean isInitialized() { @@ -21647,6 +21729,12 @@ public final class SignalServiceProtos { if (((bitField0_ & 0x00000001) == 0x00000001)) { output.writeBool(1, isApproved_); } + if (((bitField0_ & 0x00000002) == 0x00000002)) { + output.writeBytes(2, profileKey_); + } + if (((bitField0_ & 0x00000004) == 0x00000004)) { + output.writeMessage(3, profile_); + } getUnknownFields().writeTo(output); } @@ -21660,6 +21748,14 @@ public final class SignalServiceProtos { size += com.google.protobuf.CodedOutputStream .computeBoolSize(1, isApproved_); } + if (((bitField0_ & 0x00000002) == 0x00000002)) { + size += com.google.protobuf.CodedOutputStream + .computeBytesSize(2, profileKey_); + } + if (((bitField0_ & 0x00000004) == 0x00000004)) { + size += com.google.protobuf.CodedOutputStream + .computeMessageSize(3, profile_); + } size += getUnknownFields().getSerializedSize(); memoizedSerializedSize = size; return size; @@ -21768,6 +21864,7 @@ public final class SignalServiceProtos { } private void maybeForceBuilderInitialization() { if (com.google.protobuf.GeneratedMessage.alwaysUseFieldBuilders) { + getProfileFieldBuilder(); } } private static Builder create() { @@ -21778,6 +21875,14 @@ public final class SignalServiceProtos { super.clear(); isApproved_ = false; bitField0_ = (bitField0_ & ~0x00000001); + profileKey_ = com.google.protobuf.ByteString.EMPTY; + bitField0_ = (bitField0_ & ~0x00000002); + if (profileBuilder_ == null) { + profile_ = org.session.libsignal.protos.SignalServiceProtos.DataMessage.LokiProfile.getDefaultInstance(); + } else { + profileBuilder_.clear(); + } + bitField0_ = (bitField0_ & ~0x00000004); return this; } @@ -21810,6 +21915,18 @@ public final class SignalServiceProtos { to_bitField0_ |= 0x00000001; } result.isApproved_ = isApproved_; + if (((from_bitField0_ & 0x00000002) == 0x00000002)) { + to_bitField0_ |= 0x00000002; + } + result.profileKey_ = profileKey_; + if (((from_bitField0_ & 0x00000004) == 0x00000004)) { + to_bitField0_ |= 0x00000004; + } + if (profileBuilder_ == null) { + result.profile_ = profile_; + } else { + result.profile_ = profileBuilder_.build(); + } result.bitField0_ = to_bitField0_; onBuilt(); return result; @@ -21829,6 +21946,12 @@ public final class SignalServiceProtos { if (other.hasIsApproved()) { setIsApproved(other.getIsApproved()); } + if (other.hasProfileKey()) { + setProfileKey(other.getProfileKey()); + } + if (other.hasProfile()) { + mergeProfile(other.getProfile()); + } this.mergeUnknownFields(other.getUnknownFields()); return this; } @@ -21909,6 +22032,159 @@ public final class SignalServiceProtos { return this; } + // optional bytes profileKey = 2; + private com.google.protobuf.ByteString profileKey_ = com.google.protobuf.ByteString.EMPTY; + /** + * optional bytes profileKey = 2; + */ + public boolean hasProfileKey() { + return ((bitField0_ & 0x00000002) == 0x00000002); + } + /** + * optional bytes profileKey = 2; + */ + public com.google.protobuf.ByteString getProfileKey() { + return profileKey_; + } + /** + * optional bytes profileKey = 2; + */ + public Builder setProfileKey(com.google.protobuf.ByteString value) { + if (value == null) { + throw new NullPointerException(); + } + bitField0_ |= 0x00000002; + profileKey_ = value; + onChanged(); + return this; + } + /** + * optional bytes profileKey = 2; + */ + public Builder clearProfileKey() { + bitField0_ = (bitField0_ & ~0x00000002); + profileKey_ = getDefaultInstance().getProfileKey(); + onChanged(); + return this; + } + + // optional .signalservice.DataMessage.LokiProfile profile = 3; + private org.session.libsignal.protos.SignalServiceProtos.DataMessage.LokiProfile profile_ = org.session.libsignal.protos.SignalServiceProtos.DataMessage.LokiProfile.getDefaultInstance(); + private com.google.protobuf.SingleFieldBuilder< + org.session.libsignal.protos.SignalServiceProtos.DataMessage.LokiProfile, org.session.libsignal.protos.SignalServiceProtos.DataMessage.LokiProfile.Builder, org.session.libsignal.protos.SignalServiceProtos.DataMessage.LokiProfileOrBuilder> profileBuilder_; + /** + * optional .signalservice.DataMessage.LokiProfile profile = 3; + */ + public boolean hasProfile() { + return ((bitField0_ & 0x00000004) == 0x00000004); + } + /** + * optional .signalservice.DataMessage.LokiProfile profile = 3; + */ + public org.session.libsignal.protos.SignalServiceProtos.DataMessage.LokiProfile getProfile() { + if (profileBuilder_ == null) { + return profile_; + } else { + return profileBuilder_.getMessage(); + } + } + /** + * optional .signalservice.DataMessage.LokiProfile profile = 3; + */ + public Builder setProfile(org.session.libsignal.protos.SignalServiceProtos.DataMessage.LokiProfile value) { + if (profileBuilder_ == null) { + if (value == null) { + throw new NullPointerException(); + } + profile_ = value; + onChanged(); + } else { + profileBuilder_.setMessage(value); + } + bitField0_ |= 0x00000004; + return this; + } + /** + * optional .signalservice.DataMessage.LokiProfile profile = 3; + */ + public Builder setProfile( + org.session.libsignal.protos.SignalServiceProtos.DataMessage.LokiProfile.Builder builderForValue) { + if (profileBuilder_ == null) { + profile_ = builderForValue.build(); + onChanged(); + } else { + profileBuilder_.setMessage(builderForValue.build()); + } + bitField0_ |= 0x00000004; + return this; + } + /** + * optional .signalservice.DataMessage.LokiProfile profile = 3; + */ + public Builder mergeProfile(org.session.libsignal.protos.SignalServiceProtos.DataMessage.LokiProfile value) { + if (profileBuilder_ == null) { + if (((bitField0_ & 0x00000004) == 0x00000004) && + profile_ != org.session.libsignal.protos.SignalServiceProtos.DataMessage.LokiProfile.getDefaultInstance()) { + profile_ = + org.session.libsignal.protos.SignalServiceProtos.DataMessage.LokiProfile.newBuilder(profile_).mergeFrom(value).buildPartial(); + } else { + profile_ = value; + } + onChanged(); + } else { + profileBuilder_.mergeFrom(value); + } + bitField0_ |= 0x00000004; + return this; + } + /** + * optional .signalservice.DataMessage.LokiProfile profile = 3; + */ + public Builder clearProfile() { + if (profileBuilder_ == null) { + profile_ = org.session.libsignal.protos.SignalServiceProtos.DataMessage.LokiProfile.getDefaultInstance(); + onChanged(); + } else { + profileBuilder_.clear(); + } + bitField0_ = (bitField0_ & ~0x00000004); + return this; + } + /** + * optional .signalservice.DataMessage.LokiProfile profile = 3; + */ + public org.session.libsignal.protos.SignalServiceProtos.DataMessage.LokiProfile.Builder getProfileBuilder() { + bitField0_ |= 0x00000004; + onChanged(); + return getProfileFieldBuilder().getBuilder(); + } + /** + * optional .signalservice.DataMessage.LokiProfile profile = 3; + */ + public org.session.libsignal.protos.SignalServiceProtos.DataMessage.LokiProfileOrBuilder getProfileOrBuilder() { + if (profileBuilder_ != null) { + return profileBuilder_.getMessageOrBuilder(); + } else { + return profile_; + } + } + /** + * optional .signalservice.DataMessage.LokiProfile profile = 3; + */ + private com.google.protobuf.SingleFieldBuilder< + org.session.libsignal.protos.SignalServiceProtos.DataMessage.LokiProfile, org.session.libsignal.protos.SignalServiceProtos.DataMessage.LokiProfile.Builder, org.session.libsignal.protos.SignalServiceProtos.DataMessage.LokiProfileOrBuilder> + getProfileFieldBuilder() { + if (profileBuilder_ == null) { + profileBuilder_ = new com.google.protobuf.SingleFieldBuilder< + org.session.libsignal.protos.SignalServiceProtos.DataMessage.LokiProfile, org.session.libsignal.protos.SignalServiceProtos.DataMessage.LokiProfile.Builder, org.session.libsignal.protos.SignalServiceProtos.DataMessage.LokiProfileOrBuilder>( + profile_, + getParentForChildren(), + isClean()); + profile_ = null; + } + return profileBuilder_; + } + // @@protoc_insertion_point(builder_scope:signalservice.MessageRequestResponse) } @@ -25921,24 +26197,26 @@ public final class SignalServiceProtos { "\001 \002(\014\022\014\n\004name\030\002 \002(\t\022\026\n\016profilePicture\030\003 ", "\001(\t\022\022\n\nprofileKey\030\004 \001(\014\022\022\n\nisApproved\030\005 " + "\001(\010\022\021\n\tisBlocked\030\006 \001(\010\022\024\n\014didApproveMe\030\007" + - " \001(\010\",\n\026MessageRequestResponse\022\022\n\nisAppr" + - "oved\030\001 \002(\010\"u\n\016ReceiptMessage\0220\n\004type\030\001 \002" + - "(\0162\".signalservice.ReceiptMessage.Type\022\021" + - "\n\ttimestamp\030\002 \003(\004\"\036\n\004Type\022\014\n\010DELIVERY\020\000\022" + - "\010\n\004READ\020\001\"\354\001\n\021AttachmentPointer\022\n\n\002id\030\001 " + - "\002(\006\022\023\n\013contentType\030\002 \001(\t\022\013\n\003key\030\003 \001(\014\022\014\n" + - "\004size\030\004 \001(\r\022\021\n\tthumbnail\030\005 \001(\014\022\016\n\006digest" + - "\030\006 \001(\014\022\020\n\010fileName\030\007 \001(\t\022\r\n\005flags\030\010 \001(\r\022", - "\r\n\005width\030\t \001(\r\022\016\n\006height\030\n \001(\r\022\017\n\007captio" + - "n\030\013 \001(\t\022\013\n\003url\030e \001(\t\"\032\n\005Flags\022\021\n\rVOICE_M" + - "ESSAGE\020\001\"\365\001\n\014GroupContext\022\n\n\002id\030\001 \001(\014\022.\n" + - "\004type\030\002 \001(\0162 .signalservice.GroupContext" + - ".Type\022\014\n\004name\030\003 \001(\t\022\017\n\007members\030\004 \003(\t\0220\n\006" + - "avatar\030\005 \001(\0132 .signalservice.AttachmentP" + - "ointer\022\016\n\006admins\030\006 \003(\t\"H\n\004Type\022\013\n\007UNKNOW" + - "N\020\000\022\n\n\006UPDATE\020\001\022\013\n\007DELIVER\020\002\022\010\n\004QUIT\020\003\022\020" + - "\n\014REQUEST_INFO\020\004B3\n\034org.session.libsigna" + - "l.protosB\023SignalServiceProtos" + " \001(\010\"y\n\026MessageRequestResponse\022\022\n\nisAppr" + + "oved\030\001 \002(\010\022\022\n\nprofileKey\030\002 \001(\014\0227\n\007profil" + + "e\030\003 \001(\0132&.signalservice.DataMessage.Loki" + + "Profile\"u\n\016ReceiptMessage\0220\n\004type\030\001 \002(\0162" + + "\".signalservice.ReceiptMessage.Type\022\021\n\tt" + + "imestamp\030\002 \003(\004\"\036\n\004Type\022\014\n\010DELIVERY\020\000\022\010\n\004" + + "READ\020\001\"\354\001\n\021AttachmentPointer\022\n\n\002id\030\001 \002(\006" + + "\022\023\n\013contentType\030\002 \001(\t\022\013\n\003key\030\003 \001(\014\022\014\n\004si", + "ze\030\004 \001(\r\022\021\n\tthumbnail\030\005 \001(\014\022\016\n\006digest\030\006 " + + "\001(\014\022\020\n\010fileName\030\007 \001(\t\022\r\n\005flags\030\010 \001(\r\022\r\n\005" + + "width\030\t \001(\r\022\016\n\006height\030\n \001(\r\022\017\n\007caption\030\013" + + " \001(\t\022\013\n\003url\030e \001(\t\"\032\n\005Flags\022\021\n\rVOICE_MESS" + + "AGE\020\001\"\365\001\n\014GroupContext\022\n\n\002id\030\001 \001(\014\022.\n\004ty" + + "pe\030\002 \001(\0162 .signalservice.GroupContext.Ty" + + "pe\022\014\n\004name\030\003 \001(\t\022\017\n\007members\030\004 \003(\t\0220\n\006ava" + + "tar\030\005 \001(\0132 .signalservice.AttachmentPoin" + + "ter\022\016\n\006admins\030\006 \003(\t\"H\n\004Type\022\013\n\007UNKNOWN\020\000" + + "\022\n\n\006UPDATE\020\001\022\013\n\007DELIVER\020\002\022\010\n\004QUIT\020\003\022\020\n\014R", + "EQUEST_INFO\020\004B3\n\034org.session.libsignal.p" + + "rotosB\023SignalServiceProtos" }; com.google.protobuf.Descriptors.FileDescriptor.InternalDescriptorAssigner assigner = new com.google.protobuf.Descriptors.FileDescriptor.InternalDescriptorAssigner() { @@ -26064,7 +26342,7 @@ public final class SignalServiceProtos { internal_static_signalservice_MessageRequestResponse_fieldAccessorTable = new com.google.protobuf.GeneratedMessage.FieldAccessorTable( internal_static_signalservice_MessageRequestResponse_descriptor, - new java.lang.String[] { "IsApproved", }); + new java.lang.String[] { "IsApproved", "ProfileKey", "Profile", }); internal_static_signalservice_ReceiptMessage_descriptor = getDescriptor().getMessageTypes().get(10); internal_static_signalservice_ReceiptMessage_fieldAccessorTable = new From 140877f4e3201fbfc26845abf2cab8cc53457700 Mon Sep 17 00:00:00 2001 From: charles Date: Tue, 4 Oct 2022 18:31:20 +1100 Subject: [PATCH 05/47] Refactor --- .../securesms/database/Storage.kt | 16 +++++-------- .../libsession/database/StorageProtocol.kt | 5 ++-- .../control/MessageRequestResponse.kt | 2 +- .../messaging/messages/visible/Profile.kt | 2 +- .../sending_receiving/MessageSender.kt | 23 +++++-------------- 5 files changed, 16 insertions(+), 32 deletions(-) diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/Storage.kt b/app/src/main/java/org/thoughtcrime/securesms/database/Storage.kt index 14116e9e4..1017da624 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/Storage.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/database/Storage.kt @@ -23,6 +23,7 @@ import org.session.libsession.messaging.messages.signal.OutgoingGroupMediaMessag import org.session.libsession.messaging.messages.signal.OutgoingMediaMessage import org.session.libsession.messaging.messages.signal.OutgoingTextMessage import org.session.libsession.messaging.messages.visible.Attachment +import org.session.libsession.messaging.messages.visible.Profile import org.session.libsession.messaging.messages.visible.Reaction import org.session.libsession.messaging.messages.visible.VisibleMessage import org.session.libsession.messaging.open_groups.GroupMember @@ -69,16 +70,11 @@ class Storage(context: Context, helper: SQLCipherOpenHelper) : Database(context, return DatabaseComponent.get(context).lokiAPIDatabase().getUserX25519KeyPair() } - override fun getUserDisplayName(): String? { - return TextSecurePreferences.getProfileName(context) - } - - override fun getUserProfileKey(): ByteArray? { - return ProfileKeyUtil.getProfileKey(context) - } - - override fun getUserProfilePictureURL(): String? { - return TextSecurePreferences.getProfilePictureURL(context) + override fun getUserProfile(): Profile { + val displayName = TextSecurePreferences.getProfileName(context)!! + val profileKey = ProfileKeyUtil.getProfileKey(context) + val profilePictureUrl = TextSecurePreferences.getProfilePictureURL(context) + return Profile(displayName, profileKey, profilePictureUrl) } override fun setUserProfilePictureURL(newValue: String) { diff --git a/libsession/src/main/java/org/session/libsession/database/StorageProtocol.kt b/libsession/src/main/java/org/session/libsession/database/StorageProtocol.kt index fb1aac3d2..e4c66daff 100644 --- a/libsession/src/main/java/org/session/libsession/database/StorageProtocol.kt +++ b/libsession/src/main/java/org/session/libsession/database/StorageProtocol.kt @@ -12,6 +12,7 @@ import org.session.libsession.messaging.messages.Message import org.session.libsession.messaging.messages.control.ConfigurationMessage import org.session.libsession.messaging.messages.control.MessageRequestResponse import org.session.libsession.messaging.messages.visible.Attachment +import org.session.libsession.messaging.messages.visible.Profile import org.session.libsession.messaging.messages.visible.Reaction import org.session.libsession.messaging.messages.visible.VisibleMessage import org.session.libsession.messaging.open_groups.GroupMember @@ -34,9 +35,7 @@ interface StorageProtocol { // General fun getUserPublicKey(): String? fun getUserX25519KeyPair(): ECKeyPair - fun getUserDisplayName(): String? - fun getUserProfileKey(): ByteArray? - fun getUserProfilePictureURL(): String? + fun getUserProfile(): Profile fun setUserProfilePictureURL(newProfilePicture: String) // Signal fun getOrGenerateRegistrationID(): Int diff --git a/libsession/src/main/java/org/session/libsession/messaging/messages/control/MessageRequestResponse.kt b/libsession/src/main/java/org/session/libsession/messaging/messages/control/MessageRequestResponse.kt index 5722f707b..6584634fe 100644 --- a/libsession/src/main/java/org/session/libsession/messaging/messages/control/MessageRequestResponse.kt +++ b/libsession/src/main/java/org/session/libsession/messaging/messages/control/MessageRequestResponse.kt @@ -5,7 +5,7 @@ import org.session.libsession.messaging.messages.visible.Profile import org.session.libsignal.protos.SignalServiceProtos import org.session.libsignal.utilities.Log -class MessageRequestResponse(val isApproved: Boolean, val profile: Profile? = null) : ControlMessage() { +class MessageRequestResponse(val isApproved: Boolean, var profile: Profile? = null) : ControlMessage() { override val isSelfSendValid: Boolean = true diff --git a/libsession/src/main/java/org/session/libsession/messaging/messages/visible/Profile.kt b/libsession/src/main/java/org/session/libsession/messaging/messages/visible/Profile.kt index cf792e6a8..ce6b61524 100644 --- a/libsession/src/main/java/org/session/libsession/messaging/messages/visible/Profile.kt +++ b/libsession/src/main/java/org/session/libsession/messaging/messages/visible/Profile.kt @@ -25,7 +25,7 @@ class Profile() { } } - internal constructor(displayName: String, profileKey: ByteArray? = null, profilePictureURL: String? = null) : this() { + constructor(displayName: String, profileKey: ByteArray? = null, profilePictureURL: String? = null) : this() { this.displayName = displayName this.profileKey = profileKey this.profilePictureURL = profilePictureURL diff --git a/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/MessageSender.kt b/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/MessageSender.kt index d6a4618d9..f6d26dbe9 100644 --- a/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/MessageSender.kt +++ b/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/MessageSender.kt @@ -12,9 +12,9 @@ import org.session.libsession.messaging.messages.control.CallMessage import org.session.libsession.messaging.messages.control.ClosedGroupControlMessage import org.session.libsession.messaging.messages.control.ConfigurationMessage import org.session.libsession.messaging.messages.control.ExpirationTimerUpdate +import org.session.libsession.messaging.messages.control.MessageRequestResponse import org.session.libsession.messaging.messages.control.UnsendRequest import org.session.libsession.messaging.messages.visible.LinkPreview -import org.session.libsession.messaging.messages.visible.Profile import org.session.libsession.messaging.messages.visible.Quote import org.session.libsession.messaging.messages.visible.VisibleMessage import org.session.libsession.messaging.open_groups.OpenGroupApi @@ -118,14 +118,10 @@ object MessageSender { } // Attach the user's profile if needed if (message is VisibleMessage) { - val displayName = storage.getUserDisplayName()!! - val profileKey = storage.getUserProfileKey() - val profilePictureUrl = storage.getUserProfilePictureURL() - if (profileKey != null && profilePictureUrl != null) { - message.profile = Profile(displayName, profileKey, profilePictureUrl) - } else { - message.profile = Profile(displayName) - } + message.profile = storage.getUserProfile() + } + if (message is MessageRequestResponse) { + message.profile = storage.getUserProfile() } // Convert it to protobuf val proto = message.toProto() ?: throw Error.ProtoConversionFailed @@ -257,14 +253,7 @@ object MessageSender { try { // Attach the user's profile if needed if (message is VisibleMessage) { - val displayName = storage.getUserDisplayName()!! - val profileKey = storage.getUserProfileKey() - val profilePictureUrl = storage.getUserProfilePictureURL() - if (profileKey != null && profilePictureUrl != null) { - message.profile = Profile(displayName, profileKey, profilePictureUrl) - } else { - message.profile = Profile(displayName) - } + message.profile = storage.getUserProfile() } when (destination) { is Destination.OpenGroup -> { From eb74f901c12820b1687f23c83aa549d775b57f5e Mon Sep 17 00:00:00 2001 From: charles Date: Wed, 5 Oct 2022 10:05:26 +1100 Subject: [PATCH 06/47] Persist profile data --- .../securesms/database/Storage.kt | 22 +++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/Storage.kt b/app/src/main/java/org/thoughtcrime/securesms/database/Storage.kt index 1017da624..c5e67a060 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/Storage.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/database/Storage.kt @@ -2,6 +2,7 @@ package org.thoughtcrime.securesms.database import android.content.Context import android.net.Uri +import org.session.libsession.avatars.AvatarHelper import org.session.libsession.database.StorageProtocol import org.session.libsession.messaging.BlindedIdMapping import org.session.libsession.messaging.calls.CallMessageType @@ -42,6 +43,7 @@ import org.session.libsession.utilities.Address.Companion.fromSerialized import org.session.libsession.utilities.GroupRecord import org.session.libsession.utilities.GroupUtil import org.session.libsession.utilities.ProfileKeyUtil +import org.session.libsession.utilities.SSKEnvironment import org.session.libsession.utilities.TextSecurePreferences import org.session.libsession.utilities.recipients.Recipient import org.session.libsignal.crypto.ecc.ECKeyPair @@ -59,6 +61,7 @@ import org.thoughtcrime.securesms.groups.OpenGroupManager import org.thoughtcrime.securesms.jobs.RetrieveProfileAvatarJob import org.thoughtcrime.securesms.mms.PartAuthority import org.thoughtcrime.securesms.util.SessionMetaProtocol +import java.security.MessageDigest class Storage(context: Context, helper: SQLCipherOpenHelper) : Database(context, helper), StorageProtocol { @@ -760,6 +763,25 @@ class Storage(context: Context, helper: SQLCipherOpenHelper) : Database(context, val smsDb = DatabaseComponent.get(context).smsDatabase() val sender = Recipient.from(context, fromSerialized(senderPublicKey), false) val threadId = threadDB.getOrCreateThreadIdFor(sender) + val profile = response.profile + if (profile != null) { + val profileManager = SSKEnvironment.shared.profileManager + val name = profile.displayName!! + if (name.isNotEmpty()) { + profileManager.setName(context, sender, name) + } + val newProfileKey = profile.profileKey + + val needsProfilePicture = !AvatarHelper.avatarFileExists(context, sender.address) + val profileKeyValid = newProfileKey?.isNotEmpty() == true && (newProfileKey.size == 16 || newProfileKey.size == 32) && profile.profilePictureURL?.isNotEmpty() == true + val profileKeyChanged = (sender.profileKey == null || !MessageDigest.isEqual(sender.profileKey, newProfileKey)) + + if ((profileKeyValid && profileKeyChanged) || (profileKeyValid && needsProfilePicture)) { + profileManager.setProfileKey(context, sender, newProfileKey!!) + profileManager.setUnidentifiedAccessMode(context, sender, Recipient.UnidentifiedAccessMode.UNKNOWN) + profileManager.setProfilePictureURL(context, sender, profile.profilePictureURL!!) + } + } threadDB.setHasSent(threadId, true) val mappingDb = DatabaseComponent.get(context).blindedIdMappingDatabase() val mappings = mutableMapOf() From 10d0134269d178bc1381d0f90519c72d1dc936bc Mon Sep 17 00:00:00 2001 From: charles Date: Wed, 19 Oct 2022 13:15:05 +1100 Subject: [PATCH 07/47] fix: Use text color secondary for conversation pinned icon --- app/src/main/res/values/themes.xml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/app/src/main/res/values/themes.xml b/app/src/main/res/values/themes.xml index 88f1a6a2f..c6b6f3ba9 100644 --- a/app/src/main/res/values/themes.xml +++ b/app/src/main/res/values/themes.xml @@ -352,7 +352,7 @@ @color/classic_dark_1 ?colorCellBackground @color/classic_dark_2 - @color/classic_dark_4 + ?android:textColorSecondary @color/classic_dark_3 @color/classic_dark_0 @@ -435,7 +435,7 @@ @color/classic_light_5 ?colorCellBackground @color/classic_light_6 - @color/classic_light_2 + ?android:textColorSecondary @color/classic_light_3 @color/classic_light_0 @@ -513,7 +513,7 @@ @color/ocean_dark_3 ?colorCellBackground @color/ocean_dark_4 - ?colorAccent + ?android:textColorSecondary ?colorAccent @color/ocean_dark_0 @color/ocean_dark_3 @@ -632,7 +632,7 @@ ?colorAccent ?colorCellBackground @color/ocean_light_5 - ?android:textColorPrimary + ?android:textColorSecondary From 752c25d627da55dad69efbe046b06dd998756dea Mon Sep 17 00:00:00 2001 From: charles Date: Mon, 5 Dec 2022 11:27:55 +1100 Subject: [PATCH 08/47] Handle nullable profile key --- .../messaging/messages/control/MessageRequestResponse.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libsession/src/main/java/org/session/libsession/messaging/messages/control/MessageRequestResponse.kt b/libsession/src/main/java/org/session/libsession/messaging/messages/control/MessageRequestResponse.kt index 6584634fe..614a6eb81 100644 --- a/libsession/src/main/java/org/session/libsession/messaging/messages/control/MessageRequestResponse.kt +++ b/libsession/src/main/java/org/session/libsession/messaging/messages/control/MessageRequestResponse.kt @@ -36,7 +36,7 @@ class MessageRequestResponse(val isApproved: Boolean, var profile: Profile? = nu val profileProto = messageRequestResponseProto.profile val profile = Profile().apply { displayName = profileProto.displayName - profileKey = messageRequestResponseProto.profileKey.toByteArray() + profileKey = if (messageRequestResponseProto.hasProfileKey()) messageRequestResponseProto.profileKey.toByteArray() else null profilePictureURL = profileProto.profilePicture } return MessageRequestResponse(isApproved, profile) From 7fcd754f0022f588dc8dd0eaefcb0bf05eb8971c Mon Sep 17 00:00:00 2001 From: 0x330a <92654767+0x330a@users.noreply.github.com> Date: Wed, 7 Dec 2022 14:05:11 +1100 Subject: [PATCH 09/47] fix: don't use themed text color primary for the media send fragment (#1038) --- app/src/main/res/layout/mediasend_fragment.xml | 1 - app/src/main/res/values/styles.xml | 1 + 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/res/layout/mediasend_fragment.xml b/app/src/main/res/layout/mediasend_fragment.xml index 4c6115b53..4ef993d52 100644 --- a/app/src/main/res/layout/mediasend_fragment.xml +++ b/app/src/main/res/layout/mediasend_fragment.xml @@ -95,7 +95,6 @@ 2dp @null + @color/white 4 65536 sentences From 6fbfc95ad25726031aaa660b9b268f878e227f14 Mon Sep 17 00:00:00 2001 From: 0x330a <92654767+0x330a@users.noreply.github.com> Date: Wed, 14 Dec 2022 11:34:18 +1100 Subject: [PATCH 10/47] Make selected message colours more visible, fix classic dark read indicator (#1031) * fix: Selected message theme colours and crash fixes (#1025) * Fixed a bug where message requests could incorrectly appear in the thread list (#1009) * New app theming (#1010) * feat: start new app theming feature * feat: add some theming colours * refactor: start refactoring themes and colours to use dynamic attributes * feat: adding more colours and switching over default colours to be theme based instead of hard-coded or day/night specific * refactor: take a look at ocean light and logo colour * feat: global search colours for light and dark ocean * feat: more styling * feat: adding themes to conversation activity and refactoring the base theme to apply over the top of the activity's theme so it retains noActionBar etc * feat: add dynamic accent color * docs: add todo for changing how accent colour is applied * feat: update new theming to use override primary style so that the regular colorAccent attribute can be used in existing layouts * feat: coordinating styles across layouts, fixing up pinned icons and naming for conversation list items * refactor: re-styling layouts to match new themes and attributes. Need to figure out action mode close button * refactor: remove @color/text and replace with ?android:textColorPrimary to override in themes * refactor: add context theme wrapper to bottom sheet dialog that references accent color * fix: input bar bug fix and preference activity themes * refactor: new settings menu options * fix: crash for PNModeActivity.kt refactor: move ordering in seed dialog to match designs, copy changes to match new settings menu * feat: add new appearance settings activity * refactor: title and VM changes * fix: correct override * feat: add theme appearance screen UI features and start VM implementation. re-add legacy theme utils to get default for migration * fix: compile errors and missing themes from emoji features * refactor: remove background shape alteration and old bottom sheet styles, re-add the theme mode attr * feat: appearance screen wired up, just need to refresh theme * feat: add theme state recreation and fix match system settings option * refactor: add bottom margin * feat: explore custom preference category * feat: add the customized session theme for CorrectedPreferenceFragment * feat: replace AppProtectionPreferenceFragment to extend ListSummaryPreferenceFragment * refactor: change drawable style and remove explicit dividers * refactor: remove divider in CorrectedPreferenceFragment * feat: add theme state check on resume, might be jarring currently * feat: add preference divider elements for settings menu * refactor: settings menu redesigns * refactor: change led preference to integer and refactor TextSecurePreferences.kt * feat: add scroll parcel to save/restore hierarchy on restart with appearance changes * feat: add the conversations blocked contacts and refactor preference order and copy * feat: add blocked contacts activity, basic layout and vm * feat: add unblock DB functions and storage protocol, start working on the DB query state flow, might have to just implement recipient on modified listener * feat: add blocked contacts and notif recipient listeners * feat: add recipient db reader * feat: add blocked contact interactions and fix a theming crash for notifications * feat: introduce better equals and hashcode implementations to recipient, replace home diff util content check with hashcode-based comparison * feat: add settings menu vectors * fix: preview compile error * refactor: migrating settings menu to new designs * feat: help menu * refactor: simplify link opening * refactor: remove space * feat: refactor preferences and start theming for light mode options * refactor: fixing dark and light modes with dialogs * refactor: popup dialogs use proper themes now * refactor: alert dialogs and media edit fragments use attribute references * refactor: use input bar button attribute instead color control normal in vector tint * refactor: transparency, dialog fixes, notification fix * refactor: attrs and styles for buttons * fix: use prominent button color on the outline button's border * fix: fix the trash * refactor: remove the appearance * refactor: avatar placeholder generation, chips and element border styles * refactor: use colors instead of style references * refactor: theming changes to match designs and feedback * refactor: the titles are bold and the categories are tertiary coloured now * fix: appearance settings match preferences, search bottom bar uses themed attributes * refactor: increase setting button height * Update clear all data dialog * Update seed dialog * refactor: more qa feedback changes * feat: add new TLs and fa-rIR TLs * Update notification content dialog * Fix message requests clear all button text color * feat: re-add screenshot observer * refactor: make send tint accent color * feat: add unread background differences * fix: change unread count indicator * build: upgrade build numbers * Fix message requests popupmenu background color * fix: crash from attr reference in color attribute * build: upgrade build number * fix: message bubbles, thumbnail backgrounds, search bar visibility with input bar, attachment buttons * fix: tertiary text for keyboard page search view * fix: emoji overflow colour differences * fix: reaction pill dialog background is now correct colour * Add style to reactions tab layout * fix: appearance activity reverting primary color at correct time * fix: show call privacy warning every time instead of just once * fix: gradient background(?) and audio autoplay disable * fix: crash in all media containing documents * fix: reaction dialog heading fixes * Add style to reactions tab layout * fix: remove gradient backgrounds * fix: adding new reaction normal text attribute to try correct the tab layout * fix: ocean dark unread/read colours * build; update build number * build: update build number * Fix ocean light theme pin color * Update classic dark unread indicator text color * Reduce group name text size on join community screen * Restore tab indicator color on join community and all media screens * Home adapter rename * Updated the HomeViewModel to cancel old executors when new ones are added (#1) * Updated the HomeViewModel to cancel old executors when new ones are added * Removed a seemingly unneeded update * Explicitly remove observers when the HomeActivity is paused (#2) Co-authored-by: jubb Co-authored-by: charles Co-authored-by: Morgan Pretty * Play fixes (#1017) * fix: Prevent crash from recipient modification listener * fix: Add message selection highlight color * Handle crash from fingerprint auth * Update message selection highlight colors * Handle call answer/hangup processing appropriately * Add proguard rule for ContextMenuList * Handle more calls intents Co-authored-by: charles Co-authored-by: Morgan Pretty Co-authored-by: jubb Co-authored-by: charles * release: increment build number * fix: make selected colours more visible, make read indicator more visible in dark mode * refactor: revert ocean dark selected Co-authored-by: ceokot Co-authored-by: Morgan Pretty Co-authored-by: jubb Co-authored-by: charles --- app/build.gradle | 4 ++-- app/src/main/res/drawable/ic_filled_circle_check.xml | 2 +- app/src/main/res/values/themes.xml | 6 +++--- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index 49ecd7a8f..7e710ff7b 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -157,8 +157,8 @@ dependencies { testImplementation 'org.robolectric:shadows-multidex:4.4' } -def canonicalVersionCode = 309 -def canonicalVersionName = "1.16.0" +def canonicalVersionCode = 310 +def canonicalVersionName = "1.16.1" def postFixSize = 10 def abiPostFix = ['armeabi-v7a' : 1, diff --git a/app/src/main/res/drawable/ic_filled_circle_check.xml b/app/src/main/res/drawable/ic_filled_circle_check.xml index 06c3466fd..99589252b 100644 --- a/app/src/main/res/drawable/ic_filled_circle_check.xml +++ b/app/src/main/res/drawable/ic_filled_circle_check.xml @@ -8,6 +8,6 @@ android:fillColor="?android:textColorPrimary" android:pathData="M6.5,6.5m-6.5,0a6.5,6.5 0,1 1,13 0a6.5,6.5 0,1 1,-13 0"/> diff --git a/app/src/main/res/values/themes.xml b/app/src/main/res/values/themes.xml index 4c7dfd1db..ae0ddeebb 100644 --- a/app/src/main/res/values/themes.xml +++ b/app/src/main/res/values/themes.xml @@ -385,7 +385,7 @@ @color/classic_dark_1 @color/classic_dark_3 @color/classic_dark_4 - @color/classic_dark_1 + @color/classic_dark_3 @@ -636,7 +636,7 @@ ?colorCellBackground @color/ocean_light_5 ?android:textColorPrimary - @color/ocean_light_5 + @color/ocean_light_3 From d9a815a729c0b11fcb759a238d83a1a26447c3b8 Mon Sep 17 00:00:00 2001 From: 0x330a <92654767+0x330a@users.noreply.github.com> Date: Wed, 14 Dec 2022 11:34:31 +1100 Subject: [PATCH 11/47] Add adaptive monochrome icon based on the current ic_launcher_foreground (#1037) * fix: Selected message theme colours and crash fixes (#1025) * Fixed a bug where message requests could incorrectly appear in the thread list (#1009) * New app theming (#1010) * feat: start new app theming feature * feat: add some theming colours * refactor: start refactoring themes and colours to use dynamic attributes * feat: adding more colours and switching over default colours to be theme based instead of hard-coded or day/night specific * refactor: take a look at ocean light and logo colour * feat: global search colours for light and dark ocean * feat: more styling * feat: adding themes to conversation activity and refactoring the base theme to apply over the top of the activity's theme so it retains noActionBar etc * feat: add dynamic accent color * docs: add todo for changing how accent colour is applied * feat: update new theming to use override primary style so that the regular colorAccent attribute can be used in existing layouts * feat: coordinating styles across layouts, fixing up pinned icons and naming for conversation list items * refactor: re-styling layouts to match new themes and attributes. Need to figure out action mode close button * refactor: remove @color/text and replace with ?android:textColorPrimary to override in themes * refactor: add context theme wrapper to bottom sheet dialog that references accent color * fix: input bar bug fix and preference activity themes * refactor: new settings menu options * fix: crash for PNModeActivity.kt refactor: move ordering in seed dialog to match designs, copy changes to match new settings menu * feat: add new appearance settings activity * refactor: title and VM changes * fix: correct override * feat: add theme appearance screen UI features and start VM implementation. re-add legacy theme utils to get default for migration * fix: compile errors and missing themes from emoji features * refactor: remove background shape alteration and old bottom sheet styles, re-add the theme mode attr * feat: appearance screen wired up, just need to refresh theme * feat: add theme state recreation and fix match system settings option * refactor: add bottom margin * feat: explore custom preference category * feat: add the customized session theme for CorrectedPreferenceFragment * feat: replace AppProtectionPreferenceFragment to extend ListSummaryPreferenceFragment * refactor: change drawable style and remove explicit dividers * refactor: remove divider in CorrectedPreferenceFragment * feat: add theme state check on resume, might be jarring currently * feat: add preference divider elements for settings menu * refactor: settings menu redesigns * refactor: change led preference to integer and refactor TextSecurePreferences.kt * feat: add scroll parcel to save/restore hierarchy on restart with appearance changes * feat: add the conversations blocked contacts and refactor preference order and copy * feat: add blocked contacts activity, basic layout and vm * feat: add unblock DB functions and storage protocol, start working on the DB query state flow, might have to just implement recipient on modified listener * feat: add blocked contacts and notif recipient listeners * feat: add recipient db reader * feat: add blocked contact interactions and fix a theming crash for notifications * feat: introduce better equals and hashcode implementations to recipient, replace home diff util content check with hashcode-based comparison * feat: add settings menu vectors * fix: preview compile error * refactor: migrating settings menu to new designs * feat: help menu * refactor: simplify link opening * refactor: remove space * feat: refactor preferences and start theming for light mode options * refactor: fixing dark and light modes with dialogs * refactor: popup dialogs use proper themes now * refactor: alert dialogs and media edit fragments use attribute references * refactor: use input bar button attribute instead color control normal in vector tint * refactor: transparency, dialog fixes, notification fix * refactor: attrs and styles for buttons * fix: use prominent button color on the outline button's border * fix: fix the trash * refactor: remove the appearance * refactor: avatar placeholder generation, chips and element border styles * refactor: use colors instead of style references * refactor: theming changes to match designs and feedback * refactor: the titles are bold and the categories are tertiary coloured now * fix: appearance settings match preferences, search bottom bar uses themed attributes * refactor: increase setting button height * Update clear all data dialog * Update seed dialog * refactor: more qa feedback changes * feat: add new TLs and fa-rIR TLs * Update notification content dialog * Fix message requests clear all button text color * feat: re-add screenshot observer * refactor: make send tint accent color * feat: add unread background differences * fix: change unread count indicator * build: upgrade build numbers * Fix message requests popupmenu background color * fix: crash from attr reference in color attribute * build: upgrade build number * fix: message bubbles, thumbnail backgrounds, search bar visibility with input bar, attachment buttons * fix: tertiary text for keyboard page search view * fix: emoji overflow colour differences * fix: reaction pill dialog background is now correct colour * Add style to reactions tab layout * fix: appearance activity reverting primary color at correct time * fix: show call privacy warning every time instead of just once * fix: gradient background(?) and audio autoplay disable * fix: crash in all media containing documents * fix: reaction dialog heading fixes * Add style to reactions tab layout * fix: remove gradient backgrounds * fix: adding new reaction normal text attribute to try correct the tab layout * fix: ocean dark unread/read colours * build; update build number * build: update build number * Fix ocean light theme pin color * Update classic dark unread indicator text color * Reduce group name text size on join community screen * Restore tab indicator color on join community and all media screens * Home adapter rename * Updated the HomeViewModel to cancel old executors when new ones are added (#1) * Updated the HomeViewModel to cancel old executors when new ones are added * Removed a seemingly unneeded update * Explicitly remove observers when the HomeActivity is paused (#2) Co-authored-by: jubb Co-authored-by: charles Co-authored-by: Morgan Pretty * Play fixes (#1017) * fix: Prevent crash from recipient modification listener * fix: Add message selection highlight color * Handle crash from fingerprint auth * Update message selection highlight colors * Handle call answer/hangup processing appropriately * Add proguard rule for ContextMenuList * Handle more calls intents Co-authored-by: charles Co-authored-by: Morgan Pretty Co-authored-by: jubb Co-authored-by: charles * release: increment build number * feat: add adaptive monochrome icon Co-authored-by: ceokot Co-authored-by: Morgan Pretty Co-authored-by: jubb Co-authored-by: charles --- .../ic_launcher_foreground_monochrome.xml | 15 +++++++++++++++ .../main/res/mipmap-anydpi-v26/ic_launcher.xml | 1 + 2 files changed, 16 insertions(+) create mode 100644 app/src/main/res/drawable/ic_launcher_foreground_monochrome.xml diff --git a/app/src/main/res/drawable/ic_launcher_foreground_monochrome.xml b/app/src/main/res/drawable/ic_launcher_foreground_monochrome.xml new file mode 100644 index 000000000..5f21434d4 --- /dev/null +++ b/app/src/main/res/drawable/ic_launcher_foreground_monochrome.xml @@ -0,0 +1,15 @@ + + + + + + + + diff --git a/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml b/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml index 7353dbd1f..ef49c9917 100644 --- a/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml +++ b/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml @@ -2,4 +2,5 @@ + \ No newline at end of file From 95298bb9e38f5f352c8f29388456ab7856d01fac Mon Sep 17 00:00:00 2001 From: Ninad Bhuiyan Date: Sun, 18 Dec 2022 23:53:03 +0000 Subject: [PATCH 12/47] Fix voice message duration view (#948) The seconds for a voice message duration, and also its decreasing duration when played, were just the total duration of the audio. So, a 3 minute audio would appear as 3:180 at the audio's right, and 2:170 for example when said audio is being played. I added a modulo operator for 60 after the time millisecond to seconds conversion, just before setting it as viewable text, same for the progress function. --- .../securesms/conversation/v2/messages/VoiceMessageView.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/messages/VoiceMessageView.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/messages/VoiceMessageView.kt index 451368e1c..e1bf92c5f 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/messages/VoiceMessageView.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/messages/VoiceMessageView.kt @@ -78,7 +78,7 @@ class VoiceMessageView : RelativeLayout, AudioSlidePlayer.Listener { binding.voiceMessageViewDurationTextView.visibility = View.VISIBLE binding.voiceMessageViewDurationTextView.text = String.format("%01d:%02d", TimeUnit.MILLISECONDS.toMinutes(audioExtras.durationMs), - TimeUnit.MILLISECONDS.toSeconds(audioExtras.durationMs)) + TimeUnit.MILLISECONDS.toSeconds(audioExtras.durationMs) % 60) } } } @@ -102,7 +102,7 @@ class VoiceMessageView : RelativeLayout, AudioSlidePlayer.Listener { this.progress = progress binding.voiceMessageViewDurationTextView.text = String.format("%01d:%02d", TimeUnit.MILLISECONDS.toMinutes(duration - (progress * duration.toDouble()).roundToLong()), - TimeUnit.MILLISECONDS.toSeconds(duration - (progress * duration.toDouble()).roundToLong())) + TimeUnit.MILLISECONDS.toSeconds(duration - (progress * duration.toDouble()).roundToLong()) % 60) val layoutParams = binding.progressView.layoutParams as RelativeLayout.LayoutParams layoutParams.width = (width.toFloat() * progress.toFloat()).roundToInt() binding.progressView.layoutParams = layoutParams From cdd255983931f32ab6f1dff34e41fbd9511b70a1 Mon Sep 17 00:00:00 2001 From: 0x330a <92654767+0x330a@users.noreply.github.com> Date: Mon, 19 Dec 2022 11:29:05 +1100 Subject: [PATCH 13/47] Paged conversation recycler, update compile sdk version 31 (#1049) * Update build tools * Update appcompat version * Update dependencies * feat: add paging into conversation recycler and queries to fetch data off-thread * refactor: wip for updating paged results and bucketing messages / fetching enough to display * fix: currently works for scrolling and possibly refreshing? need scroll to message and auto scroll down on insert (at bottom) * fix: search and scrolling to X message works now * build: increase version code and name * fix: re-add refresh, remove the outdated comment * refactor: lets see if 25 size pages increases performance :eyes: * feat: add in some equals overrides for mms records to refresh if media has finished DLing * feat: add scroll to bottom for new messages if we are at the end of the chat * build: update build numbers * fix: update AGP and fix compile errors for sdk version 31 * feat: add log for loki-avatar and loki-fs on upload types and responses * feat: increase build number to match latest installed version * feat: changing props and permission checks for call service * fix: possible service exception when no call ID remote foreground service not terminated * revert: google services version * fix: re-add paging dependency * feat: adding new last seen function and figuring out the last seen for recycler adapter * build: update version names and codes for deploy * refactor: undo the new adapter and query changes to use previous cursor logic. revert this commit to enable new paged adapter * fix: use author's address in typist equality and hashcode for set inclusion * refactor: refactor the select contacts activity * refactor: refactor the select contacts activity * build: update version code * fix: hide all other bound views if deleted * refactor: change voice message tint, upgrade build number * fix: message detail showing up properly * revert: realise copy public key is actually not allowed if open group participant * fix: copy session ID, message detail activity support re-enabled * build: update build version code * build: remove version name * build: update build code * feat: google services version minimum compatible * fix: selection for re-created objects not properly highlighting * fix: foreground CENTER_INSIDE instead of just CENTER for scaletype * build: update version code * fix: don't show error if no error * build: update version code * fix: clear error messages if any on successful send Co-authored-by: charles --- app/build.gradle | 45 ++- app/src/main/AndroidManifest.xml | 26 +- .../securesms/ApplicationContext.java | 2 + .../securesms/backup/BackupDialog.java | 104 ------ .../securesms/backup/BackupRestoreActivity.kt | 206 ----------- .../contacts/ContactSelectionListFragment.kt | 13 +- .../contacts/SelectContactsActivity.kt | 10 +- .../conversation/paging/ConversationPager.kt | 129 +++++++ .../conversation/v2/ConversationActivityV2.kt | 72 +--- .../conversation/v2/ConversationViewModel.kt | 1 - .../conversation/v2/MessageDetailActivity.kt | 20 +- .../menus/ConversationActionModeCallback.kt | 6 +- .../v2/messages/VisibleMessageContentView.kt | 10 +- .../securesms/database/LokiMessageDatabase.kt | 5 + .../securesms/database/MmsSmsDatabase.java | 66 +++- .../securesms/database/SearchDatabase.java | 12 +- .../securesms/database/Storage.kt | 26 +- .../securesms/database/ThreadDatabase.java | 12 +- .../database/model/MessageRecord.java | 9 +- .../database/model/MmsMessageRecord.java | 6 +- .../securesms/database/model/Quote.java | 15 + .../securesms/home/HomeActivity.kt | 2 +- .../home/search/GlobalSearchAdapterUtils.kt | 2 +- .../jobmanager/AlarmManagerScheduler.java | 8 +- .../messagerequests/MessageRequestsAdapter.kt | 3 +- .../thoughtcrime/securesms/mms/SlideDeck.java | 17 +- .../notifications/DefaultMessageNotifier.java | 8 +- .../FailedNotificationBuilder.java | 7 +- .../MultipleRecipientNotificationBuilder.java | 2 +- .../notifications/NotificationItem.java | 12 +- .../notifications/NotificationState.java | 48 ++- .../PendingMessageNotificationBuilder.java | 7 +- .../securesms/onboarding/PNModeActivity.kt | 2 +- .../preferences/HelpSettingsActivity.kt | 3 +- .../securesms/search/SearchRepository.java | 4 +- .../securesms/search/model/MessageResult.java | 6 +- .../securesms/service/DirectShareService.java | 80 ++-- .../securesms/service/ExpirationListener.java | 2 +- .../service/GenericForegroundService.java | 7 +- .../securesms/service/KeyCachingService.java | 13 +- .../PersistentAlarmManagerListener.java | 3 +- .../service/UpdateApkReadyListener.java | 13 +- .../securesms/service/WebRtcCallService.kt | 347 ++++++++++++------ .../TypingStatusRepository.java | 11 +- .../thoughtcrime/securesms/util/BackupUtil.kt | 39 -- .../securesms/util/CursorUtil.java | 65 ---- .../securesms/util/PopupMenuUtil.kt | 24 -- .../webrtc/WebRtcCallServiceReceivers.kt | 18 + .../webrtc/audio/SignalAudioManager.kt | 3 +- .../webrtc/audio/SignalBluetoothManager.kt | 7 +- .../securesms/webrtc/locks/LockManager.java | 2 +- .../res/layout/activity_message_detail.xml | 2 + .../res/layout/activity_select_contacts.xml | 19 +- .../contact_selection_list_fragment.xml | 34 +- .../main/res/layout/view_voice_message.xml | 2 +- .../menu/menu_conversation_item_action.xml | 5 - .../v2/ConversationViewModelTest.kt | 6 - build.gradle | 7 +- buildSrc/build.gradle | 2 +- gradle.properties | 22 +- gradle/wrapper/gradle-wrapper.properties | 2 +- libsession/build.gradle | 17 +- .../avatars/ResourceContactPhoto.java | 3 +- .../libsession/database/StorageProtocol.kt | 1 + .../messaging/file_server/FileServerApi.kt | 5 +- .../sending_receiving/MessageSender.kt | 15 +- .../ReceivedMessageHandler.kt | 28 +- .../link_preview/LinkPreview.java | 14 + libsignal/build.gradle | 6 +- 69 files changed, 820 insertions(+), 930 deletions(-) delete mode 100644 app/src/main/java/org/thoughtcrime/securesms/backup/BackupDialog.java delete mode 100644 app/src/main/java/org/thoughtcrime/securesms/backup/BackupRestoreActivity.kt create mode 100644 app/src/main/java/org/thoughtcrime/securesms/conversation/paging/ConversationPager.kt delete mode 100644 app/src/main/java/org/thoughtcrime/securesms/util/PopupMenuUtil.kt diff --git a/app/build.gradle b/app/build.gradle index 7e710ff7b..8cc1f4146 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -4,11 +4,11 @@ buildscript { mavenCentral() } dependencies { - classpath 'com.android.tools.build:gradle:4.2.2' + classpath "com.android.tools.build:gradle:$gradlePluginVersion" classpath files('libs/gradle-witness.jar') classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlinVersion" classpath "org.jetbrains.kotlin:kotlin-serialization:$kotlinVersion" - classpath "com.google.gms:google-services:4.3.10" + classpath "com.google.gms:google-services:$googleServicesVersion" classpath "com.google.dagger:hilt-android-gradle-plugin:$daggerVersion" } } @@ -27,26 +27,27 @@ configurations.all { } dependencies { - implementation 'androidx.appcompat:appcompat:1.3.1' - implementation 'androidx.recyclerview:recyclerview:1.1.0' - implementation 'com.google.android.material:material:1.2.1' + implementation "androidx.appcompat:appcompat:$appcompatVersion" + implementation 'androidx.recyclerview:recyclerview:1.2.1' + implementation "com.google.android.material:material:$materialVersion" implementation 'com.google.android:flexbox:2.0.1' implementation 'androidx.legacy:legacy-support-v13:1.0.0' implementation 'androidx.cardview:cardview:1.0.0' - implementation 'androidx.preference:preference-ktx:1.1.1' + implementation "androidx.preference:preference-ktx:$preferenceVersion" implementation 'androidx.legacy:legacy-preference-v14:1.0.0' implementation 'androidx.gridlayout:gridlayout:1.0.0' - implementation 'androidx.exifinterface:exifinterface:1.3.3' - implementation 'androidx.constraintlayout:constraintlayout:2.1.3' + implementation 'androidx.exifinterface:exifinterface:1.3.4' + implementation 'androidx.constraintlayout:constraintlayout:2.1.4' implementation "androidx.lifecycle:lifecycle-common-java8:$lifecycleVersion" implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:$lifecycleVersion" implementation "androidx.lifecycle:lifecycle-runtime-ktx:$lifecycleVersion" implementation "androidx.lifecycle:lifecycle-livedata-ktx:$lifecycleVersion" implementation "androidx.lifecycle:lifecycle-process:$lifecycleVersion" - implementation 'androidx.activity:activity-ktx:1.2.2' - implementation 'androidx.fragment:fragment-ktx:1.3.2' - implementation "androidx.core:core-ktx:1.3.2" - implementation "androidx.work:work-runtime-ktx:2.4.0" + implementation "androidx.paging:paging-runtime-ktx:$pagingVersion" + implementation 'androidx.activity:activity-ktx:1.5.1' + implementation 'androidx.fragment:fragment-ktx:1.5.3' + implementation "androidx.core:core-ktx:$coreVersion" + implementation "androidx.work:work-runtime-ktx:2.7.1" implementation ("com.google.firebase:firebase-messaging:18.0.0") { exclude group: 'com.google.firebase', module: 'firebase-core' exclude group: 'com.google.firebase', module: 'firebase-analytics' @@ -119,7 +120,7 @@ dependencies { implementation "com.github.tbruyelle:rxpermissions:0.10.2" implementation "com.github.ybq:Android-SpinKit:1.4.0" implementation "com.opencsv:opencsv:4.6" - testImplementation 'junit:junit:4.12' + testImplementation "junit:junit:$junitVersion" testImplementation 'org.assertj:assertj-core:3.11.1' testImplementation "org.mockito:mockito-inline:4.0.0" testImplementation "org.mockito.kotlin:mockito-kotlin:$mockitoKotlinVersion" @@ -127,7 +128,7 @@ dependencies { testImplementation 'org.powermock:powermock-module-junit4:1.6.1' testImplementation 'org.powermock:powermock-module-junit4-rule:1.6.1' testImplementation 'org.powermock:powermock-classloading-xstream:1.6.1' - testImplementation 'androidx.test:core:1.3.0' + testImplementation "androidx.test:core:$testCoreVersion" testImplementation "androidx.arch.core:core-testing:2.1.0" testImplementation "org.jetbrains.kotlinx:kotlinx-coroutines-test:$coroutinesVersion" androidTestImplementation "org.jetbrains.kotlinx:kotlinx-coroutines-test:$coroutinesVersion" @@ -141,7 +142,7 @@ dependencies { // Assertions androidTestImplementation 'androidx.test.ext:junit:1.1.3' androidTestImplementation 'androidx.test.ext:truth:1.4.0' - androidTestImplementation 'com.google.truth:truth:1.0' + androidTestImplementation 'com.google.truth:truth:1.1.3' // Espresso dependencies androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0' @@ -151,14 +152,14 @@ dependencies { androidTestImplementation 'androidx.test.espresso:espresso-web:3.4.0' androidTestImplementation 'androidx.test.espresso.idling:idling-concurrent:3.4.0' androidTestImplementation 'androidx.test.espresso:espresso-idling-resource:3.4.0' - androidTestUtil 'androidx.test:orchestrator:1.4.0' + androidTestUtil 'androidx.test:orchestrator:1.4.1' testImplementation 'org.robolectric:robolectric:4.4' testImplementation 'org.robolectric:shadows-multidex:4.4' } -def canonicalVersionCode = 310 -def canonicalVersionName = "1.16.1" +def canonicalVersionCode = 321 +def canonicalVersionName = "1.16.3" def postFixSize = 10 def abiPostFix = ['armeabi-v7a' : 1, @@ -169,13 +170,9 @@ def abiPostFix = ['armeabi-v7a' : 1, android { compileSdkVersion androidCompileSdkVersion - buildToolsVersion '29.0.3' + namespace 'network.loki.messenger' useLibrary 'org.apache.http.legacy' - dexOptions { - javaMaxHeapSize "4g" - } - compileOptions { sourceCompatibility JavaVersion.VERSION_1_8 targetCompatibility JavaVersion.VERSION_1_8 @@ -209,7 +206,7 @@ android { versionName canonicalVersionName minSdkVersion androidMinimumSdkVersion - targetSdkVersion androidCompileSdkVersion + targetSdkVersion androidTargetSdkVersion multiDexEnabled = true diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 681fc00c1..6755addc0 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -1,8 +1,7 @@ + xmlns:tools="http://schemas.android.com/tools"> @@ -31,6 +30,7 @@ android:required="false" /> + @@ -174,6 +174,7 @@ android:screenOrientation="portrait"/> @@ -398,42 +400,48 @@ android:authorities="network.loki.securesms.database.recipient" android:exported="false" /> - + - + - + - + - + + android:exported="false"> + android:enabled="true" + android:exported="true"> diff --git a/app/src/main/java/org/thoughtcrime/securesms/ApplicationContext.java b/app/src/main/java/org/thoughtcrime/securesms/ApplicationContext.java index 01bc1f38a..8fe65767b 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/ApplicationContext.java +++ b/app/src/main/java/org/thoughtcrime/securesms/ApplicationContext.java @@ -481,6 +481,7 @@ public class ApplicationContext extends Application implements DefaultLifecycleO if (now - lastProfilePictureUpload <= 14 * 24 * 60 * 60 * 1000) return; ThreadUtils.queue(() -> { // Don't generate a new profile key here; we do that when the user changes their profile picture + Log.d("Loki-Avatar", "Uploading Avatar Started"); String encodedProfileKey = TextSecurePreferences.getProfileKey(ApplicationContext.this); try { // Read the file into a byte array @@ -497,6 +498,7 @@ public class ApplicationContext extends Application implements DefaultLifecycleO ProfilePictureUtilities.INSTANCE.upload(profilePicture, encodedProfileKey, ApplicationContext.this).success(unit -> { // Update the last profile picture upload date TextSecurePreferences.setLastProfilePictureUpload(ApplicationContext.this, new Date().getTime()); + Log.d("Loki-Avatar", "Uploading Avatar Finished"); return Unit.INSTANCE; }); } catch (Exception exception) { diff --git a/app/src/main/java/org/thoughtcrime/securesms/backup/BackupDialog.java b/app/src/main/java/org/thoughtcrime/securesms/backup/BackupDialog.java deleted file mode 100644 index 76342898b..000000000 --- a/app/src/main/java/org/thoughtcrime/securesms/backup/BackupDialog.java +++ /dev/null @@ -1,104 +0,0 @@ -package org.thoughtcrime.securesms.backup; - -import android.content.ClipData; -import android.content.ClipboardManager; -import android.content.Context; -import android.widget.Button; -import android.widget.CheckBox; -import android.widget.TextView; -import android.widget.Toast; - -import androidx.annotation.NonNull; -import androidx.appcompat.app.AlertDialog; - -import org.thoughtcrime.securesms.components.SwitchPreferenceCompat; -import org.session.libsignal.utilities.Log; -import org.thoughtcrime.securesms.util.BackupDirSelector; -import org.thoughtcrime.securesms.util.BackupUtil; - -import org.session.libsession.utilities.Util; - -import java.io.IOException; - -import network.loki.messenger.R; - -public class BackupDialog { - private static final String TAG = "BackupDialog"; - - public static void showEnableBackupDialog( - @NonNull Context context, - @NonNull SwitchPreferenceCompat preference, - @NonNull BackupDirSelector backupDirSelector) { - - String[] password = BackupUtil.generateBackupPassphrase(); - String passwordSt = Util.join(password, ""); - - AlertDialog dialog = new AlertDialog.Builder(context) - .setTitle(R.string.BackupDialog_enable_local_backups) - .setView(R.layout.backup_enable_dialog) - .setPositiveButton(R.string.BackupDialog_enable_backups, null) - .setNegativeButton(android.R.string.cancel, null) - .create(); - - dialog.setOnShowListener(created -> { - Button button = ((AlertDialog) created).getButton(AlertDialog.BUTTON_POSITIVE); - button.setOnClickListener(v -> { - CheckBox confirmationCheckBox = dialog.findViewById(R.id.confirmation_check); - if (confirmationCheckBox.isChecked()) { - backupDirSelector.selectBackupDir(true, uri -> { - try { - BackupUtil.enableBackups(context, passwordSt); - } catch (IOException e) { - Log.e(TAG, "Failed to activate backups.", e); - Toast.makeText(context, - context.getString(R.string.dialog_backup_activation_failed), - Toast.LENGTH_LONG) - .show(); - return; - } - - preference.setChecked(true); - created.dismiss(); - }); - } else { - Toast.makeText(context, R.string.BackupDialog_please_acknowledge_your_understanding_by_marking_the_confirmation_check_box, Toast.LENGTH_LONG).show(); - } - }); - }); - - dialog.show(); - - CheckBox checkBox = dialog.findViewById(R.id.confirmation_check); - TextView textView = dialog.findViewById(R.id.confirmation_text); - - ((TextView)dialog.findViewById(R.id.code_first)).setText(password[0]); - ((TextView)dialog.findViewById(R.id.code_second)).setText(password[1]); - ((TextView)dialog.findViewById(R.id.code_third)).setText(password[2]); - - ((TextView)dialog.findViewById(R.id.code_fourth)).setText(password[3]); - ((TextView)dialog.findViewById(R.id.code_fifth)).setText(password[4]); - ((TextView)dialog.findViewById(R.id.code_sixth)).setText(password[5]); - - textView.setOnClickListener(v -> checkBox.toggle()); - - dialog.findViewById(R.id.number_table).setOnClickListener(v -> { - ((ClipboardManager)context.getSystemService(Context.CLIPBOARD_SERVICE)).setPrimaryClip(ClipData.newPlainText("text", passwordSt)); - Toast.makeText(context, R.string.BackupDialog_copied_to_clipboard, Toast.LENGTH_SHORT).show(); - }); - - - } - - public static void showDisableBackupDialog(@NonNull Context context, @NonNull SwitchPreferenceCompat preference) { - new AlertDialog.Builder(context) - .setTitle(R.string.BackupDialog_delete_backups) - .setMessage(R.string.BackupDialog_disable_and_delete_all_local_backups) - .setNegativeButton(android.R.string.cancel, null) - .setPositiveButton(R.string.BackupDialog_delete_backups_statement, (dialog, which) -> { - BackupUtil.disableBackups(context, true); - preference.setChecked(false); - }) - .create() - .show(); - } -} diff --git a/app/src/main/java/org/thoughtcrime/securesms/backup/BackupRestoreActivity.kt b/app/src/main/java/org/thoughtcrime/securesms/backup/BackupRestoreActivity.kt deleted file mode 100644 index a94c866c0..000000000 --- a/app/src/main/java/org/thoughtcrime/securesms/backup/BackupRestoreActivity.kt +++ /dev/null @@ -1,206 +0,0 @@ -package org.thoughtcrime.securesms.backup - -import android.app.Activity -import android.app.Application -import android.content.Intent -import android.graphics.Typeface -import android.net.Uri -import android.os.Bundle -import android.provider.OpenableColumns -import android.text.Spannable -import android.text.SpannableStringBuilder -import android.text.style.ClickableSpan -import android.text.style.StyleSpan -import android.view.View -import android.widget.Toast -import androidx.activity.result.ActivityResult -import androidx.activity.result.contract.ActivityResultContracts -import androidx.activity.viewModels -import androidx.lifecycle.AndroidViewModel -import androidx.lifecycle.MutableLiveData -import androidx.lifecycle.viewModelScope -import com.google.android.gms.common.util.Strings -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.launch -import kotlinx.coroutines.withContext -import network.loki.messenger.R -import org.session.libsession.utilities.TextSecurePreferences -import org.session.libsignal.utilities.Log -import org.thoughtcrime.securesms.BaseActionBarActivity -import org.thoughtcrime.securesms.backup.FullBackupImporter.DatabaseDowngradeException -import org.thoughtcrime.securesms.crypto.AttachmentSecretProvider -import org.thoughtcrime.securesms.database.DatabaseFactory -import org.thoughtcrime.securesms.dependencies.DatabaseComponent -import org.thoughtcrime.securesms.home.HomeActivity -import org.thoughtcrime.securesms.notifications.NotificationChannels -import org.thoughtcrime.securesms.util.BackupUtil -import org.thoughtcrime.securesms.util.setUpActionBarSessionLogo -import org.thoughtcrime.securesms.util.show - -class BackupRestoreActivity : BaseActionBarActivity() { - - companion object { - private const val TAG = "BackupRestoreActivity" - } - - private val viewModel by viewModels() - - private val fileSelectionResultLauncher = registerForActivityResult(ActivityResultContracts.StartActivityForResult() - ) { result: ActivityResult -> - if (result.resultCode == Activity.RESULT_OK && result.data != null && result.data!!.data != null) { - viewModel.backupFile.value = result.data!!.data!! - } - } - - override fun onCreate(savedInstanceState: Bundle?) { - super.onCreate(savedInstanceState) - setUpActionBarSessionLogo() - -// val viewBinding = DataBindingUtil.setContentView(this, R.layout.activity_backup_restore) -// viewBinding.lifecycleOwner = this -// viewBinding.viewModel = viewModel - -// viewBinding.restoreButton.setOnClickListener { viewModel.tryRestoreBackup() } - -// viewBinding.buttonSelectFile.setOnClickListener { -// fileSelectionResultLauncher.launch(Intent(Intent.ACTION_OPEN_DOCUMENT).apply { -// //FIXME On some old APIs (tested on 21 & 23) the mime type doesn't filter properly -// // and the backup files are unavailable for selection. -//// type = BackupUtil.BACKUP_FILE_MIME_TYPE -// type = "*/*" -// }) -// } - -// viewBinding.backupCode.addTextChangedListener { text -> viewModel.backupPassphrase.value = text.toString() } - - // Focus passphrase text edit when backup file is selected. -// viewModel.backupFile.observe(this, { backupFile -> -// if (backupFile != null) viewBinding.backupCode.post { -// viewBinding.backupCode.requestFocus() -// (getSystemService(INPUT_METHOD_SERVICE) as InputMethodManager) -// .showSoftInput(viewBinding.backupCode, InputMethodManager.SHOW_IMPLICIT) -// } -// }) - - // React to backup import result. - viewModel.backupImportResult.observe(this) { result -> - if (result != null) when (result) { - BackupRestoreViewModel.BackupRestoreResult.SUCCESS -> { - val intent = Intent(this, HomeActivity::class.java) - intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK - this.show(intent) - } - BackupRestoreViewModel.BackupRestoreResult.FAILURE_VERSION_DOWNGRADE -> - Toast.makeText(this, R.string.RegistrationActivity_backup_failure_downgrade, Toast.LENGTH_LONG).show() - BackupRestoreViewModel.BackupRestoreResult.FAILURE_UNKNOWN -> - Toast.makeText(this, R.string.RegistrationActivity_incorrect_backup_passphrase, Toast.LENGTH_LONG).show() - } - } - - //region Legal info views - val termsExplanation = SpannableStringBuilder("By using this service, you agree to our Terms of Service and Privacy Policy") - termsExplanation.setSpan(StyleSpan(Typeface.BOLD), 40, 56, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE) - termsExplanation.setSpan(object : ClickableSpan() { - override fun onClick(widget: View) { - openURL("https://getsession.org/terms-of-service/") - } - }, 40, 56, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE) - termsExplanation.setSpan(StyleSpan(Typeface.BOLD), 61, 75, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE) - termsExplanation.setSpan(object : ClickableSpan() { - override fun onClick(widget: View) { - openURL("https://getsession.org/privacy-policy/") - } - }, 61, 75, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE) -// viewBinding.termsTextView.movementMethod = LinkMovementMethod.getInstance() -// viewBinding.termsTextView.text = termsExplanation - //endregion - } - - private fun openURL(url: String) { - try { - val intent = Intent(Intent.ACTION_VIEW, Uri.parse(url)) - startActivity(intent) - } catch (e: Exception) { - Toast.makeText(this, R.string.invalid_url, Toast.LENGTH_SHORT).show() - } - } -} - -class BackupRestoreViewModel(application: Application): AndroidViewModel(application) { - - companion object { - private const val TAG = "BackupRestoreViewModel" - - @JvmStatic - fun uriToFileName(view: View, fileUri: Uri?): String? { - fileUri ?: return null - - view.context.contentResolver.query(fileUri, null, null, null, null).use { - val nameIndex = it!!.getColumnIndex(OpenableColumns.DISPLAY_NAME) - it.moveToFirst() - return it.getString(nameIndex) - } - } - - @JvmStatic - fun validateData(fileUri: Uri?, passphrase: String?): Boolean { - return fileUri != null && - !Strings.isEmptyOrWhitespace(passphrase) && - passphrase!!.length == BackupUtil.BACKUP_PASSPHRASE_LENGTH - } - } - - val backupFile = MutableLiveData(null) - val backupPassphrase = MutableLiveData(null) - - val processingBackupFile = MutableLiveData(false) - val backupImportResult = MutableLiveData(null) - - fun tryRestoreBackup() = viewModelScope.launch { - if (processingBackupFile.value == true) return@launch - if (backupImportResult.value == BackupRestoreResult.SUCCESS) return@launch - if (!validateData(backupFile.value, backupPassphrase.value)) return@launch - - val context = getApplication() - val backupFile = backupFile.value!! - val passphrase = backupPassphrase.value!! - - val result: BackupRestoreResult - - processingBackupFile.value = true - - withContext(Dispatchers.IO) { - result = try { - val database = DatabaseComponent.get(context).openHelper().readableDatabase - FullBackupImporter.importFromUri( - context, - AttachmentSecretProvider.getInstance(context).getOrCreateAttachmentSecret(), - database, - backupFile, - passphrase - ) - DatabaseFactory.upgradeRestored(context, database) - NotificationChannels.restoreContactNotificationChannels(context) - TextSecurePreferences.setRestorationTime(context, System.currentTimeMillis()) - TextSecurePreferences.setHasViewedSeed(context, true) - TextSecurePreferences.setHasSeenWelcomeScreen(context, true) - - BackupRestoreResult.SUCCESS - } catch (e: DatabaseDowngradeException) { - Log.w(TAG, "Failed due to the backup being from a newer version of Signal.", e) - BackupRestoreResult.FAILURE_VERSION_DOWNGRADE - } catch (e: Exception) { - Log.w(TAG, e) - BackupRestoreResult.FAILURE_UNKNOWN - } - } - - processingBackupFile.value = false - - backupImportResult.value = result - } - - enum class BackupRestoreResult { - SUCCESS, FAILURE_VERSION_DOWNGRADE, FAILURE_UNKNOWN - } -} \ No newline at end of file diff --git a/app/src/main/java/org/thoughtcrime/securesms/contacts/ContactSelectionListFragment.kt b/app/src/main/java/org/thoughtcrime/securesms/contacts/ContactSelectionListFragment.kt index 24637c434..0b0ddf4b3 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/contacts/ContactSelectionListFragment.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/contacts/ContactSelectionListFragment.kt @@ -8,7 +8,6 @@ import androidx.fragment.app.Fragment import androidx.loader.app.LoaderManager import androidx.loader.content.Loader import androidx.recyclerview.widget.LinearLayoutManager -import androidx.swiperefreshlayout.widget.SwipeRefreshLayout.OnRefreshListener import network.loki.messenger.databinding.ContactSelectionListFragmentBinding import org.session.libsession.utilities.recipients.Recipient import org.session.libsignal.utilities.Log @@ -58,7 +57,6 @@ class ContactSelectionListFragment : Fragment(), LoaderManager.LoaderCallbacks> { @@ -106,7 +95,7 @@ class ContactSelectionListFragment : Fragment(), LoaderManager.LoaderCallbacks) { this.members = members - binding.mainContentContainer.visibility = if (members.isEmpty()) View.GONE else View.VISIBLE + binding.recyclerView.visibility = if (members.isEmpty()) View.GONE else View.VISIBLE binding.emptyStateContainer.visibility = if (members.isEmpty()) View.VISIBLE else View.GONE invalidateOptionsMenu() } diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/paging/ConversationPager.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/paging/ConversationPager.kt new file mode 100644 index 000000000..827c39454 --- /dev/null +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/paging/ConversationPager.kt @@ -0,0 +1,129 @@ +package org.thoughtcrime.securesms.conversation.paging + +import androidx.annotation.WorkerThread +import androidx.paging.Pager +import androidx.paging.PagingConfig +import androidx.paging.PagingSource +import androidx.paging.PagingState +import androidx.recyclerview.widget.DiffUtil +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.withContext +import org.session.libsession.messaging.contacts.Contact +import org.thoughtcrime.securesms.database.MmsSmsDatabase +import org.thoughtcrime.securesms.database.SessionContactDatabase +import org.thoughtcrime.securesms.database.model.MessageRecord + +private const val TIME_BUCKET = 600000L // bucket into 10 minute increments + +private fun config() = PagingConfig( + pageSize = 25, + maxSize = 100, + enablePlaceholders = false +) + +fun Long.bucketed(): Long = (TIME_BUCKET - this % TIME_BUCKET) + this + +fun conversationPager(threadId: Long, initialKey: PageLoad? = null, db: MmsSmsDatabase, contactDb: SessionContactDatabase) = Pager(config(), initialKey = initialKey) { + ConversationPagingSource(threadId, db, contactDb) +} + +class ConversationPagerDiffCallback: DiffUtil.ItemCallback() { + override fun areItemsTheSame(oldItem: MessageAndContact, newItem: MessageAndContact): Boolean = + oldItem.message.id == newItem.message.id && oldItem.message.isMms == newItem.message.isMms + + override fun areContentsTheSame(oldItem: MessageAndContact, newItem: MessageAndContact): Boolean = + oldItem == newItem +} + +data class MessageAndContact(val message: MessageRecord, + val contact: Contact?) + +data class PageLoad(val fromTime: Long, val toTime: Long? = null) + +class ConversationPagingSource( + private val threadId: Long, + private val messageDb: MmsSmsDatabase, + private val contactDb: SessionContactDatabase + ): PagingSource() { + + override fun getRefreshKey(state: PagingState): PageLoad? { + val anchorPosition = state.anchorPosition ?: return null + val anchorPage = state.closestPageToPosition(anchorPosition) ?: return null + val next = anchorPage.nextKey?.fromTime + val previous = anchorPage.prevKey?.fromTime ?: anchorPage.data.firstOrNull()?.message?.dateSent ?: return null + return PageLoad(previous, next) + } + + private val contactCache = mutableMapOf() + + @WorkerThread + private fun getContact(sessionId: String): Contact? { + contactCache[sessionId]?.let { contact -> + return contact + } ?: run { + contactDb.getContactWithSessionID(sessionId)?.let { contact -> + contactCache[sessionId] = contact + return contact + } + } + return null + } + + override suspend fun load(params: LoadParams): LoadResult { + val pageLoad = params.key ?: withContext(Dispatchers.IO) { + messageDb.getConversationSnippet(threadId).use { + val reader = messageDb.readerFor(it) + var record: MessageRecord? = null + if (reader != null) { + record = reader.next + while (record != null && record.isDeleted) { + record = reader.next + } + } + record?.dateSent?.let { fromTime -> + PageLoad(fromTime) + } + } + } ?: return LoadResult.Page(emptyList(), null, null) + + val result = withContext(Dispatchers.IO) { + val cursor = messageDb.getConversationPage( + threadId, + pageLoad.fromTime, + pageLoad.toTime ?: -1L, + params.loadSize + ) + val processedList = mutableListOf() + val reader = messageDb.readerFor(cursor) + while (reader.next != null && !invalid) { + reader.current?.let { item -> + val contact = getContact(item.individualRecipient.address.serialize()) + processedList += MessageAndContact(item, contact) + } + } + reader.close() + processedList.toMutableList() + } + + val hasNext = withContext(Dispatchers.IO) { + if (result.isEmpty()) return@withContext false + val lastTime = result.last().message.dateSent + messageDb.hasNextPage(threadId, lastTime) + } + + val nextCheckTime = if (hasNext) { + val lastSent = result.last().message.dateSent + if (lastSent == pageLoad.fromTime) null else lastSent + } else null + + val hasPrevious = withContext(Dispatchers.IO) { messageDb.hasPreviousPage(threadId, pageLoad.fromTime) } + val nextKey = if (!hasNext) null else nextCheckTime + val prevKey = if (!hasPrevious) null else messageDb.getPreviousPage(threadId, pageLoad.fromTime, params.loadSize) + + return LoadResult.Page( + data = result, // next check time is not null if drop is true + prevKey = prevKey?.let { PageLoad(it, pageLoad.fromTime) }, + nextKey = nextKey?.let { PageLoad(it) } + ) + } +} diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationActivityV2.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationActivityV2.kt index a819c3fa2..63d36c0aa 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationActivityV2.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationActivityV2.kt @@ -3,31 +3,18 @@ package org.thoughtcrime.securesms.conversation.v2 import android.Manifest import android.animation.FloatEvaluator import android.animation.ValueAnimator -import android.content.ClipData -import android.content.ClipboardManager -import android.content.Context -import android.content.DialogInterface -import android.content.Intent +import android.content.* import android.content.res.Resources import android.database.Cursor import android.graphics.Rect import android.graphics.Typeface import android.net.Uri -import android.os.AsyncTask -import android.os.Build -import android.os.Bundle -import android.os.Handler -import android.os.Looper +import android.os.* import android.provider.MediaStore import android.text.TextUtils import android.util.Pair import android.util.TypedValue -import android.view.ActionMode -import android.view.Menu -import android.view.MenuItem -import android.view.MotionEvent -import android.view.View -import android.view.WindowManager +import android.view.* import android.widget.LinearLayout import android.widget.RelativeLayout import android.widget.Toast @@ -68,12 +55,8 @@ import org.session.libsession.messaging.sending_receiving.attachments.Attachment import org.session.libsession.messaging.sending_receiving.link_preview.LinkPreview import org.session.libsession.messaging.sending_receiving.quotes.QuoteModel import org.session.libsession.messaging.utilities.SessionId -import org.session.libsession.utilities.Address +import org.session.libsession.utilities.* import org.session.libsession.utilities.Address.Companion.fromSerialized -import org.session.libsession.utilities.GroupUtil -import org.session.libsession.utilities.MediaTypes -import org.session.libsession.utilities.Stub -import org.session.libsession.utilities.TextSecurePreferences import org.session.libsession.utilities.concurrent.SimpleTask import org.session.libsession.utilities.recipients.Recipient import org.session.libsession.utilities.recipients.RecipientModifiedListener @@ -106,25 +89,10 @@ import org.thoughtcrime.securesms.conversation.v2.messages.VisibleMessageView import org.thoughtcrime.securesms.conversation.v2.messages.VisibleMessageViewDelegate import org.thoughtcrime.securesms.conversation.v2.search.SearchBottomBar import org.thoughtcrime.securesms.conversation.v2.search.SearchViewModel -import org.thoughtcrime.securesms.conversation.v2.utilities.AttachmentManager -import org.thoughtcrime.securesms.conversation.v2.utilities.BaseDialog -import org.thoughtcrime.securesms.conversation.v2.utilities.MentionManagerUtilities -import org.thoughtcrime.securesms.conversation.v2.utilities.MentionUtilities -import org.thoughtcrime.securesms.conversation.v2.utilities.ResendMessageUtilities +import org.thoughtcrime.securesms.conversation.v2.utilities.* import org.thoughtcrime.securesms.crypto.IdentityKeyUtil import org.thoughtcrime.securesms.crypto.MnemonicUtilities -import org.thoughtcrime.securesms.database.GroupDatabase -import org.thoughtcrime.securesms.database.LokiAPIDatabase -import org.thoughtcrime.securesms.database.LokiMessageDatabase -import org.thoughtcrime.securesms.database.LokiThreadDatabase -import org.thoughtcrime.securesms.database.MmsDatabase -import org.thoughtcrime.securesms.database.MmsSmsDatabase -import org.thoughtcrime.securesms.database.ReactionDatabase -import org.thoughtcrime.securesms.database.RecipientDatabase -import org.thoughtcrime.securesms.database.SessionContactDatabase -import org.thoughtcrime.securesms.database.SmsDatabase -import org.thoughtcrime.securesms.database.Storage -import org.thoughtcrime.securesms.database.ThreadDatabase +import org.thoughtcrime.securesms.database.* import org.thoughtcrime.securesms.database.model.MessageId import org.thoughtcrime.securesms.database.model.MessageRecord import org.thoughtcrime.securesms.database.model.MmsMessageRecord @@ -137,25 +105,12 @@ import org.thoughtcrime.securesms.linkpreview.LinkPreviewViewModel import org.thoughtcrime.securesms.linkpreview.LinkPreviewViewModel.LinkPreviewState import org.thoughtcrime.securesms.mediasend.Media import org.thoughtcrime.securesms.mediasend.MediaSendActivity -import org.thoughtcrime.securesms.mms.AudioSlide -import org.thoughtcrime.securesms.mms.GifSlide -import org.thoughtcrime.securesms.mms.GlideApp -import org.thoughtcrime.securesms.mms.ImageSlide -import org.thoughtcrime.securesms.mms.MediaConstraints -import org.thoughtcrime.securesms.mms.Slide -import org.thoughtcrime.securesms.mms.SlideDeck -import org.thoughtcrime.securesms.mms.VideoSlide +import org.thoughtcrime.securesms.mms.* import org.thoughtcrime.securesms.permissions.Permissions import org.thoughtcrime.securesms.reactions.ReactionsDialogFragment import org.thoughtcrime.securesms.reactions.any.ReactWithAnyEmojiDialogFragment -import org.thoughtcrime.securesms.util.ActivityDispatcher -import org.thoughtcrime.securesms.util.ConfigurationMessageUtilities -import org.thoughtcrime.securesms.util.DateUtils -import org.thoughtcrime.securesms.util.MediaUtil -import org.thoughtcrime.securesms.util.SaveAttachmentTask -import org.thoughtcrime.securesms.util.push -import org.thoughtcrime.securesms.util.toPx -import java.util.Locale +import org.thoughtcrime.securesms.util.* +import java.util.* import java.util.concurrent.ExecutionException import java.util.concurrent.atomic.AtomicLong import java.util.concurrent.atomic.AtomicReference @@ -635,7 +590,6 @@ class ConversationActivityV2 : PassphraseRequiredActionBarActivity(), InputBarDe this ) { onOptionsItemSelected(it) } } - super.onPrepareOptionsMenu(menu) return true } @@ -1789,6 +1743,10 @@ class ConversationActivityV2 : PassphraseRequiredActionBarActivity(), InputBarDe endActionMode() } + override fun destroyActionMode() { + this.actionMode = null + } + private fun sendScreenshotNotification() { val recipient = viewModel.recipient ?: return if (recipient.isGroupRecipient) return @@ -1834,7 +1792,7 @@ class ConversationActivityV2 : PassphraseRequiredActionBarActivity(), InputBarDe if (result == null) return@Observer if (result.getResults().isNotEmpty()) { result.getResults()[result.position]?.let { - jumpToMessage(it.messageRecipient.address, it.receivedTimestampMs) { + jumpToMessage(it.messageRecipient.address, it.sentTimestampMs) { searchViewModel.onMissingResult() } } } @@ -1900,7 +1858,7 @@ class ConversationActivityV2 : PassphraseRequiredActionBarActivity(), InputBarDe ConversationReactionOverlay.Action.DELETE -> deleteMessages(selectedItems) ConversationReactionOverlay.Action.BAN_AND_DELETE_ALL -> banAndDeleteAll(selectedItems) ConversationReactionOverlay.Action.BAN_USER -> banUser(selectedItems) - ConversationReactionOverlay.Action.COPY_SESSION_ID -> TODO() + ConversationReactionOverlay.Action.COPY_SESSION_ID -> copySessionID(selectedItems) } } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationViewModel.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationViewModel.kt index 4d78653ab..8db66e080 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationViewModel.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationViewModel.kt @@ -182,7 +182,6 @@ class ConversationViewModel( data class UiMessage(val id: Long, val message: String) data class ConversationUiState( - val isOxenHostedOpenGroup: Boolean = false, val uiMessages: List = emptyList(), val isMessageRequestAccepted: Boolean? = null ) diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/MessageDetailActivity.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/MessageDetailActivity.kt index 27f75701d..104c9851b 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/MessageDetailActivity.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/MessageDetailActivity.kt @@ -2,6 +2,7 @@ package org.thoughtcrime.securesms.conversation.v2 import android.os.Bundle import android.view.View +import androidx.core.view.isVisible import dagger.hilt.android.AndroidEntryPoint import network.loki.messenger.R import network.loki.messenger.databinding.ActivityMessageDetailBinding @@ -20,8 +21,7 @@ import org.thoughtcrime.securesms.database.model.MessageRecord import org.thoughtcrime.securesms.dependencies.DatabaseComponent import org.thoughtcrime.securesms.util.DateUtils import java.text.SimpleDateFormat -import java.util.Date -import java.util.Locale +import java.util.* import javax.inject.Inject @AndroidEntryPoint @@ -48,7 +48,10 @@ class MessageDetailActivity: PassphraseRequiredActionBarActivity() { // We only show this screen for messages fail to send, // so the author of the messages must be the current user. val author = Address.fromSerialized(TextSecurePreferences.getLocalNumber(this)!!) - messageRecord = DatabaseComponent.get(this).mmsSmsDatabase().getMessageFor(timestamp, author) + messageRecord = DatabaseComponent.get(this).mmsSmsDatabase().getMessageFor(timestamp, author) ?: run { + finish() + return + } val threadId = messageRecord!!.threadId val openGroup = storage.getOpenGroup(threadId) val blindedKey = openGroup?.let { group -> @@ -71,8 +74,15 @@ class MessageDetailActivity: PassphraseRequiredActionBarActivity() { val dateFormatter: SimpleDateFormat = DateUtils.getDetailedDateFormatter(this, dateLocale) binding.sentTime.text = dateFormatter.format(Date(messageRecord!!.dateSent)) - val errorMessage = DatabaseComponent.get(this).lokiMessageDatabase().getErrorMessage(messageRecord!!.getId()) ?: "Message failed to send." - binding.errorMessage.text = errorMessage + val errorMessage = DatabaseComponent.get(this).lokiMessageDatabase().getErrorMessage(messageRecord!!.getId()) + if (errorMessage != null) { + binding.errorMessage.text = errorMessage + binding.resendContainer.isVisible = true + binding.errorContainer.isVisible = true + } else { + binding.errorContainer.isVisible = false + binding.resendContainer.isVisible = false + } if (messageRecord!!.expiresIn <= 0 || messageRecord!!.expireStarted <= 0) { binding.expiresContainer.visibility = View.GONE diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/menus/ConversationActionModeCallback.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/menus/ConversationActionModeCallback.kt index cab24ce8b..d475a6444 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/menus/ConversationActionModeCallback.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/menus/ConversationActionModeCallback.kt @@ -65,9 +65,9 @@ class ConversationActionModeCallback(private val adapter: ConversationAdapter, p menu.findItem(R.id.menu_context_copy).isVisible = !containsControlMessage && hasText // Copy Session ID menu.findItem(R.id.menu_context_copy_public_key).isVisible = - (thread.isGroupRecipient && !thread.isOpenGroupRecipient && selectedItems.size == 1 && firstMessage.recipient.address.toString() != userPublicKey) + (thread.isGroupRecipient && !thread.isOpenGroupRecipient && selectedItems.size == 1 && firstMessage.individualRecipient.address.toString() != userPublicKey) // Message detail - menu.findItem(R.id.menu_message_details).isVisible = (selectedItems.size == 1 && firstMessage.isFailed) + menu.findItem(R.id.menu_message_details).isVisible = (selectedItems.size == 1 && firstMessage.isOutgoing) // Resend menu.findItem(R.id.menu_context_resend).isVisible = (selectedItems.size == 1 && firstMessage.isFailed) // Save media @@ -101,6 +101,7 @@ class ConversationActionModeCallback(private val adapter: ConversationAdapter, p override fun onDestroyActionMode(mode: ActionMode) { adapter.selectedItems.clear() adapter.notifyDataSetChanged() + delegate?.destroyActionMode() } } @@ -116,4 +117,5 @@ interface ConversationActionModeCallbackDelegate { fun showMessageDetail(messages: Set) fun saveAttachment(messages: Set) fun reply(messages: Set) + fun destroyActionMode() } \ No newline at end of file diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/messages/VisibleMessageContentView.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/messages/VisibleMessageContentView.kt index 88045f8e4..c6fc57c28 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/messages/VisibleMessageContentView.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/messages/VisibleMessageContentView.kt @@ -44,7 +44,7 @@ import org.thoughtcrime.securesms.database.model.SmsMessageRecord import org.thoughtcrime.securesms.mms.GlideRequests import org.thoughtcrime.securesms.util.SearchUtil import org.thoughtcrime.securesms.util.getAccentColor -import java.util.Locale +import java.util.* import kotlin.math.roundToInt class VisibleMessageContentView : LinearLayout { @@ -86,6 +86,14 @@ class VisibleMessageContentView : LinearLayout { if (message.isDeleted) { binding.deletedMessageView.root.isVisible = true binding.deletedMessageView.root.bind(message, getTextColor(context, message)) + binding.bodyTextView.isVisible = false + binding.quoteView.root.isVisible = false + binding.linkPreviewView.isVisible = false + binding.untrustedView.root.isVisible = false + binding.voiceMessageView.root.isVisible = false + binding.documentView.root.isVisible = false + binding.albumThumbnailView.isVisible = false + binding.openGroupInvitationView.root.isVisible = false return } else { binding.deletedMessageView.root.isVisible = false diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/LokiMessageDatabase.kt b/app/src/main/java/org/thoughtcrime/securesms/database/LokiMessageDatabase.kt index 3cfdd1301..a3f6395b1 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/LokiMessageDatabase.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/database/LokiMessageDatabase.kt @@ -136,6 +136,11 @@ class LokiMessageDatabase(context: Context, helper: SQLCipherOpenHelper) : Datab database.insertOrUpdate(errorMessageTable, contentValues, "${Companion.messageID} = ?", arrayOf(messageID.toString())) } + fun clearErrorMessage(messageID: Long) { + val database = databaseHelper.writableDatabase + database.delete(errorMessageTable, "${Companion.messageID} = ?", arrayOf(messageID.toString())) + } + fun deleteThread(threadId: Long) { val database = databaseHelper.writableDatabase try { diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/MmsSmsDatabase.java b/app/src/main/java/org/thoughtcrime/securesms/database/MmsSmsDatabase.java index 3fcb1e724..b3afeac47 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/MmsSmsDatabase.java +++ b/app/src/main/java/org/thoughtcrime/securesms/database/MmsSmsDatabase.java @@ -112,6 +112,64 @@ public class MmsSmsDatabase extends Database { return getMessageFor(timestamp, author.serialize()); } + public long getPreviousPage(long threadId, long fromTime, int limit) { + String order = MmsSmsColumns.NORMALIZED_DATE_SENT+" ASC"; + String selection = MmsSmsColumns.THREAD_ID+" = "+threadId + + " AND "+MmsSmsColumns.NORMALIZED_DATE_SENT+" > "+fromTime; + String limitStr = ""+limit; + long sent = -1; + Cursor cursor = queryTables(PROJECTION, selection, order, limitStr); + if (cursor == null) return sent; + Reader reader = readerFor(cursor); + if (!cursor.move(limit)) { + cursor.moveToLast(); + } + MessageRecord record = reader.getCurrent(); + sent = record.getDateSent(); + reader.close(); + return sent; + } + + public Cursor getConversationPage(long threadId, long fromTime, long toTime, int limit) { + String order = MmsSmsColumns.NORMALIZED_DATE_SENT+" DESC"; + String selection = MmsSmsColumns.THREAD_ID + " = "+threadId + + " AND "+MmsSmsColumns.NORMALIZED_DATE_SENT+" <= " + fromTime; + String limitStr = null; + if (toTime != -1L) { + selection += " AND "+MmsSmsColumns.NORMALIZED_DATE_SENT+" > "+toTime; + } else { + limitStr = ""+limit; + } + + return queryTables(PROJECTION, selection, order, limitStr); + } + + public boolean hasNextPage(long threadId, long toTime) { + String order = MmsSmsColumns.NORMALIZED_DATE_SENT+" DESC"; + String selection = MmsSmsColumns.THREAD_ID + " = "+threadId + + " AND "+MmsSmsColumns.NORMALIZED_DATE_SENT+" < " + toTime; // check if there's at least one message before the `toTime` + Cursor cursor = queryTables(PROJECTION, selection, order, null); + boolean hasNext = false; + if (cursor != null) { + hasNext = cursor.getCount() > 0; + cursor.close(); + } + return hasNext; + } + + public boolean hasPreviousPage(long threadId, long fromTime) { + String order = MmsSmsColumns.NORMALIZED_DATE_SENT+" DESC"; + String selection = MmsSmsColumns.THREAD_ID + " = "+threadId + + " AND "+MmsSmsColumns.NORMALIZED_DATE_SENT+" > " + fromTime; // check if there's at least one message after the `fromTime` + Cursor cursor = queryTables(PROJECTION, selection, order, null); + boolean hasNext = false; + if (cursor != null) { + hasNext = cursor.getCount() > 0; + cursor.close(); + } + return hasNext; + } + public Cursor getConversation(long threadId, boolean reverse, long offset, long limit) { String order = MmsSmsColumns.NORMALIZED_DATE_SENT + (reverse ? " DESC" : " ASC"); String selection = MmsSmsColumns.THREAD_ID + " = " + threadId; @@ -199,16 +257,16 @@ public class MmsSmsDatabase extends Database { return -1; } - public int getMessagePositionInConversation(long threadId, long receivedTimestamp, @NonNull Address address) { - String order = MmsSmsColumns.NORMALIZED_DATE_RECEIVED + " DESC"; + public int getMessagePositionInConversation(long threadId, long sentTimestamp, @NonNull Address address) { + String order = MmsSmsColumns.NORMALIZED_DATE_SENT + " DESC"; String selection = MmsSmsColumns.THREAD_ID + " = " + threadId; - try (Cursor cursor = queryTables(new String[]{ MmsSmsColumns.NORMALIZED_DATE_RECEIVED, MmsSmsColumns.ADDRESS }, selection, order, null)) { + try (Cursor cursor = queryTables(new String[]{ MmsSmsColumns.NORMALIZED_DATE_SENT, MmsSmsColumns.ADDRESS }, selection, order, null)) { String serializedAddress = address.serialize(); boolean isOwnNumber = Util.isOwnNumber(context, address.serialize()); while (cursor != null && cursor.moveToNext()) { - boolean timestampMatches = cursor.getLong(0) == receivedTimestamp; + boolean timestampMatches = cursor.getLong(0) == sentTimestamp; boolean addressMatches = serializedAddress.equals(cursor.getString(1)); if (timestampMatches && (addressMatches || isOwnNumber)) { diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/SearchDatabase.java b/app/src/main/java/org/thoughtcrime/securesms/database/SearchDatabase.java index 37efc9a43..6bce73e22 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/SearchDatabase.java +++ b/app/src/main/java/org/thoughtcrime/securesms/database/SearchDatabase.java @@ -63,7 +63,7 @@ public class SearchDatabase extends Database { ThreadDatabase.TABLE_NAME + "." + ThreadDatabase.ADDRESS + " AS " + CONVERSATION_ADDRESS + ", " + MmsSmsColumns.ADDRESS + " AS " + MESSAGE_ADDRESS + ", " + "snippet(" + SMS_FTS_TABLE_NAME + ", -1, '', '', '...', 7) AS " + SNIPPET + ", " + - SmsDatabase.TABLE_NAME + "." + SmsDatabase.DATE_RECEIVED + " AS " + MmsSmsColumns.NORMALIZED_DATE_RECEIVED + ", " + + SmsDatabase.TABLE_NAME + "." + SmsDatabase.DATE_SENT + " AS " + MmsSmsColumns.NORMALIZED_DATE_SENT + ", " + SMS_FTS_TABLE_NAME + "." + THREAD_ID + " " + "FROM " + SmsDatabase.TABLE_NAME + " " + "INNER JOIN " + SMS_FTS_TABLE_NAME + " ON " + SMS_FTS_TABLE_NAME + "." + ID + " = " + SmsDatabase.TABLE_NAME + "." + SmsDatabase.ID + " " + @@ -74,13 +74,13 @@ public class SearchDatabase extends Database { ThreadDatabase.TABLE_NAME + "." + ThreadDatabase.ADDRESS + " AS " + CONVERSATION_ADDRESS + ", " + MmsSmsColumns.ADDRESS + " AS " + MESSAGE_ADDRESS + ", " + "snippet(" + MMS_FTS_TABLE_NAME + ", -1, '', '', '...', 7) AS " + SNIPPET + ", " + - MmsDatabase.TABLE_NAME + "." + MmsDatabase.DATE_RECEIVED + " AS " + MmsSmsColumns.NORMALIZED_DATE_RECEIVED + ", " + + MmsDatabase.TABLE_NAME + "." + MmsDatabase.DATE_SENT + " AS " + MmsSmsColumns.NORMALIZED_DATE_SENT + ", " + MMS_FTS_TABLE_NAME + "." + THREAD_ID + " " + "FROM " + MmsDatabase.TABLE_NAME + " " + "INNER JOIN " + MMS_FTS_TABLE_NAME + " ON " + MMS_FTS_TABLE_NAME + "." + ID + " = " + MmsDatabase.TABLE_NAME + "." + MmsDatabase.ID + " " + "INNER JOIN " + ThreadDatabase.TABLE_NAME + " ON " + MMS_FTS_TABLE_NAME + "." + THREAD_ID + " = " + ThreadDatabase.TABLE_NAME + "." + ThreadDatabase.ID + " " + "WHERE " + MMS_FTS_TABLE_NAME + " MATCH ? " + - "ORDER BY " + MmsSmsColumns.NORMALIZED_DATE_RECEIVED + " DESC " + + "ORDER BY " + MmsSmsColumns.NORMALIZED_DATE_SENT + " DESC " + "LIMIT ?"; private static final String MESSAGES_FOR_THREAD_QUERY = @@ -88,7 +88,7 @@ public class SearchDatabase extends Database { ThreadDatabase.TABLE_NAME + "." + ThreadDatabase.ADDRESS + " AS " + CONVERSATION_ADDRESS + ", " + MmsSmsColumns.ADDRESS + " AS " + MESSAGE_ADDRESS + ", " + "snippet(" + SMS_FTS_TABLE_NAME + ", -1, '', '', '...', 7) AS " + SNIPPET + ", " + - SmsDatabase.TABLE_NAME + "." + SmsDatabase.DATE_RECEIVED + " AS " + MmsSmsColumns.NORMALIZED_DATE_RECEIVED + ", " + + SmsDatabase.TABLE_NAME + "." + SmsDatabase.DATE_SENT + " AS " + MmsSmsColumns.NORMALIZED_DATE_SENT + ", " + SMS_FTS_TABLE_NAME + "." + THREAD_ID + " " + "FROM " + SmsDatabase.TABLE_NAME + " " + "INNER JOIN " + SMS_FTS_TABLE_NAME + " ON " + SMS_FTS_TABLE_NAME + "." + ID + " = " + SmsDatabase.TABLE_NAME + "." + SmsDatabase.ID + " " + @@ -99,13 +99,13 @@ public class SearchDatabase extends Database { ThreadDatabase.TABLE_NAME + "." + ThreadDatabase.ADDRESS + " AS " + CONVERSATION_ADDRESS + ", " + MmsSmsColumns.ADDRESS + " AS " + MESSAGE_ADDRESS + ", " + "snippet(" + MMS_FTS_TABLE_NAME + ", -1, '', '', '...', 7) AS " + SNIPPET + ", " + - MmsDatabase.TABLE_NAME + "." + MmsDatabase.DATE_RECEIVED + " AS " + MmsSmsColumns.NORMALIZED_DATE_RECEIVED + ", " + + MmsDatabase.TABLE_NAME + "." + MmsDatabase.DATE_SENT + " AS " + MmsSmsColumns.NORMALIZED_DATE_SENT + ", " + MMS_FTS_TABLE_NAME + "." + THREAD_ID + " " + "FROM " + MmsDatabase.TABLE_NAME + " " + "INNER JOIN " + MMS_FTS_TABLE_NAME + " ON " + MMS_FTS_TABLE_NAME + "." + ID + " = " + MmsDatabase.TABLE_NAME + "." + MmsDatabase.ID + " " + "INNER JOIN " + ThreadDatabase.TABLE_NAME + " ON " + MMS_FTS_TABLE_NAME + "." + THREAD_ID + " = " + ThreadDatabase.TABLE_NAME + "." + ThreadDatabase.ID + " " + "WHERE " + MMS_FTS_TABLE_NAME + " MATCH ? AND " + MmsDatabase.TABLE_NAME + "." + MmsSmsColumns.THREAD_ID + " = ? " + - "ORDER BY " + MmsSmsColumns.NORMALIZED_DATE_RECEIVED + " DESC " + + "ORDER BY " + MmsSmsColumns.NORMALIZED_DATE_SENT + " DESC " + "LIMIT 500"; public SearchDatabase(@NonNull Context context, @NonNull SQLCipherOpenHelper databaseHelper) { diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/Storage.kt b/app/src/main/java/org/thoughtcrime/securesms/database/Storage.kt index 76849b8af..131fa95e9 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/Storage.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/database/Storage.kt @@ -6,22 +6,11 @@ import org.session.libsession.database.StorageProtocol import org.session.libsession.messaging.BlindedIdMapping import org.session.libsession.messaging.calls.CallMessageType import org.session.libsession.messaging.contacts.Contact -import org.session.libsession.messaging.jobs.AttachmentUploadJob -import org.session.libsession.messaging.jobs.GroupAvatarDownloadJob -import org.session.libsession.messaging.jobs.Job -import org.session.libsession.messaging.jobs.JobQueue -import org.session.libsession.messaging.jobs.MessageReceiveJob -import org.session.libsession.messaging.jobs.MessageSendJob +import org.session.libsession.messaging.jobs.* import org.session.libsession.messaging.messages.Message import org.session.libsession.messaging.messages.control.ConfigurationMessage import org.session.libsession.messaging.messages.control.MessageRequestResponse -import org.session.libsession.messaging.messages.signal.IncomingEncryptedMessage -import org.session.libsession.messaging.messages.signal.IncomingGroupMessage -import org.session.libsession.messaging.messages.signal.IncomingMediaMessage -import org.session.libsession.messaging.messages.signal.IncomingTextMessage -import org.session.libsession.messaging.messages.signal.OutgoingGroupMediaMessage -import org.session.libsession.messaging.messages.signal.OutgoingMediaMessage -import org.session.libsession.messaging.messages.signal.OutgoingTextMessage +import org.session.libsession.messaging.messages.signal.* import org.session.libsession.messaging.messages.visible.Attachment import org.session.libsession.messaging.messages.visible.Reaction import org.session.libsession.messaging.messages.visible.VisibleMessage @@ -36,12 +25,8 @@ import org.session.libsession.messaging.utilities.SessionId import org.session.libsession.messaging.utilities.SodiumUtilities import org.session.libsession.messaging.utilities.UpdateMessageData import org.session.libsession.snode.OnionRequestAPI -import org.session.libsession.utilities.Address +import org.session.libsession.utilities.* import org.session.libsession.utilities.Address.Companion.fromSerialized -import org.session.libsession.utilities.GroupRecord -import org.session.libsession.utilities.GroupUtil -import org.session.libsession.utilities.ProfileKeyUtil -import org.session.libsession.utilities.TextSecurePreferences import org.session.libsession.utilities.recipients.Recipient import org.session.libsignal.crypto.ecc.ECKeyPair import org.session.libsignal.messages.SignalServiceAttachmentPointer @@ -428,6 +413,11 @@ class Storage(context: Context, helper: SQLCipherOpenHelper) : Database(context, } } + override fun clearErrorMessage(messageID: Long) { + val db = DatabaseComponent.get(context).lokiMessageDatabase() + db.clearErrorMessage(messageID) + } + override fun setMessageServerHash(messageID: Long, serverHash: String) { DatabaseComponent.get(context).lokiMessageDatabase().setMessageServerHash(messageID, serverHash) } diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/ThreadDatabase.java b/app/src/main/java/org/thoughtcrime/securesms/database/ThreadDatabase.java index 02a82de1d..a0c701fc9 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/ThreadDatabase.java +++ b/app/src/main/java/org/thoughtcrime/securesms/database/ThreadDatabase.java @@ -502,15 +502,23 @@ public class ThreadDatabase extends Database { return db.rawQuery(query, null); } - public void setLastSeen(long threadId) { + public void setLastSeen(long threadId, long timestamp) { SQLiteDatabase db = databaseHelper.getWritableDatabase(); ContentValues contentValues = new ContentValues(1); - contentValues.put(LAST_SEEN, System.currentTimeMillis()); + if (timestamp == -1) { + contentValues.put(LAST_SEEN, System.currentTimeMillis()); + } else { + contentValues.put(LAST_SEEN, timestamp); + } db.update(TABLE_NAME, contentValues, ID_WHERE, new String[] {String.valueOf(threadId)}); notifyConversationListListeners(); } + public void setLastSeen(long threadId) { + setLastSeen(threadId, -1); + } + public Pair getLastSeenAndHasSent(long threadId) { SQLiteDatabase db = databaseHelper.getReadableDatabase(); Cursor cursor = db.query(TABLE_NAME, new String[]{LAST_SEEN, HAS_SENT}, ID_WHERE, new String[]{String.valueOf(threadId)}, null, null, null); diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/model/MessageRecord.java b/app/src/main/java/org/thoughtcrime/securesms/database/model/MessageRecord.java index da7110375..1d78314de 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/model/MessageRecord.java +++ b/app/src/main/java/org/thoughtcrime/securesms/database/model/MessageRecord.java @@ -33,6 +33,7 @@ import org.session.libsession.utilities.NetworkFailure; import org.session.libsession.utilities.recipients.Recipient; import java.util.List; +import java.util.Objects; /** * The base class for message record models that are displayed in @@ -140,14 +141,16 @@ public abstract class MessageRecord extends DisplayRecord { return spannable; } + @Override public boolean equals(Object other) { return other instanceof MessageRecord - && ((MessageRecord) other).getId() == getId() - && ((MessageRecord) other).isMms() == isMms(); + && ((MessageRecord) other).getId() == getId() + && ((MessageRecord) other).isMms() == isMms(); } + @Override public int hashCode() { - return (int)getId(); + return Objects.hash(id, isMms()); } public @NonNull List getReactions() { diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/model/MmsMessageRecord.java b/app/src/main/java/org/thoughtcrime/securesms/database/model/MmsMessageRecord.java index 46e419962..b186e668e 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/model/MmsMessageRecord.java +++ b/app/src/main/java/org/thoughtcrime/securesms/database/model/MmsMessageRecord.java @@ -2,13 +2,15 @@ package org.thoughtcrime.securesms.database.model; import androidx.annotation.NonNull; import androidx.annotation.Nullable; -import org.session.libsession.utilities.Contact; + import org.session.libsession.messaging.sending_receiving.link_preview.LinkPreview; -import org.session.libsession.utilities.recipients.Recipient; +import org.session.libsession.utilities.Contact; import org.session.libsession.utilities.IdentityKeyMismatch; import org.session.libsession.utilities.NetworkFailure; +import org.session.libsession.utilities.recipients.Recipient; import org.thoughtcrime.securesms.mms.Slide; import org.thoughtcrime.securesms.mms.SlideDeck; + import java.util.LinkedList; import java.util.List; diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/model/Quote.java b/app/src/main/java/org/thoughtcrime/securesms/database/model/Quote.java index e79626c66..4fd22ce8a 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/model/Quote.java +++ b/app/src/main/java/org/thoughtcrime/securesms/database/model/Quote.java @@ -8,6 +8,8 @@ import org.session.libsession.messaging.sending_receiving.quotes.QuoteModel; import org.session.libsession.utilities.Address; import org.thoughtcrime.securesms.mms.SlideDeck; +import java.util.Objects; + public class Quote { private final long id; @@ -47,4 +49,17 @@ public class Quote { public QuoteModel getQuoteModel() { return new QuoteModel(id, author, text, missing, attachment.asAttachments()); } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + Quote quote = (Quote) o; + return id == quote.id && missing == quote.missing && Objects.equals(author, quote.author) && Objects.equals(text, quote.text) && Objects.equals(attachment, quote.attachment); + } + + @Override + public int hashCode() { + return Objects.hash(id, author, text, missing, attachment); + } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/home/HomeActivity.kt b/app/src/main/java/org/thoughtcrime/securesms/home/HomeActivity.kt index b21eb6ff1..45f3b4a63 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/home/HomeActivity.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/home/HomeActivity.kt @@ -102,7 +102,7 @@ class HomeActivity : PassphraseRequiredActionBarActivity(), when (model) { is GlobalSearchAdapter.Model.Message -> { val threadId = model.messageResult.threadId - val timestamp = model.messageResult.receivedTimestampMs + val timestamp = model.messageResult.sentTimestampMs val author = model.messageResult.messageRecipient.address val intent = Intent(this, ConversationActivityV2::class.java) diff --git a/app/src/main/java/org/thoughtcrime/securesms/home/search/GlobalSearchAdapterUtils.kt b/app/src/main/java/org/thoughtcrime/securesms/home/search/GlobalSearchAdapterUtils.kt index 7603d3922..2c64ded86 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/home/search/GlobalSearchAdapterUtils.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/home/search/GlobalSearchAdapterUtils.kt @@ -134,7 +134,7 @@ fun ContentView.bindModel(query: String?, model: Message) { // if (hasUnreads) { // binding.unreadCountTextView.text = model.unread.toString() // } - binding.searchResultTimestamp.text = DateUtils.getDisplayFormattedTimeSpanString(binding.root.context, Locale.getDefault(), model.messageResult.receivedTimestampMs) + binding.searchResultTimestamp.text = DateUtils.getDisplayFormattedTimeSpanString(binding.root.context, Locale.getDefault(), model.messageResult.sentTimestampMs) binding.searchResultProfilePicture.root.update(model.messageResult.conversationRecipient) val textSpannable = SpannableStringBuilder() if (model.messageResult.conversationRecipient != model.messageResult.messageRecipient) { diff --git a/app/src/main/java/org/thoughtcrime/securesms/jobmanager/AlarmManagerScheduler.java b/app/src/main/java/org/thoughtcrime/securesms/jobmanager/AlarmManagerScheduler.java index 62f2ee6b2..dc1d2afcf 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/jobmanager/AlarmManagerScheduler.java +++ b/app/src/main/java/org/thoughtcrime/securesms/jobmanager/AlarmManagerScheduler.java @@ -6,17 +6,19 @@ import android.app.PendingIntent; import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; + import androidx.annotation.NonNull; import com.annimon.stream.Stream; -import org.thoughtcrime.securesms.ApplicationContext; -import network.loki.messenger.BuildConfig; import org.session.libsignal.utilities.Log; +import org.thoughtcrime.securesms.ApplicationContext; import java.util.List; import java.util.UUID; +import network.loki.messenger.BuildConfig; + /** * Schedules tasks using the {@link AlarmManager}. * @@ -51,7 +53,7 @@ public class AlarmManagerScheduler implements Scheduler { Intent intent = new Intent(context, RetryReceiver.class); intent.setAction(BuildConfig.APPLICATION_ID + UUID.randomUUID().toString()); - alarmManager.set(AlarmManager.RTC_WAKEUP, time, PendingIntent.getBroadcast(context, 0, intent, 0)); + alarmManager.set(AlarmManager.RTC_WAKEUP, time, PendingIntent.getBroadcast(context, 0, intent, PendingIntent.FLAG_IMMUTABLE)); Log.i(TAG, "Set an alarm to retry a job in " + (time - System.currentTimeMillis()) + " ms."); } diff --git a/app/src/main/java/org/thoughtcrime/securesms/messagerequests/MessageRequestsAdapter.kt b/app/src/main/java/org/thoughtcrime/securesms/messagerequests/MessageRequestsAdapter.kt index fac0a402e..89a841dc0 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/messagerequests/MessageRequestsAdapter.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/messagerequests/MessageRequestsAdapter.kt @@ -14,7 +14,6 @@ import org.thoughtcrime.securesms.database.CursorRecyclerViewAdapter import org.thoughtcrime.securesms.database.model.ThreadRecord import org.thoughtcrime.securesms.dependencies.DatabaseComponent import org.thoughtcrime.securesms.mms.GlideRequests -import org.thoughtcrime.securesms.util.forceShowIcon class MessageRequestsAdapter( context: Context, @@ -64,7 +63,7 @@ class MessageRequestsAdapter( item.iconTintList = ColorStateList.valueOf(context.getColor(R.color.destructive)) item.title = s } - popupMenu.forceShowIcon() + popupMenu.setForceShowIcon(true) popupMenu.show() } diff --git a/app/src/main/java/org/thoughtcrime/securesms/mms/SlideDeck.java b/app/src/main/java/org/thoughtcrime/securesms/mms/SlideDeck.java index 02ccf8551..6db38e6bc 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/mms/SlideDeck.java +++ b/app/src/main/java/org/thoughtcrime/securesms/mms/SlideDeck.java @@ -17,17 +17,19 @@ package org.thoughtcrime.securesms.mms; import android.content.Context; + import androidx.annotation.NonNull; import androidx.annotation.Nullable; import com.annimon.stream.Stream; import org.session.libsession.messaging.sending_receiving.attachments.Attachment; -import org.thoughtcrime.securesms.util.MediaUtil; import org.session.libsignal.utilities.guava.Optional; +import org.thoughtcrime.securesms.util.MediaUtil; import java.util.LinkedList; import java.util.List; +import java.util.Objects; public class SlideDeck { @@ -138,4 +140,17 @@ public class SlideDeck { return null; } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + SlideDeck slideDeck = (SlideDeck) o; + return Objects.equals(slides, slideDeck.slides); + } + + @Override + public int hashCode() { + return Objects.hash(slides); + } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/notifications/DefaultMessageNotifier.java b/app/src/main/java/org/thoughtcrime/securesms/notifications/DefaultMessageNotifier.java index 55a4fcd29..728462903 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/notifications/DefaultMessageNotifier.java +++ b/app/src/main/java/org/thoughtcrime/securesms/notifications/DefaultMessageNotifier.java @@ -40,7 +40,6 @@ import com.annimon.stream.Optional; import com.annimon.stream.Stream; import com.goterl.lazysodium.utils.KeyPair; -import org.session.libsession.messaging.MessagingModuleConfiguration; import org.session.libsession.messaging.open_groups.OpenGroup; import org.session.libsession.messaging.sending_receiving.notifications.MessageNotifier; import org.session.libsession.messaging.utilities.SessionId; @@ -453,8 +452,7 @@ public class DefaultMessageNotifier implements MessageNotifier { NotificationState notificationState = new NotificationState(); MmsSmsDatabase.Reader reader = DatabaseComponent.get(context).mmsSmsDatabase().readerFor(cursor); ThreadDatabase threadDatabase = DatabaseComponent.get(context).threadDatabase(); - LokiThreadDatabase lokiThreadDatabase= DatabaseComponent.get(context).lokiThreadDatabase(); - KeyPair edKeyPair = MessagingModuleConfiguration.getShared().getGetUserED25519KeyPair().invoke(); + MessageRecord record; Map cache = new HashMap(); @@ -575,7 +573,7 @@ public class DefaultMessageNotifier implements MessageNotifier { Intent alarmIntent = new Intent(ReminderReceiver.REMINDER_ACTION); alarmIntent.putExtra("reminder_count", count); - PendingIntent pendingIntent = PendingIntent.getBroadcast(context, 0, alarmIntent, PendingIntent.FLAG_CANCEL_CURRENT); + PendingIntent pendingIntent = PendingIntent.getBroadcast(context, 0, alarmIntent, PendingIntent.FLAG_CANCEL_CURRENT | PendingIntent.FLAG_IMMUTABLE); long timeout = TimeUnit.MINUTES.toMillis(2); alarmManager.set(AlarmManager.RTC_WAKEUP, System.currentTimeMillis() + timeout, pendingIntent); @@ -584,7 +582,7 @@ public class DefaultMessageNotifier implements MessageNotifier { @Override public void clearReminder(Context context) { Intent alarmIntent = new Intent(ReminderReceiver.REMINDER_ACTION); - PendingIntent pendingIntent = PendingIntent.getBroadcast(context, 0, alarmIntent, PendingIntent.FLAG_CANCEL_CURRENT); + PendingIntent pendingIntent = PendingIntent.getBroadcast(context, 0, alarmIntent, PendingIntent.FLAG_CANCEL_CURRENT | PendingIntent.FLAG_IMMUTABLE); AlarmManager alarmManager = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE); alarmManager.cancel(pendingIntent); } diff --git a/app/src/main/java/org/thoughtcrime/securesms/notifications/FailedNotificationBuilder.java b/app/src/main/java/org/thoughtcrime/securesms/notifications/FailedNotificationBuilder.java index 1ffd74be6..dc0e52abc 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/notifications/FailedNotificationBuilder.java +++ b/app/src/main/java/org/thoughtcrime/securesms/notifications/FailedNotificationBuilder.java @@ -5,9 +5,10 @@ import android.content.Context; import android.content.Intent; import android.graphics.BitmapFactory; -import network.loki.messenger.R; -import org.session.libsession.utilities.recipients.Recipient; import org.session.libsession.utilities.NotificationPrivacyPreference; +import org.session.libsession.utilities.recipients.Recipient; + +import network.loki.messenger.R; public class FailedNotificationBuilder extends AbstractNotificationBuilder { @@ -20,7 +21,7 @@ public class FailedNotificationBuilder extends AbstractNotificationBuilder { setContentTitle(context.getString(R.string.MessageNotifier_message_delivery_failed)); setContentText(context.getString(R.string.MessageNotifier_failed_to_deliver_message)); setTicker(context.getString(R.string.MessageNotifier_error_delivering_message)); - setContentIntent(PendingIntent.getActivity(context, 0, intent, 0)); + setContentIntent(PendingIntent.getActivity(context, 0, intent, PendingIntent.FLAG_IMMUTABLE)); setAutoCancel(true); setAlarms(null, Recipient.VibrateState.DEFAULT); setChannelId(NotificationChannels.FAILURES); diff --git a/app/src/main/java/org/thoughtcrime/securesms/notifications/MultipleRecipientNotificationBuilder.java b/app/src/main/java/org/thoughtcrime/securesms/notifications/MultipleRecipientNotificationBuilder.java index 81332e87d..4b6499b99 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/notifications/MultipleRecipientNotificationBuilder.java +++ b/app/src/main/java/org/thoughtcrime/securesms/notifications/MultipleRecipientNotificationBuilder.java @@ -34,7 +34,7 @@ public class MultipleRecipientNotificationBuilder extends AbstractNotificationBu setColor(context.getResources().getColor(R.color.textsecure_primary)); setSmallIcon(R.drawable.ic_notification); setContentTitle(context.getString(R.string.app_name)); - setContentIntent(PendingIntent.getActivity(context, 0, new Intent(context, HomeActivity.class), 0)); + setContentIntent(PendingIntent.getActivity(context, 0, new Intent(context, HomeActivity.class), PendingIntent.FLAG_IMMUTABLE)); setCategory(NotificationCompat.CATEGORY_MESSAGE); setGroupSummary(true); diff --git a/app/src/main/java/org/thoughtcrime/securesms/notifications/NotificationItem.java b/app/src/main/java/org/thoughtcrime/securesms/notifications/NotificationItem.java index 991989e8d..0d5775117 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/notifications/NotificationItem.java +++ b/app/src/main/java/org/thoughtcrime/securesms/notifications/NotificationItem.java @@ -4,14 +4,15 @@ import android.app.PendingIntent; import android.content.Context; import android.content.Intent; import android.net.Uri; +import android.os.Build; + import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.core.app.TaskStackBuilder; - +import org.session.libsession.utilities.recipients.Recipient; import org.thoughtcrime.securesms.conversation.v2.ConversationActivityV2; import org.thoughtcrime.securesms.mms.SlideDeck; -import org.session.libsession.utilities.recipients.Recipient; public class NotificationItem { @@ -75,9 +76,14 @@ public class NotificationItem { intent.putExtra(ConversationActivityV2.THREAD_ID, threadId); intent.setData((Uri.parse("custom://"+System.currentTimeMillis()))); + int intentFlags = PendingIntent.FLAG_UPDATE_CURRENT; + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { + intentFlags |= PendingIntent.FLAG_MUTABLE; + } + return TaskStackBuilder.create(context) .addNextIntentWithParentStack(intent) - .getPendingIntent(0, PendingIntent.FLAG_UPDATE_CURRENT); + .getPendingIntent(0, intentFlags); } public long getId() { diff --git a/app/src/main/java/org/thoughtcrime/securesms/notifications/NotificationState.java b/app/src/main/java/org/thoughtcrime/securesms/notifications/NotificationState.java index fe934e229..108aa12c5 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/notifications/NotificationState.java +++ b/app/src/main/java/org/thoughtcrime/securesms/notifications/NotificationState.java @@ -4,12 +4,14 @@ import android.app.PendingIntent; import android.content.Context; import android.content.Intent; import android.net.Uri; +import android.os.Build; + import androidx.annotation.NonNull; import androidx.annotation.Nullable; -import org.session.libsignal.utilities.Log; import org.session.libsession.utilities.recipients.Recipient; -import org.session.libsession.utilities.recipients.Recipient.*; +import org.session.libsession.utilities.recipients.Recipient.VibrateState; +import org.session.libsignal.utilities.Log; import org.thoughtcrime.securesms.conversation.v2.ConversationActivityV2; import java.util.LinkedHashSet; @@ -114,7 +116,12 @@ public class NotificationState { intent.putExtra(MarkReadReceiver.THREAD_IDS_EXTRA, threadArray); intent.putExtra(MarkReadReceiver.NOTIFICATION_ID_EXTRA, notificationId); - return PendingIntent.getBroadcast(context, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT); + int intentFlags = PendingIntent.FLAG_UPDATE_CURRENT; + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { + intentFlags |= PendingIntent.FLAG_MUTABLE; + } + + return PendingIntent.getBroadcast(context, 0, intent, intentFlags); } public PendingIntent getRemoteReplyIntent(Context context, Recipient recipient, ReplyMethod replyMethod) { @@ -127,7 +134,12 @@ public class NotificationState { intent.putExtra(RemoteReplyReceiver.REPLY_METHOD, replyMethod); intent.setPackage(context.getPackageName()); - return PendingIntent.getBroadcast(context, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT); + int intentFlags = PendingIntent.FLAG_UPDATE_CURRENT; + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { + intentFlags |= PendingIntent.FLAG_MUTABLE; + } + + return PendingIntent.getBroadcast(context, 0, intent, intentFlags); } public PendingIntent getAndroidAutoReplyIntent(Context context, Recipient recipient) { @@ -141,7 +153,12 @@ public class NotificationState { intent.putExtra(AndroidAutoReplyReceiver.THREAD_ID_EXTRA, (long)threads.toArray()[0]); intent.setPackage(context.getPackageName()); - return PendingIntent.getBroadcast(context, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT); + int intentFlags = PendingIntent.FLAG_UPDATE_CURRENT; + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { + intentFlags |= PendingIntent.FLAG_MUTABLE; + } + + return PendingIntent.getBroadcast(context, 0, intent, intentFlags); } public PendingIntent getAndroidAutoHeardIntent(Context context, int notificationId) { @@ -160,7 +177,12 @@ public class NotificationState { intent.putExtra(AndroidAutoHeardReceiver.NOTIFICATION_ID_EXTRA, notificationId); intent.setPackage(context.getPackageName()); - return PendingIntent.getBroadcast(context, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT); + int intentFlags = PendingIntent.FLAG_UPDATE_CURRENT; + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { + intentFlags |= PendingIntent.FLAG_MUTABLE; + } + + return PendingIntent.getBroadcast(context, 0, intent, intentFlags); } public PendingIntent getQuickReplyIntent(Context context, Recipient recipient) { @@ -171,7 +193,12 @@ public class NotificationState { intent.putExtra(ConversationActivityV2.THREAD_ID, (long)threads.toArray()[0]); intent.setData((Uri.parse("custom://"+System.currentTimeMillis()))); - return PendingIntent.getActivity(context, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT); + int intentFlags = PendingIntent.FLAG_UPDATE_CURRENT; + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { + intentFlags |= PendingIntent.FLAG_MUTABLE; + } + + return PendingIntent.getActivity(context, 0, intent, intentFlags); } public PendingIntent getDeleteIntent(Context context) { @@ -190,7 +217,12 @@ public class NotificationState { intent.putExtra(DeleteNotificationReceiver.EXTRA_MMS, mms); intent.setData((Uri.parse("custom://"+System.currentTimeMillis()))); - return PendingIntent.getBroadcast(context, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT); + int intentFlags = PendingIntent.FLAG_UPDATE_CURRENT; + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { + intentFlags |= PendingIntent.FLAG_MUTABLE; + } + + return PendingIntent.getBroadcast(context, 0, intent, intentFlags); } diff --git a/app/src/main/java/org/thoughtcrime/securesms/notifications/PendingMessageNotificationBuilder.java b/app/src/main/java/org/thoughtcrime/securesms/notifications/PendingMessageNotificationBuilder.java index 1d19c2c8e..935d575c5 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/notifications/PendingMessageNotificationBuilder.java +++ b/app/src/main/java/org/thoughtcrime/securesms/notifications/PendingMessageNotificationBuilder.java @@ -4,12 +4,13 @@ package org.thoughtcrime.securesms.notifications; import android.app.PendingIntent; import android.content.Context; import android.content.Intent; + import androidx.core.app.NotificationCompat; -import org.session.libsession.utilities.recipients.Recipient; -import org.thoughtcrime.securesms.home.HomeActivity; import org.session.libsession.utilities.NotificationPrivacyPreference; import org.session.libsession.utilities.TextSecurePreferences; +import org.session.libsession.utilities.recipients.Recipient; +import org.thoughtcrime.securesms.home.HomeActivity; import network.loki.messenger.R; @@ -28,7 +29,7 @@ public class PendingMessageNotificationBuilder extends AbstractNotificationBuild setContentText(context.getString(R.string.MessageNotifier_you_have_pending_signal_messages)); setTicker(context.getString(R.string.MessageNotifier_you_have_pending_signal_messages)); - setContentIntent(PendingIntent.getActivity(context, 0, intent, 0)); + setContentIntent(PendingIntent.getActivity(context, 0, intent, PendingIntent.FLAG_IMMUTABLE)); setAutoCancel(true); setAlarms(null, Recipient.VibrateState.DEFAULT); diff --git a/app/src/main/java/org/thoughtcrime/securesms/onboarding/PNModeActivity.kt b/app/src/main/java/org/thoughtcrime/securesms/onboarding/PNModeActivity.kt index 92583d89e..9cf9c3d04 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/onboarding/PNModeActivity.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/onboarding/PNModeActivity.kt @@ -52,7 +52,7 @@ class PNModeActivity : BaseActionBarActivity() { toggleFCM() } - override fun onCreateOptionsMenu(menu: Menu?): Boolean { + override fun onCreateOptionsMenu(menu: Menu): Boolean { menuInflater.inflate(R.menu.menu_pn_mode, menu) return true } diff --git a/app/src/main/java/org/thoughtcrime/securesms/preferences/HelpSettingsActivity.kt b/app/src/main/java/org/thoughtcrime/securesms/preferences/HelpSettingsActivity.kt index 90ffbd4b1..f6efd041c 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/preferences/HelpSettingsActivity.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/preferences/HelpSettingsActivity.kt @@ -41,8 +41,7 @@ class HelpSettingsFragment: CorrectedPreferenceFragment() { addPreferencesFromResource(R.xml.preferences_help) } - override fun onPreferenceTreeClick(preference: Preference?): Boolean { - preference ?: return false + override fun onPreferenceTreeClick(preference: Preference): Boolean { return when (preference.key) { EXPORT_LOGS -> { shareLogs() diff --git a/app/src/main/java/org/thoughtcrime/securesms/search/SearchRepository.java b/app/src/main/java/org/thoughtcrime/securesms/search/SearchRepository.java index a33f4dd11..ddfe85515 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/search/SearchRepository.java +++ b/app/src/main/java/org/thoughtcrime/securesms/search/SearchRepository.java @@ -301,10 +301,10 @@ public class SearchRepository { Recipient conversationRecipient = Recipient.from(context, conversationAddress, false); Recipient messageRecipient = Recipient.from(context, messageAddress, false); String body = cursor.getString(cursor.getColumnIndexOrThrow(SearchDatabase.SNIPPET)); - long receivedMs = cursor.getLong(cursor.getColumnIndexOrThrow(MmsSmsColumns.NORMALIZED_DATE_RECEIVED)); + long sentMs = cursor.getLong(cursor.getColumnIndexOrThrow(MmsSmsColumns.NORMALIZED_DATE_SENT)); long threadId = cursor.getLong(cursor.getColumnIndexOrThrow(MmsSmsColumns.THREAD_ID)); - return new MessageResult(conversationRecipient, messageRecipient, body, threadId, receivedMs); + return new MessageResult(conversationRecipient, messageRecipient, body, threadId, sentMs); } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/search/model/MessageResult.java b/app/src/main/java/org/thoughtcrime/securesms/search/model/MessageResult.java index 4523ab364..58e3f1a69 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/search/model/MessageResult.java +++ b/app/src/main/java/org/thoughtcrime/securesms/search/model/MessageResult.java @@ -13,18 +13,18 @@ public class MessageResult { public final Recipient messageRecipient; public final String bodySnippet; public final long threadId; - public final long receivedTimestampMs; + public final long sentTimestampMs; public MessageResult(@NonNull Recipient conversationRecipient, @NonNull Recipient messageRecipient, @NonNull String bodySnippet, long threadId, - long receivedTimestampMs) + long sentTimestampMs) { this.conversationRecipient = conversationRecipient; this.messageRecipient = messageRecipient; this.bodySnippet = bodySnippet; this.threadId = threadId; - this.receivedTimestampMs = receivedTimestampMs; + this.sentTimestampMs = sentTimestampMs; } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/service/DirectShareService.java b/app/src/main/java/org/thoughtcrime/securesms/service/DirectShareService.java index 13701300b..0516dc285 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/service/DirectShareService.java +++ b/app/src/main/java/org/thoughtcrime/securesms/service/DirectShareService.java @@ -6,14 +6,12 @@ import android.content.IntentFilter; import android.database.Cursor; import android.graphics.Bitmap; import android.graphics.drawable.Icon; -import android.os.Build; import android.os.Bundle; import android.os.Parcel; import android.service.chooser.ChooserTarget; import android.service.chooser.ChooserTargetService; import androidx.annotation.NonNull; -import androidx.annotation.RequiresApi; import org.session.libsession.utilities.recipients.Recipient; import org.session.libsignal.utilities.Log; @@ -28,7 +26,6 @@ import java.util.LinkedList; import java.util.List; import java.util.concurrent.ExecutionException; -@RequiresApi(api = Build.VERSION_CODES.M) public class DirectShareService extends ChooserTargetService { private static final String TAG = DirectShareService.class.getSimpleName(); @@ -40,53 +37,50 @@ public class DirectShareService extends ChooserTargetService { List results = new LinkedList<>(); ComponentName componentName = new ComponentName(this, ShareActivity.class); ThreadDatabase threadDatabase = DatabaseComponent.get(this).threadDatabase(); - Cursor cursor = threadDatabase.getDirectShareList(); - try { - ThreadDatabase.Reader reader = threadDatabase.readerFor(cursor); - ThreadRecord record; + try (Cursor cursor = threadDatabase.getDirectShareList()) { + ThreadDatabase.Reader reader = threadDatabase.readerFor(cursor); + ThreadRecord record; - while ((record = reader.getNext()) != null && results.size() < 10) { - Recipient recipient = Recipient.from(this, record.getRecipient().getAddress(), false); - String name = recipient.toShortString(); + while ((record = reader.getNext()) != null && results.size() < 10) { + Recipient recipient = Recipient.from(this, record.getRecipient().getAddress(), false); + String name = recipient.toShortString(); - Bitmap avatar; + Bitmap avatar; + + if (recipient.getContactPhoto() != null) { + try { + avatar = GlideApp.with(this) + .asBitmap() + .load(recipient.getContactPhoto()) + .circleCrop() + .submit(getResources().getDimensionPixelSize(android.R.dimen.notification_large_icon_width), + getResources().getDimensionPixelSize(android.R.dimen.notification_large_icon_width)) + .get(); + } catch (InterruptedException | ExecutionException e) { + Log.w(TAG, e); + avatar = getFallbackDrawable(recipient); + } + } else { + avatar = getFallbackDrawable(recipient); + } + + Parcel parcel = Parcel.obtain(); + parcel.writeParcelable(recipient.getAddress(), 0); + + Bundle bundle = new Bundle(); + bundle.putLong(ShareActivity.EXTRA_THREAD_ID, record.getThreadId()); + bundle.putByteArray(ShareActivity.EXTRA_ADDRESS_MARSHALLED, parcel.marshall()); + bundle.putInt(ShareActivity.EXTRA_DISTRIBUTION_TYPE, record.getDistributionType()); + bundle.setClassLoader(getClassLoader()); + + results.add(new ChooserTarget(name, Icon.createWithBitmap(avatar), 1.0f, componentName, bundle)); + parcel.recycle(); - if (recipient.getContactPhoto() != null) { - try { - avatar = GlideApp.with(this) - .asBitmap() - .load(recipient.getContactPhoto()) - .circleCrop() - .submit(getResources().getDimensionPixelSize(android.R.dimen.notification_large_icon_width), - getResources().getDimensionPixelSize(android.R.dimen.notification_large_icon_width)) - .get(); - } catch (InterruptedException | ExecutionException e) { - Log.w(TAG, e); - avatar = getFallbackDrawable(recipient); - } - } else { - avatar = getFallbackDrawable(recipient); } - Parcel parcel = Parcel.obtain(); - parcel.writeParcelable(recipient.getAddress(), 0); - - Bundle bundle = new Bundle(); - bundle.putLong(ShareActivity.EXTRA_THREAD_ID, record.getThreadId()); - bundle.putByteArray(ShareActivity.EXTRA_ADDRESS_MARSHALLED, parcel.marshall()); - bundle.putInt(ShareActivity.EXTRA_DISTRIBUTION_TYPE, record.getDistributionType()); - bundle.setClassLoader(getClassLoader()); - - results.add(new ChooserTarget(name, Icon.createWithBitmap(avatar), 1.0f, componentName, bundle)); - parcel.recycle(); - + return results; } - - return results; - } finally { - if (cursor != null) cursor.close(); - } } private Bitmap getFallbackDrawable(@NonNull Recipient recipient) { diff --git a/app/src/main/java/org/thoughtcrime/securesms/service/ExpirationListener.java b/app/src/main/java/org/thoughtcrime/securesms/service/ExpirationListener.java index 4a83707dd..a0ef945c9 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/service/ExpirationListener.java +++ b/app/src/main/java/org/thoughtcrime/securesms/service/ExpirationListener.java @@ -17,7 +17,7 @@ public class ExpirationListener extends BroadcastReceiver { public static void setAlarm(Context context, long waitTimeMillis) { Intent intent = new Intent(context, ExpirationListener.class); - PendingIntent pendingIntent = PendingIntent.getBroadcast(context, 0, intent, 0); + PendingIntent pendingIntent = PendingIntent.getBroadcast(context, 0, intent, PendingIntent.FLAG_IMMUTABLE); AlarmManager alarmManager = (AlarmManager)context.getSystemService(Context.ALARM_SERVICE); alarmManager.cancel(pendingIntent); diff --git a/app/src/main/java/org/thoughtcrime/securesms/service/GenericForegroundService.java b/app/src/main/java/org/thoughtcrime/securesms/service/GenericForegroundService.java index 0581883c5..52a259d5b 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/service/GenericForegroundService.java +++ b/app/src/main/java/org/thoughtcrime/securesms/service/GenericForegroundService.java @@ -6,6 +6,7 @@ import android.app.Service; import android.content.Context; import android.content.Intent; import android.os.IBinder; + import androidx.annotation.DrawableRes; import androidx.annotation.NonNull; import androidx.annotation.Nullable; @@ -13,9 +14,9 @@ import androidx.core.app.NotificationCompat; import androidx.core.content.ContextCompat; import org.session.libsignal.utilities.Log; +import org.session.libsignal.utilities.guava.Preconditions; import org.thoughtcrime.securesms.home.HomeActivity; import org.thoughtcrime.securesms.notifications.NotificationChannels; -import org.session.libsignal.utilities.guava.Preconditions; import network.loki.messenger.R; @@ -87,10 +88,10 @@ public class GenericForegroundService extends Service { } private void postObligatoryForegroundNotification(String title, String channelId, @DrawableRes int iconRes) { - startForeground(NOTIFICATION_ID, new NotificationCompat.Builder(this, channelId) + startForeground(NOTIFICATION_ID, new NotificationCompat.Builder(this, channelId) .setSmallIcon(iconRes) .setContentTitle(title) - .setContentIntent(PendingIntent.getActivity(this, 0, new Intent(this, HomeActivity.class), 0)) + .setContentIntent(PendingIntent.getActivity(this, 0, new Intent(this, HomeActivity.class), PendingIntent.FLAG_IMMUTABLE)) .build()); } diff --git a/app/src/main/java/org/thoughtcrime/securesms/service/KeyCachingService.java b/app/src/main/java/org/thoughtcrime/securesms/service/KeyCachingService.java index 9e79b93d6..402c0f652 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/service/KeyCachingService.java +++ b/app/src/main/java/org/thoughtcrime/securesms/service/KeyCachingService.java @@ -29,18 +29,19 @@ import android.os.AsyncTask; import android.os.Binder; import android.os.IBinder; import android.os.SystemClock; + import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.core.app.NotificationCompat; +import org.session.libsession.utilities.ServiceUtil; +import org.session.libsession.utilities.TextSecurePreferences; +import org.session.libsignal.utilities.Log; import org.thoughtcrime.securesms.ApplicationContext; import org.thoughtcrime.securesms.DatabaseUpgradeActivity; import org.thoughtcrime.securesms.DummyActivity; -import org.session.libsignal.utilities.Log; import org.thoughtcrime.securesms.home.HomeActivity; import org.thoughtcrime.securesms.notifications.NotificationChannels; -import org.session.libsession.utilities.ServiceUtil; -import org.session.libsession.utilities.TextSecurePreferences; import java.util.concurrent.TimeUnit; @@ -255,18 +256,18 @@ public class KeyCachingService extends Service { private PendingIntent buildLockIntent() { Intent intent = new Intent(this, KeyCachingService.class); intent.setAction(PASSPHRASE_EXPIRED_EVENT); - return PendingIntent.getService(getApplicationContext(), 0, intent, 0); + return PendingIntent.getService(getApplicationContext(), 0, intent, PendingIntent.FLAG_IMMUTABLE); } private PendingIntent buildLaunchIntent() { Intent intent = new Intent(this, HomeActivity.class); intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP); - return PendingIntent.getActivity(getApplicationContext(), 0, intent, 0); + return PendingIntent.getActivity(getApplicationContext(), 0, intent, PendingIntent.FLAG_IMMUTABLE); } private static PendingIntent buildExpirationPendingIntent(@NonNull Context context) { Intent expirationIntent = new Intent(PASSPHRASE_EXPIRED_EVENT, null, context, KeyCachingService.class); - return PendingIntent.getService(context, 0, expirationIntent, 0); + return PendingIntent.getService(context, 0, expirationIntent, PendingIntent.FLAG_IMMUTABLE); } @Override diff --git a/app/src/main/java/org/thoughtcrime/securesms/service/PersistentAlarmManagerListener.java b/app/src/main/java/org/thoughtcrime/securesms/service/PersistentAlarmManagerListener.java index 4091d953a..f24906c5e 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/service/PersistentAlarmManagerListener.java +++ b/app/src/main/java/org/thoughtcrime/securesms/service/PersistentAlarmManagerListener.java @@ -6,6 +6,7 @@ import android.app.PendingIntent; import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; + import org.session.libsignal.utilities.Log; public abstract class PersistentAlarmManagerListener extends BroadcastReceiver { @@ -21,7 +22,7 @@ public abstract class PersistentAlarmManagerListener extends BroadcastReceiver { long scheduledTime = getNextScheduledExecutionTime(context); AlarmManager alarmManager = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE); Intent alarmIntent = new Intent(context, getClass()); - PendingIntent pendingIntent = PendingIntent.getBroadcast(context, 0, alarmIntent, 0); + PendingIntent pendingIntent = PendingIntent.getBroadcast(context, 0, alarmIntent, PendingIntent.FLAG_IMMUTABLE); if (System.currentTimeMillis() >= scheduledTime) { scheduledTime = onAlarm(context, scheduledTime); diff --git a/app/src/main/java/org/thoughtcrime/securesms/service/UpdateApkReadyListener.java b/app/src/main/java/org/thoughtcrime/securesms/service/UpdateApkReadyListener.java index 323617d81..eea6ba00f 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/service/UpdateApkReadyListener.java +++ b/app/src/main/java/org/thoughtcrime/securesms/service/UpdateApkReadyListener.java @@ -12,21 +12,22 @@ import android.net.Uri; import androidx.annotation.Nullable; import androidx.core.app.NotificationCompat; -import org.session.libsignal.utilities.Log; -import network.loki.messenger.R; -import org.thoughtcrime.securesms.notifications.NotificationChannels; -import org.thoughtcrime.securesms.util.FileProviderUtil; import org.session.libsession.utilities.FileUtils; -import org.session.libsignal.utilities.Hex; import org.session.libsession.utilities.ServiceUtil; import org.session.libsession.utilities.TextSecurePreferences; +import org.session.libsignal.utilities.Hex; +import org.session.libsignal.utilities.Log; +import org.thoughtcrime.securesms.notifications.NotificationChannels; +import org.thoughtcrime.securesms.util.FileProviderUtil; import java.io.File; import java.io.FileInputStream; import java.io.IOException; import java.security.MessageDigest; +import network.loki.messenger.R; + public class UpdateApkReadyListener extends BroadcastReceiver { private static final String TAG = UpdateApkReadyListener.class.getSimpleName(); @@ -61,7 +62,7 @@ public class UpdateApkReadyListener extends BroadcastReceiver { intent.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); intent.setData(uri); - PendingIntent pendingIntent = PendingIntent.getActivity(context, 0, intent, 0); + PendingIntent pendingIntent = PendingIntent.getActivity(context, 0, intent, PendingIntent.FLAG_IMMUTABLE); Notification notification = new NotificationCompat.Builder(context, NotificationChannels.APP_UPDATES) .setOngoing(true) diff --git a/app/src/main/java/org/thoughtcrime/securesms/service/WebRtcCallService.kt b/app/src/main/java/org/thoughtcrime/securesms/service/WebRtcCallService.kt index 0f10a93b0..d09933ab8 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/service/WebRtcCallService.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/service/WebRtcCallService.kt @@ -7,10 +7,13 @@ import android.content.Intent import android.content.Intent.FLAG_ACTIVITY_BROUGHT_TO_FRONT import android.content.Intent.FLAG_ACTIVITY_NEW_TASK import android.content.IntentFilter +import android.content.pm.PackageManager import android.media.AudioManager +import android.os.Build import android.os.IBinder import android.os.ResultReceiver import android.telephony.PhoneStateListener +import android.telephony.PhoneStateListener.LISTEN_NONE import android.telephony.TelephonyManager import androidx.core.content.ContextCompat import androidx.core.os.bundleOf @@ -28,30 +31,13 @@ import org.thoughtcrime.securesms.util.CallNotificationBuilder.Companion.TYPE_IN import org.thoughtcrime.securesms.util.CallNotificationBuilder.Companion.TYPE_INCOMING_PRE_OFFER import org.thoughtcrime.securesms.util.CallNotificationBuilder.Companion.TYPE_INCOMING_RINGING import org.thoughtcrime.securesms.util.CallNotificationBuilder.Companion.TYPE_OUTGOING_RINGING -import org.thoughtcrime.securesms.webrtc.AudioManagerCommand -import org.thoughtcrime.securesms.webrtc.CallManager -import org.thoughtcrime.securesms.webrtc.CallViewModel -import org.thoughtcrime.securesms.webrtc.HangUpRtcOnPstnCallAnsweredListener -import org.thoughtcrime.securesms.webrtc.IncomingPstnCallReceiver -import org.thoughtcrime.securesms.webrtc.NetworkChangeReceiver -import org.thoughtcrime.securesms.webrtc.PeerConnectionException -import org.thoughtcrime.securesms.webrtc.PowerButtonReceiver -import org.thoughtcrime.securesms.webrtc.ProximityLockRelease -import org.thoughtcrime.securesms.webrtc.UncaughtExceptionHandlerManager -import org.thoughtcrime.securesms.webrtc.WiredHeadsetStateReceiver +import org.thoughtcrime.securesms.webrtc.* import org.thoughtcrime.securesms.webrtc.audio.OutgoingRinger import org.thoughtcrime.securesms.webrtc.data.Event import org.thoughtcrime.securesms.webrtc.locks.LockManager -import org.webrtc.DataChannel -import org.webrtc.IceCandidate -import org.webrtc.MediaStream -import org.webrtc.PeerConnection -import org.webrtc.PeerConnection.IceConnectionState.CONNECTED -import org.webrtc.PeerConnection.IceConnectionState.DISCONNECTED -import org.webrtc.PeerConnection.IceConnectionState.FAILED -import org.webrtc.RtpReceiver -import org.webrtc.SessionDescription -import java.util.UUID +import org.webrtc.* +import org.webrtc.PeerConnection.IceConnectionState.* +import java.util.* import java.util.concurrent.ExecutionException import java.util.concurrent.Executors import java.util.concurrent.ScheduledFuture @@ -60,7 +46,7 @@ import javax.inject.Inject import org.thoughtcrime.securesms.webrtc.data.State as CallState @AndroidEntryPoint -class WebRtcCallService: Service(), CallManager.WebRtcListener { +class WebRtcCallService : Service(), CallManager.WebRtcListener { companion object { @@ -108,62 +94,82 @@ class WebRtcCallService: Service(), CallManager.WebRtcListener { private const val RECONNECT_SECONDS = 5L private const val MAX_RECONNECTS = 5 - fun cameraEnabled(context: Context, enabled: Boolean) = Intent(context, WebRtcCallService::class.java) + fun cameraEnabled(context: Context, enabled: Boolean) = + Intent(context, WebRtcCallService::class.java) .setAction(ACTION_SET_MUTE_VIDEO) .putExtra(EXTRA_MUTE, !enabled) fun flipCamera(context: Context) = Intent(context, WebRtcCallService::class.java) - .setAction(ACTION_FLIP_CAMERA) + .setAction(ACTION_FLIP_CAMERA) fun acceptCallIntent(context: Context) = Intent(context, WebRtcCallService::class.java) - .setAction(ACTION_ANSWER_CALL) + .setAction(ACTION_ANSWER_CALL) - fun microphoneIntent(context: Context, enabled: Boolean) = Intent(context, WebRtcCallService::class.java) - .setAction(ACTION_SET_MUTE_AUDIO) - .putExtra(EXTRA_MUTE, !enabled) + fun microphoneIntent(context: Context, enabled: Boolean) = + Intent(context, WebRtcCallService::class.java) + .setAction(ACTION_SET_MUTE_AUDIO) + .putExtra(EXTRA_MUTE, !enabled) - fun createCall(context: Context, recipient: Recipient) = Intent(context, WebRtcCallService::class.java) + fun createCall(context: Context, recipient: Recipient) = + Intent(context, WebRtcCallService::class.java) .setAction(ACTION_OUTGOING_CALL) .putExtra(EXTRA_RECIPIENT_ADDRESS, recipient.address) - fun incomingCall(context: Context, address: Address, sdp: String, callId: UUID, callTime: Long) = - Intent(context, WebRtcCallService::class.java) - .setAction(ACTION_INCOMING_RING) - .putExtra(EXTRA_RECIPIENT_ADDRESS, address) - .putExtra(EXTRA_CALL_ID, callId) - .putExtra(EXTRA_REMOTE_DESCRIPTION, sdp) - .putExtra(EXTRA_TIMESTAMP, callTime) + fun incomingCall( + context: Context, + address: Address, + sdp: String, + callId: UUID, + callTime: Long + ) = + Intent(context, WebRtcCallService::class.java) + .setAction(ACTION_INCOMING_RING) + .putExtra(EXTRA_RECIPIENT_ADDRESS, address) + .putExtra(EXTRA_CALL_ID, callId) + .putExtra(EXTRA_REMOTE_DESCRIPTION, sdp) + .putExtra(EXTRA_TIMESTAMP, callTime) fun incomingAnswer(context: Context, address: Address, sdp: String, callId: UUID) = - Intent(context, WebRtcCallService::class.java) - .setAction(ACTION_RESPONSE_MESSAGE) - .putExtra(EXTRA_RECIPIENT_ADDRESS, address) - .putExtra(EXTRA_CALL_ID, callId) - .putExtra(EXTRA_REMOTE_DESCRIPTION, sdp) + Intent(context, WebRtcCallService::class.java) + .setAction(ACTION_RESPONSE_MESSAGE) + .putExtra(EXTRA_RECIPIENT_ADDRESS, address) + .putExtra(EXTRA_CALL_ID, callId) + .putExtra(EXTRA_REMOTE_DESCRIPTION, sdp) fun preOffer(context: Context, address: Address, callId: UUID, callTime: Long) = - Intent(context, WebRtcCallService::class.java) - .setAction(ACTION_PRE_OFFER) - .putExtra(EXTRA_RECIPIENT_ADDRESS, address) - .putExtra(EXTRA_CALL_ID, callId) - .putExtra(EXTRA_TIMESTAMP, callTime) + Intent(context, WebRtcCallService::class.java) + .setAction(ACTION_PRE_OFFER) + .putExtra(EXTRA_RECIPIENT_ADDRESS, address) + .putExtra(EXTRA_CALL_ID, callId) + .putExtra(EXTRA_TIMESTAMP, callTime) - fun iceCandidates(context: Context, address: Address, iceCandidates: List, callId: UUID) = - Intent(context, WebRtcCallService::class.java) - .setAction(ACTION_ICE_MESSAGE) - .putExtra(EXTRA_CALL_ID, callId) - .putExtra(EXTRA_ICE_SDP, iceCandidates.map(IceCandidate::sdp).toTypedArray()) - .putExtra(EXTRA_ICE_SDP_LINE_INDEX, iceCandidates.map(IceCandidate::sdpMLineIndex).toIntArray()) - .putExtra(EXTRA_ICE_SDP_MID, iceCandidates.map(IceCandidate::sdpMid).toTypedArray()) - .putExtra(EXTRA_RECIPIENT_ADDRESS, address) + fun iceCandidates( + context: Context, + address: Address, + iceCandidates: List, + callId: UUID + ) = + Intent(context, WebRtcCallService::class.java) + .setAction(ACTION_ICE_MESSAGE) + .putExtra(EXTRA_CALL_ID, callId) + .putExtra(EXTRA_ICE_SDP, iceCandidates.map(IceCandidate::sdp).toTypedArray()) + .putExtra( + EXTRA_ICE_SDP_LINE_INDEX, + iceCandidates.map(IceCandidate::sdpMLineIndex).toIntArray() + ) + .putExtra(EXTRA_ICE_SDP_MID, iceCandidates.map(IceCandidate::sdpMid).toTypedArray()) + .putExtra(EXTRA_RECIPIENT_ADDRESS, address) - fun denyCallIntent(context: Context) = Intent(context, WebRtcCallService::class.java).setAction(ACTION_DENY_CALL) + fun denyCallIntent(context: Context) = + Intent(context, WebRtcCallService::class.java).setAction(ACTION_DENY_CALL) - fun remoteHangupIntent(context: Context, callId: UUID) = Intent(context, WebRtcCallService::class.java) + fun remoteHangupIntent(context: Context, callId: UUID) = + Intent(context, WebRtcCallService::class.java) .setAction(ACTION_REMOTE_HANGUP) .putExtra(EXTRA_CALL_ID, callId) - fun hangupIntent(context: Context) = Intent(context, WebRtcCallService::class.java).setAction(ACTION_LOCAL_HANGUP) + fun hangupIntent(context: Context) = + Intent(context, WebRtcCallService::class.java).setAction(ACTION_LOCAL_HANGUP) fun sendAudioManagerCommand(context: Context, command: AudioManagerCommand) { val intent = Intent(context, WebRtcCallService::class.java) @@ -174,7 +180,7 @@ class WebRtcCallService: Service(), CallManager.WebRtcListener { fun broadcastWantsToAnswer(context: Context, wantsToAnswer: Boolean) { val intent = Intent(ACTION_WANTS_TO_ANSWER) - .putExtra(EXTRA_WANTS_TO_ANSWER, wantsToAnswer) + .putExtra(EXTRA_WANTS_TO_ANSWER, wantsToAnswer) LocalBroadcastManager.getInstance(context).sendBroadcast(intent) } @@ -182,13 +188,14 @@ class WebRtcCallService: Service(), CallManager.WebRtcListener { @JvmStatic fun isCallActive(context: Context, resultReceiver: ResultReceiver) { val intent = Intent(context, WebRtcCallService::class.java) - .setAction(ACTION_IS_IN_CALL_QUERY) - .putExtra(EXTRA_RESULT_RECEIVER, resultReceiver) + .setAction(ACTION_IS_IN_CALL_QUERY) + .putExtra(EXTRA_RESULT_RECEIVER, resultReceiver) context.startService(intent) } } - @Inject lateinit var callManager: CallManager + @Inject + lateinit var callManager: CallManager private var wantsToAnswer = false private var currentTimeouts = 0 @@ -199,8 +206,17 @@ class WebRtcCallService: Service(), CallManager.WebRtcListener { private val lockManager by lazy { LockManager(this) } private val serviceExecutor = Executors.newSingleThreadExecutor() private val timeoutExecutor = Executors.newScheduledThreadPool(1) - private val hangupOnCallAnswered = HangUpRtcOnPstnCallAnsweredListener { - ContextCompat.startForegroundService(this, hangupIntent(this)) + + private val hangupOnCallAnswered by lazy { + HangUpRtcOnPstnCallAnsweredListener { + ContextCompat.startForegroundService(this, hangupIntent(this)) + } + } + + private val hangupTelephonyCallback by lazy { + HangUpRtcTelephonyCallback { + ContextCompat.startForegroundService(this, hangupIntent(this)) + } } private var networkChangedReceiver: NetworkChangeReceiver? = null @@ -258,7 +274,9 @@ class WebRtcCallService: Service(), CallManager.WebRtcListener { val action = intent.action Log.i("Loki", "Handling ${intent.action}") when { - action == ACTION_INCOMING_RING && isSameCall(intent) && callManager.currentConnectionState == CallState.Reconnecting -> handleNewOffer(intent) + action == ACTION_INCOMING_RING && isSameCall(intent) && callManager.currentConnectionState == CallState.Reconnecting -> handleNewOffer( + intent + ) action == ACTION_PRE_OFFER && isIdle() -> handlePreOffer(intent) action == ACTION_INCOMING_RING && isBusy(intent) -> handleBusyCall(intent) action == ACTION_INCOMING_RING && isPreOffer() -> handleIncomingRing(intent) @@ -272,7 +290,9 @@ class WebRtcCallService: Service(), CallManager.WebRtcListener { action == ACTION_FLIP_CAMERA -> handleSetCameraFlip(intent) action == ACTION_WIRED_HEADSET_CHANGE -> handleWiredHeadsetChanged(intent) action == ACTION_SCREEN_OFF -> handleScreenOffChange(intent) - action == ACTION_RESPONSE_MESSAGE && isSameCall(intent) && callManager.currentConnectionState == CallState.Reconnecting -> handleResponseMessage(intent) + action == ACTION_RESPONSE_MESSAGE && isSameCall(intent) && callManager.currentConnectionState == CallState.Reconnecting -> handleResponseMessage( + intent + ) action == ACTION_RESPONSE_MESSAGE -> handleResponseMessage(intent) action == ACTION_ICE_MESSAGE -> handleRemoteIceCandidate(intent) action == ACTION_ICE_CONNECTED -> handleIceConnected(intent) @@ -293,8 +313,15 @@ class WebRtcCallService: Service(), CallManager.WebRtcListener { registerIncomingPstnCallReceiver() registerWiredHeadsetStateReceiver() registerWantsToAnswerReceiver() - getSystemService(TelephonyManager::class.java) - .listen(hangupOnCallAnswered, PhoneStateListener.LISTEN_CALL_STATE) + if (checkSelfPermission(android.Manifest.permission.READ_PHONE_STATE) == PackageManager.PERMISSION_GRANTED) { + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.S) { + getSystemService(TelephonyManager::class.java) + .listen(hangupOnCallAnswered, PhoneStateListener.LISTEN_CALL_STATE) + } else { + getSystemService(TelephonyManager::class.java) + .registerTelephonyCallback(serviceExecutor, hangupTelephonyCallback) + } + } registerUncaughtExceptionHandler() networkChangedReceiver = NetworkChangeReceiver(::networkChange) networkChangedReceiver!!.register(this) @@ -318,7 +345,8 @@ class WebRtcCallService: Service(), CallManager.WebRtcListener { } } wantsToAnswerReceiver = receiver - LocalBroadcastManager.getInstance(this).registerReceiver(receiver, IntentFilter(ACTION_WANTS_TO_ANSWER)) + LocalBroadcastManager.getInstance(this) + .registerReceiver(receiver, IntentFilter(ACTION_WANTS_TO_ANSWER)) } private fun registerWiredHeadsetStateReceiver() { @@ -339,7 +367,11 @@ class WebRtcCallService: Service(), CallManager.WebRtcListener { private fun handleUpdateAudio(intent: Intent) { val audioCommand = intent.getParcelableExtra(EXTRA_AUDIO_COMMAND)!! - if (callManager.currentConnectionState !in arrayOf(CallState.Connected, *CallState.PENDING_CONNECTION_STATES)) { + if (callManager.currentConnectionState !in arrayOf( + CallState.Connected, + *CallState.PENDING_CONNECTION_STATES + ) + ) { Log.w(TAG, "handling audio command not in call") return } @@ -419,8 +451,15 @@ class WebRtcCallService: Service(), CallManager.WebRtcListener { callManager.initializeAudioForCall() callManager.startOutgoingRinger(OutgoingRinger.Type.RINGING) setCallInProgressNotification(TYPE_OUTGOING_RINGING, callManager.recipient) - callManager.insertCallMessage(recipient.address.serialize(), CallMessageType.CALL_OUTGOING) - scheduledTimeout = timeoutExecutor.schedule(TimeoutRunnable(callId, this), TIMEOUT_SECONDS, TimeUnit.SECONDS) + callManager.insertCallMessage( + recipient.address.serialize(), + CallMessageType.CALL_OUTGOING + ) + scheduledTimeout = timeoutExecutor.schedule( + TimeoutRunnable(callId, this), + TIMEOUT_SECONDS, + TimeUnit.SECONDS + ) callManager.setAudioEnabled(true) val expectedState = callManager.currentConnectionState @@ -429,15 +468,21 @@ class WebRtcCallService: Service(), CallManager.WebRtcListener { try { val offerFuture = callManager.onOutgoingCall(this) offerFuture.fail { e -> - if (isConsistentState(expectedState, expectedCallId, callManager.currentConnectionState, callManager.callId)) { - Log.e(TAG,e) + if (isConsistentState( + expectedState, + expectedCallId, + callManager.currentConnectionState, + callManager.callId + ) + ) { + Log.e(TAG, e) callManager.postViewModelState(CallViewModel.State.NETWORK_FAILURE) callManager.postConnectionError() terminate() } } } catch (e: Exception) { - Log.e(TAG,e) + Log.e(TAG, e) callManager.postConnectionError() terminate() } @@ -476,7 +521,11 @@ class WebRtcCallService: Service(), CallManager.WebRtcListener { callManager.silenceIncomingRinger() callManager.postViewModelState(CallViewModel.State.CALL_INCOMING) - scheduledTimeout = timeoutExecutor.schedule(TimeoutRunnable(callId, this), TIMEOUT_SECONDS, TimeUnit.SECONDS) + scheduledTimeout = timeoutExecutor.schedule( + TimeoutRunnable(callId, this), + TIMEOUT_SECONDS, + TimeUnit.SECONDS + ) callManager.initializeAudioForCall() callManager.initializeVideo(this) @@ -487,7 +536,13 @@ class WebRtcCallService: Service(), CallManager.WebRtcListener { try { val answerFuture = callManager.onIncomingCall(this) answerFuture.fail { e -> - if (isConsistentState(expectedState,expectedCallId, callManager.currentConnectionState, callManager.callId)) { + if (isConsistentState( + expectedState, + expectedCallId, + callManager.currentConnectionState, + callManager.callId + ) + ) { Log.e(TAG, e) insertMissedCall(recipient, true) callManager.postConnectionError() @@ -497,7 +552,7 @@ class WebRtcCallService: Service(), CallManager.WebRtcListener { lockManager.updatePhoneState(LockManager.PhoneState.PROCESSING) callManager.setAudioEnabled(true) } catch (e: Exception) { - Log.e(TAG,e) + Log.e(TAG, e) callManager.postConnectionError() terminate() } @@ -518,6 +573,7 @@ class WebRtcCallService: Service(), CallManager.WebRtcListener { private fun handleRemoteHangup(intent: Intent) { if (callManager.callId != getCallId(intent)) { Log.e(TAG, "Hangup for non-active call...") + stopForeground(true) return } @@ -555,7 +611,11 @@ class WebRtcCallService: Service(), CallManager.WebRtcListener { } val callId = getCallId(intent) val description = intent.getStringExtra(EXTRA_REMOTE_DESCRIPTION) - callManager.handleResponseMessage(recipient, callId, SessionDescription(SessionDescription.Type.ANSWER, description)) + callManager.handleResponseMessage( + recipient, + callId, + SessionDescription(SessionDescription.Type.ANSWER, description) + ) } catch (e: PeerConnectionException) { terminate() } @@ -567,14 +627,14 @@ class WebRtcCallService: Service(), CallManager.WebRtcListener { val sdpLineIndexes = intent.getIntArrayExtra(EXTRA_ICE_SDP_LINE_INDEX) ?: return val sdps = intent.getStringArrayExtra(EXTRA_ICE_SDP) ?: return if (sdpMids.size != sdpLineIndexes.size || sdpLineIndexes.size != sdps.size) { - Log.w(TAG,"sdp info not of equal length") + Log.w(TAG, "sdp info not of equal length") return } val iceCandidates = sdpMids.indices.map { index -> IceCandidate( - sdpMids[index], - sdpLineIndexes[index], - sdps[index] + sdpMids[index], + sdpLineIndexes[index], + sdps[index] ) } callManager.handleRemoteIceCandidate(iceCandidates, callId) @@ -597,7 +657,11 @@ class WebRtcCallService: Service(), CallManager.WebRtcListener { private fun handleIsInCallQuery(intent: Intent) { val listener = intent.getParcelableExtra(EXTRA_RESULT_RECEIVER) ?: return val currentState = callManager.currentConnectionState - val isInCall = if (currentState in arrayOf(*CallState.PENDING_CONNECTION_STATES, CallState.Connected)) 1 else 0 + val isInCall = if (currentState in arrayOf( + *CallState.PENDING_CONNECTION_STATES, + CallState.Connected + ) + ) 1 else 0 listener.send(isInCall, bundleOf()) } @@ -616,10 +680,21 @@ class WebRtcCallService: Service(), CallManager.WebRtcListener { if (callId == getCallId(intent) && isNetworkAvailable && numTimeouts <= MAX_RECONNECTS) { Log.i("Loki", "Trying to re-connect") callManager.networkReestablished() - scheduledTimeout = timeoutExecutor.schedule(TimeoutRunnable(callId, this), TIMEOUT_SECONDS, TimeUnit.SECONDS) + scheduledTimeout = timeoutExecutor.schedule( + TimeoutRunnable(callId, this), + TIMEOUT_SECONDS, + TimeUnit.SECONDS + ) } else if (numTimeouts < MAX_RECONNECTS) { - Log.i("Loki", "Network isn't available, timeouts == $numTimeouts out of $MAX_RECONNECTS") - scheduledReconnect = timeoutExecutor.schedule(CheckReconnectedRunnable(callId, this), RECONNECT_SECONDS, TimeUnit.SECONDS) + Log.i( + "Loki", + "Network isn't available, timeouts == $numTimeouts out of $MAX_RECONNECTS" + ) + scheduledReconnect = timeoutExecutor.schedule( + CheckReconnectedRunnable(callId, this), + RECONNECT_SECONDS, + TimeUnit.SECONDS + ) } else { Log.i("Loki", "Network isn't available, timing out") handleLocalHangup(intent) @@ -627,12 +702,15 @@ class WebRtcCallService: Service(), CallManager.WebRtcListener { } - private fun handleCheckTimeout(intent: Intent) { val callId = callManager.callId ?: return val callState = callManager.currentConnectionState - if (callId == getCallId(intent) && (callState !in arrayOf(CallState.Connected, CallState.Connecting))) { + if (callId == getCallId(intent) && (callState !in arrayOf( + CallState.Connected, + CallState.Connecting + )) + ) { Log.w(TAG, "Timing out call: $callId") handleLocalHangup(intent) } @@ -640,8 +718,8 @@ class WebRtcCallService: Service(), CallManager.WebRtcListener { private fun setCallInProgressNotification(type: Int, recipient: Recipient?) { startForeground( - CallNotificationBuilder.WEBRTC_NOTIFICATION, - CallNotificationBuilder.getCallInProgressNotification(this, type, recipient) + CallNotificationBuilder.WEBRTC_NOTIFICATION, + CallNotificationBuilder.getCallInProgressNotification(this, type, recipient) ) if (!CallNotificationBuilder.areNotificationsEnabled(this) && type == TYPE_INCOMING_PRE_OFFER) { // start an intent for the fullscreen @@ -661,14 +739,14 @@ class WebRtcCallService: Service(), CallManager.WebRtcListener { private fun getRemoteRecipient(intent: Intent): Recipient { val remoteAddress = intent.getParcelableExtra
(EXTRA_RECIPIENT_ADDRESS) - ?: throw AssertionError("No recipient in intent!") + ?: throw AssertionError("No recipient in intent!") return Recipient.from(this, remoteAddress, true) } - private fun getCallId(intent: Intent) : UUID { + private fun getCallId(intent: Intent): UUID { return intent.getSerializableExtra(EXTRA_CALL_ID) as? UUID - ?: throw AssertionError("No callId in intent!") + ?: throw AssertionError("No callId in intent!") } private fun insertMissedCall(recipient: Recipient, signal: Boolean) { @@ -680,10 +758,13 @@ class WebRtcCallService: Service(), CallManager.WebRtcListener { } private fun isIncomingMessageExpired(intent: Intent) = - System.currentTimeMillis() - intent.getLongExtra(EXTRA_TIMESTAMP, -1) > TimeUnit.SECONDS.toMillis(TIMEOUT_SECONDS) + System.currentTimeMillis() - intent.getLongExtra( + EXTRA_TIMESTAMP, + -1 + ) > TimeUnit.SECONDS.toMillis(TIMEOUT_SECONDS) override fun onDestroy() { - Log.d(TAG,"onDestroy()") + Log.d(TAG, "onDestroy()") callManager.unregisterListener(this) callReceiver?.let { receiver -> unregisterReceiver(receiver) @@ -698,6 +779,16 @@ class WebRtcCallService: Service(), CallManager.WebRtcListener { wantsToAnswer = false currentTimeouts = 0 isNetworkAvailable = false + if (checkSelfPermission(android.Manifest.permission.READ_PHONE_STATE) == PackageManager.PERMISSION_GRANTED) { + val telephonyManager = getSystemService(TelephonyManager::class.java) + with(telephonyManager) { + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.S) { + this.listen(hangupOnCallAnswered, LISTEN_NONE) + } else { + this.unregisterTelephonyCallback(hangupTelephonyCallback) + } + } + } super.onDestroy() } @@ -709,7 +800,8 @@ class WebRtcCallService: Service(), CallManager.WebRtcListener { } } - private class CheckReconnectedRunnable(private val callId: UUID, private val context: Context): Runnable { + private class CheckReconnectedRunnable(private val callId: UUID, private val context: Context) : + Runnable { override fun run() { val intent = Intent(context, WebRtcCallService::class.java) .setAction(ACTION_CHECK_RECONNECT) @@ -718,7 +810,8 @@ class WebRtcCallService: Service(), CallManager.WebRtcListener { } } - private class ReconnectTimeoutRunnable(private val callId: UUID, private val context: Context): Runnable { + private class ReconnectTimeoutRunnable(private val callId: UUID, private val context: Context) : + Runnable { override fun run() { val intent = Intent(context, WebRtcCallService::class.java) .setAction(ACTION_CHECK_RECONNECT_TIMEOUT) @@ -727,26 +820,29 @@ class WebRtcCallService: Service(), CallManager.WebRtcListener { } } - private class TimeoutRunnable(private val callId: UUID, private val context: Context): Runnable { + private class TimeoutRunnable(private val callId: UUID, private val context: Context) : + Runnable { override fun run() { val intent = Intent(context, WebRtcCallService::class.java) - .setAction(ACTION_CHECK_TIMEOUT) - .putExtra(EXTRA_CALL_ID, callId) + .setAction(ACTION_CHECK_TIMEOUT) + .putExtra(EXTRA_CALL_ID, callId) context.startService(intent) } } private abstract class FailureListener( - expectedState: CallState, - expectedCallId: UUID?, - getState: () -> Pair): StateAwareListener(expectedState, expectedCallId, getState) { + expectedState: CallState, + expectedCallId: UUID?, + getState: () -> Pair + ) : StateAwareListener(expectedState, expectedCallId, getState) { override fun onSuccessContinue(result: V) {} } private abstract class SuccessOnlyListener( - expectedState: CallState, - expectedCallId: UUID?, - getState: () -> Pair): StateAwareListener(expectedState, expectedCallId, getState) { + expectedState: CallState, + expectedCallId: UUID?, + getState: () -> Pair + ) : StateAwareListener(expectedState, expectedCallId, getState) { override fun onFailureContinue(throwable: Throwable?) { Log.e(TAG, throwable) throw AssertionError(throwable) @@ -754,9 +850,10 @@ class WebRtcCallService: Service(), CallManager.WebRtcListener { } private abstract class StateAwareListener( - private val expectedState: CallState, - private val expectedCallId: UUID?, - private val getState: ()->Pair): FutureTaskListener { + private val expectedState: CallState, + private val expectedCallId: UUID?, + private val getState: () -> Pair + ) : FutureTaskListener { companion object { private val TAG = Log.tag(StateAwareListener::class.java) @@ -764,7 +861,7 @@ class WebRtcCallService: Service(), CallManager.WebRtcListener { override fun onSuccess(result: V) { if (!isConsistentState()) { - Log.w(TAG,"State has changed since request, aborting success callback...") + Log.w(TAG, "State has changed since request, aborting success callback...") } else { onSuccessContinue(result) } @@ -773,7 +870,7 @@ class WebRtcCallService: Service(), CallManager.WebRtcListener { override fun onFailure(exception: ExecutionException?) { if (!isConsistentState()) { Log.w(TAG, exception) - Log.w(TAG,"State has changed since request, aborting failure callback...") + Log.w(TAG, "State has changed since request, aborting failure callback...") } else { exception?.let { onFailureContinue(it.cause) @@ -792,10 +889,10 @@ class WebRtcCallService: Service(), CallManager.WebRtcListener { } private fun isConsistentState( - expectedState: CallState, - expectedCallId: UUID?, - currentState: CallState, - currentCallId: UUID? + expectedState: CallState, + expectedCallId: UUID?, + currentState: CallState, + currentCallId: UUID? ): Boolean { return expectedState == currentState && expectedCallId == currentCallId } @@ -817,17 +914,29 @@ class WebRtcCallService: Service(), CallManager.WebRtcListener { val intent = Intent(this, WebRtcCallService::class.java) .setAction(ACTION_ICE_CONNECTED) startService(intent) - } else if (newState in arrayOf(FAILED, DISCONNECTED) && (scheduledReconnect == null && scheduledTimeout == null)) { + } else if (newState in arrayOf( + FAILED, + DISCONNECTED + ) && (scheduledReconnect == null && scheduledTimeout == null) + ) { callManager.callId?.let { callId -> callManager.postConnectionEvent(Event.IceDisconnect) { callManager.postViewModelState(CallViewModel.State.CALL_RECONNECTING) if (callManager.isInitiator()) { Log.i("Loki", "Starting reconnect timer") - scheduledReconnect = timeoutExecutor.schedule(CheckReconnectedRunnable(callId, this), RECONNECT_SECONDS, TimeUnit.SECONDS) + scheduledReconnect = timeoutExecutor.schedule( + CheckReconnectedRunnable(callId, this), + RECONNECT_SECONDS, + TimeUnit.SECONDS + ) } else { Log.i("Loki", "Starting timeout, awaiting new reconnect") callManager.postConnectionEvent(Event.PrepareForNewOffer) { - scheduledTimeout = timeoutExecutor.schedule(TimeoutRunnable(callId, this), TIMEOUT_SECONDS, TimeUnit.SECONDS) + scheduledTimeout = timeoutExecutor.schedule( + TimeoutRunnable(callId, this), + TIMEOUT_SECONDS, + TimeUnit.SECONDS + ) } } } @@ -855,7 +964,7 @@ class WebRtcCallService: Service(), CallManager.WebRtcListener { override fun onDataChannel(p0: DataChannel?) {} override fun onRenegotiationNeeded() { - Log.w(TAG,"onRenegotiationNeeded was called!") + Log.w(TAG, "onRenegotiationNeeded was called!") } override fun onAddTrack(p0: RtpReceiver?, p1: Array?) {} diff --git a/app/src/main/java/org/thoughtcrime/securesms/sskenvironment/TypingStatusRepository.java b/app/src/main/java/org/thoughtcrime/securesms/sskenvironment/TypingStatusRepository.java index c1d6e5369..a18ad8211 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/sskenvironment/TypingStatusRepository.java +++ b/app/src/main/java/org/thoughtcrime/securesms/sskenvironment/TypingStatusRepository.java @@ -1,20 +1,21 @@ package org.thoughtcrime.securesms.sskenvironment; import android.annotation.SuppressLint; +import android.content.Context; + +import androidx.annotation.NonNull; import androidx.lifecycle.LiveData; import androidx.lifecycle.MutableLiveData; -import android.content.Context; -import androidx.annotation.NonNull; import com.annimon.stream.Collectors; import com.annimon.stream.Stream; import org.jetbrains.annotations.NotNull; import org.session.libsession.utilities.Address; -import org.session.libsession.utilities.recipients.Recipient; import org.session.libsession.utilities.SSKEnvironment; import org.session.libsession.utilities.TextSecurePreferences; import org.session.libsession.utilities.Util; +import org.session.libsession.utilities.recipients.Recipient; import org.session.libsignal.utilities.Log; import java.util.ArrayList; @@ -198,12 +199,12 @@ public class TypingStatusRepository implements SSKEnvironment.TypingIndicatorsPr if (device != typist.device) return false; if (threadId != typist.threadId) return false; - return author.equals(typist.author); + return author.getAddress().equals(typist.author.getAddress()); } @Override public int hashCode() { - int result = author.hashCode(); + int result = author.getAddress().hashCode(); result = 31 * result + device; result = 31 * result + (int) (threadId ^ (threadId >>> 32)); return result; diff --git a/app/src/main/java/org/thoughtcrime/securesms/util/BackupUtil.kt b/app/src/main/java/org/thoughtcrime/securesms/util/BackupUtil.kt index eaaf06f45..074278cb9 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/util/BackupUtil.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/util/BackupUtil.kt @@ -24,7 +24,6 @@ import org.thoughtcrime.securesms.crypto.AttachmentSecretProvider import org.thoughtcrime.securesms.crypto.IdentityKeyUtil import org.thoughtcrime.securesms.database.BackupFileRecord import org.thoughtcrime.securesms.dependencies.DatabaseComponent -import org.thoughtcrime.securesms.service.LocalBackupListener import java.io.IOException import java.security.MessageDigest import java.security.NoSuchAlgorithmException @@ -74,44 +73,6 @@ object BackupUtil { return prefList } - /** - * Set app-wide configuration to enable the backups and schedule them. - * - * Make sure that the backup dir is selected prior activating the backup. - * Use [BackupDirSelector] or [setBackupDirUri] manually. - */ - @JvmStatic - @Throws(IOException::class) - fun enableBackups(context: Context, password: String) { - val backupDir = getBackupDirUri(context) - if (backupDir == null || !validateDirAccess(context, backupDir)) { - throw IOException("Backup dir is not set or invalid.") - } - - BackupPassphrase.set(context, password) - TextSecurePreferences.setBackupEnabled(context, true) - LocalBackupListener.schedule(context) - } - - /** - * Set app-wide configuration to disable the backups. - * - * This call resets the backup dir value. - * Make sure to call [setBackupDirUri] prior next call to [enableBackups]. - * - * @param deleteBackupFiles if true, deletes all the previously created backup files - * (if the app has access to them) - */ - @JvmStatic - fun disableBackups(context: Context, deleteBackupFiles: Boolean) { - BackupPassphrase.set(context, null) - TextSecurePreferences.setBackupEnabled(context, false) - if (deleteBackupFiles) { - deleteAllBackupFiles(context) - } - setBackupDirUri(context, null) - } - @JvmStatic fun getLastBackupTimeString(context: Context, locale: Locale): String { val timestamp = DatabaseComponent.get(context).lokiBackupFilesDatabase().getLastBackupFileTime() diff --git a/app/src/main/java/org/thoughtcrime/securesms/util/CursorUtil.java b/app/src/main/java/org/thoughtcrime/securesms/util/CursorUtil.java index e328e34e0..bd3d65b9d 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/util/CursorUtil.java +++ b/app/src/main/java/org/thoughtcrime/securesms/util/CursorUtil.java @@ -4,8 +4,6 @@ import android.database.Cursor; import androidx.annotation.NonNull; -import java.util.Optional; - public final class CursorUtil { @@ -19,71 +17,8 @@ public final class CursorUtil { return cursor.getInt(cursor.getColumnIndexOrThrow(column)); } - public static float requireFloat(@NonNull Cursor cursor, @NonNull String column) { - return cursor.getFloat(cursor.getColumnIndexOrThrow(column)); - } - public static long requireLong(@NonNull Cursor cursor, @NonNull String column) { return cursor.getLong(cursor.getColumnIndexOrThrow(column)); } - public static boolean requireBoolean(@NonNull Cursor cursor, @NonNull String column) { - return requireInt(cursor, column) != 0; - } - - public static byte[] requireBlob(@NonNull Cursor cursor, @NonNull String column) { - return cursor.getBlob(cursor.getColumnIndexOrThrow(column)); - } - - public static boolean isNull(@NonNull Cursor cursor, @NonNull String column) { - return cursor.isNull(cursor.getColumnIndexOrThrow(column)); - } - - public static Optional getString(@NonNull Cursor cursor, @NonNull String column) { - if (cursor.getColumnIndex(column) < 0) { - return Optional.empty(); - } else { - return Optional.ofNullable(requireString(cursor, column)); - } - } - - public static Optional getInt(@NonNull Cursor cursor, @NonNull String column) { - if (cursor.getColumnIndex(column) < 0) { - return Optional.empty(); - } else { - return Optional.of(requireInt(cursor, column)); - } - } - - public static Optional getBoolean(@NonNull Cursor cursor, @NonNull String column) { - if (cursor.getColumnIndex(column) < 0) { - return Optional.empty(); - } else { - return Optional.of(requireBoolean(cursor, column)); - } - } - - public static Optional getBlob(@NonNull Cursor cursor, @NonNull String column) { - if (cursor.getColumnIndex(column) < 0) { - return Optional.empty(); - } else { - return Optional.ofNullable(requireBlob(cursor, column)); - } - } - - /** - * Reads each column as a string, and concatenates them together into a single string separated by | - */ - public static String readRowAsString(@NonNull Cursor cursor) { - StringBuilder row = new StringBuilder(); - - for (int i = 0, len = cursor.getColumnCount(); i < len; i++) { - row.append(cursor.getString(i)); - if (i < len - 1) { - row.append(" | "); - } - } - - return row.toString(); - } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/util/PopupMenuUtil.kt b/app/src/main/java/org/thoughtcrime/securesms/util/PopupMenuUtil.kt deleted file mode 100644 index a1105cfff..000000000 --- a/app/src/main/java/org/thoughtcrime/securesms/util/PopupMenuUtil.kt +++ /dev/null @@ -1,24 +0,0 @@ -package org.thoughtcrime.securesms.util - -import android.annotation.SuppressLint -import android.os.Build -import android.util.Log -import android.widget.PopupMenu - -@SuppressLint("PrivateApi") -@Deprecated(message = "Not needed when using appcompat 1.4.1+", replaceWith = ReplaceWith("setForceShowIcon(true)")) -fun PopupMenu.forceShowIcon() { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { - this.setForceShowIcon(true) - } else { - try { - val popupField = PopupMenu::class.java.getDeclaredField("mPopup") - popupField.isAccessible = true - val menu = popupField.get(this) - menu.javaClass.getDeclaredMethod("setForceShowIcon", Boolean::class.java) - .invoke(menu, true) - } catch (exception: Exception) { - Log.d("Loki", "Couldn't show message request popupmenu due to error: $exception.") - } - } -} diff --git a/app/src/main/java/org/thoughtcrime/securesms/webrtc/WebRtcCallServiceReceivers.kt b/app/src/main/java/org/thoughtcrime/securesms/webrtc/WebRtcCallServiceReceivers.kt index 955356c7d..09db0022d 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/webrtc/WebRtcCallServiceReceivers.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/webrtc/WebRtcCallServiceReceivers.kt @@ -3,8 +3,11 @@ package org.thoughtcrime.securesms.webrtc import android.content.BroadcastReceiver import android.content.Context import android.content.Intent +import android.os.Build import android.telephony.PhoneStateListener +import android.telephony.TelephonyCallback import android.telephony.TelephonyManager +import androidx.annotation.RequiresApi import org.session.libsignal.utilities.Log import org.thoughtcrime.securesms.service.WebRtcCallService import org.thoughtcrime.securesms.webrtc.locks.LockManager @@ -25,6 +28,21 @@ class HangUpRtcOnPstnCallAnsweredListener(private val hangupListener: ()->Unit): } } +@RequiresApi(Build.VERSION_CODES.S) +class HangUpRtcTelephonyCallback(private val hangupListener: ()->Unit): TelephonyCallback(), TelephonyCallback.CallStateListener { + + companion object { + private val TAG = Log.tag(HangUpRtcTelephonyCallback::class.java) + } + + override fun onCallStateChanged(state: Int) { + if (state == TelephonyManager.CALL_STATE_OFFHOOK) { + hangupListener() + Log.i(TAG, "Device phone call ended Session call.") + } + } +} + class PowerButtonReceiver : BroadcastReceiver() { override fun onReceive(context: Context, intent: Intent) { if (Intent.ACTION_SCREEN_OFF == intent.action) { diff --git a/app/src/main/java/org/thoughtcrime/securesms/webrtc/audio/SignalAudioManager.kt b/app/src/main/java/org/thoughtcrime/securesms/webrtc/audio/SignalAudioManager.kt index 2b4d34807..67514c58b 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/webrtc/audio/SignalAudioManager.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/webrtc/audio/SignalAudioManager.kt @@ -6,7 +6,6 @@ import android.content.Intent import android.content.IntentFilter import android.media.AudioManager import android.media.SoundPool -import android.os.Build import android.os.HandlerThread import network.loki.messenger.R import org.session.libsignal.utilities.Log @@ -108,7 +107,7 @@ class SignalAudioManager(private val context: Context, updateAudioDeviceState() wiredHeadsetReceiver = WiredHeadsetReceiver() - context.registerReceiver(wiredHeadsetReceiver, IntentFilter(if (Build.VERSION.SDK_INT >= 21) AudioManager.ACTION_HEADSET_PLUG else Intent.ACTION_HEADSET_PLUG)) + context.registerReceiver(wiredHeadsetReceiver, IntentFilter(AudioManager.ACTION_HEADSET_PLUG)) state = State.PREINITIALIZED diff --git a/app/src/main/java/org/thoughtcrime/securesms/webrtc/audio/SignalBluetoothManager.kt b/app/src/main/java/org/thoughtcrime/securesms/webrtc/audio/SignalBluetoothManager.kt index 84a36ee82..0a80cacef 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/webrtc/audio/SignalBluetoothManager.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/webrtc/audio/SignalBluetoothManager.kt @@ -2,14 +2,15 @@ package org.thoughtcrime.securesms.webrtc.audio import android.Manifest import android.bluetooth.BluetoothAdapter -import android.bluetooth.BluetoothDevice import android.bluetooth.BluetoothHeadset import android.bluetooth.BluetoothProfile import android.content.BroadcastReceiver import android.content.Context import android.content.Intent import android.content.IntentFilter +import android.content.pm.PackageManager import android.media.AudioManager +import androidx.core.app.ActivityCompat import org.session.libsignal.utilities.Log import org.thoughtcrime.securesms.webrtc.AudioManagerCommand import java.util.concurrent.TimeUnit @@ -80,7 +81,6 @@ class SignalBluetoothManager( bluetoothReceiver = BluetoothHeadsetBroadcastReceiver() context.registerReceiver(bluetoothReceiver, bluetoothHeadsetFilter) - Log.i(TAG, "Headset profile state: ${bluetoothAdapter?.getProfileConnectionState(BluetoothProfile.HEADSET)?.toStateString()}") Log.i(TAG, "Bluetooth proxy for headset profile has started") state = State.UNAVAILABLE } @@ -161,7 +161,8 @@ class SignalBluetoothManager( Log.d(TAG, "updateDevice(): state: $state") - if (state == State.UNINITIALIZED || bluetoothHeadset == null) { + if (state == State.UNINITIALIZED || bluetoothHeadset == null + || ActivityCompat.checkSelfPermission(context, Manifest.permission.BLUETOOTH_CONNECT) != PackageManager.PERMISSION_GRANTED) { return } diff --git a/app/src/main/java/org/thoughtcrime/securesms/webrtc/locks/LockManager.java b/app/src/main/java/org/thoughtcrime/securesms/webrtc/locks/LockManager.java index a7fac62bb..59c05af91 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/webrtc/locks/LockManager.java +++ b/app/src/main/java/org/thoughtcrime/securesms/webrtc/locks/LockManager.java @@ -49,7 +49,7 @@ public class LockManager { partialLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "signal:partial"); proximityLock = new ProximityLock(pm); - WifiManager wm = (WifiManager) context.getSystemService(Context.WIFI_SERVICE); + WifiManager wm = (WifiManager) context.getApplicationContext().getSystemService(Context.WIFI_SERVICE); wifiLock = wm.createWifiLock(WifiManager.WIFI_MODE_FULL_HIGH_PERF, "signal:wifi"); fullLock.setReferenceCounted(false); diff --git a/app/src/main/res/layout/activity_message_detail.xml b/app/src/main/res/layout/activity_message_detail.xml index c5a7f12bf..49c1af54e 100644 --- a/app/src/main/res/layout/activity_message_detail.xml +++ b/app/src/main/res/layout/activity_message_detail.xml @@ -69,6 +69,7 @@ - - - - - - + android:layout_height="match_parent" /> @@ -35,4 +28,4 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/app/src/main/res/layout/contact_selection_list_fragment.xml b/app/src/main/res/layout/contact_selection_list_fragment.xml index 7a297bab2..48221f632 100644 --- a/app/src/main/res/layout/contact_selection_list_fragment.xml +++ b/app/src/main/res/layout/contact_selection_list_fragment.xml @@ -18,35 +18,13 @@ - - - - - - - - - - - + android:clipToPadding="false" + android:scrollbars="vertical" + tools:listitem="@layout/view_user"/> diff --git a/app/src/main/res/layout/view_voice_message.xml b/app/src/main/res/layout/view_voice_message.xml index 535c7f234..9895ad95a 100644 --- a/app/src/main/res/layout/view_voice_message.xml +++ b/app/src/main/res/layout/view_voice_message.xml @@ -11,7 +11,7 @@ android:layout_width="0dp" android:layout_height="match_parent" android:layout_alignParentStart="true" - android:background="@color/transparent_black_6" /> + android:background="@color/transparent_black_30" /> - - val json = JsonUtil.fromJson(response, Map::class.java) - (json["id"] as? String)?.toLong() ?: throw Error.ParsingFailed + val hasId = json.containsKey("id") + val id = json.getOrDefault("id", null) + Log.d("Loki-FS", "File Upload Response hasId: $hasId of type: ${id?.javaClass}") + (id as? String)?.toLong() ?: throw Error.ParsingFailed } } diff --git a/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/MessageSender.kt b/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/MessageSender.kt index d6a4618d9..38180a8cb 100644 --- a/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/MessageSender.kt +++ b/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/MessageSender.kt @@ -8,11 +8,7 @@ import org.session.libsession.messaging.jobs.MessageSendJob import org.session.libsession.messaging.jobs.NotifyPNServerJob import org.session.libsession.messaging.messages.Destination import org.session.libsession.messaging.messages.Message -import org.session.libsession.messaging.messages.control.CallMessage -import org.session.libsession.messaging.messages.control.ClosedGroupControlMessage -import org.session.libsession.messaging.messages.control.ConfigurationMessage -import org.session.libsession.messaging.messages.control.ExpirationTimerUpdate -import org.session.libsession.messaging.messages.control.UnsendRequest +import org.session.libsession.messaging.messages.control.* import org.session.libsession.messaging.messages.visible.LinkPreview import org.session.libsession.messaging.messages.visible.Profile import org.session.libsession.messaging.messages.visible.Quote @@ -32,12 +28,7 @@ import org.session.libsession.utilities.GroupUtil import org.session.libsession.utilities.SSKEnvironment import org.session.libsignal.crypto.PushTransportDetails import org.session.libsignal.protos.SignalServiceProtos -import org.session.libsignal.utilities.Base64 -import org.session.libsignal.utilities.IdPrefix -import org.session.libsignal.utilities.Namespace -import org.session.libsignal.utilities.defaultRequiresAuth -import org.session.libsignal.utilities.hasNamespaces -import org.session.libsignal.utilities.hexEncodedPublicKey +import org.session.libsignal.utilities.* import java.util.concurrent.TimeUnit import java.util.concurrent.atomic.AtomicInteger import org.session.libsession.messaging.sending_receiving.attachments.Attachment as SignalAttachment @@ -337,6 +328,8 @@ object MessageSender { message.serverHash?.let { storage.setMessageServerHash(messageID, it) } + // in case any errors from previous sends + storage.clearErrorMessage(messageID) // Track the open group server message ID if (message.openGroupServerMessageID != null && (destination is Destination.LegacyOpenGroup || destination is Destination.OpenGroup)) { val server: String diff --git a/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/ReceivedMessageHandler.kt b/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/ReceivedMessageHandler.kt index 86b856d95..acab1f097 100644 --- a/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/ReceivedMessageHandler.kt +++ b/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/ReceivedMessageHandler.kt @@ -6,15 +6,7 @@ import org.session.libsession.messaging.MessagingModuleConfiguration import org.session.libsession.messaging.jobs.BackgroundGroupAddJob import org.session.libsession.messaging.jobs.JobQueue import org.session.libsession.messaging.messages.Message -import org.session.libsession.messaging.messages.control.CallMessage -import org.session.libsession.messaging.messages.control.ClosedGroupControlMessage -import org.session.libsession.messaging.messages.control.ConfigurationMessage -import org.session.libsession.messaging.messages.control.DataExtractionNotification -import org.session.libsession.messaging.messages.control.ExpirationTimerUpdate -import org.session.libsession.messaging.messages.control.MessageRequestResponse -import org.session.libsession.messaging.messages.control.ReadReceipt -import org.session.libsession.messaging.messages.control.TypingIndicator -import org.session.libsession.messaging.messages.control.UnsendRequest +import org.session.libsession.messaging.messages.control.* import org.session.libsession.messaging.messages.visible.Attachment import org.session.libsession.messaging.messages.visible.Reaction import org.session.libsession.messaging.messages.visible.VisibleMessage @@ -29,26 +21,18 @@ import org.session.libsession.messaging.utilities.SessionId import org.session.libsession.messaging.utilities.SodiumUtilities import org.session.libsession.messaging.utilities.WebRtcUtils import org.session.libsession.snode.SnodeAPI -import org.session.libsession.utilities.Address -import org.session.libsession.utilities.GroupRecord -import org.session.libsession.utilities.GroupUtil -import org.session.libsession.utilities.ProfileKeyUtil -import org.session.libsession.utilities.SSKEnvironment -import org.session.libsession.utilities.TextSecurePreferences +import org.session.libsession.utilities.* import org.session.libsession.utilities.recipients.Recipient import org.session.libsignal.crypto.ecc.DjbECPrivateKey import org.session.libsignal.crypto.ecc.DjbECPublicKey import org.session.libsignal.crypto.ecc.ECKeyPair import org.session.libsignal.messages.SignalServiceGroup import org.session.libsignal.protos.SignalServiceProtos +import org.session.libsignal.utilities.* import org.session.libsignal.utilities.Base64 -import org.session.libsignal.utilities.IdPrefix -import org.session.libsignal.utilities.Log import org.session.libsignal.utilities.guava.Optional -import org.session.libsignal.utilities.removingIdPrefixIfNeeded -import org.session.libsignal.utilities.toHexString import java.security.MessageDigest -import java.util.LinkedList +import java.util.* import kotlin.math.min internal fun MessageReceiver.isBlocked(publicKey: String): Boolean { @@ -307,6 +291,8 @@ fun MessageReceiver.handleVisibleMessage(message: VisibleMessage, return@mapNotNull attachment } } + // Cancel any typing indicators if needed + cancelTypingIndicatorsIfNeeded(message.sender!!) // Parse reaction if needed val threadIsGroup = threadRecipient?.isGroupRecipient == true message.reaction?.let { reaction -> @@ -332,8 +318,6 @@ fun MessageReceiver.handleVisibleMessage(message: VisibleMessage, } return messageID } - // Cancel any typing indicators if needed - cancelTypingIndicatorsIfNeeded(message.sender!!) return null } diff --git a/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/link_preview/LinkPreview.java b/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/link_preview/LinkPreview.java index 8aa8102a6..b2b7cfc7d 100644 --- a/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/link_preview/LinkPreview.java +++ b/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/link_preview/LinkPreview.java @@ -13,6 +13,7 @@ import org.session.libsignal.utilities.JsonUtil; import org.session.libsignal.utilities.guava.Optional; import java.io.IOException; +import java.util.Objects; public class LinkPreview { @@ -75,4 +76,17 @@ public class LinkPreview { public static LinkPreview deserialize(@NonNull String serialized) throws IOException { return JsonUtil.fromJson(serialized, LinkPreview.class); } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + LinkPreview that = (LinkPreview) o; + return Objects.equals(url, that.url) && Objects.equals(title, that.title) && Objects.equals(attachmentId, that.attachmentId) && Objects.equals(thumbnail, that.thumbnail); + } + + @Override + public int hashCode() { + return Objects.hash(url, title, attachmentId, thumbnail); + } } diff --git a/libsignal/build.gradle b/libsignal/build.gradle index 681fdfa12..1ea5f2de0 100644 --- a/libsignal/build.gradle +++ b/libsignal/build.gradle @@ -15,16 +15,16 @@ android { } dependencies { - implementation "androidx.annotation:annotation:1.2.0" + implementation "androidx.annotation:annotation:1.5.0" implementation "com.google.protobuf:protobuf-java:$protobufVersion" implementation "com.fasterxml.jackson.core:jackson-databind:$jacksonDatabindVersion" implementation "com.github.oxen-io.session-android-curve-25519:curve25519-java:$curve25519Version" implementation "com.squareup.okhttp3:okhttp:$okhttpVersion" implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlinVersion" implementation "org.jetbrains.kotlin:kotlin-reflect:$kotlinVersion" - implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.3.9" + implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:$coroutinesVersion" implementation "nl.komponents.kovenant:kovenant:$kovenantVersion" - testImplementation "junit:junit:3.8.2" + testImplementation "junit:junit:$junitVersion" testImplementation "org.assertj:assertj-core:1.7.1" testImplementation "org.conscrypt:conscrypt-openjdk-uber:2.0.0" } From df8a6d739ab3ddb2f5a9e0563cb44e4c83362bac Mon Sep 17 00:00:00 2001 From: hjubb Date: Mon, 19 Dec 2022 11:57:22 +1100 Subject: [PATCH 14/47] build: release v1.16.3 (3235) --- app/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/build.gradle b/app/build.gradle index 8cc1f4146..3e336e45a 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -158,7 +158,7 @@ dependencies { testImplementation 'org.robolectric:shadows-multidex:4.4' } -def canonicalVersionCode = 321 +def canonicalVersionCode = 323 def canonicalVersionName = "1.16.3" def postFixSize = 10 From 1a28fd2a9edd5de06495a531f9a15138696325a9 Mon Sep 17 00:00:00 2001 From: Morgan Pretty Date: Thu, 5 Jan 2023 16:56:52 +1100 Subject: [PATCH 15/47] Added code to migrate from SQLCipher 3 to 4 --- app/build.gradle | 3 +- .../securesms/backup/FullBackupExporter.kt | 2 +- .../securesms/backup/FullBackupImporter.kt | 2 +- .../database/AttachmentDatabase.java | 2 +- .../securesms/database/Database.java | 2 +- .../securesms/database/DatabaseFactory.java | 2 +- .../securesms/database/DatabaseUtilities.kt | 4 +- .../securesms/database/DraftDatabase.java | 2 +- .../securesms/database/GroupDatabase.java | 3 +- .../database/GroupReceiptDatabase.java | 3 +- .../securesms/database/JobDatabase.java | 2 +- .../securesms/database/LokiMessageDatabase.kt | 2 +- .../securesms/database/MediaDatabase.java | 2 +- .../securesms/database/MessagingDatabase.java | 2 +- .../securesms/database/MmsSmsDatabase.java | 4 +- .../securesms/database/PushDatabase.java | 2 +- .../securesms/database/RecipientDatabase.java | 2 +- .../securesms/database/SearchDatabase.java | 4 +- .../database/SessionContactDatabase.kt | 17 +-- .../securesms/database/SessionJobDatabase.kt | 2 +- .../securesms/database/SmsDatabase.java | 4 +- .../securesms/database/ThreadDatabase.java | 2 +- .../database/helpers/SQLCipherOpenHelper.java | 125 ++++++++++++++---- .../securesms/dependencies/DatabaseModule.kt | 5 +- 24 files changed, 129 insertions(+), 71 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index 8cc1f4146..25817a964 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -95,7 +95,8 @@ dependencies { implementation 'com.takisoft.fix:colorpicker:1.0.1' implementation 'com.codewaves.stickyheadergrid:stickyheadergrid:0.9.4' implementation 'com.github.dmytrodanylyk.circular-progress-button:library:1.1.3-S2' - implementation 'org.signal:android-database-sqlcipher:3.5.9-S3' + implementation 'androidx.sqlite:sqlite-ktx:2.2.0' + implementation 'net.zetetic:sqlcipher-android:4.5.2@aar' implementation ('com.googlecode.ez-vcard:ez-vcard:0.9.11') { exclude group: 'com.fasterxml.jackson.core' exclude group: 'org.freemarker' diff --git a/app/src/main/java/org/thoughtcrime/securesms/backup/FullBackupExporter.kt b/app/src/main/java/org/thoughtcrime/securesms/backup/FullBackupExporter.kt index 33b8b6725..6b5d47a2e 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/backup/FullBackupExporter.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/backup/FullBackupExporter.kt @@ -8,7 +8,7 @@ import androidx.annotation.WorkerThread import com.annimon.stream.function.Consumer import com.annimon.stream.function.Predicate import com.google.protobuf.ByteString -import net.sqlcipher.database.SQLiteDatabase +import net.zetetic.database.sqlcipher.SQLiteDatabase import org.greenrobot.eventbus.EventBus import org.session.libsession.avatars.AvatarHelper import org.session.libsession.messaging.sending_receiving.attachments.AttachmentId diff --git a/app/src/main/java/org/thoughtcrime/securesms/backup/FullBackupImporter.kt b/app/src/main/java/org/thoughtcrime/securesms/backup/FullBackupImporter.kt index ba1df97d5..b40c049bc 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/backup/FullBackupImporter.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/backup/FullBackupImporter.kt @@ -5,7 +5,7 @@ import android.content.ContentValues import android.content.Context import android.net.Uri import androidx.annotation.WorkerThread -import net.sqlcipher.database.SQLiteDatabase +import net.zetetic.database.sqlcipher.SQLiteDatabase import org.greenrobot.eventbus.EventBus import org.session.libsession.avatars.AvatarHelper import org.session.libsession.messaging.sending_receiving.attachments.AttachmentId diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/AttachmentDatabase.java b/app/src/main/java/org/thoughtcrime/securesms/database/AttachmentDatabase.java index 8c9916b87..182f8536d 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/AttachmentDatabase.java +++ b/app/src/main/java/org/thoughtcrime/securesms/database/AttachmentDatabase.java @@ -33,7 +33,7 @@ import androidx.annotation.VisibleForTesting; import com.bumptech.glide.Glide; -import net.sqlcipher.database.SQLiteDatabase; +import net.zetetic.database.sqlcipher.SQLiteDatabase; import org.json.JSONArray; import org.json.JSONException; diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/Database.java b/app/src/main/java/org/thoughtcrime/securesms/database/Database.java index ce950214f..b6b224589 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/Database.java +++ b/app/src/main/java/org/thoughtcrime/securesms/database/Database.java @@ -23,7 +23,7 @@ import android.database.Cursor; import androidx.annotation.NonNull; -import net.sqlcipher.database.SQLiteDatabase; +import net.zetetic.database.sqlcipher.SQLiteDatabase; import org.session.libsession.utilities.WindowDebouncer; import org.thoughtcrime.securesms.ApplicationContext; diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/DatabaseFactory.java b/app/src/main/java/org/thoughtcrime/securesms/database/DatabaseFactory.java index 74396e2a9..76fa8c5c0 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/DatabaseFactory.java +++ b/app/src/main/java/org/thoughtcrime/securesms/database/DatabaseFactory.java @@ -19,7 +19,7 @@ package org.thoughtcrime.securesms.database; import android.content.Context; -import net.sqlcipher.database.SQLiteDatabase; +import net.zetetic.database.sqlcipher.SQLiteDatabase; import org.thoughtcrime.securesms.database.helpers.SQLCipherOpenHelper; import org.thoughtcrime.securesms.dependencies.DatabaseComponent; diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/DatabaseUtilities.kt b/app/src/main/java/org/thoughtcrime/securesms/database/DatabaseUtilities.kt index e6c9b9614..f4d6530bb 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/DatabaseUtilities.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/database/DatabaseUtilities.kt @@ -1,9 +1,9 @@ package org.thoughtcrime.securesms.database import android.content.ContentValues +import android.database.Cursor import androidx.core.database.getStringOrNull -import net.sqlcipher.Cursor -import net.sqlcipher.database.SQLiteDatabase +import net.zetetic.database.sqlcipher.SQLiteDatabase import org.session.libsignal.utilities.Base64 fun SQLiteDatabase.get(table: String, query: String?, arguments: Array?, get: (Cursor) -> T): T? { diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/DraftDatabase.java b/app/src/main/java/org/thoughtcrime/securesms/database/DraftDatabase.java index 2dd8b2bf2..822e40129 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/DraftDatabase.java +++ b/app/src/main/java/org/thoughtcrime/securesms/database/DraftDatabase.java @@ -6,7 +6,7 @@ import android.database.Cursor; import android.net.Uri; import androidx.annotation.Nullable; -import net.sqlcipher.database.SQLiteDatabase; +import net.zetetic.database.sqlcipher.SQLiteDatabase; import network.loki.messenger.R; import org.thoughtcrime.securesms.database.helpers.SQLCipherOpenHelper; 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 feaccc398..3e23f524f 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/GroupDatabase.java +++ b/app/src/main/java/org/thoughtcrime/securesms/database/GroupDatabase.java @@ -1,6 +1,5 @@ package org.thoughtcrime.securesms.database; - import android.annotation.SuppressLint; import android.content.ContentValues; import android.content.Context; @@ -12,7 +11,7 @@ import androidx.annotation.Nullable; import com.annimon.stream.Stream; -import net.sqlcipher.database.SQLiteDatabase; +import net.zetetic.database.sqlcipher.SQLiteDatabase; import org.jetbrains.annotations.NotNull; import org.session.libsession.utilities.Address; diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/GroupReceiptDatabase.java b/app/src/main/java/org/thoughtcrime/securesms/database/GroupReceiptDatabase.java index 81f8b62aa..d4140910d 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/GroupReceiptDatabase.java +++ b/app/src/main/java/org/thoughtcrime/securesms/database/GroupReceiptDatabase.java @@ -1,13 +1,12 @@ package org.thoughtcrime.securesms.database; - import android.content.ContentValues; import android.content.Context; import android.database.Cursor; import androidx.annotation.NonNull; -import net.sqlcipher.database.SQLiteDatabase; +import net.zetetic.database.sqlcipher.SQLiteDatabase; import org.session.libsession.utilities.Address; import org.thoughtcrime.securesms.database.helpers.SQLCipherOpenHelper; diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/JobDatabase.java b/app/src/main/java/org/thoughtcrime/securesms/database/JobDatabase.java index f878e3061..ef4746923 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/JobDatabase.java +++ b/app/src/main/java/org/thoughtcrime/securesms/database/JobDatabase.java @@ -5,7 +5,7 @@ import android.content.Context; import android.database.Cursor; import androidx.annotation.NonNull; -import net.sqlcipher.database.SQLiteDatabase; +import net.zetetic.database.sqlcipher.SQLiteDatabase; import org.thoughtcrime.securesms.database.helpers.SQLCipherOpenHelper; import org.thoughtcrime.securesms.jobmanager.persistence.ConstraintSpec; diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/LokiMessageDatabase.kt b/app/src/main/java/org/thoughtcrime/securesms/database/LokiMessageDatabase.kt index a3f6395b1..41a136caa 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/LokiMessageDatabase.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/database/LokiMessageDatabase.kt @@ -2,7 +2,7 @@ package org.thoughtcrime.securesms.database import android.content.ContentValues import android.content.Context -import net.sqlcipher.database.SQLiteDatabase.CONFLICT_REPLACE +import net.zetetic.database.sqlcipher.SQLiteDatabase.CONFLICT_REPLACE import org.session.libsignal.database.LokiMessageDatabaseProtocol import org.thoughtcrime.securesms.database.helpers.SQLCipherOpenHelper diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/MediaDatabase.java b/app/src/main/java/org/thoughtcrime/securesms/database/MediaDatabase.java index f16d663a1..1b273de92 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/MediaDatabase.java +++ b/app/src/main/java/org/thoughtcrime/securesms/database/MediaDatabase.java @@ -7,7 +7,7 @@ import android.database.Cursor; import androidx.annotation.NonNull; import androidx.annotation.Nullable; -import net.sqlcipher.database.SQLiteDatabase; +import net.zetetic.database.sqlcipher.SQLiteDatabase; import org.session.libsession.messaging.sending_receiving.attachments.DatabaseAttachment; import org.session.libsession.utilities.Address; diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/MessagingDatabase.java b/app/src/main/java/org/thoughtcrime/securesms/database/MessagingDatabase.java index ffde5ca02..bc0594df0 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/MessagingDatabase.java +++ b/app/src/main/java/org/thoughtcrime/securesms/database/MessagingDatabase.java @@ -5,7 +5,7 @@ import android.content.Context; import android.database.Cursor; import android.text.TextUtils; -import net.sqlcipher.database.SQLiteDatabase; +import net.zetetic.database.sqlcipher.SQLiteDatabase; import org.session.libsession.utilities.Address; import org.session.libsession.utilities.Document; diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/MmsSmsDatabase.java b/app/src/main/java/org/thoughtcrime/securesms/database/MmsSmsDatabase.java index b3afeac47..73534aeb2 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/MmsSmsDatabase.java +++ b/app/src/main/java/org/thoughtcrime/securesms/database/MmsSmsDatabase.java @@ -22,8 +22,8 @@ import android.database.Cursor; import androidx.annotation.NonNull; import androidx.annotation.Nullable; -import net.sqlcipher.database.SQLiteDatabase; -import net.sqlcipher.database.SQLiteQueryBuilder; +import net.zetetic.database.sqlcipher.SQLiteDatabase; +import net.zetetic.database.sqlcipher.SQLiteQueryBuilder; import org.session.libsession.utilities.Address; import org.session.libsession.utilities.Util; diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/PushDatabase.java b/app/src/main/java/org/thoughtcrime/securesms/database/PushDatabase.java index d1ba25aa7..b832d04df 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/PushDatabase.java +++ b/app/src/main/java/org/thoughtcrime/securesms/database/PushDatabase.java @@ -6,7 +6,7 @@ import android.database.Cursor; import androidx.annotation.NonNull; import org.session.libsignal.utilities.Log; -import net.sqlcipher.database.SQLiteDatabase; +import net.zetetic.database.sqlcipher.SQLiteDatabase; import org.thoughtcrime.securesms.database.helpers.SQLCipherOpenHelper; import org.session.libsignal.utilities.Base64; 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 58693172e..af2faaaca 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/RecipientDatabase.java +++ b/app/src/main/java/org/thoughtcrime/securesms/database/RecipientDatabase.java @@ -11,7 +11,7 @@ import androidx.annotation.Nullable; import com.annimon.stream.Stream; -import net.sqlcipher.database.SQLiteDatabase; +import net.zetetic.database.sqlcipher.SQLiteDatabase; import org.session.libsession.utilities.Address; import org.session.libsession.utilities.MaterialColor; diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/SearchDatabase.java b/app/src/main/java/org/thoughtcrime/securesms/database/SearchDatabase.java index 6bce73e22..eac6a5fbc 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/SearchDatabase.java +++ b/app/src/main/java/org/thoughtcrime/securesms/database/SearchDatabase.java @@ -1,13 +1,13 @@ package org.thoughtcrime.securesms.database; import android.content.Context; +import android.database.Cursor; import androidx.annotation.NonNull; import com.annimon.stream.Stream; -import net.sqlcipher.Cursor; -import net.sqlcipher.database.SQLiteDatabase; +import net.zetetic.database.sqlcipher.SQLiteDatabase; import org.session.libsession.utilities.Util; import org.thoughtcrime.securesms.database.helpers.SQLCipherOpenHelper; diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/SessionContactDatabase.kt b/app/src/main/java/org/thoughtcrime/securesms/database/SessionContactDatabase.kt index ef9f0cc38..40eee9742 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/SessionContactDatabase.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/database/SessionContactDatabase.kt @@ -3,7 +3,7 @@ package org.thoughtcrime.securesms.database import android.content.ContentValues import android.content.Context import androidx.core.database.getStringOrNull -import net.sqlcipher.Cursor +import android.database.Cursor import org.session.libsession.messaging.contacts.Contact import org.session.libsignal.utilities.Base64 import org.thoughtcrime.securesms.database.helpers.SQLCipherOpenHelper @@ -75,21 +75,6 @@ class SessionContactDatabase(context: Context, helper: SQLCipherOpenHelper) : Da } fun contactFromCursor(cursor: Cursor): Contact { - val sessionID = cursor.getString(sessionID) - val contact = Contact(sessionID) - contact.name = cursor.getStringOrNull(name) - contact.nickname = cursor.getStringOrNull(nickname) - contact.profilePictureURL = cursor.getStringOrNull(profilePictureURL) - contact.profilePictureFileName = cursor.getStringOrNull(profilePictureFileName) - cursor.getStringOrNull(profilePictureEncryptionKey)?.let { - contact.profilePictureEncryptionKey = Base64.decode(it) - } - contact.threadID = cursor.getLong(threadID) - contact.isTrusted = cursor.getInt(isTrusted) != 0 - return contact - } - - fun contactFromCursor(cursor: android.database.Cursor): Contact { val sessionID = cursor.getString(cursor.getColumnIndexOrThrow(sessionID)) val contact = Contact(sessionID) contact.name = cursor.getStringOrNull(cursor.getColumnIndexOrThrow(name)) diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/SessionJobDatabase.kt b/app/src/main/java/org/thoughtcrime/securesms/database/SessionJobDatabase.kt index 595168fdf..4425e3d85 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/SessionJobDatabase.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/database/SessionJobDatabase.kt @@ -2,7 +2,7 @@ package org.thoughtcrime.securesms.database import android.content.ContentValues import android.content.Context -import net.sqlcipher.Cursor +import android.database.Cursor import org.session.libsession.messaging.jobs.AttachmentUploadJob import org.session.libsession.messaging.jobs.BackgroundGroupAddJob import org.session.libsession.messaging.jobs.GroupAvatarDownloadJob diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/SmsDatabase.java b/app/src/main/java/org/thoughtcrime/securesms/database/SmsDatabase.java index 67243f73b..358518dea 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/SmsDatabase.java +++ b/app/src/main/java/org/thoughtcrime/securesms/database/SmsDatabase.java @@ -28,8 +28,8 @@ import androidx.annotation.Nullable; import com.annimon.stream.Stream; -import net.sqlcipher.database.SQLiteDatabase; -import net.sqlcipher.database.SQLiteStatement; +import net.zetetic.database.sqlcipher.SQLiteDatabase; +import net.zetetic.database.sqlcipher.SQLiteStatement; import org.session.libsession.messaging.calls.CallMessageType; import org.session.libsession.messaging.messages.signal.IncomingGroupMessage; diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/ThreadDatabase.java b/app/src/main/java/org/thoughtcrime/securesms/database/ThreadDatabase.java index a0c701fc9..bf42b92ba 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/ThreadDatabase.java +++ b/app/src/main/java/org/thoughtcrime/securesms/database/ThreadDatabase.java @@ -32,7 +32,7 @@ import androidx.annotation.Nullable; import com.annimon.stream.Stream; -import net.sqlcipher.database.SQLiteDatabase; +import net.zetetic.database.sqlcipher.SQLiteDatabase; import org.jetbrains.annotations.NotNull; import org.session.libsession.utilities.Address; 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 d2266b392..585e60f71 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 @@ -1,14 +1,16 @@ package org.thoughtcrime.securesms.database.helpers; - import android.content.Context; import android.database.Cursor; import androidx.annotation.NonNull; -import net.sqlcipher.database.SQLiteDatabase; -import net.sqlcipher.database.SQLiteDatabaseHook; -import net.sqlcipher.database.SQLiteOpenHelper; +import net.zetetic.database.DatabaseErrorHandler; +import net.zetetic.database.DatabaseUtils; +import net.zetetic.database.sqlcipher.SQLiteConnection; +import net.zetetic.database.sqlcipher.SQLiteDatabase; +import net.zetetic.database.sqlcipher.SQLiteDatabaseHook; +import net.zetetic.database.sqlcipher.SQLiteOpenHelper; import org.session.libsession.utilities.TextSecurePreferences; import org.session.libsignal.utilities.Log; @@ -36,6 +38,8 @@ import org.thoughtcrime.securesms.database.SessionJobDatabase; import org.thoughtcrime.securesms.database.SmsDatabase; import org.thoughtcrime.securesms.database.ThreadDatabase; +import java.io.File; + public class SQLCipherOpenHelper extends SQLiteOpenHelper { @SuppressWarnings("unused") @@ -77,38 +81,117 @@ public class SQLCipherOpenHelper extends SQLiteOpenHelper { private static final int lokiV38 = 59; // Loki - onUpgrade(...) must be updated to use Loki version numbers if Signal makes any database changes - private static final int DATABASE_VERSION = lokiV38; - private static final String DATABASE_NAME = "signal.db"; + private static final int DATABASE_VERSION = lokiV38; + private static final String CIPHER3_DATABASE_NAME = "signal.db"; + private static final String DATABASE_NAME = "signal_v4.db"; private final Context context; private final DatabaseSecret databaseSecret; public SQLCipherOpenHelper(@NonNull Context context, @NonNull DatabaseSecret databaseSecret) { - super(context, DATABASE_NAME, null, DATABASE_VERSION, new SQLiteDatabaseHook() { + super(context, DATABASE_NAME, databaseSecret.asString(), null, DATABASE_VERSION, DATABASE_VERSION, null, new SQLiteDatabaseHook() { @Override - public void preKey(SQLiteDatabase db) { - db.rawExecSQL("PRAGMA cipher_default_kdf_iter = 1;"); - db.rawExecSQL("PRAGMA cipher_default_page_size = 4096;"); + public void preKey(SQLiteConnection connection) { + connection.execute("PRAGMA cipher_default_kdf_iter = 256000;", null, null); + connection.execute("PRAGMA cipher_default_page_size = 4096;", null, null); } @Override - public void postKey(SQLiteDatabase db) { - db.rawExecSQL("PRAGMA kdf_iter = '1';"); - db.rawExecSQL("PRAGMA cipher_page_size = 4096;"); + public void postKey(SQLiteConnection connection) { + connection.execute("PRAGMA kdf_iter = '256000';", null, null); + connection.execute("PRAGMA cipher_page_size = 4096;", null, null); // if not vacuumed in a while, perform that operation long currentTime = System.currentTimeMillis(); // 7 days if (currentTime - TextSecurePreferences.getLastVacuumTime(context) > 604_800_000) { - db.rawExecSQL("VACUUM;"); + connection.execute("VACUUM;", null, null); TextSecurePreferences.setLastVacuumNow(context); } } - }); + }, true); this.context = context.getApplicationContext(); this.databaseSecret = databaseSecret; } + public static void migrateSqlCipher3To4IfNeeded(@NonNull Context context, @NonNull DatabaseSecret databaseSecret) { + String oldDbPath = context.getDatabasePath(CIPHER3_DATABASE_NAME).getPath(); + File oldDbFile = new File(oldDbPath); + + // If the old SQLCipher3 database file doesn't exist then just return early + if (!oldDbFile.exists()) { return; } + + // If the new database file already exists then we probably had a failed migration and it's likely in + // an invalid state so should delete it + String newDbPath = context.getDatabasePath(DATABASE_NAME).getPath(); + File newDbFile = new File(newDbPath); + + if (newDbFile.exists()) { newDbFile.delete(); } + + try { + newDbFile.createNewFile(); + } + catch (Exception e) { + // TODO: Communicate the error somehow??? + return; + } + + try { + // Open the old database + SQLiteDatabase oldDb = SQLiteDatabase.openOrCreateDatabase(oldDbPath, databaseSecret.asString(), null, null, new SQLiteDatabaseHook() { + @Override + public void preKey(SQLiteConnection connection) { + connection.execute("PRAGMA cipher_compatibility = 3;", null, null); + connection.execute("PRAGMA kdf_iter = '1';", null, null); + connection.execute("PRAGMA cipher_page_size = 4096;", null, null); + } + + @Override + public void postKey(SQLiteConnection connection) { + connection.execute("PRAGMA cipher_compatibility = 3;", null, null); + connection.execute("PRAGMA kdf_iter = '1';", null, null); + connection.execute("PRAGMA cipher_page_size = 4096;", null, null); + } + }); + + // Export the old database to the new one (will have the default 'kdf_iter' and 'page_size' settings) + int oldDbVersion = oldDb.getVersion(); + oldDb.rawExecSQL( + String.format("ATTACH DATABASE '%s' AS sqlcipher4 KEY '%s'", newDbPath, databaseSecret.asString()) + ); + Cursor cursor = oldDb.rawQuery("SELECT sqlcipher_export('sqlcipher4')"); + cursor.moveToLast(); + cursor.close(); + oldDb.rawExecSQL("DETACH DATABASE sqlcipher4"); + oldDb.close(); + + // TODO: Performance testing + + SQLiteDatabase newDb = SQLiteDatabase.openDatabase(newDbPath, databaseSecret.asString(), null, SQLiteDatabase.OPEN_READWRITE, new SQLiteDatabaseHook() { + @Override + public void preKey(SQLiteConnection connection) { + connection.execute("PRAGMA cipher_default_kdf_iter = 256000;", null, null); + connection.execute("PRAGMA cipher_default_page_size = 4096;", null, null); + } + + @Override + public void postKey(SQLiteConnection connection) { + connection.execute("PRAGMA cipher_default_kdf_iter = 256000;", null, null); + connection.execute("PRAGMA cipher_default_page_size = 4096;", null, null); + } + }); + newDb.setVersion(oldDbVersion); + newDb.close(); + + // TODO: Delete 'CIPHER3_DATABASE_NAME' + // TODO: What do we do if the deletion fails??? (The current logic will end up re-migrating...) +// oldDbFile.delete(); + } + catch (Exception e) { + // TODO: Communicate the error somehow??? + } + } + @Override public void onCreate(SQLiteDatabase db) { db.execSQL(SmsDatabase.CREATE_TABLE); @@ -195,9 +278,7 @@ public class SQLCipherOpenHelper extends SQLiteOpenHelper { @Override public void onConfigure(SQLiteDatabase db) { super.onConfigure(db); - // Loki - Enable write ahead logging mode and increase the cache size. - // This should be disabled if we ever run into serious race condition bugs. - db.enableWriteAheadLogging(); + db.execSQL("PRAGMA cache_size = 10000"); } @@ -420,14 +501,6 @@ public class SQLCipherOpenHelper extends SQLiteOpenHelper { } } - public SQLiteDatabase getReadableDatabase() { - return getReadableDatabase(databaseSecret.asString()); - } - - public SQLiteDatabase getWritableDatabase() { - return getWritableDatabase(databaseSecret.asString()); - } - public void markCurrent(SQLiteDatabase db) { db.setVersion(DATABASE_VERSION); } diff --git a/app/src/main/java/org/thoughtcrime/securesms/dependencies/DatabaseModule.kt b/app/src/main/java/org/thoughtcrime/securesms/dependencies/DatabaseModule.kt index 029daefbf..fba7a7e50 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/dependencies/DatabaseModule.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/dependencies/DatabaseModule.kt @@ -6,7 +6,7 @@ import dagger.Provides import dagger.hilt.InstallIn import dagger.hilt.android.qualifiers.ApplicationContext import dagger.hilt.components.SingletonComponent -import net.sqlcipher.database.SQLiteDatabase +import net.zetetic.database.sqlcipher.SQLiteDatabase import org.session.libsession.database.MessageDataProvider import org.thoughtcrime.securesms.attachments.DatabaseAttachmentProvider import org.thoughtcrime.securesms.crypto.AttachmentSecret @@ -22,7 +22,7 @@ object DatabaseModule { @JvmStatic fun init(context: Context) { - SQLiteDatabase.loadLibs(context) + System.loadLibrary("sqlcipher") } @Provides @@ -33,6 +33,7 @@ object DatabaseModule { @Singleton fun provideOpenHelper(@ApplicationContext context: Context): SQLCipherOpenHelper { val dbSecret = DatabaseSecretProvider(context).orCreateDatabaseSecret + SQLCipherOpenHelper.migrateSqlCipher3To4IfNeeded(context, dbSecret) return SQLCipherOpenHelper(context, dbSecret) } From 12205e72b6587c6ffc4ebf85581527e2d76019f1 Mon Sep 17 00:00:00 2001 From: Morgan Pretty Date: Fri, 6 Jan 2023 09:05:29 +1100 Subject: [PATCH 16/47] Another update was released last week --- app/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/build.gradle b/app/build.gradle index 25817a964..bfbf342a5 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -96,7 +96,7 @@ dependencies { implementation 'com.codewaves.stickyheadergrid:stickyheadergrid:0.9.4' implementation 'com.github.dmytrodanylyk.circular-progress-button:library:1.1.3-S2' implementation 'androidx.sqlite:sqlite-ktx:2.2.0' - implementation 'net.zetetic:sqlcipher-android:4.5.2@aar' + implementation 'net.zetetic:sqlcipher-android:4.5.3@aar' implementation ('com.googlecode.ez-vcard:ez-vcard:0.9.11') { exclude group: 'com.fasterxml.jackson.core' exclude group: 'org.freemarker' From e6fe38587bd1787f61a8797a51c99ff6617f7893 Mon Sep 17 00:00:00 2001 From: Morgan Pretty Date: Fri, 6 Jan 2023 10:36:56 +1100 Subject: [PATCH 17/47] Fixed an issue where database migrations were broken --- .../securesms/database/helpers/SQLCipherOpenHelper.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) 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 585e60f71..affb36f9e 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 @@ -82,6 +82,7 @@ public class SQLCipherOpenHelper extends SQLiteOpenHelper { // Loki - onUpgrade(...) must be updated to use Loki version numbers if Signal makes any database changes private static final int DATABASE_VERSION = lokiV38; + private static final int MIN_DATABASE_VERSION = lokiV7; private static final String CIPHER3_DATABASE_NAME = "signal.db"; private static final String DATABASE_NAME = "signal_v4.db"; @@ -89,7 +90,7 @@ public class SQLCipherOpenHelper extends SQLiteOpenHelper { private final DatabaseSecret databaseSecret; public SQLCipherOpenHelper(@NonNull Context context, @NonNull DatabaseSecret databaseSecret) { - super(context, DATABASE_NAME, databaseSecret.asString(), null, DATABASE_VERSION, DATABASE_VERSION, null, new SQLiteDatabaseHook() { + super(context, DATABASE_NAME, databaseSecret.asString(), null, DATABASE_VERSION, MIN_DATABASE_VERSION, null, new SQLiteDatabaseHook() { @Override public void preKey(SQLiteConnection connection) { connection.execute("PRAGMA cipher_default_kdf_iter = 256000;", null, null); From 5abc3119cbbdf8e564c8dd909e382c50022dba5d Mon Sep 17 00:00:00 2001 From: Morgan Pretty Date: Fri, 6 Jan 2023 15:41:26 +1100 Subject: [PATCH 18/47] Fixed an issue where clearing device data would create an invalid DB state --- .../java/org/thoughtcrime/securesms/ApplicationContext.java | 3 ++- .../securesms/database/helpers/SQLCipherOpenHelper.java | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/app/src/main/java/org/thoughtcrime/securesms/ApplicationContext.java b/app/src/main/java/org/thoughtcrime/securesms/ApplicationContext.java index 8fe65767b..e253d0b96 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/ApplicationContext.java +++ b/app/src/main/java/org/thoughtcrime/securesms/ApplicationContext.java @@ -57,6 +57,7 @@ import org.thoughtcrime.securesms.database.EmojiSearchDatabase; import org.thoughtcrime.securesms.database.JobDatabase; import org.thoughtcrime.securesms.database.LokiAPIDatabase; import org.thoughtcrime.securesms.database.Storage; +import org.thoughtcrime.securesms.database.helpers.SQLCipherOpenHelper; import org.thoughtcrime.securesms.database.model.EmojiSearchData; import org.thoughtcrime.securesms.dependencies.DatabaseComponent; import org.thoughtcrime.securesms.dependencies.DatabaseModule; @@ -537,7 +538,7 @@ public class ApplicationContext extends Application implements DefaultLifecycleO TextSecurePreferences.setProfileName(this, displayName); } getSharedPreferences(PREFERENCES_NAME, 0).edit().clear().commit(); - if (!deleteDatabase("signal.db")) { + if (!deleteDatabase(SQLCipherOpenHelper.DATABASE_NAME)) { Log.d("Loki", "Failed to delete database."); } Util.runOnMain(() -> new Handler().postDelayed(ApplicationContext.this::restartApplication, 200)); 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 affb36f9e..d145a33a5 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 @@ -84,7 +84,7 @@ public class SQLCipherOpenHelper extends SQLiteOpenHelper { private static final int DATABASE_VERSION = lokiV38; private static final int MIN_DATABASE_VERSION = lokiV7; private static final String CIPHER3_DATABASE_NAME = "signal.db"; - private static final String DATABASE_NAME = "signal_v4.db"; + public static final String DATABASE_NAME = "signal_v4.db"; private final Context context; private final DatabaseSecret databaseSecret; From d68d26cd5db0cef528b3e89ed96c906745d33fa8 Mon Sep 17 00:00:00 2001 From: Morgan Pretty Date: Tue, 10 Jan 2023 10:41:53 +1100 Subject: [PATCH 19/47] Added the MockDataGenerator to simplify db testing --- .../securesms/util/MockDataGenerator.kt | 425 ++++++++++++++++++ .../messaging/open_groups/OpenGroupApi.kt | 2 +- 2 files changed, 426 insertions(+), 1 deletion(-) create mode 100644 app/src/main/java/org/thoughtcrime/securesms/util/MockDataGenerator.kt diff --git a/app/src/main/java/org/thoughtcrime/securesms/util/MockDataGenerator.kt b/app/src/main/java/org/thoughtcrime/securesms/util/MockDataGenerator.kt new file mode 100644 index 000000000..b1834f041 --- /dev/null +++ b/app/src/main/java/org/thoughtcrime/securesms/util/MockDataGenerator.kt @@ -0,0 +1,425 @@ +package org.thoughtcrime.securesms.util + +import android.content.Context +import org.session.libsession.messaging.MessagingModuleConfiguration +import org.session.libsession.messaging.contacts.Contact +import org.session.libsession.messaging.messages.signal.IncomingTextMessage +import org.session.libsession.messaging.messages.signal.OutgoingTextMessage +import org.session.libsession.messaging.open_groups.OpenGroup +import org.session.libsession.messaging.open_groups.OpenGroupApi +import org.session.libsession.messaging.sending_receiving.notifications.PushNotificationAPI +import org.session.libsession.messaging.sending_receiving.pollers.ClosedGroupPollerV2 +import org.session.libsession.utilities.Address +import org.session.libsession.utilities.GroupUtil +import org.session.libsession.utilities.recipients.Recipient +import org.session.libsignal.crypto.ecc.Curve +import org.session.libsignal.messages.SignalServiceGroup +import org.session.libsignal.utilities.Log +import org.session.libsignal.utilities.guava.Optional +import org.session.libsignal.utilities.hexEncodedPublicKey +import org.thoughtcrime.securesms.crypto.KeyPairUtilities +import org.thoughtcrime.securesms.dependencies.DatabaseComponent +import org.thoughtcrime.securesms.groups.GroupManager +import java.security.SecureRandom +import java.util.* +import kotlin.random.asKotlinRandom + +object MockDataGenerator { + private var printProgress = true + private var hasStartedGenerationThisRun = false + + // FIXME: Update this to run in a transaction instead of individual db writes (should drastically speed it up) + fun generateMockData(context: Context) { + // Don't re-generate the mock data if it already exists + val mockDataExistsRecipient = Recipient.from(context, Address.fromSerialized("MockDatabaseThread"), false) + val storage = MessagingModuleConfiguration.shared.storage + val threadDb = DatabaseComponent.get(context).threadDatabase() + val lokiThreadDB = DatabaseComponent.get(context).lokiThreadDatabase() + val contactDb = DatabaseComponent.get(context).sessionContactDatabase() + val recipientDb = DatabaseComponent.get(context).recipientDatabase() + val smsDb = DatabaseComponent.get(context).smsDatabase() + + if (hasStartedGenerationThisRun || threadDb.getThreadIdIfExistsFor(mockDataExistsRecipient) != -1L) { + hasStartedGenerationThisRun = true + return + } + + /// The mock data generation is quite slow, there are 3 parts which take a decent amount of time (deleting the account afterwards will + /// also take a long time): + /// Generating the threads & content - ~3m per 100 + val dmThreadCount: Int = 1000 + val closedGroupThreadCount: Int = 50 + val openGroupThreadCount: Int = 20 + val messageRangePerThread: List = listOf(0..500) + val dmRandomSeed: String = "1111" + val cgRandomSeed: String = "2222" + val ogRandomSeed: String = "3333" + val chunkSize: Int = 1000 // Chunk up the thread writing to prevent memory issues + val stringContent: List = "abcdefghijklmnopqrstuvwxyz ABCDEFGHIJKLMNOPQRSTUVWXYZ 0123456789 ".map { it.toString() } + val wordContent: List = listOf("alias", "consequatur", "aut", "perferendis", "sit", "voluptatem", "accusantium", "doloremque", "aperiam", "eaque", "ipsa", "quae", "ab", "illo", "inventore", "veritatis", "et", "quasi", "architecto", "beatae", "vitae", "dicta", "sunt", "explicabo", "aspernatur", "aut", "odit", "aut", "fugit", "sed", "quia", "consequuntur", "magni", "dolores", "eos", "qui", "ratione", "voluptatem", "sequi", "nesciunt", "neque", "dolorem", "ipsum", "quia", "dolor", "sit", "amet", "consectetur", "adipisci", "velit", "sed", "quia", "non", "numquam", "eius", "modi", "tempora", "incidunt", "ut", "labore", "et", "dolore", "magnam", "aliquam", "quaerat", "voluptatem", "ut", "enim", "ad", "minima", "veniam", "quis", "nostrum", "exercitationem", "ullam", "corporis", "nemo", "enim", "ipsam", "voluptatem", "quia", "voluptas", "sit", "suscipit", "laboriosam", "nisi", "ut", "aliquid", "ex", "ea", "commodi", "consequatur", "quis", "autem", "vel", "eum", "iure", "reprehenderit", "qui", "in", "ea", "voluptate", "velit", "esse", "quam", "nihil", "molestiae", "et", "iusto", "odio", "dignissimos", "ducimus", "qui", "blanditiis", "praesentium", "laudantium", "totam", "rem", "voluptatum", "deleniti", "atque", "corrupti", "quos", "dolores", "et", "quas", "molestias", "excepturi", "sint", "occaecati", "cupiditate", "non", "provident", "sed", "ut", "perspiciatis", "unde", "omnis", "iste", "natus", "error", "similique", "sunt", "in", "culpa", "qui", "officia", "deserunt", "mollitia", "animi", "id", "est", "laborum", "et", "dolorum", "fuga", "et", "harum", "quidem", "rerum", "facilis", "est", "et", "expedita", "distinctio", "nam", "libero", "tempore", "cum", "soluta", "nobis", "est", "eligendi", "optio", "cumque", "nihil", "impedit", "quo", "porro", "quisquam", "est", "qui", "minus", "id", "quod", "maxime", "placeat", "facere", "possimus", "omnis", "voluptas", "assumenda", "est", "omnis", "dolor", "repellendus", "temporibus", "autem", "quibusdam", "et", "aut", "consequatur", "vel", "illum", "qui", "dolorem", "eum", "fugiat", "quo", "voluptas", "nulla", "pariatur", "at", "vero", "eos", "et", "accusamus", "officiis", "debitis", "aut", "rerum", "necessitatibus", "saepe", "eveniet", "ut", "et", "voluptates", "repudiandae", "sint", "et", "molestiae", "non", "recusandae", "itaque", "earum", "rerum", "hic", "tenetur", "a", "sapiente", "delectus", "ut", "aut", "reiciendis", "voluptatibus", "maiores", "doloribus", "asperiores", "repellat") + val timestampNow: Long = System.currentTimeMillis() + val userSessionId: String = MessagingModuleConfiguration.shared.storage.getUserPublicKey()!! + val logProgress: ((String, String) -> Unit) = logProgress@{ title, event -> + if (!printProgress) { return@logProgress } + + Log.i("[MockDataGenerator]", "${System.currentTimeMillis()} $title - $event") + } + + hasStartedGenerationThisRun = true + + // FIXME: Make sure this data doesn't go off device somehow? + logProgress("", "Start") + + // First create the thread used to indicate that the mock data has been generated + threadDb.getOrCreateThreadIdFor(mockDataExistsRecipient) + + // -- DM Thread + val dmThreadRandomGenerator: SecureRandom = SecureRandom(dmRandomSeed.toByteArray()) + var dmThreadIndex: Int = 0 + logProgress("DM Threads", "Start Generating $dmThreadCount threads") + + while (dmThreadIndex < dmThreadCount) { + val remainingThreads: Int = (dmThreadCount - dmThreadIndex) + + (0 until Math.min(chunkSize, remainingThreads)).forEach { index -> + val threadIndex: Int = (dmThreadIndex + index) + + logProgress("DM Thread $threadIndex", "Start") + + val dataBytes = (0 until 16).map { dmThreadRandomGenerator.nextInt(UByte.MAX_VALUE.toInt()).toByte() } + val randomSessionId: String = KeyPairUtilities.generate(dataBytes.toByteArray()).x25519KeyPair.hexEncodedPublicKey + val isMessageRequest: Boolean = dmThreadRandomGenerator.nextBoolean() + val contactNameLength: Int = (5 + dmThreadRandomGenerator.nextInt(15)) + + val numMessages: Int = ( + messageRangePerThread[threadIndex % messageRangePerThread.count()].first + + dmThreadRandomGenerator.nextInt(messageRangePerThread[threadIndex % messageRangePerThread.count()].last()) + ) + + // Generate the thread + val recipient = Recipient.from(context, Address.fromSerialized(randomSessionId), false) + val contact = Contact(randomSessionId) + val threadId = threadDb.getOrCreateThreadIdFor(recipient) + + // Generate the contact + val contactIsApproved: Boolean = (!isMessageRequest || dmThreadRandomGenerator.nextBoolean()) + contactDb.setContact(contact) + contactDb.setContactIsTrusted(contact, true, threadId) + recipientDb.setApproved(recipient, contactIsApproved) + recipientDb.setApprovedMe(recipient, (!isMessageRequest && (dmThreadRandomGenerator.nextInt(10) < 8))) // 80% approved the current user + + contact.name = (0 until dmThreadRandomGenerator.nextInt(contactNameLength)) + .map { stringContent.random(dmThreadRandomGenerator.asKotlinRandom()) } + .joinToString() + recipientDb.setProfileName(recipient, contact.name) + contactDb.setContact(contact) + + // Generate the message history (Note: Unapproved message requests will only include incoming messages) + logProgress("DM Thread $threadIndex", "Generate $numMessages Messages") + (0 until numMessages).forEach { index -> + val isIncoming: Boolean = ( + dmThreadRandomGenerator.nextBoolean() && + (!isMessageRequest || contactIsApproved) + ) + val messageWords: Int = (1 + dmThreadRandomGenerator.nextInt(19)) + + if (isIncoming) { + smsDb.insertMessageInbox( + IncomingTextMessage( + recipient.address, + 1, + (timestampNow - (index * 5000)), + (0 until messageWords) + .map { wordContent.random(dmThreadRandomGenerator.asKotlinRandom()) } + .joinToString(), + Optional.absent(), + 0, + false, + -1 + ), + (timestampNow - (index * 5000)), + false, + false + ) + } + else { + smsDb.insertMessageOutbox( + threadId, + OutgoingTextMessage( + recipient, + (0 until messageWords) + .map { wordContent.random(dmThreadRandomGenerator.asKotlinRandom()) } + .joinToString(), + 0, + -1, + (timestampNow - (index * 5000)) + ), + (timestampNow - (index * 5000)), + false + ) + } + } + + logProgress("DM Thread $threadIndex", "Done") + } + logProgress("DM Threads", "Done") + + dmThreadIndex += chunkSize + } + logProgress("DM Threads", "Done") + + // -- Closed Group + + val cgThreadRandomGenerator: SecureRandom = SecureRandom(cgRandomSeed.toByteArray()) + var cgThreadIndex: Int = 0 + logProgress("Closed Group Threads", "Start Generating $closedGroupThreadCount threads") + + while (cgThreadIndex < closedGroupThreadCount) { + val remainingThreads: Int = (closedGroupThreadCount - cgThreadIndex) + + (0 until Math.min(chunkSize, remainingThreads)).forEach { index -> + val threadIndex: Int = (cgThreadIndex + index) + + logProgress("Closed Group Thread $threadIndex", "Start") + + val dataBytes = (0 until 16).map { cgThreadRandomGenerator.nextInt(UByte.MAX_VALUE.toInt()).toByte() } + val randomGroupPublicKey: String = KeyPairUtilities.generate(dataBytes.toByteArray()).x25519KeyPair.hexEncodedPublicKey + val groupNameLength: Int = (5 + cgThreadRandomGenerator.nextInt(15)) + val groupName: String = (0 until groupNameLength) + .map { stringContent.random(cgThreadRandomGenerator.asKotlinRandom()) } + .joinToString() + val numGroupMembers: Int = cgThreadRandomGenerator.nextInt (10) + val numMessages: Int = ( + messageRangePerThread[threadIndex % messageRangePerThread.count()].first + + cgThreadRandomGenerator.nextInt(messageRangePerThread[threadIndex % messageRangePerThread.count()].last()) + ) + + // Generate the Contacts in the group + val members: MutableList = mutableListOf(userSessionId) + logProgress("Closed Group Thread $threadIndex", "Generate $numGroupMembers Contacts") + + (0 until numGroupMembers).forEach { + val contactBytes = (0 until 16).map { cgThreadRandomGenerator.nextInt(UByte.MAX_VALUE.toInt()).toByte() } + val randomSessionId: String = KeyPairUtilities.generate(contactBytes.toByteArray()).x25519KeyPair.hexEncodedPublicKey + val contactNameLength: Int = (5 + cgThreadRandomGenerator.nextInt(15)) + + val recipient = Recipient.from(context, Address.fromSerialized(randomSessionId), false) + val contact = Contact(randomSessionId) + contactDb.setContact(contact) + recipientDb.setApproved(recipient, true) + recipientDb.setApprovedMe(recipient, true) + + contact.name = (0 until cgThreadRandomGenerator.nextInt(contactNameLength)) + .map { stringContent.random(cgThreadRandomGenerator.asKotlinRandom()) } + .joinToString() + recipientDb.setProfileName(recipient, contact.name) + contactDb.setContact(contact) + members.add(randomSessionId) + } + + val groupId = GroupUtil.doubleEncodeGroupID(randomGroupPublicKey) + val threadId = storage.getOrCreateThreadIdFor(Address.fromSerialized(groupId)) + val adminUserId = members.random(cgThreadRandomGenerator.asKotlinRandom()) + storage.createGroup( + groupId, + groupName, + members.map { Address.fromSerialized(it) }, + null, + null, + listOf(Address.fromSerialized(adminUserId)), + timestampNow + ) + storage.setProfileSharing(Address.fromSerialized(groupId), true) + storage.addClosedGroupPublicKey(randomGroupPublicKey) + + // Add the group to the user's set of public keys to poll for and store the key pair + val encryptionKeyPair = Curve.generateKeyPair() + storage.addClosedGroupEncryptionKeyPair(encryptionKeyPair, randomGroupPublicKey) + storage.setExpirationTimer(groupId, 0) + + // Add the group created message + if (userSessionId == adminUserId) { + storage.insertOutgoingInfoMessage(context, groupId, SignalServiceGroup.Type.CREATION, groupName, members, listOf(adminUserId), threadId, (timestampNow - (numMessages * 5000))) + } + else { + storage.insertIncomingInfoMessage(context, adminUserId, groupId, SignalServiceGroup.Type.CREATION, groupName, members, listOf(adminUserId), (timestampNow - (numMessages * 5000))) + } + + // Generate the message history (Note: Unapproved message requests will only include incoming messages) + logProgress("Closed Group Thread $threadIndex", "Generate $numMessages Messages") + + (0 until numGroupMembers).forEach { + val messageWords: Int = (1 + cgThreadRandomGenerator.nextInt(19)) + val senderId: String = members.random(cgThreadRandomGenerator.asKotlinRandom()) + + if (senderId != userSessionId) { + smsDb.insertMessageInbox( + IncomingTextMessage( + Address.fromSerialized(senderId), + 1, + (timestampNow - (index * 5000)), + (0 until messageWords) + .map { wordContent.random(cgThreadRandomGenerator.asKotlinRandom()) } + .joinToString(), + Optional.absent(), + 0, + false, + -1 + ), + (timestampNow - (index * 5000)), + false, + false + ) + } + else { + smsDb.insertMessageOutbox( + threadId, + OutgoingTextMessage( + threadDb.getRecipientForThreadId(threadId), + (0 until messageWords) + .map { wordContent.random(cgThreadRandomGenerator.asKotlinRandom()) } + .joinToString(), + 0, + -1, + (timestampNow - (index * 5000)) + ), + (timestampNow - (index * 5000)), + false + ) + } + } + + logProgress("Closed Group Thread $threadIndex", "Done") + } + + cgThreadIndex += chunkSize + } + logProgress("Closed Group Threads", "Done") + + // --Open Group + + val ogThreadRandomGenerator: SecureRandom = SecureRandom(cgRandomSeed.toByteArray()) + var ogThreadIndex: Int = 0 + logProgress("Open Group Threads", "Start Generating $openGroupThreadCount threads") + + while (ogThreadIndex < openGroupThreadCount) { + val remainingThreads: Int = (openGroupThreadCount - ogThreadIndex) + + (0 until Math.min(chunkSize, remainingThreads)).forEach { index -> + val threadIndex: Int = (ogThreadIndex + index) + + logProgress("Open Group Thread $threadIndex", "Start") + + val dataBytes = (0 until 32).map { ogThreadRandomGenerator.nextInt(UByte.MAX_VALUE.toInt()).toByte() } + val randomGroupPublicKey: String = KeyPairUtilities.generate(dataBytes.toByteArray()).x25519KeyPair.hexEncodedPublicKey + val serverNameLength: Int = (5 + ogThreadRandomGenerator.nextInt(15)) + val roomNameLength: Int = (5 + ogThreadRandomGenerator.nextInt(15)) + val roomDescriptionLength: Int = (10 + ogThreadRandomGenerator.nextInt(40)) + val serverName: String = (0 until serverNameLength) + .map { stringContent.random(ogThreadRandomGenerator.asKotlinRandom()) } + .joinToString() + val roomName: String = (0 until roomNameLength) + .map { stringContent.random(ogThreadRandomGenerator.asKotlinRandom()) } + .joinToString() + val roomDescription: String = (0 until roomDescriptionLength) + .map { stringContent.random(ogThreadRandomGenerator.asKotlinRandom()) } + .joinToString() + val numGroupMembers: Int = ogThreadRandomGenerator.nextInt(250) + val numMessages: Int = ( + messageRangePerThread[threadIndex % messageRangePerThread.count()].first + + ogThreadRandomGenerator.nextInt(messageRangePerThread[threadIndex % messageRangePerThread.count()].last()) + ) + + // Generate the Contacts in the group + val members: MutableList = mutableListOf(userSessionId) + logProgress("Open Group Thread $threadIndex", "Generate $numGroupMembers Contacts") + + (0 until numGroupMembers).forEach { + val contactBytes = (0 until 16).map { ogThreadRandomGenerator.nextInt(UByte.MAX_VALUE.toInt()).toByte() } + val randomSessionId: String = KeyPairUtilities.generate(contactBytes.toByteArray()).x25519KeyPair.hexEncodedPublicKey + val contactNameLength: Int = (5 + ogThreadRandomGenerator.nextInt(15)) + + val recipient = Recipient.from(context, Address.fromSerialized(randomSessionId), false) + val contact = Contact(randomSessionId) + contactDb.setContact(contact) + recipientDb.setApproved(recipient, true) + recipientDb.setApprovedMe(recipient, true) + + contact.name = (0 until ogThreadRandomGenerator.nextInt(contactNameLength)) + .map { stringContent.random(cgThreadRandomGenerator.asKotlinRandom()) } + .joinToString() + recipientDb.setProfileName(recipient, contact.name) + contactDb.setContact(contact) + members.add(randomSessionId) + } + + // Create the open group model and the thread + val openGroupId = "$serverName.$roomName" + val threadId = GroupManager.createOpenGroup(openGroupId, context, null, roomName).threadId + val hasBlinding: Boolean = ogThreadRandomGenerator.nextBoolean() + + // Generate the capabilities and other data + storage.setOpenGroupPublicKey(serverName, randomGroupPublicKey) + storage.setServerCapabilities( + serverName, + ( + listOf(OpenGroupApi.Capability.SOGS.name.lowercase()) + + if (hasBlinding) { listOf(OpenGroupApi.Capability.BLIND.name.lowercase()) } else { emptyList() } + ) + ) + storage.setUserCount(roomName, serverName, numGroupMembers) + lokiThreadDB.setOpenGroupChat(OpenGroup(serverName, roomName, roomName, 0, randomGroupPublicKey), threadId) + + // Generate the message history (Note: Unapproved message requests will only include incoming messages) + logProgress("Open Group Thread $threadIndex", "Generate $numMessages Messages") + + (0 until numMessages).forEach { index -> + val messageWords: Int = (1 + ogThreadRandomGenerator.nextInt(19)) + val senderId: String = members.random(ogThreadRandomGenerator.asKotlinRandom()) + + if (senderId != userSessionId) { + smsDb.insertMessageInbox( + IncomingTextMessage( + Address.fromSerialized(senderId), + 1, + (timestampNow - (index * 5000)), + (0 until messageWords) + .map { wordContent.random(ogThreadRandomGenerator.asKotlinRandom()) } + .joinToString(), + Optional.absent(), + 0, + false, + -1 + ), + (timestampNow - (index * 5000)), + false, + false + ) + } else { + smsDb.insertMessageOutbox( + threadId, + OutgoingTextMessage( + threadDb.getRecipientForThreadId(threadId), + (0 until messageWords) + .map { wordContent.random(ogThreadRandomGenerator.asKotlinRandom()) } + .joinToString(), + 0, + -1, + (timestampNow - (index * 5000)) + ), + (timestampNow - (index * 5000)), + false + ) + } + } + + logProgress("Open Group Thread $threadIndex", "Done") + } + + ogThreadIndex += chunkSize + } + + logProgress("Open Group Threads", "Done") + logProgress("", "Complete") + } +} \ No newline at end of file diff --git a/libsession/src/main/java/org/session/libsession/messaging/open_groups/OpenGroupApi.kt b/libsession/src/main/java/org/session/libsession/messaging/open_groups/OpenGroupApi.kt index daa735aa4..d5bc6dde0 100644 --- a/libsession/src/main/java/org/session/libsession/messaging/open_groups/OpenGroupApi.kt +++ b/libsession/src/main/java/org/session/libsession/messaging/open_groups/OpenGroupApi.kt @@ -148,7 +148,7 @@ object OpenGroupApi { ) enum class Capability { - BLIND, REACTIONS + SOGS, BLIND, REACTIONS } @JsonNaming(PropertyNamingStrategy.SnakeCaseStrategy::class) From c0bef51fe0994010b1b10401c5e021fc5cf73444 Mon Sep 17 00:00:00 2001 From: Morgan Pretty Date: Fri, 6 Jan 2023 09:49:10 +1100 Subject: [PATCH 20/47] Fixed a couple of bugs where the HomeDiffUtil could incorrectly detect differences --- .../database/model/ThreadRecord.java | 6 ---- .../securesms/home/HomeDiffUtil.kt | 36 +++++++++++-------- 2 files changed, 21 insertions(+), 21 deletions(-) diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/model/ThreadRecord.java b/app/src/main/java/org/thoughtcrime/securesms/database/model/ThreadRecord.java index 6ce69a591..1e5a2fef0 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/model/ThreadRecord.java +++ b/app/src/main/java/org/thoughtcrime/securesms/database/model/ThreadRecord.java @@ -50,7 +50,6 @@ public class ThreadRecord extends DisplayRecord { private final long expiresIn; private final long lastSeen; private final boolean pinned; - private final int recipientHash; public ThreadRecord(@NonNull String body, @Nullable Uri snippetUri, @NonNull Recipient recipient, long date, long count, int unreadCount, @@ -67,17 +66,12 @@ public class ThreadRecord extends DisplayRecord { this.expiresIn = expiresIn; this.lastSeen = lastSeen; this.pinned = pinned; - this.recipientHash = recipient.hashCode(); } public @Nullable Uri getSnippetUri() { return snippetUri; } - public int getRecipientHash() { - return recipientHash; - } - @Override public SpannableString getDisplayBody(@NonNull Context context) { if (isGroupUpdateMessage()) { diff --git a/app/src/main/java/org/thoughtcrime/securesms/home/HomeDiffUtil.kt b/app/src/main/java/org/thoughtcrime/securesms/home/HomeDiffUtil.kt index fcaf565e0..1baec2085 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/home/HomeDiffUtil.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/home/HomeDiffUtil.kt @@ -22,22 +22,28 @@ class HomeDiffUtil( val newItem = new[newItemPosition] // return early to save getDisplayBody or expensive calls - val sameCount = oldItem.count == newItem.count - if (!sameCount) return false - val sameUnreads = oldItem.unreadCount == newItem.unreadCount - if (!sameUnreads) return false - val samePinned = oldItem.isPinned == newItem.isPinned - if (!samePinned) return false - val sameRecipientHash = oldItem.recipientHash == newItem.recipientHash - if (!sameRecipientHash) return false - val sameSnippet = oldItem.getDisplayBody(context) == newItem.getDisplayBody(context) - if (!sameSnippet) return false - val sameSendStatus = oldItem.isFailed == newItem.isFailed && oldItem.isDelivered == newItem.isDelivered - && oldItem.isSent == newItem.isSent && oldItem.isPending == newItem.isPending - if (!sameSendStatus) return false + var isSameItem = true - // all same - return true + if (isSameItem) { isSameItem = (oldItem.count == newItem.count) } + if (isSameItem) { isSameItem = (oldItem.unreadCount == newItem.unreadCount) } + if (isSameItem) { isSameItem = (oldItem.isPinned == newItem.isPinned) } + + // Note: For some reason the 'hashCode' value can change after initialisation so we can't cache it + if (isSameItem) { isSameItem = (oldItem.recipient.hashCode() == newItem.recipient.hashCode()) } + + // Note: Two instances of 'SpannableString' may not equate even though their content matches + if (isSameItem) { isSameItem = (oldItem.getDisplayBody(context).toString() == newItem.getDisplayBody(context).toString()) } + + if (isSameItem) { + isSameItem = ( + oldItem.isFailed == newItem.isFailed && + oldItem.isDelivered == newItem.isDelivered && + oldItem.isSent == newItem.isSent && + oldItem.isPending == newItem.isPending + ) + } + + return isSameItem } } \ No newline at end of file From e7b6ddacbb82c5fbce227a7d9f25817e7992d435 Mon Sep 17 00:00:00 2001 From: Morgan Pretty Date: Fri, 6 Jan 2023 09:50:31 +1100 Subject: [PATCH 21/47] Shifted a number of db writes when opening conversations to the IO thread so they don't block --- .../conversation/v2/ConversationActivityV2.kt | 6 +++++- .../conversation/v2/ConversationViewModel.kt | 14 ++++++++++++-- .../securesms/repository/ConversationRepository.kt | 6 +++++- 3 files changed, 22 insertions(+), 4 deletions(-) diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationActivityV2.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationActivityV2.kt index 63d36c0aa..6bdf95474 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationActivityV2.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationActivityV2.kt @@ -343,7 +343,11 @@ class ConversationActivityV2 : PassphraseRequiredActionBarActivity(), InputBarDe super.onResume() ApplicationContext.getInstance(this).messageNotifier.setVisibleThread(viewModel.threadId) val recipient = viewModel.recipient ?: return - threadDb.markAllAsRead(viewModel.threadId, recipient.isOpenGroupRecipient) + + lifecycleScope.launch(Dispatchers.IO) { + threadDb.markAllAsRead(viewModel.threadId, recipient.isOpenGroupRecipient) + } + contentResolver.registerContentObserver( MediaStore.Images.Media.EXTERNAL_CONTENT_URI, true, diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationViewModel.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationViewModel.kt index 8db66e080..9a5610896 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationViewModel.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationViewModel.kt @@ -6,6 +6,8 @@ import androidx.lifecycle.viewModelScope import com.goterl.lazysodium.utils.KeyPair import dagger.assisted.Assisted import dagger.assisted.AssistedInject +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.GlobalScope import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.update @@ -48,11 +50,19 @@ class ConversationViewModel( } fun saveDraft(text: String) { - repository.saveDraft(threadId, text) + GlobalScope.launch(Dispatchers.IO) { + repository.saveDraft(threadId, text) + } } fun getDraft(): String? { - return repository.getDraft(threadId) + val draft: String? = repository.getDraft(threadId) + + viewModelScope.launch(Dispatchers.IO) { + repository.clearDrafts(threadId) + } + + return draft } fun inviteContacts(contacts: List) { diff --git a/app/src/main/java/org/thoughtcrime/securesms/repository/ConversationRepository.kt b/app/src/main/java/org/thoughtcrime/securesms/repository/ConversationRepository.kt index 00f5d72c7..2d6789401 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/repository/ConversationRepository.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/repository/ConversationRepository.kt @@ -35,6 +35,7 @@ interface ConversationRepository { fun maybeGetRecipientForThreadId(threadId: Long): Recipient? fun saveDraft(threadId: Long, text: String) fun getDraft(threadId: Long): String? + fun clearDrafts(threadId: Long) fun inviteContacts(threadId: Long, contacts: List) fun setBlocked(recipient: Recipient, blocked: Boolean) fun deleteLocally(recipient: Recipient, message: MessageRecord) @@ -98,10 +99,13 @@ class DefaultConversationRepository @Inject constructor( override fun getDraft(threadId: Long): String? { val drafts = draftDb.getDrafts(threadId) - draftDb.clearDrafts(threadId) return drafts.find { it.type == DraftDatabase.Draft.TEXT }?.value } + override fun clearDrafts(threadId: Long) { + draftDb.clearDrafts(threadId) + } + override fun inviteContacts(threadId: Long, contacts: List) { val openGroup = lokiThreadDb.getOpenGroupChat(threadId) ?: return for (contact in contacts) { From a1b052ef8223f67d2d675e8e896b5c158d4ad61e Mon Sep 17 00:00:00 2001 From: Morgan Pretty Date: Fri, 6 Jan 2023 10:39:01 +1100 Subject: [PATCH 22/47] Added indexes to the Reactions database --- .../thoughtcrime/securesms/database/ReactionDatabase.kt | 8 ++++++++ .../securesms/database/helpers/SQLCipherOpenHelper.java | 8 +++++++- 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/ReactionDatabase.kt b/app/src/main/java/org/thoughtcrime/securesms/database/ReactionDatabase.kt index 74e452db0..87c0b6c18 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/ReactionDatabase.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/database/ReactionDatabase.kt @@ -48,6 +48,14 @@ class ReactionDatabase(context: Context, helper: SQLCipherOpenHelper) : Database ) """.trimIndent() + @JvmField + val CREATE_INDEXS = arrayOf( + "CREATE INDEX IF NOT EXISTS reaction_message_id_index ON " + ReactionDatabase.TABLE_NAME + " (" + ReactionDatabase.MESSAGE_ID + ");", + "CREATE INDEX IF NOT EXISTS reaction_is_mms_index ON " + ReactionDatabase.TABLE_NAME + " (" + ReactionDatabase.IS_MMS + ");", + "CREATE INDEX IF NOT EXISTS reaction_message_id_is_mms_index ON " + ReactionDatabase.TABLE_NAME + " (" + ReactionDatabase.MESSAGE_ID + ", " + ReactionDatabase.IS_MMS + ");", + "CREATE INDEX IF NOT EXISTS reaction_sort_id_index ON " + ReactionDatabase.TABLE_NAME + " (" + ReactionDatabase.SORT_ID + ");", + ) + @JvmField val CREATE_REACTION_TRIGGERS = arrayOf( """ 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 d145a33a5..7ab6bfad9 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 @@ -79,9 +79,10 @@ public class SQLCipherOpenHelper extends SQLiteOpenHelper { private static final int lokiV36 = 57; private static final int lokiV37 = 58; private static final int lokiV38 = 59; + private static final int lokiV39 = 60; // Loki - onUpgrade(...) must be updated to use Loki version numbers if Signal makes any database changes - private static final int DATABASE_VERSION = lokiV38; + private static final int DATABASE_VERSION = lokiV39; private static final int MIN_DATABASE_VERSION = lokiV7; private static final String CIPHER3_DATABASE_NAME = "signal.db"; public static final String DATABASE_NAME = "signal_v4.db"; @@ -272,6 +273,7 @@ public class SQLCipherOpenHelper extends SQLiteOpenHelper { executeStatements(db, DraftDatabase.CREATE_INDEXS); executeStatements(db, GroupDatabase.CREATE_INDEXS); executeStatements(db, GroupReceiptDatabase.CREATE_INDEXES); + executeStatements(db, ReactionDatabase.CREATE_INDEXS); executeStatements(db, ReactionDatabase.CREATE_REACTION_TRIGGERS); } @@ -496,6 +498,10 @@ public class SQLCipherOpenHelper extends SQLiteOpenHelper { db.execSQL(EmojiSearchDatabase.CREATE_EMOJI_SEARCH_TABLE_COMMAND); } + if (oldVersion < lokiV39) { + executeStatements(db, ReactionDatabase.CREATE_INDEXS); + } + db.setTransactionSuccessful(); } finally { db.endTransaction(); From d0a4bac83e233d98d1de67e4b00e01e04f6a8edc Mon Sep 17 00:00:00 2001 From: Morgan Pretty Date: Fri, 6 Jan 2023 15:36:31 +1100 Subject: [PATCH 23/47] Shifted the creation of AttachmentDownloadJobs to the IO thread --- .../conversation/v2/ConversationAdapter.kt | 18 +++++++++--- .../v2/components/AlbumThumbnailView.kt | 13 +++++++-- .../v2/messages/VisibleMessageContentView.kt | 28 +++++++++++++++---- .../v2/messages/VisibleMessageView.kt | 5 +++- 4 files changed, 50 insertions(+), 14 deletions(-) diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationAdapter.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationAdapter.kt index 17a47a843..721edad31 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationAdapter.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationAdapter.kt @@ -40,9 +40,8 @@ class ConversationAdapter( private val onItemLongPress: (MessageRecord, Int, VisibleMessageView) -> Unit, private val onDeselect: (MessageRecord, Int) -> Unit, private val glide: GlideRequests, - lifecycleCoroutineScope: LifecycleCoroutineScope -) - : CursorRecyclerViewAdapter(context, cursor) { + private val lifecycleCoroutineScope: LifecycleCoroutineScope +) : CursorRecyclerViewAdapter(context, cursor) { private val messageDB by lazy { DatabaseComponent.get(context).mmsSmsDatabase() } private val contactDB by lazy { DatabaseComponent.get(context).sessionContactDatabase() } var selectedItems = mutableSetOf() @@ -120,7 +119,18 @@ class ConversationAdapter( } val contact = contactCache[senderIdHash] - visibleMessageView.bind(message, messageBefore, getMessageAfter(position, cursor), glide, searchQuery, contact, senderId, visibleMessageViewDelegate) + visibleMessageView.bind( + message, + messageBefore, + getMessageAfter(position, cursor), + glide, + searchQuery, + contact, + senderId, + visibleMessageViewDelegate, + lifecycleCoroutineScope + ) + if (!message.isDeleted) { visibleMessageView.onPress = { event -> onItemPress(message, viewHolder.adapterPosition, visibleMessageView, event) } visibleMessageView.onSwipeToReply = { onItemSwipeToReply(message, viewHolder.adapterPosition) } diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/components/AlbumThumbnailView.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/components/AlbumThumbnailView.kt index 8f0ddd8be..0825e9b20 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/components/AlbumThumbnailView.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/components/AlbumThumbnailView.kt @@ -11,6 +11,9 @@ import android.widget.FrameLayout import android.widget.TextView import androidx.core.view.children import androidx.core.view.isVisible +import androidx.lifecycle.LifecycleCoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch import network.loki.messenger.R import network.loki.messenger.databinding.AlbumThumbnailViewBinding import org.session.libsession.messaging.jobs.AttachmentDownloadJob @@ -63,7 +66,7 @@ class AlbumThumbnailView : FrameLayout { // region Interaction - fun calculateHitObject(event: MotionEvent, mms: MmsMessageRecord, threadRecipient: Recipient) { + fun calculateHitObject(event: MotionEvent, mms: MmsMessageRecord, threadRecipient: Recipient, lifecycleCoroutineScope: LifecycleCoroutineScope) { val rawXInt = event.rawX.toInt() val rawYInt = event.rawY.toInt() val eventRect = Rect(rawXInt, rawYInt, rawXInt, rawYInt) @@ -76,10 +79,14 @@ class AlbumThumbnailView : FrameLayout { val slide = slides.getOrNull(index) ?: return // only open to downloaded images if (slide.transferState == AttachmentTransferProgress.TRANSFER_PROGRESS_FAILED) { - // restart download here + // Restart download here (on IO thread) (slide.asAttachment() as? DatabaseAttachment)?.let { attachment -> val attachmentId = attachment.attachmentId.rowId - JobQueue.shared.add(AttachmentDownloadJob(attachmentId, mms.getId())) + + // Start download (on IO thread) + lifecycleCoroutineScope.launch(Dispatchers.IO) { + JobQueue.shared.add(AttachmentDownloadJob(attachmentId, mms.getId())) + } } } if (slide.isInProgress) return diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/messages/VisibleMessageContentView.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/messages/VisibleMessageContentView.kt index c6fc57c28..fccc88a2a 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/messages/VisibleMessageContentView.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/messages/VisibleMessageContentView.kt @@ -23,6 +23,9 @@ import androidx.core.graphics.BlendModeCompat import androidx.core.text.getSpans import androidx.core.text.toSpannable import androidx.core.view.isVisible +import androidx.lifecycle.LifecycleCoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch import network.loki.messenger.R import network.loki.messenger.databinding.ViewVisibleMessageContentBinding import okhttp3.HttpUrl @@ -65,8 +68,16 @@ class VisibleMessageContentView : LinearLayout { // endregion // region Updating - fun bind(message: MessageRecord, isStartOfMessageCluster: Boolean, isEndOfMessageCluster: Boolean, - glide: GlideRequests, thread: Recipient, searchQuery: String?, contactIsTrusted: Boolean) { + fun bind( + message: MessageRecord, + isStartOfMessageCluster: Boolean, + isEndOfMessageCluster: Boolean, + glide: GlideRequests, + thread: Recipient, + searchQuery: String?, + contactIsTrusted: Boolean, + lifecycleCoroutineScope: LifecycleCoroutineScope + ) { // Background val background = getBackground(message.isOutgoing) val color = if (message.isOutgoing) context.getAccentColor() @@ -141,8 +152,10 @@ class VisibleMessageContentView : LinearLayout { val attachmentId = dbAttachment.attachmentId.rowId if (attach.transferState == AttachmentTransferProgress.TRANSFER_PROGRESS_PENDING && MessagingModuleConfiguration.shared.storage.getAttachmentUploadJob(attachmentId) == null) { - // start download - JobQueue.shared.add(AttachmentDownloadJob(attachmentId, dbAttachment.mmsId)) + // Start download (on IO thread) + lifecycleCoroutineScope.launch(Dispatchers.IO) { + JobQueue.shared.add(AttachmentDownloadJob(attachmentId, dbAttachment.mmsId)) + } } } message.linkPreviews.forEach { preview -> @@ -150,7 +163,10 @@ class VisibleMessageContentView : LinearLayout { val attachmentId = previewThumbnail.attachmentId.rowId if (previewThumbnail.transferState == AttachmentTransferProgress.TRANSFER_PROGRESS_PENDING && MessagingModuleConfiguration.shared.storage.getAttachmentUploadJob(attachmentId) == null) { - JobQueue.shared.add(AttachmentDownloadJob(attachmentId, previewThumbnail.mmsId)) + // Start download (on IO thread) + lifecycleCoroutineScope.launch(Dispatchers.IO) { + JobQueue.shared.add(AttachmentDownloadJob(attachmentId, previewThumbnail.mmsId)) + } } } } @@ -205,7 +221,7 @@ class VisibleMessageContentView : LinearLayout { layoutParams.horizontalBias = if (message.isOutgoing) 1f else 0f binding.albumThumbnailView.layoutParams = layoutParams onContentClick.add { event -> - binding.albumThumbnailView.calculateHitObject(event, message, thread) + binding.albumThumbnailView.calculateHitObject(event, message, thread, lifecycleCoroutineScope) } } else { hideBody = true diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/messages/VisibleMessageView.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/messages/VisibleMessageView.kt index 0b4c1455f..b3f1ac15e 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/messages/VisibleMessageView.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/messages/VisibleMessageView.kt @@ -20,6 +20,7 @@ import androidx.core.os.bundleOf import androidx.core.view.isInvisible import androidx.core.view.isVisible import androidx.core.view.marginBottom +import androidx.lifecycle.LifecycleCoroutineScope import dagger.hilt.android.AndroidEntryPoint import network.loki.messenger.R import network.loki.messenger.databinding.ViewVisibleMessageBinding @@ -122,6 +123,7 @@ class VisibleMessageView : LinearLayout { contact: Contact?, senderSessionID: String, delegate: VisibleMessageViewDelegate?, + lifecycleCoroutineScope: LifecycleCoroutineScope ) { val threadID = message.threadId val thread = threadDb.getRecipientForThreadId(threadID) ?: return @@ -230,7 +232,8 @@ class VisibleMessageView : LinearLayout { glide, thread, searchQuery, - message.isOutgoing || isGroupThread || (contact?.isTrusted ?: false) + message.isOutgoing || isGroupThread || (contact?.isTrusted ?: false), + lifecycleCoroutineScope ) binding.messageContentView.delegate = delegate onDoubleTap = { binding.messageContentView.onContentDoubleTap?.invoke() } From 5afd6476867956b4c286ca51b82d9f1f8c3fed3e Mon Sep 17 00:00:00 2001 From: Morgan Pretty Date: Mon, 9 Jan 2023 15:22:29 +1100 Subject: [PATCH 24/47] Tweaked some open group handling and a couple of onboarding issues Updated the OpenGroup adding and polling logic to reduce duplicate API calls Updated the BackgroundGroupAddJob to start a GroupAvatarDownloadJob instead of running the download itself (to appear to run faster) Defaulted OpenGroups to use blinded auth when no server capabilities are present Fixed an issue where the background poller could be started even though the onboarding hadn't been completed Fixed an issue where the database could get into an invalid state if the app was restarted during onboarding --- .../securesms/ApplicationContext.java | 6 +++++ .../securesms/database/LokiAPIDatabase.kt | 10 ++++++++ .../securesms/groups/OpenGroupManager.kt | 24 +++++++++---------- .../notifications/BackgroundPollWorker.kt | 2 +- .../onboarding/LinkDeviceActivity.kt | 9 +++++++ .../RecoveryPhraseRestoreActivity.kt | 9 +++++++ .../securesms/onboarding/RegisterActivity.kt | 9 +++++++ .../messaging/jobs/BackgroundGroupAddJob.kt | 11 +++------ .../messaging/jobs/GroupAvatarDownloadJob.kt | 5 ++-- .../messaging/open_groups/OpenGroup.kt | 14 +++++++---- .../messaging/open_groups/OpenGroupApi.kt | 19 +++++++-------- .../pollers/OpenGroupPoller.kt | 24 +++++++------------ .../database/LokiAPIDatabaseProtocol.kt | 2 ++ 13 files changed, 89 insertions(+), 55 deletions(-) diff --git a/app/src/main/java/org/thoughtcrime/securesms/ApplicationContext.java b/app/src/main/java/org/thoughtcrime/securesms/ApplicationContext.java index e253d0b96..4f1270acc 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/ApplicationContext.java +++ b/app/src/main/java/org/thoughtcrime/securesms/ApplicationContext.java @@ -245,6 +245,12 @@ public class ApplicationContext extends Application implements DefaultLifecycleO Log.i(TAG, "App is now visible."); KeyCachingService.onAppForegrounded(this); + // If the user account hasn't been created or onboarding wasn't finished then don't start + // the pollers + if (TextSecurePreferences.getLocalNumber(this) == null || !TextSecurePreferences.hasSeenWelcomeScreen(this)) { + return; + } + ThreadUtils.queue(()->{ if (poller != null) { poller.setCaughtUp(false); diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/LokiAPIDatabase.kt b/app/src/main/java/org/thoughtcrime/securesms/database/LokiAPIDatabase.kt index 6aeadc2b7..0300f1fcc 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/LokiAPIDatabase.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/database/LokiAPIDatabase.kt @@ -300,6 +300,11 @@ class LokiAPIDatabase(context: Context, helper: SQLCipherOpenHelper) : Database( val lastHash = database.insertOrUpdate(lastMessageHashValueTable2, row, query, arrayOf( snode.toString(), publicKey, namespace.toString() )) } + override fun clearAllLastMessageHashes() { + val database = databaseHelper.writableDatabase + database.delete(lastMessageHashValueTable2, null, null) + } + override fun getReceivedMessageHashValues(publicKey: String, namespace: Int): Set? { val database = databaseHelper.readableDatabase val query = "${Companion.publicKey} = ? AND ${Companion.receivedMessageHashNamespace} = ?" @@ -321,6 +326,11 @@ class LokiAPIDatabase(context: Context, helper: SQLCipherOpenHelper) : Database( database.insertOrUpdate(receivedMessageHashValuesTable, row, query, arrayOf( publicKey, namespace.toString() )) } + override fun clearReceivedMessageHashValues() { + val database = databaseHelper.writableDatabase + database.delete(receivedMessageHashValuesTable, null, null) + } + override fun getAuthToken(server: String): String? { val database = databaseHelper.readableDatabase return database.get(openGroupAuthTokenTable, "${Companion.server} = ?", wrap(server)) { cursor -> diff --git a/app/src/main/java/org/thoughtcrime/securesms/groups/OpenGroupManager.kt b/app/src/main/java/org/thoughtcrime/securesms/groups/OpenGroupManager.kt index d39ba709d..09c6f19da 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/groups/OpenGroupManager.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/groups/OpenGroupManager.kt @@ -58,14 +58,14 @@ object OpenGroupManager { } @WorkerThread - fun add(server: String, room: String, publicKey: String, context: Context) { + fun add(server: String, room: String, publicKey: String, context: Context): OpenGroupApi.RoomInfo? { val openGroupID = "$server.$room" var threadID = GroupManager.getOpenGroupThreadID(openGroupID, context) val storage = MessagingModuleConfiguration.shared.storage val threadDB = DatabaseComponent.get(context).lokiThreadDatabase() // Check it it's added already val existingOpenGroup = threadDB.getOpenGroupChat(threadID) - if (existingOpenGroup != null) { return } + if (existingOpenGroup != null) { return null } // Clear any existing data if needed storage.removeLastDeletionServerID(room, server) storage.removeLastMessageServerID(room, server) @@ -73,18 +73,17 @@ object OpenGroupManager { storage.removeLastOutboxMessageId(server) // Store the public key storage.setOpenGroupPublicKey(server, publicKey) - // Get capabilities - val capabilities = OpenGroupApi.getCapabilities(server).get() + // Get capabilities & room info + val (capabilities, info) = OpenGroupApi.getCapabilitiesAndRoomInfo(room, server).get() storage.setServerCapabilities(server, capabilities.capabilities) - // Get room info - val info = OpenGroupApi.getRoomInfo(room, server).get() storage.setUserCount(room, server, info.activeUsers) // Create the group locally if not available already if (threadID < 0) { threadID = GroupManager.createOpenGroup(openGroupID, context, null, info.name).threadId } - val openGroup = OpenGroup(server, room, info.name, info.infoUpdates, publicKey) + val openGroup = OpenGroup(server, room, publicKey, info.name, info.imageId, info.infoUpdates) threadDB.setOpenGroupChat(openGroup, threadID) + return info } fun restartPollerForServer(server: String) { @@ -130,12 +129,13 @@ object OpenGroupManager { } } - fun addOpenGroup(urlAsString: String, context: Context) { - val url = HttpUrl.parse(urlAsString) ?: return + fun addOpenGroup(urlAsString: String, context: Context): OpenGroupApi.RoomInfo? { + val url = HttpUrl.parse(urlAsString) ?: return null val server = OpenGroup.getServer(urlAsString) - val room = url.pathSegments().firstOrNull() ?: return - val publicKey = url.queryParameter("public_key") ?: return - add(server.toString().removeSuffix("/"), room, publicKey, context) // assume migrated from calling function + val room = url.pathSegments().firstOrNull() ?: return null + val publicKey = url.queryParameter("public_key") ?: return null + + return add(server.toString().removeSuffix("/"), room, publicKey, context) // assume migrated from calling function } fun updateOpenGroup(openGroup: OpenGroup, context: Context) { diff --git a/app/src/main/java/org/thoughtcrime/securesms/notifications/BackgroundPollWorker.kt b/app/src/main/java/org/thoughtcrime/securesms/notifications/BackgroundPollWorker.kt index 48a572552..5a0438e15 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/notifications/BackgroundPollWorker.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/notifications/BackgroundPollWorker.kt @@ -44,7 +44,7 @@ class BackgroundPollWorker(val context: Context, params: WorkerParameters) : Wor } override fun doWork(): Result { - if (TextSecurePreferences.getLocalNumber(context) == null) { + if (TextSecurePreferences.getLocalNumber(context) == null || !TextSecurePreferences.hasSeenWelcomeScreen(context)) { Log.v(TAG, "User not registered yet.") return Result.failure() } diff --git a/app/src/main/java/org/thoughtcrime/securesms/onboarding/LinkDeviceActivity.kt b/app/src/main/java/org/thoughtcrime/securesms/onboarding/LinkDeviceActivity.kt index ee1631a00..31117ae94 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/onboarding/LinkDeviceActivity.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/onboarding/LinkDeviceActivity.kt @@ -22,8 +22,10 @@ import kotlinx.coroutines.launch import network.loki.messenger.R import network.loki.messenger.databinding.ActivityLinkDeviceBinding import network.loki.messenger.databinding.FragmentRecoveryPhraseBinding +import org.session.libsession.snode.SnodeModule import org.session.libsession.utilities.TextSecurePreferences import org.session.libsignal.crypto.MnemonicCodec +import org.session.libsignal.database.LokiAPIDatabaseProtocol import org.session.libsignal.utilities.Hex import org.session.libsignal.utilities.KeyHelper import org.session.libsignal.utilities.Log @@ -39,6 +41,8 @@ import org.thoughtcrime.securesms.util.setUpActionBarSessionLogo class LinkDeviceActivity : BaseActionBarActivity(), ScanQRCodeWrapperFragmentDelegate { private lateinit var binding: ActivityLinkDeviceBinding + internal val database: LokiAPIDatabaseProtocol + get() = SnodeModule.shared.storage private val adapter = LinkDeviceActivityAdapter(this) private var restoreJob: Job? = null @@ -99,6 +103,11 @@ class LinkDeviceActivity : BaseActionBarActivity(), ScanQRCodeWrapperFragmentDel if (restoreJob?.isActive == true) return restoreJob = lifecycleScope.launch { + // This is here to resolve a case where the app restarts before a user completes onboarding + // which can result in an invalid database state + database.clearAllLastMessageHashes() + database.clearReceivedMessageHashValues() + // RestoreActivity handles seed this way val keyPairGenerationResult = KeyPairUtilities.generate(seed) val x25519KeyPair = keyPairGenerationResult.x25519KeyPair diff --git a/app/src/main/java/org/thoughtcrime/securesms/onboarding/RecoveryPhraseRestoreActivity.kt b/app/src/main/java/org/thoughtcrime/securesms/onboarding/RecoveryPhraseRestoreActivity.kt index 6a1c785ad..5531fea49 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/onboarding/RecoveryPhraseRestoreActivity.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/onboarding/RecoveryPhraseRestoreActivity.kt @@ -13,8 +13,10 @@ import android.view.View import android.widget.Toast import network.loki.messenger.R import network.loki.messenger.databinding.ActivityRecoveryPhraseRestoreBinding +import org.session.libsession.snode.SnodeModule import org.session.libsession.utilities.TextSecurePreferences import org.session.libsignal.crypto.MnemonicCodec +import org.session.libsignal.database.LokiAPIDatabaseProtocol import org.session.libsignal.utilities.Hex import org.session.libsignal.utilities.KeyHelper import org.session.libsignal.utilities.hexEncodedPublicKey @@ -26,6 +28,8 @@ import org.thoughtcrime.securesms.util.setUpActionBarSessionLogo class RecoveryPhraseRestoreActivity : BaseActionBarActivity() { private lateinit var binding: ActivityRecoveryPhraseRestoreBinding + internal val database: LokiAPIDatabaseProtocol + get() = SnodeModule.shared.storage // region Lifecycle override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) @@ -64,6 +68,11 @@ class RecoveryPhraseRestoreActivity : BaseActionBarActivity() { private fun restore() { val mnemonic = binding.mnemonicEditText.text.toString() try { + // This is here to resolve a case where the app restarts before a user completes onboarding + // which can result in an invalid database state + database.clearAllLastMessageHashes() + database.clearReceivedMessageHashValues() + val loadFileContents: (String) -> String = { fileName -> MnemonicUtilities.loadFileContents(this, fileName) } diff --git a/app/src/main/java/org/thoughtcrime/securesms/onboarding/RegisterActivity.kt b/app/src/main/java/org/thoughtcrime/securesms/onboarding/RegisterActivity.kt index 0105fbedf..b6fdaf9cf 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/onboarding/RegisterActivity.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/onboarding/RegisterActivity.kt @@ -18,8 +18,10 @@ import android.widget.Toast import com.goterl.lazysodium.utils.KeyPair import network.loki.messenger.R import network.loki.messenger.databinding.ActivityRegisterBinding +import org.session.libsession.snode.SnodeModule import org.session.libsession.utilities.TextSecurePreferences import org.session.libsignal.crypto.ecc.ECKeyPair +import org.session.libsignal.database.LokiAPIDatabaseProtocol import org.session.libsignal.utilities.KeyHelper import org.session.libsignal.utilities.hexEncodedPublicKey import org.thoughtcrime.securesms.BaseActionBarActivity @@ -29,6 +31,8 @@ import org.thoughtcrime.securesms.util.setUpActionBarSessionLogo class RegisterActivity : BaseActionBarActivity() { private lateinit var binding: ActivityRegisterBinding + internal val database: LokiAPIDatabaseProtocol + get() = SnodeModule.shared.storage private var seed: ByteArray? = null private var ed25519KeyPair: KeyPair? = null private var x25519KeyPair: ECKeyPair? = null @@ -109,6 +113,11 @@ class RegisterActivity : BaseActionBarActivity() { // region Interaction private fun register() { + // This is here to resolve a case where the app restarts before a user completes onboarding + // which can result in an invalid database state + database.clearAllLastMessageHashes() + database.clearReceivedMessageHashValues() + KeyPairUtilities.store(this, seed!!, ed25519KeyPair!!, x25519KeyPair!!) val userHexEncodedPublicKey = x25519KeyPair!!.hexEncodedPublicKey val registrationID = KeyHelper.generateRegistrationId(false) diff --git a/libsession/src/main/java/org/session/libsession/messaging/jobs/BackgroundGroupAddJob.kt b/libsession/src/main/java/org/session/libsession/messaging/jobs/BackgroundGroupAddJob.kt index aa37e0f0a..c679724b9 100644 --- a/libsession/src/main/java/org/session/libsession/messaging/jobs/BackgroundGroupAddJob.kt +++ b/libsession/src/main/java/org/session/libsession/messaging/jobs/BackgroundGroupAddJob.kt @@ -41,15 +41,10 @@ class BackgroundGroupAddJob(val joinUrl: String): Job { } // get image storage.setOpenGroupPublicKey(openGroup.server, openGroup.serverPublicKey) - val (capabilities, info) = OpenGroupApi.getCapabilitiesAndRoomInfo(openGroup.room, openGroup.server, false).get() - storage.setServerCapabilities(openGroup.server, capabilities.capabilities) - val imageId = info.imageId - storage.addOpenGroup(openGroup.joinUrl()) + val info = storage.addOpenGroup(openGroup.joinUrl()) + val imageId = info?.imageId if (imageId != null) { - val bytes = OpenGroupApi.downloadOpenGroupProfilePicture(openGroup.server, openGroup.room, imageId).get() - val groupId = GroupUtil.getEncodedOpenGroupID("${openGroup.server}.${openGroup.room}".toByteArray()) - storage.updateProfilePicture(groupId, bytes) - storage.updateTimestampUpdated(groupId, System.currentTimeMillis()) + JobQueue.shared.add(GroupAvatarDownloadJob(openGroup.room, openGroup.server)) } Log.d(KEY, "onOpenGroupAdded(${openGroup.server})") storage.onOpenGroupAdded(openGroup.server) diff --git a/libsession/src/main/java/org/session/libsession/messaging/jobs/GroupAvatarDownloadJob.kt b/libsession/src/main/java/org/session/libsession/messaging/jobs/GroupAvatarDownloadJob.kt index 38e8831fb..02f792117 100644 --- a/libsession/src/main/java/org/session/libsession/messaging/jobs/GroupAvatarDownloadJob.kt +++ b/libsession/src/main/java/org/session/libsession/messaging/jobs/GroupAvatarDownloadJob.kt @@ -14,10 +14,9 @@ class GroupAvatarDownloadJob(val room: String, val server: String) : Job { override fun execute() { val storage = MessagingModuleConfiguration.shared.storage + val imageId = storage.getOpenGroup(room, server)?.imageId ?: return try { - val info = OpenGroupApi.getRoomInfo(room, server).get() - val imageId = info.imageId ?: return - val bytes = OpenGroupApi.downloadOpenGroupProfilePicture(server, info.token, imageId).get() + val bytes = OpenGroupApi.downloadOpenGroupProfilePicture(server, room, imageId).get() val groupId = GroupUtil.getEncodedOpenGroupID("$server.$room".toByteArray()) storage.updateProfilePicture(groupId, bytes) storage.updateTimestampUpdated(groupId, System.currentTimeMillis()) diff --git a/libsession/src/main/java/org/session/libsession/messaging/open_groups/OpenGroup.kt b/libsession/src/main/java/org/session/libsession/messaging/open_groups/OpenGroup.kt index 9efeaf15d..b2bdd6384 100644 --- a/libsession/src/main/java/org/session/libsession/messaging/open_groups/OpenGroup.kt +++ b/libsession/src/main/java/org/session/libsession/messaging/open_groups/OpenGroup.kt @@ -11,15 +11,17 @@ data class OpenGroup( val id: String, val name: String, val publicKey: String, + val imageId: String?, val infoUpdates: Int, ) { - constructor(server: String, room: String, name: String, infoUpdates: Int, publicKey: String) : this( + constructor(server: String, room: String, publicKey: String, name: String, imageId: String?, infoUpdates: Int) : this( server = server, room = room, id = "$server.$room", name = name, publicKey = publicKey, + imageId = imageId, infoUpdates = infoUpdates, ) @@ -31,11 +33,12 @@ data class OpenGroup( if (!json.has("room")) return null val room = json.get("room").asText().toLowerCase(Locale.US) val server = json.get("server").asText().toLowerCase(Locale.US) - val displayName = json.get("displayName").asText() val publicKey = json.get("publicKey").asText() + val displayName = json.get("displayName").asText() + val imageId = json.get("imageId")?.asText() val infoUpdates = json.get("infoUpdates")?.asText()?.toIntOrNull() ?: 0 val capabilities = json.get("capabilities")?.asText()?.split(",") ?: emptyList() - OpenGroup(server, room, displayName, infoUpdates, publicKey) + OpenGroup(server, room, displayName, publicKey, imageId, infoUpdates) } catch (e: Exception) { Log.w("Loki", "Couldn't parse open group from JSON: $jsonAsString.", e); null @@ -53,11 +56,12 @@ data class OpenGroup( } } - fun toJson(): Map = mapOf( + fun toJson(): Map = mapOf( "room" to room, "server" to server, - "displayName" to name, "publicKey" to publicKey, + "displayName" to name, + "imageId" to imageId, "infoUpdates" to infoUpdates.toString(), ) diff --git a/libsession/src/main/java/org/session/libsession/messaging/open_groups/OpenGroupApi.kt b/libsession/src/main/java/org/session/libsession/messaging/open_groups/OpenGroupApi.kt index d5bc6dde0..51f7108f5 100644 --- a/libsession/src/main/java/org/session/libsession/messaging/open_groups/OpenGroupApi.kt +++ b/libsession/src/main/java/org/session/libsession/messaging/open_groups/OpenGroupApi.kt @@ -91,7 +91,7 @@ object OpenGroupApi { val created: Long = 0, val activeUsers: Int = 0, val activeUsersCutoff: Int = 0, - val imageId: Long? = null, + val imageId: String? = null, val pinnedMessages: List = emptyList(), val admin: Boolean = false, val globalAdmin: Boolean = false, @@ -337,7 +337,7 @@ object OpenGroupApi { .plus(request.verb.rawValue.toByteArray()) .plus("/${request.endpoint.value}".toByteArray()) .plus(bodyHash) - if (serverCapabilities.contains(Capability.BLIND.name.lowercase())) { + if (serverCapabilities.isEmpty() || serverCapabilities.contains(Capability.BLIND.name.lowercase())) { SodiumUtilities.blindedKeyPair(publicKey, ed25519KeyPair)?.let { keyPair -> pubKey = SessionId( IdPrefix.BLINDED, @@ -395,13 +395,13 @@ object OpenGroupApi { fun downloadOpenGroupProfilePicture( server: String, roomID: String, - imageId: Long + imageId: String ): Promise { val request = Request( verb = GET, room = roomID, server = server, - endpoint = Endpoint.RoomFileIndividual(roomID, imageId.toString()) + endpoint = Endpoint.RoomFileIndividual(roomID, imageId) ) return getResponseBody(request) } @@ -794,16 +794,14 @@ object OpenGroupApi { private fun sequentialBatch( server: String, - requests: MutableList>, - authRequired: Boolean = true + requests: MutableList> ): Promise>, Exception> { val request = Request( verb = POST, room = null, server = server, endpoint = Endpoint.Sequence, - parameters = requests.map { it.request }, - isAuthRequired = authRequired + parameters = requests.map { it.request } ) return getBatchResponseJson(request, requests) } @@ -912,8 +910,7 @@ object OpenGroupApi { fun getCapabilitiesAndRoomInfo( room: String, - server: String, - authRequired: Boolean = true + server: String ): Promise, Exception> { val requests = mutableListOf>( BatchRequestInfo( @@ -933,7 +930,7 @@ object OpenGroupApi { responseType = object : TypeReference(){} ) ) - return sequentialBatch(server, requests, authRequired).map { + return sequentialBatch(server, requests).map { val capabilities = it.firstOrNull()?.body as? Capabilities ?: throw Error.ParsingFailed val roomInfo = it.lastOrNull()?.body as? RoomInfo ?: throw Error.ParsingFailed capabilities to roomInfo diff --git a/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/pollers/OpenGroupPoller.kt b/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/pollers/OpenGroupPoller.kt index 145155e97..595f7d4dc 100644 --- a/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/pollers/OpenGroupPoller.kt +++ b/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/pollers/OpenGroupPoller.kt @@ -59,7 +59,7 @@ class OpenGroupPoller(private val server: String, private val executorService: S fun poll(isPostCapabilitiesRetry: Boolean = false): Promise { val storage = MessagingModuleConfiguration.shared.storage val rooms = storage.getAllOpenGroups().values.filter { it.server == server }.map { it.room } - rooms.forEach { downloadGroupAvatarIfNeeded(it) } + return OpenGroupApi.poll(rooms, server).successBackground { responses -> responses.filterNot { it.body == null }.forEach { response -> when (response.endpoint) { @@ -123,9 +123,10 @@ class OpenGroupPoller(private val server: String, private val executorService: S val openGroup = OpenGroup( server = server, room = pollInfo.token, - name = pollInfo.details?.name ?: "", - infoUpdates = pollInfo.details?.infoUpdates ?: 0, + name = if (pollInfo.details != null) { pollInfo.details.name } else { existingOpenGroup.name }, + infoUpdates = if (pollInfo.details != null) { pollInfo.details.infoUpdates } else { existingOpenGroup.infoUpdates }, publicKey = publicKey, + imageId = if (pollInfo.details != null) { pollInfo.details.imageId } else { existingOpenGroup.imageId } ) // - Open Group changes storage.updateOpenGroup(openGroup) @@ -155,6 +156,11 @@ class OpenGroupPoller(private val server: String, private val executorService: S GroupMember(groupId, it, GroupMemberRole.HIDDEN_ADMIN) }) } + + // Start downloading the room image (if we don't have one or it's been updated) + if (pollInfo.details?.imageId != null && pollInfo.details.imageId != existingOpenGroup.imageId) { + JobQueue.shared.add(GroupAvatarDownloadJob(roomToken, server)) + } } private fun handleMessages( @@ -284,16 +290,4 @@ class OpenGroupPoller(private val server: String, private val executorService: S JobQueue.shared.add(deleteJob) } } - - private fun downloadGroupAvatarIfNeeded(room: String) { - val storage = MessagingModuleConfiguration.shared.storage - if (storage.getGroupAvatarDownloadJob(server, room) != null) return - val groupId = GroupUtil.getEncodedOpenGroupID("$server.$room".toByteArray()) - storage.getGroup(groupId)?.let { - if (System.currentTimeMillis() > it.updatedTimestamp + TimeUnit.DAYS.toMillis(7)) { - JobQueue.shared.add(GroupAvatarDownloadJob(room, server)) - } - } - } - } \ No newline at end of file diff --git a/libsignal/src/main/java/org/session/libsignal/database/LokiAPIDatabaseProtocol.kt b/libsignal/src/main/java/org/session/libsignal/database/LokiAPIDatabaseProtocol.kt index a1866bf21..18880f553 100644 --- a/libsignal/src/main/java/org/session/libsignal/database/LokiAPIDatabaseProtocol.kt +++ b/libsignal/src/main/java/org/session/libsignal/database/LokiAPIDatabaseProtocol.kt @@ -16,8 +16,10 @@ interface LokiAPIDatabaseProtocol { fun setSwarm(publicKey: String, newValue: Set) fun getLastMessageHashValue(snode: Snode, publicKey: String, namespace: Int): String? fun setLastMessageHashValue(snode: Snode, publicKey: String, newValue: String, namespace: Int) + fun clearAllLastMessageHashes() fun getReceivedMessageHashValues(publicKey: String, namespace: Int): Set? fun setReceivedMessageHashValues(publicKey: String, newValue: Set, namespace: Int) + fun clearReceivedMessageHashValues() fun getAuthToken(server: String): String? fun setAuthToken(server: String, newValue: String?) fun setUserCount(room: String, server: String, newValue: Int) From 3e68bdc2f8fbda6ef245874230bc0e5a7b8c0d42 Mon Sep 17 00:00:00 2001 From: Morgan Pretty Date: Tue, 10 Jan 2023 13:02:25 +1100 Subject: [PATCH 25/47] Fixed an issue introduced by the last commit with OpenGroup initialisation --- .../java/org/thoughtcrime/securesms/groups/OpenGroupManager.kt | 2 +- .../org/session/libsession/messaging/open_groups/OpenGroup.kt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/app/src/main/java/org/thoughtcrime/securesms/groups/OpenGroupManager.kt b/app/src/main/java/org/thoughtcrime/securesms/groups/OpenGroupManager.kt index 09c6f19da..bdf7c1da0 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/groups/OpenGroupManager.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/groups/OpenGroupManager.kt @@ -81,7 +81,7 @@ object OpenGroupManager { if (threadID < 0) { threadID = GroupManager.createOpenGroup(openGroupID, context, null, info.name).threadId } - val openGroup = OpenGroup(server, room, publicKey, info.name, info.imageId, info.infoUpdates) + val openGroup = OpenGroup(server = server, room = room, publicKey = publicKey, name = info.name, imageId = info.imageId, infoUpdates = info.infoUpdates) threadDB.setOpenGroupChat(openGroup, threadID) return info } diff --git a/libsession/src/main/java/org/session/libsession/messaging/open_groups/OpenGroup.kt b/libsession/src/main/java/org/session/libsession/messaging/open_groups/OpenGroup.kt index b2bdd6384..b7cce7768 100644 --- a/libsession/src/main/java/org/session/libsession/messaging/open_groups/OpenGroup.kt +++ b/libsession/src/main/java/org/session/libsession/messaging/open_groups/OpenGroup.kt @@ -38,7 +38,7 @@ data class OpenGroup( val imageId = json.get("imageId")?.asText() val infoUpdates = json.get("infoUpdates")?.asText()?.toIntOrNull() ?: 0 val capabilities = json.get("capabilities")?.asText()?.split(",") ?: emptyList() - OpenGroup(server, room, displayName, publicKey, imageId, infoUpdates) + OpenGroup(server = server, room = room, name = displayName, publicKey = publicKey, imageId = imageId, infoUpdates = infoUpdates) } catch (e: Exception) { Log.w("Loki", "Couldn't parse open group from JSON: $jsonAsString.", e); null From afdf730eaa2b7ba9f61c4aeca1182ddcf117e97c Mon Sep 17 00:00:00 2001 From: Morgan Pretty Date: Tue, 10 Jan 2023 17:33:50 +1100 Subject: [PATCH 26/47] Added a couple of minor UI optimisations --- .../v2/messages/LinkPreviewView.kt | 2 +- .../v2/messages/VisibleMessageView.kt | 35 +++++---- app/src/main/res/layout/view_link_preview.xml | 74 +++++++++---------- 3 files changed, 54 insertions(+), 57 deletions(-) diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/messages/LinkPreviewView.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/messages/LinkPreviewView.kt index cb6bb536f..8a27bc4e5 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/messages/LinkPreviewView.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/messages/LinkPreviewView.kt @@ -80,7 +80,7 @@ class LinkPreviewView : LinearLayout { val rawYInt = event.rawY.toInt() val hitRect = Rect(rawXInt, rawYInt, rawXInt, rawYInt) val previewRect = Rect() - binding.mainLinkPreviewParent.getGlobalVisibleRect(previewRect) + binding.mainLinkPreviewContainer.getGlobalVisibleRect(previewRect) if (previewRect.contains(hitRect)) { openURL() return diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/messages/VisibleMessageView.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/messages/VisibleMessageView.kt index b3f1ac15e..64ddd9617 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/messages/VisibleMessageView.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/messages/VisibleMessageView.kt @@ -192,15 +192,16 @@ class VisibleMessageView : LinearLayout { binding.dateBreakTextView.text = if (showDateBreak) DateUtils.getDisplayFormattedTimeSpanString(context, Locale.getDefault(), message.timestamp) else null binding.dateBreakTextView.isVisible = showDateBreak // Message status indicator - val (iconID, iconColor) = getMessageStatusImage(message) - if (iconID != null) { - val drawable = ContextCompat.getDrawable(context, iconID)?.mutate() - if (iconColor != null) { - drawable?.setTint(iconColor) - } - binding.messageStatusImageView.setImageDrawable(drawable) - } if (message.isOutgoing) { + val (iconID, iconColor) = getMessageStatusImage(message) + if (iconID != null) { + val drawable = ContextCompat.getDrawable(context, iconID)?.mutate() + if (iconColor != null) { + drawable?.setTint(iconColor) + } + binding.messageStatusImageView.setImageDrawable(drawable) + } + val lastMessageID = mmsSmsDb.getLastMessageID(message.threadId) binding.messageStatusImageView.isVisible = !message.isSent || message.id == lastMessageID @@ -213,13 +214,17 @@ class VisibleMessageView : LinearLayout { val emojiLayoutParams = binding.emojiReactionsView.layoutParams as ConstraintLayout.LayoutParams emojiLayoutParams.horizontalBias = if (message.isOutgoing) 1f else 0f binding.emojiReactionsView.layoutParams = emojiLayoutParams - val capabilities = lokiThreadDb.getOpenGroupChat(threadID)?.server?.let { lokiApiDb.getServerCapabilities(it) } - if (message.reactions.isNotEmpty() && - (capabilities.isNullOrEmpty() || capabilities.contains(OpenGroupApi.Capability.REACTIONS.name.lowercase())) - ) { - binding.emojiReactionsView.setReactions(message.id, message.reactions, message.isOutgoing, delegate) - binding.emojiReactionsView.isVisible = true - } else { + + if (message.reactions.isNotEmpty()) { + val capabilities = lokiThreadDb.getOpenGroupChat(threadID)?.server?.let { lokiApiDb.getServerCapabilities(it) } + if (capabilities.isNullOrEmpty() || capabilities.contains(OpenGroupApi.Capability.REACTIONS.name.lowercase())) { + binding.emojiReactionsView.setReactions(message.id, message.reactions, message.isOutgoing, delegate) + binding.emojiReactionsView.isVisible = true + } else { + binding.emojiReactionsView.isVisible = false + } + } + else { binding.emojiReactionsView.isVisible = false } diff --git a/app/src/main/res/layout/view_link_preview.xml b/app/src/main/res/layout/view_link_preview.xml index 096ff5dac..7e209c2a9 100644 --- a/app/src/main/res/layout/view_link_preview.xml +++ b/app/src/main/res/layout/view_link_preview.xml @@ -1,54 +1,46 @@ + android:orientation="horizontal" + android:gravity="center"> - + - + - - - - - - - + android:scaleType="centerCrop" /> - + + + \ No newline at end of file From f9ff3feb29fb86056a87650709f294ec2521e4af Mon Sep 17 00:00:00 2001 From: Morgan Pretty Date: Wed, 11 Jan 2023 12:42:09 +1100 Subject: [PATCH 27/47] Refactored code to avoid passing lifecycleCoroutineScope as a parameter --- .../conversation/v2/ConversationActivityV2.kt | 8 ++++++++ .../conversation/v2/ConversationAdapter.kt | 5 +++-- .../v2/components/AlbumThumbnailView.kt | 14 ++------------ .../v2/messages/VisibleMessageContentView.kt | 19 ++++--------------- .../v2/messages/VisibleMessageView.kt | 5 ++--- 5 files changed, 19 insertions(+), 32 deletions(-) diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationActivityV2.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationActivityV2.kt index 6bdf95474..9b985384f 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationActivityV2.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationActivityV2.kt @@ -40,6 +40,8 @@ import network.loki.messenger.databinding.ViewVisibleMessageBinding import nl.komponents.kovenant.ui.successUi import org.session.libsession.messaging.MessagingModuleConfiguration import org.session.libsession.messaging.contacts.Contact +import org.session.libsession.messaging.jobs.AttachmentDownloadJob +import org.session.libsession.messaging.jobs.JobQueue import org.session.libsession.messaging.mentions.Mention import org.session.libsession.messaging.mentions.MentionsManager import org.session.libsession.messaging.messages.control.DataExtractionNotification @@ -250,6 +252,12 @@ class ConversationActivityV2 : PassphraseRequiredActionBarActivity(), InputBarDe onDeselect(message, position, it) } }, + onAttachmentNeedsDownload = { attachmentId, mmsId -> + // Start download (on IO thread) + lifecycleScope.launch(Dispatchers.IO) { + JobQueue.shared.add(AttachmentDownloadJob(attachmentId, mmsId)) + } + }, glide = glide, lifecycleCoroutineScope = lifecycleScope ) diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationAdapter.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationAdapter.kt index 721edad31..85d3c8e6d 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationAdapter.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationAdapter.kt @@ -39,8 +39,9 @@ class ConversationAdapter( private val onItemSwipeToReply: (MessageRecord, Int) -> Unit, private val onItemLongPress: (MessageRecord, Int, VisibleMessageView) -> Unit, private val onDeselect: (MessageRecord, Int) -> Unit, + private val onAttachmentNeedsDownload: (Long, Long) -> Unit, private val glide: GlideRequests, - private val lifecycleCoroutineScope: LifecycleCoroutineScope + lifecycleCoroutineScope: LifecycleCoroutineScope ) : CursorRecyclerViewAdapter(context, cursor) { private val messageDB by lazy { DatabaseComponent.get(context).mmsSmsDatabase() } private val contactDB by lazy { DatabaseComponent.get(context).sessionContactDatabase() } @@ -128,7 +129,7 @@ class ConversationAdapter( contact, senderId, visibleMessageViewDelegate, - lifecycleCoroutineScope + onAttachmentNeedsDownload ) if (!message.isDeleted) { diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/components/AlbumThumbnailView.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/components/AlbumThumbnailView.kt index 0825e9b20..4d8e3c5b2 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/components/AlbumThumbnailView.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/components/AlbumThumbnailView.kt @@ -11,13 +11,8 @@ import android.widget.FrameLayout import android.widget.TextView import androidx.core.view.children import androidx.core.view.isVisible -import androidx.lifecycle.LifecycleCoroutineScope -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.launch import network.loki.messenger.R import network.loki.messenger.databinding.AlbumThumbnailViewBinding -import org.session.libsession.messaging.jobs.AttachmentDownloadJob -import org.session.libsession.messaging.jobs.JobQueue import org.session.libsession.messaging.sending_receiving.attachments.AttachmentTransferProgress import org.session.libsession.messaging.sending_receiving.attachments.DatabaseAttachment import org.session.libsession.utilities.recipients.Recipient @@ -66,7 +61,7 @@ class AlbumThumbnailView : FrameLayout { // region Interaction - fun calculateHitObject(event: MotionEvent, mms: MmsMessageRecord, threadRecipient: Recipient, lifecycleCoroutineScope: LifecycleCoroutineScope) { + fun calculateHitObject(event: MotionEvent, mms: MmsMessageRecord, threadRecipient: Recipient, onAttachmentNeedsDownload: (Long, Long) -> Unit) { val rawXInt = event.rawX.toInt() val rawYInt = event.rawY.toInt() val eventRect = Rect(rawXInt, rawYInt, rawXInt, rawYInt) @@ -81,12 +76,7 @@ class AlbumThumbnailView : FrameLayout { if (slide.transferState == AttachmentTransferProgress.TRANSFER_PROGRESS_FAILED) { // Restart download here (on IO thread) (slide.asAttachment() as? DatabaseAttachment)?.let { attachment -> - val attachmentId = attachment.attachmentId.rowId - - // Start download (on IO thread) - lifecycleCoroutineScope.launch(Dispatchers.IO) { - JobQueue.shared.add(AttachmentDownloadJob(attachmentId, mms.getId())) - } + onAttachmentNeedsDownload(attachment.attachmentId.rowId, mms.getId()) } } if (slide.isInProgress) return diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/messages/VisibleMessageContentView.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/messages/VisibleMessageContentView.kt index fccc88a2a..334f1cf16 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/messages/VisibleMessageContentView.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/messages/VisibleMessageContentView.kt @@ -23,15 +23,10 @@ import androidx.core.graphics.BlendModeCompat import androidx.core.text.getSpans import androidx.core.text.toSpannable import androidx.core.view.isVisible -import androidx.lifecycle.LifecycleCoroutineScope -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.launch import network.loki.messenger.R import network.loki.messenger.databinding.ViewVisibleMessageContentBinding import okhttp3.HttpUrl import org.session.libsession.messaging.MessagingModuleConfiguration -import org.session.libsession.messaging.jobs.AttachmentDownloadJob -import org.session.libsession.messaging.jobs.JobQueue import org.session.libsession.messaging.sending_receiving.attachments.AttachmentTransferProgress import org.session.libsession.messaging.sending_receiving.attachments.DatabaseAttachment import org.session.libsession.utilities.getColorFromAttr @@ -76,7 +71,7 @@ class VisibleMessageContentView : LinearLayout { thread: Recipient, searchQuery: String?, contactIsTrusted: Boolean, - lifecycleCoroutineScope: LifecycleCoroutineScope + onAttachmentNeedsDownload: (Long, Long) -> Unit ) { // Background val background = getBackground(message.isOutgoing) @@ -152,10 +147,7 @@ class VisibleMessageContentView : LinearLayout { val attachmentId = dbAttachment.attachmentId.rowId if (attach.transferState == AttachmentTransferProgress.TRANSFER_PROGRESS_PENDING && MessagingModuleConfiguration.shared.storage.getAttachmentUploadJob(attachmentId) == null) { - // Start download (on IO thread) - lifecycleCoroutineScope.launch(Dispatchers.IO) { - JobQueue.shared.add(AttachmentDownloadJob(attachmentId, dbAttachment.mmsId)) - } + onAttachmentNeedsDownload(attachmentId, dbAttachment.mmsId) } } message.linkPreviews.forEach { preview -> @@ -163,10 +155,7 @@ class VisibleMessageContentView : LinearLayout { val attachmentId = previewThumbnail.attachmentId.rowId if (previewThumbnail.transferState == AttachmentTransferProgress.TRANSFER_PROGRESS_PENDING && MessagingModuleConfiguration.shared.storage.getAttachmentUploadJob(attachmentId) == null) { - // Start download (on IO thread) - lifecycleCoroutineScope.launch(Dispatchers.IO) { - JobQueue.shared.add(AttachmentDownloadJob(attachmentId, previewThumbnail.mmsId)) - } + onAttachmentNeedsDownload(attachmentId, previewThumbnail.mmsId) } } } @@ -221,7 +210,7 @@ class VisibleMessageContentView : LinearLayout { layoutParams.horizontalBias = if (message.isOutgoing) 1f else 0f binding.albumThumbnailView.layoutParams = layoutParams onContentClick.add { event -> - binding.albumThumbnailView.calculateHitObject(event, message, thread, lifecycleCoroutineScope) + binding.albumThumbnailView.calculateHitObject(event, message, thread, onAttachmentNeedsDownload) } } else { hideBody = true diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/messages/VisibleMessageView.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/messages/VisibleMessageView.kt index 64ddd9617..7a421298d 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/messages/VisibleMessageView.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/messages/VisibleMessageView.kt @@ -20,7 +20,6 @@ import androidx.core.os.bundleOf import androidx.core.view.isInvisible import androidx.core.view.isVisible import androidx.core.view.marginBottom -import androidx.lifecycle.LifecycleCoroutineScope import dagger.hilt.android.AndroidEntryPoint import network.loki.messenger.R import network.loki.messenger.databinding.ViewVisibleMessageBinding @@ -123,7 +122,7 @@ class VisibleMessageView : LinearLayout { contact: Contact?, senderSessionID: String, delegate: VisibleMessageViewDelegate?, - lifecycleCoroutineScope: LifecycleCoroutineScope + onAttachmentNeedsDownload: (Long, Long) -> Unit ) { val threadID = message.threadId val thread = threadDb.getRecipientForThreadId(threadID) ?: return @@ -238,7 +237,7 @@ class VisibleMessageView : LinearLayout { thread, searchQuery, message.isOutgoing || isGroupThread || (contact?.isTrusted ?: false), - lifecycleCoroutineScope + onAttachmentNeedsDownload ) binding.messageContentView.delegate = delegate onDoubleTap = { binding.messageContentView.onContentDoubleTap?.invoke() } From 693c3a9656fad0ad8db53a6ffde86b6dbacf5d5e Mon Sep 17 00:00:00 2001 From: Morgan Pretty Date: Wed, 11 Jan 2023 16:33:04 +1100 Subject: [PATCH 28/47] Fixed a few cases where we were using the write access for read operations --- .../thoughtcrime/securesms/database/LokiAPIDatabase.kt | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/LokiAPIDatabase.kt b/app/src/main/java/org/thoughtcrime/securesms/database/LokiAPIDatabase.kt index 0300f1fcc..b0f6a676c 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/LokiAPIDatabase.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/database/LokiAPIDatabase.kt @@ -349,7 +349,7 @@ class LokiAPIDatabase(context: Context, helper: SQLCipherOpenHelper) : Database( } override fun getLastMessageServerID(room: String, server: String): Long? { - val database = databaseHelper.writableDatabase + val database = databaseHelper.readableDatabase val index = "$server.$room" return database.get(lastMessageServerIDTable, "$lastMessageServerIDTableIndex = ?", wrap(index)) { cursor -> cursor.getInt(lastMessageServerID) @@ -520,7 +520,7 @@ class LokiAPIDatabase(context: Context, helper: SQLCipherOpenHelper) : Database( } fun getServerCapabilities(serverName: String): List { - val database = databaseHelper.writableDatabase + val database = databaseHelper.readableDatabase return database.get(serverCapabilitiesTable, "$server = ?", wrap(serverName)) { cursor -> cursor.getString(capabilities) }?.split(",") ?: emptyList() @@ -533,7 +533,7 @@ class LokiAPIDatabase(context: Context, helper: SQLCipherOpenHelper) : Database( } fun getLastInboxMessageId(serverName: String): Long? { - val database = databaseHelper.writableDatabase + val database = databaseHelper.readableDatabase return database.get(lastInboxMessageServerIdTable, "$server = ?", wrap(serverName)) { cursor -> cursor.getInt(lastInboxMessageServerId) }?.toLong() @@ -550,7 +550,7 @@ class LokiAPIDatabase(context: Context, helper: SQLCipherOpenHelper) : Database( } fun getLastOutboxMessageId(serverName: String): Long? { - val database = databaseHelper.writableDatabase + val database = databaseHelper.readableDatabase return database.get(lastOutboxMessageServerIdTable, "$server = ?", wrap(serverName)) { cursor -> cursor.getInt(lastOutboxMessageServerId) }?.toLong() From 70f0dad36e0926b692092f693b4adef9627508c0 Mon Sep 17 00:00:00 2001 From: Morgan Pretty Date: Fri, 13 Jan 2023 13:22:18 +1100 Subject: [PATCH 29/47] Fixed a few bugs and some optimisations Updated a number of nested layout components to be included instead of inflated Added a couple of optimisations to the EmojiTextView Fixed an issue where long conversation titles could squish the unread count Fixed an issue where the typing indicator wasn't working on the home screen --- .../securesms/MediaGalleryAdapter.java | 2 +- .../securesms/components/LinkPreviewView.java | 158 ------- .../components/OutlinedThumbnailView.java | 48 -- .../securesms/components/StickerView.java | 15 - .../components/emoji/EmojiTextView.java | 21 +- .../v2/components/AlbumThumbnailView.kt | 36 +- .../v2/components/LinkPreviewDraftView.kt | 6 +- .../v2/components/TypingIndicatorView.java | 118 ----- .../v2/components/TypingIndicatorView.kt | 105 +++++ .../TypingIndicatorViewContainer.kt | 4 +- .../v2/messages/EmojiReactionsView.java | 346 -------------- .../v2/messages/EmojiReactionsView.kt | 291 ++++++++++++ .../v2/messages/LinkPreviewView.kt | 19 +- .../conversation/v2/messages/QuoteView.kt | 8 +- .../v2/messages/VisibleMessageContentView.kt | 44 +- .../v2/messages/VisibleMessageView.kt | 34 +- .../v2/utilities/ThumbnailView.java | 425 ------------------ .../{KThumbnailView.kt => ThumbnailView.kt} | 69 ++- .../securesms/home/ConversationView.kt | 6 +- .../securesms/home/HomeActivity.kt | 6 +- .../securesms/home/HomeAdapter.kt | 2 + app/src/main/res/layout/album_thumbnail_1.xml | 2 +- app/src/main/res/layout/album_thumbnail_2.xml | 4 +- app/src/main/res/layout/album_thumbnail_3.xml | 6 +- .../main/res/layout/album_thumbnail_view.xml | 5 +- app/src/main/res/layout/link_preview.xml | 104 ----- .../layout/media_overview_gallery_item.xml | 2 +- .../main/res/layout/mediarail_media_item.xml | 2 +- app/src/main/res/layout/thumbnail_view.xml | 9 +- app/src/main/res/layout/view_conversation.xml | 97 ++-- .../view_conversation_typing_container.xml | 2 +- .../main/res/layout/view_emoji_reactions.xml | 9 +- app/src/main/res/layout/view_link_preview.xml | 7 +- .../res/layout/view_link_preview_draft.xml | 2 +- app/src/main/res/layout/view_quote.xml | 2 +- app/src/main/res/layout/view_quote_draft.xml | 2 +- .../main/res/layout/view_typing_indicator.xml | 7 +- .../main/res/layout/view_visible_message.xml | 7 +- .../layout/view_visible_message_content.xml | 10 +- 39 files changed, 625 insertions(+), 1417 deletions(-) delete mode 100644 app/src/main/java/org/thoughtcrime/securesms/components/LinkPreviewView.java delete mode 100644 app/src/main/java/org/thoughtcrime/securesms/components/OutlinedThumbnailView.java delete mode 100644 app/src/main/java/org/thoughtcrime/securesms/conversation/v2/components/TypingIndicatorView.java create mode 100644 app/src/main/java/org/thoughtcrime/securesms/conversation/v2/components/TypingIndicatorView.kt delete mode 100644 app/src/main/java/org/thoughtcrime/securesms/conversation/v2/messages/EmojiReactionsView.java create mode 100644 app/src/main/java/org/thoughtcrime/securesms/conversation/v2/messages/EmojiReactionsView.kt delete mode 100644 app/src/main/java/org/thoughtcrime/securesms/conversation/v2/utilities/ThumbnailView.java rename app/src/main/java/org/thoughtcrime/securesms/conversation/v2/utilities/{KThumbnailView.kt => ThumbnailView.kt} (76%) delete mode 100644 app/src/main/res/layout/link_preview.xml diff --git a/app/src/main/java/org/thoughtcrime/securesms/MediaGalleryAdapter.java b/app/src/main/java/org/thoughtcrime/securesms/MediaGalleryAdapter.java index aad4c1700..0fd813cf4 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/MediaGalleryAdapter.java +++ b/app/src/main/java/org/thoughtcrime/securesms/MediaGalleryAdapter.java @@ -114,7 +114,7 @@ class MediaGalleryAdapter extends StickyHeaderGridAdapter { Slide slide = MediaUtil.getSlideForAttachment(context, mediaRecord.getAttachment()); if (slide != null) { - thumbnailView.setImageResource(glideRequests, slide, false, false); + thumbnailView.setImageResource(glideRequests, slide, false, null); } thumbnailView.setOnClickListener(view -> itemClickListener.onMediaClicked(mediaRecord)); diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/LinkPreviewView.java b/app/src/main/java/org/thoughtcrime/securesms/components/LinkPreviewView.java deleted file mode 100644 index 5b2199896..000000000 --- a/app/src/main/java/org/thoughtcrime/securesms/components/LinkPreviewView.java +++ /dev/null @@ -1,158 +0,0 @@ -package org.thoughtcrime.securesms.components; - -import android.content.Context; -import android.content.res.TypedArray; -import android.graphics.Canvas; -import android.graphics.Color; -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; -import android.util.AttributeSet; -import android.view.View; -import android.view.ViewGroup; -import android.widget.FrameLayout; -import android.widget.TextView; - -import org.thoughtcrime.securesms.mms.GlideRequests; - -import org.thoughtcrime.securesms.mms.ImageSlide; -import org.thoughtcrime.securesms.mms.SlidesClickedListener; - -import org.session.libsession.messaging.sending_receiving.link_preview.LinkPreview; - -import network.loki.messenger.R; -import okhttp3.HttpUrl; - -public class LinkPreviewView extends FrameLayout { - - private static final int TYPE_CONVERSATION = 0; - private static final int TYPE_COMPOSE = 1; - - private ViewGroup container; - private OutlinedThumbnailView thumbnail; - private TextView title; - private TextView site; - private View divider; - private View closeButton; - private View spinner; - - private int type; - private int defaultRadius; - private CornerMask cornerMask; - private Outliner outliner; - private CloseClickedListener closeClickedListener; - - public LinkPreviewView(Context context) { - super(context); - init(null); - } - - public LinkPreviewView(Context context, @Nullable AttributeSet attrs) { - super(context, attrs); - init(attrs); - } - - private void init(@Nullable AttributeSet attrs) { - inflate(getContext(), R.layout.link_preview, this); - - container = findViewById(R.id.linkpreview_container); - thumbnail = findViewById(R.id.linkpreview_thumbnail); - title = findViewById(R.id.linkpreview_title); - site = findViewById(R.id.linkpreview_site); - divider = findViewById(R.id.linkpreview_divider); - spinner = findViewById(R.id.linkpreview_progress_wheel); - closeButton = findViewById(R.id.linkpreview_close); - defaultRadius = getResources().getDimensionPixelSize(R.dimen.thumbnail_default_radius); - cornerMask = new CornerMask(this); - outliner = new Outliner(); - - outliner.setColor(getResources().getColor(R.color.transparent)); - - if (attrs != null) { - TypedArray typedArray = getContext().getTheme().obtainStyledAttributes(attrs, R.styleable.LinkPreviewView, 0, 0); - type = typedArray.getInt(R.styleable.LinkPreviewView_linkpreview_type, 0); - typedArray.recycle(); - } - - if (type == TYPE_COMPOSE) { - container.setBackgroundColor(Color.TRANSPARENT); - container.setPadding(0, 0, 0, 0); - divider.setVisibility(VISIBLE); - - closeButton.setOnClickListener(v -> { - if (closeClickedListener != null) { - closeClickedListener.onCloseClicked(); - } - }); - } - - setWillNotDraw(false); - } - - @Override - protected void dispatchDraw(Canvas canvas) { - super.dispatchDraw(canvas); - if (type == TYPE_COMPOSE) return; - - cornerMask.mask(canvas); - outliner.draw(canvas); - } - - public void setLoading() { - title.setVisibility(GONE); - site.setVisibility(GONE); - thumbnail.setVisibility(GONE); - spinner.setVisibility(VISIBLE); - closeButton.setVisibility(GONE); - } - - public void setLinkPreview(@NonNull GlideRequests glideRequests, @NonNull LinkPreview linkPreview, boolean showThumbnail, boolean showCloseButton) { - setLinkPreview(glideRequests, linkPreview, showThumbnail); - if (showCloseButton) { - closeButton.setVisibility(VISIBLE); - } else { - closeButton.setVisibility(GONE); - } - } - - public void setLinkPreview(@NonNull GlideRequests glideRequests, @NonNull LinkPreview linkPreview, boolean showThumbnail) { - title.setVisibility(VISIBLE); - site.setVisibility(VISIBLE); - thumbnail.setVisibility(VISIBLE); - spinner.setVisibility(GONE); - closeButton.setVisibility(VISIBLE); - - title.setText(linkPreview.getTitle()); - - HttpUrl url = HttpUrl.parse(linkPreview.getUrl()); - if (url != null) { - site.setText(url.topPrivateDomain()); - } - - if (showThumbnail && linkPreview.getThumbnail().isPresent()) { - thumbnail.setVisibility(VISIBLE); - thumbnail.setImageResource(glideRequests, new ImageSlide(getContext(), linkPreview.getThumbnail().get()), type == TYPE_CONVERSATION, false); - thumbnail.showDownloadText(false); - } else { - thumbnail.setVisibility(GONE); - } - } - - public void setCorners(int topLeft, int topRight) { - cornerMask.setRadii(topLeft, topRight, 0, 0); - outliner.setRadii(topLeft, topRight, 0, 0); - thumbnail.setCorners(topLeft, defaultRadius, defaultRadius, defaultRadius); - postInvalidate(); - } - - public void setCloseClickedListener(@Nullable CloseClickedListener closeClickedListener) { - this.closeClickedListener = closeClickedListener; - } - - public void setDownloadClickedListener(SlidesClickedListener listener) { - thumbnail.setDownloadClickListener(listener); - } - - public interface CloseClickedListener { - void onCloseClicked(); - } -} diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/OutlinedThumbnailView.java b/app/src/main/java/org/thoughtcrime/securesms/components/OutlinedThumbnailView.java deleted file mode 100644 index 71bf8a280..000000000 --- a/app/src/main/java/org/thoughtcrime/securesms/components/OutlinedThumbnailView.java +++ /dev/null @@ -1,48 +0,0 @@ -package org.thoughtcrime.securesms.components; - -import android.content.Context; -import android.graphics.Canvas; -import android.util.AttributeSet; - -import org.session.libsession.utilities.ThemeUtil; -import org.thoughtcrime.securesms.conversation.v2.utilities.ThumbnailView; - -import network.loki.messenger.R; - -public class OutlinedThumbnailView extends ThumbnailView { - - private CornerMask cornerMask; - private Outliner outliner; - - public OutlinedThumbnailView(Context context) { - super(context); - init(); - } - - public OutlinedThumbnailView(Context context, AttributeSet attrs) { - super(context, attrs); - init(); - } - - private void init() { - cornerMask = new CornerMask(this); - outliner = new Outliner(); - - outliner.setColor(ThemeUtil.getThemedColor(getContext(), R.attr.conversation_item_image_outline_color)); - setWillNotDraw(false); - } - - @Override - protected void dispatchDraw(Canvas canvas) { - super.dispatchDraw(canvas); - - cornerMask.mask(canvas); - outliner.draw(canvas); - } - - public void setCorners(int topLeft, int topRight, int bottomRight, int bottomLeft) { - cornerMask.setRadii(topLeft, topRight, bottomRight, bottomLeft); - outliner.setRadii(topLeft, topRight, bottomRight, bottomLeft); - postInvalidate(); - } -} diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/StickerView.java b/app/src/main/java/org/thoughtcrime/securesms/components/StickerView.java index 6214c5853..98a623eef 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/components/StickerView.java +++ b/app/src/main/java/org/thoughtcrime/securesms/components/StickerView.java @@ -52,19 +52,4 @@ public class StickerView extends FrameLayout { public void setOnLongClickListener(@Nullable OnLongClickListener l) { image.setOnLongClickListener(l); } - - public void setSticker(@NonNull GlideRequests glideRequests, @NonNull Slide stickerSlide) { - boolean showControls = stickerSlide.asAttachment().getDataUri() == null; - - image.setImageResource(glideRequests, stickerSlide, showControls, false); - missingShade.setVisibility(showControls ? View.VISIBLE : View.GONE); - } - - public void setThumbnailClickListener(@NonNull SlideClickListener listener) { - image.setThumbnailClickListener(listener); - } - - public void setDownloadClickListener(@NonNull SlidesClickedListener listener) { - image.setDownloadClickListener(listener); - } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/emoji/EmojiTextView.java b/app/src/main/java/org/thoughtcrime/securesms/components/emoji/EmojiTextView.java index d512e0924..211df4f20 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/components/emoji/EmojiTextView.java +++ b/app/src/main/java/org/thoughtcrime/securesms/components/emoji/EmojiTextView.java @@ -24,7 +24,7 @@ public class EmojiTextView extends AppCompatTextView { private static final char ELLIPSIS = '…'; private CharSequence previousText; - private BufferType previousBufferType; + private BufferType previousBufferType = BufferType.NORMAL; private float originalFontSize; private boolean useSystemEmoji; private boolean sizeChangeInProgress; @@ -49,6 +49,12 @@ public class EmojiTextView extends AppCompatTextView { } @Override public void setText(@Nullable CharSequence text, BufferType type) { + // No need to do anything special if the text is null or empty + if (text == null || text.length() == 0) { + super.setText(text, type); + return; + } + EmojiParser.CandidateList candidates = EmojiProvider.getCandidates(text); if (scaleEmojis && candidates != null && candidates.allEmojis) { @@ -149,10 +155,15 @@ public class EmojiTextView extends AppCompatTextView { } private boolean unchanged(CharSequence text, CharSequence overflowText, BufferType bufferType) { - return Util.equals(previousText, text) && - Util.equals(previousOverflowText, overflowText) && - Util.equals(previousBufferType, bufferType) && - useSystemEmoji == useSystemEmoji() && + CharSequence finalPrevText = (previousText == null || previousText.length() == 0 ? "" : previousText); + CharSequence finalText = (text == null || text.length() == 0 ? "" : text); + CharSequence finalPrevOverflowText = (previousOverflowText == null || previousOverflowText.length() == 0 ? "" : previousOverflowText); + CharSequence finalOverflowText = (overflowText == null || overflowText.length() == 0 ? "" : overflowText); + + return Util.equals(finalPrevText, finalText) && + Util.equals(finalPrevOverflowText, finalOverflowText) && + Util.equals(previousBufferType, bufferType) && + useSystemEmoji == useSystemEmoji() && !sizeChangeInProgress; } diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/components/AlbumThumbnailView.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/components/AlbumThumbnailView.kt index 4d8e3c5b2..330534e23 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/components/AlbumThumbnailView.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/components/AlbumThumbnailView.kt @@ -8,6 +8,7 @@ import android.view.LayoutInflater import android.view.MotionEvent import android.view.ViewGroup import android.widget.FrameLayout +import android.widget.RelativeLayout import android.widget.TextView import androidx.core.view.children import androidx.core.view.isVisible @@ -18,41 +19,28 @@ import org.session.libsession.messaging.sending_receiving.attachments.DatabaseAt import org.session.libsession.utilities.recipients.Recipient import org.thoughtcrime.securesms.MediaPreviewActivity import org.thoughtcrime.securesms.components.CornerMask -import org.thoughtcrime.securesms.conversation.v2.utilities.KThumbnailView +import org.thoughtcrime.securesms.conversation.v2.utilities.ThumbnailView import org.thoughtcrime.securesms.database.model.MmsMessageRecord import org.thoughtcrime.securesms.mms.GlideRequests import org.thoughtcrime.securesms.mms.Slide import org.thoughtcrime.securesms.util.ActivityDispatcher -class AlbumThumbnailView : FrameLayout { - - private lateinit var binding: AlbumThumbnailViewBinding - +class AlbumThumbnailView : RelativeLayout { companion object { const val MAX_ALBUM_DISPLAY_SIZE = 3 } + private val binding: AlbumThumbnailViewBinding by lazy { AlbumThumbnailViewBinding.bind(this) } + // region Lifecycle - constructor(context: Context) : super(context) { - initialize() - } - - constructor(context: Context, attrs: AttributeSet) : super(context, attrs) { - initialize() - } - - constructor(context: Context, attrs: AttributeSet, defStyleAttr: Int) : super(context, attrs, defStyleAttr) { - initialize() - } + constructor(context: Context) : super(context) + constructor(context: Context, attrs: AttributeSet) : super(context, attrs) + constructor(context: Context, attrs: AttributeSet, defStyleAttr: Int) : super(context, attrs, defStyleAttr) private val cornerMask by lazy { CornerMask(this) } private var slides: List = listOf() private var slideSize: Int = 0 - private fun initialize() { - binding = AlbumThumbnailViewBinding.inflate(LayoutInflater.from(context), this, true) - } - override fun dispatchDraw(canvas: Canvas?) { super.dispatchDraw(canvas) cornerMask.mask(canvas) @@ -67,11 +55,11 @@ class AlbumThumbnailView : FrameLayout { val eventRect = Rect(rawXInt, rawYInt, rawXInt, rawYInt) val testRect = Rect() // test each album child - binding.albumCellContainer.findViewById(R.id.album_thumbnail_root)?.children?.forEachIndexed { index, child -> + binding.albumCellContainer.findViewById(R.id.album_thumbnail_root)?.children?.forEachIndexed forEach@{ index, child -> child.getGlobalVisibleRect(testRect) if (testRect.contains(eventRect)) { // hit intersects with this particular child - val slide = slides.getOrNull(index) ?: return + val slide = slides.getOrNull(index) ?: return@forEach // only open to downloaded images if (slide.transferState == AttachmentTransferProgress.TRANSFER_PROGRESS_FAILED) { // Restart download here (on IO thread) @@ -79,7 +67,7 @@ class AlbumThumbnailView : FrameLayout { onAttachmentNeedsDownload(attachment.attachmentId.rowId, mms.getId()) } } - if (slide.isInProgress) return + if (slide.isInProgress) return@forEach ActivityDispatcher.get(context)?.dispatchIntent { context -> MediaPreviewActivity.getPreviewIntent(context, slide, mms, threadRecipient) @@ -130,7 +118,7 @@ class AlbumThumbnailView : FrameLayout { else -> R.layout.album_thumbnail_3 // three stacked with additional text } - fun getThumbnailView(position: Int): KThumbnailView = when (position) { + fun getThumbnailView(position: Int): ThumbnailView = when (position) { 0 -> binding.albumCellContainer.findViewById(R.id.albumCellContainer).findViewById(R.id.album_cell_1) 1 -> binding.albumCellContainer.findViewById(R.id.albumCellContainer).findViewById(R.id.album_cell_2) 2 -> binding.albumCellContainer.findViewById(R.id.albumCellContainer).findViewById(R.id.album_cell_3) diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/components/LinkPreviewDraftView.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/components/LinkPreviewDraftView.kt index c1fce3f50..66164f100 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/components/LinkPreviewDraftView.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/components/LinkPreviewDraftView.kt @@ -23,7 +23,7 @@ class LinkPreviewDraftView : LinearLayout { // Start out with the loader showing and the content view hidden binding = ViewLinkPreviewDraftBinding.inflate(LayoutInflater.from(context), this, true) binding.linkPreviewDraftContainer.isVisible = false - binding.thumbnailImageView.clipToOutline = true + binding.thumbnailImageView.root.clipToOutline = true binding.linkPreviewDraftCancelButton.setOnClickListener { cancel() } } @@ -31,10 +31,10 @@ class LinkPreviewDraftView : LinearLayout { // Hide the loader and show the content view binding.linkPreviewDraftContainer.isVisible = true binding.linkPreviewDraftLoader.isVisible = false - binding.thumbnailImageView.radius = toPx(4, resources) + binding.thumbnailImageView.root.radius = toPx(4, resources) if (linkPreview.getThumbnail().isPresent) { // This internally fetches the thumbnail - binding.thumbnailImageView.setImageResource(glide, ImageSlide(context, linkPreview.getThumbnail().get()), false, false) + binding.thumbnailImageView.root.setImageResource(glide, ImageSlide(context, linkPreview.getThumbnail().get()), false, null) } binding.linkPreviewDraftTitleTextView.text = linkPreview.title } diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/components/TypingIndicatorView.java b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/components/TypingIndicatorView.java deleted file mode 100644 index 826cfe7b3..000000000 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/components/TypingIndicatorView.java +++ /dev/null @@ -1,118 +0,0 @@ -package org.thoughtcrime.securesms.conversation.v2.components; - -import android.content.Context; -import android.content.res.TypedArray; -import android.graphics.Canvas; -import android.graphics.Color; -import android.graphics.PorterDuff; -import androidx.annotation.Nullable; -import android.util.AttributeSet; -import android.view.View; -import android.widget.LinearLayout; - -import network.loki.messenger.R; - -public class TypingIndicatorView extends LinearLayout { - private boolean isActive; - private long startTime; - - private static final long CYCLE_DURATION = 1500; - private static final long DOT_DURATION = 600; - private static final float MIN_ALPHA = 0.4f; - private static final float MIN_SCALE = 0.75f; - - private View dot1; - private View dot2; - private View dot3; - - public TypingIndicatorView(Context context) { - super(context); - initialize(null); - } - - public TypingIndicatorView(Context context, @Nullable AttributeSet attrs) { - super(context, attrs); - initialize(attrs); - } - - private void initialize(@Nullable AttributeSet attrs) { - inflate(getContext(), R.layout.view_typing_indicator, this); - - setWillNotDraw(false); - - dot1 = findViewById(R.id.typing_dot1); - dot2 = findViewById(R.id.typing_dot2); - dot3 = findViewById(R.id.typing_dot3); - - if (attrs != null) { - TypedArray typedArray = getContext().getTheme().obtainStyledAttributes(attrs, R.styleable.TypingIndicatorView, 0, 0); - int tint = typedArray.getColor(R.styleable.TypingIndicatorView_typingIndicator_tint, Color.WHITE); - typedArray.recycle(); - - dot1.getBackground().setColorFilter(tint, PorterDuff.Mode.MULTIPLY); - dot2.getBackground().setColorFilter(tint, PorterDuff.Mode.MULTIPLY); - dot3.getBackground().setColorFilter(tint, PorterDuff.Mode.MULTIPLY); - } - } - - @Override - protected void onDraw(Canvas canvas) { - if (!isActive) { - super.onDraw(canvas); - return; - } - - long timeInCycle = (System.currentTimeMillis() - startTime) % CYCLE_DURATION; - - render(dot1, timeInCycle, 0); - render(dot2, timeInCycle, 150); - render(dot3, timeInCycle, 300); - - super.onDraw(canvas); - postInvalidate(); - } - - private void render(View dot, long timeInCycle, long start) { - long end = start + DOT_DURATION; - long peak = start + (DOT_DURATION / 2); - - if (timeInCycle < start || timeInCycle > end) { - renderDefault(dot); - } else if (timeInCycle < peak) { - renderFadeIn(dot, timeInCycle, start); - } else { - renderFadeOut(dot, timeInCycle, peak); - } - } - - private void renderDefault(View dot) { - dot.setAlpha(MIN_ALPHA); - dot.setScaleX(MIN_SCALE); - dot.setScaleY(MIN_SCALE); - } - - private void renderFadeIn(View dot, long timeInCycle, long fadeInStart) { - float percent = (float) (timeInCycle - fadeInStart) / 300; - dot.setAlpha(MIN_ALPHA + (1 - MIN_ALPHA) * percent); - dot.setScaleX(MIN_SCALE + (1 - MIN_SCALE) * percent); - dot.setScaleY(MIN_SCALE + (1 - MIN_SCALE) * percent); - } - - private void renderFadeOut(View dot, long timeInCycle, long fadeOutStart) { - float percent = (float) (timeInCycle - fadeOutStart) / 300; - dot.setAlpha(1 - (1 - MIN_ALPHA) * percent); - dot.setScaleX(1 - (1 - MIN_SCALE) * percent); - dot.setScaleY(1 - (1 - MIN_SCALE) * percent); - } - - public void startAnimation() { - isActive = true; - startTime = System.currentTimeMillis(); - - postInvalidate(); - } - - public void stopAnimation() { - isActive = false; - } -} diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/components/TypingIndicatorView.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/components/TypingIndicatorView.kt new file mode 100644 index 000000000..d1310bffb --- /dev/null +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/components/TypingIndicatorView.kt @@ -0,0 +1,105 @@ +package org.thoughtcrime.securesms.conversation.v2.components + +import android.content.Context +import android.graphics.Canvas +import android.graphics.Color +import android.graphics.PorterDuff +import android.util.AttributeSet +import android.view.View +import android.widget.LinearLayout +import network.loki.messenger.R +import network.loki.messenger.databinding.ViewTypingIndicatorBinding + +class TypingIndicatorView : LinearLayout { + companion object { + private const val CYCLE_DURATION: Long = 1500 + private const val DOT_DURATION: Long = 600 + private const val MIN_ALPHA = 0.4f + private const val MIN_SCALE = 0.75f + } + + private val binding: ViewTypingIndicatorBinding by lazy { + val binding = ViewTypingIndicatorBinding.bind(this) + + if (tint != -1) { + binding.typingDot1.getBackground().setColorFilter(tint, PorterDuff.Mode.MULTIPLY) + binding.typingDot2.getBackground().setColorFilter(tint, PorterDuff.Mode.MULTIPLY) + binding.typingDot3.getBackground().setColorFilter(tint, PorterDuff.Mode.MULTIPLY) + } + + return@lazy binding + } + + private var isActive = false + private var startTime: Long = 0 + private var tint: Int = -1 + + constructor(context: Context) : super(context) { initialize(null) } + constructor(context: Context, attrs: AttributeSet) : super(context, attrs) { initialize(attrs) } + constructor(context: Context, attrs: AttributeSet, defStyleAttr: Int) : super(context, attrs, defStyleAttr) { initialize(attrs) } + + private fun initialize(attrs: AttributeSet?) { + setWillNotDraw(false) + + if (attrs != null) { + val typedArray = context.theme.obtainStyledAttributes(attrs, R.styleable.TypingIndicatorView, 0, 0) + this.tint = typedArray.getColor(R.styleable.TypingIndicatorView_typingIndicator_tint, Color.WHITE) + typedArray.recycle() + } + } + + override fun onDraw(canvas: Canvas) { + if (!isActive) { + super.onDraw(canvas) + return + } + val timeInCycle = (System.currentTimeMillis() - startTime) % CYCLE_DURATION + render(binding.typingDot1, timeInCycle, 0) + render(binding.typingDot2, timeInCycle, 150) + render(binding.typingDot3, timeInCycle, 300) + super.onDraw(canvas) + postInvalidate() + } + + private fun render(dot: View?, timeInCycle: Long, start: Long) { + val end = start + DOT_DURATION + val peak = start + DOT_DURATION / 2 + if (timeInCycle < start || timeInCycle > end) { + renderDefault(dot) + } else if (timeInCycle < peak) { + renderFadeIn(dot, timeInCycle, start) + } else { + renderFadeOut(dot, timeInCycle, peak) + } + } + + private fun renderDefault(dot: View?) { + dot!!.alpha = MIN_ALPHA + dot.scaleX = MIN_SCALE + dot.scaleY = MIN_SCALE + } + + private fun renderFadeIn(dot: View?, timeInCycle: Long, fadeInStart: Long) { + val percent = (timeInCycle - fadeInStart).toFloat() / 300 + dot!!.alpha = MIN_ALPHA + (1 - MIN_ALPHA) * percent + dot.scaleX = MIN_SCALE + (1 - MIN_SCALE) * percent + dot.scaleY = MIN_SCALE + (1 - MIN_SCALE) * percent + } + + private fun renderFadeOut(dot: View?, timeInCycle: Long, fadeOutStart: Long) { + val percent = (timeInCycle - fadeOutStart).toFloat() / 300 + dot!!.alpha = 1 - (1 - MIN_ALPHA) * percent + dot.scaleX = 1 - (1 - MIN_SCALE) * percent + dot.scaleY = 1 - (1 - MIN_SCALE) * percent + } + + fun startAnimation() { + isActive = true + startTime = System.currentTimeMillis() + postInvalidate() + } + + fun stopAnimation() { + isActive = false + } +} \ No newline at end of file diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/components/TypingIndicatorViewContainer.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/components/TypingIndicatorViewContainer.kt index 768d49146..3077d227e 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/components/TypingIndicatorViewContainer.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/components/TypingIndicatorViewContainer.kt @@ -19,7 +19,7 @@ class TypingIndicatorViewContainer : LinearLayout { } fun setTypists(typists: List) { - if (typists.isEmpty()) { binding.typingIndicator.stopAnimation(); return } - binding.typingIndicator.startAnimation() + if (typists.isEmpty()) { binding.typingIndicator.root.stopAnimation(); return } + binding.typingIndicator.root.startAnimation() } } \ No newline at end of file diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/messages/EmojiReactionsView.java b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/messages/EmojiReactionsView.java deleted file mode 100644 index 6d16f1f42..000000000 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/messages/EmojiReactionsView.java +++ /dev/null @@ -1,346 +0,0 @@ -package org.thoughtcrime.securesms.conversation.v2.messages; - -import android.content.Context; -import android.content.res.TypedArray; -import android.os.Handler; -import android.os.Looper; -import android.util.AttributeSet; -import android.view.HapticFeedbackConstants; -import android.view.LayoutInflater; -import android.view.MotionEvent; -import android.view.View; -import android.view.ViewGroup; -import android.widget.LinearLayout; -import android.widget.TextView; - -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; -import androidx.constraintlayout.widget.Group; -import androidx.core.content.ContextCompat; - -import com.google.android.flexbox.FlexboxLayout; -import com.google.android.flexbox.JustifyContent; - -import org.session.libsession.utilities.TextSecurePreferences; -import org.session.libsession.utilities.ThemeUtil; -import org.thoughtcrime.securesms.components.emoji.EmojiImageView; -import org.thoughtcrime.securesms.components.emoji.EmojiUtil; -import org.thoughtcrime.securesms.conversation.v2.ViewUtil; -import org.thoughtcrime.securesms.database.model.MessageId; -import org.thoughtcrime.securesms.database.model.ReactionRecord; -import org.thoughtcrime.securesms.util.NumberUtil; - -import java.util.ArrayList; -import java.util.Collections; -import java.util.Date; -import java.util.LinkedHashMap; -import java.util.List; -import java.util.Map; - -import network.loki.messenger.R; - -public class EmojiReactionsView extends LinearLayout implements View.OnTouchListener { - - // Normally 6dp, but we have 1dp left+right margin on the pills themselves - private final int OUTER_MARGIN = ViewUtil.dpToPx(2); - private static final int DEFAULT_THRESHOLD = 5; - - private List records; - private long messageId; - private ViewGroup container; - private Group showLess; - private VisibleMessageViewDelegate delegate; - private Handler gestureHandler = new Handler(Looper.getMainLooper()); - private Runnable pressCallback; - private Runnable longPressCallback; - private long onDownTimestamp = 0; - private static long longPressDurationThreshold = 250; - private static long maxDoubleTapInterval = 200; - private boolean extended = false; - - public EmojiReactionsView(Context context) { - super(context); - init(null); - } - - public EmojiReactionsView(Context context, @Nullable AttributeSet attrs) { - super(context, attrs); - init(attrs); - } - - private void init(@Nullable AttributeSet attrs) { - inflate(getContext(), R.layout.view_emoji_reactions, this); - - this.container = findViewById(R.id.layout_emoji_container); - this.showLess = findViewById(R.id.group_show_less); - - records = new ArrayList<>(); - - if (attrs != null) { - TypedArray typedArray = getContext().getTheme().obtainStyledAttributes(attrs, R.styleable.EmojiReactionsView, 0, 0); - typedArray.recycle(); - } - } - - public void clear() { - this.records.clear(); - container.removeAllViews(); - } - - public void setReactions(long messageId, @NonNull List records, boolean outgoing, VisibleMessageViewDelegate delegate) { - this.delegate = delegate; - if (records.equals(this.records)) { - return; - } - - FlexboxLayout containerLayout = (FlexboxLayout) this.container; - containerLayout.setJustifyContent(outgoing ? JustifyContent.FLEX_END : JustifyContent.FLEX_START); - this.records.clear(); - this.records.addAll(records); - if (this.messageId != messageId) { - extended = false; - } - this.messageId = messageId; - - displayReactions(extended ? Integer.MAX_VALUE : DEFAULT_THRESHOLD); - } - - @Override - public boolean onTouch(View v, MotionEvent event) { - if (v.getTag() == null) return false; - - Reaction reaction = (Reaction) v.getTag(); - int action = event.getAction(); - if (action == MotionEvent.ACTION_DOWN) onDown(new MessageId(reaction.messageId, reaction.isMms)); - else if (action == MotionEvent.ACTION_CANCEL) removeLongPressCallback(); - else if (action == MotionEvent.ACTION_UP) onUp(reaction); - return true; - } - - private void displayReactions(int threshold) { - String userPublicKey = TextSecurePreferences.getLocalNumber(getContext()); - List reactions = buildSortedReactionsList(records, userPublicKey, threshold); - - container.removeAllViews(); - LinearLayout overflowContainer = new LinearLayout(getContext()); - overflowContainer.setOrientation(LinearLayout.HORIZONTAL); - int innerPadding = ViewUtil.dpToPx(4); - overflowContainer.setPaddingRelative(innerPadding,innerPadding,innerPadding,innerPadding); - - int pixelSize = ViewUtil.dpToPx(1); - - for (Reaction reaction : reactions) { - if (container.getChildCount() + 1 >= DEFAULT_THRESHOLD && threshold != Integer.MAX_VALUE && reactions.size() > threshold) { - if (overflowContainer.getParent() == null) { - container.addView(overflowContainer); - MarginLayoutParams overflowParams = (MarginLayoutParams) overflowContainer.getLayoutParams(); - overflowParams.height = ViewUtil.dpToPx(26); - overflowParams.setMargins(pixelSize, pixelSize, pixelSize, pixelSize); - overflowContainer.setLayoutParams(overflowParams); - overflowContainer.setBackground(ContextCompat.getDrawable(getContext(), R.drawable.reaction_pill_background)); - } - View pill = buildPill(getContext(), this, reaction, true); - pill.setOnClickListener(v -> { - extended = true; - displayReactions(Integer.MAX_VALUE); - }); - pill.findViewById(R.id.reactions_pill_count).setVisibility(View.GONE); - pill.findViewById(R.id.reactions_pill_spacer).setVisibility(View.GONE); - overflowContainer.addView(pill); - } else { - View pill = buildPill(getContext(), this, reaction, false); - pill.setTag(reaction); - pill.setOnTouchListener(this); - MarginLayoutParams params = (MarginLayoutParams) pill.getLayoutParams(); - params.setMargins(pixelSize, pixelSize, pixelSize, pixelSize); - pill.setLayoutParams(params); - container.addView(pill); - } - } - - int overflowChildren = overflowContainer.getChildCount(); - int negativeMargin = ViewUtil.dpToPx(-8); - for (int i = 0; i < overflowChildren; i++) { - View child = overflowContainer.getChildAt(i); - MarginLayoutParams childParams = (MarginLayoutParams) child.getLayoutParams(); - if ((i == 0 && overflowChildren > 1) || i + 1 < overflowChildren) { - // if first and there is more than one child, or we are not the last child then set negative right margin - childParams.setMargins(0,0, negativeMargin, 0); - child.setLayoutParams(childParams); - } - } - - if (threshold == Integer.MAX_VALUE) { - showLess.setVisibility(VISIBLE); - for (int id : showLess.getReferencedIds()) { - findViewById(id).setOnClickListener(view -> { - extended = false; - displayReactions(DEFAULT_THRESHOLD); - }); - } - } else { - showLess.setVisibility(GONE); - } - } - - private void onReactionClicked(Reaction reaction) { - if (reaction.messageId != 0) { - MessageId messageId = new MessageId(reaction.messageId, reaction.isMms); - delegate.onReactionClicked(reaction.emoji, messageId, reaction.userWasSender); - } - } - - private static @NonNull List buildSortedReactionsList(@NonNull List records, String userPublicKey, int threshold) { - Map counters = new LinkedHashMap<>(); - - for (ReactionRecord record : records) { - String baseEmoji = EmojiUtil.getCanonicalRepresentation(record.getEmoji()); - Reaction info = counters.get(baseEmoji); - - if (info == null) { - info = new Reaction(record.getMessageId(), record.isMms(), record.getEmoji(), record.getCount(), record.getSortId(), record.getDateReceived(), userPublicKey.equals(record.getAuthor())); - } else { - info.update(record.getEmoji(), record.getCount(), record.getDateReceived(), userPublicKey.equals(record.getAuthor())); - } - - counters.put(baseEmoji, info); - } - - List reactions = new ArrayList<>(counters.values()); - - Collections.sort(reactions, Collections.reverseOrder()); - - if (reactions.size() >= threshold + 2 && threshold != Integer.MAX_VALUE) { - List shortened = new ArrayList<>(threshold + 2); - shortened.addAll(reactions.subList(0, threshold + 2)); - return shortened; - } else { - return reactions; - } - } - - private static View buildPill(@NonNull Context context, @NonNull ViewGroup parent, @NonNull Reaction reaction, boolean isCompact) { - View root = LayoutInflater.from(context).inflate(R.layout.reactions_pill, parent, false); - EmojiImageView emojiView = root.findViewById(R.id.reactions_pill_emoji); - TextView countView = root.findViewById(R.id.reactions_pill_count); - View spacer = root.findViewById(R.id.reactions_pill_spacer); - - if (isCompact) { - root.setPaddingRelative(1,1,1,1); - ViewGroup.LayoutParams layoutParams = root.getLayoutParams(); - layoutParams.height = ViewGroup.LayoutParams.WRAP_CONTENT; - root.setLayoutParams(layoutParams); - } - - if (reaction.emoji != null) { - emojiView.setImageEmoji(reaction.emoji); - - if (reaction.count >= 1) { - countView.setText(NumberUtil.getFormattedNumber(reaction.count)); - } else { - countView.setVisibility(GONE); - spacer.setVisibility(GONE); - } - } else { - emojiView.setVisibility(GONE); - spacer.setVisibility(GONE); - countView.setText(context.getString(R.string.ReactionsConversationView_plus, reaction.count)); - } - - if (reaction.userWasSender && !isCompact) { - root.setBackground(ContextCompat.getDrawable(context, R.drawable.reaction_pill_background_selected)); - countView.setTextColor(ThemeUtil.getThemedColor(context, R.attr.reactionsPillSelectedTextColor)); - } else { - if (!isCompact) { - root.setBackground(ContextCompat.getDrawable(context, R.drawable.reaction_pill_background)); - } - } - - return root; - } - - private void onDown(MessageId messageId) { - removeLongPressCallback(); - Runnable newLongPressCallback = () -> { - performHapticFeedback(HapticFeedbackConstants.LONG_PRESS); - if (delegate != null) { - delegate.onReactionLongClicked(messageId); - } - }; - this.longPressCallback = newLongPressCallback; - gestureHandler.postDelayed(newLongPressCallback, longPressDurationThreshold); - onDownTimestamp = new Date().getTime(); - } - - private void removeLongPressCallback() { - if (longPressCallback != null) { - gestureHandler.removeCallbacks(longPressCallback); - } - } - - private void onUp(Reaction reaction) { - if ((new Date().getTime() - onDownTimestamp) < longPressDurationThreshold) { - removeLongPressCallback(); - if (pressCallback != null) { - gestureHandler.removeCallbacks(pressCallback); - this.pressCallback = null; - } else { - Runnable newPressCallback = () -> { - onReactionClicked(reaction); - pressCallback = null; - }; - this.pressCallback = newPressCallback; - gestureHandler.postDelayed(newPressCallback, maxDoubleTapInterval); - } - } - } - - private static class Reaction implements Comparable { - private final long messageId; - private final boolean isMms; - private String emoji; - private long count; - private long sortIndex; - private long lastSeen; - private boolean userWasSender; - - Reaction(long messageId, boolean isMms, @Nullable String emoji, long count, long sortIndex, long lastSeen, boolean userWasSender) { - this.messageId = messageId; - this.isMms = isMms; - this.emoji = emoji; - this.count = count; - this.sortIndex = sortIndex; - this.lastSeen = lastSeen; - this.userWasSender = userWasSender; - } - - void update(@NonNull String emoji, long count, long lastSeen, boolean userWasSender) { - if (!this.userWasSender) { - if (userWasSender || lastSeen > this.lastSeen) { - this.emoji = emoji; - } - } - - this.count = this.count + count; - this.lastSeen = Math.max(this.lastSeen, lastSeen); - this.userWasSender = this.userWasSender || userWasSender; - } - - @NonNull Reaction merge(@NonNull Reaction other) { - this.count = this.count + other.count; - this.lastSeen = Math.max(this.lastSeen, other.lastSeen); - this.userWasSender = this.userWasSender || other.userWasSender; - return this; - } - - @Override - public int compareTo(Reaction rhs) { - Reaction lhs = this; - if (lhs.count == rhs.count ) { - return Long.compare(lhs.sortIndex, rhs.sortIndex); - } else { - return Long.compare(lhs.count, rhs.count); - } - } - } -} diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/messages/EmojiReactionsView.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/messages/EmojiReactionsView.kt new file mode 100644 index 000000000..49e4b1044 --- /dev/null +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/messages/EmojiReactionsView.kt @@ -0,0 +1,291 @@ +package org.thoughtcrime.securesms.conversation.v2.messages + +import android.content.Context +import android.os.Handler +import android.os.Looper +import android.util.AttributeSet +import android.view.* +import android.view.View.OnTouchListener +import android.widget.LinearLayout +import android.widget.TextView +import androidx.constraintlayout.widget.ConstraintLayout +import androidx.core.content.ContextCompat +import com.google.android.flexbox.JustifyContent +import network.loki.messenger.R +import network.loki.messenger.databinding.ViewEmojiReactionsBinding +import org.session.libsession.utilities.TextSecurePreferences.Companion.getLocalNumber +import org.session.libsession.utilities.ThemeUtil +import org.thoughtcrime.securesms.components.emoji.EmojiImageView +import org.thoughtcrime.securesms.components.emoji.EmojiUtil +import org.thoughtcrime.securesms.conversation.v2.ViewUtil +import org.thoughtcrime.securesms.database.model.MessageId +import org.thoughtcrime.securesms.database.model.ReactionRecord +import org.thoughtcrime.securesms.util.NumberUtil.getFormattedNumber +import java.util.* + +class EmojiReactionsView : ConstraintLayout, OnTouchListener { + companion object { + private const val DEFAULT_THRESHOLD = 5 + private const val longPressDurationThreshold: Long = 250 + private const val maxDoubleTapInterval: Long = 200 + } + + private val binding: ViewEmojiReactionsBinding by lazy { ViewEmojiReactionsBinding.bind(this) } + + // Normally 6dp, but we have 1dp left+right margin on the pills themselves + private val OUTER_MARGIN = ViewUtil.dpToPx(2) + private var records: MutableList? = null + private var messageId: Long = 0 + private var delegate: VisibleMessageViewDelegate? = null + private val gestureHandler = Handler(Looper.getMainLooper()) + private var pressCallback: Runnable? = null + private var longPressCallback: Runnable? = null + private var onDownTimestamp: Long = 0 + private var extended = false + + constructor(context: Context) : super(context) { init(null) } + constructor(context: Context, attrs: AttributeSet) : super(context, attrs) { init(attrs) } + constructor(context: Context, attrs: AttributeSet, defStyleAttr: Int) : super(context, attrs, defStyleAttr) { init(attrs) } + + private fun init(attrs: AttributeSet?) { + records = ArrayList() + + if (attrs != null) { + val typedArray = context.theme.obtainStyledAttributes(attrs, R.styleable.EmojiReactionsView, 0, 0) + typedArray.recycle() + } + } + + fun clear() { + records!!.clear() + binding.layoutEmojiContainer.removeAllViews() + } + + fun setReactions(messageId: Long, records: List, outgoing: Boolean, delegate: VisibleMessageViewDelegate?) { + this.delegate = delegate + if (records == this.records) { + return + } + + binding.layoutEmojiContainer.justifyContent = if (outgoing) JustifyContent.FLEX_END else JustifyContent.FLEX_START + this.records!!.clear() + this.records!!.addAll(records) + if (this.messageId != messageId) { + extended = false + } + this.messageId = messageId + displayReactions(if (extended) Int.MAX_VALUE else DEFAULT_THRESHOLD) + } + + override fun onTouch(v: View, event: MotionEvent): Boolean { + if (v.tag == null) return false + val reaction = v.tag as Reaction + val action = event.action + if (action == MotionEvent.ACTION_DOWN) onDown(MessageId(reaction.messageId, reaction.isMms)) else if (action == MotionEvent.ACTION_CANCEL) removeLongPressCallback() else if (action == MotionEvent.ACTION_UP) onUp(reaction) + return true + } + + private fun displayReactions(threshold: Int) { + val userPublicKey = getLocalNumber(context) + val reactions = buildSortedReactionsList(records!!, userPublicKey, threshold) + binding.layoutEmojiContainer.removeAllViews() + val overflowContainer = LinearLayout(context) + overflowContainer.orientation = LinearLayout.HORIZONTAL + val innerPadding = ViewUtil.dpToPx(4) + overflowContainer.setPaddingRelative(innerPadding, innerPadding, innerPadding, innerPadding) + val pixelSize = ViewUtil.dpToPx(1) + for (reaction in reactions) { + if (binding.layoutEmojiContainer.childCount + 1 >= DEFAULT_THRESHOLD && threshold != Int.MAX_VALUE && reactions.size > threshold) { + if (overflowContainer.parent == null) { + binding.layoutEmojiContainer.addView(overflowContainer) + val overflowParams = overflowContainer.layoutParams as MarginLayoutParams + overflowParams.height = ViewUtil.dpToPx(26) + overflowParams.setMargins(pixelSize, pixelSize, pixelSize, pixelSize) + overflowContainer.layoutParams = overflowParams + overflowContainer.background = ContextCompat.getDrawable(context, R.drawable.reaction_pill_background) + } + val pill = buildPill(context, this, reaction, true) + pill.setOnClickListener { v: View? -> + extended = true + displayReactions(Int.MAX_VALUE) + } + pill.findViewById(R.id.reactions_pill_count).visibility = GONE + pill.findViewById(R.id.reactions_pill_spacer).visibility = GONE + overflowContainer.addView(pill) + } else { + val pill = buildPill(context, this, reaction, false) + pill.tag = reaction + pill.setOnTouchListener(this) + val params = pill.layoutParams as MarginLayoutParams + params.setMargins(pixelSize, pixelSize, pixelSize, pixelSize) + pill.layoutParams = params + binding.layoutEmojiContainer.addView(pill) + } + } + val overflowChildren = overflowContainer.childCount + val negativeMargin = ViewUtil.dpToPx(-8) + for (i in 0 until overflowChildren) { + val child = overflowContainer.getChildAt(i) + val childParams = child.layoutParams as MarginLayoutParams + if (i == 0 && overflowChildren > 1 || i + 1 < overflowChildren) { + // if first and there is more than one child, or we are not the last child then set negative right margin + childParams.setMargins(0, 0, negativeMargin, 0) + child.layoutParams = childParams + } + } + if (threshold == Int.MAX_VALUE) { + binding.groupShowLess.visibility = VISIBLE + for (id in binding.groupShowLess.referencedIds) { + findViewById(id).setOnClickListener { view: View? -> + extended = false + displayReactions(DEFAULT_THRESHOLD) + } + } + } else { + binding.groupShowLess.visibility = GONE + } + } + + private fun buildSortedReactionsList(records: List, userPublicKey: String?, threshold: Int): List { + val counters: MutableMap = LinkedHashMap() + + records.forEach { + val baseEmoji = EmojiUtil.getCanonicalRepresentation(it.emoji) + val info = counters[baseEmoji] + + if (info == null) { + counters[baseEmoji] = Reaction(messageId, it.isMms, it.emoji, it.count, it.sortId, it.dateReceived, userPublicKey == it.author) + } + else { + info.update(it.emoji, it.count, it.dateReceived, userPublicKey == it.author) + } + } + + val reactions: List = ArrayList(counters.values) + Collections.sort(reactions, Collections.reverseOrder()) + + return if (reactions.size >= threshold + 2 && threshold != Int.MAX_VALUE) { + val shortened: MutableList = ArrayList(threshold + 2) + shortened.addAll(reactions.subList(0, threshold + 2)) + shortened + } else { + reactions + } + } + + private fun buildPill(context: Context, parent: ViewGroup, reaction: Reaction, isCompact: Boolean): View { + val root = LayoutInflater.from(context).inflate(R.layout.reactions_pill, parent, false) + val emojiView = root.findViewById(R.id.reactions_pill_emoji) + val countView = root.findViewById(R.id.reactions_pill_count) + val spacer = root.findViewById(R.id.reactions_pill_spacer) + if (isCompact) { + root.setPaddingRelative(1, 1, 1, 1) + val layoutParams = root.layoutParams + layoutParams.height = ViewGroup.LayoutParams.WRAP_CONTENT + root.layoutParams = layoutParams + } + if (reaction.emoji != null) { + emojiView.setImageEmoji(reaction.emoji) + if (reaction.count >= 1) { + countView.text = getFormattedNumber(reaction.count) + } else { + countView.visibility = GONE + spacer.visibility = GONE + } + } else { + emojiView.visibility = GONE + spacer.visibility = GONE + countView.text = context.getString(R.string.ReactionsConversationView_plus, reaction.count) + } + if (reaction.userWasSender && !isCompact) { + root.background = ContextCompat.getDrawable(context, R.drawable.reaction_pill_background_selected) + countView.setTextColor(ThemeUtil.getThemedColor(context, R.attr.reactionsPillSelectedTextColor)) + } else { + if (!isCompact) { + root.background = ContextCompat.getDrawable(context, R.drawable.reaction_pill_background) + } + } + return root + } + + private fun onReactionClicked(reaction: Reaction) { + if (reaction.messageId != 0L) { + val messageId = MessageId(reaction.messageId, reaction.isMms) + delegate!!.onReactionClicked(reaction.emoji!!, messageId, reaction.userWasSender) + } + } + + private fun onDown(messageId: MessageId) { + removeLongPressCallback() + val newLongPressCallback = Runnable { + performHapticFeedback(HapticFeedbackConstants.LONG_PRESS) + if (delegate != null) { + delegate!!.onReactionLongClicked(messageId) + } + } + longPressCallback = newLongPressCallback + gestureHandler.postDelayed(newLongPressCallback, longPressDurationThreshold) + onDownTimestamp = Date().time + } + + private fun removeLongPressCallback() { + if (longPressCallback != null) { + gestureHandler.removeCallbacks(longPressCallback!!) + } + } + + private fun onUp(reaction: Reaction) { + if (Date().time - onDownTimestamp < longPressDurationThreshold) { + removeLongPressCallback() + if (pressCallback != null) { + gestureHandler.removeCallbacks(pressCallback!!) + pressCallback = null + } else { + val newPressCallback = Runnable { + onReactionClicked(reaction) + pressCallback = null + } + pressCallback = newPressCallback + gestureHandler.postDelayed(newPressCallback, maxDoubleTapInterval) + } + } + } + + internal class Reaction( + internal val messageId: Long, + internal val isMms: Boolean, + internal var emoji: String?, + internal var count: Long, + internal val sortIndex: Long, + internal var lastSeen: Long, + internal var userWasSender: Boolean + ) : Comparable { + fun update(emoji: String, count: Long, lastSeen: Long, userWasSender: Boolean) { + if (!this.userWasSender) { + if (userWasSender || lastSeen > this.lastSeen) { + this.emoji = emoji + } + } + this.count = this.count + count + this.lastSeen = Math.max(this.lastSeen, lastSeen) + this.userWasSender = this.userWasSender || userWasSender + } + + fun merge(other: Reaction): Reaction { + count = count + other.count + lastSeen = Math.max(lastSeen, other.lastSeen) + userWasSender = userWasSender || other.userWasSender + return this + } + + override fun compareTo(other: Reaction?): Int { + if (other == null) { return -1 } + + if (this.count == other.count) { + return this.sortIndex.compareTo(other.sortIndex) + } + + return this.count.compareTo(other.count) + } + } +} \ No newline at end of file diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/messages/LinkPreviewView.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/messages/LinkPreviewView.kt index 8a27bc4e5..45d353cc3 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/messages/LinkPreviewView.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/messages/LinkPreviewView.kt @@ -4,11 +4,9 @@ import android.content.Context import android.graphics.Canvas import android.graphics.Rect import android.util.AttributeSet -import android.view.LayoutInflater import android.view.MotionEvent import android.widget.LinearLayout import androidx.appcompat.app.AppCompatActivity -import androidx.core.content.res.ResourcesCompat import androidx.core.view.isVisible import network.loki.messenger.R import network.loki.messenger.databinding.ViewLinkPreviewBinding @@ -19,21 +17,16 @@ import org.thoughtcrime.securesms.conversation.v2.utilities.MessageBubbleUtiliti import org.thoughtcrime.securesms.database.model.MmsMessageRecord import org.thoughtcrime.securesms.mms.GlideRequests import org.thoughtcrime.securesms.mms.ImageSlide -import org.thoughtcrime.securesms.util.UiModeUtilities class LinkPreviewView : LinearLayout { - private lateinit var binding: ViewLinkPreviewBinding + private val binding: ViewLinkPreviewBinding by lazy { ViewLinkPreviewBinding.bind(this) } private val cornerMask by lazy { CornerMask(this) } private var url: String? = null // region Lifecycle - constructor(context: Context) : super(context) { initialize() } - constructor(context: Context, attrs: AttributeSet) : super(context, attrs) { initialize() } - constructor(context: Context, attrs: AttributeSet, defStyleAttr: Int) : super(context, attrs, defStyleAttr) { initialize() } - - private fun initialize() { - binding = ViewLinkPreviewBinding.inflate(LayoutInflater.from(context), this, true) - } + constructor(context: Context) : super(context) + constructor(context: Context, attrs: AttributeSet) : super(context, attrs) + constructor(context: Context, attrs: AttributeSet, defStyleAttr: Int) : super(context, attrs, defStyleAttr) // endregion // region Updating @@ -48,8 +41,8 @@ class LinkPreviewView : LinearLayout { // Thumbnail if (linkPreview.getThumbnail().isPresent) { // This internally fetches the thumbnail - binding.thumbnailImageView.setImageResource(glide, ImageSlide(context, linkPreview.getThumbnail().get()), isPreview = false, message) - binding.thumbnailImageView.loadIndicator.isVisible = false + binding.thumbnailImageView.root.setImageResource(glide, ImageSlide(context, linkPreview.getThumbnail().get()), isPreview = false, message) + binding.thumbnailImageView.root.loadIndicator.isVisible = false } // Title binding.titleTextView.text = linkPreview.title diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/messages/QuoteView.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/messages/QuoteView.kt index 91ab4c106..4e9140043 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/messages/QuoteView.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/messages/QuoteView.kt @@ -93,7 +93,7 @@ class QuoteView @JvmOverloads constructor(context: Context, attrs: AttributeSet? val backgroundColor = context.getAccentColor() binding.quoteViewAttachmentPreviewContainer.backgroundTintList = ColorStateList.valueOf(backgroundColor) binding.quoteViewAttachmentPreviewImageView.isVisible = false - binding.quoteViewAttachmentThumbnailImageView.isVisible = false + binding.quoteViewAttachmentThumbnailImageView.root.isVisible = false when { attachments.audioSlide != null -> { binding.quoteViewAttachmentPreviewImageView.setImageResource(R.drawable.ic_microphone) @@ -108,9 +108,9 @@ class QuoteView @JvmOverloads constructor(context: Context, attrs: AttributeSet? attachments.thumbnailSlide != null -> { val slide = attachments.thumbnailSlide!! // This internally fetches the thumbnail - binding.quoteViewAttachmentThumbnailImageView.radius = toPx(4, resources) - binding.quoteViewAttachmentThumbnailImageView.setImageResource(glide, slide, false, false) - binding.quoteViewAttachmentThumbnailImageView.isVisible = true + binding.quoteViewAttachmentThumbnailImageView.root.radius = toPx(4, resources) + binding.quoteViewAttachmentThumbnailImageView.root.setImageResource(glide, slide, false, null) + binding.quoteViewAttachmentThumbnailImageView.root.isVisible = true binding.quoteViewBodyTextView.text = if (MediaUtil.isVideo(slide.asAttachment())) resources.getString(R.string.Slide_video) else resources.getString(R.string.Slide_image) } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/messages/VisibleMessageContentView.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/messages/VisibleMessageContentView.kt index 334f1cf16..6e6d562cd 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/messages/VisibleMessageContentView.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/messages/VisibleMessageContentView.kt @@ -45,21 +45,17 @@ import org.thoughtcrime.securesms.util.getAccentColor import java.util.* import kotlin.math.roundToInt -class VisibleMessageContentView : LinearLayout { - private lateinit var binding: ViewVisibleMessageContentBinding +class VisibleMessageContentView : ConstraintLayout { + private val binding: ViewVisibleMessageContentBinding by lazy { ViewVisibleMessageContentBinding.bind(this) } var onContentClick: MutableList<((event: MotionEvent) -> Unit)> = mutableListOf() var onContentDoubleTap: (() -> Unit)? = null var delegate: VisibleMessageViewDelegate? = null var indexInAdapter: Int = -1 // region Lifecycle - constructor(context: Context) : super(context) { initialize() } - constructor(context: Context, attrs: AttributeSet) : super(context, attrs) { initialize() } - constructor(context: Context, attrs: AttributeSet, defStyleAttr: Int) : super(context, attrs, defStyleAttr) { initialize() } - - private fun initialize() { - binding = ViewVisibleMessageContentBinding.inflate(LayoutInflater.from(context), this, true) - } + constructor(context: Context) : super(context) + constructor(context: Context, attrs: AttributeSet) : super(context, attrs) + constructor(context: Context, attrs: AttributeSet, defStyleAttr: Int) : super(context, attrs, defStyleAttr) // endregion // region Updating @@ -86,7 +82,7 @@ class VisibleMessageContentView : LinearLayout { // reset visibilities / containers onContentClick.clear() - binding.albumThumbnailView.clearViews() + binding.albumThumbnailView.root.clearViews() onContentDoubleTap = null if (message.isDeleted) { @@ -94,11 +90,11 @@ class VisibleMessageContentView : LinearLayout { binding.deletedMessageView.root.bind(message, getTextColor(context, message)) binding.bodyTextView.isVisible = false binding.quoteView.root.isVisible = false - binding.linkPreviewView.isVisible = false + binding.linkPreviewView.root.isVisible = false binding.untrustedView.root.isVisible = false binding.voiceMessageView.root.isVisible = false binding.documentView.root.isVisible = false - binding.albumThumbnailView.isVisible = false + binding.albumThumbnailView.root.isVisible = false binding.openGroupInvitationView.root.isVisible = false return } else { @@ -110,12 +106,12 @@ class VisibleMessageContentView : LinearLayout { binding.quoteView.root.isVisible = message is MmsMessageRecord && message.quote != null - binding.linkPreviewView.isVisible = message is MmsMessageRecord && message.linkPreviews.isNotEmpty() + binding.linkPreviewView.root.isVisible = message is MmsMessageRecord && message.linkPreviews.isNotEmpty() binding.untrustedView.root.isVisible = !contactIsTrusted && message is MmsMessageRecord && message.quote == null && message.linkPreviews.isEmpty() binding.voiceMessageView.root.isVisible = contactIsTrusted && message is MmsMessageRecord && message.slideDeck.audioSlide != null binding.documentView.root.isVisible = contactIsTrusted && message is MmsMessageRecord && message.slideDeck.documentSlide != null - binding.albumThumbnailView.isVisible = mediaThumbnailMessage + binding.albumThumbnailView.root.isVisible = mediaThumbnailMessage binding.openGroupInvitationView.root.isVisible = message.isOpenGroupInvitation var hideBody = false @@ -162,8 +158,8 @@ class VisibleMessageContentView : LinearLayout { when { message is MmsMessageRecord && message.linkPreviews.isNotEmpty() -> { - binding.linkPreviewView.bind(message, glide, isStartOfMessageCluster, isEndOfMessageCluster) - onContentClick.add { event -> binding.linkPreviewView.calculateHit(event) } + binding.linkPreviewView.root.bind(message, glide, isStartOfMessageCluster, isEndOfMessageCluster) + onContentClick.add { event -> binding.linkPreviewView.root.calculateHit(event) } // Body text view is inside the link preview for layout convenience } message is MmsMessageRecord && message.slideDeck.audioSlide != null -> { @@ -200,21 +196,21 @@ class VisibleMessageContentView : LinearLayout { if (contactIsTrusted || message.isOutgoing) { // isStart and isEnd of cluster needed for calculating the mask for full bubble image groups // bind after add view because views are inflated and calculated during bind - binding.albumThumbnailView.bind( + binding.albumThumbnailView.root.bind( glideRequests = glide, message = message, isStart = isStartOfMessageCluster, isEnd = isEndOfMessageCluster ) - val layoutParams = binding.albumThumbnailView.layoutParams as ConstraintLayout.LayoutParams + val layoutParams = binding.albumThumbnailView.root.layoutParams as ConstraintLayout.LayoutParams layoutParams.horizontalBias = if (message.isOutgoing) 1f else 0f - binding.albumThumbnailView.layoutParams = layoutParams + binding.albumThumbnailView.root.layoutParams = layoutParams onContentClick.add { event -> - binding.albumThumbnailView.calculateHitObject(event, message, thread, onAttachmentNeedsDownload) + binding.albumThumbnailView.root.calculateHitObject(event, message, thread, onAttachmentNeedsDownload) } } else { hideBody = true - binding.albumThumbnailView.clearViews() + binding.albumThumbnailView.root.clearViews() binding.untrustedView.root.bind(UntrustedAttachmentView.AttachmentType.MEDIA, VisibleMessageContentView.getTextColor(context,message)) onContentClick.add { binding.untrustedView.root.showTrustDialog(message.individualRecipient) } } @@ -246,7 +242,7 @@ class VisibleMessageContentView : LinearLayout { } private fun ViewVisibleMessageContentBinding.barrierViewsGone(): Boolean = - listOf(albumThumbnailView, linkPreviewView, voiceMessageView.root, quoteView.root).none { it.isVisible } + listOf(albumThumbnailView.root, linkPreviewView.root, voiceMessageView.root, quoteView.root).none { it.isVisible } private fun getBackground(isOutgoing: Boolean): Drawable { val backgroundID = if (isOutgoing) R.drawable.message_bubble_background_sent_alone else R.drawable.message_bubble_background_received_alone @@ -261,8 +257,8 @@ class VisibleMessageContentView : LinearLayout { binding.openGroupInvitationView.root, binding.documentView.root, binding.quoteView.root, - binding.linkPreviewView, - binding.albumThumbnailView, + binding.linkPreviewView.root, + binding.albumThumbnailView.root, binding.bodyTextView ).forEach { view: View -> view.isVisible = false } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/messages/VisibleMessageView.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/messages/VisibleMessageView.kt index 7a421298d..3a38da0bb 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/messages/VisibleMessageView.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/messages/VisibleMessageView.kt @@ -85,7 +85,7 @@ class VisibleMessageView : LinearLayout { var onPress: ((event: MotionEvent) -> Unit)? = null var onSwipeToReply: (() -> Unit)? = null var onLongPress: (() -> Unit)? = null - val messageContentView: VisibleMessageContentView by lazy { binding.messageContentView } + val messageContentView: VisibleMessageContentView by lazy { binding.messageContentView.root } companion object { const val swipeToReplyThreshold = 64.0f // dp @@ -108,7 +108,7 @@ class VisibleMessageView : LinearLayout { isHapticFeedbackEnabled = true setWillNotDraw(false) binding.messageInnerContainer.disableClipping() - binding.messageContentView.disableClipping() + binding.messageContentView.root.disableClipping() } // endregion @@ -210,26 +210,26 @@ class VisibleMessageView : LinearLayout { // Expiration timer updateExpirationTimer(message) // Emoji Reactions - val emojiLayoutParams = binding.emojiReactionsView.layoutParams as ConstraintLayout.LayoutParams + val emojiLayoutParams = binding.emojiReactionsView.root.layoutParams as ConstraintLayout.LayoutParams emojiLayoutParams.horizontalBias = if (message.isOutgoing) 1f else 0f - binding.emojiReactionsView.layoutParams = emojiLayoutParams + binding.emojiReactionsView.root.layoutParams = emojiLayoutParams if (message.reactions.isNotEmpty()) { val capabilities = lokiThreadDb.getOpenGroupChat(threadID)?.server?.let { lokiApiDb.getServerCapabilities(it) } if (capabilities.isNullOrEmpty() || capabilities.contains(OpenGroupApi.Capability.REACTIONS.name.lowercase())) { - binding.emojiReactionsView.setReactions(message.id, message.reactions, message.isOutgoing, delegate) - binding.emojiReactionsView.isVisible = true + binding.emojiReactionsView.root.setReactions(message.id, message.reactions, message.isOutgoing, delegate) + binding.emojiReactionsView.root.isVisible = true } else { - binding.emojiReactionsView.isVisible = false + binding.emojiReactionsView.root.isVisible = false } } else { - binding.emojiReactionsView.isVisible = false + binding.emojiReactionsView.root.isVisible = false } // Populate content view - binding.messageContentView.indexInAdapter = indexInAdapter - binding.messageContentView.bind( + binding.messageContentView.root.indexInAdapter = indexInAdapter + binding.messageContentView.root.bind( message, isStartOfMessageCluster, isEndOfMessageCluster, @@ -239,8 +239,8 @@ class VisibleMessageView : LinearLayout { message.isOutgoing || isGroupThread || (contact?.isTrusted ?: false), onAttachmentNeedsDownload ) - binding.messageContentView.delegate = delegate - onDoubleTap = { binding.messageContentView.onContentDoubleTap?.invoke() } + binding.messageContentView.root.delegate = delegate + onDoubleTap = { binding.messageContentView.root.onContentDoubleTap?.invoke() } } private fun isStartOfMessageCluster(current: MessageRecord, previous: MessageRecord?, isGroupThread: Boolean): Boolean { @@ -275,7 +275,7 @@ class VisibleMessageView : LinearLayout { private fun updateExpirationTimer(message: MessageRecord) { val container = binding.messageInnerContainer - val content = binding.messageContentView + val content = binding.messageContentView.root val expiration = binding.expirationTimerView val spacing = binding.messageContentSpacing container.removeAllViewsInLayout() @@ -326,7 +326,7 @@ class VisibleMessageView : LinearLayout { override fun onDraw(canvas: Canvas) { val spacing = context.resources.getDimensionPixelSize(R.dimen.small_spacing) val iconSize = toPx(24, context.resources) - val left = binding.messageInnerContainer.left + binding.messageContentView.right + spacing + val left = binding.messageInnerContainer.left + binding.messageContentView.root.right + spacing val top = height - (binding.messageInnerContainer.height / 2) - binding.profilePictureView.root.marginBottom - (iconSize / 2) val right = left + iconSize val bottom = top + iconSize @@ -348,7 +348,7 @@ class VisibleMessageView : LinearLayout { fun recycle() { binding.profilePictureView.root.recycle() - binding.messageContentView.recycle() + binding.messageContentView.root.recycle() } // endregion @@ -444,7 +444,7 @@ class VisibleMessageView : LinearLayout { } fun onContentClick(event: MotionEvent) { - binding.messageContentView.onContentClick.iterator().forEach { clickHandler -> clickHandler.invoke(event) } + binding.messageContentView.root.onContentClick.iterator().forEach { clickHandler -> clickHandler.invoke(event) } } private fun onPress(event: MotionEvent) { @@ -464,7 +464,7 @@ class VisibleMessageView : LinearLayout { } fun playVoiceMessage() { - binding.messageContentView.playVoiceMessage() + binding.messageContentView.root.playVoiceMessage() } // endregion } diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/utilities/ThumbnailView.java b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/utilities/ThumbnailView.java deleted file mode 100644 index 912253ecd..000000000 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/utilities/ThumbnailView.java +++ /dev/null @@ -1,425 +0,0 @@ -package org.thoughtcrime.securesms.conversation.v2.utilities; - -import static com.bumptech.glide.load.resource.drawable.DrawableTransitionOptions.withCrossFade; - -import android.content.Context; -import android.content.res.TypedArray; -import android.net.Uri; -import android.util.AttributeSet; -import android.view.View; -import android.view.ViewGroup; -import android.widget.FrameLayout; -import android.widget.ImageView; - -import androidx.annotation.NonNull; -import androidx.annotation.UiThread; - -import com.bumptech.glide.RequestBuilder; -import com.bumptech.glide.load.engine.DiskCacheStrategy; -import com.bumptech.glide.load.resource.bitmap.BitmapTransformation; -import com.bumptech.glide.load.resource.bitmap.CenterCrop; -import com.bumptech.glide.load.resource.bitmap.FitCenter; -import com.bumptech.glide.load.resource.bitmap.RoundedCorners; -import com.bumptech.glide.request.RequestOptions; - -import org.session.libsession.messaging.sending_receiving.attachments.AttachmentTransferProgress; -import org.session.libsession.utilities.Util; -import org.session.libsession.utilities.ViewUtil; -import org.session.libsignal.utilities.ListenableFuture; -import org.session.libsignal.utilities.Log; -import org.session.libsignal.utilities.SettableFuture; -import org.session.libsignal.utilities.guava.Optional; -import org.thoughtcrime.securesms.components.GlideBitmapListeningTarget; -import org.thoughtcrime.securesms.components.GlideDrawableListeningTarget; -import org.thoughtcrime.securesms.components.TransferControlView; -import org.thoughtcrime.securesms.mms.DecryptableStreamUriLoader.DecryptableUri; -import org.thoughtcrime.securesms.mms.GlideRequest; -import org.thoughtcrime.securesms.mms.GlideRequests; -import org.thoughtcrime.securesms.mms.Slide; -import org.thoughtcrime.securesms.mms.SlideClickListener; -import org.thoughtcrime.securesms.mms.SlidesClickedListener; - -import java.util.Collections; -import java.util.Locale; - -import network.loki.messenger.R; - -public class ThumbnailView extends FrameLayout { - - private static final String TAG = ThumbnailView.class.getSimpleName(); - private static final int WIDTH = 0; - private static final int HEIGHT = 1; - private static final int MIN_WIDTH = 0; - private static final int MAX_WIDTH = 1; - private static final int MIN_HEIGHT = 2; - private static final int MAX_HEIGHT = 3; - - private ImageView image; - private View playOverlay; - private View loadIndicator; - private OnClickListener parentClickListener; - - private final int[] dimens = new int[2]; - private final int[] bounds = new int[4]; - private final int[] measureDimens = new int[2]; - - private Optional transferControls = Optional.absent(); - private SlideClickListener thumbnailClickListener = null; - private SlidesClickedListener downloadClickListener = null; - private Slide slide = null; - - public int radius; - - public ThumbnailView(Context context) { - this(context, null); - } - - public ThumbnailView(Context context, AttributeSet attrs) { - this(context, attrs, 0); - } - - public ThumbnailView(final Context context, AttributeSet attrs, int defStyle) { - super(context, attrs, defStyle); - - inflate(context, R.layout.thumbnail_view, this); - - this.image = findViewById(R.id.thumbnail_image); - this.playOverlay = findViewById(R.id.play_overlay); - this.loadIndicator = findViewById(R.id.thumbnail_load_indicator); - super.setOnClickListener(new ThumbnailClickDispatcher()); - - if (attrs != null) { - TypedArray typedArray = context.getTheme().obtainStyledAttributes(attrs, R.styleable.ThumbnailView, 0, 0); - bounds[MIN_WIDTH] = typedArray.getDimensionPixelSize(R.styleable.ThumbnailView_minWidth, 0); - bounds[MAX_WIDTH] = typedArray.getDimensionPixelSize(R.styleable.ThumbnailView_maxWidth, 0); - bounds[MIN_HEIGHT] = typedArray.getDimensionPixelSize(R.styleable.ThumbnailView_minHeight, 0); - bounds[MAX_HEIGHT] = typedArray.getDimensionPixelSize(R.styleable.ThumbnailView_maxHeight, 0); - radius = typedArray.getDimensionPixelSize(R.styleable.ThumbnailView_thumbnail_radius, 0); - typedArray.recycle(); - } else { - radius = 0; - } - } - - @Override - protected void onMeasure(int originalWidthMeasureSpec, int originalHeightMeasureSpec) { - fillTargetDimensions(measureDimens, dimens, bounds); - if (measureDimens[WIDTH] == 0 && measureDimens[HEIGHT] == 0) { - super.onMeasure(originalWidthMeasureSpec, originalHeightMeasureSpec); - return; - } - - int finalWidth = measureDimens[WIDTH] + getPaddingLeft() + getPaddingRight(); - int finalHeight = measureDimens[HEIGHT] + getPaddingTop() + getPaddingBottom(); - - super.onMeasure(MeasureSpec.makeMeasureSpec(finalWidth, MeasureSpec.EXACTLY), - MeasureSpec.makeMeasureSpec(finalHeight, MeasureSpec.EXACTLY)); - } - - @SuppressWarnings("SuspiciousNameCombination") - private void fillTargetDimensions(int[] targetDimens, int[] dimens, int[] bounds) { - int dimensFilledCount = getNonZeroCount(dimens); - int boundsFilledCount = getNonZeroCount(bounds); - - if (dimensFilledCount == 0 || boundsFilledCount == 0) { - targetDimens[WIDTH] = 0; - targetDimens[HEIGHT] = 0; - return; - } - - double naturalWidth = dimens[WIDTH]; - double naturalHeight = dimens[HEIGHT]; - - int minWidth = bounds[MIN_WIDTH]; - int maxWidth = bounds[MAX_WIDTH]; - int minHeight = bounds[MIN_HEIGHT]; - int maxHeight = bounds[MAX_HEIGHT]; - - if (dimensFilledCount > 0 && dimensFilledCount < dimens.length) { - throw new IllegalStateException(String.format(Locale.ENGLISH, "Width or height has been specified, but not both. Dimens: %f x %f", - naturalWidth, naturalHeight)); - } - if (boundsFilledCount > 0 && boundsFilledCount < bounds.length) { - throw new IllegalStateException(String.format(Locale.ENGLISH, "One or more min/max dimensions have been specified, but not all. Bounds: [%d, %d, %d, %d]", - minWidth, maxWidth, minHeight, maxHeight)); - } - - double measuredWidth = naturalWidth; - double measuredHeight = naturalHeight; - - boolean widthInBounds = measuredWidth >= minWidth && measuredWidth <= maxWidth; - boolean heightInBounds = measuredHeight >= minHeight && measuredHeight <= maxHeight; - - if (!widthInBounds || !heightInBounds) { - double minWidthRatio = naturalWidth / minWidth; - double maxWidthRatio = naturalWidth / maxWidth; - double minHeightRatio = naturalHeight / minHeight; - double maxHeightRatio = naturalHeight / maxHeight; - - if (maxWidthRatio > 1 || maxHeightRatio > 1) { - if (maxWidthRatio >= maxHeightRatio) { - measuredWidth /= maxWidthRatio; - measuredHeight /= maxWidthRatio; - } else { - measuredWidth /= maxHeightRatio; - measuredHeight /= maxHeightRatio; - } - - measuredWidth = Math.max(measuredWidth, minWidth); - measuredHeight = Math.max(measuredHeight, minHeight); - - } else if (minWidthRatio < 1 || minHeightRatio < 1) { - if (minWidthRatio <= minHeightRatio) { - measuredWidth /= minWidthRatio; - measuredHeight /= minWidthRatio; - } else { - measuredWidth /= minHeightRatio; - measuredHeight /= minHeightRatio; - } - - measuredWidth = Math.min(measuredWidth, maxWidth); - measuredHeight = Math.min(measuredHeight, maxHeight); - } - } - - targetDimens[WIDTH] = (int) measuredWidth; - targetDimens[HEIGHT] = (int) measuredHeight; - } - - private int getNonZeroCount(int[] vals) { - int count = 0; - for (int val : vals) { - if (val > 0) { - count++; - } - } - return count; - } - - @Override - public void setOnClickListener(OnClickListener l) { - parentClickListener = l; - } - - @Override - public void setFocusable(boolean focusable) { - super.setFocusable(focusable); - if (transferControls.isPresent()) transferControls.get().setFocusable(focusable); - } - - @Override - public void setClickable(boolean clickable) { - super.setClickable(clickable); - if (transferControls.isPresent()) transferControls.get().setClickable(clickable); - } - - private TransferControlView getTransferControls() { - if (!transferControls.isPresent()) { - transferControls = Optional.of(ViewUtil.inflateStub(this, R.id.transfer_controls_stub)); - } - return transferControls.get(); - } - - public void setBounds(int minWidth, int maxWidth, int minHeight, int maxHeight) { - bounds[MIN_WIDTH] = minWidth; - bounds[MAX_WIDTH] = maxWidth; - bounds[MIN_HEIGHT] = minHeight; - bounds[MAX_HEIGHT] = maxHeight; - - forceLayout(); - } - - @UiThread - public ListenableFuture setImageResource(@NonNull GlideRequests glideRequests, @NonNull Slide slide, - boolean showControls, boolean isPreview) - { - return setImageResource(glideRequests, slide, showControls, isPreview, 0, 0); - } - - @UiThread - public ListenableFuture setImageResource(@NonNull GlideRequests glideRequests, @NonNull Slide slide, - boolean showControls, boolean isPreview, - int naturalWidth, int naturalHeight) - { - if (showControls) { - getTransferControls().setSlide(slide); - getTransferControls().setDownloadClickListener(new DownloadClickDispatcher()); - } else if (transferControls.isPresent()) { - getTransferControls().setVisibility(View.GONE); - } - - if (slide.getThumbnailUri() != null && slide.hasPlayOverlay() && - (slide.getTransferState() == AttachmentTransferProgress.TRANSFER_PROGRESS_DONE || isPreview)) - { - this.playOverlay.setVisibility(View.VISIBLE); - } else { - this.playOverlay.setVisibility(View.GONE); - } - - if (Util.equals(slide, this.slide)) { - Log.i(TAG, "Not re-loading slide " + slide.asAttachment().getDataUri()); - return new SettableFuture<>(false); - } - - if (this.slide != null && this.slide.getFastPreflightId() != null && - this.slide.getFastPreflightId().equals(slide.getFastPreflightId())) - { - Log.i(TAG, "Not re-loading slide for fast preflight: " + slide.getFastPreflightId()); - this.slide = slide; - return new SettableFuture<>(false); - } - - Log.i(TAG, "loading part with id " + slide.asAttachment().getDataUri() - + ", progress " + slide.getTransferState() + ", fast preflight id: " + - slide.asAttachment().getFastPreflightId()); - - this.slide = slide; - - dimens[WIDTH] = naturalWidth; - dimens[HEIGHT] = naturalHeight; - invalidate(); - - SettableFuture result = new SettableFuture<>(); - - if (slide.getThumbnailUri() != null) { - buildThumbnailGlideRequest(glideRequests, slide).into(new GlideDrawableListeningTarget(image, result)); - } else if (slide.hasPlaceholder()) { - buildPlaceholderGlideRequest(glideRequests, slide).into(new GlideBitmapListeningTarget(image, result)); - } else { - glideRequests.load(R.drawable.ic_image_white_24dp).centerInside().into(image); - result.set(false); - } - - return result; - } - - public ListenableFuture setImageResource(@NonNull GlideRequests glideRequests, @NonNull Uri uri) { - SettableFuture future = new SettableFuture<>(); - - if (transferControls.isPresent()) getTransferControls().setVisibility(View.GONE); - - GlideRequest request = glideRequests.load(new DecryptableUri(uri)) - .diskCacheStrategy(DiskCacheStrategy.NONE) - .transition(withCrossFade()); - - if (radius > 0) { - request = request.transforms(new CenterCrop(), new RoundedCorners(radius)); - } else { - request = request.transforms(new CenterCrop()); - } - - request.into(new GlideDrawableListeningTarget(image, future)); - - return future; - } - - public void setThumbnailClickListener(SlideClickListener listener) { - this.thumbnailClickListener = listener; - } - - public void setDownloadClickListener(SlidesClickedListener listener) { - this.downloadClickListener = listener; - } - - public void clear(GlideRequests glideRequests) { - glideRequests.clear(image); - - if (transferControls.isPresent()) { - getTransferControls().clear(); - } - - slide = null; - } - - public void showDownloadText(boolean showDownloadText) { - getTransferControls().setShowDownloadText(showDownloadText); - } - - public void showProgressSpinner() { - getTransferControls().showProgressSpinner(); - } - - public void setLoadIndicatorVisibile(boolean visible) { - this.loadIndicator.setVisibility(visible ? VISIBLE : GONE); - } - - protected void setRadius(int radius) { - this.radius = radius; - } - - private GlideRequest buildThumbnailGlideRequest(@NonNull GlideRequests glideRequests, @NonNull Slide slide) { - GlideRequest request = applySizing(glideRequests.load(new DecryptableUri(slide.getThumbnailUri())) - .diskCacheStrategy(DiskCacheStrategy.NONE) - .transition(withCrossFade()), new CenterCrop()); - - if (slide.isInProgress()) return request; - else return request.apply(RequestOptions.errorOf(R.drawable.ic_missing_thumbnail_picture)); - } - - private RequestBuilder buildPlaceholderGlideRequest(@NonNull GlideRequests glideRequests, @NonNull Slide slide) { - return applySizing(glideRequests.asBitmap() - .load(slide.getPlaceholderRes(getContext().getTheme())) - .diskCacheStrategy(DiskCacheStrategy.NONE), new FitCenter()); - } - - private GlideRequest applySizing(@NonNull GlideRequest request, @NonNull BitmapTransformation fitting) { - int[] size = new int[2]; - fillTargetDimensions(size, dimens, bounds); - if (size[WIDTH] == 0 && size[HEIGHT] == 0) { - size[WIDTH] = getDefaultWidth(); - size[HEIGHT] = getDefaultHeight(); - } - - request = request.override(size[WIDTH], size[HEIGHT]); - - if (radius > 0) { - return request.transforms(fitting, new RoundedCorners(radius)); - } else { - return request.transforms(fitting); - } - } - - private int getDefaultWidth() { - ViewGroup.LayoutParams params = getLayoutParams(); - if (params != null) { - return Math.max(params.width, 0); - } - return 0; - } - - private int getDefaultHeight() { - ViewGroup.LayoutParams params = getLayoutParams(); - if (params != null) { - return Math.max(params.height, 0); - } - return 0; - } - - private class ThumbnailClickDispatcher implements View.OnClickListener { - - @Override - public void onClick(View view) { - if (thumbnailClickListener != null && - slide != null && - slide.asAttachment().getDataUri() != null && - slide.getTransferState() == AttachmentTransferProgress.TRANSFER_PROGRESS_DONE) - { - thumbnailClickListener.onClick(view, slide); - } else if (parentClickListener != null) { - parentClickListener.onClick(view); - } - } - } - - private class DownloadClickDispatcher implements View.OnClickListener { - - @Override - public void onClick(View view) { - if (downloadClickListener != null && slide != null) { - downloadClickListener.onClick(view, Collections.singletonList(slide)); - } else { - Log.w(TAG, "Received a download button click, but unable to execute it. slide: " + String.valueOf(slide) + " downloadClickListener: " + String.valueOf(downloadClickListener)); - } - } - } -} diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/utilities/KThumbnailView.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/utilities/ThumbnailView.kt similarity index 76% rename from app/src/main/java/org/thoughtcrime/securesms/conversation/v2/utilities/KThumbnailView.kt rename to app/src/main/java/org/thoughtcrime/securesms/conversation/v2/utilities/ThumbnailView.kt index 1ae290218..275947a81 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/utilities/KThumbnailView.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/utilities/ThumbnailView.kt @@ -2,14 +2,11 @@ package org.thoughtcrime.securesms.conversation.v2.utilities import android.content.Context import android.graphics.Bitmap -import android.graphics.drawable.ColorDrawable import android.graphics.drawable.Drawable import android.net.Uri import android.util.AttributeSet -import android.view.LayoutInflater import android.view.View import android.widget.FrameLayout -import androidx.core.content.ContextCompat import androidx.core.view.isVisible import com.bumptech.glide.load.engine.DiskCacheStrategy import com.bumptech.glide.load.resource.bitmap.CenterCrop @@ -29,31 +26,33 @@ import org.thoughtcrime.securesms.mms.DecryptableStreamUriLoader.DecryptableUri import org.thoughtcrime.securesms.mms.GlideRequest import org.thoughtcrime.securesms.mms.GlideRequests import org.thoughtcrime.securesms.mms.Slide +import kotlin.Boolean +import kotlin.Int +import kotlin.getValue +import kotlin.lazy +import kotlin.let -open class KThumbnailView: FrameLayout { - private lateinit var binding: ThumbnailViewBinding +open class ThumbnailView: FrameLayout { companion object { private const val WIDTH = 0 private const val HEIGHT = 1 } + private val binding: ThumbnailViewBinding by lazy { ThumbnailViewBinding.bind(this) } + // region Lifecycle constructor(context: Context) : super(context) { initialize(null) } constructor(context: Context, attrs: AttributeSet) : super(context, attrs) { initialize(attrs) } constructor(context: Context, attrs: AttributeSet, defStyleAttr: Int) : super(context, attrs, defStyleAttr) { initialize(attrs) } - private val image by lazy { binding.thumbnailImage } - private val playOverlay by lazy { binding.playOverlay } val loadIndicator: View by lazy { binding.thumbnailLoadIndicator } - val downloadIndicator: View by lazy { binding.thumbnailDownloadIcon } private val dimensDelegate = ThumbnailDimensDelegate() private var slide: Slide? = null - private var radius: Int = 0 + var radius: Int = 0 private fun initialize(attrs: AttributeSet?) { - binding = ThumbnailViewBinding.inflate(LayoutInflater.from(context), this) if (attrs != null) { val typedArray = context.theme.obtainStyledAttributes(attrs, R.styleable.ThumbnailView, 0, 0) @@ -66,8 +65,6 @@ open class KThumbnailView: FrameLayout { typedArray.recycle() } - val background = ContextCompat.getColor(context, R.color.transparent_black_6) - binding.root.background = ColorDrawable(background) } override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) { @@ -80,8 +77,8 @@ open class KThumbnailView: FrameLayout { val finalHeight: Int = adjustedDimens[HEIGHT] + paddingTop + paddingBottom super.onMeasure( - MeasureSpec.makeMeasureSpec(finalWidth, MeasureSpec.EXACTLY), - MeasureSpec.makeMeasureSpec(finalHeight, MeasureSpec.EXACTLY) + MeasureSpec.makeMeasureSpec(finalWidth, MeasureSpec.EXACTLY), + MeasureSpec.makeMeasureSpec(finalHeight, MeasureSpec.EXACTLY) ) } @@ -90,17 +87,17 @@ open class KThumbnailView: FrameLayout { // endregion // region Interaction - fun setImageResource(glide: GlideRequests, slide: Slide, isPreview: Boolean, mms: MmsMessageRecord): ListenableFuture { + fun setImageResource(glide: GlideRequests, slide: Slide, isPreview: Boolean, mms: MmsMessageRecord?): ListenableFuture { return setImageResource(glide, slide, isPreview, 0, 0, mms) } fun setImageResource(glide: GlideRequests, slide: Slide, isPreview: Boolean, naturalWidth: Int, - naturalHeight: Int, mms: MmsMessageRecord): ListenableFuture { + naturalHeight: Int, mms: MmsMessageRecord?): ListenableFuture { val currentSlide = this.slide - playOverlay.isVisible = (slide.thumbnailUri != null && slide.hasPlayOverlay() && + binding.playOverlay.isVisible = (slide.thumbnailUri != null && slide.hasPlayOverlay() && (slide.transferState == AttachmentTransferProgress.TRANSFER_PROGRESS_DONE || isPreview)) if (equals(currentSlide, slide)) { @@ -116,8 +113,8 @@ open class KThumbnailView: FrameLayout { this.slide = slide - loadIndicator.isVisible = slide.isInProgress - downloadIndicator.isVisible = slide.transferState == AttachmentTransferProgress.TRANSFER_PROGRESS_FAILED + binding.thumbnailLoadIndicator.isVisible = slide.isInProgress + binding.thumbnailDownloadIcon.isVisible = slide.transferState == AttachmentTransferProgress.TRANSFER_PROGRESS_FAILED dimensDelegate.setDimens(naturalWidth, naturalHeight) invalidate() @@ -126,13 +123,13 @@ open class KThumbnailView: FrameLayout { when { slide.thumbnailUri != null -> { - buildThumbnailGlideRequest(glide, slide).into(GlideDrawableListeningTarget(image, result)) + buildThumbnailGlideRequest(glide, slide).into(GlideDrawableListeningTarget(binding.thumbnailImage, result)) } slide.hasPlaceholder() -> { - buildPlaceholderGlideRequest(glide, slide).into(GlideBitmapListeningTarget(image, result)) + buildPlaceholderGlideRequest(glide, slide).into(GlideBitmapListeningTarget(binding.thumbnailImage, result)) } else -> { - glide.clear(image) + glide.clear(binding.thumbnailImage) result.set(false) } } @@ -176,7 +173,7 @@ open class KThumbnailView: FrameLayout { } open fun clear(glideRequests: GlideRequests) { - glideRequests.clear(image) + glideRequests.clear(binding.thumbnailImage) slide = null } @@ -193,11 +190,35 @@ open class KThumbnailView: FrameLayout { request.transforms(CenterCrop()) } - request.into(GlideDrawableListeningTarget(image, future)) + request.into(GlideDrawableListeningTarget(binding.thumbnailImage, future)) return future } +// fun showDownloadText(showDownloadText: Boolean) { +// getTransferControls()?.setShowDownloadText(showDownloadText); +// } +// +// fun setDownloadClickListener(listener: SlidesClickedListener) { +// this.downloadClickListener = listener; +// } +// +// private fun getTransferControls(): TransferControlView? { +// if (transferControls == null) { +// transferControls = ViewUtil.inflateStub(this, R.id.transfer_controls_stub); +// } +// +// return transferControls +// } // endregion +// private class DownloadClickDispatcher : OnClickListener { +// override fun onClick(view: View?) { +// if (downloadClickListener != null && slide != null) { +// downloadClickListener.onClick(view, Collections.singletonList(slide)) +// } else { +// Log.w(TAG, "Received a download button click, but unable to execute it. slide: " + String.valueOf(slide) + " downloadClickListener: " + String.valueOf(downloadClickListener)) +// } +// } +// } } \ No newline at end of file diff --git a/app/src/main/java/org/thoughtcrime/securesms/home/ConversationView.kt b/app/src/main/java/org/thoughtcrime/securesms/home/ConversationView.kt index bfa9b1448..7a0c865a4 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/home/ConversationView.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/home/ConversationView.kt @@ -99,11 +99,11 @@ class ConversationView : LinearLayout { binding.snippetTextView.typeface = if (unreadCount > 0 && !thread.isRead) Typeface.DEFAULT_BOLD else Typeface.DEFAULT binding.snippetTextView.visibility = if (isTyping) View.GONE else View.VISIBLE if (isTyping) { - binding.typingIndicatorView.startAnimation() + binding.typingIndicatorView.root.startAnimation() } else { - binding.typingIndicatorView.stopAnimation() + binding.typingIndicatorView.root.stopAnimation() } - binding.typingIndicatorView.visibility = if (isTyping) View.VISIBLE else View.GONE + binding.typingIndicatorView.root.visibility = if (isTyping) View.VISIBLE else View.GONE binding.statusIndicatorImageView.visibility = View.VISIBLE when { !thread.isOutgoing -> binding.statusIndicatorImageView.visibility = View.GONE diff --git a/app/src/main/java/org/thoughtcrime/securesms/home/HomeActivity.kt b/app/src/main/java/org/thoughtcrime/securesms/home/HomeActivity.kt index 45f3b4a63..f1a9c8ed9 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/home/HomeActivity.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/home/HomeActivity.kt @@ -202,7 +202,7 @@ class HomeActivity : PassphraseRequiredActionBarActivity(), OpenGroupManager.startPolling() JobQueue.shared.resumePendingJobs() } - // Set up typing observer + withContext(Dispatchers.Main) { updateProfileButton() TextSecurePreferences.events.filter { it == TextSecurePreferences.PROFILE_NAME_PREF }.collect { @@ -365,6 +365,10 @@ class HomeActivity : PassphraseRequiredActionBarActivity(), setupMessageRequestsBanner() updateEmptyState() } + + ApplicationContext.getInstance(this@HomeActivity).typingStatusRepository.typingThreads.observe(this) { threadIds -> + homeAdapter.typingThreadIDs = (threadIds ?: setOf()) + } } private fun updateEmptyState() { diff --git a/app/src/main/java/org/thoughtcrime/securesms/home/HomeAdapter.kt b/app/src/main/java/org/thoughtcrime/securesms/home/HomeAdapter.kt index 3efa841b5..0effc43fb 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/home/HomeAdapter.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/home/HomeAdapter.kt @@ -63,6 +63,8 @@ class HomeAdapter( lateinit var glide: GlideRequests var typingThreadIDs = setOf() set(value) { + if (field == value) { return } + field = value // TODO: replace this with a diffed update or a partial change set with payloads notifyDataSetChanged() diff --git a/app/src/main/res/layout/album_thumbnail_1.xml b/app/src/main/res/layout/album_thumbnail_1.xml index cf0f5d489..cee81ba3e 100644 --- a/app/src/main/res/layout/album_thumbnail_1.xml +++ b/app/src/main/res/layout/album_thumbnail_1.xml @@ -6,7 +6,7 @@ android:layout_width="@dimen/media_bubble_default_dimens" android:layout_height="@dimen/media_bubble_default_dimens"> - - - - - - - @@ -20,4 +21,4 @@ android:layout_gravity="center" android:layout="@layout/transfer_controls_stub" /> - \ No newline at end of file + \ No newline at end of file diff --git a/app/src/main/res/layout/link_preview.xml b/app/src/main/res/layout/link_preview.xml deleted file mode 100644 index f76ad1010..000000000 --- a/app/src/main/res/layout/link_preview.xml +++ /dev/null @@ -1,104 +0,0 @@ - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/app/src/main/res/layout/media_overview_gallery_item.xml b/app/src/main/res/layout/media_overview_gallery_item.xml index 607261175..a4c3f324a 100644 --- a/app/src/main/res/layout/media_overview_gallery_item.xml +++ b/app/src/main/res/layout/media_overview_gallery_item.xml @@ -5,7 +5,7 @@ android:layout_height="match_parent" android:padding="2dp"> - - - + xmlns:app="http://schemas.android.com/apk/res-auto" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:background="@color/transparent_black_6"> - + diff --git a/app/src/main/res/layout/view_conversation.xml b/app/src/main/res/layout/view_conversation.xml index 8f26f17c7..04833b6a9 100644 --- a/app/src/main/res/layout/view_conversation.xml +++ b/app/src/main/res/layout/view_conversation.xml @@ -34,64 +34,69 @@ android:layout_gravity="center_vertical" android:orientation="vertical"> - + android:layout_height="wrap_content"> - + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintEnd_toStartOf="@id/unreadCountIndicator" + app:layout_constraintTop_toTopOf="parent" + app:layout_constraintBottom_toBottomOf="parent" + app:layout_constrainedWidth="true" + app:layout_constraintHorizontal_chainStyle="packed" + app:layout_constraintHorizontal_bias="0" + android:drawablePadding="4dp" + android:maxLines="1" + android:ellipsize="end" + android:textAlignment="viewStart" + android:textSize="@dimen/medium_font_size" + android:textStyle="bold" + android:textColor="?android:textColorPrimary" + app:drawableTint="?conversation_pinned_icon_color" + tools:drawableRight="@drawable/ic_pin" + tools:text="I'm a very long display name. What are you going to do about it?" /> + + + tools:text="8" + tools:textColor="?android:textColorPrimary" /> - - - - - - - + - + - - - @@ -65,4 +66,4 @@ android:visibility="gone" app:constraint_referenced_ids="image_view_show_less, text_view_show_less"/> - \ No newline at end of file + \ No newline at end of file diff --git a/app/src/main/res/layout/view_link_preview.xml b/app/src/main/res/layout/view_link_preview.xml index 7e209c2a9..dd2e133be 100644 --- a/app/src/main/res/layout/view_link_preview.xml +++ b/app/src/main/res/layout/view_link_preview.xml @@ -1,8 +1,9 @@ - - - \ No newline at end of file + \ No newline at end of file diff --git a/app/src/main/res/layout/view_link_preview_draft.xml b/app/src/main/res/layout/view_link_preview_draft.xml index 65e2cf7fd..bf7cd3ebb 100644 --- a/app/src/main/res/layout/view_link_preview_draft.xml +++ b/app/src/main/res/layout/view_link_preview_draft.xml @@ -26,7 +26,7 @@ android:src="@drawable/ic_link" app:tint="?android:textColorPrimary" /> - - - - + android:layout_height="match_parent"> - \ No newline at end of file + \ No newline at end of file diff --git a/app/src/main/res/layout/view_visible_message.xml b/app/src/main/res/layout/view_visible_message.xml index 48c2d1d8e..d36a5dfde 100644 --- a/app/src/main/res/layout/view_visible_message.xml +++ b/app/src/main/res/layout/view_visible_message.xml @@ -1,5 +1,6 @@ - - @@ -104,7 +105,7 @@ - - - - - \ No newline at end of file + \ No newline at end of file From cc5c63b2114ed6acf5f73c7ec3adafb40b386921 Mon Sep 17 00:00:00 2001 From: Morgan Pretty Date: Fri, 13 Jan 2023 16:16:52 +1100 Subject: [PATCH 30/47] Fixed a couple of issues from the rebase, removed an unneeded text clear --- .../securesms/components/emoji/EmojiTextView.java | 3 +++ .../conversation/v2/messages/VisibleMessageContentView.kt | 5 ----- .../main/java/org/thoughtcrime/securesms/database/Storage.kt | 5 +++-- .../securesms/database/helpers/SQLCipherOpenHelper.java | 2 +- .../org/thoughtcrime/securesms/util/MockDataGenerator.kt | 2 +- .../java/org/session/libsession/database/StorageProtocol.kt | 3 ++- 6 files changed, 10 insertions(+), 10 deletions(-) diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/emoji/EmojiTextView.java b/app/src/main/java/org/thoughtcrime/securesms/components/emoji/EmojiTextView.java index 211df4f20..4f0072cc2 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/components/emoji/EmojiTextView.java +++ b/app/src/main/java/org/thoughtcrime/securesms/components/emoji/EmojiTextView.java @@ -51,6 +51,9 @@ public class EmojiTextView extends AppCompatTextView { @Override public void setText(@Nullable CharSequence text, BufferType type) { // No need to do anything special if the text is null or empty if (text == null || text.length() == 0) { + previousText = text; + previousOverflowText = overflowText; + previousBufferType = type; super.setText(text, type); return; } diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/messages/VisibleMessageContentView.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/messages/VisibleMessageContentView.kt index 6e6d562cd..d53ab4568 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/messages/VisibleMessageContentView.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/messages/VisibleMessageContentView.kt @@ -100,14 +100,9 @@ class VisibleMessageContentView : ConstraintLayout { } else { binding.deletedMessageView.root.isVisible = false } - // clear the - binding.bodyTextView.text = null - binding.quoteView.root.isVisible = message is MmsMessageRecord && message.quote != null - binding.linkPreviewView.root.isVisible = message is MmsMessageRecord && message.linkPreviews.isNotEmpty() - binding.untrustedView.root.isVisible = !contactIsTrusted && message is MmsMessageRecord && message.quote == null && message.linkPreviews.isEmpty() binding.voiceMessageView.root.isVisible = contactIsTrusted && message is MmsMessageRecord && message.slideDeck.audioSlide != null binding.documentView.root.isVisible = contactIsTrusted && message is MmsMessageRecord && message.slideDeck.documentSlide != null diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/Storage.kt b/app/src/main/java/org/thoughtcrime/securesms/database/Storage.kt index 131fa95e9..7daae9ef0 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/Storage.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/database/Storage.kt @@ -16,6 +16,7 @@ import org.session.libsession.messaging.messages.visible.Reaction import org.session.libsession.messaging.messages.visible.VisibleMessage import org.session.libsession.messaging.open_groups.GroupMember import org.session.libsession.messaging.open_groups.OpenGroup +import org.session.libsession.messaging.open_groups.OpenGroupApi import org.session.libsession.messaging.sending_receiving.attachments.AttachmentId import org.session.libsession.messaging.sending_receiving.attachments.DatabaseAttachment import org.session.libsession.messaging.sending_receiving.data_extraction.DataExtractionNotificationInfoMessage @@ -552,8 +553,8 @@ class Storage(context: Context, helper: SQLCipherOpenHelper) : Database(context, return DatabaseComponent.get(context).groupDatabase().allGroups } - override fun addOpenGroup(urlAsString: String) { - OpenGroupManager.addOpenGroup(urlAsString, context) + override fun addOpenGroup(urlAsString: String): OpenGroupApi.RoomInfo? { + return OpenGroupManager.addOpenGroup(urlAsString, context) } override fun onOpenGroupAdded(server: String) { 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 7ab6bfad9..4c52a1fa1 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 @@ -85,7 +85,7 @@ public class SQLCipherOpenHelper extends SQLiteOpenHelper { private static final int DATABASE_VERSION = lokiV39; private static final int MIN_DATABASE_VERSION = lokiV7; private static final String CIPHER3_DATABASE_NAME = "signal.db"; - public static final String DATABASE_NAME = "signal_v4.db"; + public static final String DATABASE_NAME = "signal_v4.db"; private final Context context; private final DatabaseSecret databaseSecret; diff --git a/app/src/main/java/org/thoughtcrime/securesms/util/MockDataGenerator.kt b/app/src/main/java/org/thoughtcrime/securesms/util/MockDataGenerator.kt index b1834f041..4d76e6aad 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/util/MockDataGenerator.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/util/MockDataGenerator.kt @@ -368,7 +368,7 @@ object MockDataGenerator { ) ) storage.setUserCount(roomName, serverName, numGroupMembers) - lokiThreadDB.setOpenGroupChat(OpenGroup(serverName, roomName, roomName, 0, randomGroupPublicKey), threadId) + lokiThreadDB.setOpenGroupChat(OpenGroup(server = serverName, room = roomName, publicKey = randomGroupPublicKey, name = roomName, imageId = null, infoUpdates = 0), threadId) // Generate the message history (Note: Unapproved message requests will only include incoming messages) logProgress("Open Group Thread $threadIndex", "Generate $numMessages Messages") diff --git a/libsession/src/main/java/org/session/libsession/database/StorageProtocol.kt b/libsession/src/main/java/org/session/libsession/database/StorageProtocol.kt index d86937a54..edca7cd15 100644 --- a/libsession/src/main/java/org/session/libsession/database/StorageProtocol.kt +++ b/libsession/src/main/java/org/session/libsession/database/StorageProtocol.kt @@ -16,6 +16,7 @@ import org.session.libsession.messaging.messages.visible.Reaction import org.session.libsession.messaging.messages.visible.VisibleMessage import org.session.libsession.messaging.open_groups.GroupMember import org.session.libsession.messaging.open_groups.OpenGroup +import org.session.libsession.messaging.open_groups.OpenGroupApi import org.session.libsession.messaging.sending_receiving.attachments.AttachmentId import org.session.libsession.messaging.sending_receiving.attachments.DatabaseAttachment import org.session.libsession.messaging.sending_receiving.data_extraction.DataExtractionNotificationInfoMessage @@ -66,7 +67,7 @@ interface StorageProtocol { fun getAllOpenGroups(): Map fun updateOpenGroup(openGroup: OpenGroup) fun getOpenGroup(threadId: Long): OpenGroup? - fun addOpenGroup(urlAsString: String) + fun addOpenGroup(urlAsString: String): OpenGroupApi.RoomInfo? fun onOpenGroupAdded(server: String) fun hasBackgroundGroupAddJob(groupJoinUrl: String): Boolean fun setOpenGroupServerMessageID(messageID: Long, serverID: Long, threadID: Long, isSms: Boolean) From f4fdfd7410b4a7909bc631af1729535da132aebf Mon Sep 17 00:00:00 2001 From: Morgan Pretty Date: Mon, 16 Jan 2023 14:48:38 +1100 Subject: [PATCH 31/47] Added the updated delivery status UI --- .../v2/messages/VisibleMessageView.kt | 28 +++++++--- .../ic_delivery_status_failed.png | Bin 0 -> 291 bytes .../drawable-hdpi/ic_delivery_status_read.png | Bin 478 -> 275 bytes .../ic_delivery_status_sending.png | Bin 299 -> 293 bytes .../drawable-hdpi/ic_delivery_status_sent.png | Bin 415 -> 288 bytes .../ic_delivery_status_failed.png | Bin 0 -> 232 bytes .../drawable-mdpi/ic_delivery_status_read.png | Bin 313 -> 221 bytes .../ic_delivery_status_sending.png | Bin 193 -> 218 bytes .../drawable-mdpi/ic_delivery_status_sent.png | Bin 261 -> 234 bytes .../ic_delivery_status_failed.png | Bin 0 -> 315 bytes .../ic_delivery_status_read.png | Bin 679 -> 330 bytes .../ic_delivery_status_sending.png | Bin 298 -> 339 bytes .../ic_delivery_status_sent.png | Bin 524 -> 359 bytes .../ic_delivery_status_failed.png | Bin 0 -> 426 bytes .../ic_delivery_status_read.png | Bin 1023 -> 445 bytes .../ic_delivery_status_sending.png | Bin 556 -> 473 bytes .../ic_delivery_status_sent.png | Bin 863 -> 473 bytes .../ic_delivery_status_failed.png | Bin 0 -> 507 bytes .../ic_delivery_status_read.png | Bin 1473 -> 539 bytes .../ic_delivery_status_sending.png | Bin 588 -> 596 bytes .../ic_delivery_status_sent.png | Bin 1158 -> 567 bytes .../main/res/layout/view_visible_message.xml | 50 +++++++++--------- app/src/main/res/values/attrs.xml | 1 + app/src/main/res/values/strings.xml | 4 ++ app/src/main/res/values/themes.xml | 4 ++ 25 files changed, 55 insertions(+), 32 deletions(-) create mode 100644 app/src/main/res/drawable-hdpi/ic_delivery_status_failed.png create mode 100644 app/src/main/res/drawable-mdpi/ic_delivery_status_failed.png create mode 100644 app/src/main/res/drawable-xhdpi/ic_delivery_status_failed.png create mode 100644 app/src/main/res/drawable-xxhdpi/ic_delivery_status_failed.png create mode 100644 app/src/main/res/drawable-xxxhdpi/ic_delivery_status_failed.png diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/messages/VisibleMessageView.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/messages/VisibleMessageView.kt index 0b4c1455f..cd4bd4b6e 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/messages/VisibleMessageView.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/messages/VisibleMessageView.kt @@ -190,7 +190,14 @@ class VisibleMessageView : LinearLayout { binding.dateBreakTextView.text = if (showDateBreak) DateUtils.getDisplayFormattedTimeSpanString(context, Locale.getDefault(), message.timestamp) else null binding.dateBreakTextView.isVisible = showDateBreak // Message status indicator - val (iconID, iconColor) = getMessageStatusImage(message) + val (iconID, iconColor, textId) = getMessageStatusImage(message) + if (textId != null) { + binding.messageStatusTextView.setText(textId) + + if (iconColor != null) { + binding.messageStatusTextView.setTextColor(iconColor) + } + } if (iconID != null) { val drawable = ContextCompat.getDrawable(context, iconID)?.mutate() if (iconColor != null) { @@ -200,9 +207,12 @@ class VisibleMessageView : LinearLayout { } if (message.isOutgoing) { val lastMessageID = mmsSmsDb.getLastMessageID(message.threadId) + binding.messageStatusTextView.isVisible = + !message.isSent || message.id == lastMessageID binding.messageStatusImageView.isVisible = !message.isSent || message.id == lastMessageID } else { + binding.messageStatusTextView.isVisible = false binding.messageStatusImageView.isVisible = false } // Expiration timer @@ -256,13 +266,17 @@ class VisibleMessageView : LinearLayout { } } - private fun getMessageStatusImage(message: MessageRecord): Pair { + private fun getMessageStatusImage(message: MessageRecord): Triple { return when { - !message.isOutgoing -> null to null - message.isFailed -> R.drawable.ic_error to resources.getColor(R.color.destructive, context.theme) - message.isPending -> R.drawable.ic_circle_dot_dot_dot to null - message.isRead -> R.drawable.ic_filled_circle_check to null - else -> R.drawable.ic_circle_check to null + !message.isOutgoing -> Triple(null, null, null) + message.isFailed -> + Triple(R.drawable.ic_delivery_status_failed, resources.getColor(R.color.destructive, context.theme), R.string.delivery_status_failed) + message.isPending -> + Triple(R.drawable.ic_delivery_status_sending, context.getColorFromAttr(R.attr.message_status_color), R.string.delivery_status_sending) + message.isRead -> + Triple(R.drawable.ic_delivery_status_read, context.getColorFromAttr(R.attr.message_status_color), R.string.delivery_status_read) + else -> + Triple(R.drawable.ic_delivery_status_sent, context.getColorFromAttr(R.attr.message_status_color), R.string.delivery_status_sent) } } diff --git a/app/src/main/res/drawable-hdpi/ic_delivery_status_failed.png b/app/src/main/res/drawable-hdpi/ic_delivery_status_failed.png new file mode 100644 index 0000000000000000000000000000000000000000..c5de641a606bc05b7d867a551d825c8de5232174 GIT binary patch literal 291 zcmeAS@N?(olHy`uVBq!ia0vp^{2``W z$lZxy-8q?;Kn_c~qpu?a!^VE@KZ&eB{tizU$B+ufs}uL~G8yu;%G>kCy6q6W(a>{% zV>{!PkPVDUO$XSvgcvYM7f82Enw0)wp~lHr7FqvaPfm2cT`uwB`jm)23f?w5C#vq7 zB68JHSlC{oRK#myMBGI7nG+-}7cTHx?sZ3CdGKDdKN&ML<0f!to`@|LDo^P?6taHf z*7sj>u1tKZRhDI%BNnywmO|j%ofAZ57vHgz`fy3^rJeQ??OEHMk2!wwx>iviF@MQT jN51AWt&Wkr|HA!q-4tSyJ}SHi`hmgI)z4*}Q$iB}tNLo7 literal 0 HcmV?d00001 diff --git a/app/src/main/res/drawable-hdpi/ic_delivery_status_read.png b/app/src/main/res/drawable-hdpi/ic_delivery_status_read.png index 307190cae65210f401537e4021cd82cd3acdd163..bc55b7dfee830842e30666b229f7f95a0c8ad7b2 100644 GIT binary patch literal 275 zcmeAS@N?(olHy`uVBq!ia0vp^d?3uh1|;P@bT0xa&H|6fVg?3*IUvlacGk)SD9BhG z}&%?1K3>q7-NCkk**4d9#@ zz`mAQ=L(}x5c>lLnI(J)#+%Qmxc{h3N#7^=Ag}+d>k}`dCtGYd5|o%LXYY^J-eqBzND}-{n!$FiJ$jA-~3dqFWoig z#-^LbnPx$m`OxIR5%fhmAgsQ?V%dQ>;dN{$}xVwsn}>T0)6y`V)lfyhjL7;pT_I2B*dZFrV@P;+b=MLlcsT( zE94sf(dU}7JHZ5VjNex)>Y^j`^Paa% zYc6PHxA1N@SmW`E;(4V<7YI_OICA6s# z%reAFi8TIL?=1CME8!*D`3EDmWWW0*RGlHOw88?D>Fz>PtC9xFZC4+}cMFUgF>zVl zuG*RBHrYo8+Cn)dmTP?@wk1O6jt?c=E--#Vy_)3)Y)Qg9PMLfasU7T diff --git a/app/src/main/res/drawable-hdpi/ic_delivery_status_sending.png b/app/src/main/res/drawable-hdpi/ic_delivery_status_sending.png index fbdcef3583e349b398dd3d0de9bd8c6f0f48ab6a..1b8991d98b45be67d567e2a3d6923fc9c395585c 100644 GIT binary patch literal 293 zcmeAS@N?(olHy`uVBq!ia0vp^{2``W z$lZxy-8q?;Kn_c~qpu?a!^VE@KZ&eB{w_}!$B+ufvlI979&+GmmAB`O^*WW~;O22$ zDu+QjAh?zH1l!7$5{(9oITLygY(DqX`e>)p(i4;I=9cbzzBx5vCD&9>tI7JBBAx41 zB#iA&%svvwzVhB|R=;1}Umy4hoz^+Vv297lLiKAewy90GJb8jO@0&~U%zeKd-EUP* z_10C`Uw>ku=u*$c|76l*bZp+8Uy~ST&HW~+DxTyp$>*V4#lg3G myR460^_a+UmaXW_O8YGvRV2%|J?#Vfg2B_(&t;ucLK6VJj&B11 literal 299 zcmV+`0o4A9P)Px#DFc3^c^BwU6rO)u#zjzRT(Wev-^(cNv!8jwkVVfdtgNFvj zOr|^QZkp7&Y{VEn5L5>52(u21(Y4MSC7dNe4|+0s3r^_ZvtD9VbKAr~y!TJ}_lN4x zTIDj|F~1p`>8X;DK1WEEfpAZ5zO+6LI~U-%v_yKu8@NX=paS+w6N!gj=J?YU`DQXdD3!UpttNyGw%N~9Ch1yeR)vkB7C9A#{nJ(QTV%E22vhj}1+m=@_T{pdWtSfj=m7>#S3j3^P6Px$SxH1eR5%f>ltD@ZK@de}6v3TG2tuwAT+9VLf>}sBh6l(Ag5n8WDuRe}3BiTn z0R&O9aU$rMf5a0oG=+stHGtZ*T_`a%T1} z6!KWFSDRMXa59ltw^t2?yeD%Dc%O0uP9ibn2Sq|{4u83mHiUQ=e;LK_Q`X+CdvMI% z`NF5x-9?Dm)>!+moPo2r3cj65+8tx+{nploO)-iQc7u(Lsi&3=d+_*!*ttG!PGf3k zKf4aTa()7Bet#3xhBt!>l*8o|0J>k`L&)djv*C{YbR{vZ7|?r;my?0)Rbg2OPG?w zAbo(#N5Cwh<&1;pbRjD*FS|+HrbSx4$>%0WiC z3!e`xHFwoKzcZb?+N+91`*~yMZT2Aff=9NQqRM;jE_k$1+{B0doRd#fkh-9m*(o{s YLNAd;UJ)}HfevBtboFyt=akR{07qIzyZ`_I literal 0 HcmV?d00001 diff --git a/app/src/main/res/drawable-mdpi/ic_delivery_status_read.png b/app/src/main/res/drawable-mdpi/ic_delivery_status_read.png index eee83ef591cb7bc8747070e9d74d8746e6cb44fa..072dac7b300219d6b232f9bcaf01e40e6addbbe1 100644 GIT binary patch literal 221 zcmeAS@N?(olHy`uVBq!ia0vp^AT}2V8<6ZZI=>f4aTa()7Bet#3xhBt!>l*8o|0J>k`9+>Cjv*C{Tl)if8w_|{q?;I26c{-@4sd!% za7}OsXlPMn6lrMoY{)K>KYgQa+M1Km*PeX6);VR3T*L<^_GvS&1abU4sU6IgFk@eE zUdW0)M|f@ClqNjedwFqMapOf{{(Xx|y*4cFev_f{QSo%6w9lkqd1EeFrI+zu+(6qI NJYD@<);T3K0RS7AM&keg literal 313 zcmV-90mlA`P)Px#^GQTOR45gFlf4SUFcgJTaCZ=gg3!K#4^?nDx1dlVt=DP_@OYQEp^@WD3rZEz=1GFPx)tM) zx5pK>MZ~JX$F7I4r2oLrA3Va>_=Ge37S;Mo(5!MrJ+2s5j#a%hyQxZ?VUK%7RJ7h~ z@AuycjM@_t$(i)em00000 LNkvXXu0mjf=;4Y~ diff --git a/app/src/main/res/drawable-mdpi/ic_delivery_status_sending.png b/app/src/main/res/drawable-mdpi/ic_delivery_status_sending.png index b34ea32b8b676f2ca13445fe985479af991f1453..f9b7fe3b79803d020f7d4b7438ea70f87c722f72 100644 GIT binary patch literal 218 zcmeAS@N?(olHy`uVBq!ia0vp^AT}2V8<6ZZI=>f4aTa()7Bet#3xhBt!>l*8o|0J>k`T3qMjv*C{OZy#p8w_|{y_*_5SBS_cFv=LP zv#?t*h=efn9pDuDp<+Ab(%c^>t-s`+z28x%RvEanl7V*zLo@H1x=favJ0r#81%qeu z^e>O%k9aM%p_y;ag9Qpg7JW6>$|tQ9-}~ohDSwo)|I%Mcd0*EE7({%IH3M4B;OXk; Jvd$@?2>=QNLY)8r literal 193 zcmeAS@N?(olHy`uVBq!ia0vp^JRr=$1|-8uW1a&k#^NA%Cx&(BWL^R})t)YnAsV7v zCpq#pDDbd!s^5R)C>;0HxT5v`T5HZLAvOmNs!88^J8crL=2e5up(mzTaj&Rc<}T;- zL@}l@gi-y?xz!JzwSVyQxv(JCCI67*?fIfoTP80wO)X*3_5WqLg>AmI_5JLN3ywb5 qjQ*>{`>1$3XWu;TXr-bT*>|=cD^RHs%!~#)gTd3)&t;ucLK6UcpGR5% diff --git a/app/src/main/res/drawable-mdpi/ic_delivery_status_sent.png b/app/src/main/res/drawable-mdpi/ic_delivery_status_sent.png index ff6bf0fac5c104028bd5e65c422c9a6cb5373e4f..26fceea79c193fcb29f8c7a21e5386418c4b5fac 100644 GIT binary patch literal 234 zcmeAS@N?(olHy`uVBq!ia0vp^AT}2V8<6ZZI=>f4aTa()7Bet#3xhBt!>l*8o|0J>k`SqSIjv*C{y%RR_HYfPx#zez+vR45gdlCcqjKoCVa$IJ@I7(1~5YS4tp)B;HC%E*W=um=jj_n18xFu|Mo zb9?{(-`ia%idf?~CeVNm3;|=gly%b%QGyqEgF4P~#&X`3S!`yBCpfG!agd8UHA%uJ z&GIAG41WXX&@>X(S#L9k_|9DqDm9?fV1w2xdCf-RSsZ3H!!ZkDZy`^_PUQhEDWwnl zvJlKA5&KFXs20`K%Usf`=`d}@PRhYdP5(DmpT)WV!{s_L?fNyo16n)HGv@hk00000 LNkvXXu0mjfm*i^Z diff --git a/app/src/main/res/drawable-xhdpi/ic_delivery_status_failed.png b/app/src/main/res/drawable-xhdpi/ic_delivery_status_failed.png new file mode 100644 index 0000000000000000000000000000000000000000..79aaa03f2be131a0b4d95b718f0e8d2c53526a81 GIT binary patch literal 315 zcmeAS@N?(olHy`uVBq!ia0vp^!XV7S1|*9D%+3HQ&H|6fVg?2=RS;(M3{v?36l5$8 za(7}_cTVOdki(Mh=oGT>yf)rzG2ETeIR~a5eDpJ?)nDXWKt$;K&=np@C#^&rCOhod&Z2w0OEjI=xnjr1 z8sUA_so$B63=%B2PFRz6sH%6x!-d`t_Pf{UWfn>76RElJ&*Opc8jq)KOGRX!xlCFT za?GV+wNBaGuFfllpEkSAJmmjpAK%0`Qcv}dFw4Jvt8qm7y{eJcnM#>GK)*3~y85}S Ib4q9e09V3xa{vGU literal 0 HcmV?d00001 diff --git a/app/src/main/res/drawable-xhdpi/ic_delivery_status_read.png b/app/src/main/res/drawable-xhdpi/ic_delivery_status_read.png index 79c4857f6c903cbc9fe5a353838516e7452eed7a..af79508abf145eefd645d8144207d8e172fca3d1 100644 GIT binary patch literal 330 zcmeAS@N?(olHy`uVBq!ia0vp^!XV7S1|*9D%+3HQ&H|6fVg?2=RS;(M3{v?36l5$8 za(7}_cTVOdki(Mh=7;{#hZF={|7S2;RxN3{!C1CH zHo(csKz0V-7G`e`&S?#@D(?Bae5MIejqrDVS0_Yt<>m|I>{s_fPJ=QLo1{(c!#k7ytd-G!@qWOT6Z-H`o&Oad*r8 zWjzOdSY`h>8!XSh9V`)_)XRDG#BM3mv@2>&dL9!px6foWJu&6M)w6Ri|1lBhtbf7E YS6{sOc5&fppjR0@UHx3vIVCg!0QYx(AOHXW literal 679 zcmV;Y0$BZtP)Px%VM#(i7i++-KnW8ek+bCPJan zLMnU$N-&Xv#6oE3pfg;rzi&7rdxx3XvnM%scIW%fIp5CA&dzOSDmIx+2Egn1OZ+Lm zjL+hi_zC{5X`0Wp!3um~7x)?e4gX5aZ(6^Rcp2ZuZ}5^G0W2vit%o1;D5rAk;kpgt z8h(Y>>=5vQveA0@xm>ETR#gOHh>vs8t-f=v7fpgqoq4EFMK+4kss%|0}yZ4!bstn0NsNR*atUnxNk zewrtYjO7@mpxqy+z-a+IN%~hTnU~)V_lObyRD!PCk#?;on8){9*h3TK<;PI381b*Q zhO)*y-vDgj`$qVaC0b{OFXaE4q1cG~Ep=86VrdFS3Ix|1v4=mldoRDw$dEo*n6lcU zZi@tDXM1S`p82;xCzB^x$_15q|&p zp#n0%p%FwZ*-wB+J@}MtMAf}&ed8mX?c(RVltCd$KC@zhjf`d}7Cq%B0PDV=$0 z$C5+vI8q)y^kB-V+;<1EmVopK(1OAgcKr={%U19u22mu(7wrBU^bds@7U$fAJk$UH N002ovPDHLkV1oGML)!oV diff --git a/app/src/main/res/drawable-xhdpi/ic_delivery_status_sending.png b/app/src/main/res/drawable-xhdpi/ic_delivery_status_sending.png index aca7fe7ef33f888bbf2f7e7fcbf22327de6d3f0e..74b8694dd4b155d7ccf7771669f007fc1228141e 100644 GIT binary patch literal 339 zcmeAS@N?(olHy`uVBq!ia0vp^!XV7S1|*9D%+3HQ&H|6fVg?2=RS;(M3{v?36l5$8 za(7}_cTVOdki(Mh=L*#EUlGr%XN&Fp;4 z^B*-CZ#%8yi+qg@H6^~AOt08rB7FW%M#ti0GpmgsGLP~fp1xIV&V$CJV?r+{WM$u9 zHsy{ouhhhI&qZPc-Hxrg)seSznZ&PSZkID09z}?XoIewHv1azwbEaK;r6gBRki7G+ h=;brn?DYTb=J5{BMw9NHegX6|gQu&X%Q~loCID5Gh8q9? literal 298 zcmV+_0oDGAP)Px#JyOLz0E*Nz81M! zWII(h`te3fMH%%S{Z9tCkh(t_3)CnTZ#ygt)GcpF07*qoM6N<$f?hIuDF6Tf diff --git a/app/src/main/res/drawable-xhdpi/ic_delivery_status_sent.png b/app/src/main/res/drawable-xhdpi/ic_delivery_status_sent.png index 811a54373df5e88873b4bfb6940fe575fdf45481..094d8b34cea107ab71ae47f31829f6566bf9eefa 100644 GIT binary patch literal 359 zcmV-t0hs=YP)8jrB1&Cigdx{YIZvop1IIiJA!arfO_r_TbF?$dy>;Xn$|zmjdzzASt2ygpgQlzE z=;nCtXbb}1-U{s+$ats20^i+@s9$N$42@E6&!VbRs_5&^2a)te^RUHm=RY`8m?&p; z{p9?Bkx0?J#S5w{$yktDAoXq5uxP(U>DrSSP5OKM4sR#mK^?n_Rc!zO002ovPDHLk FV1nE)lz0FD literal 524 zcmV+n0`vWeP)Px$#z{m$R7efAmc32_Q4ob)ZG8elXJTV~5<}>e@Ct;|p67tp%7S1a??Pc`d;sbE z34Y&oM=r`P5M+|W+?{*o%-#F96h}3-SS*@!x4{)S0}Y_PfG6+>9*UxP#&2&3H^2~l zfK4VxZWOOC1hEBPf%XROK^L5Z(-O9g*(XOcr&X&Cp$|TRHU>^ht;s35xRl#pn?R={ zS6tk#CDjDY$?{jXt=tNo%<#1V%=&agcZM}6EI;vySKMC1UiuKfceQvjR_CN+E$zbt@};c?iYTH z+}uH)rItZ#YJpj5vd@2!F7oHR4Zl)c9{rvZZRW!~H915f!t(&UgX@TeCsr#i4~_>? zo7WYP;6mN(BEGIb)fD*_S}KCyIPz>DD^pat$cW}eETj!WsLI1xS>#?yK_!B4C0EfHmtVDssUDxp{ z6gk`nCoAHc#B{mv|M$tvint~&eC&Oil_p2-@&gk>_#$inX_B<}-+aD=0Vv}yV$<%ys4slu`B z*sfK8H}j485UXJQv3^)5tO&8Lth=KED{_`3dsgEl&9MSnf}5lyw#KfyM5MBer5#703N=#f6LVOngtZm?I z@|yIm!Bj@CiFFLM%VJ&%Q$-%}iC%n6D>CO&dcaR1G8*e(+CUg4k0i4+60_d)9Fjb5 ztP6%YVceJgZewo(L2TlqBwmV==Pu+>;`Q%C3YnKD`$8DPb4Dq?Z5P%7A3n_C0Rs_* UvDa^jd;kCd07*qoM6N<$g8hcA?f?J) literal 0 HcmV?d00001 diff --git a/app/src/main/res/drawable-xxhdpi/ic_delivery_status_read.png b/app/src/main/res/drawable-xxhdpi/ic_delivery_status_read.png index 474a570724f6c44106b228996ff16b49dc45e53b..69376c9a20c3502389f23c5bdfaa7774c81bfa96 100644 GIT binary patch literal 445 zcmeAS@N?(olHy`uVBq!ia0vp^G9b*s1|*Ak?@s|zoCO|{#S9FJ79h;%I?XTvD9BhG znu$bx9-wQYaqWtAL1zqjLJmqfnD zf`dPJ?jP0K9vH9xD=K7)8~@&pxe+ruZ>1&OJF)cE^G=?P&3*bU#)~%Z-Ecwb^0nn3 z{bw|${f)9pIi#m>ZK1$y=_4mHBE`3>&HDYZcW$(**#QNSUvmU>&zxn|{@H27@%@?9 l{!~Zvv*u2P@*m|tGH3L%2fxVbJ`D_Q22WQ%mvv4FO#mCMwx$38 literal 1023 zcmVPx&xk*GpR9FesS;1-)K@fF~U<41lc#D$ENxX>Q!ONa=(17j-7@8=!3pJLEkli+)JE_ebS;G3()SkQS0owQ zR-BeJuC_VJ6J>$ehi7L#&naS79AkPHwkG*27DL>%ta4kx z0FZ-Y%`b7YyCp_aw{!t}Bcy0Ea0IW-o-{b@>RMRLsKL2y=)Qnu;a7Q zz#JR}Q)zDER|+W!UM4ZVdbG)(8i#kPKLYn|^0Sg6zyjCoVa-j*Zr-2;Jz+0Ex1d_ibBuz{92||AvQg+b-9F@uMG2O~ z5ghMA`9>=e9WMbjI5M2NQApgho$yNUYpQ{EIcto@_&k(Xm2z1S(aymk))T0EPWe0q zelxd4_F3pSB9aLTMMB8&-gAr>}5VI0b59t{mAQvDE~G3OSiA z+AeT_Q9zj^S5{e2>QRV^3r)%s%fcZhvIEi8okKxPl^92|sqBB!q{o}BeX^F7MxDRNevE?8AxG(E9l2J#(6EC(l}1Q$B{ zX~2`Sd>|zvAI|!CWyNvT8UKVo=6GT`pUobrSP_GT7vO z+`Rmxh>%4vce&Ip{3T*4FP=smgv|(R3GaR(0&RT&8$o$O;gKaqxGc+mQV2f!QgN7z tIhniq@DAj%}xLS002ovPDHLkV1lw2-7o+E diff --git a/app/src/main/res/drawable-xxhdpi/ic_delivery_status_sending.png b/app/src/main/res/drawable-xxhdpi/ic_delivery_status_sending.png index 4a81c629d8f6b146b79e7e85a2665dc80dc6c183..3dce4a05ea972d6d59ce32b35cfed0ce4b759655 100644 GIT binary patch literal 473 zcmV;~0Ve*5P)}+F#Z zF&P^*FW8BXHw2KB_G(U2(A{_d9NDxFOgc^G^${Yai%=oJW*phptX5o;#DiaCt}_@ksmu$4Z4`T!p#O P00000NkvXXu0mjfQvPx$=1D|BR9Fe^n7vNJFcgJrikP|}#8O_VBO8@?4A!u8=+JjyVC%@l3-AI=?66dV z6(OPU9qQ|}A%9KO$Vk1?t$)usw~p=D84NmFnx+YEmUN^W95o?8?F7GQ(zmFOu;M6c z%h%Ng1knW0GrlD6@&t8dk6DVtbJao0+aVa^pSanyymxU6z^Q>r#WgPOxvs4oA?Ms1 z-4>qUgLLTgyClL0D_eNZb*arS|7e8I@v}B~VTAP@qqZDz;P@An&vzeDmTJ;4!8Pe- z_}+(B8eU79TFIGFp?&5BH=Adz`^h1sd1)r?Y@ec_jqAy`zLU0|z)i!K1}-g`w0}3= zu+dSW{1rZAY2Gy@?$QsZZN-ooqZH2(*1{sFcnE5KPO4vxPBXyM@tc3Acaco}*jbtO$@uSi>vpqocNT>5QR~2Ef uOYkf5UzK7s)&?X3PxM+xR@M%_G4=;D`ptU>DcctS0000cR6OavXH(-OX0oMuW2CzZcAlC_6yOFUI5<3F^aL0G|E{7va zvSq~pFP@z@fo)->t(I04ji0Q(tiJIe$m(GAn^D6F-Nn0A8##4>E348<@pH$)yiJX6 zH^girCwug_af~WT?kEkaqF_caj}TT{3+7!W8OR}om8v-tG{Pa6%NkuhJqDrXS^SCA zn1>#5?&>#dl?6iW2zi`+Ghx5u@hTo%42)L%JhO7FInd{b{fVE;lmmtFy(e^aDF$Py zTI(@iiwR>I{B{Ro!}54XA4v0)dP$sBI~b2^{E=bW!xKVNt@5Pq0J= zgXtbp2&Y&ld?*NdnULVt9*wqc&lO P00000NkvXXu0mjfwiv}& literal 863 zcmV-l1EBngP)Px&8A(JzR9Fekn7?ilK@i4mLO~HEyP-e|IvPI#U79OMQqZ`h$Yap)25FK9Arcf6 z2w5(oJOW7RObS{gB0&)a^ZPC{WU_Z>?#@o+k-p6C?ELw1d%L&mey^p>=kp;UufPs? z2A&#Fz3+x|^k?v~-|zoGuU;TB1`}`znu>fCGhP`Zl5K-iu&gM@c4uM)Z-Ao;7FXC# zz#EW*5!f`SEt|&VBi~dGF>JIVM!*1kN;3VXe*m5|O(tK9QEU|D9yH1$NAM*^(|!lG z8YQT3QY<(IQB-3wDI7FS@yYz5|F)t=CzEmk<#bW4`srgZzB3AZ-Kp||Ams#+)M2GN zPFhV-R`z7lf)vq{ic(&EjK-yXoF-YePyUB72mSa?oyM^qgJa;L718+xzLnTITZ{qo=pA*Kc0#WE$T_PsYPk6Sz!8NN>N($Q@lXkb$q@rQ7N>KCQ?o z4>2R)awfxt_t2ZM7=4kM+4wKC4rP_XSZgv$M4V0vE*EVGDM-t=L<(CcGDhgJ5}W)K zp(a!2Pf2ki^=~22-ZLAj5cy^*)$yUIO+G3{(rk9ztf-e{sNMWB|QZda&-Uz002ovPDHLkV1lXgi#GrO diff --git a/app/src/main/res/drawable-xxxhdpi/ic_delivery_status_failed.png b/app/src/main/res/drawable-xxxhdpi/ic_delivery_status_failed.png new file mode 100644 index 0000000000000000000000000000000000000000..6780212b8be58b04cdd25e60553f32de56cf8bd8 GIT binary patch literal 507 zcmV@~0drDELIAGL9O(c600d`2O+f$vv5yPVK~#7F?U&JQ zgD?<=|F#S8*vA_oBY1>tP>+xe@CNAy=>{1Ab%KnLZje0otu+Fo-)=DuVAD!`(n-iZ z=gw!}9{~O!Pne&=+H?E-iG`_?*<1S}J6IDe$N_Fbot>}{nv%F!tE`=A%wPdHfo(e` za1$+%krM75VhqjgkqJRlg)Xpo^MyCBkO98jrpISTJ{1xp6zmMQO5*vRkf~Z6NYi+y zRa_MXcLPZ-z4n4Ie216{i6w$1Kn3O)K|+Zr(Juj^q-xB-+EEC?5-^lh5{ZbBfG(*^ z&-|D`~(vu zqC|o=ZZTtiTj0J$O2it~2G9|{qa-3m0{kGniJ_yexl1B8xW7aEoeSdV`*Z)nG1k#T z&{S2$FL5LySS`+8ke=JIKL(tx^!$Rfu&Q4l0*(fa9Ne7YCORiV&u<~e`qJ_d=CHb< xBvh#mjpq0Yr2BUJu=Z~473of6Wo6}m@(rh<;9~An;5PsO002ovPDHLkV1gPG)WiS) literal 0 HcmV?d00001 diff --git a/app/src/main/res/drawable-xxxhdpi/ic_delivery_status_read.png b/app/src/main/res/drawable-xxxhdpi/ic_delivery_status_read.png index c138886aa3cda1f0ebc80c338e42156c168c86e0..d0b16705c03b6da077d27d5123fce8e2404c91a8 100644 GIT binary patch literal 539 zcmV+$0_6RPP)@~0drDELIAGL9O(c600d`2O+f$vv5yPGB;{e-v zwiQ_ku(7eR@yi{;Nmz#O-&p?+naF$9y}lfxcNs2bSHtwGCXBdLVI^}|$h-t{`fB>=;3|XaEfWc23tYwPEPTV}pWsJ63@KuWi zwbT$3*;tE6zidn-V^TT^f|k1sFjQbg4CV_rtBxmP;6ccL3AA!4YllnV(g903i;j;o dJPzEO<{QK~)TFvn4j2Fc002ovPDHLkV1oGV;dTH3 literal 1473 zcmV;y1wQ(TP)Px)dr3q=RA>d&o6CzGMHGkU8boHmXf}$8h~R)h5L`$CBEb=&xEZpa#l=N%<3?~d zN?LrN3(3ZXe?=yskcA6f2zelgD58un5R!$X@qxzC;Q0MIU484G>35wT@xY-TUG<&E zboK47?wKq&$j;8rBADao3VIk_Mh~F3qSw(MBK?&52k3{B$>cZ6R%zjDzBpIWuh1{h z_tE#@U!(kQ1g8(6Q*;CU9qlWc^Jty-9P{AIx2aflcHH|hwuG*uzo0|q3KJixa*lov zzV%DK%IBpjn=XC8_n{}z%V>&x$>h7OQ}lW9n|zb+ieaC%pVS6;5WR$^%GUr6S!d|; z;&=5=u^hC13~B>9f~KA|a{PPr9_s{sUVPc;BZ}#W^`}=Gz#4k3kJXy7+w>o`#_99o z*O+bXR%~n5uTJg4`Bn=Z2Rv(yI`ccaQ9G<(lECP+_c(Lp(K{8%@#M=!swmdVizKkMatThw=4t47(|A;JkkR&Y~X45&CtL9B;mC zc!^>@TjZdc$wA#Cc*&1|=5EYrd-A>fw49T1cFHAI1fypeue0Y>&Yl;7u@U$I?OkfA zE#vzi0>?}2Ioe*y*@pF&3`F9|kFcGq7Z~|UhtbM`e4Pn@T|F44JXi*>3H1##*PCzfjZK|7PqJiw)hNoBDKga? zM7fa9r8mpO!DL#bYt@(N6X|yBg<=V)Mu+VeCAYIM2GC>)Sap4V@o6O z>+IV83v}#S74&6xU`<;&S=18KPT<_naU9@)`p0IDH{akZn>urz2+JyrP;#iB3hkCs zIXSKbX`9qjJ~1$FzQONmdL$=6SCk;_A$!Va2IkE-__?Mo$f;*e3_Pp^X@k9wyun~S ztJTStIpEDV__A4fLGHP%N?1`@)_uVi+Ep@5Wbo~1-45fO1w1bp=gqJ3HT8mA#Z=#x zg4JeL7s*WEbxFF#jPvGOU&l(0>}+d^4HAAKerBS6p)MJ~U}nr|;K#!}FUT=>Rwqln zm%waxzT9quaZ%|mWNuTQK5u@oGv=8SY~)L9GqgIES)3r%nHMMN!T31Gy!mo-FOnlW zn+oonYCR>3lb|~Dl_WX-SbU7OH-E2^gX=rz6xeCi21phsLUkDXrEx#)QTc`4Y=Hk= zrkqw#t6fIcjLG6;RMq!@KUQ)S@~RS)`moxVq%rxh$4pNCu$uqTF|>iENL!NJ`8Ydf za+HhNo)(R%dJ?5f5*&LsJxye5ddwd?IkGp^*^_S^(a`qdcj(*&mUdrxshDt-8@;E( zq0#5(FF{{Xr#uvOCbju5@(3csh zax;LpNqYH*aOS7_7v?M06NPV~uel_sHPlOBg>v;K8z7eRP@Nc9)26>HRy zKQuv#ZCG+bFaW$hvb+jw*BcH;KB!)j{t;)Cq(hkY_;vdR&K-dpO8UI`T{EOud_Dt? zg7Es#dlTpG58P1F=f!VI(l{TIhagm9eFg=@jm-i=a1DlTHz#_->3P`_Hx)AGas+{`Y b|2x#bQw83>|Io`_00000NkvXXu0mjfzW(y0 diff --git a/app/src/main/res/drawable-xxxhdpi/ic_delivery_status_sending.png b/app/src/main/res/drawable-xxxhdpi/ic_delivery_status_sending.png index 461c2ea6361a83ea088626809fdd9c142c45a154..411dc9d502715780984077036bf307170b884d78 100644 GIT binary patch literal 596 zcmV-a0;~OrP)@~0drDELIAGL9O(c600d`2O+f$vv5yP)p0 z`zSllTGFzYT1TzV=3F$--lOhP``L%)I2wVvwY>FvM}V$!2lTTxN9KS|0l~O@yfGgY zMvk+BIU~n2q;r7TxFlc7aw0Gu-pDNy;N2#W2g**B>Q})W)H&HO-@BNCJk0`m;n}KU z3h}{x4GydhNru~j0(>5MweGD^cc-%K%43&}odz|3+m4?4yeM?7STXw+=0v!1JWtwq z?1dGq@%&JxIA_Zd=dn31Eqi-yXF#&^lAYhh2E{pB*JX43@YqX_oe=E&3jxr!#O{jL zQM+6e=p46;wQVPM=w6t4KpkEL+~Xi8J6LaD2q$#&$e8tR$p0nA{|1rijX)x?Cj`u` zM$T;UEG-ZT@Ap0Ou!gzwh-L<}RfGHN0hTZ)Q-MUJ~9jwy9V!(e=s^OqLAU2=u>BLPY_N{)+hxiEdd52}XuE&Gj!~)>KFtxMOx& iVCi4N3;K)r|Kb}?2IlmRZ(y4M0000Px%21!IgRA>e5nn7;DFc3wXvZ&ZpVu@5v2OBPc#D+-a1RS$ccU9Nz`^8q>m;k{J ziK`iDh_NSsz9-2zwo^UyBwt7fT_NHBT;<1HKtthl?f`*Zpa=9R7inQR%$Ho45e^Qf z=nnlvCELg-s5qMZ{sv7^QCQAW*zz%rj8ZEA-8(1#@Ihgo0e23LutM3V;zs9#K11I_ zFO**+!;TfgXkg06tRu{I<^Zpqvp0?_wDU96^dt<2B}~b>X_M_g;1hI%K4$}UK*Ds` zlib*bcee;|36(09ZTfkL5_%#|j$3<*#h^wd$^?2~2frW1!Ex~!us#_Fj5NafL5kv! zY)%^Au@V+a8li~eBBDU0UJpW1TFahoBF7gh!~yjw*7G|=r_rMK5e?~nR9r?z169CD zV~#}J#IgN7j7AOZ8Xc1dsa*XP7so02)g>1v!?6fh1S|r_2vnq}yR07M3V4(6Xmb6n z9kdSI8aX@9B481)2>jCsFw&Jdp@DdFO1T=#HGwWXua& zyVhG9;$<%_w)V^#MiK#Tv9dQf=zvzNC0eUoqRA5?VK^+m{@KqK6HdzB aLHQf6CtTs`?61}U0000@~0drDELIAGL9O(c600d`2O+f$vv5yPLz0@#2N=mzNqv=f9*KsTT^#D|Nlkw34!*EjBr@v6NJUdIuIYn!(3U7R^86>La2hAMq4uSKVA@Ac*N zgLB#fmDd~2DWy(-=kT_+ZL9B$~?lHDlGc!qoo(v2}k z2848WB@RU_(x_KZ!q<^Q9u@inuXFE9NYj=BF?;S<_#e}#ik4+gS>(}Rd{R7G2106P zM9{-Gh#k0Y*!4Ap@bGFOX1^KfQ!%G@5WNUl8-%uSPq>j&^D~P=RCxkd$UmHA)+Ct0g zloi?}6}_J5=Q>UiWHcEzCwK~{c2bW?(kD8h970}X6L{{g6+?+bJZ%mI z>`?BQL3+U3q7Yt}_*x)=EunwD-y!30H|E$D{Spw`!X_}dyp+z{wtAqy_(Y(=q8~Mh z8f2cyrI5n85Umh3Rk}*QCBzrb@cY`f{o?YHy2q18;~z!8{^_v7xBvhE002ovPDHLk FV1fdF`da`1 literal 1158 zcmV;11bO?3P)Px(KuJVFRA>d&n!isJK@`XDq=JYEosm*QDC|_Ugu)hSw2$L!wj-Y$2%f-9F+t8iDi-=ztyY2b2}r$hHHN?8OMdrocM*1?m#{ zlFzBevcUNSSOecdQzGArF;O&;b`eD!-azj)xR;~EmD(97Vhz(zgk&rVJ{a$Vc!c zpI#JuwSwDLONQQCIDf_QdL^ehM)C>dJ0|6lz=NiZzw-a+(^W6DZgRM04#{_jp;%VL zrPy>raTfSF|ChcenUQNAP%NOBR>Vm+f^0!Sx5|&!ingxC<7}&DMu@2-{4XJH*|%5* zbr1Hk*$l>t!u!iI$$Qe(GpZH8$s?L``kZkx34A?Y zy0&r72cC*NNTv8 z6OqUHJK(3yCR3$5Ah74Qqy+R1Yen^ZBQhj;aq`^eHHG%v&$5`u9^Gf)Zk|qT44c^9 zMxnbg{a7Emi!v~>rv&y*hnjOSWA!oC4Y~&$^WM%E2xoP0#&Av@exk~g<8pn z1fo-@>>~D4Ny(&si6+R01Qr#{NN*AEnhrIeN;yx^$x1?3z4j}ga1l3PWmjup%l6QI znHc09(TgRa(x7m8kmo-(CT_sWmJ+C3N?;pH!Uj;g8zWPt4`1g>2_-O8F(-l`OX?&a zFXnV5h^}2?D;s655QecNY9k*wY6sBD&M8NFJ9dP2=@%MoY!Y>o*8?p2Leoov!5QEe zU+GuMkVLKIMG(LFuarS)q%Za25EIA`eyNusikEv^B_wfqH?~3oWP#7Ig0)?!Fv z78Acir;XMN+aFadP=0e)2}m$r07t;e<=9^Dr_f7c)v`Bj?N!#k - - - + + - - - - + app:layout_constraintTop_toBottomOf="@+id/emojiReactionsView" + tools:tint="@color/classic_dark_1" + android:src="@drawable/ic_delivery_status_sent" /> diff --git a/app/src/main/res/values/attrs.xml b/app/src/main/res/values/attrs.xml index 888b0af63..f0d04162b 100644 --- a/app/src/main/res/values/attrs.xml +++ b/app/src/main/res/values/attrs.xml @@ -164,6 +164,7 @@ + diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 8bde853c7..805ec6fb2 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -864,4 +864,8 @@ Join Navigate Back Close Dialog + Sending + Read + Sent + Failed to send diff --git a/app/src/main/res/values/themes.xml b/app/src/main/res/values/themes.xml index 4c7dfd1db..7dccb3a79 100644 --- a/app/src/main/res/values/themes.xml +++ b/app/src/main/res/values/themes.xml @@ -370,6 +370,7 @@ @color/classic_dark_6 ?colorAccent @color/classic_dark_0 + @color/classic_dark_5 @color/classic_dark_1 @color/classic_dark_5 @color/classic_dark_6 @@ -454,6 +455,7 @@ @color/classic_light_0 ?colorAccent @color/classic_light_0 + @color/classic_light_1 @color/classic_light_6 @color/classic_light_1 @color/classic_light_0 @@ -535,6 +537,7 @@ @color/ocean_dark_6 ?colorAccent @color/ocean_dark_0 + @color/ocean_dark_5 @color/ocean_dark_1 @color/ocean_dark_5 @color/ocean_dark_6 @@ -618,6 +621,7 @@ @color/ocean_light_0 ?colorAccent @color/ocean_light_0 + @color/ocean_light_2 @color/ocean_light_6 @color/ocean_light_1 @color/ocean_light_0 From a2fcb3195da37386629f240edef0ff0cc0d2d77c Mon Sep 17 00:00:00 2001 From: Morgan Pretty Date: Mon, 16 Jan 2023 16:46:24 +1100 Subject: [PATCH 32/47] General cleanup Fixed a bug where open groups were incorrectly displaying closed group avatar images Removed some commented out code --- .../components/ProfilePictureView.kt | 15 +++++++---- .../v2/utilities/ThumbnailView.kt | 27 ------------------- 2 files changed, 10 insertions(+), 32 deletions(-) diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/ProfilePictureView.kt b/app/src/main/java/org/thoughtcrime/securesms/components/ProfilePictureView.kt index 0ded9f346..a827a7d26 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/components/ProfilePictureView.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/components/ProfilePictureView.kt @@ -34,6 +34,8 @@ class ProfilePictureView @JvmOverloads constructor( private val profilePicturesCache = mutableMapOf() private val unknownRecipientDrawable = ResourceContactPhoto(R.drawable.ic_profile_default) .asDrawable(context, ContactColors.UNKNOWN_COLOR.toConversationColor(context), false) + private val unknownOpenGroupDrawable = ResourceContactPhoto(R.drawable.ic_notification) + .asDrawable(context, ContactColors.UNKNOWN_COLOR.toConversationColor(context), false) // endregion @@ -43,10 +45,8 @@ class ProfilePictureView @JvmOverloads constructor( val contact = DatabaseComponent.get(context).sessionContactDatabase().getContactWithSessionID(publicKey) return contact?.displayName(Contact.ContactContext.REGULAR) ?: publicKey } - fun isOpenGroupWithProfilePicture(recipient: Recipient): Boolean { - return recipient.isOpenGroupRecipient && recipient.groupAvatarId != null - } - if (recipient.isGroupRecipient && !isOpenGroupWithProfilePicture(recipient)) { + + if (recipient.isClosedGroupRecipient) { val members = DatabaseComponent.get(context).groupDatabase() .getGroupMemberAddresses(recipient.address.toGroupString(), true) .sorted() @@ -107,7 +107,7 @@ class ProfilePictureView @JvmOverloads constructor( if (profilePicturesCache.containsKey(publicKey) && profilePicturesCache[publicKey] == recipient.profileAvatar) return val signalProfilePicture = recipient.contactPhoto val avatar = (signalProfilePicture as? ProfileContactPhoto)?.avatarObject - val placeholder = PlaceholderAvatarPhoto(context, publicKey, displayName ?: "${publicKey.take(4)}...${publicKey.takeLast(4)}") + if (signalProfilePicture != null && avatar != "0" && avatar != "") { glide.clear(imageView) glide.load(signalProfilePicture) @@ -117,7 +117,12 @@ class ProfilePictureView @JvmOverloads constructor( .diskCacheStrategy(DiskCacheStrategy.NONE) .circleCrop() .into(imageView) + } else if (recipient.isOpenGroupRecipient && recipient.groupAvatarId == null) { + glide.clear(imageView) + imageView.setImageDrawable(unknownOpenGroupDrawable) } else { + val placeholder = PlaceholderAvatarPhoto(context, publicKey, displayName ?: "${publicKey.take(4)}...${publicKey.takeLast(4)}") + glide.clear(imageView) glide.load(placeholder) .placeholder(unknownRecipientDrawable) diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/utilities/ThumbnailView.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/utilities/ThumbnailView.kt index 275947a81..e15855667 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/utilities/ThumbnailView.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/utilities/ThumbnailView.kt @@ -194,31 +194,4 @@ open class ThumbnailView: FrameLayout { return future } - -// fun showDownloadText(showDownloadText: Boolean) { -// getTransferControls()?.setShowDownloadText(showDownloadText); -// } -// -// fun setDownloadClickListener(listener: SlidesClickedListener) { -// this.downloadClickListener = listener; -// } -// -// private fun getTransferControls(): TransferControlView? { -// if (transferControls == null) { -// transferControls = ViewUtil.inflateStub(this, R.id.transfer_controls_stub); -// } -// -// return transferControls -// } - // endregion - -// private class DownloadClickDispatcher : OnClickListener { -// override fun onClick(view: View?) { -// if (downloadClickListener != null && slide != null) { -// downloadClickListener.onClick(view, Collections.singletonList(slide)) -// } else { -// Log.w(TAG, "Received a download button click, but unable to execute it. slide: " + String.valueOf(slide) + " downloadClickListener: " + String.valueOf(downloadClickListener)) -// } -// } -// } } \ No newline at end of file From cae15a200df5d39bed8b57668d5fcbc47d7e741f Mon Sep 17 00:00:00 2001 From: Morgan Pretty Date: Thu, 19 Jan 2023 15:24:09 +1100 Subject: [PATCH 33/47] Added temporary support for downgrading and notify the user upon failure --- .../database/helpers/SQLCipherOpenHelper.java | 151 +++++++++++------- app/src/main/res/values/strings.xml | 2 + 2 files changed, 98 insertions(+), 55 deletions(-) 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 4c52a1fa1..a6ac90698 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 @@ -1,15 +1,17 @@ package org.thoughtcrime.securesms.database.helpers; +import android.app.NotificationChannel; +import android.app.NotificationManager; import android.content.Context; import android.database.Cursor; import androidx.annotation.NonNull; +import androidx.core.app.NotificationCompat; -import net.zetetic.database.DatabaseErrorHandler; -import net.zetetic.database.DatabaseUtils; import net.zetetic.database.sqlcipher.SQLiteConnection; import net.zetetic.database.sqlcipher.SQLiteDatabase; import net.zetetic.database.sqlcipher.SQLiteDatabaseHook; +import net.zetetic.database.sqlcipher.SQLiteException; import net.zetetic.database.sqlcipher.SQLiteOpenHelper; import org.session.libsession.utilities.TextSecurePreferences; @@ -37,9 +39,12 @@ import org.thoughtcrime.securesms.database.SessionContactDatabase; import org.thoughtcrime.securesms.database.SessionJobDatabase; import org.thoughtcrime.securesms.database.SmsDatabase; import org.thoughtcrime.securesms.database.ThreadDatabase; +import org.thoughtcrime.securesms.notifications.NotificationChannels; import java.io.File; +import network.loki.messenger.R; + public class SQLCipherOpenHelper extends SQLiteOpenHelper { @SuppressWarnings("unused") @@ -85,7 +90,7 @@ public class SQLCipherOpenHelper extends SQLiteOpenHelper { private static final int DATABASE_VERSION = lokiV39; private static final int MIN_DATABASE_VERSION = lokiV7; private static final String CIPHER3_DATABASE_NAME = "signal.db"; - public static final String DATABASE_NAME = "signal_v4.db"; + public static final String DATABASE_NAME = "signal_v4.db"; private final Context context; private final DatabaseSecret databaseSecret; @@ -94,14 +99,13 @@ public class SQLCipherOpenHelper extends SQLiteOpenHelper { super(context, DATABASE_NAME, databaseSecret.asString(), null, DATABASE_VERSION, MIN_DATABASE_VERSION, null, new SQLiteDatabaseHook() { @Override public void preKey(SQLiteConnection connection) { - connection.execute("PRAGMA cipher_default_kdf_iter = 256000;", null, null); - connection.execute("PRAGMA cipher_default_page_size = 4096;", null, null); + SQLCipherOpenHelper.applySQLCipherPragmas(connection, true); } @Override public void postKey(SQLiteConnection connection) { - connection.execute("PRAGMA kdf_iter = '256000';", null, null); - connection.execute("PRAGMA cipher_page_size = 4096;", null, null); + SQLCipherOpenHelper.applySQLCipherPragmas(connection, true); + // if not vacuumed in a while, perform that operation long currentTime = System.currentTimeMillis(); // 7 days @@ -116,48 +120,69 @@ public class SQLCipherOpenHelper extends SQLiteOpenHelper { this.databaseSecret = databaseSecret; } - public static void migrateSqlCipher3To4IfNeeded(@NonNull Context context, @NonNull DatabaseSecret databaseSecret) { + private static void applySQLCipherPragmas(SQLiteConnection connection, boolean useSQLCipher4) { + if (useSQLCipher4) { + connection.execute("PRAGMA kdf_iter = '256000';", null, null); + } + else { + connection.execute("PRAGMA cipher_compatibility = 3;", null, null); + connection.execute("PRAGMA kdf_iter = '1';", null, null); + } + + connection.execute("PRAGMA cipher_page_size = 4096;", null, null); + } + + private static SQLiteDatabase open(String path, DatabaseSecret databaseSecret, boolean useSQLCipher4) throws SQLiteException { + return SQLiteDatabase.openDatabase(path, databaseSecret.asString(), null, SQLiteDatabase.OPEN_READWRITE, new SQLiteDatabaseHook() { + @Override + public void preKey(SQLiteConnection connection) { SQLCipherOpenHelper.applySQLCipherPragmas(connection, useSQLCipher4); } + + @Override + public void postKey(SQLiteConnection connection) { SQLCipherOpenHelper.applySQLCipherPragmas(connection, useSQLCipher4); } + }); + } + + public static void migrateSqlCipher3To4IfNeeded(@NonNull Context context, @NonNull DatabaseSecret databaseSecret) throws Exception { String oldDbPath = context.getDatabasePath(CIPHER3_DATABASE_NAME).getPath(); File oldDbFile = new File(oldDbPath); - // If the old SQLCipher3 database file doesn't exist then just return early + // If the old SQLCipher3 database file doesn't exist then no need to do anything if (!oldDbFile.exists()) { return; } - // If the new database file already exists then we probably had a failed migration and it's likely in - // an invalid state so should delete it - String newDbPath = context.getDatabasePath(DATABASE_NAME).getPath(); - File newDbFile = new File(newDbPath); - - if (newDbFile.exists()) { newDbFile.delete(); } - try { - newDbFile.createNewFile(); - } - catch (Exception e) { - // TODO: Communicate the error somehow??? - return; - } + // Define the location for the new database + String newDbPath = context.getDatabasePath(DATABASE_NAME).getPath(); + File newDbFile = new File(newDbPath); - try { - // Open the old database - SQLiteDatabase oldDb = SQLiteDatabase.openOrCreateDatabase(oldDbPath, databaseSecret.asString(), null, null, new SQLiteDatabaseHook() { - @Override - public void preKey(SQLiteConnection connection) { - connection.execute("PRAGMA cipher_compatibility = 3;", null, null); - connection.execute("PRAGMA kdf_iter = '1';", null, null); - connection.execute("PRAGMA cipher_page_size = 4096;", null, null); + // If the new database file already exists then check if it's valid first, if it's in an + // invalid state we should delete it and try to migrate again + if (newDbFile.exists()) { + // If the old database hasn't been modified since the new database was created, then we can + // assume the user hasn't downgraded for some reason and made changes to the old database and + // can remove the old database file (it won't be used anymore) + if (oldDbFile.lastModified() <= newDbFile.lastModified()) { + // TODO: Delete 'CIPHER3_DATABASE_NAME' once enough time has past +// //noinspection ResultOfMethodCallIgnored +// oldDbFile.delete(); + return; } - @Override - public void postKey(SQLiteConnection connection) { - connection.execute("PRAGMA cipher_compatibility = 3;", null, null); - connection.execute("PRAGMA kdf_iter = '1';", null, null); - connection.execute("PRAGMA cipher_page_size = 4096;", null, null); + // If the old database does have newer changes then the new database could have stale/invalid + // data and we should re-migrate to avoid losing any data or issues + if (!newDbFile.delete()) { + throw new Exception("Failed to remove invalid new database"); } - }); + } + + if (!newDbFile.createNewFile()) { + throw new Exception("Failed to create new database"); + } + + // Open the old database and extract it's version + SQLiteDatabase oldDb = SQLCipherOpenHelper.open(oldDbPath, databaseSecret, false); + int oldDbVersion = oldDb.getVersion(); // Export the old database to the new one (will have the default 'kdf_iter' and 'page_size' settings) - int oldDbVersion = oldDb.getVersion(); oldDb.rawExecSQL( String.format("ATTACH DATABASE '%s' AS sqlcipher4 KEY '%s'", newDbPath, databaseSecret.asString()) ); @@ -167,30 +192,46 @@ public class SQLCipherOpenHelper extends SQLiteOpenHelper { oldDb.rawExecSQL("DETACH DATABASE sqlcipher4"); oldDb.close(); - // TODO: Performance testing - - SQLiteDatabase newDb = SQLiteDatabase.openDatabase(newDbPath, databaseSecret.asString(), null, SQLiteDatabase.OPEN_READWRITE, new SQLiteDatabaseHook() { - @Override - public void preKey(SQLiteConnection connection) { - connection.execute("PRAGMA cipher_default_kdf_iter = 256000;", null, null); - connection.execute("PRAGMA cipher_default_page_size = 4096;", null, null); - } - - @Override - public void postKey(SQLiteConnection connection) { - connection.execute("PRAGMA cipher_default_kdf_iter = 256000;", null, null); - connection.execute("PRAGMA cipher_default_page_size = 4096;", null, null); - } - }); + // Open the newly migrated database (to ensure it works) and set it's version so we don't try + // to run any of our custom migrations + SQLiteDatabase newDb = SQLCipherOpenHelper.open(newDbPath, databaseSecret, true); newDb.setVersion(oldDbVersion); newDb.close(); - // TODO: Delete 'CIPHER3_DATABASE_NAME' - // TODO: What do we do if the deletion fails??? (The current logic will end up re-migrating...) + // TODO: Delete 'CIPHER3_DATABASE_NAME' once enough time has past + // Remove the old database file since it will no longer be used +// //noinspection ResultOfMethodCallIgnored // oldDbFile.delete(); } catch (Exception e) { - // TODO: Communicate the error somehow??? + Log.e(TAG, "Migration from SQLCipher3 to SQLCipher4 failed", e); + + // Notify the user of the issue so they know they can downgrade until the issue is fixed + NotificationManager notificationManager = context.getSystemService(NotificationManager.class); + String channelId = context.getString(R.string.NotificationChannel_failures); + + if (NotificationChannels.supported()) { + NotificationChannel channel = new NotificationChannel(channelId, channelId, NotificationManager.IMPORTANCE_HIGH); + channel.enableVibration(true); + notificationManager.createNotificationChannel(channel); + } + + NotificationCompat.Builder builder = new NotificationCompat.Builder(context, channelId) + .setSmallIcon(R.drawable.ic_notification) + .setColor(context.getResources().getColor(R.color.textsecure_primary)) + .setCategory(NotificationCompat.CATEGORY_ERROR) + .setContentTitle(context.getString(R.string.ErrorNotifier_migration)) + .setContentText(context.getString(R.string.ErrorNotifier_migration_downgrade)) + .setAutoCancel(true); + + if (!NotificationChannels.supported()) { + builder.setPriority(NotificationCompat.PRIORITY_HIGH); + } + + notificationManager.notify(5874, builder.build()); + + // Throw the error (app will crash but there is nothing else we can do unfortunately) + throw e; } } diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 8bde853c7..81e6ae325 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -864,4 +864,6 @@ Join Navigate Back Close Dialog + Database Upgrade Failed + Please contact support to report the error and then install an older version to continue using Session. From 694ca79958634038c177f6abb30aae8f2a29c1e5 Mon Sep 17 00:00:00 2001 From: Morgan Pretty Date: Tue, 17 Jan 2023 16:30:05 +1100 Subject: [PATCH 34/47] Added the unread mention indicator to the conversation list Fixed the unread indicator colours to match correct theming designs Fixed a bug where the unread count could be incorrect when receiving UnsendRequests within the same poll Added a couple missing theme colours --- .../attachments/DatabaseAttachmentProvider.kt | 12 ++- .../securesms/database/MessagingDatabase.java | 2 +- .../securesms/database/MmsDatabase.kt | 19 ++-- .../securesms/database/MmsSmsColumns.java | 2 + .../securesms/database/MmsSmsDatabase.java | 14 ++- .../securesms/database/SmsDatabase.java | 18 +++- .../securesms/database/Storage.kt | 8 +- .../securesms/database/ThreadDatabase.java | 36 +++++-- .../database/helpers/SQLCipherOpenHelper.java | 12 ++- .../database/model/MediaMmsMessageRecord.java | 4 +- .../database/model/MessageRecord.java | 8 +- .../database/model/MmsMessageRecord.java | 4 +- .../model/NotificationMmsMessageRecord.java | 4 +- .../database/model/SmsMessageRecord.java | 4 +- .../database/model/ThreadRecord.java | 28 +++-- .../securesms/home/ConversationView.kt | 17 +-- .../securesms/home/HomeAdapter.kt | 19 ++-- .../service/ExpiringMessageManager.java | 1 + .../securesms/util/MockDataGenerator.kt | 9 +- app/src/main/res/layout/view_conversation.xml | 36 ++++++- app/src/main/res/values/colors.xml | 23 ++-- app/src/main/res/values/themes.xml | 100 +++++++++--------- .../database/MessageDataProvider.kt | 4 +- .../libsession/database/StorageProtocol.kt | 2 +- .../messaging/jobs/BatchMessageReceiveJob.kt | 60 +++++++---- .../messages/signal/IncomingMediaMessage.java | 10 +- .../messages/signal/IncomingTextMessage.java | 23 ++-- .../messages/signal/OutgoingMediaMessage.java | 4 +- .../messages/visible/VisibleMessage.kt | 1 + .../ReceivedMessageHandler.kt | 30 ++++-- 30 files changed, 335 insertions(+), 179 deletions(-) diff --git a/app/src/main/java/org/thoughtcrime/securesms/attachments/DatabaseAttachmentProvider.kt b/app/src/main/java/org/thoughtcrime/securesms/attachments/DatabaseAttachmentProvider.kt index fa0fce7bd..ef4503b0c 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/attachments/DatabaseAttachmentProvider.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/attachments/DatabaseAttachmentProvider.kt @@ -74,10 +74,10 @@ class DatabaseAttachmentProvider(context: Context, helper: SQLCipherOpenHelper) attachmentDatabase.setTransferState(messageID, attachmentId, attachmentState.value) } - override fun getMessageForQuote(timestamp: Long, author: Address): Pair? { + override fun getMessageForQuote(timestamp: Long, author: Address): Triple? { val messagingDatabase = DatabaseComponent.get(context).mmsSmsDatabase() val message = messagingDatabase.getMessageFor(timestamp, author) - return if (message != null) Pair(message.id, message.isMms) else null + return if (message != null) Triple(message.id, message.isMms, message.body) else null } override fun getAttachmentsAndLinkPreviewFor(mmsId: Long): List { @@ -184,16 +184,18 @@ class DatabaseAttachmentProvider(context: Context, helper: SQLCipherOpenHelper) DatabaseComponent.get(context).lokiMessageDatabase().deleteMessageServerHash(messageID) } - override fun updateMessageAsDeleted(timestamp: Long, author: String) { + override fun updateMessageAsDeleted(timestamp: Long, author: String): Long? { val database = DatabaseComponent.get(context).mmsSmsDatabase() val address = Address.fromSerialized(author) - val message = database.getMessageFor(timestamp, address) ?: return + val message = database.getMessageFor(timestamp, address) ?: return null val messagingDatabase: MessagingDatabase = if (message.isMms) DatabaseComponent.get(context).mmsDatabase() else DatabaseComponent.get(context).smsDatabase() - messagingDatabase.markAsDeleted(message.id, message.isRead) + messagingDatabase.markAsDeleted(message.id, message.isRead, message.hasMention) if (message.isOutgoing) { messagingDatabase.deleteMessage(message.id) } + + return message.id } override fun getServerHashForMessage(messageID: Long): String? { diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/MessagingDatabase.java b/app/src/main/java/org/thoughtcrime/securesms/database/MessagingDatabase.java index bc0594df0..04db75a31 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/MessagingDatabase.java +++ b/app/src/main/java/org/thoughtcrime/securesms/database/MessagingDatabase.java @@ -39,7 +39,7 @@ public abstract class MessagingDatabase extends Database implements MmsSmsColumn public abstract void markAsSent(long messageId, boolean secure); public abstract void markUnidentified(long messageId, boolean unidentified); - public abstract void markAsDeleted(long messageId, boolean read); + public abstract void markAsDeleted(long messageId, boolean read, boolean hasMention); public abstract boolean deleteMessage(long messageId); diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/MmsDatabase.kt b/app/src/main/java/org/thoughtcrime/securesms/database/MmsDatabase.kt index d82c6bb27..30b84d790 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/MmsDatabase.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/database/MmsDatabase.kt @@ -356,17 +356,19 @@ class MmsDatabase(context: Context, databaseHelper: SQLCipherOpenHelper) : Messa db.update(TABLE_NAME, contentValues, ID_WHERE, arrayOf(messageId.toString())) } - override fun markAsDeleted(messageId: Long, read: Boolean) { + override fun markAsDeleted(messageId: Long, read: Boolean, hasMention: Boolean) { val database = databaseHelper.writableDatabase val contentValues = ContentValues() contentValues.put(READ, 1) contentValues.put(BODY, "") + contentValues.put(HAS_MENTION, 0) database.update(TABLE_NAME, contentValues, ID_WHERE, arrayOf(messageId.toString())) val attachmentDatabase = get(context).attachmentDatabase() queue(Runnable { attachmentDatabase.deleteAttachmentsForMessage(messageId) }) val threadId = getThreadIdForMessage(messageId) if (!read) { - get(context).threadDatabase().decrementUnread(threadId, 1) + val mentionChange = if (hasMention) { 1 } else { 0 } + get(context).threadDatabase().decrementUnread(threadId, 1, mentionChange) } updateMailboxBitmask( messageId, @@ -659,6 +661,7 @@ class MmsDatabase(context: Context, databaseHelper: SQLCipherOpenHelper) : Messa contentValues.put(EXPIRES_IN, retrieved.expiresIn) contentValues.put(READ, if (retrieved.isExpirationUpdate) 1 else 0) contentValues.put(UNIDENTIFIED, retrieved.isUnidentified) + contentValues.put(HAS_MENTION, retrieved.hasMention()) contentValues.put(MESSAGE_REQUEST_RESPONSE, retrieved.isMessageRequestResponse) if (!contentValues.containsKey(DATE_SENT)) { contentValues.put(DATE_SENT, contentValues.getAsLong(DATE_RECEIVED)) @@ -690,7 +693,8 @@ class MmsDatabase(context: Context, databaseHelper: SQLCipherOpenHelper) : Messa ) if (!MmsSmsColumns.Types.isExpirationTimerUpdate(mailbox)) { if (runIncrement) { - get(context).threadDatabase().incrementUnread(threadId, 1) + val mentionAmount = if (retrieved.hasMention()) { 1 } else { 0 } + get(context).threadDatabase().incrementUnread(threadId, 1, mentionAmount) } if (runThreadUpdate) { get(context).threadDatabase().update(threadId, true) @@ -1272,7 +1276,7 @@ class MmsDatabase(context: Context, databaseHelper: SQLCipherOpenHelper) : Messa message.outgoingQuote!!.missing, SlideDeck(context, message.outgoingQuote!!.attachments!!) ) else null, - message.sharedContacts, message.linkPreviews, listOf(), false + message.sharedContacts, message.linkPreviews, listOf(), false, false ) } @@ -1316,6 +1320,7 @@ class MmsDatabase(context: Context, databaseHelper: SQLCipherOpenHelper) : Messa ) var readReceiptCount = cursor.getInt(cursor.getColumnIndexOrThrow(READ_RECEIPT_COUNT)) val subscriptionId = cursor.getInt(cursor.getColumnIndexOrThrow(SUBSCRIPTION_ID)) + val hasMention = (cursor.getInt(cursor.getColumnIndexOrThrow(HAS_MENTION)) == 1) if (!isReadReceiptsEnabled(context)) { readReceiptCount = 0 } @@ -1333,7 +1338,7 @@ class MmsDatabase(context: Context, databaseHelper: SQLCipherOpenHelper) : Messa dateSent, dateReceived, deliveryReceiptCount, threadId, contentLocationBytes, messageSize, expiry, status, transactionIdBytes, mailbox, slideDeck, - readReceiptCount + readReceiptCount, hasMention ) } @@ -1367,6 +1372,7 @@ class MmsDatabase(context: Context, databaseHelper: SQLCipherOpenHelper) : Messa val expiresIn = cursor.getLong(cursor.getColumnIndexOrThrow(EXPIRES_IN)) val expireStarted = cursor.getLong(cursor.getColumnIndexOrThrow(EXPIRE_STARTED)) val unidentified = cursor.getInt(cursor.getColumnIndexOrThrow(UNIDENTIFIED)) == 1 + val hasMention = cursor.getInt(cursor.getColumnIndexOrThrow(HAS_MENTION)) == 1 if (!isReadReceiptsEnabled(context)) { readReceiptCount = 0 } @@ -1403,7 +1409,7 @@ class MmsDatabase(context: Context, databaseHelper: SQLCipherOpenHelper) : Messa addressDeviceId, dateSent, dateReceived, deliveryReceiptCount, threadId, body, slideDeck!!, partCount, box, mismatches, networkFailures, subscriptionId, expiresIn, expireStarted, - readReceiptCount, quote, contacts, previews, reactions, unidentified + readReceiptCount, quote, contacts, previews, reactions, unidentified, hasMention ) } @@ -1596,5 +1602,6 @@ class MmsDatabase(context: Context, databaseHelper: SQLCipherOpenHelper) : Messa const val CREATE_MESSAGE_REQUEST_RESPONSE_COMMAND = "ALTER TABLE $TABLE_NAME ADD COLUMN $MESSAGE_REQUEST_RESPONSE INTEGER DEFAULT 0;" const val CREATE_REACTIONS_UNREAD_COMMAND = "ALTER TABLE $TABLE_NAME ADD COLUMN $REACTIONS_UNREAD INTEGER DEFAULT 0;" const val CREATE_REACTIONS_LAST_SEEN_COMMAND = "ALTER TABLE $TABLE_NAME ADD COLUMN $REACTIONS_LAST_SEEN INTEGER DEFAULT 0;" + const val CREATE_HAS_MENTION_COMMAND = "ALTER TABLE $TABLE_NAME ADD COLUMN $HAS_MENTION INTEGER DEFAULT 0;" } } \ No newline at end of file diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/MmsSmsColumns.java b/app/src/main/java/org/thoughtcrime/securesms/database/MmsSmsColumns.java index c4fe3d243..f3110a5c7 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/MmsSmsColumns.java +++ b/app/src/main/java/org/thoughtcrime/securesms/database/MmsSmsColumns.java @@ -24,6 +24,8 @@ public interface MmsSmsColumns { public static final String REACTIONS_UNREAD = "reactions_unread"; public static final String REACTIONS_LAST_SEEN = "reactions_last_seen"; + public static final String HAS_MENTION = "has_mention"; + public static class Types { protected static final long TOTAL_MASK = 0xFFFFFFFF; diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/MmsSmsDatabase.java b/app/src/main/java/org/thoughtcrime/securesms/database/MmsSmsDatabase.java index 73534aeb2..447993643 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/MmsSmsDatabase.java +++ b/app/src/main/java/org/thoughtcrime/securesms/database/MmsSmsDatabase.java @@ -75,7 +75,9 @@ public class MmsSmsDatabase extends Database { MmsDatabase.QUOTE_ATTACHMENT, MmsDatabase.SHARED_CONTACTS, MmsDatabase.LINK_PREVIEWS, - ReactionDatabase.REACTION_JSON_ALIAS}; + ReactionDatabase.REACTION_JSON_ALIAS, + MmsSmsColumns.HAS_MENTION + }; public MmsSmsDatabase(Context context, SQLCipherOpenHelper databaseHelper) { super(context, databaseHelper); @@ -337,7 +339,9 @@ public class MmsSmsDatabase extends Database { MmsDatabase.QUOTE_MISSING, MmsDatabase.QUOTE_ATTACHMENT, MmsDatabase.SHARED_CONTACTS, - MmsDatabase.LINK_PREVIEWS}; + MmsDatabase.LINK_PREVIEWS, + MmsSmsColumns.HAS_MENTION + }; String[] smsProjection = {SmsDatabase.DATE_SENT + " AS " + MmsSmsColumns.NORMALIZED_DATE_SENT, SmsDatabase.DATE_RECEIVED + " AS " + MmsSmsColumns.NORMALIZED_DATE_RECEIVED, @@ -364,7 +368,9 @@ public class MmsSmsDatabase extends Database { MmsDatabase.QUOTE_MISSING, MmsDatabase.QUOTE_ATTACHMENT, MmsDatabase.SHARED_CONTACTS, - MmsDatabase.LINK_PREVIEWS}; + MmsDatabase.LINK_PREVIEWS, + MmsSmsColumns.HAS_MENTION + }; SQLiteQueryBuilder mmsQueryBuilder = new SQLiteQueryBuilder(); SQLiteQueryBuilder smsQueryBuilder = new SQLiteQueryBuilder(); @@ -408,6 +414,7 @@ public class MmsSmsDatabase extends Database { mmsColumnsPresent.add(MmsDatabase.STATUS); mmsColumnsPresent.add(MmsDatabase.UNIDENTIFIED); mmsColumnsPresent.add(MmsDatabase.NETWORK_FAILURE); + mmsColumnsPresent.add(MmsSmsColumns.HAS_MENTION); mmsColumnsPresent.add(AttachmentDatabase.ROW_ID); mmsColumnsPresent.add(AttachmentDatabase.UNIQUE_ID); @@ -470,6 +477,7 @@ public class MmsSmsDatabase extends Database { smsColumnsPresent.add(SmsDatabase.DATE_RECEIVED); smsColumnsPresent.add(SmsDatabase.STATUS); smsColumnsPresent.add(SmsDatabase.UNIDENTIFIED); + smsColumnsPresent.add(MmsSmsColumns.HAS_MENTION); smsColumnsPresent.add(ReactionDatabase.ROW_ID); smsColumnsPresent.add(ReactionDatabase.MESSAGE_ID); smsColumnsPresent.add(ReactionDatabase.IS_MMS); diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/SmsDatabase.java b/app/src/main/java/org/thoughtcrime/securesms/database/SmsDatabase.java index 358518dea..4b51cf534 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/SmsDatabase.java +++ b/app/src/main/java/org/thoughtcrime/securesms/database/SmsDatabase.java @@ -121,6 +121,9 @@ public class SmsDatabase extends MessagingDatabase { public static String CREATE_REACTIONS_UNREAD_COMMAND = "ALTER TABLE "+ TABLE_NAME + " " + "ADD COLUMN " + REACTIONS_UNREAD + " INTEGER DEFAULT 0;"; + public static String CREATE_HAS_MENTION_COMMAND = "ALTER TABLE "+ TABLE_NAME + " " + + "ADD COLUMN " + HAS_MENTION + " INTEGER DEFAULT 0;"; + private static final EarlyReceiptCache earlyDeliveryReceiptCache = new EarlyReceiptCache(); private static final EarlyReceiptCache earlyReadReceiptCache = new EarlyReceiptCache(); @@ -206,14 +209,17 @@ public class SmsDatabase extends MessagingDatabase { } @Override - public void markAsDeleted(long messageId, boolean read) { + public void markAsDeleted(long messageId, boolean read, boolean hasMention) { SQLiteDatabase database = databaseHelper.getWritableDatabase(); ContentValues contentValues = new ContentValues(); contentValues.put(READ, 1); contentValues.put(BODY, ""); + contentValues.put(HAS_MENTION, 0); database.update(TABLE_NAME, contentValues, ID_WHERE, new String[] {String.valueOf(messageId)}); long threadId = getThreadIdForMessage(messageId); - if (!read) { DatabaseComponent.get(context).threadDatabase().decrementUnread(threadId, 1); } + if (!read) { + DatabaseComponent.get(context).threadDatabase().decrementUnread(threadId, 1, (hasMention ? 1 : 0)); + } updateTypeBitmask(messageId, Types.BASE_TYPE_MASK, Types.BASE_DELETED_TYPE); } @@ -444,6 +450,7 @@ public class SmsDatabase extends MessagingDatabase { values.put(SUBSCRIPTION_ID, message.getSubscriptionId()); values.put(EXPIRES_IN, message.getExpiresIn()); values.put(UNIDENTIFIED, message.isUnidentified()); + values.put(HAS_MENTION, message.hasMention()); if (!TextUtils.isEmpty(message.getPseudoSubject())) values.put(SUBJECT, message.getPseudoSubject()); @@ -462,7 +469,7 @@ public class SmsDatabase extends MessagingDatabase { long messageId = db.insert(TABLE_NAME, null, values); if (unread && runIncrement) { - DatabaseComponent.get(context).threadDatabase().incrementUnread(threadId, 1); + DatabaseComponent.get(context).threadDatabase().incrementUnread(threadId, 1, (message.hasMention() ? 1 : 0)); } if (runThreadUpdate) { @@ -736,7 +743,7 @@ public class SmsDatabase extends MessagingDatabase { 0, message.isSecureMessage() ? MmsSmsColumns.Types.getOutgoingEncryptedMessageType() : MmsSmsColumns.Types.getOutgoingSmsMessageType(), threadId, 0, new LinkedList(), message.getExpiresIn(), - System.currentTimeMillis(), 0, false, Collections.emptyList()); + System.currentTimeMillis(), 0, false, Collections.emptyList(), false); } } @@ -777,6 +784,7 @@ public class SmsDatabase extends MessagingDatabase { long expireStarted = cursor.getLong(cursor.getColumnIndexOrThrow(SmsDatabase.EXPIRE_STARTED)); String body = cursor.getString(cursor.getColumnIndexOrThrow(SmsDatabase.BODY)); boolean unidentified = cursor.getInt(cursor.getColumnIndexOrThrow(SmsDatabase.UNIDENTIFIED)) == 1; + boolean hasMention = cursor.getInt(cursor.getColumnIndexOrThrow(SmsDatabase.HAS_MENTION)) == 1; if (!TextSecurePreferences.isReadReceiptsEnabled(context)) { readReceiptCount = 0; @@ -790,7 +798,7 @@ public class SmsDatabase extends MessagingDatabase { recipient, dateSent, dateReceived, deliveryReceiptCount, type, threadId, status, mismatches, - expiresIn, expireStarted, readReceiptCount, unidentified, reactions); + expiresIn, expireStarted, readReceiptCount, unidentified, reactions, hasMention); } private List getMismatches(String document) { diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/Storage.kt b/app/src/main/java/org/thoughtcrime/securesms/database/Storage.kt index 7daae9ef0..a4f658eef 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/Storage.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/database/Storage.kt @@ -101,9 +101,9 @@ class Storage(context: Context, helper: SQLCipherOpenHelper) : Database(context, threadDb.setRead(threadId, updateLastSeen) } - override fun incrementUnread(threadId: Long, amount: Int) { + override fun incrementUnread(threadId: Long, amount: Int, unreadMentionAmount: Int) { val threadDb = DatabaseComponent.get(context).threadDatabase() - threadDb.incrementUnread(threadId, amount) + threadDb.incrementUnread(threadId, amount, unreadMentionAmount) } override fun updateThread(threadId: Long, unarchive: Boolean) { @@ -458,7 +458,7 @@ class Storage(context: Context, helper: SQLCipherOpenHelper) : Database(context, override fun insertIncomingInfoMessage(context: Context, senderPublicKey: String, groupID: String, type: SignalServiceGroup.Type, name: String, members: Collection, admins: Collection, sentTimestamp: Long) { val group = SignalServiceGroup(type, GroupUtil.getDecodedGroupIDAsData(groupID), SignalServiceGroup.GroupType.SIGNAL, name, members.toList(), null, admins.toList()) - val m = IncomingTextMessage(fromSerialized(senderPublicKey), 1, sentTimestamp, "", Optional.of(group), 0, true) + val m = IncomingTextMessage(fromSerialized(senderPublicKey), 1, sentTimestamp, "", Optional.of(group), 0, true, false) val updateData = UpdateMessageData.buildGroupUpdate(type, name, members)?.toJSON() val infoMessage = IncomingGroupMessage(m, groupID, updateData, true) val smsDB = DatabaseComponent.get(context).smsDatabase() @@ -721,6 +721,7 @@ class Storage(context: Context, helper: SQLCipherOpenHelper) : Database(context, false, false, false, + false, Optional.absent(), Optional.absent(), Optional.absent(), @@ -795,6 +796,7 @@ class Storage(context: Context, helper: SQLCipherOpenHelper) : Database(context, false, false, true, + false, Optional.absent(), Optional.absent(), Optional.absent(), diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/ThreadDatabase.java b/app/src/main/java/org/thoughtcrime/securesms/database/ThreadDatabase.java index bf42b92ba..26b20ab00 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/ThreadDatabase.java +++ b/app/src/main/java/org/thoughtcrime/securesms/database/ThreadDatabase.java @@ -87,6 +87,7 @@ public class ThreadDatabase extends Database { private static final String SNIPPET_CHARSET = "snippet_cs"; public static final String READ = "read"; public static final String UNREAD_COUNT = "unread_count"; + public static final String UNREAD_MENTION_COUNT = "unread_mention_count"; public static final String TYPE = "type"; private static final String ERROR = "error"; public static final String SNIPPET_TYPE = "snippet_type"; @@ -117,7 +118,7 @@ public class ThreadDatabase extends Database { }; private static final String[] THREAD_PROJECTION = { - ID, DATE, MESSAGE_COUNT, ADDRESS, SNIPPET, SNIPPET_CHARSET, READ, UNREAD_COUNT, TYPE, ERROR, SNIPPET_TYPE, + ID, DATE, MESSAGE_COUNT, ADDRESS, SNIPPET, SNIPPET_CHARSET, READ, UNREAD_COUNT, UNREAD_MENTION_COUNT, TYPE, ERROR, SNIPPET_TYPE, SNIPPET_URI, ARCHIVED, STATUS, DELIVERY_RECEIPT_COUNT, EXPIRES_IN, LAST_SEEN, READ_RECEIPT_COUNT, IS_PINNED }; @@ -135,6 +136,11 @@ public class ThreadDatabase extends Database { "ADD COLUMN " + IS_PINNED + " INTEGER DEFAULT 0;"; } + public static String getUnreadMentionCountCommand() { + return "ALTER TABLE "+ TABLE_NAME + " " + + "ADD COLUMN " + UNREAD_MENTION_COUNT + " INTEGER DEFAULT 0;"; + } + public ThreadDatabase(Context context, SQLCipherOpenHelper databaseHelper) { super(context, databaseHelper); } @@ -293,6 +299,7 @@ public class ThreadDatabase extends Database { ContentValues contentValues = new ContentValues(1); contentValues.put(READ, 1); contentValues.put(UNREAD_COUNT, 0); + contentValues.put(UNREAD_MENTION_COUNT, 0); if (lastSeen) { contentValues.put(LAST_SEEN, System.currentTimeMillis()); @@ -312,20 +319,28 @@ public class ThreadDatabase extends Database { }}; } - public void incrementUnread(long threadId, int amount) { + public void incrementUnread(long threadId, int amount, int unreadMentionAmount) { SQLiteDatabase db = databaseHelper.getWritableDatabase(); db.execSQL("UPDATE " + TABLE_NAME + " SET " + READ + " = 0, " + - UNREAD_COUNT + " = " + UNREAD_COUNT + " + ? WHERE " + ID + " = ?", - new String[] {String.valueOf(amount), - String.valueOf(threadId)}); + UNREAD_COUNT + " = " + UNREAD_COUNT + " + ?, " + + UNREAD_MENTION_COUNT + " = " + UNREAD_MENTION_COUNT + " + ? WHERE " + ID + " = ?", + new String[] { + String.valueOf(amount), + String.valueOf(unreadMentionAmount), + String.valueOf(threadId) + }); } - public void decrementUnread(long threadId, int amount) { + public void decrementUnread(long threadId, int amount, int unreadMentionAmount) { SQLiteDatabase db = databaseHelper.getWritableDatabase(); db.execSQL("UPDATE " + TABLE_NAME + " SET " + READ + " = 0, " + - UNREAD_COUNT + " = " + UNREAD_COUNT + " - ? WHERE " + ID + " = ? AND " + UNREAD_COUNT + " > 0", - new String[] {String.valueOf(amount), - String.valueOf(threadId)}); + UNREAD_COUNT + " = " + UNREAD_COUNT + " - ?, " + + UNREAD_MENTION_COUNT + " = " + UNREAD_MENTION_COUNT + " - ? WHERE " + ID + " = ? AND " + UNREAD_COUNT + " > 0", + new String[] { + String.valueOf(amount), + String.valueOf(unreadMentionAmount), + String.valueOf(threadId) + }); } public void setDistributionType(long threadId, int distributionType) { @@ -911,6 +926,7 @@ public class ThreadDatabase extends Database { long date = cursor.getLong(cursor.getColumnIndexOrThrow(ThreadDatabase.DATE)); long count = cursor.getLong(cursor.getColumnIndexOrThrow(ThreadDatabase.MESSAGE_COUNT)); int unreadCount = cursor.getInt(cursor.getColumnIndexOrThrow(ThreadDatabase.UNREAD_COUNT)); + int unreadMentionCount = cursor.getInt(cursor.getColumnIndexOrThrow(ThreadDatabase.UNREAD_MENTION_COUNT)); long type = cursor.getLong(cursor.getColumnIndexOrThrow(ThreadDatabase.SNIPPET_TYPE)); boolean archived = cursor.getInt(cursor.getColumnIndexOrThrow(ThreadDatabase.ARCHIVED)) != 0; int status = cursor.getInt(cursor.getColumnIndexOrThrow(ThreadDatabase.STATUS)); @@ -926,7 +942,7 @@ public class ThreadDatabase extends Database { } return new ThreadRecord(body, snippetUri, recipient, date, count, - unreadCount, threadId, deliveryReceiptCount, status, type, + unreadCount, unreadMentionCount, threadId, deliveryReceiptCount, status, type, distributionType, archived, expiresIn, lastSeen, readReceiptCount, pinned); } 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 a6ac90698..4a48aa244 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 @@ -85,9 +85,10 @@ public class SQLCipherOpenHelper extends SQLiteOpenHelper { private static final int lokiV37 = 58; private static final int lokiV38 = 59; private static final int lokiV39 = 60; + private static final int lokiV40 = 61; // Loki - onUpgrade(...) must be updated to use Loki version numbers if Signal makes any database changes - private static final int DATABASE_VERSION = lokiV39; + private static final int DATABASE_VERSION = lokiV40; private static final int MIN_DATABASE_VERSION = lokiV7; private static final String CIPHER3_DATABASE_NAME = "signal.db"; public static final String DATABASE_NAME = "signal_v4.db"; @@ -306,6 +307,9 @@ public class SQLCipherOpenHelper extends SQLiteOpenHelper { db.execSQL(LokiAPIDatabase.RESET_SEQ_NO); // probably not needed but consistent with all migrations db.execSQL(EmojiSearchDatabase.CREATE_EMOJI_SEARCH_TABLE_COMMAND); db.execSQL(ReactionDatabase.CREATE_REACTION_TABLE_COMMAND); + db.execSQL(ThreadDatabase.getUnreadMentionCountCommand()); + db.execSQL(SmsDatabase.CREATE_HAS_MENTION_COMMAND); + db.execSQL(MmsDatabase.CREATE_HAS_MENTION_COMMAND); executeStatements(db, SmsDatabase.CREATE_INDEXS); executeStatements(db, MmsDatabase.CREATE_INDEXS); @@ -543,6 +547,12 @@ public class SQLCipherOpenHelper extends SQLiteOpenHelper { executeStatements(db, ReactionDatabase.CREATE_INDEXS); } + if (oldVersion < lokiV40) { + db.execSQL(ThreadDatabase.getUnreadMentionCountCommand()); + db.execSQL(SmsDatabase.CREATE_HAS_MENTION_COMMAND); + db.execSQL(MmsDatabase.CREATE_HAS_MENTION_COMMAND); + } + db.setTransactionSuccessful(); } finally { db.endTransaction(); diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/model/MediaMmsMessageRecord.java b/app/src/main/java/org/thoughtcrime/securesms/database/model/MediaMmsMessageRecord.java index 570cb48bc..1b566169d 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/model/MediaMmsMessageRecord.java +++ b/app/src/main/java/org/thoughtcrime/securesms/database/model/MediaMmsMessageRecord.java @@ -57,12 +57,12 @@ public class MediaMmsMessageRecord extends MmsMessageRecord { long expiresIn, long expireStarted, int readReceiptCount, @Nullable Quote quote, @NonNull List contacts, @NonNull List linkPreviews, - @NonNull List reactions, boolean unidentified) + @NonNull List reactions, boolean unidentified, boolean hasMention) { super(id, body, conversationRecipient, individualRecipient, dateSent, dateReceived, threadId, Status.STATUS_NONE, deliveryReceiptCount, mailbox, mismatches, failures, expiresIn, expireStarted, slideDeck, readReceiptCount, quote, contacts, - linkPreviews, unidentified, reactions); + linkPreviews, unidentified, reactions, hasMention); this.partCount = partCount; } diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/model/MessageRecord.java b/app/src/main/java/org/thoughtcrime/securesms/database/model/MessageRecord.java index 1d78314de..ba01ffd9c 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/model/MessageRecord.java +++ b/app/src/main/java/org/thoughtcrime/securesms/database/model/MessageRecord.java @@ -51,7 +51,8 @@ public abstract class MessageRecord extends DisplayRecord { private final long expireStarted; private final boolean unidentified; public final long id; - private final List reactions; + private final List reactions; + private final boolean hasMention; public abstract boolean isMms(); public abstract boolean isMmsNotification(); @@ -63,7 +64,7 @@ public abstract class MessageRecord extends DisplayRecord { List mismatches, List networkFailures, long expiresIn, long expireStarted, - int readReceiptCount, boolean unidentified, List reactions) + int readReceiptCount, boolean unidentified, List reactions, boolean hasMention) { super(body, conversationRecipient, dateSent, dateReceived, threadId, deliveryStatus, deliveryReceiptCount, type, readReceiptCount); @@ -75,6 +76,7 @@ public abstract class MessageRecord extends DisplayRecord { this.expireStarted = expireStarted; this.unidentified = unidentified; this.reactions = reactions; + this.hasMention = hasMention; } public long getId() { @@ -97,6 +99,8 @@ public abstract class MessageRecord extends DisplayRecord { } public long getExpireStarted() { return expireStarted; } + public boolean getHasMention() { return hasMention; } + public boolean isMediaPending() { return false; } diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/model/MmsMessageRecord.java b/app/src/main/java/org/thoughtcrime/securesms/database/model/MmsMessageRecord.java index b186e668e..9f34f3fa0 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/model/MmsMessageRecord.java +++ b/app/src/main/java/org/thoughtcrime/securesms/database/model/MmsMessageRecord.java @@ -27,9 +27,9 @@ public abstract class MmsMessageRecord extends MessageRecord { List networkFailures, long expiresIn, long expireStarted, @NonNull SlideDeck slideDeck, int readReceiptCount, @Nullable Quote quote, @NonNull List contacts, - @NonNull List linkPreviews, boolean unidentified, List reactions) + @NonNull List linkPreviews, boolean unidentified, List reactions, boolean hasMention) { - super(id, body, conversationRecipient, individualRecipient, dateSent, dateReceived, threadId, deliveryStatus, deliveryReceiptCount, type, mismatches, networkFailures, expiresIn, expireStarted, readReceiptCount, unidentified, reactions); + super(id, body, conversationRecipient, individualRecipient, dateSent, dateReceived, threadId, deliveryStatus, deliveryReceiptCount, type, mismatches, networkFailures, expiresIn, expireStarted, readReceiptCount, unidentified, reactions, hasMention); this.slideDeck = slideDeck; this.quote = quote; this.contacts.addAll(contacts); diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/model/NotificationMmsMessageRecord.java b/app/src/main/java/org/thoughtcrime/securesms/database/model/NotificationMmsMessageRecord.java index c1c87800d..9fb404787 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/model/NotificationMmsMessageRecord.java +++ b/app/src/main/java/org/thoughtcrime/securesms/database/model/NotificationMmsMessageRecord.java @@ -50,12 +50,12 @@ public class NotificationMmsMessageRecord extends MmsMessageRecord { long dateSent, long dateReceived, int deliveryReceiptCount, long threadId, byte[] contentLocation, long messageSize, long expiry, int status, byte[] transactionId, long mailbox, - SlideDeck slideDeck, int readReceiptCount) + SlideDeck slideDeck, int readReceiptCount, boolean hasMention) { super(id, "", conversationRecipient, individualRecipient, dateSent, dateReceived, threadId, Status.STATUS_NONE, deliveryReceiptCount, mailbox, emptyList(), emptyList(), - 0, 0, slideDeck, readReceiptCount, null, emptyList(), emptyList(), false, emptyList()); + 0, 0, slideDeck, readReceiptCount, null, emptyList(), emptyList(), false, emptyList(), hasMention); this.contentLocation = contentLocation; this.messageSize = messageSize; diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/model/SmsMessageRecord.java b/app/src/main/java/org/thoughtcrime/securesms/database/model/SmsMessageRecord.java index c1d50def2..83ee921a2 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/model/SmsMessageRecord.java +++ b/app/src/main/java/org/thoughtcrime/securesms/database/model/SmsMessageRecord.java @@ -43,12 +43,12 @@ public class SmsMessageRecord extends MessageRecord { long type, long threadId, int status, List mismatches, long expiresIn, long expireStarted, - int readReceiptCount, boolean unidentified, List reactions) + int readReceiptCount, boolean unidentified, List reactions, boolean hasMention) { super(id, body, recipient, individualRecipient, dateSent, dateReceived, threadId, status, deliveryReceiptCount, type, mismatches, new LinkedList<>(), - expiresIn, expireStarted, readReceiptCount, unidentified, reactions); + expiresIn, expireStarted, readReceiptCount, unidentified, reactions, hasMention); } public long getType() { diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/model/ThreadRecord.java b/app/src/main/java/org/thoughtcrime/securesms/database/model/ThreadRecord.java index 1e5a2fef0..dfc4c1bc8 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/model/ThreadRecord.java +++ b/app/src/main/java/org/thoughtcrime/securesms/database/model/ThreadRecord.java @@ -45,6 +45,7 @@ public class ThreadRecord extends DisplayRecord { private @Nullable final Uri snippetUri; private final long count; private final int unreadCount; + private final int unreadMentionCount; private final int distributionType; private final boolean archived; private final long expiresIn; @@ -53,19 +54,20 @@ public class ThreadRecord extends DisplayRecord { public ThreadRecord(@NonNull String body, @Nullable Uri snippetUri, @NonNull Recipient recipient, long date, long count, int unreadCount, - long threadId, int deliveryReceiptCount, int status, long snippetType, - int distributionType, boolean archived, long expiresIn, long lastSeen, - int readReceiptCount, boolean pinned) + int unreadMentionCount, long threadId, int deliveryReceiptCount, int status, + long snippetType, int distributionType, boolean archived, long expiresIn, + long lastSeen, int readReceiptCount, boolean pinned) { super(body, recipient, date, date, threadId, status, deliveryReceiptCount, snippetType, readReceiptCount); - this.snippetUri = snippetUri; - this.count = count; - this.unreadCount = unreadCount; - this.distributionType = distributionType; - this.archived = archived; - this.expiresIn = expiresIn; - this.lastSeen = lastSeen; - this.pinned = pinned; + this.snippetUri = snippetUri; + this.count = count; + this.unreadCount = unreadCount; + this.unreadMentionCount = unreadMentionCount; + this.distributionType = distributionType; + this.archived = archived; + this.expiresIn = expiresIn; + this.lastSeen = lastSeen; + this.pinned = pinned; } public @Nullable Uri getSnippetUri() { @@ -147,6 +149,10 @@ public class ThreadRecord extends DisplayRecord { return unreadCount; } + public int getUnreadMentionCount() { + return unreadMentionCount; + } + public long getDate() { return getDateReceived(); } diff --git a/app/src/main/java/org/thoughtcrime/securesms/home/ConversationView.kt b/app/src/main/java/org/thoughtcrime/securesms/home/ConversationView.kt index 7a0c865a4..c6a6e1f7f 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/home/ConversationView.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/home/ConversationView.kt @@ -25,17 +25,17 @@ import org.thoughtcrime.securesms.util.getAccentColor import java.util.Locale class ConversationView : LinearLayout { - private lateinit var binding: ViewConversationBinding + private val binding: ViewConversationBinding by lazy { ViewConversationBinding.bind(this) } private val screenWidth = Resources.getSystem().displayMetrics.widthPixels var thread: ThreadRecord? = null // region Lifecycle - constructor(context: Context) : super(context) { initialize() } - constructor(context: Context, attrs: AttributeSet) : super(context, attrs) { initialize() } - constructor(context: Context, attrs: AttributeSet, defStyleAttr: Int) : super(context, attrs, defStyleAttr) { initialize() } + constructor(context: Context) : super(context) + constructor(context: Context, attrs: AttributeSet) : super(context, attrs) + constructor(context: Context, attrs: AttributeSet, defStyleAttr: Int) : super(context, attrs, defStyleAttr) - private fun initialize() { - binding = ViewConversationBinding.inflate(LayoutInflater.from(context), this, true) + override fun onFinishInflate() { + super.onFinishInflate() layoutParams = RecyclerView.LayoutParams(screenWidth, RecyclerView.LayoutParams.WRAP_CONTENT) } // endregion @@ -53,7 +53,7 @@ class ConversationView : LinearLayout { } else { binding.conversationViewDisplayNameTextView.setCompoundDrawablesRelativeWithIntrinsicBounds(0, 0, 0, 0) } - background = if (thread.unreadCount > 0) { + binding.root.background = if (thread.unreadCount > 0) { ContextCompat.getDrawable(context, R.drawable.conversation_unread_background) } else { ContextCompat.getDrawable(context, R.drawable.conversation_view_background) @@ -79,8 +79,9 @@ class ConversationView : LinearLayout { binding.unreadCountTextView.text = formattedUnreadCount val textSize = if (unreadCount < 1000) 12.0f else 10.0f binding.unreadCountTextView.setTextSize(TypedValue.COMPLEX_UNIT_DIP, textSize) - binding.unreadCountIndicator.background.setTint(context.getAccentColor()) binding.unreadCountIndicator.isVisible = (unreadCount != 0 && !thread.isRead) + binding.unreadMentionTextView.setTextSize(TypedValue.COMPLEX_UNIT_DIP, textSize) + binding.unreadMentionIndicator.isVisible = (thread.unreadMentionCount != 0 && thread.recipient.address.isGroup) val senderDisplayName = getUserDisplayName(thread.recipient) ?: thread.recipient.address.toString() binding.conversationViewDisplayNameTextView.text = senderDisplayName diff --git a/app/src/main/java/org/thoughtcrime/securesms/home/HomeAdapter.kt b/app/src/main/java/org/thoughtcrime/securesms/home/HomeAdapter.kt index 0effc43fb..4273794f5 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/home/HomeAdapter.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/home/HomeAdapter.kt @@ -1,12 +1,14 @@ package org.thoughtcrime.securesms.home import android.content.Context +import android.view.LayoutInflater import android.view.View import android.view.ViewGroup import androidx.recyclerview.widget.DiffUtil import androidx.recyclerview.widget.ListUpdateCallback import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView.NO_ID +import network.loki.messenger.R import org.thoughtcrime.securesms.database.model.ThreadRecord import org.thoughtcrime.securesms.mms.GlideRequests @@ -76,19 +78,20 @@ class HomeAdapter( HeaderFooterViewHolder(header!!) } ITEM -> { - val view = ConversationView(context) - view.setOnClickListener { view.thread?.let { listener.onConversationClick(it) } } - view.setOnLongClickListener { - view.thread?.let { listener.onLongConversationClick(it) } + val conversationView = LayoutInflater.from(parent.context).inflate(R.layout.view_conversation, parent, false) as ConversationView + val viewHolder = ConversationViewHolder(conversationView) + viewHolder.view.setOnClickListener { viewHolder.view.thread?.let { listener.onConversationClick(it) } } + viewHolder.view.setOnLongClickListener { + viewHolder.view.thread?.let { listener.onLongConversationClick(it) } true } - ViewHolder(view) + viewHolder } else -> throw Exception("viewType $viewType isn't valid") } override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) { - if (holder is ViewHolder) { + if (holder is ConversationViewHolder) { val offset = if (hasHeaderView()) position - 1 else position val thread = data[offset] val isTyping = typingThreadIDs.contains(thread.threadId) @@ -97,7 +100,7 @@ class HomeAdapter( } override fun onViewRecycled(holder: RecyclerView.ViewHolder) { - if (holder is ViewHolder) { + if (holder is ConversationViewHolder) { holder.view.recycle() } else { super.onViewRecycled(holder) @@ -110,7 +113,7 @@ class HomeAdapter( override fun getItemCount(): Int = data.size + if (hasHeaderView()) 1 else 0 - class ViewHolder(val view: ConversationView) : RecyclerView.ViewHolder(view) + class ConversationViewHolder(val view: ConversationView) : RecyclerView.ViewHolder(view) class HeaderFooterViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) diff --git a/app/src/main/java/org/thoughtcrime/securesms/service/ExpiringMessageManager.java b/app/src/main/java/org/thoughtcrime/securesms/service/ExpiringMessageManager.java index b6d2e2bc5..f42b55b5f 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/service/ExpiringMessageManager.java +++ b/app/src/main/java/org/thoughtcrime/securesms/service/ExpiringMessageManager.java @@ -111,6 +111,7 @@ public class ExpiringMessageManager implements SSKEnvironment.MessageExpirationM duration * 1000L, true, false, false, + false, Optional.absent(), groupInfo, Optional.absent(), diff --git a/app/src/main/java/org/thoughtcrime/securesms/util/MockDataGenerator.kt b/app/src/main/java/org/thoughtcrime/securesms/util/MockDataGenerator.kt index 4d76e6aad..f70ea7bd0 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/util/MockDataGenerator.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/util/MockDataGenerator.kt @@ -135,7 +135,8 @@ object MockDataGenerator { Optional.absent(), 0, false, - -1 + -1, + false ), (timestampNow - (index * 5000)), false, @@ -264,7 +265,8 @@ object MockDataGenerator { Optional.absent(), 0, false, - -1 + -1, + false ), (timestampNow - (index * 5000)), false, @@ -389,7 +391,8 @@ object MockDataGenerator { Optional.absent(), 0, false, - -1 + -1, + false ), (timestampNow - (index * 5000)), false, diff --git a/app/src/main/res/layout/view_conversation.xml b/app/src/main/res/layout/view_conversation.xml index 04833b6a9..08c3693c1 100644 --- a/app/src/main/res/layout/view_conversation.xml +++ b/app/src/main/res/layout/view_conversation.xml @@ -1,5 +1,5 @@ - + + + + + + - + diff --git a/app/src/main/res/values/colors.xml b/app/src/main/res/values/colors.xml index 3fd16ffc6..d3b9d9b25 100644 --- a/app/src/main/res/values/colors.xml +++ b/app/src/main/res/values/colors.xml @@ -141,22 +141,25 @@ #57C9FA - #111111 + #000000 #1A1C28 #252735 #2B2D40 #3D4A5D #A6A9CE - #FFFFFF + #5CAACC + #FFFFFF - #19345D - #6A6E90 - #5CAACC - #B3EDF2 - #E7F3F4 - #ECFAFB - #FCFFFF + #000000 + #19345D + #6A6E90 + #5CAACC + #B3EDF2 + #E7F3F4 + #ECFAFB + #FCFFFF - #EA5545 + #FF3A3A + #E12D19 diff --git a/app/src/main/res/values/themes.xml b/app/src/main/res/values/themes.xml index 4c7dfd1db..a2c3842b5 100644 --- a/app/src/main/res/values/themes.xml +++ b/app/src/main/res/values/themes.xml @@ -277,7 +277,7 @@ ?android:textColorPrimary diff --git a/libsession/src/main/java/org/session/libsession/database/MessageDataProvider.kt b/libsession/src/main/java/org/session/libsession/database/MessageDataProvider.kt index eb40df6e0..81ce45b34 100644 --- a/libsession/src/main/java/org/session/libsession/database/MessageDataProvider.kt +++ b/libsession/src/main/java/org/session/libsession/database/MessageDataProvider.kt @@ -21,7 +21,7 @@ interface MessageDataProvider { */ fun getMessageID(serverId: Long, threadId: Long): Pair? fun deleteMessage(messageID: Long, isSms: Boolean) - fun updateMessageAsDeleted(timestamp: Long, author: String) + fun updateMessageAsDeleted(timestamp: Long, author: String): Long? fun getServerHashForMessage(messageID: Long): String? fun getDatabaseAttachment(attachmentId: Long): DatabaseAttachment? fun getAttachmentStream(attachmentId: Long): SessionServiceAttachmentStream? @@ -36,7 +36,7 @@ interface MessageDataProvider { fun isOutgoingMessage(timestamp: Long): Boolean fun handleSuccessfulAttachmentUpload(attachmentId: Long, attachmentStream: SignalServiceAttachmentStream, attachmentKey: ByteArray, uploadResult: UploadResult) fun handleFailedAttachmentUpload(attachmentId: Long) - fun getMessageForQuote(timestamp: Long, author: Address): Pair? + fun getMessageForQuote(timestamp: Long, author: Address): Triple? fun getAttachmentsAndLinkPreviewFor(mmsId: Long): List fun getMessageBodyFor(timestamp: Long, author: String): String fun getAttachmentIDsFor(messageID: Long): List diff --git a/libsession/src/main/java/org/session/libsession/database/StorageProtocol.kt b/libsession/src/main/java/org/session/libsession/database/StorageProtocol.kt index edca7cd15..34a7b81cb 100644 --- a/libsession/src/main/java/org/session/libsession/database/StorageProtocol.kt +++ b/libsession/src/main/java/org/session/libsession/database/StorageProtocol.kt @@ -174,7 +174,7 @@ interface StorageProtocol { */ fun persist(message: VisibleMessage, quotes: QuoteModel?, linkPreview: List, groupPublicKey: String?, openGroupID: String?, attachments: List, runIncrement: Boolean, runThreadUpdate: Boolean): Long? fun markConversationAsRead(threadId: Long, updateLastSeen: Boolean) - fun incrementUnread(threadId: Long, amount: Int) + fun incrementUnread(threadId: Long, amount: Int, unreadMentionAmount: Int) fun updateThread(threadId: Long, unarchive: Boolean) fun insertDataExtractionNotificationMessage(senderPublicKey: String, message: DataExtractionNotificationInfoMessage, sentTimestamp: Long) fun insertMessageRequestResponse(response: MessageRequestResponse) diff --git a/libsession/src/main/java/org/session/libsession/messaging/jobs/BatchMessageReceiveJob.kt b/libsession/src/main/java/org/session/libsession/messaging/jobs/BatchMessageReceiveJob.kt index 07c104cfd..de512c556 100644 --- a/libsession/src/main/java/org/session/libsession/messaging/jobs/BatchMessageReceiveJob.kt +++ b/libsession/src/main/java/org/session/libsession/messaging/jobs/BatchMessageReceiveJob.kt @@ -11,13 +11,11 @@ import org.session.libsession.database.StorageProtocol import org.session.libsession.messaging.MessagingModuleConfiguration import org.session.libsession.messaging.messages.Message import org.session.libsession.messaging.messages.control.ExpirationTimerUpdate +import org.session.libsession.messaging.messages.control.UnsendRequest import org.session.libsession.messaging.messages.visible.ParsedMessage import org.session.libsession.messaging.messages.visible.VisibleMessage import org.session.libsession.messaging.open_groups.OpenGroupApi -import org.session.libsession.messaging.sending_receiving.MessageReceiver -import org.session.libsession.messaging.sending_receiving.handle -import org.session.libsession.messaging.sending_receiving.handleOpenGroupReactions -import org.session.libsession.messaging.sending_receiving.handleVisibleMessage +import org.session.libsession.messaging.sending_receiving.* import org.session.libsession.messaging.utilities.Data import org.session.libsession.messaging.utilities.SessionId import org.session.libsession.messaging.utilities.SodiumUtilities @@ -108,25 +106,42 @@ class BatchMessageReceiveJob( runBlocking(Dispatchers.IO) { val deferredThreadMap = threadMap.entries.map { (threadId, messages) -> async { - val messageIds = mutableListOf>() + // The LinkedHashMap should preserve insertion order + val messageIds = linkedMapOf>() + messages.forEach { (parameters, message, proto) -> try { - if (message is VisibleMessage) { - val messageId = MessageReceiver.handleVisibleMessage(message, proto, openGroupID, - runIncrement = false, - runThreadUpdate = false, - runProfileUpdate = true - ) - if (messageId != null && message.reaction == null) { - val isUserBlindedSender = message.sender == serverPublicKey?.let { SodiumUtilities.blindedKeyPair(it, MessagingModuleConfiguration.shared.getUserED25519KeyPair()!!) }?.let { SessionId( - IdPrefix.BLINDED, it.publicKey.asBytes).hexString } - messageIds += messageId to (message.sender == localUserPublicKey || isUserBlindedSender) + when (message) { + is VisibleMessage -> { + val messageId = MessageReceiver.handleVisibleMessage(message, proto, openGroupID, + runIncrement = false, + runThreadUpdate = false, + runProfileUpdate = true + ) + + if (messageId != null && message.reaction == null) { + val isUserBlindedSender = message.sender == serverPublicKey?.let { SodiumUtilities.blindedKeyPair(it, MessagingModuleConfiguration.shared.getUserED25519KeyPair()!!) }?.let { SessionId( + IdPrefix.BLINDED, it.publicKey.asBytes).hexString } + messageIds[messageId] = Pair( + (message.sender == localUserPublicKey || isUserBlindedSender), + message.hasMention + ) + } + parameters.openGroupMessageServerID?.let { + MessageReceiver.handleOpenGroupReactions(threadId, it, parameters.reactions) + } } - parameters.openGroupMessageServerID?.let { - MessageReceiver.handleOpenGroupReactions(threadId, it, parameters.reactions) + + is UnsendRequest -> { + val deletedMessageId = MessageReceiver.handleUnsendRequest(message) + + // If we removed a message then ensure it isn't in the 'messageIds' + if (deletedMessageId != null) { + messageIds.remove(deletedMessageId) + } } - } else { - MessageReceiver.handle(message, proto, openGroupID) + + else -> MessageReceiver.handle(message, proto, openGroupID) } } catch (e: Exception) { Log.e(TAG, "Couldn't process message.", e) @@ -139,14 +154,15 @@ class BatchMessageReceiveJob( } } // increment unreads, notify, and update thread - val unreadFromMine = messageIds.indexOfLast { (_,fromMe) -> fromMe } - var trueUnreadCount = messageIds.filter { (_,fromMe) -> !fromMe }.size + val unreadFromMine = messageIds.map { it.value.first }.indexOfLast { it } + var trueUnreadCount = messageIds.filter { !it.value.first }.size + val trueUnreadMentionCount = messageIds.filter { !it.value.first && it.value.second }.size if (unreadFromMine >= 0) { trueUnreadCount -= (unreadFromMine + 1) storage.markConversationAsRead(threadId, false) } if (trueUnreadCount > 0) { - storage.incrementUnread(threadId, trueUnreadCount) + storage.incrementUnread(threadId, trueUnreadCount, trueUnreadMentionCount) } storage.updateThread(threadId, true) SSKEnvironment.shared.notificationManager.updateNotification(context, threadId) diff --git a/libsession/src/main/java/org/session/libsession/messaging/messages/signal/IncomingMediaMessage.java b/libsession/src/main/java/org/session/libsession/messaging/messages/signal/IncomingMediaMessage.java index e5160b754..ab24234e8 100644 --- a/libsession/src/main/java/org/session/libsession/messaging/messages/signal/IncomingMediaMessage.java +++ b/libsession/src/main/java/org/session/libsession/messaging/messages/signal/IncomingMediaMessage.java @@ -29,6 +29,7 @@ public class IncomingMediaMessage { private final boolean expirationUpdate; private final boolean unidentified; private final boolean messageRequestResponse; + private final boolean hasMention; private final DataExtractionNotificationInfoMessage dataExtractionNotification; private final QuoteModel quote; @@ -44,6 +45,7 @@ public class IncomingMediaMessage { boolean expirationUpdate, boolean unidentified, boolean messageRequestResponse, + boolean hasMention, Optional body, Optional group, Optional> attachments, @@ -63,6 +65,7 @@ public class IncomingMediaMessage { this.quote = quote.orNull(); this.unidentified = unidentified; this.messageRequestResponse = messageRequestResponse; + this.hasMention = hasMention; if (group.isPresent()) this.groupId = Address.fromSerialized(GroupUtil.INSTANCE.getEncodedId(group.get())); else this.groupId = null; @@ -81,7 +84,8 @@ public class IncomingMediaMessage { Optional> linkPreviews) { return new IncomingMediaMessage(from, message.getSentTimestamp(), -1, expiresIn, false, - false, false, Optional.fromNullable(message.getText()), group, Optional.fromNullable(attachments), quote, Optional.absent(), linkPreviews, Optional.absent()); + false, false, message.getHasMention(), Optional.fromNullable(message.getText()), + group, Optional.fromNullable(attachments), quote, Optional.absent(), linkPreviews, Optional.absent()); } public int getSubscriptionId() { @@ -124,6 +128,10 @@ public class IncomingMediaMessage { return groupId != null; } + public boolean hasMention() { + return hasMention; + } + public boolean isScreenshotDataExtraction() { if (dataExtractionNotification == null) return false; else { diff --git a/libsession/src/main/java/org/session/libsession/messaging/messages/signal/IncomingTextMessage.java b/libsession/src/main/java/org/session/libsession/messaging/messages/signal/IncomingTextMessage.java index 93347f527..ca8e89f1e 100644 --- a/libsession/src/main/java/org/session/libsession/messaging/messages/signal/IncomingTextMessage.java +++ b/libsession/src/main/java/org/session/libsession/messaging/messages/signal/IncomingTextMessage.java @@ -43,24 +43,25 @@ public class IncomingTextMessage implements Parcelable { private final long expiresInMillis; private final boolean unidentified; private final int callType; + private final boolean hasMention; private boolean isOpenGroupInvitation = false; public IncomingTextMessage(Address sender, int senderDeviceId, long sentTimestampMillis, String encodedBody, Optional group, - long expiresInMillis, boolean unidentified) { - this(sender, senderDeviceId, sentTimestampMillis, encodedBody, group, expiresInMillis, unidentified, -1); + long expiresInMillis, boolean unidentified, boolean hasMention) { + this(sender, senderDeviceId, sentTimestampMillis, encodedBody, group, expiresInMillis, unidentified, -1, hasMention); } public IncomingTextMessage(Address sender, int senderDeviceId, long sentTimestampMillis, String encodedBody, Optional group, - long expiresInMillis, boolean unidentified, int callType) { - this(sender, senderDeviceId, sentTimestampMillis, encodedBody, group, expiresInMillis, unidentified, callType, true); + long expiresInMillis, boolean unidentified, int callType, boolean hasMention) { + this(sender, senderDeviceId, sentTimestampMillis, encodedBody, group, expiresInMillis, unidentified, callType, hasMention, true); } public IncomingTextMessage(Address sender, int senderDeviceId, long sentTimestampMillis, String encodedBody, Optional group, - long expiresInMillis, boolean unidentified, int callType, boolean isPush) { + long expiresInMillis, boolean unidentified, int callType, boolean hasMention, boolean isPush) { this.message = encodedBody; this.sender = sender; this.senderDeviceId = senderDeviceId; @@ -74,6 +75,7 @@ public class IncomingTextMessage implements Parcelable { this.expiresInMillis = expiresInMillis; this.unidentified = unidentified; this.callType = callType; + this.hasMention = hasMention; if (group.isPresent()) { this.groupId = Address.fromSerialized(GroupUtil.getEncodedId(group.get())); @@ -98,6 +100,7 @@ public class IncomingTextMessage implements Parcelable { this.unidentified = in.readInt() == 1; this.isOpenGroupInvitation = in.readInt() == 1; this.callType = in.readInt(); + this.hasMention = in.readInt() == 1; } public IncomingTextMessage(IncomingTextMessage base, String newBody) { @@ -116,6 +119,7 @@ public class IncomingTextMessage implements Parcelable { this.unidentified = base.isUnidentified(); this.isOpenGroupInvitation = base.isOpenGroupInvitation(); this.callType = base.callType; + this.hasMention = base.hasMention; } public static IncomingTextMessage from(VisibleMessage message, @@ -123,7 +127,7 @@ public class IncomingTextMessage implements Parcelable { Optional group, long expiresInMillis) { - return new IncomingTextMessage(sender, 1, message.getSentTimestamp(), message.getText(), group, expiresInMillis, false); + return new IncomingTextMessage(sender, 1, message.getSentTimestamp(), message.getText(), group, expiresInMillis, false, message.getHasMention()); } public static IncomingTextMessage fromOpenGroupInvitation(OpenGroupInvitation openGroupInvitation, Address sender, Long sentTimestamp) @@ -133,7 +137,7 @@ public class IncomingTextMessage implements Parcelable { if (url == null || name == null) { return null; } // FIXME: Doing toJSON() to get the body here is weird String body = UpdateMessageData.Companion.buildOpenGroupInvitation(url, name).toJSON(); - IncomingTextMessage incomingTextMessage = new IncomingTextMessage(sender, 1, sentTimestamp, body, Optional.absent(), 0, false); + IncomingTextMessage incomingTextMessage = new IncomingTextMessage(sender, 1, sentTimestamp, body, Optional.absent(), 0, false, false); incomingTextMessage.isOpenGroupInvitation = true; return incomingTextMessage; } @@ -142,7 +146,7 @@ public class IncomingTextMessage implements Parcelable { Address sender, Optional group, long sentTimestamp) { - return new IncomingTextMessage(sender, 1, sentTimestamp, null, group, 0, false, callMessageType.ordinal(), false); + return new IncomingTextMessage(sender, 1, sentTimestamp, null, group, 0, false, callMessageType.ordinal(), false, false); } public int getSubscriptionId() { @@ -207,6 +211,8 @@ public class IncomingTextMessage implements Parcelable { public boolean isOpenGroupInvitation() { return isOpenGroupInvitation; } + public boolean hasMention() { return hasMention; } + public boolean isCallInfo() { int callMessageTypeLength = CallMessageType.values().length; return callType >= 0 && callType < callMessageTypeLength; @@ -240,5 +246,6 @@ public class IncomingTextMessage implements Parcelable { out.writeInt(unidentified ? 1 : 0); out.writeInt(isOpenGroupInvitation ? 1 : 0); out.writeInt(callType); + out.writeInt(hasMention ? 1 : 0); } } diff --git a/libsession/src/main/java/org/session/libsession/messaging/messages/signal/OutgoingMediaMessage.java b/libsession/src/main/java/org/session/libsession/messaging/messages/signal/OutgoingMediaMessage.java index a163b667d..08cb2c4b0 100644 --- a/libsession/src/main/java/org/session/libsession/messaging/messages/signal/OutgoingMediaMessage.java +++ b/libsession/src/main/java/org/session/libsession/messaging/messages/signal/OutgoingMediaMessage.java @@ -85,8 +85,8 @@ public class OutgoingMediaMessage { previews = Collections.singletonList(linkPreview); } return new OutgoingMediaMessage(recipient, message.getText(), attachments, message.getSentTimestamp(), -1, - recipient.getExpireMessages() * 1000, DistributionTypes.DEFAULT, outgoingQuote, Collections.emptyList(), - previews, Collections.emptyList(), Collections.emptyList()); + recipient.getExpireMessages() * 1000, DistributionTypes.DEFAULT, outgoingQuote, + Collections.emptyList(), previews, Collections.emptyList(), Collections.emptyList()); } public Recipient getRecipient() { diff --git a/libsession/src/main/java/org/session/libsession/messaging/messages/visible/VisibleMessage.kt b/libsession/src/main/java/org/session/libsession/messaging/messages/visible/VisibleMessage.kt index 2891400c9..e66147da1 100644 --- a/libsession/src/main/java/org/session/libsession/messaging/messages/visible/VisibleMessage.kt +++ b/libsession/src/main/java/org/session/libsession/messaging/messages/visible/VisibleMessage.kt @@ -24,6 +24,7 @@ class VisibleMessage : Message() { var profile: Profile? = null var openGroupInvitation: OpenGroupInvitation? = null var reaction: Reaction? = null + var hasMention: Boolean = false override val isSelfSendValid: Boolean = true diff --git a/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/ReceivedMessageHandler.kt b/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/ReceivedMessageHandler.kt index acab1f097..dccb2ec88 100644 --- a/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/ReceivedMessageHandler.kt +++ b/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/ReceivedMessageHandler.kt @@ -173,22 +173,24 @@ private fun handleConfigurationMessage(message: ConfigurationMessage) { storage.addContacts(message.contacts) } -fun MessageReceiver.handleUnsendRequest(message: UnsendRequest) { +fun MessageReceiver.handleUnsendRequest(message: UnsendRequest): Long? { val userPublicKey = MessagingModuleConfiguration.shared.storage.getUserPublicKey() - if (message.sender != message.author && (message.sender != userPublicKey && userPublicKey != null)) { return } + if (message.sender != message.author && (message.sender != userPublicKey && userPublicKey != null)) { return null } val context = MessagingModuleConfiguration.shared.context val storage = MessagingModuleConfiguration.shared.storage val messageDataProvider = MessagingModuleConfiguration.shared.messageDataProvider - val timestamp = message.timestamp ?: return - val author = message.author ?: return - val messageIdToDelete = storage.getMessageIdInDatabase(timestamp, author) ?: return + val timestamp = message.timestamp ?: return null + val author = message.author ?: return null + val messageIdToDelete = storage.getMessageIdInDatabase(timestamp, author) ?: return null messageDataProvider.getServerHashForMessage(messageIdToDelete)?.let { serverHash -> SnodeAPI.deleteMessage(author, listOf(serverHash)) } - messageDataProvider.updateMessageAsDeleted(timestamp, author) + val deletedMessageId = messageDataProvider.updateMessageAsDeleted(timestamp, author) if (!messageDataProvider.isOutgoingMessage(messageIdToDelete)) { SSKEnvironment.shared.notificationManager.updateNotification(context) } + + return deletedMessageId } fun handleMessageRequestResponse(message: MessageRequestResponse) { @@ -248,6 +250,7 @@ fun MessageReceiver.handleVisibleMessage(message: VisibleMessage, } // Parse quote if needed var quoteModel: QuoteModel? = null + var quoteMessageBody: String? = null if (message.quote != null && proto.dataMessage.hasQuote()) { val quote = proto.dataMessage.quote @@ -259,6 +262,7 @@ fun MessageReceiver.handleVisibleMessage(message: VisibleMessage, val messageDataProvider = MessagingModuleConfiguration.shared.messageDataProvider val messageInfo = messageDataProvider.getMessageForQuote(quote.id, author) + quoteMessageBody = messageInfo?.third quoteModel = if (messageInfo != null) { val attachments = if (messageInfo.second) messageDataProvider.getAttachmentsAndLinkPreviewFor(messageInfo.first) else ArrayList() QuoteModel(quote.id, author,null,false, attachments) @@ -305,6 +309,20 @@ fun MessageReceiver.handleVisibleMessage(message: VisibleMessage, storage.removeReaction(reaction.emoji!!, reaction.timestamp!!, reaction.publicKey!!, threadIsGroup) } } ?: run { + // A user is mentioned if their public key is in the body of a message or one of their messages + // was quoted + val messageText = message.text + message.hasMention = listOf(userPublicKey, userBlindedKey) + .filterNotNull() + .any { key -> + return@any ( + messageText != null && + messageText.contains("@$key") + ) || ( + (quoteModel?.author?.serialize() ?: "") == key + ) + } + // Persist the message message.threadID = threadID val messageID = From ce8e5c596e87a874da3d5ba63a31a5e7b0da9d62 Mon Sep 17 00:00:00 2001 From: Morgan Pretty Date: Tue, 17 Jan 2023 17:01:20 +1100 Subject: [PATCH 35/47] Fixed the message sorting for a couple of database queries --- .../org/thoughtcrime/securesms/database/MmsSmsDatabase.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/MmsSmsDatabase.java b/app/src/main/java/org/thoughtcrime/securesms/database/MmsSmsDatabase.java index 447993643..c7f9d6132 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/MmsSmsDatabase.java +++ b/app/src/main/java/org/thoughtcrime/securesms/database/MmsSmsDatabase.java @@ -188,7 +188,7 @@ public class MmsSmsDatabase extends Database { } public Cursor getConversationSnippet(long threadId) { - String order = MmsSmsColumns.NORMALIZED_DATE_RECEIVED + " DESC"; + String order = MmsSmsColumns.NORMALIZED_DATE_SENT + " DESC"; String selection = MmsSmsColumns.THREAD_ID + " = " + threadId; return queryTables(PROJECTION, selection, order, null); @@ -205,7 +205,7 @@ public class MmsSmsDatabase extends Database { } public Cursor getUnread() { - String order = MmsSmsColumns.NORMALIZED_DATE_RECEIVED + " ASC"; + String order = MmsSmsColumns.NORMALIZED_DATE_SENT + " ASC"; String selection = "(" + MmsSmsColumns.READ + " = 0 OR " + MmsSmsColumns.REACTIONS_UNREAD + " = 1) AND " + MmsSmsColumns.NOTIFIED + " = 0"; return queryTables(PROJECTION, selection, order, null); @@ -240,7 +240,7 @@ public class MmsSmsDatabase extends Database { } public int getQuotedMessagePosition(long threadId, long quoteId, @NonNull Address address) { - String order = MmsSmsColumns.NORMALIZED_DATE_RECEIVED + " DESC"; + String order = MmsSmsColumns.NORMALIZED_DATE_SENT + " DESC"; String selection = MmsSmsColumns.THREAD_ID + " = " + threadId; try (Cursor cursor = queryTables(new String[]{ MmsSmsColumns.NORMALIZED_DATE_SENT, MmsSmsColumns.ADDRESS }, selection, order, null)) { From 810430e80651a5a5113b74fcff98b854fa01188e Mon Sep 17 00:00:00 2001 From: Morgan Pretty Date: Fri, 20 Jan 2023 09:02:59 +1100 Subject: [PATCH 36/47] Fixed a couple of issues with the OpenGroupDeleteJob Updated the OpenGroupDispatcher to have a thread limit of 8 (was previously unlimited which could result in the app getting flooded with threads under certain conditions) Updated the OpenGroupDeleteJob to do bulk deletions (instead of individual message deletions) Updated the OpenGroupDeleteJob to catch and report failures (wasn't previously happening) --- .../attachments/DatabaseAttachmentProvider.kt | 14 +++++ .../database/AttachmentDatabase.java | 23 ++++++++ .../database/GroupReceiptDatabase.java | 6 ++ .../securesms/database/LokiMessageDatabase.kt | 59 +++++++++++++++++++ .../securesms/database/MessagingDatabase.java | 1 + .../securesms/database/MmsDatabase.kt | 17 ++++++ .../securesms/database/SmsDatabase.java | 26 ++++++++ .../database/MessageDataProvider.kt | 2 + .../libsession/messaging/jobs/JobQueue.kt | 2 +- .../messaging/jobs/OpenGroupDeleteJob.kt | 27 ++++++--- 10 files changed, 169 insertions(+), 8 deletions(-) diff --git a/app/src/main/java/org/thoughtcrime/securesms/attachments/DatabaseAttachmentProvider.kt b/app/src/main/java/org/thoughtcrime/securesms/attachments/DatabaseAttachmentProvider.kt index fa0fce7bd..6e9185094 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/attachments/DatabaseAttachmentProvider.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/attachments/DatabaseAttachmentProvider.kt @@ -176,6 +176,11 @@ class DatabaseAttachmentProvider(context: Context, helper: SQLCipherOpenHelper) return messageDB.getMessageID(serverId, threadId) } + override fun getMessageIDs(serverIds: List, threadId: Long): Pair, List> { + val messageDB = DatabaseComponent.get(context).lokiMessageDatabase() + return messageDB.getMessageIDs(serverIds, threadId) + } + override fun deleteMessage(messageID: Long, isSms: Boolean) { val messagingDatabase: MessagingDatabase = if (isSms) DatabaseComponent.get(context).smsDatabase() else DatabaseComponent.get(context).mmsDatabase() @@ -184,6 +189,15 @@ class DatabaseAttachmentProvider(context: Context, helper: SQLCipherOpenHelper) DatabaseComponent.get(context).lokiMessageDatabase().deleteMessageServerHash(messageID) } + override fun deleteMessages(messageIDs: List, threadId: Long, isSms: Boolean) { + val messagingDatabase: MessagingDatabase = if (isSms) DatabaseComponent.get(context).smsDatabase() + else DatabaseComponent.get(context).mmsDatabase() + + messagingDatabase.deleteMessages(messageIDs.toLongArray(), threadId) + DatabaseComponent.get(context).lokiMessageDatabase().deleteMessages(messageIDs) + DatabaseComponent.get(context).lokiMessageDatabase().deleteMessageServerHashes(messageIDs) + } + override fun updateMessageAsDeleted(timestamp: Long, author: String) { val database = DatabaseComponent.get(context).mmsSmsDatabase() val address = Address.fromSerialized(author) diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/AttachmentDatabase.java b/app/src/main/java/org/thoughtcrime/securesms/database/AttachmentDatabase.java index 182f8536d..45172e2f6 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/AttachmentDatabase.java +++ b/app/src/main/java/org/thoughtcrime/securesms/database/AttachmentDatabase.java @@ -35,6 +35,7 @@ import com.bumptech.glide.Glide; import net.zetetic.database.sqlcipher.SQLiteDatabase; +import org.apache.commons.lang3.StringUtils; import org.json.JSONArray; import org.json.JSONException; import org.session.libsession.messaging.sending_receiving.attachments.Attachment; @@ -318,6 +319,28 @@ public class AttachmentDatabase extends Database { notifyAttachmentListeners(); } + @SuppressWarnings("ResultOfMethodCallIgnored") + void deleteAttachmentsForMessages(long[] mmsIds) { + SQLiteDatabase database = databaseHelper.getWritableDatabase(); + Cursor cursor = null; + String mmsIdString = StringUtils.join(mmsIds, ','); + + try { + cursor = database.query(TABLE_NAME, new String[] {DATA, THUMBNAIL, CONTENT_TYPE}, MMS_ID + " IN (?)", + new String[] {mmsIdString}, null, null, null); + + while (cursor != null && cursor.moveToNext()) { + deleteAttachmentOnDisk(cursor.getString(0), cursor.getString(1), cursor.getString(2)); + } + } finally { + if (cursor != null) + cursor.close(); + } + + database.delete(TABLE_NAME, MMS_ID + " IN (?)", new String[] {mmsIdString}); + notifyAttachmentListeners(); + } + public void deleteAttachment(@NonNull AttachmentId id) { SQLiteDatabase database = databaseHelper.getWritableDatabase(); diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/GroupReceiptDatabase.java b/app/src/main/java/org/thoughtcrime/securesms/database/GroupReceiptDatabase.java index d4140910d..a6fed5be8 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/GroupReceiptDatabase.java +++ b/app/src/main/java/org/thoughtcrime/securesms/database/GroupReceiptDatabase.java @@ -8,6 +8,7 @@ import androidx.annotation.NonNull; import net.zetetic.database.sqlcipher.SQLiteDatabase; +import org.apache.commons.lang3.StringUtils; import org.session.libsession.utilities.Address; import org.thoughtcrime.securesms.database.helpers.SQLCipherOpenHelper; @@ -109,6 +110,11 @@ public class GroupReceiptDatabase extends Database { db.delete(TABLE_NAME, MMS_ID + " = ?", new String[] {String.valueOf(mmsId)}); } + void deleteRowsForMessages(long[] mmsIds) { + SQLiteDatabase db = databaseHelper.getWritableDatabase(); + db.delete(TABLE_NAME, MMS_ID + " IN (?)", new String[] {StringUtils.join(mmsIds, ',')}); + } + void deleteAllRows() { SQLiteDatabase db = databaseHelper.getWritableDatabase(); db.delete(TABLE_NAME, null, null); diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/LokiMessageDatabase.kt b/app/src/main/java/org/thoughtcrime/securesms/database/LokiMessageDatabase.kt index 41a136caa..45184c2d2 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/LokiMessageDatabase.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/database/LokiMessageDatabase.kt @@ -77,6 +77,25 @@ class LokiMessageDatabase(context: Context, helper: SQLCipherOpenHelper) : Datab database.endTransaction() } + fun deleteMessages(messageIDs: List) { + val database = databaseHelper.writableDatabase + database.beginTransaction() + + database.delete( + messageIDTable, + "${Companion.messageID} IN (${messageIDs.map { "?" }.joinToString(",")})", + messageIDs.map { "$it" }.toTypedArray() + ) + database.delete( + messageThreadMappingTable, + "${Companion.messageID} IN (${messageIDs.map { "?" }.joinToString(",")})", + messageIDs.map { "$it" }.toTypedArray() + ) + + database.setTransactionSuccessful() + database.endTransaction() + } + /** * @return pair of sms or mms table-specific ID and whether it is in SMS table */ @@ -96,6 +115,37 @@ class LokiMessageDatabase(context: Context, helper: SQLCipherOpenHelper) : Datab } } + fun getMessageIDs(serverIDs: List, threadID: Long): Pair, List> { + val database = databaseHelper.readableDatabase + + // Retrieve the message ids + val messageIdCursor = database + .rawQuery( + """ + SELECT ${messageThreadMappingTable}.${messageID}, ${messageIDTable}.${messageType} + FROM ${messageThreadMappingTable} + JOIN ${messageIDTable} ON ${messageIDTable}.message_id = ${messageThreadMappingTable}.${messageID} + WHERE ( + ${messageThreadMappingTable}.${Companion.threadID} = $threadID AND + ${messageThreadMappingTable}.${Companion.serverID} IN (${serverIDs.joinToString(",")}) + ) + """ + ) + + val smsMessageIds: MutableList = mutableListOf() + val mmsMessageIds: MutableList = mutableListOf() + while (messageIdCursor.moveToNext()) { + if (messageIdCursor.getInt(1) == SMS_TYPE) { + smsMessageIds.add(messageIdCursor.getLong(0)) + } + else { + mmsMessageIds.add(messageIdCursor.getLong(0)) + } + } + + return Pair(smsMessageIds, mmsMessageIds) + } + override fun setServerID(messageID: Long, serverID: Long, isSms: Boolean) { val database = databaseHelper.writableDatabase val contentValues = ContentValues(3) @@ -183,6 +233,15 @@ class LokiMessageDatabase(context: Context, helper: SQLCipherOpenHelper) : Datab database.delete(messageHashTable, "${Companion.messageID} = ?", arrayOf(messageID.toString())) } + fun deleteMessageServerHashes(messageIDs: List) { + val database = databaseHelper.writableDatabase + database.delete( + messageHashTable, + "${Companion.messageID} IN (${messageIDs.map { "?" }.joinToString(",")})", + messageIDs.map { "$it" }.toTypedArray() + ) + } + fun migrateThreadId(legacyThreadId: Long, newThreadId: Long) { val database = databaseHelper.writableDatabase val contentValues = ContentValues(1) diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/MessagingDatabase.java b/app/src/main/java/org/thoughtcrime/securesms/database/MessagingDatabase.java index bc0594df0..2471db1cb 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/MessagingDatabase.java +++ b/app/src/main/java/org/thoughtcrime/securesms/database/MessagingDatabase.java @@ -42,6 +42,7 @@ public abstract class MessagingDatabase extends Database implements MmsSmsColumn public abstract void markAsDeleted(long messageId, boolean read); public abstract boolean deleteMessage(long messageId); + public abstract boolean deleteMessages(long[] messageId, long threadId); public abstract void updateThreadId(long fromId, long toId); diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/MmsDatabase.kt b/app/src/main/java/org/thoughtcrime/securesms/database/MmsDatabase.kt index d82c6bb27..3f94dd6bc 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/MmsDatabase.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/database/MmsDatabase.kt @@ -995,6 +995,23 @@ class MmsDatabase(context: Context, databaseHelper: SQLCipherOpenHelper) : Messa return threadDeleted } + override fun deleteMessages(messageIds: LongArray, threadId: Long): Boolean { + val attachmentDatabase = get(context).attachmentDatabase() + val groupReceiptDatabase = get(context).groupReceiptDatabase() + + queue(Runnable { attachmentDatabase.deleteAttachmentsForMessages(messageIds) }) + groupReceiptDatabase.deleteRowsForMessages(messageIds) + + val database = databaseHelper.writableDatabase + database!!.delete(TABLE_NAME, ID_IN, arrayOf(messageIds.joinToString(","))) + + val threadDeleted = get(context).threadDatabase().update(threadId, false) + notifyConversationListeners(threadId) + notifyStickerListeners() + notifyStickerPackListeners() + return threadDeleted + } + override fun updateThreadId(fromId: Long, toId: Long) { val contentValues = ContentValues(1) contentValues.put(THREAD_ID, toId) diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/SmsDatabase.java b/app/src/main/java/org/thoughtcrime/securesms/database/SmsDatabase.java index 358518dea..320cee477 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/SmsDatabase.java +++ b/app/src/main/java/org/thoughtcrime/securesms/database/SmsDatabase.java @@ -31,6 +31,7 @@ import com.annimon.stream.Stream; import net.zetetic.database.sqlcipher.SQLiteDatabase; import net.zetetic.database.sqlcipher.SQLiteStatement; +import org.apache.commons.lang3.StringUtils; import org.session.libsession.messaging.calls.CallMessageType; import org.session.libsession.messaging.messages.signal.IncomingGroupMessage; import org.session.libsession.messaging.messages.signal.IncomingTextMessage; @@ -52,6 +53,7 @@ import org.thoughtcrime.securesms.dependencies.DatabaseComponent; import java.io.IOException; import java.security.SecureRandom; +import java.util.Arrays; import java.util.Collections; import java.util.LinkedList; import java.util.List; @@ -596,6 +598,30 @@ public class SmsDatabase extends MessagingDatabase { return threadDeleted; } + @Override + public boolean deleteMessages(long[] messageIds, long threadId) { + String[] argsArray = new String[messageIds.length]; + String[] argValues = new String[messageIds.length]; + Arrays.fill(argsArray, "?"); + + for (int i = 0; i < messageIds.length; i++) { + argValues[i] = (messageIds[i] + ""); + } + + String combinedMessageIdArgss = StringUtils.join(messageIds, ','); + String combinedMessageIds = StringUtils.join(messageIds, ','); + Log.i("MessageDatabase", "Deleting: " + combinedMessageIds); + SQLiteDatabase db = databaseHelper.getWritableDatabase(); + db.delete( + TABLE_NAME, + ID + " IN (" + StringUtils.join(argsArray, ',') + ")", + argValues + ); + boolean threadDeleted = DatabaseComponent.get(context).threadDatabase().update(threadId, false); + notifyConversationListeners(threadId); + return threadDeleted; + } + @Override public void updateThreadId(long fromId, long toId) { ContentValues contentValues = new ContentValues(1); diff --git a/libsession/src/main/java/org/session/libsession/database/MessageDataProvider.kt b/libsession/src/main/java/org/session/libsession/database/MessageDataProvider.kt index eb40df6e0..9adf6b932 100644 --- a/libsession/src/main/java/org/session/libsession/database/MessageDataProvider.kt +++ b/libsession/src/main/java/org/session/libsession/database/MessageDataProvider.kt @@ -20,7 +20,9 @@ interface MessageDataProvider { * @return pair of sms or mms table-specific ID and whether it is in SMS table */ fun getMessageID(serverId: Long, threadId: Long): Pair? + fun getMessageIDs(serverIDs: List, threadID: Long): Pair, List> fun deleteMessage(messageID: Long, isSms: Boolean) + fun deleteMessages(messageIDs: List, threadId: Long, isSms: Boolean) fun updateMessageAsDeleted(timestamp: Long, author: String) fun getServerHashForMessage(messageID: Long): String? fun getDatabaseAttachment(attachmentId: Long): DatabaseAttachment? diff --git a/libsession/src/main/java/org/session/libsession/messaging/jobs/JobQueue.kt b/libsession/src/main/java/org/session/libsession/messaging/jobs/JobQueue.kt index 215d20834..f4d71fadf 100644 --- a/libsession/src/main/java/org/session/libsession/messaging/jobs/JobQueue.kt +++ b/libsession/src/main/java/org/session/libsession/messaging/jobs/JobQueue.kt @@ -26,7 +26,7 @@ class JobQueue : JobDelegate { private val jobTimestampMap = ConcurrentHashMap() private val rxDispatcher = Executors.newSingleThreadExecutor().asCoroutineDispatcher() private val rxMediaDispatcher = Executors.newFixedThreadPool(4).asCoroutineDispatcher() - private val openGroupDispatcher = Executors.newCachedThreadPool().asCoroutineDispatcher() + private val openGroupDispatcher = Executors.newFixedThreadPool(8).asCoroutineDispatcher()//Executors.newCachedThreadPool().asCoroutineDispatcher() private val txDispatcher = Executors.newSingleThreadExecutor().asCoroutineDispatcher() private val scope = CoroutineScope(Dispatchers.Default) + SupervisorJob() private val queue = Channel(UNLIMITED) diff --git a/libsession/src/main/java/org/session/libsession/messaging/jobs/OpenGroupDeleteJob.kt b/libsession/src/main/java/org/session/libsession/messaging/jobs/OpenGroupDeleteJob.kt index c4180c002..1fb2d0df2 100644 --- a/libsession/src/main/java/org/session/libsession/messaging/jobs/OpenGroupDeleteJob.kt +++ b/libsession/src/main/java/org/session/libsession/messaging/jobs/OpenGroupDeleteJob.kt @@ -23,14 +23,27 @@ class OpenGroupDeleteJob(private val messageServerIds: LongArray, private val th val dataProvider = MessagingModuleConfiguration.shared.messageDataProvider val numberToDelete = messageServerIds.size Log.d(TAG, "Deleting $numberToDelete messages") - var numberDeleted = 0 - messageServerIds.forEach { serverId -> - val (messageId, isSms) = dataProvider.getMessageID(serverId, threadId) ?: return@forEach - dataProvider.deleteMessage(messageId, isSms) - numberDeleted++ + + // FIXME: This entire process should probably run in a transaction (with the attachment deletion happening only if it succeeded) + try { + val messageIds = dataProvider.getMessageIDs(messageServerIds.toList(), threadId) + + // Delete the SMS messages + if (messageIds.first.isNotEmpty()) { + dataProvider.deleteMessages(messageIds.first, threadId, true) + } + + // Delete the MMS messages + if (messageIds.second.isNotEmpty()) { + dataProvider.deleteMessages(messageIds.second, threadId, false) + } + + Log.d(TAG, "Deleted ${messageIds.first.size + messageIds.second.size} messages successfully") + delegate?.handleJobSucceeded(this) + } + catch (e: Exception) { + delegate?.handleJobFailed(this, e) } - Log.d(TAG, "Deleted $numberDeleted messages successfully") - delegate?.handleJobSucceeded(this) } override fun serialize(): Data = Data.Builder() From afa42daab1f1c4091c22902fe1665289c83296a6 Mon Sep 17 00:00:00 2001 From: Morgan Pretty Date: Fri, 20 Jan 2023 09:19:29 +1100 Subject: [PATCH 37/47] Updated the 'scrollToBottom' behaviour to be more efficient --- .../conversation/v2/ConversationActivityV2.kt | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationActivityV2.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationActivityV2.kt index 9b985384f..2a0d7a55e 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationActivityV2.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationActivityV2.kt @@ -315,11 +315,24 @@ class ConversationActivityV2 : PassphraseRequiredActionBarActivity(), InputBarDe restoreDraftIfNeeded() setUpUiStateObserver() binding!!.scrollToBottomButton.setOnClickListener { - val layoutManager = binding?.conversationRecyclerView?.layoutManager ?: return@setOnClickListener + val layoutManager = (binding?.conversationRecyclerView?.layoutManager as? LinearLayoutManager) ?: return@setOnClickListener + if (layoutManager.isSmoothScrolling) { binding?.conversationRecyclerView?.scrollToPosition(0) } else { - binding?.conversationRecyclerView?.smoothScrollToPosition(0) + // It looks like 'smoothScrollToPosition' will actually load all intermediate items in + // order to do the scroll, this can be very slow if there are a lot of messages so + // instead we check the current position and if there are more than 10 items to scroll + // we jump instantly to the 10th item and scroll from there (this should happen quick + // enough to give a similar scroll effect without having to load everything) + val position = layoutManager.findFirstVisibleItemPosition() + if (position > 10) { + binding?.conversationRecyclerView?.scrollToPosition(10) + } + + binding?.conversationRecyclerView?.post { + binding?.conversationRecyclerView?.smoothScrollToPosition(0) + } } } unreadCount = mmsSmsDb.getUnreadCount(viewModel.threadId) From 0ed5c5825d4c3f9932cdee6c1c9858ab651edf82 Mon Sep 17 00:00:00 2001 From: Morgan Pretty Date: Fri, 20 Jan 2023 15:24:14 +1100 Subject: [PATCH 38/47] Cleaned up some of the error logging --- .../securesms/ApplicationContext.java | 5 ++++ .../messaging/file_server/FileServerApi.kt | 6 ++++- .../messaging/jobs/BatchMessageReceiveJob.kt | 24 ++++++++++++----- .../messaging/jobs/MessageSendJob.kt | 26 ++++++++++++++----- .../messaging/open_groups/OpenGroupApi.kt | 6 ++++- .../libsession/snode/OnionRequestAPI.kt | 4 +-- .../libsession/utilities/DownloadUtilities.kt | 7 ++++- .../org/session/libsignal/utilities/HTTP.kt | 14 +++++++--- 8 files changed, 71 insertions(+), 21 deletions(-) diff --git a/app/src/main/java/org/thoughtcrime/securesms/ApplicationContext.java b/app/src/main/java/org/thoughtcrime/securesms/ApplicationContext.java index 4f1270acc..ef4f5c46a 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/ApplicationContext.java +++ b/app/src/main/java/org/thoughtcrime/securesms/ApplicationContext.java @@ -47,6 +47,7 @@ import org.session.libsession.utilities.Util; import org.session.libsession.utilities.WindowDebouncer; import org.session.libsession.utilities.dynamiclanguage.DynamicLanguageContextWrapper; import org.session.libsession.utilities.dynamiclanguage.LocaleParser; +import org.session.libsignal.utilities.HTTP; import org.session.libsignal.utilities.JsonUtil; import org.session.libsignal.utilities.Log; import org.session.libsignal.utilities.ThreadUtils; @@ -67,6 +68,7 @@ import org.thoughtcrime.securesms.groups.OpenGroupMigrator; import org.thoughtcrime.securesms.home.HomeActivity; import org.thoughtcrime.securesms.jobmanager.JobManager; import org.thoughtcrime.securesms.jobmanager.impl.JsonDataSerializer; +import org.thoughtcrime.securesms.jobmanager.impl.NetworkConstraint; import org.thoughtcrime.securesms.jobs.FastJobStorage; import org.thoughtcrime.securesms.jobs.JobManagerFactories; import org.thoughtcrime.securesms.logging.AndroidLogger; @@ -237,6 +239,9 @@ public class ApplicationContext extends Application implements DefaultLifecycleO resubmitProfilePictureIfNeeded(); loadEmojiSearchIndexIfNeeded(); EmojiSource.refresh(); + + NetworkConstraint networkConstraint = new NetworkConstraint.Factory(this).create(); + HTTP.INSTANCE.setConnectedToNetwork(networkConstraint::isMet); } @Override diff --git a/libsession/src/main/java/org/session/libsession/messaging/file_server/FileServerApi.kt b/libsession/src/main/java/org/session/libsession/messaging/file_server/FileServerApi.kt index f4972080b..01fae1f50 100644 --- a/libsession/src/main/java/org/session/libsession/messaging/file_server/FileServerApi.kt +++ b/libsession/src/main/java/org/session/libsession/messaging/file_server/FileServerApi.kt @@ -77,7 +77,11 @@ object FileServerApi { OnionRequestAPI.sendOnionRequest(requestBuilder.build(), server, serverPublicKey).map { it.body ?: throw Error.ParsingFailed }.fail { e -> - Log.e("Loki", "File server request failed.", e) + when (e) { + // No need for the stack trace for HTTP errors + is HTTP.HTTPRequestFailedException -> Log.e("Loki", "File server request failed due to error: ${e.message}") + else -> Log.e("Loki", "File server request failed", e) + } } } else { Promise.ofFail(IllegalStateException("It's currently not allowed to send non onion routed requests.")) diff --git a/libsession/src/main/java/org/session/libsession/messaging/jobs/BatchMessageReceiveJob.kt b/libsession/src/main/java/org/session/libsession/messaging/jobs/BatchMessageReceiveJob.kt index 07c104cfd..7bf330fe3 100644 --- a/libsession/src/main/java/org/session/libsession/messaging/jobs/BatchMessageReceiveJob.kt +++ b/libsession/src/main/java/org/session/libsession/messaging/jobs/BatchMessageReceiveJob.kt @@ -94,12 +94,24 @@ class BatchMessageReceiveJob( threadMap[threadID]!! += parsedParams } } catch (e: Exception) { - Log.e(TAG, "Couldn't receive message.", e) - if (e is MessageReceiver.Error && !e.isRetryable) { - Log.e(TAG, "Message failed permanently",e) - } else { - Log.e(TAG, "Message failed",e) - failures += messageParameters + when (e) { + is MessageReceiver.Error.DuplicateMessage, MessageReceiver.Error.SelfSend -> { + Log.i(TAG, "Couldn't receive message, failed with error: ${e.message}") + failures += messageParameters + } + is MessageReceiver.Error -> { + if (!e.isRetryable) { + Log.e(TAG, "Couldn't receive message, failed permanently", e) + } + else { + Log.e(TAG, "Couldn't receive message, failed", e) + failures += messageParameters + } + } + else -> { + Log.e(TAG, "Couldn't receive message, failed", e) + failures += messageParameters + } } } } diff --git a/libsession/src/main/java/org/session/libsession/messaging/jobs/MessageSendJob.kt b/libsession/src/main/java/org/session/libsession/messaging/jobs/MessageSendJob.kt index cdd9e0a3a..8ce1adf48 100644 --- a/libsession/src/main/java/org/session/libsession/messaging/jobs/MessageSendJob.kt +++ b/libsession/src/main/java/org/session/libsession/messaging/jobs/MessageSendJob.kt @@ -11,6 +11,7 @@ import org.session.libsession.messaging.messages.visible.VisibleMessage import org.session.libsession.messaging.sending_receiving.MessageSender import org.session.libsession.messaging.utilities.Data import org.session.libsession.snode.OnionRequestAPI +import org.session.libsignal.utilities.HTTP import org.session.libsignal.utilities.Log class MessageSendJob(val message: Message, val destination: Destination) : Job { @@ -67,14 +68,25 @@ class MessageSendJob(val message: Message, val destination: Destination) : Job { val promise = MessageSender.send(this.message, this.destination).success { this.handleSuccess() }.fail { exception -> - Log.e(TAG, "Couldn't send message due to error: $exception.") - if (exception is MessageSender.Error) { - if (!exception.isRetryable) { this.handlePermanentFailure(exception) } + var logStacktrace = true + + when (exception) { + // No need for the stack trace for HTTP errors + is HTTP.HTTPRequestFailedException -> { + logStacktrace = false + + if (exception.statusCode == 429) { this.handlePermanentFailure(exception) } + else { this.handleFailure(exception) } + } + is MessageSender.Error -> { + if (!exception.isRetryable) { this.handlePermanentFailure(exception) } + else { this.handleFailure(exception) } + } + else -> this.handleFailure(exception) } - if (exception is OnionRequestAPI.HTTPRequestFailedAtDestinationException && exception.statusCode == 429) { - this.handlePermanentFailure(exception) - } - this.handleFailure(exception) + + if (logStacktrace) { Log.e(TAG, "Couldn't send message due to error", exception) } + else { Log.e(TAG, "Couldn't send message due to error: ${exception.message}") } } try { promise.get() diff --git a/libsession/src/main/java/org/session/libsession/messaging/open_groups/OpenGroupApi.kt b/libsession/src/main/java/org/session/libsession/messaging/open_groups/OpenGroupApi.kt index 51f7108f5..46eff4b03 100644 --- a/libsession/src/main/java/org/session/libsession/messaging/open_groups/OpenGroupApi.kt +++ b/libsession/src/main/java/org/session/libsession/messaging/open_groups/OpenGroupApi.kt @@ -383,7 +383,11 @@ object OpenGroupApi { } return if (request.useOnionRouting) { OnionRequestAPI.sendOnionRequest(requestBuilder.build(), request.server, publicKey).fail { e -> - Log.e("SOGS", "Failed onion request", e) + when (e) { + // No need for the stack trace for HTTP errors + is HTTP.HTTPRequestFailedException -> Log.e("SOGS", "Failed onion request: ${e.message}") + else -> Log.e("SOGS", "Failed onion request", e) + } } } else { Promise.ofFail(IllegalStateException("It's currently not allowed to send non onion routed requests.")) diff --git a/libsession/src/main/java/org/session/libsession/snode/OnionRequestAPI.kt b/libsession/src/main/java/org/session/libsession/snode/OnionRequestAPI.kt index f93a7b243..bcce887a5 100644 --- a/libsession/src/main/java/org/session/libsession/snode/OnionRequestAPI.kt +++ b/libsession/src/main/java/org/session/libsession/snode/OnionRequestAPI.kt @@ -78,8 +78,8 @@ object OnionRequestAPI { // endregion class HTTPRequestFailedBlindingRequiredException(statusCode: Int, json: Map<*, *>, destination: String): HTTPRequestFailedAtDestinationException(statusCode, json, destination) - open class HTTPRequestFailedAtDestinationException(val statusCode: Int, val json: Map<*, *>, val destination: String) - : Exception("HTTP request failed at destination ($destination) with status code $statusCode.") + open class HTTPRequestFailedAtDestinationException(statusCode: Int, json: Map<*, *>, val destination: String) + : HTTP.HTTPRequestFailedException(statusCode, json, "HTTP request failed at destination ($destination) with status code $statusCode.") class InsufficientSnodesException : Exception("Couldn't find enough snodes to build a path.") private data class OnionBuildingResult( diff --git a/libsession/src/main/java/org/session/libsession/utilities/DownloadUtilities.kt b/libsession/src/main/java/org/session/libsession/utilities/DownloadUtilities.kt index 0a61d1ede..b850baa25 100644 --- a/libsession/src/main/java/org/session/libsession/utilities/DownloadUtilities.kt +++ b/libsession/src/main/java/org/session/libsession/utilities/DownloadUtilities.kt @@ -2,6 +2,7 @@ package org.session.libsession.utilities import okhttp3.HttpUrl import org.session.libsession.messaging.file_server.FileServerApi +import org.session.libsignal.utilities.HTTP import org.session.libsignal.utilities.Log import java.io.* @@ -40,7 +41,11 @@ object DownloadUtilities { outputStream.write(it) } } catch (e: Exception) { - Log.e("Loki", "Couldn't download attachment.", e) + when (e) { + // No need for the stack trace for HTTP errors + is HTTP.HTTPRequestFailedException -> Log.e("Loki", "Couldn't download attachment due to error: ${e.message}") + else -> Log.e("Loki", "Couldn't download attachment", e) + } throw e } } diff --git a/libsignal/src/main/java/org/session/libsignal/utilities/HTTP.kt b/libsignal/src/main/java/org/session/libsignal/utilities/HTTP.kt index aea1fce2d..5eac7cecd 100644 --- a/libsignal/src/main/java/org/session/libsignal/utilities/HTTP.kt +++ b/libsignal/src/main/java/org/session/libsignal/utilities/HTTP.kt @@ -12,6 +12,7 @@ import javax.net.ssl.SSLContext import javax.net.ssl.X509TrustManager object HTTP { + var isConnectedToNetwork: (() -> Boolean) = { false } private val seedNodeConnection by lazy { OkHttpClient().newBuilder() @@ -64,8 +65,12 @@ object HTTP { private const val timeout: Long = 120 - class HTTPRequestFailedException(val statusCode: Int, val json: Map<*, *>?) - : kotlin.Exception("HTTP request failed with status code $statusCode.") + open class HTTPRequestFailedException( + val statusCode: Int, + val json: Map<*, *>?, + message: String = "HTTP request failed with status code $statusCode" + ) : kotlin.Exception(message) + class HTTPNoNetworkException : HTTPRequestFailedException(0, null, "No network connection") enum class Verb(val rawValue: String) { GET("GET"), PUT("PUT"), POST("POST"), DELETE("DELETE") @@ -120,8 +125,11 @@ object HTTP { response = connection.newCall(request.build()).execute() } catch (exception: Exception) { Log.d("Loki", "${verb.rawValue} request to $url failed due to error: ${exception.localizedMessage}.") + + if (!isConnectedToNetwork()) { throw HTTPNoNetworkException() } + // Override the actual error so that we can correctly catch failed requests in OnionRequestAPI - throw HTTPRequestFailedException(0, null) + throw HTTPRequestFailedException(0, null, "HTTP request failed due to: ${exception.message}") } return when (val statusCode = response.code()) { 200 -> { From 8a4a9623ccac57f4e8eb1108593ff2d8183a5c76 Mon Sep 17 00:00:00 2001 From: Morgan Pretty Date: Fri, 20 Jan 2023 17:42:46 +1100 Subject: [PATCH 39/47] Fixed an edge case where an OpenGroup might not download it's image --- .../securesms/database/GroupDatabase.java | 13 +++++++++++++ .../thoughtcrime/securesms/database/Storage.kt | 4 ++++ .../libsession/database/StorageProtocol.kt | 1 + .../sending_receiving/pollers/OpenGroupPoller.kt | 16 ++++++++++++++-- 4 files changed, 32 insertions(+), 2 deletions(-) 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 3e23f524f..584bf3a71 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/GroupDatabase.java +++ b/app/src/main/java/org/thoughtcrime/securesms/database/GroupDatabase.java @@ -318,6 +318,19 @@ public class GroupDatabase extends Database implements LokiOpenGroupDatabaseProt notifyConversationListListeners(); } + public boolean hasDownloadedProfilePicture(String groupId) { + try (Cursor cursor = databaseHelper.getReadableDatabase().query(TABLE_NAME, new String[]{AVATAR}, GROUP_ID + " = ?", + new String[] {groupId}, + null, null, null)) + { + if (cursor != null && cursor.moveToNext()) { + return !cursor.isNull(0); + } + + return false; + } + } + public void updateMembers(String groupId, List
members) { Collections.sort(members); diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/Storage.kt b/app/src/main/java/org/thoughtcrime/securesms/database/Storage.kt index 7daae9ef0..cc31e71ec 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/Storage.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/database/Storage.kt @@ -321,6 +321,10 @@ class Storage(context: Context, helper: SQLCipherOpenHelper) : Database(context, DatabaseComponent.get(context).groupDatabase().updateProfilePicture(groupID, newValue) } + override fun hasDownloadedProfilePicture(groupID: String): Boolean { + return DatabaseComponent.get(context).groupDatabase().hasDownloadedProfilePicture(groupID) + } + override fun getReceivedMessageTimestamps(): Set { return SessionMetaProtocol.getTimestamps() } diff --git a/libsession/src/main/java/org/session/libsession/database/StorageProtocol.kt b/libsession/src/main/java/org/session/libsession/database/StorageProtocol.kt index edca7cd15..660b919c3 100644 --- a/libsession/src/main/java/org/session/libsession/database/StorageProtocol.kt +++ b/libsession/src/main/java/org/session/libsession/database/StorageProtocol.kt @@ -81,6 +81,7 @@ interface StorageProtocol { // Open Group Metadata fun updateTitle(groupID: String, newValue: String) fun updateProfilePicture(groupID: String, newValue: ByteArray) + fun hasDownloadedProfilePicture(groupID: String): Boolean fun setUserCount(room: String, server: String, newValue: Int) // Last Message Server ID diff --git a/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/pollers/OpenGroupPoller.kt b/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/pollers/OpenGroupPoller.kt index 595f7d4dc..3f4cbc312 100644 --- a/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/pollers/OpenGroupPoller.kt +++ b/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/pollers/OpenGroupPoller.kt @@ -117,6 +117,7 @@ class OpenGroupPoller(private val server: String, private val executorService: S ) { val storage = MessagingModuleConfiguration.shared.storage val groupId = "$server.$roomToken" + val dbGroupId = GroupUtil.getEncodedOpenGroupID(groupId.toByteArray()) val existingOpenGroup = storage.getOpenGroup(roomToken, server) val publicKey = existingOpenGroup?.publicKey ?: return @@ -157,8 +158,19 @@ class OpenGroupPoller(private val server: String, private val executorService: S }) } - // Start downloading the room image (if we don't have one or it's been updated) - if (pollInfo.details?.imageId != null && pollInfo.details.imageId != existingOpenGroup.imageId) { + if ( + ( + pollInfo.details != null && + pollInfo.details.imageId != null && ( + pollInfo.details.imageId != existingOpenGroup.imageId || + !storage.hasDownloadedProfilePicture(dbGroupId) + ) + ) || ( + pollInfo.details == null && + existingOpenGroup.imageId != null && + !storage.hasDownloadedProfilePicture(dbGroupId) + ) + ) { JobQueue.shared.add(GroupAvatarDownloadJob(roomToken, server)) } } From a6f09c6fefb5dc654b15d994288fdeac30f710c5 Mon Sep 17 00:00:00 2001 From: Morgan Pretty Date: Mon, 23 Jan 2023 12:42:10 +1100 Subject: [PATCH 40/47] Added some defensive coding to help prevent incorrect message statuses --- .../v2/messages/VisibleMessageView.kt | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/messages/VisibleMessageView.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/messages/VisibleMessageView.kt index cd4bd4b6e..46864fc93 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/messages/VisibleMessageView.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/messages/VisibleMessageView.kt @@ -207,10 +207,18 @@ class VisibleMessageView : LinearLayout { } if (message.isOutgoing) { val lastMessageID = mmsSmsDb.getLastMessageID(message.threadId) - binding.messageStatusTextView.isVisible = - !message.isSent || message.id == lastMessageID - binding.messageStatusImageView.isVisible = - !message.isSent || message.id == lastMessageID + binding.messageStatusTextView.isVisible = ( + textId != null && ( + !message.isSent || + message.id == lastMessageID + ) + ) + binding.messageStatusImageView.isVisible = ( + iconID != null && ( + !message.isSent || + message.id == lastMessageID + ) + ) } else { binding.messageStatusTextView.isVisible = false binding.messageStatusImageView.isVisible = false From 86b065203fdff832980d02a8f8d8609ec0604d62 Mon Sep 17 00:00:00 2001 From: 0x330a <92654767+0x330a@users.noreply.github.com> Date: Mon, 23 Jan 2023 15:43:43 +1100 Subject: [PATCH 41/47] fix: prevent very old messages (15 minutes ago) being processed to prevent endless crashes in some cases --- .../securesms/webrtc/CallMessageProcessor.kt | 12 ++++++++++++ .../java/org/session/libsession/snode/SnodeAPI.kt | 4 ++++ 2 files changed, 16 insertions(+) diff --git a/app/src/main/java/org/thoughtcrime/securesms/webrtc/CallMessageProcessor.kt b/app/src/main/java/org/thoughtcrime/securesms/webrtc/CallMessageProcessor.kt index f007ace97..bace75484 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/webrtc/CallMessageProcessor.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/webrtc/CallMessageProcessor.kt @@ -12,6 +12,7 @@ import org.session.libsession.database.StorageProtocol import org.session.libsession.messaging.calls.CallMessageType import org.session.libsession.messaging.messages.control.CallMessage import org.session.libsession.messaging.utilities.WebRtcUtils +import org.session.libsession.snode.SnodeAPI import org.session.libsession.utilities.Address import org.session.libsession.utilities.TextSecurePreferences import org.session.libsession.utilities.recipients.Recipient @@ -29,6 +30,10 @@ import org.webrtc.IceCandidate class CallMessageProcessor(private val context: Context, private val textSecurePreferences: TextSecurePreferences, lifecycle: Lifecycle, private val storage: StorageProtocol) { + companion object { + private const val VERY_EXPIRED_TIME = 15 * 60 * 1000L + } + init { lifecycle.coroutineScope.launch(IO) { while (isActive) { @@ -53,6 +58,13 @@ class CallMessageProcessor(private val context: Context, private val textSecureP } continue } + + val isVeryExpired = (nextMessage.sentTimestamp?:0) + VERY_EXPIRED_TIME < SnodeAPI.nowWithOffset + if (isVeryExpired) { + Log.e("Loki", "Dropping very expired call message") + continue + } + when (nextMessage.type) { OFFER -> incomingCall(nextMessage) ANSWER -> incomingAnswer(nextMessage) diff --git a/libsession/src/main/java/org/session/libsession/snode/SnodeAPI.kt b/libsession/src/main/java/org/session/libsession/snode/SnodeAPI.kt index 9a1795895..807759464 100644 --- a/libsession/src/main/java/org/session/libsession/snode/SnodeAPI.kt +++ b/libsession/src/main/java/org/session/libsession/snode/SnodeAPI.kt @@ -56,6 +56,10 @@ object SnodeAPI { * user's clock is incorrect. */ internal var clockOffset = 0L + + val nowWithOffset + get() = System.currentTimeMillis() + clockOffset + internal var forkInfo by observable(database.getForkInfo()) { _, oldValue, newValue -> if (newValue > oldValue) { Log.d("Loki", "Setting new fork info new: $newValue, old: $oldValue") From ebe8479e4c510d1479f63eaf7dfbd1547e0caca6 Mon Sep 17 00:00:00 2001 From: Morgan Pretty Date: Mon, 23 Jan 2023 16:14:50 +1100 Subject: [PATCH 42/47] Resolved PR comments --- app/src/main/res/values/strings.xml | 2 +- .../session/libsession/messaging/jobs/BatchMessageReceiveJob.kt | 1 - .../main/java/org/session/libsession/messaging/jobs/JobQueue.kt | 2 +- 3 files changed, 2 insertions(+), 3 deletions(-) diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 81e6ae325..50a1f0457 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -865,5 +865,5 @@ Navigate Back Close Dialog Database Upgrade Failed - Please contact support to report the error and then install an older version to continue using Session. + Please contact support to report the error. diff --git a/libsession/src/main/java/org/session/libsession/messaging/jobs/BatchMessageReceiveJob.kt b/libsession/src/main/java/org/session/libsession/messaging/jobs/BatchMessageReceiveJob.kt index 7bf330fe3..38a193b8d 100644 --- a/libsession/src/main/java/org/session/libsession/messaging/jobs/BatchMessageReceiveJob.kt +++ b/libsession/src/main/java/org/session/libsession/messaging/jobs/BatchMessageReceiveJob.kt @@ -97,7 +97,6 @@ class BatchMessageReceiveJob( when (e) { is MessageReceiver.Error.DuplicateMessage, MessageReceiver.Error.SelfSend -> { Log.i(TAG, "Couldn't receive message, failed with error: ${e.message}") - failures += messageParameters } is MessageReceiver.Error -> { if (!e.isRetryable) { diff --git a/libsession/src/main/java/org/session/libsession/messaging/jobs/JobQueue.kt b/libsession/src/main/java/org/session/libsession/messaging/jobs/JobQueue.kt index f4d71fadf..8e46f275f 100644 --- a/libsession/src/main/java/org/session/libsession/messaging/jobs/JobQueue.kt +++ b/libsession/src/main/java/org/session/libsession/messaging/jobs/JobQueue.kt @@ -26,7 +26,7 @@ class JobQueue : JobDelegate { private val jobTimestampMap = ConcurrentHashMap() private val rxDispatcher = Executors.newSingleThreadExecutor().asCoroutineDispatcher() private val rxMediaDispatcher = Executors.newFixedThreadPool(4).asCoroutineDispatcher() - private val openGroupDispatcher = Executors.newFixedThreadPool(8).asCoroutineDispatcher()//Executors.newCachedThreadPool().asCoroutineDispatcher() + private val openGroupDispatcher = Executors.newFixedThreadPool(8).asCoroutineDispatcher() private val txDispatcher = Executors.newSingleThreadExecutor().asCoroutineDispatcher() private val scope = CoroutineScope(Dispatchers.Default) + SupervisorJob() private val queue = Channel(UNLIMITED) From 586162336975660ce6133a66c51cafd35b44928a Mon Sep 17 00:00:00 2001 From: 0x330a <92654767+0x330a@users.noreply.github.com> Date: Mon, 23 Jan 2023 17:20:07 +1100 Subject: [PATCH 43/47] fix: may have been preventing new closed group message on multi-device (#1081) --- .../ReceivedMessageHandler.kt | 26 +++++++++++++++---- 1 file changed, 21 insertions(+), 5 deletions(-) diff --git a/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/ReceivedMessageHandler.kt b/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/ReceivedMessageHandler.kt index acab1f097..f2fdc6703 100644 --- a/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/ReceivedMessageHandler.kt +++ b/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/ReceivedMessageHandler.kt @@ -6,7 +6,15 @@ import org.session.libsession.messaging.MessagingModuleConfiguration import org.session.libsession.messaging.jobs.BackgroundGroupAddJob import org.session.libsession.messaging.jobs.JobQueue import org.session.libsession.messaging.messages.Message -import org.session.libsession.messaging.messages.control.* +import org.session.libsession.messaging.messages.control.CallMessage +import org.session.libsession.messaging.messages.control.ClosedGroupControlMessage +import org.session.libsession.messaging.messages.control.ConfigurationMessage +import org.session.libsession.messaging.messages.control.DataExtractionNotification +import org.session.libsession.messaging.messages.control.ExpirationTimerUpdate +import org.session.libsession.messaging.messages.control.MessageRequestResponse +import org.session.libsession.messaging.messages.control.ReadReceipt +import org.session.libsession.messaging.messages.control.TypingIndicator +import org.session.libsession.messaging.messages.control.UnsendRequest import org.session.libsession.messaging.messages.visible.Attachment import org.session.libsession.messaging.messages.visible.Reaction import org.session.libsession.messaging.messages.visible.VisibleMessage @@ -21,18 +29,26 @@ import org.session.libsession.messaging.utilities.SessionId import org.session.libsession.messaging.utilities.SodiumUtilities import org.session.libsession.messaging.utilities.WebRtcUtils import org.session.libsession.snode.SnodeAPI -import org.session.libsession.utilities.* +import org.session.libsession.utilities.Address +import org.session.libsession.utilities.GroupRecord +import org.session.libsession.utilities.GroupUtil +import org.session.libsession.utilities.ProfileKeyUtil +import org.session.libsession.utilities.SSKEnvironment +import org.session.libsession.utilities.TextSecurePreferences import org.session.libsession.utilities.recipients.Recipient import org.session.libsignal.crypto.ecc.DjbECPrivateKey import org.session.libsignal.crypto.ecc.DjbECPublicKey import org.session.libsignal.crypto.ecc.ECKeyPair import org.session.libsignal.messages.SignalServiceGroup import org.session.libsignal.protos.SignalServiceProtos -import org.session.libsignal.utilities.* import org.session.libsignal.utilities.Base64 +import org.session.libsignal.utilities.IdPrefix +import org.session.libsignal.utilities.Log import org.session.libsignal.utilities.guava.Optional +import org.session.libsignal.utilities.removingIdPrefixIfNeeded +import org.session.libsignal.utilities.toHexString import java.security.MessageDigest -import java.util.* +import java.util.LinkedList import kotlin.math.min internal fun MessageReceiver.isBlocked(publicKey: String): Boolean { @@ -407,7 +423,7 @@ private fun MessageReceiver.handleClosedGroupControlMessage(message: ClosedGroup private fun MessageReceiver.handleNewClosedGroup(message: ClosedGroupControlMessage) { val kind = message.kind!! as? ClosedGroupControlMessage.Kind.New ?: return val recipient = Recipient.from(MessagingModuleConfiguration.shared.context, Address.fromSerialized(message.sender!!), false) - if (!recipient.isApproved) return + if (!recipient.isApproved && !recipient.isLocalNumber) return val groupPublicKey = kind.publicKey.toByteArray().toHexString() val members = kind.members.map { it.toByteArray().toHexString() } val admins = kind.admins.map { it.toByteArray().toHexString() } From e0785c485472c9edd07c0745d0fe03c0c23254f3 Mon Sep 17 00:00:00 2001 From: Morgan Pretty Date: Tue, 24 Jan 2023 13:31:56 +1100 Subject: [PATCH 44/47] Added in fixes and defensive coding for the most frequent crashes Fixed a crash which could occur when dealing will calls in the background on Android 12 and newer Fixed a crash when we don't have permission to check the current call state Fixed a crash when the ScreenshotObserver receives an invalid Uri (just prevent the specific case) --- .../attachments/ScreenshotObserver.kt | 5 +++++ .../securesms/webrtc/CallManager.kt | 20 +++++++++++++++++-- .../securesms/webrtc/CallMessageProcessor.kt | 9 ++++----- 3 files changed, 27 insertions(+), 7 deletions(-) diff --git a/app/src/main/java/org/thoughtcrime/securesms/attachments/ScreenshotObserver.kt b/app/src/main/java/org/thoughtcrime/securesms/attachments/ScreenshotObserver.kt index 94c7517eb..84a9b6cfc 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/attachments/ScreenshotObserver.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/attachments/ScreenshotObserver.kt @@ -13,6 +13,11 @@ class ScreenshotObserver(private val context: Context, handler: Handler, private override fun onChange(selfChange: Boolean, uri: Uri?) { super.onChange(selfChange, uri) uri ?: return + + // There is an odd bug where we can get notified for changes to 'content://media/external' + // directly which is a protected folder, this code is to prevent that crash + if (uri.scheme == "content" && uri.host == "media" && uri.path == "/external") { return } + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { queryRelativeDataColumn(uri) } else { diff --git a/app/src/main/java/org/thoughtcrime/securesms/webrtc/CallManager.kt b/app/src/main/java/org/thoughtcrime/securesms/webrtc/CallManager.kt index 006da2b63..b7a9b6fd6 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/webrtc/CallManager.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/webrtc/CallManager.kt @@ -1,7 +1,9 @@ package org.thoughtcrime.securesms.webrtc import android.content.Context +import android.content.pm.PackageManager import android.telephony.TelephonyManager +import androidx.core.content.ContextCompat import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.asSharedFlow import kotlinx.serialization.json.Json @@ -176,8 +178,22 @@ class CallManager(context: Context, audioManager: AudioManagerCompat, private va _callStateEvents.value = newState } - fun isBusy(context: Context, callId: UUID) = callId != this.callId && (currentConnectionState != CallState.Idle - || context.getSystemService(TelephonyManager::class.java).callState != TelephonyManager.CALL_STATE_IDLE) + fun isBusy(context: Context, callId: UUID): Boolean { + // Make sure we have the permission before accessing the callState + if (ContextCompat.checkSelfPermission(context, android.Manifest.permission.READ_PHONE_STATE) == PackageManager.PERMISSION_GRANTED) { + return ( + callId != this.callId && ( + currentConnectionState != CallState.Idle || + context.getSystemService(TelephonyManager::class.java).callState != TelephonyManager.CALL_STATE_IDLE + ) + ) + } + + return ( + callId != this.callId && + currentConnectionState != CallState.Idle + ) + } fun isPreOffer() = currentConnectionState == CallState.RemotePreOffer diff --git a/app/src/main/java/org/thoughtcrime/securesms/webrtc/CallMessageProcessor.kt b/app/src/main/java/org/thoughtcrime/securesms/webrtc/CallMessageProcessor.kt index f007ace97..a85b1bcbe 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/webrtc/CallMessageProcessor.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/webrtc/CallMessageProcessor.kt @@ -78,7 +78,7 @@ class CallMessageProcessor(private val context: Context, private val textSecureP private fun incomingHangup(callMessage: CallMessage) { val callId = callMessage.callId ?: return val hangupIntent = WebRtcCallService.remoteHangupIntent(context, callId) - ContextCompat.startForegroundService(context, hangupIntent) + context.startService(hangupIntent) } private fun incomingAnswer(callMessage: CallMessage) { @@ -91,7 +91,7 @@ class CallMessageProcessor(private val context: Context, private val textSecureP sdp = sdp, callId = callId ) - ContextCompat.startForegroundService(context, answerIntent) + context.startService(answerIntent) } private fun handleIceCandidates(callMessage: CallMessage) { @@ -120,7 +120,7 @@ class CallMessageProcessor(private val context: Context, private val textSecureP callId = callId, callTime = callMessage.sentTimestamp!! ) - ContextCompat.startForegroundService(context, incomingIntent) + context.startService(incomingIntent) } private fun incomingCall(callMessage: CallMessage) { @@ -134,8 +134,7 @@ class CallMessageProcessor(private val context: Context, private val textSecureP callId = callId, callTime = callMessage.sentTimestamp!! ) - ContextCompat.startForegroundService(context, incomingIntent) - + context.startService(incomingIntent) } private fun CallMessage.iceCandidates(): List { From 05838faaf02706ae4445eeaa918a0a6ead3fd122 Mon Sep 17 00:00:00 2001 From: Morgan Pretty Date: Tue, 24 Jan 2023 15:41:44 +1100 Subject: [PATCH 45/47] Updated the unread count logic to recalculate correctly --- .../libsession/messaging/jobs/BatchMessageReceiveJob.kt | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/libsession/src/main/java/org/session/libsession/messaging/jobs/BatchMessageReceiveJob.kt b/libsession/src/main/java/org/session/libsession/messaging/jobs/BatchMessageReceiveJob.kt index 136297b3a..18a8cc4ae 100644 --- a/libsession/src/main/java/org/session/libsession/messaging/jobs/BatchMessageReceiveJob.kt +++ b/libsession/src/main/java/org/session/libsession/messaging/jobs/BatchMessageReceiveJob.kt @@ -167,10 +167,15 @@ class BatchMessageReceiveJob( // increment unreads, notify, and update thread val unreadFromMine = messageIds.map { it.value.first }.indexOfLast { it } var trueUnreadCount = messageIds.filter { !it.value.first }.size - val trueUnreadMentionCount = messageIds.filter { !it.value.first && it.value.second }.size + var trueUnreadMentionCount = messageIds.filter { !it.value.first && it.value.second }.size if (unreadFromMine >= 0) { - trueUnreadCount -= (unreadFromMine + 1) storage.markConversationAsRead(threadId, false) + + val trueUnreadIds = messageIds.keys.toList().subList(unreadFromMine + 1, messageIds.keys.count()) + trueUnreadCount = trueUnreadIds.size + trueUnreadMentionCount = messageIds + .filter { trueUnreadIds.contains(it.key) && !it.value.first && it.value.second } + .size } if (trueUnreadCount > 0) { storage.incrementUnread(threadId, trueUnreadCount, trueUnreadMentionCount) From 30615be0290ad9ebe8f3056823aa2bcbdcb56e86 Mon Sep 17 00:00:00 2001 From: Morgan Pretty Date: Tue, 24 Jan 2023 15:56:14 +1100 Subject: [PATCH 46/47] Fixed a build error in the MockDataGenerator --- .../java/org/thoughtcrime/securesms/util/MockDataGenerator.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/java/org/thoughtcrime/securesms/util/MockDataGenerator.kt b/app/src/main/java/org/thoughtcrime/securesms/util/MockDataGenerator.kt index f70ea7bd0..10d507a53 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/util/MockDataGenerator.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/util/MockDataGenerator.kt @@ -370,7 +370,7 @@ object MockDataGenerator { ) ) storage.setUserCount(roomName, serverName, numGroupMembers) - lokiThreadDB.setOpenGroupChat(OpenGroup(server = serverName, room = roomName, publicKey = randomGroupPublicKey, name = roomName, imageId = null, infoUpdates = 0), threadId) + lokiThreadDB.setOpenGroupChat(OpenGroup(server = serverName, room = roomName, publicKey = randomGroupPublicKey, name = roomName, imageId = null, canWrite = true, infoUpdates = 0), threadId) // Generate the message history (Note: Unapproved message requests will only include incoming messages) logProgress("Open Group Thread $threadIndex", "Generate $numMessages Messages") From c861d571f89d802bcc9857d47e3bbd44fd737690 Mon Sep 17 00:00:00 2001 From: 0x330a <92654767+0x330a@users.noreply.github.com> Date: Wed, 25 Jan 2023 16:48:27 +1100 Subject: [PATCH 47/47] fix: compile errors --- .../securesms/MediaGalleryAdapter.kt | 7 +- ...ionNotificationSettingsActivityContract.kt | 2 +- .../ConversationSettingsActivityContract.kt | 2 +- .../v2/messages/VisibleMessageContentView.kt | 5 +- .../database/SessionContactDatabase.kt | 15 - .../libsignal/protos/SignalServiceProtos.java | 6561 ++++++++++++----- 6 files changed, 4739 insertions(+), 1853 deletions(-) diff --git a/app/src/main/java/org/thoughtcrime/securesms/MediaGalleryAdapter.kt b/app/src/main/java/org/thoughtcrime/securesms/MediaGalleryAdapter.kt index e03258c21..d36cc4828 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/MediaGalleryAdapter.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/MediaGalleryAdapter.kt @@ -7,7 +7,6 @@ import androidx.core.view.isVisible import androidx.recyclerview.widget.RecyclerView import network.loki.messenger.R import network.loki.messenger.databinding.MediaOverviewGalleryItemBinding -import org.thoughtcrime.securesms.conversation.v2.utilities.ThumbnailView import org.thoughtcrime.securesms.database.MediaDatabase.MediaRecord import org.thoughtcrime.securesms.mms.GlideApp import org.thoughtcrime.securesms.util.MediaUtil @@ -68,11 +67,11 @@ class MediaGalleryAdapter(private val itemClickListener: ItemClickListener): Rec val slide = MediaUtil.getSlideForAttachment(itemView.context, item.attachment) if (slide != null) { - binding.image.setImageResource(glide, slide, false, false) + binding.image.root.setImageResource(glide, slide, false, null) } - binding.image.setOnClickListener { itemClickListener.onMediaClicked(item) } - binding.image.setOnLongClickListener { + binding.image.root.setOnClickListener { itemClickListener.onMediaClicked(item) } + binding.image.root.setOnLongClickListener { itemClickListener.onMediaLongClicked(item) true } diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/settings/ConversationNotificationSettingsActivityContract.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/settings/ConversationNotificationSettingsActivityContract.kt index b8a6e4852..d55d0b249 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/settings/ConversationNotificationSettingsActivityContract.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/settings/ConversationNotificationSettingsActivityContract.kt @@ -7,7 +7,7 @@ import org.thoughtcrime.securesms.conversation.v2.ConversationActivityV2 class ConversationNotificationSettingsActivityContract: ActivityResultContract() { - override fun createIntent(context: Context, input: Long?): Intent = + override fun createIntent(context: Context, input: Long): Intent = Intent(context, ConversationNotificationSettingsActivity::class.java).apply { putExtra(ConversationActivityV2.THREAD_ID, input) } diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/settings/ConversationSettingsActivityContract.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/settings/ConversationSettingsActivityContract.kt index aa06ebcd7..a79d94b3a 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/settings/ConversationSettingsActivityContract.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/settings/ConversationSettingsActivityContract.kt @@ -12,7 +12,7 @@ sealed class ConversationSettingsActivityResult { class ConversationSettingsActivityContract: ActivityResultContract() { - override fun createIntent(context: Context, input: Long?) = Intent(context, ConversationSettingsActivity::class.java).apply { + override fun createIntent(context: Context, input: Long) = Intent(context, ConversationSettingsActivity::class.java).apply { putExtra(ConversationActivityV2.THREAD_ID, input ?: -1L) } diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/messages/VisibleMessageContentView.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/messages/VisibleMessageContentView.kt index 3836daf54..060cd0b04 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/messages/VisibleMessageContentView.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/messages/VisibleMessageContentView.kt @@ -10,10 +10,8 @@ import android.text.style.ForegroundColorSpan import android.text.style.URLSpan import android.text.util.Linkify import android.util.AttributeSet -import android.view.LayoutInflater import android.view.MotionEvent import android.view.View -import android.widget.LinearLayout import androidx.annotation.ColorInt import androidx.appcompat.app.AppCompatActivity import androidx.constraintlayout.widget.ConstraintLayout @@ -26,6 +24,7 @@ import androidx.core.view.isVisible import network.loki.messenger.R import network.loki.messenger.databinding.ViewVisibleMessageContentBinding import okhttp3.HttpUrl +import org.session.libsession.messaging.MessagingModuleConfiguration import org.session.libsession.messaging.sending_receiving.attachments.AttachmentTransferProgress import org.session.libsession.messaging.sending_receiving.attachments.DatabaseAttachment import org.session.libsession.utilities.getColorFromAttr @@ -64,7 +63,6 @@ class VisibleMessageContentView : ConstraintLayout { glide: GlideRequests, thread: Recipient, searchQuery: String?, - contactIsTrusted: Boolean, onAttachmentNeedsDownload: (Long, Long) -> Unit ) { // Background @@ -90,7 +88,6 @@ class VisibleMessageContentView : ConstraintLayout { binding.bodyTextView.isVisible = false binding.quoteView.root.isVisible = false binding.linkPreviewView.root.isVisible = false - binding.untrustedView.root.isVisible = false binding.voiceMessageView.root.isVisible = false binding.documentView.root.isVisible = false binding.albumThumbnailView.root.isVisible = false diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/SessionContactDatabase.kt b/app/src/main/java/org/thoughtcrime/securesms/database/SessionContactDatabase.kt index 3e37c5c41..7080d9cb8 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/SessionContactDatabase.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/database/SessionContactDatabase.kt @@ -2,7 +2,6 @@ package org.thoughtcrime.securesms.database import android.content.ContentValues import android.content.Context -import androidx.core.database.getStringOrNull import android.database.Cursor import org.session.libsession.messaging.contacts.Contact import org.session.libsignal.utilities.Base64 @@ -87,20 +86,6 @@ class SessionContactDatabase(context: Context, helper: SQLCipherOpenHelper) : Da return contact } - fun contactFromCursor(cursor: android.database.Cursor): Contact { - val sessionID = cursor.getString(cursor.getColumnIndexOrThrow(sessionID)) - val contact = Contact(sessionID) - contact.name = cursor.getStringOrNull(cursor.getColumnIndexOrThrow(name)) - contact.nickname = cursor.getStringOrNull(cursor.getColumnIndexOrThrow(nickname)) - contact.profilePictureURL = cursor.getStringOrNull(cursor.getColumnIndexOrThrow(profilePictureURL)) - contact.profilePictureFileName = cursor.getStringOrNull(cursor.getColumnIndexOrThrow(profilePictureFileName)) - cursor.getStringOrNull(cursor.getColumnIndexOrThrow(profilePictureEncryptionKey))?.let { - contact.profilePictureEncryptionKey = Base64.decode(it) - } - contact.threadID = cursor.getLong(cursor.getColumnIndexOrThrow(threadID)) - return contact - } - fun queryContactsByName(constraint: String): Cursor { return databaseHelper.readableDatabase.query( sessionContactTable, null, " $name LIKE ? OR $nickname LIKE ?", arrayOf( diff --git a/libsignal/src/main/java/org/session/libsignal/protos/SignalServiceProtos.java b/libsignal/src/main/java/org/session/libsignal/protos/SignalServiceProtos.java index 7c44087f8..c6daddb6e 100644 --- a/libsignal/src/main/java/org/session/libsignal/protos/SignalServiceProtos.java +++ b/libsignal/src/main/java/org/session/libsignal/protos/SignalServiceProtos.java @@ -5523,20 +5523,6 @@ public final class SignalServiceProtos { org.session.libsignal.protos.SignalServiceProtos.AttachmentPointerOrBuilder getAttachmentsOrBuilder( int index); - // optional .signalservice.GroupContext group = 3; - /** - * optional .signalservice.GroupContext group = 3; - */ - boolean hasGroup(); - /** - * optional .signalservice.GroupContext group = 3; - */ - org.session.libsignal.protos.SignalServiceProtos.GroupContext getGroup(); - /** - * optional .signalservice.GroupContext group = 3; - */ - org.session.libsignal.protos.SignalServiceProtos.GroupContextOrBuilder getGroupOrBuilder(); - // optional uint32 flags = 4; /** * optional uint32 flags = 4; @@ -5686,6 +5672,20 @@ public final class SignalServiceProtos { */ com.google.protobuf.ByteString getSyncTargetBytes(); + + // optional .signalservice.DataMessage.GroupMessage groupMessage = 120; + /** + * optional .signalservice.DataMessage.GroupMessage groupMessage = 120; + */ + boolean hasGroupMessage(); + /** + * optional .signalservice.DataMessage.GroupMessage groupMessage = 120; + */ + org.session.libsignal.protos.SignalServiceProtos.DataMessage.GroupMessage getGroupMessage(); + /** + * optional .signalservice.DataMessage.GroupMessage groupMessage = 120; + */ + org.session.libsignal.protos.SignalServiceProtos.DataMessage.GroupMessageOrBuilder getGroupMessageOrBuilder(); } /** * Protobuf type {@code signalservice.DataMessage} @@ -5751,42 +5751,29 @@ public final class SignalServiceProtos { attachments_.add(input.readMessage(org.session.libsignal.protos.SignalServiceProtos.AttachmentPointer.PARSER, extensionRegistry)); break; } - case 26: { - org.session.libsignal.protos.SignalServiceProtos.GroupContext.Builder subBuilder = null; - if (((bitField0_ & 0x00000002) == 0x00000002)) { - subBuilder = group_.toBuilder(); - } - group_ = input.readMessage(org.session.libsignal.protos.SignalServiceProtos.GroupContext.PARSER, extensionRegistry); - if (subBuilder != null) { - subBuilder.mergeFrom(group_); - group_ = subBuilder.buildPartial(); - } - bitField0_ |= 0x00000002; - break; - } case 32: { - bitField0_ |= 0x00000004; + bitField0_ |= 0x00000002; flags_ = input.readUInt32(); break; } case 40: { - bitField0_ |= 0x00000008; + bitField0_ |= 0x00000004; expireTimer_ = input.readUInt32(); break; } case 50: { - bitField0_ |= 0x00000010; + bitField0_ |= 0x00000008; profileKey_ = input.readBytes(); break; } case 56: { - bitField0_ |= 0x00000020; + bitField0_ |= 0x00000010; timestamp_ = input.readUInt64(); break; } case 66: { org.session.libsignal.protos.SignalServiceProtos.DataMessage.Quote.Builder subBuilder = null; - if (((bitField0_ & 0x00000040) == 0x00000040)) { + if (((bitField0_ & 0x00000020) == 0x00000020)) { subBuilder = quote_.toBuilder(); } quote_ = input.readMessage(org.session.libsignal.protos.SignalServiceProtos.DataMessage.Quote.PARSER, extensionRegistry); @@ -5794,20 +5781,20 @@ public final class SignalServiceProtos { subBuilder.mergeFrom(quote_); quote_ = subBuilder.buildPartial(); } - bitField0_ |= 0x00000040; + bitField0_ |= 0x00000020; break; } case 82: { - if (!((mutable_bitField0_ & 0x00000100) == 0x00000100)) { + if (!((mutable_bitField0_ & 0x00000080) == 0x00000080)) { preview_ = new java.util.ArrayList(); - mutable_bitField0_ |= 0x00000100; + mutable_bitField0_ |= 0x00000080; } preview_.add(input.readMessage(org.session.libsignal.protos.SignalServiceProtos.DataMessage.Preview.PARSER, extensionRegistry)); break; } case 90: { org.session.libsignal.protos.SignalServiceProtos.DataMessage.Reaction.Builder subBuilder = null; - if (((bitField0_ & 0x00000080) == 0x00000080)) { + if (((bitField0_ & 0x00000040) == 0x00000040)) { subBuilder = reaction_.toBuilder(); } reaction_ = input.readMessage(org.session.libsignal.protos.SignalServiceProtos.DataMessage.Reaction.PARSER, extensionRegistry); @@ -5815,12 +5802,12 @@ public final class SignalServiceProtos { subBuilder.mergeFrom(reaction_); reaction_ = subBuilder.buildPartial(); } - bitField0_ |= 0x00000080; + bitField0_ |= 0x00000040; break; } case 810: { org.session.libsignal.protos.SignalServiceProtos.DataMessage.LokiProfile.Builder subBuilder = null; - if (((bitField0_ & 0x00000100) == 0x00000100)) { + if (((bitField0_ & 0x00000080) == 0x00000080)) { subBuilder = profile_.toBuilder(); } profile_ = input.readMessage(org.session.libsignal.protos.SignalServiceProtos.DataMessage.LokiProfile.PARSER, extensionRegistry); @@ -5828,12 +5815,12 @@ public final class SignalServiceProtos { subBuilder.mergeFrom(profile_); profile_ = subBuilder.buildPartial(); } - bitField0_ |= 0x00000100; + bitField0_ |= 0x00000080; break; } case 818: { org.session.libsignal.protos.SignalServiceProtos.DataMessage.OpenGroupInvitation.Builder subBuilder = null; - if (((bitField0_ & 0x00000200) == 0x00000200)) { + if (((bitField0_ & 0x00000100) == 0x00000100)) { subBuilder = openGroupInvitation_.toBuilder(); } openGroupInvitation_ = input.readMessage(org.session.libsignal.protos.SignalServiceProtos.DataMessage.OpenGroupInvitation.PARSER, extensionRegistry); @@ -5841,12 +5828,12 @@ public final class SignalServiceProtos { subBuilder.mergeFrom(openGroupInvitation_); openGroupInvitation_ = subBuilder.buildPartial(); } - bitField0_ |= 0x00000200; + bitField0_ |= 0x00000100; break; } case 834: { org.session.libsignal.protos.SignalServiceProtos.DataMessage.ClosedGroupControlMessage.Builder subBuilder = null; - if (((bitField0_ & 0x00000400) == 0x00000400)) { + if (((bitField0_ & 0x00000200) == 0x00000200)) { subBuilder = closedGroupControlMessage_.toBuilder(); } closedGroupControlMessage_ = input.readMessage(org.session.libsignal.protos.SignalServiceProtos.DataMessage.ClosedGroupControlMessage.PARSER, extensionRegistry); @@ -5854,14 +5841,27 @@ public final class SignalServiceProtos { subBuilder.mergeFrom(closedGroupControlMessage_); closedGroupControlMessage_ = subBuilder.buildPartial(); } - bitField0_ |= 0x00000400; + bitField0_ |= 0x00000200; break; } case 842: { - bitField0_ |= 0x00000800; + bitField0_ |= 0x00000400; syncTarget_ = input.readBytes(); break; } + case 962: { + org.session.libsignal.protos.SignalServiceProtos.DataMessage.GroupMessage.Builder subBuilder = null; + if (((bitField0_ & 0x00000800) == 0x00000800)) { + subBuilder = groupMessage_.toBuilder(); + } + groupMessage_ = input.readMessage(org.session.libsignal.protos.SignalServiceProtos.DataMessage.GroupMessage.PARSER, extensionRegistry); + if (subBuilder != null) { + subBuilder.mergeFrom(groupMessage_); + groupMessage_ = subBuilder.buildPartial(); + } + bitField0_ |= 0x00000800; + break; + } } } } catch (com.google.protobuf.InvalidProtocolBufferException e) { @@ -5873,7 +5873,7 @@ public final class SignalServiceProtos { if (((mutable_bitField0_ & 0x00000002) == 0x00000002)) { attachments_ = java.util.Collections.unmodifiableList(attachments_); } - if (((mutable_bitField0_ & 0x00000100) == 0x00000100)) { + if (((mutable_bitField0_ & 0x00000080) == 0x00000080)) { preview_ = java.util.Collections.unmodifiableList(preview_); } this.unknownFields = unknownFields.build(); @@ -10396,6 +10396,1125 @@ public final class SignalServiceProtos { // @@protoc_insertion_point(class_scope:signalservice.DataMessage.OpenGroupInvitation) } + public interface GroupMessageOrBuilder + extends com.google.protobuf.MessageOrBuilder { + + // optional .signalservice.GroupDeleteMessage deleteMessage = 31; + /** + * optional .signalservice.GroupDeleteMessage deleteMessage = 31; + */ + boolean hasDeleteMessage(); + /** + * optional .signalservice.GroupDeleteMessage deleteMessage = 31; + */ + org.session.libsignal.protos.SignalServiceProtos.GroupDeleteMessage getDeleteMessage(); + /** + * optional .signalservice.GroupDeleteMessage deleteMessage = 31; + */ + org.session.libsignal.protos.SignalServiceProtos.GroupDeleteMessageOrBuilder getDeleteMessageOrBuilder(); + + // optional .signalservice.GroupMemberLeftMessage memberLeftMessage = 32; + /** + * optional .signalservice.GroupMemberLeftMessage memberLeftMessage = 32; + */ + boolean hasMemberLeftMessage(); + /** + * optional .signalservice.GroupMemberLeftMessage memberLeftMessage = 32; + */ + org.session.libsignal.protos.SignalServiceProtos.GroupMemberLeftMessage getMemberLeftMessage(); + /** + * optional .signalservice.GroupMemberLeftMessage memberLeftMessage = 32; + */ + org.session.libsignal.protos.SignalServiceProtos.GroupMemberLeftMessageOrBuilder getMemberLeftMessageOrBuilder(); + + // optional .signalservice.GroupInviteMessage inviteMessage = 33; + /** + * optional .signalservice.GroupInviteMessage inviteMessage = 33; + */ + boolean hasInviteMessage(); + /** + * optional .signalservice.GroupInviteMessage inviteMessage = 33; + */ + org.session.libsignal.protos.SignalServiceProtos.GroupInviteMessage getInviteMessage(); + /** + * optional .signalservice.GroupInviteMessage inviteMessage = 33; + */ + org.session.libsignal.protos.SignalServiceProtos.GroupInviteMessageOrBuilder getInviteMessageOrBuilder(); + + // optional .signalservice.GroupPromoteMessage promoteMessage = 34; + /** + * optional .signalservice.GroupPromoteMessage promoteMessage = 34; + */ + boolean hasPromoteMessage(); + /** + * optional .signalservice.GroupPromoteMessage promoteMessage = 34; + */ + org.session.libsignal.protos.SignalServiceProtos.GroupPromoteMessage getPromoteMessage(); + /** + * optional .signalservice.GroupPromoteMessage promoteMessage = 34; + */ + org.session.libsignal.protos.SignalServiceProtos.GroupPromoteMessageOrBuilder getPromoteMessageOrBuilder(); + } + /** + * Protobuf type {@code signalservice.DataMessage.GroupMessage} + */ + public static final class GroupMessage extends + com.google.protobuf.GeneratedMessage + implements GroupMessageOrBuilder { + // Use GroupMessage.newBuilder() to construct. + private GroupMessage(com.google.protobuf.GeneratedMessage.Builder builder) { + super(builder); + this.unknownFields = builder.getUnknownFields(); + } + private GroupMessage(boolean noInit) { this.unknownFields = com.google.protobuf.UnknownFieldSet.getDefaultInstance(); } + + private static final GroupMessage defaultInstance; + public static GroupMessage getDefaultInstance() { + return defaultInstance; + } + + public GroupMessage getDefaultInstanceForType() { + return defaultInstance; + } + + private final com.google.protobuf.UnknownFieldSet unknownFields; + @java.lang.Override + public final com.google.protobuf.UnknownFieldSet + getUnknownFields() { + return this.unknownFields; + } + private GroupMessage( + com.google.protobuf.CodedInputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + initFields(); + int mutable_bitField0_ = 0; + com.google.protobuf.UnknownFieldSet.Builder unknownFields = + com.google.protobuf.UnknownFieldSet.newBuilder(); + try { + boolean done = false; + while (!done) { + int tag = input.readTag(); + switch (tag) { + case 0: + done = true; + break; + default: { + if (!parseUnknownField(input, unknownFields, + extensionRegistry, tag)) { + done = true; + } + break; + } + case 250: { + org.session.libsignal.protos.SignalServiceProtos.GroupDeleteMessage.Builder subBuilder = null; + if (((bitField0_ & 0x00000001) == 0x00000001)) { + subBuilder = deleteMessage_.toBuilder(); + } + deleteMessage_ = input.readMessage(org.session.libsignal.protos.SignalServiceProtos.GroupDeleteMessage.PARSER, extensionRegistry); + if (subBuilder != null) { + subBuilder.mergeFrom(deleteMessage_); + deleteMessage_ = subBuilder.buildPartial(); + } + bitField0_ |= 0x00000001; + break; + } + case 258: { + org.session.libsignal.protos.SignalServiceProtos.GroupMemberLeftMessage.Builder subBuilder = null; + if (((bitField0_ & 0x00000002) == 0x00000002)) { + subBuilder = memberLeftMessage_.toBuilder(); + } + memberLeftMessage_ = input.readMessage(org.session.libsignal.protos.SignalServiceProtos.GroupMemberLeftMessage.PARSER, extensionRegistry); + if (subBuilder != null) { + subBuilder.mergeFrom(memberLeftMessage_); + memberLeftMessage_ = subBuilder.buildPartial(); + } + bitField0_ |= 0x00000002; + break; + } + case 266: { + org.session.libsignal.protos.SignalServiceProtos.GroupInviteMessage.Builder subBuilder = null; + if (((bitField0_ & 0x00000004) == 0x00000004)) { + subBuilder = inviteMessage_.toBuilder(); + } + inviteMessage_ = input.readMessage(org.session.libsignal.protos.SignalServiceProtos.GroupInviteMessage.PARSER, extensionRegistry); + if (subBuilder != null) { + subBuilder.mergeFrom(inviteMessage_); + inviteMessage_ = subBuilder.buildPartial(); + } + bitField0_ |= 0x00000004; + break; + } + case 274: { + org.session.libsignal.protos.SignalServiceProtos.GroupPromoteMessage.Builder subBuilder = null; + if (((bitField0_ & 0x00000008) == 0x00000008)) { + subBuilder = promoteMessage_.toBuilder(); + } + promoteMessage_ = input.readMessage(org.session.libsignal.protos.SignalServiceProtos.GroupPromoteMessage.PARSER, extensionRegistry); + if (subBuilder != null) { + subBuilder.mergeFrom(promoteMessage_); + promoteMessage_ = subBuilder.buildPartial(); + } + bitField0_ |= 0x00000008; + break; + } + } + } + } catch (com.google.protobuf.InvalidProtocolBufferException e) { + throw e.setUnfinishedMessage(this); + } catch (java.io.IOException e) { + throw new com.google.protobuf.InvalidProtocolBufferException( + e.getMessage()).setUnfinishedMessage(this); + } finally { + this.unknownFields = unknownFields.build(); + makeExtensionsImmutable(); + } + } + public static final com.google.protobuf.Descriptors.Descriptor + getDescriptor() { + return org.session.libsignal.protos.SignalServiceProtos.internal_static_signalservice_DataMessage_GroupMessage_descriptor; + } + + protected com.google.protobuf.GeneratedMessage.FieldAccessorTable + internalGetFieldAccessorTable() { + return org.session.libsignal.protos.SignalServiceProtos.internal_static_signalservice_DataMessage_GroupMessage_fieldAccessorTable + .ensureFieldAccessorsInitialized( + org.session.libsignal.protos.SignalServiceProtos.DataMessage.GroupMessage.class, org.session.libsignal.protos.SignalServiceProtos.DataMessage.GroupMessage.Builder.class); + } + + public static com.google.protobuf.Parser PARSER = + new com.google.protobuf.AbstractParser() { + public GroupMessage parsePartialFrom( + com.google.protobuf.CodedInputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + return new GroupMessage(input, extensionRegistry); + } + }; + + @java.lang.Override + public com.google.protobuf.Parser getParserForType() { + return PARSER; + } + + private int bitField0_; + // optional .signalservice.GroupDeleteMessage deleteMessage = 31; + public static final int DELETEMESSAGE_FIELD_NUMBER = 31; + private org.session.libsignal.protos.SignalServiceProtos.GroupDeleteMessage deleteMessage_; + /** + * optional .signalservice.GroupDeleteMessage deleteMessage = 31; + */ + public boolean hasDeleteMessage() { + return ((bitField0_ & 0x00000001) == 0x00000001); + } + /** + * optional .signalservice.GroupDeleteMessage deleteMessage = 31; + */ + public org.session.libsignal.protos.SignalServiceProtos.GroupDeleteMessage getDeleteMessage() { + return deleteMessage_; + } + /** + * optional .signalservice.GroupDeleteMessage deleteMessage = 31; + */ + public org.session.libsignal.protos.SignalServiceProtos.GroupDeleteMessageOrBuilder getDeleteMessageOrBuilder() { + return deleteMessage_; + } + + // optional .signalservice.GroupMemberLeftMessage memberLeftMessage = 32; + public static final int MEMBERLEFTMESSAGE_FIELD_NUMBER = 32; + private org.session.libsignal.protos.SignalServiceProtos.GroupMemberLeftMessage memberLeftMessage_; + /** + * optional .signalservice.GroupMemberLeftMessage memberLeftMessage = 32; + */ + public boolean hasMemberLeftMessage() { + return ((bitField0_ & 0x00000002) == 0x00000002); + } + /** + * optional .signalservice.GroupMemberLeftMessage memberLeftMessage = 32; + */ + public org.session.libsignal.protos.SignalServiceProtos.GroupMemberLeftMessage getMemberLeftMessage() { + return memberLeftMessage_; + } + /** + * optional .signalservice.GroupMemberLeftMessage memberLeftMessage = 32; + */ + public org.session.libsignal.protos.SignalServiceProtos.GroupMemberLeftMessageOrBuilder getMemberLeftMessageOrBuilder() { + return memberLeftMessage_; + } + + // optional .signalservice.GroupInviteMessage inviteMessage = 33; + public static final int INVITEMESSAGE_FIELD_NUMBER = 33; + private org.session.libsignal.protos.SignalServiceProtos.GroupInviteMessage inviteMessage_; + /** + * optional .signalservice.GroupInviteMessage inviteMessage = 33; + */ + public boolean hasInviteMessage() { + return ((bitField0_ & 0x00000004) == 0x00000004); + } + /** + * optional .signalservice.GroupInviteMessage inviteMessage = 33; + */ + public org.session.libsignal.protos.SignalServiceProtos.GroupInviteMessage getInviteMessage() { + return inviteMessage_; + } + /** + * optional .signalservice.GroupInviteMessage inviteMessage = 33; + */ + public org.session.libsignal.protos.SignalServiceProtos.GroupInviteMessageOrBuilder getInviteMessageOrBuilder() { + return inviteMessage_; + } + + // optional .signalservice.GroupPromoteMessage promoteMessage = 34; + public static final int PROMOTEMESSAGE_FIELD_NUMBER = 34; + private org.session.libsignal.protos.SignalServiceProtos.GroupPromoteMessage promoteMessage_; + /** + * optional .signalservice.GroupPromoteMessage promoteMessage = 34; + */ + public boolean hasPromoteMessage() { + return ((bitField0_ & 0x00000008) == 0x00000008); + } + /** + * optional .signalservice.GroupPromoteMessage promoteMessage = 34; + */ + public org.session.libsignal.protos.SignalServiceProtos.GroupPromoteMessage getPromoteMessage() { + return promoteMessage_; + } + /** + * optional .signalservice.GroupPromoteMessage promoteMessage = 34; + */ + public org.session.libsignal.protos.SignalServiceProtos.GroupPromoteMessageOrBuilder getPromoteMessageOrBuilder() { + return promoteMessage_; + } + + private void initFields() { + deleteMessage_ = org.session.libsignal.protos.SignalServiceProtos.GroupDeleteMessage.getDefaultInstance(); + memberLeftMessage_ = org.session.libsignal.protos.SignalServiceProtos.GroupMemberLeftMessage.getDefaultInstance(); + inviteMessage_ = org.session.libsignal.protos.SignalServiceProtos.GroupInviteMessage.getDefaultInstance(); + promoteMessage_ = org.session.libsignal.protos.SignalServiceProtos.GroupPromoteMessage.getDefaultInstance(); + } + private byte memoizedIsInitialized = -1; + public final boolean isInitialized() { + byte isInitialized = memoizedIsInitialized; + if (isInitialized != -1) return isInitialized == 1; + + if (hasDeleteMessage()) { + if (!getDeleteMessage().isInitialized()) { + memoizedIsInitialized = 0; + return false; + } + } + if (hasInviteMessage()) { + if (!getInviteMessage().isInitialized()) { + memoizedIsInitialized = 0; + return false; + } + } + if (hasPromoteMessage()) { + if (!getPromoteMessage().isInitialized()) { + memoizedIsInitialized = 0; + return false; + } + } + memoizedIsInitialized = 1; + return true; + } + + public void writeTo(com.google.protobuf.CodedOutputStream output) + throws java.io.IOException { + getSerializedSize(); + if (((bitField0_ & 0x00000001) == 0x00000001)) { + output.writeMessage(31, deleteMessage_); + } + if (((bitField0_ & 0x00000002) == 0x00000002)) { + output.writeMessage(32, memberLeftMessage_); + } + if (((bitField0_ & 0x00000004) == 0x00000004)) { + output.writeMessage(33, inviteMessage_); + } + if (((bitField0_ & 0x00000008) == 0x00000008)) { + output.writeMessage(34, promoteMessage_); + } + getUnknownFields().writeTo(output); + } + + private int memoizedSerializedSize = -1; + public int getSerializedSize() { + int size = memoizedSerializedSize; + if (size != -1) return size; + + size = 0; + if (((bitField0_ & 0x00000001) == 0x00000001)) { + size += com.google.protobuf.CodedOutputStream + .computeMessageSize(31, deleteMessage_); + } + if (((bitField0_ & 0x00000002) == 0x00000002)) { + size += com.google.protobuf.CodedOutputStream + .computeMessageSize(32, memberLeftMessage_); + } + if (((bitField0_ & 0x00000004) == 0x00000004)) { + size += com.google.protobuf.CodedOutputStream + .computeMessageSize(33, inviteMessage_); + } + if (((bitField0_ & 0x00000008) == 0x00000008)) { + size += com.google.protobuf.CodedOutputStream + .computeMessageSize(34, promoteMessage_); + } + size += getUnknownFields().getSerializedSize(); + memoizedSerializedSize = size; + return size; + } + + private static final long serialVersionUID = 0L; + @java.lang.Override + protected java.lang.Object writeReplace() + throws java.io.ObjectStreamException { + return super.writeReplace(); + } + + public static org.session.libsignal.protos.SignalServiceProtos.DataMessage.GroupMessage parseFrom( + com.google.protobuf.ByteString data) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data); + } + public static org.session.libsignal.protos.SignalServiceProtos.DataMessage.GroupMessage parseFrom( + com.google.protobuf.ByteString data, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data, extensionRegistry); + } + public static org.session.libsignal.protos.SignalServiceProtos.DataMessage.GroupMessage parseFrom(byte[] data) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data); + } + public static org.session.libsignal.protos.SignalServiceProtos.DataMessage.GroupMessage parseFrom( + byte[] data, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data, extensionRegistry); + } + public static org.session.libsignal.protos.SignalServiceProtos.DataMessage.GroupMessage parseFrom(java.io.InputStream input) + throws java.io.IOException { + return PARSER.parseFrom(input); + } + public static org.session.libsignal.protos.SignalServiceProtos.DataMessage.GroupMessage parseFrom( + java.io.InputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws java.io.IOException { + return PARSER.parseFrom(input, extensionRegistry); + } + public static org.session.libsignal.protos.SignalServiceProtos.DataMessage.GroupMessage parseDelimitedFrom(java.io.InputStream input) + throws java.io.IOException { + return PARSER.parseDelimitedFrom(input); + } + public static org.session.libsignal.protos.SignalServiceProtos.DataMessage.GroupMessage parseDelimitedFrom( + java.io.InputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws java.io.IOException { + return PARSER.parseDelimitedFrom(input, extensionRegistry); + } + public static org.session.libsignal.protos.SignalServiceProtos.DataMessage.GroupMessage parseFrom( + com.google.protobuf.CodedInputStream input) + throws java.io.IOException { + return PARSER.parseFrom(input); + } + public static org.session.libsignal.protos.SignalServiceProtos.DataMessage.GroupMessage parseFrom( + com.google.protobuf.CodedInputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws java.io.IOException { + return PARSER.parseFrom(input, extensionRegistry); + } + + public static Builder newBuilder() { return Builder.create(); } + public Builder newBuilderForType() { return newBuilder(); } + public static Builder newBuilder(org.session.libsignal.protos.SignalServiceProtos.DataMessage.GroupMessage prototype) { + return newBuilder().mergeFrom(prototype); + } + public Builder toBuilder() { return newBuilder(this); } + + @java.lang.Override + protected Builder newBuilderForType( + com.google.protobuf.GeneratedMessage.BuilderParent parent) { + Builder builder = new Builder(parent); + return builder; + } + /** + * Protobuf type {@code signalservice.DataMessage.GroupMessage} + */ + public static final class Builder extends + com.google.protobuf.GeneratedMessage.Builder + implements org.session.libsignal.protos.SignalServiceProtos.DataMessage.GroupMessageOrBuilder { + public static final com.google.protobuf.Descriptors.Descriptor + getDescriptor() { + return org.session.libsignal.protos.SignalServiceProtos.internal_static_signalservice_DataMessage_GroupMessage_descriptor; + } + + protected com.google.protobuf.GeneratedMessage.FieldAccessorTable + internalGetFieldAccessorTable() { + return org.session.libsignal.protos.SignalServiceProtos.internal_static_signalservice_DataMessage_GroupMessage_fieldAccessorTable + .ensureFieldAccessorsInitialized( + org.session.libsignal.protos.SignalServiceProtos.DataMessage.GroupMessage.class, org.session.libsignal.protos.SignalServiceProtos.DataMessage.GroupMessage.Builder.class); + } + + // Construct using org.session.libsignal.protos.SignalServiceProtos.DataMessage.GroupMessage.newBuilder() + private Builder() { + maybeForceBuilderInitialization(); + } + + private Builder( + com.google.protobuf.GeneratedMessage.BuilderParent parent) { + super(parent); + maybeForceBuilderInitialization(); + } + private void maybeForceBuilderInitialization() { + if (com.google.protobuf.GeneratedMessage.alwaysUseFieldBuilders) { + getDeleteMessageFieldBuilder(); + getMemberLeftMessageFieldBuilder(); + getInviteMessageFieldBuilder(); + getPromoteMessageFieldBuilder(); + } + } + private static Builder create() { + return new Builder(); + } + + public Builder clear() { + super.clear(); + if (deleteMessageBuilder_ == null) { + deleteMessage_ = org.session.libsignal.protos.SignalServiceProtos.GroupDeleteMessage.getDefaultInstance(); + } else { + deleteMessageBuilder_.clear(); + } + bitField0_ = (bitField0_ & ~0x00000001); + if (memberLeftMessageBuilder_ == null) { + memberLeftMessage_ = org.session.libsignal.protos.SignalServiceProtos.GroupMemberLeftMessage.getDefaultInstance(); + } else { + memberLeftMessageBuilder_.clear(); + } + bitField0_ = (bitField0_ & ~0x00000002); + if (inviteMessageBuilder_ == null) { + inviteMessage_ = org.session.libsignal.protos.SignalServiceProtos.GroupInviteMessage.getDefaultInstance(); + } else { + inviteMessageBuilder_.clear(); + } + bitField0_ = (bitField0_ & ~0x00000004); + if (promoteMessageBuilder_ == null) { + promoteMessage_ = org.session.libsignal.protos.SignalServiceProtos.GroupPromoteMessage.getDefaultInstance(); + } else { + promoteMessageBuilder_.clear(); + } + bitField0_ = (bitField0_ & ~0x00000008); + return this; + } + + public Builder clone() { + return create().mergeFrom(buildPartial()); + } + + public com.google.protobuf.Descriptors.Descriptor + getDescriptorForType() { + return org.session.libsignal.protos.SignalServiceProtos.internal_static_signalservice_DataMessage_GroupMessage_descriptor; + } + + public org.session.libsignal.protos.SignalServiceProtos.DataMessage.GroupMessage getDefaultInstanceForType() { + return org.session.libsignal.protos.SignalServiceProtos.DataMessage.GroupMessage.getDefaultInstance(); + } + + public org.session.libsignal.protos.SignalServiceProtos.DataMessage.GroupMessage build() { + org.session.libsignal.protos.SignalServiceProtos.DataMessage.GroupMessage result = buildPartial(); + if (!result.isInitialized()) { + throw newUninitializedMessageException(result); + } + return result; + } + + public org.session.libsignal.protos.SignalServiceProtos.DataMessage.GroupMessage buildPartial() { + org.session.libsignal.protos.SignalServiceProtos.DataMessage.GroupMessage result = new org.session.libsignal.protos.SignalServiceProtos.DataMessage.GroupMessage(this); + int from_bitField0_ = bitField0_; + int to_bitField0_ = 0; + if (((from_bitField0_ & 0x00000001) == 0x00000001)) { + to_bitField0_ |= 0x00000001; + } + if (deleteMessageBuilder_ == null) { + result.deleteMessage_ = deleteMessage_; + } else { + result.deleteMessage_ = deleteMessageBuilder_.build(); + } + if (((from_bitField0_ & 0x00000002) == 0x00000002)) { + to_bitField0_ |= 0x00000002; + } + if (memberLeftMessageBuilder_ == null) { + result.memberLeftMessage_ = memberLeftMessage_; + } else { + result.memberLeftMessage_ = memberLeftMessageBuilder_.build(); + } + if (((from_bitField0_ & 0x00000004) == 0x00000004)) { + to_bitField0_ |= 0x00000004; + } + if (inviteMessageBuilder_ == null) { + result.inviteMessage_ = inviteMessage_; + } else { + result.inviteMessage_ = inviteMessageBuilder_.build(); + } + if (((from_bitField0_ & 0x00000008) == 0x00000008)) { + to_bitField0_ |= 0x00000008; + } + if (promoteMessageBuilder_ == null) { + result.promoteMessage_ = promoteMessage_; + } else { + result.promoteMessage_ = promoteMessageBuilder_.build(); + } + result.bitField0_ = to_bitField0_; + onBuilt(); + return result; + } + + public Builder mergeFrom(com.google.protobuf.Message other) { + if (other instanceof org.session.libsignal.protos.SignalServiceProtos.DataMessage.GroupMessage) { + return mergeFrom((org.session.libsignal.protos.SignalServiceProtos.DataMessage.GroupMessage)other); + } else { + super.mergeFrom(other); + return this; + } + } + + public Builder mergeFrom(org.session.libsignal.protos.SignalServiceProtos.DataMessage.GroupMessage other) { + if (other == org.session.libsignal.protos.SignalServiceProtos.DataMessage.GroupMessage.getDefaultInstance()) return this; + if (other.hasDeleteMessage()) { + mergeDeleteMessage(other.getDeleteMessage()); + } + if (other.hasMemberLeftMessage()) { + mergeMemberLeftMessage(other.getMemberLeftMessage()); + } + if (other.hasInviteMessage()) { + mergeInviteMessage(other.getInviteMessage()); + } + if (other.hasPromoteMessage()) { + mergePromoteMessage(other.getPromoteMessage()); + } + this.mergeUnknownFields(other.getUnknownFields()); + return this; + } + + public final boolean isInitialized() { + if (hasDeleteMessage()) { + if (!getDeleteMessage().isInitialized()) { + + return false; + } + } + if (hasInviteMessage()) { + if (!getInviteMessage().isInitialized()) { + + return false; + } + } + if (hasPromoteMessage()) { + if (!getPromoteMessage().isInitialized()) { + + return false; + } + } + return true; + } + + public Builder mergeFrom( + com.google.protobuf.CodedInputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws java.io.IOException { + org.session.libsignal.protos.SignalServiceProtos.DataMessage.GroupMessage parsedMessage = null; + try { + parsedMessage = PARSER.parsePartialFrom(input, extensionRegistry); + } catch (com.google.protobuf.InvalidProtocolBufferException e) { + parsedMessage = (org.session.libsignal.protos.SignalServiceProtos.DataMessage.GroupMessage) e.getUnfinishedMessage(); + throw e; + } finally { + if (parsedMessage != null) { + mergeFrom(parsedMessage); + } + } + return this; + } + private int bitField0_; + + // optional .signalservice.GroupDeleteMessage deleteMessage = 31; + private org.session.libsignal.protos.SignalServiceProtos.GroupDeleteMessage deleteMessage_ = org.session.libsignal.protos.SignalServiceProtos.GroupDeleteMessage.getDefaultInstance(); + private com.google.protobuf.SingleFieldBuilder< + org.session.libsignal.protos.SignalServiceProtos.GroupDeleteMessage, org.session.libsignal.protos.SignalServiceProtos.GroupDeleteMessage.Builder, org.session.libsignal.protos.SignalServiceProtos.GroupDeleteMessageOrBuilder> deleteMessageBuilder_; + /** + * optional .signalservice.GroupDeleteMessage deleteMessage = 31; + */ + public boolean hasDeleteMessage() { + return ((bitField0_ & 0x00000001) == 0x00000001); + } + /** + * optional .signalservice.GroupDeleteMessage deleteMessage = 31; + */ + public org.session.libsignal.protos.SignalServiceProtos.GroupDeleteMessage getDeleteMessage() { + if (deleteMessageBuilder_ == null) { + return deleteMessage_; + } else { + return deleteMessageBuilder_.getMessage(); + } + } + /** + * optional .signalservice.GroupDeleteMessage deleteMessage = 31; + */ + public Builder setDeleteMessage(org.session.libsignal.protos.SignalServiceProtos.GroupDeleteMessage value) { + if (deleteMessageBuilder_ == null) { + if (value == null) { + throw new NullPointerException(); + } + deleteMessage_ = value; + onChanged(); + } else { + deleteMessageBuilder_.setMessage(value); + } + bitField0_ |= 0x00000001; + return this; + } + /** + * optional .signalservice.GroupDeleteMessage deleteMessage = 31; + */ + public Builder setDeleteMessage( + org.session.libsignal.protos.SignalServiceProtos.GroupDeleteMessage.Builder builderForValue) { + if (deleteMessageBuilder_ == null) { + deleteMessage_ = builderForValue.build(); + onChanged(); + } else { + deleteMessageBuilder_.setMessage(builderForValue.build()); + } + bitField0_ |= 0x00000001; + return this; + } + /** + * optional .signalservice.GroupDeleteMessage deleteMessage = 31; + */ + public Builder mergeDeleteMessage(org.session.libsignal.protos.SignalServiceProtos.GroupDeleteMessage value) { + if (deleteMessageBuilder_ == null) { + if (((bitField0_ & 0x00000001) == 0x00000001) && + deleteMessage_ != org.session.libsignal.protos.SignalServiceProtos.GroupDeleteMessage.getDefaultInstance()) { + deleteMessage_ = + org.session.libsignal.protos.SignalServiceProtos.GroupDeleteMessage.newBuilder(deleteMessage_).mergeFrom(value).buildPartial(); + } else { + deleteMessage_ = value; + } + onChanged(); + } else { + deleteMessageBuilder_.mergeFrom(value); + } + bitField0_ |= 0x00000001; + return this; + } + /** + * optional .signalservice.GroupDeleteMessage deleteMessage = 31; + */ + public Builder clearDeleteMessage() { + if (deleteMessageBuilder_ == null) { + deleteMessage_ = org.session.libsignal.protos.SignalServiceProtos.GroupDeleteMessage.getDefaultInstance(); + onChanged(); + } else { + deleteMessageBuilder_.clear(); + } + bitField0_ = (bitField0_ & ~0x00000001); + return this; + } + /** + * optional .signalservice.GroupDeleteMessage deleteMessage = 31; + */ + public org.session.libsignal.protos.SignalServiceProtos.GroupDeleteMessage.Builder getDeleteMessageBuilder() { + bitField0_ |= 0x00000001; + onChanged(); + return getDeleteMessageFieldBuilder().getBuilder(); + } + /** + * optional .signalservice.GroupDeleteMessage deleteMessage = 31; + */ + public org.session.libsignal.protos.SignalServiceProtos.GroupDeleteMessageOrBuilder getDeleteMessageOrBuilder() { + if (deleteMessageBuilder_ != null) { + return deleteMessageBuilder_.getMessageOrBuilder(); + } else { + return deleteMessage_; + } + } + /** + * optional .signalservice.GroupDeleteMessage deleteMessage = 31; + */ + private com.google.protobuf.SingleFieldBuilder< + org.session.libsignal.protos.SignalServiceProtos.GroupDeleteMessage, org.session.libsignal.protos.SignalServiceProtos.GroupDeleteMessage.Builder, org.session.libsignal.protos.SignalServiceProtos.GroupDeleteMessageOrBuilder> + getDeleteMessageFieldBuilder() { + if (deleteMessageBuilder_ == null) { + deleteMessageBuilder_ = new com.google.protobuf.SingleFieldBuilder< + org.session.libsignal.protos.SignalServiceProtos.GroupDeleteMessage, org.session.libsignal.protos.SignalServiceProtos.GroupDeleteMessage.Builder, org.session.libsignal.protos.SignalServiceProtos.GroupDeleteMessageOrBuilder>( + deleteMessage_, + getParentForChildren(), + isClean()); + deleteMessage_ = null; + } + return deleteMessageBuilder_; + } + + // optional .signalservice.GroupMemberLeftMessage memberLeftMessage = 32; + private org.session.libsignal.protos.SignalServiceProtos.GroupMemberLeftMessage memberLeftMessage_ = org.session.libsignal.protos.SignalServiceProtos.GroupMemberLeftMessage.getDefaultInstance(); + private com.google.protobuf.SingleFieldBuilder< + org.session.libsignal.protos.SignalServiceProtos.GroupMemberLeftMessage, org.session.libsignal.protos.SignalServiceProtos.GroupMemberLeftMessage.Builder, org.session.libsignal.protos.SignalServiceProtos.GroupMemberLeftMessageOrBuilder> memberLeftMessageBuilder_; + /** + * optional .signalservice.GroupMemberLeftMessage memberLeftMessage = 32; + */ + public boolean hasMemberLeftMessage() { + return ((bitField0_ & 0x00000002) == 0x00000002); + } + /** + * optional .signalservice.GroupMemberLeftMessage memberLeftMessage = 32; + */ + public org.session.libsignal.protos.SignalServiceProtos.GroupMemberLeftMessage getMemberLeftMessage() { + if (memberLeftMessageBuilder_ == null) { + return memberLeftMessage_; + } else { + return memberLeftMessageBuilder_.getMessage(); + } + } + /** + * optional .signalservice.GroupMemberLeftMessage memberLeftMessage = 32; + */ + public Builder setMemberLeftMessage(org.session.libsignal.protos.SignalServiceProtos.GroupMemberLeftMessage value) { + if (memberLeftMessageBuilder_ == null) { + if (value == null) { + throw new NullPointerException(); + } + memberLeftMessage_ = value; + onChanged(); + } else { + memberLeftMessageBuilder_.setMessage(value); + } + bitField0_ |= 0x00000002; + return this; + } + /** + * optional .signalservice.GroupMemberLeftMessage memberLeftMessage = 32; + */ + public Builder setMemberLeftMessage( + org.session.libsignal.protos.SignalServiceProtos.GroupMemberLeftMessage.Builder builderForValue) { + if (memberLeftMessageBuilder_ == null) { + memberLeftMessage_ = builderForValue.build(); + onChanged(); + } else { + memberLeftMessageBuilder_.setMessage(builderForValue.build()); + } + bitField0_ |= 0x00000002; + return this; + } + /** + * optional .signalservice.GroupMemberLeftMessage memberLeftMessage = 32; + */ + public Builder mergeMemberLeftMessage(org.session.libsignal.protos.SignalServiceProtos.GroupMemberLeftMessage value) { + if (memberLeftMessageBuilder_ == null) { + if (((bitField0_ & 0x00000002) == 0x00000002) && + memberLeftMessage_ != org.session.libsignal.protos.SignalServiceProtos.GroupMemberLeftMessage.getDefaultInstance()) { + memberLeftMessage_ = + org.session.libsignal.protos.SignalServiceProtos.GroupMemberLeftMessage.newBuilder(memberLeftMessage_).mergeFrom(value).buildPartial(); + } else { + memberLeftMessage_ = value; + } + onChanged(); + } else { + memberLeftMessageBuilder_.mergeFrom(value); + } + bitField0_ |= 0x00000002; + return this; + } + /** + * optional .signalservice.GroupMemberLeftMessage memberLeftMessage = 32; + */ + public Builder clearMemberLeftMessage() { + if (memberLeftMessageBuilder_ == null) { + memberLeftMessage_ = org.session.libsignal.protos.SignalServiceProtos.GroupMemberLeftMessage.getDefaultInstance(); + onChanged(); + } else { + memberLeftMessageBuilder_.clear(); + } + bitField0_ = (bitField0_ & ~0x00000002); + return this; + } + /** + * optional .signalservice.GroupMemberLeftMessage memberLeftMessage = 32; + */ + public org.session.libsignal.protos.SignalServiceProtos.GroupMemberLeftMessage.Builder getMemberLeftMessageBuilder() { + bitField0_ |= 0x00000002; + onChanged(); + return getMemberLeftMessageFieldBuilder().getBuilder(); + } + /** + * optional .signalservice.GroupMemberLeftMessage memberLeftMessage = 32; + */ + public org.session.libsignal.protos.SignalServiceProtos.GroupMemberLeftMessageOrBuilder getMemberLeftMessageOrBuilder() { + if (memberLeftMessageBuilder_ != null) { + return memberLeftMessageBuilder_.getMessageOrBuilder(); + } else { + return memberLeftMessage_; + } + } + /** + * optional .signalservice.GroupMemberLeftMessage memberLeftMessage = 32; + */ + private com.google.protobuf.SingleFieldBuilder< + org.session.libsignal.protos.SignalServiceProtos.GroupMemberLeftMessage, org.session.libsignal.protos.SignalServiceProtos.GroupMemberLeftMessage.Builder, org.session.libsignal.protos.SignalServiceProtos.GroupMemberLeftMessageOrBuilder> + getMemberLeftMessageFieldBuilder() { + if (memberLeftMessageBuilder_ == null) { + memberLeftMessageBuilder_ = new com.google.protobuf.SingleFieldBuilder< + org.session.libsignal.protos.SignalServiceProtos.GroupMemberLeftMessage, org.session.libsignal.protos.SignalServiceProtos.GroupMemberLeftMessage.Builder, org.session.libsignal.protos.SignalServiceProtos.GroupMemberLeftMessageOrBuilder>( + memberLeftMessage_, + getParentForChildren(), + isClean()); + memberLeftMessage_ = null; + } + return memberLeftMessageBuilder_; + } + + // optional .signalservice.GroupInviteMessage inviteMessage = 33; + private org.session.libsignal.protos.SignalServiceProtos.GroupInviteMessage inviteMessage_ = org.session.libsignal.protos.SignalServiceProtos.GroupInviteMessage.getDefaultInstance(); + private com.google.protobuf.SingleFieldBuilder< + org.session.libsignal.protos.SignalServiceProtos.GroupInviteMessage, org.session.libsignal.protos.SignalServiceProtos.GroupInviteMessage.Builder, org.session.libsignal.protos.SignalServiceProtos.GroupInviteMessageOrBuilder> inviteMessageBuilder_; + /** + * optional .signalservice.GroupInviteMessage inviteMessage = 33; + */ + public boolean hasInviteMessage() { + return ((bitField0_ & 0x00000004) == 0x00000004); + } + /** + * optional .signalservice.GroupInviteMessage inviteMessage = 33; + */ + public org.session.libsignal.protos.SignalServiceProtos.GroupInviteMessage getInviteMessage() { + if (inviteMessageBuilder_ == null) { + return inviteMessage_; + } else { + return inviteMessageBuilder_.getMessage(); + } + } + /** + * optional .signalservice.GroupInviteMessage inviteMessage = 33; + */ + public Builder setInviteMessage(org.session.libsignal.protos.SignalServiceProtos.GroupInviteMessage value) { + if (inviteMessageBuilder_ == null) { + if (value == null) { + throw new NullPointerException(); + } + inviteMessage_ = value; + onChanged(); + } else { + inviteMessageBuilder_.setMessage(value); + } + bitField0_ |= 0x00000004; + return this; + } + /** + * optional .signalservice.GroupInviteMessage inviteMessage = 33; + */ + public Builder setInviteMessage( + org.session.libsignal.protos.SignalServiceProtos.GroupInviteMessage.Builder builderForValue) { + if (inviteMessageBuilder_ == null) { + inviteMessage_ = builderForValue.build(); + onChanged(); + } else { + inviteMessageBuilder_.setMessage(builderForValue.build()); + } + bitField0_ |= 0x00000004; + return this; + } + /** + * optional .signalservice.GroupInviteMessage inviteMessage = 33; + */ + public Builder mergeInviteMessage(org.session.libsignal.protos.SignalServiceProtos.GroupInviteMessage value) { + if (inviteMessageBuilder_ == null) { + if (((bitField0_ & 0x00000004) == 0x00000004) && + inviteMessage_ != org.session.libsignal.protos.SignalServiceProtos.GroupInviteMessage.getDefaultInstance()) { + inviteMessage_ = + org.session.libsignal.protos.SignalServiceProtos.GroupInviteMessage.newBuilder(inviteMessage_).mergeFrom(value).buildPartial(); + } else { + inviteMessage_ = value; + } + onChanged(); + } else { + inviteMessageBuilder_.mergeFrom(value); + } + bitField0_ |= 0x00000004; + return this; + } + /** + * optional .signalservice.GroupInviteMessage inviteMessage = 33; + */ + public Builder clearInviteMessage() { + if (inviteMessageBuilder_ == null) { + inviteMessage_ = org.session.libsignal.protos.SignalServiceProtos.GroupInviteMessage.getDefaultInstance(); + onChanged(); + } else { + inviteMessageBuilder_.clear(); + } + bitField0_ = (bitField0_ & ~0x00000004); + return this; + } + /** + * optional .signalservice.GroupInviteMessage inviteMessage = 33; + */ + public org.session.libsignal.protos.SignalServiceProtos.GroupInviteMessage.Builder getInviteMessageBuilder() { + bitField0_ |= 0x00000004; + onChanged(); + return getInviteMessageFieldBuilder().getBuilder(); + } + /** + * optional .signalservice.GroupInviteMessage inviteMessage = 33; + */ + public org.session.libsignal.protos.SignalServiceProtos.GroupInviteMessageOrBuilder getInviteMessageOrBuilder() { + if (inviteMessageBuilder_ != null) { + return inviteMessageBuilder_.getMessageOrBuilder(); + } else { + return inviteMessage_; + } + } + /** + * optional .signalservice.GroupInviteMessage inviteMessage = 33; + */ + private com.google.protobuf.SingleFieldBuilder< + org.session.libsignal.protos.SignalServiceProtos.GroupInviteMessage, org.session.libsignal.protos.SignalServiceProtos.GroupInviteMessage.Builder, org.session.libsignal.protos.SignalServiceProtos.GroupInviteMessageOrBuilder> + getInviteMessageFieldBuilder() { + if (inviteMessageBuilder_ == null) { + inviteMessageBuilder_ = new com.google.protobuf.SingleFieldBuilder< + org.session.libsignal.protos.SignalServiceProtos.GroupInviteMessage, org.session.libsignal.protos.SignalServiceProtos.GroupInviteMessage.Builder, org.session.libsignal.protos.SignalServiceProtos.GroupInviteMessageOrBuilder>( + inviteMessage_, + getParentForChildren(), + isClean()); + inviteMessage_ = null; + } + return inviteMessageBuilder_; + } + + // optional .signalservice.GroupPromoteMessage promoteMessage = 34; + private org.session.libsignal.protos.SignalServiceProtos.GroupPromoteMessage promoteMessage_ = org.session.libsignal.protos.SignalServiceProtos.GroupPromoteMessage.getDefaultInstance(); + private com.google.protobuf.SingleFieldBuilder< + org.session.libsignal.protos.SignalServiceProtos.GroupPromoteMessage, org.session.libsignal.protos.SignalServiceProtos.GroupPromoteMessage.Builder, org.session.libsignal.protos.SignalServiceProtos.GroupPromoteMessageOrBuilder> promoteMessageBuilder_; + /** + * optional .signalservice.GroupPromoteMessage promoteMessage = 34; + */ + public boolean hasPromoteMessage() { + return ((bitField0_ & 0x00000008) == 0x00000008); + } + /** + * optional .signalservice.GroupPromoteMessage promoteMessage = 34; + */ + public org.session.libsignal.protos.SignalServiceProtos.GroupPromoteMessage getPromoteMessage() { + if (promoteMessageBuilder_ == null) { + return promoteMessage_; + } else { + return promoteMessageBuilder_.getMessage(); + } + } + /** + * optional .signalservice.GroupPromoteMessage promoteMessage = 34; + */ + public Builder setPromoteMessage(org.session.libsignal.protos.SignalServiceProtos.GroupPromoteMessage value) { + if (promoteMessageBuilder_ == null) { + if (value == null) { + throw new NullPointerException(); + } + promoteMessage_ = value; + onChanged(); + } else { + promoteMessageBuilder_.setMessage(value); + } + bitField0_ |= 0x00000008; + return this; + } + /** + * optional .signalservice.GroupPromoteMessage promoteMessage = 34; + */ + public Builder setPromoteMessage( + org.session.libsignal.protos.SignalServiceProtos.GroupPromoteMessage.Builder builderForValue) { + if (promoteMessageBuilder_ == null) { + promoteMessage_ = builderForValue.build(); + onChanged(); + } else { + promoteMessageBuilder_.setMessage(builderForValue.build()); + } + bitField0_ |= 0x00000008; + return this; + } + /** + * optional .signalservice.GroupPromoteMessage promoteMessage = 34; + */ + public Builder mergePromoteMessage(org.session.libsignal.protos.SignalServiceProtos.GroupPromoteMessage value) { + if (promoteMessageBuilder_ == null) { + if (((bitField0_ & 0x00000008) == 0x00000008) && + promoteMessage_ != org.session.libsignal.protos.SignalServiceProtos.GroupPromoteMessage.getDefaultInstance()) { + promoteMessage_ = + org.session.libsignal.protos.SignalServiceProtos.GroupPromoteMessage.newBuilder(promoteMessage_).mergeFrom(value).buildPartial(); + } else { + promoteMessage_ = value; + } + onChanged(); + } else { + promoteMessageBuilder_.mergeFrom(value); + } + bitField0_ |= 0x00000008; + return this; + } + /** + * optional .signalservice.GroupPromoteMessage promoteMessage = 34; + */ + public Builder clearPromoteMessage() { + if (promoteMessageBuilder_ == null) { + promoteMessage_ = org.session.libsignal.protos.SignalServiceProtos.GroupPromoteMessage.getDefaultInstance(); + onChanged(); + } else { + promoteMessageBuilder_.clear(); + } + bitField0_ = (bitField0_ & ~0x00000008); + return this; + } + /** + * optional .signalservice.GroupPromoteMessage promoteMessage = 34; + */ + public org.session.libsignal.protos.SignalServiceProtos.GroupPromoteMessage.Builder getPromoteMessageBuilder() { + bitField0_ |= 0x00000008; + onChanged(); + return getPromoteMessageFieldBuilder().getBuilder(); + } + /** + * optional .signalservice.GroupPromoteMessage promoteMessage = 34; + */ + public org.session.libsignal.protos.SignalServiceProtos.GroupPromoteMessageOrBuilder getPromoteMessageOrBuilder() { + if (promoteMessageBuilder_ != null) { + return promoteMessageBuilder_.getMessageOrBuilder(); + } else { + return promoteMessage_; + } + } + /** + * optional .signalservice.GroupPromoteMessage promoteMessage = 34; + */ + private com.google.protobuf.SingleFieldBuilder< + org.session.libsignal.protos.SignalServiceProtos.GroupPromoteMessage, org.session.libsignal.protos.SignalServiceProtos.GroupPromoteMessage.Builder, org.session.libsignal.protos.SignalServiceProtos.GroupPromoteMessageOrBuilder> + getPromoteMessageFieldBuilder() { + if (promoteMessageBuilder_ == null) { + promoteMessageBuilder_ = new com.google.protobuf.SingleFieldBuilder< + org.session.libsignal.protos.SignalServiceProtos.GroupPromoteMessage, org.session.libsignal.protos.SignalServiceProtos.GroupPromoteMessage.Builder, org.session.libsignal.protos.SignalServiceProtos.GroupPromoteMessageOrBuilder>( + promoteMessage_, + getParentForChildren(), + isClean()); + promoteMessage_ = null; + } + return promoteMessageBuilder_; + } + + // @@protoc_insertion_point(builder_scope:signalservice.DataMessage.GroupMessage) + } + + static { + defaultInstance = new GroupMessage(true); + defaultInstance.initFields(); + } + + // @@protoc_insertion_point(class_scope:signalservice.DataMessage.GroupMessage) + } + public interface ClosedGroupControlMessageOrBuilder extends com.google.protobuf.MessageOrBuilder { @@ -10518,6 +11637,26 @@ public final class SignalServiceProtos { * optional uint32 expirationTimer = 8; */ int getExpirationTimer(); + + // optional bytes memberPrivateKey = 9; + /** + * optional bytes memberPrivateKey = 9; + */ + boolean hasMemberPrivateKey(); + /** + * optional bytes memberPrivateKey = 9; + */ + com.google.protobuf.ByteString getMemberPrivateKey(); + + // optional bytes privateKey = 10; + /** + * optional bytes privateKey = 10; + */ + boolean hasPrivateKey(); + /** + * optional bytes privateKey = 10; + */ + com.google.protobuf.ByteString getPrivateKey(); } /** * Protobuf type {@code signalservice.DataMessage.ClosedGroupControlMessage} @@ -10633,6 +11772,16 @@ public final class SignalServiceProtos { expirationTimer_ = input.readUInt32(); break; } + case 74: { + bitField0_ |= 0x00000020; + memberPrivateKey_ = input.readBytes(); + break; + } + case 82: { + bitField0_ |= 0x00000040; + privateKey_ = input.readBytes(); + break; + } } } } catch (com.google.protobuf.InvalidProtocolBufferException e) { @@ -10730,6 +11879,46 @@ public final class SignalServiceProtos { * MEMBER_LEFT = 7; */ MEMBER_LEFT(5, 7), + /** + * INVITE = 9; + * + *
+         * publicKey, name, memberPrivateKey
+         * 
+ */ + INVITE(6, 9), + /** + * PROMOTE = 10; + * + *
+         * publicKey, privateKey
+         * 
+ */ + PROMOTE(7, 10), + /** + * DELETE_GROUP = 11; + * + *
+         * publicKey, members
+         * 
+ */ + DELETE_GROUP(8, 11), + /** + * DELETE_MESSAGES = 12; + * + *
+         * publicKey
+         * 
+ */ + DELETE_MESSAGES(9, 12), + /** + * DELETE_ATTACHMENTS = 13; + * + *
+         * publicKey
+         * 
+ */ + DELETE_ATTACHMENTS(10, 13), ; /** @@ -10776,6 +11965,46 @@ public final class SignalServiceProtos { * MEMBER_LEFT = 7; */ public static final int MEMBER_LEFT_VALUE = 7; + /** + * INVITE = 9; + * + *
+         * publicKey, name, memberPrivateKey
+         * 
+ */ + public static final int INVITE_VALUE = 9; + /** + * PROMOTE = 10; + * + *
+         * publicKey, privateKey
+         * 
+ */ + public static final int PROMOTE_VALUE = 10; + /** + * DELETE_GROUP = 11; + * + *
+         * publicKey, members
+         * 
+ */ + public static final int DELETE_GROUP_VALUE = 11; + /** + * DELETE_MESSAGES = 12; + * + *
+         * publicKey
+         * 
+ */ + public static final int DELETE_MESSAGES_VALUE = 12; + /** + * DELETE_ATTACHMENTS = 13; + * + *
+         * publicKey
+         * 
+ */ + public static final int DELETE_ATTACHMENTS_VALUE = 13; public final int getNumber() { return value; } @@ -10788,6 +12017,11 @@ public final class SignalServiceProtos { case 5: return MEMBERS_ADDED; case 6: return MEMBERS_REMOVED; case 7: return MEMBER_LEFT; + case 9: return INVITE; + case 10: return PROMOTE; + case 11: return DELETE_GROUP; + case 12: return DELETE_MESSAGES; + case 13: return DELETE_ATTACHMENTS; default: return null; } } @@ -11606,6 +12840,38 @@ public final class SignalServiceProtos { return expirationTimer_; } + // optional bytes memberPrivateKey = 9; + public static final int MEMBERPRIVATEKEY_FIELD_NUMBER = 9; + private com.google.protobuf.ByteString memberPrivateKey_; + /** + * optional bytes memberPrivateKey = 9; + */ + public boolean hasMemberPrivateKey() { + return ((bitField0_ & 0x00000020) == 0x00000020); + } + /** + * optional bytes memberPrivateKey = 9; + */ + public com.google.protobuf.ByteString getMemberPrivateKey() { + return memberPrivateKey_; + } + + // optional bytes privateKey = 10; + public static final int PRIVATEKEY_FIELD_NUMBER = 10; + private com.google.protobuf.ByteString privateKey_; + /** + * optional bytes privateKey = 10; + */ + public boolean hasPrivateKey() { + return ((bitField0_ & 0x00000040) == 0x00000040); + } + /** + * optional bytes privateKey = 10; + */ + public com.google.protobuf.ByteString getPrivateKey() { + return privateKey_; + } + private void initFields() { type_ = org.session.libsignal.protos.SignalServiceProtos.DataMessage.ClosedGroupControlMessage.Type.NEW; publicKey_ = com.google.protobuf.ByteString.EMPTY; @@ -11615,6 +12881,8 @@ public final class SignalServiceProtos { admins_ = java.util.Collections.emptyList(); wrappers_ = java.util.Collections.emptyList(); expirationTimer_ = 0; + memberPrivateKey_ = com.google.protobuf.ByteString.EMPTY; + privateKey_ = com.google.protobuf.ByteString.EMPTY; } private byte memoizedIsInitialized = -1; public final boolean isInitialized() { @@ -11668,6 +12936,12 @@ public final class SignalServiceProtos { if (((bitField0_ & 0x00000010) == 0x00000010)) { output.writeUInt32(8, expirationTimer_); } + if (((bitField0_ & 0x00000020) == 0x00000020)) { + output.writeBytes(9, memberPrivateKey_); + } + if (((bitField0_ & 0x00000040) == 0x00000040)) { + output.writeBytes(10, privateKey_); + } getUnknownFields().writeTo(output); } @@ -11719,6 +12993,14 @@ public final class SignalServiceProtos { size += com.google.protobuf.CodedOutputStream .computeUInt32Size(8, expirationTimer_); } + if (((bitField0_ & 0x00000020) == 0x00000020)) { + size += com.google.protobuf.CodedOutputStream + .computeBytesSize(9, memberPrivateKey_); + } + if (((bitField0_ & 0x00000040) == 0x00000040)) { + size += com.google.protobuf.CodedOutputStream + .computeBytesSize(10, privateKey_); + } size += getUnknownFields().getSerializedSize(); memoizedSerializedSize = size; return size; @@ -11861,6 +13143,10 @@ public final class SignalServiceProtos { } expirationTimer_ = 0; bitField0_ = (bitField0_ & ~0x00000080); + memberPrivateKey_ = com.google.protobuf.ByteString.EMPTY; + bitField0_ = (bitField0_ & ~0x00000100); + privateKey_ = com.google.protobuf.ByteString.EMPTY; + bitField0_ = (bitField0_ & ~0x00000200); return this; } @@ -11932,6 +13218,14 @@ public final class SignalServiceProtos { to_bitField0_ |= 0x00000010; } result.expirationTimer_ = expirationTimer_; + if (((from_bitField0_ & 0x00000100) == 0x00000100)) { + to_bitField0_ |= 0x00000020; + } + result.memberPrivateKey_ = memberPrivateKey_; + if (((from_bitField0_ & 0x00000200) == 0x00000200)) { + to_bitField0_ |= 0x00000040; + } + result.privateKey_ = privateKey_; result.bitField0_ = to_bitField0_; onBuilt(); return result; @@ -12011,6 +13305,12 @@ public final class SignalServiceProtos { if (other.hasExpirationTimer()) { setExpirationTimer(other.getExpirationTimer()); } + if (other.hasMemberPrivateKey()) { + setMemberPrivateKey(other.getMemberPrivateKey()); + } + if (other.hasPrivateKey()) { + setPrivateKey(other.getPrivateKey()); + } this.mergeUnknownFields(other.getUnknownFields()); return this; } @@ -12750,6 +14050,78 @@ public final class SignalServiceProtos { return this; } + // optional bytes memberPrivateKey = 9; + private com.google.protobuf.ByteString memberPrivateKey_ = com.google.protobuf.ByteString.EMPTY; + /** + * optional bytes memberPrivateKey = 9; + */ + public boolean hasMemberPrivateKey() { + return ((bitField0_ & 0x00000100) == 0x00000100); + } + /** + * optional bytes memberPrivateKey = 9; + */ + public com.google.protobuf.ByteString getMemberPrivateKey() { + return memberPrivateKey_; + } + /** + * optional bytes memberPrivateKey = 9; + */ + public Builder setMemberPrivateKey(com.google.protobuf.ByteString value) { + if (value == null) { + throw new NullPointerException(); + } + bitField0_ |= 0x00000100; + memberPrivateKey_ = value; + onChanged(); + return this; + } + /** + * optional bytes memberPrivateKey = 9; + */ + public Builder clearMemberPrivateKey() { + bitField0_ = (bitField0_ & ~0x00000100); + memberPrivateKey_ = getDefaultInstance().getMemberPrivateKey(); + onChanged(); + return this; + } + + // optional bytes privateKey = 10; + private com.google.protobuf.ByteString privateKey_ = com.google.protobuf.ByteString.EMPTY; + /** + * optional bytes privateKey = 10; + */ + public boolean hasPrivateKey() { + return ((bitField0_ & 0x00000200) == 0x00000200); + } + /** + * optional bytes privateKey = 10; + */ + public com.google.protobuf.ByteString getPrivateKey() { + return privateKey_; + } + /** + * optional bytes privateKey = 10; + */ + public Builder setPrivateKey(com.google.protobuf.ByteString value) { + if (value == null) { + throw new NullPointerException(); + } + bitField0_ |= 0x00000200; + privateKey_ = value; + onChanged(); + return this; + } + /** + * optional bytes privateKey = 10; + */ + public Builder clearPrivateKey() { + bitField0_ = (bitField0_ & ~0x00000200); + privateKey_ = getDefaultInstance().getPrivateKey(); + onChanged(); + return this; + } + // @@protoc_insertion_point(builder_scope:signalservice.DataMessage.ClosedGroupControlMessage) } @@ -13857,28 +15229,6 @@ public final class SignalServiceProtos { return attachments_.get(index); } - // optional .signalservice.GroupContext group = 3; - public static final int GROUP_FIELD_NUMBER = 3; - private org.session.libsignal.protos.SignalServiceProtos.GroupContext group_; - /** - * optional .signalservice.GroupContext group = 3; - */ - public boolean hasGroup() { - return ((bitField0_ & 0x00000002) == 0x00000002); - } - /** - * optional .signalservice.GroupContext group = 3; - */ - public org.session.libsignal.protos.SignalServiceProtos.GroupContext getGroup() { - return group_; - } - /** - * optional .signalservice.GroupContext group = 3; - */ - public org.session.libsignal.protos.SignalServiceProtos.GroupContextOrBuilder getGroupOrBuilder() { - return group_; - } - // optional uint32 flags = 4; public static final int FLAGS_FIELD_NUMBER = 4; private int flags_; @@ -13886,7 +15236,7 @@ public final class SignalServiceProtos { * optional uint32 flags = 4; */ public boolean hasFlags() { - return ((bitField0_ & 0x00000004) == 0x00000004); + return ((bitField0_ & 0x00000002) == 0x00000002); } /** * optional uint32 flags = 4; @@ -13902,7 +15252,7 @@ public final class SignalServiceProtos { * optional uint32 expireTimer = 5; */ public boolean hasExpireTimer() { - return ((bitField0_ & 0x00000008) == 0x00000008); + return ((bitField0_ & 0x00000004) == 0x00000004); } /** * optional uint32 expireTimer = 5; @@ -13918,7 +15268,7 @@ public final class SignalServiceProtos { * optional bytes profileKey = 6; */ public boolean hasProfileKey() { - return ((bitField0_ & 0x00000010) == 0x00000010); + return ((bitField0_ & 0x00000008) == 0x00000008); } /** * optional bytes profileKey = 6; @@ -13934,7 +15284,7 @@ public final class SignalServiceProtos { * optional uint64 timestamp = 7; */ public boolean hasTimestamp() { - return ((bitField0_ & 0x00000020) == 0x00000020); + return ((bitField0_ & 0x00000010) == 0x00000010); } /** * optional uint64 timestamp = 7; @@ -13950,7 +15300,7 @@ public final class SignalServiceProtos { * optional .signalservice.DataMessage.Quote quote = 8; */ public boolean hasQuote() { - return ((bitField0_ & 0x00000040) == 0x00000040); + return ((bitField0_ & 0x00000020) == 0x00000020); } /** * optional .signalservice.DataMessage.Quote quote = 8; @@ -14008,7 +15358,7 @@ public final class SignalServiceProtos { * optional .signalservice.DataMessage.Reaction reaction = 11; */ public boolean hasReaction() { - return ((bitField0_ & 0x00000080) == 0x00000080); + return ((bitField0_ & 0x00000040) == 0x00000040); } /** * optional .signalservice.DataMessage.Reaction reaction = 11; @@ -14030,7 +15380,7 @@ public final class SignalServiceProtos { * optional .signalservice.DataMessage.LokiProfile profile = 101; */ public boolean hasProfile() { - return ((bitField0_ & 0x00000100) == 0x00000100); + return ((bitField0_ & 0x00000080) == 0x00000080); } /** * optional .signalservice.DataMessage.LokiProfile profile = 101; @@ -14052,7 +15402,7 @@ public final class SignalServiceProtos { * optional .signalservice.DataMessage.OpenGroupInvitation openGroupInvitation = 102; */ public boolean hasOpenGroupInvitation() { - return ((bitField0_ & 0x00000200) == 0x00000200); + return ((bitField0_ & 0x00000100) == 0x00000100); } /** * optional .signalservice.DataMessage.OpenGroupInvitation openGroupInvitation = 102; @@ -14074,7 +15424,7 @@ public final class SignalServiceProtos { * optional .signalservice.DataMessage.ClosedGroupControlMessage closedGroupControlMessage = 104; */ public boolean hasClosedGroupControlMessage() { - return ((bitField0_ & 0x00000400) == 0x00000400); + return ((bitField0_ & 0x00000200) == 0x00000200); } /** * optional .signalservice.DataMessage.ClosedGroupControlMessage closedGroupControlMessage = 104; @@ -14096,7 +15446,7 @@ public final class SignalServiceProtos { * optional string syncTarget = 105; */ public boolean hasSyncTarget() { - return ((bitField0_ & 0x00000800) == 0x00000800); + return ((bitField0_ & 0x00000400) == 0x00000400); } /** * optional string syncTarget = 105; @@ -14132,10 +15482,31 @@ public final class SignalServiceProtos { } } + // optional .signalservice.DataMessage.GroupMessage groupMessage = 120; + public static final int GROUPMESSAGE_FIELD_NUMBER = 120; + private org.session.libsignal.protos.SignalServiceProtos.DataMessage.GroupMessage groupMessage_; + /** + * optional .signalservice.DataMessage.GroupMessage groupMessage = 120; + */ + public boolean hasGroupMessage() { + return ((bitField0_ & 0x00000800) == 0x00000800); + } + /** + * optional .signalservice.DataMessage.GroupMessage groupMessage = 120; + */ + public org.session.libsignal.protos.SignalServiceProtos.DataMessage.GroupMessage getGroupMessage() { + return groupMessage_; + } + /** + * optional .signalservice.DataMessage.GroupMessage groupMessage = 120; + */ + public org.session.libsignal.protos.SignalServiceProtos.DataMessage.GroupMessageOrBuilder getGroupMessageOrBuilder() { + return groupMessage_; + } + private void initFields() { body_ = ""; attachments_ = java.util.Collections.emptyList(); - group_ = org.session.libsignal.protos.SignalServiceProtos.GroupContext.getDefaultInstance(); flags_ = 0; expireTimer_ = 0; profileKey_ = com.google.protobuf.ByteString.EMPTY; @@ -14147,6 +15518,7 @@ public final class SignalServiceProtos { openGroupInvitation_ = org.session.libsignal.protos.SignalServiceProtos.DataMessage.OpenGroupInvitation.getDefaultInstance(); closedGroupControlMessage_ = org.session.libsignal.protos.SignalServiceProtos.DataMessage.ClosedGroupControlMessage.getDefaultInstance(); syncTarget_ = ""; + groupMessage_ = org.session.libsignal.protos.SignalServiceProtos.DataMessage.GroupMessage.getDefaultInstance(); } private byte memoizedIsInitialized = -1; public final boolean isInitialized() { @@ -14159,12 +15531,6 @@ public final class SignalServiceProtos { return false; } } - if (hasGroup()) { - if (!getGroup().isInitialized()) { - memoizedIsInitialized = 0; - return false; - } - } if (hasQuote()) { if (!getQuote().isInitialized()) { memoizedIsInitialized = 0; @@ -14195,6 +15561,12 @@ public final class SignalServiceProtos { return false; } } + if (hasGroupMessage()) { + if (!getGroupMessage().isInitialized()) { + memoizedIsInitialized = 0; + return false; + } + } memoizedIsInitialized = 1; return true; } @@ -14209,41 +15581,41 @@ public final class SignalServiceProtos { output.writeMessage(2, attachments_.get(i)); } if (((bitField0_ & 0x00000002) == 0x00000002)) { - output.writeMessage(3, group_); - } - if (((bitField0_ & 0x00000004) == 0x00000004)) { output.writeUInt32(4, flags_); } - if (((bitField0_ & 0x00000008) == 0x00000008)) { + if (((bitField0_ & 0x00000004) == 0x00000004)) { output.writeUInt32(5, expireTimer_); } - if (((bitField0_ & 0x00000010) == 0x00000010)) { + if (((bitField0_ & 0x00000008) == 0x00000008)) { output.writeBytes(6, profileKey_); } - if (((bitField0_ & 0x00000020) == 0x00000020)) { + if (((bitField0_ & 0x00000010) == 0x00000010)) { output.writeUInt64(7, timestamp_); } - if (((bitField0_ & 0x00000040) == 0x00000040)) { + if (((bitField0_ & 0x00000020) == 0x00000020)) { output.writeMessage(8, quote_); } for (int i = 0; i < preview_.size(); i++) { output.writeMessage(10, preview_.get(i)); } - if (((bitField0_ & 0x00000080) == 0x00000080)) { + if (((bitField0_ & 0x00000040) == 0x00000040)) { output.writeMessage(11, reaction_); } - if (((bitField0_ & 0x00000100) == 0x00000100)) { + if (((bitField0_ & 0x00000080) == 0x00000080)) { output.writeMessage(101, profile_); } - if (((bitField0_ & 0x00000200) == 0x00000200)) { + if (((bitField0_ & 0x00000100) == 0x00000100)) { output.writeMessage(102, openGroupInvitation_); } - if (((bitField0_ & 0x00000400) == 0x00000400)) { + if (((bitField0_ & 0x00000200) == 0x00000200)) { output.writeMessage(104, closedGroupControlMessage_); } - if (((bitField0_ & 0x00000800) == 0x00000800)) { + if (((bitField0_ & 0x00000400) == 0x00000400)) { output.writeBytes(105, getSyncTargetBytes()); } + if (((bitField0_ & 0x00000800) == 0x00000800)) { + output.writeMessage(120, groupMessage_); + } getUnknownFields().writeTo(output); } @@ -14263,25 +15635,21 @@ public final class SignalServiceProtos { } if (((bitField0_ & 0x00000002) == 0x00000002)) { size += com.google.protobuf.CodedOutputStream - .computeMessageSize(3, group_); + .computeUInt32Size(4, flags_); } if (((bitField0_ & 0x00000004) == 0x00000004)) { size += com.google.protobuf.CodedOutputStream - .computeUInt32Size(4, flags_); + .computeUInt32Size(5, expireTimer_); } if (((bitField0_ & 0x00000008) == 0x00000008)) { size += com.google.protobuf.CodedOutputStream - .computeUInt32Size(5, expireTimer_); + .computeBytesSize(6, profileKey_); } if (((bitField0_ & 0x00000010) == 0x00000010)) { - size += com.google.protobuf.CodedOutputStream - .computeBytesSize(6, profileKey_); - } - if (((bitField0_ & 0x00000020) == 0x00000020)) { size += com.google.protobuf.CodedOutputStream .computeUInt64Size(7, timestamp_); } - if (((bitField0_ & 0x00000040) == 0x00000040)) { + if (((bitField0_ & 0x00000020) == 0x00000020)) { size += com.google.protobuf.CodedOutputStream .computeMessageSize(8, quote_); } @@ -14289,26 +15657,30 @@ public final class SignalServiceProtos { size += com.google.protobuf.CodedOutputStream .computeMessageSize(10, preview_.get(i)); } - if (((bitField0_ & 0x00000080) == 0x00000080)) { + if (((bitField0_ & 0x00000040) == 0x00000040)) { size += com.google.protobuf.CodedOutputStream .computeMessageSize(11, reaction_); } - if (((bitField0_ & 0x00000100) == 0x00000100)) { + if (((bitField0_ & 0x00000080) == 0x00000080)) { size += com.google.protobuf.CodedOutputStream .computeMessageSize(101, profile_); } - if (((bitField0_ & 0x00000200) == 0x00000200)) { + if (((bitField0_ & 0x00000100) == 0x00000100)) { size += com.google.protobuf.CodedOutputStream .computeMessageSize(102, openGroupInvitation_); } - if (((bitField0_ & 0x00000400) == 0x00000400)) { + if (((bitField0_ & 0x00000200) == 0x00000200)) { size += com.google.protobuf.CodedOutputStream .computeMessageSize(104, closedGroupControlMessage_); } - if (((bitField0_ & 0x00000800) == 0x00000800)) { + if (((bitField0_ & 0x00000400) == 0x00000400)) { size += com.google.protobuf.CodedOutputStream .computeBytesSize(105, getSyncTargetBytes()); } + if (((bitField0_ & 0x00000800) == 0x00000800)) { + size += com.google.protobuf.CodedOutputStream + .computeMessageSize(120, groupMessage_); + } size += getUnknownFields().getSerializedSize(); memoizedSerializedSize = size; return size; @@ -14418,13 +15790,13 @@ public final class SignalServiceProtos { private void maybeForceBuilderInitialization() { if (com.google.protobuf.GeneratedMessage.alwaysUseFieldBuilders) { getAttachmentsFieldBuilder(); - getGroupFieldBuilder(); getQuoteFieldBuilder(); getPreviewFieldBuilder(); getReactionFieldBuilder(); getProfileFieldBuilder(); getOpenGroupInvitationFieldBuilder(); getClosedGroupControlMessageFieldBuilder(); + getGroupMessageFieldBuilder(); } } private static Builder create() { @@ -14441,29 +15813,23 @@ public final class SignalServiceProtos { } else { attachmentsBuilder_.clear(); } - if (groupBuilder_ == null) { - group_ = org.session.libsignal.protos.SignalServiceProtos.GroupContext.getDefaultInstance(); - } else { - groupBuilder_.clear(); - } - bitField0_ = (bitField0_ & ~0x00000004); flags_ = 0; - bitField0_ = (bitField0_ & ~0x00000008); + bitField0_ = (bitField0_ & ~0x00000004); expireTimer_ = 0; - bitField0_ = (bitField0_ & ~0x00000010); + bitField0_ = (bitField0_ & ~0x00000008); profileKey_ = com.google.protobuf.ByteString.EMPTY; - bitField0_ = (bitField0_ & ~0x00000020); + bitField0_ = (bitField0_ & ~0x00000010); timestamp_ = 0L; - bitField0_ = (bitField0_ & ~0x00000040); + bitField0_ = (bitField0_ & ~0x00000020); if (quoteBuilder_ == null) { quote_ = org.session.libsignal.protos.SignalServiceProtos.DataMessage.Quote.getDefaultInstance(); } else { quoteBuilder_.clear(); } - bitField0_ = (bitField0_ & ~0x00000080); + bitField0_ = (bitField0_ & ~0x00000040); if (previewBuilder_ == null) { preview_ = java.util.Collections.emptyList(); - bitField0_ = (bitField0_ & ~0x00000100); + bitField0_ = (bitField0_ & ~0x00000080); } else { previewBuilder_.clear(); } @@ -14472,26 +15838,32 @@ public final class SignalServiceProtos { } else { reactionBuilder_.clear(); } - bitField0_ = (bitField0_ & ~0x00000200); + bitField0_ = (bitField0_ & ~0x00000100); if (profileBuilder_ == null) { profile_ = org.session.libsignal.protos.SignalServiceProtos.DataMessage.LokiProfile.getDefaultInstance(); } else { profileBuilder_.clear(); } - bitField0_ = (bitField0_ & ~0x00000400); + bitField0_ = (bitField0_ & ~0x00000200); if (openGroupInvitationBuilder_ == null) { openGroupInvitation_ = org.session.libsignal.protos.SignalServiceProtos.DataMessage.OpenGroupInvitation.getDefaultInstance(); } else { openGroupInvitationBuilder_.clear(); } - bitField0_ = (bitField0_ & ~0x00000800); + bitField0_ = (bitField0_ & ~0x00000400); if (closedGroupControlMessageBuilder_ == null) { closedGroupControlMessage_ = org.session.libsignal.protos.SignalServiceProtos.DataMessage.ClosedGroupControlMessage.getDefaultInstance(); } else { closedGroupControlMessageBuilder_.clear(); } - bitField0_ = (bitField0_ & ~0x00001000); + bitField0_ = (bitField0_ & ~0x00000800); syncTarget_ = ""; + bitField0_ = (bitField0_ & ~0x00001000); + if (groupMessageBuilder_ == null) { + groupMessage_ = org.session.libsignal.protos.SignalServiceProtos.DataMessage.GroupMessage.getDefaultInstance(); + } else { + groupMessageBuilder_.clear(); + } bitField0_ = (bitField0_ & ~0x00002000); return this; } @@ -14537,80 +15909,80 @@ public final class SignalServiceProtos { if (((from_bitField0_ & 0x00000004) == 0x00000004)) { to_bitField0_ |= 0x00000002; } - if (groupBuilder_ == null) { - result.group_ = group_; - } else { - result.group_ = groupBuilder_.build(); - } + result.flags_ = flags_; if (((from_bitField0_ & 0x00000008) == 0x00000008)) { to_bitField0_ |= 0x00000004; } - result.flags_ = flags_; + result.expireTimer_ = expireTimer_; if (((from_bitField0_ & 0x00000010) == 0x00000010)) { to_bitField0_ |= 0x00000008; } - result.expireTimer_ = expireTimer_; + result.profileKey_ = profileKey_; if (((from_bitField0_ & 0x00000020) == 0x00000020)) { to_bitField0_ |= 0x00000010; } - result.profileKey_ = profileKey_; + result.timestamp_ = timestamp_; if (((from_bitField0_ & 0x00000040) == 0x00000040)) { to_bitField0_ |= 0x00000020; } - result.timestamp_ = timestamp_; - if (((from_bitField0_ & 0x00000080) == 0x00000080)) { - to_bitField0_ |= 0x00000040; - } if (quoteBuilder_ == null) { result.quote_ = quote_; } else { result.quote_ = quoteBuilder_.build(); } if (previewBuilder_ == null) { - if (((bitField0_ & 0x00000100) == 0x00000100)) { + if (((bitField0_ & 0x00000080) == 0x00000080)) { preview_ = java.util.Collections.unmodifiableList(preview_); - bitField0_ = (bitField0_ & ~0x00000100); + bitField0_ = (bitField0_ & ~0x00000080); } result.preview_ = preview_; } else { result.preview_ = previewBuilder_.build(); } - if (((from_bitField0_ & 0x00000200) == 0x00000200)) { - to_bitField0_ |= 0x00000080; + if (((from_bitField0_ & 0x00000100) == 0x00000100)) { + to_bitField0_ |= 0x00000040; } if (reactionBuilder_ == null) { result.reaction_ = reaction_; } else { result.reaction_ = reactionBuilder_.build(); } - if (((from_bitField0_ & 0x00000400) == 0x00000400)) { - to_bitField0_ |= 0x00000100; + if (((from_bitField0_ & 0x00000200) == 0x00000200)) { + to_bitField0_ |= 0x00000080; } if (profileBuilder_ == null) { result.profile_ = profile_; } else { result.profile_ = profileBuilder_.build(); } - if (((from_bitField0_ & 0x00000800) == 0x00000800)) { - to_bitField0_ |= 0x00000200; + if (((from_bitField0_ & 0x00000400) == 0x00000400)) { + to_bitField0_ |= 0x00000100; } if (openGroupInvitationBuilder_ == null) { result.openGroupInvitation_ = openGroupInvitation_; } else { result.openGroupInvitation_ = openGroupInvitationBuilder_.build(); } - if (((from_bitField0_ & 0x00001000) == 0x00001000)) { - to_bitField0_ |= 0x00000400; + if (((from_bitField0_ & 0x00000800) == 0x00000800)) { + to_bitField0_ |= 0x00000200; } if (closedGroupControlMessageBuilder_ == null) { result.closedGroupControlMessage_ = closedGroupControlMessage_; } else { result.closedGroupControlMessage_ = closedGroupControlMessageBuilder_.build(); } + if (((from_bitField0_ & 0x00001000) == 0x00001000)) { + to_bitField0_ |= 0x00000400; + } + result.syncTarget_ = syncTarget_; if (((from_bitField0_ & 0x00002000) == 0x00002000)) { to_bitField0_ |= 0x00000800; } - result.syncTarget_ = syncTarget_; + if (groupMessageBuilder_ == null) { + result.groupMessage_ = groupMessage_; + } else { + result.groupMessage_ = groupMessageBuilder_.build(); + } result.bitField0_ = to_bitField0_; onBuilt(); return result; @@ -14658,9 +16030,6 @@ public final class SignalServiceProtos { } } } - if (other.hasGroup()) { - mergeGroup(other.getGroup()); - } if (other.hasFlags()) { setFlags(other.getFlags()); } @@ -14680,7 +16049,7 @@ public final class SignalServiceProtos { if (!other.preview_.isEmpty()) { if (preview_.isEmpty()) { preview_ = other.preview_; - bitField0_ = (bitField0_ & ~0x00000100); + bitField0_ = (bitField0_ & ~0x00000080); } else { ensurePreviewIsMutable(); preview_.addAll(other.preview_); @@ -14693,7 +16062,7 @@ public final class SignalServiceProtos { previewBuilder_.dispose(); previewBuilder_ = null; preview_ = other.preview_; - bitField0_ = (bitField0_ & ~0x00000100); + bitField0_ = (bitField0_ & ~0x00000080); previewBuilder_ = com.google.protobuf.GeneratedMessage.alwaysUseFieldBuilders ? getPreviewFieldBuilder() : null; @@ -14715,10 +16084,13 @@ public final class SignalServiceProtos { mergeClosedGroupControlMessage(other.getClosedGroupControlMessage()); } if (other.hasSyncTarget()) { - bitField0_ |= 0x00002000; + bitField0_ |= 0x00001000; syncTarget_ = other.syncTarget_; onChanged(); } + if (other.hasGroupMessage()) { + mergeGroupMessage(other.getGroupMessage()); + } this.mergeUnknownFields(other.getUnknownFields()); return this; } @@ -14730,12 +16102,6 @@ public final class SignalServiceProtos { return false; } } - if (hasGroup()) { - if (!getGroup().isInitialized()) { - - return false; - } - } if (hasQuote()) { if (!getQuote().isInitialized()) { @@ -14766,6 +16132,12 @@ public final class SignalServiceProtos { return false; } } + if (hasGroupMessage()) { + if (!getGroupMessage().isInitialized()) { + + return false; + } + } return true; } @@ -15102,130 +16474,13 @@ public final class SignalServiceProtos { return attachmentsBuilder_; } - // optional .signalservice.GroupContext group = 3; - private org.session.libsignal.protos.SignalServiceProtos.GroupContext group_ = org.session.libsignal.protos.SignalServiceProtos.GroupContext.getDefaultInstance(); - private com.google.protobuf.SingleFieldBuilder< - org.session.libsignal.protos.SignalServiceProtos.GroupContext, org.session.libsignal.protos.SignalServiceProtos.GroupContext.Builder, org.session.libsignal.protos.SignalServiceProtos.GroupContextOrBuilder> groupBuilder_; - /** - * optional .signalservice.GroupContext group = 3; - */ - public boolean hasGroup() { - return ((bitField0_ & 0x00000004) == 0x00000004); - } - /** - * optional .signalservice.GroupContext group = 3; - */ - public org.session.libsignal.protos.SignalServiceProtos.GroupContext getGroup() { - if (groupBuilder_ == null) { - return group_; - } else { - return groupBuilder_.getMessage(); - } - } - /** - * optional .signalservice.GroupContext group = 3; - */ - public Builder setGroup(org.session.libsignal.protos.SignalServiceProtos.GroupContext value) { - if (groupBuilder_ == null) { - if (value == null) { - throw new NullPointerException(); - } - group_ = value; - onChanged(); - } else { - groupBuilder_.setMessage(value); - } - bitField0_ |= 0x00000004; - return this; - } - /** - * optional .signalservice.GroupContext group = 3; - */ - public Builder setGroup( - org.session.libsignal.protos.SignalServiceProtos.GroupContext.Builder builderForValue) { - if (groupBuilder_ == null) { - group_ = builderForValue.build(); - onChanged(); - } else { - groupBuilder_.setMessage(builderForValue.build()); - } - bitField0_ |= 0x00000004; - return this; - } - /** - * optional .signalservice.GroupContext group = 3; - */ - public Builder mergeGroup(org.session.libsignal.protos.SignalServiceProtos.GroupContext value) { - if (groupBuilder_ == null) { - if (((bitField0_ & 0x00000004) == 0x00000004) && - group_ != org.session.libsignal.protos.SignalServiceProtos.GroupContext.getDefaultInstance()) { - group_ = - org.session.libsignal.protos.SignalServiceProtos.GroupContext.newBuilder(group_).mergeFrom(value).buildPartial(); - } else { - group_ = value; - } - onChanged(); - } else { - groupBuilder_.mergeFrom(value); - } - bitField0_ |= 0x00000004; - return this; - } - /** - * optional .signalservice.GroupContext group = 3; - */ - public Builder clearGroup() { - if (groupBuilder_ == null) { - group_ = org.session.libsignal.protos.SignalServiceProtos.GroupContext.getDefaultInstance(); - onChanged(); - } else { - groupBuilder_.clear(); - } - bitField0_ = (bitField0_ & ~0x00000004); - return this; - } - /** - * optional .signalservice.GroupContext group = 3; - */ - public org.session.libsignal.protos.SignalServiceProtos.GroupContext.Builder getGroupBuilder() { - bitField0_ |= 0x00000004; - onChanged(); - return getGroupFieldBuilder().getBuilder(); - } - /** - * optional .signalservice.GroupContext group = 3; - */ - public org.session.libsignal.protos.SignalServiceProtos.GroupContextOrBuilder getGroupOrBuilder() { - if (groupBuilder_ != null) { - return groupBuilder_.getMessageOrBuilder(); - } else { - return group_; - } - } - /** - * optional .signalservice.GroupContext group = 3; - */ - private com.google.protobuf.SingleFieldBuilder< - org.session.libsignal.protos.SignalServiceProtos.GroupContext, org.session.libsignal.protos.SignalServiceProtos.GroupContext.Builder, org.session.libsignal.protos.SignalServiceProtos.GroupContextOrBuilder> - getGroupFieldBuilder() { - if (groupBuilder_ == null) { - groupBuilder_ = new com.google.protobuf.SingleFieldBuilder< - org.session.libsignal.protos.SignalServiceProtos.GroupContext, org.session.libsignal.protos.SignalServiceProtos.GroupContext.Builder, org.session.libsignal.protos.SignalServiceProtos.GroupContextOrBuilder>( - group_, - getParentForChildren(), - isClean()); - group_ = null; - } - return groupBuilder_; - } - // optional uint32 flags = 4; private int flags_ ; /** * optional uint32 flags = 4; */ public boolean hasFlags() { - return ((bitField0_ & 0x00000008) == 0x00000008); + return ((bitField0_ & 0x00000004) == 0x00000004); } /** * optional uint32 flags = 4; @@ -15237,7 +16492,7 @@ public final class SignalServiceProtos { * optional uint32 flags = 4; */ public Builder setFlags(int value) { - bitField0_ |= 0x00000008; + bitField0_ |= 0x00000004; flags_ = value; onChanged(); return this; @@ -15246,7 +16501,7 @@ public final class SignalServiceProtos { * optional uint32 flags = 4; */ public Builder clearFlags() { - bitField0_ = (bitField0_ & ~0x00000008); + bitField0_ = (bitField0_ & ~0x00000004); flags_ = 0; onChanged(); return this; @@ -15258,7 +16513,7 @@ public final class SignalServiceProtos { * optional uint32 expireTimer = 5; */ public boolean hasExpireTimer() { - return ((bitField0_ & 0x00000010) == 0x00000010); + return ((bitField0_ & 0x00000008) == 0x00000008); } /** * optional uint32 expireTimer = 5; @@ -15270,7 +16525,7 @@ public final class SignalServiceProtos { * optional uint32 expireTimer = 5; */ public Builder setExpireTimer(int value) { - bitField0_ |= 0x00000010; + bitField0_ |= 0x00000008; expireTimer_ = value; onChanged(); return this; @@ -15279,7 +16534,7 @@ public final class SignalServiceProtos { * optional uint32 expireTimer = 5; */ public Builder clearExpireTimer() { - bitField0_ = (bitField0_ & ~0x00000010); + bitField0_ = (bitField0_ & ~0x00000008); expireTimer_ = 0; onChanged(); return this; @@ -15291,7 +16546,7 @@ public final class SignalServiceProtos { * optional bytes profileKey = 6; */ public boolean hasProfileKey() { - return ((bitField0_ & 0x00000020) == 0x00000020); + return ((bitField0_ & 0x00000010) == 0x00000010); } /** * optional bytes profileKey = 6; @@ -15306,7 +16561,7 @@ public final class SignalServiceProtos { if (value == null) { throw new NullPointerException(); } - bitField0_ |= 0x00000020; + bitField0_ |= 0x00000010; profileKey_ = value; onChanged(); return this; @@ -15315,7 +16570,7 @@ public final class SignalServiceProtos { * optional bytes profileKey = 6; */ public Builder clearProfileKey() { - bitField0_ = (bitField0_ & ~0x00000020); + bitField0_ = (bitField0_ & ~0x00000010); profileKey_ = getDefaultInstance().getProfileKey(); onChanged(); return this; @@ -15327,7 +16582,7 @@ public final class SignalServiceProtos { * optional uint64 timestamp = 7; */ public boolean hasTimestamp() { - return ((bitField0_ & 0x00000040) == 0x00000040); + return ((bitField0_ & 0x00000020) == 0x00000020); } /** * optional uint64 timestamp = 7; @@ -15339,7 +16594,7 @@ public final class SignalServiceProtos { * optional uint64 timestamp = 7; */ public Builder setTimestamp(long value) { - bitField0_ |= 0x00000040; + bitField0_ |= 0x00000020; timestamp_ = value; onChanged(); return this; @@ -15348,7 +16603,7 @@ public final class SignalServiceProtos { * optional uint64 timestamp = 7; */ public Builder clearTimestamp() { - bitField0_ = (bitField0_ & ~0x00000040); + bitField0_ = (bitField0_ & ~0x00000020); timestamp_ = 0L; onChanged(); return this; @@ -15362,7 +16617,7 @@ public final class SignalServiceProtos { * optional .signalservice.DataMessage.Quote quote = 8; */ public boolean hasQuote() { - return ((bitField0_ & 0x00000080) == 0x00000080); + return ((bitField0_ & 0x00000040) == 0x00000040); } /** * optional .signalservice.DataMessage.Quote quote = 8; @@ -15387,7 +16642,7 @@ public final class SignalServiceProtos { } else { quoteBuilder_.setMessage(value); } - bitField0_ |= 0x00000080; + bitField0_ |= 0x00000040; return this; } /** @@ -15401,7 +16656,7 @@ public final class SignalServiceProtos { } else { quoteBuilder_.setMessage(builderForValue.build()); } - bitField0_ |= 0x00000080; + bitField0_ |= 0x00000040; return this; } /** @@ -15409,7 +16664,7 @@ public final class SignalServiceProtos { */ public Builder mergeQuote(org.session.libsignal.protos.SignalServiceProtos.DataMessage.Quote value) { if (quoteBuilder_ == null) { - if (((bitField0_ & 0x00000080) == 0x00000080) && + if (((bitField0_ & 0x00000040) == 0x00000040) && quote_ != org.session.libsignal.protos.SignalServiceProtos.DataMessage.Quote.getDefaultInstance()) { quote_ = org.session.libsignal.protos.SignalServiceProtos.DataMessage.Quote.newBuilder(quote_).mergeFrom(value).buildPartial(); @@ -15420,7 +16675,7 @@ public final class SignalServiceProtos { } else { quoteBuilder_.mergeFrom(value); } - bitField0_ |= 0x00000080; + bitField0_ |= 0x00000040; return this; } /** @@ -15433,14 +16688,14 @@ public final class SignalServiceProtos { } else { quoteBuilder_.clear(); } - bitField0_ = (bitField0_ & ~0x00000080); + bitField0_ = (bitField0_ & ~0x00000040); return this; } /** * optional .signalservice.DataMessage.Quote quote = 8; */ public org.session.libsignal.protos.SignalServiceProtos.DataMessage.Quote.Builder getQuoteBuilder() { - bitField0_ |= 0x00000080; + bitField0_ |= 0x00000040; onChanged(); return getQuoteFieldBuilder().getBuilder(); } @@ -15475,9 +16730,9 @@ public final class SignalServiceProtos { private java.util.List preview_ = java.util.Collections.emptyList(); private void ensurePreviewIsMutable() { - if (!((bitField0_ & 0x00000100) == 0x00000100)) { + if (!((bitField0_ & 0x00000080) == 0x00000080)) { preview_ = new java.util.ArrayList(preview_); - bitField0_ |= 0x00000100; + bitField0_ |= 0x00000080; } } @@ -15626,7 +16881,7 @@ public final class SignalServiceProtos { public Builder clearPreview() { if (previewBuilder_ == null) { preview_ = java.util.Collections.emptyList(); - bitField0_ = (bitField0_ & ~0x00000100); + bitField0_ = (bitField0_ & ~0x00000080); onChanged(); } else { previewBuilder_.clear(); @@ -15703,7 +16958,7 @@ public final class SignalServiceProtos { previewBuilder_ = new com.google.protobuf.RepeatedFieldBuilder< org.session.libsignal.protos.SignalServiceProtos.DataMessage.Preview, org.session.libsignal.protos.SignalServiceProtos.DataMessage.Preview.Builder, org.session.libsignal.protos.SignalServiceProtos.DataMessage.PreviewOrBuilder>( preview_, - ((bitField0_ & 0x00000100) == 0x00000100), + ((bitField0_ & 0x00000080) == 0x00000080), getParentForChildren(), isClean()); preview_ = null; @@ -15719,7 +16974,7 @@ public final class SignalServiceProtos { * optional .signalservice.DataMessage.Reaction reaction = 11; */ public boolean hasReaction() { - return ((bitField0_ & 0x00000200) == 0x00000200); + return ((bitField0_ & 0x00000100) == 0x00000100); } /** * optional .signalservice.DataMessage.Reaction reaction = 11; @@ -15744,7 +16999,7 @@ public final class SignalServiceProtos { } else { reactionBuilder_.setMessage(value); } - bitField0_ |= 0x00000200; + bitField0_ |= 0x00000100; return this; } /** @@ -15758,7 +17013,7 @@ public final class SignalServiceProtos { } else { reactionBuilder_.setMessage(builderForValue.build()); } - bitField0_ |= 0x00000200; + bitField0_ |= 0x00000100; return this; } /** @@ -15766,7 +17021,7 @@ public final class SignalServiceProtos { */ public Builder mergeReaction(org.session.libsignal.protos.SignalServiceProtos.DataMessage.Reaction value) { if (reactionBuilder_ == null) { - if (((bitField0_ & 0x00000200) == 0x00000200) && + if (((bitField0_ & 0x00000100) == 0x00000100) && reaction_ != org.session.libsignal.protos.SignalServiceProtos.DataMessage.Reaction.getDefaultInstance()) { reaction_ = org.session.libsignal.protos.SignalServiceProtos.DataMessage.Reaction.newBuilder(reaction_).mergeFrom(value).buildPartial(); @@ -15777,7 +17032,7 @@ public final class SignalServiceProtos { } else { reactionBuilder_.mergeFrom(value); } - bitField0_ |= 0x00000200; + bitField0_ |= 0x00000100; return this; } /** @@ -15790,14 +17045,14 @@ public final class SignalServiceProtos { } else { reactionBuilder_.clear(); } - bitField0_ = (bitField0_ & ~0x00000200); + bitField0_ = (bitField0_ & ~0x00000100); return this; } /** * optional .signalservice.DataMessage.Reaction reaction = 11; */ public org.session.libsignal.protos.SignalServiceProtos.DataMessage.Reaction.Builder getReactionBuilder() { - bitField0_ |= 0x00000200; + bitField0_ |= 0x00000100; onChanged(); return getReactionFieldBuilder().getBuilder(); } @@ -15836,7 +17091,7 @@ public final class SignalServiceProtos { * optional .signalservice.DataMessage.LokiProfile profile = 101; */ public boolean hasProfile() { - return ((bitField0_ & 0x00000400) == 0x00000400); + return ((bitField0_ & 0x00000200) == 0x00000200); } /** * optional .signalservice.DataMessage.LokiProfile profile = 101; @@ -15861,7 +17116,7 @@ public final class SignalServiceProtos { } else { profileBuilder_.setMessage(value); } - bitField0_ |= 0x00000400; + bitField0_ |= 0x00000200; return this; } /** @@ -15875,7 +17130,7 @@ public final class SignalServiceProtos { } else { profileBuilder_.setMessage(builderForValue.build()); } - bitField0_ |= 0x00000400; + bitField0_ |= 0x00000200; return this; } /** @@ -15883,7 +17138,7 @@ public final class SignalServiceProtos { */ public Builder mergeProfile(org.session.libsignal.protos.SignalServiceProtos.DataMessage.LokiProfile value) { if (profileBuilder_ == null) { - if (((bitField0_ & 0x00000400) == 0x00000400) && + if (((bitField0_ & 0x00000200) == 0x00000200) && profile_ != org.session.libsignal.protos.SignalServiceProtos.DataMessage.LokiProfile.getDefaultInstance()) { profile_ = org.session.libsignal.protos.SignalServiceProtos.DataMessage.LokiProfile.newBuilder(profile_).mergeFrom(value).buildPartial(); @@ -15894,7 +17149,7 @@ public final class SignalServiceProtos { } else { profileBuilder_.mergeFrom(value); } - bitField0_ |= 0x00000400; + bitField0_ |= 0x00000200; return this; } /** @@ -15907,14 +17162,14 @@ public final class SignalServiceProtos { } else { profileBuilder_.clear(); } - bitField0_ = (bitField0_ & ~0x00000400); + bitField0_ = (bitField0_ & ~0x00000200); return this; } /** * optional .signalservice.DataMessage.LokiProfile profile = 101; */ public org.session.libsignal.protos.SignalServiceProtos.DataMessage.LokiProfile.Builder getProfileBuilder() { - bitField0_ |= 0x00000400; + bitField0_ |= 0x00000200; onChanged(); return getProfileFieldBuilder().getBuilder(); } @@ -15953,7 +17208,7 @@ public final class SignalServiceProtos { * optional .signalservice.DataMessage.OpenGroupInvitation openGroupInvitation = 102; */ public boolean hasOpenGroupInvitation() { - return ((bitField0_ & 0x00000800) == 0x00000800); + return ((bitField0_ & 0x00000400) == 0x00000400); } /** * optional .signalservice.DataMessage.OpenGroupInvitation openGroupInvitation = 102; @@ -15978,7 +17233,7 @@ public final class SignalServiceProtos { } else { openGroupInvitationBuilder_.setMessage(value); } - bitField0_ |= 0x00000800; + bitField0_ |= 0x00000400; return this; } /** @@ -15992,7 +17247,7 @@ public final class SignalServiceProtos { } else { openGroupInvitationBuilder_.setMessage(builderForValue.build()); } - bitField0_ |= 0x00000800; + bitField0_ |= 0x00000400; return this; } /** @@ -16000,7 +17255,7 @@ public final class SignalServiceProtos { */ public Builder mergeOpenGroupInvitation(org.session.libsignal.protos.SignalServiceProtos.DataMessage.OpenGroupInvitation value) { if (openGroupInvitationBuilder_ == null) { - if (((bitField0_ & 0x00000800) == 0x00000800) && + if (((bitField0_ & 0x00000400) == 0x00000400) && openGroupInvitation_ != org.session.libsignal.protos.SignalServiceProtos.DataMessage.OpenGroupInvitation.getDefaultInstance()) { openGroupInvitation_ = org.session.libsignal.protos.SignalServiceProtos.DataMessage.OpenGroupInvitation.newBuilder(openGroupInvitation_).mergeFrom(value).buildPartial(); @@ -16011,7 +17266,7 @@ public final class SignalServiceProtos { } else { openGroupInvitationBuilder_.mergeFrom(value); } - bitField0_ |= 0x00000800; + bitField0_ |= 0x00000400; return this; } /** @@ -16024,14 +17279,14 @@ public final class SignalServiceProtos { } else { openGroupInvitationBuilder_.clear(); } - bitField0_ = (bitField0_ & ~0x00000800); + bitField0_ = (bitField0_ & ~0x00000400); return this; } /** * optional .signalservice.DataMessage.OpenGroupInvitation openGroupInvitation = 102; */ public org.session.libsignal.protos.SignalServiceProtos.DataMessage.OpenGroupInvitation.Builder getOpenGroupInvitationBuilder() { - bitField0_ |= 0x00000800; + bitField0_ |= 0x00000400; onChanged(); return getOpenGroupInvitationFieldBuilder().getBuilder(); } @@ -16070,7 +17325,7 @@ public final class SignalServiceProtos { * optional .signalservice.DataMessage.ClosedGroupControlMessage closedGroupControlMessage = 104; */ public boolean hasClosedGroupControlMessage() { - return ((bitField0_ & 0x00001000) == 0x00001000); + return ((bitField0_ & 0x00000800) == 0x00000800); } /** * optional .signalservice.DataMessage.ClosedGroupControlMessage closedGroupControlMessage = 104; @@ -16095,7 +17350,7 @@ public final class SignalServiceProtos { } else { closedGroupControlMessageBuilder_.setMessage(value); } - bitField0_ |= 0x00001000; + bitField0_ |= 0x00000800; return this; } /** @@ -16109,7 +17364,7 @@ public final class SignalServiceProtos { } else { closedGroupControlMessageBuilder_.setMessage(builderForValue.build()); } - bitField0_ |= 0x00001000; + bitField0_ |= 0x00000800; return this; } /** @@ -16117,7 +17372,7 @@ public final class SignalServiceProtos { */ public Builder mergeClosedGroupControlMessage(org.session.libsignal.protos.SignalServiceProtos.DataMessage.ClosedGroupControlMessage value) { if (closedGroupControlMessageBuilder_ == null) { - if (((bitField0_ & 0x00001000) == 0x00001000) && + if (((bitField0_ & 0x00000800) == 0x00000800) && closedGroupControlMessage_ != org.session.libsignal.protos.SignalServiceProtos.DataMessage.ClosedGroupControlMessage.getDefaultInstance()) { closedGroupControlMessage_ = org.session.libsignal.protos.SignalServiceProtos.DataMessage.ClosedGroupControlMessage.newBuilder(closedGroupControlMessage_).mergeFrom(value).buildPartial(); @@ -16128,7 +17383,7 @@ public final class SignalServiceProtos { } else { closedGroupControlMessageBuilder_.mergeFrom(value); } - bitField0_ |= 0x00001000; + bitField0_ |= 0x00000800; return this; } /** @@ -16141,14 +17396,14 @@ public final class SignalServiceProtos { } else { closedGroupControlMessageBuilder_.clear(); } - bitField0_ = (bitField0_ & ~0x00001000); + bitField0_ = (bitField0_ & ~0x00000800); return this; } /** * optional .signalservice.DataMessage.ClosedGroupControlMessage closedGroupControlMessage = 104; */ public org.session.libsignal.protos.SignalServiceProtos.DataMessage.ClosedGroupControlMessage.Builder getClosedGroupControlMessageBuilder() { - bitField0_ |= 0x00001000; + bitField0_ |= 0x00000800; onChanged(); return getClosedGroupControlMessageFieldBuilder().getBuilder(); } @@ -16185,7 +17440,7 @@ public final class SignalServiceProtos { * optional string syncTarget = 105; */ public boolean hasSyncTarget() { - return ((bitField0_ & 0x00002000) == 0x00002000); + return ((bitField0_ & 0x00001000) == 0x00001000); } /** * optional string syncTarget = 105; @@ -16225,7 +17480,7 @@ public final class SignalServiceProtos { if (value == null) { throw new NullPointerException(); } - bitField0_ |= 0x00002000; + bitField0_ |= 0x00001000; syncTarget_ = value; onChanged(); return this; @@ -16234,7 +17489,7 @@ public final class SignalServiceProtos { * optional string syncTarget = 105; */ public Builder clearSyncTarget() { - bitField0_ = (bitField0_ & ~0x00002000); + bitField0_ = (bitField0_ & ~0x00001000); syncTarget_ = getDefaultInstance().getSyncTarget(); onChanged(); return this; @@ -16247,12 +17502,129 @@ public final class SignalServiceProtos { if (value == null) { throw new NullPointerException(); } - bitField0_ |= 0x00002000; + bitField0_ |= 0x00001000; syncTarget_ = value; onChanged(); return this; } + // optional .signalservice.DataMessage.GroupMessage groupMessage = 120; + private org.session.libsignal.protos.SignalServiceProtos.DataMessage.GroupMessage groupMessage_ = org.session.libsignal.protos.SignalServiceProtos.DataMessage.GroupMessage.getDefaultInstance(); + private com.google.protobuf.SingleFieldBuilder< + org.session.libsignal.protos.SignalServiceProtos.DataMessage.GroupMessage, org.session.libsignal.protos.SignalServiceProtos.DataMessage.GroupMessage.Builder, org.session.libsignal.protos.SignalServiceProtos.DataMessage.GroupMessageOrBuilder> groupMessageBuilder_; + /** + * optional .signalservice.DataMessage.GroupMessage groupMessage = 120; + */ + public boolean hasGroupMessage() { + return ((bitField0_ & 0x00002000) == 0x00002000); + } + /** + * optional .signalservice.DataMessage.GroupMessage groupMessage = 120; + */ + public org.session.libsignal.protos.SignalServiceProtos.DataMessage.GroupMessage getGroupMessage() { + if (groupMessageBuilder_ == null) { + return groupMessage_; + } else { + return groupMessageBuilder_.getMessage(); + } + } + /** + * optional .signalservice.DataMessage.GroupMessage groupMessage = 120; + */ + public Builder setGroupMessage(org.session.libsignal.protos.SignalServiceProtos.DataMessage.GroupMessage value) { + if (groupMessageBuilder_ == null) { + if (value == null) { + throw new NullPointerException(); + } + groupMessage_ = value; + onChanged(); + } else { + groupMessageBuilder_.setMessage(value); + } + bitField0_ |= 0x00002000; + return this; + } + /** + * optional .signalservice.DataMessage.GroupMessage groupMessage = 120; + */ + public Builder setGroupMessage( + org.session.libsignal.protos.SignalServiceProtos.DataMessage.GroupMessage.Builder builderForValue) { + if (groupMessageBuilder_ == null) { + groupMessage_ = builderForValue.build(); + onChanged(); + } else { + groupMessageBuilder_.setMessage(builderForValue.build()); + } + bitField0_ |= 0x00002000; + return this; + } + /** + * optional .signalservice.DataMessage.GroupMessage groupMessage = 120; + */ + public Builder mergeGroupMessage(org.session.libsignal.protos.SignalServiceProtos.DataMessage.GroupMessage value) { + if (groupMessageBuilder_ == null) { + if (((bitField0_ & 0x00002000) == 0x00002000) && + groupMessage_ != org.session.libsignal.protos.SignalServiceProtos.DataMessage.GroupMessage.getDefaultInstance()) { + groupMessage_ = + org.session.libsignal.protos.SignalServiceProtos.DataMessage.GroupMessage.newBuilder(groupMessage_).mergeFrom(value).buildPartial(); + } else { + groupMessage_ = value; + } + onChanged(); + } else { + groupMessageBuilder_.mergeFrom(value); + } + bitField0_ |= 0x00002000; + return this; + } + /** + * optional .signalservice.DataMessage.GroupMessage groupMessage = 120; + */ + public Builder clearGroupMessage() { + if (groupMessageBuilder_ == null) { + groupMessage_ = org.session.libsignal.protos.SignalServiceProtos.DataMessage.GroupMessage.getDefaultInstance(); + onChanged(); + } else { + groupMessageBuilder_.clear(); + } + bitField0_ = (bitField0_ & ~0x00002000); + return this; + } + /** + * optional .signalservice.DataMessage.GroupMessage groupMessage = 120; + */ + public org.session.libsignal.protos.SignalServiceProtos.DataMessage.GroupMessage.Builder getGroupMessageBuilder() { + bitField0_ |= 0x00002000; + onChanged(); + return getGroupMessageFieldBuilder().getBuilder(); + } + /** + * optional .signalservice.DataMessage.GroupMessage groupMessage = 120; + */ + public org.session.libsignal.protos.SignalServiceProtos.DataMessage.GroupMessageOrBuilder getGroupMessageOrBuilder() { + if (groupMessageBuilder_ != null) { + return groupMessageBuilder_.getMessageOrBuilder(); + } else { + return groupMessage_; + } + } + /** + * optional .signalservice.DataMessage.GroupMessage groupMessage = 120; + */ + private com.google.protobuf.SingleFieldBuilder< + org.session.libsignal.protos.SignalServiceProtos.DataMessage.GroupMessage, org.session.libsignal.protos.SignalServiceProtos.DataMessage.GroupMessage.Builder, org.session.libsignal.protos.SignalServiceProtos.DataMessage.GroupMessageOrBuilder> + getGroupMessageFieldBuilder() { + if (groupMessageBuilder_ == null) { + groupMessageBuilder_ = new com.google.protobuf.SingleFieldBuilder< + org.session.libsignal.protos.SignalServiceProtos.DataMessage.GroupMessage, org.session.libsignal.protos.SignalServiceProtos.DataMessage.GroupMessage.Builder, org.session.libsignal.protos.SignalServiceProtos.DataMessage.GroupMessageOrBuilder>( + groupMessage_, + getParentForChildren(), + isClean()); + groupMessage_ = null; + } + return groupMessageBuilder_; + } + // @@protoc_insertion_point(builder_scope:signalservice.DataMessage) } @@ -16264,6 +17636,2224 @@ public final class SignalServiceProtos { // @@protoc_insertion_point(class_scope:signalservice.DataMessage) } + public interface GroupDeleteMessageOrBuilder + extends com.google.protobuf.MessageOrBuilder { + + // required bytes publicKey = 1; + /** + * required bytes publicKey = 1; + * + *
+     * @required
+     * 
+ */ + boolean hasPublicKey(); + /** + * required bytes publicKey = 1; + * + *
+     * @required
+     * 
+ */ + com.google.protobuf.ByteString getPublicKey(); + + // required bytes lastEncryptionKey = 2; + /** + * required bytes lastEncryptionKey = 2; + * + *
+     * @required
+     * 
+ */ + boolean hasLastEncryptionKey(); + /** + * required bytes lastEncryptionKey = 2; + * + *
+     * @required
+     * 
+ */ + com.google.protobuf.ByteString getLastEncryptionKey(); + } + /** + * Protobuf type {@code signalservice.GroupDeleteMessage} + */ + public static final class GroupDeleteMessage extends + com.google.protobuf.GeneratedMessage + implements GroupDeleteMessageOrBuilder { + // Use GroupDeleteMessage.newBuilder() to construct. + private GroupDeleteMessage(com.google.protobuf.GeneratedMessage.Builder builder) { + super(builder); + this.unknownFields = builder.getUnknownFields(); + } + private GroupDeleteMessage(boolean noInit) { this.unknownFields = com.google.protobuf.UnknownFieldSet.getDefaultInstance(); } + + private static final GroupDeleteMessage defaultInstance; + public static GroupDeleteMessage getDefaultInstance() { + return defaultInstance; + } + + public GroupDeleteMessage getDefaultInstanceForType() { + return defaultInstance; + } + + private final com.google.protobuf.UnknownFieldSet unknownFields; + @java.lang.Override + public final com.google.protobuf.UnknownFieldSet + getUnknownFields() { + return this.unknownFields; + } + private GroupDeleteMessage( + com.google.protobuf.CodedInputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + initFields(); + int mutable_bitField0_ = 0; + com.google.protobuf.UnknownFieldSet.Builder unknownFields = + com.google.protobuf.UnknownFieldSet.newBuilder(); + try { + boolean done = false; + while (!done) { + int tag = input.readTag(); + switch (tag) { + case 0: + done = true; + break; + default: { + if (!parseUnknownField(input, unknownFields, + extensionRegistry, tag)) { + done = true; + } + break; + } + case 10: { + bitField0_ |= 0x00000001; + publicKey_ = input.readBytes(); + break; + } + case 18: { + bitField0_ |= 0x00000002; + lastEncryptionKey_ = input.readBytes(); + break; + } + } + } + } catch (com.google.protobuf.InvalidProtocolBufferException e) { + throw e.setUnfinishedMessage(this); + } catch (java.io.IOException e) { + throw new com.google.protobuf.InvalidProtocolBufferException( + e.getMessage()).setUnfinishedMessage(this); + } finally { + this.unknownFields = unknownFields.build(); + makeExtensionsImmutable(); + } + } + public static final com.google.protobuf.Descriptors.Descriptor + getDescriptor() { + return org.session.libsignal.protos.SignalServiceProtos.internal_static_signalservice_GroupDeleteMessage_descriptor; + } + + protected com.google.protobuf.GeneratedMessage.FieldAccessorTable + internalGetFieldAccessorTable() { + return org.session.libsignal.protos.SignalServiceProtos.internal_static_signalservice_GroupDeleteMessage_fieldAccessorTable + .ensureFieldAccessorsInitialized( + org.session.libsignal.protos.SignalServiceProtos.GroupDeleteMessage.class, org.session.libsignal.protos.SignalServiceProtos.GroupDeleteMessage.Builder.class); + } + + public static com.google.protobuf.Parser PARSER = + new com.google.protobuf.AbstractParser() { + public GroupDeleteMessage parsePartialFrom( + com.google.protobuf.CodedInputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + return new GroupDeleteMessage(input, extensionRegistry); + } + }; + + @java.lang.Override + public com.google.protobuf.Parser getParserForType() { + return PARSER; + } + + private int bitField0_; + // required bytes publicKey = 1; + public static final int PUBLICKEY_FIELD_NUMBER = 1; + private com.google.protobuf.ByteString publicKey_; + /** + * required bytes publicKey = 1; + * + *
+     * @required
+     * 
+ */ + public boolean hasPublicKey() { + return ((bitField0_ & 0x00000001) == 0x00000001); + } + /** + * required bytes publicKey = 1; + * + *
+     * @required
+     * 
+ */ + public com.google.protobuf.ByteString getPublicKey() { + return publicKey_; + } + + // required bytes lastEncryptionKey = 2; + public static final int LASTENCRYPTIONKEY_FIELD_NUMBER = 2; + private com.google.protobuf.ByteString lastEncryptionKey_; + /** + * required bytes lastEncryptionKey = 2; + * + *
+     * @required
+     * 
+ */ + public boolean hasLastEncryptionKey() { + return ((bitField0_ & 0x00000002) == 0x00000002); + } + /** + * required bytes lastEncryptionKey = 2; + * + *
+     * @required
+     * 
+ */ + public com.google.protobuf.ByteString getLastEncryptionKey() { + return lastEncryptionKey_; + } + + private void initFields() { + publicKey_ = com.google.protobuf.ByteString.EMPTY; + lastEncryptionKey_ = com.google.protobuf.ByteString.EMPTY; + } + private byte memoizedIsInitialized = -1; + public final boolean isInitialized() { + byte isInitialized = memoizedIsInitialized; + if (isInitialized != -1) return isInitialized == 1; + + if (!hasPublicKey()) { + memoizedIsInitialized = 0; + return false; + } + if (!hasLastEncryptionKey()) { + memoizedIsInitialized = 0; + return false; + } + memoizedIsInitialized = 1; + return true; + } + + public void writeTo(com.google.protobuf.CodedOutputStream output) + throws java.io.IOException { + getSerializedSize(); + if (((bitField0_ & 0x00000001) == 0x00000001)) { + output.writeBytes(1, publicKey_); + } + if (((bitField0_ & 0x00000002) == 0x00000002)) { + output.writeBytes(2, lastEncryptionKey_); + } + getUnknownFields().writeTo(output); + } + + private int memoizedSerializedSize = -1; + public int getSerializedSize() { + int size = memoizedSerializedSize; + if (size != -1) return size; + + size = 0; + if (((bitField0_ & 0x00000001) == 0x00000001)) { + size += com.google.protobuf.CodedOutputStream + .computeBytesSize(1, publicKey_); + } + if (((bitField0_ & 0x00000002) == 0x00000002)) { + size += com.google.protobuf.CodedOutputStream + .computeBytesSize(2, lastEncryptionKey_); + } + size += getUnknownFields().getSerializedSize(); + memoizedSerializedSize = size; + return size; + } + + private static final long serialVersionUID = 0L; + @java.lang.Override + protected java.lang.Object writeReplace() + throws java.io.ObjectStreamException { + return super.writeReplace(); + } + + public static org.session.libsignal.protos.SignalServiceProtos.GroupDeleteMessage parseFrom( + com.google.protobuf.ByteString data) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data); + } + public static org.session.libsignal.protos.SignalServiceProtos.GroupDeleteMessage parseFrom( + com.google.protobuf.ByteString data, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data, extensionRegistry); + } + public static org.session.libsignal.protos.SignalServiceProtos.GroupDeleteMessage parseFrom(byte[] data) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data); + } + public static org.session.libsignal.protos.SignalServiceProtos.GroupDeleteMessage parseFrom( + byte[] data, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data, extensionRegistry); + } + public static org.session.libsignal.protos.SignalServiceProtos.GroupDeleteMessage parseFrom(java.io.InputStream input) + throws java.io.IOException { + return PARSER.parseFrom(input); + } + public static org.session.libsignal.protos.SignalServiceProtos.GroupDeleteMessage parseFrom( + java.io.InputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws java.io.IOException { + return PARSER.parseFrom(input, extensionRegistry); + } + public static org.session.libsignal.protos.SignalServiceProtos.GroupDeleteMessage parseDelimitedFrom(java.io.InputStream input) + throws java.io.IOException { + return PARSER.parseDelimitedFrom(input); + } + public static org.session.libsignal.protos.SignalServiceProtos.GroupDeleteMessage parseDelimitedFrom( + java.io.InputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws java.io.IOException { + return PARSER.parseDelimitedFrom(input, extensionRegistry); + } + public static org.session.libsignal.protos.SignalServiceProtos.GroupDeleteMessage parseFrom( + com.google.protobuf.CodedInputStream input) + throws java.io.IOException { + return PARSER.parseFrom(input); + } + public static org.session.libsignal.protos.SignalServiceProtos.GroupDeleteMessage parseFrom( + com.google.protobuf.CodedInputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws java.io.IOException { + return PARSER.parseFrom(input, extensionRegistry); + } + + public static Builder newBuilder() { return Builder.create(); } + public Builder newBuilderForType() { return newBuilder(); } + public static Builder newBuilder(org.session.libsignal.protos.SignalServiceProtos.GroupDeleteMessage prototype) { + return newBuilder().mergeFrom(prototype); + } + public Builder toBuilder() { return newBuilder(this); } + + @java.lang.Override + protected Builder newBuilderForType( + com.google.protobuf.GeneratedMessage.BuilderParent parent) { + Builder builder = new Builder(parent); + return builder; + } + /** + * Protobuf type {@code signalservice.GroupDeleteMessage} + */ + public static final class Builder extends + com.google.protobuf.GeneratedMessage.Builder + implements org.session.libsignal.protos.SignalServiceProtos.GroupDeleteMessageOrBuilder { + public static final com.google.protobuf.Descriptors.Descriptor + getDescriptor() { + return org.session.libsignal.protos.SignalServiceProtos.internal_static_signalservice_GroupDeleteMessage_descriptor; + } + + protected com.google.protobuf.GeneratedMessage.FieldAccessorTable + internalGetFieldAccessorTable() { + return org.session.libsignal.protos.SignalServiceProtos.internal_static_signalservice_GroupDeleteMessage_fieldAccessorTable + .ensureFieldAccessorsInitialized( + org.session.libsignal.protos.SignalServiceProtos.GroupDeleteMessage.class, org.session.libsignal.protos.SignalServiceProtos.GroupDeleteMessage.Builder.class); + } + + // Construct using org.session.libsignal.protos.SignalServiceProtos.GroupDeleteMessage.newBuilder() + private Builder() { + maybeForceBuilderInitialization(); + } + + private Builder( + com.google.protobuf.GeneratedMessage.BuilderParent parent) { + super(parent); + maybeForceBuilderInitialization(); + } + private void maybeForceBuilderInitialization() { + if (com.google.protobuf.GeneratedMessage.alwaysUseFieldBuilders) { + } + } + private static Builder create() { + return new Builder(); + } + + public Builder clear() { + super.clear(); + publicKey_ = com.google.protobuf.ByteString.EMPTY; + bitField0_ = (bitField0_ & ~0x00000001); + lastEncryptionKey_ = com.google.protobuf.ByteString.EMPTY; + bitField0_ = (bitField0_ & ~0x00000002); + return this; + } + + public Builder clone() { + return create().mergeFrom(buildPartial()); + } + + public com.google.protobuf.Descriptors.Descriptor + getDescriptorForType() { + return org.session.libsignal.protos.SignalServiceProtos.internal_static_signalservice_GroupDeleteMessage_descriptor; + } + + public org.session.libsignal.protos.SignalServiceProtos.GroupDeleteMessage getDefaultInstanceForType() { + return org.session.libsignal.protos.SignalServiceProtos.GroupDeleteMessage.getDefaultInstance(); + } + + public org.session.libsignal.protos.SignalServiceProtos.GroupDeleteMessage build() { + org.session.libsignal.protos.SignalServiceProtos.GroupDeleteMessage result = buildPartial(); + if (!result.isInitialized()) { + throw newUninitializedMessageException(result); + } + return result; + } + + public org.session.libsignal.protos.SignalServiceProtos.GroupDeleteMessage buildPartial() { + org.session.libsignal.protos.SignalServiceProtos.GroupDeleteMessage result = new org.session.libsignal.protos.SignalServiceProtos.GroupDeleteMessage(this); + int from_bitField0_ = bitField0_; + int to_bitField0_ = 0; + if (((from_bitField0_ & 0x00000001) == 0x00000001)) { + to_bitField0_ |= 0x00000001; + } + result.publicKey_ = publicKey_; + if (((from_bitField0_ & 0x00000002) == 0x00000002)) { + to_bitField0_ |= 0x00000002; + } + result.lastEncryptionKey_ = lastEncryptionKey_; + result.bitField0_ = to_bitField0_; + onBuilt(); + return result; + } + + public Builder mergeFrom(com.google.protobuf.Message other) { + if (other instanceof org.session.libsignal.protos.SignalServiceProtos.GroupDeleteMessage) { + return mergeFrom((org.session.libsignal.protos.SignalServiceProtos.GroupDeleteMessage)other); + } else { + super.mergeFrom(other); + return this; + } + } + + public Builder mergeFrom(org.session.libsignal.protos.SignalServiceProtos.GroupDeleteMessage other) { + if (other == org.session.libsignal.protos.SignalServiceProtos.GroupDeleteMessage.getDefaultInstance()) return this; + if (other.hasPublicKey()) { + setPublicKey(other.getPublicKey()); + } + if (other.hasLastEncryptionKey()) { + setLastEncryptionKey(other.getLastEncryptionKey()); + } + this.mergeUnknownFields(other.getUnknownFields()); + return this; + } + + public final boolean isInitialized() { + if (!hasPublicKey()) { + + return false; + } + if (!hasLastEncryptionKey()) { + + return false; + } + return true; + } + + public Builder mergeFrom( + com.google.protobuf.CodedInputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws java.io.IOException { + org.session.libsignal.protos.SignalServiceProtos.GroupDeleteMessage parsedMessage = null; + try { + parsedMessage = PARSER.parsePartialFrom(input, extensionRegistry); + } catch (com.google.protobuf.InvalidProtocolBufferException e) { + parsedMessage = (org.session.libsignal.protos.SignalServiceProtos.GroupDeleteMessage) e.getUnfinishedMessage(); + throw e; + } finally { + if (parsedMessage != null) { + mergeFrom(parsedMessage); + } + } + return this; + } + private int bitField0_; + + // required bytes publicKey = 1; + private com.google.protobuf.ByteString publicKey_ = com.google.protobuf.ByteString.EMPTY; + /** + * required bytes publicKey = 1; + * + *
+       * @required
+       * 
+ */ + public boolean hasPublicKey() { + return ((bitField0_ & 0x00000001) == 0x00000001); + } + /** + * required bytes publicKey = 1; + * + *
+       * @required
+       * 
+ */ + public com.google.protobuf.ByteString getPublicKey() { + return publicKey_; + } + /** + * required bytes publicKey = 1; + * + *
+       * @required
+       * 
+ */ + public Builder setPublicKey(com.google.protobuf.ByteString value) { + if (value == null) { + throw new NullPointerException(); + } + bitField0_ |= 0x00000001; + publicKey_ = value; + onChanged(); + return this; + } + /** + * required bytes publicKey = 1; + * + *
+       * @required
+       * 
+ */ + public Builder clearPublicKey() { + bitField0_ = (bitField0_ & ~0x00000001); + publicKey_ = getDefaultInstance().getPublicKey(); + onChanged(); + return this; + } + + // required bytes lastEncryptionKey = 2; + private com.google.protobuf.ByteString lastEncryptionKey_ = com.google.protobuf.ByteString.EMPTY; + /** + * required bytes lastEncryptionKey = 2; + * + *
+       * @required
+       * 
+ */ + public boolean hasLastEncryptionKey() { + return ((bitField0_ & 0x00000002) == 0x00000002); + } + /** + * required bytes lastEncryptionKey = 2; + * + *
+       * @required
+       * 
+ */ + public com.google.protobuf.ByteString getLastEncryptionKey() { + return lastEncryptionKey_; + } + /** + * required bytes lastEncryptionKey = 2; + * + *
+       * @required
+       * 
+ */ + public Builder setLastEncryptionKey(com.google.protobuf.ByteString value) { + if (value == null) { + throw new NullPointerException(); + } + bitField0_ |= 0x00000002; + lastEncryptionKey_ = value; + onChanged(); + return this; + } + /** + * required bytes lastEncryptionKey = 2; + * + *
+       * @required
+       * 
+ */ + public Builder clearLastEncryptionKey() { + bitField0_ = (bitField0_ & ~0x00000002); + lastEncryptionKey_ = getDefaultInstance().getLastEncryptionKey(); + onChanged(); + return this; + } + + // @@protoc_insertion_point(builder_scope:signalservice.GroupDeleteMessage) + } + + static { + defaultInstance = new GroupDeleteMessage(true); + defaultInstance.initFields(); + } + + // @@protoc_insertion_point(class_scope:signalservice.GroupDeleteMessage) + } + + public interface GroupMemberLeftMessageOrBuilder + extends com.google.protobuf.MessageOrBuilder { + } + /** + * Protobuf type {@code signalservice.GroupMemberLeftMessage} + * + *
+   * the pubkey of the member left is included as part of the closed group encryption logic (senderIdentity on desktop)
+   * 
+ */ + public static final class GroupMemberLeftMessage extends + com.google.protobuf.GeneratedMessage + implements GroupMemberLeftMessageOrBuilder { + // Use GroupMemberLeftMessage.newBuilder() to construct. + private GroupMemberLeftMessage(com.google.protobuf.GeneratedMessage.Builder builder) { + super(builder); + this.unknownFields = builder.getUnknownFields(); + } + private GroupMemberLeftMessage(boolean noInit) { this.unknownFields = com.google.protobuf.UnknownFieldSet.getDefaultInstance(); } + + private static final GroupMemberLeftMessage defaultInstance; + public static GroupMemberLeftMessage getDefaultInstance() { + return defaultInstance; + } + + public GroupMemberLeftMessage getDefaultInstanceForType() { + return defaultInstance; + } + + private final com.google.protobuf.UnknownFieldSet unknownFields; + @java.lang.Override + public final com.google.protobuf.UnknownFieldSet + getUnknownFields() { + return this.unknownFields; + } + private GroupMemberLeftMessage( + com.google.protobuf.CodedInputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + initFields(); + com.google.protobuf.UnknownFieldSet.Builder unknownFields = + com.google.protobuf.UnknownFieldSet.newBuilder(); + try { + boolean done = false; + while (!done) { + int tag = input.readTag(); + switch (tag) { + case 0: + done = true; + break; + default: { + if (!parseUnknownField(input, unknownFields, + extensionRegistry, tag)) { + done = true; + } + break; + } + } + } + } catch (com.google.protobuf.InvalidProtocolBufferException e) { + throw e.setUnfinishedMessage(this); + } catch (java.io.IOException e) { + throw new com.google.protobuf.InvalidProtocolBufferException( + e.getMessage()).setUnfinishedMessage(this); + } finally { + this.unknownFields = unknownFields.build(); + makeExtensionsImmutable(); + } + } + public static final com.google.protobuf.Descriptors.Descriptor + getDescriptor() { + return org.session.libsignal.protos.SignalServiceProtos.internal_static_signalservice_GroupMemberLeftMessage_descriptor; + } + + protected com.google.protobuf.GeneratedMessage.FieldAccessorTable + internalGetFieldAccessorTable() { + return org.session.libsignal.protos.SignalServiceProtos.internal_static_signalservice_GroupMemberLeftMessage_fieldAccessorTable + .ensureFieldAccessorsInitialized( + org.session.libsignal.protos.SignalServiceProtos.GroupMemberLeftMessage.class, org.session.libsignal.protos.SignalServiceProtos.GroupMemberLeftMessage.Builder.class); + } + + public static com.google.protobuf.Parser PARSER = + new com.google.protobuf.AbstractParser() { + public GroupMemberLeftMessage parsePartialFrom( + com.google.protobuf.CodedInputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + return new GroupMemberLeftMessage(input, extensionRegistry); + } + }; + + @java.lang.Override + public com.google.protobuf.Parser getParserForType() { + return PARSER; + } + + private void initFields() { + } + private byte memoizedIsInitialized = -1; + public final boolean isInitialized() { + byte isInitialized = memoizedIsInitialized; + if (isInitialized != -1) return isInitialized == 1; + + memoizedIsInitialized = 1; + return true; + } + + public void writeTo(com.google.protobuf.CodedOutputStream output) + throws java.io.IOException { + getSerializedSize(); + getUnknownFields().writeTo(output); + } + + private int memoizedSerializedSize = -1; + public int getSerializedSize() { + int size = memoizedSerializedSize; + if (size != -1) return size; + + size = 0; + size += getUnknownFields().getSerializedSize(); + memoizedSerializedSize = size; + return size; + } + + private static final long serialVersionUID = 0L; + @java.lang.Override + protected java.lang.Object writeReplace() + throws java.io.ObjectStreamException { + return super.writeReplace(); + } + + public static org.session.libsignal.protos.SignalServiceProtos.GroupMemberLeftMessage parseFrom( + com.google.protobuf.ByteString data) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data); + } + public static org.session.libsignal.protos.SignalServiceProtos.GroupMemberLeftMessage parseFrom( + com.google.protobuf.ByteString data, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data, extensionRegistry); + } + public static org.session.libsignal.protos.SignalServiceProtos.GroupMemberLeftMessage parseFrom(byte[] data) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data); + } + public static org.session.libsignal.protos.SignalServiceProtos.GroupMemberLeftMessage parseFrom( + byte[] data, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data, extensionRegistry); + } + public static org.session.libsignal.protos.SignalServiceProtos.GroupMemberLeftMessage parseFrom(java.io.InputStream input) + throws java.io.IOException { + return PARSER.parseFrom(input); + } + public static org.session.libsignal.protos.SignalServiceProtos.GroupMemberLeftMessage parseFrom( + java.io.InputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws java.io.IOException { + return PARSER.parseFrom(input, extensionRegistry); + } + public static org.session.libsignal.protos.SignalServiceProtos.GroupMemberLeftMessage parseDelimitedFrom(java.io.InputStream input) + throws java.io.IOException { + return PARSER.parseDelimitedFrom(input); + } + public static org.session.libsignal.protos.SignalServiceProtos.GroupMemberLeftMessage parseDelimitedFrom( + java.io.InputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws java.io.IOException { + return PARSER.parseDelimitedFrom(input, extensionRegistry); + } + public static org.session.libsignal.protos.SignalServiceProtos.GroupMemberLeftMessage parseFrom( + com.google.protobuf.CodedInputStream input) + throws java.io.IOException { + return PARSER.parseFrom(input); + } + public static org.session.libsignal.protos.SignalServiceProtos.GroupMemberLeftMessage parseFrom( + com.google.protobuf.CodedInputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws java.io.IOException { + return PARSER.parseFrom(input, extensionRegistry); + } + + public static Builder newBuilder() { return Builder.create(); } + public Builder newBuilderForType() { return newBuilder(); } + public static Builder newBuilder(org.session.libsignal.protos.SignalServiceProtos.GroupMemberLeftMessage prototype) { + return newBuilder().mergeFrom(prototype); + } + public Builder toBuilder() { return newBuilder(this); } + + @java.lang.Override + protected Builder newBuilderForType( + com.google.protobuf.GeneratedMessage.BuilderParent parent) { + Builder builder = new Builder(parent); + return builder; + } + /** + * Protobuf type {@code signalservice.GroupMemberLeftMessage} + * + *
+     * the pubkey of the member left is included as part of the closed group encryption logic (senderIdentity on desktop)
+     * 
+ */ + public static final class Builder extends + com.google.protobuf.GeneratedMessage.Builder + implements org.session.libsignal.protos.SignalServiceProtos.GroupMemberLeftMessageOrBuilder { + public static final com.google.protobuf.Descriptors.Descriptor + getDescriptor() { + return org.session.libsignal.protos.SignalServiceProtos.internal_static_signalservice_GroupMemberLeftMessage_descriptor; + } + + protected com.google.protobuf.GeneratedMessage.FieldAccessorTable + internalGetFieldAccessorTable() { + return org.session.libsignal.protos.SignalServiceProtos.internal_static_signalservice_GroupMemberLeftMessage_fieldAccessorTable + .ensureFieldAccessorsInitialized( + org.session.libsignal.protos.SignalServiceProtos.GroupMemberLeftMessage.class, org.session.libsignal.protos.SignalServiceProtos.GroupMemberLeftMessage.Builder.class); + } + + // Construct using org.session.libsignal.protos.SignalServiceProtos.GroupMemberLeftMessage.newBuilder() + private Builder() { + maybeForceBuilderInitialization(); + } + + private Builder( + com.google.protobuf.GeneratedMessage.BuilderParent parent) { + super(parent); + maybeForceBuilderInitialization(); + } + private void maybeForceBuilderInitialization() { + if (com.google.protobuf.GeneratedMessage.alwaysUseFieldBuilders) { + } + } + private static Builder create() { + return new Builder(); + } + + public Builder clear() { + super.clear(); + return this; + } + + public Builder clone() { + return create().mergeFrom(buildPartial()); + } + + public com.google.protobuf.Descriptors.Descriptor + getDescriptorForType() { + return org.session.libsignal.protos.SignalServiceProtos.internal_static_signalservice_GroupMemberLeftMessage_descriptor; + } + + public org.session.libsignal.protos.SignalServiceProtos.GroupMemberLeftMessage getDefaultInstanceForType() { + return org.session.libsignal.protos.SignalServiceProtos.GroupMemberLeftMessage.getDefaultInstance(); + } + + public org.session.libsignal.protos.SignalServiceProtos.GroupMemberLeftMessage build() { + org.session.libsignal.protos.SignalServiceProtos.GroupMemberLeftMessage result = buildPartial(); + if (!result.isInitialized()) { + throw newUninitializedMessageException(result); + } + return result; + } + + public org.session.libsignal.protos.SignalServiceProtos.GroupMemberLeftMessage buildPartial() { + org.session.libsignal.protos.SignalServiceProtos.GroupMemberLeftMessage result = new org.session.libsignal.protos.SignalServiceProtos.GroupMemberLeftMessage(this); + onBuilt(); + return result; + } + + public Builder mergeFrom(com.google.protobuf.Message other) { + if (other instanceof org.session.libsignal.protos.SignalServiceProtos.GroupMemberLeftMessage) { + return mergeFrom((org.session.libsignal.protos.SignalServiceProtos.GroupMemberLeftMessage)other); + } else { + super.mergeFrom(other); + return this; + } + } + + public Builder mergeFrom(org.session.libsignal.protos.SignalServiceProtos.GroupMemberLeftMessage other) { + if (other == org.session.libsignal.protos.SignalServiceProtos.GroupMemberLeftMessage.getDefaultInstance()) return this; + this.mergeUnknownFields(other.getUnknownFields()); + return this; + } + + public final boolean isInitialized() { + return true; + } + + public Builder mergeFrom( + com.google.protobuf.CodedInputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws java.io.IOException { + org.session.libsignal.protos.SignalServiceProtos.GroupMemberLeftMessage parsedMessage = null; + try { + parsedMessage = PARSER.parsePartialFrom(input, extensionRegistry); + } catch (com.google.protobuf.InvalidProtocolBufferException e) { + parsedMessage = (org.session.libsignal.protos.SignalServiceProtos.GroupMemberLeftMessage) e.getUnfinishedMessage(); + throw e; + } finally { + if (parsedMessage != null) { + mergeFrom(parsedMessage); + } + } + return this; + } + + // @@protoc_insertion_point(builder_scope:signalservice.GroupMemberLeftMessage) + } + + static { + defaultInstance = new GroupMemberLeftMessage(true); + defaultInstance.initFields(); + } + + // @@protoc_insertion_point(class_scope:signalservice.GroupMemberLeftMessage) + } + + public interface GroupInviteMessageOrBuilder + extends com.google.protobuf.MessageOrBuilder { + + // required bytes publicKey = 1; + /** + * required bytes publicKey = 1; + * + *
+     * @required
+     * 
+ */ + boolean hasPublicKey(); + /** + * required bytes publicKey = 1; + * + *
+     * @required
+     * 
+ */ + com.google.protobuf.ByteString getPublicKey(); + + // required string name = 2; + /** + * required string name = 2; + * + *
+     * @required
+     * 
+ */ + boolean hasName(); + /** + * required string name = 2; + * + *
+     * @required
+     * 
+ */ + java.lang.String getName(); + /** + * required string name = 2; + * + *
+     * @required
+     * 
+ */ + com.google.protobuf.ByteString + getNameBytes(); + + // required bytes memberPrivateKey = 3; + /** + * required bytes memberPrivateKey = 3; + * + *
+     * @required
+     * 
+ */ + boolean hasMemberPrivateKey(); + /** + * required bytes memberPrivateKey = 3; + * + *
+     * @required
+     * 
+ */ + com.google.protobuf.ByteString getMemberPrivateKey(); + } + /** + * Protobuf type {@code signalservice.GroupInviteMessage} + */ + public static final class GroupInviteMessage extends + com.google.protobuf.GeneratedMessage + implements GroupInviteMessageOrBuilder { + // Use GroupInviteMessage.newBuilder() to construct. + private GroupInviteMessage(com.google.protobuf.GeneratedMessage.Builder builder) { + super(builder); + this.unknownFields = builder.getUnknownFields(); + } + private GroupInviteMessage(boolean noInit) { this.unknownFields = com.google.protobuf.UnknownFieldSet.getDefaultInstance(); } + + private static final GroupInviteMessage defaultInstance; + public static GroupInviteMessage getDefaultInstance() { + return defaultInstance; + } + + public GroupInviteMessage getDefaultInstanceForType() { + return defaultInstance; + } + + private final com.google.protobuf.UnknownFieldSet unknownFields; + @java.lang.Override + public final com.google.protobuf.UnknownFieldSet + getUnknownFields() { + return this.unknownFields; + } + private GroupInviteMessage( + com.google.protobuf.CodedInputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + initFields(); + int mutable_bitField0_ = 0; + com.google.protobuf.UnknownFieldSet.Builder unknownFields = + com.google.protobuf.UnknownFieldSet.newBuilder(); + try { + boolean done = false; + while (!done) { + int tag = input.readTag(); + switch (tag) { + case 0: + done = true; + break; + default: { + if (!parseUnknownField(input, unknownFields, + extensionRegistry, tag)) { + done = true; + } + break; + } + case 10: { + bitField0_ |= 0x00000001; + publicKey_ = input.readBytes(); + break; + } + case 18: { + bitField0_ |= 0x00000002; + name_ = input.readBytes(); + break; + } + case 26: { + bitField0_ |= 0x00000004; + memberPrivateKey_ = input.readBytes(); + break; + } + } + } + } catch (com.google.protobuf.InvalidProtocolBufferException e) { + throw e.setUnfinishedMessage(this); + } catch (java.io.IOException e) { + throw new com.google.protobuf.InvalidProtocolBufferException( + e.getMessage()).setUnfinishedMessage(this); + } finally { + this.unknownFields = unknownFields.build(); + makeExtensionsImmutable(); + } + } + public static final com.google.protobuf.Descriptors.Descriptor + getDescriptor() { + return org.session.libsignal.protos.SignalServiceProtos.internal_static_signalservice_GroupInviteMessage_descriptor; + } + + protected com.google.protobuf.GeneratedMessage.FieldAccessorTable + internalGetFieldAccessorTable() { + return org.session.libsignal.protos.SignalServiceProtos.internal_static_signalservice_GroupInviteMessage_fieldAccessorTable + .ensureFieldAccessorsInitialized( + org.session.libsignal.protos.SignalServiceProtos.GroupInviteMessage.class, org.session.libsignal.protos.SignalServiceProtos.GroupInviteMessage.Builder.class); + } + + public static com.google.protobuf.Parser PARSER = + new com.google.protobuf.AbstractParser() { + public GroupInviteMessage parsePartialFrom( + com.google.protobuf.CodedInputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + return new GroupInviteMessage(input, extensionRegistry); + } + }; + + @java.lang.Override + public com.google.protobuf.Parser getParserForType() { + return PARSER; + } + + private int bitField0_; + // required bytes publicKey = 1; + public static final int PUBLICKEY_FIELD_NUMBER = 1; + private com.google.protobuf.ByteString publicKey_; + /** + * required bytes publicKey = 1; + * + *
+     * @required
+     * 
+ */ + public boolean hasPublicKey() { + return ((bitField0_ & 0x00000001) == 0x00000001); + } + /** + * required bytes publicKey = 1; + * + *
+     * @required
+     * 
+ */ + public com.google.protobuf.ByteString getPublicKey() { + return publicKey_; + } + + // required string name = 2; + public static final int NAME_FIELD_NUMBER = 2; + private java.lang.Object name_; + /** + * required string name = 2; + * + *
+     * @required
+     * 
+ */ + public boolean hasName() { + return ((bitField0_ & 0x00000002) == 0x00000002); + } + /** + * required string name = 2; + * + *
+     * @required
+     * 
+ */ + public java.lang.String getName() { + java.lang.Object ref = name_; + if (ref instanceof java.lang.String) { + return (java.lang.String) ref; + } else { + com.google.protobuf.ByteString bs = + (com.google.protobuf.ByteString) ref; + java.lang.String s = bs.toStringUtf8(); + if (bs.isValidUtf8()) { + name_ = s; + } + return s; + } + } + /** + * required string name = 2; + * + *
+     * @required
+     * 
+ */ + public com.google.protobuf.ByteString + getNameBytes() { + java.lang.Object ref = name_; + if (ref instanceof java.lang.String) { + com.google.protobuf.ByteString b = + com.google.protobuf.ByteString.copyFromUtf8( + (java.lang.String) ref); + name_ = b; + return b; + } else { + return (com.google.protobuf.ByteString) ref; + } + } + + // required bytes memberPrivateKey = 3; + public static final int MEMBERPRIVATEKEY_FIELD_NUMBER = 3; + private com.google.protobuf.ByteString memberPrivateKey_; + /** + * required bytes memberPrivateKey = 3; + * + *
+     * @required
+     * 
+ */ + public boolean hasMemberPrivateKey() { + return ((bitField0_ & 0x00000004) == 0x00000004); + } + /** + * required bytes memberPrivateKey = 3; + * + *
+     * @required
+     * 
+ */ + public com.google.protobuf.ByteString getMemberPrivateKey() { + return memberPrivateKey_; + } + + private void initFields() { + publicKey_ = com.google.protobuf.ByteString.EMPTY; + name_ = ""; + memberPrivateKey_ = com.google.protobuf.ByteString.EMPTY; + } + private byte memoizedIsInitialized = -1; + public final boolean isInitialized() { + byte isInitialized = memoizedIsInitialized; + if (isInitialized != -1) return isInitialized == 1; + + if (!hasPublicKey()) { + memoizedIsInitialized = 0; + return false; + } + if (!hasName()) { + memoizedIsInitialized = 0; + return false; + } + if (!hasMemberPrivateKey()) { + memoizedIsInitialized = 0; + return false; + } + memoizedIsInitialized = 1; + return true; + } + + public void writeTo(com.google.protobuf.CodedOutputStream output) + throws java.io.IOException { + getSerializedSize(); + if (((bitField0_ & 0x00000001) == 0x00000001)) { + output.writeBytes(1, publicKey_); + } + if (((bitField0_ & 0x00000002) == 0x00000002)) { + output.writeBytes(2, getNameBytes()); + } + if (((bitField0_ & 0x00000004) == 0x00000004)) { + output.writeBytes(3, memberPrivateKey_); + } + getUnknownFields().writeTo(output); + } + + private int memoizedSerializedSize = -1; + public int getSerializedSize() { + int size = memoizedSerializedSize; + if (size != -1) return size; + + size = 0; + if (((bitField0_ & 0x00000001) == 0x00000001)) { + size += com.google.protobuf.CodedOutputStream + .computeBytesSize(1, publicKey_); + } + if (((bitField0_ & 0x00000002) == 0x00000002)) { + size += com.google.protobuf.CodedOutputStream + .computeBytesSize(2, getNameBytes()); + } + if (((bitField0_ & 0x00000004) == 0x00000004)) { + size += com.google.protobuf.CodedOutputStream + .computeBytesSize(3, memberPrivateKey_); + } + size += getUnknownFields().getSerializedSize(); + memoizedSerializedSize = size; + return size; + } + + private static final long serialVersionUID = 0L; + @java.lang.Override + protected java.lang.Object writeReplace() + throws java.io.ObjectStreamException { + return super.writeReplace(); + } + + public static org.session.libsignal.protos.SignalServiceProtos.GroupInviteMessage parseFrom( + com.google.protobuf.ByteString data) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data); + } + public static org.session.libsignal.protos.SignalServiceProtos.GroupInviteMessage parseFrom( + com.google.protobuf.ByteString data, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data, extensionRegistry); + } + public static org.session.libsignal.protos.SignalServiceProtos.GroupInviteMessage parseFrom(byte[] data) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data); + } + public static org.session.libsignal.protos.SignalServiceProtos.GroupInviteMessage parseFrom( + byte[] data, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data, extensionRegistry); + } + public static org.session.libsignal.protos.SignalServiceProtos.GroupInviteMessage parseFrom(java.io.InputStream input) + throws java.io.IOException { + return PARSER.parseFrom(input); + } + public static org.session.libsignal.protos.SignalServiceProtos.GroupInviteMessage parseFrom( + java.io.InputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws java.io.IOException { + return PARSER.parseFrom(input, extensionRegistry); + } + public static org.session.libsignal.protos.SignalServiceProtos.GroupInviteMessage parseDelimitedFrom(java.io.InputStream input) + throws java.io.IOException { + return PARSER.parseDelimitedFrom(input); + } + public static org.session.libsignal.protos.SignalServiceProtos.GroupInviteMessage parseDelimitedFrom( + java.io.InputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws java.io.IOException { + return PARSER.parseDelimitedFrom(input, extensionRegistry); + } + public static org.session.libsignal.protos.SignalServiceProtos.GroupInviteMessage parseFrom( + com.google.protobuf.CodedInputStream input) + throws java.io.IOException { + return PARSER.parseFrom(input); + } + public static org.session.libsignal.protos.SignalServiceProtos.GroupInviteMessage parseFrom( + com.google.protobuf.CodedInputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws java.io.IOException { + return PARSER.parseFrom(input, extensionRegistry); + } + + public static Builder newBuilder() { return Builder.create(); } + public Builder newBuilderForType() { return newBuilder(); } + public static Builder newBuilder(org.session.libsignal.protos.SignalServiceProtos.GroupInviteMessage prototype) { + return newBuilder().mergeFrom(prototype); + } + public Builder toBuilder() { return newBuilder(this); } + + @java.lang.Override + protected Builder newBuilderForType( + com.google.protobuf.GeneratedMessage.BuilderParent parent) { + Builder builder = new Builder(parent); + return builder; + } + /** + * Protobuf type {@code signalservice.GroupInviteMessage} + */ + public static final class Builder extends + com.google.protobuf.GeneratedMessage.Builder + implements org.session.libsignal.protos.SignalServiceProtos.GroupInviteMessageOrBuilder { + public static final com.google.protobuf.Descriptors.Descriptor + getDescriptor() { + return org.session.libsignal.protos.SignalServiceProtos.internal_static_signalservice_GroupInviteMessage_descriptor; + } + + protected com.google.protobuf.GeneratedMessage.FieldAccessorTable + internalGetFieldAccessorTable() { + return org.session.libsignal.protos.SignalServiceProtos.internal_static_signalservice_GroupInviteMessage_fieldAccessorTable + .ensureFieldAccessorsInitialized( + org.session.libsignal.protos.SignalServiceProtos.GroupInviteMessage.class, org.session.libsignal.protos.SignalServiceProtos.GroupInviteMessage.Builder.class); + } + + // Construct using org.session.libsignal.protos.SignalServiceProtos.GroupInviteMessage.newBuilder() + private Builder() { + maybeForceBuilderInitialization(); + } + + private Builder( + com.google.protobuf.GeneratedMessage.BuilderParent parent) { + super(parent); + maybeForceBuilderInitialization(); + } + private void maybeForceBuilderInitialization() { + if (com.google.protobuf.GeneratedMessage.alwaysUseFieldBuilders) { + } + } + private static Builder create() { + return new Builder(); + } + + public Builder clear() { + super.clear(); + publicKey_ = com.google.protobuf.ByteString.EMPTY; + bitField0_ = (bitField0_ & ~0x00000001); + name_ = ""; + bitField0_ = (bitField0_ & ~0x00000002); + memberPrivateKey_ = com.google.protobuf.ByteString.EMPTY; + bitField0_ = (bitField0_ & ~0x00000004); + return this; + } + + public Builder clone() { + return create().mergeFrom(buildPartial()); + } + + public com.google.protobuf.Descriptors.Descriptor + getDescriptorForType() { + return org.session.libsignal.protos.SignalServiceProtos.internal_static_signalservice_GroupInviteMessage_descriptor; + } + + public org.session.libsignal.protos.SignalServiceProtos.GroupInviteMessage getDefaultInstanceForType() { + return org.session.libsignal.protos.SignalServiceProtos.GroupInviteMessage.getDefaultInstance(); + } + + public org.session.libsignal.protos.SignalServiceProtos.GroupInviteMessage build() { + org.session.libsignal.protos.SignalServiceProtos.GroupInviteMessage result = buildPartial(); + if (!result.isInitialized()) { + throw newUninitializedMessageException(result); + } + return result; + } + + public org.session.libsignal.protos.SignalServiceProtos.GroupInviteMessage buildPartial() { + org.session.libsignal.protos.SignalServiceProtos.GroupInviteMessage result = new org.session.libsignal.protos.SignalServiceProtos.GroupInviteMessage(this); + int from_bitField0_ = bitField0_; + int to_bitField0_ = 0; + if (((from_bitField0_ & 0x00000001) == 0x00000001)) { + to_bitField0_ |= 0x00000001; + } + result.publicKey_ = publicKey_; + if (((from_bitField0_ & 0x00000002) == 0x00000002)) { + to_bitField0_ |= 0x00000002; + } + result.name_ = name_; + if (((from_bitField0_ & 0x00000004) == 0x00000004)) { + to_bitField0_ |= 0x00000004; + } + result.memberPrivateKey_ = memberPrivateKey_; + result.bitField0_ = to_bitField0_; + onBuilt(); + return result; + } + + public Builder mergeFrom(com.google.protobuf.Message other) { + if (other instanceof org.session.libsignal.protos.SignalServiceProtos.GroupInviteMessage) { + return mergeFrom((org.session.libsignal.protos.SignalServiceProtos.GroupInviteMessage)other); + } else { + super.mergeFrom(other); + return this; + } + } + + public Builder mergeFrom(org.session.libsignal.protos.SignalServiceProtos.GroupInviteMessage other) { + if (other == org.session.libsignal.protos.SignalServiceProtos.GroupInviteMessage.getDefaultInstance()) return this; + if (other.hasPublicKey()) { + setPublicKey(other.getPublicKey()); + } + if (other.hasName()) { + bitField0_ |= 0x00000002; + name_ = other.name_; + onChanged(); + } + if (other.hasMemberPrivateKey()) { + setMemberPrivateKey(other.getMemberPrivateKey()); + } + this.mergeUnknownFields(other.getUnknownFields()); + return this; + } + + public final boolean isInitialized() { + if (!hasPublicKey()) { + + return false; + } + if (!hasName()) { + + return false; + } + if (!hasMemberPrivateKey()) { + + return false; + } + return true; + } + + public Builder mergeFrom( + com.google.protobuf.CodedInputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws java.io.IOException { + org.session.libsignal.protos.SignalServiceProtos.GroupInviteMessage parsedMessage = null; + try { + parsedMessage = PARSER.parsePartialFrom(input, extensionRegistry); + } catch (com.google.protobuf.InvalidProtocolBufferException e) { + parsedMessage = (org.session.libsignal.protos.SignalServiceProtos.GroupInviteMessage) e.getUnfinishedMessage(); + throw e; + } finally { + if (parsedMessage != null) { + mergeFrom(parsedMessage); + } + } + return this; + } + private int bitField0_; + + // required bytes publicKey = 1; + private com.google.protobuf.ByteString publicKey_ = com.google.protobuf.ByteString.EMPTY; + /** + * required bytes publicKey = 1; + * + *
+       * @required
+       * 
+ */ + public boolean hasPublicKey() { + return ((bitField0_ & 0x00000001) == 0x00000001); + } + /** + * required bytes publicKey = 1; + * + *
+       * @required
+       * 
+ */ + public com.google.protobuf.ByteString getPublicKey() { + return publicKey_; + } + /** + * required bytes publicKey = 1; + * + *
+       * @required
+       * 
+ */ + public Builder setPublicKey(com.google.protobuf.ByteString value) { + if (value == null) { + throw new NullPointerException(); + } + bitField0_ |= 0x00000001; + publicKey_ = value; + onChanged(); + return this; + } + /** + * required bytes publicKey = 1; + * + *
+       * @required
+       * 
+ */ + public Builder clearPublicKey() { + bitField0_ = (bitField0_ & ~0x00000001); + publicKey_ = getDefaultInstance().getPublicKey(); + onChanged(); + return this; + } + + // required string name = 2; + private java.lang.Object name_ = ""; + /** + * required string name = 2; + * + *
+       * @required
+       * 
+ */ + public boolean hasName() { + return ((bitField0_ & 0x00000002) == 0x00000002); + } + /** + * required string name = 2; + * + *
+       * @required
+       * 
+ */ + public java.lang.String getName() { + java.lang.Object ref = name_; + if (!(ref instanceof java.lang.String)) { + java.lang.String s = ((com.google.protobuf.ByteString) ref) + .toStringUtf8(); + name_ = s; + return s; + } else { + return (java.lang.String) ref; + } + } + /** + * required string name = 2; + * + *
+       * @required
+       * 
+ */ + public com.google.protobuf.ByteString + getNameBytes() { + java.lang.Object ref = name_; + if (ref instanceof String) { + com.google.protobuf.ByteString b = + com.google.protobuf.ByteString.copyFromUtf8( + (java.lang.String) ref); + name_ = b; + return b; + } else { + return (com.google.protobuf.ByteString) ref; + } + } + /** + * required string name = 2; + * + *
+       * @required
+       * 
+ */ + public Builder setName( + java.lang.String value) { + if (value == null) { + throw new NullPointerException(); + } + bitField0_ |= 0x00000002; + name_ = value; + onChanged(); + return this; + } + /** + * required string name = 2; + * + *
+       * @required
+       * 
+ */ + public Builder clearName() { + bitField0_ = (bitField0_ & ~0x00000002); + name_ = getDefaultInstance().getName(); + onChanged(); + return this; + } + /** + * required string name = 2; + * + *
+       * @required
+       * 
+ */ + public Builder setNameBytes( + com.google.protobuf.ByteString value) { + if (value == null) { + throw new NullPointerException(); + } + bitField0_ |= 0x00000002; + name_ = value; + onChanged(); + return this; + } + + // required bytes memberPrivateKey = 3; + private com.google.protobuf.ByteString memberPrivateKey_ = com.google.protobuf.ByteString.EMPTY; + /** + * required bytes memberPrivateKey = 3; + * + *
+       * @required
+       * 
+ */ + public boolean hasMemberPrivateKey() { + return ((bitField0_ & 0x00000004) == 0x00000004); + } + /** + * required bytes memberPrivateKey = 3; + * + *
+       * @required
+       * 
+ */ + public com.google.protobuf.ByteString getMemberPrivateKey() { + return memberPrivateKey_; + } + /** + * required bytes memberPrivateKey = 3; + * + *
+       * @required
+       * 
+ */ + public Builder setMemberPrivateKey(com.google.protobuf.ByteString value) { + if (value == null) { + throw new NullPointerException(); + } + bitField0_ |= 0x00000004; + memberPrivateKey_ = value; + onChanged(); + return this; + } + /** + * required bytes memberPrivateKey = 3; + * + *
+       * @required
+       * 
+ */ + public Builder clearMemberPrivateKey() { + bitField0_ = (bitField0_ & ~0x00000004); + memberPrivateKey_ = getDefaultInstance().getMemberPrivateKey(); + onChanged(); + return this; + } + + // @@protoc_insertion_point(builder_scope:signalservice.GroupInviteMessage) + } + + static { + defaultInstance = new GroupInviteMessage(true); + defaultInstance.initFields(); + } + + // @@protoc_insertion_point(class_scope:signalservice.GroupInviteMessage) + } + + public interface GroupPromoteMessageOrBuilder + extends com.google.protobuf.MessageOrBuilder { + + // required bytes publicKey = 1; + /** + * required bytes publicKey = 1; + * + *
+     * @required
+     * 
+ */ + boolean hasPublicKey(); + /** + * required bytes publicKey = 1; + * + *
+     * @required
+     * 
+ */ + com.google.protobuf.ByteString getPublicKey(); + + // required bytes encryptedPrivateKey = 2; + /** + * required bytes encryptedPrivateKey = 2; + * + *
+     * @required
+     * 
+ */ + boolean hasEncryptedPrivateKey(); + /** + * required bytes encryptedPrivateKey = 2; + * + *
+     * @required
+     * 
+ */ + com.google.protobuf.ByteString getEncryptedPrivateKey(); + } + /** + * Protobuf type {@code signalservice.GroupPromoteMessage} + */ + public static final class GroupPromoteMessage extends + com.google.protobuf.GeneratedMessage + implements GroupPromoteMessageOrBuilder { + // Use GroupPromoteMessage.newBuilder() to construct. + private GroupPromoteMessage(com.google.protobuf.GeneratedMessage.Builder builder) { + super(builder); + this.unknownFields = builder.getUnknownFields(); + } + private GroupPromoteMessage(boolean noInit) { this.unknownFields = com.google.protobuf.UnknownFieldSet.getDefaultInstance(); } + + private static final GroupPromoteMessage defaultInstance; + public static GroupPromoteMessage getDefaultInstance() { + return defaultInstance; + } + + public GroupPromoteMessage getDefaultInstanceForType() { + return defaultInstance; + } + + private final com.google.protobuf.UnknownFieldSet unknownFields; + @java.lang.Override + public final com.google.protobuf.UnknownFieldSet + getUnknownFields() { + return this.unknownFields; + } + private GroupPromoteMessage( + com.google.protobuf.CodedInputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + initFields(); + int mutable_bitField0_ = 0; + com.google.protobuf.UnknownFieldSet.Builder unknownFields = + com.google.protobuf.UnknownFieldSet.newBuilder(); + try { + boolean done = false; + while (!done) { + int tag = input.readTag(); + switch (tag) { + case 0: + done = true; + break; + default: { + if (!parseUnknownField(input, unknownFields, + extensionRegistry, tag)) { + done = true; + } + break; + } + case 10: { + bitField0_ |= 0x00000001; + publicKey_ = input.readBytes(); + break; + } + case 18: { + bitField0_ |= 0x00000002; + encryptedPrivateKey_ = input.readBytes(); + break; + } + } + } + } catch (com.google.protobuf.InvalidProtocolBufferException e) { + throw e.setUnfinishedMessage(this); + } catch (java.io.IOException e) { + throw new com.google.protobuf.InvalidProtocolBufferException( + e.getMessage()).setUnfinishedMessage(this); + } finally { + this.unknownFields = unknownFields.build(); + makeExtensionsImmutable(); + } + } + public static final com.google.protobuf.Descriptors.Descriptor + getDescriptor() { + return org.session.libsignal.protos.SignalServiceProtos.internal_static_signalservice_GroupPromoteMessage_descriptor; + } + + protected com.google.protobuf.GeneratedMessage.FieldAccessorTable + internalGetFieldAccessorTable() { + return org.session.libsignal.protos.SignalServiceProtos.internal_static_signalservice_GroupPromoteMessage_fieldAccessorTable + .ensureFieldAccessorsInitialized( + org.session.libsignal.protos.SignalServiceProtos.GroupPromoteMessage.class, org.session.libsignal.protos.SignalServiceProtos.GroupPromoteMessage.Builder.class); + } + + public static com.google.protobuf.Parser PARSER = + new com.google.protobuf.AbstractParser() { + public GroupPromoteMessage parsePartialFrom( + com.google.protobuf.CodedInputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + return new GroupPromoteMessage(input, extensionRegistry); + } + }; + + @java.lang.Override + public com.google.protobuf.Parser getParserForType() { + return PARSER; + } + + private int bitField0_; + // required bytes publicKey = 1; + public static final int PUBLICKEY_FIELD_NUMBER = 1; + private com.google.protobuf.ByteString publicKey_; + /** + * required bytes publicKey = 1; + * + *
+     * @required
+     * 
+ */ + public boolean hasPublicKey() { + return ((bitField0_ & 0x00000001) == 0x00000001); + } + /** + * required bytes publicKey = 1; + * + *
+     * @required
+     * 
+ */ + public com.google.protobuf.ByteString getPublicKey() { + return publicKey_; + } + + // required bytes encryptedPrivateKey = 2; + public static final int ENCRYPTEDPRIVATEKEY_FIELD_NUMBER = 2; + private com.google.protobuf.ByteString encryptedPrivateKey_; + /** + * required bytes encryptedPrivateKey = 2; + * + *
+     * @required
+     * 
+ */ + public boolean hasEncryptedPrivateKey() { + return ((bitField0_ & 0x00000002) == 0x00000002); + } + /** + * required bytes encryptedPrivateKey = 2; + * + *
+     * @required
+     * 
+ */ + public com.google.protobuf.ByteString getEncryptedPrivateKey() { + return encryptedPrivateKey_; + } + + private void initFields() { + publicKey_ = com.google.protobuf.ByteString.EMPTY; + encryptedPrivateKey_ = com.google.protobuf.ByteString.EMPTY; + } + private byte memoizedIsInitialized = -1; + public final boolean isInitialized() { + byte isInitialized = memoizedIsInitialized; + if (isInitialized != -1) return isInitialized == 1; + + if (!hasPublicKey()) { + memoizedIsInitialized = 0; + return false; + } + if (!hasEncryptedPrivateKey()) { + memoizedIsInitialized = 0; + return false; + } + memoizedIsInitialized = 1; + return true; + } + + public void writeTo(com.google.protobuf.CodedOutputStream output) + throws java.io.IOException { + getSerializedSize(); + if (((bitField0_ & 0x00000001) == 0x00000001)) { + output.writeBytes(1, publicKey_); + } + if (((bitField0_ & 0x00000002) == 0x00000002)) { + output.writeBytes(2, encryptedPrivateKey_); + } + getUnknownFields().writeTo(output); + } + + private int memoizedSerializedSize = -1; + public int getSerializedSize() { + int size = memoizedSerializedSize; + if (size != -1) return size; + + size = 0; + if (((bitField0_ & 0x00000001) == 0x00000001)) { + size += com.google.protobuf.CodedOutputStream + .computeBytesSize(1, publicKey_); + } + if (((bitField0_ & 0x00000002) == 0x00000002)) { + size += com.google.protobuf.CodedOutputStream + .computeBytesSize(2, encryptedPrivateKey_); + } + size += getUnknownFields().getSerializedSize(); + memoizedSerializedSize = size; + return size; + } + + private static final long serialVersionUID = 0L; + @java.lang.Override + protected java.lang.Object writeReplace() + throws java.io.ObjectStreamException { + return super.writeReplace(); + } + + public static org.session.libsignal.protos.SignalServiceProtos.GroupPromoteMessage parseFrom( + com.google.protobuf.ByteString data) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data); + } + public static org.session.libsignal.protos.SignalServiceProtos.GroupPromoteMessage parseFrom( + com.google.protobuf.ByteString data, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data, extensionRegistry); + } + public static org.session.libsignal.protos.SignalServiceProtos.GroupPromoteMessage parseFrom(byte[] data) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data); + } + public static org.session.libsignal.protos.SignalServiceProtos.GroupPromoteMessage parseFrom( + byte[] data, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data, extensionRegistry); + } + public static org.session.libsignal.protos.SignalServiceProtos.GroupPromoteMessage parseFrom(java.io.InputStream input) + throws java.io.IOException { + return PARSER.parseFrom(input); + } + public static org.session.libsignal.protos.SignalServiceProtos.GroupPromoteMessage parseFrom( + java.io.InputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws java.io.IOException { + return PARSER.parseFrom(input, extensionRegistry); + } + public static org.session.libsignal.protos.SignalServiceProtos.GroupPromoteMessage parseDelimitedFrom(java.io.InputStream input) + throws java.io.IOException { + return PARSER.parseDelimitedFrom(input); + } + public static org.session.libsignal.protos.SignalServiceProtos.GroupPromoteMessage parseDelimitedFrom( + java.io.InputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws java.io.IOException { + return PARSER.parseDelimitedFrom(input, extensionRegistry); + } + public static org.session.libsignal.protos.SignalServiceProtos.GroupPromoteMessage parseFrom( + com.google.protobuf.CodedInputStream input) + throws java.io.IOException { + return PARSER.parseFrom(input); + } + public static org.session.libsignal.protos.SignalServiceProtos.GroupPromoteMessage parseFrom( + com.google.protobuf.CodedInputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws java.io.IOException { + return PARSER.parseFrom(input, extensionRegistry); + } + + public static Builder newBuilder() { return Builder.create(); } + public Builder newBuilderForType() { return newBuilder(); } + public static Builder newBuilder(org.session.libsignal.protos.SignalServiceProtos.GroupPromoteMessage prototype) { + return newBuilder().mergeFrom(prototype); + } + public Builder toBuilder() { return newBuilder(this); } + + @java.lang.Override + protected Builder newBuilderForType( + com.google.protobuf.GeneratedMessage.BuilderParent parent) { + Builder builder = new Builder(parent); + return builder; + } + /** + * Protobuf type {@code signalservice.GroupPromoteMessage} + */ + public static final class Builder extends + com.google.protobuf.GeneratedMessage.Builder + implements org.session.libsignal.protos.SignalServiceProtos.GroupPromoteMessageOrBuilder { + public static final com.google.protobuf.Descriptors.Descriptor + getDescriptor() { + return org.session.libsignal.protos.SignalServiceProtos.internal_static_signalservice_GroupPromoteMessage_descriptor; + } + + protected com.google.protobuf.GeneratedMessage.FieldAccessorTable + internalGetFieldAccessorTable() { + return org.session.libsignal.protos.SignalServiceProtos.internal_static_signalservice_GroupPromoteMessage_fieldAccessorTable + .ensureFieldAccessorsInitialized( + org.session.libsignal.protos.SignalServiceProtos.GroupPromoteMessage.class, org.session.libsignal.protos.SignalServiceProtos.GroupPromoteMessage.Builder.class); + } + + // Construct using org.session.libsignal.protos.SignalServiceProtos.GroupPromoteMessage.newBuilder() + private Builder() { + maybeForceBuilderInitialization(); + } + + private Builder( + com.google.protobuf.GeneratedMessage.BuilderParent parent) { + super(parent); + maybeForceBuilderInitialization(); + } + private void maybeForceBuilderInitialization() { + if (com.google.protobuf.GeneratedMessage.alwaysUseFieldBuilders) { + } + } + private static Builder create() { + return new Builder(); + } + + public Builder clear() { + super.clear(); + publicKey_ = com.google.protobuf.ByteString.EMPTY; + bitField0_ = (bitField0_ & ~0x00000001); + encryptedPrivateKey_ = com.google.protobuf.ByteString.EMPTY; + bitField0_ = (bitField0_ & ~0x00000002); + return this; + } + + public Builder clone() { + return create().mergeFrom(buildPartial()); + } + + public com.google.protobuf.Descriptors.Descriptor + getDescriptorForType() { + return org.session.libsignal.protos.SignalServiceProtos.internal_static_signalservice_GroupPromoteMessage_descriptor; + } + + public org.session.libsignal.protos.SignalServiceProtos.GroupPromoteMessage getDefaultInstanceForType() { + return org.session.libsignal.protos.SignalServiceProtos.GroupPromoteMessage.getDefaultInstance(); + } + + public org.session.libsignal.protos.SignalServiceProtos.GroupPromoteMessage build() { + org.session.libsignal.protos.SignalServiceProtos.GroupPromoteMessage result = buildPartial(); + if (!result.isInitialized()) { + throw newUninitializedMessageException(result); + } + return result; + } + + public org.session.libsignal.protos.SignalServiceProtos.GroupPromoteMessage buildPartial() { + org.session.libsignal.protos.SignalServiceProtos.GroupPromoteMessage result = new org.session.libsignal.protos.SignalServiceProtos.GroupPromoteMessage(this); + int from_bitField0_ = bitField0_; + int to_bitField0_ = 0; + if (((from_bitField0_ & 0x00000001) == 0x00000001)) { + to_bitField0_ |= 0x00000001; + } + result.publicKey_ = publicKey_; + if (((from_bitField0_ & 0x00000002) == 0x00000002)) { + to_bitField0_ |= 0x00000002; + } + result.encryptedPrivateKey_ = encryptedPrivateKey_; + result.bitField0_ = to_bitField0_; + onBuilt(); + return result; + } + + public Builder mergeFrom(com.google.protobuf.Message other) { + if (other instanceof org.session.libsignal.protos.SignalServiceProtos.GroupPromoteMessage) { + return mergeFrom((org.session.libsignal.protos.SignalServiceProtos.GroupPromoteMessage)other); + } else { + super.mergeFrom(other); + return this; + } + } + + public Builder mergeFrom(org.session.libsignal.protos.SignalServiceProtos.GroupPromoteMessage other) { + if (other == org.session.libsignal.protos.SignalServiceProtos.GroupPromoteMessage.getDefaultInstance()) return this; + if (other.hasPublicKey()) { + setPublicKey(other.getPublicKey()); + } + if (other.hasEncryptedPrivateKey()) { + setEncryptedPrivateKey(other.getEncryptedPrivateKey()); + } + this.mergeUnknownFields(other.getUnknownFields()); + return this; + } + + public final boolean isInitialized() { + if (!hasPublicKey()) { + + return false; + } + if (!hasEncryptedPrivateKey()) { + + return false; + } + return true; + } + + public Builder mergeFrom( + com.google.protobuf.CodedInputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws java.io.IOException { + org.session.libsignal.protos.SignalServiceProtos.GroupPromoteMessage parsedMessage = null; + try { + parsedMessage = PARSER.parsePartialFrom(input, extensionRegistry); + } catch (com.google.protobuf.InvalidProtocolBufferException e) { + parsedMessage = (org.session.libsignal.protos.SignalServiceProtos.GroupPromoteMessage) e.getUnfinishedMessage(); + throw e; + } finally { + if (parsedMessage != null) { + mergeFrom(parsedMessage); + } + } + return this; + } + private int bitField0_; + + // required bytes publicKey = 1; + private com.google.protobuf.ByteString publicKey_ = com.google.protobuf.ByteString.EMPTY; + /** + * required bytes publicKey = 1; + * + *
+       * @required
+       * 
+ */ + public boolean hasPublicKey() { + return ((bitField0_ & 0x00000001) == 0x00000001); + } + /** + * required bytes publicKey = 1; + * + *
+       * @required
+       * 
+ */ + public com.google.protobuf.ByteString getPublicKey() { + return publicKey_; + } + /** + * required bytes publicKey = 1; + * + *
+       * @required
+       * 
+ */ + public Builder setPublicKey(com.google.protobuf.ByteString value) { + if (value == null) { + throw new NullPointerException(); + } + bitField0_ |= 0x00000001; + publicKey_ = value; + onChanged(); + return this; + } + /** + * required bytes publicKey = 1; + * + *
+       * @required
+       * 
+ */ + public Builder clearPublicKey() { + bitField0_ = (bitField0_ & ~0x00000001); + publicKey_ = getDefaultInstance().getPublicKey(); + onChanged(); + return this; + } + + // required bytes encryptedPrivateKey = 2; + private com.google.protobuf.ByteString encryptedPrivateKey_ = com.google.protobuf.ByteString.EMPTY; + /** + * required bytes encryptedPrivateKey = 2; + * + *
+       * @required
+       * 
+ */ + public boolean hasEncryptedPrivateKey() { + return ((bitField0_ & 0x00000002) == 0x00000002); + } + /** + * required bytes encryptedPrivateKey = 2; + * + *
+       * @required
+       * 
+ */ + public com.google.protobuf.ByteString getEncryptedPrivateKey() { + return encryptedPrivateKey_; + } + /** + * required bytes encryptedPrivateKey = 2; + * + *
+       * @required
+       * 
+ */ + public Builder setEncryptedPrivateKey(com.google.protobuf.ByteString value) { + if (value == null) { + throw new NullPointerException(); + } + bitField0_ |= 0x00000002; + encryptedPrivateKey_ = value; + onChanged(); + return this; + } + /** + * required bytes encryptedPrivateKey = 2; + * + *
+       * @required
+       * 
+ */ + public Builder clearEncryptedPrivateKey() { + bitField0_ = (bitField0_ & ~0x00000002); + encryptedPrivateKey_ = getDefaultInstance().getEncryptedPrivateKey(); + onChanged(); + return this; + } + + // @@protoc_insertion_point(builder_scope:signalservice.GroupPromoteMessage) + } + + static { + defaultInstance = new GroupPromoteMessage(true); + defaultInstance.initFields(); + } + + // @@protoc_insertion_point(class_scope:signalservice.GroupPromoteMessage) + } + public interface CallMessageOrBuilder extends com.google.protobuf.MessageOrBuilder { @@ -17566,6 +21156,643 @@ public final class SignalServiceProtos { // @@protoc_insertion_point(class_scope:signalservice.CallMessage) } + public interface SharedConfigMessageOrBuilder + extends com.google.protobuf.MessageOrBuilder { + + // required .signalservice.SharedConfigMessage.Type type = 1; + /** + * required .signalservice.SharedConfigMessage.Type type = 1; + * + *
+     * @required
+     * 
+ */ + boolean hasType(); + /** + * required .signalservice.SharedConfigMessage.Type type = 1; + * + *
+     * @required
+     * 
+ */ + org.session.libsignal.protos.SignalServiceProtos.SharedConfigMessage.Type getType(); + + // required bytes data = 2; + /** + * required bytes data = 2; + */ + boolean hasData(); + /** + * required bytes data = 2; + */ + com.google.protobuf.ByteString getData(); + } + /** + * Protobuf type {@code signalservice.SharedConfigMessage} + */ + public static final class SharedConfigMessage extends + com.google.protobuf.GeneratedMessage + implements SharedConfigMessageOrBuilder { + // Use SharedConfigMessage.newBuilder() to construct. + private SharedConfigMessage(com.google.protobuf.GeneratedMessage.Builder builder) { + super(builder); + this.unknownFields = builder.getUnknownFields(); + } + private SharedConfigMessage(boolean noInit) { this.unknownFields = com.google.protobuf.UnknownFieldSet.getDefaultInstance(); } + + private static final SharedConfigMessage defaultInstance; + public static SharedConfigMessage getDefaultInstance() { + return defaultInstance; + } + + public SharedConfigMessage getDefaultInstanceForType() { + return defaultInstance; + } + + private final com.google.protobuf.UnknownFieldSet unknownFields; + @java.lang.Override + public final com.google.protobuf.UnknownFieldSet + getUnknownFields() { + return this.unknownFields; + } + private SharedConfigMessage( + com.google.protobuf.CodedInputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + initFields(); + int mutable_bitField0_ = 0; + com.google.protobuf.UnknownFieldSet.Builder unknownFields = + com.google.protobuf.UnknownFieldSet.newBuilder(); + try { + boolean done = false; + while (!done) { + int tag = input.readTag(); + switch (tag) { + case 0: + done = true; + break; + default: { + if (!parseUnknownField(input, unknownFields, + extensionRegistry, tag)) { + done = true; + } + break; + } + case 8: { + int rawValue = input.readEnum(); + org.session.libsignal.protos.SignalServiceProtos.SharedConfigMessage.Type value = org.session.libsignal.protos.SignalServiceProtos.SharedConfigMessage.Type.valueOf(rawValue); + if (value == null) { + unknownFields.mergeVarintField(1, rawValue); + } else { + bitField0_ |= 0x00000001; + type_ = value; + } + break; + } + case 18: { + bitField0_ |= 0x00000002; + data_ = input.readBytes(); + break; + } + } + } + } catch (com.google.protobuf.InvalidProtocolBufferException e) { + throw e.setUnfinishedMessage(this); + } catch (java.io.IOException e) { + throw new com.google.protobuf.InvalidProtocolBufferException( + e.getMessage()).setUnfinishedMessage(this); + } finally { + this.unknownFields = unknownFields.build(); + makeExtensionsImmutable(); + } + } + public static final com.google.protobuf.Descriptors.Descriptor + getDescriptor() { + return org.session.libsignal.protos.SignalServiceProtos.internal_static_signalservice_SharedConfigMessage_descriptor; + } + + protected com.google.protobuf.GeneratedMessage.FieldAccessorTable + internalGetFieldAccessorTable() { + return org.session.libsignal.protos.SignalServiceProtos.internal_static_signalservice_SharedConfigMessage_fieldAccessorTable + .ensureFieldAccessorsInitialized( + org.session.libsignal.protos.SignalServiceProtos.SharedConfigMessage.class, org.session.libsignal.protos.SignalServiceProtos.SharedConfigMessage.Builder.class); + } + + public static com.google.protobuf.Parser PARSER = + new com.google.protobuf.AbstractParser() { + public SharedConfigMessage parsePartialFrom( + com.google.protobuf.CodedInputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + return new SharedConfigMessage(input, extensionRegistry); + } + }; + + @java.lang.Override + public com.google.protobuf.Parser getParserForType() { + return PARSER; + } + + /** + * Protobuf enum {@code signalservice.SharedConfigMessage.Type} + */ + public enum Type + implements com.google.protobuf.ProtocolMessageEnum { + /** + * USER = 1; + */ + USER(0, 1), + /** + * CLOSED_GROUP_INFO = 2; + */ + CLOSED_GROUP_INFO(1, 2), + /** + * ENCRYPTION_KEYS = 3; + */ + ENCRYPTION_KEYS(2, 3), + /** + * CONVERSATION_READ_STATE = 4; + */ + CONVERSATION_READ_STATE(3, 4), + ; + + /** + * USER = 1; + */ + public static final int USER_VALUE = 1; + /** + * CLOSED_GROUP_INFO = 2; + */ + public static final int CLOSED_GROUP_INFO_VALUE = 2; + /** + * ENCRYPTION_KEYS = 3; + */ + public static final int ENCRYPTION_KEYS_VALUE = 3; + /** + * CONVERSATION_READ_STATE = 4; + */ + public static final int CONVERSATION_READ_STATE_VALUE = 4; + + + public final int getNumber() { return value; } + + public static Type valueOf(int value) { + switch (value) { + case 1: return USER; + case 2: return CLOSED_GROUP_INFO; + case 3: return ENCRYPTION_KEYS; + case 4: return CONVERSATION_READ_STATE; + default: return null; + } + } + + public static com.google.protobuf.Internal.EnumLiteMap + internalGetValueMap() { + return internalValueMap; + } + private static com.google.protobuf.Internal.EnumLiteMap + internalValueMap = + new com.google.protobuf.Internal.EnumLiteMap() { + public Type findValueByNumber(int number) { + return Type.valueOf(number); + } + }; + + public final com.google.protobuf.Descriptors.EnumValueDescriptor + getValueDescriptor() { + return getDescriptor().getValues().get(index); + } + public final com.google.protobuf.Descriptors.EnumDescriptor + getDescriptorForType() { + return getDescriptor(); + } + public static final com.google.protobuf.Descriptors.EnumDescriptor + getDescriptor() { + return org.session.libsignal.protos.SignalServiceProtos.SharedConfigMessage.getDescriptor().getEnumTypes().get(0); + } + + private static final Type[] VALUES = values(); + + public static Type valueOf( + com.google.protobuf.Descriptors.EnumValueDescriptor desc) { + if (desc.getType() != getDescriptor()) { + throw new java.lang.IllegalArgumentException( + "EnumValueDescriptor is not for this type."); + } + return VALUES[desc.getIndex()]; + } + + private final int index; + private final int value; + + private Type(int index, int value) { + this.index = index; + this.value = value; + } + + // @@protoc_insertion_point(enum_scope:signalservice.SharedConfigMessage.Type) + } + + private int bitField0_; + // required .signalservice.SharedConfigMessage.Type type = 1; + public static final int TYPE_FIELD_NUMBER = 1; + private org.session.libsignal.protos.SignalServiceProtos.SharedConfigMessage.Type type_; + /** + * required .signalservice.SharedConfigMessage.Type type = 1; + * + *
+     * @required
+     * 
+ */ + public boolean hasType() { + return ((bitField0_ & 0x00000001) == 0x00000001); + } + /** + * required .signalservice.SharedConfigMessage.Type type = 1; + * + *
+     * @required
+     * 
+ */ + public org.session.libsignal.protos.SignalServiceProtos.SharedConfigMessage.Type getType() { + return type_; + } + + // required bytes data = 2; + public static final int DATA_FIELD_NUMBER = 2; + private com.google.protobuf.ByteString data_; + /** + * required bytes data = 2; + */ + public boolean hasData() { + return ((bitField0_ & 0x00000002) == 0x00000002); + } + /** + * required bytes data = 2; + */ + public com.google.protobuf.ByteString getData() { + return data_; + } + + private void initFields() { + type_ = org.session.libsignal.protos.SignalServiceProtos.SharedConfigMessage.Type.USER; + data_ = com.google.protobuf.ByteString.EMPTY; + } + private byte memoizedIsInitialized = -1; + public final boolean isInitialized() { + byte isInitialized = memoizedIsInitialized; + if (isInitialized != -1) return isInitialized == 1; + + if (!hasType()) { + memoizedIsInitialized = 0; + return false; + } + if (!hasData()) { + memoizedIsInitialized = 0; + return false; + } + memoizedIsInitialized = 1; + return true; + } + + public void writeTo(com.google.protobuf.CodedOutputStream output) + throws java.io.IOException { + getSerializedSize(); + if (((bitField0_ & 0x00000001) == 0x00000001)) { + output.writeEnum(1, type_.getNumber()); + } + if (((bitField0_ & 0x00000002) == 0x00000002)) { + output.writeBytes(2, data_); + } + getUnknownFields().writeTo(output); + } + + private int memoizedSerializedSize = -1; + public int getSerializedSize() { + int size = memoizedSerializedSize; + if (size != -1) return size; + + size = 0; + if (((bitField0_ & 0x00000001) == 0x00000001)) { + size += com.google.protobuf.CodedOutputStream + .computeEnumSize(1, type_.getNumber()); + } + if (((bitField0_ & 0x00000002) == 0x00000002)) { + size += com.google.protobuf.CodedOutputStream + .computeBytesSize(2, data_); + } + size += getUnknownFields().getSerializedSize(); + memoizedSerializedSize = size; + return size; + } + + private static final long serialVersionUID = 0L; + @java.lang.Override + protected java.lang.Object writeReplace() + throws java.io.ObjectStreamException { + return super.writeReplace(); + } + + public static org.session.libsignal.protos.SignalServiceProtos.SharedConfigMessage parseFrom( + com.google.protobuf.ByteString data) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data); + } + public static org.session.libsignal.protos.SignalServiceProtos.SharedConfigMessage parseFrom( + com.google.protobuf.ByteString data, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data, extensionRegistry); + } + public static org.session.libsignal.protos.SignalServiceProtos.SharedConfigMessage parseFrom(byte[] data) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data); + } + public static org.session.libsignal.protos.SignalServiceProtos.SharedConfigMessage parseFrom( + byte[] data, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws com.google.protobuf.InvalidProtocolBufferException { + return PARSER.parseFrom(data, extensionRegistry); + } + public static org.session.libsignal.protos.SignalServiceProtos.SharedConfigMessage parseFrom(java.io.InputStream input) + throws java.io.IOException { + return PARSER.parseFrom(input); + } + public static org.session.libsignal.protos.SignalServiceProtos.SharedConfigMessage parseFrom( + java.io.InputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws java.io.IOException { + return PARSER.parseFrom(input, extensionRegistry); + } + public static org.session.libsignal.protos.SignalServiceProtos.SharedConfigMessage parseDelimitedFrom(java.io.InputStream input) + throws java.io.IOException { + return PARSER.parseDelimitedFrom(input); + } + public static org.session.libsignal.protos.SignalServiceProtos.SharedConfigMessage parseDelimitedFrom( + java.io.InputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws java.io.IOException { + return PARSER.parseDelimitedFrom(input, extensionRegistry); + } + public static org.session.libsignal.protos.SignalServiceProtos.SharedConfigMessage parseFrom( + com.google.protobuf.CodedInputStream input) + throws java.io.IOException { + return PARSER.parseFrom(input); + } + public static org.session.libsignal.protos.SignalServiceProtos.SharedConfigMessage parseFrom( + com.google.protobuf.CodedInputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws java.io.IOException { + return PARSER.parseFrom(input, extensionRegistry); + } + + public static Builder newBuilder() { return Builder.create(); } + public Builder newBuilderForType() { return newBuilder(); } + public static Builder newBuilder(org.session.libsignal.protos.SignalServiceProtos.SharedConfigMessage prototype) { + return newBuilder().mergeFrom(prototype); + } + public Builder toBuilder() { return newBuilder(this); } + + @java.lang.Override + protected Builder newBuilderForType( + com.google.protobuf.GeneratedMessage.BuilderParent parent) { + Builder builder = new Builder(parent); + return builder; + } + /** + * Protobuf type {@code signalservice.SharedConfigMessage} + */ + public static final class Builder extends + com.google.protobuf.GeneratedMessage.Builder + implements org.session.libsignal.protos.SignalServiceProtos.SharedConfigMessageOrBuilder { + public static final com.google.protobuf.Descriptors.Descriptor + getDescriptor() { + return org.session.libsignal.protos.SignalServiceProtos.internal_static_signalservice_SharedConfigMessage_descriptor; + } + + protected com.google.protobuf.GeneratedMessage.FieldAccessorTable + internalGetFieldAccessorTable() { + return org.session.libsignal.protos.SignalServiceProtos.internal_static_signalservice_SharedConfigMessage_fieldAccessorTable + .ensureFieldAccessorsInitialized( + org.session.libsignal.protos.SignalServiceProtos.SharedConfigMessage.class, org.session.libsignal.protos.SignalServiceProtos.SharedConfigMessage.Builder.class); + } + + // Construct using org.session.libsignal.protos.SignalServiceProtos.SharedConfigMessage.newBuilder() + private Builder() { + maybeForceBuilderInitialization(); + } + + private Builder( + com.google.protobuf.GeneratedMessage.BuilderParent parent) { + super(parent); + maybeForceBuilderInitialization(); + } + private void maybeForceBuilderInitialization() { + if (com.google.protobuf.GeneratedMessage.alwaysUseFieldBuilders) { + } + } + private static Builder create() { + return new Builder(); + } + + public Builder clear() { + super.clear(); + type_ = org.session.libsignal.protos.SignalServiceProtos.SharedConfigMessage.Type.USER; + bitField0_ = (bitField0_ & ~0x00000001); + data_ = com.google.protobuf.ByteString.EMPTY; + bitField0_ = (bitField0_ & ~0x00000002); + return this; + } + + public Builder clone() { + return create().mergeFrom(buildPartial()); + } + + public com.google.protobuf.Descriptors.Descriptor + getDescriptorForType() { + return org.session.libsignal.protos.SignalServiceProtos.internal_static_signalservice_SharedConfigMessage_descriptor; + } + + public org.session.libsignal.protos.SignalServiceProtos.SharedConfigMessage getDefaultInstanceForType() { + return org.session.libsignal.protos.SignalServiceProtos.SharedConfigMessage.getDefaultInstance(); + } + + public org.session.libsignal.protos.SignalServiceProtos.SharedConfigMessage build() { + org.session.libsignal.protos.SignalServiceProtos.SharedConfigMessage result = buildPartial(); + if (!result.isInitialized()) { + throw newUninitializedMessageException(result); + } + return result; + } + + public org.session.libsignal.protos.SignalServiceProtos.SharedConfigMessage buildPartial() { + org.session.libsignal.protos.SignalServiceProtos.SharedConfigMessage result = new org.session.libsignal.protos.SignalServiceProtos.SharedConfigMessage(this); + int from_bitField0_ = bitField0_; + int to_bitField0_ = 0; + if (((from_bitField0_ & 0x00000001) == 0x00000001)) { + to_bitField0_ |= 0x00000001; + } + result.type_ = type_; + if (((from_bitField0_ & 0x00000002) == 0x00000002)) { + to_bitField0_ |= 0x00000002; + } + result.data_ = data_; + result.bitField0_ = to_bitField0_; + onBuilt(); + return result; + } + + public Builder mergeFrom(com.google.protobuf.Message other) { + if (other instanceof org.session.libsignal.protos.SignalServiceProtos.SharedConfigMessage) { + return mergeFrom((org.session.libsignal.protos.SignalServiceProtos.SharedConfigMessage)other); + } else { + super.mergeFrom(other); + return this; + } + } + + public Builder mergeFrom(org.session.libsignal.protos.SignalServiceProtos.SharedConfigMessage other) { + if (other == org.session.libsignal.protos.SignalServiceProtos.SharedConfigMessage.getDefaultInstance()) return this; + if (other.hasType()) { + setType(other.getType()); + } + if (other.hasData()) { + setData(other.getData()); + } + this.mergeUnknownFields(other.getUnknownFields()); + return this; + } + + public final boolean isInitialized() { + if (!hasType()) { + + return false; + } + if (!hasData()) { + + return false; + } + return true; + } + + public Builder mergeFrom( + com.google.protobuf.CodedInputStream input, + com.google.protobuf.ExtensionRegistryLite extensionRegistry) + throws java.io.IOException { + org.session.libsignal.protos.SignalServiceProtos.SharedConfigMessage parsedMessage = null; + try { + parsedMessage = PARSER.parsePartialFrom(input, extensionRegistry); + } catch (com.google.protobuf.InvalidProtocolBufferException e) { + parsedMessage = (org.session.libsignal.protos.SignalServiceProtos.SharedConfigMessage) e.getUnfinishedMessage(); + throw e; + } finally { + if (parsedMessage != null) { + mergeFrom(parsedMessage); + } + } + return this; + } + private int bitField0_; + + // required .signalservice.SharedConfigMessage.Type type = 1; + private org.session.libsignal.protos.SignalServiceProtos.SharedConfigMessage.Type type_ = org.session.libsignal.protos.SignalServiceProtos.SharedConfigMessage.Type.USER; + /** + * required .signalservice.SharedConfigMessage.Type type = 1; + * + *
+       * @required
+       * 
+ */ + public boolean hasType() { + return ((bitField0_ & 0x00000001) == 0x00000001); + } + /** + * required .signalservice.SharedConfigMessage.Type type = 1; + * + *
+       * @required
+       * 
+ */ + public org.session.libsignal.protos.SignalServiceProtos.SharedConfigMessage.Type getType() { + return type_; + } + /** + * required .signalservice.SharedConfigMessage.Type type = 1; + * + *
+       * @required
+       * 
+ */ + public Builder setType(org.session.libsignal.protos.SignalServiceProtos.SharedConfigMessage.Type value) { + if (value == null) { + throw new NullPointerException(); + } + bitField0_ |= 0x00000001; + type_ = value; + onChanged(); + return this; + } + /** + * required .signalservice.SharedConfigMessage.Type type = 1; + * + *
+       * @required
+       * 
+ */ + public Builder clearType() { + bitField0_ = (bitField0_ & ~0x00000001); + type_ = org.session.libsignal.protos.SignalServiceProtos.SharedConfigMessage.Type.USER; + onChanged(); + return this; + } + + // required bytes data = 2; + private com.google.protobuf.ByteString data_ = com.google.protobuf.ByteString.EMPTY; + /** + * required bytes data = 2; + */ + public boolean hasData() { + return ((bitField0_ & 0x00000002) == 0x00000002); + } + /** + * required bytes data = 2; + */ + public com.google.protobuf.ByteString getData() { + return data_; + } + /** + * required bytes data = 2; + */ + public Builder setData(com.google.protobuf.ByteString value) { + if (value == null) { + throw new NullPointerException(); + } + bitField0_ |= 0x00000002; + data_ = value; + onChanged(); + return this; + } + /** + * required bytes data = 2; + */ + public Builder clearData() { + bitField0_ = (bitField0_ & ~0x00000002); + data_ = getDefaultInstance().getData(); + onChanged(); + return this; + } + + // @@protoc_insertion_point(builder_scope:signalservice.SharedConfigMessage) + } + + static { + defaultInstance = new SharedConfigMessage(true); + defaultInstance.initFields(); + } + + // @@protoc_insertion_point(class_scope:signalservice.SharedConfigMessage) + } + public interface ConfigurationMessageOrBuilder extends com.google.protobuf.MessageOrBuilder { @@ -24589,1398 +28816,6 @@ public final class SignalServiceProtos { // @@protoc_insertion_point(class_scope:signalservice.AttachmentPointer) } - public interface GroupContextOrBuilder - extends com.google.protobuf.MessageOrBuilder { - - // optional bytes id = 1; - /** - * optional bytes id = 1; - * - *
-     * @required
-     * 
- */ - boolean hasId(); - /** - * optional bytes id = 1; - * - *
-     * @required
-     * 
- */ - com.google.protobuf.ByteString getId(); - - // optional .signalservice.GroupContext.Type type = 2; - /** - * optional .signalservice.GroupContext.Type type = 2; - * - *
-     * @required
-     * 
- */ - boolean hasType(); - /** - * optional .signalservice.GroupContext.Type type = 2; - * - *
-     * @required
-     * 
- */ - org.session.libsignal.protos.SignalServiceProtos.GroupContext.Type getType(); - - // optional string name = 3; - /** - * optional string name = 3; - */ - boolean hasName(); - /** - * optional string name = 3; - */ - java.lang.String getName(); - /** - * optional string name = 3; - */ - com.google.protobuf.ByteString - getNameBytes(); - - // repeated string members = 4; - /** - * repeated string members = 4; - */ - java.util.List - getMembersList(); - /** - * repeated string members = 4; - */ - int getMembersCount(); - /** - * repeated string members = 4; - */ - java.lang.String getMembers(int index); - /** - * repeated string members = 4; - */ - com.google.protobuf.ByteString - getMembersBytes(int index); - - // optional .signalservice.AttachmentPointer avatar = 5; - /** - * optional .signalservice.AttachmentPointer avatar = 5; - */ - boolean hasAvatar(); - /** - * optional .signalservice.AttachmentPointer avatar = 5; - */ - org.session.libsignal.protos.SignalServiceProtos.AttachmentPointer getAvatar(); - /** - * optional .signalservice.AttachmentPointer avatar = 5; - */ - org.session.libsignal.protos.SignalServiceProtos.AttachmentPointerOrBuilder getAvatarOrBuilder(); - - // repeated string admins = 6; - /** - * repeated string admins = 6; - */ - java.util.List - getAdminsList(); - /** - * repeated string admins = 6; - */ - int getAdminsCount(); - /** - * repeated string admins = 6; - */ - java.lang.String getAdmins(int index); - /** - * repeated string admins = 6; - */ - com.google.protobuf.ByteString - getAdminsBytes(int index); - } - /** - * Protobuf type {@code signalservice.GroupContext} - */ - public static final class GroupContext extends - com.google.protobuf.GeneratedMessage - implements GroupContextOrBuilder { - // Use GroupContext.newBuilder() to construct. - private GroupContext(com.google.protobuf.GeneratedMessage.Builder builder) { - super(builder); - this.unknownFields = builder.getUnknownFields(); - } - private GroupContext(boolean noInit) { this.unknownFields = com.google.protobuf.UnknownFieldSet.getDefaultInstance(); } - - private static final GroupContext defaultInstance; - public static GroupContext getDefaultInstance() { - return defaultInstance; - } - - public GroupContext getDefaultInstanceForType() { - return defaultInstance; - } - - private final com.google.protobuf.UnknownFieldSet unknownFields; - @java.lang.Override - public final com.google.protobuf.UnknownFieldSet - getUnknownFields() { - return this.unknownFields; - } - private GroupContext( - com.google.protobuf.CodedInputStream input, - com.google.protobuf.ExtensionRegistryLite extensionRegistry) - throws com.google.protobuf.InvalidProtocolBufferException { - initFields(); - int mutable_bitField0_ = 0; - com.google.protobuf.UnknownFieldSet.Builder unknownFields = - com.google.protobuf.UnknownFieldSet.newBuilder(); - try { - boolean done = false; - while (!done) { - int tag = input.readTag(); - switch (tag) { - case 0: - done = true; - break; - default: { - if (!parseUnknownField(input, unknownFields, - extensionRegistry, tag)) { - done = true; - } - break; - } - case 10: { - bitField0_ |= 0x00000001; - id_ = input.readBytes(); - break; - } - case 16: { - int rawValue = input.readEnum(); - org.session.libsignal.protos.SignalServiceProtos.GroupContext.Type value = org.session.libsignal.protos.SignalServiceProtos.GroupContext.Type.valueOf(rawValue); - if (value == null) { - unknownFields.mergeVarintField(2, rawValue); - } else { - bitField0_ |= 0x00000002; - type_ = value; - } - break; - } - case 26: { - bitField0_ |= 0x00000004; - name_ = input.readBytes(); - break; - } - case 34: { - if (!((mutable_bitField0_ & 0x00000008) == 0x00000008)) { - members_ = new com.google.protobuf.LazyStringArrayList(); - mutable_bitField0_ |= 0x00000008; - } - members_.add(input.readBytes()); - break; - } - case 42: { - org.session.libsignal.protos.SignalServiceProtos.AttachmentPointer.Builder subBuilder = null; - if (((bitField0_ & 0x00000008) == 0x00000008)) { - subBuilder = avatar_.toBuilder(); - } - avatar_ = input.readMessage(org.session.libsignal.protos.SignalServiceProtos.AttachmentPointer.PARSER, extensionRegistry); - if (subBuilder != null) { - subBuilder.mergeFrom(avatar_); - avatar_ = subBuilder.buildPartial(); - } - bitField0_ |= 0x00000008; - break; - } - case 50: { - if (!((mutable_bitField0_ & 0x00000020) == 0x00000020)) { - admins_ = new com.google.protobuf.LazyStringArrayList(); - mutable_bitField0_ |= 0x00000020; - } - admins_.add(input.readBytes()); - break; - } - } - } - } catch (com.google.protobuf.InvalidProtocolBufferException e) { - throw e.setUnfinishedMessage(this); - } catch (java.io.IOException e) { - throw new com.google.protobuf.InvalidProtocolBufferException( - e.getMessage()).setUnfinishedMessage(this); - } finally { - if (((mutable_bitField0_ & 0x00000008) == 0x00000008)) { - members_ = new com.google.protobuf.UnmodifiableLazyStringList(members_); - } - if (((mutable_bitField0_ & 0x00000020) == 0x00000020)) { - admins_ = new com.google.protobuf.UnmodifiableLazyStringList(admins_); - } - this.unknownFields = unknownFields.build(); - makeExtensionsImmutable(); - } - } - public static final com.google.protobuf.Descriptors.Descriptor - getDescriptor() { - return org.session.libsignal.protos.SignalServiceProtos.internal_static_signalservice_GroupContext_descriptor; - } - - protected com.google.protobuf.GeneratedMessage.FieldAccessorTable - internalGetFieldAccessorTable() { - return org.session.libsignal.protos.SignalServiceProtos.internal_static_signalservice_GroupContext_fieldAccessorTable - .ensureFieldAccessorsInitialized( - org.session.libsignal.protos.SignalServiceProtos.GroupContext.class, org.session.libsignal.protos.SignalServiceProtos.GroupContext.Builder.class); - } - - public static com.google.protobuf.Parser PARSER = - new com.google.protobuf.AbstractParser() { - public GroupContext parsePartialFrom( - com.google.protobuf.CodedInputStream input, - com.google.protobuf.ExtensionRegistryLite extensionRegistry) - throws com.google.protobuf.InvalidProtocolBufferException { - return new GroupContext(input, extensionRegistry); - } - }; - - @java.lang.Override - public com.google.protobuf.Parser getParserForType() { - return PARSER; - } - - /** - * Protobuf enum {@code signalservice.GroupContext.Type} - */ - public enum Type - implements com.google.protobuf.ProtocolMessageEnum { - /** - * UNKNOWN = 0; - */ - UNKNOWN(0, 0), - /** - * UPDATE = 1; - */ - UPDATE(1, 1), - /** - * DELIVER = 2; - */ - DELIVER(2, 2), - /** - * QUIT = 3; - */ - QUIT(3, 3), - /** - * REQUEST_INFO = 4; - */ - REQUEST_INFO(4, 4), - ; - - /** - * UNKNOWN = 0; - */ - public static final int UNKNOWN_VALUE = 0; - /** - * UPDATE = 1; - */ - public static final int UPDATE_VALUE = 1; - /** - * DELIVER = 2; - */ - public static final int DELIVER_VALUE = 2; - /** - * QUIT = 3; - */ - public static final int QUIT_VALUE = 3; - /** - * REQUEST_INFO = 4; - */ - public static final int REQUEST_INFO_VALUE = 4; - - - public final int getNumber() { return value; } - - public static Type valueOf(int value) { - switch (value) { - case 0: return UNKNOWN; - case 1: return UPDATE; - case 2: return DELIVER; - case 3: return QUIT; - case 4: return REQUEST_INFO; - default: return null; - } - } - - public static com.google.protobuf.Internal.EnumLiteMap - internalGetValueMap() { - return internalValueMap; - } - private static com.google.protobuf.Internal.EnumLiteMap - internalValueMap = - new com.google.protobuf.Internal.EnumLiteMap() { - public Type findValueByNumber(int number) { - return Type.valueOf(number); - } - }; - - public final com.google.protobuf.Descriptors.EnumValueDescriptor - getValueDescriptor() { - return getDescriptor().getValues().get(index); - } - public final com.google.protobuf.Descriptors.EnumDescriptor - getDescriptorForType() { - return getDescriptor(); - } - public static final com.google.protobuf.Descriptors.EnumDescriptor - getDescriptor() { - return org.session.libsignal.protos.SignalServiceProtos.GroupContext.getDescriptor().getEnumTypes().get(0); - } - - private static final Type[] VALUES = values(); - - public static Type valueOf( - com.google.protobuf.Descriptors.EnumValueDescriptor desc) { - if (desc.getType() != getDescriptor()) { - throw new java.lang.IllegalArgumentException( - "EnumValueDescriptor is not for this type."); - } - return VALUES[desc.getIndex()]; - } - - private final int index; - private final int value; - - private Type(int index, int value) { - this.index = index; - this.value = value; - } - - // @@protoc_insertion_point(enum_scope:signalservice.GroupContext.Type) - } - - private int bitField0_; - // optional bytes id = 1; - public static final int ID_FIELD_NUMBER = 1; - private com.google.protobuf.ByteString id_; - /** - * optional bytes id = 1; - * - *
-     * @required
-     * 
- */ - public boolean hasId() { - return ((bitField0_ & 0x00000001) == 0x00000001); - } - /** - * optional bytes id = 1; - * - *
-     * @required
-     * 
- */ - public com.google.protobuf.ByteString getId() { - return id_; - } - - // optional .signalservice.GroupContext.Type type = 2; - public static final int TYPE_FIELD_NUMBER = 2; - private org.session.libsignal.protos.SignalServiceProtos.GroupContext.Type type_; - /** - * optional .signalservice.GroupContext.Type type = 2; - * - *
-     * @required
-     * 
- */ - public boolean hasType() { - return ((bitField0_ & 0x00000002) == 0x00000002); - } - /** - * optional .signalservice.GroupContext.Type type = 2; - * - *
-     * @required
-     * 
- */ - public org.session.libsignal.protos.SignalServiceProtos.GroupContext.Type getType() { - return type_; - } - - // optional string name = 3; - public static final int NAME_FIELD_NUMBER = 3; - private java.lang.Object name_; - /** - * optional string name = 3; - */ - public boolean hasName() { - return ((bitField0_ & 0x00000004) == 0x00000004); - } - /** - * optional string name = 3; - */ - public java.lang.String getName() { - java.lang.Object ref = name_; - if (ref instanceof java.lang.String) { - return (java.lang.String) ref; - } else { - com.google.protobuf.ByteString bs = - (com.google.protobuf.ByteString) ref; - java.lang.String s = bs.toStringUtf8(); - if (bs.isValidUtf8()) { - name_ = s; - } - return s; - } - } - /** - * optional string name = 3; - */ - public com.google.protobuf.ByteString - getNameBytes() { - java.lang.Object ref = name_; - if (ref instanceof java.lang.String) { - com.google.protobuf.ByteString b = - com.google.protobuf.ByteString.copyFromUtf8( - (java.lang.String) ref); - name_ = b; - return b; - } else { - return (com.google.protobuf.ByteString) ref; - } - } - - // repeated string members = 4; - public static final int MEMBERS_FIELD_NUMBER = 4; - private com.google.protobuf.LazyStringList members_; - /** - * repeated string members = 4; - */ - public java.util.List - getMembersList() { - return members_; - } - /** - * repeated string members = 4; - */ - public int getMembersCount() { - return members_.size(); - } - /** - * repeated string members = 4; - */ - public java.lang.String getMembers(int index) { - return members_.get(index); - } - /** - * repeated string members = 4; - */ - public com.google.protobuf.ByteString - getMembersBytes(int index) { - return members_.getByteString(index); - } - - // optional .signalservice.AttachmentPointer avatar = 5; - public static final int AVATAR_FIELD_NUMBER = 5; - private org.session.libsignal.protos.SignalServiceProtos.AttachmentPointer avatar_; - /** - * optional .signalservice.AttachmentPointer avatar = 5; - */ - public boolean hasAvatar() { - return ((bitField0_ & 0x00000008) == 0x00000008); - } - /** - * optional .signalservice.AttachmentPointer avatar = 5; - */ - public org.session.libsignal.protos.SignalServiceProtos.AttachmentPointer getAvatar() { - return avatar_; - } - /** - * optional .signalservice.AttachmentPointer avatar = 5; - */ - public org.session.libsignal.protos.SignalServiceProtos.AttachmentPointerOrBuilder getAvatarOrBuilder() { - return avatar_; - } - - // repeated string admins = 6; - public static final int ADMINS_FIELD_NUMBER = 6; - private com.google.protobuf.LazyStringList admins_; - /** - * repeated string admins = 6; - */ - public java.util.List - getAdminsList() { - return admins_; - } - /** - * repeated string admins = 6; - */ - public int getAdminsCount() { - return admins_.size(); - } - /** - * repeated string admins = 6; - */ - public java.lang.String getAdmins(int index) { - return admins_.get(index); - } - /** - * repeated string admins = 6; - */ - public com.google.protobuf.ByteString - getAdminsBytes(int index) { - return admins_.getByteString(index); - } - - private void initFields() { - id_ = com.google.protobuf.ByteString.EMPTY; - type_ = org.session.libsignal.protos.SignalServiceProtos.GroupContext.Type.UNKNOWN; - name_ = ""; - members_ = com.google.protobuf.LazyStringArrayList.EMPTY; - avatar_ = org.session.libsignal.protos.SignalServiceProtos.AttachmentPointer.getDefaultInstance(); - admins_ = com.google.protobuf.LazyStringArrayList.EMPTY; - } - private byte memoizedIsInitialized = -1; - public final boolean isInitialized() { - byte isInitialized = memoizedIsInitialized; - if (isInitialized != -1) return isInitialized == 1; - - if (hasAvatar()) { - if (!getAvatar().isInitialized()) { - memoizedIsInitialized = 0; - return false; - } - } - memoizedIsInitialized = 1; - return true; - } - - public void writeTo(com.google.protobuf.CodedOutputStream output) - throws java.io.IOException { - getSerializedSize(); - if (((bitField0_ & 0x00000001) == 0x00000001)) { - output.writeBytes(1, id_); - } - if (((bitField0_ & 0x00000002) == 0x00000002)) { - output.writeEnum(2, type_.getNumber()); - } - if (((bitField0_ & 0x00000004) == 0x00000004)) { - output.writeBytes(3, getNameBytes()); - } - for (int i = 0; i < members_.size(); i++) { - output.writeBytes(4, members_.getByteString(i)); - } - if (((bitField0_ & 0x00000008) == 0x00000008)) { - output.writeMessage(5, avatar_); - } - for (int i = 0; i < admins_.size(); i++) { - output.writeBytes(6, admins_.getByteString(i)); - } - getUnknownFields().writeTo(output); - } - - private int memoizedSerializedSize = -1; - public int getSerializedSize() { - int size = memoizedSerializedSize; - if (size != -1) return size; - - size = 0; - if (((bitField0_ & 0x00000001) == 0x00000001)) { - size += com.google.protobuf.CodedOutputStream - .computeBytesSize(1, id_); - } - if (((bitField0_ & 0x00000002) == 0x00000002)) { - size += com.google.protobuf.CodedOutputStream - .computeEnumSize(2, type_.getNumber()); - } - if (((bitField0_ & 0x00000004) == 0x00000004)) { - size += com.google.protobuf.CodedOutputStream - .computeBytesSize(3, getNameBytes()); - } - { - int dataSize = 0; - for (int i = 0; i < members_.size(); i++) { - dataSize += com.google.protobuf.CodedOutputStream - .computeBytesSizeNoTag(members_.getByteString(i)); - } - size += dataSize; - size += 1 * getMembersList().size(); - } - if (((bitField0_ & 0x00000008) == 0x00000008)) { - size += com.google.protobuf.CodedOutputStream - .computeMessageSize(5, avatar_); - } - { - int dataSize = 0; - for (int i = 0; i < admins_.size(); i++) { - dataSize += com.google.protobuf.CodedOutputStream - .computeBytesSizeNoTag(admins_.getByteString(i)); - } - size += dataSize; - size += 1 * getAdminsList().size(); - } - size += getUnknownFields().getSerializedSize(); - memoizedSerializedSize = size; - return size; - } - - private static final long serialVersionUID = 0L; - @java.lang.Override - protected java.lang.Object writeReplace() - throws java.io.ObjectStreamException { - return super.writeReplace(); - } - - public static org.session.libsignal.protos.SignalServiceProtos.GroupContext parseFrom( - com.google.protobuf.ByteString data) - throws com.google.protobuf.InvalidProtocolBufferException { - return PARSER.parseFrom(data); - } - public static org.session.libsignal.protos.SignalServiceProtos.GroupContext parseFrom( - com.google.protobuf.ByteString data, - com.google.protobuf.ExtensionRegistryLite extensionRegistry) - throws com.google.protobuf.InvalidProtocolBufferException { - return PARSER.parseFrom(data, extensionRegistry); - } - public static org.session.libsignal.protos.SignalServiceProtos.GroupContext parseFrom(byte[] data) - throws com.google.protobuf.InvalidProtocolBufferException { - return PARSER.parseFrom(data); - } - public static org.session.libsignal.protos.SignalServiceProtos.GroupContext parseFrom( - byte[] data, - com.google.protobuf.ExtensionRegistryLite extensionRegistry) - throws com.google.protobuf.InvalidProtocolBufferException { - return PARSER.parseFrom(data, extensionRegistry); - } - public static org.session.libsignal.protos.SignalServiceProtos.GroupContext parseFrom(java.io.InputStream input) - throws java.io.IOException { - return PARSER.parseFrom(input); - } - public static org.session.libsignal.protos.SignalServiceProtos.GroupContext parseFrom( - java.io.InputStream input, - com.google.protobuf.ExtensionRegistryLite extensionRegistry) - throws java.io.IOException { - return PARSER.parseFrom(input, extensionRegistry); - } - public static org.session.libsignal.protos.SignalServiceProtos.GroupContext parseDelimitedFrom(java.io.InputStream input) - throws java.io.IOException { - return PARSER.parseDelimitedFrom(input); - } - public static org.session.libsignal.protos.SignalServiceProtos.GroupContext parseDelimitedFrom( - java.io.InputStream input, - com.google.protobuf.ExtensionRegistryLite extensionRegistry) - throws java.io.IOException { - return PARSER.parseDelimitedFrom(input, extensionRegistry); - } - public static org.session.libsignal.protos.SignalServiceProtos.GroupContext parseFrom( - com.google.protobuf.CodedInputStream input) - throws java.io.IOException { - return PARSER.parseFrom(input); - } - public static org.session.libsignal.protos.SignalServiceProtos.GroupContext parseFrom( - com.google.protobuf.CodedInputStream input, - com.google.protobuf.ExtensionRegistryLite extensionRegistry) - throws java.io.IOException { - return PARSER.parseFrom(input, extensionRegistry); - } - - public static Builder newBuilder() { return Builder.create(); } - public Builder newBuilderForType() { return newBuilder(); } - public static Builder newBuilder(org.session.libsignal.protos.SignalServiceProtos.GroupContext prototype) { - return newBuilder().mergeFrom(prototype); - } - public Builder toBuilder() { return newBuilder(this); } - - @java.lang.Override - protected Builder newBuilderForType( - com.google.protobuf.GeneratedMessage.BuilderParent parent) { - Builder builder = new Builder(parent); - return builder; - } - /** - * Protobuf type {@code signalservice.GroupContext} - */ - public static final class Builder extends - com.google.protobuf.GeneratedMessage.Builder - implements org.session.libsignal.protos.SignalServiceProtos.GroupContextOrBuilder { - public static final com.google.protobuf.Descriptors.Descriptor - getDescriptor() { - return org.session.libsignal.protos.SignalServiceProtos.internal_static_signalservice_GroupContext_descriptor; - } - - protected com.google.protobuf.GeneratedMessage.FieldAccessorTable - internalGetFieldAccessorTable() { - return org.session.libsignal.protos.SignalServiceProtos.internal_static_signalservice_GroupContext_fieldAccessorTable - .ensureFieldAccessorsInitialized( - org.session.libsignal.protos.SignalServiceProtos.GroupContext.class, org.session.libsignal.protos.SignalServiceProtos.GroupContext.Builder.class); - } - - // Construct using org.session.libsignal.protos.SignalServiceProtos.GroupContext.newBuilder() - private Builder() { - maybeForceBuilderInitialization(); - } - - private Builder( - com.google.protobuf.GeneratedMessage.BuilderParent parent) { - super(parent); - maybeForceBuilderInitialization(); - } - private void maybeForceBuilderInitialization() { - if (com.google.protobuf.GeneratedMessage.alwaysUseFieldBuilders) { - getAvatarFieldBuilder(); - } - } - private static Builder create() { - return new Builder(); - } - - public Builder clear() { - super.clear(); - id_ = com.google.protobuf.ByteString.EMPTY; - bitField0_ = (bitField0_ & ~0x00000001); - type_ = org.session.libsignal.protos.SignalServiceProtos.GroupContext.Type.UNKNOWN; - bitField0_ = (bitField0_ & ~0x00000002); - name_ = ""; - bitField0_ = (bitField0_ & ~0x00000004); - members_ = com.google.protobuf.LazyStringArrayList.EMPTY; - bitField0_ = (bitField0_ & ~0x00000008); - if (avatarBuilder_ == null) { - avatar_ = org.session.libsignal.protos.SignalServiceProtos.AttachmentPointer.getDefaultInstance(); - } else { - avatarBuilder_.clear(); - } - bitField0_ = (bitField0_ & ~0x00000010); - admins_ = com.google.protobuf.LazyStringArrayList.EMPTY; - bitField0_ = (bitField0_ & ~0x00000020); - return this; - } - - public Builder clone() { - return create().mergeFrom(buildPartial()); - } - - public com.google.protobuf.Descriptors.Descriptor - getDescriptorForType() { - return org.session.libsignal.protos.SignalServiceProtos.internal_static_signalservice_GroupContext_descriptor; - } - - public org.session.libsignal.protos.SignalServiceProtos.GroupContext getDefaultInstanceForType() { - return org.session.libsignal.protos.SignalServiceProtos.GroupContext.getDefaultInstance(); - } - - public org.session.libsignal.protos.SignalServiceProtos.GroupContext build() { - org.session.libsignal.protos.SignalServiceProtos.GroupContext result = buildPartial(); - if (!result.isInitialized()) { - throw newUninitializedMessageException(result); - } - return result; - } - - public org.session.libsignal.protos.SignalServiceProtos.GroupContext buildPartial() { - org.session.libsignal.protos.SignalServiceProtos.GroupContext result = new org.session.libsignal.protos.SignalServiceProtos.GroupContext(this); - int from_bitField0_ = bitField0_; - int to_bitField0_ = 0; - if (((from_bitField0_ & 0x00000001) == 0x00000001)) { - to_bitField0_ |= 0x00000001; - } - result.id_ = id_; - if (((from_bitField0_ & 0x00000002) == 0x00000002)) { - to_bitField0_ |= 0x00000002; - } - result.type_ = type_; - if (((from_bitField0_ & 0x00000004) == 0x00000004)) { - to_bitField0_ |= 0x00000004; - } - result.name_ = name_; - if (((bitField0_ & 0x00000008) == 0x00000008)) { - members_ = new com.google.protobuf.UnmodifiableLazyStringList( - members_); - bitField0_ = (bitField0_ & ~0x00000008); - } - result.members_ = members_; - if (((from_bitField0_ & 0x00000010) == 0x00000010)) { - to_bitField0_ |= 0x00000008; - } - if (avatarBuilder_ == null) { - result.avatar_ = avatar_; - } else { - result.avatar_ = avatarBuilder_.build(); - } - if (((bitField0_ & 0x00000020) == 0x00000020)) { - admins_ = new com.google.protobuf.UnmodifiableLazyStringList( - admins_); - bitField0_ = (bitField0_ & ~0x00000020); - } - result.admins_ = admins_; - result.bitField0_ = to_bitField0_; - onBuilt(); - return result; - } - - public Builder mergeFrom(com.google.protobuf.Message other) { - if (other instanceof org.session.libsignal.protos.SignalServiceProtos.GroupContext) { - return mergeFrom((org.session.libsignal.protos.SignalServiceProtos.GroupContext)other); - } else { - super.mergeFrom(other); - return this; - } - } - - public Builder mergeFrom(org.session.libsignal.protos.SignalServiceProtos.GroupContext other) { - if (other == org.session.libsignal.protos.SignalServiceProtos.GroupContext.getDefaultInstance()) return this; - if (other.hasId()) { - setId(other.getId()); - } - if (other.hasType()) { - setType(other.getType()); - } - if (other.hasName()) { - bitField0_ |= 0x00000004; - name_ = other.name_; - onChanged(); - } - if (!other.members_.isEmpty()) { - if (members_.isEmpty()) { - members_ = other.members_; - bitField0_ = (bitField0_ & ~0x00000008); - } else { - ensureMembersIsMutable(); - members_.addAll(other.members_); - } - onChanged(); - } - if (other.hasAvatar()) { - mergeAvatar(other.getAvatar()); - } - if (!other.admins_.isEmpty()) { - if (admins_.isEmpty()) { - admins_ = other.admins_; - bitField0_ = (bitField0_ & ~0x00000020); - } else { - ensureAdminsIsMutable(); - admins_.addAll(other.admins_); - } - onChanged(); - } - this.mergeUnknownFields(other.getUnknownFields()); - return this; - } - - public final boolean isInitialized() { - if (hasAvatar()) { - if (!getAvatar().isInitialized()) { - - return false; - } - } - return true; - } - - public Builder mergeFrom( - com.google.protobuf.CodedInputStream input, - com.google.protobuf.ExtensionRegistryLite extensionRegistry) - throws java.io.IOException { - org.session.libsignal.protos.SignalServiceProtos.GroupContext parsedMessage = null; - try { - parsedMessage = PARSER.parsePartialFrom(input, extensionRegistry); - } catch (com.google.protobuf.InvalidProtocolBufferException e) { - parsedMessage = (org.session.libsignal.protos.SignalServiceProtos.GroupContext) e.getUnfinishedMessage(); - throw e; - } finally { - if (parsedMessage != null) { - mergeFrom(parsedMessage); - } - } - return this; - } - private int bitField0_; - - // optional bytes id = 1; - private com.google.protobuf.ByteString id_ = com.google.protobuf.ByteString.EMPTY; - /** - * optional bytes id = 1; - * - *
-       * @required
-       * 
- */ - public boolean hasId() { - return ((bitField0_ & 0x00000001) == 0x00000001); - } - /** - * optional bytes id = 1; - * - *
-       * @required
-       * 
- */ - public com.google.protobuf.ByteString getId() { - return id_; - } - /** - * optional bytes id = 1; - * - *
-       * @required
-       * 
- */ - public Builder setId(com.google.protobuf.ByteString value) { - if (value == null) { - throw new NullPointerException(); - } - bitField0_ |= 0x00000001; - id_ = value; - onChanged(); - return this; - } - /** - * optional bytes id = 1; - * - *
-       * @required
-       * 
- */ - public Builder clearId() { - bitField0_ = (bitField0_ & ~0x00000001); - id_ = getDefaultInstance().getId(); - onChanged(); - return this; - } - - // optional .signalservice.GroupContext.Type type = 2; - private org.session.libsignal.protos.SignalServiceProtos.GroupContext.Type type_ = org.session.libsignal.protos.SignalServiceProtos.GroupContext.Type.UNKNOWN; - /** - * optional .signalservice.GroupContext.Type type = 2; - * - *
-       * @required
-       * 
- */ - public boolean hasType() { - return ((bitField0_ & 0x00000002) == 0x00000002); - } - /** - * optional .signalservice.GroupContext.Type type = 2; - * - *
-       * @required
-       * 
- */ - public org.session.libsignal.protos.SignalServiceProtos.GroupContext.Type getType() { - return type_; - } - /** - * optional .signalservice.GroupContext.Type type = 2; - * - *
-       * @required
-       * 
- */ - public Builder setType(org.session.libsignal.protos.SignalServiceProtos.GroupContext.Type value) { - if (value == null) { - throw new NullPointerException(); - } - bitField0_ |= 0x00000002; - type_ = value; - onChanged(); - return this; - } - /** - * optional .signalservice.GroupContext.Type type = 2; - * - *
-       * @required
-       * 
- */ - public Builder clearType() { - bitField0_ = (bitField0_ & ~0x00000002); - type_ = org.session.libsignal.protos.SignalServiceProtos.GroupContext.Type.UNKNOWN; - onChanged(); - return this; - } - - // optional string name = 3; - private java.lang.Object name_ = ""; - /** - * optional string name = 3; - */ - public boolean hasName() { - return ((bitField0_ & 0x00000004) == 0x00000004); - } - /** - * optional string name = 3; - */ - public java.lang.String getName() { - java.lang.Object ref = name_; - if (!(ref instanceof java.lang.String)) { - java.lang.String s = ((com.google.protobuf.ByteString) ref) - .toStringUtf8(); - name_ = s; - return s; - } else { - return (java.lang.String) ref; - } - } - /** - * optional string name = 3; - */ - public com.google.protobuf.ByteString - getNameBytes() { - java.lang.Object ref = name_; - if (ref instanceof String) { - com.google.protobuf.ByteString b = - com.google.protobuf.ByteString.copyFromUtf8( - (java.lang.String) ref); - name_ = b; - return b; - } else { - return (com.google.protobuf.ByteString) ref; - } - } - /** - * optional string name = 3; - */ - public Builder setName( - java.lang.String value) { - if (value == null) { - throw new NullPointerException(); - } - bitField0_ |= 0x00000004; - name_ = value; - onChanged(); - return this; - } - /** - * optional string name = 3; - */ - public Builder clearName() { - bitField0_ = (bitField0_ & ~0x00000004); - name_ = getDefaultInstance().getName(); - onChanged(); - return this; - } - /** - * optional string name = 3; - */ - public Builder setNameBytes( - com.google.protobuf.ByteString value) { - if (value == null) { - throw new NullPointerException(); - } - bitField0_ |= 0x00000004; - name_ = value; - onChanged(); - return this; - } - - // repeated string members = 4; - private com.google.protobuf.LazyStringList members_ = com.google.protobuf.LazyStringArrayList.EMPTY; - private void ensureMembersIsMutable() { - if (!((bitField0_ & 0x00000008) == 0x00000008)) { - members_ = new com.google.protobuf.LazyStringArrayList(members_); - bitField0_ |= 0x00000008; - } - } - /** - * repeated string members = 4; - */ - public java.util.List - getMembersList() { - return java.util.Collections.unmodifiableList(members_); - } - /** - * repeated string members = 4; - */ - public int getMembersCount() { - return members_.size(); - } - /** - * repeated string members = 4; - */ - public java.lang.String getMembers(int index) { - return members_.get(index); - } - /** - * repeated string members = 4; - */ - public com.google.protobuf.ByteString - getMembersBytes(int index) { - return members_.getByteString(index); - } - /** - * repeated string members = 4; - */ - public Builder setMembers( - int index, java.lang.String value) { - if (value == null) { - throw new NullPointerException(); - } - ensureMembersIsMutable(); - members_.set(index, value); - onChanged(); - return this; - } - /** - * repeated string members = 4; - */ - public Builder addMembers( - java.lang.String value) { - if (value == null) { - throw new NullPointerException(); - } - ensureMembersIsMutable(); - members_.add(value); - onChanged(); - return this; - } - /** - * repeated string members = 4; - */ - public Builder addAllMembers( - java.lang.Iterable values) { - ensureMembersIsMutable(); - super.addAll(values, members_); - onChanged(); - return this; - } - /** - * repeated string members = 4; - */ - public Builder clearMembers() { - members_ = com.google.protobuf.LazyStringArrayList.EMPTY; - bitField0_ = (bitField0_ & ~0x00000008); - onChanged(); - return this; - } - /** - * repeated string members = 4; - */ - public Builder addMembersBytes( - com.google.protobuf.ByteString value) { - if (value == null) { - throw new NullPointerException(); - } - ensureMembersIsMutable(); - members_.add(value); - onChanged(); - return this; - } - - // optional .signalservice.AttachmentPointer avatar = 5; - private org.session.libsignal.protos.SignalServiceProtos.AttachmentPointer avatar_ = org.session.libsignal.protos.SignalServiceProtos.AttachmentPointer.getDefaultInstance(); - private com.google.protobuf.SingleFieldBuilder< - org.session.libsignal.protos.SignalServiceProtos.AttachmentPointer, org.session.libsignal.protos.SignalServiceProtos.AttachmentPointer.Builder, org.session.libsignal.protos.SignalServiceProtos.AttachmentPointerOrBuilder> avatarBuilder_; - /** - * optional .signalservice.AttachmentPointer avatar = 5; - */ - public boolean hasAvatar() { - return ((bitField0_ & 0x00000010) == 0x00000010); - } - /** - * optional .signalservice.AttachmentPointer avatar = 5; - */ - public org.session.libsignal.protos.SignalServiceProtos.AttachmentPointer getAvatar() { - if (avatarBuilder_ == null) { - return avatar_; - } else { - return avatarBuilder_.getMessage(); - } - } - /** - * optional .signalservice.AttachmentPointer avatar = 5; - */ - public Builder setAvatar(org.session.libsignal.protos.SignalServiceProtos.AttachmentPointer value) { - if (avatarBuilder_ == null) { - if (value == null) { - throw new NullPointerException(); - } - avatar_ = value; - onChanged(); - } else { - avatarBuilder_.setMessage(value); - } - bitField0_ |= 0x00000010; - return this; - } - /** - * optional .signalservice.AttachmentPointer avatar = 5; - */ - public Builder setAvatar( - org.session.libsignal.protos.SignalServiceProtos.AttachmentPointer.Builder builderForValue) { - if (avatarBuilder_ == null) { - avatar_ = builderForValue.build(); - onChanged(); - } else { - avatarBuilder_.setMessage(builderForValue.build()); - } - bitField0_ |= 0x00000010; - return this; - } - /** - * optional .signalservice.AttachmentPointer avatar = 5; - */ - public Builder mergeAvatar(org.session.libsignal.protos.SignalServiceProtos.AttachmentPointer value) { - if (avatarBuilder_ == null) { - if (((bitField0_ & 0x00000010) == 0x00000010) && - avatar_ != org.session.libsignal.protos.SignalServiceProtos.AttachmentPointer.getDefaultInstance()) { - avatar_ = - org.session.libsignal.protos.SignalServiceProtos.AttachmentPointer.newBuilder(avatar_).mergeFrom(value).buildPartial(); - } else { - avatar_ = value; - } - onChanged(); - } else { - avatarBuilder_.mergeFrom(value); - } - bitField0_ |= 0x00000010; - return this; - } - /** - * optional .signalservice.AttachmentPointer avatar = 5; - */ - public Builder clearAvatar() { - if (avatarBuilder_ == null) { - avatar_ = org.session.libsignal.protos.SignalServiceProtos.AttachmentPointer.getDefaultInstance(); - onChanged(); - } else { - avatarBuilder_.clear(); - } - bitField0_ = (bitField0_ & ~0x00000010); - return this; - } - /** - * optional .signalservice.AttachmentPointer avatar = 5; - */ - public org.session.libsignal.protos.SignalServiceProtos.AttachmentPointer.Builder getAvatarBuilder() { - bitField0_ |= 0x00000010; - onChanged(); - return getAvatarFieldBuilder().getBuilder(); - } - /** - * optional .signalservice.AttachmentPointer avatar = 5; - */ - public org.session.libsignal.protos.SignalServiceProtos.AttachmentPointerOrBuilder getAvatarOrBuilder() { - if (avatarBuilder_ != null) { - return avatarBuilder_.getMessageOrBuilder(); - } else { - return avatar_; - } - } - /** - * optional .signalservice.AttachmentPointer avatar = 5; - */ - private com.google.protobuf.SingleFieldBuilder< - org.session.libsignal.protos.SignalServiceProtos.AttachmentPointer, org.session.libsignal.protos.SignalServiceProtos.AttachmentPointer.Builder, org.session.libsignal.protos.SignalServiceProtos.AttachmentPointerOrBuilder> - getAvatarFieldBuilder() { - if (avatarBuilder_ == null) { - avatarBuilder_ = new com.google.protobuf.SingleFieldBuilder< - org.session.libsignal.protos.SignalServiceProtos.AttachmentPointer, org.session.libsignal.protos.SignalServiceProtos.AttachmentPointer.Builder, org.session.libsignal.protos.SignalServiceProtos.AttachmentPointerOrBuilder>( - avatar_, - getParentForChildren(), - isClean()); - avatar_ = null; - } - return avatarBuilder_; - } - - // repeated string admins = 6; - private com.google.protobuf.LazyStringList admins_ = com.google.protobuf.LazyStringArrayList.EMPTY; - private void ensureAdminsIsMutable() { - if (!((bitField0_ & 0x00000020) == 0x00000020)) { - admins_ = new com.google.protobuf.LazyStringArrayList(admins_); - bitField0_ |= 0x00000020; - } - } - /** - * repeated string admins = 6; - */ - public java.util.List - getAdminsList() { - return java.util.Collections.unmodifiableList(admins_); - } - /** - * repeated string admins = 6; - */ - public int getAdminsCount() { - return admins_.size(); - } - /** - * repeated string admins = 6; - */ - public java.lang.String getAdmins(int index) { - return admins_.get(index); - } - /** - * repeated string admins = 6; - */ - public com.google.protobuf.ByteString - getAdminsBytes(int index) { - return admins_.getByteString(index); - } - /** - * repeated string admins = 6; - */ - public Builder setAdmins( - int index, java.lang.String value) { - if (value == null) { - throw new NullPointerException(); - } - ensureAdminsIsMutable(); - admins_.set(index, value); - onChanged(); - return this; - } - /** - * repeated string admins = 6; - */ - public Builder addAdmins( - java.lang.String value) { - if (value == null) { - throw new NullPointerException(); - } - ensureAdminsIsMutable(); - admins_.add(value); - onChanged(); - return this; - } - /** - * repeated string admins = 6; - */ - public Builder addAllAdmins( - java.lang.Iterable values) { - ensureAdminsIsMutable(); - super.addAll(values, admins_); - onChanged(); - return this; - } - /** - * repeated string admins = 6; - */ - public Builder clearAdmins() { - admins_ = com.google.protobuf.LazyStringArrayList.EMPTY; - bitField0_ = (bitField0_ & ~0x00000020); - onChanged(); - return this; - } - /** - * repeated string admins = 6; - */ - public Builder addAdminsBytes( - com.google.protobuf.ByteString value) { - if (value == null) { - throw new NullPointerException(); - } - ensureAdminsIsMutable(); - admins_.add(value); - onChanged(); - return this; - } - - // @@protoc_insertion_point(builder_scope:signalservice.GroupContext) - } - - static { - defaultInstance = new GroupContext(true); - defaultInstance.initFields(); - } - - // @@protoc_insertion_point(class_scope:signalservice.GroupContext) - } - private static com.google.protobuf.Descriptors.Descriptor internal_static_signalservice_Envelope_descriptor; private static @@ -26041,6 +28876,11 @@ public final class SignalServiceProtos { private static com.google.protobuf.GeneratedMessage.FieldAccessorTable internal_static_signalservice_DataMessage_OpenGroupInvitation_fieldAccessorTable; + private static com.google.protobuf.Descriptors.Descriptor + internal_static_signalservice_DataMessage_GroupMessage_descriptor; + private static + com.google.protobuf.GeneratedMessage.FieldAccessorTable + internal_static_signalservice_DataMessage_GroupMessage_fieldAccessorTable; private static com.google.protobuf.Descriptors.Descriptor internal_static_signalservice_DataMessage_ClosedGroupControlMessage_descriptor; private static @@ -26056,11 +28896,36 @@ public final class SignalServiceProtos { private static com.google.protobuf.GeneratedMessage.FieldAccessorTable internal_static_signalservice_DataMessage_Reaction_fieldAccessorTable; + private static com.google.protobuf.Descriptors.Descriptor + internal_static_signalservice_GroupDeleteMessage_descriptor; + private static + com.google.protobuf.GeneratedMessage.FieldAccessorTable + internal_static_signalservice_GroupDeleteMessage_fieldAccessorTable; + private static com.google.protobuf.Descriptors.Descriptor + internal_static_signalservice_GroupMemberLeftMessage_descriptor; + private static + com.google.protobuf.GeneratedMessage.FieldAccessorTable + internal_static_signalservice_GroupMemberLeftMessage_fieldAccessorTable; + private static com.google.protobuf.Descriptors.Descriptor + internal_static_signalservice_GroupInviteMessage_descriptor; + private static + com.google.protobuf.GeneratedMessage.FieldAccessorTable + internal_static_signalservice_GroupInviteMessage_fieldAccessorTable; + private static com.google.protobuf.Descriptors.Descriptor + internal_static_signalservice_GroupPromoteMessage_descriptor; + private static + com.google.protobuf.GeneratedMessage.FieldAccessorTable + internal_static_signalservice_GroupPromoteMessage_fieldAccessorTable; private static com.google.protobuf.Descriptors.Descriptor internal_static_signalservice_CallMessage_descriptor; private static com.google.protobuf.GeneratedMessage.FieldAccessorTable internal_static_signalservice_CallMessage_fieldAccessorTable; + private static com.google.protobuf.Descriptors.Descriptor + internal_static_signalservice_SharedConfigMessage_descriptor; + private static + com.google.protobuf.GeneratedMessage.FieldAccessorTable + internal_static_signalservice_SharedConfigMessage_fieldAccessorTable; private static com.google.protobuf.Descriptors.Descriptor internal_static_signalservice_ConfigurationMessage_descriptor; private static @@ -26091,11 +28956,6 @@ public final class SignalServiceProtos { private static com.google.protobuf.GeneratedMessage.FieldAccessorTable internal_static_signalservice_AttachmentPointer_fieldAccessorTable; - private static com.google.protobuf.Descriptors.Descriptor - internal_static_signalservice_GroupContext_descriptor; - private static - com.google.protobuf.GeneratedMessage.FieldAccessorTable - internal_static_signalservice_GroupContext_fieldAccessorTable; public static com.google.protobuf.Descriptors.FileDescriptor getDescriptor() { @@ -26132,91 +28992,106 @@ public final class SignalServiceProtos { "(\014\"\226\001\n\032DataExtractionNotification\022<\n\004typ" + "e\030\001 \002(\0162..signalservice.DataExtractionNo" + "tification.Type\022\021\n\ttimestamp\030\002 \001(\004\"\'\n\004Ty" + - "pe\022\016\n\nSCREENSHOT\020\001\022\017\n\013MEDIA_SAVED\020\002\"\361\r\n\013" + + "pe\022\016\n\nSCREENSHOT\020\001\022\017\n\013MEDIA_SAVED\020\002\"\216\021\n\013" + "DataMessage\022\014\n\004body\030\001 \001(\t\0225\n\013attachments" + "\030\002 \003(\0132 .signalservice.AttachmentPointer", - "\022*\n\005group\030\003 \001(\0132\033.signalservice.GroupCon" + - "text\022\r\n\005flags\030\004 \001(\r\022\023\n\013expireTimer\030\005 \001(\r" + - "\022\022\n\nprofileKey\030\006 \001(\014\022\021\n\ttimestamp\030\007 \001(\004\022" + - "/\n\005quote\030\010 \001(\0132 .signalservice.DataMessa" + - "ge.Quote\0223\n\007preview\030\n \003(\0132\".signalservic" + - "e.DataMessage.Preview\0225\n\010reaction\030\013 \001(\0132" + - "#.signalservice.DataMessage.Reaction\0227\n\007" + - "profile\030e \001(\0132&.signalservice.DataMessag" + - "e.LokiProfile\022K\n\023openGroupInvitation\030f \001" + - "(\0132..signalservice.DataMessage.OpenGroup", - "Invitation\022W\n\031closedGroupControlMessage\030" + - "h \001(\01324.signalservice.DataMessage.Closed" + - "GroupControlMessage\022\022\n\nsyncTarget\030i \001(\t\032" + - "\225\002\n\005Quote\022\n\n\002id\030\001 \002(\004\022\016\n\006author\030\002 \002(\t\022\014\n" + - "\004text\030\003 \001(\t\022F\n\013attachments\030\004 \003(\01321.signa" + - "lservice.DataMessage.Quote.QuotedAttachm" + - "ent\032\231\001\n\020QuotedAttachment\022\023\n\013contentType\030" + - "\001 \001(\t\022\020\n\010fileName\030\002 \001(\t\0223\n\tthumbnail\030\003 \001" + - "(\0132 .signalservice.AttachmentPointer\022\r\n\005" + - "flags\030\004 \001(\r\"\032\n\005Flags\022\021\n\rVOICE_MESSAGE\020\001\032", - "V\n\007Preview\022\013\n\003url\030\001 \002(\t\022\r\n\005title\030\002 \001(\t\022/" + - "\n\005image\030\003 \001(\0132 .signalservice.Attachment" + - "Pointer\032:\n\013LokiProfile\022\023\n\013displayName\030\001 " + - "\001(\t\022\026\n\016profilePicture\030\002 \001(\t\0320\n\023OpenGroup" + - "Invitation\022\013\n\003url\030\001 \002(\t\022\014\n\004name\030\003 \002(\t\032\374\003" + - "\n\031ClosedGroupControlMessage\022G\n\004type\030\001 \002(" + - "\01629.signalservice.DataMessage.ClosedGrou" + - "pControlMessage.Type\022\021\n\tpublicKey\030\002 \001(\014\022" + - "\014\n\004name\030\003 \001(\t\0221\n\021encryptionKeyPair\030\004 \001(\013" + - "2\026.signalservice.KeyPair\022\017\n\007members\030\005 \003(", - "\014\022\016\n\006admins\030\006 \003(\014\022U\n\010wrappers\030\007 \003(\0132C.si" + - "gnalservice.DataMessage.ClosedGroupContr" + - "olMessage.KeyPairWrapper\022\027\n\017expirationTi" + - "mer\030\010 \001(\r\032=\n\016KeyPairWrapper\022\021\n\tpublicKey" + - "\030\001 \002(\014\022\030\n\020encryptedKeyPair\030\002 \002(\014\"r\n\004Type" + - "\022\007\n\003NEW\020\001\022\027\n\023ENCRYPTION_KEY_PAIR\020\003\022\017\n\013NA" + - "ME_CHANGE\020\004\022\021\n\rMEMBERS_ADDED\020\005\022\023\n\017MEMBER" + - "S_REMOVED\020\006\022\017\n\013MEMBER_LEFT\020\007\032\222\001\n\010Reactio" + - "n\022\n\n\002id\030\001 \002(\004\022\016\n\006author\030\002 \002(\t\022\r\n\005emoji\030\003" + - " \001(\t\022:\n\006action\030\004 \002(\0162*.signalservice.Dat", - "aMessage.Reaction.Action\"\037\n\006Action\022\t\n\005RE" + - "ACT\020\000\022\n\n\006REMOVE\020\001\"$\n\005Flags\022\033\n\027EXPIRATION" + - "_TIMER_UPDATE\020\002\"\352\001\n\013CallMessage\022-\n\004type\030" + - "\001 \002(\0162\037.signalservice.CallMessage.Type\022\014" + - "\n\004sdps\030\002 \003(\t\022\027\n\017sdpMLineIndexes\030\003 \003(\r\022\017\n" + - "\007sdpMids\030\004 \003(\t\022\014\n\004uuid\030\005 \002(\t\"f\n\004Type\022\r\n\t" + - "PRE_OFFER\020\006\022\t\n\005OFFER\020\001\022\n\n\006ANSWER\020\002\022\026\n\022PR" + - "OVISIONAL_ANSWER\020\003\022\022\n\016ICE_CANDIDATES\020\004\022\014" + - "\n\010END_CALL\020\005\"\245\004\n\024ConfigurationMessage\022E\n" + - "\014closedGroups\030\001 \003(\0132/.signalservice.Conf", - "igurationMessage.ClosedGroup\022\022\n\nopenGrou" + - "ps\030\002 \003(\t\022\023\n\013displayName\030\003 \001(\t\022\026\n\016profile" + - "Picture\030\004 \001(\t\022\022\n\nprofileKey\030\005 \001(\014\022=\n\010con" + - "tacts\030\006 \003(\0132+.signalservice.Configuratio" + - "nMessage.Contact\032\233\001\n\013ClosedGroup\022\021\n\tpubl" + - "icKey\030\001 \001(\014\022\014\n\004name\030\002 \001(\t\0221\n\021encryptionK" + - "eyPair\030\003 \001(\0132\026.signalservice.KeyPair\022\017\n\007" + - "members\030\004 \003(\014\022\016\n\006admins\030\005 \003(\014\022\027\n\017expirat" + - "ionTimer\030\006 \001(\r\032\223\001\n\007Contact\022\021\n\tpublicKey\030" + - "\001 \002(\014\022\014\n\004name\030\002 \002(\t\022\026\n\016profilePicture\030\003 ", - "\001(\t\022\022\n\nprofileKey\030\004 \001(\014\022\022\n\nisApproved\030\005 " + - "\001(\010\022\021\n\tisBlocked\030\006 \001(\010\022\024\n\014didApproveMe\030\007" + - " \001(\010\"y\n\026MessageRequestResponse\022\022\n\nisAppr" + - "oved\030\001 \002(\010\022\022\n\nprofileKey\030\002 \001(\014\0227\n\007profil" + - "e\030\003 \001(\0132&.signalservice.DataMessage.Loki" + - "Profile\"u\n\016ReceiptMessage\0220\n\004type\030\001 \002(\0162" + - "\".signalservice.ReceiptMessage.Type\022\021\n\tt" + - "imestamp\030\002 \003(\004\"\036\n\004Type\022\014\n\010DELIVERY\020\000\022\010\n\004" + - "READ\020\001\"\354\001\n\021AttachmentPointer\022\n\n\002id\030\001 \002(\006" + - "\022\023\n\013contentType\030\002 \001(\t\022\013\n\003key\030\003 \001(\014\022\014\n\004si", - "ze\030\004 \001(\r\022\021\n\tthumbnail\030\005 \001(\014\022\016\n\006digest\030\006 " + - "\001(\014\022\020\n\010fileName\030\007 \001(\t\022\r\n\005flags\030\010 \001(\r\022\r\n\005" + - "width\030\t \001(\r\022\016\n\006height\030\n \001(\r\022\017\n\007caption\030\013" + - " \001(\t\022\013\n\003url\030e \001(\t\"\032\n\005Flags\022\021\n\rVOICE_MESS" + - "AGE\020\001\"\365\001\n\014GroupContext\022\n\n\002id\030\001 \001(\014\022.\n\004ty" + - "pe\030\002 \001(\0162 .signalservice.GroupContext.Ty" + - "pe\022\014\n\004name\030\003 \001(\t\022\017\n\007members\030\004 \003(\t\0220\n\006ava" + - "tar\030\005 \001(\0132 .signalservice.AttachmentPoin" + - "ter\022\016\n\006admins\030\006 \003(\t\"H\n\004Type\022\013\n\007UNKNOWN\020\000" + - "\022\n\n\006UPDATE\020\001\022\013\n\007DELIVER\020\002\022\010\n\004QUIT\020\003\022\020\n\014R", - "EQUEST_INFO\020\004B3\n\034org.session.libsignal.p" + - "rotosB\023SignalServiceProtos" + "\022\r\n\005flags\030\004 \001(\r\022\023\n\013expireTimer\030\005 \001(\r\022\022\n\n" + + "profileKey\030\006 \001(\014\022\021\n\ttimestamp\030\007 \001(\004\022/\n\005q" + + "uote\030\010 \001(\0132 .signalservice.DataMessage.Q" + + "uote\0223\n\007preview\030\n \003(\0132\".signalservice.Da" + + "taMessage.Preview\0225\n\010reaction\030\013 \001(\0132#.si" + + "gnalservice.DataMessage.Reaction\0227\n\007prof" + + "ile\030e \001(\0132&.signalservice.DataMessage.Lo" + + "kiProfile\022K\n\023openGroupInvitation\030f \001(\0132." + + ".signalservice.DataMessage.OpenGroupInvi" + + "tation\022W\n\031closedGroupControlMessage\030h \001(", + "\01324.signalservice.DataMessage.ClosedGrou" + + "pControlMessage\022\022\n\nsyncTarget\030i \001(\t\022=\n\014g" + + "roupMessage\030x \001(\0132\'.signalservice.DataMe" + + "ssage.GroupMessage\032\225\002\n\005Quote\022\n\n\002id\030\001 \002(\004" + + "\022\016\n\006author\030\002 \002(\t\022\014\n\004text\030\003 \001(\t\022F\n\013attach" + + "ments\030\004 \003(\01321.signalservice.DataMessage." + + "Quote.QuotedAttachment\032\231\001\n\020QuotedAttachm" + + "ent\022\023\n\013contentType\030\001 \001(\t\022\020\n\010fileName\030\002 \001" + + "(\t\0223\n\tthumbnail\030\003 \001(\0132 .signalservice.At" + + "tachmentPointer\022\r\n\005flags\030\004 \001(\r\"\032\n\005Flags\022", + "\021\n\rVOICE_MESSAGE\020\001\032V\n\007Preview\022\013\n\003url\030\001 \002" + + "(\t\022\r\n\005title\030\002 \001(\t\022/\n\005image\030\003 \001(\0132 .signa" + + "lservice.AttachmentPointer\032:\n\013LokiProfil" + + "e\022\023\n\013displayName\030\001 \001(\t\022\026\n\016profilePicture" + + "\030\002 \001(\t\0320\n\023OpenGroupInvitation\022\013\n\003url\030\001 \002" + + "(\t\022\014\n\004name\030\003 \002(\t\032\200\002\n\014GroupMessage\0228\n\rdel" + + "eteMessage\030\037 \001(\0132!.signalservice.GroupDe" + + "leteMessage\022@\n\021memberLeftMessage\030 \001(\0132%" + + ".signalservice.GroupMemberLeftMessage\0228\n" + + "\rinviteMessage\030! \001(\0132!.signalservice.Gro", + "upInviteMessage\022:\n\016promoteMessage\030\" \001(\0132" + + "\".signalservice.GroupPromoteMessage\032\203\005\n\031" + + "ClosedGroupControlMessage\022G\n\004type\030\001 \002(\0162" + + "9.signalservice.DataMessage.ClosedGroupC" + + "ontrolMessage.Type\022\021\n\tpublicKey\030\002 \001(\014\022\014\n" + + "\004name\030\003 \001(\t\0221\n\021encryptionKeyPair\030\004 \001(\0132\026" + + ".signalservice.KeyPair\022\017\n\007members\030\005 \003(\014\022" + + "\016\n\006admins\030\006 \003(\014\022U\n\010wrappers\030\007 \003(\0132C.sign" + + "alservice.DataMessage.ClosedGroupControl" + + "Message.KeyPairWrapper\022\027\n\017expirationTime", + "r\030\010 \001(\r\022\030\n\020memberPrivateKey\030\t \001(\014\022\022\n\npri" + + "vateKey\030\n \001(\014\032=\n\016KeyPairWrapper\022\021\n\tpubli" + + "cKey\030\001 \002(\014\022\030\n\020encryptedKeyPair\030\002 \002(\014\"\312\001\n" + + "\004Type\022\007\n\003NEW\020\001\022\027\n\023ENCRYPTION_KEY_PAIR\020\003\022" + + "\017\n\013NAME_CHANGE\020\004\022\021\n\rMEMBERS_ADDED\020\005\022\023\n\017M" + + "EMBERS_REMOVED\020\006\022\017\n\013MEMBER_LEFT\020\007\022\n\n\006INV" + + "ITE\020\t\022\013\n\007PROMOTE\020\n\022\020\n\014DELETE_GROUP\020\013\022\023\n\017" + + "DELETE_MESSAGES\020\014\022\026\n\022DELETE_ATTACHMENTS\020" + + "\r\032\222\001\n\010Reaction\022\n\n\002id\030\001 \002(\004\022\016\n\006author\030\002 \002" + + "(\t\022\r\n\005emoji\030\003 \001(\t\022:\n\006action\030\004 \002(\0162*.sign", + "alservice.DataMessage.Reaction.Action\"\037\n" + + "\006Action\022\t\n\005REACT\020\000\022\n\n\006REMOVE\020\001\"$\n\005Flags\022" + + "\033\n\027EXPIRATION_TIMER_UPDATE\020\002\"B\n\022GroupDel" + + "eteMessage\022\021\n\tpublicKey\030\001 \002(\014\022\031\n\021lastEnc" + + "ryptionKey\030\002 \002(\014\"\030\n\026GroupMemberLeftMessa" + + "ge\"O\n\022GroupInviteMessage\022\021\n\tpublicKey\030\001 " + + "\002(\014\022\014\n\004name\030\002 \002(\t\022\030\n\020memberPrivateKey\030\003 " + + "\002(\014\"E\n\023GroupPromoteMessage\022\021\n\tpublicKey\030" + + "\001 \002(\014\022\033\n\023encryptedPrivateKey\030\002 \002(\014\"\352\001\n\013C" + + "allMessage\022-\n\004type\030\001 \002(\0162\037.signalservice", + ".CallMessage.Type\022\014\n\004sdps\030\002 \003(\t\022\027\n\017sdpML" + + "ineIndexes\030\003 \003(\r\022\017\n\007sdpMids\030\004 \003(\t\022\014\n\004uui" + + "d\030\005 \002(\t\"f\n\004Type\022\r\n\tPRE_OFFER\020\006\022\t\n\005OFFER\020" + + "\001\022\n\n\006ANSWER\020\002\022\026\n\022PROVISIONAL_ANSWER\020\003\022\022\n" + + "\016ICE_CANDIDATES\020\004\022\014\n\010END_CALL\020\005\"\265\001\n\023Shar" + + "edConfigMessage\0225\n\004type\030\001 \002(\0162\'.signalse" + + "rvice.SharedConfigMessage.Type\022\014\n\004data\030\002" + + " \002(\014\"Y\n\004Type\022\010\n\004USER\020\001\022\025\n\021CLOSED_GROUP_I" + + "NFO\020\002\022\023\n\017ENCRYPTION_KEYS\020\003\022\033\n\027CONVERSATI" + + "ON_READ_STATE\020\004\"\245\004\n\024ConfigurationMessage", + "\022E\n\014closedGroups\030\001 \003(\0132/.signalservice.C" + + "onfigurationMessage.ClosedGroup\022\022\n\nopenG" + + "roups\030\002 \003(\t\022\023\n\013displayName\030\003 \001(\t\022\026\n\016prof" + + "ilePicture\030\004 \001(\t\022\022\n\nprofileKey\030\005 \001(\014\022=\n\010" + + "contacts\030\006 \003(\0132+.signalservice.Configura" + + "tionMessage.Contact\032\233\001\n\013ClosedGroup\022\021\n\tp" + + "ublicKey\030\001 \001(\014\022\014\n\004name\030\002 \001(\t\0221\n\021encrypti" + + "onKeyPair\030\003 \001(\0132\026.signalservice.KeyPair\022" + + "\017\n\007members\030\004 \003(\014\022\016\n\006admins\030\005 \003(\014\022\027\n\017expi" + + "rationTimer\030\006 \001(\r\032\223\001\n\007Contact\022\021\n\tpublicK", + "ey\030\001 \002(\014\022\014\n\004name\030\002 \002(\t\022\026\n\016profilePicture" + + "\030\003 \001(\t\022\022\n\nprofileKey\030\004 \001(\014\022\022\n\nisApproved" + + "\030\005 \001(\010\022\021\n\tisBlocked\030\006 \001(\010\022\024\n\014didApproveM" + + "e\030\007 \001(\010\"y\n\026MessageRequestResponse\022\022\n\nisA" + + "pproved\030\001 \002(\010\022\022\n\nprofileKey\030\002 \001(\014\0227\n\007pro" + + "file\030\003 \001(\0132&.signalservice.DataMessage.L" + + "okiProfile\"u\n\016ReceiptMessage\0220\n\004type\030\001 \002" + + "(\0162\".signalservice.ReceiptMessage.Type\022\021" + + "\n\ttimestamp\030\002 \003(\004\"\036\n\004Type\022\014\n\010DELIVERY\020\000\022" + + "\010\n\004READ\020\001\"\354\001\n\021AttachmentPointer\022\n\n\002id\030\001 ", + "\002(\006\022\023\n\013contentType\030\002 \001(\t\022\013\n\003key\030\003 \001(\014\022\014\n" + + "\004size\030\004 \001(\r\022\021\n\tthumbnail\030\005 \001(\014\022\016\n\006digest" + + "\030\006 \001(\014\022\020\n\010fileName\030\007 \001(\t\022\r\n\005flags\030\010 \001(\r\022" + + "\r\n\005width\030\t \001(\r\022\016\n\006height\030\n \001(\r\022\017\n\007captio" + + "n\030\013 \001(\t\022\013\n\003url\030e \001(\t\"\032\n\005Flags\022\021\n\rVOICE_M" + + "ESSAGE\020\001B3\n\034org.session.libsignal.protos" + + "B\023SignalServiceProtos" }; com.google.protobuf.Descriptors.FileDescriptor.InternalDescriptorAssigner assigner = new com.google.protobuf.Descriptors.FileDescriptor.InternalDescriptorAssigner() { @@ -26264,7 +29139,7 @@ public final class SignalServiceProtos { internal_static_signalservice_DataMessage_fieldAccessorTable = new com.google.protobuf.GeneratedMessage.FieldAccessorTable( internal_static_signalservice_DataMessage_descriptor, - new java.lang.String[] { "Body", "Attachments", "Group", "Flags", "ExpireTimer", "ProfileKey", "Timestamp", "Quote", "Preview", "Reaction", "Profile", "OpenGroupInvitation", "ClosedGroupControlMessage", "SyncTarget", }); + new java.lang.String[] { "Body", "Attachments", "Flags", "ExpireTimer", "ProfileKey", "Timestamp", "Quote", "Preview", "Reaction", "Profile", "OpenGroupInvitation", "ClosedGroupControlMessage", "SyncTarget", "GroupMessage", }); internal_static_signalservice_DataMessage_Quote_descriptor = internal_static_signalservice_DataMessage_descriptor.getNestedTypes().get(0); internal_static_signalservice_DataMessage_Quote_fieldAccessorTable = new @@ -26295,12 +29170,18 @@ public final class SignalServiceProtos { com.google.protobuf.GeneratedMessage.FieldAccessorTable( internal_static_signalservice_DataMessage_OpenGroupInvitation_descriptor, new java.lang.String[] { "Url", "Name", }); - internal_static_signalservice_DataMessage_ClosedGroupControlMessage_descriptor = + internal_static_signalservice_DataMessage_GroupMessage_descriptor = internal_static_signalservice_DataMessage_descriptor.getNestedTypes().get(4); + internal_static_signalservice_DataMessage_GroupMessage_fieldAccessorTable = new + com.google.protobuf.GeneratedMessage.FieldAccessorTable( + internal_static_signalservice_DataMessage_GroupMessage_descriptor, + new java.lang.String[] { "DeleteMessage", "MemberLeftMessage", "InviteMessage", "PromoteMessage", }); + internal_static_signalservice_DataMessage_ClosedGroupControlMessage_descriptor = + internal_static_signalservice_DataMessage_descriptor.getNestedTypes().get(5); internal_static_signalservice_DataMessage_ClosedGroupControlMessage_fieldAccessorTable = new com.google.protobuf.GeneratedMessage.FieldAccessorTable( internal_static_signalservice_DataMessage_ClosedGroupControlMessage_descriptor, - new java.lang.String[] { "Type", "PublicKey", "Name", "EncryptionKeyPair", "Members", "Admins", "Wrappers", "ExpirationTimer", }); + new java.lang.String[] { "Type", "PublicKey", "Name", "EncryptionKeyPair", "Members", "Admins", "Wrappers", "ExpirationTimer", "MemberPrivateKey", "PrivateKey", }); internal_static_signalservice_DataMessage_ClosedGroupControlMessage_KeyPairWrapper_descriptor = internal_static_signalservice_DataMessage_ClosedGroupControlMessage_descriptor.getNestedTypes().get(0); internal_static_signalservice_DataMessage_ClosedGroupControlMessage_KeyPairWrapper_fieldAccessorTable = new @@ -26308,19 +29189,49 @@ public final class SignalServiceProtos { internal_static_signalservice_DataMessage_ClosedGroupControlMessage_KeyPairWrapper_descriptor, new java.lang.String[] { "PublicKey", "EncryptedKeyPair", }); internal_static_signalservice_DataMessage_Reaction_descriptor = - internal_static_signalservice_DataMessage_descriptor.getNestedTypes().get(5); + internal_static_signalservice_DataMessage_descriptor.getNestedTypes().get(6); internal_static_signalservice_DataMessage_Reaction_fieldAccessorTable = new com.google.protobuf.GeneratedMessage.FieldAccessorTable( internal_static_signalservice_DataMessage_Reaction_descriptor, new java.lang.String[] { "Id", "Author", "Emoji", "Action", }); - internal_static_signalservice_CallMessage_descriptor = + internal_static_signalservice_GroupDeleteMessage_descriptor = getDescriptor().getMessageTypes().get(7); + internal_static_signalservice_GroupDeleteMessage_fieldAccessorTable = new + com.google.protobuf.GeneratedMessage.FieldAccessorTable( + internal_static_signalservice_GroupDeleteMessage_descriptor, + new java.lang.String[] { "PublicKey", "LastEncryptionKey", }); + internal_static_signalservice_GroupMemberLeftMessage_descriptor = + getDescriptor().getMessageTypes().get(8); + internal_static_signalservice_GroupMemberLeftMessage_fieldAccessorTable = new + com.google.protobuf.GeneratedMessage.FieldAccessorTable( + internal_static_signalservice_GroupMemberLeftMessage_descriptor, + new java.lang.String[] { }); + internal_static_signalservice_GroupInviteMessage_descriptor = + getDescriptor().getMessageTypes().get(9); + internal_static_signalservice_GroupInviteMessage_fieldAccessorTable = new + com.google.protobuf.GeneratedMessage.FieldAccessorTable( + internal_static_signalservice_GroupInviteMessage_descriptor, + new java.lang.String[] { "PublicKey", "Name", "MemberPrivateKey", }); + internal_static_signalservice_GroupPromoteMessage_descriptor = + getDescriptor().getMessageTypes().get(10); + internal_static_signalservice_GroupPromoteMessage_fieldAccessorTable = new + com.google.protobuf.GeneratedMessage.FieldAccessorTable( + internal_static_signalservice_GroupPromoteMessage_descriptor, + new java.lang.String[] { "PublicKey", "EncryptedPrivateKey", }); + internal_static_signalservice_CallMessage_descriptor = + getDescriptor().getMessageTypes().get(11); internal_static_signalservice_CallMessage_fieldAccessorTable = new com.google.protobuf.GeneratedMessage.FieldAccessorTable( internal_static_signalservice_CallMessage_descriptor, new java.lang.String[] { "Type", "Sdps", "SdpMLineIndexes", "SdpMids", "Uuid", }); + internal_static_signalservice_SharedConfigMessage_descriptor = + getDescriptor().getMessageTypes().get(12); + internal_static_signalservice_SharedConfigMessage_fieldAccessorTable = new + com.google.protobuf.GeneratedMessage.FieldAccessorTable( + internal_static_signalservice_SharedConfigMessage_descriptor, + new java.lang.String[] { "Type", "Data", }); internal_static_signalservice_ConfigurationMessage_descriptor = - getDescriptor().getMessageTypes().get(8); + getDescriptor().getMessageTypes().get(13); internal_static_signalservice_ConfigurationMessage_fieldAccessorTable = new com.google.protobuf.GeneratedMessage.FieldAccessorTable( internal_static_signalservice_ConfigurationMessage_descriptor, @@ -26338,29 +29249,23 @@ public final class SignalServiceProtos { internal_static_signalservice_ConfigurationMessage_Contact_descriptor, new java.lang.String[] { "PublicKey", "Name", "ProfilePicture", "ProfileKey", "IsApproved", "IsBlocked", "DidApproveMe", }); internal_static_signalservice_MessageRequestResponse_descriptor = - getDescriptor().getMessageTypes().get(9); + getDescriptor().getMessageTypes().get(14); internal_static_signalservice_MessageRequestResponse_fieldAccessorTable = new com.google.protobuf.GeneratedMessage.FieldAccessorTable( internal_static_signalservice_MessageRequestResponse_descriptor, new java.lang.String[] { "IsApproved", "ProfileKey", "Profile", }); internal_static_signalservice_ReceiptMessage_descriptor = - getDescriptor().getMessageTypes().get(10); + getDescriptor().getMessageTypes().get(15); internal_static_signalservice_ReceiptMessage_fieldAccessorTable = new com.google.protobuf.GeneratedMessage.FieldAccessorTable( internal_static_signalservice_ReceiptMessage_descriptor, new java.lang.String[] { "Type", "Timestamp", }); internal_static_signalservice_AttachmentPointer_descriptor = - getDescriptor().getMessageTypes().get(11); + getDescriptor().getMessageTypes().get(16); internal_static_signalservice_AttachmentPointer_fieldAccessorTable = new com.google.protobuf.GeneratedMessage.FieldAccessorTable( internal_static_signalservice_AttachmentPointer_descriptor, new java.lang.String[] { "Id", "ContentType", "Key", "Size", "Thumbnail", "Digest", "FileName", "Flags", "Width", "Height", "Caption", "Url", }); - internal_static_signalservice_GroupContext_descriptor = - getDescriptor().getMessageTypes().get(12); - internal_static_signalservice_GroupContext_fieldAccessorTable = new - com.google.protobuf.GeneratedMessage.FieldAccessorTable( - internal_static_signalservice_GroupContext_descriptor, - new java.lang.String[] { "Id", "Type", "Name", "Members", "Avatar", "Admins", }); return null; } };