4

I'm trying to create a call app using Android's ConnectionService. I can successfully receive calls, but I'm trying to pass application specific data through the "extras" parameter in TelecomManager.addNewIncomingCall. When I'm actually creating the Connection object in my ConnectionService class, however, I can't find where to access the Bundle that I set and passed in.

When I check the incoming parameters in onCreateIncomingConnection(PhoneAccountHandle connectionManagerPhoneAccount, ConnectionRequest request), neither connectionManagerPhoneAccount.getExtras() or request.getExtras() are the extras that I passed into TelecomManager.addNewIncomingCall.

Does anyone have experience setting and retrieving this extra object after passing it into TelecomManager.addNewIncomingCall?

I'm trying to pass the phone number that's calling into the Connection object so I can properly resolve the caller ID from the device's contact and display it.

Code:

package com.twilio.voice.quickstart;

...

public class VoiceActivity extends AppCompatActivity {

   

    @RequiresApi(api = Build.VERSION_CODES.M)
    private void handleIncomingCall() {
        Log.d(TAG, "handleIncomingCall() triggered");

        Bundle extras = new Bundle();
        Uri uri = Uri.fromParts(PhoneAccount.SCHEME_TEL, activeCallInvite.getFrom(), null);
        extras.putParcelable(TelecomManager.EXTRA_INCOMING_CALL_ADDRESS, uri);
        extras.putParcelable(TelecomManager.EXTRA_PHONE_ACCOUNT_HANDLE, handle);
        extras.putParcelable(Constants.INCOMING_CALL_INVITE, activeCallInvite);
        telecomManager.addNewIncomingCall(handle, extras);
    }



package com.twilio.voice.quickstart;

@RequiresApi(api = Build.VERSION_CODES.M)
public class VoiceConnectionService extends ConnectionService {
    private static final String TAG = "VoiceConnectionService";

    private static Connection connection;

    public static Connection getConnection() {
        return connection;
    }

    public static void deinitConnection() {
        connection = null;
    }

    @Override
    public Connection onCreateIncomingConnection(PhoneAccountHandle connectionManagerPhoneAccount, ConnectionRequest request) {
        //Not sure how to get the extra information to rebuild the twilio call invitation back up..
        Log.i(TAG, "Incoming Connection Request, came with extras: " + request.getExtras());
//        CallInvite activeCallInvite = intent.getParcelableExtra(Constants.INCOMING_CALL_INVITE);

        Connection incomingCallConnection = createConnection(request);
        incomingCallConnection.setRinging();
        return incomingCallConnection;
    }

    @Override
    public Connection onCreateOutgoingConnection(PhoneAccountHandle connectionManagerPhoneAccount, ConnectionRequest request) {
        Connection outgoingCallConnection = createConnection(request);
        outgoingCallConnection.setDialing();
        return outgoingCallConnection;
    }

    private Connection createConnection(ConnectionRequest request) {
        connection = new Connection() {

            @Override
            public void onStateChanged(int state) { //Is this where we can get the phone number & set the caller id?
                Log.i(TAG, "Connection Request: " + request.toString());
                if (state == Connection.STATE_DIALING) {
                    final Handler handler = new Handler();
                    handler.post(new Runnable() {
                        @Override
                        public void run() {
//                            sendCallRequestToActivity(ACTION_OUTGOING_CALL); //I think this is getting triggered as an extra thing....
                        }
                    });
                }
            }

            @Override
            public void onCallAudioStateChanged(CallAudioState state) {
                Log.d(TAG, "onCallAudioStateChanged called, current state is " + state);
            }

            @Override
            public void onPlayDtmfTone(char c) {
                Log.d(TAG, "onPlayDtmfTone called with DTMF " + c);
                Bundle extras = new Bundle();
                extras.putString(DTMF, Character.toString(c));
                connection.setExtras(extras);
                final Handler handler = new Handler();
                handler.post(new Runnable() {
                    @Override
                    public void run() {
                        sendCallRequestToActivity(ACTION_DTMF_SEND);
                    }
                });
            }

            @Override
            public void onDisconnect() {
                super.onDisconnect();
                connection.setDisconnected(new DisconnectCause(DisconnectCause.LOCAL));
                connection.destroy();
                connection = null;
                final Handler handler = new Handler();
                handler.post(new Runnable() {
                    @Override
                    public void run() {
                        sendCallRequestToActivity(ACTION_DISCONNECT_CALL);
                    }
                });
            }

            @Override
            public void onSeparate() {
                super.onSeparate();
            }

            @Override
            public void onAbort() {
                super.onAbort();
                connection.setDisconnected(new DisconnectCause(DisconnectCause.CANCELED));
                connection.destroy();
            }

            @Override
            public void onAnswer() {
                super.onAnswer();
                Log.i(TAG, "Connection was answered!");
                connection.setActive();
                final Handler handler = new Handler();
                handler.post(new Runnable() {
                    @Override
                    public void run() {
                        sendCallRequestToActivity(ACTION_ANSWER_CALL);
                    }
                });
            }

            @Override
            public void onReject() {
                super.onReject();
                connection.setDisconnected(new DisconnectCause(DisconnectCause.CANCELED));
                connection.destroy();
            }

            @Override
            public void onPostDialContinue(boolean proceed) {
                super.onPostDialContinue(true);
            }
        };

        //How to get the phone number, and resolve it to a contact/address?
        //https://developer.android.com/reference/android/telecom/Connection
        Log.i(TAG, "Creating the connection object for the incoming call, here are the extras: " + request.getExtras() + "; " + connection.getExtras());

        connection.setConnectionCapabilities(Connection.CAPABILITY_MUTE);
        if (request.getExtras().getString(CALLEE) == null) {
            connection.setAddress(request.getAddress(), TelecomManager.PRESENTATION_ALLOWED);
        } else {
            connection.setAddress(Uri.parse(request.getExtras().getString(CALLEE)), TelecomManager.PRESENTATION_ALLOWED);
        }

//both returning null
//        Log.i(TAG, "CALLEE: " + request.getExtras().getString(CALLEE)); 
//        Log.i(TAG, "CALLER: " + request.getExtras().getString(CALLER));
//        connection.setDialing();
        connection.setExtras(request.getExtras()); //This is where we want to connect..?

        //How can we get the caller Id...?
        connection.setCallerDisplayName("Example Contact.", TelecomManager.PRESENTATION_ALLOWED);
        return connection;
    }

