Android: escuche los mensajes SMS entrantes

155

Estoy tratando de crear una aplicación para monitorear los mensajes SMS entrantes, y ejecutar un programa a través de SMS entrantes, también debería leer el contenido del SMS.

Flujo de trabajo:

  • SMS enviado al dispositivo Android
  • Aplicación auto ejecutable
  • Lee la información de SMS
iShader
fuente
1
Sé crear una aplicación para enviar el SMS, pero aquí necesito crear una aplicación de SMS que obtenga la información del SMS y la guarde en la base de datos SQLite ..... ¿Cómo puedo desarrollar dicha aplicación
IShader
@ iShader, espero que hayas tenido éxito en la creación de la aplicación, solo quería saber cómo lograste sincronizar los mensajes en blanco y negro con el dispositivo y el servidor
John x

Respuestas:

265
public class SmsListener extends BroadcastReceiver{

    private SharedPreferences preferences;

    @Override
    public void onReceive(Context context, Intent intent) {
        // TODO Auto-generated method stub

        if(intent.getAction().equals("android.provider.Telephony.SMS_RECEIVED")){
            Bundle bundle = intent.getExtras();           //---get the SMS message passed in---
            SmsMessage[] msgs = null;
            String msg_from;
            if (bundle != null){
                //---retrieve the SMS message received---
                try{
                    Object[] pdus = (Object[]) bundle.get("pdus");
                    msgs = new SmsMessage[pdus.length];
                    for(int i=0; i<msgs.length; i++){
                        msgs[i] = SmsMessage.createFromPdu((byte[])pdus[i]);
                        msg_from = msgs[i].getOriginatingAddress();
                        String msgBody = msgs[i].getMessageBody();
                    }
                }catch(Exception e){
//                            Log.d("Exception caught",e.getMessage());
                }
            }
        }
    }
}

Nota: en su archivo de manifiesto, agregue BroadcastReceiver-

<receiver android:name=".listener.SmsListener">
    <intent-filter>
        <action android:name="android.provider.Telephony.SMS_RECEIVED" />
    </intent-filter>
</receiver>

Agregue este permiso:

<uses-permission android:name="android.permission.RECEIVE_SMS" />
Vineet Shukla
fuente
2
¿Me puede explicar por qué usa un receptor secundario?
WindRider
2
@VineetShukla, ¿puedes explicar qué es pdus?
TheGraduateGuy
11
use Intents.SMS_RECEIVED_ACTION en lugar del codificado.
Ahmad Kayyali
66
El comentario anterior no es correcto. Cualquier aplicación aún puede recibir la SMS_RECEIVEDtransmisión en 4.4+, y, ahora que esa transmisión no puede ser cancelada, es más segura que en versiones anteriores.
Mike M.
3
@RuchirBaronia Mensajes multiparte. Un solo mensaje SMS tiene un límite de caracteres (varía según el conjunto de caracteres que esté utilizando, pero los límites comunes son 70, 140, 160 caracteres). Si un mensaje excede ese límite, se puede dividir en múltiples mensajes, partes. Esa matriz es la matriz de partes que necesita concatenar para obtener el mensaje completo. Su receptor solo recibirá un mensaje completo a la vez; Puede ser en varias partes.
Mike M.
65

Tenga en cuenta que en algunos dispositivos su código no funcionará sin Android: priority = "1000" en el filtro de intención:

<receiver android:name=".listener.SmsListener">
    <intent-filter android:priority="1000">
        <action android:name="android.provider.Telephony.SMS_RECEIVED" />
    </intent-filter>
</receiver>

Y aquí hay algunas optimizaciones:

public class SmsListener extends BroadcastReceiver{

    @Override
    public void onReceive(Context context, Intent intent) {
        if (Telephony.Sms.Intents.SMS_RECEIVED_ACTION.equals(intent.getAction())) {
            for (SmsMessage smsMessage : Telephony.Sms.Intents.getMessagesFromIntent(intent)) {
                String messageBody = smsMessage.getMessageBody();
            }
        }
    }
}

Nota :
El valor debe ser un número entero, como "100". Los números más altos tienen una prioridad más alta. El valor predeterminado es 0. El valor debe ser mayor que -1000 y menor que 1000.

Aquí hay un enlace.

stefan.nsk
fuente
30
Esta respuesta puede ser más elegante, pero requiere API 19. Solo un FYI para otros.
baekacaek
10
De acuerdo con esto , android:priorityno puede ser mayor que 1000(o menor que -1000).
Craned
2
No funciona en Xiaomi Redmi Note 3 Pro con Android 5.1. Todos están proporcionando esta solución, pero no parece funcionar para mí.
Sermilion
¿Dónde se inserta el marcado <receptor ... en el archivo de manifiesto?
John Ward,
3
@Sermilion Debe permitir manualmente el permiso para leer SMS en el administrador de aplicaciones del móvil.
Sanjay Kushwah
6

