Android监听消息(二)——电话及短信监听

news2025/1/24 11:42:49

学更好的别人,

做更好的自己。

——《微卡智享》

541ca03168e8b1b77e905f275152c364.jpeg

本文长度为2747,预计阅读6分钟

前言

前面一篇《Android监听消息(一)——应用消息捕获》我们使用NotificationListenerService实现了应用的消息监听,但是电话和短信是接收不到的,所以这一篇我们就来解决怎么监听电话及短信,电话主要就是在响铃时发送来电人消息,短信的话是捕获到消息内容直接发送出来。

fcd000ebcbe34f08ffddc3fa1a2019a6.png

微卡智享

实现思路

在Android中实现电话捕获用到的还是TelephonyManager,在Android12前TelephonyManager可以使用PhoneStateListener来进行监听,里面的onCallStateChanged可以直接获取到来电状态和来电号码,比较方便,如下图:

30380aba28c920f27dd27bd352f915fc.png

但是在Android12(sdk31)后,listen已经不能用了,需要使用registerTelephonyCallback来获取到来电状态,但是这里无法获取到来电号码了,所以为了实现接到到来电状态并能获取到来电人的信息,需要使用BroadcastReceiver来实现。

而捕获短信的消息则是使用android.telephony.SmsMessage,并且短信的接收也是通过BroadcastReceiver来实现,这样我们就把电话和短信接收直接写在一个BroadcastReceiver中即可。

代码实现

394da72110f59a41bc8e4bca493aac8e.png

微卡智享

01

权限申请

接着上一篇的Demo,我们在这个基础上再增加相关的设置,首先要捕获电话及短信,那相关的权限必须要先申请出来。

1c82ccfe8ad2c5dac39ba2d06b5ec97f.png

<uses-permission android:name="android.permission.ACTION_NOTIFICATION_LISTENER_SETTINGS" />
    <uses-permission android:name="android.permission.ANSWER_PHONE_CALLS" />
    <uses-permission android:name="android.permission.READ_PHONE_STATE" />
    <uses-permission android:name="android.permission.READ_PHONE_NUMBERS" />
    <uses-permission android:name="android.permission.CALL_PHONE" />
    <uses-permission android:name="android.permission.RECEIVE_SMS" />
    <uses-permission android:name="android.permission.SEND_SMS" />
    <uses-permission android:name="android.permission.READ_SMS" />
    <uses-permission android:name="android.permission.READ_CALL_LOG" />
    <uses-permission android:name="android.permission.WRITE_CALL_LOG" />
    <uses-permission android:name="android.permission.READ_CONTACTS" />
    <uses-permission android:name="android.permission.WRITE_CONTACTS" />

在Manifest中加入权限申请,当然Android6.0后需要动态申请权限了,所以要在MainActivity中加入动态申请权限。

//权限申请
    companion object {
        private const val REQUEST_CODE_PERMISSIONS = 10
        private val REQUIRED_PERMISSIONS = arrayOf(
            Manifest.permission.READ_PHONE_STATE, Manifest.permission.READ_PHONE_NUMBERS,
            Manifest.permission.ANSWER_PHONE_CALLS, Manifest.permission.CALL_PHONE,
            Manifest.permission.RECEIVE_SMS,Manifest.permission.SEND_SMS,
            Manifest.permission.READ_SMS, Manifest.permission.READ_CALL_LOG,
            Manifest.permission.WRITE_CALL_LOG, Manifest.permission.READ_CALL_LOG,
            Manifest.permission.WRITE_CONTACTS, Manifest.permission.READ_CONTACTS
        )
    }


    private fun allPermissionsGranted() = REQUIRED_PERMISSIONS.all {
        ContextCompat.checkSelfPermission(BaseApp.mContext, it) == PackageManager.PERMISSION_GRANTED
    }


    override fun onRequestPermissionsResult(
        requestCode: Int,
        permissions: Array<out String>,
        grantResults: IntArray
    ) {
        super.onRequestPermissionsResult(requestCode, permissions, grantResults)
        if (requestCode == REQUEST_CODE_PERMISSIONS) {
            if (allPermissionsGranted()) {
                //监听开关按钮
                isListened = DataStoreHelper.getData(ISLISTENMSG, false)
                Log.i("pkg", "NLSrv ${isListened}")
                val status = if (isListened) 0 else 2
                NLSrvUtil.updateStatus(status)
            } else {
                Toast.makeText(this, "未开启权限.", Toast.LENGTH_SHORT).show()
                finish()
            }
        }
    }
    
    //onCreate中再加入申请权限的动作
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        //申请权限
        if (!allPermissionsGranted()) {
            requestPermissions(REQUIRED_PERMISSIONS, REQUEST_CODE_PERMISSIONS)
        }
    }

