From f3bb40e0242847e73198b756a0700d54f3e8abbf Mon Sep 17 00:00:00 2001 From: Anton Chekulaev Date: Fri, 28 Aug 2020 17:38:19 +1000 Subject: [PATCH 01/20] Loading indicator for message thumbnail view. --- res/layout/conversation_item_thumbnail.xml | 15 +++++++- .../components/ConversationItemThumbnail.java | 37 ++++++++++++++----- 2 files changed, 40 insertions(+), 12 deletions(-) diff --git a/res/layout/conversation_item_thumbnail.xml b/res/layout/conversation_item_thumbnail.xml index 23544e8e68..fe438060da 100644 --- a/res/layout/conversation_item_thumbnail.xml +++ b/res/layout/conversation_item_thumbnail.xml @@ -42,7 +42,18 @@ android:layout_marginStart="@dimen/message_bubble_horizontal_padding" android:layout_marginEnd="@dimen/message_bubble_horizontal_padding" android:layout_marginBottom="@dimen/message_bubble_bottom_padding" - app:footer_text_color="@color/core_white" - app:footer_icon_color="@color/core_white"/> + app:footer_text_color="@android:color/white" + app:footer_icon_color="@android:color/white"/> + + diff --git a/src/org/thoughtcrime/securesms/components/ConversationItemThumbnail.java b/src/org/thoughtcrime/securesms/components/ConversationItemThumbnail.java index 4b4e791ab2..ef7db1abb1 100644 --- a/src/org/thoughtcrime/securesms/components/ConversationItemThumbnail.java +++ b/src/org/thoughtcrime/securesms/components/ConversationItemThumbnail.java @@ -3,15 +3,16 @@ package org.thoughtcrime.securesms.components; import android.content.Context; import android.content.res.TypedArray; import android.graphics.Canvas; +import android.util.AttributeSet; +import android.widget.FrameLayout; +import android.widget.ImageView; +import android.widget.ProgressBar; + import androidx.annotation.ColorInt; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.annotation.UiThread; -import android.util.AttributeSet; -import android.widget.FrameLayout; -import android.widget.ImageView; -import network.loki.messenger.R; import org.thoughtcrime.securesms.attachments.Attachment; import org.thoughtcrime.securesms.mms.GlideRequests; import org.thoughtcrime.securesms.mms.Slide; @@ -21,12 +22,15 @@ import org.thoughtcrime.securesms.util.ThemeUtil; import java.util.List; +import network.loki.messenger.R; + public class ConversationItemThumbnail extends FrameLayout { private ThumbnailView thumbnail; private AlbumThumbnailView album; private ImageView shade; private ConversationItemFooter footer; + private ProgressBar loadIndicator; private CornerMask cornerMask; private Outliner outliner; private boolean borderless; @@ -49,12 +53,13 @@ public class ConversationItemThumbnail extends FrameLayout { private void init(@Nullable AttributeSet attrs) { inflate(getContext(), R.layout.conversation_item_thumbnail, this); - this.thumbnail = findViewById(R.id.conversation_thumbnail_image); - this.album = findViewById(R.id.conversation_thumbnail_album); - this.shade = findViewById(R.id.conversation_thumbnail_shade); - this.footer = findViewById(R.id.conversation_thumbnail_footer); - this.cornerMask = new CornerMask(this); - this.outliner = new Outliner(); + this.thumbnail = findViewById(R.id.conversation_thumbnail_image); + this.album = findViewById(R.id.conversation_thumbnail_album); + this.shade = findViewById(R.id.conversation_thumbnail_shade); + this.footer = findViewById(R.id.conversation_thumbnail_footer); + this.loadIndicator = findViewById(R.id.conversation_thumbnail_load_indicator); + this.cornerMask = new CornerMask(this); + this.outliner = new Outliner(); outliner.setColor(ThemeUtil.getThemedColor(getContext(), R.attr.conversation_item_image_outline_color)); @@ -136,6 +141,18 @@ public class ConversationItemThumbnail extends FrameLayout { album.setSlides(glideRequests, slides, showControls); setTouchDelegate(album.getTouchDelegate()); } + + // Display loading indicator if any attachment is in loading state. + { + boolean anyLoading = false; + for (int i = 0; i < slides.size(); i++) { + if (slides.get(i).asAttachment().isInProgress()) { + anyLoading = true; + break; + } + } + loadIndicator.setVisibility(anyLoading ? VISIBLE : GONE); + } } public void setConversationColor(@ColorInt int color) { From 52f50e3252cd98a44ee868b7d0b40e4923314436 Mon Sep 17 00:00:00 2001 From: nielsandriesse Date: Mon, 31 Aug 2020 13:53:31 +1000 Subject: [PATCH 02/20] Fix SSK based closed group size inconsistency --- .../securesms/loki/activities/CreateClosedGroupActivity.kt | 2 +- .../securesms/loki/activities/EditClosedGroupActivity.kt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/org/thoughtcrime/securesms/loki/activities/CreateClosedGroupActivity.kt b/src/org/thoughtcrime/securesms/loki/activities/CreateClosedGroupActivity.kt index 306173446e..16b2304238 100644 --- a/src/org/thoughtcrime/securesms/loki/activities/CreateClosedGroupActivity.kt +++ b/src/org/thoughtcrime/securesms/loki/activities/CreateClosedGroupActivity.kt @@ -118,7 +118,7 @@ class CreateClosedGroupActivity : PassphraseRequiredActionBarActivity(), LoaderM if (selectedMembers.count() < 1) { return Toast.makeText(this, R.string.activity_create_closed_group_not_enough_group_members_error, Toast.LENGTH_LONG).show() } - if (selectedMembers.count() > ClosedGroupsProtocol.groupSizeLimit) { // Minus one because we're going to include self later + if (selectedMembers.count() >= ClosedGroupsProtocol.groupSizeLimit) { // Minus one because we're going to include self later return Toast.makeText(this, R.string.activity_create_closed_group_too_many_group_members_error, Toast.LENGTH_LONG).show() } val userPublicKey = TextSecurePreferences.getLocalNumber(this) diff --git a/src/org/thoughtcrime/securesms/loki/activities/EditClosedGroupActivity.kt b/src/org/thoughtcrime/securesms/loki/activities/EditClosedGroupActivity.kt index cf66fff5ea..2326562a48 100644 --- a/src/org/thoughtcrime/securesms/loki/activities/EditClosedGroupActivity.kt +++ b/src/org/thoughtcrime/securesms/loki/activities/EditClosedGroupActivity.kt @@ -228,7 +228,7 @@ class EditClosedGroupActivity : PassphraseRequiredActionBarActivity() { } val maxGroupMembers = if (isSSKBasedClosedGroup) ClosedGroupsProtocol.groupSizeLimit else Companion.legacyGroupSizeLimit - if (members.size > maxGroupMembers) { + if (members.size >= maxGroupMembers) { // TODO: Update copy for SSK based closed groups return Toast.makeText(this, R.string.activity_edit_closed_group_too_many_group_members_error, Toast.LENGTH_LONG).show() } From 5957afa1479eb739b5ba4ff342695b57b8e8b286 Mon Sep 17 00:00:00 2001 From: nielsandriesse Date: Mon, 31 Aug 2020 14:54:03 +1000 Subject: [PATCH 03/20] Debug SSK based closed group editing --- .../activities/EditClosedGroupActivity.kt | 3 +- .../loki/protocol/ClosedGroupsProtocol.kt | 177 ++++++++---------- 2 files changed, 76 insertions(+), 104 deletions(-) diff --git a/src/org/thoughtcrime/securesms/loki/activities/EditClosedGroupActivity.kt b/src/org/thoughtcrime/securesms/loki/activities/EditClosedGroupActivity.kt index 2326562a48..455587c8af 100644 --- a/src/org/thoughtcrime/securesms/loki/activities/EditClosedGroupActivity.kt +++ b/src/org/thoughtcrime/securesms/loki/activities/EditClosedGroupActivity.kt @@ -234,8 +234,7 @@ class EditClosedGroupActivity : PassphraseRequiredActionBarActivity() { } if (isSSKBasedClosedGroup) { - ClosedGroupsProtocol.update(this, groupPublicKey!!, members.map { it.address.serialize() }, - name, admins.map { it.address.serialize() }) + ClosedGroupsProtocol.update(this, groupPublicKey!!, members.map { it.address.serialize() }, name) } else { GroupManager.updateGroup(this, groupID, members, null, name, admins) } diff --git a/src/org/thoughtcrime/securesms/loki/protocol/ClosedGroupsProtocol.kt b/src/org/thoughtcrime/securesms/loki/protocol/ClosedGroupsProtocol.kt index 3df6187a7b..1c347de7ab 100644 --- a/src/org/thoughtcrime/securesms/loki/protocol/ClosedGroupsProtocol.kt +++ b/src/org/thoughtcrime/securesms/loki/protocol/ClosedGroupsProtocol.kt @@ -73,17 +73,34 @@ object ClosedGroupsProtocol { return groupID } - public fun addMembers(context: Context, newMembers: Collection, groupPublicKey: String) { - // Prepare + @JvmStatic + public fun leave(context: Context, groupPublicKey: String) { + val userPublicKey = TextSecurePreferences.getLocalNumber(context) + val groupDB = DatabaseFactory.getGroupDatabase(context) + val groupID = doubleEncodeGroupID(groupPublicKey) + val group = groupDB.getGroup(groupID).orNull() + if (group == null) { + Log.d("Loki", "Can't leave nonexistent closed group.") + return + } + val name = group.title + val oldMembers = group.members.map { it.serialize() }.toSet() + val newMembers = oldMembers.minus(userPublicKey) + update(context, groupPublicKey, newMembers, name) + } + + public fun update(context: Context, groupPublicKey: String, members: Collection, name: String) { + val userPublicKey = TextSecurePreferences.getLocalNumber(context) val sskDatabase = DatabaseFactory.getSSKDatabase(context) val groupDB = DatabaseFactory.getGroupDatabase(context) val groupID = doubleEncodeGroupID(groupPublicKey) val group = groupDB.getGroup(groupID).orNull() if (group == null) { - Log.d("Loki", "Can't add users to nonexistent closed group.") + Log.d("Loki", "Can't update nonexistent closed group.") return } - val name = group.title + val oldMembers = group.members.map { it.serialize() }.toSet() + val membersAsData = members.map { Hex.fromStringCondensed(it) } val admins = group.admins.map { it.serialize() } val adminsAsData = admins.map { Hex.fromStringCondensed(it) } val groupPrivateKey = DatabaseFactory.getSSKDatabase(context).getClosedGroupPrivateKey(groupPublicKey) @@ -91,113 +108,69 @@ object ClosedGroupsProtocol { Log.d("Loki", "Couldn't get private key for closed group.") return } - // Add the members to the member list - val members = group.members.map { it.serialize() }.toMutableSet() - members.addAll(newMembers) - val membersAsData = members.map { Hex.fromStringCondensed(it) } - // Generate ratchets for the new members - val senderKeys: List = newMembers.map { publicKey -> - val ratchet = SharedSenderKeysImplementation.shared.generateRatchet(groupPublicKey, publicKey) - ClosedGroupSenderKey(Hex.fromStringCondensed(ratchet.chainKey), ratchet.keyIndex, Hex.fromStringCondensed(publicKey)) - } - // Send a closed group update message to the existing members with the new members' ratchets (this message is aimed at the group) - val closedGroupUpdateKind = ClosedGroupUpdateMessageSendJob.Kind.Info(Hex.fromStringCondensed(groupPublicKey), name, - senderKeys, membersAsData, adminsAsData) - val job = ClosedGroupUpdateMessageSendJob(groupPublicKey, closedGroupUpdateKind) - ApplicationContext.getInstance(context).jobManager.add(job) - // Establish sessions if needed - establishSessionsWithMembersIfNeeded(context, newMembers) - // Send closed group update messages to the new members using established channels - val allSenderKeys = sskDatabase.getAllClosedGroupSenderKeys(groupPublicKey) + senderKeys - for (member in members) { - @Suppress("NAME_SHADOWING") - val closedGroupUpdateKind = ClosedGroupUpdateMessageSendJob.Kind.New(Hex.fromStringCondensed(groupPublicKey), name, - Hex.fromStringCondensed(groupPrivateKey), allSenderKeys, membersAsData, adminsAsData) - @Suppress("NAME_SHADOWING") - val job = ClosedGroupUpdateMessageSendJob(member, closedGroupUpdateKind) - ApplicationContext.getInstance(context).jobManager.add(job) - } - // Update the group - groupDB.updateMembers(groupID, members.map { Address.fromSerialized(it) }) - // Notify the user - val threadID = DatabaseFactory.getThreadDatabase(context).getThreadIdFor(Recipient.from(context, Address.fromSerialized(groupID), false)) - insertOutgoingInfoMessage(context, groupID, GroupContext.Type.UPDATE, name, members, admins, threadID) - } - - @JvmStatic - public fun leave(context: Context, groupPublicKey: String) { - val userPublicKey = TextSecurePreferences.getLocalNumber(context) - removeMembers(context, setOf( userPublicKey ), groupPublicKey) - } - - public fun removeMembers(context: Context, membersToRemove: Collection, groupPublicKey: String) { - val userPublicKey = TextSecurePreferences.getLocalNumber(context) - val sskDatabase = DatabaseFactory.getSSKDatabase(context) - val isUserLeaving = membersToRemove.contains(userPublicKey) - if (isUserLeaving && membersToRemove.count() != 1) { - Log.d("Loki", "Can't remove self and others simultaneously.") - return - } - val groupDB = DatabaseFactory.getGroupDatabase(context) - val groupID = doubleEncodeGroupID(groupPublicKey) - val group = groupDB.getGroup(groupID).orNull() - if (group == null) { - Log.d("Loki", "Can't add users to nonexistent closed group.") - return - } - val name = group.title - val admins = group.admins.map { it.serialize() } - val adminsAsData = admins.map { Hex.fromStringCondensed(it) } - // Remove the members from the member list - val members = group.members.map { it.serialize() }.toSet().minus(membersToRemove) - val membersAsData = members.map { Hex.fromStringCondensed(it) } - // Send the update to the group (don't include new ratchets as everyone should regenerate new ratchets individually) - val closedGroupUpdateKind = ClosedGroupUpdateMessageSendJob.Kind.Info(Hex.fromStringCondensed(groupPublicKey), - name, setOf(), membersAsData, adminsAsData) - val job = ClosedGroupUpdateMessageSendJob(groupPublicKey, closedGroupUpdateKind) - job.setContext(context) - job.onRun() // Run the job immediately - // Delete all ratchets (it's important that this happens after sending out the update) - sskDatabase.removeAllClosedGroupRatchets(groupPublicKey) - // Remove the group from the user's set of public keys to poll for if the user is leaving. Otherwise generate a new ratchet and - // send it out to all members (minus the removed ones) using established channels. - if (isUserLeaving) { - sskDatabase.removeClosedGroupPrivateKey(groupPublicKey) - groupDB.setActive(groupID, false) + val wasAnyUserRemoved = members.toSet().intersect(oldMembers) != oldMembers.toSet() + if (wasAnyUserRemoved) { + val removedMembers = oldMembers.minus(members) + val isUserLeaving = removedMembers.contains(userPublicKey) + if (isUserLeaving && removedMembers.count() != 1) { + Log.d("Loki", "Can't remove self and others simultaneously.") + return + } + // Send the update to the group (don't include new ratchets as everyone should regenerate new ratchets individually) + val closedGroupUpdateKind = ClosedGroupUpdateMessageSendJob.Kind.Info(Hex.fromStringCondensed(groupPublicKey), + name, setOf(), membersAsData, adminsAsData) + val job = ClosedGroupUpdateMessageSendJob(groupPublicKey, closedGroupUpdateKind) + job.setContext(context) + job.onRun() // Run the job immediately + // Delete all ratchets (it's important that this happens * after * sending out the update) + sskDatabase.removeAllClosedGroupRatchets(groupPublicKey) + // Remove the group from the user's set of public keys to poll for if the user is leaving. Otherwise generate a new ratchet and + // send it out to all members (minus the removed ones) using established channels. + if (isUserLeaving) { + sskDatabase.removeClosedGroupPrivateKey(groupPublicKey) + groupDB.setActive(groupID, false) + } else { + // Establish sessions if needed + establishSessionsWithMembersIfNeeded(context, members) + // Send out the user's new ratchet to all members (minus the removed ones) using established channels + val userRatchet = SharedSenderKeysImplementation.shared.generateRatchet(groupPublicKey, userPublicKey) + val userSenderKey = ClosedGroupSenderKey(Hex.fromStringCondensed(userRatchet.chainKey), userRatchet.keyIndex, Hex.fromStringCondensed(userPublicKey)) + for (member in members) { + if (member == userPublicKey) { continue } + @Suppress("NAME_SHADOWING") + val closedGroupUpdateKind = ClosedGroupUpdateMessageSendJob.Kind.SenderKey(Hex.fromStringCondensed(groupPublicKey), userSenderKey) + @Suppress("NAME_SHADOWING") + val job = ClosedGroupUpdateMessageSendJob(member, closedGroupUpdateKind) + ApplicationContext.getInstance(context).jobManager.add(job) + } + } } else { + // Generate ratchets for any new members + val newMembers = members.minus(oldMembers) + val senderKeys: List = newMembers.map { publicKey -> + val ratchet = SharedSenderKeysImplementation.shared.generateRatchet(groupPublicKey, publicKey) + ClosedGroupSenderKey(Hex.fromStringCondensed(ratchet.chainKey), ratchet.keyIndex, Hex.fromStringCondensed(publicKey)) + } + // Send a closed group update message to the existing members with the new members' ratchets (this message is aimed at the group) + val closedGroupUpdateKind = ClosedGroupUpdateMessageSendJob.Kind.Info(Hex.fromStringCondensed(groupPublicKey), name, + senderKeys, membersAsData, adminsAsData) + val job = ClosedGroupUpdateMessageSendJob(groupPublicKey, closedGroupUpdateKind) + ApplicationContext.getInstance(context).jobManager.add(job) // Establish sessions if needed - establishSessionsWithMembersIfNeeded(context, members) - // Send out the user's new ratchet to all members (minus the removed ones) using established channels - val userRatchet = SharedSenderKeysImplementation.shared.generateRatchet(groupPublicKey, userPublicKey) - val userSenderKey = ClosedGroupSenderKey(Hex.fromStringCondensed(userRatchet.chainKey), userRatchet.keyIndex, Hex.fromStringCondensed(userPublicKey)) - for (member in members) { + establishSessionsWithMembersIfNeeded(context, newMembers) + // Send closed group update messages to the new members using established channels + val allSenderKeys = sskDatabase.getAllClosedGroupSenderKeys(groupPublicKey) + senderKeys + for (member in newMembers) { @Suppress("NAME_SHADOWING") - val closedGroupUpdateKind = ClosedGroupUpdateMessageSendJob.Kind.SenderKey(Hex.fromStringCondensed(groupPublicKey), userSenderKey) + val closedGroupUpdateKind = ClosedGroupUpdateMessageSendJob.Kind.New(Hex.fromStringCondensed(groupPublicKey), name, + Hex.fromStringCondensed(groupPrivateKey), allSenderKeys, membersAsData, adminsAsData) @Suppress("NAME_SHADOWING") val job = ClosedGroupUpdateMessageSendJob(member, closedGroupUpdateKind) ApplicationContext.getInstance(context).jobManager.add(job) } } // Update the group - groupDB.updateMembers(groupID, members.map { Address.fromSerialized(it) }) - // Notify the user - val threadID = DatabaseFactory.getThreadDatabase(context).getThreadIdFor(Recipient.from(context, Address.fromSerialized(groupID), false)) - insertOutgoingInfoMessage(context, groupID, GroupContext.Type.QUIT, name, members, admins, threadID) - } - - public fun update(context: Context, groupPublicKey: String, members: Collection, name: String, admins: Collection) { - val groupDB = DatabaseFactory.getGroupDatabase(context) - val groupID = doubleEncodeGroupID(groupPublicKey) - if (groupDB.getGroup(groupID).orNull() == null) { - Log.d("Loki", "Can't update nonexistent closed group.") - return - } - // Send the update to the group - val closedGroupUpdateKind = ClosedGroupUpdateMessageSendJob.Kind.Info(Hex.fromStringCondensed(groupPublicKey), - name, setOf(), members.map { Hex.fromStringCondensed(it) }, admins.map { Hex.fromStringCondensed(it) }) - val job = ClosedGroupUpdateMessageSendJob(groupPublicKey, closedGroupUpdateKind) - ApplicationContext.getInstance(context).jobManager.add(job) - // Update the group + groupDB.updateTitle(groupID, name) groupDB.updateMembers(groupID, members.map { Address.fromSerialized(it) }) // Notify the user val threadID = DatabaseFactory.getThreadDatabase(context).getThreadIdFor(Recipient.from(context, Address.fromSerialized(groupID), false)) From e7b2e3a223ce642f50eb03f33bbd4d2d12058d0b Mon Sep 17 00:00:00 2001 From: nielsandriesse Date: Mon, 31 Aug 2020 16:03:46 +1000 Subject: [PATCH 04/20] Debug --- .../securesms/loki/protocol/ClosedGroupsProtocol.kt | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/org/thoughtcrime/securesms/loki/protocol/ClosedGroupsProtocol.kt b/src/org/thoughtcrime/securesms/loki/protocol/ClosedGroupsProtocol.kt index 1c347de7ab..8e92e7f312 100644 --- a/src/org/thoughtcrime/securesms/loki/protocol/ClosedGroupsProtocol.kt +++ b/src/org/thoughtcrime/securesms/loki/protocol/ClosedGroupsProtocol.kt @@ -1,7 +1,6 @@ package org.thoughtcrime.securesms.loki.protocol import android.content.Context -import android.graphics.Bitmap import android.util.Log import com.google.protobuf.ByteString import org.thoughtcrime.securesms.ApplicationContext @@ -60,6 +59,7 @@ object ClosedGroupsProtocol { val closedGroupUpdateKind = ClosedGroupUpdateMessageSendJob.Kind.New(Hex.fromStringCondensed(groupPublicKey), name, groupKeyPair.privateKey.serialize(), senderKeys, membersAsData, adminsAsData) for (member in members) { + if (member == userPublicKey) { continue } val job = ClosedGroupUpdateMessageSendJob(member, closedGroupUpdateKind) ApplicationContext.getInstance(context).jobManager.add(job) } @@ -147,19 +147,19 @@ object ClosedGroupsProtocol { } else { // Generate ratchets for any new members val newMembers = members.minus(oldMembers) - val senderKeys: List = newMembers.map { publicKey -> + val newSenderKeys: List = newMembers.map { publicKey -> val ratchet = SharedSenderKeysImplementation.shared.generateRatchet(groupPublicKey, publicKey) ClosedGroupSenderKey(Hex.fromStringCondensed(ratchet.chainKey), ratchet.keyIndex, Hex.fromStringCondensed(publicKey)) } // Send a closed group update message to the existing members with the new members' ratchets (this message is aimed at the group) val closedGroupUpdateKind = ClosedGroupUpdateMessageSendJob.Kind.Info(Hex.fromStringCondensed(groupPublicKey), name, - senderKeys, membersAsData, adminsAsData) + newSenderKeys, membersAsData, adminsAsData) val job = ClosedGroupUpdateMessageSendJob(groupPublicKey, closedGroupUpdateKind) ApplicationContext.getInstance(context).jobManager.add(job) // Establish sessions if needed establishSessionsWithMembersIfNeeded(context, newMembers) // Send closed group update messages to the new members using established channels - val allSenderKeys = sskDatabase.getAllClosedGroupSenderKeys(groupPublicKey) + senderKeys + val allSenderKeys = sskDatabase.getAllClosedGroupSenderKeys(groupPublicKey) + newSenderKeys for (member in newMembers) { @Suppress("NAME_SHADOWING") val closedGroupUpdateKind = ClosedGroupUpdateMessageSendJob.Kind.New(Hex.fromStringCondensed(groupPublicKey), name, @@ -295,6 +295,7 @@ object ClosedGroupsProtocol { val userRatchet = SharedSenderKeysImplementation.shared.generateRatchet(groupPublicKey, userPublicKey) val userSenderKey = ClosedGroupSenderKey(Hex.fromStringCondensed(userRatchet.chainKey), userRatchet.keyIndex, Hex.fromStringCondensed(userPublicKey)) for (member in members) { + if (member == userPublicKey) { continue } val closedGroupUpdateKind = ClosedGroupUpdateMessageSendJob.Kind.SenderKey(Hex.fromStringCondensed(groupPublicKey), userSenderKey) val job = ClosedGroupUpdateMessageSendJob(member, closedGroupUpdateKind) ApplicationContext.getInstance(context).jobManager.add(job) From 7cc67daf80603e204fb0c669607aab0623e6bf18 Mon Sep 17 00:00:00 2001 From: Anton Chekulaev Date: Mon, 31 Aug 2020 19:46:05 +1000 Subject: [PATCH 05/20] DB attachment record gets properly updated on upload error. Individual loading indicators per conversation item thumbnail. --- res/layout/conversation_item_thumbnail.xml | 11 ---------- res/layout/thumbnail_view.xml | 9 ++++++++ .../components/AlbumThumbnailView.java | 7 ++----- .../components/ConversationItemThumbnail.java | 21 ++++--------------- .../securesms/components/ThumbnailView.java | 12 ++++++++--- .../database/AttachmentDatabase.java | 11 +++++++++- .../securesms/jobs/AttachmentUploadJob.java | 19 +++++++++++------ 7 files changed, 47 insertions(+), 43 deletions(-) diff --git a/res/layout/conversation_item_thumbnail.xml b/res/layout/conversation_item_thumbnail.xml index fe438060da..bff1a90fa8 100644 --- a/res/layout/conversation_item_thumbnail.xml +++ b/res/layout/conversation_item_thumbnail.xml @@ -45,15 +45,4 @@ app:footer_text_color="@android:color/white" app:footer_icon_color="@android:color/white"/> - - diff --git a/res/layout/thumbnail_view.xml b/res/layout/thumbnail_view.xml index a9f979014c..96ec33477e 100644 --- a/res/layout/thumbnail_view.xml +++ b/res/layout/thumbnail_view.xml @@ -22,6 +22,15 @@ android:src="@drawable/ic_caption_28" android:visibility="gone" /> + + insertAttachmentsForMessage(long mmsId, @NonNull List attachments, @NonNull List quoteAttachment) throws MmsException { diff --git a/src/org/thoughtcrime/securesms/jobs/AttachmentUploadJob.java b/src/org/thoughtcrime/securesms/jobs/AttachmentUploadJob.java index 766e8f20f6..a356deabec 100644 --- a/src/org/thoughtcrime/securesms/jobs/AttachmentUploadJob.java +++ b/src/org/thoughtcrime/securesms/jobs/AttachmentUploadJob.java @@ -87,13 +87,20 @@ public class AttachmentUploadJob extends BaseJob implements InjectableType { // Only upload attachment if necessary if (databaseAttachment.getUrl().isEmpty()) { - MediaConstraints mediaConstraints = MediaConstraints.getPushMediaConstraints(); - Attachment scaledAttachment = scaleAndStripExif(database, mediaConstraints, databaseAttachment); - SignalServiceAttachment localAttachment = getAttachmentFor(scaledAttachment); - SignalServiceAttachmentPointer remoteAttachment = messageSender.uploadAttachment(localAttachment.asStream(), databaseAttachment.isSticker(), new SignalServiceAddress(destination.serialize())); - Attachment attachment = PointerAttachment.forPointer(Optional.of(remoteAttachment), null, databaseAttachment.getFastPreflightId()).get(); + final Attachment attachment; + try { + MediaConstraints mediaConstraints = MediaConstraints.getPushMediaConstraints(); + Attachment scaledAttachment = scaleAndStripExif(database, mediaConstraints, databaseAttachment); + SignalServiceAttachment localAttachment = getAttachmentFor(scaledAttachment); + SignalServiceAttachmentPointer remoteAttachment = messageSender.uploadAttachment(localAttachment.asStream(), databaseAttachment.isSticker(), new SignalServiceAddress(destination.serialize())); + attachment = PointerAttachment.forPointer(Optional.of(remoteAttachment), null, databaseAttachment.getFastPreflightId()).get(); + } catch (Exception e) { + // On any error make sure we mark the related DB record's transfer state as failed. + database.updateAttachmentAfterUploadFailed(databaseAttachment.getAttachmentId()); + throw e; + } - database.updateAttachmentAfterUpload(databaseAttachment.getAttachmentId(), attachment); + database.updateAttachmentAfterUploadSucceeded(databaseAttachment.getAttachmentId(), attachment); } } From f0ad59370acb067182a7d0468443b6adbe2fc112 Mon Sep 17 00:00:00 2001 From: nielsandriesse Date: Tue, 1 Sep 2020 09:10:23 +1000 Subject: [PATCH 06/20] Minor refactoring --- res/layout/thumbnail_view.xml | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/res/layout/thumbnail_view.xml b/res/layout/thumbnail_view.xml index 96ec33477e..a22164f376 100644 --- a/res/layout/thumbnail_view.xml +++ b/res/layout/thumbnail_view.xml @@ -24,12 +24,14 @@ + android:indeterminateTint="@android:color/white" + android:indeterminateTintMode="src_in" + tools:visibility="visible" /> Date: Tue, 1 Sep 2020 09:48:14 +1000 Subject: [PATCH 07/20] fix open group date handling --- .../securesms/conversation/ConversationAdapter.java | 7 +++++-- .../securesms/database/MmsDatabase.java | 13 ++++++++++++- .../securesms/database/model/MessageRecord.java | 6 +++--- .../thoughtcrime/securesms/jobs/PushDecryptJob.java | 4 ++-- .../securesms/loki/api/PublicChatPoller.kt | 2 +- 5 files changed, 23 insertions(+), 9 deletions(-) diff --git a/src/org/thoughtcrime/securesms/conversation/ConversationAdapter.java b/src/org/thoughtcrime/securesms/conversation/ConversationAdapter.java index 64e7365b1d..09c25bc303 100644 --- a/src/org/thoughtcrime/securesms/conversation/ConversationAdapter.java +++ b/src/org/thoughtcrime/securesms/conversation/ConversationAdapter.java @@ -396,8 +396,11 @@ public class ConversationAdapter if (position < 0) return -1; MessageRecord record = getRecordForPositionOrThrow(position); - - calendar.setTime(new Date(record.getDateSent())); + if (record.getRecipient().getAddress().isOpenGroup()) { + calendar.setTime(new Date(record.getDateReceived())); + } else { + calendar.setTime(new Date(record.getDateSent())); + } return Util.hashCode(calendar.get(Calendar.YEAR), calendar.get(Calendar.DAY_OF_YEAR)); } diff --git a/src/org/thoughtcrime/securesms/database/MmsDatabase.java b/src/org/thoughtcrime/securesms/database/MmsDatabase.java index 8948ff6123..97a59c5007 100644 --- a/src/org/thoughtcrime/securesms/database/MmsDatabase.java +++ b/src/org/thoughtcrime/securesms/database/MmsDatabase.java @@ -972,6 +972,14 @@ public class MmsDatabase extends MessagingDatabase { public long insertMessageOutbox(@NonNull OutgoingMediaMessage message, long threadId, boolean forceSms, @Nullable SmsDatabase.InsertListener insertListener) + throws MmsException { + return insertMessageOutbox(message, threadId, forceSms, insertListener, 0); + } + + public long insertMessageOutbox(@NonNull OutgoingMediaMessage message, + long threadId, boolean forceSms, + @Nullable SmsDatabase.InsertListener insertListener, + long serverTimestamp) throws MmsException { long type = Types.BASE_SENDING_TYPE; @@ -998,7 +1006,10 @@ public class MmsDatabase extends MessagingDatabase { contentValues.put(MESSAGE_BOX, type); contentValues.put(THREAD_ID, threadId); contentValues.put(READ, 1); - contentValues.put(DATE_RECEIVED, System.currentTimeMillis()); + // In open groups messages should be sorted by their server timestamp + long receivedTimestamp = serverTimestamp; + if (serverTimestamp == 0) { receivedTimestamp = System.currentTimeMillis(); } + contentValues.put(DATE_RECEIVED, receivedTimestamp); contentValues.put(SUBSCRIPTION_ID, message.getSubscriptionId()); contentValues.put(EXPIRES_IN, message.getExpiresIn()); contentValues.put(ADDRESS, message.getRecipient().getAddress().serialize()); diff --git a/src/org/thoughtcrime/securesms/database/model/MessageRecord.java b/src/org/thoughtcrime/securesms/database/model/MessageRecord.java index effea38c60..7e8fe34985 100644 --- a/src/org/thoughtcrime/securesms/database/model/MessageRecord.java +++ b/src/org/thoughtcrime/securesms/database/model/MessageRecord.java @@ -136,10 +136,10 @@ public abstract class MessageRecord extends DisplayRecord { } public long getTimestamp() { - if (isPush() && getDateSent() < getDateReceived()) { - return getDateSent(); - } if (getRecipient().getAddress().isOpenGroup()) { + return getDateReceived(); + } + if (isPush() && getDateSent() < getDateReceived()) { return getDateSent(); } return getDateReceived(); diff --git a/src/org/thoughtcrime/securesms/jobs/PushDecryptJob.java b/src/org/thoughtcrime/securesms/jobs/PushDecryptJob.java index 28ec14dfdf..4b97762c9b 100644 --- a/src/org/thoughtcrime/securesms/jobs/PushDecryptJob.java +++ b/src/org/thoughtcrime/securesms/jobs/PushDecryptJob.java @@ -1021,10 +1021,10 @@ public class PushDecryptJob extends BaseJob implements InjectableType { long messageId; if (isGroup) { - OutgoingMediaMessage outgoingMediaMessage = new OutgoingMediaMessage(recipient, new SlideDeck(), body, message.getTimestamp(), -1, expiresInMillis, ThreadDatabase.DistributionTypes.DEFAULT, null, Collections.emptyList(), Collections.emptyList()); + OutgoingMediaMessage outgoingMediaMessage = new OutgoingMediaMessage(recipient, new SlideDeck(), body, message.getMessage().getTimestamp(), -1, expiresInMillis, ThreadDatabase.DistributionTypes.DEFAULT, null, Collections.emptyList(), Collections.emptyList()); outgoingMediaMessage = new OutgoingSecureMediaMessage(outgoingMediaMessage); - messageId = DatabaseFactory.getMmsDatabase(context).insertMessageOutbox(outgoingMediaMessage, threadId, false, null); + messageId = DatabaseFactory.getMmsDatabase(context).insertMessageOutbox(outgoingMediaMessage, threadId, false, null,message.getTimestamp()); if (message.messageServerID >= 0) { DatabaseFactory.getLokiMessageDatabase(context).setServerID(messageId, message.messageServerID); } database = DatabaseFactory.getMmsDatabase(context); diff --git a/src/org/thoughtcrime/securesms/loki/api/PublicChatPoller.kt b/src/org/thoughtcrime/securesms/loki/api/PublicChatPoller.kt index 28aa5f93f3..6a5ee5dad0 100644 --- a/src/org/thoughtcrime/securesms/loki/api/PublicChatPoller.kt +++ b/src/org/thoughtcrime/securesms/loki/api/PublicChatPoller.kt @@ -192,7 +192,7 @@ class PublicChatPoller(private val context: Context, private val group: PublicCh val userHexEncodedPublicKey = TextSecurePreferences.getLocalNumber(context) val dataMessage = getDataMessage(message) SessionMetaProtocol.dropFromTimestampCacheIfNeeded(dataMessage.timestamp) - val transcript = SentTranscriptMessage(userHexEncodedPublicKey, dataMessage.timestamp, dataMessage, dataMessage.expiresInSeconds.toLong(), Collections.singletonMap(userHexEncodedPublicKey, false)) + val transcript = SentTranscriptMessage(userHexEncodedPublicKey, message.serverTimestamp, dataMessage, dataMessage.expiresInSeconds.toLong(), Collections.singletonMap(userHexEncodedPublicKey, false)) transcript.messageServerID = messageServerID if (dataMessage.quote.isPresent || (dataMessage.attachments.isPresent && dataMessage.attachments.get().size > 0) || dataMessage.previews.isPresent) { PushDecryptJob(context).handleSynchronizeSentMediaMessage(transcript) From 968177c012c707456f1193206d7563800bac8bbd Mon Sep 17 00:00:00 2001 From: nielsandriesse Date: Tue, 1 Sep 2020 10:38:16 +1000 Subject: [PATCH 08/20] Add logs --- .../securesms/loki/protocol/ClosedGroupsProtocol.kt | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/org/thoughtcrime/securesms/loki/protocol/ClosedGroupsProtocol.kt b/src/org/thoughtcrime/securesms/loki/protocol/ClosedGroupsProtocol.kt index 8e92e7f312..050b62774a 100644 --- a/src/org/thoughtcrime/securesms/loki/protocol/ClosedGroupsProtocol.kt +++ b/src/org/thoughtcrime/securesms/loki/protocol/ClosedGroupsProtocol.kt @@ -179,6 +179,7 @@ object ClosedGroupsProtocol { @JvmStatic public fun requestSenderKey(context: Context, groupPublicKey: String, senderPublicKey: String) { + Log.d("Loki", "Requesting sender key for group public key: $groupPublicKey, sender public key: $senderPublicKey.") // Establish session if needed ApplicationContext.getInstance(context).sendSessionRequestIfNeeded(senderPublicKey) // Send the request @@ -328,6 +329,7 @@ object ClosedGroupsProtocol { return } // Respond to the request + Log.d("Loki", "Responding to sender key request from: $senderPublicKey.") ApplicationContext.getInstance(context).sendSessionRequestIfNeeded(senderPublicKey) val userRatchet = SharedSenderKeysImplementation.shared.generateRatchet(groupPublicKey, userPublicKey) val userSenderKey = ClosedGroupSenderKey(Hex.fromStringCondensed(userRatchet.chainKey), userRatchet.keyIndex, Hex.fromStringCondensed(userPublicKey)) @@ -363,6 +365,7 @@ object ClosedGroupsProtocol { return } // Store the sender key + Log.d("Loki", "Received a sender key from: $senderPublicKey.") val ratchet = ClosedGroupRatchet(senderKey.chainKey.toHexString(), senderKey.keyIndex, listOf()) sskDatabase.setClosedGroupRatchet(groupPublicKey, senderPublicKey, ratchet) } From 59c3f03f9b57283aa98cd64c1ccc51154d408fe3 Mon Sep 17 00:00:00 2001 From: nielsandriesse Date: Tue, 1 Sep 2020 11:43:12 +1000 Subject: [PATCH 09/20] Update version number --- build.gradle | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/build.gradle b/build.gradle index 137160529e..d067aea8fd 100644 --- a/build.gradle +++ b/build.gradle @@ -185,8 +185,8 @@ dependencies { implementation "com.opencsv:opencsv:4.6" } -def canonicalVersionCode = 75 -def canonicalVersionName = "1.4.6" +def canonicalVersionCode = 76 +def canonicalVersionName = "1.4.7" def postFixSize = 10 def abiPostFix = ['armeabi-v7a' : 1, From 12f35fea04c33af73be54258ee0fe76915a71f11 Mon Sep 17 00:00:00 2001 From: nielsandriesse Date: Tue, 1 Sep 2020 13:20:03 +1000 Subject: [PATCH 10/20] Update build number --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index d067aea8fd..b6cf63b384 100644 --- a/build.gradle +++ b/build.gradle @@ -185,7 +185,7 @@ dependencies { implementation "com.opencsv:opencsv:4.6" } -def canonicalVersionCode = 76 +def canonicalVersionCode = 77 def canonicalVersionName = "1.4.7" def postFixSize = 10 From ef509d3c58665d581a069b5a52deaa94a9bdcf79 Mon Sep 17 00:00:00 2001 From: nielsandriesse Date: Tue, 1 Sep 2020 16:27:10 +1000 Subject: [PATCH 11/20] Show build number in app settings --- .../thoughtcrime/securesms/loki/activities/SettingsActivity.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/org/thoughtcrime/securesms/loki/activities/SettingsActivity.kt b/src/org/thoughtcrime/securesms/loki/activities/SettingsActivity.kt index c79812b15a..e7016b4636 100644 --- a/src/org/thoughtcrime/securesms/loki/activities/SettingsActivity.kt +++ b/src/org/thoughtcrime/securesms/loki/activities/SettingsActivity.kt @@ -95,7 +95,7 @@ class SettingsActivity : PassphraseRequiredActionBarActivity() { // linkedDevicesButton.setOnClickListener { showLinkedDevices() } seedButton.setOnClickListener { showSeed() } clearAllDataButton.setOnClickListener { clearAllData() } - versionTextView.text = String.format(getString(R.string.version_s), BuildConfig.VERSION_NAME) + versionTextView.text = String.format(getString(R.string.version_s), "${BuildConfig.VERSION_NAME} (${BuildConfig.VERSION_CODE})") } public override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { From ae956b732a51f22a6a493489b72fd4a7f57a820f Mon Sep 17 00:00:00 2001 From: nielsandriesse Date: Tue, 1 Sep 2020 16:30:35 +1000 Subject: [PATCH 12/20] Update build number --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index b6cf63b384..4c08277274 100644 --- a/build.gradle +++ b/build.gradle @@ -185,7 +185,7 @@ dependencies { implementation "com.opencsv:opencsv:4.6" } -def canonicalVersionCode = 77 +def canonicalVersionCode = 78 def canonicalVersionName = "1.4.7" def postFixSize = 10 From d26712bdb40713fe2edfbc9314a8affe647aca10 Mon Sep 17 00:00:00 2001 From: nielsandriesse Date: Tue, 1 Sep 2020 17:02:08 +1000 Subject: [PATCH 13/20] Slightly reduce background poll interval --- src/org/thoughtcrime/securesms/loki/api/BackgroundPollWorker.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/org/thoughtcrime/securesms/loki/api/BackgroundPollWorker.kt b/src/org/thoughtcrime/securesms/loki/api/BackgroundPollWorker.kt index 9cd363823e..e4eb12df94 100644 --- a/src/org/thoughtcrime/securesms/loki/api/BackgroundPollWorker.kt +++ b/src/org/thoughtcrime/securesms/loki/api/BackgroundPollWorker.kt @@ -15,7 +15,7 @@ import java.util.concurrent.TimeUnit class BackgroundPollWorker : PersistentAlarmManagerListener() { companion object { - private val pollInterval = TimeUnit.MINUTES.toMillis(20) + private val pollInterval = TimeUnit.MINUTES.toMillis(15) @JvmStatic fun schedule(context: Context) { From fcc63042470ac23cda8c8d3f451b2c15d0d81f1a Mon Sep 17 00:00:00 2001 From: nielsandriesse Date: Tue, 1 Sep 2020 18:30:54 +1000 Subject: [PATCH 14/20] Fix path rebuilding --- src/org/thoughtcrime/securesms/loki/database/LokiAPIDatabase.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/org/thoughtcrime/securesms/loki/database/LokiAPIDatabase.kt b/src/org/thoughtcrime/securesms/loki/database/LokiAPIDatabase.kt index d41ef58791..18fae93b87 100644 --- a/src/org/thoughtcrime/securesms/loki/database/LokiAPIDatabase.kt +++ b/src/org/thoughtcrime/securesms/loki/database/LokiAPIDatabase.kt @@ -137,7 +137,7 @@ class LokiAPIDatabase(context: Context, helper: SQLCipherOpenHelper) : Database( return listOf( listOf( path0Snode0, path0Snode1, path0Snode2 ), listOf( path1Snode0, path1Snode1, path1Snode2 ) ) } - fun clearOnionRequestPaths() { + override fun clearOnionRequestPaths() { val database = databaseHelper.writableDatabase fun delete(indexPath: String) { database.delete(onionRequestPathTable, "${Companion.indexPath} = ?", wrap(indexPath)) From 6b75d3338f7a553463ff5ed77b8f8b31c2fa40e9 Mon Sep 17 00:00:00 2001 From: nielsandriesse Date: Tue, 1 Sep 2020 18:31:34 +1000 Subject: [PATCH 15/20] Update build number --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index 4c08277274..df676f76fc 100644 --- a/build.gradle +++ b/build.gradle @@ -185,7 +185,7 @@ dependencies { implementation "com.opencsv:opencsv:4.6" } -def canonicalVersionCode = 78 +def canonicalVersionCode = 79 def canonicalVersionName = "1.4.7" def postFixSize = 10 From a12d9b4d98e3ab4c6bc5e0dc72c05dbd6ea95428 Mon Sep 17 00:00:00 2001 From: nielsandriesse Date: Wed, 2 Sep 2020 08:54:14 +1000 Subject: [PATCH 16/20] Update build number --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index df676f76fc..1853404371 100644 --- a/build.gradle +++ b/build.gradle @@ -185,7 +185,7 @@ dependencies { implementation "com.opencsv:opencsv:4.6" } -def canonicalVersionCode = 79 +def canonicalVersionCode = 80 def canonicalVersionName = "1.4.7" def postFixSize = 10 From 146df2c5efd08ec0acc334078cc4f5d036d94cd3 Mon Sep 17 00:00:00 2001 From: nielsandriesse Date: Wed, 2 Sep 2020 10:22:19 +1000 Subject: [PATCH 17/20] Fix incorrect database query --- .../securesms/loki/database/SharedSenderKeysDatabase.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/org/thoughtcrime/securesms/loki/database/SharedSenderKeysDatabase.kt b/src/org/thoughtcrime/securesms/loki/database/SharedSenderKeysDatabase.kt index 23afeeaca0..15b415e8e0 100644 --- a/src/org/thoughtcrime/securesms/loki/database/SharedSenderKeysDatabase.kt +++ b/src/org/thoughtcrime/securesms/loki/database/SharedSenderKeysDatabase.kt @@ -63,8 +63,8 @@ class SharedSenderKeysDatabase(context: Context, helper: SQLCipherOpenHelper) : override fun getAllClosedGroupSenderKeys(groupPublicKey: String): Set { val database = databaseHelper.readableDatabase - val query = "${Companion.closedGroupPublicKey} = ? AND ${Companion.senderPublicKey} = ?" - return database.getAll(closedGroupRatchetTable, query, arrayOf( groupPublicKey, senderPublicKey )) { cursor -> + val query = "${Companion.closedGroupPublicKey} = ?" + return database.getAll(closedGroupRatchetTable, query, arrayOf( groupPublicKey )) { cursor -> val chainKey = cursor.getString(Companion.chainKey) val keyIndex = cursor.getInt(Companion.keyIndex) val senderPublicKey = cursor.getString(Companion.senderPublicKey) From 55e273276bc0f3cdb51f926299b76d89155b9559 Mon Sep 17 00:00:00 2001 From: nielsandriesse Date: Wed, 2 Sep 2020 10:31:07 +1000 Subject: [PATCH 18/20] Update build number --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index 1853404371..c5a1e0d937 100644 --- a/build.gradle +++ b/build.gradle @@ -185,7 +185,7 @@ dependencies { implementation "com.opencsv:opencsv:4.6" } -def canonicalVersionCode = 80 +def canonicalVersionCode = 81 def canonicalVersionName = "1.4.7" def postFixSize = 10 From 2732b9594061d6c71f8b4dc68c7c614d6bbc97e6 Mon Sep 17 00:00:00 2001 From: nielsandriesse Date: Wed, 2 Sep 2020 11:47:54 +1000 Subject: [PATCH 19/20] Show a loader while the group is being created --- res/layout/activity_create_closed_group.xml | 21 ++++- .../activities/CreateClosedGroupActivity.kt | 15 +++- .../loki/protocol/ClosedGroupsProtocol.kt | 79 ++++++++++--------- 3 files changed, 75 insertions(+), 40 deletions(-) diff --git a/res/layout/activity_create_closed_group.xml b/res/layout/activity_create_closed_group.xml index 52a3300865..59d9fe00e0 100644 --- a/res/layout/activity_create_closed_group.xml +++ b/res/layout/activity_create_closed_group.xml @@ -3,7 +3,8 @@ xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" - android:background="@drawable/default_session_background" > + xmlns:app="http://schemas.android.com/apk/res-auto" + android:background="@drawable/default_session_background"> + + + + + + \ No newline at end of file diff --git a/src/org/thoughtcrime/securesms/loki/activities/CreateClosedGroupActivity.kt b/src/org/thoughtcrime/securesms/loki/activities/CreateClosedGroupActivity.kt index 16b2304238..30d59281d6 100644 --- a/src/org/thoughtcrime/securesms/loki/activities/CreateClosedGroupActivity.kt +++ b/src/org/thoughtcrime/securesms/loki/activities/CreateClosedGroupActivity.kt @@ -15,6 +15,7 @@ import android.widget.Toast import kotlinx.android.synthetic.main.activity_create_closed_group.* import kotlinx.android.synthetic.main.activity_linked_devices.recyclerView import network.loki.messenger.R +import nl.komponents.kovenant.ui.successUi import org.thoughtcrime.securesms.PassphraseRequiredActionBarActivity import org.thoughtcrime.securesms.conversation.ConversationActivity import org.thoughtcrime.securesms.database.Address @@ -22,6 +23,8 @@ import org.thoughtcrime.securesms.database.DatabaseFactory import org.thoughtcrime.securesms.database.ThreadDatabase import org.thoughtcrime.securesms.groups.GroupManager import org.thoughtcrime.securesms.loki.protocol.ClosedGroupsProtocol +import org.thoughtcrime.securesms.loki.utilities.fadeIn +import org.thoughtcrime.securesms.loki.utilities.fadeOut import org.thoughtcrime.securesms.mms.GlideApp import org.thoughtcrime.securesms.recipients.Recipient import org.thoughtcrime.securesms.util.TextSecurePreferences @@ -122,9 +125,15 @@ class CreateClosedGroupActivity : PassphraseRequiredActionBarActivity(), LoaderM return Toast.makeText(this, R.string.activity_create_closed_group_too_many_group_members_error, Toast.LENGTH_LONG).show() } val userPublicKey = TextSecurePreferences.getLocalNumber(this) - val groupID = ClosedGroupsProtocol.createClosedGroup(this, name.toString(), selectedMembers + setOf( userPublicKey )) - val threadID = DatabaseFactory.getThreadDatabase(this).getThreadIdFor(Recipient.from(this, Address.fromSerialized(groupID), false)) - openConversationActivity(this, threadID, Recipient.from(this, Address.fromSerialized(groupID), false)) + loader.fadeIn() + ClosedGroupsProtocol.createClosedGroup(this, name.toString(), selectedMembers + setOf( userPublicKey )).successUi { groupID -> + loader.fadeOut() + val threadID = DatabaseFactory.getThreadDatabase(this).getThreadIdFor(Recipient.from(this, Address.fromSerialized(groupID), false)) + if (!isFinishing) { + openConversationActivity(this, threadID, Recipient.from(this, Address.fromSerialized(groupID), false)) + finish() + } + } } private fun createLegacyClosedGroup() { diff --git a/src/org/thoughtcrime/securesms/loki/protocol/ClosedGroupsProtocol.kt b/src/org/thoughtcrime/securesms/loki/protocol/ClosedGroupsProtocol.kt index 050b62774a..363a1a7fdf 100644 --- a/src/org/thoughtcrime/securesms/loki/protocol/ClosedGroupsProtocol.kt +++ b/src/org/thoughtcrime/securesms/loki/protocol/ClosedGroupsProtocol.kt @@ -3,6 +3,8 @@ package org.thoughtcrime.securesms.loki.protocol import android.content.Context import android.util.Log import com.google.protobuf.ByteString +import nl.komponents.kovenant.Promise +import nl.komponents.kovenant.deferred import org.thoughtcrime.securesms.ApplicationContext import org.thoughtcrime.securesms.database.Address import org.thoughtcrime.securesms.database.DatabaseFactory @@ -34,43 +36,48 @@ object ClosedGroupsProtocol { val isSharedSenderKeysEnabled = false val groupSizeLimit = 10 - public fun createClosedGroup(context: Context, name: String, members: Collection): String { - // Prepare - val userPublicKey = TextSecurePreferences.getLocalNumber(context) - // Generate a key pair for the group - val groupKeyPair = Curve.generateKeyPair() - val groupPublicKey = groupKeyPair.hexEncodedPublicKey // Includes the "05" prefix - val membersAsData = members.map { Hex.fromStringCondensed(it) } - // Create ratchets for all members - val senderKeys: List = members.map { publicKey -> - val ratchet = SharedSenderKeysImplementation.shared.generateRatchet(groupPublicKey, publicKey) - ClosedGroupSenderKey(Hex.fromStringCondensed(ratchet.chainKey), ratchet.keyIndex, Hex.fromStringCondensed(publicKey)) - } - // Create the group - val groupID = doubleEncodeGroupID(groupPublicKey) - val admins = setOf( userPublicKey ) - DatabaseFactory.getGroupDatabase(context).create(groupID, name, LinkedList
(members.map { Address.fromSerialized(it) }), - null, null, LinkedList
(admins.map { Address.fromSerialized(it) })) - DatabaseFactory.getRecipientDatabase(context).setProfileSharing(Recipient.from(context, Address.fromSerialized(groupID), false), true) - // Establish sessions if needed - establishSessionsWithMembersIfNeeded(context, members) - // Send a closed group update message to all members using established channels - val adminsAsData = admins.map { Hex.fromStringCondensed(it) } - val closedGroupUpdateKind = ClosedGroupUpdateMessageSendJob.Kind.New(Hex.fromStringCondensed(groupPublicKey), name, groupKeyPair.privateKey.serialize(), - senderKeys, membersAsData, adminsAsData) - for (member in members) { - if (member == userPublicKey) { continue } - val job = ClosedGroupUpdateMessageSendJob(member, closedGroupUpdateKind) - ApplicationContext.getInstance(context).jobManager.add(job) - } - // TODO: Wait for the messages to finish sending - // Add the group to the user's set of public keys to poll for - DatabaseFactory.getSSKDatabase(context).setClosedGroupPrivateKey(groupPublicKey, groupKeyPair.hexEncodedPrivateKey) - // Notify the user - val threadID = DatabaseFactory.getThreadDatabase(context).getThreadIdFor(Recipient.from(context, Address.fromSerialized(groupID), false)) - insertOutgoingInfoMessage(context, groupID, GroupContext.Type.UPDATE, name, members, admins, threadID) + public fun createClosedGroup(context: Context, name: String, members: Collection): Promise { + val deferred = deferred() + Thread { + // Prepare + val userPublicKey = TextSecurePreferences.getLocalNumber(context) + // Generate a key pair for the group + val groupKeyPair = Curve.generateKeyPair() + val groupPublicKey = groupKeyPair.hexEncodedPublicKey // Includes the "05" prefix + val membersAsData = members.map { Hex.fromStringCondensed(it) } + // Create ratchets for all members + val senderKeys: List = members.map { publicKey -> + val ratchet = SharedSenderKeysImplementation.shared.generateRatchet(groupPublicKey, publicKey) + ClosedGroupSenderKey(Hex.fromStringCondensed(ratchet.chainKey), ratchet.keyIndex, Hex.fromStringCondensed(publicKey)) + } + // Create the group + val groupID = doubleEncodeGroupID(groupPublicKey) + val admins = setOf( userPublicKey ) + DatabaseFactory.getGroupDatabase(context).create(groupID, name, LinkedList
(members.map { Address.fromSerialized(it) }), + null, null, LinkedList
(admins.map { Address.fromSerialized(it) })) + DatabaseFactory.getRecipientDatabase(context).setProfileSharing(Recipient.from(context, Address.fromSerialized(groupID), false), true) + // Establish sessions if needed + establishSessionsWithMembersIfNeeded(context, members) + // Send a closed group update message to all members using established channels + val adminsAsData = admins.map { Hex.fromStringCondensed(it) } + val closedGroupUpdateKind = ClosedGroupUpdateMessageSendJob.Kind.New(Hex.fromStringCondensed(groupPublicKey), name, groupKeyPair.privateKey.serialize(), + senderKeys, membersAsData, adminsAsData) + for (member in members) { + if (member == userPublicKey) { continue } + val job = ClosedGroupUpdateMessageSendJob(member, closedGroupUpdateKind) + job.setContext(context) + job.onRun() // Run the job immediately + } + // Add the group to the user's set of public keys to poll for + DatabaseFactory.getSSKDatabase(context).setClosedGroupPrivateKey(groupPublicKey, groupKeyPair.hexEncodedPrivateKey) + // Notify the user + val threadID = DatabaseFactory.getThreadDatabase(context).getThreadIdFor(Recipient.from(context, Address.fromSerialized(groupID), false)) + insertOutgoingInfoMessage(context, groupID, GroupContext.Type.UPDATE, name, members, admins, threadID) + // Fulfill the promise + deferred.resolve(groupID) + }.start() // Return - return groupID + return deferred.promise } @JvmStatic From 154181f518ca63ea5c2079eddcdb2bdd0273348c Mon Sep 17 00:00:00 2001 From: nielsandriesse Date: Wed, 2 Sep 2020 12:03:15 +1000 Subject: [PATCH 20/20] Fix group leaving --- .../loki/protocol/ClosedGroupsProtocol.kt | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/src/org/thoughtcrime/securesms/loki/protocol/ClosedGroupsProtocol.kt b/src/org/thoughtcrime/securesms/loki/protocol/ClosedGroupsProtocol.kt index 363a1a7fdf..0841aaf2a6 100644 --- a/src/org/thoughtcrime/securesms/loki/protocol/ClosedGroupsProtocol.kt +++ b/src/org/thoughtcrime/securesms/loki/protocol/ClosedGroupsProtocol.kt @@ -116,9 +116,9 @@ object ClosedGroupsProtocol { return } val wasAnyUserRemoved = members.toSet().intersect(oldMembers) != oldMembers.toSet() + val removedMembers = oldMembers.minus(members) + val isUserLeaving = removedMembers.contains(userPublicKey) if (wasAnyUserRemoved) { - val removedMembers = oldMembers.minus(members) - val isUserLeaving = removedMembers.contains(userPublicKey) if (isUserLeaving && removedMembers.count() != 1) { Log.d("Loki", "Can't remove self and others simultaneously.") return @@ -136,6 +136,7 @@ object ClosedGroupsProtocol { if (isUserLeaving) { sskDatabase.removeClosedGroupPrivateKey(groupPublicKey) groupDB.setActive(groupID, false) + groupDB.remove(groupID, Address.fromSerialized(userPublicKey)) } else { // Establish sessions if needed establishSessionsWithMembersIfNeeded(context, members) @@ -178,7 +179,10 @@ object ClosedGroupsProtocol { } // Update the group groupDB.updateTitle(groupID, name) - groupDB.updateMembers(groupID, members.map { Address.fromSerialized(it) }) + if (!isUserLeaving) { + // The call below sets isActive to true, so if the user is leaving we have to use groupDB.remove(...) instead + groupDB.updateMembers(groupID, members.map { Address.fromSerialized(it) }) + } // Notify the user val threadID = DatabaseFactory.getThreadDatabase(context).getThreadIdFor(Recipient.from(context, Address.fromSerialized(groupID), false)) insertOutgoingInfoMessage(context, groupID, GroupContext.Type.UPDATE, name, members, admins, threadID) @@ -298,6 +302,7 @@ object ClosedGroupsProtocol { if (wasCurrentUserRemoved) { sskDatabase.removeClosedGroupPrivateKey(groupPublicKey) groupDB.setActive(groupID, false) + groupDB.remove(groupID, Address.fromSerialized(userPublicKey)) } else { establishSessionsWithMembersIfNeeded(context, members) val userRatchet = SharedSenderKeysImplementation.shared.generateRatchet(groupPublicKey, userPublicKey) @@ -312,7 +317,10 @@ object ClosedGroupsProtocol { } // Update the group groupDB.updateTitle(groupID, name) - groupDB.updateMembers(groupID, members.map { Address.fromSerialized(it) }) + if (!wasCurrentUserRemoved) { + // The call below sets isActive to true, so if the user is leaving we have to use groupDB.remove(...) instead + groupDB.updateMembers(groupID, members.map { Address.fromSerialized(it) }) + } // Notify the user val type0 = if (wasSenderRemoved) GroupContext.Type.QUIT else GroupContext.Type.UPDATE val type1 = if (wasSenderRemoved) SignalServiceGroup.Type.QUIT else SignalServiceGroup.Type.UPDATE