更新记录

1.0.0(2025-12-21)

新增功能

  • 电话状态监听功能(来电中、通话中、空闲状态)
  • 自动接听电话功能
  • 挂断电话功能
  • 电话号码回调功能
  • 权限检查和请求功能
  • Android 12+ 兼容性支持

技术实现

  • 使用 TelephonyManager 监听电话状态
  • 支持 Android 8.0+ 的 TelecomManager API
  • 支持 Android 12+ 的 TelephonyCallback API
  • 完整的错误处理和权限管理

平台兼容性

uni-app(4.61)

Vue2 Vue3 Chrome Safari app-vue app-nvue Android iOS 鸿蒙
- - - - - - 4.4 12 -
微信小程序 支付宝小程序 抖音小程序 百度小程序 快手小程序 京东小程序 鸿蒙元服务 QQ小程序 飞书小程序 快应用-华为 快应用-联盟
- - - - - - - - - - -

hl-call-control-uts

电话状态监听与通话控制功能 UTS 插件

功能说明

本插件提供 Android / iOS 侧电话状态监听与通话控制能力,包括:

  1. 电话状态监听:实时监听来电、通话中、空闲状态
  2. 自动接听:支持自动接听来电(需权限支持,仅 Android 支持
  3. 挂断电话:支持挂断当前通话(需权限支持,仅 Android 支持
  4. 号码回调:回调来电号码和通话号码(iOS 无法获取来电号码
  5. 权限管理:提供权限检查和请求功能

使用说明

导入插件

// #ifdef APP-PLUS
import { getHlCallControlUtsClient } from '@/uni_modules/hl-call-control-uts'
const client = getHlCallControlUtsClient()
// #endif

初始化

// 初始化客户端
client.initClient()

// 设置状态变化回调
client.onStateChanged((data) => {
  console.log('电话状态变化:', data)
  // data: { event: "CALL_STATE_CHANGED", state: "RINGING", phoneNumber: "+8613800138000", timestamp: 1732600000000 }
})

// 设置结果回调
client.onResult((result) => {
  console.log('操作结果:', result)
  // result: { code: 0, message: "success", data: {...} }
})

权限管理

// 检查权限
client.checkPermissions()

// 请求权限(会弹出系统权限对话框)
client.requestPermissions()

注意:在 Android 上,requestPermissions() 会弹出系统权限对话框。在 iOS 上,CoreTelephony 框架不需要显式权限请求,但无法获取来电号码或控制通话。

开始监听(后台服务)

// 开始监听电话状态(使用后台服务,应用关闭后仍可监听)
client.startListening()

注意startListening() 会启动一个前台服务在后台持续监听电话状态,即使应用关闭也能继续工作。服务会在通知栏显示一个常驻通知。iOS 版本不需要前台服务,但监听功能在应用进入后台后可能受系统限制。

接听电话

// 自动接听来电
client.answerCall()

注意仅 Android 支持此功能。iOS 10+ 后,无法通过公开 API 接听电话,需要使用 CallKit 框架(需要特殊配置和权限)。

挂断电话

// 挂断当前通话
client.endCall()

注意仅 Android 支持此功能。iOS 10+ 后,无法通过公开 API 挂断电话,需要使用 CallKit 框架(需要特殊配置和权限)。

获取当前状态

// 获取当前电话状态
client.getCurrentState()

停止监听

// 停止监听电话状态
client.stopListening()

释放资源

// 在页面销毁时调用
client.onDestroy()

完整示例

<template>
  <view class="container">
    <view class="status">当前状态: {{ currentState }}</view>
    <view class="phone-number">电话号码: {{ phoneNumber }}</view>

    <button @click="startListen">开始监听</button>
    <button @click="stopListen">停止监听</button>
    <button @click="answer">接听</button>
    <button @click="endCall">挂断</button>
    <button @click="checkPerms">检查权限</button>
  </view>
</template>

<script setup>
import { ref, onMounted, onUnmounted } from 'vue'
// #ifdef APP-ANDROID
import { getHlCallControlUtsClient } from '@/uni_modules/hl-call-control-uts/utssdk/app-android/index.uts'
// #endif

const client = getHlCallControlUtsClient()
const currentState = ref('IDLE')
const phoneNumber = ref('')

onMounted(() => {
  // 初始化
  client.initClient()

  // 设置回调
  client.onStateChanged((data) => {
    currentState.value = data.state
    phoneNumber.value = data.phoneNumber || ''
    console.log('状态变化:', data)
  })

  client.onResult((result) => {
    console.log('操作结果:', result)
    if (result.code !== 0) {
      uni.showToast({
        title: result.message || '操作失败',
        icon: 'none'
      })
    }
  })
})

onUnmounted(() => {
  client.onDestroy()
})

const startListen = () => {
  client.startListening()
}

const stopListen = () => {
  client.stopListening()
}

const answer = () => {
  client.answerCall()
}

const endCall = () => {
  client.endCall()
}

const checkPerms = () => {
  client.checkPermissions()
}
</script>

状态说明

CallState 枚举

  • IDLE: 空闲状态(未在通话)
  • RINGING: 来电中
  • OFF_HOOK: 通话中(已接听)

回调数据格式

状态变化回调

{
  "event": "CALL_STATE_CHANGED",
  "state": "RINGING",
  "phoneNumber": "+8613800138000",
  "timestamp": 1732600000000
}

结果回调

{
  "code": 0,
  "message": "success",
  "data": {
    "action": "answerCall"
  }
}

权限要求

Android 权限

  • READ_PHONE_STATE: 读取电话状态(必需,用于监听电话状态)
  • CALL_PHONE: 拨打电话(用于挂断电话)
  • ANSWER_PHONE_CALLS: 接听电话(Android 8.0+,用于自动接听)
  • READ_CALL_LOG: 读取通话记录(用于获取电话号码)
  • MODIFY_AUDIO_SETTINGS: 修改音频设置(用于接听和挂断电话时的音频控制)
  • PROCESS_OUTGOING_CALLS: 处理拨出电话(Android 10-11,Android 12+ 已废弃)
  • FOREGROUND_SERVICE: 前台服务(Android 9.0+,用于后台监听服务)

iOS 权限

  • iOS 上的 CoreTelephony 框架不需要显式权限声明。
  • 重要:iOS 无法通过 CoreTelephony 获取来电号码。
  • 如果需要接听/挂断电话,需要集成 CallKit 框架,这需要额外的配置和用户授权。

权限配置

权限已在 AndroidManifest.xml 中声明,但需要在运行时动态请求:

// 在 manifest.json 中配置
{
  "app-plus": {
    "distribute": {
      "android": {
        "permissions": [
          "<uses-permission android:name=\"android.permission.READ_PHONE_STATE\"/>",
          "<uses-permission android:name=\"android.permission.CALL_PHONE\"/>",
          "<uses-permission android:name=\"android.permission.ANSWER_PHONE_CALLS\"/>",
          "<uses-permission android:name=\"android.permission.READ_CALL_LOG\"/>",
          "<uses-permission android:name=\"android.permission.FOREGROUND_SERVICE\"/>"
        ]
      }
    }
  }
}

运行时权限请求

// 使用 uni-app 的权限 API
uni.authorize({
  scope: 'scope.record',
  success() {
    console.log('权限已授予')
    client.startListening()
  },
  fail() {
    console.log('权限被拒绝')
    // 引导用户到设置页面
    uni.showModal({
      title: '需要权限',
      content: '需要电话权限才能使用此功能',
      confirmText: '去设置',
      success(res) {
        if (res.confirm) {
          // 打开应用设置页面
        }
      }
    })
  }
})

注意事项

  1. 后台监听服务

    • startListening() 会启动一个前台服务,在通知栏显示常驻通知
    • 服务使用 START_STICKY 模式,被系统杀死后会自动重启
    • 应用关闭后服务仍会继续运行,确保电话状态监听不中断
    • 需要停止监听时调用 stopListening() 停止服务
  2. 自动接听限制

    • Android 8.0 之后系统对自动接听权限收紧
    • 部分厂商(如小米、华为)可能限制自动接听功能
    • 需要用户手动授予 ANSWER_PHONE_CALLS 权限
    • 某些设备可能需要 Root 权限才能实现自动接听
    • 接听前会检查当前状态是否为 RINGING,避免误操作
  3. 系统版本兼容

    • Android 12+ 使用新的 TelephonyCallback API
    • Android 12 以下使用 PhoneStateListener API
    • 插件已自动处理版本兼容
    • 后台服务会根据系统版本自动选择正确的监听方式
  4. 电话号码获取

    • Android
      • Android 12+ 由于隐私限制,可能无法直接获取来电号码
      • 需要通过 READ_CALL_LOG 权限或 CallScreeningService 获取
      • 建议在 Android 12+ 上使用系统提供的来电拦截服务
      • 当前实现中,Android 12+ 的电话号码可能为 null
    • iOS
      • iOS 系统限制:无法通过公开 API 获取真实来电号码
      • CTCall.callID 返回的是通话的唯一标识符(UUID),不是电话号码
      • 这是 iOS 系统的隐私保护机制,无法绕过
      • 因此 iOS 版本的回调中 phoneNumber 字段始终为 null 或空字符串
  5. 权限获取

    • 部分权限需要在系统设置中手动授予
    • 建议在应用启动时检查并引导用户授予权限
    • ANSWER_PHONE_CALLS 权限需要在系统设置中手动授予
    • 前台服务需要 FOREGROUND_SERVICE 权限(Android 9.0+)
  6. 错误处理

    • 所有操作都会通过 onResult 回调返回结果
    • code: 0 表示成功,非 0 表示失败
    • 失败时会返回具体的错误信息
    • 接听/挂断前会检查当前状态,避免无效操作
  7. 资源释放

    • 在页面销毁时务必调用 onDestroy() 释放资源
    • 停止监听时会自动停止后台服务
    • 广播接收器会在 onDestroy() 时自动注销
  8. 厂商定制限制

    • 部分厂商(小米、华为、OPPO、vivo 等)对电话控制功能有额外限制
    • 可能需要用户手动在系统设置中授予相关权限
    • 某些功能可能需要在系统设置中开启"后台运行"或"自启动"权限
    • 部分厂商可能限制后台服务运行,需要在系统设置中设置白名单
  9. iOS 系统限制

    • 电话号码获取:iOS 系统出于隐私保护,不允许第三方应用获取真实来电号码。CTCall.callID 返回的是 UUID,不是电话号码。
    • 接听/挂断电话
      • CallKit 只能控制 VoIP 通话(如 FaceTime、WhatsApp、Skype 等),不能控制普通电话(PSTN)
      • 对于普通电话,iOS 系统不允许第三方应用接听或挂断
      • 这是 iOS 系统的安全机制,无法绕过
      • 如果尝试控制普通电话,会返回错误:iOS system restriction: CallKit can only control VoIP calls, not regular phone calls (PSTN)
    • 后台监听:iOS 应用进入后台后,CTCallCenter 的监听可能会被系统限制或暂停
  10. 生产环境建议

    • 建议在应用启动时检查所有必要权限
    • 引导用户授予权限,特别是 ANSWER_PHONE_CALLS(Android)
    • 监控服务运行状态,处理服务异常情况
    • 记录关键操作日志,便于问题排查
    • 测试不同厂商设备的兼容性
    • iOS 用户:明确告知用户 iOS 系统的限制,电话号码无法获取,接听/挂断仅支持 VoIP 通话

错误码说明

  • 0: 操作成功
  • -1: 操作失败(具体原因见 message 字段)

常见错误:

  • no context: 上下文为空
  • permission denied: 权限未授予
  • telephonyManager is null: 电话管理器初始化失败
  • already listening: 已在监听状态
  • not listening: 未在监听状态

iOS VoIP 通话测试指南

什么是 VoIP 通话?

VoIP(Voice over Internet Protocol)是通过互联网进行语音通话的技术,与传统的电话网络(PSTN)不同。常见的 VoIP 应用包括:

  • FaceTime(苹果自带,iOS 设备之间)
  • WhatsApp(需要集成 CallKit)
  • Skype(需要集成 CallKit)
  • Zoom(需要集成 CallKit)
  • Microsoft Teams(需要集成 CallKit)
  • 其他支持 CallKit 的 VoIP 应用

重要说明

⚠️ 重要限制

  • CallKit 只能控制已经集成 CallKit 框架的 VoIP 应用的通话
  • 如果 VoIP 应用没有集成 CallKit,我们的插件也无法控制
  • 普通电话(PSTN)完全无法控制,这是 iOS 系统限制

测试方法

方法 1:使用 FaceTime(推荐,最简单)

FaceTime 是苹果自带的 VoIP 应用,天然支持 CallKit:

  1. 准备两台 iOS 设备(或一台设备 + 模拟器)
  2. 在两台设备上登录不同的 Apple ID(或使用同一 Apple ID)
  3. 在测试设备上启动应用,调用 startListening()
  4. 从另一台设备拨打 FaceTime 视频或语音通话
  5. 观察测试设备
    • 应该能检测到 RINGING 状态
    • 尝试调用 answerCall() 应该能接听
    • 尝试调用 endCall() 应该能挂断

注意事项

  • FaceTime 需要两台设备都连接到互联网
  • 需要确保 FaceTime 功能已启用(设置 > FaceTime)

方法 2:使用支持 CallKit 的第三方应用

  1. 安装支持 CallKit 的 VoIP 应用(如 WhatsApp、Skype 等)
  2. 确保应用已集成 CallKit 框架(不是所有 VoIP 应用都支持)
  3. 在测试设备上启动应用,调用 startListening()
  4. 从另一台设备拨打 VoIP 通话
  5. 观察测试设备是否能检测和控制通话

注意事项

  • 不是所有 VoIP 应用都支持 CallKit
  • 需要查看应用的文档确认是否支持 CallKit
  • 某些应用可能需要特殊配置才能使用 CallKit

方法 3:使用 Xcode 模拟器测试(有限支持)

  1. 在 Xcode 中运行应用
  2. 使用模拟器的电话功能(但模拟器可能不支持完整的 CallKit 功能)
  3. 使用其他设备拨打 FaceTime 到模拟器

注意事项

  • 模拟器对 CallKit 的支持有限
  • 建议使用真机测试

测试步骤

  1. 初始化插件

    client.initClient()
    client.onStateChanged((data) => {
     console.log('状态变化:', data)
    })
    client.onResult((result) => {
     console.log('操作结果:', result)
    })
  2. 开始监听

    client.startListening()
  3. 触发 VoIP 通话

    • 从另一台设备拨打 FaceTime 或其他 VoIP 通话
    • 或从测试设备拨打 VoIP 通话
  4. 观察状态变化

    • 应该收到 CALL_STATE_CHANGED 事件
    • 状态应该是 RINGING(来电中)
  5. 测试接听

    client.answerCall()
    • 如果成功,应该收到 code: 0 的结果
    • 如果失败,会收到详细的错误信息
  6. 测试挂断

    client.endCall()
    • 如果成功,应该收到 code: 0 的结果
    • 如果失败,会收到详细的错误信息

调试技巧

  1. 查看日志

    • 在 Xcode 控制台查看 HlCallControl: 开头的日志
    • 观察 CallKit 是否能检测到通话
  2. 检查 CallKit 状态

    • 在代码中添加日志,查看 callObserver?.calls 的内容
    • 如果为空,说明 CallKit 没有检测到 VoIP 通话
  3. 测试不同场景

    • 测试来电接听
    • 测试拨出通话挂断
    • 测试通话中挂断

常见问题

Q: 为什么无法控制普通电话? A: 这是 iOS 系统的安全限制,普通电话(PSTN)无法被第三方应用控制。

Q: 为什么某些 VoIP 应用无法控制? A: 只有集成了 CallKit 框架的 VoIP 应用才能被控制。不是所有 VoIP 应用都支持 CallKit。

Q: 如何确认 VoIP 应用是否支持 CallKit? A: 查看应用的文档或联系应用开发者。通常,如果应用在锁屏界面显示通话界面,说明它可能支持 CallKit。

Q: FaceTime 一定能工作吗? A: FaceTime 是苹果自带的,天然支持 CallKit,是最可靠的测试方法。

测试检查清单

  • [ ] 已初始化插件并设置回调
  • [ ] 已调用 startListening() 开始监听
  • [ ] 使用 FaceTime 或其他支持 CallKit 的 VoIP 应用
  • [ ] 能够检测到 RINGING 状态
  • [ ] 能够成功接听 VoIP 通话(如果支持)
  • [ ] 能够成功挂断 VoIP 通话(如果支持)
  • [ ] 查看日志确认 CallKit 是否检测到通话

隐私、权限声明

1. 本插件需要申请的系统权限列表:

2. 本插件采集的数据、发送的服务器地址、以及数据用途说明:

插件不采集任何数据

3. 本插件是否包含广告,如包含需详细说明广告表达方式、展示频率:

暂无用户评论。