From 9473ab1610bc39edc535a2706f396c6d32f6940f Mon Sep 17 00:00:00 2001 From: Jesse Young Date: Sun, 11 Sep 2011 01:35:10 -0700 Subject: [PATCH] use POST in http requests; use different url for outgoing sms; poll for outgoing sms more frequently; notify server when sms messages are sent --- AndroidManifest.xml | 28 +-- build.properties | 17 ++ build.xml | 79 +++++++++ local.properties | 10 ++ proguard.cfg | 40 +++++ res/layout/main.xml | 12 +- res/values/strings.xml | 3 +- res/xml/prefs.xml | 43 ++--- src/kalsms/niryariv/itp/Main.java | 91 ---------- src/kalsms/niryariv/itp/Prefs.java | 77 --------- src/kalsms/niryariv/itp/SMSReceiver.java | 96 ----------- src/kalsms/niryariv/itp/SMSSender.java | 39 ----- src/kalsms/niryariv/itp/TargetUrlRequest.java | 131 -------------- src/org/envaya/kalsms/App.java | 158 +++++++++++++++++ src/org/envaya/kalsms/DBHelper.java | 37 ++++ .../kalsms/IncomingMessageForwarder.java | 161 ++++++++++++++++++ src/org/envaya/kalsms/Main.java | 102 +++++++++++ .../envaya/kalsms/MessageStatusNotifier.java | 82 +++++++++ .../envaya/kalsms/OutgoingMessagePoller.java | 89 ++++++++++ src/org/envaya/kalsms/OutgoingSmsMessage.java | 60 +++++++ src/org/envaya/kalsms/Prefs.java | 71 ++++++++ 21 files changed, 952 insertions(+), 474 deletions(-) mode change 100644 => 100755 AndroidManifest.xml create mode 100755 build.properties create mode 100755 build.xml create mode 100755 local.properties create mode 100755 proguard.cfg mode change 100644 => 100755 res/layout/main.xml mode change 100644 => 100755 res/values/strings.xml mode change 100644 => 100755 res/xml/prefs.xml delete mode 100644 src/kalsms/niryariv/itp/Main.java delete mode 100755 src/kalsms/niryariv/itp/Prefs.java delete mode 100644 src/kalsms/niryariv/itp/SMSReceiver.java delete mode 100755 src/kalsms/niryariv/itp/SMSSender.java delete mode 100644 src/kalsms/niryariv/itp/TargetUrlRequest.java create mode 100755 src/org/envaya/kalsms/App.java create mode 100755 src/org/envaya/kalsms/DBHelper.java create mode 100755 src/org/envaya/kalsms/IncomingMessageForwarder.java create mode 100755 src/org/envaya/kalsms/Main.java create mode 100755 src/org/envaya/kalsms/MessageStatusNotifier.java create mode 100755 src/org/envaya/kalsms/OutgoingMessagePoller.java create mode 100755 src/org/envaya/kalsms/OutgoingSmsMessage.java create mode 100755 src/org/envaya/kalsms/Prefs.java diff --git a/AndroidManifest.xml b/AndroidManifest.xml old mode 100644 new mode 100755 index 7841597..7de0f06 --- a/AndroidManifest.xml +++ b/AndroidManifest.xml @@ -1,6 +1,6 @@ @@ -19,18 +19,24 @@ - - - - - + + + + + - - + + - - + + + + + + + + \ No newline at end of file diff --git a/build.properties b/build.properties new file mode 100755 index 0000000..ee52d86 --- /dev/null +++ b/build.properties @@ -0,0 +1,17 @@ +# This file is used to override default values used by the Ant build system. +# +# This file must be checked in Version Control Systems, as it is +# integral to the build system of your project. + +# This file is only used by the Ant script. + +# You can use this to override default values such as +# 'source.dir' for the location of your java source folder and +# 'out.dir' for the location of your output folder. + +# You can also use it define how the release builds are signed by declaring +# the following properties: +# 'key.store' for the location of your keystore and +# 'key.alias' for the name of the key to use. +# The password will be asked during the build when you use the 'release' target. + diff --git a/build.xml b/build.xml new file mode 100755 index 0000000..6f2493d --- /dev/null +++ b/build.xml @@ -0,0 +1,79 @@ + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/local.properties b/local.properties new file mode 100755 index 0000000..407948f --- /dev/null +++ b/local.properties @@ -0,0 +1,10 @@ +# This file is automatically generated by Android Tools. +# Do not modify this file -- YOUR CHANGES WILL BE ERASED! +# +# This file must *NOT* be checked in Version Control Systems, +# as it contains information specific to your local configuration. + +# location of the SDK. This is only used by Ant +# For customization when using a Version Control System, please read the +# header note. +sdk.dir=C:\\android-sdk diff --git a/proguard.cfg b/proguard.cfg new file mode 100755 index 0000000..f0b04dc --- /dev/null +++ b/proguard.cfg @@ -0,0 +1,40 @@ +-optimizationpasses 5 +-dontusemixedcaseclassnames +-dontskipnonpubliclibraryclasses +-dontpreverify +-verbose +-optimizations !code/simplification/arithmetic,!field/*,!class/merging/* + +-keep public class * extends android.app.Activity +-keep public class * extends android.app.Application +-keep public class * extends android.app.Service +-keep public class * extends android.content.BroadcastReceiver +-keep public class * extends android.content.ContentProvider +-keep public class * extends android.app.backup.BackupAgentHelper +-keep public class * extends android.preference.Preference +-keep public class com.android.vending.licensing.ILicensingService + +-keepclasseswithmembernames class * { + native ; +} + +-keepclasseswithmembers class * { + public (android.content.Context, android.util.AttributeSet); +} + +-keepclasseswithmembers class * { + public (android.content.Context, android.util.AttributeSet, int); +} + +-keepclassmembers class * extends android.app.Activity { + public void *(android.view.View); +} + +-keepclassmembers enum * { + public static **[] values(); + public static ** valueOf(java.lang.String); +} + +-keep class * implements android.os.Parcelable { + public static final android.os.Parcelable$Creator *; +} diff --git a/res/layout/main.xml b/res/layout/main.xml old mode 100644 new mode 100755 index 22272f0..634ae00 --- a/res/layout/main.xml +++ b/res/layout/main.xml @@ -3,11 +3,11 @@ android:orientation="vertical" android:layout_width="fill_parent" android:layout_height="fill_parent" android:background="#333333"> - - - + android:layout_height="fill_parent" + android:id="@+id/info" + android:textColor="#FFFFFF" + android:layout_margin="5px"> diff --git a/res/values/strings.xml b/res/values/strings.xml old mode 100644 new mode 100755 index a734339..b48bf11 --- a/res/values/strings.xml +++ b/res/values/strings.xml @@ -1,5 +1,4 @@ - SMS Gateway Running.\n - KalSMS + KalSMS Envaya diff --git a/res/xml/prefs.xml b/res/xml/prefs.xml old mode 100644 new mode 100755 index b39b361..e3d2c3c --- a/res/xml/prefs.xml +++ b/res/xml/prefs.xml @@ -1,26 +1,27 @@ + - - - - - - + + + + + \ No newline at end of file diff --git a/src/kalsms/niryariv/itp/Main.java b/src/kalsms/niryariv/itp/Main.java deleted file mode 100644 index 9188aac..0000000 --- a/src/kalsms/niryariv/itp/Main.java +++ /dev/null @@ -1,91 +0,0 @@ -package kalsms.niryariv.itp; - -import kalsms.niryariv.itp.R; -import android.app.Activity; -import android.content.Intent; -import android.content.SharedPreferences; -import android.os.Bundle; -import android.preference.PreferenceManager; -import android.text.Html; -import android.util.Log; -import android.view.Menu; -import android.widget.TextView; - -public class Main extends Activity { - -// public static final String PREFS_NAME = "KalPrefsFile"; - - public String identifier = ""; - public String targetUrl = ""; - public Boolean polling = false; - - public void onResume() { - Log.d("KALSMS", "RESUME"); - super.onResume(); - - SharedPreferences settings = PreferenceManager.getDefaultSharedPreferences(this); - - this.identifier = settings.getString("pref_identifier", ""); - this.targetUrl = settings.getString("pref_target_url", ""); - this.polling = settings.getBoolean("pref_poll_switch", false); - - Log.d("KALSMS", "onResume ident:" + this.identifier +"\ntarget:" + this.targetUrl); - - String infoText = new String(); - - // Home Screen text - infoText = "All SMS messages"; - - if (this.identifier.trim() != "") { - infoText += " starting with " + this.identifier + ""; - } - - infoText += " are now sent to " + this.targetUrl +" in the following format:"; - infoText += "

