Mensaje de monitoreo de Android (2) - monitoreo de teléfono y SMS

aprender mejor de los demás,

ser el mejor

—— "WeikaZhixiang"

541ca03168e8b1b77e905f275152c364.jpeg

La extensión de este artículo es de 2747 palabras , y se espera una lectura de 6 minutos.

prefacio

En el artículo anterior " Mensajes de monitoreo de Android (1) - Captura de mensajes de la aplicación ", usamos NotificationListenerService para implementar el monitoreo de mensajes de la aplicación, pero las llamadas y los mensajes de texto no se pueden recibir, por lo que en este artículo resolveremos cómo monitorear las llamadas y los mensajes de texto. El objetivo principal del teléfono es enviar el mensaje de la persona que llama cuando suena, y el mensaje de texto es capturar el contenido del mensaje y enviarlo directamente.

fcd000ebcbe34f08ffddc3fa1a2019a6.png

Micro tarjeta Zhixiang

Ideas de implementación

TelephonyManager todavía se usa para capturar llamadas telefónicas en Android. Antes de Android 12, TelephonyManager puede usar PhoneStateListener para monitorear. El interior onCallStateChanged puede obtener directamente el estado y la cantidad de llamadas entrantes, lo cual es más conveniente, como se muestra en la siguiente figura:

30380aba28c920f27dd27bd352f915fc.png

Sin embargo, después de Android 12 (sdk31), la escucha ya no está disponible. Debe usar registerTelephonyCallback para obtener el estado de la llamada entrante, pero el número de la llamada entrante no se puede obtener aquí. Por lo tanto, para conocer el estado de la llamada entrante y obtener la la información de la persona que llama entrante, necesita usar BroadcastReceiver para lograrlo.

El mensaje de captura de SMS está utilizando android.telephony.SmsMessage, y la recepción de SMS también se realiza a través de BroadcastReceiver, por lo que podemos escribir directamente el teléfono y la recepción de SMS en un BroadcastReceiver.

Código

394da72110f59a41bc8e4bca493aac8e.png

Micro tarjeta Zhixiang

01

Acceso requerido

Siguiendo la demostración del artículo anterior, agregaremos configuraciones relacionadas sobre esta base. Primero, necesitamos capturar llamadas telefónicas y mensajes de texto, y primero se deben aplicar los permisos relevantes.

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

Agregue la aplicación de permiso en Manifiesto. Por supuesto, después de Android 6.0, debe solicitar el permiso de forma dinámica, por lo que debe agregar el permiso de aplicación dinámica en 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 para llamadas y mensajes de texto

Esta recepción de transmisión es un punto clave de este artículo, primero vaya al código:

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

El sdk usado por mi programa de demostración es 33, y registerTelephonyCallback se usa en TelephonyManager para registrar el teléfono de monitoreo.El TelephonyManager.CALL_STATE_RINGING en el cuadro rojo de arriba representa el timbre, es decir, en este estado, LiveEventBus se usa directamente para la comunicación de mensajes.

af11771b5048bc0193133640023bbf89.png

Se definen cuatro variables, que se utilizan principalmente para procesar el teléfono que suena actualmente y la información y el número del SMS recibido. Debido a que durante la prueba, cuando se utiliza la recepción de transmisión, puede activarse varias veces, por lo que aquí se definen variables para procesar múltiples Reciba el mismo mensaje por primera vez y ya no presione el mensaje repetidamente.

e52a8a3317e6356b949d7b788a527d35.png

Al modificar onReceive para determinar si se trata de una llamada telefónica o un mensaje de texto, la misma información también se agrega al mensaje de texto y no se enviará repetidamente.

03

Acerca de la información de contacto

Los números recibidos por BroadcastReceiver son los números de las llamadas entrantes. Pocas personas recitan los números ahora, por lo que es necesario convertir los números en información de contacto y enviarlos aquí.

87e0f253f029820324c44f94c2fc0ee5.png

Agregamos una función getContactsHashTable a la clase de herramienta Demo para exportar información de contacto y almacenarla en HashTable, de modo que la búsqueda por número también sea rápida.

//获取联系人信息
        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

Y escriba dos funciones más en PhoneStateReceiver directamente, genere nuestra clase CMessage directamente a través del número de la persona que llama y la información relacionada con el SMS, y luego comuníquese con el componente del mensaje, para que se pueda realizar la comunicación de recepción del teléfono y el SMS. No publicaré las imágenes de las llamadas entrantes y los mensajes de texto para la prueba, principalmente porque es problemático tener imágenes P.

Consejos

La demostración de monitoreo de mensajes y push simulado se ha completado, y la prueba es normal, porque como quiero monitorear en tiempo real, es necesario asegurarse de que la pantalla de bloqueo también se pueda usar normalmente. Originalmente consideré hacerlo un frente -servicio final, pero lo agregué al sistema de telefonía móvil. Después de configurar el consumo de energía, siempre se puede realizar el monitoreo de la pantalla de bloqueo, por lo que el servicio de primer plano no se agregará por el momento, y se considerará cuando sea necesario.

Además, el bloqueo también se verifica en la aplicación, de modo que la aplicación actual no se elimine al eliminar el fondo. Acabo de hacer la siguiente prueba, el teléfono se dejó en espera durante una noche y el mensaje se envió al día siguiente. , y el mensaje aún se recibía con normalidad, lo que indica que la aplicación El programa se ha estado ejecutando en segundo plano.

be8dd36d3766250e472709801dd90534.png

El mío es Oppo Find N2 Flip En la configuración de la batería, después de marcar Permitir el comportamiento en segundo plano de la aplicación actual, el monitoreo siempre puede ser normal sin ningún problema. La demostración de monitoreo básicamente ha terminado, y el siguiente paso es iniciar la demostración relacionada de la comunicación Bluetooth, que se utiliza para la comunicación entre teléfonos móviles después de que se monitorean las noticias.

encima

66000154d5977246471580c86f74e8a8.png

84d9d76a2a577fbde57c960f1be35295.png

Maravillosa revisión del pasado.

 

0ac42a4389cd848df4e9159a33774a48.jpeg

Mensaje de escucha de Android (1): captura de mensajes de la aplicación

 

 

ca12fafca6e7f41a0e4815ed3ff81a94.jpeg

¿Reloj inteligente para recibir mensajes de dos teléfonos? último plan

 

 

8cc270def9c21dc3da3ffc1e05c4aa59.jpeg

Prueba el efecto de espejo del teléfono de la nueva versión de Android Studio

 

Supongo que te gusta

Origin blog.csdn.net/Vaccae/article/details/130234807
Recomendado
Clasificación