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.
Related
I have written a Service that listens for UDP messages then changes a TextView and an ImageView based on the parsed message from the UDP messages. I'm getting an NPE when attemping to use a public getParsedMessage method from the service, which means that the service has not been started. It is declared as a service in the manifest exactly as it is spelled, so I know that is not the problem. Here is my MainActivity's code:
public class MainActivity extends Activity {
AlertAssignments mAlertAssignments;
Button startListeningButton;
boolean started;
int counter;
boolean mBound = false;
Context context;
ListenerService mListenerService;
TextView mTextView;
TextView mBlinkView;
ImageView mImageView;
#Override
protected void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mImageView = (ImageView) findViewById(R.id.image_view);
mTextView = (TextView) findViewById(R.id.alert_text);
mBlinkView = (TextView) findViewById(R.id.blinking_text);
Animation mAnimation = new AlphaAnimation(0.0f, 1.0f);
mAnimation.setDuration(50);
mAnimation.setStartOffset(20);
mAnimation.setRepeatCount(Animation.INFINITE);
mAnimation.setRepeatMode(Animation.REVERSE);
mBlinkView.startAnimation(mAnimation); //animation value
mAlertAssignments = new AlertAssignments();
Integer parsedMessage = Integer.valueOf(mListenerService.getParsedMessage()); //this is the cause of the NPE
mImageView.setImageResource(mAlertAssignments.alarmImages[parsedMessage]);
if(parsedMessage >= 10 && parsedMessage <= 19 && parsedMessage != 0) {
mTextView.setText(mAlertAssignments.alertTextMessages[parsedMessage]);
} else {
mBlinkView.setText(mAlertAssignments.alertTextMessages[parsedMessage]);
}
}
private ServiceConnection mConnection = new ServiceConnection() {
#Override
public void onServiceConnected(ComponentName className, IBinder service) {
ListenerService.LocalBinder binder = (ListenerService.LocalBinder) service;
mListenerService = binder.getService();
mBound = true;
}
#Override
public void onServiceDisconnected(ComponentName name) {
mBound = false;
}
};
#Override
protected void onStart() {
super.onStart();
//start listener service
Intent listenerServiceIntent = new Intent(MainActivity.this, ListenerService.class);
this.bindService(listenerServiceIntent, mConnection, Context.BIND_AUTO_CREATE);
}
#Override
protected void onStop() {
super.onStop();
//unbind from service
if(mBound) {
this.unbindService(mConnection);
mBound = false;
}
}
}
The error occurs on line 75, which is marked above (Integer parsedMessage = Integer.valueOf(mListenerService.getParsedMessage());). I have followed the developer.android documentation in setting up and starting my service, however i seem to see conflicting information in multiple locations. Here is my ListenerService:
public class ListenerService extends Service{
public String the_alarm_S;
public String parsedMessage = "";
private final IBinder mBinder = new LocalBinder();
public class LocalBinder extends Binder {
ListenerService getService() {
return ListenerService.this;
}
}
#Override
public IBinder onBind(Intent intent) {
return mBinder;
}
static String UDP_BROADCAST = "UDPBroadcast";
//Boolean shouldListenForUDPBroadcast = false;
DatagramSocket socket;
private void listenAndWaitAndThrowIntent(InetAddress broadcastIP, Integer port) throws Exception {
byte[] recvBuf = new byte[15000];
if (socket == null || socket.isClosed()) {
socket = new DatagramSocket(port, broadcastIP);
socket.setBroadcast(true);
}
//socket.setSoTimeout(1000);
DatagramPacket packet = new DatagramPacket(recvBuf, recvBuf.length);
Log.e("UDP", "Waiting for UDP broadcast");
socket.receive(packet);
String senderIP = packet.getAddress().getHostAddress();
String message = new String(packet.getData()).trim();
Log.e("UDP", "Got UDB broadcast from " + senderIP + ", message: " + message);
broadcastIntent(senderIP, message);
setParsedMessage(message);
socket.close();
}
private void broadcastIntent(String senderIP, String message) {
Intent intent = new Intent(ListenerService.UDP_BROADCAST);
intent.putExtra("sender", senderIP);
intent.putExtra("message", message);
Log.e("UDP", "Message received containing" +message);
sendBroadcast(intent);
}
Thread UDPBroadcastThread;
void startListenForUDPBroadcast() {
UDPBroadcastThread = new Thread(new Runnable() {
public void run() {
try {
InetAddress broadcastIP = InetAddress.getByName("172.16.238.255"); //172.16.238.42 //192.168.1.255
Integer port = 12001;
while (shouldRestartSocketListen) {
listenAndWaitAndThrowIntent(broadcastIP, port);
}
//if (!shouldListenForUDPBroadcast) throw new ThreadDeath();
} catch (Exception e) {
Log.i("UDP", "no longer listening for UDP broadcasts cause of error " + e.getMessage());
}
}
});
UDPBroadcastThread.start();
}
private Boolean shouldRestartSocketListen=true;
private void setParsedMessage(String messageContents) {
the_alarm_S = messageContents;
String parseMessage[] = the_alarm_S.split("!!!");
Log.e("UDP", "Parsed message with value " + parseMessage[1]);
parsedMessage = parseMessage[1];
}
public String getParsedMessage() {
return parsedMessage;
}
void stopListen() {
shouldRestartSocketListen = false;
socket.close();
}
#Override
public void onCreate() {
};
#Override
public void onDestroy() {
stopListen();
}
#Override
public int onStartCommand(Intent intent, int flags, int startId) {
shouldRestartSocketListen = true;
startListenForUDPBroadcast();
Log.i("UDP", "Service started");
return START_STICKY;
}
}
I had previously been using an AsyncTask to fetch this data, however, I need it to be constantly fetching the data and updating the TextView and ImageView objects, and due to the ping speed using a while loop to do this caused it to run out of memory due to i assume it running on the UI thread. AlertAssignments is simply an Enum that binds image files and Strings to ordinal array values so that I can easily change the TextView and ImageView based on the integer value of the parsed message (parsedMessage[1] of the original message xxx!!!n!!!xxx gives parsedMessage[1] = n)
Any advice on how to resolve what is probably an oversight on my part would be great, thanks
Take a look at the Android activity lifecycle:
One thing you'll notice is that onCreate() is called before onStart(). This means you're trying to access the service before it actually starts.
The way I would work around this is to start your service in onCreate() (so it will start as soon as your activity is first created) and then read the value inside onResume(), so that every time your activity comes back into the foreground it will update according to the service.
You may also notice that if resources are required, your app could be killed as early as onPause(). It would be a good idea to do your cleanup there, instead of onDestroy().
EDIT:
If the above option didn't work, I suspect there's a race condition going on. Just because you started the service before you read from it, doesn't mean it was fully configured. Thankfully, you have a listener to tell you when the service is bound.
You can write a separate method specifically for updating the UI, and have your activity only call it once the service has started:
public class MyActivity {
private ServiceConnection mConnection = new ServiceConnection() {
#Override
public void onServiceConnected(ComponentName className, IBinder service) {
ListenerService.LocalBinder binder = (ListenerService.LocalBinder) service;
mListenerService = binder.getService();
mBound = true;
readFromService();
}
#Override
public void onServiceDisconnected(ComponentName name) {
mBound = false;
}
};
private void readFromService() {
Integer parsedMessage = Integer.valueOf(mListenerService.getParsedMessage()); //this is the cause of the NPE
mImageView.setImageResource(mAlertAssignments.alarmImages[parsedMessage]);
if(parsedMessage >= 10 && parsedMessage <= 19 && parsedMessage != 0) {
mTextView.setText(mAlertAssignments.alertTextMessages[parsedMessage]);
} else {
mBlinkView.setText(mAlertAssignments.alertTextMessages[parsedMessage]);
}
}
}
I have a broadcast receiver in my service, and when I send a broadcast to it from my activity to stop the service for example, I get the following error:
java.lang.NullPointerException: Attempt to invoke virtual method 'void android.content.BroadcastReceiver.onReceive(android.content.Context, android.content.Intent)' on a null object reference
Here is my Service class:
public class PhraseService extends Service{
public static List<Phrase> phrases;
private Context context;
private static final String PHRASE_SERVICE_BROADCAST_ID = "com.cryogenos.safephrase.PHRASE_SERVICE_BROADCAST_ID";
private BroadcastReceiver command_broadcast_receiver;
#Override
public int onStartCommand(Intent intent, int flags, int startId) {
this.context = getApplicationContext();
LocalBroadcastManager.getInstance(this).registerReceiver(command_broadcast_receiver, new IntentFilter(PHRASE_SERVICE_BROADCAST_ID));
Log.i("COS", "STARTED SELF");
/* Handle any command sent by the app here */
command_broadcast_receiver = new BroadcastReceiver() {
#Override
public void onReceive(Context context, Intent intent) {
Log.i("COS", "CALLED?");
try {
String command = intent.getStringExtra("command");
/* Based on the command given, perform an action */
switch (command) {
case "RESTART RECOGNIZER":
break;
case "STOP":
Toast.makeText(context, "Phrase Listener Stopped", Toast.LENGTH_SHORT).show();
stopSelf();
break;
}
}
catch (Exception e){}
}
};
Intent notif_intent = new Intent(this, Home.class);
PendingIntent pi = PendingIntent.getActivity(this, 0, notif_intent, 0);
Notification notif = new NotificationCompat.Builder(this)
.setSmallIcon(R.mipmap.ic_launcher)
.setContentTitle("Phrase Listener")
.setContentText("Listening for Phrases")
.setContentIntent(pi)
.build();
startForeground(34994, notif);
return super.onStartCommand(intent, flags, startId);
}
#Override
public void onDestroy() {
LocalBroadcastManager.getInstance(this).unregisterReceiver(command_broadcast_receiver);
super.onDestroy();
}
#Override
public IBinder onBind(Intent intent) {return null;}
}
Here is the activity from where I'm sending the broadcast from:
public class SettingsModel {
private Context context;
private static final String PHRASE_SERVICE_BROADCAST_ID = "com.cryogenos.safephrase.PHRASE_SERVICE_BROADCAST_ID";
private LocalBroadcastManager local_broadcast_manager;
public SettingsModel(Context context) {
this.context = context;
local_broadcast_manager = LocalBroadcastManager.getInstance(context);
}
public void handleSwitch(SettingsSwitch settings_switch, boolean status){
switch (settings_switch){
case POWER:
if(status) {
Intent i = new Intent(context, PhraseService.class);
context.startService(i);
}
else{
Intent i = new Intent(PHRASE_SERVICE_BROADCAST_ID);
i.putExtra("command", "STOP");
local_broadcast_manager.sendBroadcast(i);
}
break;
case VIBRATE:
break;
case RING:
break;
case TIMER:
break;
}
}
}
My service is suppose to be continuously running, and only when I tell it, it should stop. This is why I have set it up as a foreground service. The problem here is that I keep getting an error when I try to broadcast to it.
You are registering your brodcastreceiver before initializing it. So try to move the registration after initialization as below:
/* Handle any command sent by the app here */
command_broadcast_receiver = new BroadcastReceiver() {
#Override
public void onReceive(Context context, Intent intent) {
Log.i("COS", "CALLED?");
try {
String command = intent.getStringExtra("command");
/* Based on the command given, perform an action */
switch (command) {
case "RESTART RECOGNIZER":
break;
case "STOP":
Toast.makeText(context, "Phrase Listener Stopped", Toast.LENGTH_SHORT).show();
stopSelf();
break;
}
}
catch (Exception e){}
}
};
LocalBroadcastManager.getInstance(this).registerReceiver(command_broadcast_receiver, new IntentFilter(PHRASE_SERVICE_BROADCAST_ID));
Also, your SettingsModel is not an activity since it does not extend Activity.
I have a single Activity (called as NMessageActivity) that has a simple method of sending SMS. Just using this code:
public void sendSMS(MBean dBean) {
if (sentSMS != null && deliveredSMS != null) {
SmsManager sms = SmsManager.getDefault();
sms.sendTextMessage(dBean.getToInNumbers(), null,
dBean.getMessage(), sentSMS, deliveredSMS);
} else {
ToastObject.postMessage("Error code 233!");
}
}
Before that, i did initialize the Pending Intents that I use. i put them on the onCreate method i have:
smsSentReceiver = new SentReceiver(this);
smsDeliveredReceiver = new DeliveredReceiver(this);
and then I register them at onResume():
super.onResume();
registerReceiver(smsSentReceiver, new IntentFilter(TAG_SENT));
registerReceiver(smsDeliveredReceiver, new IntentFilter(TAG_DELIVERED));
and at onPause() I unregister them:
super.onPause();
unregisterReceiver(smsSentReceiver);
unregisterReceiver(smsDeliveredReceiver);
And before sending the SMS, I do preparing the AlarmManager to execute the intent
after 2 seconds. Using this code:
MBean dbbean = new MBean(txt_destNum.getText().toString(), txt_date
.getText().toString(), txt_time.getText().toString(),
MBean.STATUS_WAIT, txt_msg.getText().toString());
db.addMBean(dbbean);
Intent intentAlarm = new Intent(TAG_SENT);
Intent intentAlarm2 = new Intent(TAG_DELIVERED);
// putting the values in
intentAlarm.putExtra(MBean.MBEAN_NAME, dbbean.toString());
sentSMS = PendingIntent.getBroadcast(getApplicationContext(), 0,
intentAlarm, PendingIntent.FLAG_UPDATE_CURRENT);
deliveredSMS = PendingIntent.getBroadcast(getApplicationContext(),
0, intentAlarm2, PendingIntent.FLAG_UPDATE_CURRENT);
int sec = 2;
// create the object
AlarmManager alarmManager = (AlarmManager) getSystemService(getApplicationContext().ALARM_SERVICE);
alarmManager.set(AlarmManager.RTC_WAKEUP,
System.currentTimeMillis() + (sec * 1000), sentSMS);
The problem is not on the above method and nor the AlarmManager.
Instead, the problem is on the BroadcastReceiver that i Have.
The Broadcast receivers are never called. Why is that happened?
Here are the Broadcast Receiver class in details:
SentReceiver.java
public class SentReceiver extends BroadcastReceiver {
private MBean dBean;
private Context dCont;
private ToastSender dToast;
private Activity dNMessageActivity;
public SentReceiver(Activity obAct){
dNMessageActivity = obAct;
}
private void sendMessage() {
((NMessageActivity)dNMessageActivity).sendSMS(dBean);
}
private void vibratePhone() {
Vibrator vibrator = (Vibrator) dCont
.getSystemService(Context.VIBRATOR_SERVICE);
vibrator.vibrate(1000);
}
#Override
public void onReceive(Context arg0, Intent arg1) {
dToast = new ToastSender(arg0);
dCont = arg0;
// TODO Auto-generated method stub
switch (getResultCode()) {
case Activity.RESULT_OK:
// SMS has been sent
dBean = new MBean(arg1.getStringExtra(MBean.MBEAN_NAME));
vibratePhone();
sendMessage();
break;
case SmsManager.RESULT_ERROR_GENERIC_FAILURE:
// Generic Failure
dToast.postMessage("Error code 48!");
break;
case SmsManager.RESULT_ERROR_NO_SERVICE:
// No Service
dToast.postMessage("Error code 53!");
break;
case SmsManager.RESULT_ERROR_NULL_PDU:
// Null PDU
dToast.postMessage("Error code 58!");
break;
case SmsManager.RESULT_ERROR_RADIO_OFF:
// Radio Off
dToast.postMessage("Error code 63!");
break;
default:
break;
}
}
}
and another receiver is
DeliveredReceiver.java
public class DeliveredReceiver extends BroadcastReceiver {
private ToastSender dToast;
private Context dCont;
private Activity dNMessageActivity;
public DeliveredReceiver(Activity obAct){
dNMessageActivity = obAct;
}
private void vibratePhone() {
Vibrator vibrator = (Vibrator) dCont
.getSystemService(Context.VIBRATOR_SERVICE);
vibrator.vibrate(2000);
}
#Override
public void onReceive(Context arg0, Intent arg1) {
dCont = arg0;
dToast = new ToastSender(arg0);
switch (getResultCode()) {
case Activity.RESULT_OK:
dToast.postMessage("SMS Sent");
vibratePhone();
break;
case Activity.RESULT_CANCELED:
dToast.postMessage("SMS Pending");
break;
}
}
}
I wonder is that the Broadcast Receiver problem or the Pending Intent problem that I passed over the Alarmmanager? Why the program doesnt call the Broadcast Receiver at all?
I'm developing an Android chat app in Java. Now I finally got my service to work but as soon as I fully kill the app the connection in my service dies.
I am using asmack as library for the XMPP connection. The goal is to receive messages even if the app is killed by the user (so it's not in the background).
It does work when I use a foreground service, but I don't want to use a foreground service because of high memory useage and because I don't want the foreground message in the notification center.
My service class
public class MessagingService extends Service {
private final String TAG = "MessagingService";
private final IBinder mBinder = new MessagingBinder();
public Context context;
public XMPPConnection Connection;
public static Handler mHandler = new Handler();
private final int ONGOING_NOTIFICATION_ID = 2344;
#Override
public void onCreate() {
}
#Override
public int onStartCommand(Intent intent, int flags, int startId) {
Log.d(TAG, "onStartCommand");
return START_STICKY;
}
#Override
public IBinder onBind(Intent intent) {
Log.d(TAG, "onBind");
return mBinder;
}
#Override
public boolean onUnbind(Intent intent) {
Log.d(TAG, "onUnbind");
return true;
}
#Override
public void onRebind(Intent intent) {
super.onRebind(intent);
Log.d(TAG, "onRebind");
}
#Override
public void onDestroy() {
}
public class MessagingBinder extends Binder {
MessagingService getService() {
Log.d(TAG + " - MessagingBinder", "getService");
return MessagingService.this;
}
}
public Boolean isConnected() {
return (Connection != null);
}
public void Connect(final AuthorizeActivity authorize, final String username, final String password) {
Thread XMPPConnect = new Thread(new Runnable() {
public final String TAG = "XMPPConnect Thread";
#Override
public void run() {
AndroidConnectionConfiguration connConfig = new AndroidConnectionConfiguration(Configuration.HOST, Configuration.PORT, Configuration.SERVICE);
SmackConfiguration.setDefaultPingInterval(100);
connConfig.setReconnectionAllowed(true);
connConfig.setSASLAuthenticationEnabled(true);
connConfig.setRosterLoadedAtLogin(true);
Connection = new XMPPConnection(connConfig);
try {
Connection.connect();
Log.i(TAG, "Connected to " + Connection.getHost());
} catch (XMPPException ex) {
Log.e(TAG, "Failed to connect to " + Connection.getHost());
Log.e(TAG, ex.toString());
Connection = null;
}
if(authorize != null)
authorize.mServiceConnectCallback();
if(username != null && password != null)
Login(username, password, null);
}
});
XMPPConnect.start();
}
public void Login(final String username, final String password, final AuthorizeActivity authorize) {
Thread XMPPLogin = new Thread(new Runnable() {
public final String TAG = "XMPPConnect Thread";
#Override
public void run() {
try {
Connection.login(username, password);
Log.i(TAG, "Logged in as " + Connection.getUser());
Presence presence = new Presence(Presence.Type.available);
Connection.sendPacket(presence);
PacketFilter filter = new MessageTypeFilter(Message.Type.chat);
Connection.addPacketListener(new PacketListener() {
#Override
public void processPacket(Packet packet) {
final Message message = (Message) packet;
if (message.getBody() != null) {
final String fromName = StringUtils.parseName(message.getFrom());
Log.i(TAG, "Text Recieved " + message.getBody() + " from " + fromName );
mHandler.post(new Runnable() {
public void run() {
Receiver.recieveMessage(fromName, message.getBody());
if(!VisibilityHelper.IsVisible()) {
showNotification(fromName, message.getBody());
}
}
});
}
}
}, filter);
} catch (XMPPException ex) {
Log.e(TAG, "Failed to log in as " + "test");
Log.e(TAG, ex.toString());
Connection = null;
}
if(authorize != null)
authorize.mServiceLoginCallback();
}
});
XMPPLogin.start();
}
public void showNotification(String from, String message) {
NotificationManager notificationManager = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
CharSequence notiText = message;
long meow = System.currentTimeMillis();
Notification notification = new Notification(R.drawable.ic_launcher, notiText, meow);
Context context = getApplicationContext();
CharSequence contentTitle = from;
CharSequence contentText = message;
Intent notificationIntent = new Intent(context, MainActivity.class);
PendingIntent contentIntent = PendingIntent.getActivity(getApplicationContext(), 0, notificationIntent, PendingIntent.FLAG_UPDATE_CURRENT);
notification.setLatestEventInfo(context, contentTitle, contentText, contentIntent);
notification.flags = Notification.DEFAULT_LIGHTS | Notification.FLAG_AUTO_CANCEL;
int SERVER_DATA_RECEIVED = 1;
notificationManager.notify(SERVER_DATA_RECEIVED, notification);
}
public void Logout() {
if(Connection.isConnected()) {
Log.i(TAG, "Logout");
Connection.disconnect();
}
}
public HashMap<String, String> getVCard(String user) {
Log.d(TAG, "getVCard");
//String email = user + "#" + Configuration.HOST;
String email = user;
VCard card = new VCard();
ProviderManager.getInstance().addIQProvider("vCard", "vcard-temp", new VCardProvider());
try {
card.load(MainActivity.mService.Connection, email);
String jabber_id = card.getJabberId();
String firstname = card.getFirstName();
String middlename = card.getMiddleName();
String lastname = card.getLastName();
HashMap<String, String> vcard = new HashMap<String, String>();
vcard.put("jabber_id", jabber_id);
vcard.put("firstname", firstname);
vcard.put("middlename", middlename);
vcard.put("lastname", lastname);
return vcard;
} catch (XMPPException e) {
e.printStackTrace();
}
return null;
}
public void retrieveContactsFromList() {
if(this.isConnected()) {
Roster roster = Connection.getRoster();
Collection<RosterEntry> entries = roster.getEntries();
for(RosterEntry entry : entries) {
Receiver.onRetrieveContactFromList(entry);
}
}
}
}
My activity to start the service
public class ConnectionBinder extends FragmentActivity {
private final String TAG = "ConnectionBinder";
public static MessagingService mService;
public boolean mBound = false;
public Database DB;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
if(!this.messagingServiceIsRunning())
{
startService(new Intent(this, MessagingService.class));
}
}
private boolean messagingServiceIsRunning() {
ActivityManager manager = (ActivityManager) getSystemService(Context.ACTIVITY_SERVICE);
for (RunningServiceInfo service : manager.getRunningServices(Integer.MAX_VALUE)) {
if (MessagingService.class.getName().equals( service.service.getClassName())) {
return true;
}
}
return false;
}
#Override
protected void onResume() {
super.onResume();
doBindService();
}
#Override
protected void onPause() {
super.onPause();
doUnbindService();
}
private void doBindService() {
Intent intent = new Intent(this, MessagingService.class);
bindService(intent, mMessagingService, Context.BIND_AUTO_CREATE);
}
private void doUnbindService() {
if (mBound) {
unbindService(mMessagingService);
}
}
private void doXMPPLogin() {
HashMap<String, String> user = DB.getUser();
mService.Connect(null, user.get("username"), user.get("password"));
}
private ServiceConnection mMessagingService = new ServiceConnection() {
public void onServiceConnected(ComponentName className, IBinder service) {
Log.d(TAG, "mMessagingService.onServiceConnected()");
MessagingBinder binder = (MessagingBinder) service;
mService = binder.getService();
mBound = true;
if(!mService.isConnected()) {
doXMPPLogin();
}
mService.retrieveContactsFromList();
}
public void onServiceDisconnected(ComponentName arg0) {
Log.d(TAG, "mMessagingService.onServiceDisconnected()");
mBound = false;
}
};
}
Traditional XMPP implementations (and XMPP RFCs) does not define a way to maintain persistent user "sessions" when client disconnects - they all closing user session when underlying TCP/IP or HTTP connection is lost.
On the other hand, typical Android enviroment have "always-connected" Google Cloud Services, which can deliver messages for your application even if it is not connected. In fact, most chat and social networking applications are using GCM to inform user about new messages.
So, depend on your needs, you need to make some changes on the server side of your chat application:
Most XMPP server implementations are able to store messages which was received when user was "offline" and delivers it when user connects again. You can "hook" offline message receiving and inform user via Google Cloud Messaging about availability of new messages, user will receive it when open your application again and your XMPPConnection will established.
Use XMPP Stream Management extension - if you need to share same session across multiple user reconnections - and "resume" previous session when user open your app again. And you still should inform user about new events in his "session" via GCM.
Your server-side XMPP software should keep GCM registration ids for every user device, so when user device is registered in GCM - you need to inform your server about newly registered id - it can be achieved by sending custom <iq> packet to server with your GCM id.
Some commercial XMPP products already implement steps above and will sell you "Push-enabled XMPP service" which is in fact XMPP server with GCM backend, as I describe.
I know this has been asked multiple times, but I am having problems making it work or figuring out what will work best. I need to have the sms wait 2-3 seconds between each message before sending another message. I have looked at and tried handlers, timers and thread sleep and I am not sure which one would be the best use in my situation, or how to make it work right. I am still new to programming, so please take it easy on me.
// ---sends an SMS message---
private void sendSMS(String phoneNumber, String message) {
int i;
SmsManager sms = SmsManager.getDefault();
int amount = 10; // just making 10 the default if the EditText has an
// invalid value
try {
amount = Integer.parseInt(smsamount.getText().toString());
} catch (NumberFormatException smsamount) {
}
if (amount < 501) {
for (i = 0; i < amount; i++) {
sms.sendTextMessage(phoneNumber, null, message, sentPI, null);
}
if you want to use 2 seconds delay between each sms, use a ScheduledExecutorService thread pool (1 thread is probably enough cause you don't send parallel) and call schedule method with the code to send the sms.
For each call, raise the delay parameter by 2 seconds (0,2,4,6,...)
Hope it helps.
Maybe something like this. I've not tested it, but the idea of using a ScheduledExecutorService should be what you're after.
public class SMS extends Activity {
private final static OnClickListener EMPTY_ON_CLICK_LISTENER = new EmptyOnClickListener();
TextView smsamount;
// ---sends an SMS message---
private void sendSMS(String phoneNumber, String message) {
// just making 10 the default if the EditText has an invalid value
int amount = 10;
try {
amount = Integer.parseInt(smsamount.getText().toString());
} catch (NumberFormatException smsamount) {
// Ignore
}
sendSMS(phoneNumber, message, amount);
}
// ---sends an SMS message---
private void sendSMS(String phoneNumber, String message, int count) {
if (count >= 501) {
new AlertDialog.Builder(SMS.this).setTitle("Maximum amount of messages exceeded!")
.setMessage("Please enter 500 or less for the amount of messages")
.setNeutralButton("Ok", EMPTY_ON_CLICK_LISTENER).show();
// Quit early when we know we can't go any further.
return;
}
String SENT = "SMS_SENT";
PendingIntent sentPI = PendingIntent.getBroadcast(this, 0, new Intent(SENT), 0);
// ---when the SMS has been sent---
registerReceiver(new SmsSentBroadcastReceiver(getBaseContext()), new IntentFilter(SENT));
int delaySeconds = 3;
ScheduledExecutorService scheduler = new SmsScheduler().sendSmsMessages(phoneNumber, message, sentPI, delaySeconds,
count);
// You may cancel the scheduled messages with the scheduler.
// scheduler.shutdownNow();
new AlertDialog.Builder(SMS.this).setTitle("Attention!")
.setMessage("Your messages will start sending shortly, please do not press the send sms button again")
.setNeutralButton("Ok", EMPTY_ON_CLICK_LISTENER).show();
}
private static class SmsSentBroadcastReceiver extends BroadcastReceiver {
Context context;
public SmsSentBroadcastReceiver(Context context) {
this.context = context;
}
#Override
public void onReceive(Context arg0, Intent arg1) {
switch (getResultCode()) {
case Activity.RESULT_OK:
Toast.makeText(context, "SMS sent", Toast.LENGTH_SHORT).show();
break;
case SmsManager.RESULT_ERROR_GENERIC_FAILURE:
Toast.makeText(context, "Generic failure", Toast.LENGTH_SHORT).show();
break;
case SmsManager.RESULT_ERROR_NO_SERVICE:
Toast.makeText(context, "No service", Toast.LENGTH_SHORT).show();
break;
case SmsManager.RESULT_ERROR_NULL_PDU:
Toast.makeText(context, "Null PDU", Toast.LENGTH_SHORT).show();
break;
case SmsManager.RESULT_ERROR_RADIO_OFF:
Toast.makeText(context, "Radio off", Toast.LENGTH_SHORT).show();
break;
}
}
}
private static class EmptyOnClickListener implements OnClickListener {
public void onClick(DialogInterface dialog, int which) {
// Does nothing
}
}
private static class SmsScheduler {
public ScheduledExecutorService sendSmsMessages(final String phoneNumber, final String message,
final PendingIntent sentIntent, int count, int delaySeconds) {
final ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(1);
final SmsManager sms = SmsManager.getDefault();
// Create the task that will send a SMS message
final Runnable sender = new Runnable() {
public void run() {
sms.sendTextMessage(phoneNumber, null, message, sentIntent, null);
}
};
// Schedule the messages to be sent at intervals of delaySeconds.
for (int i = 0; i < count; i++) {
scheduler.schedule(sender, delaySeconds * i, TimeUnit.SECONDS);
}
return scheduler;
}
}
}