GET " + this.targetUrl + "?sender=<phone#>&msg=<message>

"; - infoText += "If the response body contains text, it will SMS back to the originating phone."; - - if (this.polling) { - infoText += "

The target URL will be polled every 15 minutes (note that polling increases power consumption)

"; - } - - infoText += "

Press Menu to set SMS identifier or target URL."; - - infoText += "

Questions/feedback: niryariv@gmail.com

"; - // END Home Screen text - - TextView info = (TextView) this.findViewById(R.id.info); - info.setText(Html.fromHtml(infoText)); - - } - - /** Called when the activity is first created. */ - @Override - public void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - setContentView(R.layout.main); - PreferenceManager.setDefaultValues(this, R.xml.prefs, false); - - Log.d("KALSMS", "STARTED"); - } - - - // 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(); - } - -} diff --git a/src/kalsms/niryariv/itp/Prefs.java b/src/kalsms/niryariv/itp/Prefs.java deleted file mode 100755 index 16fa71b..0000000 --- a/src/kalsms/niryariv/itp/Prefs.java +++ /dev/null @@ -1,77 +0,0 @@ -package kalsms.niryariv.itp; - -import kalsms.niryariv.itp.R; -import android.app.AlarmManager; -import android.app.PendingIntent; -import android.content.Context; -import android.content.Intent; -import android.content.SharedPreferences; -import android.content.SharedPreferences.OnSharedPreferenceChangeListener; -import android.os.Bundle; -import android.os.SystemClock; -import android.preference.CheckBoxPreference; -import android.preference.EditTextPreference; -import android.preference.Preference; -import android.preference.PreferenceActivity; -import android.util.Log; -import android.view.Menu; - - -public class Prefs extends PreferenceActivity implements OnSharedPreferenceChangeListener { - - - @Override - public void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - addPreferencesFromResource(R.xml.prefs); - } - - 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 - 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); - } -} - diff --git a/src/kalsms/niryariv/itp/SMSReceiver.java b/src/kalsms/niryariv/itp/SMSReceiver.java deleted file mode 100644 index 9791d50..0000000 --- a/src/kalsms/niryariv/itp/SMSReceiver.java +++ /dev/null @@ -1,96 +0,0 @@ -package kalsms.niryariv.itp; - -import java.util.ArrayList; -import android.content.BroadcastReceiver; -import android.content.Context; -import android.content.Intent; -import android.content.SharedPreferences; -import android.database.Cursor; -import android.net.Uri; -import android.os.Bundle; -import android.preference.PreferenceManager; -import android.telephony.SmsMessage; -import android.util.Log; - -public class SMSReceiver extends BroadcastReceiver { - - @Override - // source: http://www.devx.com/wireless/Article/39495/1954 - public void onReceive(Context context, Intent intent) { - if (!intent.getAction().equals("android.provider.Telephony.SMS_RECEIVED")) { - return; - } - - // get settings - SharedPreferences settings = PreferenceManager.getDefaultSharedPreferences(context); - TargetUrlRequest url = new TargetUrlRequest(); - - String identifier = settings.getString("pref_identifier", ""); - String targetUrl = settings.getString("pref_target_url", ""); - - SmsMessage msgs[] = getMessagesFromIntent(intent); - - for (int i = 0; i < msgs.length; i++) { - SmsMessage mesg = msgs[i]; - String message = mesg.getDisplayMessageBody(); - String sender = mesg.getDisplayOriginatingAddress(); - - if (message != null && message.length() > 0 - && (message.toLowerCase().startsWith(identifier) || identifier.trim() == "")) { - Log.d("KALSMS", "MSG RCVD:\"" + message + "\" from: " + sender); - - // send the message to the URL - String resp = url.openURL(sender, message, targetUrl, false).toString(); - Log.d("KALSMS", "RESP:\"" + resp); - - // SMS back the response - if (resp.trim().length() > 0) { - ArrayList> items = url.parseXML(resp); - url.sendMessages(items); - } - // delete SMS from inbox, to prevent it from filling up - DeleteSMSFromInbox(context, mesg); - } - } - } - - 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(); - try { - 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); - } - } catch (Exception e) { - Log.e("KALSMS", "GetMessages ERROR\n" + e); - } - return retMsgs; - } -} \ No newline at end of file diff --git a/src/kalsms/niryariv/itp/SMSSender.java b/src/kalsms/niryariv/itp/SMSSender.java deleted file mode 100755 index 774084c..0000000 --- a/src/kalsms/niryariv/itp/SMSSender.java +++ /dev/null @@ -1,39 +0,0 @@ -package kalsms.niryariv.itp; - -import java.util.ArrayList; -import android.content.BroadcastReceiver; -import android.content.Context; -import android.content.Intent; -import android.content.SharedPreferences; -import android.os.PowerManager; -import android.preference.PreferenceManager; -import android.util.Log; - -public class SMSSender extends BroadcastReceiver { - - @Override - public void onReceive(Context context, Intent intent) { - - // acquiring the wake clock to prevent device from sleeping while request is processed - final PowerManager pm = (PowerManager) context.getApplicationContext().getSystemService(Context.POWER_SERVICE); - PowerManager.WakeLock wake = pm.newWakeLock(PowerManager.SCREEN_DIM_WAKE_LOCK, "http_request"); - wake.acquire(); - - // get settings - SharedPreferences settings = PreferenceManager.getDefaultSharedPreferences(context); - String targetUrl = settings.getString("pref_target_url", ""); - Log.d("KALSMS", "url:\"" + targetUrl); - TargetUrlRequest url = new TargetUrlRequest(); - // send the message to the URL - String resp = url.openURL("","",targetUrl, true).toString(); - - Log.d("KALSMS", "RESP:\"" + resp); - - // SMS back the response - if (resp.trim().length() > 0) { - ArrayList> items = url.parseXML(resp); - url.sendMessages(items); - } - wake.release(); - } -} diff --git a/src/kalsms/niryariv/itp/TargetUrlRequest.java b/src/kalsms/niryariv/itp/TargetUrlRequest.java deleted file mode 100644 index f891d0e..0000000 --- a/src/kalsms/niryariv/itp/TargetUrlRequest.java +++ /dev/null @@ -1,131 +0,0 @@ -package kalsms.niryariv.itp; - -import java.io.StringReader; -import java.util.ArrayList; -import java.util.List; - -import javax.xml.parsers.DocumentBuilder; -import javax.xml.parsers.DocumentBuilderFactory; - -import org.apache.http.HttpEntity; -import org.apache.http.HttpResponse; -import org.apache.http.NameValuePair; -import org.apache.http.client.HttpClient; -import org.apache.http.client.methods.HttpGet; -import org.apache.http.client.utils.URLEncodedUtils; -import org.apache.http.impl.client.DefaultHttpClient; -import org.apache.http.message.BasicNameValuePair; -import org.apache.http.util.EntityUtils; -import org.w3c.dom.Document; -import org.w3c.dom.Element; -import org.w3c.dom.Node; -import org.w3c.dom.NodeList; -import org.xml.sax.InputSource; - -import android.telephony.SmsManager; -import android.util.Log; - -public class TargetUrlRequest { - - private String sender = ""; - - public String openURL(String sender, String message, String targetUrl, Boolean isPollRequest) { - - this.sender = sender; - - List qparams = new ArrayList(); - - if(sender.trim().length() > 0 && message.trim().length() > 0) { - qparams.add(new BasicNameValuePair("sender", sender)); - qparams.add(new BasicNameValuePair("msg", message)); - } else if (isPollRequest) { - qparams.add(new BasicNameValuePair("poll", "true")); - } - - String url = targetUrl + "?" + URLEncodedUtils.format(qparams, "UTF-8"); - - try { - HttpClient client = new DefaultHttpClient(); - HttpGet get = new HttpGet(url); - - HttpResponse responseGet = client.execute(get); - HttpEntity resEntityGet = responseGet.getEntity(); - if (resEntityGet != null) { - String resp = EntityUtils.toString(resEntityGet); - Log.e("KALSMS", "HTTP RESP" + resp); - return resp; - } - } catch (Exception e) { - Log.e("KALSMS", "HTTP REQ FAILED:" + url); - e.printStackTrace(); - } - return ""; - } - - public ArrayList> parseXML(String xml) { - ArrayList> output = new ArrayList>(); - - try { - DocumentBuilderFactory dbFactory = DocumentBuilderFactory.newInstance(); - DocumentBuilder dBuilder = dbFactory.newDocumentBuilder(); - - Document doc = dBuilder.parse(new InputSource(new StringReader(xml))); - - NodeList rnodes = doc.getElementsByTagName("reply"); - - NodeList nodes = rnodes.item(0).getChildNodes(); - - for (int i=0; i < nodes.getLength(); i++) { - try { - List item = new ArrayList(); - - Node node = nodes.item(i); - if (node.getNodeType() != Node.ELEMENT_NODE) continue; - - Element e = (Element) node; - String nodeName = e.getNodeName(); - - if (nodeName.equalsIgnoreCase("sms")) { - if (!e.getAttribute("phone").equals("")) { - item.add(e.getAttribute("phone")); - item.add(e.getFirstChild().getNodeValue()); - output.add((ArrayList) item); - } - } else if (nodeName.equalsIgnoreCase("sms-to-sender")) { - item.add("sender"); - item.add(e.getFirstChild().getNodeValue()); - output.add((ArrayList) item); - } else { - continue; - } - } catch (Exception e){ - Log.e("KALSMS", "FAILED PARSING XML NODE# " + i ); - } - } - Log.e("KALSMS", "PARSING XML RETURNS " + output ); - return (output); - - } catch (Exception e) { - Log.e("KALSMS", "PARSING XML FAILED: " + xml ); - e.printStackTrace(); - return (output); - } - } - - public void sendMessages(ArrayList> items) { - SmsManager smgr = SmsManager.getDefault(); - for (int j = 0; j < items.size(); j++) { - String sendTo = items.get(j).get(0); - if (sendTo.toLowerCase() == "sender") sendTo = this.sender; - String sendMsg = items.get(j).get(1); - try { - Log.d("KALSMS", "SEND MSG:\"" + sendMsg + "\" TO: " + sendTo); - smgr.sendTextMessage(sendTo, null, sendMsg, null, null); - } catch (Exception ex) { - Log.d("KALSMS", "SMS FAILED"); - } - } - } - -} - diff --git a/src/org/envaya/kalsms/App.java b/src/org/envaya/kalsms/App.java new file mode 100755 index 0000000..97866ed --- /dev/null +++ b/src/org/envaya/kalsms/App.java @@ -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); + } +} diff --git a/src/org/envaya/kalsms/DBHelper.java b/src/org/envaya/kalsms/DBHelper.java new file mode 100755 index 0000000..0710128 --- /dev/null +++ b/src/org/envaya/kalsms/DBHelper.java @@ -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) { + + } +} diff --git a/src/org/envaya/kalsms/IncomingMessageForwarder.java b/src/org/envaya/kalsms/IncomingMessageForwarder.java new file mode 100755 index 0000000..3eb2727 --- /dev/null +++ b/src/org/envaya/kalsms/IncomingMessageForwarder.java @@ -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 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(); + } + + List replies = new ArrayList(); + + try { + + List params = new ArrayList(); + 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 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; + } +} \ No newline at end of file diff --git a/src/org/envaya/kalsms/Main.java b/src/org/envaya/kalsms/Main.java new file mode 100755 index 0000000..a4e4e8f --- /dev/null +++ b/src/org/envaya/kalsms/Main.java @@ -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("SMS Gateway running.
")); + + 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("Press Menu to edit settings.
")); + + 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(); + } + +} \ No newline at end of file diff --git a/src/org/envaya/kalsms/MessageStatusNotifier.java b/src/org/envaya/kalsms/MessageStatusNotifier.java new file mode 100755 index 0000000..a2bad10 --- /dev/null +++ b/src/org/envaya/kalsms/MessageStatusNotifier.java @@ -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 params = new ArrayList(); + 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; + } + } +} diff --git a/src/org/envaya/kalsms/OutgoingMessagePoller.java b/src/org/envaya/kalsms/OutgoingMessagePoller.java new file mode 100755 index 0000000..f204cac --- /dev/null +++ b/src/org/envaya/kalsms/OutgoingMessagePoller.java @@ -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 getOutgoingMessages() { + List messages = new ArrayList(); + + try { + List params = new ArrayList(); + 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; + } + +} diff --git a/src/org/envaya/kalsms/OutgoingSmsMessage.java b/src/org/envaya/kalsms/OutgoingSmsMessage.java new file mode 100755 index 0000000..bed824a --- /dev/null +++ b/src/org/envaya/kalsms/OutgoingSmsMessage.java @@ -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; + } + +} diff --git a/src/org/envaya/kalsms/Prefs.java b/src/org/envaya/kalsms/Prefs.java new file mode 100755 index 0000000..4d00416 --- /dev/null +++ b/src/org/envaya/kalsms/Prefs.java @@ -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); + } +}