Compare commits

...

5 commits

Author SHA1 Message Date
Peter Cai d8d49e03a0 ConnectionService: implement onReject()
Forgot this for incoming calls
2022-03-12 13:08:42 -05:00
Peter Cai 6ca2ffd6ea ConnectionService: miscellaneous fixes
* Fix a few potential errors due to the use of newer APIs (minSDK is
  still only 24)
* Fix one remaining case of raw usage of generic types.
2022-03-12 12:50:29 -05:00
Peter Cai 788818fdda ConnectionService: Dialer UI integration for incoming calls
For incoming calls, we fall back to the built-in call UI if the
microphone permission is not granted. The reason is that if the Dialer
UI is displayed over keyguard, then the user may not even be able to see
the notification that we show in order for them to grant the permission.
Having a small annoyance for the first incoming call is better than
having the in-call UI hang.
2022-03-12 12:50:29 -05:00
Peter Cai b218b57c94 ConnectionService: fix unchecked type assignments 2022-03-12 12:50:29 -05:00
Stephen Paul Weber e02dee8c01
Bad number can also cause IllegalArgumentException 2022-03-11 23:00:49 -05:00
4 changed files with 113 additions and 6 deletions

View file

@ -5,9 +5,12 @@ import java.util.Collections;
import java.util.HashSet;
import java.util.Set;
import java.util.Stack;
import java.util.Vector;
import com.google.common.base.Joiner;
import com.google.common.collect.ImmutableSet;
import android.os.Build;
import android.telecom.CallAudioState;
import android.telecom.Connection;
import android.telecom.ConnectionRequest;
@ -119,7 +122,7 @@ public class ConnectionService extends android.telecom.ConnectionService {
.withSmallIcon(R.drawable.ic_notification).build()
);
Set<String> permissions = new HashSet();
Set<String> permissions = new HashSet<>();
permissions.add(Manifest.permission.RECORD_AUDIO);
permissionManager.checkPermissions(permissions, new PermissionManager.PermissionRequestListener() {
@Override
@ -150,11 +153,34 @@ public class ConnectionService extends android.telecom.ConnectionService {
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 {
protected Account account;
protected Jid with;
protected String sessionId = null;
protected Stack<String> postDial = new Stack();
protected Stack<String> postDial = new Stack<>();
protected Icon gatewayIcon;
protected WeakReference<JingleRtpConnection> rtpConnection = null;
@ -203,6 +229,8 @@ public class ConnectionService extends android.telecom.ConnectionService {
setInitialized();
} else if (state == RtpEndUserState.RINGING) {
setDialing();
} else if (state == RtpEndUserState.INCOMING_CALL) {
setRinging();
} else if (state == RtpEndUserState.CONNECTED) {
xmppConnectionService.setDiallerIntegrationActive(true);
setActive();
@ -221,6 +249,8 @@ public class ConnectionService extends android.telecom.ConnectionService {
@Override
public void onAudioDeviceChanged(AppRTCAudioManager.AudioDevice selectedAudioDevice, Set<AppRTCAudioManager.AudioDevice> availableAudioDevices) {
if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.O) return;
switch(selectedAudioDevice) {
case SPEAKER_PHONE:
setAudioRoute(CallAudioState.ROUTE_SPEAKER);
@ -235,6 +265,22 @@ 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();
setDisconnected(new DisconnectCause(DisconnectCause.LOCAL));
}
@Override
public void onDisconnect() {
if (rtpConnection == null || rtpConnection.get() == null) {
@ -276,9 +322,9 @@ public class ConnectionService extends android.telecom.ConnectionService {
while (!postDial.empty()) {
String next = postDial.pop();
if (next.equals(";")) {
Stack v = (Stack) postDial.clone();
Vector<String> v = new Vector<>(postDial);
Collections.reverse(v);
setPostDialWait(String.join("", v));
setPostDialWait(Joiner.on("").join(v));
return;
} else if (next.equals(",")) {
sleep(2000);

View file

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

View file

@ -1,5 +1,6 @@
package eu.siacs.conversations.services;
import android.Manifest;
import android.app.Notification;
import android.app.NotificationChannel;
import android.app.NotificationChannelGroup;
@ -8,6 +9,7 @@ import android.app.PendingIntent;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.content.pm.PackageManager;
import android.content.res.Resources;
import android.graphics.Bitmap;
import android.graphics.Typeface;
@ -16,9 +18,12 @@ import android.media.Ringtone;
import android.media.RingtoneManager;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.os.SystemClock;
import android.os.Vibrator;
import android.preference.PreferenceManager;
import android.telecom.PhoneAccountHandle;
import android.telecom.TelecomManager;
import android.text.SpannableString;
import android.text.style.StyleSpan;
import android.util.DisplayMetrics;
@ -423,7 +428,63 @@ public class NotificationService {
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) {
if (tryRingingWithDialerUI(id, media)) {
return;
}
showIncomingCallNotification(id, media);
final NotificationManager notificationManager = (NotificationManager) mXmppConnectionService.getSystemService(Context.NOTIFICATION_SERVICE);
final int currentInterruptionFilter;

View file

@ -276,7 +276,7 @@ public class EnterJidDialog extends DialogFragment implements OnBackendConnected
if (type != null && (type.equals("pstn") || type.equals("sms"))) {
try {
binding.jid.setText(PhoneNumberUtilWrapper.normalize(getActivity(), binding.jid.getText().toString()));
} catch (NumberParseException | NullPointerException e) { }
} catch (NumberParseException | IllegalArgumentException | NullPointerException e) { }
}
if (p == null) {