21

I'd like my app to catch incoming SMS messages. There are a few examples of this around. Looks like we just need to do this:

// AndroidManifest.xml
<receiver android:name=".SMSReceiver"> 
  <intent-filter> 
    <action android:name="android.provider.Telephony.SMS_RECEIVED" /> 
  </intent-filter> 
</receiver>        

// SMSReceiver.java
public class SMSReceiver extends BroadcastReceiver 
{ 
    @Override 
    public void onReceive(Context context, Intent intent) { 
        Log.i(TAG, "SMS received.");
        ....
    }
}

is this correct? I'm sending my phone some sms messages, but the log statement never gets printed. I do have some other SMS applications installed on the phone, which display a popup when the sms is received - are they somehow blocking the intent from getting passed down to my app, they are just consuming it completely?

Thanks

Mark
  • 39,551
  • 15
  • 41
  • 47
  • 2
    Check logcat to see whether you're missing the permission that Samuh mentions. Also I would note that other application's cannot consume or block broadcast intents from being received by other applications. – Christopher Orr Dec 29 '09 at 13:06
  • 1
    AFAIK, when there is a conflict, meaning an Intent resolves to more than one entity you would be displayed a chooser activity. Broadcasts are for every one! – Samuh Dec 29 '09 at 13:38
  • anyone know if there is any way to get the phone number in the onReceive() event? – ycomp Feb 06 '12 at 11:17

7 Answers7

31

You would also need to specify a uses-permission in your manifest file:

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

The following tutorials should help:

React on incoming SMS
SMS messaging in Android

AndyW
  • 1,431
  • 16
  • 22
Samuh
  • 36,316
  • 26
  • 109
  • 116
  • Ok this works, I had put my receiver declaration outside the tag in my manifest. No error was reported, but the app just wouldn't catch the incoming SMS messages. Now it works. Thanks – Mark Dec 29 '09 at 16:18
  • yeah thats a common mistake :) – Samuh Apr 08 '11 at 15:57
  • 2
    this is the second app that I have put together for SMS and this is the second time that I put the receiver declaration (in the manifest) outside of the application. – mobibob Jun 22 '11 at 18:54
  • For those following at home, you actually have to declare this inside of your `` element, not your `` element. This was causing my problem. See "Contained in", here: http://developer.android.com/guide/topics/manifest/uses-permission-element.html – jwir3 Dec 21 '14 at 01:47
  • @Samuh The Google administrators for the Google Play Store consider the RECEIVE_SMS permission 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, receiving only general feedback and often outright rejections are received with no explanations. Any ideas on how to avoid? – AJW Jul 15 '20 at 14:55
14

There are a few gotchas on the way. You can find all the needed info on stackoverflow. I have gathered all the info in this answer, for convenience.

Things to be noticed

  1. I assume android kitkat and above.
  2. The intent for incomming sms is "android.provider.Telephony.SMS_RECEIVED"
  3. You can change the priority of the intent filter, but it's not necessary.
  4. You need this permission "android.permission.RECEIVE_SMS" in manifest xml, in order to receive sms messages. In android 6 and above, you additionally need to ask for the permission in runtime.
  5. You do not need to set the MIME type of data in the intent filter. Intent filter should pass only on empty data if no MIME type is set, but fortunately it will still work without MIME.
  6. adb shell am broadcast will not work. Use telnet connection to simulator to test sms receiving.
  7. Long sms messages are divided into small sms chunks. We need to concatenate them.

How to send a sms message to the emulator

The most important thing is to have the possibility to send fake sms messages to the device, so we can test the code.

For this we will use a virtual device and a telnet connection to it.

  1. Create a virtual device in android studio and run the simulator
  2. Look at the title bar in the simulator window. There is the device name and a port number. We need to know this port number in the next steps.
  3. Now connect to the port number shown in the simulator title bar with telnet

     $ telnet localhost 5554
    
  4. If you see this: Android Console: Authentication required, then you need to authenticate the connection with this command:

     auth xxxxxx
    

    Replace the xxxxxx above with the token read from ~/.emulator_console_auth_token file.

  5. Now you should be able to run all the commands. To send a sms message, type this command:

     sms send 555 "This is a message"
    

    Where you can replace 555 with the sender telephone number and a message of your own.

How to listen to SMS_RECEIVED broadcasts

To get the broadcasts, you need to register a BroadcastReceiver object. You can do this in the manifest.xml OR just call registerReceiver function. I will show you the latter, as it is easier to reason about and yet more flexible.

Connecting the broadcast receiver with the main activity

The data flow is one way. From broadcast receiver to the main activity. So the simplest way to get them to talk is to use a function interface. The activity will implement such a function and the broadcast receiver will have the activity instance passed as a parameter in the constructor.

File SmsHandler.java:

package ...

interface SmsHandler {
    void handleSms(String sender, String message);
}

Implementing the broadcast receiver

