~singpolyma/cheogram-android

7be174db1372ee95f29d57eaa819976f438e6382 — Stephen Paul Weber 2 years ago 92aed12 + ffc1daa
Merge branch 'phoneaccount'

* phoneaccount:
  any means none means false (ie there exist) unless upstream reports a reason
  Small fix to address cheogram adding "+1" when making calls from the default dialer. Now only adds "+1" or "+" when necessary.
  First version of dialer integration
  Revert "Intercept DIAL and CALL to tel: and rewrite to cheogram"
M src/cheogram/AndroidManifest.xml => src/cheogram/AndroidManifest.xml +11 -0
@@ 3,7 3,18 @@
    xmlns:tools="http://schemas.android.com/tools"
    package="eu.siacs.conversations">

    <uses-permission android:name="android.permission.BIND_TELECOM_CONNECTION_SERVICE" />

    <application tools:ignore="GoogleAppIndexingWarning">

        <service android:name="com.cheogram.android.ConnectionService"
            android:label="Cheogram"
            android:permission="android.permission.BIND_TELECOM_CONNECTION_SERVICE">
            <intent-filter>
                <action android:name="android.telecom.ConnectionService" />
            </intent-filter>
        </service>

        <activity
            android:name=".ui.ManageAccountActivity"
            android:label="@string/title_activity_manage_accounts"

A src/cheogram/java/com/cheogram/android/ConnectionService.java => src/cheogram/java/com/cheogram/android/ConnectionService.java +116 -0
@@ 0,0 1,116 @@
package com.cheogram.android;

import android.telecom.Connection;
import android.telecom.ConnectionRequest;
import android.telecom.PhoneAccount;
import android.telecom.PhoneAccountHandle;
import android.telecom.StatusHints;
import android.telecom.TelecomManager;
import android.telecom.DisconnectCause;

import android.content.Intent;
import android.os.Bundle;
import android.os.Parcel;

import eu.siacs.conversations.xmpp.Jid;
import eu.siacs.conversations.ui.RtpSessionActivity;

public class ConnectionService extends android.telecom.ConnectionService {
	@Override
	public Connection onCreateOutgoingConnection(
		PhoneAccountHandle phoneAccountHandle,
		ConnectionRequest request
	) {
		String[] gateway = phoneAccountHandle.getId().split("/", 2);
		Connection connection = new CheogramConnection();
		connection.setAddress(
			request.getAddress(),
			TelecomManager.PRESENTATION_ALLOWED
		);
		connection.setAudioModeIsVoip(true);
		connection.setDialing();
		connection.setRingbackRequested(true);
		connection.setConnectionCapabilities(
			Connection.CAPABILITY_CAN_SEND_RESPONSE_VIA_CONNECTION
		);

		// TODO: jabber:iq:gateway
		String tel = request.getAddress().getSchemeSpecificPart().
		           replaceAll("[^\\+0-9]", "");
		if (tel.startsWith("1")) {
			tel = "+" + tel;
		} else if (!tel.startsWith("+")) {
			tel = "+1" + tel;
		}

		// Instead of wiring the call up to the Android call UI,
		// just show our UI for now.  This means both are showing during a call.
		final Intent intent = new Intent(this, RtpSessionActivity.class);
		intent.setAction(RtpSessionActivity.ACTION_MAKE_VOICE_CALL);
		Bundle extras = new Bundle();
		extras.putString(
			RtpSessionActivity.EXTRA_ACCOUNT,
			Jid.of(gateway[0]).toEscapedString()
		);
		extras.putString(
			RtpSessionActivity.EXTRA_WITH,
			Jid.ofLocalAndDomain(tel, gateway[1]).toEscapedString()
		);
		extras.putBinder(
			RtpSessionActivity.EXTRA_CONNECTION_BINDER,
			new ConnectionBinder(connection)
		);
		intent.putExtras(extras);
		intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
		intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK);
		startActivity(intent);

		return connection;
	}

	public class ConnectionBinder extends android.os.Binder {
		protected Connection connection;

		public static final int TRANSACT_ACTIVE = android.os.IBinder.FIRST_CALL_TRANSACTION + 1;
		public static final int TRANSACT_DISCONNECT = TRANSACT_ACTIVE + 1;

		ConnectionBinder(Connection connection) {
			super();
			this.connection = connection;
		}

		@Override
		protected boolean onTransact(int code, Parcel data, Parcel reply, int flags) {
			switch(code) {
				case TRANSACT_ACTIVE:
					this.connection.setActive();
					connection.setRingbackRequested(false);
					return true;
				case TRANSACT_DISCONNECT:
					this.connection.setDisconnected(
						new DisconnectCause(DisconnectCause.UNKNOWN)
					);
					return true;
				default:
					return false;
			}
		}
	}

	public class CheogramConnection extends Connection {
		@Override
		public void onDisconnect() {
			destroy();
		}

		@Override
		public void onAbort() {
			onDisconnect();
		}

		@Override
		public void onPlayDtmfTone(char c) {
			// TODO
		}
	}
}