@Mike M. y yo encontramos un problema con la respuesta aceptada (vea nuestros comentarios):

Básicamente, no tiene sentido pasar por el ciclo for si no estamos concatenando el mensaje multiparte cada vez:

for (int i = 0; i < msgs.length; i++) {
    msgs[i] = SmsMessage.createFromPdu((byte[])pdus[i]);
    msg_from = msgs[i].getOriginatingAddress();
    String msgBody = msgs[i].getMessageBody();
}

Tenga en cuenta que solo establecemos msgBodyel valor de cadena de la parte respectiva del mensaje sin importar el índice en el que estemos, lo que hace que todo el punto de recorrer las diferentes partes del mensaje SMS sea inútil, ya que solo se establecerá en el mismo último valor del índice. En su lugar debemos utilizar +=, o como señaló Mike, StringBuilder:

En general, así es como se ve mi código de recepción de SMS:

if (myBundle != null) {
    Object[] pdus = (Object[]) myBundle.get("pdus"); // pdus is key for SMS in bundle

    //Object [] pdus now contains array of bytes
    messages = new SmsMessage[pdus.length];
    for (int i = 0; i < messages.length; i++) {
         messages[i] = SmsMessage.createFromPdu((byte[]) pdus[i]); //Returns one message, in array because multipart message due to sms max char
         Message += messages[i].getMessageBody(); // Using +=, because need to add multipart from before also
    }

    contactNumber = messages[0].getOriginatingAddress(); //This could also be inside the loop, but there is no need
}

Simplemente exponiendo esta respuesta en caso de que alguien más tenga la misma confusión.

Ruchir Baronia
fuente
4

¡Esto es lo que usé!

public class SMSListener extends BroadcastReceiver {

    // Get the object of SmsManager
    final SmsManager sms = SmsManager.getDefault();
String mobile,body;

    public void onReceive(Context context, Intent intent) {

        // Retrieves a map of extended data from the intent.
        final Bundle bundle = intent.getExtras();

        try {

            if (bundle != null) {

                final Object[] pdusObj = (Object[]) bundle.get("pdus");

                for (int i = 0; i < pdusObj.length; i++) {

                    SmsMessage currentMessage = SmsMessage.createFromPdu((byte[]) pdusObj[i]);
                    String phoneNumber = currentMessage.getDisplayOriginatingAddress();

                    String senderNum = phoneNumber;
                    String message = currentMessage.getDisplayMessageBody();
                     mobile=senderNum.replaceAll("\\s","");
                     body=message.replaceAll("\\s","+");


                    Log.i("SmsReceiver", "senderNum: "+ senderNum + "; message: " + body);


                    // Show Alert
                    int duration = Toast.LENGTH_LONG;
                    Toast toast = Toast.makeText(context,
                            "senderNum: "+ mobile+ ", message: " + message, duration);
                    toast.show();

                } // end for loop
            } // bundle is null

        } catch (Exception e) {
            Log.e("SmsReceiver", "Exception smsReceiver" +e);

        }
    }
}
Debasish Ghosh
fuente
2

En caso de que desee manejar la intención de una actividad abierta, puede usar PendintIntent (complete los pasos a continuación):

public class SMSReciver extends BroadcastReceiver {
    @Override
    public void onReceive(Context context, Intent intent) {
        final Bundle bundle = intent.getExtras();
        try {
            if (bundle != null) {
                final Object[] pdusObj = (Object[]) bundle.get("pdus");
                for (int i = 0; i < pdusObj.length; i++) {
                    SmsMessage currentMessage = SmsMessage.createFromPdu((byte[]) pdusObj[i]);
                    String phoneNumber = currentMessage.getDisplayOriginatingAddress();
                    String senderNum = phoneNumber;
                    String message = currentMessage.getDisplayMessageBody();
                    try {
                        if (senderNum.contains("MOB_NUMBER")) {
                            Toast.makeText(context,"",Toast.LENGTH_SHORT).show();

                            Intent intentCall = new Intent(context, MainActivity.class);
                            intentCall.putExtra("message", currentMessage.getMessageBody());

                            PendingIntent pendingIntent= PendingIntent.getActivity(context, 0, intentCall, PendingIntent.FLAG_UPDATE_CURRENT);
                            pendingIntent.send();
                        }
                    } catch (Exception e) {
                    }
                }
            }
        } catch (Exception e) {
        }
    }
} 

manifiesto:

<activity android:name=".MainActivity"
            android:launchMode="singleTask"/>
