Receiving Sms on Android App

Receiving SMS on Android App

Here is my implementation of receiving sms messages. Sms message may be broken into many, notice how it is treated. Also check the android:priority attribute.

public class SmsReceiver extends BroadcastReceiver {

private static final String SMS_RECEIVED = "android.provider.Telephony.SMS_RECEIVED";

@Override
public void onReceive(Context context, Intent intent) {
if (intent.getAction().equals(SMS_RECEIVED)) {
Bundle bundle = intent.getExtras();
if (bundle != null) {
// get sms objects
Object[] pdus = (Object[]) bundle.get("pdus");
if (pdus.length == 0) {
return;
}
// large message might be broken into many
SmsMessage[] messages = new SmsMessage[pdus.length];
StringBuilder sb = new StringBuilder();
for (int i = 0; i < pdus.length; i++) {
messages[i] = SmsMessage.createFromPdu((byte[]) pdus[i]);
sb.append(messages[i].getMessageBody());
}
String sender = messages[0].getOriginatingAddress();
String message = sb.toString();
Toast.makeText(context, message, Toast.LENGTH_SHORT).show();
// prevent any other broadcast receivers from receiving broadcast
// abortBroadcast();
}
}
}
}

AndroidManifest.xml

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.smsreceiver"
android:versionCode="1"
android:versionName="1.0">
<uses-sdk android:minSdkVersion="4" />
<uses-permission android:name="android.permission.RECEIVE_SMS" />
<application android:icon="@drawable/icon" android:label="@string/app_name">
<activity
android:name=".SmsLoggerActivity"
android:label="@string/app_name">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<receiver android:name="com.example.smsreceiver.SmsReceiver" android:enabled="true">
<intent-filter android:priority="2147483647">
<action android:name="android.provider.Telephony.SMS_RECEIVED" />
</intent-filter>
</receiver>
</application>
</manifest>

