4
0
mirror of https://github.com/cwinfo/envayasms.git synced 2025-07-01 12:56:17 +00:00

use POST in http requests; use different url for outgoing sms; poll for outgoing sms more frequently; notify server when sms messages are sent

This commit is contained in:
Jesse Young
2011-09-11 01:35:10 -07:00
parent 448eafecd8
commit 9473ab1610
21 changed files with 952 additions and 474 deletions

158
src/org/envaya/kalsms/App.java Executable file
View File

@ -0,0 +1,158 @@
/*
* To change this template, choose Tools | Templates
* and open the template in the editor.
*/
package org.envaya.kalsms;
import android.app.PendingIntent;
import android.content.ContentValues;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.preference.PreferenceManager;
import android.telephony.SmsManager;
import android.util.Log;
/**
*
* @author Jesse
*/
public class App {
public static final int OUTGOING_POLL_SECONDS = 30;
public static final String LOG_NAME = "KALSMS";
public static final String LOG_INTENT = "org.envaya.kalsms.LOG";
public static final String SEND_STATUS_INTENT = "org.envaya.kalsms.SEND_STATUS";
public Context context;
public SharedPreferences settings;
public App(Context context)
{
this.context = context;
this.settings = PreferenceManager.getDefaultSharedPreferences(context);
}
static void debug(String msg)
{
Log.d(LOG_NAME, msg);
}
public void log(String msg)
{
Log.d(LOG_NAME, msg);
Intent broadcast = new Intent(App.LOG_INTENT);
broadcast.putExtra("message", msg);
context.sendBroadcast(broadcast);
}
public void logError(Throwable ex)
{
logError("ERROR", ex);
}
public void logError(String msg, Throwable ex)
{
logError(msg, ex, false);
}
public void logError(String msg, Throwable ex, boolean detail)
{
log(msg + ": " + ex.getClass().getName() + ": " + ex.getMessage());
if (detail)
{
for (StackTraceElement elem : ex.getStackTrace())
{
log(elem.getClassName() + ":" + elem.getMethodName() + ":" + elem.getLineNumber());
}
Throwable innerEx = ex.getCause();
if (innerEx != null)
{
logError("Inner exception:", innerEx, true);
}
}
}
public String getIncomingUrl()
{
return getServerUrl() + "/pg/receive_sms";
}
public String getOutgoingUrl()
{
return getServerUrl() + "/pg/dequeue_sms";
}
public String getSendStatusUrl()
{
return getServerUrl() + "/pg/sms_sent";
}
public String getServerUrl()
{
return settings.getString("server_url", "");
}
public String getPhoneNumber()
{
return settings.getString("phone_number", "");
}
public String getPassword()
{
return settings.getString("password", "");
}
private SQLiteDatabase db;
public SQLiteDatabase getWritableDatabase()
{
if (db == null)
{
db = new DBHelper(context).getWritableDatabase();
}
return db;
}
public void sendSMS(OutgoingSmsMessage sms)
{
String serverId = sms.getServerId();
if (serverId != null)
{
SQLiteDatabase db = this.getWritableDatabase();
Cursor cursor =
db.rawQuery("select 1 from sent_sms where server_id=?", new String[] { serverId });
boolean exists = (cursor.getCount() > 0);
cursor.close();
if (exists)
{
log(sms.getLogName() + " already sent, skipping");
return;
}
ContentValues values = new ContentValues();
values.put("server_id", serverId);
db.insert("sent_sms", null, values);
}
SmsManager smgr = SmsManager.getDefault();
Intent intent = new Intent(App.SEND_STATUS_INTENT);
intent.putExtra("serverId", serverId);
PendingIntent sentIntent = PendingIntent.getBroadcast(
this.context,
0,
intent,
PendingIntent.FLAG_ONE_SHOT);
log("Sending " +sms.getLogName() + " to " + sms.getTo());
smgr.sendTextMessage(sms.getTo(), null, sms.getMessage(), sentIntent, null);
}
}

View File

