手机实时提取SIM卡打电话的信令声音-双卡手机来电如何获取哪一个卡的来电
手机实时提取SIM卡打电话的信令声音
--双卡手机来电如何获取哪一个卡的来电
- 一、前言
前面的篇章《手机实时提取SIM卡打电话的信令声音-智能拨号器的双SIM卡切换方案》中,我们论述了局域网SIP坐席通过手机外呼出去时,手机中主副卡的呼叫调度策略。
但在实际的双SIM卡手机中,除了外呼策略之外,我们仍然需要考虑双卡来电时的应用场景。即不考虑同时来电的手机本身三方通话逻辑的情况下,针对时间上错开的SIM卡1和SIM卡2的来电,我们需要在来电的逻辑中实现如下两个部分的内容需求:
- 来电时,应该能识别出是哪一张手机卡的来电,以及对方号码是什么(即需要获知来电的主/被叫号码)。进而根据不同的SIM卡的号码,做不同的分发和处理策略。
- 不同手机卡来电时的声音,理应被接收到后分流到不同的局域网SIP坐席中。蓝牙电话app需要能够将这些实时的信令和声音,根据本地SIM1/SIM2号码的不同,而调度到不同的SIP坐席上。双SIM卡手机同时存在时,不同的线路的数据不应该混淆。
基于以上的前提和预设场景,本篇中针对双SIM卡手机的通话部分逻辑,补充了“非默认拨号卡”的常规使用方式的来电部分。真正实现了【双卡双待单通手机】把手机本身的双卡呼叫的能力,复制到了局域网SIP坐席的能力。
- 二、Android获取来电事件和来电号码的方式
Android操作系统中,针对手机的来电事件通常采用下述方式获取:
- 具有android.permission.READ_PHONE_STATE权限;
- 接收android.intent.action.PHONE_STATE的广播事件来触发;
- 判断TelephonyManager的.getCallState()==TelephonyManager.CALL_STATE_RINGING;
- 获取intent.getStringExtra(TelephonyManager.EXTRA_INCOMING_NUMBER)的号码做为来电号码。
这样,在Android API Level>=29的安卓版本中(Android10及以上),手机app可以通过申请READ_PHONE_STATE权限后,通过上述第3步来获取来电事件,通过第4步来获取来电的对方电话号码。
具体代码的逻辑如下图所示:

- 三、Android获取哪个SIM卡槽产生的来电
对于双卡双待手机来说,知道了上述的获取来电事件和号码,其实还是不够。因为来电跟外呼不同,没有办法通过设置的【默认呼叫卡】的方式固定来电时的本地接收号码(也没有这个必要)。
对于来电时的场景,正常情况下就需要对不同SIM卡的通话数据(语音和通话状态数据)进行分流,主动调度到不同的SIM卡对应的SIP坐席当中。在这个过程中,手机设备需要能够识别出来电的时候,到底是由哪一张SIM卡产生的来电,并能根据SIM手机卡对应的手机号来做调度和分流。
Android操作系统中,手机来电事件的卡槽号,通常采用下述方式获取:
- 具有android.permission.READ_PHONE_STATE权限;
- 接收android.intent.action.SUBSCRIPTION_PHONE_STATE的广播事件来触发;
- 判断intent.getStringExtra(TelephonyManager.EXTRA_INCOMING_NUMBER)号码不为空;
- 获取intent.getIntExtra("slot", -1)的数值来做为来电是对应SIM卡槽产生的。
通常,slot为0就是SIM卡1,slot为1就是SIM卡2的来电。这部分内容与获取本机SIM卡手机号时的slot-index值的索引保持一致。
我们在互联网上搜索这块内容时,网上对不同版本的Android出现不同的多SIM来电的号码获取方式,但实践表明,别的其它几种方式局限性太大,有的手机可以而有的型号的手机又不行。采用这个方式是普适性最广的一种方法。
具体代码的逻辑如下图所示。

