diff --git a/src/org/thoughtcrime/securesms/loki/protocol/ClosedGroupsProtocol.kt b/src/org/thoughtcrime/securesms/loki/protocol/ClosedGroupsProtocol.kt index 3618ebebfb..765da2c326 100644 --- a/src/org/thoughtcrime/securesms/loki/protocol/ClosedGroupsProtocol.kt +++ b/src/org/thoughtcrime/securesms/loki/protocol/ClosedGroupsProtocol.kt @@ -61,7 +61,6 @@ object ClosedGroupsProtocol { public fun addMembers(context: Context, newMembers: Collection, groupPublicKey: String) { // Prepare - val userPublicKey = TextSecurePreferences.getLocalNumber(context) val groupDB = DatabaseFactory.getGroupDatabase(context) val groupID = GroupUtil.getEncodedId(Hex.fromStringCondensed(groupPublicKey), false) val group = groupDB.getGroup(groupID).orNull() @@ -107,6 +106,61 @@ object ClosedGroupsProtocol { // TODO: Implement } + 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.") + } + val groupDB = DatabaseFactory.getGroupDatabase(context) + val groupID = GroupUtil.getEncodedId(Hex.fromStringCondensed(groupPublicKey), false) + 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 { Hex.fromStringCondensed(it.serialize()) } + // 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, admins) + val job = ClosedGroupUpdateMessageSendJob(groupPublicKey, closedGroupUpdateKind) + ApplicationContext.getInstance(context).jobManager.add(job) + // 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) + } 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) { + @Suppress("NAME_SHADOWING") + val closedGroupUpdateKind = ClosedGroupUpdateMessageSendJob.Kind.SenderKey(Hex.fromStringCondensed(groupPublicKey), userSenderKey) + @Suppress("NAME_SHADOWING") + val job = ClosedGroupUpdateMessageSendJob(groupPublicKey, closedGroupUpdateKind) + ApplicationContext.getInstance(context).jobManager.add(job) + } + } + // Update the group + groupDB.updateMembers(groupID, members.map { Address.fromSerialized(it) }) + // Notify the user + // TODO: Implement + } + @JvmStatic fun shouldIgnoreContentMessage(context: Context, address: Address, groupID: String?, senderPublicKey: String): Boolean { if (!address.isClosedGroup || groupID == null) { return false }