OpenHarmony 通话应用源码剖析

Posted 开源基础软件社区官方

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了OpenHarmony 通话应用源码剖析相关的知识,希望对你有一定的参考价值。

作者:赖尧

一、简介

​ 通话应用主要提供通话相关用户交互界面,根据电话服务子系统提供的通话数据和状态显示语音去电界面、语音来电界面、语音通话界面、语音多方通话界面、会议通话界面、会议管理界面;并根据用户界面上的操作完成接听、挂断、拒接、静音、保持、音频通道切换、DTMF键盘指令等下发电话服务子系统。

二、架构图

三、代码结构

/applications_call
├── callui                                       # 通话应用主Ability,提供拉起应用入口
│   └── src
│       └── main
│           ├── ets                              # ets代码目录
│               ├── default
│                   ├── assets                   # 图片资源
│                   ├── common                   # 公共组件或方法配置目录
│                       ├── components           # 公共组件
│                       ├── configs              # 应用配置对象目录
│                       ├── constant             # 应用常量对象目录
│                       ├── utils                # 公共方法
│                   ├── model                    # Model层代码目录
│                   ├── pages                    # 通话页面目录
|                   ├── app.ets                  # 全局ets逻辑和应用生命周期管理文件
│               ├── ServiceAbility               # 服务ability
│                   ├── callManagerService.ets   # ServiceAbility方法
│                   ├── service.ts               # ServiceAbility方法
│                   ├── telephonyApi.ets         # ServiceAbility方法
│           ├── resources                        # 资源配置文件存放目录
|               ├── base                         # 默认图片资源,字体大小,颜色资源存放目录
|               ├── zh_CN                        # 中文语言场景资源内容存放目录
│           ├── config.json                      # 全局配置文件
├── figures                                      # 架构图目录
│   └── callui_en.png                            # 架构设计图
├── signature                                    # 签名证书文件目录
│   └── com.ohos.callui.p7b                      # 签名文件
├── LICENSE                                      # 许可证

四、流程图

五、时序图

六、源码分析

1、启动通话常驻服务

开机启动 通话应用常驻服务PA, 由元能力子系统拉起 代码路径/foundation/aafwk/standard/services/abilitymgr/src/ability_manager_service.cpp

