Android - nasłuchiwanie przychodzących wiadomości SMS

155

Próbuję stworzyć aplikację do monitorowania przychodzących wiadomości SMS i uruchamiać program poprzez przychodzące SMS-y, również powinien czytać treść z SMS-a.

Przepływ pracy:

  • SMS wysłany na urządzenie z systemem Android
  • samowykonalna aplikacja
  • Przeczytaj informacje SMS
iShader
źródło
1
Wiem, jak stworzyć aplikację do wysyłania SMS-ów, ale tutaj muszę stworzyć aplikację SMS, która pobiera informacje z SMS-a i zapisuje je w bazie danych SQLite ..... Jak mogę stworzyć taką aplikację
iShader
@iShader Mam nadzieję, że udało Ci się stworzyć aplikację, chciałem tylko wiedzieć, w jaki sposób udało Ci się zsynchronizować wiadomości czarno-białe z urządzeniem i serwerem
Jan x

Odpowiedzi:

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

Uwaga: w pliku manifestu dodaj BroadcastReceiver-

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

Dodaj to uprawnienie:

<uses-permission android:name="android.permission.RECEIVE_SMS" />
Vineet Shukla
źródło
2
Czy możesz mi wyjaśnić, dlaczego używasz drugiego odbiornika?
WindRider
2
@VineetShukla czy możesz wyjaśnić, co to jest pdus?
TheGraduateGuy
11
użyj Intents.SMS_RECEIVED_ACTION zamiast sztywnego kodu.
Ahmad Kayyali,
6
Powyższy komentarz nie jest poprawny. Każda aplikacja może nadal odbierać SMS_RECEIVEDtransmisję w wersji 4.4+, a teraz, gdy tej transmisji nie można przerwać, jest to bardziej pewne niż w poprzednich wersjach.
Mike M.
3
@RuchirBaronia Multipart wiadomości. Pojedyncza wiadomość SMS ma limit znaków (różni się w zależności od używanego zestawu znaków, ale typowe limity to 70, 140, 160 znaków). Jeśli wiadomość przekracza ten limit, można ją podzielić na wiele wiadomości, części. Ta tablica to tablica części, które musisz połączyć, aby otrzymać kompletny komunikat. Twój odbiorca otrzyma tylko jedną kompletną wiadomość naraz; po prostu może być w wielu częściach.
Mike M.
65

Pamiętaj, że na niektórych urządzeniach Twój kod nie będzie działał bez androida: priority = "1000" w filtrze intencji:

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

A oto kilka optymalizacji:

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

Uwaga :
wartość musi być liczbą całkowitą, np. „100”. Wyższe liczby mają wyższy priorytet. Wartość domyślna to 0. Wartość musi być większa niż -1000 i mniejsza niż 1000.

Oto link.

stefan.nsk
źródło
30
Ta odpowiedź może być bardziej elegancka, ale wymaga API 19. Tylko do Twojej wiadomości dla innych.
baekacaek
10
Zgodnie z tym , android:prioritynie może być wyższa niż 1000(lub poniżej -1000).
wyciągnięty
2
Nie działa na Xiaomi Redmi Note 3 Pro z systemem Android 5.1. Wszyscy zapewniają to rozwiązanie, ale wydaje mi się, że nie działa.
Sermilion
Gdzie w pliku manifestu znajduje się znacznik <receiver ...?
John Ward
3
@Sermilion Musisz ręcznie zezwolić na odczyt SMS-ów w menedżerze aplikacji telefonu komórkowego.
Sanjay Kushwah
6

@Mike M. i ja znaleźliśmy problem z zaakceptowaną odpowiedzią (zobacz nasze komentarze):

Zasadniczo nie ma sensu przechodzenie przez pętlę for, jeśli nie łączymy wiadomości wieloczęściowej za każdym razem:

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

Zauważ, że po prostu ustawiliśmy msgBodywartość ciągu odpowiedniej części wiadomości bez względu na indeks, w którym się znajdujemy, co sprawia, że ​​cały punkt pętli przez różne części wiadomości SMS jest bezużyteczny, ponieważ zostanie ustawiony na bardzo ostatnia wartość indeksu. Zamiast tego powinniśmy użyć +=, lub jak zauważył Mike StringBuilder:

Podsumowując, oto jak wygląda mój kod odbioru 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
}

Po prostu umieszczam tę odpowiedź na wypadek, gdyby ktoś inny miał takie samo zamieszanie.

Ruchir Baronia
źródło
4

To jest to, czego użyłem!

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
źródło
2

Jeśli chcesz zająć się intencją otwartej aktywności, możesz użyć PendintIntent (wykonaj poniższe czynności):

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

oczywisty:

<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"));

            }

uprawnienia:

<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
źródło
Administratorzy Google dla Sklepu Google Play uważają, że uprawnienie RECEIVE_SMS (we wspomnianym samouczku) jest niebezpieczne. W rezultacie aplikacja zawierająca uprawnienie zostanie odrzucona. Następnie programista musi przesłać formularz do administratorów Google Play w celu zatwierdzenia. Inni programiści wspominali, że proces jest okropny, ponieważ opinie zajmują tygodnie i otrzymują bezpośrednie odrzucenia bez wyjaśnień lub ogólnych informacji zwrotnych. Jakieś pomysły, jak tego uniknąć?
AJW
2

Jeśli ktoś odnosi się do tego, jak zrobić tę samą funkcję (odczytywanie OTP za pomocą odebranego SMS-a) na Xamarin Android, tak jak ja:

  1. Dodaj ten kod do pliku 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. Następnie utwórz klasę BroadcastReveiver w swoim projekcie 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. Zarejestruj tę klasę BroadcastReceiver w swojej klasie MainActivity w 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
źródło
Wystąpił błąd kompilatora z informacją, że „android.permission.BROADCAST_SMS” jest przyznawane tylko aplikacjom systemowym.
popełnionyandroider
2

Dziękuję @Vineet Shukla (zaakceptowana odpowiedź) i @Ruchir Baronia (znalazłem problem w zaakceptowanej odpowiedzi), poniżej znajduje się Kotlinwersja:

Dodaj uprawnienia:

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

Zarejestruj BroadcastReceiver w 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>

Dodaj implementację dla 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) {
                }
            }
        }
    }
}

Czasami zdarzenie jest uruchamiane dwukrotnie, więc dodaję mLastTimeReceived = System.currentTimeMillis()

Kłamca
źródło
1

wdrożenie transmisji na 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
    }
}

Uwaga: w pliku manifestu dodaj BroadcastReceiver-

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

Dodaj to uprawnienie:

<uses-permission android:name="android.permission.RECEIVE_SMS" />
Serg Burlaka
źródło
1

Zaakceptowana odpowiedź jest poprawna i działa na starszych wersjach Androida, w których system operacyjny Android prosi o uprawnienia podczas instalacji aplikacji, jednak w nowszych wersjach Androida nie działa od razu, ponieważ nowszy system operacyjny Android prosi o uprawnienia w czasie wykonywania, gdy aplikacja wymaga tej funkcji . Dlatego, aby otrzymywać SMS-y na nowszych wersjach Androida przy użyciu techniki wspomnianej w zaakceptowanej odpowiedzi, programista musi również zaimplementować kod, który będzie sprawdzał i pytał użytkownika o uprawnienia w trakcie działania. W takim przypadku sprawdzanie uprawnień / kodu może zostać zaimplementowane w onCreate () pierwszej aktywności aplikacji. Po prostu skopiuj i wklej następujące dwie metody w swoim pierwszym działaniu i wywołaj metodę checkForSmsReceivePermissions () na końcu 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
źródło