From 4bbc6fe044418e7be7854d2e56a3eeed71393eaa Mon Sep 17 00:00:00 2001 From: Ryan ZHAO Date: Wed, 30 Sep 2020 09:39:59 +1000 Subject: [PATCH 01/15] modify leaving message --- .../securesms/loki/protocol/ClosedGroupsProtocol.kt | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/org/thoughtcrime/securesms/loki/protocol/ClosedGroupsProtocol.kt b/src/org/thoughtcrime/securesms/loki/protocol/ClosedGroupsProtocol.kt index a2cb862fae..7dfce87259 100644 --- a/src/org/thoughtcrime/securesms/loki/protocol/ClosedGroupsProtocol.kt +++ b/src/org/thoughtcrime/securesms/loki/protocol/ClosedGroupsProtocol.kt @@ -192,8 +192,12 @@ object ClosedGroupsProtocol { groupDB.updateMembers(groupID, members.map { Address.fromSerialized(it) }) } // Notify the user + var infoType = GroupContext.Type.UPDATE + if (isUserLeaving) { + infoType = GroupContext.Type.QUIT + } val threadID = DatabaseFactory.getThreadDatabase(context).getThreadIdFor(Recipient.from(context, Address.fromSerialized(groupID), false)) - insertOutgoingInfoMessage(context, groupID, GroupContext.Type.UPDATE, name, members, admins, threadID) + insertOutgoingInfoMessage(context, groupID, infoType, name, members, admins, threadID) } @JvmStatic From 9b40a2c0b8fc96be49d01484fcaf3531ef4042e9 Mon Sep 17 00:00:00 2001 From: nielsandriesse Date: Fri, 2 Oct 2020 15:24:13 +1000 Subject: [PATCH 02/15] Fix various minor UI issues --- res/layout/activity_create_closed_group.xml | 48 +++++++++++-------- res/layout/activity_select_contacts.xml | 1 + .../activities/CreatePrivateChatActivity.kt | 25 +++++++--- .../activities/EditClosedGroupActivity.kt | 1 + .../loki/activities/SelectContactsActivity.kt | 8 ++++ 5 files changed, 55 insertions(+), 28 deletions(-) diff --git a/res/layout/activity_create_closed_group.xml b/res/layout/activity_create_closed_group.xml index a965ae8551..78095701e9 100644 --- a/res/layout/activity_create_closed_group.xml +++ b/res/layout/activity_create_closed_group.xml @@ -5,34 +5,40 @@ android:layout_width="match_parent" android:layout_height="match_parent"> - + android:layout_height="wrap_content" > - + android:orientation="vertical"> - + - + - + + + + + adapter.enterPublicKeyFragment.createPrivateChatIfPossible() + } + return super.onOptionsItemSelected(item) + } + override fun handleQRCodeScanned(hexEncodedPublicKey: String) { createPrivateChatIfPossible(hexEncodedPublicKey) } @@ -71,6 +81,7 @@ class CreatePrivateChatActivity : PassphraseRequiredActionBarActivity(), ScanQRC // region Adapter private class CreatePrivateChatActivityAdapter(val activity: CreatePrivateChatActivity) : FragmentPagerAdapter(activity.supportFragmentManager) { + val enterPublicKeyFragment = EnterPublicKeyFragment() override fun getCount(): Int { return 2 @@ -78,7 +89,7 @@ private class CreatePrivateChatActivityAdapter(val activity: CreatePrivateChatAc override fun getItem(index: Int): Fragment { return when (index) { - 0 -> EnterPublicKeyFragment() + 0 -> enterPublicKeyFragment 1 -> { val result = ScanQRCodeWrapperFragment() result.delegate = activity @@ -104,8 +115,8 @@ class EnterPublicKeyFragment : Fragment() { private val hexEncodedPublicKey: String get() { - val masterHexEncodedPublicKey = TextSecurePreferences.getMasterHexEncodedPublicKey(context!!) - val userHexEncodedPublicKey = TextSecurePreferences.getLocalNumber(context!!) + val masterHexEncodedPublicKey = TextSecurePreferences.getMasterHexEncodedPublicKey(requireContext()) + val userHexEncodedPublicKey = TextSecurePreferences.getLocalNumber(requireContext()) return masterHexEncodedPublicKey ?: userHexEncodedPublicKey } @@ -148,7 +159,7 @@ class EnterPublicKeyFragment : Fragment() { startActivity(intent) } - private fun createPrivateChatIfPossible() { + fun createPrivateChatIfPossible() { val hexEncodedPublicKey = publicKeyEditText.text?.trim().toString() ?: "" (requireActivity() as CreatePrivateChatActivity).createPrivateChatIfPossible(hexEncodedPublicKey) } diff --git a/src/org/thoughtcrime/securesms/loki/activities/EditClosedGroupActivity.kt b/src/org/thoughtcrime/securesms/loki/activities/EditClosedGroupActivity.kt index f21339ab42..1ee9270e6b 100644 --- a/src/org/thoughtcrime/securesms/loki/activities/EditClosedGroupActivity.kt +++ b/src/org/thoughtcrime/securesms/loki/activities/EditClosedGroupActivity.kt @@ -184,6 +184,7 @@ class EditClosedGroupActivity : PassphraseRequiredActionBarActivity() { private fun onAddMembersClick() { val intent = Intent(this@EditClosedGroupActivity, SelectContactsActivity::class.java) intent.putExtra(SelectContactsActivity.usersToExcludeKey, members.toTypedArray()) + intent.putExtra(SelectContactsActivity.emptyStateTextKey, "No contacts to add") startActivityForResult(intent, addUsersRequestCode) } diff --git a/src/org/thoughtcrime/securesms/loki/activities/SelectContactsActivity.kt b/src/org/thoughtcrime/securesms/loki/activities/SelectContactsActivity.kt index 43a0bd72c6..76bdea6e25 100644 --- a/src/org/thoughtcrime/securesms/loki/activities/SelectContactsActivity.kt +++ b/src/org/thoughtcrime/securesms/loki/activities/SelectContactsActivity.kt @@ -10,7 +10,10 @@ import android.view.Menu import android.view.MenuItem import android.view.View import kotlinx.android.synthetic.main.activity_create_closed_group.* +import kotlinx.android.synthetic.main.activity_create_closed_group.emptyStateContainer +import kotlinx.android.synthetic.main.activity_create_closed_group.mainContentContainer import kotlinx.android.synthetic.main.activity_linked_devices.recyclerView +import kotlinx.android.synthetic.main.activity_select_contacts.* import network.loki.messenger.R import org.thoughtcrime.securesms.PassphraseRequiredActionBarActivity import org.thoughtcrime.securesms.mms.GlideApp @@ -26,6 +29,7 @@ class SelectContactsActivity : PassphraseRequiredActionBarActivity(), LoaderMana companion object { val usersToExcludeKey = "usersToExcludeKey" + val emptyStateTextKey = "emptyStateTextKey" val selectedContactsKey = "selectedContactsKey" } @@ -37,6 +41,10 @@ class SelectContactsActivity : PassphraseRequiredActionBarActivity(), LoaderMana supportActionBar!!.title = resources.getString(R.string.activity_select_contacts_title) usersToExclude = intent.getStringArrayExtra(Companion.usersToExcludeKey)?.toSet() ?: setOf() + val emptyStateText = intent.getStringExtra(Companion.emptyStateTextKey) + if (emptyStateText != null) { + emptyStateMessageTextView.text = emptyStateText + } recyclerView.adapter = selectContactsAdapter recyclerView.layoutManager = LinearLayoutManager(this) From a51850a1475ecf6b9047f268c51b4b2d5c9c8614 Mon Sep 17 00:00:00 2001 From: nielsandriesse Date: Fri, 2 Oct 2020 15:40:55 +1000 Subject: [PATCH 03/15] Potentially fix crashes --- .../securesms/conversation/ConversationActivity.java | 8 ++++++-- src/org/thoughtcrime/securesms/util/SaveAttachmentTask.kt | 7 ++++++- 2 files changed, 12 insertions(+), 3 deletions(-) diff --git a/src/org/thoughtcrime/securesms/conversation/ConversationActivity.java b/src/org/thoughtcrime/securesms/conversation/ConversationActivity.java index e053d0d7d3..886d603999 100644 --- a/src/org/thoughtcrime/securesms/conversation/ConversationActivity.java +++ b/src/org/thoughtcrime/securesms/conversation/ConversationActivity.java @@ -3102,8 +3102,12 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity } private void updateProfilePicture() { - profilePictureView.glide = GlideApp.with(this); - profilePictureView.update(recipient, threadId); + try { + profilePictureView.glide = GlideApp.with(this); + profilePictureView.update(recipient, threadId); + } catch (Exception exception) { + // Do nothing + } } private void updateSubtitleTextView() { diff --git a/src/org/thoughtcrime/securesms/util/SaveAttachmentTask.kt b/src/org/thoughtcrime/securesms/util/SaveAttachmentTask.kt index 1c57af91bc..baaedd11a9 100644 --- a/src/org/thoughtcrime/securesms/util/SaveAttachmentTask.kt +++ b/src/org/thoughtcrime/securesms/util/SaveAttachmentTask.kt @@ -140,7 +140,12 @@ class SaveAttachmentTask : ProgressDialogAsyncTask Date: Mon, 5 Oct 2020 09:13:31 +1100 Subject: [PATCH 04/15] Update build number --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index 27654e1f04..4fa73dc468 100644 --- a/build.gradle +++ b/build.gradle @@ -181,7 +181,7 @@ dependencies { testImplementation 'org.robolectric:shadows-multidex:4.2' } -def canonicalVersionCode = 108 +def canonicalVersionCode = 109 def canonicalVersionName = "1.6.0" def postFixSize = 10 From cd177ee61e94603308cb33365d26f5f2cbdc60ac Mon Sep 17 00:00:00 2001 From: nielsandriesse Date: Mon, 5 Oct 2020 11:23:59 +1100 Subject: [PATCH 05/15] Simplify expression --- .../securesms/loki/protocol/ClosedGroupsProtocol.kt | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/src/org/thoughtcrime/securesms/loki/protocol/ClosedGroupsProtocol.kt b/src/org/thoughtcrime/securesms/loki/protocol/ClosedGroupsProtocol.kt index be32ec81bc..8e5e00e695 100644 --- a/src/org/thoughtcrime/securesms/loki/protocol/ClosedGroupsProtocol.kt +++ b/src/org/thoughtcrime/securesms/loki/protocol/ClosedGroupsProtocol.kt @@ -208,10 +208,7 @@ object ClosedGroupsProtocol { groupDB.updateMembers(groupID, members.map { Address.fromSerialized(it) }) } // Notify the user - var infoType = GroupContext.Type.UPDATE - if (isUserLeaving) { - infoType = GroupContext.Type.QUIT - } + val infoType = if (isUserLeaving) GroupContext.Type.QUIT else GroupContext.Type.UPDATE val threadID = DatabaseFactory.getThreadDatabase(context).getThreadIdFor(Recipient.from(context, Address.fromSerialized(groupID), false)) insertOutgoingInfoMessage(context, groupID, infoType, name, members, admins, threadID) } From a34b1f74b343cff4bb3870599eab94501c5da050 Mon Sep 17 00:00:00 2001 From: nielsandriesse Date: Tue, 6 Oct 2020 13:47:46 +1100 Subject: [PATCH 06/15] Fix SSK group leaving race condition --- .../loki/protocol/ClosedGroupsProtocol.kt | 20 +++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/src/org/thoughtcrime/securesms/loki/protocol/ClosedGroupsProtocol.kt b/src/org/thoughtcrime/securesms/loki/protocol/ClosedGroupsProtocol.kt index 8e5e00e695..9e4d8a4379 100644 --- a/src/org/thoughtcrime/securesms/loki/protocol/ClosedGroupsProtocol.kt +++ b/src/org/thoughtcrime/securesms/loki/protocol/ClosedGroupsProtocol.kt @@ -130,12 +130,18 @@ object ClosedGroupsProtocol { 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 + // Establish sessions if needed + establishSessionsWithMembersIfNeeded(context, members) + // Send the update to the existing members using established channels (don't include new ratchets as everyone should regenerate new ratchets individually) + for (member in oldMembers) { + @Suppress("NAME_SHADOWING") + val closedGroupUpdateKind = ClosedGroupUpdateMessageSendJob.Kind.Info(Hex.fromStringCondensed(groupPublicKey), + name, setOf(), membersAsData, adminsAsData) + @Suppress("NAME_SHADOWING") + val job = ClosedGroupUpdateMessageSendJob(member, 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 @@ -147,8 +153,6 @@ object ClosedGroupsProtocol { // Notify the PN server LokiPushNotificationManager.performOperation(context, ClosedGroupOperation.Unsubscribe, groupPublicKey, userPublicKey) } else { - // Establish sessions if needed - establishSessionsWithMembersIfNeeded(context, members) // Send closed group update messages to any new members using established channels for (member in newMembers) { @Suppress("NAME_SHADOWING") From 389b4b9768f9d279c81d3c936acf36fa73a65426 Mon Sep 17 00:00:00 2001 From: nielsandriesse Date: Tue, 6 Oct 2020 14:10:57 +1100 Subject: [PATCH 07/15] Show a loader while the group is updating --- res/layout/activity_edit_closed_group.xml | 22 +- .../activities/EditClosedGroupActivity.kt | 16 +- .../loki/protocol/ClosedGroupsProtocol.kt | 218 +++++++++--------- 3 files changed, 149 insertions(+), 107 deletions(-) diff --git a/res/layout/activity_edit_closed_group.xml b/res/layout/activity_edit_closed_group.xml index 16ecefc339..b5e09eb35d 100644 --- a/res/layout/activity_edit_closed_group.xml +++ b/res/layout/activity_edit_closed_group.xml @@ -1,9 +1,9 @@ - + + + + + + \ No newline at end of file diff --git a/src/org/thoughtcrime/securesms/loki/activities/EditClosedGroupActivity.kt b/src/org/thoughtcrime/securesms/loki/activities/EditClosedGroupActivity.kt index 1ee9270e6b..3d36afcd67 100644 --- a/src/org/thoughtcrime/securesms/loki/activities/EditClosedGroupActivity.kt +++ b/src/org/thoughtcrime/securesms/loki/activities/EditClosedGroupActivity.kt @@ -13,17 +13,23 @@ import android.view.inputmethod.EditorInfo import android.view.inputmethod.InputMethodManager import android.widget.Toast import androidx.appcompat.content.res.AppCompatResources +import kotlinx.android.synthetic.main.activity_create_closed_group.* import kotlinx.android.synthetic.main.activity_create_closed_group.emptyStateContainer import kotlinx.android.synthetic.main.activity_create_closed_group.mainContentContainer import kotlinx.android.synthetic.main.activity_edit_closed_group.* +import kotlinx.android.synthetic.main.activity_edit_closed_group.loader import kotlinx.android.synthetic.main.activity_linked_devices.recyclerView import network.loki.messenger.R +import nl.komponents.kovenant.ui.failUi +import nl.komponents.kovenant.ui.successUi import org.thoughtcrime.securesms.PassphraseRequiredActionBarActivity import org.thoughtcrime.securesms.database.Address import org.thoughtcrime.securesms.database.DatabaseFactory import org.thoughtcrime.securesms.groups.GroupManager import org.thoughtcrime.securesms.loki.dialogs.ClosedGroupEditingOptionsBottomSheet 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.GroupUtil @@ -238,10 +244,16 @@ class EditClosedGroupActivity : PassphraseRequiredActionBarActivity() { } if (isSSKBasedClosedGroup) { - ClosedGroupsProtocol.update(this, groupPublicKey!!, members.map { it.address.serialize() }, name) + loader.fadeIn() + ClosedGroupsProtocol.update(this, groupPublicKey!!, members.map { it.address.serialize() }, name).successUi { + loader.fadeOut() + finish() + }.failUi { exception -> + val message = if (exception is ClosedGroupsProtocol.Error) exception.description else "An error occurred" + Toast.makeText(this@EditClosedGroupActivity, message, Toast.LENGTH_LONG).show() + } } else { GroupManager.updateGroup(this, groupID, members, null, name, admins) } - finish() } } \ No newline at end of file diff --git a/src/org/thoughtcrime/securesms/loki/protocol/ClosedGroupsProtocol.kt b/src/org/thoughtcrime/securesms/loki/protocol/ClosedGroupsProtocol.kt index 9e4d8a4379..9050e39a80 100644 --- a/src/org/thoughtcrime/securesms/loki/protocol/ClosedGroupsProtocol.kt +++ b/src/org/thoughtcrime/securesms/loki/protocol/ClosedGroupsProtocol.kt @@ -25,6 +25,7 @@ import org.whispersystems.signalservice.api.messages.SignalServiceGroup import org.whispersystems.signalservice.api.messages.SignalServiceGroup.GroupType import org.whispersystems.signalservice.internal.push.SignalServiceProtos import org.whispersystems.signalservice.internal.push.SignalServiceProtos.GroupContext +import org.whispersystems.signalservice.loki.api.SnodeAPI import org.whispersystems.signalservice.loki.protocol.closedgroups.ClosedGroupRatchet import org.whispersystems.signalservice.loki.protocol.closedgroups.ClosedGroupSenderKey import org.whispersystems.signalservice.loki.protocol.closedgroups.SharedSenderKeysImplementation @@ -38,7 +39,13 @@ import kotlin.jvm.Throws object ClosedGroupsProtocol { val isSharedSenderKeysEnabled = true val groupSizeLimit = 20 - + + sealed class Error(val description: String) : Exception() { + object NoThread : Error("Couldn't find a thread associated with the given group public key") + object NoPrivateKey : Error("Couldn't find a private key associated with the given group public key.") + object InvalidUpdate : Error("Invalid group update.") + } + public fun createClosedGroup(context: Context, name: String, members: Collection): Promise { val deferred = deferred() Thread { @@ -98,123 +105,128 @@ object ClosedGroupsProtocol { val name = group.title val oldMembers = group.members.map { it.serialize() }.toSet() val newMembers = oldMembers.minus(userPublicKey) - update(context, groupPublicKey, newMembers, name) + return update(context, groupPublicKey, newMembers, name).get() } - 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 update nonexistent closed group.") - return - } - val oldMembers = group.members.map { it.serialize() }.toSet() - val newMembers = members.minus(oldMembers) - 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) - if (groupPrivateKey == null) { - Log.d("Loki", "Couldn't get private key for closed group.") - return - } - val wasAnyUserRemoved = members.toSet().intersect(oldMembers) != oldMembers.toSet() - val removedMembers = oldMembers.minus(members) - val isUserLeaving = removedMembers.contains(userPublicKey) - var newSenderKeys = listOf() - if (wasAnyUserRemoved) { - if (isUserLeaving && removedMembers.count() != 1) { - Log.d("Loki", "Can't remove self and others simultaneously.") - return + public fun update(context: Context, groupPublicKey: String, members: Collection, name: String): Promise { + val deferred = deferred() + Thread { + 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 update nonexistent closed group.") + return@Thread deferred.reject(Error.NoThread) } - // Establish sessions if needed - establishSessionsWithMembersIfNeeded(context, members) - // Send the update to the existing members using established channels (don't include new ratchets as everyone should regenerate new ratchets individually) - for (member in oldMembers) { - @Suppress("NAME_SHADOWING") - val closedGroupUpdateKind = ClosedGroupUpdateMessageSendJob.Kind.Info(Hex.fromStringCondensed(groupPublicKey), - name, setOf(), membersAsData, adminsAsData) - @Suppress("NAME_SHADOWING") - val job = ClosedGroupUpdateMessageSendJob(member, closedGroupUpdateKind) - job.setContext(context) - job.onRun() // Run the job immediately + val oldMembers = group.members.map { it.serialize() }.toSet() + val newMembers = members.minus(oldMembers) + 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) + if (groupPrivateKey == null) { + Log.d("Loki", "Couldn't get private key for closed group.") + return@Thread deferred.reject(Error.NoPrivateKey) } - // 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) - groupDB.remove(groupID, Address.fromSerialized(userPublicKey)) - // Notify the PN server - LokiPushNotificationManager.performOperation(context, ClosedGroupOperation.Unsubscribe, groupPublicKey, userPublicKey) - } else { - // Send closed group update messages to any new members using established channels - for (member in newMembers) { + val wasAnyUserRemoved = members.toSet().intersect(oldMembers) != oldMembers.toSet() + val removedMembers = oldMembers.minus(members) + val isUserLeaving = removedMembers.contains(userPublicKey) + var newSenderKeys = listOf() + if (wasAnyUserRemoved) { + if (isUserLeaving && removedMembers.count() != 1) { + Log.d("Loki", "Can't remove self and others simultaneously.") + return@Thread deferred.reject(Error.InvalidUpdate) + } + // Establish sessions if needed + establishSessionsWithMembersIfNeeded(context, members) + // Send the update to the existing members using established channels (don't include new ratchets as everyone should regenerate new ratchets individually) + for (member in oldMembers) { @Suppress("NAME_SHADOWING") - val closedGroupUpdateKind = ClosedGroupUpdateMessageSendJob.Kind.New(Hex.fromStringCondensed(groupPublicKey), name, - Hex.fromStringCondensed(groupPrivateKey), listOf(), membersAsData, adminsAsData) + val closedGroupUpdateKind = ClosedGroupUpdateMessageSendJob.Kind.Info(Hex.fromStringCondensed(groupPublicKey), + name, setOf(), membersAsData, adminsAsData) @Suppress("NAME_SHADOWING") val job = ClosedGroupUpdateMessageSendJob(member, closedGroupUpdateKind) - ApplicationContext.getInstance(context).jobManager.add(job) + job.setContext(context) + job.onRun() // Run the job immediately } - // 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 } + // 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) + groupDB.remove(groupID, Address.fromSerialized(userPublicKey)) + // Notify the PN server + LokiPushNotificationManager.performOperation(context, ClosedGroupOperation.Unsubscribe, groupPublicKey, userPublicKey) + } else { + // Send closed group update messages to any new members using established channels + for (member in newMembers) { + @Suppress("NAME_SHADOWING") + val closedGroupUpdateKind = ClosedGroupUpdateMessageSendJob.Kind.New(Hex.fromStringCondensed(groupPublicKey), name, + Hex.fromStringCondensed(groupPrivateKey), listOf(), membersAsData, adminsAsData) + @Suppress("NAME_SHADOWING") + val job = ClosedGroupUpdateMessageSendJob(member, closedGroupUpdateKind) + ApplicationContext.getInstance(context).jobManager.add(job) + } + // 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 if (newMembers.isNotEmpty()) { + // Generate ratchets for any new members + newSenderKeys = 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, + 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 + var allSenderKeys = sskDatabase.getAllClosedGroupSenderKeys(groupPublicKey); + allSenderKeys = allSenderKeys.union(newSenderKeys) + 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) } - } - } else if (newMembers.isNotEmpty()) { - // Generate ratchets for any new members - newSenderKeys = 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, - 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 - var allSenderKeys = sskDatabase.getAllClosedGroupSenderKeys(groupPublicKey); - allSenderKeys = allSenderKeys.union(newSenderKeys) - for (member in newMembers) { - @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) + } else { + val allSenderKeys = sskDatabase.getAllClosedGroupSenderKeys(groupPublicKey); + val closedGroupUpdateKind = ClosedGroupUpdateMessageSendJob.Kind.Info(Hex.fromStringCondensed(groupPublicKey), name, + allSenderKeys, membersAsData, adminsAsData) + val job = ClosedGroupUpdateMessageSendJob(groupPublicKey, closedGroupUpdateKind) ApplicationContext.getInstance(context).jobManager.add(job) } - } else { - val allSenderKeys = sskDatabase.getAllClosedGroupSenderKeys(groupPublicKey); - val closedGroupUpdateKind = ClosedGroupUpdateMessageSendJob.Kind.Info(Hex.fromStringCondensed(groupPublicKey), name, - allSenderKeys, membersAsData, adminsAsData) - val job = ClosedGroupUpdateMessageSendJob(groupPublicKey, closedGroupUpdateKind) - ApplicationContext.getInstance(context).jobManager.add(job) - } - // Update the group - groupDB.updateTitle(groupID, name) - 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 infoType = if (isUserLeaving) GroupContext.Type.QUIT else GroupContext.Type.UPDATE - val threadID = DatabaseFactory.getThreadDatabase(context).getThreadIdFor(Recipient.from(context, Address.fromSerialized(groupID), false)) - insertOutgoingInfoMessage(context, groupID, infoType, name, members, admins, threadID) + // Update the group + groupDB.updateTitle(groupID, name) + 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 infoType = if (isUserLeaving) GroupContext.Type.QUIT else GroupContext.Type.UPDATE + val threadID = DatabaseFactory.getThreadDatabase(context).getThreadIdFor(Recipient.from(context, Address.fromSerialized(groupID), false)) + insertOutgoingInfoMessage(context, groupID, infoType, name, members, admins, threadID) + deferred.resolve(Unit) + }.start() + return deferred.promise } @JvmStatic From 6d724fe349edcdcdf57c087ede87fdafd2cd3226 Mon Sep 17 00:00:00 2001 From: nielsandriesse Date: Tue, 6 Oct 2020 14:15:37 +1100 Subject: [PATCH 08/15] Disable the apply button while the group is being updated --- .../loki/activities/EditClosedGroupActivity.kt | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/src/org/thoughtcrime/securesms/loki/activities/EditClosedGroupActivity.kt b/src/org/thoughtcrime/securesms/loki/activities/EditClosedGroupActivity.kt index 3d36afcd67..925da4cdbb 100644 --- a/src/org/thoughtcrime/securesms/loki/activities/EditClosedGroupActivity.kt +++ b/src/org/thoughtcrime/securesms/loki/activities/EditClosedGroupActivity.kt @@ -42,6 +42,8 @@ class EditClosedGroupActivity : PassphraseRequiredActionBarActivity() { private val originalMembers = HashSet() private val members = HashSet() private var hasNameChanged = false + private var isLoading = false + set(newValue) { field = newValue; invalidateOptionsMenu() } private lateinit var groupID: String private lateinit var originalName: String @@ -121,7 +123,7 @@ class EditClosedGroupActivity : PassphraseRequiredActionBarActivity() { override fun onCreateOptionsMenu(menu: Menu): Boolean { menuInflater.inflate(R.menu.menu_edit_closed_group, menu) - return members.isNotEmpty() + return members.isNotEmpty() && !isLoading } // endregion @@ -171,8 +173,8 @@ class EditClosedGroupActivity : PassphraseRequiredActionBarActivity() { // region Interaction override fun onOptionsItemSelected(item: MenuItem): Boolean { - when(item.itemId) { - R.id.action_apply -> commitChanges() + when (item.itemId) { + R.id.action_apply -> if (!isLoading) { commitChanges() } } return super.onOptionsItemSelected(item) } @@ -244,13 +246,16 @@ class EditClosedGroupActivity : PassphraseRequiredActionBarActivity() { } if (isSSKBasedClosedGroup) { + isLoading = true loader.fadeIn() ClosedGroupsProtocol.update(this, groupPublicKey!!, members.map { it.address.serialize() }, name).successUi { loader.fadeOut() + isLoading = false finish() }.failUi { exception -> val message = if (exception is ClosedGroupsProtocol.Error) exception.description else "An error occurred" Toast.makeText(this@EditClosedGroupActivity, message, Toast.LENGTH_LONG).show() + isLoading = false } } else { GroupManager.updateGroup(this, groupID, members, null, name, admins) From 0c669548f333f2f49dce8385ebeb0a80de03ce58 Mon Sep 17 00:00:00 2001 From: nielsandriesse Date: Tue, 6 Oct 2020 14:19:52 +1100 Subject: [PATCH 09/15] Disable the done button while the group is being created --- res/layout/activity_create_closed_group.xml | 2 +- res/layout/activity_home.xml | 2 +- .../activities/CreateClosedGroupActivity.kt | 19 ++++++++----------- .../securesms/loki/activities/HomeActivity.kt | 2 +- 4 files changed, 11 insertions(+), 14 deletions(-) diff --git a/res/layout/activity_create_closed_group.xml b/res/layout/activity_create_closed_group.xml index 78095701e9..382380283b 100644 --- a/res/layout/activity_create_closed_group.xml +++ b/res/layout/activity_create_closed_group.xml @@ -57,7 +57,7 @@