The broadcast receiver will get the intent in a callback. We will use the function Telephony.Sms.Intents.getMessagesFromIntent(intent) to get the sms messages. Notice the SmsHandler parameter in the constructor. It will be the activity to which we will send the received sms.

File SmsInterceptor.java:

package ...

import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.provider.Telephony;
import android.telephony.SmsMessage;

public class SmsInterceptor extends BroadcastReceiver {

    private SmsHandler handler;

    /* Constructor. Handler is the activity  *
     * which will show the messages to user. */
    public SmsInterceptor(SmsHandler handler) {
        this.handler = handler;
    }

    @Override
    public void onReceive(Context context, Intent intent) {
        /* Retrieve the sms message chunks from the intent */
        SmsMessage[] rawSmsChunks;
        try {
            rawSmsChunks = Telephony.Sms.Intents.getMessagesFromIntent(intent);
        } catch (NullPointerException ignored) { return; }

        /* Gather all sms chunks for each sender separately */
        Map<String, StringBuilder> sendersMap = new HashMap<>();
        for (SmsMessage rawSmsChunk : rawSmsChunks) {
            if (rawSmsChunk != null) {
                String sender = rawSmsChunk.getDisplayOriginatingAddress();
                String smsChunk = rawSmsChunk.getDisplayMessageBody();
                StringBuilder smsBuilder;
                if ( ! sendersMap.containsKey(sender) ) {
                    /* For each new sender create a separate StringBuilder */
                    smsBuilder = new StringBuilder();
                    sendersMap.put(sender, smsBuilder);
                } else {
                    /* Sender already in map. Retrieve the StringBuilder */
                    smsBuilder = sendersMap.get(sender);
                }
                /* Add the sms chunk to the string builder */
                smsBuilder.append(smsChunk);
            }
        }

        /* Loop over every sms thread and concatenate the sms chunks to one piece */
        for ( Map.Entry<String, StringBuilder> smsThread : sendersMap.entrySet() ) {
            String sender  = smsThread.getKey();
            StringBuilder smsBuilder = smsThread.getValue();
            String message = smsBuilder.toString();
            handler.handleSms(sender, message);
        }
    }
}

The main activity

Finally we need to implement SmsHandler interface into the main activity and add registering the broadcast receiver and permission check to the onCreate function.

File MainActivity.java:

package ...

import ...

public class MainActivity extends AppCompatActivity implements SmsHandler {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        /* Register the broadcast receiver */
        registerSmsListener();

        /* Make sure, we have the permissions */
        requestSmsPermission();
    }

    /* This function will be called by the broadcast receiver */
    @Override
    public void handleSms(String sender, String message) {
        /* Here you can display the message to the user */
    }

    private void registerSmsListener() {
        IntentFilter filter = new IntentFilter();
        filter.addAction("android.provider.Telephony.SMS_RECEIVED");
        /* filter.setPriority(999); This is optional. */
        SmsInterceptor receiver = new SmsInterceptor(this);
        registerReceiver(receiver, filter);
    }

    private void requestSmsPermission() {
        String permission = Manifest.permission.RECEIVE_SMS;
        int grant = ContextCompat.checkSelfPermission(this, permission);
        if ( grant != PackageManager.PERMISSION_GRANTED) {
            String[] permission_list = new String[1];
            permission_list[0] = permission;
            ActivityCompat.requestPermissions(this, permission_list, 1);
        }
    }
}

Finally remember to add RECEIVE_SMS permission to your manifest xml

<?xml version="1.0" encoding="utf-8"?>
<manifest ...>
    <uses-permission android:name="android.permission.RECEIVE_SMS"/>
    <application>
        ...
    </application>
