5
0
mirror of https://github.com/cwinfo/envayasms.git synced 2024-10-18 08:50:43 +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

28
AndroidManifest.xml Normal file → Executable file
View File

@ -1,6 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="kalsms.niryariv.itp"
package="org.envaya.kalsms"
android:versionCode="1"
android:versionName="1.0">
@ -19,18 +19,24 @@
</intent-filter>
</activity>
<receiver android:name=".SMSReceiver">
<intent-filter>
<action android:name="android.provider.Telephony.SMS_RECEIVED" />
</intent-filter>
</receiver>
<receiver android:name=".IncomingMessageForwarder">
<intent-filter>
<action android:name="android.provider.Telephony.SMS_RECEIVED" />
</intent-filter>
</receiver>
<receiver android:name=".SMSSender">
</receiver>
<receiver android:name=".OutgoingMessagePoller">
</receiver>
<activity android:name=".Prefs"
android:label="@string/app_name">
</activity>
<receiver android:name=".MessageStatusNotifier">
<intent-filter>
<action android:name="org.envaya.kalsms.SEND_STATUS" />
</intent-filter>
</receiver>
<activity android:name=".Prefs"
android:label="@string/app_name">
</activity>
</application>
</manifest>

17
build.properties Executable file
View File

@ -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.

79
build.xml Executable file
View File

@ -0,0 +1,79 @@
<?xml version="1.0" encoding="UTF-8"?>
<project name="KalSMS" default="help">
<!-- The local.properties file is created and updated by the 'android'
tool.
It contains the path to the SDK. It should *NOT* be checked into
Version Control Systems. -->
<property file="local.properties" />
<!-- The build.properties file can be created by you and is never touched
by the 'android' tool. This is the place to change some of the
default property values used by the Ant rules.
Here are some properties you may want to change/update:
source.dir
The name of the source directory. Default is 'src'.
out.dir
The name of the output directory. Default is 'bin'.
Properties related to the SDK location or the project target should
be updated using the 'android' tool with the 'update' action.
This file is an integral part of the build system for your
application and should be checked into Version Control Systems.
-->
<property file="build.properties" />
<!-- The default.properties file is created and updated by the 'android'
tool, as well as ADT.
This file is an integral part of the build system for your
application and should be checked into Version Control Systems. -->
<property file="default.properties" />
<!-- Required pre-setup import -->
<import file="${sdk.dir}/tools/ant/pre_setup.xml" />
<!-- extension targets. Uncomment the ones where you want to do custom work
in between standard targets -->
<!--
<target name="-pre-build">
</target>
<target name="-pre-compile">
</target>
[This is typically used for code obfuscation.
Compiled code location: ${out.classes.absolute.dir}
If this is not done in place, override ${out.dex.input.absolute.dir}]
<target name="-post-compile">
</target>
-->
<!-- Execute the Android Setup task that will setup some properties
specific to the target, and import the build rules files.
The rules file is imported from
<SDK>/tools/ant/
Depending on the project type it can be either:
- main_rules.xml
- lib_rules.xml
- test_rules.xml
To customize existing targets, there are two options:
- Customize only one target:
- copy/paste the target into this file, *before* the
<setup> task.
- customize it to your needs.
- Customize the whole script.
- copy/paste the content of the rules files (minus the top node)
into this file, *after* the <setup> task
- disable the import of the rules by changing the setup task
below to <setup import="false" />.
- customize to your needs.
-->
<setup />
</project>

10
local.properties Executable file
View File

@ -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

40
proguard.cfg Executable file
View File