<receiver android:name=".SMSReciver">
            <intent-filter android:priority="1000">
                <action android:name="android.provider.Telephony.SMS_RECEIVED"/>
            </intent-filter>
        </receiver>

onNewIntent:

 @Override
         protected void onNewIntent(Intent intent) {
                super.onNewIntent(intent);
                Toast.makeText(this, "onNewIntent", Toast.LENGTH_SHORT).show();

                onSMSReceived(intent.getStringExtra("message"));

            }

permisos:

<uses-permission android:name="android.permission.RECEIVE_SMS" />
<uses-permission android:name="android.permission.READ_SMS" />
<uses-permission android:name="android.permission.SEND_SMS" />
AskQ
fuente
Los administradores de Google para Google Play Store consideran que el permiso RECEIVE_SMS (en el tutorial que mencionas) es peligroso. Como resultado, una aplicación que contiene el permiso será rechazada. Luego, el desarrollador debe enviar un formulario a los administradores de Google Play para su aprobación. Otros desarrolladores han mencionado que el proceso es horrible, ya que los comentarios tardan semanas y reciben rechazos directos sin explicaciones ni comentarios genéricos. ¿Alguna idea sobre cómo evitar?
AJW
2

Si alguien se refiere a cómo hacer la misma función (leer OTP usando los SMS recibidos) en Xamarin Android como yo:

  1. Agregue este código a su archivo AndroidManifest.xml:

    <receiver android:name=".listener.BroadcastReveiverOTP">
    <intent-filter>
        <action android:name="android.provider.Telephony.SMS_RECEIVED" />
    </intent-filter>
    </receiver>
    <uses-permission android:name="android.permission.RECEIVE_SMS" />
    <uses-permission android:name="android.permission.BROADCAST_SMS" />
    <uses-permission android:name="android.permission.READ_SMS" />
  2. Luego cree su clase BroadcastReveiver en su Proyecto Android.

    [BroadcastReceiver(Enabled = true)] [IntentFilter(new[] { "android.provider.Telephony.SMS_RECEIVED" }, Priority = (int)IntentFilterPriority.HighPriority)] 
    public class BroadcastReveiverOTP : BroadcastReceiver {
            public static readonly string INTENT_ACTION = "android.provider.Telephony.SMS_RECEIVED";
    
            protected string message, address = string.Empty;
    
            public override void OnReceive(Context context, Intent intent)
            {
                if (intent.HasExtra("pdus"))
                {
                    var smsArray = (Java.Lang.Object[])intent.Extras.Get("pdus");
                    foreach (var item in smsArray)
                    {
                        var sms = SmsMessage.CreateFromPdu((byte[])item);
                        address = sms.OriginatingAddress;
                        if (address.Equals("NotifyDEMO"))
                        {
                            message = sms.MessageBody;
                            string[] pin = message.Split(' ');
                            if (!string.IsNullOrWhiteSpace(pin[0]))
                            { 
                                    // NOTE : Here I'm passing received OTP to Portable Project using MessagingCenter. So I can display the OTP in the relevant entry field.
                                    MessagingCenter.Send<object, string>(this,MessengerKeys.OnBroadcastReceived, pin[0]);
                            }
                            }
                    }
                }
            }
    }
  3. Registre esta clase BroadcastReceiver en su clase MainActivity en Android Project:

    public class MainActivity : global::Xamarin.Forms.Platform.Android.FormsAppCompatActivity {
    
            // Initialize your class
            private BroadcastReveiverOTP _receiver = new BroadcastReveiverOTP ();
    
            protected override void OnCreate(Bundle bundle) { 
                    base.OnCreate(bundle);
    
                    global::Xamarin.Forms.Forms.Init(this, bundle);
                    LoadApplication(new App());
    
                    // Register your receiver :  RegisterReceiver(_receiver, new IntentFilter("android.provider.Telephony.SMS_RECEIVED"));
    
            }
    }
Pabodha Wimalasuriya
fuente
Recibí un error del compilador que dice "android.permission.BROADCAST_SMS" solo se otorga a las aplicaciones del sistema.
committedandroider
2

Gracias a @Vineet Shukla (la respuesta aceptada) y @Ruchir Baronia (encontró el problema en la respuesta aceptada), a continuación se muestra la Kotlinversión:

Agregar permiso:

<uses-permission android:name="android.permission.RECEIVE_SMS" />

Registre BroadcastReceiver en AndroidManifest:

<receiver
    android:name=".receiver.SmsReceiver"
    android:enabled="true"
    android:exported="true">
    <intent-filter android:priority="2332412">
        <action android:name="android.provider.Telephony.SMS_RECEIVED" />
    </intent-filter>
</receiver>

Agregar implementación para BroadcastReceiver:

