5
0
mirror of https://github.com/cwinfo/envayasms.git synced 2024-11-09 10:20:25 +00:00

send new log messages to server on each HTTP request; notify server of changes in device status (currently power/battery state)

This commit is contained in:
Jesse Young 2011-10-10 16:19:38 -07:00
parent f253f54704
commit 2889bf9b4b
11 changed files with 259 additions and 31 deletions

View File

@ -1,8 +1,8 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android" <manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="org.envaya.sms" package="org.envaya.sms"
android:versionCode="13" android:versionCode="14"
android:versionName="2.0"> android:versionName="2.0.1">
<uses-sdk android:minSdkVersion="4" /> <uses-sdk android:minSdkVersion="4" />
@ -111,6 +111,15 @@
</intent-filter> </intent-filter>
</receiver> </receiver>
<receiver android:name=".receiver.DeviceStatusReceiver">
<intent-filter>
<action android:name="android.intent.action.ACTION_POWER_CONNECTED" />
<action android:name="android.intent.action.ACTION_POWER_DISCONNECTED" />
<action android:name="android.intent.action.BATTERY_LOW" />
<action android:name="android.intent.action.BATTERY_OKAY" />
</intent-filter>
</receiver>
<service android:name=".CheckMmsInboxService"> <service android:name=".CheckMmsInboxService">
</service> </service>

View File

@ -11,12 +11,18 @@ class EnvayaSMS
const ACTION_INCOMING = 'incoming'; const ACTION_INCOMING = 'incoming';
const ACTION_OUTGOING = 'outgoing'; const ACTION_OUTGOING = 'outgoing';
const ACTION_SEND_STATUS = 'send_status'; const ACTION_SEND_STATUS = 'send_status';
const ACTION_DEVICE_STATUS = 'device_status';
const ACTION_TEST = 'test'; const ACTION_TEST = 'test';
const STATUS_QUEUED = 'queued'; const STATUS_QUEUED = 'queued';
const STATUS_FAILED = 'failed'; const STATUS_FAILED = 'failed';
const STATUS_SENT = 'sent'; const STATUS_SENT = 'sent';
const DEVICE_STATUS_POWER_CONNECTED = "power_connected";
const DEVICE_STATUS_POWER_DISCONNECTED = "power_disconnected";
const DEVICE_STATUS_BATTERY_LOW = "battery_low";
const DEVICE_STATUS_BATTERY_OKAY = "battery_okay";
const MESSAGE_TYPE_SMS = 'sms'; const MESSAGE_TYPE_SMS = 'sms';
const MESSAGE_TYPE_MMS = 'mms'; const MESSAGE_TYPE_MMS = 'mms';
@ -49,11 +55,13 @@ class EnvayaSMS_Request
public $version; public $version;
public $phone_number; public $phone_number;
public $log;
function __construct() function __construct()
{ {
$this->version = $_POST['version']; $this->version = $_POST['version'];
$this->phone_number = $_POST['phone_number']; $this->phone_number = $_POST['phone_number'];
$this->log = @$_POST['log'];
} }
function get_action() function get_action()
@ -77,6 +85,8 @@ class EnvayaSMS_Request
return new EnvayaSMS_Action_SendStatus($this); return new EnvayaSMS_Action_SendStatus($this);
case EnvayaSMS::ACTION_TEST: case EnvayaSMS::ACTION_TEST:
return new EnvayaSMS_Action_Test($this); return new EnvayaSMS_Action_Test($this);
case EnvayaSMS::ACTION_DEVICE_STATUS:
return new EnvayaSMS_Action_DeviceStatus($this);
default: default:
return new EnvayaSMS_Action($this); return new EnvayaSMS_Action($this);
} }
@ -248,3 +258,15 @@ class EnvayaSMS_Action_SendStatus extends EnvayaSMS_Action
$this->error = $_POST['error']; $this->error = $_POST['error'];
} }
} }
class EnvayaSMS_Action_DeviceStatus extends EnvayaSMS_Action
{
public $status; // EnvayaSMS::DEVICE_STATUS_* values
function __construct($request)
{
parent::__construct($request);
$this->type = EnvayaSMS::ACTION_DEVICE_STATUS;
$this->status = $_POST['status'];
}
}

