Search code examples
androidbroadcastreceiversmsandroid-sms

Detecting incoming SMS messages


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.


Solution

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