@ -0,0 +1,37 @@
/*
* To change this template, choose Tools | Templates
* and open the template in the editor.
*/
package org.envaya.kalsms;
import android.content.Context;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;
/**
*
* @author Jesse
*/
public class DBHelper extends SQLiteOpenHelper {
private static final int DATABASE_VERSION = 1;
private static final String DATABASE_NAME = "org.envaya.kalsms.db";
private static final String SENT_SMS_TABLE_CREATE =
"CREATE TABLE sent_sms (server_id text);"
+ "CREATE INDEX server_id_index ON sent_sms (server_id);";
public DBHelper(Context context) {
super(context, DATABASE_NAME, null, DATABASE_VERSION);
}
@Override
public void onCreate(SQLiteDatabase db) {
db.execSQL(SENT_SMS_TABLE_CREATE);
}
@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
}
}

View File

@ -0,0 +1,161 @@
package org.envaya.kalsms;
import android.app.Activity;
import android.app.PendingIntent;
import java.util.ArrayList;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
import android.telephony.SmsManager;
import android.telephony.SmsMessage;
import java.io.IOException;
import java.io.InputStream;
import java.util.List;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import org.apache.http.HttpResponse;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.NodeList;
import org.xml.sax.SAXException;
import org.apache.http.NameValuePair;
import org.apache.http.client.HttpClient;
import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.impl.client.DefaultHttpClient;
import org.apache.http.message.BasicNameValuePair;
public class IncomingMessageForwarder extends BroadcastReceiver {
private App app;
public List<OutgoingSmsMessage> sendMessageToServer(SmsMessage sms) {
String message = sms.getDisplayMessageBody();
String sender = sms.getDisplayOriginatingAddress();
String recipient = app.getPhoneNumber();
app.log("Received SMS from " + sender);
if (message == null || message.length() == 0) {
return new ArrayList<OutgoingSmsMessage>();
}
List<OutgoingSmsMessage> replies = new ArrayList<OutgoingSmsMessage>();
try {
List<NameValuePair> params = new ArrayList<NameValuePair>();
params.add(new BasicNameValuePair("from", sender));
params.add(new BasicNameValuePair("to", recipient));
params.add(new BasicNameValuePair("message", message));
params.add(new BasicNameValuePair("secret", app.getPassword()));
HttpClient client = new DefaultHttpClient();
HttpPost post = new HttpPost(app.getIncomingUrl());
post.setEntity(new UrlEncodedFormEntity(params));
app.log("Forwarding incoming SMS to server");
HttpResponse response = client.execute(post);
InputStream responseStream = response.getEntity().getContent();
DocumentBuilder xmlBuilder = DocumentBuilderFactory.newInstance().newDocumentBuilder();
Document xml = xmlBuilder.parse(responseStream);
NodeList smsNodes = xml.getElementsByTagName("Sms");
for (int i = 0; i < smsNodes.getLength(); i++) {
Element smsElement = (Element) smsNodes.item(i);
OutgoingSmsMessage reply = new OutgoingSmsMessage();
reply.setFrom(recipient);
reply.setTo(sender);
reply.setMessage(smsElement.getFirstChild().getNodeValue());
replies.add(reply);
}
} catch (SAXException ex) {
app.logError("Error parsing response from server while forwarding incoming message", ex);
} catch (IOException ex) {
app.logError("Error forwarding incoming message to server", ex);
} catch (ParserConfigurationException ex) {
app.logError("Error configuring XML parser", ex);
}
return replies;
}
public void smsReceived(Intent intent) {
for (SmsMessage sms : getMessagesFromIntent(intent)) {
List<OutgoingSmsMessage> replies = sendMessageToServer(sms);
for (OutgoingSmsMessage reply : replies)
{
app.sendSMS(reply);
}
//DeleteSMSFromInbox(context, mesg);
}
}
@Override
// source: http://www.devx.com/wireless/Article/39495/1954
public void onReceive(Context context, Intent intent) {
try {
this.app = new App(context);
String action = intent.getAction();
if (action.equals("android.provider.Telephony.SMS_RECEIVED")) {
smsReceived(intent);
}
} catch (Throwable ex) {
app.logError("Unexpected error in IncomingMessageForwarder", ex, true);
}
}
/*
private void DeleteSMSFromInbox(Context context, SmsMessage mesg) {
Log.d("KALSMS", "try to delete SMS");
try {
Uri uriSms = Uri.parse("content://sms/inbox");
StringBuilder sb = new StringBuilder();
sb.append("address='" + mesg.getOriginatingAddress() + "' AND ");
sb.append("body='" + mesg.getMessageBody() + "'");
Cursor c = context.getContentResolver().query(uriSms, null, sb.toString(), null, null);
c.moveToFirst();
int thread_id = c.getInt(1);
context.getContentResolver().delete(Uri.parse("content://sms/conversations/" + thread_id), null, null);
c.close();
} catch (Exception ex) {
// deletions don't work most of the time since the timing of the
// receipt and saving to the inbox
// makes it difficult to match up perfectly. the SMS might not be in
// the inbox yet when this receiver triggers!
Log.d("SmsReceiver", "Error deleting sms from inbox: " + ex.getMessage());
}
}
*/
// from http://github.com/dimagi/rapidandroid
// source: http://www.devx.com/wireless/Article/39495/1954
private SmsMessage[] getMessagesFromIntent(Intent intent) {
SmsMessage retMsgs[] = null;
Bundle bdl = intent.getExtras();
Object pdus[] = (Object[]) bdl.get("pdus");
retMsgs = new SmsMessage[pdus.length];
for (int n = 0; n < pdus.length; n++) {
byte[] byteData = (byte[]) pdus[n];
retMsgs[n] = SmsMessage.createFromPdu(byteData);
}
return retMsgs;
}
}