M src/main/AndroidManifest.xml => src/main/AndroidManifest.xml +0 -8
@@ 144,14 144,6 @@
                <data android:host="jabber" />
            </intent-filter>
            <intent-filter>
                <action android:name="android.intent.action.VIEW" />
                <action android:name="android.intent.action.CALL" />
                <action android:name="android.intent.action.DIAL" />
                <category android:name="android.intent.category.DEFAULT" />
                <category android:name="android.intent.category.BROWSABLE" />
                <data android:scheme="tel" />
            </intent-filter>
            <intent-filter>
                <action android:name="android.intent.action.SENDTO" />

                <category android:name="android.intent.category.DEFAULT" />

M src/main/java/eu/siacs/conversations/entities/Contact.java => src/main/java/eu/siacs/conversations/entities/Contact.java +37 -0
@@ 1,9 1,14 @@
package eu.siacs.conversations.entities;

import android.content.ComponentName;
import android.content.ContentValues;
import android.content.Context;
import android.database.Cursor;
import android.net.Uri;
import android.os.Bundle;
import android.telecom.PhoneAccount;
import android.telecom.PhoneAccountHandle;
import android.telecom.TelecomManager;
import android.text.TextUtils;

import androidx.annotation.NonNull;


@@ 559,6 564,38 @@ public class Contact implements ListItem, Blockable {
        return changed;
    }

    protected String phoneAccountLabel() {
        return account.getJid().asBareJid().toString() +
            "/" + getJid().asBareJid().toString();
    }

    protected PhoneAccountHandle phoneAccountHandle() {
        ComponentName componentName = new ComponentName(
            "com.cheogram.android",
            "com.cheogram.android.ConnectionService"
        );
        return new PhoneAccountHandle(componentName, phoneAccountLabel());
    }

    // This Contact is a gateway to use for voice calls, register it with OS
    public void registerAsPhoneAccount(Context ctx) {
        TelecomManager telecomManager = ctx.getSystemService(TelecomManager.class);

        PhoneAccount phoneAccount = PhoneAccount.builder(
            phoneAccountHandle(), phoneAccountLabel()
        ).setCapabilities(
            PhoneAccount.CAPABILITY_CALL_PROVIDER
        ).build();

        telecomManager.registerPhoneAccount(phoneAccount);
    }

    // Unregister any associated PSTN gateway integration
    public void unregisterAsPhoneAccount(Context ctx) {
        TelecomManager telecomManager = ctx.getSystemService(TelecomManager.class);
        telecomManager.unregisterPhoneAccount(phoneAccountHandle());
    }

    public static int getOption(Class<? extends AbstractPhoneContact> clazz) {
        if (clazz == JabberIdContact.class) {
            return Options.SYNCED_VIA_ADDRESSBOOK;

M src/main/java/eu/siacs/conversations/entities/Presences.java => src/main/java/eu/siacs/conversations/entities/Presences.java +16 -0
@@ 149,6 149,22 @@ public class Presences {
        return false;
    }

    public boolean anyIdentity(final String category, final String type) {
        synchronized (this.presences) {
            if (this.presences.size() == 0) {
                // https://github.com/iNPUTmice/Conversations/issues/4230
                return false;
            }
            for (Presence presence : this.presences.values()) {
                ServiceDiscoveryResult disco = presence.getServiceDiscoveryResult();
                if (disco != null && disco.hasIdentity(category, type)) {
                    return true;
                }
            }
        }
        return false;
    }

    public Pair<Map<String, String>, Map<String, String>> toTypeAndNameMap() {
        Map<String, String> typeMap = new HashMap<>();
        Map<String, String> nameMap = new HashMap<>();

M src/main/java/eu/siacs/conversations/services/XmppConnectionService.java => src/main/java/eu/siacs/conversations/services/XmppConnectionService.java +13 -0
@@ 233,6 233,10 @@ public class XmppConnectionService extends Service {
                }
            }
        }

        if (contact.getPresences().anyIdentity("gateway", "pstn")) {
            contact.registerAsPhoneAccount(this);
        }
    };
    private final PresenceGenerator mPresenceGenerator = new PresenceGenerator(this);
    private List<Account> accounts;


@@ 1968,6 1972,7 @@ public class XmppConnectionService extends Service {


    public void syncRoster(final Account account) {
        unregisterPhoneAccounts(account);
        mRosterSyncTaskManager.execute(account, () -> databaseBackend.writeRoster(account.getRoster()));
    }



@@ 3449,6 3454,14 @@ public class XmppConnectionService extends Service {
        }
    }

    protected void unregisterPhoneAccounts(final Account account) {
        for (final Contact contact : account.getRoster().getContacts()) {
            if (!contact.showInRoster()) {
                contact.unregisterAsPhoneAccount(this);
            }
        }
    }

    public void createContact(final Contact contact, final boolean autoGrant) {
        createContact(contact, autoGrant, null);
    }

