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

}

1

There are 1 answers

2
David On

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)
                }
            )