put 'video' in ongoing video call notification

This commit is contained in:
Daniel Gultsch 2020-04-15 22:40:37 +02:00
parent d7e93e18e5
commit 36e117979a
5 changed files with 93 additions and 39 deletions

View file

@ -372,10 +372,15 @@ public class NotificationService {
notify(INCOMING_CALL_NOTIFICATION_ID, notification); notify(INCOMING_CALL_NOTIFICATION_ID, notification);
} }
public Notification getOngoingCallNotification(final AbstractJingleConnection.Id id) { public Notification getOngoingCallNotification(final AbstractJingleConnection.Id id, final Set<Media> media) {
final NotificationCompat.Builder builder = new NotificationCompat.Builder(mXmppConnectionService, "ongoing_calls"); final NotificationCompat.Builder builder = new NotificationCompat.Builder(mXmppConnectionService, "ongoing_calls");
if (media.contains(Media.VIDEO)) {
builder.setSmallIcon(R.drawable.ic_videocam_white_24dp);
builder.setContentTitle(mXmppConnectionService.getString(R.string.ongoing_video_call));
} else {
builder.setSmallIcon(R.drawable.ic_call_white_24dp); builder.setSmallIcon(R.drawable.ic_call_white_24dp);
builder.setContentTitle(mXmppConnectionService.getString(R.string.ongoing_call)); builder.setContentTitle(mXmppConnectionService.getString(R.string.ongoing_call));
}
builder.setContentText(id.account.getRoster().getContact(id.with).getDisplayName()); builder.setContentText(id.account.getRoster().getContact(id.with).getDisplayName());
builder.setVisibility(NotificationCompat.VISIBILITY_PUBLIC); builder.setVisibility(NotificationCompat.VISIBILITY_PUBLIC);
builder.setPriority(NotificationCompat.PRIORITY_HIGH); builder.setPriority(NotificationCompat.PRIORITY_HIGH);

View file

@ -42,6 +42,7 @@ import android.util.Log;
import android.util.LruCache; import android.util.LruCache;
import android.util.Pair; import android.util.Pair;
import com.google.common.base.Objects;
import com.google.common.base.Strings; import com.google.common.base.Strings;
import org.conscrypt.Conscrypt; import org.conscrypt.Conscrypt;
@ -145,6 +146,7 @@ import eu.siacs.conversations.xmpp.chatstate.ChatState;
import eu.siacs.conversations.xmpp.forms.Data; import eu.siacs.conversations.xmpp.forms.Data;
import eu.siacs.conversations.xmpp.jingle.AbstractJingleConnection; import eu.siacs.conversations.xmpp.jingle.AbstractJingleConnection;
import eu.siacs.conversations.xmpp.jingle.JingleConnectionManager; import eu.siacs.conversations.xmpp.jingle.JingleConnectionManager;
import eu.siacs.conversations.xmpp.jingle.Media;
import eu.siacs.conversations.xmpp.jingle.RtpEndUserState; import eu.siacs.conversations.xmpp.jingle.RtpEndUserState;
import eu.siacs.conversations.xmpp.mam.MamReference; import eu.siacs.conversations.xmpp.mam.MamReference;
import eu.siacs.conversations.xmpp.pep.Avatar; import eu.siacs.conversations.xmpp.pep.Avatar;
@ -209,7 +211,7 @@ public class XmppConnectionService extends Service {
private AtomicBoolean mInitialAddressbookSyncCompleted = new AtomicBoolean(false); private AtomicBoolean mInitialAddressbookSyncCompleted = new AtomicBoolean(false);
private AtomicBoolean mForceForegroundService = new AtomicBoolean(false); private AtomicBoolean mForceForegroundService = new AtomicBoolean(false);
private AtomicBoolean mForceDuringOnCreate = new AtomicBoolean(false); private AtomicBoolean mForceDuringOnCreate = new AtomicBoolean(false);
private AtomicReference<AbstractJingleConnection.Id> ongoingCall = new AtomicReference<>(); private AtomicReference<OngoingCall> ongoingCall = new AtomicReference<>();
private OnMessagePacketReceived mMessageParser = new MessageParser(this); private OnMessagePacketReceived mMessageParser = new MessageParser(this);
private OnPresencePacketReceived mPresenceParser = new PresenceParser(this); private OnPresencePacketReceived mPresenceParser = new PresenceParser(this);
private IqParser mIqParser = new IqParser(this); private IqParser mIqParser = new IqParser(this);
@ -1228,24 +1230,23 @@ public class XmppConnectionService extends Service {
toggleForegroundService(false); toggleForegroundService(false);
} }
public void setOngoingCall(AbstractJingleConnection.Id id) { public void setOngoingCall(AbstractJingleConnection.Id id, Set<Media> media) {
ongoingCall.set(id); ongoingCall.set(new OngoingCall(id, media));
toggleForegroundService(false); toggleForegroundService(false);
} }
public void removeOngoingCall(AbstractJingleConnection.Id id) { public void removeOngoingCall() {
if (ongoingCall.compareAndSet(id, null)) { ongoingCall.set(null);
toggleForegroundService(false); toggleForegroundService(false);
} }
}
private void toggleForegroundService(boolean force) { private void toggleForegroundService(boolean force) {
final boolean status; final boolean status;
final AbstractJingleConnection.Id ongoing = ongoingCall.get(); final OngoingCall ongoing = ongoingCall.get();
if (force || mForceDuringOnCreate.get() || mForceForegroundService.get() || ongoing != null || (Compatibility.keepForegroundService(this) && hasEnabledAccounts())) { if (force || mForceDuringOnCreate.get() || mForceForegroundService.get() || ongoing != null || (Compatibility.keepForegroundService(this) && hasEnabledAccounts())) {
final Notification notification; final Notification notification;
if (ongoing != null) { if (ongoing != null) {
notification = this.mNotificationService.getOngoingCallNotification(ongoing); notification = this.mNotificationService.getOngoingCallNotification(ongoing.id, ongoing.media);
startForeground(NotificationService.ONGOING_CALL_NOTIFICATION_ID, notification); startForeground(NotificationService.ONGOING_CALL_NOTIFICATION_ID, notification);
mNotificationService.cancel(NotificationService.FOREGROUND_NOTIFICATION_ID); mNotificationService.cancel(NotificationService.FOREGROUND_NOTIFICATION_ID);
} else { } else {
@ -4753,4 +4754,27 @@ public class XmppConnectionService extends Service {
onStartCommand(intent, 0, 0); onStartCommand(intent, 0, 0);
} }
} }
public static class OngoingCall {
private final AbstractJingleConnection.Id id;
private final Set<Media> media;
public OngoingCall(AbstractJingleConnection.Id id, Set<Media> media) {
this.id = id;
this.media = media;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
OngoingCall that = (OngoingCall) o;
return Objects.equal(id, that.id);
}
@Override
public int hashCode() {
return Objects.hashCode(id);
}
}
} }

View file

@ -47,6 +47,12 @@ import static java.util.Arrays.asList;
public class RtpSessionActivity extends XmppActivity implements XmppConnectionService.OnJingleRtpConnectionUpdate { public class RtpSessionActivity extends XmppActivity implements XmppConnectionService.OnJingleRtpConnectionUpdate {
private static final List<RtpEndUserState> END_CARD = Arrays.asList(
RtpEndUserState.APPLICATION_ERROR,
RtpEndUserState.DECLINED_OR_BUSY,
RtpEndUserState.CONNECTIVITY_ERROR
);
private static final String PROXIMITY_WAKE_LOCK_TAG = "conversations:in-rtp-session"; private static final String PROXIMITY_WAKE_LOCK_TAG = "conversations:in-rtp-session";
private static final int REQUEST_ACCEPT_CALL = 0x1111; private static final int REQUEST_ACCEPT_CALL = 0x1111;
@ -116,23 +122,27 @@ public class RtpSessionActivity extends XmppActivity implements XmppConnectionSe
permissions = ImmutableList.of(Manifest.permission.RECORD_AUDIO); permissions = ImmutableList.of(Manifest.permission.RECORD_AUDIO);
} }
if (PermissionUtils.hasPermission(this, permissions, REQUEST_ACCEPT_CALL)) { if (PermissionUtils.hasPermission(this, permissions, REQUEST_ACCEPT_CALL)) {
//TODO like wise the propose; we might just wait here for the audio manager to come up
putScreenInCallMode(); putScreenInCallMode();
requireRtpConnection().acceptCall(); requireRtpConnection().acceptCall();
} }
} }
@SuppressLint("WakelockTimeout")
private void putScreenInCallMode() { private void putScreenInCallMode() {
//TODO for video calls we actually do want to keep the screen on putScreenInCallMode(requireRtpConnection().getMedia());
getWindow().clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); }
private void putScreenInCallMode(final Set<Media> media) {
getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
if (!media.contains(Media.VIDEO)) {
final JingleRtpConnection rtpConnection = rtpConnectionReference != null ? rtpConnectionReference.get() : null; final JingleRtpConnection rtpConnection = rtpConnectionReference != null ? rtpConnectionReference.get() : null;
final AppRTCAudioManager audioManager = rtpConnection == null ? null : rtpConnection.getAudioManager(); final AppRTCAudioManager audioManager = rtpConnection == null ? null : rtpConnection.getAudioManager();
if (audioManager == null || audioManager.getSelectedAudioDevice() == AppRTCAudioManager.AudioDevice.EARPIECE) { if (audioManager == null || audioManager.getSelectedAudioDevice() == AppRTCAudioManager.AudioDevice.EARPIECE) {
acquireProximityWakeLock(); acquireProximityWakeLock();
} }
} }
}
@SuppressLint("WakelockTimeout")
private void acquireProximityWakeLock() { private void acquireProximityWakeLock() {
final PowerManager powerManager = (PowerManager) getSystemService(Context.POWER_SERVICE); final PowerManager powerManager = (PowerManager) getSystemService(Context.POWER_SERVICE);
if (powerManager == null) { if (powerManager == null) {
@ -234,8 +244,7 @@ public class RtpSessionActivity extends XmppActivity implements XmppConnectionSe
private void proposeJingleRtpSession(final Account account, final Jid with, final Set<Media> media) { private void proposeJingleRtpSession(final Account account, final Jid with, final Set<Media> media) {
xmppConnectionService.getJingleConnectionManager().proposeJingleRtpSession(account, with, media); xmppConnectionService.getJingleConnectionManager().proposeJingleRtpSession(account, with, media);
//TODO maybe we dont want to acquire a wake lock just yet and wait for audio manager to discover what speaker we are using putScreenInCallMode(media);
putScreenInCallMode();
} }
@Override @Override
@ -570,10 +579,12 @@ public class RtpSessionActivity extends XmppActivity implements XmppConnectionSe
@Override @Override
public void onJingleRtpConnectionUpdate(Account account, Jid with, final String sessionId, RtpEndUserState state) { public void onJingleRtpConnectionUpdate(Account account, Jid with, final String sessionId, RtpEndUserState state) {
if (Arrays.asList(RtpEndUserState.APPLICATION_ERROR, RtpEndUserState.DECLINED_OR_BUSY, RtpEndUserState.DECLINED_OR_BUSY).contains(state)) {
releaseProximityWakeLock();
}
Log.d(Config.LOGTAG, "onJingleRtpConnectionUpdate(" + state + ")"); Log.d(Config.LOGTAG, "onJingleRtpConnectionUpdate(" + state + ")");
if (END_CARD.contains(state)) {
Log.d(Config.LOGTAG,"end card reached");
releaseProximityWakeLock();
runOnUiThread(()-> getWindow().clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON));
}
if (with.isBareJid()) { if (with.isBareJid()) {
updateRtpSessionProposalState(account, with, state); updateRtpSessionProposalState(account, with, state);
return; return;
@ -588,7 +599,7 @@ public class RtpSessionActivity extends XmppActivity implements XmppConnectionSe
if (state == RtpEndUserState.ENDED) { if (state == RtpEndUserState.ENDED) {
finish(); finish();
return; return;
} else if (asList(RtpEndUserState.APPLICATION_ERROR, RtpEndUserState.DECLINED_OR_BUSY, RtpEndUserState.CONNECTIVITY_ERROR).contains(state)) { } else if (END_CARD.contains(state)) {
//todo remember if we were video //todo remember if we were video
resetIntent(account, with, state); resetIntent(account, with, state);
} }
@ -608,14 +619,22 @@ public class RtpSessionActivity extends XmppActivity implements XmppConnectionSe
public void onAudioDeviceChanged(AppRTCAudioManager.AudioDevice selectedAudioDevice, Set<AppRTCAudioManager.AudioDevice> availableAudioDevices) { public void onAudioDeviceChanged(AppRTCAudioManager.AudioDevice selectedAudioDevice, Set<AppRTCAudioManager.AudioDevice> availableAudioDevices) {
Log.d(Config.LOGTAG, "onAudioDeviceChanged in activity: selected:" + selectedAudioDevice + ", available:" + availableAudioDevices); Log.d(Config.LOGTAG, "onAudioDeviceChanged in activity: selected:" + selectedAudioDevice + ", available:" + availableAudioDevices);
try { try {
if (requireRtpConnection().getEndUserState() == RtpEndUserState.CONNECTED && !getMedia().contains(Media.VIDEO)) { if (getMedia().contains(Media.VIDEO)) {
Log.d(Config.LOGTAG,"nothing to do; in video mode");
return;
}
final RtpEndUserState endUserState = requireRtpConnection().getEndUserState();
if (endUserState == RtpEndUserState.CONNECTED) {
final AppRTCAudioManager audioManager = requireRtpConnection().getAudioManager(); final AppRTCAudioManager audioManager = requireRtpConnection().getAudioManager();
updateInCallButtonConfigurationSpeaker( updateInCallButtonConfigurationSpeaker(
audioManager.getSelectedAudioDevice(), audioManager.getSelectedAudioDevice(),
audioManager.getAudioDevices().size() audioManager.getAudioDevices().size()
); );
} } else if (END_CARD.contains(endUserState)) {
Log.d(Config.LOGTAG,"onAudioDeviceChanged() nothing to do because end card has been reached");
} else {
putProximityWakeLockInProperState(); putProximityWakeLockInProperState();
}
} catch (IllegalStateException e) { } catch (IllegalStateException e) {
Log.d(Config.LOGTAG, "RTP connection was not available when audio device changed"); Log.d(Config.LOGTAG, "RTP connection was not available when audio device changed");
} }

View file

@ -289,9 +289,8 @@ public class JingleRtpConnection extends AbstractJingleConnection implements Web
} else { } else {
target = State.SESSION_INITIALIZED; target = State.SESSION_INITIALIZED;
} }
if (transition(target)) { if (transition(target, () -> this.initiatorRtpContentMap = contentMap)) {
respondOk(jinglePacket); respondOk(jinglePacket);
this.initiatorRtpContentMap = contentMap;
if (target == State.SESSION_INITIALIZED_PRE_APPROVED) { if (target == State.SESSION_INITIALIZED_PRE_APPROVED) {
Log.d(Config.LOGTAG, id.account.getJid().asBareJid() + ": automatically accepting session-initiate"); Log.d(Config.LOGTAG, id.account.getJid().asBareJid() + ": automatically accepting session-initiate");
sendSessionAccept(); sendSessionAccept();
@ -323,6 +322,7 @@ public class JingleRtpConnection extends AbstractJingleConnection implements Web
sendSessionTerminate(Reason.FAILED_APPLICATION, e.getMessage()); sendSessionTerminate(Reason.FAILED_APPLICATION, e.getMessage());
return; return;
} }
//TODO check that session accept content media matched ours
Log.d(Config.LOGTAG, "processing session-accept with " + contentMap.contents.size() + " contents"); Log.d(Config.LOGTAG, "processing session-accept with " + contentMap.contents.size() + " contents");
if (transition(State.SESSION_ACCEPTED)) { if (transition(State.SESSION_ACCEPTED)) {
respondOk(jinglePacket); respondOk(jinglePacket);
@ -500,7 +500,7 @@ public class JingleRtpConnection extends AbstractJingleConnection implements Web
final boolean originatedFromMyself = from.asBareJid().equals(id.account.getJid().asBareJid()); final boolean originatedFromMyself = from.asBareJid().equals(id.account.getJid().asBareJid());
if (originatedFromMyself) { if (originatedFromMyself) {
Log.d(Config.LOGTAG, id.account.getJid().asBareJid() + ": saw proposal from mysql. ignoring"); Log.d(Config.LOGTAG, id.account.getJid().asBareJid() + ": saw proposal from mysql. ignoring");
} else if (isInState(State.NULL)) { } else if (transition(State.PROPOSED, () -> {
final Collection<RtpDescription> descriptions = Collections2.transform( final Collection<RtpDescription> descriptions = Collections2.transform(
Collections2.filter(propose.getDescriptions(), d -> d instanceof RtpDescription), Collections2.filter(propose.getDescriptions(), d -> d instanceof RtpDescription),
input -> (RtpDescription) input input -> (RtpDescription) input
@ -509,7 +509,7 @@ public class JingleRtpConnection extends AbstractJingleConnection implements Web
Preconditions.checkState(!media.contains(Media.UNKNOWN), "RTP descriptions contain unknown media"); Preconditions.checkState(!media.contains(Media.UNKNOWN), "RTP descriptions contain unknown media");
Log.d(Config.LOGTAG, id.account.getJid().asBareJid() + ": received session proposal from " + from + " for " + media); Log.d(Config.LOGTAG, id.account.getJid().asBareJid() + ": received session proposal from " + from + " for " + media);
this.proposedMedia = Sets.newHashSet(media); this.proposedMedia = Sets.newHashSet(media);
transitionOrThrow(State.PROPOSED); })) {
if (serverMsgId != null) { if (serverMsgId != null) {
this.message.setServerMsgId(serverMsgId); this.message.setServerMsgId(serverMsgId);
} }
@ -720,10 +720,8 @@ public class JingleRtpConnection extends AbstractJingleConnection implements Web
return RtpEndUserState.CONNECTING; return RtpEndUserState.CONNECTING;
} else if (state == PeerConnection.PeerConnectionState.CLOSED) { } else if (state == PeerConnection.PeerConnectionState.CLOSED) {
return RtpEndUserState.ENDING_CALL; return RtpEndUserState.ENDING_CALL;
} else if (state == PeerConnection.PeerConnectionState.FAILED) {
return RtpEndUserState.CONNECTIVITY_ERROR;
} else { } else {
return RtpEndUserState.ENDING_CALL; return RtpEndUserState.CONNECTIVITY_ERROR;
} }
case REJECTED: case REJECTED:
case TERMINATED_DECLINED_OR_BUSY: case TERMINATED_DECLINED_OR_BUSY:
@ -876,10 +874,17 @@ public class JingleRtpConnection extends AbstractJingleConnection implements Web
return Arrays.asList(state).contains(this.state); return Arrays.asList(state).contains(this.state);
} }
private synchronized boolean transition(final State target) { private boolean transition(final State target) {
return transition(target, null);
}
private synchronized boolean transition(final State target, final Runnable runnable) {
final Collection<State> validTransitions = VALID_TRANSITIONS.get(this.state); final Collection<State> validTransitions = VALID_TRANSITIONS.get(this.state);
if (validTransitions != null && validTransitions.contains(target)) { if (validTransitions != null && validTransitions.contains(target)) {
this.state = target; this.state = target;
if (runnable != null) {
runnable.run();
}
Log.d(Config.LOGTAG, id.account.getJid().asBareJid() + ": transitioned into " + target); Log.d(Config.LOGTAG, id.account.getJid().asBareJid() + ": transitioned into " + target);
updateEndUserState(); updateEndUserState();
updateOngoingCallNotification(); updateOngoingCallNotification();
@ -909,7 +914,7 @@ public class JingleRtpConnection extends AbstractJingleConnection implements Web
if (newState == PeerConnection.PeerConnectionState.CONNECTED && this.rtpConnectionStarted == 0) { if (newState == PeerConnection.PeerConnectionState.CONNECTED && this.rtpConnectionStarted == 0) {
this.rtpConnectionStarted = SystemClock.elapsedRealtime(); this.rtpConnectionStarted = SystemClock.elapsedRealtime();
} }
if (newState == PeerConnection.PeerConnectionState.FAILED) { if (Arrays.asList(PeerConnection.PeerConnectionState.FAILED, PeerConnection.PeerConnectionState.DISCONNECTED).contains(newState)) {
if (TERMINATED.contains(this.state)) { if (TERMINATED.contains(this.state)) {
Log.d(Config.LOGTAG, id.account.getJid().asBareJid() + ": not sending session-terminate after connectivity error because session is already in state " + this.state); Log.d(Config.LOGTAG, id.account.getJid().asBareJid() + ": not sending session-terminate after connectivity error because session is already in state " + this.state);
return; return;
@ -949,9 +954,9 @@ public class JingleRtpConnection extends AbstractJingleConnection implements Web
private void updateOngoingCallNotification() { private void updateOngoingCallNotification() {
if (STATES_SHOWING_ONGOING_CALL.contains(this.state)) { if (STATES_SHOWING_ONGOING_CALL.contains(this.state)) {
xmppConnectionService.setOngoingCall(id); xmppConnectionService.setOngoingCall(id, getMedia());
} else { } else {
xmppConnectionService.removeOngoingCall(id); xmppConnectionService.removeOngoingCall();
} }
} }

View file

@ -903,6 +903,7 @@
<string name="rtp_state_application_failure">Application failure</string> <string name="rtp_state_application_failure">Application failure</string>
<string name="hang_up">Hang up</string> <string name="hang_up">Hang up</string>
<string name="ongoing_call">Ongoing call</string> <string name="ongoing_call">Ongoing call</string>
<string name="ongoing_video_call">Ongoing video call</string>
<string name="disable_tor_to_make_call">Disable Tor to make calls</string> <string name="disable_tor_to_make_call">Disable Tor to make calls</string>
<string name="incoming_call">Incoming call</string> <string name="incoming_call">Incoming call</string>
<string name="incoming_call_duration">Incoming call · %s</string> <string name="incoming_call_duration">Incoming call · %s</string>