手机实时提取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内核、…...

wamp php7.4 运行dm8
背景 1、电脑安装了dm8,具体参照官网dm8安装 2、安装好了wamp,我当前的php版本切换成了7.4的,我wamp的安装路径d:\wamp64\ 操作 3、查看phpinfo,如果Thread Safet为enabled,则选择pdo74_dm.dll,否则选择…...

HTML5 进度条(Progress Bar)详解
HTML5 进度条(Progress Bar)详解 进度条是用于显示任务完成进度的控件,常用于加载、上传或下载等操作。HTML5提供了原生的<progress>元素,使得创建进度条变得简单和直观。 1. 基本用法 <progress>元素的基本语法如…...

LabVIEW开发中常见硬件通讯接口快速识别
在 LabVIEW 开发中,与硬件进行通讯是实现数据采集与控制的重要环节。准确判断通讯接口类型和协议,可以提高开发效率,减少调试时间。本文结合 LabVIEW 的实际应用,详细介绍如何识别和判断常见硬件通讯接口的定义,并提供…...

高频 SQL 50 题(基础版)_1068. 产品销售分析 I
销售表 Sales: (sale_id, year) 是销售表 Sales 的主键(具有唯一值的列的组合)。 product_id 是关联到产品表 Product 的外键(reference 列)。 该表的每一行显示 product_id 在某一年的销售情况。 注意: price 表示每…...

笔记:一次mysql主从复制延迟高的处理尝试
背景 mysql 5.7 主从复制 主库进行了一次灌数,导致多个大事务产生,主从延迟下不去,经确认该表最终truncate,并且该表仅有insert和select操作,故对该表的事务进行跳过,直到同步至truncate 跳过事务需谨慎&…...

基于C语言的卡丁车管理系统【控制台应用程序】
注意:需要提前创建对应的.dat文件 本项目实现了数据的永久存储,有用户的注册、登录。 管理员对卡丁车的管理、查看预约用户、修改帐户权限。 用户对个人信息的管理、查看并预约卡丁车、卡丁车维修上报。 维修员对卡丁车的维修状态上报、个人信息管理。 …...

Docker 搭建 Gogs
Docker 搭建 Gogs 准备工作 先准备配置目录和持久化目录,举个栗子:mkdir -p /opt/module/gogs/{data,backup} 拉取官方Gogs镜像 # 拉取 gogs:0.13 docker pull gogs/gogs:0.13 # 拉取最新版 gogs 镜像 docker pull gogs/gogs启动一个临时容器【通过创…...

PostgreSQL的备份方式
PostgreSQL 提供多种方式进行备份,适用于不同需求的场景。常用的备份方法如下: 1. 逻辑备份(pg_dump 和 pg_dumpall) 1.1 使用 pg_dump 备份单个数据库 pg_dump 是 PostgreSQL 内置的逻辑备份工具,可以将数据库导出为…...

Springboot 3项目整合Knife4j接口文档(接口分组详细教程)
文章目录 前言一、Spring Boot 3.0整合Knife4j二、OpenApi 3注解的使用规范三、使用步骤 1.Spring Boot 3.0项目中使用knife4j2.在application.yml中添加knife4j相关配置3.设置WebMvc相关配置(解决封装统一异常处理后doc.html无法打开的问题)4.创建Knif…...

深入解析 Conda 安装的默认依赖包及其作用:conda create安装了哪些包(中英双语)
深入解析 Conda 安装的默认依赖包及其作用 当我们使用 Conda 创建新环境时,例如执行命令: conda create -n olmes python3.10Conda 会自动为我们安装一系列基础依赖包,保证 Python 环境能够正常运行。这些包不仅是我们开发的基础工具&#…...