02

电话及短信的BroadcastReceiver

这个广播接收是这篇的一个重点,先上代码:

package vac.test.notificationdemo


import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import android.os.Build
import android.provider.Telephony
import android.service.notification.NotificationListenerService
import android.telecom.TelecomManager
import android.telephony.PhoneStateListener
import android.telephony.SmsMessage
import android.telephony.TelephonyCallback
import android.telephony.TelephonyManager
import android.util.Log
import com.jeremyliao.liveeventbus.LiveEventBus
import vac.test.notificationdemo.bean.CMessage
import java.util.Hashtable
import java.util.Objects


class PhoneStateReceiver : BroadcastReceiver() {


    //当前来电号码
    var mPhoneNum: String? = null
    var mLastPhoneNum: String? = null
    //当前短信号码
    var mLastSmsPhoneNum: String? = null
    var mLastSmsContent: String? = null


    val contactsht: Hashtable<String, String> = NLSrvUtil.getContactsHashTable()


    var telMng: TelephonyManager =
        BaseApp.mContext.getSystemService(Context.TELEPHONY_SERVICE) as TelephonyManager


    fun createPhone(phonenum: String?): CMessage {
        val msg = CMessage()
        msg.packagename = "来电"
        msg.appname = "来电"
        msg.title = contactsht[phonenum] ?: "未知号码"
        msg.content = phonenum ?: "未知号码"
        return msg
    }


    fun createSms(phonenum: String?, content:String?): CMessage {
        val msg = CMessage()
        msg.packagename = "短信"
        msg.appname = "短信"
        msg.title = contactsht[phonenum] ?: "未知号码"
        msg.content = content ?: "未解析内容"
        return msg
    }


    init {
        telMng = BaseApp.mContext.getSystemService(Context.TELEPHONY_SERVICE) as TelephonyManager
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
            telMng.registerTelephonyCallback(
                BaseApp.mContext.mainExecutor,
                object : TelephonyCallback(), TelephonyCallback.CallStateListener {
                    override fun onCallStateChanged(state: Int) {
                        when (state) {
                            TelephonyManager.CALL_STATE_IDLE -> {
                                Log.d("pkg", "挂断")
                                mLastPhoneNum = null
                                mPhoneNum = null
                            }
                            TelephonyManager.CALL_STATE_OFFHOOK -> {
                                Log.d("pkg", "接听")
                                mLastPhoneNum = null
                                mPhoneNum = null
                            }
                            TelephonyManager.CALL_STATE_RINGING -> {
                                Log.d("pkg", "CALL_STATE_RINGING")
                                mPhoneNum?.let {
                                    if (mLastPhoneNum != it) {
                                        mLastPhoneNum = it
                                        val msg = createPhone(mPhoneNum)
                                        LiveEventBus.get<CMessage>(MESSAGE_RECV)
                                            .post(msg)
                                        Log.d("pkg", "响,号${mPhoneNum}")
                                    }
                                }
                            }
                        }


                    }
                })
        } else {
            telMng.listen(object : PhoneStateListener() {
                override fun onCallStateChanged(state: Int, phoneNumber: String?) {
                    when (state) {
                        TelephonyManager.CALL_STATE_IDLE ->
                            Log.d("log", "挂断")
                        TelephonyManager.CALL_STATE_OFFHOOK ->
                            Log.d("log", "接听")
                        TelephonyManager.CALL_STATE_RINGING -> {
                            Log.d("log", "响铃,来电号码:${phoneNumber}")
                            val msg = createPhone(phoneNumber)
                            LiveEventBus.get<CMessage>(MESSAGE_RECV)
                                .post(msg)
                        }
                    }
                }
            }, PhoneStateListener.LISTEN_CALL_STATE)
        }
    }


    override fun onReceive(context: Context, intent: Intent) {
        Log.d("pkg", "Action:${intent.action}")
        when (intent.action) {
            //监听电话状态
            "android.intent.action.PHONE_STATE" -> {
                mPhoneNum = intent.extras?.getString("incoming_number")
                Log.d("pkg", "号码:${mPhoneNum}")
            }
            Telephony.Sms.Intents.SMS_RECEIVED_ACTION -> {
                var curphonenum: String? = null
                val content = StringBuilder()
                val smsbundle = intent.extras
                val format = intent.getStringExtra("format")
                smsbundle?.let {
                    val pdus = smsbundle.get("pdus") as Array<*>
                    pdus?.let {
                        for (item in it) {
                            val message = SmsMessage.createFromPdu(item as ByteArray, format)
                            //短信电话号码
                            curphonenum = message.originatingAddress
                            content.append(message.messageBody)
                            val mills = message.timestampMillis
                            val status = message.status
                            Log.i("pkg", "phonenum:${curphonenum}, mills:${mills}, status:${status}")
                        }
                    }
                }
                //判断相同的消息就不再发送,防止接收过多
                if(curphonenum == mLastSmsPhoneNum && content.toString() == mLastSmsContent) return
                //记录最后一次接收短信的号码和内容
                mLastSmsPhoneNum = curphonenum
                mLastSmsContent = content.toString()


                Log.i("pkg", "phone:${mLastSmsPhoneNum},Content:${mLastSmsContent}")
                val msg = createSms(mLastSmsPhoneNum,mLastSmsContent)
                LiveEventBus.get<CMessage>(MESSAGE_RECV)
                    .post(msg)
            }
        }
    }
}

