How to Make Phone Calls with the Nexmo Client SDK on Android

In this guide, you'll learn how to place a phone call from a Nexmo application to a phone device (PSTN) by implementing a webhook and linking that to a Nexmo application.

You will create an app to place a call. The app will log in a user called Jane. After logging in, Jane is able to place a call as well as to end it.

Nexmo Concepts

Before proceeding any further, here are couple of concepts that you'll need to understand.

A Nexmo application allows you to easily use Nexmo products, in this case the Voice API to build voice applications in the Cloud.

A Nexmo application requires two URLs as parameters:

  • answer_url - Nexmo will make a request to this URL as soon as someone makes a call to your Nexmo number. It contains the actions that will happen throughout the call.
  • event_url - Nexmo sends event information asynchronously to this URL when the call status changes; this ultimately depicts the flow of the call.

Both URLs need to return JSON and follow the Nexmo Call Control Object (NCCO) reference. In the example below, you will define an NCCO that reads a predefined text for an incoming call, using the Text to Speech engine.

A Nexmo virtual number will be associated with the app and serve as the "entry point" to it - this is the number you'll call to test the application.

For more information on Nexmo applications please visit the Nexmo API Reference.)

Prerequisites

Application webhook

For your application to place a phone call, you'll need to provide a URL as the Answer URL webhook. For the purpose of this tutorial, you will create a gist with the content below:

[
    {
        "action": "talk",
        "text": "Please wait while we connect you."
    },
    {
        "action": "connect",
        "timeout": 20,
        "from": "YOUR_NEXMO_NUMBER",
        "endpoint": [
            {
                "type": "phone",
                "number": "CALLEE_PHONE_NUMBER"
            }
        ]
    }
]

Do not forget to replace YOUR_NEXMO_NUMBER and CALLEE_PHONE_NUMBER with the relevant values for your app.

Once created, add the gist raw URL (make sure you're using the raw version) to your Nexmo dashboard. To do this, navigate to applications, select your application and click the 'Edit' button. Set the application's Answer URL and click 'Save changes'.

You will need to repeat this process every time you're changing the gist as a new revision (with the new raw URL) is being created.

Note: The gist you created is specific to this tutorial. In a real-life scenario, the Answer URL should be provided by a purposely built web solution. Your backend should provide that can serve custom NCCOs and, for this case, receive and validate the phone number dialled from the app.

The starter project

Clone or download the GitHub repository on either Kotlin orJava. On that repository you'll find two apps:

  • GetStartedCalls-Start - if you want to follow along and add the code with this tutorials
  • GetStartedCalls-Complete - if you want to look at the final result

Open the NexmoHelper class.

  1. Make sure that in enabledFeatures you have to Features.IN_APP_to_PHONE. You can remove the rest, if you haven't completed their tutorials yet.

  2. Replace the user IDs and tokens:

String USER_ID_JANE = "USR-XXX"; //TODO: replace with the UserId you generated for Jane
String JWT_JANE = "PLACEHOLDER";//TODO: replace with the JWT you generated for Jane
val USER_ID_JANE = "USR-XXX"; //TODO("replace with the UserId you generated for Jane")
val JWT_JANE = "PLACEHOLDER"; //TODO("replace with the JWT you generated for Jane")

Login

To start using the Nexmo Client SDK you need to log in to NexmoClient, using a JWT user token.

In production apps, your server would authenticate the user, and would return a correctly configured JWT to your app.

For testing and getting started purposes, you can use the Nexmo CLI to generate JWTs.

Open LoginActivity. It already has a button handler:onLoginJaneClick(...) that calls loginToSdk(...) method, with the jwt you provided.

When the login is successful, the logged in NexmoUser returns. For convenience, save a reference to NexmoUser on NexmoHelper, and then, start MainActivity.

Complete the loginToSdk() method implementation:

void loginToSdk(String token) {
    NexmoClient.get().login(token, new NexmoRequestListener<NexmoUser>() {

        @Override
        public void onError(NexmoApiError nexmoApiError) {}

        @Override
        public void onSuccess(NexmoUser user) {
            NexmoHelper.user = user;

            Intent intent = new Intent(getBaseContext(), MainActivity.class);
            startActivity(intent);
            finish();
        }
    });
}
fun loginToSdk(token: String) {
        NexmoClient.get().login(token, object : NexmoRequestListener<NexmoUser> {

            override fun onError(nexmoApiError: NexmoApiError) {
                notifyError(nexmoApiError)
            }

            override fun onSuccess(user: NexmoUser) {
                currentUser = user

                val intent = Intent(baseContext, MainActivity::class.java)
                startActivity(intent)
                finish()
            }
        })
    }
}