102
src/org/envaya/kalsms/Main.java Executable file
View File

@ -0,0 +1,102 @@
package org.envaya.kalsms;
import android.app.Activity;
import android.app.AlarmManager;
import android.app.PendingIntent;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.os.Bundle;
import android.os.SystemClock;
import android.preference.PreferenceManager;
import android.text.Html;
import android.text.method.ScrollingMovementMethod;
import android.view.Menu;
import android.widget.TextView;
public class Main extends Activity {
private BroadcastReceiver logReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
showLogMessage(intent.getExtras().getString("message"));
}
};
public void showLogMessage(String message)
{
TextView info = (TextView) Main.this.findViewById(R.id.info);
if (message != null)
{
info.append(message + "\n");
}
}
public void onResume() {
App.debug("RESUME");
super.onResume();
}
/** Called when the activity is first created. */
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
App.debug("STARTED");
setContentView(R.layout.main);
PreferenceManager.setDefaultValues(this, R.xml.prefs, false);
AlarmManager alarm = (AlarmManager) getSystemService(Context.ALARM_SERVICE);
PendingIntent pendingIntent = PendingIntent.getBroadcast(this,
0,
new Intent(this, OutgoingMessagePoller.class),
0);
alarm.setRepeating(
AlarmManager.ELAPSED_REALTIME_WAKEUP,
SystemClock.elapsedRealtime(),
App.OUTGOING_POLL_SECONDS * 1000,
pendingIntent);
App app = new App(this.getApplication());
TextView info = (TextView) this.findViewById(R.id.info);
info.setText(Html.fromHtml("<b>SMS Gateway running.</b><br />"));
showLogMessage("Server URL is: " + app.getServerUrl());
showLogMessage("Your phone number is: " + app.getPhoneNumber());
showLogMessage("Checking for outgoing messages every " + App.OUTGOING_POLL_SECONDS + " sec");
info.append(Html.fromHtml("<b>Press Menu to edit settings.</b><br />"));
info.setMovementMethod(new ScrollingMovementMethod());
IntentFilter logReceiverFilter = new IntentFilter();
logReceiverFilter.addAction(App.LOG_INTENT);
registerReceiver(logReceiver, logReceiverFilter);
}
// first time the Menu key is pressed
public boolean onCreateOptionsMenu(Menu menu) {
startActivity(new Intent(this, Prefs.class));
return(true);
}
// any other time the Menu key is pressed
public boolean onPrepareOptionsMenu(Menu menu) {
startActivity(new Intent(this, Prefs.class));
return(true);
}
@Override
protected void onStop(){
// dont do much with this, atm..
super.onStop();
}
}