25a090ce35647e6bae97333ce60f4820.png

我的Demo程序使用的sdk是33,,TelephonyManager中使用registerTelephonyCallback来注册监听电话,上图红框中TelephonyManager.CALL_STATE_RINGING代表着响铃,也就是这个状态时直接使用LiveEventBus进行消息通讯。

af11771b5048bc0193133640023bbf89.png

定义了四个变量,主要是用于处理当前响铃的电话及收到短信的信息和号码,因为在测试过程中,使用广播接收时可能会触发多次,所以这里定义了变量用于处理多次接收相同的不再重复推送消息。

e52a8a3317e6356b949d7b788a527d35.png

通过修改onReceive来判断是电话还是短信,短信中也加入了相同信息不再重复推送。

03

关于联系人信息

使用BroadcastReceiver接收到的都是来电的号码,现在很少有人去背号码了,所以这里需要将号码转换成联系人的信息再推送过来。

87e0f253f029820324c44f94c2fc0ee5.png

我们在Demo的工具类中加入了一个getContactsHashTable的函数,用于导出联系人信息存放于HashTable中,这样通过号码查找也快。

//获取联系人信息
        fun getContactsHashTable() : Hashtable<String,String> {
            val contactsht = Hashtable<String, String>()
            var cursor: Cursor? = null
            try{
                cursor = BaseApp.mContext.contentResolver.query(ContactsContract.CommonDataKinds.Phone.CONTENT_URI,
                    null,null,null,null)
                cursor?.let {
                    while (it.moveToNext()){
                        val phonename = it.getString(it.getColumnIndexOrThrow(ContactsContract.CommonDataKinds.Phone.DISPLAY_NAME))
                        val phonenum = it.getString(it.getColumnIndexOrThrow(ContactsContract.CommonDataKinds.Phone.NUMBER))
                        contactsht.put(phonenum, phonename)
                    }
                }
            }catch (e: Exception) {
                e.printStackTrace()
            }finally {
                cursor?.let {
                    it.close()
                }
            }
            return contactsht
        }

54049e135fa85922225a05b1cf059ac1.png

而在PhoneStateReceiver中直接再写两个函数,通过来电号码和短信相关信息直接生成我们的CMessage类,再进行消息组件的通讯,这样电话和短信的接收通讯也就可以实现了。测试的来电和短信图片我就不再发上来了,主要是还要P图麻烦。

Tips

消息监听和模拟推送的Demo就已经完成了,测试正常,因为即然是想实时监听,就要保证锁屏也能正常使用,原来我考虑要再做成前台服务,不过我在手机系统中加入耗电设置后,也一直能实现锁屏的监听情况了,所以前台服务就暂时不加入进去了,有必要的时候再考虑。

另外就是应用把锁定也勾选上,这样杀后台的时候也不会将当前应用杀掉,正好做了下测试,手机待机一晚上,第二天再发消息,还是能正常接收消息,说明应用程序一直在后台运行中。

be8dd36d3766250e472709801dd90534.png

我的是Oppo Find N2 Flip,在设置的电池中,把当前应用的允许完成后台行为打上勾后,监听可以一直正常没问题。监听这块Demo基本就告一段落了,下一步就要开始做蓝牙通讯的相关Demo,用于监听到消息后的手机间通讯。

66000154d5977246471580c86f74e8a8.png

84d9d76a2a577fbde57c960f1be35295.png

往期精彩回顾

 

0ac42a4389cd848df4e9159a33774a48.jpeg

