178

I am trying to create an application for monitoring incoming SMS messages, and launch a program via incoming SMS, also it should read the content from the SMS.

Workflow:

  • SMS sent to Android device
  • self executable Application
  • Read the SMS information
CopsOnRoad
  • 237,138
  • 77
  • 654
  • 440
iShader
  • 2,095
  • 5
  • 18
  • 17
  • 1
    I know to create an app to send the SMS, but here I need to create an SMS app which gets the information from the SMS and save it to SQLite Database..... How can I develop such App – iShader Aug 22 '11 at 11:20
  • @iShader i hope you got successfull in creating the app, just wanted to know how did you manage to sync the msgs b/w the device and the server – John x Nov 01 '13 at 10:33

9 Answers9

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

Note: In your manifest file add the BroadcastReceiver-

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

Add this permission:

<uses-permission android:name="android.permission.RECEIVE_SMS" />
Vineet Shukla
  • 23,865
  • 10
  • 55
  • 63
  • 2
    Can you expain me why you use a secondary receiver? – WindRider May 07 '13 at 00:12
  • 2
    @VineetShukla can you please explain what is pdus ?? – TheGraduateGuy Nov 28 '13 at 09:14
  • 14
    use Intents.SMS_RECEIVED_ACTION instead of the hard-coded one. – Ahmad Kayyali Dec 03 '13 at 07:13
  • 6
    The above comment is not correct. Any app can still get the `SMS_RECEIVED` broadcast in 4.4+, and, now that that broadcast cannot be aborted, it is more certain than in previous versions. – Mike M. Jul 26 '16 at 13:30
  • 1
    @MikeM. Why is msgs [] an array? Aren't we just detecting one SMS at a time? So why must we iterate through msgs.length, what does it contain at each index? – Ruchir Baronia Jan 16 '17 at 22:03
  • 4
    @RuchirBaronia Multipart messages. A single SMS message has a character limit (it varies depending on the character set you're using, but common limits are 70, 140, 160 characters). If a message exceeds that limit, it can be split into multiple messages, parts. That array is the array of parts that you need to concatenate to get the complete message. Your Receiver will only ever get one complete message at a time; it just might be in multiple parts. – Mike M. Jan 16 '17 at 22:23
  • @RuchirBaronia Yeah, now that I look at the answer, it's a little off. You need to concatenate the `msgs[i].getMessageBody()` returns in the loop to get the complete message. – Mike M. Jan 16 '17 at 22:30
  • @MikeM. Yeah that's what I was confused about. So doing something like this `Message += messages[i].getMessageBody();` rather than `Message = messages[i].getMessageBody();` would successfully concatenate the different parts of the SMS, right? – Ruchir Baronia Jan 16 '17 at 22:40
  • @MikeM. However `msg_from` would just be `=`, not `+=`, like so: `msg_from += messages[i].getOriginatingAddress();` – Ruchir Baronia Jan 16 '17 at 22:41
  • @RuchirBaronia (1st) Yeah, that'll work. Or you could use a `StringBuilder`. However you want to concatenate them. (2nd) The sender's number (originating address) will be the same for each part. You just need to grab it from one of them, and you can do that outside of the loop. – Mike M. Jan 16 '17 at 22:46
  • @MikeM. Alright, thanks for clearing that up, I really appreciate it :) I also put an answer up in case anyone else has the same confusion – Ruchir Baronia Jan 16 '17 at 22:56
  • Seems like the event wouldn't fire, unless I set my app as the default messaging app (tried on Android 7.0 with Samsung Galaxy A7, but I'd suspect that also happens in other devices). Am I missing anything here? – Yoav Feuerstein Jun 19 '18 at 12:05
  • @Vineet_Shukla or anyone else: When the above approach is implemented does the SMS show in both your app and the default SMS app? My ideal flow is: 1) Message received by my app where I do something special to the message. Default SMS app does not get the message when my app is enabled. 2) Message is sent to the default SMS app when the user chooses. – AdamHurwitz Jul 01 '18 at 03:53
  • Anyone have any suggestions on how to debug why this code doesn't fire off when a sms message is received? – committedandroider Jul 16 '18 at 07:49
  • Is code don't work in Android 8+, Please share workaround – Abhi Oct 30 '18 at 04:24
  • 2
    As per latest Google Development policies, we are not allowed to ask Receive_SMS permission or we have to fill one document which describes our need to use specific permissions. Do anyone have any alternative of Receive_SMS permission other than SMS Retriever API? – Aanal Shah Nov 19 '18 at 11:46
  • 1
    To make this work in newer versions check this answer https://stackoverflow.com/a/35972161/5783522 – nemojmenervirat Jul 16 '20 at 07:28