M src/main/java/eu/siacs/conversations/ui/RtpSessionActivity.java => src/main/java/eu/siacs/conversations/ui/RtpSessionActivity.java +37 -0
@@ 71,12 71,18 @@ import eu.siacs.conversations.xmpp.jingle.JingleRtpConnection;
import eu.siacs.conversations.xmpp.jingle.Media;
import eu.siacs.conversations.xmpp.jingle.RtpEndUserState;

import com.cheogram.android.ConnectionService.ConnectionBinder;

import static eu.siacs.conversations.utils.PermissionUtils.getFirstDenied;
import static java.util.Arrays.asList;

public class RtpSessionActivity extends XmppActivity implements XmppConnectionService.OnJingleRtpConnectionUpdate, eu.siacs.conversations.ui.widget.SurfaceViewRenderer.OnAspectRatioChanged {

    public static final String EXTRA_WITH = "with";
    public static final String EXTRA_SESSION_ID = "session_id";
    public static final String EXTRA_LAST_REPORTED_STATE = "last_reported_state";
    public static final String EXTRA_LAST_ACTION = "last_action";
    public static final String EXTRA_CONNECTION_BINDER = "connection_binder";
    public static final String ACTION_ACCEPT_CALL = "action_accept_call";
    public static final String ACTION_MAKE_VOICE_CALL = "action_make_voice_call";
    public static final String ACTION_MAKE_VIDEO_CALL = "action_make_video_call";


@@ 142,6 148,8 @@ public class RtpSessionActivity extends XmppActivity implements XmppConnectionSe
        }
    }

    protected android.os.IBinder connectionBinder = null;

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);


@@ 160,6 168,8 @@ public class RtpSessionActivity extends XmppActivity implements XmppConnectionSe
            boolean dialpadVisible = savedInstanceState.getBoolean("dialpad_visible");
            binding.dialpad.setVisibility(dialpadVisible ? View.VISIBLE : View.GONE);
        }

        this.connectionBinder = getIntent().getExtras().getBinder(EXTRA_CONNECTION_BINDER);
    }

    @Override


@@ 262,6 272,29 @@ public class RtpSessionActivity extends XmppActivity implements XmppConnectionSe
        } else {
            requireRtpConnection().endCall();
        }
        disconnectConnectionBinder();
    }

    private void disconnectConnectionBinder() {
        if (connectionBinder != null) {
            android.os.Parcel args = android.os.Parcel.obtain();
            try {
                connectionBinder.transact(ConnectionBinder.TRANSACT_DISCONNECT, args, null, 0);
            } catch (android.os.RemoteException e) {}
            args.recycle();
        }
    }

    private void activateConnectionBinder() {
        // If we do this, the other UI takes over and kills our call
        // So we can't activate that UI unless we are going to use it.
        /*if (connectionBinder != null) {
            android.os.Parcel args = android.os.Parcel.obtain();
            try {
                connectionBinder.transact(ConnectionBinder.TRANSACT_ACTIVE, args, null, 0);
            } catch (android.os.RemoteException e) {}
            args.recycle();
        }*/
    }

    private void retractSessionProposal() {


@@ 1153,10 1186,14 @@ public class RtpSessionActivity extends XmppActivity implements XmppConnectionSe
    @Override
    public void onJingleRtpConnectionUpdate(Account account, Jid with, final String sessionId, RtpEndUserState state) {
        Log.d(Config.LOGTAG, "onJingleRtpConnectionUpdate(" + state + ")");
        if (state == RtpEndUserState.CONNECTED) {
            activateConnectionBinder();
        }
        if (END_CARD.contains(state)) {
            Log.d(Config.LOGTAG, "end card reached");
            releaseProximityWakeLock();
            runOnUiThread(() -> getWindow().clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON));
            disconnectConnectionBinder();
        }
        if (with.isBareJid()) {
            updateRtpSessionProposalState(account, with, state);

M src/main/java/eu/siacs/conversations/ui/UriHandlerActivity.java => src/main/java/eu/siacs/conversations/ui/UriHandlerActivity.java +0 -2
@@ 260,8 260,6 @@ public class UriHandlerActivity extends AppCompatActivity {
                break;
            case Intent.ACTION_VIEW:
            case Intent.ACTION_SENDTO:
            case Intent.ACTION_DIAL:
            case Intent.ACTION_CALL:
                if (handleUri(data.getData())) {
                    finish();
                }

M src/main/java/eu/siacs/conversations/utils/XmppUri.java => src/main/java/eu/siacs/conversations/utils/XmppUri.java +0 -2
@@ 179,8 179,6 @@ public class XmppUri {
            } catch (final UnsupportedEncodingException ignored) {
                jid = null;
            }
        } else if ("tel".equalsIgnoreCase(scheme)) {
            jid = uri.getSchemeSpecificPart().replaceAll("[^\\d\\+]+", "") + "@cheogram.com";
        } else {
            jid = null;
        }