~singpolyma/cheogram-android

14ca6d54e6fb00a31d2e5aab0fae3c6418bbd305 — Stephen Paul Weber 10 months ago 7014e5e + 437002d
Merge branch 'for-singpolyma' of https://gitea.angry.im/PeterCxy/cheogram

* '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
M src/cheogram/java/com/cheogram/android/ConnectionService.java => src/cheogram/java/com/cheogram/android/ConnectionService.java +66 -14
@@ 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


@@ 133,7 136,7 @@ public class ConnectionService extends android.telecom.ConnectionService {

			@Override
			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;
	}

	@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,24 229,28 @@ 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();

				postDial();
			} else if (state == RtpEndUserState.DECLINED_OR_BUSY) {
				setDisconnected(new DisconnectCause(DisconnectCause.BUSY));
				close(new DisconnectCause(DisconnectCause.BUSY));
			} else if (state == RtpEndUserState.ENDED) {
				setDisconnected(new DisconnectCause(DisconnectCause.LOCAL));
				close(new DisconnectCause(DisconnectCause.LOCAL));
			} else if (state == RtpEndUserState.RETRACTED) {
				setDisconnected(new DisconnectCause(DisconnectCause.CANCELED));
				close(new DisconnectCause(DisconnectCause.CANCELED));
			} else if (RtpSessionActivity.END_CARD.contains(state)) {
				setDisconnected(new DisconnectCause(DisconnectCause.ERROR));
				close(new DisconnectCause(DisconnectCause.ERROR));
			}
		}

		@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);


@@ 236,17 266,39 @@ 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
		public void onDisconnect() {
			if (rtpConnection == null || rtpConnection.get() == null) {
				xmppConnectionService.getJingleConnectionManager().retractSessionProposal(account, with.asBareJid());
				close(new DisconnectCause(DisconnectCause.LOCAL));
			} else {
				rtpConnection.get().endCall();
			}
			destroy();
			xmppConnectionService.setDiallerIntegrationActive(false);
			xmppConnectionService.removeRtpConnectionUpdateListener(
				(XmppConnectionService.OnJingleRtpConnectionUpdate) this
			);
		}

		@Override


@@ 276,9 328,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);

M src/main/java/eu/siacs/conversations/entities/Contact.java => src/main/java/eu/siacs/conversations/entities/Contact.java +1 -1
@@ 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"

M src/main/java/eu/siacs/conversations/services/NotificationService.java => src/main/java/eu/siacs/conversations/services/NotificationService.java +61 -0
@@ 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;