From 9ed80d46b62dfa4b65ac3cb8fa90bf6b542ae154 Mon Sep 17 00:00:00 2001 From: Nicholas Tinsley Date: Mon, 11 Dec 2023 17:37:19 -0500 Subject: [PATCH] Add confirmation dialog for lowering a raised hand. --- .../securesms/WebRtcCallActivity.java | 8 ++- .../webrtc/CallOverflowPopupWindow.kt | 28 +++++++-- .../webrtc/CallParticipantsState.kt | 15 ++++- .../webrtc/controls/CallInfoView.kt | 15 ++++- .../webrtc/controls/RaiseHandSnackbar.kt | 16 ++++- .../securesms/events/CallParticipant.kt | 23 +++++-- .../securesms/events/WebRtcViewModel.kt | 6 +- .../BeginCallActionProcessorDelegate.java | 4 +- .../service/webrtc/GroupActionProcessor.java | 4 +- .../webrtc/GroupConnectedActionProcessor.java | 62 +++++++++---------- .../webrtc/GroupPreJoinActionProcessor.java | 2 +- .../IncomingGroupCallActionProcessor.java | 2 +- .../service/webrtc/state/CallInfoState.kt | 5 +- .../state/WebRtcServiceStateBuilder.java | 5 +- app/src/main/res/values/strings.xml | 2 + .../webrtc/CallParticipantListUpdateTest.java | 2 +- .../ParticipantCollectionTest.java | 2 +- 17 files changed, 136 insertions(+), 65 deletions(-) diff --git a/app/src/main/java/org/thoughtcrime/securesms/WebRtcCallActivity.java b/app/src/main/java/org/thoughtcrime/securesms/WebRtcCallActivity.java index d288dbf49f..ce8a999cd0 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/WebRtcCallActivity.java +++ b/app/src/main/java/org/thoughtcrime/securesms/WebRtcCallActivity.java @@ -437,7 +437,13 @@ public class WebRtcCallActivity extends BaseActivity implements SafetyNumberChan participantUpdateWindow = new CallParticipantsListUpdatePopupWindow(callScreen); callStateUpdatePopupWindow = new CallStateUpdatePopupWindow(callScreen); wifiToCellularPopupWindow = new WifiToCellularPopupWindow(callScreen); - callOverflowPopupWindow = new CallOverflowPopupWindow(this, callScreen); + callOverflowPopupWindow = new CallOverflowPopupWindow(this, callScreen, () -> { + CallParticipantsState state = viewModel.getCallParticipantsStateSnapshot(); + if (state == null) { + return false; + } + return state.getLocalParticipant().isHandRaised(); + }); } private void initializeViewModel(boolean isLandscapeEnabled) { diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/webrtc/CallOverflowPopupWindow.kt b/app/src/main/java/org/thoughtcrime/securesms/components/webrtc/CallOverflowPopupWindow.kt index bcf383e2f4..b0c8524389 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/components/webrtc/CallOverflowPopupWindow.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/components/webrtc/CallOverflowPopupWindow.kt @@ -12,22 +12,25 @@ import android.view.View import android.view.ViewGroup import android.widget.LinearLayout import android.widget.PopupWindow +import android.widget.TextView import androidx.constraintlayout.widget.ConstraintLayout import androidx.core.widget.PopupWindowCompat +import androidx.fragment.app.FragmentActivity +import com.google.android.material.dialog.MaterialAlertDialogBuilder import org.thoughtcrime.securesms.R -import org.thoughtcrime.securesms.WebRtcCallActivity import org.thoughtcrime.securesms.dependencies.ApplicationDependencies /** * A popup window for calls that holds extra actions, such as reactions, raise hand, and screen sharing. * */ -class CallOverflowPopupWindow(private val activity: WebRtcCallActivity, parentViewGroup: ViewGroup) : PopupWindow( +class CallOverflowPopupWindow(private val activity: FragmentActivity, parentViewGroup: ViewGroup, private val raisedHandDelegate: RaisedHandDelegate) : PopupWindow( LayoutInflater.from(activity).inflate(R.layout.call_overflow_holder, parentViewGroup, false), activity.resources.getDimension(R.dimen.calling_reaction_popup_menu_width).toInt(), activity.resources.getDimension(R.dimen.calling_reaction_popup_menu_height).toInt() ) { + private val raiseHandLabel: TextView = (contentView as LinearLayout).findViewById(R.id.raise_hand_label) init { val root = (contentView as LinearLayout) @@ -36,8 +39,19 @@ class CallOverflowPopupWindow(private val activity: WebRtcCallActivity, parentVi dismiss() } root.findViewById(R.id.raise_hand_layout_parent).setOnClickListener { - ApplicationDependencies.getSignalCallManager().raiseHand(true) - dismiss() + if (raisedHandDelegate.isSelfHandRaised()) { + MaterialAlertDialogBuilder(activity) + .setTitle(R.string.CallOverflowPopupWindow__lower_your_hand) + .setPositiveButton(R.string.CallOverflowPopupWindow__lower_hand) { _, _ -> + ApplicationDependencies.getSignalCallManager().raiseHand(false) + this@CallOverflowPopupWindow.dismiss() + } + .setNegativeButton(R.string.CallOverflowPopupWindow__cancel, null) + .show() + } else { + ApplicationDependencies.getSignalCallManager().raiseHand(true) + dismiss() + } } } @@ -58,6 +72,12 @@ class CallOverflowPopupWindow(private val activity: WebRtcCallActivity, parentVi val xOffset = windowWidth - popupWidth - margin val yOffset = -popupHeight - margin + raiseHandLabel.setText(if (raisedHandDelegate.isSelfHandRaised()) R.string.CallOverflowPopupWindow__lower_hand else R.string.CallOverflowPopupWindow__raise_hand) + PopupWindowCompat.showAsDropDown(this, anchor, xOffset, yOffset, Gravity.NO_GRAVITY) } + + interface RaisedHandDelegate { + fun isSelfHandRaised(): Boolean + } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/webrtc/CallParticipantsState.kt b/app/src/main/java/org/thoughtcrime/securesms/components/webrtc/CallParticipantsState.kt index 27de25b216..e807d4a8f4 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/components/webrtc/CallParticipantsState.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/components/webrtc/CallParticipantsState.kt @@ -4,9 +4,11 @@ import android.content.Context import androidx.annotation.PluralsRes import androidx.annotation.StringRes import com.annimon.stream.OptionalLong +import kotlinx.collections.immutable.toImmutableList import org.thoughtcrime.securesms.R import org.thoughtcrime.securesms.components.webrtc.WebRtcControls.FoldableState import org.thoughtcrime.securesms.events.CallParticipant +import org.thoughtcrime.securesms.events.CallParticipant.Companion.HAND_LOWERED import org.thoughtcrime.securesms.events.CallParticipant.Companion.createLocal import org.thoughtcrime.securesms.events.GroupCallRaiseHandEvent import org.thoughtcrime.securesms.events.GroupCallReactionEvent @@ -27,11 +29,10 @@ data class CallParticipantsState( val callState: WebRtcViewModel.State = WebRtcViewModel.State.CALL_DISCONNECTED, val groupCallState: WebRtcViewModel.GroupCallState = WebRtcViewModel.GroupCallState.IDLE, private val remoteParticipants: ParticipantCollection = ParticipantCollection(SMALL_GROUP_MAX), - val localParticipant: CallParticipant = createLocal(CameraState.UNKNOWN, BroadcastVideoSink(), microphoneEnabled = false, isHandRaised = false), + val localParticipant: CallParticipant = createLocal(CameraState.UNKNOWN, BroadcastVideoSink(), microphoneEnabled = false, handRaisedTimestamp = HAND_LOWERED), val focusedParticipant: CallParticipant = CallParticipant.EMPTY, val localRenderState: WebRtcLocalRenderState = WebRtcLocalRenderState.GONE, val reactions: List = emptyList(), - val raisedHands: List = emptyList(), val isInPipMode: Boolean = false, private val showVideoForOutgoing: Boolean = false, val isViewingFocusedParticipant: Boolean = false, @@ -50,6 +51,15 @@ data class CallParticipantsState( val isLargeVideoGroup: Boolean = allRemoteParticipants.size > SMALL_GROUP_MAX val isIncomingRing: Boolean = callState == WebRtcViewModel.State.CALL_INCOMING + val raisedHands: List + get() { + val results = allRemoteParticipants.filter { it.isHandRaised }.map { GroupCallRaiseHandEvent(it.recipient, it.handRaisedTimestamp) }.toMutableList() + if (localParticipant.isHandRaised) { + results.add(GroupCallRaiseHandEvent(localParticipant.recipient, localParticipant.handRaisedTimestamp)) + } + return results.toImmutableList() + } + val gridParticipants: List get() { return remoteParticipants.gridParticipants @@ -229,7 +239,6 @@ data class CallParticipantsState( localRenderState = localRenderState, showVideoForOutgoing = newShowVideoForOutgoing, recipient = webRtcViewModel.recipient, - raisedHands = webRtcViewModel.raisedHands, remoteDevicesCount = webRtcViewModel.remoteDevicesCount, ringGroup = webRtcViewModel.ringGroup, isInOutgoingRingingMode = isInOutgoingRingingMode, diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/webrtc/controls/CallInfoView.kt b/app/src/main/java/org/thoughtcrime/securesms/components/webrtc/controls/CallInfoView.kt index fe0af4ff86..a7f1e8c2b9 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/components/webrtc/controls/CallInfoView.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/components/webrtc/controls/CallInfoView.kt @@ -5,6 +5,7 @@ package org.thoughtcrime.securesms.components.webrtc.controls +import android.content.Context import androidx.compose.foundation.background import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.Box @@ -42,6 +43,7 @@ import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import androidx.compose.ui.viewinterop.AndroidView import androidx.lifecycle.toLiveData +import com.google.android.material.dialog.MaterialAlertDialogBuilder import io.reactivex.rxjava3.core.BackpressureStrategy import io.reactivex.rxjava3.core.Observable import org.signal.core.ui.Rows @@ -337,9 +339,10 @@ private fun CallParticipantRow( ) if (showIcons && showHandRaised && canLowerHand) { + val context = LocalContext.current TextButton(onClick = { if (recipient.isSelf) { - ApplicationDependencies.getSignalCallManager().raiseHand(false) + showLowerHandDialog(context) } }) { Text(text = stringResource(id = R.string.CallOverflowPopupWindow__lower_hand)) @@ -391,6 +394,16 @@ private fun CallParticipantRow( } } +private fun showLowerHandDialog(context: Context) { + MaterialAlertDialogBuilder(context) + .setTitle(R.string.CallOverflowPopupWindow__lower_your_hand) + .setPositiveButton( + R.string.CallOverflowPopupWindow__lower_hand + ) { _, _ -> ApplicationDependencies.getSignalCallManager().raiseHand(false) } + .setNegativeButton(R.string.CallOverflowPopupWindow__cancel, null) + .show() +} + @Composable private fun GroupMemberRow( groupMember: GroupMemberEntry.FullMember, diff --git a/app/src/main/java/org/thoughtcrime/securesms/components/webrtc/controls/RaiseHandSnackbar.kt b/app/src/main/java/org/thoughtcrime/securesms/components/webrtc/controls/RaiseHandSnackbar.kt index f69edba841..540e644b61 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/components/webrtc/controls/RaiseHandSnackbar.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/components/webrtc/controls/RaiseHandSnackbar.kt @@ -5,6 +5,7 @@ package org.thoughtcrime.securesms.components.webrtc.controls +import android.content.Context import androidx.compose.animation.AnimatedVisibility import androidx.compose.animation.animateContentSize import androidx.compose.foundation.background @@ -41,6 +42,7 @@ import androidx.compose.ui.res.vectorResource import androidx.compose.ui.semantics.Role import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp +import com.google.android.material.dialog.MaterialAlertDialogBuilder import io.reactivex.rxjava3.core.BackpressureStrategy import kotlinx.coroutines.delay import org.signal.core.ui.theme.SignalTheme @@ -50,7 +52,6 @@ import org.thoughtcrime.securesms.dependencies.ApplicationDependencies import org.thoughtcrime.securesms.events.GroupCallRaiseHandEvent import org.thoughtcrime.securesms.recipients.Recipient import java.util.concurrent.TimeUnit -import kotlin.time.Duration /** * This is a UI element to display the status of one or more people with raised hands in a group call. @@ -149,8 +150,9 @@ private fun RaiseHand( Spacer(modifier = Modifier.weight(1f)) if (state.raisedHands.first().sender.isSelf) { + val context = LocalContext.current TextButton(onClick = { - ApplicationDependencies.getSignalCallManager().raiseHand(false) + showLowerHandDialog(context) }) { Text(text = stringResource(id = R.string.CallOverflowPopupWindow__lower_hand)) } @@ -167,6 +169,16 @@ private fun RaiseHand( } } +private fun showLowerHandDialog(context: Context) { + MaterialAlertDialogBuilder(context) + .setTitle(R.string.CallOverflowPopupWindow__lower_your_hand) + .setPositiveButton( + R.string.CallOverflowPopupWindow__lower_hand + ) { _, _ -> ApplicationDependencies.getSignalCallManager().raiseHand(false) } + .setNegativeButton(R.string.CallOverflowPopupWindow__cancel, null) + .show() +} + @Composable private fun getSnackbarText(state: RaiseHandState): String { if (state.isEmpty()) { diff --git a/app/src/main/java/org/thoughtcrime/securesms/events/CallParticipant.kt b/app/src/main/java/org/thoughtcrime/securesms/events/CallParticipant.kt index fc0745c302..5c37196f64 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/events/CallParticipant.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/events/CallParticipant.kt @@ -16,7 +16,7 @@ data class CallParticipant constructor( val isForwardingVideo: Boolean = true, val isVideoEnabled: Boolean = false, val isMicrophoneEnabled: Boolean = false, - val isHandRaised: Boolean = false, + val handRaisedTimestamp: Long = HAND_LOWERED, val lastSpoke: Long = 0, val audioLevel: AudioLevel? = null, val isMediaKeysReceived: Boolean = true, @@ -36,6 +36,9 @@ data class CallParticipant constructor( val isSelf: Boolean get() = recipient.isSelf + val isHandRaised: Boolean + get() = handRaisedTimestamp > 0 + fun getRecipientDisplayName(context: Context): String { return if (recipient.isSelf && isPrimary) { context.getString(R.string.CallParticipant__you) @@ -72,6 +75,10 @@ data class CallParticipant constructor( return copy(isScreenSharing = enable) } + fun withHandRaisedTimestamp(timestamp: Long): CallParticipant { + return copy(handRaisedTimestamp = timestamp) + } + enum class DeviceOrdinal { PRIMARY, SECONDARY } @@ -103,24 +110,28 @@ data class CallParticipant constructor( } companion object { + const val HAND_LOWERED = -1L + @JvmField val EMPTY: CallParticipant = CallParticipant() @JvmStatic + @JvmOverloads fun createLocal( cameraState: CameraState, renderer: BroadcastVideoSink, microphoneEnabled: Boolean, - isHandRaised: Boolean + handRaisedTimestamp: Long, + callParticipantId: CallParticipantId = CallParticipantId(Recipient.self()) ): CallParticipant { return CallParticipant( - callParticipantId = CallParticipantId(Recipient.self()), + callParticipantId = callParticipantId, recipient = Recipient.self(), videoSink = renderer, cameraState = cameraState, isVideoEnabled = cameraState.isEnabled && cameraState.cameraCount > 0, isMicrophoneEnabled = microphoneEnabled, - isHandRaised = isHandRaised + handRaisedTimestamp = handRaisedTimestamp ) } @@ -133,7 +144,7 @@ data class CallParticipant constructor( isForwardingVideo: Boolean, audioEnabled: Boolean, videoEnabled: Boolean, - isHandRaised: Boolean, + handRaisedTimestamp: Long, lastSpoke: Long, mediaKeysReceived: Boolean, addedToCallTime: Long, @@ -148,7 +159,7 @@ data class CallParticipant constructor( isForwardingVideo = isForwardingVideo, isVideoEnabled = videoEnabled, isMicrophoneEnabled = audioEnabled, - isHandRaised = isHandRaised, + handRaisedTimestamp = handRaisedTimestamp, lastSpoke = lastSpoke, isMediaKeysReceived = mediaKeysReceived, addedToCallTime = addedToCallTime, diff --git a/app/src/main/java/org/thoughtcrime/securesms/events/WebRtcViewModel.kt b/app/src/main/java/org/thoughtcrime/securesms/events/WebRtcViewModel.kt index 2bee6b75da..9e9180db2f 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/events/WebRtcViewModel.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/events/WebRtcViewModel.kt @@ -2,6 +2,7 @@ package org.thoughtcrime.securesms.events import com.annimon.stream.OptionalLong import org.thoughtcrime.securesms.components.webrtc.BroadcastVideoSink +import org.thoughtcrime.securesms.events.CallParticipant.Companion.HAND_LOWERED import org.thoughtcrime.securesms.events.CallParticipant.Companion.createLocal import org.thoughtcrime.securesms.recipients.Recipient import org.thoughtcrime.securesms.recipients.RecipientId @@ -92,7 +93,6 @@ class WebRtcViewModel(state: WebRtcServiceState) { val isRemoteVideoOffer: Boolean = state.getCallSetupState(state.callInfoState.activePeer?.callId).isRemoteVideoOffer val callConnectedTime: Long = state.callInfoState.callConnectedTime val remoteParticipants: List = state.callInfoState.remoteCallParticipants - val raisedHands: List = state.callInfoState.raisedHands val identityChangedParticipants: Set = state.callInfoState.identityChangedRecipients val remoteDevicesCount: OptionalLong = state.callInfoState.remoteDevicesCount val participantLimit: Long? = state.callInfoState.participantLimit @@ -107,11 +107,11 @@ class WebRtcViewModel(state: WebRtcServiceState) { val availableDevices: Set = state.localDeviceState.availableDevices val bluetoothPermissionDenied: Boolean = state.localDeviceState.bluetoothPermissionDenied - val localParticipant: CallParticipant = createLocal( + val localParticipant: CallParticipant = state.callInfoState.localParticipant ?: createLocal( state.localDeviceState.cameraState, (if (state.videoState.localSink != null) state.videoState.localSink else BroadcastVideoSink())!!, state.localDeviceState.isMicrophoneEnabled, - state.callInfoState.raisedHands.map { it.sender }.contains(Recipient.self()) + HAND_LOWERED ) val isCellularConnection: Boolean = when (state.localDeviceState.networkConnectionType) { diff --git a/app/src/main/java/org/thoughtcrime/securesms/service/webrtc/BeginCallActionProcessorDelegate.java b/app/src/main/java/org/thoughtcrime/securesms/service/webrtc/BeginCallActionProcessorDelegate.java index 7615277d08..5bf1b23d00 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/service/webrtc/BeginCallActionProcessorDelegate.java +++ b/app/src/main/java/org/thoughtcrime/securesms/service/webrtc/BeginCallActionProcessorDelegate.java @@ -50,7 +50,7 @@ public class BeginCallActionProcessorDelegate extends WebRtcActionProcessor { true, true, false, - currentState.getCallInfoState().getRaisedHands().contains(remotePeer.getRecipient()), + CallParticipant.HAND_LOWERED, 0, true, 0, @@ -109,7 +109,7 @@ public class BeginCallActionProcessorDelegate extends WebRtcActionProcessor { true, true, false, - currentState.getCallInfoState().getRaisedHands().contains(remotePeer.getRecipient()), + CallParticipant.HAND_LOWERED, 0, true, 0, diff --git a/app/src/main/java/org/thoughtcrime/securesms/service/webrtc/GroupActionProcessor.java b/app/src/main/java/org/thoughtcrime/securesms/service/webrtc/GroupActionProcessor.java index f161f47f62..57457d94d5 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/service/webrtc/GroupActionProcessor.java +++ b/app/src/main/java/org/thoughtcrime/securesms/service/webrtc/GroupActionProcessor.java @@ -108,6 +108,8 @@ public class GroupActionProcessor extends DeviceAwareActionProcessor { videoSink = new BroadcastVideoSink(); } + long handRaisedTimestamp = callParticipant != null ? callParticipant.getHandRaisedTimestamp() : CallParticipant.HAND_LOWERED; + builder.putParticipant(callParticipantId, CallParticipant.createRemote(callParticipantId, recipient, @@ -116,7 +118,7 @@ public class GroupActionProcessor extends DeviceAwareActionProcessor { device.getForwardingVideo() == null || device.getForwardingVideo(), Boolean.FALSE.equals(device.getAudioMuted()), Boolean.FALSE.equals(device.getVideoMuted()), - currentState.getCallInfoState().getRaisedHands().contains(recipient), + handRaisedTimestamp, device.getSpeakerTime(), device.getMediaKeysReceived(), device.getAddedTime(), diff --git a/app/src/main/java/org/thoughtcrime/securesms/service/webrtc/GroupConnectedActionProcessor.java b/app/src/main/java/org/thoughtcrime/securesms/service/webrtc/GroupConnectedActionProcessor.java index 333dc470b2..a00f06908f 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/service/webrtc/GroupConnectedActionProcessor.java +++ b/app/src/main/java/org/thoughtcrime/securesms/service/webrtc/GroupConnectedActionProcessor.java @@ -14,7 +14,6 @@ import org.signal.ringrtc.GroupCall; import org.signal.ringrtc.PeekInfo; import org.thoughtcrime.securesms.events.CallParticipant; import org.thoughtcrime.securesms.events.CallParticipantId; -import org.thoughtcrime.securesms.events.GroupCallRaiseHandEvent; import org.thoughtcrime.securesms.events.GroupCallReactionEvent; import org.thoughtcrime.securesms.events.WebRtcViewModel; import org.thoughtcrime.securesms.keyvalue.SignalStore; @@ -24,18 +23,13 @@ import org.thoughtcrime.securesms.ringrtc.RemotePeer; import org.thoughtcrime.securesms.service.webrtc.state.CallInfoState; import org.thoughtcrime.securesms.service.webrtc.state.WebRtcEphemeralState; import org.thoughtcrime.securesms.service.webrtc.state.WebRtcServiceState; +import org.thoughtcrime.securesms.service.webrtc.state.WebRtcServiceStateBuilder; import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; import java.util.List; -import java.util.Objects; -import java.util.Optional; -import java.util.Set; import java.util.UUID; -import java.util.stream.Collectors; - -import ezvcard.util.StringUtils; /** * Process actions for when the call has at least once been connected and joined. @@ -266,34 +260,38 @@ public class GroupConnectedActionProcessor extends GroupActionProcessor { @Override protected @NonNull WebRtcServiceState handleGroupCallRaisedHand(@NonNull WebRtcServiceState currentState, List raisedHands) { - Log.i(tag, "handleGroupCallRaisedHand():"); - List existingHands = currentState.getCallInfoState().getRaisedHands(); + Log.i(TAG, "handleGroupCallRaisedHand():"); + long now = System.currentTimeMillis(); + WebRtcServiceStateBuilder.CallInfoStateBuilder builder = currentState.builder().changeCallInfoState(); + Long localDemuxId = currentState.getCallInfoState().requireGroupCall().getLocalDeviceState().getDemuxId(); List participants = currentState.getCallInfoState().getRemoteCallParticipants(); - List currentRaisedHands = raisedHands - .stream().map(demuxId -> { - if (Objects.equals(demuxId, currentState.getCallInfoState().requireGroupCall().getLocalDeviceState().getDemuxId())) { - return Recipient.self(); - } - CallParticipant participant = participants.stream().filter(it -> it.getCallParticipantId().getDemuxId() == demuxId).findFirst().orElse(null); - if (participant == null) { - Log.v(TAG, "Could not find CallParticipantId in list of call participants based on demuxId for raise hand."); - return null; - } - return participant.getRecipient(); - }) - .filter(Objects::nonNull) - .map(recipient -> { - final Optional matchingEvent = existingHands.stream().filter(existingEvent -> existingEvent.getSender().equals(recipient)).findFirst(); - if (matchingEvent.isPresent()) { - return matchingEvent.get(); - } else { - return new GroupCallRaiseHandEvent(recipient, System.currentTimeMillis()); - } - }) - .collect(Collectors.toList()); + for (CallParticipant updatedParticipant : participants) { + boolean isHandCurrentlyRaised = raisedHands.contains(updatedParticipant.getCallParticipantId().getDemuxId()); + boolean wasHandAlreadyRaised = updatedParticipant.isHandRaised(); + if (isHandCurrentlyRaised && !wasHandAlreadyRaised) { + builder.putParticipant(updatedParticipant.getCallParticipantId(), updatedParticipant.withHandRaisedTimestamp(now)); + } else if (!isHandCurrentlyRaised && wasHandAlreadyRaised) { + builder.putParticipant(updatedParticipant.getCallParticipantId(), updatedParticipant.withHandRaisedTimestamp(CallParticipant.HAND_LOWERED)); + } + } - return currentState.builder().changeCallInfoState().setRaisedHand(currentRaisedHands).build(); + if (localDemuxId != null) { + if (raisedHands.contains(localDemuxId)) { + builder.setLocalParticipant(CallParticipant.createLocal(currentState.getLocalDeviceState().getCameraState(), + currentState.getVideoState().requireLocalSink(), + currentState.getLocalDeviceState().isMicrophoneEnabled(), + now, + new CallParticipantId(localDemuxId, Recipient.self().getId()))); + } else { + builder.setLocalParticipant(CallParticipant.createLocal(currentState.getLocalDeviceState().getCameraState(), + currentState.getVideoState().requireLocalSink(), + currentState.getLocalDeviceState().isMicrophoneEnabled(), + CallParticipant.HAND_LOWERED, + new CallParticipantId(localDemuxId, Recipient.self().getId()))); + } + } + return builder.build(); } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/service/webrtc/GroupPreJoinActionProcessor.java b/app/src/main/java/org/thoughtcrime/securesms/service/webrtc/GroupPreJoinActionProcessor.java index 52b1380a28..3b7b452852 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/service/webrtc/GroupPreJoinActionProcessor.java +++ b/app/src/main/java/org/thoughtcrime/securesms/service/webrtc/GroupPreJoinActionProcessor.java @@ -137,7 +137,7 @@ public class GroupPreJoinActionProcessor extends GroupActionProcessor { true, true, true, - currentState.getCallInfoState().getRaisedHands().contains(recipient), + CallParticipant.HAND_LOWERED, 0, false, 0, diff --git a/app/src/main/java/org/thoughtcrime/securesms/service/webrtc/IncomingGroupCallActionProcessor.java b/app/src/main/java/org/thoughtcrime/securesms/service/webrtc/IncomingGroupCallActionProcessor.java index 683bdaebe0..905f554597 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/service/webrtc/IncomingGroupCallActionProcessor.java +++ b/app/src/main/java/org/thoughtcrime/securesms/service/webrtc/IncomingGroupCallActionProcessor.java @@ -167,7 +167,7 @@ public final class IncomingGroupCallActionProcessor extends DeviceAwareActionPro true, true, false, - currentState.getCallInfoState().getRaisedHands().contains(remotePeerGroup.getRecipient()), + CallParticipant.HAND_LOWERED, 0, true, 0, diff --git a/app/src/main/java/org/thoughtcrime/securesms/service/webrtc/state/CallInfoState.kt b/app/src/main/java/org/thoughtcrime/securesms/service/webrtc/state/CallInfoState.kt index 0bdd58482b..81a8be220e 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/service/webrtc/state/CallInfoState.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/service/webrtc/state/CallInfoState.kt @@ -5,7 +5,6 @@ import org.signal.ringrtc.CallId import org.signal.ringrtc.GroupCall import org.thoughtcrime.securesms.events.CallParticipant import org.thoughtcrime.securesms.events.CallParticipantId -import org.thoughtcrime.securesms.events.GroupCallRaiseHandEvent import org.thoughtcrime.securesms.events.WebRtcViewModel import org.thoughtcrime.securesms.recipients.Recipient import org.thoughtcrime.securesms.recipients.RecipientId @@ -23,6 +22,7 @@ data class CallInfoState( var callRecipient: Recipient = Recipient.UNKNOWN, var callConnectedTime: Long = -1, @get:JvmName("getRemoteCallParticipantsMap") var remoteParticipants: MutableMap = mutableMapOf(), + var localParticipant: CallParticipant? = null, var peerMap: MutableMap = mutableMapOf(), var activePeer: RemotePeer? = null, var groupCall: GroupCall? = null, @@ -31,8 +31,7 @@ data class CallInfoState( var remoteDevicesCount: OptionalLong = OptionalLong.empty(), var participantLimit: Long? = null, var pendingParticipants: PendingParticipantCollection = PendingParticipantCollection(), - var callLinkDisconnectReason: CallLinkDisconnectReason? = null, - var raisedHands: List = emptyList() + var callLinkDisconnectReason: CallLinkDisconnectReason? = null ) { val remoteCallParticipants: List diff --git a/app/src/main/java/org/thoughtcrime/securesms/service/webrtc/state/WebRtcServiceStateBuilder.java b/app/src/main/java/org/thoughtcrime/securesms/service/webrtc/state/WebRtcServiceStateBuilder.java index cc4f8ea375..714e6c05b6 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/service/webrtc/state/WebRtcServiceStateBuilder.java +++ b/app/src/main/java/org/thoughtcrime/securesms/service/webrtc/state/WebRtcServiceStateBuilder.java @@ -12,7 +12,6 @@ import org.thoughtcrime.securesms.components.webrtc.BroadcastVideoSink; import org.thoughtcrime.securesms.components.webrtc.EglBaseWrapper; import org.thoughtcrime.securesms.events.CallParticipant; import org.thoughtcrime.securesms.events.CallParticipantId; -import org.thoughtcrime.securesms.events.GroupCallRaiseHandEvent; import org.thoughtcrime.securesms.events.WebRtcViewModel; import org.thoughtcrime.securesms.recipients.Recipient; import org.thoughtcrime.securesms.recipients.RecipientId; @@ -366,8 +365,8 @@ public class WebRtcServiceStateBuilder { return this; } - public @NonNull CallInfoStateBuilder setRaisedHand(@NonNull List raisedHands) { - toBuild.setRaisedHands(raisedHands); + public @NonNull CallInfoStateBuilder setLocalParticipant(@NonNull CallParticipant callParticipant) { + toBuild.setLocalParticipant(callParticipant); return this; } } diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index a5f431d940..5a1743d4f2 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -1857,6 +1857,8 @@ Lower your hand? Lower hand + + Cancel You raised your hand diff --git a/app/src/test/java/org/thoughtcrime/securesms/components/webrtc/CallParticipantListUpdateTest.java b/app/src/test/java/org/thoughtcrime/securesms/components/webrtc/CallParticipantListUpdateTest.java index eadb9a1418..e3e4e7ada5 100644 --- a/app/src/test/java/org/thoughtcrime/securesms/components/webrtc/CallParticipantListUpdateTest.java +++ b/app/src/test/java/org/thoughtcrime/securesms/components/webrtc/CallParticipantListUpdateTest.java @@ -178,7 +178,7 @@ public class CallParticipantListUpdateTest { private static CallParticipant createParticipant(long recipientId, long deMuxId, @NonNull CallParticipant.DeviceOrdinal deviceOrdinal) { Recipient recipient = new Recipient(RecipientId.from(recipientId), mock(RecipientDetails.class), true); - return CallParticipant.createRemote(new CallParticipantId(deMuxId, recipient.getId()), recipient, null, new BroadcastVideoSink(), false, false, false, false, -1, false, 0, false, deviceOrdinal); + return CallParticipant.createRemote(new CallParticipantId(deMuxId, recipient.getId()), recipient, null, new BroadcastVideoSink(), false, false, false, CallParticipant.HAND_LOWERED, -1, false, 0, false, deviceOrdinal); } } \ No newline at end of file diff --git a/app/src/test/java/org/thoughtcrime/securesms/service/webrtc/collections/ParticipantCollectionTest.java b/app/src/test/java/org/thoughtcrime/securesms/service/webrtc/collections/ParticipantCollectionTest.java index d7c8fce289..664bdea89b 100644 --- a/app/src/test/java/org/thoughtcrime/securesms/service/webrtc/collections/ParticipantCollectionTest.java +++ b/app/src/test/java/org/thoughtcrime/securesms/service/webrtc/collections/ParticipantCollectionTest.java @@ -243,7 +243,7 @@ public class ParticipantCollectionTest { false, false, false, - false, + CallParticipant.HAND_LOWERED, lastSpoke, false, added,