class SmsReceiver : BroadcastReceiver() {
    private var mLastTimeReceived = System.currentTimeMillis()

    override fun onReceive(p0: Context?, intent: Intent?) {
        val currentTimeMillis = System.currentTimeMillis()
        if (currentTimeMillis - mLastTimeReceived > 200) {
            mLastTimeReceived = currentTimeMillis

            val pdus: Array<*>
            val msgs: Array<SmsMessage?>
            var msgFrom: String?
            var msgText: String?
            val strBuilder = StringBuilder()
            intent?.extras?.let {
                try {
                    pdus = it.get("pdus") as Array<*>
                    msgs = arrayOfNulls(pdus.size)
                    for (i in msgs.indices) {
                        msgs[i] = SmsMessage.createFromPdu(pdus[i] as ByteArray)
                        strBuilder.append(msgs[i]?.messageBody)
                    }

                    msgText = strBuilder.toString()
                    msgFrom = msgs[0]?.originatingAddress

                    if (!msgFrom.isNullOrBlank() && !msgText.isNullOrBlank()) {
                        //
                        // Do some thing here
                        //
                    }
                } catch (e: Exception) {
                }
            }
        }
    }
}

En algún momento, el evento se dispara dos veces, así que agrego mLastTimeReceived = System.currentTimeMillis()

Mentiroso
fuente
1

implementación de difusión en Kotlin:

 private class SmsListener : BroadcastReceiver() {
    override fun onReceive(context: Context?, intent: Intent?) {
        Log.d(TAG, "SMS Received!")

        val txt = getTextFromSms(intent?.extras)
        Log.d(TAG, "message=" + txt)
    }

    private fun getTextFromSms(extras: Bundle?): String {
        val pdus = extras?.get("pdus") as Array<*>
        val format = extras.getString("format")
        var txt = ""
        for (pdu in pdus) {
            val smsmsg = getSmsMsg(pdu as ByteArray?, format)
            val submsg = smsmsg?.displayMessageBody
            submsg?.let { txt = "$txt$it" }
        }
        return txt
    }

    private fun getSmsMsg(pdu: ByteArray?, format: String?): SmsMessage? {
        return when {
            SDK_INT >= Build.VERSION_CODES.M -> SmsMessage.createFromPdu(pdu, format)
            else -> SmsMessage.createFromPdu(pdu)
        }
    }

    companion object {
        private val TAG = SmsListener::class.java.simpleName
    }
}

Nota: en su archivo de manifiesto, agregue BroadcastReceiver-

<receiver android:name=".listener.SmsListener">
    <intent-filter>
        <action android:name="android.provider.Telephony.SMS_RECEIVED" />
    </intent-filter>
</receiver>

Agregue este permiso:

<uses-permission android:name="android.permission.RECEIVE_SMS" />
Serg Burlaka
fuente
1

La respuesta aceptada es correcta y funciona en versiones anteriores de Android donde el sistema operativo Android solicita permisos en la instalación de la aplicación, sin embargo, en las versiones más recientes de Android no funciona de inmediato porque el sistema operativo Android más nuevo solicita permisos durante el tiempo de ejecución cuando la aplicación requiere esa función . Por lo tanto, para recibir SMS en las versiones más recientes de Android utilizando la técnica mencionada en el programador de respuestas aceptado, también debe implementar un código que verifique y solicite permisos del usuario durante el tiempo de ejecución. En este caso, la funcionalidad / código de comprobación de permisos se puede implementar en onCreate () de la primera actividad de la aplicación. Simplemente copie y pegue los dos métodos siguientes en su primera actividad y llame al método checkForSmsReceivePermissions () al final de onCreate ().

    void checkForSmsReceivePermissions(){
    // Check if App already has permissions for receiving SMS
    if(ContextCompat.checkSelfPermission(getBaseContext(), "android.permission.RECEIVE_SMS") == PackageManager.PERMISSION_GRANTED) {
        // App has permissions to listen incoming SMS messages
        Log.d("adnan", "checkForSmsReceivePermissions: Allowed");
    } else {
        // App don't have permissions to listen incoming SMS messages
        Log.d("adnan", "checkForSmsReceivePermissions: Denied");

        // Request permissions from user 
        ActivityCompat.requestPermissions(this, new String[] {Manifest.permission.RECEIVE_SMS}, 43391);
    }
}

@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
    super.onRequestPermissionsResult(requestCode, permissions, grantResults);
    if(requestCode == 43391){
        if(grantResults.length>0 && grantResults[0] == PackageManager.PERMISSION_GRANTED){
            Log.d("adnan", "Sms Receive Permissions granted");
        } else {
            Log.d("adnan", "Sms Receive Permissions denied");
        }
    }
}
Adnan Ahmed
fuente