View File

@ -0,0 +1,82 @@
/*
* To change this template, choose Tools | Templates
* and open the template in the editor.
*/
package org.envaya.kalsms;
import android.app.Activity;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.telephony.SmsManager;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.List;
import org.apache.http.HttpResponse;
import org.apache.http.NameValuePair;
import org.apache.http.client.HttpClient;
import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.impl.client.DefaultHttpClient;
import org.apache.http.message.BasicNameValuePair;
public class MessageStatusNotifier extends BroadcastReceiver {
private App app;
public void notifySuccess(String serverId)
{
if (serverId != null)
{
try {
app.log("Notifying server of sent SMS id=" + serverId);
List<NameValuePair> params = new ArrayList<NameValuePair>();
params.add(new BasicNameValuePair("from", app.getPhoneNumber()));
params.add(new BasicNameValuePair("secret", app.getPassword()));
params.add(new BasicNameValuePair("id", serverId));
HttpClient client = new DefaultHttpClient();
HttpPost post = new HttpPost(app.getSendStatusUrl());
post.setEntity(new UrlEncodedFormEntity(params));
client.execute(post);
}
catch (IOException ex)
{
app.logError("Error while notifying server of outgoing message", ex);
}
}
}
@Override
public void onReceive(Context context, Intent intent) {
app = new App(context);
String serverId = intent.getExtras().getString("serverId");
String desc = serverId == null ? "SMS reply" : ("SMS id=" + serverId);
switch (getResultCode()) {
case Activity.RESULT_OK:
app.log(desc + " sent successfully");
this.notifySuccess(serverId);
break;
case SmsManager.RESULT_ERROR_GENERIC_FAILURE:
app.log(desc + " could not be sent (generic failure)");
break;
case SmsManager.RESULT_ERROR_RADIO_OFF:
app.log(desc + " could not be sent (radio off)");
break;
case SmsManager.RESULT_ERROR_NO_SERVICE:
app.log(desc + " could not be sent (no service)");
break;
case SmsManager.RESULT_ERROR_NULL_PDU:
app.log(desc + " could not be sent (null PDU");
break;
default:
app.log("SMS could not be sent (unknown error)");
break;
}
}
}

View File

@ -0,0 +1,89 @@
package org.envaya.kalsms;
import java.util.ArrayList;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import java.io.IOException;
import java.io.InputStream;
import java.util.List;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import org.apache.http.HttpResponse;
import org.apache.http.NameValuePair;
import org.apache.http.client.HttpClient;
import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.impl.client.DefaultHttpClient;
import org.apache.http.message.BasicNameValuePair;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.NodeList;
import org.xml.sax.SAXException;
public class OutgoingMessagePoller extends BroadcastReceiver {
private App app;
@Override
public void onReceive(Context context, Intent intent) {
try
{
app = new App(context);
app.log("Checking for outgoing messages");
for (OutgoingSmsMessage sms : getOutgoingMessages())
{
app.sendSMS(sms);
}
}
catch (Throwable ex)
{
app.logError("Unexpected error in OutgoingMessagePoller", ex, true);
}
}
public List<OutgoingSmsMessage> getOutgoingMessages() {
List<OutgoingSmsMessage> messages = new ArrayList<OutgoingSmsMessage>();
try {
List<NameValuePair> params = new ArrayList<NameValuePair>();
params.add(new BasicNameValuePair("from", app.getPhoneNumber()));
params.add(new BasicNameValuePair("secret", app.getPassword()));
HttpClient client = new DefaultHttpClient();
HttpPost post = new HttpPost(app.getOutgoingUrl());
post.setEntity(new UrlEncodedFormEntity(params));
HttpResponse response = client.execute(post);
InputStream responseStream = response.getEntity().getContent();
DocumentBuilder xmlBuilder = DocumentBuilderFactory.newInstance().newDocumentBuilder();
Document xml = xmlBuilder.parse(responseStream);
NodeList smsNodes = xml.getElementsByTagName("Sms");
for (int i = 0; i < smsNodes.getLength(); i++) {
Element smsElement = (Element) smsNodes.item(i);
OutgoingSmsMessage sms = new OutgoingSmsMessage();
sms.setFrom(app.getPhoneNumber());
sms.setTo(smsElement.getAttribute("to"));
sms.setMessage(smsElement.getFirstChild().getNodeValue());
sms.setServerId(smsElement.getAttribute("id"));
messages.add(sms);
}
} catch (SAXException ex) {
app.logError("Error parsing response from server while retreiving outgoing messages", ex);
} catch (IOException ex) {
app.logError("Error retreiving outgoing messages from server", ex);
} catch (ParserConfigurationException ex) {
app.logError("Error configuring XML parser", ex);
}
return messages;
}
}