</manifest>
drzymala
  • 2,009
  • 20
  • 26
  • 1. Not true. The API wasn't made public until KitKat, but it was certainly implemented rather widely prior to that. 3. The default app cannot abort the `SMS_RECEIVED` broadcast. It is responsible for saving incoming messages, which it can decide not to do, so it may appear to have "blocked" messages in apps that only query the Provider for messages, but it can't abort that broadcast. Prior to KitKat, that broadcast could be aborted, and any app with a higher priority could abort it before yours receives it, but there was no concept of a default SMS app before 4.4. – Mike M. Nov 12 '16 at 03:51
  • Also, you're not handling the received `SmsMessage`s correctly, and your interface method will fire once for each part of a multipart message. You should concatenate the message bodies in the loop, then pass the whole message after. – Mike M. Nov 12 '16 at 03:51
  • @MikeM. Thanks, I have fixed the code. Regarding the aborting of broadcast, it is unfortunately possible. Try for yourself. Maybe this is a bug but it is a reality. At least in Marshmallow. However you can not stop the default sms app from receiving an sms. – drzymala Nov 13 '16 at 14:05
  • I really don't think so. Not on stock, anyway. Maybe in some odd OEM versions, custom ROMs, or rooted devices, but in standard ol' Android, the `SMS_RECEIVED` broadcast can't be aborted since 4.4. The other answer you link to was written when KitKat was on ~2% of devices, so it's surely talking about Hangouts' well-known behavior on older versions. Granted, Hangouts can still seem to intercept SMS on newer versions - if that's what you're referring to specifically - when using Project Fi, e.g., since it will handle it over the data network, rather than as actual SMS, and won't broadcast anyway – Mike M. Nov 13 '16 at 15:24
  • There are also other circumstances where it might appear like an SMS is being aborted, but, again, it's not actually SMS; like when a message comes through as MMS or RCS. Your Receiver won't fire for those, but it's quite possible that the carrier's own messaging app seamlessly lists all message types together. I'd need some hard evidence to be convinced. :-) – Mike M. Nov 13 '16 at 15:25
  • Well Mike, I have checked again and seems your'e right. No need to set the priority :-P – drzymala Nov 13 '16 at 19:22
  • Wow, this was unbelievably useful for setting up receiving SMS!! Thank you – benzabill Feb 01 '17 at 21:11
  • @drzymala 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:27
  • @AJW I think they will let you be in the Play Store if the SMS service is the core idea behind your app. The only other option seems to be to skip the Play Store. – drzymala Jul 22 '20 at 00:22
  • @drzymala Are there other ways to distribute an Android app other than the Play Store? Samsung's store? Or is the Play Store the exclusive place for Android apps? – AJW Jul 22 '20 at 02:15
  • @AJW Sure, other than sharing the APK directly, ther is also Aptoide, F-Droid, etc. Keep in mind that this IS making your device effectively defenseless if there is malware in any of the apps, as Google at least can remotely remove the problematic APK. Not using Play Store, you are on your own. – drzymala Jul 22 '20 at 08:44
9

One more thing that these answers haven't mentioned - you should require the permission android.permission.BROADCAST_SMS. If you don't do this, any application can spoof messages in your app.

<receiver android:name=".SMSReceiver"
              android:exported="true"
              android:permission="android.permission.BROADCAST_SMS">
             <intent-filter>
                 <action android:name="android.provider.Telephony.SMS_RECEIVED"/>
             </intent-filter>
 </receiver>
Charles Munger
  • 1,417
  • 12
  • 11
  • Wondering if you could elaborate a little more on how adding this permission specific to this receiver could prevent spoof messages? Your view will be greatly appreciated. – tfboy Mar 22 '14 at 13:41
  • 4
    Without requiring that permission, any app on the device could broadcast an intent with the action "android.provider.Telephony.SMS_RECEIVED" and your app would think it came from the system. – Charles Munger Apr 19 '14 at 19:29
  • That's misleading. The BRODCAST_SMS permission is not relevant for an app receiving incoming messages. – pipacs Mar 26 '17 at 11:15
  • Hi there. If change properly "exported" value for "false". Work fine? – marlonpya Nov 05 '21 at 22:53
3

Also note that the Hangouts application will currently block my BroadcastReceiver from receiving SMS messages. I had to disable SMS functionality in the Hangouts application (Settings->SMS->Turn on SMS), before my SMS BroadcastReceived started getting fired.

Edit: It appears as though some applications will abortBroadcast() on the intent which will prevent other applications from receiving the intent. The solution is to increase the android:priority attribute in the intent-filter tag:

    <receiver android:name="com.company.application.SMSBroadcastReceiver" >
        <intent-filter android:priority="500">
            <action android:name="android.provider.Telephony.SMS_RECEIVED" />
        </intent-filter>
    </receiver>

See more details here: Enabling SMS support in Hangouts 2.0 breaks the BroadcastReceiver of SMS_RECEIVED in my app

Community
  • 1
  • 1
TheIT
  • 11,919
  • 4
  • 64
  • 56
  • @ThelT 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:28
2

Did you try with the emulator ?

After deploying your application in the emulator, you can send events like SMS via the DDMS or via the command line by connecting with telnet :

telnet localhost <port_emulator>
send sms <incoming_tel_number> <sms_content>

port_emulator is usually 5554

tbruyelle
  • 12,895
  • 9
  • 60
  • 74
1

You should read this acticle about send and receive sms programmatically. http://mobiforge.com/developing/story/sms-messaging-android

Nguyen Minh Binh
  • 23,891
  • 30
  • 115
  • 165
  • The code in the article you mention requires the RECEIVE_SMS permission. 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:29
0

Android Messenger (the SMS client) has a "Chat" feature which transmits messages over WiFi instead of SMS.

If the person you are testing with uses Messenger as well, you'll need to disable this feature on one or both of your devices otherwise there is no SMS message actually being received:

To turn chat features off:

  1. Open Messages Messages Logo Round.
  2. Tap More More and then Settings.
  3. Tap Advanced and then Chat features.
  4. Turn Enable chat features on or off.

https://support.google.com/messages/answer/7189714?hl=en

Community
  • 1
  • 1