    /*
     * Send call request to the VoiceConnectionServiceActivity
     */
    private void sendCallRequestToActivity(String action) {
        Log.i(TAG, "Transmitting broadcast from VoiceConnectionService to Voice Activity with action: " + action);
        Intent intent = new Intent(action);
        Bundle extras = new Bundle();
        switch (action) {
            case ACTION_OUTGOING_CALL:
                Uri address = connection.getAddress();
                extras.putString(OUTGOING_CALL_ADDRESS, address.toString());
                intent.putExtras(extras);
                intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_SINGLE_TOP);
                LocalBroadcastManager.getInstance(this).sendBroadcast(intent);
                break;
            case ACTION_DISCONNECT_CALL:
                extras.putInt("Reason", DisconnectCause.LOCAL);
                intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_SINGLE_TOP);
                intent.putExtras(extras);
                LocalBroadcastManager.getInstance(this).sendBroadcast(intent);
                break;
            case ACTION_DTMF_SEND:
                String d = connection.getExtras().getString(DTMF);
                extras.putString(DTMF, connection.getExtras().getString(DTMF));
                intent.putExtras(extras);
                intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_SINGLE_TOP);
                LocalBroadcastManager.getInstance(this).sendBroadcast(intent);
                break;
            case ACTION_ANSWER_CALL:
//                Uri address2 = connection.getAddress();
//                extras.putString(OUTGOING_CALL_ADDRESS, address2.toString());
                intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_SINGLE_TOP);
                intent.putExtras(extras);
                LocalBroadcastManager.getInstance(this).sendBroadcast(intent);
                break;
            default:
                break;
        }
    }

}

Kody_06
  • 163
  • 2
  • 9
  • Hi Martin, thanks for your comment, can you elaborate please? The documentation says: "extras", Bundle: A bundle that will be passed through to ConnectionService#onCreateIncomingConnection. Source: https://developer.android.com/reference/android/telecom/TelecomManager#addNewIncomingCall(android.telecom.PhoneAccountHandle,%20android.os.Bundle) – Kody_06 Feb 25 '21 at 05:15
  • You are not meant to handle an Intent, which your app does not receive. That's alike when your neighbor's phone is ringing and you want to go there and answer the call ...would you do that? – Martin Zeitler Feb 25 '21 at 05:19
  • Thanks for your comments. My application is receiving phone calls via Voice Over IP through Twilio, and I am using the phone's native UI by using ConnectionService to display the call and connect it. I have the proper permissions set up in the manifest file, and the user allows the permissions when app is first launched. I'm not quite sure what you're implying, I was under the impression this is pretty standard for a VOIP call app similar to WhatsApp – Kody_06 Feb 25 '21 at 05:26
  • @Kody_06 I'm facing a very similar issue, though after implementing the suggested 'solution' from the answer below, I'm running into a `ClassNotFoundException` when reading the `CallInvite` from the sent `Bundle` in `onCreateIncomingConnection`. Did you ever get this solution working? – CybeX Aug 10 '23 at 21:56

1 Answers1

3

I once had the same issue, and it took quite a while to discover the solution.

The key was to store my own bundle within the key EXTRA_INCOMING_CALL_EXTRAS, similar to the recommendation to use EXTRA_OUTGOING_CALL_EXTRAS for the outgoing call case.

val myBundle = Bundle().apply { 
    putParcelable(TWILIO_CALL_INVITE, callInvite)
}
telecomManager.addNewIncomingCall(
                phoneAccountHandle,
                Bundle().apply {
                    putBundle(EXTRA_INCOMING_CALL_EXTRAS, myBundle)
                    putParcelable(EXTRA_PHONE_ACCOUNT_HANDLE, phoneAccountHandle)
                }
            )

David
  • 31
  • 2
  • 2
    I've noticed that putting parcelables in `EXTRA_INCOMING_CALL_EXTRAS` don't work, but it does work for primitive types; at least in twice devices I've tested. – Rubén Viguera Aug 24 '21 at 09:47
  • 1
    echoing @RubénViguera for `EXTRA_OUTGOING_CALL_EXTRAS` - when trying to add Parcelable it simply doesn't work and one will not see the entire Extras – Tomer Petel Feb 13 '22 at 13:55