rtp session propsoal: fix race condition with very fast 'busy' or 'error'

This commit is contained in:
Daniel Gultsch 2024-03-30 11:15:41 +01:00
parent c527e76337
commit cb37321ecb
No known key found for this signature in database
GPG key ID: F43D18AD2A0982C2
4 changed files with 67 additions and 18 deletions

View file

@ -131,6 +131,7 @@ public class CallIntegrationConnectionService extends ConnectionService {
intent.putExtra( intent.putExtra(
RtpSessionActivity.EXTRA_LAST_REPORTED_STATE, RtpSessionActivity.EXTRA_LAST_REPORTED_STATE,
RtpEndUserState.FINDING_DEVICE.toString()); RtpEndUserState.FINDING_DEVICE.toString());
intent.putExtra(RtpSessionActivity.EXTRA_PROPOSED_SESSION_ID, proposal.sessionId);
callIntegration = proposal.getCallIntegration(); callIntegration = proposal.getCallIntegration();
} }
if (Media.audioOnly(media)) { if (Media.audioOnly(media)) {

View file

@ -80,6 +80,7 @@ public class RtpSessionActivity extends XmppActivity
public static final String EXTRA_WITH = "with"; public static final String EXTRA_WITH = "with";
public static final String EXTRA_SESSION_ID = "session_id"; public static final String EXTRA_SESSION_ID = "session_id";
public static final String EXTRA_PROPOSED_SESSION_ID = "proposed_session_id";
public static final String EXTRA_LAST_REPORTED_STATE = "last_reported_state"; public static final String EXTRA_LAST_REPORTED_STATE = "last_reported_state";
public static final String EXTRA_LAST_ACTION = "last_action"; public static final String EXTRA_LAST_ACTION = "last_action";
public static final String ACTION_ACCEPT_CALL = "action_accept_call"; public static final String ACTION_ACCEPT_CALL = "action_accept_call";
@ -386,7 +387,6 @@ public class RtpSessionActivity extends XmppActivity
} }
} }
private void putScreenInCallMode() { private void putScreenInCallMode() {
putScreenInCallMode(requireRtpConnection().getMedia()); putScreenInCallMode(requireRtpConnection().getMedia());
} }
@ -509,6 +509,16 @@ public class RtpSessionActivity extends XmppActivity
proposeJingleRtpSession(account, with, actionToMedia(action)); proposeJingleRtpSession(account, with, actionToMedia(action));
setWith(account.getRoster().getContact(with), null); setWith(account.getRoster().getContact(with), null);
} else if (Intent.ACTION_VIEW.equals(action)) { } else if (Intent.ACTION_VIEW.equals(action)) {
final String proposedSessionId = intent.getStringExtra(EXTRA_PROPOSED_SESSION_ID);
final JingleConnectionManager.TerminatedRtpSession terminatedRtpSession =
xmppConnectionService
.getJingleConnectionManager()
.getTerminalSessionState(with, proposedSessionId);
if (terminatedRtpSession != null) {
// termination (due to message error or 'busy' was faster than opening the activity
initializeWithTerminatedSessionState(account, with, terminatedRtpSession);
return;
}
final String extraLastState = intent.getStringExtra(EXTRA_LAST_REPORTED_STATE); final String extraLastState = intent.getStringExtra(EXTRA_LAST_REPORTED_STATE);
final String lastAction = intent.getStringExtra(EXTRA_LAST_ACTION); final String lastAction = intent.getStringExtra(EXTRA_LAST_ACTION);
final Set<Media> media = actionToMedia(lastAction); final Set<Media> media = actionToMedia(lastAction);
@ -1007,7 +1017,7 @@ public class RtpSessionActivity extends XmppActivity
private void updateInCallButtonConfiguration( private void updateInCallButtonConfiguration(
final RtpEndUserState state, final Set<Media> media) { final RtpEndUserState state, final Set<Media> media) {
if (STATES_CONSIDERED_CONNECTED.contains(state) && !isPictureInPicture()) { if (STATES_CONSIDERED_CONNECTED.contains(state) && !isPictureInPicture()) {
Preconditions.checkArgument(media.size() > 0, "Media must not be empty"); Preconditions.checkArgument(!media.isEmpty(), "Media must not be empty");
if (media.contains(Media.VIDEO)) { if (media.contains(Media.VIDEO)) {
final JingleRtpConnection rtpConnection = requireRtpConnection(); final JingleRtpConnection rtpConnection = requireRtpConnection();
updateInCallButtonConfigurationVideo( updateInCallButtonConfigurationVideo(
@ -1028,7 +1038,13 @@ public class RtpSessionActivity extends XmppActivity
} else if (STATES_SHOWING_SPEAKER_CONFIGURATION.contains(state) } else if (STATES_SHOWING_SPEAKER_CONFIGURATION.contains(state)
&& !isPictureInPicture() && !isPictureInPicture()
&& Media.audioOnly(media)) { && Media.audioOnly(media)) {
final CallIntegration callIntegration = requireCallIntegration(); final CallIntegration callIntegration;
try {
callIntegration = requireCallIntegration();
} catch (final IllegalStateException e) {
Log.e(Config.LOGTAG, "can not update InCallButtonConfiguration in state " + state);
return;
}
updateInCallButtonConfigurationSpeaker( updateInCallButtonConfigurationSpeaker(
callIntegration.getSelectedAudioDevice(), callIntegration.getSelectedAudioDevice(),
callIntegration.getAudioDevices().size()); callIntegration.getAudioDevices().size());

View file

@ -505,6 +505,7 @@ public class JingleConnectionManager extends AbstractConnectionManager {
getRtpSessionProposal(account, from.asBareJid(), sessionId); getRtpSessionProposal(account, from.asBareJid(), sessionId);
synchronized (rtpSessionProposals) { synchronized (rtpSessionProposals) {
if (proposal != null) { if (proposal != null) {
setTerminalSessionState(proposal, RtpEndUserState.DECLINED_OR_BUSY);
rtpSessionProposals.remove(proposal); rtpSessionProposals.remove(proposal);
proposal.callIntegration.busy(); proposal.callIntegration.busy();
writeLogMissedOutgoing( writeLogMissedOutgoing(
@ -938,7 +939,12 @@ public class JingleConnectionManager extends AbstractConnectionManager {
final DeviceDiscoveryState currentState = final DeviceDiscoveryState currentState =
sessionProposal == null ? null : rtpSessionProposals.get(sessionProposal); sessionProposal == null ? null : rtpSessionProposals.get(sessionProposal);
if (currentState == null) { if (currentState == null) {
Log.d(Config.LOGTAG, "unable to find session proposal for session id " + sessionId); Log.d(
Config.LOGTAG,
"unable to find session proposal for session id "
+ sessionId
+ " target="
+ target);
return; return;
} }
if (currentState == DeviceDiscoveryState.DISCOVERED) { if (currentState == DeviceDiscoveryState.DISCOVERED) {
@ -947,14 +953,7 @@ public class JingleConnectionManager extends AbstractConnectionManager {
"session proposal already at discovered. not going to fall back"); "session proposal already at discovered. not going to fall back");
return; return;
} }
this.rtpSessionProposals.put(sessionProposal, target);
final RtpEndUserState endUserState = target.toEndUserState();
if (endUserState == RtpEndUserState.RINGING) {
sessionProposal.callIntegration.setDialing();
}
// toneManager.transition(endUserState, sessionProposal.media);
mXmppConnectionService.notifyJingleRtpConnectionUpdate(
account, sessionProposal.with, sessionProposal.sessionId, endUserState);
Log.d( Log.d(
Config.LOGTAG, Config.LOGTAG,
account.getJid().asBareJid() account.getJid().asBareJid()
@ -962,6 +961,30 @@ public class JingleConnectionManager extends AbstractConnectionManager {
+ sessionId + sessionId
+ " as " + " as "
+ target); + target);
final RtpEndUserState endUserState = target.toEndUserState();
if (target == DeviceDiscoveryState.FAILED) {
Log.d(Config.LOGTAG, "removing session proposal after failure");
setTerminalSessionState(sessionProposal, endUserState);
this.rtpSessionProposals.remove(sessionProposal);
sessionProposal.getCallIntegration().error();
mXmppConnectionService.notifyJingleRtpConnectionUpdate(
account,
sessionProposal.with,
sessionProposal.sessionId,
endUserState);
return;
}
this.rtpSessionProposals.put(sessionProposal, target);
if (endUserState == RtpEndUserState.RINGING) {
sessionProposal.callIntegration.setDialing();
}
mXmppConnectionService.notifyJingleRtpConnectionUpdate(
account, sessionProposal.with, sessionProposal.sessionId, endUserState);
} }
} }
@ -1020,6 +1043,11 @@ public class JingleConnectionManager extends AbstractConnectionManager {
PersistableSessionId.of(id), new TerminatedRtpSession(state, media)); PersistableSessionId.of(id), new TerminatedRtpSession(state, media));
} }
void setTerminalSessionState(final RtpSessionProposal proposal, final RtpEndUserState state) {
this.terminatedSessions.put(
PersistableSessionId.of(proposal), new TerminatedRtpSession(state, proposal.media));
}
public TerminatedRtpSession getTerminalSessionState(final Jid with, final String sessionId) { public TerminatedRtpSession getTerminalSessionState(final Jid with, final String sessionId) {
return this.terminatedSessions.getIfPresent(new PersistableSessionId(with, sessionId)); return this.terminatedSessions.getIfPresent(new PersistableSessionId(with, sessionId));
} }
@ -1033,10 +1061,14 @@ public class JingleConnectionManager extends AbstractConnectionManager {
this.sessionId = sessionId; this.sessionId = sessionId;
} }
public static PersistableSessionId of(AbstractJingleConnection.Id id) { public static PersistableSessionId of(final AbstractJingleConnection.Id id) {
return new PersistableSessionId(id.with, id.sessionId); return new PersistableSessionId(id.with, id.sessionId);
} }
public static PersistableSessionId of(final RtpSessionProposal proposal) {
return new PersistableSessionId(proposal.with, proposal.sessionId);
}
@Override @Override
public boolean equals(Object o) { public boolean equals(Object o) {
if (this == o) return true; if (this == o) return true;

View file

@ -2708,14 +2708,14 @@ public class JingleRtpConnection extends AbstractJingleConnection
} }
@Override @Override
public void onCallIntegrationShowIncomingCallUi() { public synchronized void onCallIntegrationShowIncomingCallUi() {
if (isTerminated()) { if (isTerminated()) {
// there might be race conditions with the call integration service invoking this // there might be race conditions with the call integration service invoking this
// callback when the rtp session has already ended. It should be enough to just return // callback when the rtp session has already ended.
// instead of throwing an exception. however throwing an exception gives us a sense of Log.w(
// if and how frequently this happens Config.LOGTAG,
throw new IllegalStateException(
"CallIntegration requested incoming call UI but session was already terminated"); "CallIntegration requested incoming call UI but session was already terminated");
return;
} }
// TODO apparently this can be called too early as well? // TODO apparently this can be called too early as well?
xmppConnectionService.getNotificationService().startRinging(id, getMedia()); xmppConnectionService.getNotificationService().startRinging(id, getMedia());