mirror of
https://github.com/signalapp/Signal-Android.git
synced 2026-04-19 16:19:33 +01:00
Add Small Group Ringing support.
This commit is contained in:
committed by
Alex Hart
parent
5787a5f68a
commit
db7272730e
@@ -10,7 +10,6 @@ import org.signal.core.util.logging.Log;
|
||||
import org.signal.ringrtc.CallException;
|
||||
import org.signal.ringrtc.GroupCall;
|
||||
import org.thoughtcrime.securesms.components.webrtc.BroadcastVideoSink;
|
||||
import org.thoughtcrime.securesms.components.webrtc.GroupCallSafetyNumberChangeNotificationUtil;
|
||||
import org.thoughtcrime.securesms.events.CallParticipant;
|
||||
import org.thoughtcrime.securesms.events.CallParticipantId;
|
||||
import org.thoughtcrime.securesms.events.WebRtcViewModel;
|
||||
@@ -21,13 +20,11 @@ import org.thoughtcrime.securesms.ringrtc.RemotePeer;
|
||||
import org.thoughtcrime.securesms.service.webrtc.state.VideoState;
|
||||
import org.thoughtcrime.securesms.service.webrtc.state.WebRtcServiceState;
|
||||
import org.thoughtcrime.securesms.service.webrtc.state.WebRtcServiceStateBuilder;
|
||||
import org.thoughtcrime.securesms.webrtc.locks.LockManager;
|
||||
import org.webrtc.VideoTrack;
|
||||
import org.whispersystems.libsignal.IdentityKey;
|
||||
import org.whispersystems.libsignal.util.guava.Optional;
|
||||
import org.whispersystems.signalservice.api.messages.calls.OfferMessage;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
@@ -186,40 +183,37 @@ public class GroupActionProcessor extends DeviceAwareActionProcessor {
|
||||
}
|
||||
|
||||
@Override
|
||||
protected @NonNull WebRtcServiceState handleReceivedOpaqueMessage(@NonNull WebRtcServiceState currentState, @NonNull WebRtcData.OpaqueMessageMetadata opaqueMessageMetadata) {
|
||||
Log.i(tag, "handleReceivedOpaqueMessage():");
|
||||
protected @NonNull WebRtcServiceState handleSetRingGroup(@NonNull WebRtcServiceState currentState, boolean ringGroup) {
|
||||
Log.i(tag, "handleReceivedOpaqueMessage(): ring: " + ringGroup);
|
||||
|
||||
try {
|
||||
webRtcInteractor.getCallManager().receivedCallMessage(opaqueMessageMetadata.getUuid(),
|
||||
opaqueMessageMetadata.getRemoteDeviceId(),
|
||||
1,
|
||||
opaqueMessageMetadata.getOpaque(),
|
||||
opaqueMessageMetadata.getMessageAgeSeconds());
|
||||
} catch (CallException e) {
|
||||
return groupCallFailure(currentState, "Unable to receive opaque message", e);
|
||||
if (currentState.getCallSetupState().shouldRingGroup() == ringGroup) {
|
||||
return currentState;
|
||||
}
|
||||
|
||||
return currentState;
|
||||
return currentState.builder()
|
||||
.changeCallSetupState()
|
||||
.setRingGroup(ringGroup)
|
||||
.build();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected @NonNull WebRtcServiceState handleGroupMessageSentError(@NonNull WebRtcServiceState currentState,
|
||||
@NonNull RemotePeer remotePeer,
|
||||
@NonNull WebRtcViewModel.State errorCallState,
|
||||
@NonNull Optional<IdentityKey> identityKey)
|
||||
@NonNull Collection<RecipientId> recipientIds,
|
||||
@NonNull WebRtcViewModel.State errorCallState)
|
||||
{
|
||||
Log.w(tag, "handleGroupMessageSentError(): error: " + errorCallState);
|
||||
|
||||
if (errorCallState == WebRtcViewModel.State.UNTRUSTED_IDENTITY) {
|
||||
return currentState.builder()
|
||||
.changeCallInfoState()
|
||||
.addIdentityChangedRecipient(remotePeer.getId())
|
||||
.addIdentityChangedRecipients(recipientIds)
|
||||
.build();
|
||||
}
|
||||
|
||||
return currentState;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected @NonNull WebRtcServiceState handleGroupApproveSafetyNumberChange(@NonNull WebRtcServiceState currentState,
|
||||
@NonNull List<RecipientId> recipientIds)
|
||||
{
|
||||
@@ -291,56 +285,4 @@ public class GroupActionProcessor extends DeviceAwareActionProcessor {
|
||||
|
||||
return terminateGroupCall(currentState);
|
||||
}
|
||||
|
||||
public @NonNull WebRtcServiceState groupCallFailure(@NonNull WebRtcServiceState currentState, @NonNull String message, @NonNull Throwable error) {
|
||||
Log.w(tag, "groupCallFailure(): " + message, error);
|
||||
|
||||
GroupCall groupCall = currentState.getCallInfoState().getGroupCall();
|
||||
Recipient recipient = currentState.getCallInfoState().getCallRecipient();
|
||||
|
||||
if (recipient != null && currentState.getCallInfoState().getGroupCallState().isConnected()) {
|
||||
webRtcInteractor.sendGroupCallMessage(recipient, WebRtcUtil.getGroupCallEraId(groupCall));
|
||||
}
|
||||
|
||||
currentState = currentState.builder()
|
||||
.changeCallInfoState()
|
||||
.callState(WebRtcViewModel.State.CALL_DISCONNECTED)
|
||||
.groupCallState(WebRtcViewModel.GroupCallState.DISCONNECTED)
|
||||
.build();
|
||||
|
||||
webRtcInteractor.postStateUpdate(currentState);
|
||||
|
||||
try {
|
||||
if (groupCall != null) {
|
||||
groupCall.disconnect();
|
||||
}
|
||||
webRtcInteractor.getCallManager().reset();
|
||||
} catch (CallException e) {
|
||||
Log.w(tag, "Unable to reset call manager: ", e);
|
||||
}
|
||||
|
||||
return terminateGroupCall(currentState);
|
||||
}
|
||||
|
||||
public synchronized @NonNull WebRtcServiceState terminateGroupCall(@NonNull WebRtcServiceState currentState) {
|
||||
return terminateGroupCall(currentState, true);
|
||||
}
|
||||
|
||||
public synchronized @NonNull WebRtcServiceState terminateGroupCall(@NonNull WebRtcServiceState currentState, boolean terminateVideo) {
|
||||
webRtcInteractor.updatePhoneState(LockManager.PhoneState.PROCESSING);
|
||||
boolean playDisconnectSound = currentState.getCallInfoState().getCallState() == WebRtcViewModel.State.CALL_DISCONNECTED;
|
||||
webRtcInteractor.stopAudio(playDisconnectSound);
|
||||
webRtcInteractor.setWantsBluetoothConnection(false);
|
||||
webRtcInteractor.stopForegroundService();
|
||||
|
||||
webRtcInteractor.updatePhoneState(LockManager.PhoneState.IDLE);
|
||||
|
||||
if (terminateVideo) {
|
||||
WebRtcVideoUtil.deinitializeVideo(currentState);
|
||||
}
|
||||
|
||||
GroupCallSafetyNumberChangeNotificationUtil.cancelNotification(context, currentState.getCallInfoState().getCallRecipient());
|
||||
|
||||
return new WebRtcServiceState(new IdleActionProcessor(webRtcInteractor));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
package org.thoughtcrime.securesms.service.webrtc
|
||||
|
||||
import org.signal.ringrtc.CallManager
|
||||
import org.thoughtcrime.securesms.groups.GroupId
|
||||
import org.thoughtcrime.securesms.recipients.RecipientId
|
||||
import java.util.UUID
|
||||
|
||||
data class GroupCallRingCheckInfo(
|
||||
val recipientId: RecipientId,
|
||||
val groupId: GroupId.V2,
|
||||
val ringId: Long,
|
||||
val ringerUuid: UUID,
|
||||
val ringUpdate: CallManager.RingUpdate
|
||||
)
|
||||
@@ -1,5 +1,7 @@
|
||||
package org.thoughtcrime.securesms.service.webrtc;
|
||||
|
||||
import static org.thoughtcrime.securesms.webrtc.CallNotificationBuilder.TYPE_ESTABLISHED;
|
||||
|
||||
import android.os.ResultReceiver;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
@@ -12,11 +14,10 @@ import org.thoughtcrime.securesms.events.WebRtcViewModel;
|
||||
import org.thoughtcrime.securesms.ringrtc.Camera;
|
||||
import org.thoughtcrime.securesms.service.webrtc.state.WebRtcServiceState;
|
||||
import org.thoughtcrime.securesms.service.webrtc.state.WebRtcServiceStateBuilder;
|
||||
import org.thoughtcrime.securesms.util.FeatureFlags;
|
||||
import org.thoughtcrime.securesms.util.NetworkUtil;
|
||||
import org.thoughtcrime.securesms.webrtc.locks.LockManager;
|
||||
|
||||
import static org.thoughtcrime.securesms.webrtc.CallNotificationBuilder.TYPE_ESTABLISHED;
|
||||
|
||||
/**
|
||||
* Process actions to go from lobby to a joined call.
|
||||
*/
|
||||
@@ -81,6 +82,14 @@ public class GroupJoiningActionProcessor extends GroupActionProcessor {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
|
||||
if (FeatureFlags.groupCallRinging() && currentState.getCallSetupState().shouldRingGroup()) {
|
||||
try {
|
||||
groupCall.ringAll();
|
||||
} catch (CallException e) {
|
||||
return groupCallFailure(currentState, "Unable to ring group", e);
|
||||
}
|
||||
}
|
||||
|
||||
builder.changeCallInfoState()
|
||||
.callState(WebRtcViewModel.State.CALL_CONNECTED)
|
||||
.groupCallState(WebRtcViewModel.GroupCallState.CONNECTED_AND_JOINED)
|
||||
@@ -89,8 +98,7 @@ public class GroupJoiningActionProcessor extends GroupActionProcessor {
|
||||
.changeLocalDeviceState()
|
||||
.wantsBluetooth(true)
|
||||
.commit()
|
||||
.actionProcessor(new GroupConnectedActionProcessor(webRtcInteractor))
|
||||
.build();
|
||||
.actionProcessor(new GroupConnectedActionProcessor(webRtcInteractor));
|
||||
} else if (device.getJoinState() == GroupCall.JoinState.JOINING) {
|
||||
builder.changeCallInfoState()
|
||||
.groupCallState(WebRtcViewModel.GroupCallState.CONNECTED_AND_JOINING)
|
||||
|
||||
@@ -3,15 +3,17 @@ package org.thoughtcrime.securesms.service.webrtc;
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import org.signal.core.util.logging.Log;
|
||||
import org.thoughtcrime.securesms.components.webrtc.BroadcastVideoSink;
|
||||
import org.signal.ringrtc.CallException;
|
||||
import org.signal.ringrtc.CallManager;
|
||||
import org.thoughtcrime.securesms.database.DatabaseFactory;
|
||||
import org.thoughtcrime.securesms.events.WebRtcViewModel;
|
||||
import org.thoughtcrime.securesms.ringrtc.Camera;
|
||||
import org.thoughtcrime.securesms.groups.GroupId;
|
||||
import org.thoughtcrime.securesms.ringrtc.RemotePeer;
|
||||
import org.thoughtcrime.securesms.service.webrtc.state.WebRtcServiceState;
|
||||
import org.webrtc.CapturerObserver;
|
||||
import org.webrtc.VideoFrame;
|
||||
import org.whispersystems.signalservice.api.messages.calls.OfferMessage;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
* Action handler for when the system is at rest. Mainly responsible
|
||||
* for starting pre-call state, starting an outgoing call, or receiving an
|
||||
@@ -66,4 +68,59 @@ public class IdleActionProcessor extends WebRtcActionProcessor {
|
||||
return isGroupCall ? currentState.getActionProcessor().handlePreJoinCall(currentState, remotePeer)
|
||||
: currentState;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected @NonNull WebRtcServiceState handleGroupCallRingUpdate(@NonNull WebRtcServiceState currentState,
|
||||
@NonNull RemotePeer remotePeerGroup,
|
||||
@NonNull GroupId.V2 groupId,
|
||||
long ringId,
|
||||
@NonNull UUID uuid,
|
||||
@NonNull CallManager.RingUpdate ringUpdate)
|
||||
{
|
||||
Log.i(TAG, "handleGroupCallRingUpdate(): recipient: " + remotePeerGroup.getId() + " ring: " + ringId + " update: " + ringUpdate);
|
||||
|
||||
if (ringUpdate != CallManager.RingUpdate.REQUESTED) {
|
||||
DatabaseFactory.getGroupCallRingDatabase(context).insertOrUpdateGroupRing(ringId, System.currentTimeMillis(), ringUpdate);
|
||||
return currentState;
|
||||
} else if (DatabaseFactory.getGroupCallRingDatabase(context).isCancelled(ringId)) {
|
||||
try {
|
||||
Log.i(TAG, "Incoming ring request for already cancelled ring: " + ringId);
|
||||
webRtcInteractor.getCallManager().cancelGroupRing(groupId.getDecodedId(), ringId, null);
|
||||
} catch (CallException e) {
|
||||
Log.w(TAG, "Error while trying to cancel ring: " + ringId, e);
|
||||
}
|
||||
return currentState;
|
||||
}
|
||||
|
||||
webRtcInteractor.peekGroupCallForRingingCheck(new GroupCallRingCheckInfo(remotePeerGroup.getId(), groupId, ringId, uuid, ringUpdate));
|
||||
|
||||
return currentState;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected @NonNull WebRtcServiceState handleReceivedGroupCallPeekForRingingCheck(@NonNull WebRtcServiceState currentState, @NonNull GroupCallRingCheckInfo info, long deviceCount) {
|
||||
Log.i(tag, "handleReceivedGroupCallPeekForRingingCheck(): recipient: " + info.getRecipientId() + " ring: " + info.getRingId() + " deviceCount: " + deviceCount);
|
||||
|
||||
if (DatabaseFactory.getGroupCallRingDatabase(context).isCancelled(info.getRingId())) {
|
||||
try {
|
||||
Log.i(TAG, "Ring was cancelled while getting peek info ring: " + info.getRingId());
|
||||
webRtcInteractor.getCallManager().cancelGroupRing(info.getGroupId().getDecodedId(), info.getRingId(), null);
|
||||
} catch (CallException e) {
|
||||
Log.w(TAG, "Error while trying to cancel ring: " + info.getRingId(), e);
|
||||
}
|
||||
return currentState;
|
||||
}
|
||||
|
||||
if (deviceCount == 0) {
|
||||
Log.i(TAG, "No one in the group call, mark as expired and do not ring");
|
||||
DatabaseFactory.getGroupCallRingDatabase(context).insertOrUpdateGroupRing(info.getRingId(), System.currentTimeMillis(), CallManager.RingUpdate.EXPIRED_REQUEST);
|
||||
return currentState;
|
||||
}
|
||||
|
||||
currentState = currentState.builder()
|
||||
.actionProcessor(new IncomingGroupCallActionProcessor(webRtcInteractor))
|
||||
.build();
|
||||
|
||||
return currentState.getActionProcessor().handleGroupCallRingUpdate(currentState, new RemotePeer(info.getRecipientId()), info.getGroupId(), info.getRingId(), info.getRingerUuid(), info.getRingUpdate());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,258 @@
|
||||
package org.thoughtcrime.securesms.service.webrtc;
|
||||
|
||||
import android.media.AudioManager;
|
||||
import android.net.Uri;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import org.signal.core.util.logging.Log;
|
||||
import org.signal.ringrtc.CallException;
|
||||
import org.signal.ringrtc.CallManager;
|
||||
import org.signal.ringrtc.GroupCall;
|
||||
import org.thoughtcrime.securesms.components.webrtc.BroadcastVideoSink;
|
||||
import org.thoughtcrime.securesms.database.DatabaseFactory;
|
||||
import org.thoughtcrime.securesms.database.RecipientDatabase;
|
||||
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies;
|
||||
import org.thoughtcrime.securesms.events.CallParticipant;
|
||||
import org.thoughtcrime.securesms.events.CallParticipantId;
|
||||
import org.thoughtcrime.securesms.events.WebRtcViewModel;
|
||||
import org.thoughtcrime.securesms.groups.GroupId;
|
||||
import org.thoughtcrime.securesms.keyvalue.SignalStore;
|
||||
import org.thoughtcrime.securesms.notifications.DoNotDisturbUtil;
|
||||
import org.thoughtcrime.securesms.recipients.Recipient;
|
||||
import org.thoughtcrime.securesms.ringrtc.RemotePeer;
|
||||
import org.thoughtcrime.securesms.service.webrtc.state.WebRtcServiceState;
|
||||
import org.thoughtcrime.securesms.util.NetworkUtil;
|
||||
import org.thoughtcrime.securesms.util.ServiceUtil;
|
||||
import org.thoughtcrime.securesms.webrtc.locks.LockManager;
|
||||
import org.whispersystems.libsignal.util.guava.Optional;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
import static org.thoughtcrime.securesms.webrtc.CallNotificationBuilder.TYPE_INCOMING_CONNECTING;
|
||||
import static org.thoughtcrime.securesms.webrtc.CallNotificationBuilder.TYPE_INCOMING_RINGING;
|
||||
|
||||
/**
|
||||
* Process actions to go from incoming "ringing" group call to joining. By the time this processor
|
||||
* is running, the group call to ring has been verified to have at least one active device.
|
||||
*/
|
||||
public final class IncomingGroupCallActionProcessor extends DeviceAwareActionProcessor {
|
||||
|
||||
private static final String TAG = Log.tag(IncomingGroupCallActionProcessor.class);
|
||||
|
||||
public IncomingGroupCallActionProcessor(WebRtcInteractor webRtcInteractor) {
|
||||
super(webRtcInteractor, TAG);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected @NonNull WebRtcServiceState handleGroupCallRingUpdate(@NonNull WebRtcServiceState currentState,
|
||||
@NonNull RemotePeer remotePeerGroup,
|
||||
@NonNull GroupId.V2 groupId,
|
||||
long ringId,
|
||||
@NonNull UUID uuid,
|
||||
@NonNull CallManager.RingUpdate ringUpdate)
|
||||
{
|
||||
Log.i(TAG, "handleGroupCallRingUpdate(): recipient: " + remotePeerGroup.getId() + " ring: " + ringId + " update: " + ringUpdate);
|
||||
|
||||
Recipient recipient = remotePeerGroup.getRecipient();
|
||||
boolean updateForCurrentRingId = ringId == currentState.getCallSetupState().getRingId();
|
||||
boolean isCurrentlyRinging = currentState.getCallInfoState().getGroupCallState().isRinging();
|
||||
|
||||
if (DatabaseFactory.getGroupCallRingDatabase(context).isCancelled(ringId)) {
|
||||
try {
|
||||
Log.i(TAG, "Ignoring incoming ring request for already cancelled ring: " + ringId);
|
||||
webRtcInteractor.getCallManager().cancelGroupRing(groupId.getDecodedId(), ringId, null);
|
||||
} catch (CallException e) {
|
||||
Log.w(TAG, "Error while trying to cancel ring: " + ringId, e);
|
||||
}
|
||||
return currentState;
|
||||
}
|
||||
|
||||
if (ringUpdate != CallManager.RingUpdate.REQUESTED) {
|
||||
DatabaseFactory.getGroupCallRingDatabase(context).insertOrUpdateGroupRing(ringId, System.currentTimeMillis(), ringUpdate);
|
||||
|
||||
if (updateForCurrentRingId && isCurrentlyRinging) {
|
||||
Log.i(TAG, "Cancelling current ring: " + ringId);
|
||||
|
||||
currentState = currentState.builder()
|
||||
.changeCallInfoState()
|
||||
.callState(WebRtcViewModel.State.CALL_DISCONNECTED)
|
||||
.build();
|
||||
|
||||
webRtcInteractor.postStateUpdate(currentState);
|
||||
|
||||
return terminateGroupCall(currentState);
|
||||
} else {
|
||||
return currentState;
|
||||
}
|
||||
}
|
||||
|
||||
if (!updateForCurrentRingId && isCurrentlyRinging) {
|
||||
try {
|
||||
Log.i(TAG, "Already ringing so reply busy for new ring: " + ringId);
|
||||
webRtcInteractor.getCallManager().cancelGroupRing(groupId.getDecodedId(), ringId, CallManager.RingCancelReason.Busy);
|
||||
} catch (CallException e) {
|
||||
Log.w(TAG, "Error while trying to cancel ring: " + ringId, e);
|
||||
}
|
||||
return currentState;
|
||||
}
|
||||
|
||||
if (updateForCurrentRingId) {
|
||||
Log.i(TAG, "Already ringing for ring: " + ringId);
|
||||
return currentState;
|
||||
}
|
||||
|
||||
Log.i(TAG, "Requesting new ring: " + ringId);
|
||||
|
||||
DatabaseFactory.getGroupCallRingDatabase(context).insertGroupRing(ringId, System.currentTimeMillis(), ringUpdate);
|
||||
|
||||
currentState = WebRtcVideoUtil.initializeVideo(context, webRtcInteractor.getCameraEventListener(), currentState);
|
||||
|
||||
AudioManager androidAudioManager = ServiceUtil.getAudioManager(context);
|
||||
androidAudioManager.setSpeakerphoneOn(false);
|
||||
|
||||
webRtcInteractor.setCallInProgressNotification(TYPE_INCOMING_RINGING, remotePeerGroup);
|
||||
webRtcInteractor.updatePhoneState(LockManager.PhoneState.INTERACTIVE);
|
||||
|
||||
boolean shouldDisturbUserWithCall = DoNotDisturbUtil.shouldDisturbUserWithCall(context.getApplicationContext());
|
||||
if (shouldDisturbUserWithCall) {
|
||||
boolean started = webRtcInteractor.startWebRtcCallActivityIfPossible();
|
||||
if (!started) {
|
||||
Log.i(TAG, "Unable to start call activity due to OS version or not being in the foreground");
|
||||
ApplicationDependencies.getAppForegroundObserver().addListener(webRtcInteractor.getForegroundListener());
|
||||
}
|
||||
}
|
||||
|
||||
webRtcInteractor.initializeAudioForCall();
|
||||
if (shouldDisturbUserWithCall && SignalStore.settings().isCallNotificationsEnabled()) {
|
||||
Uri ringtone = recipient.resolve().getCallRingtone();
|
||||
RecipientDatabase.VibrateState vibrateState = recipient.resolve().getCallVibrate();
|
||||
|
||||
if (ringtone == null) {
|
||||
ringtone = SignalStore.settings().getCallRingtone();
|
||||
}
|
||||
|
||||
webRtcInteractor.startIncomingRinger(ringtone, vibrateState == RecipientDatabase.VibrateState.ENABLED || (vibrateState == RecipientDatabase.VibrateState.DEFAULT && SignalStore.settings().isCallVibrateEnabled()));
|
||||
}
|
||||
|
||||
webRtcInteractor.setCallInProgressNotification(TYPE_INCOMING_RINGING, remotePeerGroup);
|
||||
webRtcInteractor.registerPowerButtonReceiver();
|
||||
|
||||
return currentState.builder()
|
||||
.changeCallSetupState()
|
||||
.isRemoteVideoOffer(true)
|
||||
.ringId(ringId)
|
||||
.ringerRecipient(Recipient.externalPush(context, uuid, null, false))
|
||||
.commit()
|
||||
.changeCallInfoState()
|
||||
.callRecipient(remotePeerGroup.getRecipient())
|
||||
.callState(WebRtcViewModel.State.CALL_INCOMING)
|
||||
.groupCallState(WebRtcViewModel.GroupCallState.RINGING)
|
||||
.putParticipant(remotePeerGroup.getRecipient(),
|
||||
CallParticipant.createRemote(new CallParticipantId(remotePeerGroup.getRecipient()),
|
||||
remotePeerGroup.getRecipient(),
|
||||
null,
|
||||
new BroadcastVideoSink(currentState.getVideoState().getLockableEglBase(),
|
||||
false,
|
||||
true,
|
||||
currentState.getLocalDeviceState().getOrientation().getDegrees()),
|
||||
true,
|
||||
false,
|
||||
0,
|
||||
true,
|
||||
0,
|
||||
false,
|
||||
CallParticipant.DeviceOrdinal.PRIMARY
|
||||
))
|
||||
.build();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected @NonNull WebRtcServiceState handleAcceptCall(@NonNull WebRtcServiceState currentState, boolean answerWithVideo) {
|
||||
byte[] groupId = currentState.getCallInfoState().getCallRecipient().requireGroupId().getDecodedId();
|
||||
GroupCall groupCall = webRtcInteractor.getCallManager().createGroupCall(groupId,
|
||||
SignalStore.internalValues().groupCallingServer(),
|
||||
currentState.getVideoState().getLockableEglBase().require(),
|
||||
webRtcInteractor.getGroupCallObserver());
|
||||
|
||||
try {
|
||||
groupCall.setOutgoingAudioMuted(true);
|
||||
groupCall.setOutgoingVideoMuted(true);
|
||||
groupCall.setBandwidthMode(NetworkUtil.getCallingBandwidthMode(context));
|
||||
|
||||
Log.i(TAG, "Connecting to group call: " + currentState.getCallInfoState().getCallRecipient().getId());
|
||||
groupCall.connect();
|
||||
} catch (CallException e) {
|
||||
return groupCallFailure(currentState, "Unable to connect to group call", e);
|
||||
}
|
||||
|
||||
currentState = currentState.builder()
|
||||
.changeCallInfoState()
|
||||
.groupCall(groupCall)
|
||||
.groupCallState(WebRtcViewModel.GroupCallState.DISCONNECTED)
|
||||
.commit()
|
||||
.changeCallSetupState()
|
||||
.enableVideoOnCreate(answerWithVideo)
|
||||
.build();
|
||||
|
||||
AudioManager androidAudioManager = ServiceUtil.getAudioManager(context);
|
||||
androidAudioManager.setSpeakerphoneOn(false);
|
||||
|
||||
webRtcInteractor.updatePhoneState(WebRtcUtil.getInCallPhoneState(context));
|
||||
webRtcInteractor.initializeAudioForCall();
|
||||
webRtcInteractor.setCallInProgressNotification(TYPE_INCOMING_CONNECTING, currentState.getCallInfoState().getCallRecipient());
|
||||
webRtcInteractor.setWantsBluetoothConnection(true);
|
||||
|
||||
try {
|
||||
groupCall.setOutgoingVideoSource(currentState.getVideoState().requireLocalSink(), currentState.getVideoState().requireCamera());
|
||||
groupCall.setOutgoingVideoMuted(answerWithVideo);
|
||||
groupCall.setOutgoingAudioMuted(!currentState.getLocalDeviceState().isMicrophoneEnabled());
|
||||
groupCall.setBandwidthMode(NetworkUtil.getCallingBandwidthMode(context));
|
||||
|
||||
groupCall.join();
|
||||
} catch (CallException e) {
|
||||
return groupCallFailure(currentState, "Unable to join group call", e);
|
||||
}
|
||||
|
||||
return currentState.builder()
|
||||
.actionProcessor(new GroupJoiningActionProcessor(webRtcInteractor))
|
||||
.changeCallInfoState()
|
||||
.callState(WebRtcViewModel.State.CALL_OUTGOING)
|
||||
.groupCallState(WebRtcViewModel.GroupCallState.CONNECTED_AND_JOINING)
|
||||
.commit()
|
||||
.changeLocalDeviceState()
|
||||
.wantsBluetooth(true)
|
||||
.build();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected @NonNull WebRtcServiceState handleDenyCall(@NonNull WebRtcServiceState currentState) {
|
||||
Recipient recipient = currentState.getCallInfoState().getCallRecipient();
|
||||
Optional<GroupId> groupId = recipient.getGroupId();
|
||||
long ringId = currentState.getCallSetupState().getRingId();
|
||||
|
||||
DatabaseFactory.getGroupCallRingDatabase(context).insertOrUpdateGroupRing(ringId,
|
||||
System.currentTimeMillis(),
|
||||
CallManager.RingUpdate.DECLINED_ON_ANOTHER_DEVICE);
|
||||
|
||||
try {
|
||||
webRtcInteractor.getCallManager().cancelGroupRing(groupId.get().getDecodedId(),
|
||||
ringId,
|
||||
CallManager.RingCancelReason.DeclinedByUser);
|
||||
} catch (CallException e) {
|
||||
Log.w(TAG, "Error while trying to cancel ring " + ringId, e);
|
||||
}
|
||||
|
||||
webRtcInteractor.updatePhoneState(LockManager.PhoneState.PROCESSING);
|
||||
webRtcInteractor.stopAudio(false);
|
||||
webRtcInteractor.setWantsBluetoothConnection(false);
|
||||
webRtcInteractor.updatePhoneState(LockManager.PhoneState.IDLE);
|
||||
webRtcInteractor.stopForegroundService();
|
||||
|
||||
return WebRtcVideoUtil.deinitializeVideo(currentState)
|
||||
.builder()
|
||||
.actionProcessor(new IdleActionProcessor(webRtcInteractor))
|
||||
.terminate()
|
||||
.build();
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,11 @@
|
||||
package org.thoughtcrime.securesms.service.webrtc;
|
||||
|
||||
import static org.thoughtcrime.securesms.events.WebRtcViewModel.GroupCallState.IDLE;
|
||||
import static org.thoughtcrime.securesms.events.WebRtcViewModel.State.CALL_INCOMING;
|
||||
import static org.thoughtcrime.securesms.events.WebRtcViewModel.State.NETWORK_FAILURE;
|
||||
import static org.thoughtcrime.securesms.events.WebRtcViewModel.State.NO_SUCH_USER;
|
||||
import static org.thoughtcrime.securesms.events.WebRtcViewModel.State.UNTRUSTED_IDENTITY;
|
||||
|
||||
import android.app.Application;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
@@ -21,10 +27,13 @@ import org.signal.ringrtc.GroupCall;
|
||||
import org.signal.ringrtc.HttpHeader;
|
||||
import org.signal.ringrtc.Remote;
|
||||
import org.signal.storageservice.protos.groups.GroupExternalCredential;
|
||||
import org.signal.zkgroup.InvalidInputException;
|
||||
import org.signal.zkgroup.VerificationFailedException;
|
||||
import org.signal.zkgroup.groups.GroupIdentifier;
|
||||
import org.thoughtcrime.securesms.WebRtcCallActivity;
|
||||
import org.thoughtcrime.securesms.crypto.UnidentifiedAccessUtil;
|
||||
import org.thoughtcrime.securesms.database.DatabaseFactory;
|
||||
import org.thoughtcrime.securesms.database.GroupDatabase;
|
||||
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies;
|
||||
import org.thoughtcrime.securesms.events.GroupCallPeekEvent;
|
||||
import org.thoughtcrime.securesms.events.WebRtcViewModel;
|
||||
@@ -33,6 +42,7 @@ import org.thoughtcrime.securesms.groups.GroupManager;
|
||||
import org.thoughtcrime.securesms.jobs.GroupCallUpdateSendJob;
|
||||
import org.thoughtcrime.securesms.jobs.RetrieveProfileJob;
|
||||
import org.thoughtcrime.securesms.keyvalue.SignalStore;
|
||||
import org.thoughtcrime.securesms.messages.GroupSendUtil;
|
||||
import org.thoughtcrime.securesms.recipients.Recipient;
|
||||
import org.thoughtcrime.securesms.recipients.RecipientId;
|
||||
import org.thoughtcrime.securesms.recipients.RecipientUtil;
|
||||
@@ -42,7 +52,9 @@ import org.thoughtcrime.securesms.ringrtc.RemotePeer;
|
||||
import org.thoughtcrime.securesms.service.webrtc.state.WebRtcServiceState;
|
||||
import org.thoughtcrime.securesms.util.AppForegroundObserver;
|
||||
import org.thoughtcrime.securesms.util.BubbleUtil;
|
||||
import org.thoughtcrime.securesms.util.RecipientAccessList;
|
||||
import org.thoughtcrime.securesms.util.TextSecurePreferences;
|
||||
import org.thoughtcrime.securesms.util.Util;
|
||||
import org.thoughtcrime.securesms.webrtc.audio.SignalAudioManager;
|
||||
import org.thoughtcrime.securesms.webrtc.locks.LockManager;
|
||||
import org.webrtc.PeerConnection;
|
||||
@@ -51,6 +63,7 @@ import org.whispersystems.libsignal.util.guava.Optional;
|
||||
import org.whispersystems.signalservice.api.SignalServiceAccountManager;
|
||||
import org.whispersystems.signalservice.api.SignalServiceMessageSender;
|
||||
import org.whispersystems.signalservice.api.crypto.UntrustedIdentityException;
|
||||
import org.whispersystems.signalservice.api.messages.SendMessageResult;
|
||||
import org.whispersystems.signalservice.api.messages.calls.CallingResponse;
|
||||
import org.whispersystems.signalservice.api.messages.calls.OfferMessage;
|
||||
import org.whispersystems.signalservice.api.messages.calls.OpaqueMessage;
|
||||
@@ -63,16 +76,12 @@ import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.Executor;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Executors;
|
||||
|
||||
import static org.thoughtcrime.securesms.events.WebRtcViewModel.GroupCallState.IDLE;
|
||||
import static org.thoughtcrime.securesms.events.WebRtcViewModel.State.CALL_INCOMING;
|
||||
import static org.thoughtcrime.securesms.events.WebRtcViewModel.State.NETWORK_FAILURE;
|
||||
import static org.thoughtcrime.securesms.events.WebRtcViewModel.State.NO_SUCH_USER;
|
||||
import static org.thoughtcrime.securesms.events.WebRtcViewModel.State.UNTRUSTED_IDENTITY;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* Entry point for all things calling. Lives for the life of the app instance and will spin up a foreground service when needed to
|
||||
@@ -94,6 +103,7 @@ public final class SignalCallManager implements CallManager.Observer, GroupCall.
|
||||
private final LockManager lockManager;
|
||||
|
||||
private WebRtcServiceState serviceState;
|
||||
private boolean needsToSetSelfUuid = true;
|
||||
|
||||
public SignalCallManager(@NonNull Application application) {
|
||||
this.context = application.getApplicationContext();
|
||||
@@ -136,6 +146,15 @@ public final class SignalCallManager implements CallManager.Observer, GroupCall.
|
||||
}
|
||||
|
||||
serviceExecutor.execute(() -> {
|
||||
if (needsToSetSelfUuid) {
|
||||
try {
|
||||
callManager.setSelfUuid(Recipient.self().requireUuid());
|
||||
needsToSetSelfUuid = false;
|
||||
} catch (CallException e) {
|
||||
Log.w(TAG, "Unable to set self UUID on CallManager", e);
|
||||
}
|
||||
}
|
||||
|
||||
Log.v(TAG, "Processing action, handler: " + serviceState.getActionProcessor().getTag());
|
||||
WebRtcServiceState previous = serviceState;
|
||||
serviceState = action.process(previous, previous.getActionProcessor());
|
||||
@@ -270,6 +289,14 @@ public final class SignalCallManager implements CallManager.Observer, GroupCall.
|
||||
process((s, p) -> p.handleReceivedOpaqueMessage(s, opaqueMessageMetadata));
|
||||
}
|
||||
|
||||
public void setRingGroup(boolean ringGroup) {
|
||||
process((s, p) -> p.handleSetRingGroup(s, ringGroup));
|
||||
}
|
||||
|
||||
private void receivedGroupCallPeekForRingingCheck(@NonNull GroupCallRingCheckInfo groupCallRingCheckInfo, long deviceCount) {
|
||||
process((s, p) -> p.handleReceivedGroupCallPeekForRingingCheck(s, groupCallRingCheckInfo, deviceCount));
|
||||
}
|
||||
|
||||
public void peekGroupCall(@NonNull RecipientId id) {
|
||||
if (callManager == null) {
|
||||
Log.i(TAG, "Unable to peekGroupCall, call manager is null");
|
||||
@@ -307,6 +334,33 @@ public final class SignalCallManager implements CallManager.Observer, GroupCall.
|
||||
});
|
||||
}
|
||||
|
||||
public void peekGroupCallForRingingCheck(@NonNull GroupCallRingCheckInfo info) {
|
||||
if (callManager == null) {
|
||||
Log.i(TAG, "Unable to peekGroupCall, call manager is null");
|
||||
return;
|
||||
}
|
||||
|
||||
networkExecutor.execute(() -> {
|
||||
try {
|
||||
Recipient group = Recipient.resolved(info.getRecipientId());
|
||||
GroupId.V2 groupId = group.requireGroupId().requireV2();
|
||||
GroupExternalCredential credential = GroupManager.getGroupExternalCredential(context, groupId);
|
||||
|
||||
List<GroupCall.GroupMemberInfo> members = GroupManager.getUuidCipherTexts(context, groupId)
|
||||
.entrySet()
|
||||
.stream()
|
||||
.map(entry -> new GroupCall.GroupMemberInfo(entry.getKey(), entry.getValue().serialize()))
|
||||
.collect(Collectors.toList());
|
||||
|
||||
callManager.peekGroupCall(SignalStore.internalValues().groupCallingServer(), credential.getTokenBytes().toByteArray(), members, peekInfo -> {
|
||||
receivedGroupCallPeekForRingingCheck(info, peekInfo.getDeviceCount());
|
||||
});
|
||||
} catch (IOException | VerificationFailedException | CallException e) {
|
||||
Log.e(TAG, "error peeking for ringing check", e);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public boolean startCallCardActivityIfPossible() {
|
||||
if (Build.VERSION.SDK_INT >= 29 && !ApplicationDependencies.getAppForegroundObserver().isForegrounded()) {
|
||||
return false;
|
||||
@@ -535,7 +589,7 @@ public final class SignalCallManager implements CallManager.Observer, GroupCall.
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSendCallMessage(@NonNull final UUID uuid, @NonNull final byte[] bytes) {
|
||||
public void onSendCallMessage(@NonNull UUID uuid, @NonNull byte[] bytes, @NonNull CallManager.CallMessageUrgency unused) {
|
||||
Log.i(TAG, "onSendCallMessage():");
|
||||
|
||||
OpaqueMessage opaqueMessage = new OpaqueMessage(bytes);
|
||||
@@ -553,10 +607,49 @@ public final class SignalCallManager implements CallManager.Observer, GroupCall.
|
||||
} catch (UntrustedIdentityException e) {
|
||||
Log.i(TAG, "sendOpaqueCallMessage onFailure: ", e);
|
||||
RetrieveProfileJob.enqueue(recipient.getId());
|
||||
process((s, p) -> p.handleGroupMessageSentError(s, new RemotePeer(recipient.getId()), UNTRUSTED_IDENTITY, Optional.fromNullable(e.getIdentityKey())));
|
||||
process((s, p) -> p.handleGroupMessageSentError(s, Collections.singletonList(recipient.getId()), UNTRUSTED_IDENTITY));
|
||||
} catch (IOException e) {
|
||||
Log.i(TAG, "sendOpaqueCallMessage onFailure: ", e);
|
||||
process((s, p) -> p.handleGroupMessageSentError(s, new RemotePeer(recipient.getId()), NETWORK_FAILURE, Optional.absent()));
|
||||
process((s, p) -> p.handleGroupMessageSentError(s, Collections.singletonList(recipient.getId()), NETWORK_FAILURE));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSendCallMessageToGroup(@NonNull byte[] groupIdBytes, @NonNull byte[] message, @NonNull CallManager.CallMessageUrgency unused) {
|
||||
Log.i(TAG, "onSendCallMessageToGroup():");
|
||||
|
||||
networkExecutor.execute(() -> {
|
||||
try {
|
||||
GroupId groupId = GroupId.v2(new GroupIdentifier(groupIdBytes));
|
||||
List<Recipient> recipients = DatabaseFactory.getGroupDatabase(context).getGroupMembers(groupId, GroupDatabase.MemberSet.FULL_MEMBERS_EXCLUDING_SELF);
|
||||
|
||||
recipients = RecipientUtil.getEligibleForSending((recipients.stream()
|
||||
.map(Recipient::resolve)
|
||||
.filter(r -> !r.isBlocked())
|
||||
.collect(Collectors.toList())));
|
||||
|
||||
OpaqueMessage opaqueMessage = new OpaqueMessage(message);
|
||||
SignalServiceCallMessage callMessage = SignalServiceCallMessage.forOutgoingGroupOpaque(groupId.getDecodedId(), System.currentTimeMillis(), opaqueMessage, true, null);
|
||||
RecipientAccessList accessList = new RecipientAccessList(recipients);
|
||||
|
||||
List<SendMessageResult> results = GroupSendUtil.sendCallMessage(context,
|
||||
groupId.requireV2(),
|
||||
recipients,
|
||||
callMessage);
|
||||
|
||||
Set<RecipientId> identifyFailureRecipientIds = results.stream()
|
||||
.filter(result -> result.getIdentityFailure() != null)
|
||||
.map(result -> accessList.requireIdByAddress(result.getAddress()))
|
||||
.collect(Collectors.toSet());
|
||||
|
||||
if (Util.hasItems(identifyFailureRecipientIds)) {
|
||||
process((s, p) -> p.handleGroupMessageSentError(s, identifyFailureRecipientIds, UNTRUSTED_IDENTITY));
|
||||
|
||||
RetrieveProfileJob.enqueue(identifyFailureRecipientIds);
|
||||
}
|
||||
} catch (UntrustedIdentityException | IOException | InvalidInputException e) {
|
||||
Log.w(TAG, "onSendCallMessageToGroup failed", e);
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -594,6 +687,22 @@ public final class SignalCallManager implements CallManager.Observer, GroupCall.
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onGroupCallRingUpdate(@NonNull byte[] groupIdBytes, long ringId, @NonNull UUID uuid, @NonNull CallManager.RingUpdate ringUpdate) {
|
||||
try {
|
||||
GroupId.V2 groupId = GroupId.v2(new GroupIdentifier(groupIdBytes));
|
||||
Optional<GroupDatabase.GroupRecord> group = DatabaseFactory.getGroupDatabase(context).getGroup(groupId);
|
||||
|
||||
if (group.isPresent()) {
|
||||
process((s, p) -> p.handleGroupCallRingUpdate(s, new RemotePeer(group.get().getRecipientId()), groupId, ringId, uuid, ringUpdate));
|
||||
} else {
|
||||
Log.w(TAG, "Unable to ring unknown group.");
|
||||
}
|
||||
} catch (InvalidInputException e) {
|
||||
Log.w(TAG, "Unable to ring group due to invalid group id", e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void requestMembershipProof(@NonNull final GroupCall groupCall) {
|
||||
Log.i(TAG, "requestMembershipProof():");
|
||||
@@ -662,8 +771,10 @@ public final class SignalCallManager implements CallManager.Observer, GroupCall.
|
||||
@Override
|
||||
public void onForeground() {
|
||||
process((s, p) -> {
|
||||
WebRtcViewModel.State callState = s.getCallInfoState().getCallState();
|
||||
if (callState == CALL_INCOMING && s.getCallInfoState().getGroupCallState() == IDLE) {
|
||||
WebRtcViewModel.State callState = s.getCallInfoState().getCallState();
|
||||
WebRtcViewModel.GroupCallState groupCallState = s.getCallInfoState().getGroupCallState();
|
||||
|
||||
if (callState == CALL_INCOMING && (groupCallState == IDLE || groupCallState.isRinging())) {
|
||||
startCallCardActivityIfPossible();
|
||||
}
|
||||
ApplicationDependencies.getAppForegroundObserver().removeListener(this);
|
||||
|
||||
@@ -1,5 +1,9 @@
|
||||
package org.thoughtcrime.securesms.service.webrtc;
|
||||
|
||||
import static org.thoughtcrime.securesms.service.webrtc.WebRtcData.AnswerMetadata;
|
||||
import static org.thoughtcrime.securesms.service.webrtc.WebRtcData.HangupMetadata;
|
||||
import static org.thoughtcrime.securesms.service.webrtc.WebRtcData.ReceivedAnswerMetadata;
|
||||
|
||||
import android.content.Context;
|
||||
import android.os.ResultReceiver;
|
||||
|
||||
@@ -10,14 +14,18 @@ import org.signal.core.util.logging.Log;
|
||||
import org.signal.ringrtc.CallException;
|
||||
import org.signal.ringrtc.CallId;
|
||||
import org.signal.ringrtc.CallManager;
|
||||
import org.signal.ringrtc.CallManager.RingUpdate;
|
||||
import org.signal.ringrtc.GroupCall;
|
||||
import org.thoughtcrime.securesms.R;
|
||||
import org.thoughtcrime.securesms.components.sensors.Orientation;
|
||||
import org.thoughtcrime.securesms.components.webrtc.BroadcastVideoSink;
|
||||
import org.thoughtcrime.securesms.components.webrtc.GroupCallSafetyNumberChangeNotificationUtil;
|
||||
import org.thoughtcrime.securesms.crypto.IdentityKeyUtil;
|
||||
import org.thoughtcrime.securesms.database.DatabaseFactory;
|
||||
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies;
|
||||
import org.thoughtcrime.securesms.events.CallParticipant;
|
||||
import org.thoughtcrime.securesms.events.WebRtcViewModel;
|
||||
import org.thoughtcrime.securesms.groups.GroupId;
|
||||
import org.thoughtcrime.securesms.recipients.Recipient;
|
||||
import org.thoughtcrime.securesms.recipients.RecipientId;
|
||||
import org.thoughtcrime.securesms.recipients.RecipientUtil;
|
||||
import org.thoughtcrime.securesms.ringrtc.CallState;
|
||||
@@ -41,13 +49,10 @@ import org.whispersystems.signalservice.api.messages.calls.HangupMessage;
|
||||
import org.whispersystems.signalservice.api.messages.calls.OfferMessage;
|
||||
import org.whispersystems.signalservice.api.messages.calls.SignalServiceCallMessage;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
|
||||
import static org.thoughtcrime.securesms.service.webrtc.WebRtcData.AnswerMetadata;
|
||||
import static org.thoughtcrime.securesms.service.webrtc.WebRtcData.HangupMetadata;
|
||||
import static org.thoughtcrime.securesms.service.webrtc.WebRtcData.OpaqueMessageMetadata;
|
||||
import static org.thoughtcrime.securesms.service.webrtc.WebRtcData.ReceivedAnswerMetadata;
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
* Base WebRTC action processor and core of the calling state machine. As actions (as intents)
|
||||
@@ -616,9 +621,8 @@ public abstract class WebRtcActionProcessor {
|
||||
}
|
||||
|
||||
protected @NonNull WebRtcServiceState handleGroupMessageSentError(@NonNull WebRtcServiceState currentState,
|
||||
@NonNull RemotePeer remotePeer,
|
||||
@NonNull WebRtcViewModel.State errorCallState,
|
||||
@NonNull Optional<IdentityKey> identityKey)
|
||||
@NonNull Collection<RecipientId> recipientIds,
|
||||
@NonNull WebRtcViewModel.State errorCallState)
|
||||
{
|
||||
Log.i(tag, "handleGroupMessageSentError not processed");
|
||||
return currentState;
|
||||
@@ -631,11 +635,106 @@ public abstract class WebRtcActionProcessor {
|
||||
return currentState;
|
||||
}
|
||||
|
||||
//endregion
|
||||
protected @NonNull WebRtcServiceState handleReceivedOpaqueMessage(@NonNull WebRtcServiceState currentState, @NonNull WebRtcData.OpaqueMessageMetadata opaqueMessageMetadata) {
|
||||
Log.i(tag, "handleReceivedOpaqueMessage():");
|
||||
|
||||
protected @NonNull WebRtcServiceState handleReceivedOpaqueMessage(@NonNull WebRtcServiceState currentState, @NonNull OpaqueMessageMetadata opaqueMessageMetadata) {
|
||||
Log.i(tag, "handleReceivedOpaqueMessage not processed");
|
||||
try {
|
||||
webRtcInteractor.getCallManager().receivedCallMessage(opaqueMessageMetadata.getUuid(),
|
||||
opaqueMessageMetadata.getRemoteDeviceId(),
|
||||
1,
|
||||
opaqueMessageMetadata.getOpaque(),
|
||||
opaqueMessageMetadata.getMessageAgeSeconds());
|
||||
} catch (CallException e) {
|
||||
return groupCallFailure(currentState, "Unable to receive opaque message", e);
|
||||
}
|
||||
|
||||
return currentState;
|
||||
}
|
||||
|
||||
protected @NonNull WebRtcServiceState handleGroupCallRingUpdate(@NonNull WebRtcServiceState currentState,
|
||||
@NonNull RemotePeer remotePeerGroup,
|
||||
@NonNull GroupId.V2 groupId,
|
||||
long ringId,
|
||||
@NonNull UUID uuid,
|
||||
@NonNull RingUpdate ringUpdate)
|
||||
{
|
||||
Log.i(tag, "handleGroupCallRingUpdate(): recipient: " + remotePeerGroup.getId() + " ring: " + ringId + " update: " + ringUpdate);
|
||||
|
||||
try {
|
||||
if (ringUpdate != RingUpdate.BUSY_LOCALLY && ringUpdate != RingUpdate.BUSY_ON_ANOTHER_DEVICE) {
|
||||
webRtcInteractor.getCallManager().cancelGroupRing(groupId.getDecodedId(), ringId, CallManager.RingCancelReason.Busy);
|
||||
}
|
||||
DatabaseFactory.getGroupCallRingDatabase(context).insertOrUpdateGroupRing(ringId,
|
||||
System.currentTimeMillis(),
|
||||
ringUpdate == RingUpdate.REQUESTED ? RingUpdate.BUSY_LOCALLY : ringUpdate);
|
||||
} catch (CallException e) {
|
||||
Log.w(tag, "Unable to cancel ring", e);
|
||||
}
|
||||
return currentState;
|
||||
}
|
||||
|
||||
protected @NonNull WebRtcServiceState handleSetRingGroup(@NonNull WebRtcServiceState currentState, boolean ringGroup) {
|
||||
Log.i(tag, "handleSetRingGroup not processed");
|
||||
|
||||
return currentState;
|
||||
}
|
||||
|
||||
protected @NonNull WebRtcServiceState handleReceivedGroupCallPeekForRingingCheck(@NonNull WebRtcServiceState currentState, @NonNull GroupCallRingCheckInfo info, long deviceCount) {
|
||||
Log.i(tag, "handleReceivedGroupCallPeekForRingingCheck not processed");
|
||||
|
||||
return currentState;
|
||||
}
|
||||
|
||||
protected @NonNull WebRtcServiceState groupCallFailure(@NonNull WebRtcServiceState currentState, @NonNull String message, @NonNull Throwable error) {
|
||||
Log.w(tag, "groupCallFailure(): " + message, error);
|
||||
|
||||
GroupCall groupCall = currentState.getCallInfoState().getGroupCall();
|
||||
Recipient recipient = currentState.getCallInfoState().getCallRecipient();
|
||||
|
||||
if (recipient != null && currentState.getCallInfoState().getGroupCallState().isConnected()) {
|
||||
webRtcInteractor.sendGroupCallMessage(recipient, WebRtcUtil.getGroupCallEraId(groupCall));
|
||||
}
|
||||
|
||||
currentState = currentState.builder()
|
||||
.changeCallInfoState()
|
||||
.callState(WebRtcViewModel.State.CALL_DISCONNECTED)
|
||||
.groupCallState(WebRtcViewModel.GroupCallState.DISCONNECTED)
|
||||
.build();
|
||||
|
||||
webRtcInteractor.postStateUpdate(currentState);
|
||||
|
||||
try {
|
||||
if (groupCall != null) {
|
||||
groupCall.disconnect();
|
||||
}
|
||||
webRtcInteractor.getCallManager().reset();
|
||||
} catch (CallException e) {
|
||||
Log.w(tag, "Unable to reset call manager: ", e);
|
||||
}
|
||||
|
||||
return terminateGroupCall(currentState);
|
||||
}
|
||||
|
||||
protected synchronized @NonNull WebRtcServiceState terminateGroupCall(@NonNull WebRtcServiceState currentState) {
|
||||
return terminateGroupCall(currentState, true);
|
||||
}
|
||||
|
||||
protected synchronized @NonNull WebRtcServiceState terminateGroupCall(@NonNull WebRtcServiceState currentState, boolean terminateVideo) {
|
||||
webRtcInteractor.updatePhoneState(LockManager.PhoneState.PROCESSING);
|
||||
boolean playDisconnectSound = currentState.getCallInfoState().getCallState() == WebRtcViewModel.State.CALL_DISCONNECTED;
|
||||
webRtcInteractor.stopAudio(playDisconnectSound);
|
||||
webRtcInteractor.setWantsBluetoothConnection(false);
|
||||
webRtcInteractor.updatePhoneState(LockManager.PhoneState.IDLE);
|
||||
webRtcInteractor.stopForegroundService();
|
||||
|
||||
if (terminateVideo) {
|
||||
WebRtcVideoUtil.deinitializeVideo(currentState);
|
||||
}
|
||||
|
||||
GroupCallSafetyNumberChangeNotificationUtil.cancelNotification(context, currentState.getCallInfoState().getCallRecipient());
|
||||
|
||||
return new WebRtcServiceState(new IdleActionProcessor(webRtcInteractor));
|
||||
}
|
||||
|
||||
//endregion
|
||||
}
|
||||
|
||||
@@ -45,9 +45,9 @@ public class WebRtcInteractor {
|
||||
@NonNull GroupCall.Observer groupCallObserver,
|
||||
@NonNull AppForegroundObserver.Listener foregroundListener)
|
||||
{
|
||||
this.context = context;
|
||||
this.signalCallManager = signalCallManager;
|
||||
this.lockManager = lockManager;
|
||||
this.context = context;
|
||||
this.signalCallManager = signalCallManager;
|
||||
this.lockManager = lockManager;
|
||||
this.audioManager = audioManager;
|
||||
this.cameraEventListener = cameraEventListener;
|
||||
this.groupCallObserver = groupCallObserver;
|
||||
@@ -154,7 +154,7 @@ public class WebRtcInteractor {
|
||||
audioManager.startCommunication(preserveSpeakerphone);
|
||||
}
|
||||
|
||||
void peekGroupCall(@NonNull RecipientId recipientId) {
|
||||
signalCallManager.peekGroupCall(recipientId);
|
||||
void peekGroupCallForRingingCheck(@NonNull GroupCallRingCheckInfo groupCallRingCheckInfo) {
|
||||
signalCallManager.peekGroupCallForRingingCheck(groupCallRingCheckInfo);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,44 +0,0 @@
|
||||
package org.thoughtcrime.securesms.service.webrtc.state;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
/**
|
||||
* Information specific to setting up a call.
|
||||
*/
|
||||
public final class CallSetupState {
|
||||
boolean enableVideoOnCreate;
|
||||
boolean isRemoteVideoOffer;
|
||||
boolean acceptWithVideo;
|
||||
boolean sentJoinedMessage;
|
||||
|
||||
public CallSetupState() {
|
||||
this(false, false, false, false);
|
||||
}
|
||||
|
||||
public CallSetupState(@NonNull CallSetupState toCopy) {
|
||||
this(toCopy.enableVideoOnCreate, toCopy.isRemoteVideoOffer, toCopy.acceptWithVideo, toCopy.sentJoinedMessage);
|
||||
}
|
||||
|
||||
public CallSetupState(boolean enableVideoOnCreate, boolean isRemoteVideoOffer, boolean acceptWithVideo, boolean sentJoinedMessage) {
|
||||
this.enableVideoOnCreate = enableVideoOnCreate;
|
||||
this.isRemoteVideoOffer = isRemoteVideoOffer;
|
||||
this.acceptWithVideo = acceptWithVideo;
|
||||
this.sentJoinedMessage = sentJoinedMessage;
|
||||
}
|
||||
|
||||
public boolean isEnableVideoOnCreate() {
|
||||
return enableVideoOnCreate;
|
||||
}
|
||||
|
||||
public boolean isRemoteVideoOffer() {
|
||||
return isRemoteVideoOffer;
|
||||
}
|
||||
|
||||
public boolean isAcceptWithVideo() {
|
||||
return acceptWithVideo;
|
||||
}
|
||||
|
||||
public boolean hasSentJoinedMessage() {
|
||||
return sentJoinedMessage;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
package org.thoughtcrime.securesms.service.webrtc.state
|
||||
|
||||
import org.thoughtcrime.securesms.recipients.Recipient
|
||||
|
||||
/**
|
||||
* Information specific to setting up a call.
|
||||
*/
|
||||
data class CallSetupState(
|
||||
var isEnableVideoOnCreate: Boolean = false,
|
||||
var isRemoteVideoOffer: Boolean = false,
|
||||
var isAcceptWithVideo: Boolean = false,
|
||||
@get:JvmName("hasSentJoinedMessage") var sentJoinedMessage: Boolean = false,
|
||||
@get:JvmName("shouldRingGroup") var ringGroup: Boolean = true,
|
||||
var ringId: Long = NO_RING,
|
||||
var ringerRecipient: Recipient = Recipient.UNKNOWN
|
||||
) {
|
||||
|
||||
fun duplicate(): CallSetupState {
|
||||
return copy()
|
||||
}
|
||||
|
||||
companion object {
|
||||
const val NO_RING = 0L
|
||||
}
|
||||
}
|
||||
@@ -25,7 +25,7 @@ public final class WebRtcServiceState {
|
||||
|
||||
public WebRtcServiceState(@NonNull WebRtcServiceState toCopy) {
|
||||
this.actionProcessor = toCopy.actionProcessor;
|
||||
this.callSetupState = new CallSetupState(toCopy.callSetupState);
|
||||
this.callSetupState = toCopy.callSetupState.duplicate();
|
||||
this.callInfoState = new CallInfoState(toCopy.callInfoState);
|
||||
this.localDeviceState = new LocalDeviceState(toCopy.localDeviceState);
|
||||
this.videoState = new VideoState(toCopy.videoState);
|
||||
|
||||
@@ -126,7 +126,7 @@ public class WebRtcServiceStateBuilder {
|
||||
private CallSetupState toBuild;
|
||||
|
||||
public CallSetupStateBuilder() {
|
||||
toBuild = new CallSetupState(WebRtcServiceStateBuilder.this.toBuild.callSetupState);
|
||||
toBuild = WebRtcServiceStateBuilder.this.toBuild.callSetupState.duplicate();
|
||||
}
|
||||
|
||||
public @NonNull WebRtcServiceStateBuilder commit() {
|
||||
@@ -140,22 +140,37 @@ public class WebRtcServiceStateBuilder {
|
||||
}
|
||||
|
||||
public @NonNull CallSetupStateBuilder enableVideoOnCreate(boolean enableVideoOnCreate) {
|
||||
toBuild.enableVideoOnCreate = enableVideoOnCreate;
|
||||
toBuild.setEnableVideoOnCreate(enableVideoOnCreate);
|
||||
return this;
|
||||
}
|
||||
|
||||
public @NonNull CallSetupStateBuilder isRemoteVideoOffer(boolean isRemoteVideoOffer) {
|
||||
toBuild.isRemoteVideoOffer = isRemoteVideoOffer;
|
||||
toBuild.setRemoteVideoOffer(isRemoteVideoOffer);
|
||||
return this;
|
||||
}
|
||||
|
||||
public @NonNull CallSetupStateBuilder acceptWithVideo(boolean acceptWithVideo) {
|
||||
toBuild.acceptWithVideo = acceptWithVideo;
|
||||
toBuild.setAcceptWithVideo(acceptWithVideo);
|
||||
return this;
|
||||
}
|
||||
|
||||
public @NonNull CallSetupStateBuilder sentJoinedMessage(boolean sentJoinedMessage) {
|
||||
toBuild.sentJoinedMessage = sentJoinedMessage;
|
||||
toBuild.setSentJoinedMessage(sentJoinedMessage);
|
||||
return this;
|
||||
}
|
||||
|
||||
public @NonNull CallSetupStateBuilder setRingGroup(boolean ringGroup) {
|
||||
toBuild.setRingGroup(ringGroup);
|
||||
return this;
|
||||
}
|
||||
|
||||
public @NonNull CallSetupStateBuilder ringId(long ringId) {
|
||||
toBuild.setRingId(ringId);
|
||||
return this;
|
||||
}
|
||||
|
||||
public @NonNull CallSetupStateBuilder ringerRecipient(@NonNull Recipient ringerRecipient) {
|
||||
toBuild.setRingerRecipient(ringerRecipient);
|
||||
return this;
|
||||
}
|
||||
}
|
||||
@@ -270,8 +285,8 @@ public class WebRtcServiceStateBuilder {
|
||||
return this;
|
||||
}
|
||||
|
||||
public @NonNull CallInfoStateBuilder addIdentityChangedRecipient(@NonNull RecipientId id) {
|
||||
toBuild.identityChangedRecipients.add(id);
|
||||
public @NonNull CallInfoStateBuilder addIdentityChangedRecipients(@NonNull Collection<RecipientId> id) {
|
||||
toBuild.identityChangedRecipients.addAll(id);
|
||||
return this;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user