3

In my android code, I am trying to detect incoming SMS messages. The code below was working since 2 years, but now it stopped working. What updates am I missing?

public class SmsListener extends BroadcastReceiver {

    private String msgBody;
    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")){

            Toast.makeText(context,"message received",Toast.LENGTH_SHORT).show();

            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();
                        msgBody = msgs[i].getMessageBody();
                        MainActivity.handleMessage(msgBody);
                    }

                    Toast.makeText(context,"message is:"+msgBody,Toast.LENGTH_SHORT).show();
                }catch(Exception e){
                    Log.d("Exception caught",e.getMessage());
                }
            }
        }
    }

In my Main Activity I am requesting the user permission, and using the SMS receiver as the following:

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



//Updated///////////////////////////////////////////////////////////////////////////////////
           if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M)
            requestSmsPermission();
        else {
            smsListener = new SmsListener();
            IntentFilter intentFilter = new IntentFilter();
            intentFilter.addAction("android.provider.Telephony.SMS_RECEIVED");
            registerReceiver(smsListener, intentFilter);
        }

        ///////Updated///////////////////////////////////////////////////////////////////////////////////

    }


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

    }
}

  @Override
    public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
        super.onRequestPermissionsResult(requestCode, permissions, grantResults);
        if (requestCode == 1) {
            smsListener = new SmsListener();
            IntentFilter intentFilter = new IntentFilter();
            intentFilter.addAction("android.provider.Telephony.SMS_RECEIVED");
            registerReceiver(smsListener, intentFilter);
        }
    }

My Manifest.xml:

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.example.zaidalmahmoud.expenseless">

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

    <application
        android:name=".MyApplication"
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:supportsRtl="true"
        android:theme="@style/AppTheme">
        <activity android:name=".GraphActivity"></activity>
        <activity android:name=".MainActivity">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>

        <receiver
            android:name=".SmsListener"
            android:exported="true"
            android:permission="android.permission.BROADCAST_SMS">
            <intent-filter>
                <action android:name="android.provider.Telephony.SMS_RECEIVED" />
            </intent-filter>
        </receiver>

        <activity android:name=".DetailedExpenseActivity" />
    </application>

</manifest>

When the SMS is received, my android application does not detect it, but it does not crash or show anything. Why? Thanks.

NOTE: I am not interested in catching SMS with verification code. I want my application to catch any incoming SMS.

Traveling Salesman
  • 2,209
  • 11
  • 46
  • 83

2 Answers2

6

The Android app needs SMS receive/read permission to retrieve SMS content. Google has introduced SMS Retriever API, this API allows to retrieve the OTP without needing of the SMS permission in your application.

enter image description here

Add These Dependency for SMS Retriever API

implementation 'com.google.android.gms:play-services-base:16.0.1'
implementation 'com.google.android.gms:play-services-identity:16.0.0'
implementation 'com.google.android.gms:play-services-auth:16.0.1'
implementation 'com.google.android.gms:play-services-auth-api-phone:16.0.0'

Then craete an interface like below:

public interface OnNewMessageListener {
    void onNewMessageReceived(String activationCode);
}

Then, create a broadCastReceiver to catch sms:

public class SmsBroadcastReceiver extends BroadcastReceiver {
    OnNewMessageListener onNewMessageListener;

    public SmsBroadcastReceiver() {
    }

    public SmsBroadcastReceiver(OnNewMessageListener onNewMessageListener) {
        this.onNewMessageListener = onNewMessageListener;
    }

    @Override
    public void onReceive(Context context, Intent intent) {
        if (SmsRetriever.SMS_RETRIEVED_ACTION.equals(intent.getAction())) {
            Bundle extras = intent.getExtras();
            if (extras != null) {
                Status status = (Status) extras.get(SmsRetriever.EXTRA_STATUS);

                if (status != null)
                    switch (status.getStatusCode()) {
                        case CommonStatusCodes.SUCCESS:
                            // Get SMS message contents
                            String message = (String) extras.get(SmsRetriever.EXTRA_SMS_MESSAGE);
                            // Extract one-time code from the message and complete verification
                            // by sending the code back to your server.
                            if (!TextUtils.isEmpty(message)) {
                                String activationCode = null;
                                Pattern p = Pattern.compile("your pattern like \\b\\d{4}\\b");
                                Matcher m = p.matcher(message);
                                if (m.find()) {
                                    activationCode = (m.group(0));  // The matched substring
                                }

                                if (onNewMessageListener != null && !TextUtils.isEmpty(activationCode))
                                    onNewMessageListener.onNewMessageReceived(activationCode);
                            }
                            break;
                        case CommonStatusCodes.TIMEOUT:
                            // Waiting for SMS timed out (5 minutes)
                            // Handle the error ...
                            break;
                    }
            }
        }
    }
}