1
server/php/example/log/.gitignore vendored Normal file
View File

@ -0,0 +1 @@
*.log

View File

@ -22,6 +22,16 @@ if (!isset($password) || !$request->is_validated($password))
return; return;
} }
// append to EnvayaSMS app log
$app_log = $request->log;
if ($app_log)
{
$log_file = dirname(__DIR__)."/log/sms_".preg_replace('#[^\w]#', '', $request->phone_number).".log";
$f = fopen($log_file, "a");
fwrite($f, $app_log);
fclose($f);
}
$action = $request->get_action(); $action = $request->get_action();
switch ($action->type) switch ($action->type)
@ -79,6 +89,10 @@ switch ($action->type)
echo "invalid id"; echo "invalid id";
} }
return; return;
case EnvayaSMS::ACTION_DEVICE_STATUS:
error_log("device_status = {$action->status}");
echo "OK";
return;
case EnvayaSMS::ACTION_TEST: case EnvayaSMS::ACTION_TEST:
echo "OK"; echo "OK";
return; return;

View File

@ -1,6 +1,6 @@
<html> <html>
<head> <head>
<title>EnvayaSMS Request Simulator</title>
<style type='text/css'> <style type='text/css'>
body body
{ {
@ -30,10 +30,12 @@ body
<tr><th>Server URL</th><td><input id='server_url' type='text' size='40' /></td></tr> <tr><th>Server URL</th><td><input id='server_url' type='text' size='40' /></td></tr>
<tr><th>Phone Number</th><td><input id='phone_number' type='text' /></td></tr> <tr><th>Phone Number</th><td><input id='phone_number' type='text' /></td></tr>
<tr><th>Password</th><td><input id='password' type='password' /></td></tr> <tr><th>Password</th><td><input id='password' type='password' /></td></tr>
<tr><th>Log Messages</th><td><textarea id='log' style='width:250px'></textarea></td></tr>
<tr><th>Action</th><td><select id='action' onchange='actionChanged()' onkeypress='actionChanged()'> <tr><th>Action</th><td><select id='action' onchange='actionChanged()' onkeypress='actionChanged()'>
<option value='incoming'>incoming</option> <option value='incoming'>incoming</option>
<option value='outgoing'>outgoing</option> <option value='outgoing'>outgoing</option>
<option value='send_status'>send_status</option> <option value='send_status'>send_status</option>
<option value='device_status'>device_status</option>
<option value='test'>test</option> <option value='test'>test</option>
</select></td></tr> </select></td></tr>
</table> </table>
@ -46,7 +48,7 @@ body
<option value='sms'>sms</option> <option value='sms'>sms</option>
<option value='mms'>mms</option> <option value='mms'>mms</option>
</select></td></tr> </select></td></tr>
<tr><th>Message</th><td><textarea id='message'></textarea></td></tr> <tr><th>Message</th><td><textarea id='message' style='width:250px'></textarea></td></tr>
<tr><th>Timestamp</th><td><input id='timestamp' type='text' /></td></tr> <tr><th>Timestamp</th><td><input id='timestamp' type='text' /></td></tr>
</table> </table>
</div> </div>
@ -70,6 +72,17 @@ body
<h4>Parameters for action=test:</h4> <h4>Parameters for action=test:</h4>
(None) (None)
</div> </div>
<div id='action_device_status' style='display:none'>
<h4>Parameters for action=device_status:</h4>
<table class='smsTable'>
<tr><th>Status</th><td><select id='device_status'>
<option value='power_connected'>power_connected</option>
<option value='power_disconnected'>power_disconnected</option>
<option value='battery_low'>battery_low</option>
<option value='battery_okay'>battery_okay</option>
</select></td></tr>
</table>
</div>
<script type='text/javascript'> <script type='text/javascript'>
@ -100,6 +113,7 @@ function performAction() {
version: '13', version: '13',
phone_number: $('phone_number').value, phone_number: $('phone_number').value,
action: action, action: action,
log: $('log').value
}; };
if (action == 'incoming') if (action == 'incoming')
@ -115,6 +129,10 @@ function performAction() {
params.status = $('status').value; params.status = $('status').value;
params.error = $('error').value; params.error = $('error').value;
} }
else if (action == 'device_status')
{
params.status = $('device_status').value;
}
var xhr = (window.ActiveXObject && !window.XMLHttpRequest) ? new ActiveXObject("Msxml2.XMLHTTP") : new XMLHttpRequest(); var xhr = (window.ActiveXObject && !window.XMLHttpRequest) ? new ActiveXObject("Msxml2.XMLHTTP") : new XMLHttpRequest();