@ -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 <methods>;
}
-keepclasseswithmembers class * {
public <init>(android.content.Context, android.util.AttributeSet);
}
-keepclasseswithmembers class * {
public <init>(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 *;
}

12
res/layout/main.xml Normal file → Executable file
View File

@ -3,11 +3,11 @@
android:orientation="vertical"
android:layout_width="fill_parent"
android:layout_height="fill_parent" android:background="#333333">
<TextView
<TextView
android:scrollbars="vertical"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="@string/hello"
android:textColor="#FFFFFF" android:textSize="30px" android:layout_margin="2px"/>
<TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:id="@+id/info" android:textColor="#FFFFFF" android:layout_margin="5px"></TextView>
<!--<Button android:layout_width="fill_parent" android:layout_height="wrap_content" android:gravity="center" android:id="@+id/settingsButton" android:text="Edit Settings" android:layout_marginRight="50px" android:layout_marginLeft="50px"></Button>-->
android:layout_height="fill_parent"
android:id="@+id/info"
android:textColor="#FFFFFF"
android:layout_margin="5px"></TextView>
</LinearLayout>

3
res/values/strings.xml Normal file → Executable file
View File

@ -1,5 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="hello">SMS Gateway Running.\n</string>
<string name="app_name">KalSMS</string>
<string name="app_name">KalSMS Envaya</string>
</resources>

43
res/xml/prefs.xml Normal file → Executable file
View File

@ -1,26 +1,27 @@
<?xml version="1.0" encoding="utf-8"?>
<PreferenceScreen
xmlns:android="http://schemas.android.com/apk/res/android">
<EditTextPreference
android:key="server_url"
android:title="Server URL"
android:defaultValue="http://192.168.70.1:3000/"
></EditTextPreference>
<EditTextPreference
android:key="pref_target_url"
android:title="Target URL"
android:summary="URL to load when the SMS is received"
android:defaultValue="http://qkhack.appspot.com/kalsms_demo"
></EditTextPreference>
<EditTextPreference
android:key="pref_identifier"
android:title="SMS Identifier"
android:summary="Handle only SMS messages starting with this string (leave empty to handle all messages)"
android:defaultValue="kal "
></EditTextPreference>
<CheckBoxPreference
android:key="pref_poll_switch"
android:title="Server Polling"
android:disableDependentsState="false"
android:summary="Poll target URL every 15 minutes (Note: increases power usage)"
></CheckBoxPreference>
<EditTextPreference
android:key="phone_number"
android:title="Your Phone Number"
android:defaultValue="16507993371"
></EditTextPreference>
<EditTextPreference
android:key="password"
android:title="Password"
android:password="true"
></EditTextPreference>
<CheckBoxPreference
android:key="detailed_log"
android:title="Detailed log messages?"
android:disableDependentsState="false"
></CheckBoxPreference>
</PreferenceScreen>

View File

@ -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 <b>" + this.identifier + "</b>";
}
infoText += " are now sent to <b>" + this.targetUrl +"</b> in the following format:";
infoText += "<p><tt>GET " + this.targetUrl + "?sender=&lt;phone#&gt;&msg=&lt;message&gt;</tt></p>";
infoText += "If the response body contains text, it will SMS back to the originating phone.";
if (this.polling) {
infoText += "<p>The target URL will be polled every 15 minutes (<i>note that polling increases power consumption</i>)</p>";
}
infoText += "<br /><br /><b>Press Menu to set SMS identifier or target URL.</b>";
infoText += "<p>Questions/feedback: niryariv@gmail.com</p>";
// 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();
}
}

View File

@ -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);
}
}

View File

@ -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<ArrayList<String>> 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;
}
}

View File

@ -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<ArrayList<String>> items = url.parseXML(resp);
url.sendMessages(items);
}
wake.release();
}
}

View File

@ -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<NameValuePair> qparams = new ArrayList<NameValuePair>();
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<ArrayList<String>> parseXML(String xml) {
ArrayList<ArrayList<String>> output = new ArrayList<ArrayList<String>>();
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<String> item = new ArrayList<String>();
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<String>) item);
}
} else if (nodeName.equalsIgnoreCase("sms-to-sender")) {
item.add("sender");
item.add(e.getFirstChild().getNodeValue());
output.add((ArrayList<String>) 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<ArrayList<String>> 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");
}
}
}
}

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);
}
}