At your AndroidManifest declare broadcastReceiver:

        <receiver
            android:name=".SmsBroadcastReceiver"
            android:exported="true"
            tools:ignore="ExportedReceiver">
            <intent-filter>
                <action android:name="com.google.android.gms.auth.api.phone.SMS_RETRIEVED" />
            </intent-filter>
        </receiver>

Inside your activity add these code:

    private SmsBroadcastReceiver smsListener;

     @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
    // Get an instance of SmsRetrieverClient, used to start listening for a matching
            // SMS message.
            SmsRetrieverClient client = SmsRetriever.getClient(Objects.requireNonNull(getContext()) /* context */);

            // Starts SmsRetriever, which waits for ONE matching SMS message until timeout
            // (5 minutes). The matching SMS message will be sent via a Broadcast Intent with
            // action SmsRetriever#SMS_RETRIEVED_ACTION.
            Task<Void> task = client.startSmsRetriever();

            // Listen for success/failure of the start Task. If in a background thread, this
            // can be made blocking using Tasks.await(task, [timeout]);
            task.addOnSuccessListener(aVoid -> {
                // Successfully started retriever, expect broadcast intent
                // ...
            });

            task.addOnFailureListener(e -> {
                // Failed to start retriever, inspect Exception for more details
                // ...
            });

            OnNewMessageListener onNewMessageListener = activationCode -> {
                if (!TextUtils.isEmpty(activationCode)) {
                    editText.setText(String.valueOf(activationCode));
                }
            };
            smsListener = new SmsBroadcastReceiver(onNewMessageListener);
            if (getContext() != null)
                if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
                    getContext().registerReceiver(smsListener, new IntentFilter(SmsRetriever.SMS_RETRIEVED_ACTION));
                }
    }

    @Override
    public void onStop() {
        super.onStop();
        try {
            if (getContext() != null && smsListener != null) {
                getContext().unregisterReceiver(smsListener);
                smsListener = null;
            }
        } catch (Exception ignored) {
        }
    }

Your sms should be like this:

<#> Use 123456 as your verification code 
FC+7qAH5AZu

Message must:

For more information see this link.

UPDATE

See this link.

EDIT

Change your activity to this:

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


    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M)
            requestSmsPermission();
        else {
            smsListener = new SmsListener();
            IntentFilter intentFilter = new IntentFilter();
            intentFilter.addAction("android.provider.Telephony.SMS_RECEIVED");
            registerReceiver(smsListener, intentFilter);
        }
}

@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
    super.onRequestPermissionsResult(requestCode, permissions, grantResults);
    if (requestCode == 1) {
    smsListener= new SmsListener();
    IntentFilter intentFilter=new IntentFilter();
    intentFilter.addAction("android.provider.Telephony.SMS_RECEIVED");
    registerReceiver(smsListener, intentFilter);
    }
}

Update

Change your BroadcastReceiver to this:

public class SmsListener extends BroadcastReceiver {

    private String msgBody;
    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")) {

            Toast.makeText(context, "message received", Toast.LENGTH_SHORT).show();

