A React Native module for telecommunication functionality based on Android's InCall and android.telecom Java APIs. This module enables you to build Android applications that can communicate with calls, such as custom dialers.
- âś… Android Support: Full support for Android platform
- âś… Audio Call Events: Complete audio call event handling and actions
- âś… Background Processing: Continues working when app goes to background
- âś… React Native 0.60+: Compatible with modern React Native versions (AndroidX support)
- âś… Custom Dialer: Can replace the default Android dialer
- âś… Call Management: Make, answer, hangup, hold, mute, and manage calls
- âś… Event System: Comprehensive event system for call state changes
- React Native 0.60 or higher
- Android SDK
- Android device or emulator
npm install react-native-tele
# or
yarn add react-native-teleFollow the detailed Android installation guide: Android Installation Guide
Add the following permissions to your android/app/src/main/AndroidManifest.xml:
<uses-permission android:name="android.permission.READ_PHONE_STATE" />
<uses-permission android:name="android.permission.CALL_PHONE" />
<uses-permission android:name="android.permission.READ_CALL_LOG" />
<uses-permission android:name="android.permission.MODIFY_PHONE_STATE" />
<uses-permission android:name="android.permission.READ_CONTACTS" />To build the Android library (AAR) for use in other projects or for distribution:
- Open a terminal and navigate to the root of the repository.
- Run the following command:
cd android && ./gradlew bundleReleaseAar- After a successful build, the generated
.aarfile will be located at:
android/app/build/outputs/aar/app-release.aar
You can use this file as a standard Android library in your projects.
import { Endpoint } from 'react-native-tele';
class MyApp extends Component {
async componentDidMount() {
// Initialize the telecommunication module
const endpoint = new Endpoint();
try {
// Start the module and get current state
const state = await endpoint.start({
ReplaceDialer: false, // Set to true to replace default dialer
Permissions: false // Set to true to request permissions automatically
});
const { calls, settings } = state;
console.log('Current calls:', calls);
console.log('Settings:', settings);
// Subscribe to call events
this.setupEventListeners(endpoint);
} catch (error) {
console.error('Failed to start tele module:', error);
}
}
setupEventListeners(endpoint) {
endpoint.on("call_received", (call) => {
console.log("Incoming call from:", call.getRemoteNumber());
});
endpoint.on("call_changed", (call) => {
console.log("Call state changed:", call.getState());
});
endpoint.on("call_terminated", (call) => {
console.log("Call ended:", call.getRemoteNumber());
});
}
}async makeCall(phoneNumber) {
try {
const call = await this.endpoint.makeCall(
1, // SIM slot (1 or 2)
phoneNumber, // Phone number to call
{}, // Call settings (optional)
{} // Message data (optional)
);
console.log("Call initiated with ID:", call.getId());
// Listen for this specific call
this.endpoint.on("call_changed", (updatedCall) => {
if (call.getId() === updatedCall.getId()) {
console.log("Our call state:", updatedCall.getState());
}
});
} catch (error) {
console.error("Failed to make call:", error);
}
}async answerCall(call) {
try {
await this.endpoint.answerCall(call);
console.log("Call answered successfully");
} catch (error) {
console.error("Failed to answer call:", error);
}
}The main class for managing telecommunication functionality.
Initializes the telecommunication module.
Parameters:
configuration(Object):ReplaceDialer(Boolean): Set to true to replace default dialerPermissions(Boolean): Set to true to request permissions automatically
Returns: Promise that resolves with current state including calls and settings
Makes an outgoing call.
Parameters:
sim(Number): SIM slot (1 or 2)destination(String): Phone number to callcallSettings(Object): Call configuration (optional)msgData(Object): Additional message data (optional)
Returns: Promise that resolves with Call instance
Answers an incoming call.
Parameters:
call(Call): Call instance to answer
Returns: Promise
Hangs up a call.
Parameters:
call(Call): Call instance to hangup
Returns: Promise
Declines an incoming call (sends 603 response).
Parameters:
call(Call): Call instance to decline
Returns: Promise
Puts a call on hold.
Parameters:
call(Call): Call instance to hold
Returns: Promise
Takes a call off hold.
Parameters:
call(Call): Call instance to unhold
Returns: Promise
Mutes a call.
Parameters:
call(Call): Call instance to mute
Returns: Promise
Unmutes a call.
Parameters:
call(Call): Call instance to unmute
Returns: Promise
Enables speaker mode for a call.
Parameters:
call(Call): Call instance
Returns: Promise
Enables earpiece mode for a call.
Parameters:
call(Call): Call instance
Returns: Promise
Represents a call instance with various properties and methods.
id(Number): Unique call identifierstate(String): Current call statedirection(String): Call direction (incoming/outgoing)remoteNumber(String): Remote party's phone numberremoteName(String): Remote party's name (if available)
Returns the call's unique identifier.
Returns: Number
Returns the current call state.
Returns: String
Returns the remote party's phone number.
Returns: String
Returns the remote party's name.
Returns: String
Returns the total call duration in seconds.
Returns: Number
Returns the connected call duration in seconds.
Returns: Number
Checks if the call is on hold.
Returns: Boolean
Checks if the call is muted.
Returns: Boolean
Checks if speaker mode is enabled.
Returns: Boolean
The Endpoint class extends EventEmitter and provides the following events:
Emitted when an incoming call is received.
Callback: (call: Call) => void
Emitted when a call's state changes.
Callback: (call: Call) => void
Emitted when a call ends.
Callback: (call: Call) => void
Emitted when the screen is locked during a call.
Callback: (lock: Boolean) => void
Emitted when connectivity status changes.
Callback: (available: Boolean) => void
import React, { Component } from 'react';
import { View, Text, TouchableOpacity, TextInput } from 'react-native';
import { Endpoint } from 'react-native-tele';
class DialerApp extends Component {
state = {
phoneNumber: '',
currentCall: null,
isInCall: false
};
async componentDidMount() {
this.endpoint = new Endpoint();
try {
await this.endpoint.start({ ReplaceDialer: true });
this.setupEventListeners();
} catch (error) {
console.error('Failed to initialize:', error);
}
}
setupEventListeners() {
this.endpoint.on("call_received", (call) => {
this.setState({
currentCall: call,
isInCall: true,
phoneNumber: call.getRemoteNumber()
});
});
this.endpoint.on("call_changed", (call) => {
if (call.getState() === "PJSIP_INV_STATE_DISCONNECTED") {
this.setState({ currentCall: null, isInCall: false });
}
});
this.endpoint.on("call_terminated", (call) => {
this.setState({ currentCall: null, isInCall: false });
});
}
async makeCall() {
if (!this.state.phoneNumber) return;
try {
const call = await this.endpoint.makeCall(1, this.state.phoneNumber);
this.setState({ currentCall: call, isInCall: true });
} catch (error) {
console.error('Call failed:', error);
}
}
async answerCall() {
if (!this.state.currentCall) return;
try {
await this.endpoint.answerCall(this.state.currentCall);
} catch (error) {
console.error('Answer failed:', error);
}
}
async hangupCall() {
if (!this.state.currentCall) return;
try {
await this.endpoint.hangupCall(this.state.currentCall);
} catch (error) {
console.error('Hangup failed:', error);
}
}
render() {
return (
<View style={{ flex: 1, padding: 20 }}>
<TextInput
value={this.state.phoneNumber}
onChangeText={(text) => this.setState({ phoneNumber: text })}
placeholder="Enter phone number"
keyboardType="phone-pad"
style={{ borderWidth: 1, padding: 10, marginBottom: 20 }}
/>
{!this.state.isInCall ? (
<TouchableOpacity onPress={this.makeCall}>
<Text>Call</Text>
</TouchableOpacity>
) : (
<View>
<TouchableOpacity onPress={this.answerCall}>
<Text>Answer</Text>
</TouchableOpacity>
<TouchableOpacity onPress={this.hangupCall}>
<Text>Hangup</Text>
</TouchableOpacity>
</View>
)}
</View>
);
}
}To replace the default Android dialer:
const state = await endpoint.start({ ReplaceDialer: true });This will prompt the user to set your app as the default dialer.
To automatically request required permissions:
const state = await endpoint.start({ Permissions: true });-
NativeModule.TeleModule is null
- Rebuild and re-run the app
- Check that the library was linked correctly
- For unit testing, mock the native module
-
Permission Denied
- Ensure all required permissions are added to AndroidManifest.xml
- Request permissions at runtime if needed
-
Call not working in background
- The module continues working in background automatically
- Check that your app has proper background permissions
Enable debug logging by checking the console output. The module provides detailed logs for troubleshooting.
We welcome contributions! Please feel free to submit a Pull Request. For major changes, please open an issue first to discuss what you would like to change.
- Fork the repository
- Create your feature branch (
git checkout -b feature/AmazingFeature) - Commit your changes (
git commit -m 'Add some AmazingFeature') - Push to the branch (
git push origin feature/AmazingFeature) - Open a Pull Request
This project is licensed under the ISC License - see the LICENSE file for details.
- Issues: GitHub Issues
- Documentation: GitHub Wiki
- Examples: Examples Directory
- react-native-replace-dialer - Replace default Android dialer
- react-native-sip2 - SIP communication module
Note: This module is currently Android-only. iOS support is planned for future releases.
FROM LEGACY installation.md:
TODO: REPLACE TO INCALL SERVICE!!!
Add permissions & service to android/app/src/main/AndroidManifest.xml
<uses-feature android:name="android.hardware.camera" />
<uses-feature android:name="android.hardware.camera.autofocus"/>
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.CAMERA" />
<uses-permission android:name="android.permission.RECORD_AUDIO"/>
<uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS"/>
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
<uses-permission android:name="android.permission.WAKE_LOCK"/>
<uses-permission android:name="android.permission.PROCESS_OUTGOING_CALLS"/>
<uses-permission android:name="android.permission.CALL_PHONE"/>
<uses-permission android:name="android.permission.READ_PHONE_STATE" /><application>
...
<service
android:name=""
android:enabled="true"
android:exported="true" />
...
</application>react-native linkIn android/app/src/main/java/com/xxx/MainActivity.java
import android.view.Window;
import android.view.WindowManager;
import android.os.Bundle;
...
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Window w = getWindow();
w.setFlags(
WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED,
WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED
);
}If your Android targetSdk is 23 or above you should grant android.permission.RECORD_AUDIO at runtime before making/receiving an audio call.
To check and request Android permissions, please check out react-native-android-permissions.