learn better from others,
be the better one.
—— "Weika Zhixiang"
The length of this article is 2747 words , and it is expected to read for 6 minutes
foreword
In the previous article " Android Monitoring Messages (1) - Application Message Capture ", we used NotificationListenerService to implement application message monitoring, but calls and text messages cannot be received, so in this article we will solve how to monitor calls and text messages. The main purpose of the phone is to send the caller's message when it rings, and the text message is to capture the content of the message and send it directly.
Micro card Zhixiang
Implementation ideas
TelephonyManager is still used to capture phone calls in Android. Before Android 12, TelephonyManager can use PhoneStateListener to monitor. The onCallStateChanged inside can directly obtain the status and number of incoming calls, which is more convenient, as shown in the following figure:
However, after Android 12 (sdk31), listen is no longer available. You need to use registerTelephonyCallback to obtain the incoming call status, but the incoming call number cannot be obtained here. Therefore, in order to realize the incoming call status and obtain the incoming caller's information, you need Use BroadcastReceiver to achieve.
The message of capturing SMS is using android.telephony.SmsMessage, and the receiving of SMS is also realized through BroadcastReceiver, so we can directly write the phone and SMS reception in a BroadcastReceiver.
Code
Micro card Zhixiang
01
Access Request
Following the demo in the previous article, we will add related settings on this basis. First, we need to capture phone calls and text messages, and the relevant permissions must be applied for first.
<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" />
Add permission application in Manifest. Of course, after Android 6.0, you need to apply for permission dynamically, so you need to add dynamic application permission in 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 for calls and text messages
This broadcast reception is a key point of this article, first go to the code:
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)
}
}
}
}
The sdk used by my Demo program is 33, and registerTelephonyCallback is used in TelephonyManager to register the monitoring phone. The TelephonyManager.CALL_STATE_RINGING in the red box above represents ringing, that is, in this state, LiveEventBus is directly used for message communication.
Four variables are defined, which are mainly used to process the current ringing phone and the information and number of the SMS received. Because during the test, when using broadcast reception, it may be triggered multiple times, so variables are defined here for processing multiple Receive the same message for the first time and no longer push the message repeatedly.
By modifying onReceive to determine whether it is a phone call or a text message, the same information is also added to the text message and will not be repeatedly pushed.
03
About contact information
The numbers received by BroadcastReceiver are the numbers of the incoming calls. Few people recite the numbers now, so it is necessary to convert the numbers into contact information and push them here.
We added a getContactsHashTable function to the tool class of Demo to export contact information and store it in HashTable, so that searching by number is also fast.
//获取联系人信息
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
}
And write two more functions in PhoneStateReceiver directly, generate our CMessage class directly through the caller number and SMS related information, and then communicate with the message component, so that the receiving communication of the phone and SMS can be realized. I won't post the pictures of the incoming calls and text messages for the test, mainly because it is troublesome to have P pictures.
Tips
The demo of message monitoring and simulated push has been completed, and the test is normal, because since I want to monitor in real time, it is necessary to ensure that the lock screen can also be used normally. I originally considered making it a front-end service, but I added it to the mobile phone system. After the power consumption is set, the monitoring of the lock screen can always be realized, so the foreground service will not be added for the time being, and it will be considered when necessary.
In addition, the lock is also checked on the application, so that the current application will not be killed when killing the background. Just did the next test, the phone was left on standby for one night, and the message was sent the next day, and the message was still received normally, indicating that the application The program has been running in the background.
Mine is Oppo Find N2 Flip. In the battery settings, after ticking the current app's Allow background behavior, the monitoring can always be normal without any problem. The demo of monitoring is basically over, and the next step is to start the related demo of Bluetooth communication, which is used for communication between mobile phones after the news is monitored.
over
Wonderful review of the past
Android listening message (1) - application message capture
Smart watch to receive messages from two phones? latest plan
Test the phone mirroring effect of the new version of Android Studio