View File

@ -0,0 +1,60 @@
package org.envaya.kalsms;
public class OutgoingSmsMessage {
private String serverId;
private String message;
private String from;
private String to;
public OutgoingSmsMessage()
{
}
public String getLogName()
{
return (serverId == null) ? "SMS reply" : ("SMS id=" + serverId);
}
public String getServerId()
{
return serverId;
}
public void setServerId(String id)
{
this.serverId = id;
}
public String getMessage()
{
return message;
}
public void setMessage(String message)
{
this.message = message;
}
public String getFrom()
{
return from;
}
public void setFrom(String from)
{
this.from = from;
}
public String getTo()
{
return to;
}
public void setTo(String to)
{
this.to = to;
}
}

View File

@ -0,0 +1,71 @@
package org.envaya.kalsms;
import android.content.Intent;
import android.content.SharedPreferences;
import android.content.SharedPreferences.OnSharedPreferenceChangeListener;
import android.os.Bundle;
import android.preference.PreferenceActivity;
import android.view.Menu;
public class Prefs extends PreferenceActivity implements OnSharedPreferenceChangeListener {
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
addPreferencesFromResource(R.xml.prefs);
}
@Override
protected void onResume() {
super.onResume();
// Set up a listener whenever a key changes
getPreferenceScreen().getSharedPreferences().registerOnSharedPreferenceChangeListener(this);
}
@Override
protected void onPause() {
super.onPause();
// Unregister the listener whenever a key changes
getPreferenceScreen().getSharedPreferences().unregisterOnSharedPreferenceChangeListener(this);
}
public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) {
/*
Preference pref = findPreference(key);
if (pref instanceof EditTextPreference) {
EditTextPreference textPref = (EditTextPreference) pref;
pref.setSummary(textPref.getSummary());
Log.d("KALSMS", "textPref.getSummary(): " + textPref.getSummary());
}
if(pref instanceof CheckBoxPreference) {
CheckBoxPreference checkbox = (CheckBoxPreference) pref;
AlarmManager alarm = (AlarmManager) getSystemService(Context.ALARM_SERVICE);
Intent pintent = new Intent(this, SMSSender.class);
PendingIntent pIntent = PendingIntent.getBroadcast(this,0,pintent, 0);
if(checkbox.isChecked()) {
alarm.setInexactRepeating(AlarmManager.ELAPSED_REALTIME_WAKEUP,
SystemClock.elapsedRealtime(),
AlarmManager.INTERVAL_FIFTEEN_MINUTES, pIntent);
Log.d("KALSMS", "alarm manager turned on");
} else {
alarm.cancel(pIntent);
Log.d("SMS_GATEWAY", "alarm manager turned off");
}
}
*/
}
// first time the Menu key is pressed
@Override
public boolean onCreateOptionsMenu(Menu menu) {
startActivity(new Intent(this, Prefs.class));
return (true);
}
// any other time the Menu key is pressed
@Override
public boolean onPrepareOptionsMenu(Menu menu) {
startActivity(new Intent(this, Prefs.class));
return (true);
}
}