手机实时提取SIM卡打电话的信令声音-智能拨号器的双SIM卡切换方案
手机实时提取SIM卡打电话的信令声音
--智能拨号器app的双SIM卡切换方案
- 一、前言
在蓝牙电话的方案中,由于采用市场上的存量手机来做为通讯呼叫的载体,而现在市面上大部分的手机都是“双卡双待单通”手机,简称双卡双待手机。即在手机开机后的使用过程中,两张SIM手机卡都能同时连接到对应的运营商基站、收发运营商预设的指令进行状态查询、话费状态、来电振铃等消息,但某一个时刻仅能有一张SIM卡可以处于通话过程。
这样的话,双卡双待手机就一定存在一个三方通话的互斥问题,分别为:
- 外呼时到底是默认选择SIM卡1/SIM卡2进行外呼?还是不默认,等真正外呼时再由用户进行手动选择?
- 通话过程中如果另外一张SIM有新的来电,是直接拒接?直接接通并挂起/挂断原通话?还是弹框提示用户,由用户进行手动选择?
本篇章中讨论的“双SIM卡自动切换策略”主要针对的是上述第1个场景。第2种场景我们使用手机默认自带的三方通话的逻辑,即:通话中来电让它自动弹框提醒但蓝牙电话不做处理,等待来电自动超时拒接或呼叫取消。
- 二、为何有双SIM卡切换的需求
主要根源在于蓝牙电话方案采用蓝牙HFP协议来做为通讯的载体,而蓝牙HFP协议并没有对多SIM卡手机的主/副卡进行足够的支持。
手机app运行过程中,app通过usb蓝牙与手机蓝牙保持长时间的SLC连接,并通过蓝牙HFP协议来发送操作指令和接收返回的事件状态,以及接收蓝牙返回的SCO语音。
蓝牙HFP协议中,蓝牙双方建立SLC连接后,可以使用ATD10086;的AT指令,通过蓝牙让手机呼叫10086的目标号码。蓝牙电话方案中,由于兼顾了Android手机、iPhone手机、老人机、儿童手表等各种带蓝牙和SIM卡的标准设备,在外呼时默认采用蓝牙HFP协议的ATD指令进行外呼。
在这样的使用方式中,蓝牙电话app并不是唯一遇到此蛋疼问题的应用场景,在中国大陆,蓝牙耳机、汽车的车机与手机进行蓝牙连接来拨打电话等经典场景照样被这个问题所困扰。我们随便在百度等信息过滤入口检索一下,分分钟能搜到诸如下列内容的描述:

以及,某一些品牌的官网上对蓝牙电话的权威性描述和解决方法:

