* 'for-singpolyma' of https://gitea.angry.im/PeterCxy/cheogram:
  ConnectionService: handle disconnected state correctly
  ConnectionService: implement onReject()
  ConnectionService: miscellaneous fixes
  ConnectionService: Dialer UI integration for incoming calls
  ConnectionService: fix unchecked type assignments
This commit is contained in:
Stephen Paul Weber 2022-03-14 15:29:23 -05:00
commit 14ca6d54e6
No known key found for this signature in database
GPG key ID: D11C2911CE519CDE
3 changed files with 128 additions and 15 deletions

View file

@ -5,9 +5,12 @@ import java.util.Collections;
import java.util.HashSet; import java.util.HashSet;
import java.util.Set; import java.util.Set;
import java.util.Stack; import java.util.Stack;
import java.util.Vector;
import com.google.common.base.Joiner;
import com.google.common.collect.ImmutableSet; import com.google.common.collect.ImmutableSet;
import android.os.Build;
import android.telecom.CallAudioState; import android.telecom.CallAudioState;
import android.telecom.Connection; import android.telecom.Connection;
import android.telecom.ConnectionRequest; import android.telecom.ConnectionRequest;
@ -119,7 +122,7 @@ public class ConnectionService extends android.telecom.ConnectionService {
.withSmallIcon(R.drawable.ic_notification).build() .withSmallIcon(R.drawable.ic_notification).build()
); );
Set<String> permissions = new HashSet(); Set<String> permissions = new HashSet<>();
permissions.add(Manifest.permission.RECORD_AUDIO); permissions.add(Manifest.permission.RECORD_AUDIO);
permissionManager.checkPermissions(permissions, new PermissionManager.PermissionRequestListener() { permissionManager.checkPermissions(permissions, new PermissionManager.PermissionRequestListener() {
@Override @Override
@ -133,7 +136,7 @@ public class ConnectionService extends android.telecom.ConnectionService {
@Override @Override
public void onPermissionDenied(DeniedPermissions deniedPermissions) { public void onPermissionDenied(DeniedPermissions deniedPermissions) {
connection.setDisconnected(new DisconnectCause(DisconnectCause.ERROR)); connection.close(new DisconnectCause(DisconnectCause.ERROR));
} }
}); });
@ -150,11 +153,34 @@ public class ConnectionService extends android.telecom.ConnectionService {
return connection; return connection;
} }
@Override
public Connection onCreateIncomingConnection(PhoneAccountHandle handle, ConnectionRequest request) {
Bundle extras = request.getExtras();
String accountJid = extras.getString("account");
String withJid = extras.getString("with");
String sessionId = extras.getString("sessionId");
Account account = xmppConnectionService.findAccountByJid(Jid.of(accountJid));
Jid with = Jid.of(withJid);
CheogramConnection connection = new CheogramConnection(account, with, null);
connection.setSessionId(sessionId);
connection.setAddress(
Uri.fromParts("tel", with.getLocal(), null),
TelecomManager.PRESENTATION_ALLOWED
);
connection.setRinging();
xmppConnectionService.setOnRtpConnectionUpdateListener(connection);
return connection;
}
public class CheogramConnection extends Connection implements XmppConnectionService.OnJingleRtpConnectionUpdate { public class CheogramConnection extends Connection implements XmppConnectionService.OnJingleRtpConnectionUpdate {
protected Account account; protected Account account;
protected Jid with; protected Jid with;
protected String sessionId = null; protected String sessionId = null;
protected Stack<String> postDial = new Stack(); protected Stack<String> postDial = new Stack<>();
protected Icon gatewayIcon; protected Icon gatewayIcon;
protected WeakReference<JingleRtpConnection> rtpConnection = null; protected WeakReference<JingleRtpConnection> rtpConnection = null;
@ -203,24 +229,28 @@ public class ConnectionService extends android.telecom.ConnectionService {
setInitialized(); setInitialized();
} else if (state == RtpEndUserState.RINGING) { } else if (state == RtpEndUserState.RINGING) {
setDialing(); setDialing();
} else if (state == RtpEndUserState.INCOMING_CALL) {
setRinging();
} else if (state == RtpEndUserState.CONNECTED) { } else if (state == RtpEndUserState.CONNECTED) {
xmppConnectionService.setDiallerIntegrationActive(true); xmppConnectionService.setDiallerIntegrationActive(true);
setActive(); setActive();
postDial(); postDial();
} else if (state == RtpEndUserState.DECLINED_OR_BUSY) { } else if (state == RtpEndUserState.DECLINED_OR_BUSY) {
setDisconnected(new DisconnectCause(DisconnectCause.BUSY)); close(new DisconnectCause(DisconnectCause.BUSY));
} else if (state == RtpEndUserState.ENDED) { } else if (state == RtpEndUserState.ENDED) {
setDisconnected(new DisconnectCause(DisconnectCause.LOCAL)); close(new DisconnectCause(DisconnectCause.LOCAL));
} else if (state == RtpEndUserState.RETRACTED) { } else if (state == RtpEndUserState.RETRACTED) {
setDisconnected(new DisconnectCause(DisconnectCause.CANCELED)); close(new DisconnectCause(DisconnectCause.CANCELED));
} else if (RtpSessionActivity.END_CARD.contains(state)) { } else if (RtpSessionActivity.END_CARD.contains(state)) {
setDisconnected(new DisconnectCause(DisconnectCause.ERROR)); close(new DisconnectCause(DisconnectCause.ERROR));
} }
} }
@Override @Override
public void onAudioDeviceChanged(AppRTCAudioManager.AudioDevice selectedAudioDevice, Set<AppRTCAudioManager.AudioDevice> availableAudioDevices) { public void onAudioDeviceChanged(AppRTCAudioManager.AudioDevice selectedAudioDevice, Set<AppRTCAudioManager.AudioDevice> availableAudioDevices) {
if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.O) return;
switch(selectedAudioDevice) { switch(selectedAudioDevice) {
case SPEAKER_PHONE: case SPEAKER_PHONE:
setAudioRoute(CallAudioState.ROUTE_SPEAKER); setAudioRoute(CallAudioState.ROUTE_SPEAKER);
@ -235,18 +265,40 @@ public class ConnectionService extends android.telecom.ConnectionService {
} }
} }
@Override
public void onAnswer() {
// For incoming calls, a connection update may not have been triggered before answering
// so we have to acquire the rtp connection object here
this.rtpConnection = xmppConnectionService.getJingleConnectionManager().findJingleRtpConnection(account, with, sessionId);
rtpConnection.get().acceptCall();
}
@Override
public void onReject() {
this.rtpConnection = xmppConnectionService.getJingleConnectionManager().findJingleRtpConnection(account, with, sessionId);
rtpConnection.get().rejectCall();
close(new DisconnectCause(DisconnectCause.LOCAL));
}
// Set the connection to the disconnected state and clean up the resources
// Note that we cannot do this from onStateChanged() because calling destroy
// there seems to trigger a deadlock somewhere in the telephony stack.
public void close(DisconnectCause reason) {
setDisconnected(reason);
destroy();
xmppConnectionService.setDiallerIntegrationActive(false);
xmppConnectionService.removeRtpConnectionUpdateListener(this);
}
@Override @Override
public void onDisconnect() { public void onDisconnect() {
if (rtpConnection == null || rtpConnection.get() == null) { if (rtpConnection == null || rtpConnection.get() == null) {
xmppConnectionService.getJingleConnectionManager().retractSessionProposal(account, with.asBareJid()); xmppConnectionService.getJingleConnectionManager().retractSessionProposal(account, with.asBareJid());
close(new DisconnectCause(DisconnectCause.LOCAL));
} else { } else {
rtpConnection.get().endCall(); rtpConnection.get().endCall();
} }
destroy();
xmppConnectionService.setDiallerIntegrationActive(false);
xmppConnectionService.removeRtpConnectionUpdateListener(
(XmppConnectionService.OnJingleRtpConnectionUpdate) this
);
} }
@Override @Override
@ -276,9 +328,9 @@ public class ConnectionService extends android.telecom.ConnectionService {
while (!postDial.empty()) { while (!postDial.empty()) {
String next = postDial.pop(); String next = postDial.pop();
if (next.equals(";")) { if (next.equals(";")) {
Stack v = (Stack) postDial.clone(); Vector<String> v = new Vector<>(postDial);
Collections.reverse(v); Collections.reverse(v);
setPostDialWait(String.join("", v)); setPostDialWait(Joiner.on("").join(v));
return; return;
} else if (next.equals(",")) { } else if (next.equals(",")) {
sleep(2000); sleep(2000);

View file

@ -599,7 +599,7 @@ public class Contact implements ListItem, Blockable {
"/" + getJid().asBareJid().toString(); "/" + getJid().asBareJid().toString();
} }
protected PhoneAccountHandle phoneAccountHandle() { public PhoneAccountHandle phoneAccountHandle() {
ComponentName componentName = new ComponentName( ComponentName componentName = new ComponentName(
"com.cheogram.android", "com.cheogram.android",
"com.cheogram.android.ConnectionService" "com.cheogram.android.ConnectionService"

View file

@ -1,5 +1,6 @@
package eu.siacs.conversations.services; package eu.siacs.conversations.services;
import android.Manifest;
import android.app.Notification; import android.app.Notification;
import android.app.NotificationChannel; import android.app.NotificationChannel;
import android.app.NotificationChannelGroup; import android.app.NotificationChannelGroup;
@ -8,6 +9,7 @@ import android.app.PendingIntent;
import android.content.Context; import android.content.Context;
import android.content.Intent; import android.content.Intent;
import android.content.SharedPreferences; import android.content.SharedPreferences;
import android.content.pm.PackageManager;
import android.content.res.Resources; import android.content.res.Resources;
import android.graphics.Bitmap; import android.graphics.Bitmap;
import android.graphics.Typeface; import android.graphics.Typeface;
@ -16,9 +18,12 @@ import android.media.Ringtone;
import android.media.RingtoneManager; import android.media.RingtoneManager;
import android.net.Uri; import android.net.Uri;
import android.os.Build; import android.os.Build;
import android.os.Bundle;
import android.os.SystemClock; import android.os.SystemClock;
import android.os.Vibrator; import android.os.Vibrator;
import android.preference.PreferenceManager; import android.preference.PreferenceManager;
import android.telecom.PhoneAccountHandle;
import android.telecom.TelecomManager;
import android.text.SpannableString; import android.text.SpannableString;
import android.text.style.StyleSpan; import android.text.style.StyleSpan;
import android.util.DisplayMetrics; import android.util.DisplayMetrics;
@ -423,7 +428,63 @@ public class NotificationService {
notify(DELIVERY_FAILED_NOTIFICATION_ID, summaryNotification); notify(DELIVERY_FAILED_NOTIFICATION_ID, summaryNotification);
} }
private synchronized boolean tryRingingWithDialerUI(final AbstractJingleConnection.Id id, final Set<Media> media) {
if (mXmppConnectionService.checkSelfPermission(Manifest.permission.RECORD_AUDIO) != PackageManager.PERMISSION_GRANTED) {
// We cannot request audio permission in Dialer UI
// when Dialer is shown over keyguard, the user cannot even necessarily
// see notifications.
return false;
}
if (media.size() != 1 || !media.contains(Media.AUDIO)) {
// Currently our ConnectionService only handles single audio calls
Log.w(Config.LOGTAG, "only audio calls can be handled by cheogram connection service");
return false;
}
PhoneAccountHandle handle = null;
for (Contact contact : id.account.getRoster().getContacts()) {
if (!contact.getJid().getDomain().equals(id.with.getDomain())) {
continue;
}
if (!contact.getPresences().anyIdentity("gateway", "pstn")) {
continue;
}
handle = contact.phoneAccountHandle();
break;
}
if (handle == null) {
Log.w(Config.LOGTAG, "Could not find phone account handle for " + id.account.getJid().toString());
return false;
}
Bundle callInfo = new Bundle();
callInfo.putString("account", id.account.getJid().toString());
callInfo.putString("with", id.with.toString());
callInfo.putString("sessionId", id.sessionId);
TelecomManager telecomManager = mXmppConnectionService.getSystemService(TelecomManager.class);
try {
telecomManager.addNewIncomingCall(handle, callInfo);
} catch (SecurityException e) {
// If the account is not registered or enabled, it could result in a security exception
// Just fall back to the built-in UI in this case.
Log.w(Config.LOGTAG, e);
return false;
}
return true;
}
public synchronized void startRinging(final AbstractJingleConnection.Id id, final Set<Media> media) { public synchronized void startRinging(final AbstractJingleConnection.Id id, final Set<Media> media) {
if (tryRingingWithDialerUI(id, media)) {
return;
}
showIncomingCallNotification(id, media); showIncomingCallNotification(id, media);
final NotificationManager notificationManager = (NotificationManager) mXmppConnectionService.getSystemService(Context.NOTIFICATION_SERVICE); final NotificationManager notificationManager = (NotificationManager) mXmppConnectionService.getSystemService(Context.NOTIFICATION_SERVICE);
final int currentInterruptionFilter; final int currentInterruptionFilter;