Vendor VoIP Integration Guide

Last updated:2026-08-27
Expand all
Vendor VoIP Integration Guide

This guide explains how to integrate the Jiguang Vendor VoIP Service Kit and implement outgoing calls, incoming calls, and call-state synchronization. Before integration, verify that the device, vendor push services, and VoIP entitlements meet all requirements.

Prerequisites

Device and system versions

Brand System version requirement
vivo OriginOS 6.0 or later
Xiaomi HyperOS 3.1 or later
OPPO ColorOS 16.0 or later
Honor MagicOS 10.0 or later

Note: Currently, only mobile phones sold in Mainland China are supported. Tablets and other device types are not supported.

Vendor system push services

VoIP messages depend on vendor system push channels for delivery. First enable and integrate the Xiaomi, Honor, vivo, and OPPO system push services. For details, see the Vendor Channel Parameter Application Guide and Vendor Channel SDK Integration Guide.

Vendor VoIP entitlements

VoIP entitlements are controlled by each vendor and must be requested by the app developer. An app without approval cannot launch the system incoming call UI even when the system version meets the requirement. See the following vendor documentation:

Important: If any of the three conditions above are not met, on() returns ERROR_FEATURE_UNSUPPORTED, and the module does not display the system incoming call UI. The app must implement subsequent call handling and a fallback UI.


Integrate dependencies

Add the following dependency to your project's build.gradle:

implementation 'cn.jiguang.sdk.plugin:voip-service-kit:6.2.1'
          implementation 'cn.jiguang.sdk.plugin:voip-service-kit:6.2.1'

        
This code block is shown in the floating window

This dependency is sufficient. No additional repository configuration or adaptation is required.

Note: The VoIP Service Kit SDK is downloaded automatically with the Jiguang voip-service-kit dependency. For offline integration or version verification, download the VoIP Service Kit SDK.

If you need to use a specified version of the VoIP Service Kit SDK, use the following configuration so the matching SDK is no longer pulled automatically:

