Android monitoring message (2) - phone and SMS monitoring

learn better from others,

be the better one.

—— "Weika Zhixiang"

541ca03168e8b1b77e905f275152c364.jpeg

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.

fcd000ebcbe34f08ffddc3fa1a2019a6.png

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:

30380aba28c920f27dd27bd352f915fc.png

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

394da72110f59a41bc8e4bca493aac8e.png

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.

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" />

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)
            }
        }
    }
}

25a090ce35647e6bae97333ce60f4820.png

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.

af11771b5048bc0193133640023bbf89.png

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.

e52a8a3317e6356b949d7b788a527d35.png

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.

87e0f253f029820324c44f94c2fc0ee5.png

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
        }

54049e135fa85922225a05b1cf059ac1.png

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.

be8dd36d3766250e472709801dd90534.png

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

66000154d5977246471580c86f74e8a8.png

84d9d76a2a577fbde57c960f1be35295.png

Wonderful review of the past

 

0ac42a4389cd848df4e9159a33774a48.jpeg

Android listening message (1) - application message capture

 

 

ca12fafca6e7f41a0e4815ed3ff81a94.jpeg

Smart watch to receive messages from two phones? latest plan

 

 

8cc270def9c21dc3da3ffc1e05c4aa59.jpeg

Test the phone mirroring effect of the new version of Android Studio

 

Guess you like

Origin blog.csdn.net/Vaccae/article/details/130234807