Android Core

Intercept Incoming SMS on Android

Last week I talked about using SMS to activate your application which is a pretty powerful way to verify a user account. I left a couple of things out though. One of those things is the ability to grab the incoming SMS automatically. This is only possible on Android but it’s pretty cool for the users as it saves on the pain of typing the activation text.

Broadcast Receiver

In order to grab an incoming SMS we need a broadcast receiver which is a standalone Android class that receives a specific event type. This is often confusing to developers who sometimes derive the impl class from broadcast receiver…​ That’s a mistake…​

The trick is you can just place any native Android class into the native/android directory. It will get compiled with the rest of the native code and “just works”. So I placed this class under native/android/com/codename1/sms/intercept:

package com.codename1.sms.intercept;

import android.content.*;
import android.os.Bundle;
import android.telephony.*;

public class SMSListener extends BroadcastReceiver {

    public void onReceive(Context cntxt, Intent intent) {
        // based on code from
        if(intent.getAction().equals("android.provider.Telephony.SMS_RECEIVED")) {
            Bundle bundle = intent.getExtras();
            SmsMessage[] msgs = null;
            if (bundle != null){
                    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]);
                        String msgBody = msgs[i].getMessageBody();
                } catch(Exception e) {

The code above is pretty standard native Android code, it’s just a callback in which most of the logic is similar to the native Android code mentioned in this stackoverflow question.

But there is still more we need to do. In order to implement this natively we need to register the permission and the reciever in the manifest.xml file as explained in that question. This is how their native manifest looked:

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android=""
    <uses-permission android:name="android.permission.RECEIVE_SMS" />
    <uses-permission android:name="android.permission.READ_SMS" />
    <uses-permission android:name="android.permission.SEND_SMS"/>
    <uses-permission android:name="android.permission.READ_CONTACTS" />

        <activity android:name=".MainActivity">
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.LAUNCHER" />
        <receiver android:name="com.bulsy.smstalk1.SmsListener"
            <intent-filter android:priority="2147483647">//this doesnt work
                <category android:name="android.intent.category.DEFAULT" />
                <action android:name="android.provider.Telephony.SMS_RECEIVED" />

We only need the broadcast permission XML and the permission XML. Both are doable via the build hints. The former is pretty easy:

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

The latter isn’t much harder, notice I took multiple lines and made them into a single line for convenience:

android.xapplication=<receiver android:name="com.codename1.sms.intercept.SMSListener"  android:enabled="true" android:permission="android.permission.BROADCAST_SMS"  android:exported="true">                    <intent-filter android:priority="2147483647"><category android:name="android.intent.category.DEFAULT" />        <action android:name="android.provider.Telephony.SMS_RECEIVED" />                 </intent-filter>             </receiver>

Here it is formatted nicely:

<receiver android:name="com.codename1.sms.intercept.SMSListener"
                   <intent-filter android:priority="2147483647">
                          <category android:name="android.intent.category.DEFAULT" />
                          <action android:name="android.provider.Telephony.SMS_RECEIVED" />

Listening & Permissions

You will notice that these don’t include the actual binding or permission prompts you would expect for something like this. To do this we need a native interface.

The native sample in stack overflow bound the listener in the activity but here we want the app code to decide when we should bind the listening:

public interface NativeSMSInterceptor extends NativeInterface {
    public void bindSMSListener();
    public void unbindSMSListener();

That’s easy!

Notice that isSupported() returns false for all other OS’s so we won’t need to ask whether this is “Android” we can just use isSupported().

The implementation is pretty easy too:

package com.codename1.sms.intercept;

import android.Manifest;
import android.content.IntentFilter;

public class NativeSMSInterceptorImpl {
    private SMSListener smsListener;
    public void bindSMSListener() {
        if(AndroidNativeUtil.checkForPermission(Manifest.permission.RECEIVE_SMS, "We can automatically enter the SMS code for you")) { (1)
            smsListener = new SMSListener();
            IntentFilter filter = new IntentFilter();
            AndroidNativeUtil.getActivity().registerReceiver(smsListener, filter); (2)

    public void unbindSMSListener() {

    public boolean isSupported() {
        return true;
1 This will trigger the permission prompt on Android 6 and newer. Even though the permission is declared in XML this isn’t enough for 6+. Notice that even when you run on Android 6 you still need to declare permissions in XML!
2 Here we actually bind the listener, this allows us to grab one SMS and not listen in on every SMS coming thru


Up until now the code wasn’t very usable so lets abstract it a bit. But first we need to implement the callback class to which SMS’s and errors are sent from the code above:

package com.codename1.sms.intercept; (1)

import com.codename1.util.FailureCallback;
import com.codename1.util.SuccessCallback;
import static com.codename1.ui.CN.*;

 * This is an internal class, it's package protect to hide that
class SMSCallback {
    static SuccessCallback<String> onSuccess;
    static FailureCallback onFail;

    public static void smsReceived(String sms) {
        if(onSuccess != null) {
            SuccessCallback<String> s = onSuccess;
            onSuccess = null;
            onFail = null;
            callSerially(() -> s.onSucess(sms)); (2)

    public static void smsReceiveError(Exception err) {
        if(onFail != null) {
            FailureCallback f = onFail;
            onFail = null;
            onSuccess = null;
            callSerially(() -> f.onError(null, err, 1, err.toString()));
        } else {
            if(onSuccess != null) {
                onSuccess = null;
1 Notice that the package is the same as the native code and the other classes. This allows the callback class to be package protected so it isn’t exposed via the API (the class doesn’t have the public modifier)
2 We wrap the callback in call serially to match the Codename One convention of using the EDT by default. The call will probably arrive on the Android native thread so it makes sense to normalize it and not expose the Android native thread to the user code

A simple API

The final piece of the puzzle is a simple API that can wrap the whole thing up and also hide the fact that this is Android specific. We’ll get into the full API in the last installment but for now this is the user level API that hides the native interface. Using a class like this is generally good practice as it allows us flexibility with the actual underlying native interface.

package com.codename1.sms.intercept;

import com.codename1.system.NativeLookup;
import com.codename1.util.FailureCallback;
import com.codename1.util.SuccessCallback;

 * This is a high level abstraction of the native classes and callbacks rolled into one.
public class SMSInterceptor {
    private static NativeSMSInterceptor nativeImpl;

    private static NativeSMSInterceptor get() {
        if(nativeImpl == null) {
            nativeImpl = NativeLookup.create(NativeSMSInterceptor.class);
            if(!nativeImpl.isSupported()) {
                nativeImpl = null;
        return nativeImpl;

    public static boolean isSupported() {
        return get() != null;

    public static void grabNextSMS(SuccessCallback<String> onSuccess) {
        SMSCallback.onSuccess = onSuccess;

    static void unbindListener() {

Next Time

Next time I will wrap this all up with the user experience and package everything into an easy to use cn1lib.

Some of the things I touched here might be a bit “hairy” in terms of native interface usage so if something isn’t clear just ask in the comments.

Published on Java Code Geeks with permission by Shai Almog, partner at our JCG program. See the original article here: TIP: Intercept Incoming SMS on Android

Opinions expressed by Java Code Geeks contributors are their own.

Want to know how to develop your skillset to become a Java Rockstar?

Join our newsletter to start rocking!

To get you started we give you our best selling eBooks for FREE!


1. JPA Mini Book

2. JVM Troubleshooting Guide

3. JUnit Tutorial for Unit Testing

4. Java Annotations Tutorial

5. Java Interview Questions

6. Spring Interview Questions

7. Android UI Design


and many more ....


Receive Java & Developer job alerts in your Area

I have read and agree to the terms & conditions


Shai Almog

Shai is the co-founder of Codename One, he has been programming professionally for over 20 years and developing in Java since 96. Shai worked for countless industry leaders including Sun Microsystems where he was a part of the original WTK (Wireless Toolkit) team & the co-creator of LWUIT. He worked with most major device operators/manufactures including Nokia, Samsung, Sony Ericson, Sprint, Vodafone, Verizon, NTT DoCoMo etc. Shai is a blogger and writer who often speaks at conventions. He is a Java One rockstar and top rated speaker for JavaZone, corporate conventions from Oracle, IBM and many others.
Notify of

This site uses Akismet to reduce spam. Learn how your comment data is processed.

Inline Feedbacks
View all comments
Back to top button