View File

@ -26,8 +26,10 @@ import java.text.DateFormat;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Date; import java.util.Date;
import java.util.HashMap; import java.util.HashMap;
import java.util.LinkedList;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Queue;
import org.apache.http.client.HttpClient; import org.apache.http.client.HttpClient;
import org.apache.http.conn.scheme.PlainSocketFactory; import org.apache.http.conn.scheme.PlainSocketFactory;
import org.apache.http.conn.scheme.Scheme; import org.apache.http.conn.scheme.Scheme;
@ -41,6 +43,7 @@ import org.apache.http.params.HttpParams;
import org.apache.http.params.HttpProtocolParams; import org.apache.http.params.HttpProtocolParams;
import org.envaya.sms.receiver.OutgoingMessagePoller; import org.envaya.sms.receiver.OutgoingMessagePoller;
import org.envaya.sms.receiver.ReenableWifiReceiver; import org.envaya.sms.receiver.ReenableWifiReceiver;
import org.envaya.sms.task.HttpTask;
import org.envaya.sms.task.PollerTask; import org.envaya.sms.task.PollerTask;
import org.json.JSONArray; import org.json.JSONArray;
import org.json.JSONException; import org.json.JSONException;
@ -50,12 +53,18 @@ public final class App extends Application {
public static final String ACTION_OUTGOING = "outgoing"; public static final String ACTION_OUTGOING = "outgoing";
public static final String ACTION_INCOMING = "incoming"; public static final String ACTION_INCOMING = "incoming";
public static final String ACTION_SEND_STATUS = "send_status"; public static final String ACTION_SEND_STATUS = "send_status";
public static final String ACTION_DEVICE_STATUS = "device_status";
public static final String ACTION_TEST = "test"; public static final String ACTION_TEST = "test";
public static final String STATUS_QUEUED = "queued"; public static final String STATUS_QUEUED = "queued";
public static final String STATUS_FAILED = "failed"; public static final String STATUS_FAILED = "failed";
public static final String STATUS_SENT = "sent"; public static final String STATUS_SENT = "sent";
public static final String DEVICE_STATUS_POWER_CONNECTED = "power_connected";
public static final String DEVICE_STATUS_POWER_DISCONNECTED = "power_disconnected";
public static final String DEVICE_STATUS_BATTERY_LOW = "battery_low";
public static final String DEVICE_STATUS_BATTERY_OKAY = "battery_okay";
public static final String MESSAGE_TYPE_MMS = "mms"; public static final String MESSAGE_TYPE_MMS = "mms";
public static final String MESSAGE_TYPE_SMS = "sms"; public static final String MESSAGE_TYPE_SMS = "sms";
@ -114,6 +123,8 @@ public final class App extends Application {
public final Inbox inbox = new Inbox(this); public final Inbox inbox = new Inbox(this);
public final Outbox outbox = new Outbox(this); public final Outbox outbox = new Outbox(this);
public final Queue<HttpTask> queuedTasks = new LinkedList<HttpTask>();
private SharedPreferences settings; private SharedPreferences settings;
private MmsObserver mmsObserver; private MmsObserver mmsObserver;
private SpannableStringBuilder displayedLog = new SpannableStringBuilder(); private SpannableStringBuilder displayedLog = new SpannableStringBuilder();
@ -427,10 +438,11 @@ public final class App extends Application {
public synchronized void retryStuckMessages() { public synchronized void retryStuckMessages() {
outbox.retryAll(); outbox.retryAll();
inbox.retryAll(); inbox.retryAll();
retryQueuedTasks();
} }
public synchronized int getPendingMessageCount() { public synchronized int getPendingTaskCount() {
return outbox.size() + inbox.size(); return outbox.size() + inbox.size() + queuedTasks.size();
} }
public void debug(String msg) { public void debug(String msg) {
@ -439,6 +451,22 @@ public final class App extends Application {
private int logEpoch = 0; private int logEpoch = 0;
private StringBuilder newLogBuffer = new StringBuilder();
public synchronized String getNewLogEntries()
{
String res = newLogBuffer.toString();
newLogBuffer.setLength(0);
return res;
}
// clients may sometimes unget log entries out of order,
// but most of the time this will be the right order
public synchronized void ungetNewLogEntries(String logEntries)
{
newLogBuffer.insert(0, logEntries);
}
public synchronized void log(CharSequence msg) public synchronized void log(CharSequence msg)
{ {
Log.d(LOG_NAME, msg.toString()); Log.d(LOG_NAME, msg.toString());
@ -462,6 +490,8 @@ public final class App extends Application {
logEpoch++; logEpoch++;
} }
int prevLength = displayedLog.length();
// display a timestamp in the log occasionally // display a timestamp in the log occasionally
long logTime = SystemClock.elapsedRealtime(); long logTime = SystemClock.elapsedRealtime();
if (logTime - lastLogTime > LOG_TIMESTAMP_INTERVAL) if (logTime - lastLogTime > LOG_TIMESTAMP_INTERVAL)
@ -474,6 +504,8 @@ public final class App extends Application {
displayedLog.append(msg); displayedLog.append(msg);
displayedLog.append("\n"); displayedLog.append("\n");
newLogBuffer.append(displayedLog, prevLength, displayedLog.length());
sendBroadcast(new Intent(App.LOG_CHANGED_INTENT)); sendBroadcast(new Intent(App.LOG_CHANGED_INTENT));
} }
@ -672,10 +704,21 @@ public final class App extends Application {
{ {
WifiManager wmgr = (WifiManager)getSystemService(Context.WIFI_SERVICE); WifiManager wmgr = (WifiManager)getSystemService(Context.WIFI_SERVICE);
if (activeNetwork != null)
{
log(activeNetwork.getTypeName() + "=" + activeNetwork.getState());
}
else
{
log("Not connected to any network.");
}
if (!wmgr.isWifiEnabled() && isNetworkFailoverEnabled()) if (!wmgr.isWifiEnabled() && isNetworkFailoverEnabled())
{ {
log("Enabling WIFI...");
wmgr.setWifiEnabled(true); wmgr.setWifiEnabled(true);
} }
return; return;
} }
@ -800,6 +843,27 @@ public final class App extends Application {
{ {
checkOutgoingMessages(); checkOutgoingMessages();
} }
// failed outgoing message status notifications are dropped...
retryQueuedTasks();
}
public synchronized void retryQueuedTasks()
{
while (true)
{
HttpTask task = queuedTasks.poll();
if (task == null)
{
break;
}
task.execute();
}
}
public synchronized void addQueuedTask(HttpTask task)
{
queuedTasks.add(task);
} }
} }

View File

@ -57,8 +57,8 @@ public class Outbox {
this.app = app; this.app = app;
} }
private void notifyMessageStatus(OutgoingMessage sms, String status, String errorMessage) { private void notifyMessageStatus(OutgoingMessage sms, final String status, final String errorMessage) {
String serverId = sms.getServerId(); final String serverId = sms.getServerId();
String logMessage; String logMessage;
if (status.equals(App.STATUS_SENT)) { if (status.equals(App.STATUS_SENT)) {
@ -73,12 +73,15 @@ public class Outbox {
if (serverId != null) { if (serverId != null) {
app.log("Notifying server " + smsDesc + " " + logMessage); app.log("Notifying server " + smsDesc + " " + logMessage);
new HttpTask(app, HttpTask task = new HttpTask(app,
new BasicNameValuePair("id", serverId), new BasicNameValuePair("id", serverId),
new BasicNameValuePair("status", status), new BasicNameValuePair("status", status),
new BasicNameValuePair("error", errorMessage), new BasicNameValuePair("error", errorMessage),
new BasicNameValuePair("action", App.ACTION_SEND_STATUS) new BasicNameValuePair("action", App.ACTION_SEND_STATUS)
).execute(); );
task.setRetryOnConnectivityError(true);
task.execute();
} else { } else {
app.log(smsDesc + " " + logMessage); app.log(smsDesc + " " + logMessage);
} }

View File

@ -0,0 +1,53 @@
package org.envaya.sms.receiver;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import org.apache.http.message.BasicNameValuePair;
import org.envaya.sms.App;
import org.envaya.sms.task.HttpTask;
public class DeviceStatusReceiver extends BroadcastReceiver
{
@Override
public void onReceive(Context context, Intent intent)
{
App app = (App) context.getApplicationContext();
if (!app.isEnabled())
{
return;
}
String action = intent.getAction();
String status = "";
if (Intent.ACTION_POWER_CONNECTED.equals(action))
{
status = App.DEVICE_STATUS_POWER_CONNECTED;
app.log("Power connected");
}
else if (Intent.ACTION_POWER_DISCONNECTED.equals(action))
{
status = App.DEVICE_STATUS_POWER_DISCONNECTED;
app.log("Power disconnected");
}
else if (Intent.ACTION_BATTERY_LOW.equals(action))
{
status = App.DEVICE_STATUS_BATTERY_LOW;
app.log("Battery low");
}
else if (Intent.ACTION_BATTERY_OKAY.equals(action))
{
status = App.DEVICE_STATUS_BATTERY_OKAY;
app.log("Battery okay");
}
HttpTask task = new HttpTask(app,
new BasicNameValuePair("action", App.ACTION_DEVICE_STATUS),
new BasicNameValuePair("status", status)
);
task.setRetryOnConnectivityError(true);
task.execute();
}
}

View File

@ -44,19 +44,32 @@ public class HttpTask extends AsyncTask<String, Void, HttpResponse> {
protected String url; protected String url;
protected List<BasicNameValuePair> params = new ArrayList<BasicNameValuePair>(); protected List<BasicNameValuePair> params = new ArrayList<BasicNameValuePair>();
protected BasicNameValuePair[] paramsArr;
private List<FormBodyPart> formParts; private List<FormBodyPart> formParts;
private boolean useMultipartPost = false; private boolean useMultipartPost = false;
private HttpPost post; private HttpPost post;
private String logEntries;
private boolean retryOnConnectivityError;
public HttpTask(App app, BasicNameValuePair... paramsArr) public HttpTask(App app, BasicNameValuePair... paramsArr)
{ {
super(); super();
this.app = app; this.app = app;
this.url = app.getServerUrl(); this.paramsArr = paramsArr;
params = new ArrayList<BasicNameValuePair>(Arrays.asList(paramsArr)); params = new ArrayList<BasicNameValuePair>(Arrays.asList(paramsArr));
params.add(new BasicNameValuePair("version", "" + app.getPackageInfo().versionCode)); }
params.add(new BasicNameValuePair("phone_number", app.getPhoneNumber()));
public void setRetryOnConnectivityError(boolean retry)
{
this.retryOnConnectivityError = retry;
}
protected HttpTask getCopy()
{
return new HttpTask(app, paramsArr);
} }
public void setFormParts(List<FormBodyPart> formParts) public void setFormParts(List<FormBodyPart> formParts)
@ -98,11 +111,19 @@ public class HttpTask extends AsyncTask<String, Void, HttpResponse> {
} }
protected HttpResponse doInBackground(String... ignored) { protected HttpResponse doInBackground(String... ignored) {
url = app.getServerUrl();
if (url.length() == 0) { if (url.length() == 0) {
app.log("Can't contact server; Server URL not set"); app.log("Can't contact server; Server URL not set");
return null; return null;
} }
logEntries = app.getNewLogEntries();
params.add(new BasicNameValuePair("version", "" + app.getPackageInfo().versionCode));
params.add(new BasicNameValuePair("phone_number", app.getPhoneNumber()));
params.add(new BasicNameValuePair("log", logEntries));
post = new HttpPost(url); post = new HttpPost(url);
try try
@ -145,6 +166,7 @@ public class HttpTask extends AsyncTask<String, Void, HttpResponse> {
else if (statusCode == 403) else if (statusCode == 403)
{ {
response.getEntity().consumeContent(); response.getEntity().consumeContent();
app.ungetNewLogEntries(logEntries);
app.log("Failed to authenticate to server"); app.log("Failed to authenticate to server");
app.log("(Phone number or password may be incorrect)"); app.log("(Phone number or password may be incorrect)");
return null; return null;
@ -152,6 +174,7 @@ public class HttpTask extends AsyncTask<String, Void, HttpResponse> {
else else
{ {
response.getEntity().consumeContent(); response.getEntity().consumeContent();
app.ungetNewLogEntries(logEntries);
app.log("Received HTTP " + statusCode + " from server"); app.log("Received HTTP " + statusCode + " from server");
return null; return null;
} }
@ -159,10 +182,16 @@ public class HttpTask extends AsyncTask<String, Void, HttpResponse> {
catch (IOException ex) catch (IOException ex)
{ {
post.abort(); post.abort();
app.ungetNewLogEntries(logEntries);
app.logError("Error while contacting server", ex); app.logError("Error while contacting server", ex);
if (ex instanceof UnknownHostException || ex instanceof SocketTimeoutException) if (ex instanceof UnknownHostException || ex instanceof SocketTimeoutException)
{ {
if (retryOnConnectivityError)
{
app.addQueuedTask(getCopy());
}
app.asyncCheckConnectivity(); app.asyncCheckConnectivity();
} }
return null; return null;
@ -170,6 +199,7 @@ public class HttpTask extends AsyncTask<String, Void, HttpResponse> {
catch (Throwable ex) catch (Throwable ex)
{ {
post.abort(); post.abort();
app.ungetNewLogEntries(logEntries);
app.logError("Unexpected error while contacting server", ex, true); app.logError("Unexpected error while contacting server", ex, true);
return null; return null;
} }

View File

@ -100,6 +100,13 @@ public class Main extends Activity {
registerReceiver(logReceiver, logReceiverFilter); registerReceiver(logReceiver, logReceiverFilter);
} }
@Override
public void onDestroy()
{
this.unregisterReceiver(logReceiver);
super.onDestroy();
}
@Override @Override
public boolean onOptionsItemSelected(MenuItem item) { public boolean onOptionsItemSelected(MenuItem item) {
// Handle item selection // Handle item selection
@ -140,9 +147,9 @@ public class Main extends Activity {
@Override @Override
public boolean onPrepareOptionsMenu(Menu menu) { public boolean onPrepareOptionsMenu(Menu menu) {
MenuItem retryItem = menu.findItem(R.id.retry_now); MenuItem retryItem = menu.findItem(R.id.retry_now);
int pendingMessages = app.getPendingMessageCount(); int pendingTasks = app.getPendingTaskCount();
retryItem.setEnabled(pendingMessages > 0); retryItem.setEnabled(pendingTasks > 0);
retryItem.setTitle("Retry All (" + pendingMessages + ")"); retryItem.setTitle("Retry All (" + pendingTasks + ")");
return true; return true;
} }

View File

@ -94,6 +94,13 @@ public class PendingMessages extends ListActivity {
refreshMessages(); refreshMessages();
} }
@Override
public void onDestroy()
{
this.unregisterReceiver(refreshReceiver);
super.onDestroy();
}
public void refreshMessages() public void refreshMessages()
{ {
final ArrayList<QueuedMessage> messages = new ArrayList<QueuedMessage>(); final ArrayList<QueuedMessage> messages = new ArrayList<QueuedMessage>();