事实上,蓝牙电话方案对双SIM卡支持的疑问并不仅仅在于外呼的时候默认选用哪一张SIM卡进行外呼。我们对于双卡双待的SIM卡需求,通常包括下述三个场景:
- App运行的时候,应用应该能知道手机到底插入了几张SIM卡?每一张卡的手机号码分别是多少?
- 手机外呼的时候使用的是哪一张卡打出去的?能不能呼叫时手动指定或者代码中指定?如果不能,是否存在默认呼叫卡的方式来进行外呼操作?
- 如果外呼时需要指定默认呼叫卡的方式发起呼叫,假设当前默认SIM卡1,那我想用SIM卡2来呼叫时,如何去切换默认卡,能让电话能从SIM卡2中拨打出去?
看出来了吗?其实切换默认呼叫卡的方式,不是外呼的必要条件。按数学逻辑思维来讲,这个叫“充分但不必要条件”??^V^,脑袋中无用的知识又增加了一些。
但是为什么我们还要“默认卡默认卡”的长篇论述,还要搞一堆的双卡切换方式出来呢?都是被这蓝牙HFP协议给逼的,你要是还想用蓝牙通讯来发起呼叫,你就没得选。就这么简单。
- 三、预设的双SIM卡的切换方式
有个鬼的预设,能直接切换SIM卡的操作都得是Android系统级应用或者能够进行提权的超级用户组的应用。我们这种立志做普通权限的app,还想着任意一个品牌和型号的手机都能正常安装和使用的普通应用想都不用想它能够直接在代码中做切换。
我们能够做的,就是在应用中跳转到手机厂商预设的【切换SIM卡操作的界面】,然后让用户手动点击,或者在app中开启“无障碍”功能之后,代码模拟进行点击的办法,来点击手机厂商预设的界面中,默认拨号卡的SIM卡1和SIM卡2的位置,进行手动或自动的SIM卡切换。
感谢大自然的馈赠,感谢前辈先驱的开源奉献,^V^,我们在GitHub中一下就搜索到了一个经典的、使用无障碍功能进行自动点击的开源库Smart-AutoClicker。使用它,能快速的通过界面截图和坐标点击的方式,记录下预设的点击脚本,并在需要使用时触发自动点击的操作。多余的话我就不说了,直接上链接,感兴趣的读者可以自行下载分析和研究。
GitHub - Nain57/Smart-AutoClicker: An open-source auto clicker on images for Android
笔者个人的AndroidStudio版本的原因,最新的master代码下载下来后没法编译和构建,好坑啊,然后就尝试着回退版本,最后试验使用的是它的1.3.0版本,下载分支如下地址所示。
Release SmartAutoClicker-1.3.0 · Nain57/Smart-AutoClicker · GitHub
确实能正常编译、安装和运行,也达到了预设的记录脚本和重放执行来进行SIM卡1/卡2切换的效果。

- 四、预设的切换双SIM卡的界面规划
在预设的规划中,对双SIM卡自动切换的方式,我们打算以手机型号做为脚本记录的主键(因为某个型号的某个厂商的手机,屏幕分辨率一定是一样的,甚至手机配置都会完全相同),使用屏幕分辨率来做为屏幕点击的基准,通过预录脚本或用户手动录制脚本的方式,将脚本分发到所有安装了【智能拨号器app】的手机设备中,进行SIM卡的自动切换。
app的设置界面中,会分别列举有【切换SIM卡1】和【切换SIM卡2】的脚本,设置界面允许用户自己修改与重新录制脚本的内容。并且设置界面中,会配置两个SIM卡之间的切换策略的触发条件,如每张SIM卡外呼次数或接通次数超过某个值就进行SIM卡切换等策略。
通过上述的做法,在代码层面进行双SIM卡的自动切换。SIM卡的脚本列表界面大致可以如下图所示:

每个用户的每个手机设备,都可以对切换的脚本进行手动的修改。但由于我们采用手机型号来做为预设内置的脚本,因此,同一个型号的手机理论上只需要录制一次,就可以将脚本应用到所有同型号的手机中进行生效。
脚本的录制的内容,大致如下面几个图片所示:



录制完毕后的脚本,最终会保存到智能拨号器的后台服务器中。在使用和用户账号的登录时加载和拉取到本地手机中进行使用。在电话呼叫过程中,如果满足SIM卡的切换条件,则会自动触发脚本进行SIM卡内容的切换。
切换后的默认SIM卡的手机号,应当可以从蓝牙HFP协议的AT+CNUM或其它方式将它读取出来,并实时作用到SIP注册账号等领域,供后台坐席进行切换后电话的业务拨打。
- 五、新功能和脚本引入带来的风险
由前文可知,这种切换方式主要风险在于引入了两个弹框权限【悬浮窗-Overlay】和【无障碍-Accessibility Service】,以及不同手机品牌厂商对【切换SIM卡操作的界面】访问路径和方式差异的脚本适配。如下图弹框授权的界面所示:


这里还是要多说一句:无障碍这个功能,不需要点那些默认的开关项,只需要点开进入【已下载的应用】里面,找到自己的那个应用app(如智能拨号器app),点进去,把它的开关打开即可(连应用的“快捷方式”的开关项都不用打开),就算完成了无障碍功能的授权。