- 四、Android获取双SIM卡槽对应的手机号码的方式
这个需求也可以描述为:手机app获取插入的双SIM卡的手机号。(卡槽1的手机号和卡槽2的手机号)
这是一个非常复杂的任务,其难度不在获取方式,而在于不同Android版本和不同厂商型号的支持适配。
通常来说获取双卡甚至多卡的手机号码,一般需要经过下列3个步骤,不同品牌型号的手机会在不同的阶段获取得到手机号码,如下:
1)通过SubscriptionManager.getActiveSubscriptionInfoList()获取卡号列表。
- 具有android.permission.READ_PHONE_STATE权限;
- Android API Level>=23;
- 通过Context.getSystemService(TELEPHONY_SUBSCRIPTION_SERVICE)获取SubscriptionManager;
- 遍历SubscriptionManager.getActiveSubscriptionInfoList()读取卡槽号和手机号。
(注:标准方法,这个方法国内的手机,基本都不能够支持)
2)通过content://telephony/siminfo 来读取手机的所有SIM卡号列表。
- 普通权限,最多授予READ_PHONE_STATE和通讯录权限即可;
- 有一些手机通常无法通过这个来获取号码,代码需要加try-catch;
(注:这个是手机中真实存放SIM卡的地方,拔插物理卡槽会直接变更这个表)
3)从通话历史呼叫中读取subscription_id和phone_account_address。
- 具有android.permission.READ_CALL_LOG权限;
- Android API Level>=29;
- 使用Context.getContentResolver().query(CallLog.Calls.CONTENT_URI查询通话记录;
- cursor.getString(cursor.getColumnIndex("subscription_id"))读取卡槽号;
- cursor.getString(cursor.getColumnIndex("phone_account_address"))读取手机号;
(注:有些手机phone_account_address存有本机号码,有的型号只有subscription_id有值,比如荣耀X10手机)
经由以上三种方式的排列组合的方式,手机app总能够有一种办法可以直接检索出手机中SIM1/SIM2卡槽分别有没有插入手机卡、以及插入的卡的手机号是多少。我们在实际的app中也使用这个方法来适配和兼容多款不同手机厂商、不同型号的安卓手机,使手机插入多张SIM卡时,能将各手机卡的通话的操作、事件状态、以及语音数据,各自单独的分流到局域网内不同的SIP坐席中进行通话调度和操作。
- 五、方案的局限性和风险点
上述这一堆的过程函数和权限,基本都要求Android API Level在29及以上,即大于等于Android10的版本中才会进入这些授权和权限读取。
但我们实践中发现,使用Android6、Android7以及Android10以下的其它版本时,其实根本不用授权,它自动就能使用上述段落的方法来直接获取到对应的事件和消息内容。Android10及以后,仅仅是把权限给规范化、需要专门授权和弹框授权而已。
在Android系统中,API Level大于等于23,即Android6.0版本的手机,在大陆乃至全球市场上占据几乎100%的市场份额。因此,使用此种方式来进行双SIM卡的外呼和来电转发,在操作系统层面上基本不存在问题。
但是,本篇文章中讲述的安卓手机的通话事件和语音部分,仅仅包括外呼的呼叫发起、新来电、以及呼叫挂断,并不包括呼叫的接通状态、以及来电时的接听/拒接操作。
我们在蓝牙电话方案中针对接通状态、来电接听/拒接操作,均通过蓝牙HFP协议的AT指令来完成。假设蓝牙模块在使用中因故障不能正常工作,或者后续规划的去掉外置蓝牙模块的场景下,手机的多SIM卡呼叫需要针对这块内容进行专项的分析和深入挖掘。
这块内容我们将在后续的篇章中,花一两个篇幅的内容来进行详细的分析和论述。
- 六、总结
在手机双SIM卡甚至多SIM卡的呼叫业务中,不管是【双卡双待单通】,还是【双卡双待双通】,都会面临通话的发起方和呼叫目标的问题。由于手机硬件设备本身会把所有的语音数据都转接到手机麦克风/听筒/扬声器中进行使用,在外呼的时候可以设置【默认拨号卡】的方式进行呼叫。但在来电时手机设备应能明确区分并标识出是归属于SIM卡1还是SIM卡2产生的来电,并做好卡1/卡2同时来电时的【三方通话】的业务场景约束。
在本篇章中,我们针对Android系统本身的双SIM卡的来电能力进行了一定的探索,实现了在代码逻辑层面中识别“双卡手机来电如何获取哪一个卡的来电”的数据。并在后续的功能逻辑中,根据不同SIM卡手机号来实现通话数据和业务调度到不同的局域网SIP坐席的功能。
这样,对于手机app而言,就充分的利用上了手机本身自带的“双卡双待”的功能,降低了操作的复杂度,也减少了应用扩展的成本,为后续更加复杂的场景提供了理论和数据上的支撑。
上一篇:手机实时提取SIM卡打电话的信令声音-智能拨号器的双SIM卡切换方案
下一篇:手机实时提取SIM卡打电话的信令声音-智能拨号器的SIP线路-双卡双待单通方案
相关文章:
手机实时提取SIM卡打电话的信令声音-双卡手机来电如何获取哪一个卡的来电
手机实时提取SIM卡打电话的信令声音 --双卡手机来电如何获取哪一个卡的来电 一、前言 前面的篇章《手机实时提取SIM卡打电话的信令声音-智能拨号器的双SIM卡切换方案》中,我们论述了局域网SIP坐席通过手机外呼出去时,手机中主副卡的呼叫调度策略。 但…...
共阳极LED的控制与短路问题解析
共阳极LED的控制与短路问题解析 在电子电路中,LED(发光二极管)是最常见的元件之一。LED的连接方式分为共阳极和共阴极,不同的连接方式决定了LED的控制逻辑。本文将重点讲解共阳极LED的工作原理,并解答“为什么给1不会…...
华为消费级QLC SSD来了
近日,有关消息显示,华为的消费级SSD产品线,eKitStor Xtreme 200E系列,在韩国一家在线零售商处首次公开销售,引起了业界的广泛关注。 尽管华为已经涉足服务器级别的SSD制造多年,但直到今年6月才正式推出面向…...
liunx下载gitlab
1.地址: https://mirrors.tuna.tsinghua.edu.cn/gitlab-ce/yum/el7/ 安装 postfix 并启动 yum install postfix systemctl start postfix systemctl enable postfix ssh服务启动 systemctl enable sshd systemctl start sshd开放 ssh 以及 http 服务,…...
深度学习模型预测值集中在某一个值
深度学习模型,训练过程中,经常遇到预测的结果集中在某个值,而且在学习的过程中会变,样例如下。 主要有如下解决方案 1、更换relu ->tanh 或者其他激活函数 2、更改随机种子,估计是没有初始化好,或者调…...
Sqoop的使用
每个人的生活都是一个世界,即使最平凡的人也要为他那个世界的存在而战斗。 ——《平凡的世界》 目录 一、sqoop简介 1.1 导入流程 1.2 导出流程 二、使用sqoop 2.1 sqoop的常用参数 2.2 连接参数列表 2.3 操作hive表参数 2.4 其它参数 三、sqoop应用 - 导入…...
OpenGL ES 04 图片数据是怎么写入到对应纹理单元的
从指定路径加载图像并转换为 CGImage。获取图像的宽度和高度。创建一个 RGB 颜色空间。为图像数据分配内存。创建一个位图上下文并将图像绘制到上下文中。创建一个新的纹理对象并绑定到指定的纹理单元。指定二维纹理图像。释放分配的内存。设置纹理参数,包括放大和缩…...
C# 设计模式的六大原则(SOLID)
C# 设计模式的六大原则(SOLID) 引言 在面向对象编程中,设计模式提供了高效、可复用和可维护的代码结构。SOLID原则是软件设计中的一组重要原则,用于确保代码具有良好的可维护性、可扩展性和灵活性。SOLID是五个设计原则的首字母…...
数据库自增 id 过大导致前端时数据丢失
可以看到,前端响应参数是没有丢失精度的 但是在接受 axios 请求参数时出现了精度丢失 解决方案一:改变 axios 字符编码 axios.defaults.headers[Content-Type] application/json;charsetUTF-8; 未解决 解决方案二:手动使用 json.parse() …...
第二十六天 自然语言处理(NLP)词嵌入(Word2Vec、GloVe)
自然语言处理(NLP)中的词嵌入(Word2Vec、GloVe)技术,是NLP领域的重要组成部分,它们为词汇提供了高维空间到低维向量的映射,使得语义相似的词汇在向量空间中的距离更近。以下是对这些技术的详细解…...
MongoDB 固定集合
MongoDB 固定集合 MongoDB中的固定集合(Capped Collections)是一种具有固定大小的集合,当集合中的数据达到其最大大小时,它会自动覆盖最早的文档。这种类型的集合在MongoDB中用于实现高效的、固定大小的循环缓冲区。本文将详细介…...
数据结构9.3 - 文件基础(C++)
目录 1 打开文件字符读写关闭文件 上图源自:https://blog.csdn.net/LG1259156776/article/details/47035583 1 打开文件 法 1法 2ofstream file(path);ofstream file;file.open(path); #include<bits/stdc.h> using namespace std;int main() {char path[]…...
Leetcode 1254 Number of Closed Islands + Leetcode 1020 Number of Enclaves
Leetcode 1254 题意 给定一个m*n的矩阵含有0和1,1代表水,0代表陆地,岛屿是陆地的集合,如果一个岛屿和四个方向的边界相连,则不算封闭岛屿。求有多少个封闭的岛屿。 题目链接 https://leetcode.com/problems/number…...
Junit4单元测试快速上手
文章目录 POM依赖引入业务层测试代码Web层测试代码生成测试类文件 在工作中我用的最多的单元测试框架是Junit4。通常在写DAO、Service、Web层代码的时候都会进行单元测试,方便后续编码,前端甩锅。 POM依赖引入 <dependency><groupId>org.spr…...
U盘提示格式化?原因、恢复方案与预防措施全解析
一、U盘提示格式化现象概述 在日常使用U盘的过程中,我们有时会遇到一个令人头疼的问题——U盘插入电脑后,系统却弹出一个提示框,告知我们U盘需要格式化才能访问。这个提示往往伴随着数据的潜在丢失风险,让我们不禁为之心焦。U盘提…...
HTML——13.超链接
<!DOCTYPE html> <html><head><meta charset"UTF-8"><title>超链接</title></head><body><!--超链接:从一个网页链接到另一个网页--><!--语法:<a href"淘宝网链接的地址"> 淘宝…...
vue中的设计模式
vue中使用了哪些设计模式 1. 观察者模式(Observer Pattern) 应用场景:Vue 的响应式系统核心就是观察者模式。 实现方式:通过 Object.defineProperty 或 Proxy 监听数据变化,当数据发生变化时,通知依赖的视…...
利用python将图片转换为pdf格式的多种方法,实现批量转换,内置模板代码,全网最全,超详细!!!
文章目录 前言1、img2pdf库的使用1.1 安装img2pdf库1.2 案例演示(模板代码) 2、Pillow库的使用2.1 pillow库的安装2.2 案例演示(模板代码) 3、PyMuPDF库的使用3.1 安装pymupdf库3.2 案例演示(模板代码)3.3 …...
tcpdump的常见方法
详解tcpdump的使用方法:网络数据包捕获与分析 tcpdump是一个功能强大的命令行工具,用于捕获和分析通过网络接口传输的数据包。它广泛应用于网络故障诊断、网络安全监控和协议分析等领域。本文将详细介绍tcpdump的使用方法,包括安装、基本命令…...
工控主板ESM7000/6800E支持远程桌面控制
英创公司ESM7000 是面向工业领域的双核 Cortex-A7 高性能嵌入式主板,ESM6800E则为单核Cortex-A7 高性价比嵌入式主板,ESM7000、ESM6800E都是公司的成熟产品,已广泛应用于工业很多领域。ESM7000/6800E板卡中Linux系统配置为linux-4.9.11内核、…...
深度学习在微纳光子学中的应用
深度学习在微纳光子学中的主要应用方向 深度学习与微纳光子学的结合主要集中在以下几个方向: 逆向设计 通过神经网络快速预测微纳结构的光学响应,替代传统耗时的数值模拟方法。例如设计超表面、光子晶体等结构。 特征提取与优化 从复杂的光学数据中自…...
多场景 OkHttpClient 管理器 - Android 网络通信解决方案
下面是一个完整的 Android 实现,展示如何创建和管理多个 OkHttpClient 实例,分别用于长连接、普通 HTTP 请求和文件下载场景。 <?xml version"1.0" encoding"utf-8"?> <LinearLayout xmlns:android"http://schemas…...
c++ 面试题(1)-----深度优先搜索(DFS)实现
操作系统:ubuntu22.04 IDE:Visual Studio Code 编程语言:C11 题目描述 地上有一个 m 行 n 列的方格,从坐标 [0,0] 起始。一个机器人可以从某一格移动到上下左右四个格子,但不能进入行坐标和列坐标的数位之和大于 k 的格子。 例…...
linux arm系统烧录
1、打开瑞芯微程序 2、按住linux arm 的 recover按键 插入电源 3、当瑞芯微检测到有设备 4、松开recover按键 5、选择升级固件 6、点击固件选择本地刷机的linux arm 镜像 7、点击升级 (忘了有没有这步了 估计有) 刷机程序 和 镜像 就不提供了。要刷的时…...
【Zephyr 系列 10】实战项目:打造一个蓝牙传感器终端 + 网关系统(完整架构与全栈实现)
🧠关键词:Zephyr、BLE、终端、网关、广播、连接、传感器、数据采集、低功耗、系统集成 📌目标读者:希望基于 Zephyr 构建 BLE 系统架构、实现终端与网关协作、具备产品交付能力的开发者 📊篇幅字数:约 5200 字 ✨ 项目总览 在物联网实际项目中,**“终端 + 网关”**是…...
浅谈不同二分算法的查找情况
二分算法原理比较简单,但是实际的算法模板却有很多,这一切都源于二分查找问题中的复杂情况和二分算法的边界处理,以下是博主对一些二分算法查找的情况分析。 需要说明的是,以下二分算法都是基于有序序列为升序有序的情况…...
Spring是如何解决Bean的循环依赖:三级缓存机制
1、什么是 Bean 的循环依赖 在 Spring框架中,Bean 的循环依赖是指多个 Bean 之间互相持有对方引用,形成闭环依赖关系的现象。 多个 Bean 的依赖关系构成环形链路,例如: 双向依赖:Bean A 依赖 Bean B,同时 Bean B 也依赖 Bean A(A↔B)。链条循环: Bean A → Bean…...
JavaScript基础-API 和 Web API
在学习JavaScript的过程中,理解API(应用程序接口)和Web API的概念及其应用是非常重要的。这些工具极大地扩展了JavaScript的功能,使得开发者能够创建出功能丰富、交互性强的Web应用程序。本文将深入探讨JavaScript中的API与Web AP…...
【C++特殊工具与技术】优化内存分配(一):C++中的内存分配
目录 一、C 内存的基本概念 1.1 内存的物理与逻辑结构 1.2 C 程序的内存区域划分 二、栈内存分配 2.1 栈内存的特点 2.2 栈内存分配示例 三、堆内存分配 3.1 new和delete操作符 4.2 内存泄漏与悬空指针问题 4.3 new和delete的重载 四、智能指针…...
Selenium常用函数介绍
目录 一,元素定位 1.1 cssSeector 1.2 xpath 二,操作测试对象 三,窗口 3.1 案例 3.2 窗口切换 3.3 窗口大小 3.4 屏幕截图 3.5 关闭窗口 四,弹窗 五,等待 六,导航 七,文件上传 …...
