I have some logic that I need to execute when the user is getting any SMS. My Broadcast receiver is working fine when the app is running but if I am killing my then it is not working as expected.
Can someone help me with this? I have tried all the possible approaches which are present on the Internet but still nothing is working.
Thank you so much in advance.
My broadcast receiver class:
public class EveryDay_SMSListener extends BroadcastReceiver {
private SharedPreferences preferences;
String msgBody ="";
String msg_from;
EveryDay_DataBase mydb;
#Override
public void onReceive(Context context, Intent intent) {
Intent background = new Intent(context, EveryDay_Service.class);
context.startService(background);
if(intent.getAction().equals("android.provider.Telephony.SMS_RECEIVED")){
Bundle bundle = intent.getExtras();
SmsMessage[] msgs = null;
enter code here
if (bundle != null){
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]);
msgBody = msgBody+(msgs[i].getMessageBody()).toString();
msg_from = msgs[i].getOriginatingAddress();
}
}catch(Exception e){
}
}
}
}
}
In recent versions of Android, system does not launch app when app is dead
and broadcast gets emitted (for most of broadcast flags).
An approach for making this work you should use Foreground Services, some links that may help you implement it:
https://androidwave.com/foreground-service-android-example/
https://medium.com/#ramkumarv3/foreground-service-with-notification-channel-ac6697c8a6d1
https://developer.android.com/guide/components/services
Take care, some manufacturers even close these services for battery optimizations like Huawei, Samsung,... . So you should add brand specific codes to make user disallow battery optimization and automatic close of services for your app.
You said registering receiver in manifest did not work for you thus the only remaining option is:
create a foreground service and register SMS receiver in onCreate of
it (do NOT register sms receiver in manifest).
create a jobscheduler that starts your service every (n) seconds
it is the last solution but if you wont be lucky between app kill and next jobscheduler work you may lost sms so you may want to make (n) small enough.
Better solution is to have read sms permission and every time your activity starts read all sms from android inbox and see if you lost any sms.
Related
I am developing an android app that rapidly sends multiple SMS messages almost simultaneously to the same number (Please note this is for research purposes). I need this app to also track it's delivery intent, which I do by adding a bundle containing the information I want the intent to carry. The problem I am running into, however, is as follows:
If I use no pending intent flag (aka 0) or FLAG_IMMUTABLE, the bundle value always remains the same as the first message sent and I get a dump of all the messages delivered.
If I use FLAG_UPDATE_CURRENT, I get a bundle value that updates sporadically/randomly (multiple intents contain the same bundle value), and I get a dump of all the messages delivered.
If I use FLAG_ONE_SHOT, I get the accurate bundle value assigned (the value as it is meant to be received) BUT I only get a dump of a very small amount of the intents, and that amount stays small(~1%) even if I happen to send over 1000 messages back-to-back.
Here is the portion of the message sending method responsible for sending the SMSs in MainActivity:
int z = 0;
for(int j=1; j<=messageCnt;j++){
try {
Intent dI = new Intent("SMS_DELIVERED");
Bundle b = new Bundle();
b.putString("MSGNUM",Integer.toString(z+1));
dI.putExtras(b);
dI.putExtra("MNUMSTR",Integer.toString(z+1));
PendingIntent deliveredIntent = PendingIntent.getBroadcast(getApplicationContext(),0,dI,PendingIntent.FLAG_ONE_SHOT);
deliveredPendingIntents.add(z,deliveredIntent);
SmsManager.getDefault().sendTextMessage(phoneNum, null, message[z], null, deliveredIntent);
b.clear();
}
catch (Exception e){
StringWriter sw = new StringWriter();
PrintWriter pw = new PrintWriter(sw);
e.printStackTrace(pw);
crashTrack.add((Long.toString(System.currentTimeMillis())).concat(",").concat("'").concat(sw.toString()).concat("'\n"));
//reset all incremental value back one for proper repeat
j=j-1;
continue; //jump back to for loop and reattempt send.
}
}
Then, wherever I want (whether it be in a new app or in the current app), I put the following to register my receiver class.
//Create Broadcast receivers for sent and delivered intents
SmsDeliveredReceiver SDR = new SmsDeliveredReceiver();
//register the receivers
registerReceiver(SDR, new IntentFilter("SMS_DELIVERED"));
And finally, my broadcast reciever class for the intent, SmsDeliveredReciever:
public class SmsDeliveredReceiver extends BroadcastReceiver {
protected static int sentCount = 0;
#Override
public void onReceive(Context context, Intent dI){
/*File delivDumpName = new File(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS),MainActivity.dateTime.concat("DelivDump.txt"));
File delivfailDumpName = new File(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS),MainActivity.dateTime.concat("DelivFailDump.txt"));*/
Bundle bundle = dI.getExtras();
switch (getResultCode()){
case Activity.RESULT_OK:
if(bundle!=null){
String msgNum = bundle.getString("MSGNUM","0");
MainActivity.delvDump.add(Long.toString(System.currentTimeMillis()).concat(",'Msg ").concat(msgNum).concat("'\n"));
} else {
MainActivity.delvDump.add(Long.toString(System.currentTimeMillis()).concat(",'Msg ").concat("Number Unknown'\n"));//Integer.toString(sentCount)).concat("'\n"));
}
break;
case Activity.RESULT_CANCELED:
if(bundle!=null){
String msgNum = bundle.getString("MSGNUM","0");
MainActivity.delvDump.add(Long.toString(System.currentTimeMillis()).concat(",'Sms Failed to Deliver.',' Msg ").concat(msgNum).concat("'\n"));
} else {
MainActivity.delvDump.add(Long.toString(System.currentTimeMillis()).concat(",'Sms Failed to Deliver.',' Msg ").concat("Number Unknown'\n"));//Integer.toString(sentCount)).concat("'\n"));
}
break;
}
/*MainActivity.writeFiles(delivDumpName,String.valueOf(MainActivity.delvDump));
MainActivity.writeFiles(delivfailDumpName, String.valueOf(MainActivity.delvFailDump));*/
sentCount++;
}
}
Note that the exact same thing happens for the SendIntent. I've read all of the Google Android SDK Docs, and stackoverflow so far has provided answers that only work for alerts/alarms and not SMS intents, where the only existing tutorials and forum entries for SMS are single-send SMS. And no, I am not trying to send a multi-part message.
Desired output:
All SMS delivery intents should be captured in the broadcast receiver
Each intent captured must output its own unique message association number via the bundle.
The amount of intents captured should equal the number of messages successfully sent (yes, I do have a way to check whether the message got received or not)
Is this desired output even possible, or am I asking too much out of Android?
Whether the receiver is a separate app or not does not matter to me, though it is preferred if its a separate app.
Note: Everything in the app is functional and error-free except accurately tracking/logging the sent/delivered intents.
I'm trying to intercept the received SMSs by using a broadcast receiver. Here is the code:
<receiver android:name=".receivers.SmsReceiver" android:enabled="true"
android:exported="true" android:priority="999">
<intent-filter>
<action android:name="android.provider.Telephony.SMS_RECEIVED" />
</intent-filter>
</receiver>
and:
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 (SMS_RECEIVED.equals(intent.getAction())) {
this.abortBroadcast();
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();
Log.d("sms", sender);
Log.d("sms", message);
}
}
}
}
The SMS is intercepted fine, but the stock Android SMS app is still showing its notifications and I can also find the message inside the stock app sms list.
Is there any way to stop the stock SMS app notifications and to avoid the message from appearing inside its list?
You need to call abortBroadcast();, see my answer to communication between two device using sms
If you are running Android 4.4 KitKat, it seems to be more difficult to do these sorts of things and have not looked into it yet myself.
As you said you are running KitKat, then answer is - you cannot mute default SMS app. You can also receive messages or send (that's why you get messages), but still, you cannot "consume" the message.
If user has set your application as default SMS app, then he/she is not going to get an SMS notification. You have to handle the notification, as well as other feature of the SMS in your app.
For more information read this blog.
Check out other blog and sample app.
I'm developing a business SMS application. In this app, if an incoming message is from a particular number, say 999999999, it should go to the application's inbox and not to the default native inbox. All other messages should go to the phone's native inbox. How do I do this?
When SMS is received by the Android system, it broadcasts an ordered broadcast Intent with action "android.provider.Telephony.SMS_RECEIVED". All registered receivers, including the system default SMS application, receive this Intent in order of priority that was set in their intent-filter. The order for broadcast receirers with the same priority is unspecified. Any BroadcastReceiver could prevent any other registered broadcast receivers from receiving the broadcast using abortBroadcast().
So, everything you need is broadcast receiver like this:
public class SmsFilter extends BroadcastReceiver {
#Override
public void onReceive(Context context, Intent intent) {
if (intent.getAction().equals("android.provider.Telephony.SMS_RECEIVED")) {
Bundle extras = intent.getExtras();
if (extras != null) {
Object[] pdus = (Object[])extras.get("pdus");
if (pdus.length < 1) return; // Invalid SMS. Not sure that it's possible.
StringBuilder sb = new StringBuilder();
String sender = null;
for (int i = 0; i < pdus.length; i++) {
SmsMessage message = SmsMessage.createFromPdu((byte[]) pdus[i]);
if (sender == null) sender = message.getOriginatingAddress();
String text = message.getMessageBody();
if (text != null) sb.append(text);
}
if (sender != null && sender.equals("999999999")) {
// Process our sms...
abortBroadcast();
}
return;
}
}
// ...
}
}
Looks like the system default SMS processing application uses priority of 0, so you could try 1 for your application to be before it. Add these lines to your AndroidManifest.xml:
<receiver android:name=".SmsFilter">
<intent-filter android:priority="1">
<action android:name="android.provider.Telephony.SMS_RECEIVED" />
</intent-filter>
</receiver>
Don't forget about necessary permissions:
<uses-permission android:name="android.permission.RECEIVE_SMS"/>
By the way, you can find all registered receivers and their priorities using this code:
Intent smsRecvIntent = new Intent("android.provider.Telephony.SMS_RECEIVED");
List<ResolveInfo> infos = context.getPackageManager().queryBroadcastReceivers(smsRecvIntent, 0);
for (ResolveInfo info : infos) {
System.out.println("Receiver: " + info.activityInfo.name + ", priority=" + info.priority);
}
Update: As FantasticJamieBurn said below, starting from Android 4.4 the only app that can intercept SMS (and block if it wish) is the default SMS app (selected by user). All other apps can only listen for incoming SMS if default SMS app not blocked it.
See also SMS Provider in the Android 4.4 APIs.
With the release of Android 4.4 KitKat (API level 19), the option to block an SMS message and prevent it from being delivered to the default SMS app has been removed. Non-default SMS app's may observe SMS messages as they are received, but any attempt to abort the broadcast will be ignored by Android 4.4+.
If you have an existing app which relies on aborting SMS message broadcasts then you may want to consider the impact this change in behaviour will have when your users upgrade to Android 4.4+.
http://android-developers.blogspot.co.uk/2013/10/getting-your-sms-apps-ready-for-kitkat.html
Yes it can be DOne
public void onReceive(Context context, Intent intent)
{
Bundle bundle=intent.getExtras();
Object[] messages=(Object[])bundle.get("pdus");
SmsMessage[] sms=new SmsMessage[messages.length];
Toast.makeText(context, "Hello", 1).show();
for(int n=0;n<messages.length;n++){
sms[n]=SmsMessage.createFromPdu((byte[]) messages[n]);
}
for(SmsMessage msg:sms){
if(msg.getOriginatingAddress().endsWith(number))
{
SMS.updateMessageBox("\nFrom: "+msg.getOriginatingAddress()+"\n"+
"Message: "+msg.getMessageBody()+"\n");
/*((SMS) context).delete();*/
abortBroadcast();
}
}
}
just use abortbroadcast() after receiving in app
Are you the one sending the messages? If so consider using datasms instead as they will not show up in the inbox.
Check this question for more info on how to use it
Check the sender number is equal to the mobile number of your sms sending phone.
replace the following code line of Mr "praetorian droid"
if (sender != null && sender.equals("999999999")) {
to
if (sender != null && sender.equals("YOUR SMS SENDING MOBILE NUMBER HERE")) {
further more you can give a setting to user to manually add sms sending number if he want to change it.
I will try to sum up simply because my original code is long. Suppose that My broadcast receiver is like this :
public class SMSBroadcastReceiver extends BroadcastReceiver {
static ArrayList<String> phonenumber_array = new ArrayList<String>();
static ArrayList<String> message_array = new ArrayList<String>();
#Override
public void onReceive(Context context, Intent intent) {
Bundle bundle = intent.getExtras();
if (bundle != null) {
Object[] pdus = (Object[]) bundle.get("pdus");
SmsMessage[] sms = new SmsMessage[pdus.length];
for (int i = 0; i < sms.length; i++) {
sms[i] = SmsMessage.createFromPdu((byte[]) pdus[i]);
phonenumber_array.add(0,sms[i].getOriginatingAddress());
message_array.add(0,sms[i].getMessageBody());
com.test.ListActivityClass.myadapter.notifyDataSetChanged();
}
}
In my AndroidManifest.xml, The BroadcastReceiver has the IntentFilter "android.provider.Telephony.SMS_RECEIVED".
My ListActivityClass extends ListActivity, contains a myadapter which is an ArrayAdapter, like : myadapter = new CustomAdapter (this,com.test.SMSBroadcastReceiver.phonenumber_array,com.test.SMSBroadcastReceiver.message_array)
So my problem is : I send an sms from eclipse to emulator. whenever myadapter.notifyDataSetChanged(); is called in my BroadcastReceiver when my application is not running(completely stoped, no process launched, no activities) , I get a nullpointer exception.
When I remove myadapter.notifyDataSetChanged(); the problem is gone.
When the app is running, there is no problem as well, my application gets correctly the new sms received and displays it in the list.
So my guess was = When the app is running, the BroadcastReceiver can detect the myadapter, because the app is in memory, so it can notify changes. when the app isn't running, it is not in memory so BroadcastReceiver can't see any myadapter, so it gives a nullpointer exception. it makes sense.
So I tried to check : if the process is in memory, notify changes, else, don't notify ( because my applications gets all the sms received onCreate(), so it is not necessary.
ActivityManager activityManager = (ActivityManager) context
.getSystemService(context.ACTIVITY_SERVICE);
List<RunningAppProcessInfo> runningProcInfo = activityManager
.getRunningAppProcesses(); for (int i1 = 0; i1 <
runningProcInfo.size(); i1++) {
if (runningProcInfo.get(i1).processName.equals("com.test")) {
com.test.ListActivityClass.myadapter.notifyDataSetChanged();
}
But nothing changed. I still have a nullpointer exception when app is not running.
The worst thing is, I tried to replace phonenumber_array by phonenumber_array.add(0,"somephonenumber"); and message_array.add(0,"somemessage"); to check if a BroadcastReceiver runs in the backround even if the app is closed, my thoughts were correct. After the null pointer exception, I launched the app, and somephonenumber and somemessage were both present.
What do I miss? Does a BroadcastReceiver runs in background, or only when application launches? what is wrong?
First, static data members (and the comment-suggested replacement, Application) only live as long as your process lives. Your process may go away microseconds after onReceive() completes.
If you want to use static data members as a cache, that's fine, but durable data needs to live on the filesystem (database, flat file, etc.).
Second, myadapter should not be a static data member. You are leaking memory.
Either:
Move this BroadcastReceiver to be registered by the activity via registerReceiver(), or
Have this BroadcastReceiver send another ordered broadcast to be picked up by the running activity (if it is in the foreground), as is described in this blog post and is demonstrated in this sample project
You can create static boolean variable onScreen and set it to true in onResume method, and set to false in onPause method. It is the easiest way to understand if application is on the screen
Is it possible for my application to receive an SMS from a specific phone number, without letting it trig a system notification, but let all other messages pass to the default SMS application to be treated normally?
If so, how can the system know which process is first on queue to pick which messages to receive?
I didn't try this, but in theory it should work:
In Android SMS broadcast is sent as ordered broadcast, which means that receivers are handled in order and can cancel the broadcast. See SMSDispatcher.java, line 420.
In order to be called first, a receiver must have a higher priority then others.
<intent-filter android:priority="1000" >
. . .
</intent-filter>
To cancel a broadcast call broadcastReceiver.setResultCode(RESULT_CANCELED). That way a SMS broadcast will be cancelled and will not be shown by system SMS app (and SMS notifier).
Update:
Also try using broadcastReceiver.setResultCode(Intents.RESULT_SMS_HANDLED).
Update 2:
user672601 noted in another answer that this indeed works, but he used abortBroadcast() inside broadcast receiver.
I second farhan its not possible for number of reason. Anybody can do anything with such allowance. Check this out for details http://groups.google.com/group/android-developers/browse_thread/thread/78fecbc156f4a1ea
Peter Knego's answer is correct. I was trying to do this exact thing, tried his solution, and it indeed works except for I used:
this.abortBroadcast();
inside the broadcastReceiver.
public class NotifyServiceReceiver extends BroadcastReceiver{
static final String ACTION ="android.provider.Telephony.SMS_RECEIVED";
#Override
public void onReceive(Context arg0, Intent arg1) {
// TODO Auto-generated method stub
if(arg1.getAction().equalsIgnoreCase(ACTION))
{
Bundle extras = arg1.getExtras();
String strMessage = "private message";
if ( extras != null )
{
Object[] smsextras = (Object[]) extras.get( "pdus" );
for ( int i = 0; i < smsextras.length; i++ )
{
SmsMessage smsmsg = SmsMessage.createFromPdu((byte[])smsextras[i]);
String strMsgBody = smsmsg.getMessageBody().toString();
String strMsgSrc = smsmsg.getOriginatingAddress();
//Toast.makeText(GasService.this,strMessage, Toast.LENGTH_SHORT).show();
if(strMsgSrc.equals("+919XXXXXXXXX"))
{
strMessage += "SMS from " + strMsgSrc + " : " + strMsgBody;
Toast.makeText(PrivatesmsService.this,strMessage, Toast.LENGTH_SHORT).show();
abortBroadcast();
}
}
}
}
}
}
you can tell to the system by setting the priority of the activity to 100 or greater then that in manifest file so that when ever you receive sms then your application will access it and by calling abortBroadcast() it will prevent sms reaching to inbox or any other application which has set BroadcastReceiver to receive sms
I dont think its possible.... because android gives us broadcast Listener which only listen the event. so you have to read every message and check the number if its yours, do an operation else just ignore it.... the default messaging application will automatically handle it....