Better error handling for group calls.
This commit is contained in:
parent
84f1da76ad
commit
cf32b93269
13 changed files with 268 additions and 43 deletions
|
@ -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);
|
||||||
|
|
|
@ -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()));
|
||||||
|
|
|
@ -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 -> {
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
|
@ -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());
|
||||||
|
|
||||||
|
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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"
|
||||||
|
|
Loading…
Add table
Reference in a new issue