            Bundle bundle = intent.getExtras();
            try {
                if (bundle != null) {
                    final Object[] pdus = (Object[]) bundle.get("pdus");
                    for (int i = 0; i < pdus.length; i++) {
                        SmsMessage smsMessage;
                        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP)
                            smsMessage = SmsMessage.createFromPdu((byte[]) pdus[i], bundle.getString("format"));
                        else smsMessage = SmsMessage.createFromPdu((byte[]) pdus[i]);

                        msg_from = smsMessage.getDisplayOriginatingAddress();
                        msgBody = smsMessage.getMessageBody();
                        MainActivity.handleMessage(msgBody);
                    }
                    Toast.makeText(context, "message is:" + msgBody, Toast.LENGTH_SHORT).show();
                }
            } catch (Exception e) {
                Log.d("Exception caught", e.getMessage());
            }
        }
    }
}
Masoud Mokhtari
  • 2,390
  • 1
  • 17
  • 45
  • I appreciate your help, but I do not want to detect SMS with verification code. My question is about catching any SMS. In fact, I am interested in a totally different kind of SMS. – Traveling Salesman Nov 11 '19 at 07:23
  • Thanks, I tried to reuse some of that code, but I don't see much difference between my code and his code, and I did not get any change in my application. SMS is not detected. I need to know where is my mistake exactly. – Traveling Salesman Nov 12 '19 at 09:50
  • Hi. I would like to tell you that it worked only once, and then it stopped working and I don't know why. I put exactly your code above in the last edit – Traveling Salesman Nov 17 '19 at 10:24
  • @TravelingSalesman I think your problem is at this: MainActivity.handleMessage(msgBody); use an interface to update or send your data to main activity. – Masoud Mokhtari Nov 18 '19 at 10:11
  • I didn't change anything (except maybe one toast message) and now it is working again. I will keep testing it, and let you know. But I highly doubt there is a problem in MainActivity.handleMessage(msgBody) because I tested it well, or at least because now it is working without having any issue. – Traveling Salesman Nov 18 '19 at 16:00
  • @TravelingSalesman I change something in my answer and update it. Check it again. – Masoud Mokhtari Nov 19 '19 at 05:07
  • @Masoud Mokhtari I know this is a little bit late to ask but I needed help. I followed your above code and it work, but I want to display the message in an TextView. I tried creating a static string ````message```` in Activity and set it value to ````msgBody```` in ````BroadcastReceiver```` but when I called the ````handleMessage```` in Activity after ````registerReceiver```` but somehow it did not work. It seem no code after ````registerReceiver```` was ran because I put a Log after it but was not shown in debug. Can you help me out? – user3676506 Oct 13 '20 at 00:46
4

Declare SMS Read and Receive permission in your android manifest file :

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

Then we need a SMS Receiver as follows:

class SMSReceiver : BroadcastReceiver() {

override fun onReceive(context: Context, intent: Intent?) {
    if (intent != null && intent.action != null && intent.action!!.equals("android.provider.Telephony.SMS_RECEIVED", ignoreCase = true)) {
        val bundle = intent.extras
        if (bundle != null) {
            val sms = bundle.get(SMS_BUNDLE) as Array<Any>?
            val smsMsg = StringBuilder()

            var smsMessage: SmsMessage
            if (sms != null) {
                for (sm in sms) {
                    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
                        val format = bundle.getString("format")
                        smsMessage = SmsMessage.createFromPdu(sm as ByteArray, format)
                    } else {
                        smsMessage = SmsMessage.createFromPdu(sm as ByteArray)
                    }


                    val msgBody = smsMessage.messageBody.toString()
                    val msgAddress = smsMessage.originatingAddress

                    smsMsg.append("SMS from : ").append(msgAddress).append("\n")
                    smsMsg.append(msgBody).append("\n")
                }

                sendBroadcast(smsMsg.toString())
            }
        }
    }
}

private fun sendBroadcast(smsMSG: String) {
    val broadcastIntent = Intent()
    broadcastIntent.action = AppConstants.mBroadcastSMSUpdateAction
    broadcastIntent.putExtra(AppConstants.message, smsMSG)
    EventBus.getDefault().post(EventIntent(broadcastIntent))
}

companion object {

    val SMS_BUNDLE = "pdus"
}
}

You can broadcast the received SMS information to your MainActivity.

Then you need to declare the SMS Broadcast receiver in your manifest file as follows:

  <receiver
        android:name=".SMSReceiver"
        android:enabled="true">
        <intent-filter android:priority="2147483647">
            <category android:name="android.intent.category.DEFAULT" />

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

After that on application launch in MainActivity you need to check for SMS permissions by checking:

Manifest.permission.READ_SMS,
Manifest.permission.RECEIVE_SMS

You can use built in android permission module or RxPermission for that :

  val rxPermissions = RxPermissions(this)
    rxPermissions.request(
            Manifest.permission.READ_SMS,
            Manifest.permission.RECEIVE_SMS)
            .subscribe(object : Observer<Boolean> {
                override fun onNext(t: Boolean) {
                    if (t) {

                    } else {
                        Toast.makeText(activity, getString(R.string.permission_request_denied), Toast.LENGTH_LONG).show()
                    }
                }
                override fun onSubscribe(d: Disposable) {}
                override fun onError(e: Throwable) {}
                override fun onComplete() {}
            })
evandrix
  • 6,041
  • 4
  • 27
  • 38