Better error handling for group calls.

This commit is contained in:
Cody Henthorne 2021-01-06 15:15:50 -05:00 committed by Alan Evans
parent 84f1da76ad
commit cf32b93269
13 changed files with 268 additions and 43 deletions

View file

@ -141,9 +141,9 @@ public class WebRtcCallActivity extends BaseActivity implements SafetyNumberChan
EventBus.getDefault().unregister(this); EventBus.getDefault().unregister(this);
} }
if (!viewModel.isCallingStarted()) { if (!viewModel.isCallStarting()) {
CallParticipantsState state = viewModel.getCallParticipantsState().getValue(); CallParticipantsState state = viewModel.getCallParticipantsState().getValue();
if (state != null && state.getCallState() == WebRtcViewModel.State.CALL_PRE_JOIN) { if (state != null && state.getCallState().isPreJoinOrNetworkUnavailable()) {
finish(); finish();
} }
} }
@ -156,9 +156,9 @@ public class WebRtcCallActivity extends BaseActivity implements SafetyNumberChan
EventBus.getDefault().unregister(this); EventBus.getDefault().unregister(this);
if (!viewModel.isCallingStarted()) { if (!viewModel.isCallStarting()) {
CallParticipantsState state = viewModel.getCallParticipantsState().getValue(); CallParticipantsState state = viewModel.getCallParticipantsState().getValue();
if (state != null && state.getCallState() == WebRtcViewModel.State.CALL_PRE_JOIN) { if (state != null && state.getCallState().isPreJoinOrNetworkUnavailable()) {
Intent intent = new Intent(this, WebRtcCallService.class); Intent intent = new Intent(this, WebRtcCallService.class);
intent.setAction(WebRtcCallService.ACTION_CANCEL_PRE_JOIN_CALL); intent.setAction(WebRtcCallService.ACTION_CANCEL_PRE_JOIN_CALL);
startService(intent); startService(intent);
@ -471,7 +471,6 @@ public class WebRtcCallActivity extends BaseActivity implements SafetyNumberChan
private void handleServerFailure() { private void handleServerFailure() {
EventBus.getDefault().removeStickyEvent(WebRtcViewModel.class); EventBus.getDefault().removeStickyEvent(WebRtcViewModel.class);
callScreen.setStatus(getString(R.string.RedPhone_network_failed)); callScreen.setStatus(getString(R.string.RedPhone_network_failed));
delayedFinish();
} }
private void handleNoSuchUser(final @NonNull WebRtcViewModel event) { private void handleNoSuchUser(final @NonNull WebRtcViewModel event) {
@ -529,7 +528,7 @@ public class WebRtcCallActivity extends BaseActivity implements SafetyNumberChan
.putExtra(WebRtcCallService.EXTRA_RECIPIENT_IDS, RecipientId.toSerializedList(changedRecipients)); .putExtra(WebRtcCallService.EXTRA_RECIPIENT_IDS, RecipientId.toSerializedList(changedRecipients));
startService(intent); startService(intent);
} else { } else {
startCall(state.getLocalParticipant().isVideoEnabled()); viewModel.startCall(state.getLocalParticipant().isVideoEnabled());
} }
} }
@ -540,7 +539,7 @@ public class WebRtcCallActivity extends BaseActivity implements SafetyNumberChan
public void onCanceled() { public void onCanceled() {
CallParticipantsState state = viewModel.getCallParticipantsState().getValue(); CallParticipantsState state = viewModel.getCallParticipantsState().getValue();
if (state != null && state.getGroupCallState().isNotIdle()) { if (state != null && state.getGroupCallState().isNotIdle()) {
if (state.getCallState() == WebRtcViewModel.State.CALL_PRE_JOIN) { if (state.getCallState().isPreJoinOrNetworkUnavailable()) {
Intent intent = new Intent(this, WebRtcCallService.class); Intent intent = new Intent(this, WebRtcCallService.class);
intent.setAction(WebRtcCallService.ACTION_CANCEL_PRE_JOIN_CALL); intent.setAction(WebRtcCallService.ACTION_CANCEL_PRE_JOIN_CALL);
startService(intent); startService(intent);

View file

@ -96,6 +96,7 @@ public class WebRtcCallView extends FrameLayout {
private TextView participantCount; private TextView participantCount;
private Stub<FrameLayout> groupCallSpeakerHint; private Stub<FrameLayout> groupCallSpeakerHint;
private Stub<View> groupCallFullStub; private Stub<View> groupCallFullStub;
private View errorButton;
private int pagerBottomMarginDp; private int pagerBottomMarginDp;
private boolean controlsVisible = true; private boolean controlsVisible = true;
@ -151,6 +152,7 @@ public class WebRtcCallView extends FrameLayout {
callParticipantsRecycler = findViewById(R.id.call_screen_participants_recycler); callParticipantsRecycler = findViewById(R.id.call_screen_participants_recycler);
toolbar = findViewById(R.id.call_screen_toolbar); toolbar = findViewById(R.id.call_screen_toolbar);
startCall = findViewById(R.id.call_screen_start_call_start_call); startCall = findViewById(R.id.call_screen_start_call_start_call);
errorButton = findViewById(R.id.call_screen_error_cancel);
groupCallSpeakerHint = new Stub<>(findViewById(R.id.call_screen_group_call_speaker_hint)); groupCallSpeakerHint = new Stub<>(findViewById(R.id.call_screen_group_call_speaker_hint));
groupCallFullStub = new Stub<>(findViewById(R.id.group_call_call_full_view)); groupCallFullStub = new Stub<>(findViewById(R.id.group_call_call_full_view));
@ -227,6 +229,12 @@ public class WebRtcCallView extends FrameLayout {
int statusBarHeight = ViewUtil.getStatusBarHeight(this); int statusBarHeight = ViewUtil.getStatusBarHeight(this);
statusBarGuideline.setGuidelineBegin(statusBarHeight); statusBarGuideline.setGuidelineBegin(statusBarHeight);
errorButton.setOnClickListener(v -> {
if (controlsListener != null) {
controlsListener.onCancelStartCall();
}
});
} }
@Override @Override
@ -426,6 +434,11 @@ public class WebRtcCallView extends FrameLayout {
startCall.setEnabled(webRtcControls.isStartCallEnabled()); startCall.setEnabled(webRtcControls.isStartCallEnabled());
} }
if (webRtcControls.displayErrorControls()) {
visibleViewSet.add(footerGradient);
visibleViewSet.add(errorButton);
}
if (webRtcControls.displayGroupCallFull()) { if (webRtcControls.displayGroupCallFull()) {
groupCallFullStub.get().setVisibility(View.VISIBLE); groupCallFullStub.get().setVisibility(View.VISIBLE);
((TextView) groupCallFullStub.get().findViewById(R.id.group_call_call_full_message)).setText(webRtcControls.getGroupCallFullMessage(getContext())); ((TextView) groupCallFullStub.get().findViewById(R.id.group_call_call_full_message)).setText(webRtcControls.getGroupCallFullMessage(getContext()));

View file

@ -56,8 +56,8 @@ public class WebRtcCallViewModel extends ViewModel {
private boolean answerWithVideoAvailable = false; private boolean answerWithVideoAvailable = false;
private Runnable elapsedTimeRunnable = this::handleTick; private Runnable elapsedTimeRunnable = this::handleTick;
private boolean canEnterPipMode = false; private boolean canEnterPipMode = false;
private List<CallParticipant> previousParticipantsList = Collections.emptyList(); private List<CallParticipant> previousParticipantsList = Collections.emptyList();
private boolean callingStarted = false; private boolean callStarting = false;
private final WebRtcCallRepository repository = new WebRtcCallRepository(ApplicationDependencies.getApplication()); private final WebRtcCallRepository repository = new WebRtcCallRepository(ApplicationDependencies.getApplication());
@ -113,8 +113,8 @@ public class WebRtcCallViewModel extends ViewModel {
return answerWithVideoAvailable; return answerWithVideoAvailable;
} }
public boolean isCallingStarted() { public boolean isCallStarting() {
return callingStarted; return callStarting;
} }
@MainThread @MainThread
@ -141,7 +141,10 @@ public class WebRtcCallViewModel extends ViewModel {
@MainThread @MainThread
public void updateFromWebRtcViewModel(@NonNull WebRtcViewModel webRtcViewModel, boolean enableVideo) { public void updateFromWebRtcViewModel(@NonNull WebRtcViewModel webRtcViewModel, boolean enableVideo) {
canEnterPipMode = webRtcViewModel.getState() != WebRtcViewModel.State.CALL_PRE_JOIN; canEnterPipMode = !webRtcViewModel.getState().isPreJoinOrNetworkUnavailable();
if (callStarting && webRtcViewModel.getState().isPassedPreJoin()) {
callStarting = false;
}
CallParticipant localParticipant = webRtcViewModel.getLocalParticipant(); CallParticipant localParticipant = webRtcViewModel.getLocalParticipant();
@ -232,6 +235,9 @@ public class WebRtcCallViewModel extends ViewModel {
case CALL_DISCONNECTED: case CALL_DISCONNECTED:
callState = WebRtcControls.CallState.ENDING; callState = WebRtcControls.CallState.ENDING;
break; break;
case NETWORK_FAILURE:
callState = WebRtcControls.CallState.ERROR;
break;
default: default:
callState = WebRtcControls.CallState.ONGOING; callState = WebRtcControls.CallState.ONGOING;
} }
@ -309,7 +315,7 @@ public class WebRtcCallViewModel extends ViewModel {
} }
public void startCall(boolean isVideoCall) { public void startCall(boolean isVideoCall) {
callingStarted = true; callStarting = true;
Recipient recipient = getRecipient().get(); Recipient recipient = getRecipient().get();
if (recipient.isGroup()) { if (recipient.isGroup()) {
repository.getIdentityRecords(recipient, identityRecords -> { repository.getIdentityRecords(recipient, identityRecords -> {

View file

@ -51,6 +51,10 @@ public final class WebRtcControls {
this.participantLimit = participantLimit; this.participantLimit = participantLimit;
} }
boolean displayErrorControls() {
return isError();
}
boolean displayStartCallControls() { boolean displayStartCallControls() {
return isPreJoin(); return isPreJoin();
} }
@ -145,6 +149,10 @@ public final class WebRtcControls {
return audioOutput; return audioOutput;
} }
private boolean isError() {
return callState == CallState.ERROR;
}
private boolean isPreJoin() { private boolean isPreJoin() {
return callState == CallState.PRE_JOIN; return callState == CallState.PRE_JOIN;
} }
@ -167,6 +175,7 @@ public final class WebRtcControls {
public enum CallState { public enum CallState {
NONE, NONE,
ERROR,
PRE_JOIN, PRE_JOIN,
INCOMING, INCOMING,
OUTGOING, OUTGOING,

View file

@ -46,6 +46,14 @@ public class WebRtcViewModel {
this == NO_SUCH_USER || this == NO_SUCH_USER ||
this == UNTRUSTED_IDENTITY; this == UNTRUSTED_IDENTITY;
} }
public boolean isPreJoinOrNetworkUnavailable() {
return this == CALL_PRE_JOIN || this == NETWORK_FAILURE;
}
public boolean isPassedPreJoin() {
return this.ordinal() > CALL_PRE_JOIN.ordinal();
}
} }
public enum GroupCallState { public enum GroupCallState {

View file

@ -6,6 +6,8 @@ import android.content.Context;
import android.content.Intent; import android.content.Intent;
import android.content.IntentFilter; import android.content.IntentFilter;
import android.media.AudioManager; import android.media.AudioManager;
import android.net.ConnectivityManager;
import android.net.NetworkInfo;
import android.os.Build; import android.os.Build;
import android.os.IBinder; import android.os.IBinder;
import android.os.ResultReceiver; import android.os.ResultReceiver;
@ -41,6 +43,7 @@ import org.thoughtcrime.securesms.events.GroupCallPeekEvent;
import org.thoughtcrime.securesms.events.WebRtcViewModel; import org.thoughtcrime.securesms.events.WebRtcViewModel;
import org.thoughtcrime.securesms.groups.GroupId; import org.thoughtcrime.securesms.groups.GroupId;
import org.thoughtcrime.securesms.groups.GroupManager; import org.thoughtcrime.securesms.groups.GroupManager;
import org.thoughtcrime.securesms.jobmanager.impl.NetworkConstraint;
import org.thoughtcrime.securesms.jobs.GroupCallUpdateSendJob; import org.thoughtcrime.securesms.jobs.GroupCallUpdateSendJob;
import org.thoughtcrime.securesms.recipients.Recipient; import org.thoughtcrime.securesms.recipients.Recipient;
import org.thoughtcrime.securesms.recipients.RecipientId; import org.thoughtcrime.securesms.recipients.RecipientId;
@ -148,6 +151,7 @@ public class WebRtcCallService extends Service implements CallManager.Observer,
public static final String ACTION_SET_MUTE_AUDIO = "SET_MUTE_AUDIO"; public static final String ACTION_SET_MUTE_AUDIO = "SET_MUTE_AUDIO";
public static final String ACTION_FLIP_CAMERA = "FLIP_CAMERA"; public static final String ACTION_FLIP_CAMERA = "FLIP_CAMERA";
public static final String ACTION_BLUETOOTH_CHANGE = "BLUETOOTH_CHANGE"; public static final String ACTION_BLUETOOTH_CHANGE = "BLUETOOTH_CHANGE";
public static final String ACTION_NETWORK_CHANGE = "NETWORK_CHANGE";
public static final String ACTION_WIRED_HEADSET_CHANGE = "WIRED_HEADSET_CHANGE"; public static final String ACTION_WIRED_HEADSET_CHANGE = "WIRED_HEADSET_CHANGE";
public static final String ACTION_SCREEN_OFF = "SCREEN_OFF"; public static final String ACTION_SCREEN_OFF = "SCREEN_OFF";
public static final String ACTION_IS_IN_CALL_QUERY = "IS_IN_CALL"; public static final String ACTION_IS_IN_CALL_QUERY = "IS_IN_CALL";
@ -212,6 +216,7 @@ public class WebRtcCallService extends Service implements CallManager.Observer,
private SignalServiceAccountManager accountManager; private SignalServiceAccountManager accountManager;
private BluetoothStateManager bluetoothStateManager; private BluetoothStateManager bluetoothStateManager;
private WiredHeadsetStateReceiver wiredHeadsetStateReceiver; private WiredHeadsetStateReceiver wiredHeadsetStateReceiver;
private NetworkReceiver networkReceiver;
private PowerButtonReceiver powerButtonReceiver; private PowerButtonReceiver powerButtonReceiver;
private LockManager lockManager; private LockManager lockManager;
private UncaughtExceptionHandlerManager uncaughtExceptionHandlerManager; private UncaughtExceptionHandlerManager uncaughtExceptionHandlerManager;
@ -241,6 +246,7 @@ public class WebRtcCallService extends Service implements CallManager.Observer,
registerUncaughtExceptionHandler(); registerUncaughtExceptionHandler();
registerWiredHeadsetStateReceiver(); registerWiredHeadsetStateReceiver();
registerNetworkReceiver();
TelephonyUtil.getManager(this) TelephonyUtil.getManager(this)
.listen(hangUpRtcOnDeviceCallAnswered, PhoneStateListener.LISTEN_CALL_STATE); .listen(hangUpRtcOnDeviceCallAnswered, PhoneStateListener.LISTEN_CALL_STATE);
@ -328,6 +334,8 @@ public class WebRtcCallService extends Service implements CallManager.Observer,
powerButtonReceiver = null; powerButtonReceiver = null;
} }
unregisterNetworkReceiver();
TelephonyUtil.getManager(this) TelephonyUtil.getManager(this)
.listen(hangUpRtcOnDeviceCallAnswered, PhoneStateListener.LISTEN_NONE); .listen(hangUpRtcOnDeviceCallAnswered, PhoneStateListener.LISTEN_NONE);
} }
@ -371,6 +379,22 @@ public class WebRtcCallService extends Service implements CallManager.Observer,
registerReceiver(wiredHeadsetStateReceiver, new IntentFilter(action)); registerReceiver(wiredHeadsetStateReceiver, new IntentFilter(action));
} }
private void registerNetworkReceiver() {
if (networkReceiver == null) {
networkReceiver = new NetworkReceiver();
registerReceiver(networkReceiver, new IntentFilter(ConnectivityManager.CONNECTIVITY_ACTION));
}
}
private void unregisterNetworkReceiver() {
if (networkReceiver != null) {
unregisterReceiver(networkReceiver);
networkReceiver = null;
}
}
public void registerPowerButtonReceiver() { public void registerPowerButtonReceiver() {
if (powerButtonReceiver == null) { if (powerButtonReceiver == null) {
powerButtonReceiver = new PowerButtonReceiver(); powerButtonReceiver = new PowerButtonReceiver();
@ -502,6 +526,19 @@ public class WebRtcCallService extends Service implements CallManager.Observer,
} }
} }
private static class NetworkReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
ConnectivityManager connectivityManager = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
NetworkInfo activeNetworkInfo = connectivityManager.getActiveNetworkInfo();
Intent serviceIntent = new Intent(context, WebRtcCallService.class);
serviceIntent.setAction(ACTION_NETWORK_CHANGE);
serviceIntent.putExtra(EXTRA_AVAILABLE, activeNetworkInfo != null && activeNetworkInfo.isConnected());
context.startService(serviceIntent);
}
}
private static class PowerButtonReceiver extends BroadcastReceiver { private static class PowerButtonReceiver extends BroadcastReceiver {
@Override @Override
public void onReceive(@NonNull Context context, @NonNull Intent intent) { public void onReceive(@NonNull Context context, @NonNull Intent intent) {
@ -1057,8 +1094,11 @@ public class WebRtcCallService extends Service implements CallManager.Observer,
.putExtra(EXTRA_GROUP_CALL_HASH, groupCall.hashCode()); .putExtra(EXTRA_GROUP_CALL_HASH, groupCall.hashCode());
startService(intent); startService(intent);
} catch (IOException | VerificationFailedException e) { } catch (IOException e) {
Log.w(TAG, "Unable to fetch group membership proof", e); Log.w(TAG, "Unable to get group membership proof from service", e);
onEnded(groupCall, GroupCall.GroupCallEndReason.SFU_CLIENT_FAILED_TO_JOIN);
} catch (VerificationFailedException e) {
Log.w(TAG, "Unable to verify group membership proof", e);
onEnded(groupCall, GroupCall.GroupCallEndReason.DEVICE_EXPLICITLY_DISCONNECTED); onEnded(groupCall, GroupCall.GroupCallEndReason.DEVICE_EXPLICITLY_DISCONNECTED);
} }
}); });

View file

@ -18,6 +18,7 @@ import org.thoughtcrime.securesms.groups.GroupManager;
import org.thoughtcrime.securesms.recipients.Recipient; import org.thoughtcrime.securesms.recipients.Recipient;
import org.thoughtcrime.securesms.recipients.RecipientId; import org.thoughtcrime.securesms.recipients.RecipientId;
import org.thoughtcrime.securesms.ringrtc.RemotePeer; 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.WebRtcServiceState;
import org.thoughtcrime.securesms.service.webrtc.state.WebRtcServiceStateBuilder; import org.thoughtcrime.securesms.service.webrtc.state.WebRtcServiceStateBuilder;
import org.thoughtcrime.securesms.webrtc.locks.LockManager; import org.thoughtcrime.securesms.webrtc.locks.LockManager;
@ -271,6 +272,28 @@ public class GroupActionProcessor extends DeviceAwareActionProcessor {
return groupCallFailure(currentState, "Unable to disconnect from group call", e); return groupCallFailure(currentState, "Unable to disconnect from group call", e);
} }
if (groupCallEndReason != GroupCall.GroupCallEndReason.DEVICE_EXPLICITLY_DISCONNECTED) {
Log.i(tag, "Group call ended unexpectedly, reinitializing and dropping back to lobby");
Recipient currentRecipient = currentState.getCallInfoState().getCallRecipient();
VideoState videoState = currentState.getVideoState();
currentState = terminateGroupCall(currentState, false).builder()
.actionProcessor(new GroupNetworkUnavailableActionProcessor(webRtcInteractor))
.changeVideoState()
.eglBase(videoState.getEglBase())
.camera(videoState.getCamera())
.localSink(videoState.getLocalSink())
.commit()
.changeCallInfoState()
.callState(WebRtcViewModel.State.CALL_PRE_JOIN)
.callRecipient(currentRecipient)
.build();
currentState = WebRtcVideoUtil.initializeVanityCamera(WebRtcVideoUtil.reinitializeCamera(context, webRtcInteractor.getCameraEventListener(), currentState));
return currentState.getActionProcessor().handlePreJoinCall(currentState, new RemotePeer(currentRecipient.getId()));
}
currentState = currentState.builder() currentState = currentState.builder()
.changeCallInfoState() .changeCallInfoState()
.callState(WebRtcViewModel.State.CALL_DISCONNECTED) .callState(WebRtcViewModel.State.CALL_DISCONNECTED)
@ -313,6 +336,10 @@ public class GroupActionProcessor extends DeviceAwareActionProcessor {
} }
public synchronized @NonNull WebRtcServiceState terminateGroupCall(@NonNull WebRtcServiceState 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); webRtcInteractor.updatePhoneState(LockManager.PhoneState.PROCESSING);
webRtcInteractor.stopForegroundService(); webRtcInteractor.stopForegroundService();
boolean playDisconnectSound = currentState.getCallInfoState().getCallState() == WebRtcViewModel.State.CALL_DISCONNECTED; boolean playDisconnectSound = currentState.getCallInfoState().getCallState() == WebRtcViewModel.State.CALL_DISCONNECTED;
@ -321,7 +348,9 @@ public class GroupActionProcessor extends DeviceAwareActionProcessor {
webRtcInteractor.updatePhoneState(LockManager.PhoneState.IDLE); webRtcInteractor.updatePhoneState(LockManager.PhoneState.IDLE);
WebRtcVideoUtil.deinitializeVideo(currentState); if (terminateVideo) {
WebRtcVideoUtil.deinitializeVideo(currentState);
}
GroupCallSafetyNumberChangeNotificationUtil.cancelNotification(context, currentState.getCallInfoState().getCallRecipient()); GroupCallSafetyNumberChangeNotificationUtil.cancelNotification(context, currentState.getCallInfoState().getCallRecipient());

View file

@ -0,0 +1,80 @@
package org.thoughtcrime.securesms.service.webrtc;
import android.content.Context;
import android.net.ConnectivityManager;
import android.net.NetworkInfo;
import androidx.annotation.NonNull;
import org.signal.core.util.logging.Log;
import org.signal.ringrtc.GroupCall;
import org.thoughtcrime.securesms.BuildConfig;
import org.thoughtcrime.securesms.events.WebRtcViewModel;
import org.thoughtcrime.securesms.keyvalue.SignalStore;
import org.thoughtcrime.securesms.ringrtc.RemotePeer;
import org.thoughtcrime.securesms.service.webrtc.state.WebRtcServiceState;
/**
* Processor which is utilized when the network becomes unavailable during a group call. In general,
* this is triggered whenever there is a call ended, and the ending was not the result of direct user
* action.
*
* This class will check the network status when handlePreJoinCall is invoked, and transition to
* GroupPreJoinActionProcessor as network becomes available again.
*/
class GroupNetworkUnavailableActionProcessor extends WebRtcActionProcessor {
private static final String TAG = Log.tag(GroupNetworkUnavailableActionProcessor.class);
public GroupNetworkUnavailableActionProcessor(@NonNull WebRtcInteractor webRtcInteractor) {
super(webRtcInteractor, TAG);
}
@Override
protected @NonNull WebRtcServiceState handlePreJoinCall(@NonNull WebRtcServiceState currentState, @NonNull RemotePeer remotePeer) {
Log.i(TAG, "handlePreJoinCall():");
ConnectivityManager connectivityManager = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
NetworkInfo activeNetworkInfo = connectivityManager.getActiveNetworkInfo();
if (activeNetworkInfo != null && activeNetworkInfo.isConnected()) {
GroupPreJoinActionProcessor processor = new GroupPreJoinActionProcessor(webRtcInteractor);
return processor.handlePreJoinCall(currentState.builder().actionProcessor(processor).build(), remotePeer);
}
byte[] groupId = currentState.getCallInfoState().getCallRecipient().requireGroupId().getDecodedId();
GroupCall groupCall = webRtcInteractor.getCallManager().createGroupCall(groupId,
BuildConfig.SIGNAL_SFU_URL,
currentState.getVideoState().requireEglBase(),
webRtcInteractor.getGroupCallObserver());
return currentState.builder()
.changeCallInfoState()
.callState(WebRtcViewModel.State.NETWORK_FAILURE)
.groupCall(groupCall)
.groupCallState(WebRtcViewModel.GroupCallState.DISCONNECTED)
.build();
}
@Override
protected @NonNull WebRtcServiceState handleCancelPreJoinCall(@NonNull WebRtcServiceState currentState) {
Log.i(TAG, "handleCancelPreJoinCall():");
WebRtcVideoUtil.deinitializeVideo(currentState);
return new WebRtcServiceState(new IdleActionProcessor(webRtcInteractor));
}
@Override
public @NonNull WebRtcServiceState handleNetworkChanged(@NonNull WebRtcServiceState currentState, boolean available) {
if (available) {
return currentState.builder()
.actionProcessor(new GroupPreJoinActionProcessor(webRtcInteractor))
.changeCallInfoState()
.callState(WebRtcViewModel.State.CALL_PRE_JOIN)
.build();
} else {
return currentState;
}
}
}

View file

@ -187,4 +187,17 @@ public class GroupPreJoinActionProcessor extends GroupActionProcessor {
.isMicrophoneEnabled(!muted) .isMicrophoneEnabled(!muted)
.build(); .build();
} }
@Override
public @NonNull WebRtcServiceState handleNetworkChanged(@NonNull WebRtcServiceState currentState, boolean available) {
if (!available) {
return currentState.builder()
.actionProcessor(new GroupNetworkUnavailableActionProcessor(webRtcInteractor))
.changeCallInfoState()
.callState(WebRtcViewModel.State.NETWORK_FAILURE)
.build();
} else {
return currentState;
}
}
} }

View file

@ -54,7 +54,7 @@ public class IdleActionProcessor extends WebRtcActionProcessor {
WebRtcActionProcessor processor = isGroupCall ? new GroupPreJoinActionProcessor(webRtcInteractor) WebRtcActionProcessor processor = isGroupCall ? new GroupPreJoinActionProcessor(webRtcInteractor)
: new PreJoinActionProcessor(webRtcInteractor); : new PreJoinActionProcessor(webRtcInteractor);
currentState = initializeVanityCamera(WebRtcVideoUtil.initializeVideo(context, webRtcInteractor.getCameraEventListener(), currentState)); currentState = WebRtcVideoUtil.initializeVanityCamera(WebRtcVideoUtil.initializeVideo(context, webRtcInteractor.getCameraEventListener(), currentState));
currentState = currentState.builder() currentState = currentState.builder()
.actionProcessor(processor) .actionProcessor(processor)
@ -66,30 +66,4 @@ public class IdleActionProcessor extends WebRtcActionProcessor {
return isGroupCall ? currentState.getActionProcessor().handlePreJoinCall(currentState, remotePeer) return isGroupCall ? currentState.getActionProcessor().handlePreJoinCall(currentState, remotePeer)
: currentState; : currentState;
} }
private @NonNull WebRtcServiceState initializeVanityCamera(@NonNull WebRtcServiceState currentState) {
Camera camera = currentState.getVideoState().requireCamera();
BroadcastVideoSink sink = currentState.getVideoState().requireLocalSink();
if (camera.hasCapturer()) {
camera.initCapturer(new CapturerObserver() {
@Override
public void onFrameCaptured(VideoFrame videoFrame) {
sink.onFrame(videoFrame);
}
@Override
public void onCapturerStarted(boolean success) {}
@Override
public void onCapturerStopped() {}
});
camera.setEnabled(true);
}
return currentState.builder()
.changeLocalDeviceState()
.cameraState(camera.getCameraState())
.build();
}
} }

View file

@ -77,6 +77,7 @@ import static org.thoughtcrime.securesms.service.WebRtcCallService.ACTION_LOCAL_
import static org.thoughtcrime.securesms.service.WebRtcCallService.ACTION_LOCAL_RINGING; import static org.thoughtcrime.securesms.service.WebRtcCallService.ACTION_LOCAL_RINGING;
import static org.thoughtcrime.securesms.service.WebRtcCallService.ACTION_MESSAGE_SENT_ERROR; import static org.thoughtcrime.securesms.service.WebRtcCallService.ACTION_MESSAGE_SENT_ERROR;
import static org.thoughtcrime.securesms.service.WebRtcCallService.ACTION_MESSAGE_SENT_SUCCESS; import static org.thoughtcrime.securesms.service.WebRtcCallService.ACTION_MESSAGE_SENT_SUCCESS;
import static org.thoughtcrime.securesms.service.WebRtcCallService.ACTION_NETWORK_CHANGE;
import static org.thoughtcrime.securesms.service.WebRtcCallService.ACTION_OUTGOING_CALL; import static org.thoughtcrime.securesms.service.WebRtcCallService.ACTION_OUTGOING_CALL;
import static org.thoughtcrime.securesms.service.WebRtcCallService.ACTION_PRE_JOIN_CALL; import static org.thoughtcrime.securesms.service.WebRtcCallService.ACTION_PRE_JOIN_CALL;
import static org.thoughtcrime.securesms.service.WebRtcCallService.ACTION_RECEIVED_OFFER_EXPIRED; import static org.thoughtcrime.securesms.service.WebRtcCallService.ACTION_RECEIVED_OFFER_EXPIRED;
@ -213,6 +214,7 @@ public abstract class WebRtcActionProcessor {
case ACTION_SET_AUDIO_BLUETOOTH: return handleSetBluetoothAudio(currentState, intent.getBooleanExtra(EXTRA_BLUETOOTH, false)); case ACTION_SET_AUDIO_BLUETOOTH: return handleSetBluetoothAudio(currentState, intent.getBooleanExtra(EXTRA_BLUETOOTH, false));
case ACTION_BLUETOOTH_CHANGE: return handleBluetoothChange(currentState, getAvailable(intent)); case ACTION_BLUETOOTH_CHANGE: return handleBluetoothChange(currentState, getAvailable(intent));
case ACTION_CAMERA_SWITCH_COMPLETED: return handleCameraSwitchCompleted(currentState, getCameraState(intent)); case ACTION_CAMERA_SWITCH_COMPLETED: return handleCameraSwitchCompleted(currentState, getCameraState(intent));
case ACTION_NETWORK_CHANGE: return handleNetworkChanged(currentState, getAvailable(intent));
// End Call Actions // End Call Actions
case ACTION_ENDED_REMOTE_HANGUP: case ACTION_ENDED_REMOTE_HANGUP:
@ -598,6 +600,11 @@ public abstract class WebRtcActionProcessor {
return currentState; return currentState;
} }
public @NonNull WebRtcServiceState handleNetworkChanged(@NonNull WebRtcServiceState currentState, boolean available) {
Log.i(tag, "handleNetworkChanged not processed");
return currentState;
}
//endregion Local device //endregion Local device
//region End call //region End call

View file

@ -11,7 +11,9 @@ import org.thoughtcrime.securesms.ringrtc.CameraState;
import org.thoughtcrime.securesms.service.webrtc.state.WebRtcServiceState; import org.thoughtcrime.securesms.service.webrtc.state.WebRtcServiceState;
import org.thoughtcrime.securesms.service.webrtc.state.WebRtcServiceStateBuilder; import org.thoughtcrime.securesms.service.webrtc.state.WebRtcServiceStateBuilder;
import org.thoughtcrime.securesms.util.Util; import org.thoughtcrime.securesms.util.Util;
import org.webrtc.CapturerObserver;
import org.webrtc.EglBase; import org.webrtc.EglBase;
import org.webrtc.VideoFrame;
/** /**
* Helper for initializing, reinitializing, and deinitializing the camera and it's related * Helper for initializing, reinitializing, and deinitializing the camera and it's related
@ -93,4 +95,30 @@ public final class WebRtcVideoUtil {
.cameraState(CameraState.UNKNOWN) .cameraState(CameraState.UNKNOWN)
.build(); .build();
} }
public static @NonNull WebRtcServiceState initializeVanityCamera(@NonNull WebRtcServiceState currentState) {
Camera camera = currentState.getVideoState().requireCamera();
BroadcastVideoSink sink = currentState.getVideoState().requireLocalSink();
if (camera.hasCapturer()) {
camera.initCapturer(new CapturerObserver() {
@Override
public void onFrameCaptured(VideoFrame videoFrame) {
sink.onFrame(videoFrame);
}
@Override
public void onCapturerStarted(boolean success) {}
@Override
public void onCapturerStopped() {}
});
camera.setEnabled(true);
}
return currentState.builder()
.changeLocalDeviceState()
.cameraState(camera.getCameraState())
.build();
}
} }

View file

@ -322,6 +322,25 @@
</LinearLayout> </LinearLayout>
<com.google.android.material.button.MaterialButton
android:id="@+id/call_screen_error_cancel"
style="@style/Widget.Signal.Button.Flat"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginEnd="16dp"
android:layout_marginBottom="32dp"
android:layout_weight="1"
android:text="@android:string/cancel"
android:textAllCaps="false"
android:textColor="@color/core_white"
android:visibility="gone"
app:backgroundTint="@color/transparent_white_40"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
tools:visibility="visible" />
<ViewStub <ViewStub
android:id="@+id/call_screen_group_call_speaker_hint" android:id="@+id/call_screen_group_call_speaker_hint"
android:layout="@layout/group_call_speaker_hint" android:layout="@layout/group_call_speaker_hint"