学更好的别人,
做更好的自己。
——《微卡智享》
本文长度为2747字,预计阅读6分钟
前言
前面一篇《Android监听消息(一)——应用消息捕获》我们使用NotificationListenerService实现了应用的消息监听,但是电话和短信是接收不到的,所以这一篇我们就来解决怎么监听电话及短信,电话主要就是在响铃时发送来电人消息,短信的话是捕获到消息内容直接发送出来。
微卡智享
实现思路
在Android中实现电话捕获用到的还是TelephonyManager,在Android12前TelephonyManager可以使用PhoneStateListener来进行监听,里面的onCallStateChanged可以直接获取到来电状态和来电号码,比较方便,如下图:
但是在Android12(sdk31)后,listen已经不能用了,需要使用registerTelephonyCallback来获取到来电状态,但是这里无法获取到来电号码了,所以为了实现接到到来电状态并能获取到来电人的信息,需要使用BroadcastReceiver来实现。
而捕获短信的消息则是使用android.telephony.SmsMessage,并且短信的接收也是通过BroadcastReceiver来实现,这样我们就把电话和短信接收直接写在一个BroadcastReceiver中即可。
代码实现
微卡智享
01
权限申请
接着上一篇的Demo,我们在这个基础上再增加相关的设置,首先要捕获电话及短信,那相关的权限必须要先申请出来。
在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,
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 = 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(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(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(MESSAGE_RECV)
.post(msg)
}
}
}
}
我的Demo程序使用的sdk是33,,TelephonyManager中使用registerTelephonyCallback来注册监听电话,上图红框中TelephonyManager.CALL_STATE_RINGING代表着响铃,也就是这个状态时直接使用LiveEventBus进行消息通讯。
定义了四个变量,主要是用于处理当前响铃的电话及收到短信的信息和号码,因为在测试过程中,使用广播接收时可能会触发多次,所以这里定义了变量用于处理多次接收相同的不再重复推送消息。
通过修改onReceive来判断是电话还是短信,短信中也加入了相同信息不再重复推送。
03
关于联系人信息
使用BroadcastReceiver接收到的都是来电的号码,现在很少有人去背号码了,所以这里需要将号码转换成联系人的信息再推送过来。
我们在Demo的工具类中加入了一个getContactsHashTable的函数,用于导出联系人信息存放于HashTable中,这样通过号码查找也快。
//获取联系人信息
fun getContactsHashTable() : Hashtable {
val contactsht = Hashtable()
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
}
而在PhoneStateReceiver中直接再写两个函数,通过来电号码和短信相关信息直接生成我们的CMessage类,再进行消息组件的通讯,这样电话和短信的接收通讯也就可以实现了。测试的来电和短信图片我就不再发上来了,主要是还要P图麻烦。
Tips
消息监听和模拟推送的Demo就已经完成了,测试正常,因为即然是想实时监听,就要保证锁屏也能正常使用,原来我考虑要再做成前台服务,不过我在手机系统中加入耗电设置后,也一直能实现锁屏的监听情况了,所以前台服务就暂时不加入进去了,有必要的时候再考虑。
另外就是应用把锁定也勾选上,这样杀后台的时候也不会将当前应用杀掉,正好做了下测试,手机待机一晚上,第二天再发消息,还是能正常接收消息,说明应用程序一直在后台运行中。
我的是Oppo Find N2 Flip,在设置的电池中,把当前应用的允许完成后台行为打上勾后,监听可以一直正常没问题。监听这块Demo基本就告一段落了,下一步就要开始做蓝牙通讯的相关Demo,用于监听到消息后的手机间通讯。
完
往期精彩回顾
Android监听消息(一)——应用消息捕获
智能手表接收两台手机消息?最近计划
测试新版Android Studio的手机镜像效果