写到这里,我其实想说有一个好消息和一个坏消息,但是踏马的真没有好消息。
坑爹的是,有部分手机(应该是大部分手机)无障碍的功能权限,需要应用每次启动运行的时候,都要手动再次弹一遍框然后再授一次权(即无障碍权限是跟进程号绑定的,应用退出了授权就失效了)。
这踏马要怎么玩?谁能告诉我要怎么玩?是安排一个人天天没事干就盯着屏幕,看看应用运行起来后会不会弹框然后点允许?还是应用能确保一旦运行就永远都不会崩溃,运行到手机报废都不会退出??真是喜极而泣,泪流满面。
- 六、反思与总结
这样的话,就真的不得不逼着我们进行反思了。这个使用“无障碍”功能+脚本重放点击,来实现双SIM卡切换的方式,实实在在是不具备商用的实际操作性。
操作又复杂、步骤又繁琐,一顿操作后好不容易稳定能用了,结果,应用退出了一下,居然要重新进行弹框授权??真是比大爷还难伺候。(这里我再次点名批评某些手机厂商,我X,手机熄屏你凭啥强杀我运行在前台最顶层的app,凭啥?你说它是不是有问题?最后再次特意提醒【智能拨号器app】的用户,手机运行过程中不要手动按电源熄屏,智能拨号器运行过程中加了电源锁了的,是永远不会熄屏的,不要手动熄屏)
人,只有知道了更多的知识,才会发现更多的未知。
方案走到这一步,我们开始进行总结和反思,我们做的到底是什么?为什么会需要引入这些莫名其妙、乱七八糟的东西?我们需要对外提供什么样的功能和能力?进而思考,我们到底需要的是什么?
从全局视图来看,我们可以很清晰的知道,智能拨号器app,就是一个app,而且是一个Android的app。与什么蓝牙方案、usb蓝牙、蓝牙HFP协议这些鬼东西完全毫无关系。我们是因为我们想用它(想走捷径),所以才引入了它,才使用它来对外提供服务。
但是现在,引入它了之后呢?到处都是被逼和妥协,到处都是没有办法、没有其它方案可选。就比如本篇前面我们说的:切换后的默认SIM卡的手机号,应当可以从蓝牙HFP协议的AT+CNUM或其它方式将它读取出来??真的是这样?操蛋的是我们实践发现,有部分手机,时灵时不灵,有时候它AT+CNUM死活返回的都是SIM卡1的手机号。坑爹不坑爹?
我们回过头看本篇前面篇章的分析:【
我们对于双卡双待的SIM卡需求,通常包括下述三个场景:
1、App运行的时候,应用应该能知道手机到底插入了几张SIM卡?每一张卡的手机号码分别是多少?
2、手机外呼的时候使用的是哪一张卡打出去的?能不能呼叫时手动指定或者代码中指定?如果不能,是否存在默认呼叫卡的方式来进行外呼操作?
如果外呼时需要指定默认呼叫卡的方式发起呼叫,假设当前默认SIM卡1,那我想用SIM卡2来呼叫时,如何去切换默认卡,能让电话能从SIM卡2中拨打出去?
】
我们开始思考是否真的有必要设置默认呼叫卡?我直接呼叫拨打电话行不行?哪怕我不用蓝牙电话方案、不用HFP协议、不用ATD10086;这样的呼叫指令外呼,可不可以?
一顿操作,发现真的可以。^V^,哎呀我的妈,之前那么多天的预研算是白搞了,方向走错走进死胡同了。
可行的方案就是下面章节的【最终的双卡呼叫方案或双卡切换方案】。
- 七、最终采用的双SIM卡切换方案
Android手机中,本身就可以使用代码Intent.ACTION_CALL的方式,在传递呼叫参数时,指定外呼的时候是使用SIM卡1,或是SIM卡2来进行外呼。^V^。
我们查阅了Android相关文档,此功能在API Level23、即Android6及之后的版本中生效。
智能拨号器app中,我们使用usb蓝牙与手机建立SLC蓝牙连接后,针对多SIM卡的场景,不再使用ATD10086;的方式进行外呼,而是直接采用app授权呼叫权限后,调用下述代码并传递SIM卡的卡号的方式进行指定SIM卡外呼。
然后在呼叫过程中,使用建立好的蓝牙SLC连接,接收+CLCC等事件状态反馈,获知呼叫的目标号码、振铃状态、接通/挂断状态,并将其同步到局域网的SIP服务器中,转接到局域网的呼叫坐席上。
通过以上方式,即可正常的实现SIM卡1和SIM卡2的对外呼叫。根本不需要设置什么默认呼叫卡、不需要做什么默认卡的设置和切换。几下就搞定了,一天天的净踏马的听他扯犊子。^V^。
为什么可以这样搞呢?为什么之前的电脑蓝牙打电话、手机蓝牙电话方案搞不了这种大智若愚、大巧不工的方案呢?根本原因是我们本来就是Android的app,我们也不考虑什么多系统的兼容和适配,去掉了中间商,“价格”和操作更加透明。
代码如下图所示。

