In-app call messages empower your app with voice and video call capabilities. Push Kit processes an in-app call message differently when the device screen is locked or unlocked:
When the device screen is locked, a user can tap the answer or reject button on the lock screen. In this situation, only voice calls are supported.
When the device screen is unlocked, a banner notification is displayed to inform the user of the incoming video or voice call. In this situation, both the voice and video calls are supported.
When a user answers a video call, the in-app call answer screen is displayed. After the call is connected, it can be ended proactively or passively.
The in-app call message capability is available on phones and tablets. Support for kids' watches was added in API version 6.1.0(23) and later.
You need to apply for the in-app call message permission to push in-app call messages to your users.
During the debugging phase, the maximum number of test messages that can be pushed for each project every day is 1000. To send test messages, you need to set testMessage to true.
After formal release, the total number of messages that each app can push to a single device each day is subject to the device-based message frequency control policy. The message sending frequency is governed based on service scenarios and traffic. In cases where message sending is unnecessary or unreasonable, frequency limits will be applied.
Create a UIAbility component in your project, for example, VoIPUIAbility.ets (in the src/main/ets/entryability directory of the project), to process the main process of in-app call messages. Override the onCreate(), onWindowStageCreate(), and onDestroy() methods. The sample code is as follows:
import { UIAbility } from '@kit.AbilityKit';
import { pushService } from '@kit.PushKit';
import { window } from '@kit.ArkUI';
import { hilog } from '@kit.PerformanceAnalysisKit';
import { VoipCallService } from '../service/VoipCallService';
import { BusinessError } from '@kit.BasicServicesKit';
const DOMAIN = 0x0000;
export default class VoIPUIAbility extends UIAbility {
onCreate(): void {
hilog.info(DOMAIN, 'testTag', 'VoIPUIAbility onCreate');
try {
pushService.receiveMessage('VoIP', this, (data) => {
// Process the in-app call message data.
try {
VoipCallService.processVoIPMainMsg(data.data, this.context);
} catch (error) {
hilog.error(DOMAIN, 'testTag', 'Failed to process VoIP message: %{public}d %{public}s', error.code,
error.message);
}
});
} catch (e) {
hilog.error(DOMAIN, 'testTag', `Failed to register VoIP, error: ${e.code}, ${e.message}.`);
}
}
onWindowStageCreate(windowStage: window.WindowStage): void {
hilog.info(DOMAIN, 'testTag', 'VoIPUIAbility onWindowStageCreate.');
windowStage.loadContent('pages/CalleePage').catch((err: BusinessError) => {
hilog.error(DOMAIN, 'testTag', `Failed to load content, error: ${err.code}, ${err.message}.`);
});
}
onDestroy(): void {
hilog.info(DOMAIN, 'testTag', 'VoIPUIAbility onDestroy');
}
} VoipCallService.ets (in the src/main/ets/service directory of the project) is used to process an in-app call message. The sample code is as follows:
import { voipCall } from '@kit.CallServiceKit';
import { hilog } from '@kit.PerformanceAnalysisKit';
import { common } from '@kit.AbilityKit';
import { image } from '@kit.ImageKit';
import { resourceManager } from '@kit.LocalizationKit';
import { BusinessError } from '@kit.BasicServicesKit';
export interface VoipScene {
scene: string;
}
export interface Content {
data: string;
header: string;
callId: string;
}
const DOMAIN = 0x0000;
export class VoipCallService {
private static callId: string | undefined;
public static async processVoIPMainMsg(data: string,
context: common.UIAbilityContext): Promise<void> {
hilog.info(DOMAIN, 'testTag', `Process VoIP message: ${data}`);
let content: Content;
let scene: VoipScene;
let callId: string;
try {
content = JSON.parse(data);
scene = JSON.parse(content.data);
callId = content.callId;
if (!callId) {
hilog.error(DOMAIN, 'testTag', 'CallId is null');
}
VoipCallService.callId = callId;
} catch (e) {
let err: BusinessError = e as BusinessError;
hilog.error(DOMAIN, 'testTag', 'Failed to parse VoIP message data: %{public}d %{public}s', err.code, err.message);
return;
}
try {
// Subscribe to voipCallUiEvent.
voipCall.on('voipCallUiEvent', async (event) => {
hilog.info(DOMAIN, 'testTag', `Process voip call ui event: ${JSON.stringify(event)}.`);
await VoipCallService.processVoipCallEvent(event.voipCallUiEvent);
});
} catch (err) {
let e: BusinessError = err as BusinessError;
hilog.error(DOMAIN, 'testTag', 'Failed to register event: %{public}d %{public}s', e.code, e.message);
}
const resourceMgr: resourceManager.ResourceManager = context.resourceManager;
let fileData: Uint8Array = new Uint8Array(0);
try {
// example.png indicates the user avatar. The value is the corresponding file name under /resources/rawfile.
fileData = await resourceMgr.getRawFileContent('example.png');
} catch (e) {
hilog.error(DOMAIN, 'testTag', 'Failed to get raw file: %{public}d %{public}s', e.code, e.message);
}
const buffer = fileData.buffer;
const imageSource: image.ImageSource = image.createImageSource(buffer);
const pixelMap: image.PixelMap = await imageSource.createPixelMap();
if (pixelMap) {
pixelMap.getImageInfo((err, imageInfo) => {
if (imageInfo) {
hilog.info(DOMAIN, 'testTag',
`User profile imageInfo: ${imageInfo.size.width} * ${imageInfo.size.height}.`);
} else {
hilog.error(DOMAIN, 'testTag', `Failed to obtain the image information.code is ${err.code}, message is ${err.message}`);
}
});
}
// Define the parameters for reporting an incoming call. Note that the voipCallType.scene field is defined by yourself to specify the call scenario type. Ensure that the options of this field defined on the device side are consistent with those used for pushing messages from the server side.
let call: voipCall.VoipCallAttribute = {
callId: callId,
voipCallType: scene?.scene === 'video' ? voipCall.VoipCallType.VOIP_CALL_VIDEO :
voipCall.VoipCallType.VOIP_CALL_VOICE,
userName: 'push',
userProfile: pixelMap,
abilityName: 'VoIPUIAbility',
voipCallState: voipCall.VoipCallState.VOIP_CALL_STATE_RINGING
};
try {
// Report the incoming call.
let error = await voipCall.reportIncomingCall(call);
hilog.info(DOMAIN, 'testTag', `ReportIncomingCall result: ${error}.`);
} catch (err) {
let e: BusinessError = err as BusinessError;
hilog.error(DOMAIN, 'testTag', 'Failed to report incoming call: %{public}d %{public}s', e.code, e.message);
}
// ...Trigger vibration and play a ringtone in the app.
}
public static async processVoipCallEvent(event: voipCall.VoipCallUiEvent) {
try {
switch (event) {
case voipCall.VoipCallUiEvent.VOIP_CALL_EVENT_VOICE_ANSWER:
case voipCall.VoipCallUiEvent.VOIP_CALL_EVENT_VIDEO_ANSWER:
// Immediately report the answered state to Call Service Kit.
await voipCall.reportCallStateChange(VoipCallService.callId,
voipCall.VoipCallState.VOIP_CALL_STATE_ANSWERED);
// Implement call answering in the app.
// After the call is answered in the app, report the active state to Call Service Kit.
await voipCall.reportCallStateChange(VoipCallService.callId,
voipCall.VoipCallState.VOIP_CALL_STATE_ACTIVE);
break;
case voipCall.VoipCallUiEvent.VOIP_CALL_EVENT_REJECT:
case voipCall.VoipCallUiEvent.VOIP_CALL_EVENT_HANGUP:
// ...Implement call hang-up in the app.
// Report the call status to Call Service Kit.
await voipCall.reportCallStateChange(VoipCallService.callId,
voipCall.VoipCallState.VOIP_CALL_STATE_DISCONNECTED);
break;
default: {
break;
}
}
} catch (err) {
let e: BusinessError = err as BusinessError;
hilog.error(DOMAIN, 'testTag', 'Failed to report call state change: %{public}d %{public}s', e.code, e.message);
}
}
public static close(): void {
hilog.info(DOMAIN, 'testTag', 'Close VoIP');
VoipCallService.processVoipCallEvent(voipCall.VoipCallUiEvent.VOIP_CALL_EVENT_HANGUP);
try {
voipCall.off('voipCallUiEvent');
} catch (err) {
let e: BusinessError = err as BusinessError;
hilog.error(DOMAIN, 'testTag', 'Failed to unregister event: %{public}d %{public}s', e.code, e.message);
}
}
}
You need to add the example.png file to the src/main/resources/rawfile directory of the project to specify the avatar displayed upon an incoming call.
Add the video call answering screen CalleePage.ets to the src/main/ets/pages directory of the project. The sample code is as follows:
import CallComponent from '../component/CallComponent';
import { hilog } from '@kit.PerformanceAnalysisKit';
const DOMAIN = 0x0000;
@Entry
@Component
struct CalleePage {
@StorageLink('close') @Watch('close') end: boolean | undefined = undefined;
aboutToAppear() {
hilog.info(DOMAIN, 'testTag', 'CalleePage aboutToAppear');
this.end = false;
}
private close() {
if (this.end) {
hilog.info(DOMAIN, 'testTag', 'CalleePage close');
this.getUIContext().getRouter().back(); // This is only an example (going back). Set the routing based on your needs.
}
}
aboutToDisappear() {
hilog.info(DOMAIN, 'testTag', 'CalleePage aboutToDisappear');
}
build() {
Column() {
CallComponent({})
}
}
} The sample code of CallComponent.ets (in the src/main/ets/component directory of the project) is as follows:
import { VoipCallService } from '../service/VoipCallService';
import { voipCall } from '@kit.CallServiceKit';
@Component
export default struct CallComponent {
@StorageLink('close') end: boolean | undefined = undefined;
build() {
Flex({ direction: FlexDirection.Column, justifyContent: FlexAlign.SpaceBetween }) {
Row() {
}
.width('100%')
.justifyContent(FlexAlign.Center)
Row({ space: 30 }) {
Column() {
Button()
.width(80)
.height(80)
.backgroundColor(Color.Green)
.onClick(() => {
VoipCallService.processVoipCallEvent(voipCall.VoipCallUiEvent.VOIP_CALL_EVENT_VIDEO_ANSWER);
})
Text('Answer').fontColor(Color.White).padding({ top: 5 })
}
Column() {
Button()
.width(80)
.height(80)
.backgroundColor(Color.Red)
.onClick(() => {
this.end = true;
VoipCallService.close();
})
Text('Hang Up').fontColor(Color.White).padding({ top: 5 })
}
}
.width('100%')
.justifyContent(FlexAlign.Center)
}
.padding('30 10')
.backgroundColor(Color.Black)
}
} Add page entries to src/main/resources/base/profile/main_pages.json at the project level. The sample code is as follows:
{
"src": [
"pages/Index",
"pages/CalleePage"
]
}
The page effect implemented by the sample code is for reference only.
Configure actions for VoIPUIAbility in the abilities section of the src/main/module.json5 file of the project.
"abilities": [
// ...
{
"name": "VoIPUIAbility",
"srcEntry": "./ets/entryability/VoIPUIAbility.ets",
"launchType": "singleton",
"description": "$string:module_desc",
"startWindowIcon": "$media:startIcon",
"startWindowBackground": "$color:start_window_background",
"exported": false,
"skills": [
// Keep the existing skill object unchanged.
// Add an independent skill object and set actions to action.ohos.push.listener. This action can be defined for only one ability.
{
"actions": [
"action.ohos.push.listener"
]
}
]
}
// ...
] Call the REST API provided by Push Kit on your app server to send a message. For details, please refer to the function description for the API used to push scenario-specific messages.
To notify the callee of an incoming call, call the REST API provided by Push Kit on your app server to send an in-app call message. The following is a request example:
// Request URL
POST "https://push-api.cloud.huawei.com/v3/[projectId]/messages:send"
// Request header
Content-Type: application/json
Authorization: Bearer eyJr*****OiIx---****.eyJh*****iJodHR--***.QRod*****4Gp---****
push-type: 10
// Request body
{
"pushOptions": {
"ttl": 30
},
"payload": {
"extraData": "{\"scene\": \"voice\"}"
},
"target": {
"token": ["MAMzLg**********aZW"]
}
}
In-app call messages can only be used to wake up an app client during a voice/video call and notify users of the incoming call. Do not use this type of messages to end a call or communicate with the app client directly. Your app server should communicate with your app client through its own network connection. Compared with pushing messages, it is faster to use the existing network connection between the app server and app client for communication. If the connection to Push Kit is poor, push messages may fail to reach the app client.
You are advised to use Push Kit to send in-app call messages and then send call requests through the existing network connection between the app server and app client, regardless of whether the app client is running in the foreground. This ensures that calls can reach the app client properly.
To send a missed call notification to the callee, call the corresponding REST API on your app server to send a notification message. The following uses a notification message as an example:
// Request URL
POST "https://push-api.cloud.huawei.com/v3/[projectId]/messages:send"
// Request header
Content-Type: application/json
Authorization: Bearer eyJr*****OiIx---****.eyJh*****iJodHR--***.QRod*****4Gp---****
push-type: 0
// Request body
{
"pushOptions": {
"ttl":86400
},
"payload": {
"notification": {
"category": "MISS_CALL",
"title": "Notification title",
"body": "Notification body",
"clickAction": {
"actionType": 0
},
"appMessageId": "12345"
}
},
"target": {
"token": ["MAMzLg**********aZW"]
}
}