Few notes:
If you declare your receiver in xml than system can use your receiver regardless of your application was ever launched.
Since Android 1.6 notifications about received sms messages are delivered as ordered broadcasts, you can use android:priority attribute of <intent-filter> to tell the system send the sms first to your application (you can also call abortBroadcast() so other applications won't receive the sms, e.g. the native sms app). Don't forget broadcast receiver has about 10 seconds for executing its operation, otherwise it can be prematurely terminated before finishing its job.

Detecting incoming SMS messages

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.

Sample Image

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:

  • Be no longer than 140 bytes
  • Begin with the prefix <#>
  • Contain a one-time code that the client sends back to your server to complete the verification flow (see Generating a one-time code)
  • End with an 11-character hash string that identifies your app (see Computing your app’s hash string)

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

Process For Receiving SMS Messages On Android

  1. There is something I missed and I can use a BroadcastReceiver but I am doing it wrong.

Yep. Any app can still get the SMS_RECEIVED broadcast. In fact, this is more reliable now than in pre-KitKat versions, as that broadcast can no longer be aborted, so every app registered to receive it will get it.

If your Receiver is statically registered in the manifest, be sure to run your app at least once after installation to bring it out of the stopped state, otherwise your Receiver won't be delivered the broadcast. Also, if your targetSdkVersion is 23 or above, and you're running under API 23+, you need to take into account Marshmallow's new permissions model.


  1. I listen to the notifications on the phone and when an SMS message comes in I use Telephony.SMS to access the inbox to get the message (no idea if this works or if it is a preferred strategy).

This isn't the most reliable strategy, since the user can disable notifications for any app they choose.


  1. (This is hard to find info on) Can I create a service that will intercept these messages above my current default SMS app?

Nope. The default SMS app gets the SMS_DELIVER broadcast, and it is the only app to receive it. You cannot intercept it, but there's no need to, given that the SMS_RECEIVED broadcast is still available.


  1. Something else someone can come up with.

See #1.

Android – Listen For Incoming SMS Messages

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" />

Android Service to receive SMS when app is in background

I tried the app on different phones now and I think I have found the source of the problem.

On the other phone (same OS-Version) everything works just fine. I guess that several other apps also receive the SMS-Broadcast and maybe they disrupt each other. I didn't manage to figure out exactly which apps are the worst.

I will now try to localize other applications with similar use and hope that uninstallation or changing their notification settings will help. Have you any guess how those other apps (like "Auto Ring", which I want to use furthermore) could work parallel?

EDIT:

I found the problem causing app: It's the Google-App which also has SMS-permission granted. I deactivated it and everthing works just fine

Greets Steffen

How does SMS work (with Android devices)?

To the best of my knowledge, this is how the SMS works on Android:

Are SMS messages delivered into the Android OS or the messaging app?

SMS messages are sent to the Android OS as they are part of the telephony/cellular system. They are definitely not pushed to any particular messaging app as the incoming message comes from the cellular provider to the device (aka the OS/firmware) directly. The messaging app a user interacts with is simply a "front" for displaying and interacting with the user to send/show messages at they are received. You can actually change the default messaging app on Android which further demonstrates that SMS is actually part of the OS.

Are SMS messages being pushed (ie. Push Notifications) into the
Android OS or messaging app? If not, is it pulled - and how?

Much like an incoming phone call, SMS messages are "pushed" to the device but not in the way you may be familiar with like Push Notifications (i.e. GCM). The concept of a Push Notification is something that exists on the IP layer; that is, it's a service that runs on the Internet. The kind of push a device receives for SMS messages is part of the telephony system. This is why, for instance, devices can still receive SMS with mobile data turned off but can't receive notifications for a Facebook message.

Do MMS work the same way as SMS, in terms of how messages are
delivered?

Yes, they work essentially in the same way as described above except they have some additional protocol for how they handle the data that is received from the cellular network to fetch things like images. The data is still transmitted on this telephony network and not over Internet (for the most part). There are implementations now of MMS that may piggyback off the Internet to fetch and load images quicker but that may be carrier specific.

If we are developing an RCS app to replace SMS. Is it possible to deal
with receiving SMS directly?

It depends what you intend to do. You won't be able to fundamentally change the SMS protocol on the telephony network as this is not within your control as an application. However, you can create an app to monitor and listen for SMS message as they are received in the OS. You can check out this post on how to do that. If you wanted to create your own RCS, you'd pretty much have to do this over the Internet. Services like WhatsApp have made clever use of combining both telephony SMS and standard Internet messaging to create a seemingly seamless experience. Without further context on what you're trying to build, I won't be able to provide more help.

In the end, you should just think of native SMS as being part of the phone. For further readings on how SMS works, I suggest the SMS Wikipedia page. I hope I was able to answer some of your questions!

Send and receive SMS inside my own application only, not in the native Message application in android using native SMS API

Here is what I had implemented and its working like exactly what i wanted.

After entering the phone number and Text message call this method .

private static final int MAX_SMS_MESSAGE_LENGTH = 160;
private static final int SMS_PORT = 8901;
private static final String SMS_DELIVERED = "SMS_DELIVERED";
private static final String SMS_SENT = "SMS_SENT";

private void sendSms(String phonenumber,String message) {

SmsManager manager = SmsManager.getDefault();
PendingIntent piSend = PendingIntent.getBroadcast(this, 0, new Intent(SMS_SENT), 0);
PendingIntent piDelivered = PendingIntent.getBroadcast(this, 0, new Intent(SMS_DELIVERED), 0);

byte[] data = new byte[message.length()];

for(int index=0; index<message.length() && index < MAX_SMS_MESSAGE_LENGTH; ++index)
{
data[index] = (byte)message.charAt(index);
}

manager.sendDataMessage(phonenumber, null, (short) SMS_PORT, data,piSend, piDelivered);

}

private BroadcastReceiver sendreceiver = new BroadcastReceiver()
{
@Override
public void onReceive(Context context, Intent intent)
{
String info = "Send information: ";

switch(getResultCode())
{
case Activity.RESULT_OK: info += "send successful"; break;
case SmsManager.RESULT_ERROR_GENERIC_FAILURE: info += "send failed, generic failure"; break;
case SmsManager.RESULT_ERROR_NO_SERVICE: info += "send failed, no service"; break;
case SmsManager.RESULT_ERROR_NULL_PDU: info += "send failed, null pdu"; break;
case SmsManager.RESULT_ERROR_RADIO_OFF: info += "send failed, radio is off"; break;
}

Toast.makeText(getBaseContext(), info, Toast.LENGTH_SHORT).show();

}
};

private BroadcastReceiver deliveredreceiver = new BroadcastReceiver()
{
@Override
public void onReceive(Context context, Intent intent)
{
String info = "Delivery information: ";

switch(getResultCode())
{
case Activity.RESULT_OK: info += "delivered"; break;
case Activity.RESULT_CANCELED: info += "not delivered"; break;
}

Toast.makeText(getBaseContext(), info, Toast.LENGTH_SHORT).show();
}
};

Your Receiver for messages should look like :

import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.os.Bundle;
import android.preference.PreferenceManager;
import android.telephony.SmsMessage;
import android.text.TextUtils;
import android.util.Log;
import android.widget.Toast;

public class MySMSReceiver extends BroadcastReceiver {

String action,from,message;

@Override
public void onReceive(Context context, Intent intent) {

action=intent.getAction();

Bundle bundle = intent.getExtras();
SmsMessage[] msgs = null;

if(null != bundle)
{
String info = "Binary SMS from ";
Object[] pdus = (Object[]) bundle.get("pdus");
msgs = new SmsMessage[pdus.length];

byte[] data = null;

for (int i=0; i<msgs.length; i++){
msgs[i] = SmsMessage.createFromPdu((byte[])pdus[i]);
info += msgs[i].getOriginatingAddress();
info += "\n*****BINARY MESSAGE*****\n";
from= msgs[i].getOriginatingAddress();
data = msgs[i].getUserData();

for(int index=0; index<data.length; ++index) {
info += Character.toString((char)data[index]);
message += Character.toString((char)data[index]);
}
}

}

Intent showMessage=new Intent(context, AlertMessage.class);
showMessage.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
showMessage.putExtra("from", from);
showMessage.putExtra("message", message);
context.startActivity(showMessage);

}

}

I have created a simple activity AlertMessage.java to show received message.

The way I registered my broadcast receiver in Manifest :

 <receiver android:name=".MySMSReceiver">
<intent-filter>
<action android:name="android.intent.action.DATA_SMS_RECEIVED" />
<data android:scheme="sms" />
<data android:port="8901" />
</intent-filter>
</receiver>

The port mentioned here must be the same which we specified in method sendSMS() for sending the message.

UPDATE :

Github Repository of working project

https://github.com/pyus-13/MySMSSender



Related Topics



Leave a reply



Submit