Compare commits

...

2 commits

Author SHA1 Message Date
Peter Cai 414a94cb2f 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-11 22:37:27 -05:00
Peter Cai e585a89657 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-11 22:35:21 -05:00
3 changed files with 101 additions and 3 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;
@ -150,6 +153,29 @@ 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;
@ -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,17 @@ 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 onDisconnect() {
if (rtpConnection == null || rtpConnection.get() == null) {
@ -276,9 +317,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,59 @@ 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;