/*
* Copyright (c) 2010 - 2011, Sana
* All rights reserved.
* License: BSD New
*/
package org.sana.android.net;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.sana.R;
import org.sana.android.db.NotificationMessage;
import org.sana.android.provider.Notifications;
import android.app.Notification;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.content.BroadcastReceiver;
import android.content.ContentUris;
import android.content.ContentValues;
import android.content.Context;
import android.content.Intent;
import android.database.Cursor;
import android.net.Uri;
import android.os.Bundle;
import android.telephony.SmsMessage;
import android.util.Log;
import android.widget.Toast;
import com.google.gson.Gson;
import com.google.gson.JsonParseException;
/**
* SMSReceive handles the doctor->healthworker diagnosis exchange.
*
* The diagnosis from the physician comes to the phone via SMS. The body of the
* SMS has a Sana header that tells Sana, which listens for SMS messages, that
* the SMS is destined for it. Sana grabs the SMS and creates a custom Android
* notification from it. In addition, the diagnosis is filed in the phone's
* database of received notifications.
*
* @author Sana Development Team
*/
public class SMSReceive extends BroadcastReceiver {
private static final String TAG = SMSReceive.class.toString();
private static final String ACTION = "android.provider.Telephony.SMS_RECEIVED";
/**
* Automatically called when an SMS message is received. This method does
* four things: 1) It reads the incoming SMS to make sure it is well formed
* (it came from the dispatch server) 2) It stores the diagnosis
* notification in the notification database 3) It pops up a temporary
* message saying a diagnosis has been received 4) It inserts an alert into
* the status bar, clearing all previous alerts in the status bar
*
* A well-formed SMS message looks like this: <patient=422>Prescribe him
* antibiotics.
*/
public void onReceive(Context context, Intent intent) {
if (!intent.getAction().equals(ACTION))
return;
Bundle bundle = intent.getExtras();
if (bundle == null)
return;
Object[] pdus = (Object[]) bundle.get("pdus");
if (pdus == null)
return;
for (int i = 0; i < pdus.length; ++i) {
SmsMessage m = SmsMessage.createFromPdu((byte[]) pdus[i]);
Log.i(TAG, "Got message from" + m.getOriginatingAddress());
Log.i(TAG, m.toString());
processMessage(context, m);
}
}
private void processNotificationMessage(Context context,
MDSNotification notificationHeader, String message)
{
Gson g = new Gson();
if (notificationHeader.n == null) {
Log.e(TAG, "Received mal-formed notification UUID -- none provided.");
}
Cursor c = context.getContentResolver().query(Notifications.CONTENT_URI,
new String[] { Notifications.Contract._ID,
Notifications.Contract.PATIENT_ID,
Notifications.Contract.PROCEDURE_ID,
Notifications.Contract.MESSAGE },
Notifications.Contract.UUID+"=?",
new String[] { notificationHeader.n }, null);
ContentValues cv = new ContentValues();
String patientId = null;
if (notificationHeader.p != null) {
patientId = notificationHeader.p;
cv.put(Notifications.Contract.PATIENT_ID, notificationHeader.p);
}
if (notificationHeader.c != null) {
cv.put(Notifications.Contract.PROCEDURE_ID, notificationHeader.c);
}
Uri notificationUri;
boolean complete = false;
String fullMessage = "";
Pattern pattern = Pattern.compile("^(\\d+)/(\\d+)$");
if (c.moveToFirst()) {
// Notification already exists
int notificationId = c.getInt(c.getColumnIndex(
Notifications.Contract._ID));
String storedMessage = c.getString(c.getColumnIndexOrThrow(
Notifications.Contract.MESSAGE));
NotificationMessage m = g.fromJson(storedMessage,
NotificationMessage.class);
if (patientId == null) {
patientId = c.getString(c.getColumnIndex(
Notifications.Contract.PATIENT_ID));
}
if (notificationHeader.d != null) {
Matcher matcher = pattern.matcher(notificationHeader.d);
if (matcher.matches()) {
Integer current = Integer.parseInt(matcher.group(1));
Integer total = Integer.parseInt(matcher.group(2));
m.receivedMessages++;
assert(m.totalMessages == total);
m.messages.put(current, message);
if (m.totalMessages == m.receivedMessages) {
complete = true;
StringBuilder sbFullMessage = new StringBuilder();
for (int i = 1; i <= m.totalMessages; i++) {
sbFullMessage.append(m.messages.get(i));
}
fullMessage = sbFullMessage.toString();
cv.put(Notifications.Contract.FULL_MESSAGE, fullMessage);
cv.put(Notifications.Contract.DOWNLOADED, 1);
}
}
} else {
Log.e(TAG, "Received mal-formed Notification Message length: "
+ notificationHeader.d);
}
c.close();
storedMessage = g.toJson(m);
cv.put(Notifications.Contract.MESSAGE, storedMessage);
notificationUri = ContentUris.withAppendedId(
Notifications.CONTENT_URI, notificationId);
int rowsUpdated = context.getContentResolver().update(
notificationUri, cv, null, null);
if (rowsUpdated != 1) {
Log.e(TAG, "Failed updating notification URI: "
+ notificationUri);
}
} else {
// Notification is new, create one.
NotificationMessage m = new NotificationMessage();
if (notificationHeader.d != null) {
// This is a multipart message
Log.i(TAG, "Received multi-part SMS");
String parts = notificationHeader.d;
Matcher matcher = pattern.matcher(notificationHeader.d);
if (matcher.matches()) {
Integer current = Integer.parseInt(matcher.group(1));
Integer total = Integer.parseInt(matcher.group(2));
m.totalMessages = total;
m.receivedMessages = 1;
m.messages.put(current, message);
if (m.totalMessages == m.receivedMessages) {
complete = true;
StringBuilder sbFullMessage = new StringBuilder();
for (int i = 1; i <= m.totalMessages; i++) {
sbFullMessage.append(m.messages.get(i));
}
fullMessage = sbFullMessage.toString();
cv.put(Notifications.Contract.FULL_MESSAGE, fullMessage);
cv.put(Notifications.Contract.DOWNLOADED, 1);
}
} else {
Log.e(TAG, "Received malformed Notification Message length:"
+ " " + notificationHeader.d);
}
} else {
// This is a single message.
Log.i(TAG, "Received single-part SMS");
m.totalMessages = 1;
m.receivedMessages = 1;
m.messages.put(1, message);
cv.put(Notifications.Contract.FULL_MESSAGE, message);
cv.put(Notifications.Contract.DOWNLOADED, 1);
complete = true;
}
String storedMessage = g.toJson(m);
cv.put(Notifications.Contract.MESSAGE, storedMessage);
cv.put(Notifications.Contract.UUID,
notificationHeader.n);
notificationUri = context.getContentResolver().insert(
Notifications.CONTENT_URI, cv);
}
if (complete) {
// Show Toast that a notification was received
String notifHdr = "DIAGNOSIS RECEIVED\nPatient ID# "
+ patientId + "\n";
Toast.makeText(context, notifHdr, Toast.LENGTH_LONG).show();
Intent viewIntent = new Intent(Intent.ACTION_VIEW, notificationUri);
showNotification(context, "Patient ID# " + patientId, fullMessage,
viewIntent);
}
}
private void processMessage(Context context, SmsMessage m) {
String msg = m.getDisplayMessageBody();
// check if this is a patient diagnosis SMS
Log.i(TAG, "Got SMS message " + msg);
String leftCurly = new String(new byte[] { 0x1B, 0x28 });
String rightCurly = new String(new byte[] { 0x1b, 0x29 });
// Decode escapes
msg = msg.replace(leftCurly, "{").replace(rightCurly, "}");
Log.i(TAG, "Decode1: " + msg);
msg = msg.replace("?(", "{").replace("?)", "}");
Log.i(TAG, "Decode2: " + msg);
int lastRightBrace = msg.lastIndexOf('}');
if (lastRightBrace == -1) {
Log.i(TAG, "SMS not destined for Sana. Wrong header.");
return;
}
Gson gson = new Gson();
try {
String header = msg.substring(0, lastRightBrace+1);
String message = msg.substring(lastRightBrace+1);
MDSNotification notificationHeader = gson.fromJson(header,
MDSNotification.class);
processNotificationMessage(context, notificationHeader, message);
} catch (JsonParseException e) {
Log.i(TAG, "Could not parse Sana header in SMS: " + e.toString());
return;
} catch (Exception e) {
Log.e(TAG, e.toString());
return;
}
}
/**
* Creates a notification used when a patient diagnosis is received.
*
* @param c current context
* @param title the patient's ID number should be the title
* @param textMessage the message text sent from the doctor
* @param viewIntent
*/
private void showNotification(Context c, String title, String textMessage,
Intent viewIntent) {
// Look up the notification manager service
NotificationManager nm = (NotificationManager) c
.getSystemService(Context.NOTIFICATION_SERVICE);
// The PendingIntent launches the Notification Viewer for the particular
// alert
PendingIntent contentIntent = PendingIntent.getActivity(c, 0,
viewIntent, 0);
// The ticker text
String tickerText = "PATIENT DIAGNOSIS RECEIVED";
// Construct the Notification object.
Notification notif = new Notification(R.drawable.ic_notification, tickerText,
System.currentTimeMillis());
// Set the info for the views that show in the notification panel
//notif.setLatestEventInfo(c, title, textMessage, contentIntent);
// After a 100ms delay, vibrate for 200ms, pause for 100 ms and
// then vibrate for 300ms.
notif.vibrate = new long[] { 100, 200, 100, 300 };
// Use this line if you want a new persistent notification each time:
// nm.notify((int)Math.round((Math.random() * 32000)), notif);
// Or use this to overwrite the last notification each time:
nm.cancelAll();
nm.notify(1, notif);
}
}