76

Note that on some devices your code wont work without android:priority="1000" in intent filter:

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

And here is some optimizations:

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

Note:
The value must be an integer, such as "100". Higher numbers have a higher priority. The default value is 0. The value must be greater than -1000 and less than 1000.

Here's a link.

noɥʇʎԀʎzɐɹƆ
  • 9,967
  • 2
  • 50
  • 67
stefan.nsk
  • 1,755
  • 1
  • 16
  • 12
8

@Mike M. and I found an issue with the accepted answer (see our comments):

Basically, there is no point in going through the for loop if we are not concatenating the multipart message each time:

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

Notice that we just set msgBody to the string value of the respective part of the message no matter what index we are on, which makes the entire point of looping through the different parts of the SMS message useless, since it will just be set to the very last index value. Instead we should use +=, or as Mike noted, StringBuilder:

All in all, here is what my SMS receiving code looks like:

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
}

Just putting this answer out there in case anyone else has the same confusion.

Alexis Wilke
  • 19,179
  • 10
  • 84
  • 156
Ruchir Baronia
  • 7,406
  • 5
  • 48
  • 83
5

The accepted answer is correct and works on older versions of Android where Android OS asks for permissions at the app install, However on newer versions Android it doesn't work straight away because newer Android OS asks for permissions during runtime when the app requires that feature. Therefore in order to receive SMS on newer versions of Android using technique mentioned in accepted answer programmer must also implement code that will check and ask for permissions from user during runtime. In this case permissions checking functionality/code can be implemented in onCreate() of app's first activity. Just copy and paste following two methods in your first activity and call checkForSmsReceivePermissions() method at the end of 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
  • 143
  • 2
  • 10
4

If someone referring how to do the same feature (reading OTP using received SMS) on Xamarin Android like me :

  1. Add this code to your AndroidManifest.xml file :

    <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. Then create your BroadcastReveiver class in your Android Project.

    [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. Register this BroadcastReceiver class in your MainActivity class on 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"));
    
            }
    }
    
Yoav Feuerstein
  • 1,925
  • 2
  • 22
  • 53
2

In case you want to handle intent on opened activity, you can use PendintIntent (Complete steps below):

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

manifest:

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

            }

permissions:

<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
  • 4,215
  • 7
  • 34
  • 61
  • 1
    The Google administrators for the Google Play Store consider the RECEIVE_SMS permission (in the tutorial you mention) to be dangerous. As a result, an app that contains the permission will be rejected. Then the developer has to submit a form to Google Play administrators for approval. Other developers have mentioned the process is awful with feedback taking weeks and receiving outright rejections with either no explanations or generic feedback. Any ideas on how to avoid? – AJW Jul 15 '20 at 15:15
2

Thank to @Vineet Shukla (the accepted answer) and @Ruchir Baronia (found the issue in the accepted answer), below is the Kotlin version:

Add permission:

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

Register BroadcastReceiver in 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>

Add implementation for 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) {
                }
            }
        }
    }
}

Sometime event fires twice so I add mLastTimeReceived = System.currentTimeMillis()

Liar
  • 1,235
  • 1
  • 9
  • 19
1

Since some time now, it becomes nearly impossible to publish an app with the android.permission.RECEIVE_SMS permission, if you are not a default sms app. Google provide a new tool for SMS catching ==> Automatic SMS Verification with the SMS Retriever API

Arno Abomo
  • 46
  • 4
0

broadcast implementation on 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
    }
}

Note: In your manifest file add the BroadcastReceiver-

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

Add this permission:

<uses-permission android:name="android.permission.RECEIVE_SMS" />
Serg Burlaka
  • 2,351
  • 24
  • 35