- 八、结语
我们足足花了好几个月,使用16页来论证这个方案,结果最终方案就是两三句话,就实现了多SIM卡呼叫方案的选型和实现。一顿操作,结果你懂的。^V^。
无奈吧,人生有时就是这样无奈。

上一篇:蓝牙电话-如何设置双SIM卡自动切换策略(设想)
下一篇:手机实时提取SIM卡打电话的信令声音-(插播一条广告)蓝牙电话的Android版本-即将输出sdk
相关文章:
手机实时提取SIM卡打电话的信令声音-智能拨号器的双SIM卡切换方案
手机实时提取SIM卡打电话的信令声音 --智能拨号器app的双SIM卡切换方案 一、前言 在蓝牙电话的方案中,由于采用市场上的存量手机来做为通讯呼叫的载体,而现在市面上大部分的手机都是“双卡双待单通”手机,简称双卡双待手机。即在手机开机后…...
探索Python WebSocket新境界:picows库揭秘
文章目录 探索Python WebSocket新境界:picows库揭秘第一部分:背景介绍第二部分:picows库概述第三部分:安装picows库第四部分:简单库函数使用方法第五部分:场景应用第六部分:常见Bug及解决方案第…...
2024年11月24日Github流行趋势
项目名称:FreeCAD 项目维护者:wwmayer, yorikvanhavre, berndhahnebach, chennes, WandererFan等项目介绍:FreeCAD是一个免费且开源的多平台3D参数化建模工具。项目star数:20,875项目fork数:4,117 项目名称࿱…...
NewStar CTF week5 Crypto wp
easy_ecc ecc的模板题,稍加推理就会发现c1mc2*k因此做一个减法就行,需要注意的点是c1,c2必须放到ecc里面过一道才能出正确结果 k 86388708736702446338970388622357740462258632504448854088010402300997950626097 p 644088904089909773124499208053…...
vue3+antd注册全局v-loading指令
文章目录 1. 创建指令文件2. 全局注册3. 使用 1. 创建指令文件 src/directives 在directives中创建如下文件 src│─directives│ index.ts└─loadingindex.tsindex.vuedirectives/ index.ts export * from ./loadingdirectives/loading/index.ts import { createApp } f…...
初试无监督学习 - K均值聚类算法
文章目录 1. K均值聚类算法概述2. k均值聚类算法演示2.1 准备工作2.2 生成聚类用的样本数据集2.3 初始化KMeans模型对象,并指定类别数量2.4 用样本数据训练模型2.5 用训练好的模型生成预测结果2.6 输出预测结果2.7 可视化预测结果 3. 实战小结 1. K均值聚类算法概述…...
捉虫笔记(七)-再探谁把系统卡住了
捉虫笔记(七)-再探谁把系统卡住 1、内核调试 在实体物理机上,内核调试的第一个门槛就是如何建立调试链接。 这里我选择的建立网络连接进行内核调试。 至于如何建立网络连接后续文章再和大家分享。 2、如何分析 在上一篇文章中,我们…...
【Linux课程学习】:《简易版shell实现和原理》 《哪些命令可以让子进程执行,哪些命令让shell执行(内键命令)?为什么?》
🎁个人主页:我们的五年 🔍系列专栏:Linux课程学习 🌷追光的人,终会万丈光芒 🎉欢迎大家点赞👍评论📝收藏⭐文章 目录 打印命令行提示符(PrintCommandLin…...
2024年11月27日Github流行趋势
项目名称:screenshot-to-code 项目维护者:abi clean99 sweep-ai kachbit vagusX项目介绍:通过上传截图将其转换为整洁的代码(支持HTML/Tailwind/React/Vue)。项目star数:62,429项目fork数:7,614…...
Java中的线程池使用详解
文章目录 Java中的线程池使用详解一、引言二、线程池的创建与使用1、线程池的创建1.1、FixedThreadPool(固定大小线程池)1.2、CachedThreadPool(可缓存线程池)1.3、SingleThreadExecutor(单线程化线程池)1.…...
Redis(概念、IO模型、多路选择算法、安装和启停)
一、概念 关系型数据库是典型的行存储数据库,存在的问题是,按行存储的数据在物理层面占用的是连续存储空间,不适合海量数据存储。 Redis在生产中使用的最多的是用作数据缓存。 服务器先在缓存中查询数据,查到则返回,…...
计算机网络 第4章 网络层
计算机网络 (第八版)谢希仁 第 4 章 网络层4.2.2 IP地址**无分类编址CIDR**IP地址的特点 4.2.3 IP地址与MAC地址4.2.4 ARP 地址解析协议4.2.5 IP数据报的格式题目2:IP数据报分片与重组题目:计算IP数据报的首部校验和(不正确未改) …...
Java学习笔记--继承方法的重写介绍,重写方法的注意事项,方法重写的使用场景,super和this
目录 一,方法的重写 二,重写方法的注意事项 三,方法重写的使用场景 四,super和this 1.继承中构造方法的特点 2.super和this的具体使用 super的具体使用 this的具体使用 一,方法的重写 1.概述:子类中有一个和父类…...
高级java每日一道面试题-2024年11月27日-JVM篇-JVM的永久代中会发生垃圾回收么?
如果有遗漏,评论区告诉我进行补充 面试官: JVM的永久代中会发生垃圾回收么? 我回答: 在Java虚拟机(JVM)的历史版本中,确实存在一个称为“永久代”(Permanent Generation, 或者简称PermGen)的内存区域。永久代主要用…...
Spring Boot教程之十: 使用 Spring Boot 实现从数据库动态下拉列表
使用 Spring Boot 实现从数据库动态下拉列表 动态下拉列表(或依赖下拉列表)的概念令人兴奋,但编写起来却颇具挑战性。动态下拉列表意味着一个下拉列表中的值依赖于前一个下拉列表中选择的值。一个简单的例子是三个下拉框,分别显示…...
基于混合ABC和A*算法复现
基于混合ABC和A*算法复现 一、背景介绍二、算法原理(一)A*算法原理(二)人工蜂群算法原理(三)混合ABC和A*算法策略 三、代码实现(一)数据准备(二)关键函数实现…...
狂野飙车8+(Asphalt 8+) for Mac 赛车竞速游戏 安装教程
Mac分享吧 文章目录 狂野飙车8(Asphalt 8) for Mac 赛车竞速游戏软件 效果图展示一、狂野飙车8(Asphalt 8) 赛车竞速游戏 Mac电脑版——v2.1.11️⃣:下载软件2️⃣:安装软件2.1 左侧安装包拖入右侧文件夹中,等待安装完成,运行软件…...
网络技术-VRRP(虚拟路由冗余协议)部署介绍
一、VRRP的含义 VRRP(Virtual Router Redundancy Protocol,虚拟路由冗余协议)是一种高度可靠的路由器备用协议,用于在局域网内部提供路由器冗余。 其部署方式主要是通过多个路由器组成一个虚拟路由器组,通过协议选…...
C语言解决空瓶换水问题:高效算法与实现
标题:C语言解决空瓶换水问题:高效算法与实现 一、问题描述 在一个饮料促销活动中,你可以通过空瓶换水的方式免费获得更多的水:3个空瓶可以换1瓶水。喝完这瓶水后,空瓶会再次变为空瓶。假设你最初拥有一定数量的空瓶&a…...
day2全局注册
全局注册代码: //文件核心作用:导入App.vue,基于App.vue创建结构渲染index.htmlimport Vue from vue import App from ./App.vue //编写导入的代码,往代码的顶部编写(规范) import HmButton from ./components/Hm-But…...
web vue 项目 Docker化部署
Web 项目 Docker 化部署详细教程 目录 Web 项目 Docker 化部署概述Dockerfile 详解 构建阶段生产阶段 构建和运行 Docker 镜像 1. Web 项目 Docker 化部署概述 Docker 化部署的主要步骤分为以下几个阶段: 构建阶段(Build Stage):…...
【Linux】shell脚本忽略错误继续执行
在 shell 脚本中,可以使用 set -e 命令来设置脚本在遇到错误时退出执行。如果你希望脚本忽略错误并继续执行,可以在脚本开头添加 set e 命令来取消该设置。 举例1 #!/bin/bash# 取消 set -e 的设置 set e# 执行命令,并忽略错误 rm somefile…...
rknn优化教程(二)
文章目录 1. 前述2. 三方库的封装2.1 xrepo中的库2.2 xrepo之外的库2.2.1 opencv2.2.2 rknnrt2.2.3 spdlog 3. rknn_engine库 1. 前述 OK,开始写第二篇的内容了。这篇博客主要能写一下: 如何给一些三方库按照xmake方式进行封装,供调用如何按…...
使用van-uploader 的UI组件,结合vue2如何实现图片上传组件的封装
以下是基于 vant-ui(适配 Vue2 版本 )实现截图中照片上传预览、删除功能,并封装成可复用组件的完整代码,包含样式和逻辑实现,可直接在 Vue2 项目中使用: 1. 封装的图片上传组件 ImageUploader.vue <te…...
【算法训练营Day07】字符串part1
文章目录 反转字符串反转字符串II替换数字 反转字符串 题目链接:344. 反转字符串 双指针法,两个指针的元素直接调转即可 class Solution {public void reverseString(char[] s) {int head 0;int end s.length - 1;while(head < end) {char temp …...
NFT模式:数字资产确权与链游经济系统构建
NFT模式:数字资产确权与链游经济系统构建 ——从技术架构到可持续生态的范式革命 一、确权技术革新:构建可信数字资产基石 1. 区块链底层架构的进化 跨链互操作协议:基于LayerZero协议实现以太坊、Solana等公链资产互通,通过零知…...
实现弹窗随键盘上移居中
实现弹窗随键盘上移的核心思路 在Android中,可以通过监听键盘的显示和隐藏事件,动态调整弹窗的位置。关键点在于获取键盘高度,并计算剩余屏幕空间以重新定位弹窗。 // 在Activity或Fragment中设置键盘监听 val rootView findViewById<V…...
python执行测试用例,allure报乱码且未成功生成报告
allure执行测试用例时显示乱码:‘allure’ �����ڲ����ⲿ���Ҳ���ǿ�&am…...
Python 包管理器 uv 介绍
Python 包管理器 uv 全面介绍 uv 是由 Astral(热门工具 Ruff 的开发者)推出的下一代高性能 Python 包管理器和构建工具,用 Rust 编写。它旨在解决传统工具(如 pip、virtualenv、pip-tools)的性能瓶颈,同时…...
管理学院权限管理系统开发总结
文章目录 🎓 管理学院权限管理系统开发总结 - 现代化Web应用实践之路📝 项目概述🏗️ 技术架构设计后端技术栈前端技术栈 💡 核心功能特性1. 用户管理模块2. 权限管理系统3. 统计报表功能4. 用户体验优化 🗄️ 数据库设…...
