I use React Native 0.47.1 in the article, if it doesn’t work for your version, please let me know.

In this article I want to show you how to build a simple Android service using React Native for call recording. Call recording is a complex task, so we’ll make a very simple one that uses MIC as a source. Our goal is to understand how to use a broadcast receiver and services together with React Native. Let’s get started!

Not all devices and Android versions support call recording, you can hear only your side of a phone call, but it’s not a big deal in our case, right?

Preparation

Install dependencies

Let’s make a new RN project with

react-native init CallRecorder

Once the app is Android only, we can completely remove an iOS folder and index.ios.js:

rm -R ./ios index.ios.js

We’re going to record an audio and play so we need two modules for it:

npm install --save react-native-audio react-native-sound

We have to link these libraries, because they use a native code:

react-native link

Add permissions

To record audio and calls we need some permissions, add them to AndroidManifest.xml right after other uses-permissions:

CallRecorder/android/app/src/main/AndroidManifest.xml

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

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

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

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

Make sure your targetSdkVersion is 22 in app build.gradle, because if it is above these permissions wouldn’t work, you have to ask permissions in runtime else.

CallRecorder/android/app/build.gradle

Creating service and receiver

In simple words Android service is a code kept in background and receiver is a code that will execute itself when some of registered events is fired. So in our case the receiver will call our service when something happened like a new outgoing call is started or a phone state is changed (for example, a phone is picked up).

Registering

First of all, we have to register them in AndroidManifest.xml inside application :

<service android:name="com.callrecorder.service.RecService" />

<receiver android:name="com.callrecorder.receiver.RecReceiver">

<intent-filter android:priority="0">

<action android:name="android.intent.action.PHONE_STATE" />

<action android:name="android.intent.action.NEW_OUTGOING_CALL" />

</intent-filter>

</receiver>

Now let’s register our JS task, add these lines to index.android.js:

const Rec = async (data) => {

console.log('It works!')

} AppRegistry.registerHeadlessTask('Rec', () => Rec)

Write code

Create two files:

java/com/callrecorder/service/RecService.java java/com/callrecorder/receiver/RecReceiver.java

Add this code into RecService, just like documentation says (https://facebook.github.io/react-native/docs/headless-js-android.html):

package com.callrecorder.service; import android.content.Intent;

import android.os.Bundle;

import com.facebook.react.HeadlessJsTaskService;

import com.facebook.react.bridge.Arguments;

import com.facebook.react.jstasks.HeadlessJsTaskConfig;

import javax.annotation.Nullable;

@Nullable

protected HeadlessJsTaskConfig getTaskConfig(Intent intent) {

Bundle extras = intent.getExtras();

return new HeadlessJsTaskConfig(

"Rec",

extras != null ? Arguments.fromBundle(extras) : null,

5000);

}

} public class RecService extends HeadlessJsTaskService {protected HeadlessJsTaskConfig getTaskConfig(Intent intent) {Bundle extras = intent.getExtras();return new HeadlessJsTaskConfig("Rec",extras != null ? Arguments.fromBundle(extras) : null,5000);

The main part here is new HeadlessJsTaskConfig("Rec", extras != null ? Arguments.fromBundle(extras) : null, 5000); it will call our JS task, that’s all we want from RecService.

And here is code for our RecReceiver:

package com.callrecorder.receiver; import android.content.BroadcastReceiver;

import android.content.Context;

import android.content.Intent;

import com.callrecorder.service.RecService;

import com.facebook.react.HeadlessJsTaskService; public final class RecReceiver extends BroadcastReceiver {

public final void onReceive(Context context, Intent intent) {

Intent recIntent = new Intent(context, RecService.class);

context.startService(recIntent);

HeadlessJsTaskService.acquireWakeLockNow(context);

}

}

Now it’s just calls our RecService if some of events is happened.

First run

Run your app with react-native run-android

If you have an error such as method does not override or implement a method from a supertype it means react-native-audio doesn’t merge PR like https://github.com/jsierles/react-native-audio/pull/206, so probably you have to manually patch it

Enable Remote JS Debugging mode and try to call to someone, on a new outgoing call and any phone state changes our JS function will be executed and you can see “It works!” several times in a console output.

So it’s time to record a call.

Recording

Once a JS task is executed on both “new outgoing call” and “phone state change” we want to know which event called our function. As you can see we have a data argument const Rec = async (data) => { , now it’s null, to pass some params to it we add a code like this to the receiver:

if (intent.getAction().equals("android.intent.action.PHONE_STATE")) {

recIntent.putExtra("action", "phone_state");

} else {

recIntent.putExtra("action", "new_outgoing_call");

}

Let’s output data now:

const Rec = async (data) => {

console.log('DATA:', data)

}

Now we know what action the function is processing using data.action , it’ll be either phone_state or new_outgoing_call

We also want to determine if it’s an outgoing or incoming call, what phone state is ‘ringing’, ‘offhook’ or ‘idle’ and what incoming phone number is. Let’s add some code for it:

...

import android.telephony.TelephonyManager;

... if (intent.getAction().equals("android.intent.action.PHONE_STATE")) {

recIntent.putExtra("action", "phone_state");

String phoneState = intent.getStringExtra("state");

if (phoneState.equals(TelephonyManager.EXTRA_STATE_RINGING)) {

String phoneNumber = intent.getStringExtra("incoming_number");

incomingCall = true;

recIntent.putExtra("state", "extra_state_ringing");

recIntent.putExtra("incoming_call", true);

recIntent.putExtra("number", phoneNumber);

} else if (phoneState.equals(TelephonyManager.EXTRA_STATE_OFFHOOK)) {

if (incomingCall) {

incomingCall = false;

}

recIntent.putExtra("state", "extra_state_offhook");

recIntent.putExtra("incoming_call", false);

} else if (phoneState.equals(TelephonyManager.EXTRA_STATE_IDLE)) {

if (incomingCall) {

incomingCall = false;

}

recIntent.putExtra("state", "extra_state_idle");

recIntent.putExtra("incoming_call", false);

}

} else {

recIntent.putExtra("action", "new_outgoing_call");

}

Finally, to start our recording let’s rewrite the Rec function:

import { AudioRecorder, AudioUtils } from 'react-native-audio' const audioPath = AudioUtils.DocumentDirectoryPath + '/record.aac' const Rec = async (data) => {

if (data.state === 'extra_state_offhook') {

AudioRecorder.prepareRecordingAtPath(audioPath, {

SampleRate: 22050,

Channels: 1,

AudioQuality: "Low",

AudioEncoding: "aac"

}) await AudioRecorder.startRecording()

} else if (data.state === 'extra_state_idle') {

await AudioRecorder.stopRecording()

}

}

And add some code to the main component to play the record:

import Sound from 'react-native-sound'

onPress = () => {

// These timeouts are a hacky workaround for some issues with react-native-sound.

// See

setTimeout(() => {

const sound = new Sound(audioPath, '') export default class CallRecorder extends Component {onPress = () => {// These timeouts are a hacky workaround for some issues with react-native-sound.// See https://github.com/zmxv/react-native-sound/issues/89 setTimeout(() => {const sound = new Sound(audioPath, '') setTimeout(() => {

sound.play((success) => {

if (!success) Alert.alert('Error', 'no records found')

})

}, 100)

}, 100)

} render() {

return (

<View style={styles.container}>

<Button title='Play the last record' onPress={this.onPress} />

</View>

);

}

}

Final code is here:

Thanks. You can play with data arguments to save a phone number etc. Comments are welcome!