Android监听消息(一)——应用消息捕获

 

 

ca12fafca6e7f41a0e4815ed3ff81a94.jpeg

智能手表接收两台手机消息?最近计划

 

 

8cc270def9c21dc3da3ffc1e05c4aa59.jpeg

测试新版Android Studio的手机镜像效果

 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/433816.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【花雕学AI】爆款ChatGPT的核心算法和技术逻辑到底是什么?

一、ChatGPT是一种基于GPT模型的聊天机器人 由OpenAI研究中心开发&#xff0c;于2022年11月30日发布。它可以根据用户的输入&#xff0c;生成自然、流畅、有趣的对话回复。它的技术逻辑主要是利用大规模的预训练语言模型&#xff08;LLM&#xff09;&#xff0c;通过Transforme…

总结823

学习目标&#xff1a; 4月&#xff08;复习完高数18讲内容&#xff0c;背诵21篇短文&#xff0c;熟词僻义300词基础词&#xff09; 学习内容&#xff1a; 暴力英语&#xff1a;早上1.5小时背单词&#xff0c;背了两篇文章&#xff0c;之后抄写5篇文章。晚上做了一道长难句。 …

CAN-FD协议

总目录链接>> AutoSAR入门和实战系列总目录 总目录链接>> AutoSAR BSW高阶配置系列总目录 文章目录 CAN-FD协议**CAN-FD协议需要什么&#xff1f;**CAN-FD 协议的属性CAN-FD 协议中的安全性 OSI 层中的 CAN-FD**CAN-FD物理层设计**CAN-FD 数据链路层数据链路层…

【2023】cookie是什么?有什么用?一篇文章彻底搞懂cookie

一个不大不小的问题 假设服务器有一个接口&#xff0c;通过请求这个接口&#xff0c;可以添加一个管理员 但是&#xff0c;不是任何人都有权力做这种操作的 那么服务器如何知道请求接口的人是有权力的呢&#xff1f; 答案是&#xff1a;只有登录过的管理员才能做这种操作 …

一天掌握C51单片机基础1-计算机数值与MCS51单片机

目录 简介计算机的数值表示源码反码补码 MCS51 单片机型号与构成存储结构外部引脚与总线接口并行 IO 口工作原理工作周期 简介 本笔记参考B站高宏亮老师的教学视频&#xff1a;点击观看 计算机的数值表示 源码 正数&#xff1a;首位 0&#xff0c;其余七位表示实际数值 负数&…

Java内存模型JMM

大厂面试题&#xff1f; 你知道什么是java内存模型JMM吗&#xff1f; JMM和Volatile它们两个之间的关系&#xff1f; JMM有哪些特性和他的三大特性是什么&#xff1f; 为什么要有JMM&#xff0c;他为什么出现&#xff1f;作用和功能是什么&#xff1f; happens-before先行发…

如何用jmeter+ant+jenkins搭建一个接口自动化测试框架?

目录 前言 一、什么是Jmeter&#xff1f; 二、什么是Ant&#xff1f; 三、什么是Jenkins&#xff1f; 四、如何构建一个JmeterAntJenkins的接口自动化测试框架&#xff1f; 五、JmeterAntJenkins接口自动化测试框架的优势和特点 六、总结 前言 Jmeter是一款功能强大的开…

陪诊小程序开发|陪诊软件开发功能特色

为了提升就医的服务质量&#xff0c;人们对于医疗服务的需求也在不断提高。这几年随着生活水平和医疗水平的提升&#xff0c;陪诊服务越来越受到人们的重视和青睐&#xff0c;越来越多的人开始意识到&#xff0c;陪伴和关爱在疾病治疗过程中的重要性&#xff0c;为了更好的规划…

Python数据结构与算法-贪心算法(一)

一、贪心算法 1、定义 贪心算法(贪婪算法)是指&#xff0c;在对问题求解时&#xff0c;总是做出在当前看来是最好的选择。也就是说&#xff0c;不从整体最优上加以考虑&#xff0c;他所作出的是在某种意义上的局部最优解。 贪心算法并不保证会得到最优解&#xff0c;但是在某些…

debian 10 安装prometheus 2.37.6 配置rc.local自启动

debian 10 安装prometheus 2.37.6 配置rc.local自启动 1、下载安装包2、安装3、访问普罗米修斯4、加入开机自启动4.1、配置rc-local.service4.2、添加自定义启动命令4.3、查看rc-local.service 1、下载安装包 https://prometheus.io/download/ wget -c https://github.com/pro…

