diff --git a/app/src/main/java/org/thoughtcrime/securesms/WebRtcCallActivity.java b/app/src/main/java/org/thoughtcrime/securesms/WebRtcCallActivity.java index 672cd6d497..7fcf3b65bf 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/WebRtcCallActivity.java +++ b/app/src/main/java/org/thoughtcrime/securesms/WebRtcCallActivity.java @@ -58,7 +58,7 @@ import org.signal.libsignal.protocol.IdentityKey; import org.thoughtcrime.securesms.components.TooltipPopup; import org.thoughtcrime.securesms.components.sensors.DeviceOrientationMonitor; import org.thoughtcrime.securesms.components.webrtc.CallLinkInfoSheet; -import org.thoughtcrime.securesms.components.webrtc.CallLinkNullMessageSender; +import org.thoughtcrime.securesms.components.webrtc.CallLinkProfileKeySender; import org.thoughtcrime.securesms.components.webrtc.CallParticipantsListUpdatePopupWindow; import org.thoughtcrime.securesms.components.webrtc.CallParticipantsState; import org.thoughtcrime.securesms.components.webrtc.CallStateUpdatePopupWindow; @@ -778,7 +778,7 @@ public class WebRtcCallActivity extends BaseActivity implements SafetyNumberChan } if (state.isCallLink()) { - CallLinkNullMessageSender.onSendAnyway(new HashSet<>(changedRecipients)); + CallLinkProfileKeySender.onSendAnyway(new HashSet<>(changedRecipients)); } if (state.getGroupCallState().isConnected()) { diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/webrtc/CallLinkInfoSheet.kt b/app/src/main/java/org/thoughtcrime/securesms/components/webrtc/CallLinkInfoSheet.kt index d69bd0ec2e..5342ea24f0 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/components/webrtc/CallLinkInfoSheet.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/components/webrtc/CallLinkInfoSheet.kt @@ -52,7 +52,6 @@ import com.google.android.material.dialog.MaterialAlertDialogBuilder import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers import io.reactivex.rxjava3.core.BackpressureStrategy import io.reactivex.rxjava3.kotlin.subscribeBy -import kotlinx.collections.immutable.ImmutableList import kotlinx.collections.immutable.toImmutableList import org.signal.core.ui.BottomSheets import org.signal.core.ui.Dividers @@ -73,7 +72,6 @@ import org.thoughtcrime.securesms.compose.ComposeBottomSheetDialogFragment import org.thoughtcrime.securesms.database.CallLinkTable import org.thoughtcrime.securesms.dependencies.ApplicationDependencies import org.thoughtcrime.securesms.events.CallParticipant -import org.thoughtcrime.securesms.events.WebRtcViewModel import org.thoughtcrime.securesms.recipients.Recipient import org.thoughtcrime.securesms.recipients.RecipientId import org.thoughtcrime.securesms.service.webrtc.links.CallLinkCredentials @@ -128,11 +126,11 @@ class CallLinkInfoSheet : ComposeBottomSheetDialogFragment() { .toLiveData() .observeAsState() - val participants: ImmutableList = if (callParticipantsState?.callState == WebRtcViewModel.State.CALL_CONNECTED) { - listOf(CallParticipant(recipient = Recipient.self())) + (callParticipantsState?.allRemoteParticipants?.map { it } ?: emptyList()) - } else { - emptyList() - }.toImmutableList() + val participants: List by webRtcCallViewModel.callParticipantsState + .toFlowable(BackpressureStrategy.LATEST) + .map { state -> state.allRemoteParticipants } + .toLiveData() + .observeAsState(emptyList()) val onEditNameClicked: () -> Unit = remember(callLinkDetailsState) { { @@ -249,7 +247,7 @@ private fun SheetPreview() { @Composable private fun Sheet( callLink: CallLinkTable.CallLink, - participants: ImmutableList, + participants: List, onShareLinkClicked: () -> Unit, onEditNameClicked: () -> Unit, onToggleAdminApprovalClicked: (Boolean) -> Unit, @@ -334,6 +332,11 @@ private fun CallLinkMemberRow( .fillMaxWidth() .padding(Rows.defaultPadding()) ) { + val recipient by Recipient.observable(callParticipant.recipient.id) + .toFlowable(BackpressureStrategy.LATEST) + .toLiveData() + .observeAsState(initial = callParticipant.recipient) + if (LocalInspectionMode.current) { Spacer( modifier = Modifier @@ -345,20 +348,20 @@ private fun CallLinkMemberRow( factory = ::AvatarImageView, modifier = Modifier.size(40.dp) ) { - it.setAvatarUsingProfile(callParticipant.recipient) + it.setAvatarUsingProfile(recipient) } } Spacer(modifier = Modifier.width(24.dp)) Text( - text = callParticipant.recipient.getShortDisplayName(LocalContext.current), + text = recipient.getShortDisplayName(LocalContext.current), modifier = Modifier .weight(1f) .align(Alignment.CenterVertically) ) - if (isSelfAdmin && !callParticipant.recipient.isSelf) { + if (isSelfAdmin && !recipient.isSelf) { Icon( imageVector = ImageVector.vectorResource(id = R.drawable.symbol_minus_circle_24), contentDescription = null, diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/webrtc/CallLinkNullMessageSender.kt b/app/src/main/java/org/thoughtcrime/securesms/components/webrtc/CallLinkProfileKeySender.kt similarity index 62% rename from app/src/main/java/org/thoughtcrime/securesms/components/webrtc/CallLinkNullMessageSender.kt rename to app/src/main/java/org/thoughtcrime/securesms/components/webrtc/CallLinkProfileKeySender.kt index d7f0081c6c..d23d985857 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/components/webrtc/CallLinkNullMessageSender.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/components/webrtc/CallLinkProfileKeySender.kt @@ -5,18 +5,21 @@ package org.thoughtcrime.securesms.components.webrtc +import org.signal.core.util.logging.Log import org.thoughtcrime.securesms.database.identity.IdentityRecordList import org.thoughtcrime.securesms.dependencies.ApplicationDependencies -import org.thoughtcrime.securesms.jobs.NullMessageSendJob +import org.thoughtcrime.securesms.jobs.ProfileKeySendJob import org.thoughtcrime.securesms.recipients.Recipient import org.thoughtcrime.securesms.recipients.RecipientId /** - * In-Memory Cache that keeps track of whom we've sent NullMessages to. This is + * In-Memory Cache that keeps track of whom we've sent profile keys to. This is * something that really only needs to happen once so that profile information is * displayed correctly, so we maintain an application process scoped cache. */ -object CallLinkNullMessageSender { +object CallLinkProfileKeySender { + private val TAG = Log.tag(CallLinkProfileKeySender::class.java) + private val cache = hashSetOf() /** @@ -28,21 +31,26 @@ object CallLinkNullMessageSender { val toSendMessagesTo: Set = recipientIds - cache cache += recipientIds - val jobs: List = toSendMessagesTo.map { NullMessageSendJob(it) } - ApplicationDependencies.getJobManager().addAll(jobs) + if (toSendMessagesTo.isNotEmpty()) { + Log.i(TAG, "Sending profile key to $toSendMessagesTo users.") + val job = ProfileKeySendJob.createForCallLinks(toSendMessagesTo.toList()) + ApplicationDependencies.getJobManager().add(job) + } else { + Log.i(TAG, "No users to send profile key to.") + } } /** * Given the set of recipients, for each unblocked recipient we don't distrust, send a NullMessage */ fun onRecipientsUpdated(recipients: Set) { - val nonBlockedRecipients: List = recipients.filterNot { it.isBlocked } + val nonBlockedNonSelfRecipients: List = recipients.filterNot { it.isBlocked || it.isSelf } val identityRecords: IdentityRecordList = ApplicationDependencies .getProtocolStore() .aci() .identities() - .getIdentityRecords(nonBlockedRecipients) + .getIdentityRecords(nonBlockedNonSelfRecipients) val untrustedAndUnverifiedRecipients = if (identityRecords.isUntrusted(false) || identityRecords.isUnverified(false)) { (identityRecords.untrustedRecipients + identityRecords.unverifiedRecipients).toSet() @@ -50,7 +58,8 @@ object CallLinkNullMessageSender { emptySet() } - val trustedRecipients: Set = (nonBlockedRecipients - untrustedAndUnverifiedRecipients).map { it.id }.toSet() + val trustedRecipients: Set = (nonBlockedNonSelfRecipients - untrustedAndUnverifiedRecipients).map { it.id }.toSet() + onSendAnyway(trustedRecipients) } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/jobs/ProfileKeySendJob.java b/app/src/main/java/org/thoughtcrime/securesms/jobs/ProfileKeySendJob.java index 194e9256fc..ccfb593321 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/jobs/ProfileKeySendJob.java +++ b/app/src/main/java/org/thoughtcrime/securesms/jobs/ProfileKeySendJob.java @@ -8,8 +8,8 @@ import com.annimon.stream.Stream; import org.signal.core.util.logging.Log; import org.thoughtcrime.securesms.database.SignalDatabase; -import org.thoughtcrime.securesms.jobmanager.JsonJobData; import org.thoughtcrime.securesms.jobmanager.Job; +import org.thoughtcrime.securesms.jobmanager.JsonJobData; import org.thoughtcrime.securesms.jobmanager.impl.DecryptionsDrainedConstraint; import org.thoughtcrime.securesms.jobmanager.impl.NetworkConstraint; import org.thoughtcrime.securesms.messages.GroupSendUtil; @@ -39,6 +39,21 @@ public class ProfileKeySendJob extends BaseJob { private final long threadId; private final List recipients; + public static ProfileKeySendJob createForCallLinks(List recipientIds) { + return new ProfileKeySendJob( + new Parameters.Builder() + .setQueue("ProfileKeySendJob__call_links") + .setMaxInstancesForQueue(Parameters.UNLIMITED) + .addConstraint(NetworkConstraint.KEY) + .addConstraint(DecryptionsDrainedConstraint.KEY) + .setLifespan(TimeUnit.DAYS.toMillis(1)) + .setMaxAttempts(Parameters.UNLIMITED) + .build(), + -1L, + recipientIds + ); + } + /** * Suitable for a 1:1 conversation or a GV1 group only. * @@ -99,11 +114,13 @@ public class ProfileKeySendJob extends BaseJob { throw new NotPushRegisteredException(); } - Recipient conversationRecipient = SignalDatabase.threads().getRecipientForThreadId(threadId); + if (threadId > 0) { + Recipient conversationRecipient = SignalDatabase.threads().getRecipientForThreadId(threadId); - if (conversationRecipient == null) { - Log.w(TAG, "Thread no longer present"); - return; + if (conversationRecipient == null) { + Log.w(TAG, "Thread no longer present"); + return; + } } List destinations = Stream.of(recipients).map(Recipient::resolved).toList(); diff --git a/app/src/main/java/org/thoughtcrime/securesms/service/webrtc/CallLinkConnectedActionProcessor.kt b/app/src/main/java/org/thoughtcrime/securesms/service/webrtc/CallLinkConnectedActionProcessor.kt index a63082582f..99fe05ec92 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/service/webrtc/CallLinkConnectedActionProcessor.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/service/webrtc/CallLinkConnectedActionProcessor.kt @@ -9,7 +9,7 @@ import org.signal.core.util.logging.Log import org.signal.ringrtc.CallException import org.signal.ringrtc.GroupCall import org.signal.ringrtc.PeekInfo -import org.thoughtcrime.securesms.components.webrtc.CallLinkNullMessageSender +import org.thoughtcrime.securesms.components.webrtc.CallLinkProfileKeySender import org.thoughtcrime.securesms.database.CallLinkTable import org.thoughtcrime.securesms.database.SignalDatabase import org.thoughtcrime.securesms.events.CallParticipant @@ -46,6 +46,9 @@ class CallLinkConnectedActionProcessor( val callLinkRoomId: CallLinkRoomId = superState.callInfoState.callRecipient.requireCallLinkRoomId() val callLink: CallLinkTable.CallLink = SignalDatabase.callLinks.getCallLinkByRoomId(callLinkRoomId) ?: return superState + val joinedParticipants: Set = peekInfo.joinedMembers.map { Recipient.externalPush(ServiceId.ACI.from(it)) }.toSet() + + CallLinkProfileKeySender.onRecipientsUpdated(joinedParticipants) if (callLink.credentials?.adminPassBytes == null) { Log.i(tag, "User is not an admin.") @@ -55,8 +58,6 @@ class CallLinkConnectedActionProcessor( Log.i(tag, "Updating pending list with ${peekInfo.pendingUsers.size} entries.") val pendingParticipants: List = peekInfo.pendingUsers.map { Recipient.externalPush(ServiceId.ACI.from(it)) } - CallLinkNullMessageSender.onRecipientsUpdated(superState.callInfoState.remoteCallParticipants.map { it.recipient }.toSet()) - return superState.builder() .changeCallInfoState() .setCallLinkPendingParticipants(pendingParticipants)