At this point you should already be able to run the app and see that you can login successfully with the SDK.

Start a call

You can now make an App-to-Phone call.

Open MainActivity and complete the prepared onPhoneCallClick() handler:

public void onPhoneCallClick(View view) {
    List<String> callee = new ArrayList<>();
    callee.add(CALLEE_PHONE_NUMBER); //TODO: swap with your phone number

    NexmoClient.get().call(callee, NexmoCallHandler.SERVER, callListener);
}
fun onPhoneCallClick(view: View) {
        val callee = listOf(CALLEE_PHONE_NUMBER) //TODO: swap with your phone number
        NexmoClient.get().call(callee, NexmoCallHandler.SERVER, callListener)
    }

You are expected to replace CALLEE_PHONE_NUMBER with the number to be called. But, ultimately, the number that is actually called is the one supplied in the Answer URL webhook. In a real-life use case, you would create a server component to serve as the Answer URL. The app will send to your backend, through the Answer URL the CALLEE_PHONE_NUMBER, the backend would validate it and then supply it in the JSON returned.

Note: Whilst the default HTTP method for the Answer URL is GET, POST can also be used.

Answer a call

Once a incoming call is received, it can be answered. Open IncomingCallActivity, and complete the prepared onAnswer() button handler, to start OnCallActivity after a successful answer:

public void onAnswer(View view) {
    NexmoHelper.currentCall.answer(new NexmoRequestListener<NexmoCall>() {
        @Override
        public void onError(NexmoApiError nexmoApiError) { }

        @Override
        public void onSuccess(NexmoCall call) {
            startActivity(new Intent(IncomingCallActivity.this, OnCallActivity.class));
            finish();
        }
    });
}
fun onAnswer(view: View) {
    currentCall?.answer(object : NexmoRequestListener<NexmoCall> {
        override fun onError(nexmoApiError: NexmoApiError) {}

        override fun onSuccess(call: NexmoCall) {
            startActivity(Intent(this@IncomingCallActivity, OnCallActivity::class.java))
            finish()
        }
    })
}

Hangup

The onHangup() handler allows to reject the call. Complete the implementation in IncomingCallActivity to finish the activity:

public void onHangup(View view) {
    NexmoHelper.currentCall.hangup(new NexmoRequestListener<NexmoCall>() {
        @Override
        public void onError(NexmoApiError nexmoApiError) { }

        @Override
        public void onSuccess(NexmoCall call) {
            finish();
        }
    });
}
currentCall?.hangup(object : NexmoRequestListener<NexmoCall> {
    override fun onError(nexmoApiError: NexmoApiError) {}

    override fun onSuccess(call: NexmoCall) {
        startActivity(Intent(this@IncomingCallActivity, OnCallActivity::class.java))
        finish()
    }
})

Register to call status

To be aware of the call status, for example, if one of the members answers or hangs up, you should register to CallEvents. The FinishOnCallEnd is a NexmoCallEventListener that finishes the current activity if the call is completed or canceled.

Register to its instance, to address the use cases mentioned previously.

On both OnCallActivity and IncomingCallActivity, add:

NexmoCallEventListener callEventListener = new FinishOnCallEnd(this);

@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
    NexmoHelper.currentCall.addCallEventListener(callEventListener);
}


@Override
protected void onDestroy() {
    NexmoHelper.currentCall.removeCallEventListener(callEventListener);
    super.onDestroy();
}
var callEventListener = FinishOnCallEnd(this)

override fun onCreate(savedInstanceState: Bundle?) {
    currentCall?.addCallEventListener(callEventListener)
}


override fun onDestroy() {
    currentCall?.removeCallEventListener(callEventListener)
    super.onDestroy()
}

Handle permissions

For devices running Android 6.0 (API level 23) and higher, creation and operation of calls requires requesting runtime permissions. To simplify the implementation in this tutorial, BaseActivity checks the permissions in every Activity's onStart() and onStop().

To read more about the permissions required, see the setup tutorial.

Conclusion

Congratulations! You have implemented your first Phone to App Voice application with the Nexmo Client SDK for Android.

Run the app on a simulator or a device, and with another device call the Nexmo Number you linked. Then, see that you can answer, reject and hangup a call received on the phone number associated with your Nexmo application.