人工智能前沿——「小海带」超全视觉注意力机制资源分享(附下载链接)

&#x1f4da;&#x1f4da; 人工智能 | 计算机视觉 —— 致力于目标检测领域科研Tricks改进与推荐 | 主要包括主干网络改进、轻量化网络、注意力机制、检测头部改进、空间金字塔池化、损失函数及NMS改进、ICCV/CVPR/ECCV视觉顶会创新点改进、各类数据集资源分享以及算法训练相…

Serge让你在本地运行LLaMa模型

什么是 Serge &#xff1f; Serge 是基于 llama.cpp 运行 Alpaca 模型的聊天界面。完全自托管&#xff0c;不需要 API 密钥。适合 4GB RAM 并且能在 CPU 上运行。 什么是 LLaMA ? LLaMA 是一种机器学习算法&#xff0c;全称为 Laplacian Regularized Least Squares for Multip…

windows安装mysql详解

目录 1. mysql下载2. 添加环境变量3. 添加配置文件4. 初始化 data 目录5. 安装启动服务6. 修改密码 1. mysql下载 mysql官网&#xff1a;https://www.mysql.com/downloads/ MySQL Community Server&#xff1a;MySQL数据库的服务端&#xff0c;MySQL的核心&#xff0c;只下载它…

FISCO BCOS(三十四)———商品溯源(智能合约+后端)

FISCO BCOS(三十四)———商品溯源(智能合约+后端) 一、智能合约函数调用流程 注:智能合约来源(官网的合约仓库中) 但是TraceabilityFactory合约有问题,我已经做了修改,可以看原版与我的,只有一个函数不同。 官网上这套合约在TraceabilityFactory这个合约上缺少getGo…

Springboot信息泄露以及heapdump的利用

本文转载于https://blog.csdn.net/weixin_44309905/article/details/127279561 heapdump的利用 0x01 Springboot信息泄露 路由列表 0x02 下载heapdump0x03 利用heapdump的姿势 工具一&#xff1a;heapdump_tool工具二&#xff1a;Eclipse MemoryAnalyzer 0x01 Springboot信息…

【C++11】关于C++11新特性简介

目录 一、关于C11的简介 二、统一的列表初始化 2.1 {}初始化 2.2 std::initializer_list 三、声明 3.1 auto 3.2 decltype 3.3 nullptr 四、范围for循环 五、C11中STL的一些变化 一、关于C11的简介 在2003年 C标准委员会曾经提交了一份技术勘误表(简称TC1)&#xff…

“分割一切”大模型SAM、超轻量PP-MobileSeg、工业质检工具、全景分割方案,PaddleSeg全新版本等你来体验!

图像分割是计算机视觉的一项基础技术&#xff0c;其目标是将图像中的像素按内容分成不同的类别。它在许多领域有重要应用&#xff0c;比如自动驾驶、工业质检、医疗图像分析、遥感图像解译等。 导读 PaddleSeg 是飞桨高性能图像分割开发套件&#xff0c;在图像分割领域做了大…

分类预测 | MATLAB实现BO-CNN-BiLSTM贝叶斯优化卷积双向长短期记忆网络多输入分类预测

分类预测 | MATLAB实现BO-CNN-BiLSTM贝叶斯优化卷积双向长短期记忆网络多输入分类预测 目录 分类预测 | MATLAB实现BO-CNN-BiLSTM贝叶斯优化卷积双向长短期记忆网络多输入分类预测效果一览基本介绍模型搭建程序设计参考资料 效果一览 基本介绍 MATLAB实现BO-CNN-BiLSTM贝叶斯优…

上海车展:深蓝汽车首次亮相,全场景电动出行实力圈粉

4月18日&#xff0c;2023上海国际车展如约而至。 作为疫情结束后的首个国际车展&#xff0c;本届上海车展自然吸睛无数&#xff0c;光是首个媒体日进场时的阵仗&#xff0c;就让无数媒体人高呼“人潮汹涌”。 而在本次参展的众多汽车品牌中&#xff0c;刚刚成立一周年的深蓝汽车…

【IEEE期刊专区】这本IEEE旗下期刊中科院升级为1区(TOP),什么来头?(附IEEE在检SCI目录)

近期国自然也提交等待审核了&#xff0c;放榜等到7、8月份了&#xff0c;祝愿各位科研人有好运&#xff01;现在的节点正是发表评职代表作的好时机&#xff0c;本期小编带来IEEE旗下高分区、高影响因子SCI期刊推荐&#xff0c;是不可多得的好刊代表&#xff0c;有意向作者切不可…