implementation ('cn.jiguang.sdk.plugin:voip-service-kit:6.2.1') { // Do not automatically pull the matching VoIP Service Kit SDK. exclude group: 'cn.jiguang.sdk.plugin', module: 'voip_service_kit_th' } // Use a specified VoIP Service Kit SDK version. implementation files('libs/callService-1.0.0.2.aar')
          implementation ('cn.jiguang.sdk.plugin:voip-service-kit:6.2.1') {
    // Do not automatically pull the matching VoIP Service Kit SDK.
    exclude group: 'cn.jiguang.sdk.plugin', module: 'voip_service_kit_th'
}
// Use a specified VoIP Service Kit SDK version.
implementation files('libs/callService-1.0.0.2.aar')

        
This code block is shown in the floating window

After excluding the dependency, you must provide the VoIP Service Kit SDK yourself. Otherwise, the app will crash at runtime because classes are missing.


Usage guide

Outgoing calls

API description

API Description
on(Application context) Checks whether the current device supports the system incoming call UI.
register(JCallUiListener listener) Registers VoIP call callback events.
off() Unregisters VoIP call callback events.
reportOutgoingCall(JCallUiInfo call) Reports an outgoing call message.
reportCallAudioRouteEvent(String callId, int currentRoute, int supportRoute) Reports audio events during a call.
updateCallState(String callId, int callState) Reports in-app call state changes.
updateCallType(String callId, int callType) Reports in-app call type changes.
reportCallError(String callId, int errorCode, String errorMessage) Reports call failure.

For detailed API descriptions, see VoIP Call API.

Development steps

Step 1: Register VoIP call callback events
// Check whether the device is supported. int workable = JCallUiManager.getInstance().on(application); // Register callbacks. JCallUiListener mCallUiListener = new JCallUiListener() { @Override public void answerCall(String callId) { Log.i(TAG, "answerCall callId " + callId); } @Override public void answerCallWithType(String callId, int callType) { Log.i(TAG, "answerCallWithType callId " + callId + ", type " + callType); } @Override public void rejectCall(String callId) { Log.i(TAG, "rejectCall callId " + callId); } @Override public void changeCallType(String callId, int callType) { Log.i(TAG, "changeCallType callId " + callId + ", type " + callType); } @Override public void disconnectCall(String callId) { Log.i(TAG, "disconnectCall callId " + callId); } @Override public void mute(String callId, boolean mute) { Log.i(TAG, "mute callId " + callId + ", mute " + mute); } @Override public void changeAudioRoute(String callId, int route) { Log.i(TAG, "changeAudioRoute callId " + callId + ", route " + route); } @Override public void onCallKitEvent(int event, Bundle extras) { Log.i(TAG, "onCallKitEvent: " + event + " , extras " + extras); } }; if (workable == JCallUiConstants.ERROR_NONE) { JCallUiManager.getInstance().register(mCallUiListener); }
          // Check whether the device is supported.
int workable = JCallUiManager.getInstance().on(application);

// Register callbacks.
JCallUiListener mCallUiListener = new JCallUiListener() {
    @Override
    public void answerCall(String callId) {
        Log.i(TAG, "answerCall callId " + callId);
    }

    @Override
    public void answerCallWithType(String callId, int callType) {
        Log.i(TAG, "answerCallWithType callId " + callId + ", type " + callType);
    }

    @Override
    public void rejectCall(String callId) {
        Log.i(TAG, "rejectCall callId " + callId);
    }

    @Override
    public void changeCallType(String callId, int callType) {
        Log.i(TAG, "changeCallType callId " + callId + ", type " + callType);
    }

    @Override
    public void disconnectCall(String callId) {
        Log.i(TAG, "disconnectCall callId " + callId);
    }

    @Override
    public void mute(String callId, boolean mute) {
        Log.i(TAG, "mute callId " + callId + ", mute " + mute);
    }

    @Override
    public void changeAudioRoute(String callId, int route) {
        Log.i(TAG, "changeAudioRoute callId " + callId + ", route " + route);
    }

    @Override
    public void onCallKitEvent(int event, Bundle extras) {
        Log.i(TAG, "onCallKitEvent: " + event + " , extras " + extras);
    }
};

if (workable == JCallUiConstants.ERROR_NONE) {
    JCallUiManager.getInstance().register(mCallUiListener);
}

        
This code block is shown in the floating window

Return values:

  • ERROR_NONE: supported.
  • ERROR_FEATURE_UNSUPPORTED: not supported.

Step 2: Report an outgoing call event

After the app establishes an internal call connection, report the outgoing call with call information. For fields, see JCallUiInfo in the API document.

JCallUiInfo call = new JCallUiInfo(); call.setCallId("123456789"); call.setUserName("Mark"); call.setPackageName(getPackageName()); call.setActivityName(this.getClass().getName()); call.setVoipCallType(JCallUiConstants.VOIP_CALL_TYPE_VOICE); call.setVoipCallState(JCallUiConstants.VOIP_CALL_STATE_DIALING); call.setDirection(JCallUiConstants.DIRECTION_OUTGOING); // Report the outgoing call event. JCallUiManager.getInstance().reportOutgoingCall(call);
          JCallUiInfo call = new JCallUiInfo();
call.setCallId("123456789");
call.setUserName("Mark");
call.setPackageName(getPackageName());
call.setActivityName(this.getClass().getName());
call.setVoipCallType(JCallUiConstants.VOIP_CALL_TYPE_VOICE);
call.setVoipCallState(JCallUiConstants.VOIP_CALL_STATE_DIALING);
call.setDirection(JCallUiConstants.DIRECTION_OUTGOING);

// Report the outgoing call event.
JCallUiManager.getInstance().reportOutgoingCall(call);

        
This code block is shown in the floating window

Step 3: Report call state

After the peer answers, report the call state:

JCallUiManager.getInstance().updateCallState(callId, JCallUiConstants.VOIP_CALL_STATE_ACTIVE);
          JCallUiManager.getInstance().updateCallState(callId, JCallUiConstants.VOIP_CALL_STATE_ACTIVE);

        
This code block is shown in the floating window

Step 4: Report audio events during the call

During a call, users can choose different audio routes as needed, including earpiece, speaker, Bluetooth device, and wired headset. When the audio route is switched, the app receives the changeAudioRoute callback. After completing the switch, the app must call the API to report the current audio route.

String callId = "123456789"; int currentRoute = JCallUiConstants.ROUTE_SPEAKER; int supportRoute = JCallUiConstants.ROUTE_EARPIECE | JCallUiConstants.ROUTE_BLUETOOTH | JCallUiConstants.ROUTE_SPEAKER; JCallUiManager.getInstance().reportCallAudioRouteEvent(callId, currentRoute, supportRoute);
          String callId = "123456789";
int currentRoute = JCallUiConstants.ROUTE_SPEAKER;
int supportRoute = JCallUiConstants.ROUTE_EARPIECE | JCallUiConstants.ROUTE_BLUETOOTH | JCallUiConstants.ROUTE_SPEAKER;

JCallUiManager.getInstance().reportCallAudioRouteEvent(callId, currentRoute, supportRoute);

        
This code block is shown in the floating window

Step 5: Report in-app call type changes

Call types include video calls and voice calls. For scenarios such as answering a video incoming call as voice, downgrading video to voice during a call, or upgrading voice to video, the app must call the API to report the call type change.

String callId = "123456789"; JCallUiManager.getInstance().updateCallType(callId, JCallUiConstants.VOIP_CALL_TYPE_VOICE);
          String callId = "123456789";
JCallUiManager.getInstance().updateCallType(callId, JCallUiConstants.VOIP_CALL_TYPE_VOICE);

        
This code block is shown in the floating window

Step 6: Report call end

When the user taps the hang-up button on the notification, the app receives the disconnectCall callback. After handling the hang-up in the app, immediately call the API to report the call-ended state.

Important: To ensure system status synchronization and resource release, always call this API to report the ended state after a call ends.

JCallUiManager.getInstance().updateCallState(callId, JCallUiConstants.VOIP_CALL_STATE_DISCONNECTED);
          JCallUiManager.getInstance().updateCallState(callId, JCallUiConstants.VOIP_CALL_STATE_DISCONNECTED);

        
This code block is shown in the floating window

Step 7: Unregister

Unsubscribe after the call ends:

JCallUiManager.getInstance().off();
          JCallUiManager.getInstance().off();

        
This code block is shown in the floating window

Note: After unsubscribing, callbacks must be registered again before the next use.


Step 8: Report call errors

When an exception occurs during a call, the app must report the failure reason through the API:

JCallUiManager.getInstance().reportCallError(callId, errorCode, errorMessage);
          JCallUiManager.getInstance().reportCallError(callId, errorCode, errorMessage);

        
This code block is shown in the floating window

Incoming calls

API description

API Description
on(Application context) Checks whether the current device supports the system incoming call UI.
register(JCallUiListener listener) Registers VoIP call callback events.
off() Unregisters VoIP call callback events.
reportIncomingCall(JCallUiInfo call) Reports an incoming call message.
reportCallAudioRouteEvent(String callId, int currentRoute, int supportRoute) Reports audio events during a call.
updateCallState(String callId, int callState) Reports in-app call state changes.
updateCallType(String callId, int callType) Reports in-app call type changes.
reportCallError(String callId, int errorCode, String errorMessage) Reports call failure.

Development steps

Step 1: Detect a VoIP incoming call

Detect VoIP incoming calls through JPushMessageReceiver#onVoipMessage:

public class MyReceiver extends JPushMessageReceiver { @Override public void onVoipMessage(Context context, VoipDataMessage message) { // message.getVoipSupportStatus() indicates the device's VoIP capability for this delivery: // VOIP_STATUS_SUPPORTED = 2 Supported // VOIP_STATUS_UNSUPPORTED = 1 Unsupported // VOIP_STATUS_UNKNOWN = 0 Unknown // message.getExtraData() is the business-defined JSON payload. } }
          public class MyReceiver extends JPushMessageReceiver {
    @Override
    public void onVoipMessage(Context context, VoipDataMessage message) {
        // message.getVoipSupportStatus() indicates the device's VoIP capability for this delivery:
        //   VOIP_STATUS_SUPPORTED   = 2  Supported
        //   VOIP_STATUS_UNSUPPORTED = 1  Unsupported
        //   VOIP_STATUS_UNKNOWN     = 0  Unknown
        // message.getExtraData() is the business-defined JSON payload.
    }
}

        
This code block is shown in the floating window

Important: For devices that do not support the system incoming call UI, the integrator must decide how to display incoming calls.


Step 2: Register VoIP call callback events
int workable = JCallUiManager.getInstance().on(application); JCallUiListener mCallUiListener = new JCallUiListener() { @Override public void answerCall(String callId) { Log.i(TAG, "answerCall callId " + callId); } @Override public void answerCallWithType(String callId, int callType) { Log.i(TAG, "answerCallWithType callId " + callId + ", type " + callType); } @Override public void rejectCall(String callId) { Log.i(TAG, "rejectCall callId " + callId); } @Override public void changeCallType(String callId, int callType) { Log.i(TAG, "changeCallType callId " + callId + ", type " + callType); } @Override public void disconnectCall(String callId) { Log.i(TAG, "disconnectCall callId " + callId); } @Override public void mute(String callId, boolean mute) { Log.i(TAG, "mute callId " + callId + ", mute " + mute); } @Override public void changeAudioRoute(String callId, int route) { Log.i(TAG, "changeAudioRoute callId " + callId + ", route " + route); } @Override public void onCallKitEvent(int event, Bundle extras) { Log.i(TAG, "onCallKitEvent: " + event + " , extras " + extras); } }; if (workable == JCallUiConstants.ERROR_NONE) { JCallUiManager.getInstance().register(mCallUiListener); }
          int workable = JCallUiManager.getInstance().on(application);

JCallUiListener mCallUiListener = new JCallUiListener() {
    @Override
    public void answerCall(String callId) {
        Log.i(TAG, "answerCall callId " + callId);
    }

    @Override
    public void answerCallWithType(String callId, int callType) {
        Log.i(TAG, "answerCallWithType callId " + callId + ", type " + callType);
    }

    @Override
    public void rejectCall(String callId) {
        Log.i(TAG, "rejectCall callId " + callId);
    }

    @Override
    public void changeCallType(String callId, int callType) {
        Log.i(TAG, "changeCallType callId " + callId + ", type " + callType);
    }

    @Override
    public void disconnectCall(String callId) {
        Log.i(TAG, "disconnectCall callId " + callId);
    }

    @Override
    public void mute(String callId, boolean mute) {
        Log.i(TAG, "mute callId " + callId + ", mute " + mute);
    }

    @Override
    public void changeAudioRoute(String callId, int route) {
        Log.i(TAG, "changeAudioRoute callId " + callId + ", route " + route);
    }

    @Override
    public void onCallKitEvent(int event, Bundle extras) {
        Log.i(TAG, "onCallKitEvent: " + event + " , extras " + extras);
    }
};

if (workable == JCallUiConstants.ERROR_NONE) {
    JCallUiManager.getInstance().register(mCallUiListener);
}

        
This code block is shown in the floating window

Step 3: Report an incoming call event

After the app completes internal call connection setup, report the incoming call with call information. For fields, see JCallUiInfo in the API document:

JCallUiInfo call = new JCallUiInfo(); call.setCallId("123456789"); call.setMsgId("msg78680bjbp680"); call.setUserName("Mark"); call.setPackageName(getPackageName()); call.setActivityName(this.getClass().getName()); call.setVoipCallType(JCallUiConstants.VOIP_CALL_TYPE_VOICE); call.setVoipCallState(JCallUiConstants.VOIP_CALL_STATE_RINGING); call.setDirection(JCallUiConstants.DIRECTION_INCOMING); // Report the incoming call event. JCallUiManager.getInstance().reportIncomingCall(call);
          JCallUiInfo call = new JCallUiInfo();
call.setCallId("123456789");
call.setMsgId("msg78680bjbp680");
call.setUserName("Mark");
call.setPackageName(getPackageName());
call.setActivityName(this.getClass().getName());
call.setVoipCallType(JCallUiConstants.VOIP_CALL_TYPE_VOICE);
call.setVoipCallState(JCallUiConstants.VOIP_CALL_STATE_RINGING);
call.setDirection(JCallUiConstants.DIRECTION_INCOMING);

// Report the incoming call event.
JCallUiManager.getInstance().reportIncomingCall(call);

        
This code block is shown in the floating window

If the app is in the background, the system displays an incoming call banner notification.


Step 4: Report call state

Answer:

JCallUiManager.getInstance().updateCallState(callId, JCallUiConstants.VOIP_CALL_STATE_ACTIVE);
          JCallUiManager.getInstance().updateCallState(callId, JCallUiConstants.VOIP_CALL_STATE_ACTIVE);

        
This code block is shown in the floating window

Recommendation: Network conditions can introduce a delay of about one second between the user tapping Answer and the call actually connecting. During this delay, the notification remains in the incoming-call state and may appear unresponsive. Report VOIP_CALL_STATE_ANSWERED once while answering so the system UI can provide immediate feedback.

Reject:

After rejection succeeds, report VOIP_CALL_STATE_DISCONNECTED; the system cancels the call banner notification.


Step 5: Report audio events during the call

During a call, users can choose different audio routes as needed. When the audio route is switched, after completing the switch, the app must call the API to report the current audio route.

The implementation is the same as the outgoing call scenario. See Outgoing calls: Step 4.


Step 6: Report in-app call type changes

For scenarios such as answering a video incoming call as voice, downgrading video to voice during a call, or upgrading voice to video, the app must call the API to report the call type change.

The implementation is the same as the outgoing call scenario. See Outgoing calls: Step 5.


Step 7: Report call end

When the user taps the hang-up button on the notification, the app receives the hang-up callback. After handling the hang-up in the app, immediately call the API to report the call-ended state.

Important: To ensure system status synchronization and resource release, always report the ended state after a call ends. See Outgoing calls: Step 6.


Step 8: Unregister

Unsubscribe after the call ends.

See Outgoing calls: Step 7 for the implementation.


Step 9: Report call errors

When an exception occurs during a call, the app must report the failure reason through the API.

See Outgoing calls: Step 8 for the implementation.


App-side responsibilities

Pay attention to the following items during integration:

Item Description
Report VOIP_CALL_STATE_DISCONNECTED when the call ends Missing this report causes the system incoming call UI to remain and resources not to be released. This is the most common production issue for this type of integration.
Ringing timeout handling When no one answers, have the app report VOIP_CALL_STATE_DISCONNECTED on a timer.
Cleanup before off() If a call is still in progress, report VOIP_CALL_STATE_DISCONNECTED before calling off().
ERROR_SERVICE_BINDER_DIED in onCallKitEvent The system call service is disconnected. End the call yourself.
Avatar compression Avatar data is transferred through Binder. Oversized data may be rejected or even throw TransactionTooLargeException. Recommended: <=256px and <=200KB.
Push payload parsing The module does not parse extraData.
Keep-alive during a call If a foreground service is required, implement it yourself and declare foregroundServiceType and permissions.

Known limitations

  • The system incoming call page is drawn by each ROM and cannot be customized. Pixel-level consistency across vendors is not guaranteed.
  • On unsupported devices, the module does not display the system incoming call UI. The app must provide a fallback incoming call UI.
  • If vendor push services are not enabled or VoIP entitlements are not approved, on() is still rejected even if the device model and system version meet the requirements.
Was this document helpful?

Copyright 2011-2026, jiguang.cn, All Rights Reserved. 粤ICP备12056275号-13 Shenzhen Hexun Huagu Information Technology Co., Ltd.

Open in Docs Center