I am trying to listen for any incoming SMS messages on an Android device and then showing a toast in my Flutter app whenever an SMS is received. I am connecting to Flutter through EventChannel and detecting SMS using a BroadcastReceiver. How do I send an events.success(message) whenever my broadcast receiver detects an SMS?
I tried adding the BroadcastReceiver directly inside the EventChannel but that did not work. The flutter SMS package also doesn't seem to work.
This is what my MainActivity looks like:
public class MainActivity extends FlutterActivity{
public static final String STREAM = "com.myapp.thisapp/stream";
public static final String TAG = "THIS IS A MESSAGE: ";
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
GeneratedPluginRegistrant.registerWith(this);
new EventChannel(getFlutterView(), STREAM).setStreamHandler(
new EventChannel.StreamHandler() {
#Override
public void onListen(Object args, final
EventChannel.EventSink events) {
//Send events.success() when SMS received
Log.w(TAG, "adding listener");
}
#Override
public void onCancel(Object args) {
Log.w(TAG, "cancelling listener");
}
}
);
}
}
And this is the code for my BroadcastReceiver:
public class IncomingSmsBroadcastReceiver extends BroadcastReceiver {
private static final String SMS_RECEIVED = "android.provider.Telephony.SMS_RECEIVED";
#Override
public void onReceive(final Context context, final Intent intent) {
if (intent != null && SMS_RECEIVED.equals(intent.getAction())) {
final SmsMessage smsMessage = extractSmsMessage(intent);
processMessage(context, smsMessage);
}
}
private SmsMessage extractSmsMessage(final Intent intent) {
final Bundle pudsBundle = intent.getExtras();
final Object[] pdus = (Object[]) pudsBundle.get("pdus");
final SmsMessage smsMessage = SmsMessage.createFromPdu((byte[]) pdus[0]);
return smsMessage;
}
private void processMessage(final Context context, final SmsMessage smsMessage) {
//TODO: Send message to event channel
}
}
Whenever the BroadCastReceiver encounters an SMS, I want the content of the message to be sent to the EventChannel which will send the message text to the Flutter front-end. How do I do this?
Based on your first comment Laksh22 YES it can be possible.
Create a constructor for your 'Activity' like this :
_yourclassnameState() {
platform.setMethodCallHandler(JavaMethodHandler);
}
and then implement a handler for response
Future<dynamic> JavaMethodHandler(MethodCall methodcall) async
{
switch(methodcall.method)
{
case 'SMSRecived':
print('DataRecived is' + methodcall.arguments);
break;
default:
break;
}
}
then in your 'BroadcastReceiver' use this code to call a flutter method :
MethodChannel channel =new MethodChannel(view,CHANNEL);
channel.invokeMethod("SMSRecived",args, new MethodChannel.Result() {
#Override
public void success(Object o) {
System.out.println(o);
}
#Override
public void error(String s, String s1, Object o) {
}
#Override
public void notImplemented() {
}
});
Don't forget about CHANNEL. It should be the same both side.
Don't forget about manifest file.
For receiving SMS while your app is closed use 'ForegroundService'.Unfortunately Flutter dosen't support ForegroundService yet. you should implement it in Java.
This is what you should be using in MainActivity, store the Result
public class MainActivity extends FlutterActivity{
public static final String STREAM = "com.myapp.thisapp/stream";
public static final String TAG = "THIS IS A MESSAGE: ";
public Result resultLater;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
GeneratedPluginRegistrant.registerWith(this);
new MethodChannel(getFlutterView(), CHANNEL).setMethodCallHandler(
new MethodCallHandler() {
#Override
public void onMethodCall(MethodCall call, Result result) {
//store the reference for later access
resultLater = result;
}
});
}
public class IncomingSmsBroadcastReceiver extends BroadcastReceiver {
private static final String SMS_RECEIVED = "android.provider.Telephony.SMS_RECEIVED";
#Override
public void onReceive(final Context context, final Intent intent) {
if (intent != null && SMS_RECEIVED.equals(intent.getAction())) {
final SmsMessage smsMessage = extractSmsMessage(intent);
processMessage(context, smsMessage);
}
}
private SmsMessage extractSmsMessage(final Intent intent) {
final Bundle pudsBundle = intent.getExtras();
final Object[] pdus = (Object[]) pudsBundle.get("pdus");
final SmsMessage smsMessage = SmsMessage.createFromPdu((byte[]) pdus[0]);
return smsMessage;
}
}
private void processMessage(final Context context, final SmsMessage smsMessage) {
//here send back result, like this
if(smsMessage.getMessageBody()!=null){
result.success(smsMessage.getMessageBody());
}else{
result.error("Error", "Sms not found", null);
}
}
}
now make the sms broadcast sub class in your main activity and declare below. and access the result when you get the sms. And from your flutter side just make channel call as usual.
Follow the reference for docs sample.
Register your broadcast receiver in EventChannel's onListen method
override fun onListen(arguments: Any?, eventSink: EventSink) {
val receiver = IncomingSmsBroadcastReceiver()
receiver.setListener(object : SmsReceiveListener() {
override fun onSmsReceive(sms: String?) {
eventSink.success(sms)
}
})
val filter = IntentFilter(IncomingSmsBroadcastReceiver.SMS_RECEIVED)
context.registerReceiver(receiver, filter)
}
A SmsReceiveListener is registered to IncomingSmsBroadcastReceiver to listen to the received message.
For more detail check the below URL:-
https://medium.com/cashify-engineering/event-channel-to-listen-to-broadcast-events-from-android-43a813672896
Related
I am using firebase to receive messages. When message received (onMessageReceived()). I want to check whether the message contains required string (say "hello"). If yes, i want it to be known to another fragment, so that some method in fragment will be called based on message.
For this i want to create event listener when message received.
In my fragment, two APIs will be called. on API will be called onCreatedView().
based on message from firebase , second API should be called.
The two API calls should be done within fraction of milliseconds.
MyFireBaseMessage.java
public class MyMessagingService extends FirebaseMessagingService {
#Override
public void onMessageReceived(RemoteMessage remoteMessage) {
super.onMessageReceived(remoteMessage);
Log.d("message here","message here "+ remoteMessage.getData().toString());
String value = remoteMessage.getData().get("msg");
Log.d("value here","value here"+" "+value);
if (value.equalsIgnoreCase("hello"))
{
//implement custom event listener here
}
}
}
One way I can think of right now is using LocalBroadcast. to do this
Amend your Firebase service and put this content
public class MyMessagingService extends FirebaseMessagingService {
private LocalBroadcastManager broadcaster;
#Override
public void onCreate() {
broadcaster = LocalBroadcastManager.getInstance(this);
}
#Override
public void onMessageReceived(RemoteMessage remoteMessage) {
super.onMessageReceived(remoteMessage);
Log.d("message here", "message here " + remoteMessage.getData().toString());
String value = remoteMessage.getData().get("msg");
Log.d("value here", "value here" + " " + value);
if (value.equalsIgnoreCase("hello")) {
Intent intent = new Intent("custom-listener");
broadcaster.sendBroadcast(intent)
}
}
}
Make changes in your fragment class
public class BlankFragment extends Fragment {
Context context;
BroadcastReceiver br;
public BlankFragment() {
// Required empty public constructor
}
#Override
public void onAttach(Context context) {
super.onAttach(context);
this.context = context;
}
#Override
public void onResume() {
super.onResume();
setup();
}
#Override
public void onDestroyView() {
super.onDestroyView();
LocalBroadcastManager.getInstance(context).unregisterReceiver(mMessageReceiver);
}
private void setup() {
LocalBroadcastManager.getInstance(context).registerReceiver(mMessageReceiver,
new IntentFilter("custom-listener"));
}
private BroadcastReceiver mMessageReceiver = new BroadcastReceiver() {
#Override
public void onReceive(Context context, Intent intent) {
// Get extra data included in the Intent
String message = intent.getStringExtra("message");
Log.d("receiver", "Got message: " + message);
}
};
}
with broadcasts you will be able to listen to the event you want to. The onReceive method inside the Fragment class will get fired when your if condition is true i.e. value.equalsIgnoreCase("hello")
Apologies for the formatting of this answer. having difficulties adjusting it.
Please try this, if hello is key in message format
if (remoteMessage.getData().containsKey("hello")) {
}
Or if hello is as message string, then try this
Map<String, String> params = remoteMessage.getData();
JSONObject fullObject = new JSONObject(params);
String messageString = fullObject.getString("msg");
Hope it works!!
For this use the Local broadcast receiver in your app
example:
in notification screen use the receiver like this
#Override
public void onMessageReceived(RemoteMessage remoteMessage)
{
super.onMessageReceived(remoteMessage);
Log.d("message here", "message here " + remoteMessage.getData().toString());
String value = remoteMessage.getData().get("msg");
Log.d("value here", "value here" + " " + value);
if (value.equalsIgnoreCase("hello"))
{
Intent intent = new Intent("custom-listener");
broadcaster.sendBroadcast(intent)
}
}
and in fragment register broadcast receiver in onResume method like this
getActivity().registerReceiver(broadcastReceiver, new IntentFilter("custom-listener"));
also unregister the receiver in onPause nethod
getActivity().unregisterReceiver(broadcastReceiver);
and create broadcast receiver method in fragment where you can call the api , like this
private BroadcastReceiver broadcastReceiver = new BroadcastReceiver()
{
#Override
public void onReceive(Context context, Intent intent)
};
It's the first time that I'm using this library, but I was following this video tutorial to send data through Fragments, but in my case, it's just Activities.. So this how I did
Activity that I'm sending data :
public void onClick(View view) {
String passing_data = new Gson().toJson(user);
BusStation.getBus().post(new MessageData(passing_data));
Intent intent = new Intent(activity,UserAdsView.class);
activity.startActivity(intent);
}
BusStation Class :
public class BusStation {
private static Bus bus = new Bus();
public static Bus getBus() {
return bus;
}
}
MessageData Class :
public class MessageData {
private String msgData;
public MessageData(String msgData) {
this.msgData = msgData;
}
public String getMsgData() {
return msgData;
}
}
And finally at the UserAdsView Activity :
#Override
protected void onResume() {
super.onResume();
BusStation.getBus().register(this);
}
#Override
protected void onPause() {
super.onPause();
BusStation.getBus().unregister(this);
}
#Subscribe
public void recievedData(MessageData messageData){
target = messageData.getMsgData();
Toast.makeText(getApplicationContext(), target, Toast.LENGTH_SHORT).show();
}
As was mentioned on video, this method recievedData should be fired!
When you send notification in first activity at that time, UserAdsView Activity is not registered hence there are no listeners for events.
At this line
BusStation.getBus().post(new MessageData(passing_data));
you are sending notification but there is nothing registered to receive this notification. i.e. UserAdsView Activity has not started yet.
If you need to pass data to activity at launch time, simply send it via
Intent.
add in file Gradle
dependencies {
compile 'com.squareup:otto:1.3.8'
}
Create class OttoBus
public class OttoBus {
private static Bus sBus;
public static Bus getBus() {
if (sBus == null)
sBus = new Bus();
return sBus;
}
}
Create Events Class when pass data in android
public class Events {
public static class FragmentActivityMessage {
private String message;
public FragmentActivityMessage(String message) {
this.message = message;
}
public String getMessage() {
return message;
}
}
public static class ActivityFragmentMessage {
private String message;
public ActivityFragmentMessage(String message) {
this.message = message;
}
public String getMessage() {
return message;
}
}
}
function pass data
public void sendMessageToFragment(View view) {
EditText etMessage = findViewById(R.id.activityData);
OttoBus.getBus().post(String.valueOf(etMessage.getText()));
}
function event getdata
#Subscribe
public void getMessage(Events.ActivityFragmentMessage message) {
TextView messageView = findViewById(R.id.message);
messageView.setText(message.getMessage());
}
You need to make your MessageData object parcelable.
Then in your onClick() :
public void onClick(View view) {
String passing_data = new Gson().toJson(user);
Bundle extras = new Bundle();
extras.putParcelable("key",new MessageData(passing_data));
Intent intent = new Intent(activity,UserAdsView.class);
intent.putExtras(extras)
activity.startActivity(intent);
}
Then in onCreate() of your UserAdsView Activity :
MessageData data = (MessageData)getIntent().getExtras().getParcelable("key");
I'm trying to pass the data from the brodcast receiver to the activity to display it on the view using an interface, but, it's not working.Here are my files,MainActivity.java
public class MainActivity extends AppCompatActivity implements OtpPassing {
private TextView textView;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
#Override
public void onReceive(String msg) {
Toast.makeText(this, "o" + msg, Toast.LENGTH_SHORT).show();
textView = (TextView) findViewById(R.id.textView);
textView.setText("OTP: " + msg);
}
}
OtpPassing.java
public interface OtpPassing {
public void onReceive(String msg);
}
Receiver.java
public class Receiver extends BroadcastReceiver {
// Get the object of SmsManager
final SmsManager sms = SmsManager.getDefault();
public void onReceive(Context context, Intent intent) {
// Retrieves a map of extended data from the intent.
final Bundle bundle = intent.getExtras();
try {
if (bundle != null) {
final Object[] pdusObj = (Object[]) bundle.get("pdus");
for (int i = 0; i < pdusObj.length; i++) {
SmsMessage currentMessage = SmsMessage.createFromPdu((byte[]) pdusObj[i]);
String phoneNumber = currentMessage.getDisplayOriginatingAddress();
String senderNum = phoneNumber;
String message = currentMessage.getDisplayMessageBody();
Log.i("SmsReceiver", "senderNum: " + senderNum + "; message: " + message);
String otp = getOtp(message);
Toast.makeText(context, otp, Toast.LENGTH_LONG).show(); //This is working fine
OtpPassing otpPassing = new MainActivity();
otpPassing.onReceive(otp); //This is not working
} // end for loop
} // bundle is null
} catch (Exception e) {
Log.e("SmsReceiver", "Exception smsReceiver" +e);
}
}
private String getOtp(String message) {
String otp = null;
int index = message.indexOf("otp:");
if(index != -1){
int start = index + 5;
otp = message.substring(start, start+4);
}
return otp;
}
}
The receiver is woking(it's displaying the message on a Toast) but why is displaying the message on the screen via MainActivity not working?
You can create a broadcast receiver as a data member of your activity. You register for it in onCreate and unregister in onDestroy (or onStart and onStop).
That way the receiver has a reference to the parent activity and you can do whatever.
public class MainActivity extends AppCompatActivity implements OtpPassing {
private TextView textView;
BroadcastReceiver broadcastReceiver = new BroadcastReceiver() {
public void onReceive(Context context, Intent intent) {
//Access the parent activity as follows
MainActivity.this.textView.setText("...");
}
};
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
//instantiate and register the receiver
setContentView(R.layout.activity_main);
}
#Override
protected void onDestroy() {
// Unregister the receiver here
super.onDestroy();
}
Here better way (I suppose) would be to use Android Intents
in your receiver, something like this
Intent intent = new Intent(context,MainActivity.class);
intent.putExtra(.....);
startactivity(intent);
now in your MainActivity, override onNewIntent(). Here check for the 'extras' you had passed from the receiver.
Please note, this might result in having multiple MainActivities running in your phone if your "older" MainActivity was not killed by the time this intent was passed via receiver. To overcome this, use 'singleTop' or similar flags in your Manifest file
I am working on an android application with push notification feature using GCM. I have created a class called PushNotificationService which extends GCMListenerService. Inside the onMessageReceived(String from, Bundle data) I am able to get the message in the push notification.
Now, I want to access a method inside my MainActivity class whenever a particular message is received in the push.
Below is my code :-
PushNotificationService.java
public class PushNotificationService extends GcmListenerService {
#Override
public void onMessageReceived(String from, Bundle data) {
// TODO Auto-generated method stub
super.onMessageReceived(from, data);
String message = data.getString("message");
if(message.equalsIgnoreCase("Begin Task"))
{
//call method from MainActivity.class
}
}
}
MainActivty.java
public class MainActivity extends Activity {
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
public void beginTask()
{
Log.d("GCM","Message Received from Server");
finish();
}
}
I want the beginTask() method to execute whenever the message "Begin Task" is received.
I know one approach is via Service->Interface->Activity architecture but I am not able to use this as I never create an object of PushNotificationService.
Please help.
UPDATE :-
I am now using Otto Library and below is my code.
Added new MyBus.java
public class MyBus extends Bus {
private static Bus bus;
//isRegistered is used to track the current registration status
private static boolean isRegistered;
private Handler handler = new Handler(Looper.getMainLooper());
public MyBus() {
if (bus == null) {
//ANY will allow event bus to run even with services
//and broadcast receivers
bus = new Bus(ThreadEnforcer.ANY);
}
}
#Override
public void register(Object obj) {
//The bus is registered when an activity starts
bus.register(obj);
isRegistered = true;
}
#Override
public void unregister(Object obj) {
//The bus is unregistered when an activity goes to background
bus.unregister(obj);
isRegistered = false;
}
#Override
public void post(final Object event) {
if (Looper.myLooper() == Looper.getMainLooper()) {
//post the event in main thread or background thread
bus.post(event);
} else {
handler.post(new Runnable() {
#Override
public void run() {
bus.post(event);
}
});
}
}
public boolean isRegistered(){
return isRegistered;
}
}
PushNotificationService.java
public class PushNotificationService extends GcmListenerService {
#Override
public void onMessageReceived(String from, Bundle data) {
// TODO Auto-generated method stub
super.onMessageReceived(from, data);
MyBus myBus = new MyBus();
myBus.register(myBus);
String message = data.getString("message");
if(message.equalsIgnoreCase("Begin Task"))
{
myBus.post(message);
}
}
}
MainActivity.java
public class MainActivity extends Activity {
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
#Subscribe
public void beginTask()
{
Log.d("GCM","Message Received from Server");
}
}
The problem is still not solved. The beginTask() inside MainActivity.java is still not getting called.
Use eventBus libraries to facilitate this process...
I use Otto for this process
http://square.github.io/otto/
Here is an another eventBus library https://greenrobot.github.io/EventBus/
Steps:
1.Create an event from the service
2.Add a listener in the activity
3.If the activity is running the method will be executed
**EDIT 1 : **
I have abstracted the otto bus like this.
package com.mypackage.eventBus;
import android.os.Handler;
import android.os.Looper;
import com.squareup.otto.Bus;
import com.squareup.otto.ThreadEnforcer;
/**
* Created by gowtham on 10/6/15.
*/
public class MyBus extends Bus {
private static Bus bus;
//isRegistered is used to track the current registration status
private static boolean isRegistered;
private Handler handler = new Handler(Looper.getMainLooper());
public MyBus() {
if (bus == null) {
//ANY will allow event bus to run even with services
//and broadcast receivers
bus = new Bus(ThreadEnforcer.ANY);
}
}
#Override
public void register(Object obj) {
//The bus is registered when an activity starts
bus.register(obj);
isRegistered = true;
}
#Override
public void unregister(Object obj) {
//The bus is unregistered when an activity goes to background
bus.unregister(obj);
isRegistered = false;
}
#Override
public void post(final Object event) {
if (Looper.myLooper() == Looper.getMainLooper()) {
//post the event in main thread or background thread
bus.post(event);
} else {
handler.post(new Runnable() {
#Override
public void run() {
bus.post(event);
}
});
}
}
public boolean isRegistered(){
return isRegistered;
}
}
create an instance of the above object and try posting event
EDIT 2 for Jcarlo's comment
Follow these steps to find the state of the activity.
In your activity's onResume call MyBus.getInstance().register(this).
In your activity's onPause call MyBus.getInstance().unregister(this).
In your GCM IntentService before posting the message
if(MyBus.getInstance().isRegistered()){
//app is alive
//post data
}else{
//show notification
}
Hope this helps
You can use LocalBroadcastManager. Create a LocalBroadcastManager object mBroadcaster = LocalBroadcastManager.getInstance(this); on onCreate of your GCMListener and send broadcast with
#Override
public void onCreate() {
super.onCreate();
mBroadcaster = LocalBroadcastManager.getInstance(this);
}
#Override
public void onMessageReceived(String from, Bundle data) {
super.onMessageReceived(from, data);
String message = data.getString("message");
if(message.equalsIgnoreCase("Begin Task")) {
Intent i = new Intent();
i.setAction("yourPackageName");
i.putExtra("DATA", yourData);
mBroadcaster.send(i);
}
}
Then you can receive message in MainActivity using a BroadcastReceiver.
BroadCastReceiver mBroadcastReceiver = new BroadcastReceiver() {
#Override
public void onReceive(Context context, Intent intent) {
beginTask();
}
};
Also you need to register and unregister the receiver in onStart and onStop of your activity
#Override
protected void onStart() {
super.onStart();
IntentFilter filter = new IntentFilter();
filter.addAction("yourPackageName);
LocalBroadcastManager.getInstance(this).registerReceiver((mBroadcastReceiver), filter);
}
#Override
protected void onStop() {
super.onStop();
LocalBroadcastManager.getInstance(this).unregisterReceiver(mBroadcastReceiver);
}
I am trying to send an urgent SMS from my application. I have to make sure that the SMS is being sent successfully.
The SMS is being sent after the boot of the Android system and after a check is being made.
So I have a service class that handles the BOOT_COMPLETED intent-filter. This class makes a check and if something is true then it tries to send an SMS message via another class that "extends Service"
After it makes sure that the sms is successfully sent, both services (the one that handles the boot call and the one that sends the sms) must exit.
Question 1: How to make my sms sending function be called with a timeout without getting the application being unresponsive message? Currently I am using this (I don't know if it is the correct way to do it, though it works):
Timer mTimer = new Timer();
//wait a small timeout prior to sending the message.
mTimer.scheduleAtFixedRate(new TimerTask() {
#Override
public void run() {
this.cancel(); //I don't want to run the timer more than once
sms_sender sms = new sms_sender();
sms.sendSMS(phoneNumber, messageText);
}
}, 30000, 30000); //run sendSMS() after 30 seconds
Question 2: How to implement the sendSMS function so as to retry every 30 seconds after realizing that the last attempt was a fail?
Question 3: How to stop both services after I realize that the SMS was successfully sent?
This is my code which does not work:
public class sms_sender extends Service {
#Override
public IBinder onBind(Intent arg0) {
// TODO Auto-generated method stub
return null;
}
final String SENT = "SMS_SENT";
public void sendSMS(final String phoneNumber, final String message, final boolean check_result)
{
PendingIntent sentPI = PendingIntent.getBroadcast(this, 0, new Intent(SENT), 0);
registerReceiver(new BroadcastReceiver(){
#Override
public void onReceive(Context arg0, Intent arg1) {
if(!check_result)
return;
switch (getResultCode())
{
case Activity.RESULT_OK:
//exit
stopSelf();
return;
case SmsManager.RESULT_ERROR_GENERIC_FAILURE:
case SmsManager.RESULT_ERROR_NO_SERVICE:
case SmsManager.RESULT_ERROR_NULL_PDU:
case SmsManager.RESULT_ERROR_RADIO_OFF:
//try again in 1 minute
Timer mTimer = new Timer();
mTimer.scheduleAtFixedRate(new TimerTask() {
#Override
public void run() {
this.cancel(); //no need to run again, if it fails, this exact code will run again
sendSMS(phoneNumber, message, true);
}
}, 60000, 60000);
return;
}
}
}, new IntentFilter(SENT));
SmsManager smsManager = SmsManager.getDefault();
smsManager.sendTextMessage(phoneNumber, null, message, sentPI, null);
}
}
Currently the program crashes on the PendingIntent call. I tried to implement the BroadCastReceiver on the onCreate method using private member variables so as to call the sendSMS() function again through the onReceive method, but the onReceive never seemed to run.
-- EDIT --
So, this is my final working code. I guess that my case is special because it doesn't work on a UI thread. I have a Broadcast Receiver that runs on Boot. I am trying to send an SMS message until it is successfully sent.
This Boot Broadcast Receiver starts a service. This is some code from it:
public class service extends Service{
static public service serv;
//member variable. Initializing to null so as to know whether to unregister the service or not
private BroadcastReceiver messageSent = null;
...
...
#Override
public void onStart(Intent intent, int startid)
{
serv=this; //will use this static variable in order to shutdown the service when the message is successfully sent
...
...
if(somethingIsTrue()){
//register receiver
messageSent = new sent_message();
registerReceiver(messageSent, new IntentFilter(sms_sender.INTENT_MESSAGE_SENT));
startMessageServiceIntent(messageText, phoneNumber); //function code can be found on accepted answer
}
}
}
The sent_message class is the following:
public class sent_message extends BroadcastReceiver {
private Context pubCon;
private void startMessageServiceIntent(String message, String receiver) {
Intent i = new Intent(pubCon, sms_sender.class);
i.putExtra(sms_sender.EXTRA_MESSAGE, message);
i.putExtra(sms_sender.EXTRA_RECEIVERS, new String[] { receiver });
pubCon.startService(i);
}
#Override
public void onReceive(Context context, Intent intent) {
pubCon=context;
switch (getResultCode()) {
case Activity.RESULT_OK:
//all went OK, stop the service where this is called from
service.serv.stopSelf();
break;
case SmsManager.RESULT_ERROR_GENERIC_FAILURE:
case SmsManager.RESULT_ERROR_NO_SERVICE:
case SmsManager.RESULT_ERROR_NULL_PDU:
case SmsManager.RESULT_ERROR_RADIO_OFF:
//try sending the message again after 30s
new Handler().postDelayed(new Runnable(){
#Override
public void run(){
startMessageServiceIntent(service.messageText, service.phoneNumber);
}
}, 30000);
break;
}
}
}
And a simplified (accepts only one receiver) version of the sms_sender class is the following:
public class sms_sender extends IntentService {
public static final String INTENT_MESSAGE_SENT = "message.sent";
public static final String INTENT_MESSAGE_DELIVERED = "message.delivered";
public static final String EXTRA_MESSAGE = "extra.message";
public static final String EXTRA_RECEIVERS = "extra.receivers";
public sms_sender() {
super("sms_sender");
}
private static class IDGenerator {
private static final AtomicInteger counter = new AtomicInteger();
public static int nextValue() {
return counter.getAndIncrement();
}
}
public void sendSMS(String message, String receiver) {
SmsManager sm = SmsManager.getDefault();
PendingIntent sentPI = null;
Intent sentIntent = new Intent(INTENT_MESSAGE_SENT);
int sentID = IDGenerator.nextValue();
sentPI = PendingIntent.getBroadcast(sms_sender.this, sentID, sentIntent,
PendingIntent.FLAG_CANCEL_CURRENT);
try {
sm.sendTextMessage(receiver, null, message, sentPI, null);
} catch (IllegalArgumentException e) {
System.out.println("Illegal argument");
}
}
protected void onHandleIntent(Intent intent) {
String message = intent.getStringExtra(EXTRA_MESSAGE);
String[] receivers = intent.getStringArrayExtra(EXTRA_RECEIVERS);
sendSMS(message, receivers[0]);
}
}
Here is what I have done:
public class SMSSender extends IntentService {
public static final String INTENT_MESSAGE_SENT = "message.sent";
public static final String INTENT_MESSAGE_DELIVERED = "message.delivered";
public static final String EXTRA_MESSAGE = "extra.message";
public static final String EXTRA_RECEIVERS = "extra.receivers";
public SMSSender() {
super("SMSSender");
}
private final String TAG = "SendSMS";
private static class IDGenerator {
private static final AtomicInteger counter = new AtomicInteger();
public static int nextValue() {
return counter.getAndIncrement();
}
}
private void sendSMS(String message, String[] receivers) {
SmsManager sm = SmsManager.getDefault();
ArrayList<String> parts = sm.divideMessage(message);
PendingIntent sentPI = null;
PendingIntent deliveredPI = null;
Intent sentIntent = new Intent(INTENT_MESSAGE_SENT);
int sentID = IDGenerator.nextValue();
sentPI = PendingIntent.getBroadcast(SMSSender.this, sentID, sentIntent,
PendingIntent.FLAG_CANCEL_CURRENT);
Intent deliveryIntent = new Intent(INTENT_MESSAGE_DELIVERED);
int deliveredID = IDGenerator.nextValue();
deliveredPI = PendingIntent.getBroadcast(SMSSender.this, deliveredID,
deliveryIntent, PendingIntent.FLAG_CANCEL_CURRENT);
Log.i(TAG, "sending SMS: parts: " + parts.size() + " message: "
+ message);
if (parts.size() > 1) {
ArrayList<PendingIntent> sentIntents = null;
ArrayList<PendingIntent> deliveredIntents = null;
sentIntents = new ArrayList<PendingIntent>();
deliveredIntents = new ArrayList<PendingIntent>();
for (int i = 0; i < parts.size(); i++) {
sentIntents.add(sentPI);
deliveredIntents.add(deliveredPI);
}
for (String receiver : receivers) {
try {
sm.sendMultipartTextMessage(receiver, null, parts,
sentIntents, deliveredIntents);
} catch (IllegalArgumentException e) {
Log.e(TAG, "illegal receiver: " + receiver);
}
}
} else {
for (String receiver : receivers) {
try {
sm.sendTextMessage(receiver, null, parts.get(0), sentPI,
deliveredPI);
} catch (IllegalArgumentException e) {
Log.e(TAG, "illegal receiver: " + receiver);
}
}
}
}
#Override
protected void onHandleIntent(Intent intent) {
String message = intent.getStringExtra(EXTRA_MESSAGE);
String[] receivers = intent.getStringArrayExtra(EXTRA_RECEIVERS);
sendSMS(message, receivers);
}
And to use it:
private void startMessageServiceIntent(String message, String receiver) {
Intent i = new Intent(context, SMSSender.class);
i.putExtra(SMSSender.EXTRA_MESSAGE, message);
i.putExtra(SMSSender.EXTRA_RECEIVERS, new String[] { receiver });
startService(i)
}
Notice it supports multiple receivers, which this method does not demonstrate/use.
Remember in your manifest:
<uses-permission android:name="android.permission.SEND_SMS" />
<service android:name="your.package.SMSSender" android:enabled="true" />
Optionally you can listen for when messages are sent and/or delivered:
#Override
protected void onCreate() {
...
// ---when the SMS has been sent---
private BroadcastReceiver messageSent; // <- stored as a field
messageSent = new SentMessage();
registerReceiver(messageSent, new IntentFilter(SMSSender.INTENT_MESSAGE_SENT));
// ---when the SMS has been delivered---
private BroadcastReceiver messageDelivered; // <- stored as a field
messageDelivered = new MessageDelivered();
registerReceiver(messageDelivered, new IntentFilter(
SMSSender.INTENT_MESSAGE_DELIVERED));
}
#Override
protected void onDestroy() { // remember to unregister
unregisterReceiver(messageSent);
unregisterReceiver(messageDelivered );
}
I know this does not demonstrate answers to all your questions but I hope that it is sufficient.
Edit: Added my implementations of messageSent and messageDelivered
These are specific to my implementation, so includes some code that you cannot use, it is simply for demonstration.
Message sent:
public class SentMessage extends BroadcastReceiver {
private final String TAG = "SentMessage";
#Override
public void onReceive(Context context, Intent intent) {
long _id = intent.getLongExtra(EXTRA_ID, -1);
long protocol_id = intent.getLongExtra(EXTRA_PROTOCOL, -1);
Log.d(TAG, "SentMessage");
switch (getResultCode()) {
case Activity.RESULT_OK:
Log.d(TAG, "RESULT_OK");
if (MessageData.sentMessage(_id, protocol_id)) {
try {
Database.messageSent(_id);
} catch (DatabaseRowNotFoundException e) {
Log.e(TAG, e.toString(), e);
}
}
break;
case SmsManager.RESULT_ERROR_GENERIC_FAILURE:
Log.d(TAG, "RESULT_ERROR_GENERIC_FAILURE");
MessageData.postponeMessage(_id);
ApplicationData.hasSignal(false);
break;
case SmsManager.RESULT_ERROR_NO_SERVICE:
Log.d(TAG, "RESULT_ERROR_NO_SERVICE");
MessageData.postponeMessage(_id);
ApplicationData.hasSignal(false);
break;
case SmsManager.RESULT_ERROR_NULL_PDU:
Log.d(TAG, "RESULT_ERROR_NULL_PDU");
break;
case SmsManager.RESULT_ERROR_RADIO_OFF:
Log.d(TAG, "RESULT_ERROR_RADIO_OFF");
MessageData.postponeMessage(_id);
ApplicationData.hasSignal(false);
break;
}
}
Message delivered:
public class DeliveredMessage extends BroadcastReceiver {
private final String TAG = "DeliveredMessage ";
#Override
public void onReceive(Context context, Intent intent) {
long _id = intent.getLongExtra(EXTRA_ID, -1);
long protocol_id = intent.getLongExtra(EXTRA_PROTOCOL, -1);
switch (getResultCode()) {
case Activity.RESULT_OK:
if (_id != -1 && MessageData.deliveredMessage(_id, protocol_id)) {
try {
Database.messageDelivered(_id);
Cursor messageCursor = Database.getCursorByID(MessageOutboxContentProvider.CONTENT_URI, MessageOutboxContentProvider._ID, _id);
messageCursor.close();
} catch (DatabaseRowNotFoundException e) {
Log.e(TAG, e.toString(), e);
}
}
break;
case Activity.RESULT_CANCELED:
break;
}
}
}
I was in the need for reliable sending too, so kept references to all pending messages in a database, which I would frequently scan for postponed messages. A message would get postponed if there is no radio, or the sending simply fails for whatever reason.
I also used GCM together with SMS to get the message delivered as fast as possible, sending messages using both channels at the same time.
Edit2: Oh well, might as well address the questions, we are almost there anyway:
Question 1: Since using IntentService the sending is done in the background.
You only want the sending to happen once after a delay so you should do this instead:
new Handler().postDelayed(new Runnable() {
#Override
public void run() {
// send sms
}
}, delay);
Question 2: Easy, when your sent message broadcast detects an error do the above method. You could add an extra information, besides receiver and message, counting the number of retries up until now so you have a chance of stopping the send/retry loop.
Question 3: The sending stops by itself, as it is an Intent Service. As for the other service the most simple approach, I think, would be to send a common broadcast, which is picked up by your main activity. This way you can get a hold of the service the right place and stop it.