From 6384942c576da93d9b81a27b4697461117f1f1aa Mon Sep 17 00:00:00 2001 From: Jesse Young Date: Fri, 23 Sep 2011 23:00:51 -0700 Subject: [PATCH] handle sending and receiving multipart sms messages > 160 chars --- AndroidManifest.xml | 15 +++++- server/php/EnvayaSMS.php | 2 + src/org/envaya/sms/App.java | 42 +++++++++++++--- src/org/envaya/sms/IncomingSms.java | 27 ++++++++-- src/org/envaya/sms/OutgoingMessage.java | 14 ++++-- .../sms/receiver/MessageDeliveryNotifier.java | 26 ++++++++++ .../sms/receiver/MessageStatusNotifier.java | 7 ++- .../sms/receiver/OutgoingSmsReceiver.java | 39 +++++++++++---- src/org/envaya/sms/receiver/SmsReceiver.java | 49 ++++++++++--------- src/org/envaya/sms/task/HttpTask.java | 8 ++- 10 files changed, 180 insertions(+), 49 deletions(-) create mode 100755 src/org/envaya/sms/receiver/MessageDeliveryNotifier.java diff --git a/AndroidManifest.xml b/AndroidManifest.xml index 12f3ae0..4accbfb 100755 --- a/AndroidManifest.xml +++ b/AndroidManifest.xml @@ -1,8 +1,8 @@ + android:versionCode="7" + android:versionName="2.0-beta 5"> @@ -56,6 +56,17 @@ + + diff --git a/server/php/EnvayaSMS.php b/server/php/EnvayaSMS.php index 42f9fd6..5e7366e 100755 --- a/server/php/EnvayaSMS.php +++ b/server/php/EnvayaSMS.php @@ -228,6 +228,7 @@ class EnvayaSMS_Action_SendStatus extends EnvayaSMS_Action { public $status; // EnvayaSMS::STATUS_* values public $id; // server ID previously used in EnvayaSMS_OutgoingMessage + public $error; // textual descrption of error (if applicable) function __construct($request) { @@ -235,5 +236,6 @@ class EnvayaSMS_Action_SendStatus extends EnvayaSMS_Action $this->type = EnvayaSMS::ACTION_SEND_STATUS; $this->status = $_POST['status']; $this->id = $_POST['id']; + $this->error = $_POST['error']; } } \ No newline at end of file diff --git a/src/org/envaya/sms/App.java b/src/org/envaya/sms/App.java index 11bb363..01af089 100755 --- a/src/org/envaya/sms/App.java +++ b/src/org/envaya/sms/App.java @@ -72,6 +72,10 @@ public final class App extends Application { // intent for MessageStatusNotifier to receive status updates for outgoing SMS // (even if sent by an expansion pack) public static final String MESSAGE_STATUS_INTENT = "org.envaya.sms.MESSAGE_STATUS"; + public static final String MESSAGE_DELIVERY_INTENT = "org.envaya.sms.MESSAGE_DELIVERY"; + + public static final String STATUS_EXTRA_INDEX = "status"; + public static final String STATUS_EXTRA_NUM_PARTS = "num_parts"; public static final int MAX_DISPLAYED_LOG = 4000; public static final int LOG_TIMESTAMP_INTERVAL = 60000; @@ -180,7 +184,7 @@ public final class App extends Application { } - public synchronized String chooseOutgoingSmsPackage() + public synchronized String chooseOutgoingSmsPackage(int numParts) { outgoingMessageCount++; @@ -211,9 +215,13 @@ public final class App extends Application { sent.remove(0); } - if ( (sent.size() + 1) <= OUTGOING_SMS_MAX_COUNT) + if ( (sent.size() + numParts) <= OUTGOING_SMS_MAX_COUNT) { - sent.add(ct); + // each part counts towards message limit + for (int j = 0; j < numParts; j++) + { + sent.add(ct); + } return packageName; } } @@ -422,12 +430,18 @@ public final class App extends Application { } } - public synchronized void notifyOutgoingMessageStatus(Uri uri, int resultCode) { + public synchronized void notifyOutgoingMessageStatus(Uri uri, int resultCode, int partIndex, int numParts) { OutgoingMessage sms = outgoingMessages.get(uri); if (sms == null) { return; } + + if (partIndex != 0) + { + // TODO: process message status for parts other than the first one + return; + } switch (resultCode) { case Activity.RESULT_OK: @@ -466,15 +480,31 @@ public final class App extends Application { public synchronized void sendOutgoingMessage(OutgoingMessage sms) { - if (isTestMode() && !isTestPhoneNumber(sms.getTo())) + String to = sms.getTo(); + if (to == null || to.length() == 0) + { + log("Ignoring outgoing SMS; destination is empty"); + return; + } + + if (isTestMode() && !isTestPhoneNumber(to)) { // this is mostly to prevent accidentally sending real messages to // random people while testing... - log("Ignoring outgoing SMS to " + sms.getTo()); + log("Ignoring outgoing SMS to " + to); return; } + String messageBody = sms.getMessageBody(); + + if (messageBody == null || messageBody.length() == 0) + { + log("Ignoring outgoing SMS; message body is empty"); + return; + } + + Uri uri = sms.getUri(); if (outgoingMessages.containsKey(uri)) { log("Duplicate outgoing " + sms.getLogName() + ", skipping"); diff --git a/src/org/envaya/sms/IncomingSms.java b/src/org/envaya/sms/IncomingSms.java index 388419a..bcf9e17 100755 --- a/src/org/envaya/sms/IncomingSms.java +++ b/src/org/envaya/sms/IncomingSms.java @@ -3,6 +3,8 @@ package org.envaya.sms; import android.net.Uri; import android.telephony.SmsMessage; +import java.security.InvalidParameterException; +import java.util.List; import org.apache.http.message.BasicNameValuePair; import org.envaya.sms.task.ForwarderTask; @@ -13,10 +15,27 @@ public class IncomingSms extends IncomingMessage { protected long timestampMillis; // constructor for SMS retrieved from android.provider.Telephony.SMS_RECEIVED intent - public IncomingSms(App app, SmsMessage sms) { - super(app, sms.getOriginatingAddress()); - this.message = sms.getMessageBody(); - this.timestampMillis = sms.getTimestampMillis(); + public IncomingSms(App app, List smsParts) throws InvalidParameterException { + super(app, smsParts.get(0).getOriginatingAddress()); + + this.message = smsParts.get(0).getMessageBody(); + + int numParts = smsParts.size(); + + for (int i = 1; i < numParts; i++) + { + SmsMessage smsPart = smsParts.get(i); + + if (!smsPart.getOriginatingAddress().equals(from)) + { + throw new InvalidParameterException( + "Tried to create IncomingSms from two different senders"); + } + + message = message + smsPart.getMessageBody(); + } + + this.timestampMillis = smsParts.get(0).getTimestampMillis(); } // constructor for SMS retrieved from Messaging inbox diff --git a/src/org/envaya/sms/OutgoingMessage.java b/src/org/envaya/sms/OutgoingMessage.java index deb3a7e..adf2e88 100755 --- a/src/org/envaya/sms/OutgoingMessage.java +++ b/src/org/envaya/sms/OutgoingMessage.java @@ -4,6 +4,8 @@ package org.envaya.sms; import org.envaya.sms.receiver.OutgoingMessageRetry; import android.content.Intent; import android.net.Uri; +import android.telephony.SmsManager; +import java.util.ArrayList; public class OutgoingMessage extends QueuedMessage { @@ -12,10 +14,11 @@ public class OutgoingMessage extends QueuedMessage { private String from; private String to; - private String localId; - + private String localId; private static int nextLocalId = 1; + + public OutgoingMessage(App app) { super(app); @@ -84,7 +87,10 @@ public class OutgoingMessage extends QueuedMessage { public void trySend() { - String packageName = app.chooseOutgoingSmsPackage(); + SmsManager smgr = SmsManager.getDefault(); + ArrayList bodyParts = smgr.divideMessage(getMessageBody()); + + String packageName = app.chooseOutgoingSmsPackage(bodyParts.size()); if (packageName == null) { @@ -94,7 +100,7 @@ public class OutgoingMessage extends QueuedMessage { Intent intent = new Intent(packageName + App.OUTGOING_SMS_INTENT_SUFFIX, this.getUri()); intent.putExtra(App.OUTGOING_SMS_EXTRA_TO, getTo()); - intent.putExtra(App.OUTGOING_SMS_EXTRA_BODY, getMessageBody()); + intent.putExtra(App.OUTGOING_SMS_EXTRA_BODY, bodyParts); app.sendBroadcast(intent, "android.permission.SEND_SMS"); } diff --git a/src/org/envaya/sms/receiver/MessageDeliveryNotifier.java b/src/org/envaya/sms/receiver/MessageDeliveryNotifier.java new file mode 100755 index 0000000..f069d70 --- /dev/null +++ b/src/org/envaya/sms/receiver/MessageDeliveryNotifier.java @@ -0,0 +1,26 @@ +package org.envaya.sms.receiver; + + +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.net.Uri; +import android.os.Bundle; +import org.envaya.sms.App; + +public class MessageDeliveryNotifier extends BroadcastReceiver { + + @Override + public void onReceive(Context context, Intent intent) { + App app = (App) context.getApplicationContext(); + Uri uri = intent.getData(); + + Bundle extras = intent.getExtras(); + int index = extras.getInt(App.STATUS_EXTRA_INDEX); + int numParts = extras.getInt(App.STATUS_EXTRA_NUM_PARTS); + + app.log("Message " + uri + " part "+index + "/" + numParts + " delivered"); + + // todo... could notify the server of message delivery + } +} diff --git a/src/org/envaya/sms/receiver/MessageStatusNotifier.java b/src/org/envaya/sms/receiver/MessageStatusNotifier.java index d1b4034..bca1b05 100755 --- a/src/org/envaya/sms/receiver/MessageStatusNotifier.java +++ b/src/org/envaya/sms/receiver/MessageStatusNotifier.java @@ -8,6 +8,7 @@ import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; import android.net.Uri; +import android.os.Bundle; import org.envaya.sms.App; public class MessageStatusNotifier extends BroadcastReceiver { @@ -16,6 +17,10 @@ public class MessageStatusNotifier extends BroadcastReceiver { public void onReceive(Context context, Intent intent) { App app = (App) context.getApplicationContext(); Uri uri = intent.getData(); + + Bundle extras = intent.getExtras(); + int index = extras.getInt(App.STATUS_EXTRA_INDEX); + int numParts = extras.getInt(App.STATUS_EXTRA_NUM_PARTS); int resultCode = getResultCode(); @@ -27,6 +32,6 @@ public class MessageStatusNotifier extends BroadcastReceiver { } */ - app.notifyOutgoingMessageStatus(uri, resultCode); + app.notifyOutgoingMessageStatus(uri, resultCode, index, numParts); } } diff --git a/src/org/envaya/sms/receiver/OutgoingSmsReceiver.java b/src/org/envaya/sms/receiver/OutgoingSmsReceiver.java index 6fb476a..873e8cf 100755 --- a/src/org/envaya/sms/receiver/OutgoingSmsReceiver.java +++ b/src/org/envaya/sms/receiver/OutgoingSmsReceiver.java @@ -1,12 +1,13 @@ package org.envaya.sms.receiver; +import org.envaya.sms.App; import android.app.PendingIntent; import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; import android.os.Bundle; import android.telephony.SmsManager; -import org.envaya.sms.App; +import java.util.ArrayList; public class OutgoingSmsReceiver extends BroadcastReceiver { @Override @@ -14,18 +15,38 @@ public class OutgoingSmsReceiver extends BroadcastReceiver { { Bundle extras = intent.getExtras(); String to = extras.getString(App.OUTGOING_SMS_EXTRA_TO); - String body = extras.getString(App.OUTGOING_SMS_EXTRA_BODY); + ArrayList bodyParts = extras.getStringArrayList(App.OUTGOING_SMS_EXTRA_BODY); SmsManager smgr = SmsManager.getDefault(); - - Intent statusIntent = new Intent(App.MESSAGE_STATUS_INTENT, intent.getData()); - - PendingIntent sentIntent = PendingIntent.getBroadcast( + + ArrayList sentIntents = new ArrayList(); + ArrayList deliveryIntents = new ArrayList(); + + int numParts = bodyParts.size(); + + for (int i = 0; i < numParts; i++) + { + Intent statusIntent = new Intent(App.MESSAGE_STATUS_INTENT, intent.getData()); + statusIntent.putExtra(App.STATUS_EXTRA_INDEX, i); + statusIntent.putExtra(App.STATUS_EXTRA_NUM_PARTS, numParts); + + sentIntents.add(PendingIntent.getBroadcast( context, 0, statusIntent, - PendingIntent.FLAG_ONE_SHOT); + PendingIntent.FLAG_ONE_SHOT)); - smgr.sendTextMessage(to, null, body, sentIntent, null); + Intent deliveryIntent = new Intent(App.MESSAGE_DELIVERY_INTENT, intent.getData()); + deliveryIntent.putExtra(App.STATUS_EXTRA_INDEX, i); + deliveryIntent.putExtra(App.STATUS_EXTRA_NUM_PARTS, numParts); + + deliveryIntents.add(PendingIntent.getBroadcast( + context, + 0, + deliveryIntent, + PendingIntent.FLAG_ONE_SHOT)); + } + + smgr.sendMultipartTextMessage(to, null, bodyParts, sentIntents, deliveryIntents); } -} +} \ No newline at end of file diff --git a/src/org/envaya/sms/receiver/SmsReceiver.java b/src/org/envaya/sms/receiver/SmsReceiver.java index d81573b..2448cfd 100755 --- a/src/org/envaya/sms/receiver/SmsReceiver.java +++ b/src/org/envaya/sms/receiver/SmsReceiver.java @@ -6,7 +6,9 @@ import android.content.Intent; import android.os.Bundle; import android.telephony.SmsMessage; import java.util.ArrayList; +import java.util.HashMap; import java.util.List; +import java.util.Map; import org.envaya.sms.App; import org.envaya.sms.IncomingMessage; import org.envaya.sms.IncomingSms; @@ -17,7 +19,6 @@ public class SmsReceiver extends BroadcastReceiver { private App app; @Override - // source: http://www.devx.com/wireless/Article/39495/1954 public void onReceive(Context context, Intent intent) { app = (App) context.getApplicationContext(); @@ -27,42 +28,46 @@ public class SmsReceiver extends BroadcastReceiver { } try { - boolean hasUnhandledMessage = false; + IncomingMessage sms = getMessageFromIntent(intent); + + if (sms.isForwardable()) + { + app.forwardToServer(sms); - for (IncomingMessage sms : getMessagesFromIntent(intent)) { - - if (sms.isForwardable()) - { - app.forwardToServer(sms); - } - else + if (!app.getKeepInInbox()) { - app.log("Ignoring incoming SMS from " + sms.getFrom()); - hasUnhandledMessage = true; - } + this.abortBroadcast(); + } } - - if (!hasUnhandledMessage && !app.getKeepInInbox()) + else { - this.abortBroadcast(); + app.log("Ignoring incoming SMS from " + sms.getFrom()); } } catch (Throwable ex) { app.logError("Unexpected error in SmsReceiver", ex, true); } } - // from http://github.com/dimagi/rapidandroid - // source: http://www.devx.com/wireless/Article/39495/1954 - private List getMessagesFromIntent(Intent intent) + private IncomingMessage getMessageFromIntent(Intent intent) { Bundle bundle = intent.getExtras(); - List messages = new ArrayList(); + // SMSDispatcher may send us multiple pdus from a multipart sms, + // in order (all in one broadcast though) + + // The comments in the gtalksms app indicate that we could get PDUs + // from multiple different senders at once, but I don't see how this + // could happen by looking at the SMSDispatcher source code... + // so I'm going to assume it doesn't happen and throw an exception if + // it does. + + List smsParts = new ArrayList(); + for (Object pdu : (Object[]) bundle.get("pdus")) { - SmsMessage sms = SmsMessage.createFromPdu((byte[]) pdu); - messages.add(new IncomingSms(app, sms)); + smsParts.add(SmsMessage.createFromPdu((byte[]) pdu)); } - return messages; + + return new IncomingSms(app, smsParts); } } \ No newline at end of file diff --git a/src/org/envaya/sms/task/HttpTask.java b/src/org/envaya/sms/task/HttpTask.java index cfd0315..525a8f9 100755 --- a/src/org/envaya/sms/task/HttpTask.java +++ b/src/org/envaya/sms/task/HttpTask.java @@ -217,7 +217,6 @@ public class HttpTask extends AsyncTask { try { handleResponse(response); - response.getEntity().consumeContent(); } catch (Throwable ex) { @@ -225,6 +224,13 @@ public class HttpTask extends AsyncTask { app.logError("Error processing server response", ex); handleFailure(); } + try + { + response.getEntity().consumeContent(); + } + catch (IOException ex) + { + } } else {