Skip to content

Commit cf32b93

Browse files
cody-signalalan-signal
authored andcommitted
Better error handling for group calls.
1 parent 84f1da7 commit cf32b93

13 files changed

Lines changed: 268 additions & 43 deletions

app/src/main/java/org/thoughtcrime/securesms/WebRtcCallActivity.java

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -141,9 +141,9 @@ public void onPause() {
141141
EventBus.getDefault().unregister(this);
142142
}
143143

144-
if (!viewModel.isCallingStarted()) {
144+
if (!viewModel.isCallStarting()) {
145145
CallParticipantsState state = viewModel.getCallParticipantsState().getValue();
146-
if (state != null && state.getCallState() == WebRtcViewModel.State.CALL_PRE_JOIN) {
146+
if (state != null && state.getCallState().isPreJoinOrNetworkUnavailable()) {
147147
finish();
148148
}
149149
}
@@ -156,9 +156,9 @@ protected void onStop() {
156156

157157
EventBus.getDefault().unregister(this);
158158

159-
if (!viewModel.isCallingStarted()) {
159+
if (!viewModel.isCallStarting()) {
160160
CallParticipantsState state = viewModel.getCallParticipantsState().getValue();
161-
if (state != null && state.getCallState() == WebRtcViewModel.State.CALL_PRE_JOIN) {
161+
if (state != null && state.getCallState().isPreJoinOrNetworkUnavailable()) {
162162
Intent intent = new Intent(this, WebRtcCallService.class);
163163
intent.setAction(WebRtcCallService.ACTION_CANCEL_PRE_JOIN_CALL);
164164
startService(intent);
@@ -471,7 +471,6 @@ private void handleRecipientUnavailable() {
471471
private void handleServerFailure() {
472472
EventBus.getDefault().removeStickyEvent(WebRtcViewModel.class);
473473
callScreen.setStatus(getString(R.string.RedPhone_network_failed));
474-
delayedFinish();
475474
}
476475

477476
private void handleNoSuchUser(final @NonNull WebRtcViewModel event) {
@@ -529,7 +528,7 @@ public void onSendAnywayAfterSafetyNumberChange(@NonNull List<RecipientId> chang
529528
.putExtra(WebRtcCallService.EXTRA_RECIPIENT_IDS, RecipientId.toSerializedList(changedRecipients));
530529
startService(intent);
531530
} else {
532-
startCall(state.getLocalParticipant().isVideoEnabled());
531+
viewModel.startCall(state.getLocalParticipant().isVideoEnabled());
533532
}
534533
}
535534

@@ -540,7 +539,7 @@ public void onMessageResentAfterSafetyNumberChange() { }
540539
public void onCanceled() {
541540
CallParticipantsState state = viewModel.getCallParticipantsState().getValue();
542541
if (state != null && state.getGroupCallState().isNotIdle()) {
543-
if (state.getCallState() == WebRtcViewModel.State.CALL_PRE_JOIN) {
542+
if (state.getCallState().isPreJoinOrNetworkUnavailable()) {
544543
Intent intent = new Intent(this, WebRtcCallService.class);
545544
intent.setAction(WebRtcCallService.ACTION_CANCEL_PRE_JOIN_CALL);
546545
startService(intent);

app/src/main/java/org/thoughtcrime/securesms/components/webrtc/WebRtcCallView.java

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,7 @@ public class WebRtcCallView extends FrameLayout {
9696
private TextView participantCount;
9797
private Stub<FrameLayout> groupCallSpeakerHint;
9898
private Stub<View> groupCallFullStub;
99+
private View errorButton;
99100
private int pagerBottomMarginDp;
100101
private boolean controlsVisible = true;
101102

@@ -151,6 +152,7 @@ protected void onFinishInflate() {
151152
callParticipantsRecycler = findViewById(R.id.call_screen_participants_recycler);
152153
toolbar = findViewById(R.id.call_screen_toolbar);
153154
startCall = findViewById(R.id.call_screen_start_call_start_call);
155+
errorButton = findViewById(R.id.call_screen_error_cancel);
154156
groupCallSpeakerHint = new Stub<>(findViewById(R.id.call_screen_group_call_speaker_hint));
155157
groupCallFullStub = new Stub<>(findViewById(R.id.group_call_call_full_view));
156158

@@ -227,6 +229,12 @@ public void onPageSelected(int position) {
227229

228230
int statusBarHeight = ViewUtil.getStatusBarHeight(this);
229231
statusBarGuideline.setGuidelineBegin(statusBarHeight);
232+
233+
errorButton.setOnClickListener(v -> {
234+
if (controlsListener != null) {
235+
controlsListener.onCancelStartCall();
236+
}
237+
});
230238
}
231239

232240
@Override
@@ -426,6 +434,11 @@ public void setWebRtcControls(@NonNull WebRtcControls webRtcControls) {
426434
startCall.setEnabled(webRtcControls.isStartCallEnabled());
427435
}
428436

437+
if (webRtcControls.displayErrorControls()) {
438+
visibleViewSet.add(footerGradient);
439+
visibleViewSet.add(errorButton);
440+
}
441+
429442
if (webRtcControls.displayGroupCallFull()) {
430443
groupCallFullStub.get().setVisibility(View.VISIBLE);
431444
((TextView) groupCallFullStub.get().findViewById(R.id.group_call_call_full_message)).setText(webRtcControls.getGroupCallFullMessage(getContext()));

app/src/main/java/org/thoughtcrime/securesms/components/webrtc/WebRtcCallViewModel.java

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -56,8 +56,8 @@ public class WebRtcCallViewModel extends ViewModel {
5656
private boolean answerWithVideoAvailable = false;
5757
private Runnable elapsedTimeRunnable = this::handleTick;
5858
private boolean canEnterPipMode = false;
59-
private List<CallParticipant> previousParticipantsList = Collections.emptyList();
60-
private boolean callingStarted = false;
59+
private List<CallParticipant> previousParticipantsList = Collections.emptyList();
60+
private boolean callStarting = false;
6161

6262
private final WebRtcCallRepository repository = new WebRtcCallRepository(ApplicationDependencies.getApplication());
6363

@@ -113,8 +113,8 @@ public boolean isAnswerWithVideoAvailable() {
113113
return answerWithVideoAvailable;
114114
}
115115

116-
public boolean isCallingStarted() {
117-
return callingStarted;
116+
public boolean isCallStarting() {
117+
return callStarting;
118118
}
119119

120120
@MainThread
@@ -141,7 +141,10 @@ public void onDismissedVideoTooltip() {
141141

142142
@MainThread
143143
public void updateFromWebRtcViewModel(@NonNull WebRtcViewModel webRtcViewModel, boolean enableVideo) {
144-
canEnterPipMode = webRtcViewModel.getState() != WebRtcViewModel.State.CALL_PRE_JOIN;
144+
canEnterPipMode = !webRtcViewModel.getState().isPreJoinOrNetworkUnavailable();
145+
if (callStarting && webRtcViewModel.getState().isPassedPreJoin()) {
146+
callStarting = false;
147+
}
145148

146149
CallParticipant localParticipant = webRtcViewModel.getLocalParticipant();
147150

@@ -232,6 +235,9 @@ private void updateWebRtcControls(@NonNull WebRtcViewModel.State state,
232235
case CALL_DISCONNECTED:
233236
callState = WebRtcControls.CallState.ENDING;
234237
break;
238+
case NETWORK_FAILURE:
239+
callState = WebRtcControls.CallState.ERROR;
240+
break;
235241
default:
236242
callState = WebRtcControls.CallState.ONGOING;
237243
}
@@ -309,7 +315,7 @@ protected void onCleared() {
309315
}
310316

311317
public void startCall(boolean isVideoCall) {
312-
callingStarted = true;
318+
callStarting = true;
313319
Recipient recipient = getRecipient().get();
314320
if (recipient.isGroup()) {
315321
repository.getIdentityRecords(recipient, identityRecords -> {

app/src/main/java/org/thoughtcrime/securesms/components/webrtc/WebRtcControls.java

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,10 @@ private WebRtcControls() {
5151
this.participantLimit = participantLimit;
5252
}
5353

54+
boolean displayErrorControls() {
55+
return isError();
56+
}
57+
5458
boolean displayStartCallControls() {
5559
return isPreJoin();
5660
}
@@ -145,6 +149,10 @@ boolean displayTopViews() {
145149
return audioOutput;
146150
}
147151

152+
private boolean isError() {
153+
return callState == CallState.ERROR;
154+
}
155+
148156
private boolean isPreJoin() {
149157
return callState == CallState.PRE_JOIN;
150158
}
@@ -167,6 +175,7 @@ private boolean isGroupCall() {
167175

168176
public enum CallState {
169177
NONE,
178+
ERROR,
170179
PRE_JOIN,
171180
INCOMING,
172181
OUTGOING,

app/src/main/java/org/thoughtcrime/securesms/events/WebRtcViewModel.java

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,14 @@ public boolean isErrorState() {
4646
this == NO_SUCH_USER ||
4747
this == UNTRUSTED_IDENTITY;
4848
}
49+
50+
public boolean isPreJoinOrNetworkUnavailable() {
51+
return this == CALL_PRE_JOIN || this == NETWORK_FAILURE;
52+
}
53+
54+
public boolean isPassedPreJoin() {
55+
return this.ordinal() > CALL_PRE_JOIN.ordinal();
56+
}
4957
}
5058

5159
public enum GroupCallState {

app/src/main/java/org/thoughtcrime/securesms/service/WebRtcCallService.java

Lines changed: 42 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@
66
import android.content.Intent;
77
import android.content.IntentFilter;
88
import android.media.AudioManager;
9+
import android.net.ConnectivityManager;
10+
import android.net.NetworkInfo;
911
import android.os.Build;
1012
import android.os.IBinder;
1113
import android.os.ResultReceiver;
@@ -41,6 +43,7 @@
4143
import org.thoughtcrime.securesms.events.WebRtcViewModel;
4244
import org.thoughtcrime.securesms.groups.GroupId;
4345
import org.thoughtcrime.securesms.groups.GroupManager;
46+
import org.thoughtcrime.securesms.jobmanager.impl.NetworkConstraint;
4447
import org.thoughtcrime.securesms.jobs.GroupCallUpdateSendJob;
4548
import org.thoughtcrime.securesms.recipients.Recipient;
4649
import org.thoughtcrime.securesms.recipients.RecipientId;
@@ -148,6 +151,7 @@ public class WebRtcCallService extends Service implements CallManager.Observer,
148151
public static final String ACTION_SET_MUTE_AUDIO = "SET_MUTE_AUDIO";
149152
public static final String ACTION_FLIP_CAMERA = "FLIP_CAMERA";
150153
public static final String ACTION_BLUETOOTH_CHANGE = "BLUETOOTH_CHANGE";
154+
public static final String ACTION_NETWORK_CHANGE = "NETWORK_CHANGE";
151155
public static final String ACTION_WIRED_HEADSET_CHANGE = "WIRED_HEADSET_CHANGE";
152156
public static final String ACTION_SCREEN_OFF = "SCREEN_OFF";
153157
public static final String ACTION_IS_IN_CALL_QUERY = "IS_IN_CALL";
@@ -212,6 +216,7 @@ public class WebRtcCallService extends Service implements CallManager.Observer,
212216
private SignalServiceAccountManager accountManager;
213217
private BluetoothStateManager bluetoothStateManager;
214218
private WiredHeadsetStateReceiver wiredHeadsetStateReceiver;
219+
private NetworkReceiver networkReceiver;
215220
private PowerButtonReceiver powerButtonReceiver;
216221
private LockManager lockManager;
217222
private UncaughtExceptionHandlerManager uncaughtExceptionHandlerManager;
@@ -241,6 +246,7 @@ public void onCreate() {
241246

242247
registerUncaughtExceptionHandler();
243248
registerWiredHeadsetStateReceiver();
249+
registerNetworkReceiver();
244250

245251
TelephonyUtil.getManager(this)
246252
.listen(hangUpRtcOnDeviceCallAnswered, PhoneStateListener.LISTEN_CALL_STATE);
@@ -328,6 +334,8 @@ public void onDestroy() {
328334
powerButtonReceiver = null;
329335
}
330336

337+
unregisterNetworkReceiver();
338+
331339
TelephonyUtil.getManager(this)
332340
.listen(hangUpRtcOnDeviceCallAnswered, PhoneStateListener.LISTEN_NONE);
333341
}
@@ -371,6 +379,22 @@ private void registerWiredHeadsetStateReceiver() {
371379
registerReceiver(wiredHeadsetStateReceiver, new IntentFilter(action));
372380
}
373381

382+
private void registerNetworkReceiver() {
383+
if (networkReceiver == null) {
384+
networkReceiver = new NetworkReceiver();
385+
386+
registerReceiver(networkReceiver, new IntentFilter(ConnectivityManager.CONNECTIVITY_ACTION));
387+
}
388+
}
389+
390+
private void unregisterNetworkReceiver() {
391+
if (networkReceiver != null) {
392+
unregisterReceiver(networkReceiver);
393+
394+
networkReceiver = null;
395+
}
396+
}
397+
374398
public void registerPowerButtonReceiver() {
375399
if (powerButtonReceiver == null) {
376400
powerButtonReceiver = new PowerButtonReceiver();
@@ -502,6 +526,19 @@ public void onReceive(@NonNull Context context, @NonNull Intent intent) {
502526
}
503527
}
504528

529+
private static class NetworkReceiver extends BroadcastReceiver {
530+
@Override
531+
public void onReceive(Context context, Intent intent) {
532+
ConnectivityManager connectivityManager = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
533+
NetworkInfo activeNetworkInfo = connectivityManager.getActiveNetworkInfo();
534+
Intent serviceIntent = new Intent(context, WebRtcCallService.class);
535+
536+
serviceIntent.setAction(ACTION_NETWORK_CHANGE);
537+
serviceIntent.putExtra(EXTRA_AVAILABLE, activeNetworkInfo != null && activeNetworkInfo.isConnected());
538+
context.startService(serviceIntent);
539+
}
540+
}
541+
505542
private static class PowerButtonReceiver extends BroadcastReceiver {
506543
@Override
507544
public void onReceive(@NonNull Context context, @NonNull Intent intent) {
@@ -1057,8 +1094,11 @@ public void requestMembershipProof(@NonNull GroupCall groupCall) {
10571094
.putExtra(EXTRA_GROUP_CALL_HASH, groupCall.hashCode());
10581095

10591096
startService(intent);
1060-
} catch (IOException | VerificationFailedException e) {
1061-
Log.w(TAG, "Unable to fetch group membership proof", e);
1097+
} catch (IOException e) {
1098+
Log.w(TAG, "Unable to get group membership proof from service", e);
1099+
onEnded(groupCall, GroupCall.GroupCallEndReason.SFU_CLIENT_FAILED_TO_JOIN);
1100+
} catch (VerificationFailedException e) {
1101+
Log.w(TAG, "Unable to verify group membership proof", e);
10621102
onEnded(groupCall, GroupCall.GroupCallEndReason.DEVICE_EXPLICITLY_DISCONNECTED);
10631103
}
10641104
});

app/src/main/java/org/thoughtcrime/securesms/service/webrtc/GroupActionProcessor.java

Lines changed: 30 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
import org.thoughtcrime.securesms.recipients.Recipient;
1919
import org.thoughtcrime.securesms.recipients.RecipientId;
2020
import org.thoughtcrime.securesms.ringrtc.RemotePeer;
21+
import org.thoughtcrime.securesms.service.webrtc.state.VideoState;
2122
import org.thoughtcrime.securesms.service.webrtc.state.WebRtcServiceState;
2223
import org.thoughtcrime.securesms.service.webrtc.state.WebRtcServiceStateBuilder;
2324
import org.thoughtcrime.securesms.webrtc.locks.LockManager;
@@ -271,6 +272,28 @@ public GroupActionProcessor(@NonNull WebRtcInteractor webRtcInteractor, @NonNull
271272
return groupCallFailure(currentState, "Unable to disconnect from group call", e);
272273
}
273274

275+
if (groupCallEndReason != GroupCall.GroupCallEndReason.DEVICE_EXPLICITLY_DISCONNECTED) {
276+
Log.i(tag, "Group call ended unexpectedly, reinitializing and dropping back to lobby");
277+
Recipient currentRecipient = currentState.getCallInfoState().getCallRecipient();
278+
VideoState videoState = currentState.getVideoState();
279+
280+
currentState = terminateGroupCall(currentState, false).builder()
281+
.actionProcessor(new GroupNetworkUnavailableActionProcessor(webRtcInteractor))
282+
.changeVideoState()
283+
.eglBase(videoState.getEglBase())
284+
.camera(videoState.getCamera())
285+
.localSink(videoState.getLocalSink())
286+
.commit()
287+
.changeCallInfoState()
288+
.callState(WebRtcViewModel.State.CALL_PRE_JOIN)
289+
.callRecipient(currentRecipient)
290+
.build();
291+
292+
currentState = WebRtcVideoUtil.initializeVanityCamera(WebRtcVideoUtil.reinitializeCamera(context, webRtcInteractor.getCameraEventListener(), currentState));
293+
294+
return currentState.getActionProcessor().handlePreJoinCall(currentState, new RemotePeer(currentRecipient.getId()));
295+
}
296+
274297
currentState = currentState.builder()
275298
.changeCallInfoState()
276299
.callState(WebRtcViewModel.State.CALL_DISCONNECTED)
@@ -313,6 +336,10 @@ public GroupActionProcessor(@NonNull WebRtcInteractor webRtcInteractor, @NonNull
313336
}
314337

315338
public synchronized @NonNull WebRtcServiceState terminateGroupCall(@NonNull WebRtcServiceState currentState) {
339+
return terminateGroupCall(currentState, true);
340+
}
341+
342+
public synchronized @NonNull WebRtcServiceState terminateGroupCall(@NonNull WebRtcServiceState currentState, boolean terminateVideo) {
316343
webRtcInteractor.updatePhoneState(LockManager.PhoneState.PROCESSING);
317344
webRtcInteractor.stopForegroundService();
318345
boolean playDisconnectSound = currentState.getCallInfoState().getCallState() == WebRtcViewModel.State.CALL_DISCONNECTED;
@@ -321,7 +348,9 @@ public GroupActionProcessor(@NonNull WebRtcInteractor webRtcInteractor, @NonNull
321348

322349
webRtcInteractor.updatePhoneState(LockManager.PhoneState.IDLE);
323350

324-
WebRtcVideoUtil.deinitializeVideo(currentState);
351+
if (terminateVideo) {
352+
WebRtcVideoUtil.deinitializeVideo(currentState);
353+
}
325354

326355
GroupCallSafetyNumberChangeNotificationUtil.cancelNotification(context, currentState.getCallInfoState().getCallRecipient());
327356

0 commit comments

Comments
 (0)