```c++
void AbilityManagerService::StartingPhoneServiceAbility()

HILOG_DEBUG("%publics", func);
auto bms = GetBundleManager();
CHECK_POINTER_IS_NULLPTR(bms);

AppExecFwk::AbilityInfo phoneServiceInfo;
Want phoneServiceWant;
phoneServiceWant.SetElementName(AbilityConfig::PHONE_SERVICE_BUNDLE_NAME,
    AbilityConfig::PHONE_SERVICE_ABILITY_NAME);

auto userId = GetUserId();
int attemptNums = 1;
HILOG_DEBUG("%publics, QueryAbilityInfo, userId is %publicd", __func__, userId);
IN_PROCESS_CALL_WITHOUT_RET(
    while (!(bms->QueryAbilityInfo(phoneServiceWant,
        OHOS::AppExecFwk::AbilityInfoFlag::GET_ABILITY_INFO_DEFAULT, userId, phoneServiceInfo)) &&
        attemptNums <= MAX_NUMBER_OF_CONNECT_BMS) 
        HILOG_INFO("Waiting query phone service ability info completed.");
        usleep(REPOLL_TIME_MICRO_SECONDS);
        attemptNums++;
    
);

(void)StartAbility(phoneServiceWant, userId, DEFAULT_INVAL_VALUE);


##### 2、服务注册监听

service ability 应用启动加载入口文件service.ts文件, 执行onStart钩子函数, 实例化CallManagerService类时添加注册监听, registerCallStateCallback 调用注册电话子系统接口function on(type: callDetailsChange, callback: Callback<CallAttributeOptions>): void;

添加注册监听子系统上报的状态

/**

  • add register listener
    */
    addRegisterListener()
    this.mTelephonyCall.registerCallStateCallback(this.getCallData.bind(this));

    public registerCallStateCallback(callBack)
    call.on(callDetailsChange, (data) =>
    if (!data)
    HiLog.i(TAG,prefixLog + call.on registerCallStateCallback + JSON.stringify(data))
    return;

    HiLog.i(TAG,prefixLog + call.on registerCallStateCallback callState: + JSON.stringify(data.callState))
    callBack(data);
    );


根据上报通话当前状态校验是来电还是去电如果是就做拉起操作, 否则通话发布公共事件;

getCallData(callData)
this.callData = callData;
this.updateCallList();
const callState = this.callData;

/**
 * single call or dialing pull up the application
 */
if ((callState === CALL_STATUS_INCOMING && this.callList.length === 1) || callState === CALL_STATUS_DIALING) 
  this.startAbility(callData);
 else if (callState !== CALL_STATUS_DISCONNECTING) 
  this.publishData(callData);


publishData(callData)
commonEvent.publish(callui.event.callDetailsChange,
bundleName: CALL_BUNDLE_NAME,
isOrdered: false,
data: JSON.stringify(callData)
, (res) =>
HiLog.i(TAG, "callUI service commonEvent.publish callback res : %s")
);


##### 3、服务注册公共事件广播

在启动注册监听后同时也注册添加公共事件广播, 其中callui.event.callEvent事件监听通话FA获取初始化数据, callui.event.click 事件监听systemui 通知栏操作按钮事件;

const events = [callui.event.callEvent, callui.event.click];

async addSubscriber()
subscriber = await new Promise((resolve) =>
commonEvent.createSubscriber(
events
, (err, data) =>
HiLog.i(TAG, "addSubscriber %s")
resolve(data);
);
);

commonEvent.subscribe(subscriber, (err, res) => 
  if (err.code === 0) 
    if (res.event === events[0]) 
      const obj = JSON.parse(res.data);
      if (obj && obj.key === getInitCallData) 
        this.publishData(this.callData);
      
    

    if (res.event === events[1]) 
      const callId,btnType = res.parameters
      this.btnclickAgent(callId, btnType)
    
   else 
    HiLog.i(TAG, "callui service commonEvent.subscribe failed err : %s" + JSON.stringify(err))
  
  subscriber.finishCommonEvent()
    .then(() => 
    HiLog.i(TAG, "addSubscriber finishCommonEvent : %s")
  )
);


##### 4、拉起通话应用初始化数据

在service中通过 PA.startAbility方法拉起 通话FA应用, 通话应用FA在启动入口页面index, 实例化CallManager类,调用initCallData方法, 获取初始通话数据, 调用update更新通话状态;

private initCallData()
featureAbility.getWant().then((want) =>
if (want && want.parameters && (callState in want.parameters))
this.update(want.parameters);
HiLog.i(TAG, "initCallData featureAbility.getWant : %s")
else
this.mCallServiceProxy.publish(
key: getInitCallData,
params: []
);

)
.catch((error) =>
HiLog.i(TAG, "initCallData catch error : %s" + JSON.stringify(error))
);


##### 5、更新通话状态

在CallManger类实例化时候, 添加公共事件广播监听callui.event.callDetailsChange, 应用中订阅到数据调用callDataManager中的update方法,更新callData和callList校验通话状态, 是单方通话或者是多方通话;

const events = [callui.event.callDetailsChange];
private async registerSubscriber()
subscriber = await new Promise((resolve) =>
commonEvent.createSubscriber(
events
,
(err, data) =>
resolve(data);

);
);

commonEvent.subscribe(subscriber, (err, res) => 
  if (err.code === 0) 
    const callData = JSON.parse(res.data);
    this.callData = callData
    HiLog.i(TAG, "commonEvent subscribe : %s")
    if (callData) 
      this.update(callData);
    
    HiLog.i(TAG, "commonEvent subscribe : %s")
   else 
    HiLog.i(TAG, "commonEvent.subscribe err: %s" + JSON.stringify(err))
  
);

async update(callData)
if (globalThis.permissionFlag)
await this.contactManager.getContactInfo(callData)

this.mCallDataManager.update(callData);
call.formatPhoneNumber(callData.accountNumber, (err, data) =>
if (data === undefined)
AppStorage.SetOrCreate("AccountNumber", callData.accountNumber)
else
AppStorage.SetOrCreate("AccountNumber", data)

);
HiLog.i(TAG, "update : ")

public update(callData)
const callState, callId = callData;
const targetObj = this.callList.find((v) => v.callId === callId);
HiLog.i(TAG, "update : ")
if (targetObj)
Object.assign(targetObj,
...callData
);
else
this.addCallList(
...callData
);

if (callData.callState === CallStateConst.CALL_STATUS_ACTIVE) 
  this.updateCallTimeList(callData);

const singleCallState = callState === CallStateConst.CALL_STATUS_ACTIVE ||
callState === CallStateConst.CALL_STATUS_WAITING || this.callList.length === 1;
const multiCallState = (callState === CallStateConst.CALL_STATUS_DIALING ||
callState === CallStateConst.CALL_STATUS_ALERTING) && this.callList.length > 1;
if (singleCallState || multiCallState) 
  this.mCallStateManager.update(callData);
  this.callStateChange(callState);


if (callState === CallStateConst.CALL_STATUS_DISCONNECTED) 
  if (this.callList.length === 1) 
    this.NotificationManager.cancelNotification();
    AppStorage.Get<NotificationManager>(notificationManager).sendCapsuleNotification(callData, true);
    app.terminate();
   else 
    this.removeCallById(callId);
    const activeCallData = this.callList.find((v) => v.callState === CallStateConst.CALL_STATUS_ACTIVE);
    if (activeCallData) 
      this.mCallStateManager.update(activeCallData);
      this.callStateChange(activeCallData);
     else if (this.callList[0]) 
      this.mCallStateManager.update(this.callList[0]);
      this.callStateChange(this.callList[0].callState);
    
  


通话状态callState 包含的类型:

// calling
public static CALL_STATUS_ACTIVE: number = 0; // 通话中

// State keeping
public static CALL_STATUS_HOLDING: number = 1; // 保持

// Dialing
public static CALL_STATUS_DIALING: number = 2; // 正在拨号

// The other party is ringing
public static CALL_STATUS_ALERTING: number = 3; // 对方已振铃

// Call from the other party
public static CALL_STATUS_INCOMING: number = 4; // 来电

// Waiting for third-party calls
public static CALL_STATUS_WAITING: number = 5; // 三方来电等待

// Hung up
public static CALL_STATUS_DISCONNECTED: number = 6; // 已挂断

// Hanging up
public static CALL_STATUS_DISCONNECTING: number = 7; // 正在挂断


##### 6、发布通知

在通话应用切换到后台, 会触发onPageHide钩子函数发送通知, 其中notificationManager.sendNotification方法发送按钮通知, notificationManager.sendCapsuleNotification方法发送的是胶囊通知;

发布按钮通知, 配置actionButtons, 其中消息类型contentType 等于 notification.ContentType.NOTIFICATION_CONTENT_LONG_TEXT, 表示长消息类型, want下的 action配置为callui.event.click, 点击通知按钮会触发systemUI 中的WantAgent.trigger 系统会发布公共事件callui.event.click,在通话service中订阅

公共事件callui.event.click响应的数据

onPageHide()
HiLog.i(TAG, "onPageHide :")
this.appInactiveState = true;
const callState, accountNumber, contactName, callId = this.callData;
let fool = (callState !== CallStateConst.callStateObj.CALL_STATUS_DISCONNECTED && callId)
if (callState !== CallStateConst.callStateObj.CALL_STATUS_DISCONNECTED && callId)
let text = contactName + + accountNumber + ;
if (!contactName)
text = accountNumber + ;

this.notificationManager.sendNotification(text, this.callData);
this.notificationManager.sendCapsuleNotification(this.callData, true);
HiLog.i(TAG, "onPageHide end : ")

// 发布通知
async sendNotification(text, callData)
const callState, callId = callData;
const actionBtnKeys = this.getMapObj(callState) || [];
const START_ABILITY, SEND_COMMON_EVENT = wantAgent.OperationType;
const wantAgentObj = await this.getWantAgent(callData, START_ABILITY);
notificationRequest.wantAgent = wantAgentObj;
notificationRequest.actionButtons = [];
if (actionBtnKeys.length)
for (const key of actionBtnKeys)
const data =
callId, btnType: key
;
const wantAgentObj = await this.getWantAgent(data, SEND_COMMON_EVENT);
resourceManager.getResourceManager((error, mgr) =>
if (error != null)
return;

mgr.getString(textMap[key].id, (error, value) =>
if (error != null)
else
notificationRequest.actionButtons.push(
title: value,
wantAgent: wantAgentObj
);
Object.assign(notificationRequest.content.longText,
title: text,
expandedTitle: text
);
notification.publish(notificationRequest);

);
);


HiLog.i(TAG, "sendNotification end : ")

// 发送胶囊通知
sendCapsuleNotification(callData, isBackground)
HiLog.i(TAG, "sendCapsuleNotification isBackground : %s" + JSON.stringify(isBackground))
callData.startTime = (callData.startTime)
HiLog.i(TAG, "sendCapsuleNotification callData.startTime : ")
const callState, startTime = callData;
commonEvent.publish(CAPSULE_EVENT_CALL_UI,
bundleName: com.ohos.callui,
isOrdered: false,
data: JSON.stringify(
callState,
startTime: startTime*1000,
isBackground,
wantBundleName: CALL_BUNDLE_NAME,
wantAbilityName: CALL_ABILITY_NAME
)
, (res) =>
HiLog.i(TAG, "callUI app commonEvent.publish CAPSULE_EVENT_CALL_UI callback res : %s")
);


### 七、列举调用电话子系统接口

1、来电接听, 拒接;通话中挂断, 保持, 取消保持接口调用;

// 接听
public acceptCall = function (callId)
call.answer(callId).then((res) =>
HiLog.i(TAG,prefixLog + "call.answer : %s")
).catch((err) =>
HiLog.i(TAG, prefixLog + "call.answer catch : %s" + JSON.stringify(err))
);
;

// 拒接
public rejectCall = function (callId, isSendSms = false, msg = )
// 普通拒接和短信拒接
const rejectCallPromise = isSendSms ? call.reject(callId, messageContent: msg) : call.reject(callId);
rejectCallPromise.then((res) =>
HiLog.i(TAG,prefixLog + "then:rejectCall : %s")
)
.catch((err) =>
HiLog.i(TAG, prefixLog + "catch:rejectCall : %s" + JSON.stringify(err))
);
;

// 挂断
public hangUpCall = (callId) => new Promise((resolve, reject) =>
call.hangup(callId).then((res) =>
resolve(res);
HiLog.i(TAG, prefixLog + "then:hangUpCall : %s")
).catch((err) =>
reject(err);
HiLog.i(TAG, prefixLog + "catch:hangUpCall : %s" + JSON.stringify(err))
);
);

// 保持
public holdCall = (callId) => new Promise((resolve, reject) =>
call.holdCall(callId).then((res) =>
resolve(res);
HiLog.i(TAG,prefixLog + "then:holdCall : %s")
)
.catch((err) =>
reject(err);
HiLog.i(TAG,prefixLog + "catch:holdCall : %s" + JSON.stringify(err))
);
);

// 取消保持
public unHoldCall = (callId) => new Promise((resolve, reject) =>
call.unHoldCall(callId).then((res) =>
resolve(res);
HiLog.i(TAG,prefixLog + "then:unHoldCall : %s")
)
.catch((err) =>
reject(err);
HiLog.i(TAG,prefixLog + "catch:unHoldCall : %s" + JSON.stringify(err))
);
);


2、拨号盘拨号接口调用

// 拨号
public dialCall(phoneNumber, accountId = 0, videoState = 0, dialScene = 0)
HiLog.i(TAG, "dialCall phoneNumber : ")
return call.dial(phoneNumber,
accountId,
videoState,
dialScene
);



## 更多原创内容请关注:[深开鸿技术团队](https://harmonyos.51cto.com/person/posts/15292440)

入门到精通、技巧到案例,系统化分享HarmonyOS开发技术,欢迎投稿和订阅,让我们一起携手前行共建鸿蒙生态。

[想了解更多关于开源的内容,请访问:](https://ost.51cto.com/#bkwz)

[51CTO 开源基础软件社区](https://ost.51cto.com#bkwz)

https://ost.51cto.com/#bkwz

以上是关于OpenHarmony 通话应用源码剖析的主要内容,如果未能解决你的问题,请参考以下文章

《OpenHarmony 3GPP协议开发深度剖析》之--PLMN业务源码解读

v78.01 鸿蒙内核源码分析(消息映射篇) | 剖析LiteIpc(下)进程通讯机制 | 百篇博客分析OpenHarmony源码

v78.01 鸿蒙内核源码分析(消息映射篇) | 剖析LiteIpc(下)进程通讯机制 | 百篇博客分析OpenHarmony源码

《OpenHarmony 3GPP协议开发深度剖析》--不得不弄明白的RIL

OpenHarmony 源码解析之安全子系统 (应用权限管理)

HDF驱动框架探路:openharmony最新源码,打通应用态到内核态