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

handle sending and receiving multipart sms messages > 160 chars

This commit is contained in:
Jesse Young 2011-09-23 23:00:51 -07:00
parent 1cf69e882a
commit 6384942c57
10 changed files with 180 additions and 49 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="6" android:versionCode="7"
android:versionName="2.0-beta 4"> android:versionName="2.0-beta 5">
<uses-sdk android:minSdkVersion="4" /> <uses-sdk android:minSdkVersion="4" />
@ -57,6 +57,17 @@
</intent-filter> </intent-filter>
</receiver> </receiver>
<!--
we don't really use message delivery notifications yet...
<receiver android:name=".receiver.MessageDeliveryNotifier" android:exported="true">
<intent-filter>
<action android:name="org.envaya.sms.MESSAGE_DELIVERY" />
<data android:scheme="content" />
</intent-filter>
</receiver>
-->
<receiver android:name=".receiver.OutgoingMessagePoller"> <receiver android:name=".receiver.OutgoingMessagePoller">
</receiver> </receiver>

View File

@ -228,6 +228,7 @@ class EnvayaSMS_Action_SendStatus extends EnvayaSMS_Action
{ {
public $status; // EnvayaSMS::STATUS_* values public $status; // EnvayaSMS::STATUS_* values
public $id; // server ID previously used in EnvayaSMS_OutgoingMessage public $id; // server ID previously used in EnvayaSMS_OutgoingMessage
public $error; // textual descrption of error (if applicable)
function __construct($request) function __construct($request)
{ {
@ -235,5 +236,6 @@ class EnvayaSMS_Action_SendStatus extends EnvayaSMS_Action
$this->type = EnvayaSMS::ACTION_SEND_STATUS; $this->type = EnvayaSMS::ACTION_SEND_STATUS;
$this->status = $_POST['status']; $this->status = $_POST['status'];
$this->id = $_POST['id']; $this->id = $_POST['id'];
$this->error = $_POST['error'];
} }
} }

View File

@ -72,6 +72,10 @@ public final class App extends Application {
// intent for MessageStatusNotifier to receive status updates for outgoing SMS // intent for MessageStatusNotifier to receive status updates for outgoing SMS
// (even if sent by an expansion pack) // (even if sent by an expansion pack)
public static final String MESSAGE_STATUS_INTENT = "org.envaya.sms.MESSAGE_STATUS"; 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 MAX_DISPLAYED_LOG = 4000;
public static final int LOG_TIMESTAMP_INTERVAL = 60000; 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++; outgoingMessageCount++;
@ -211,9 +215,13 @@ public final class App extends Application {
sent.remove(0); sent.remove(0);
} }
if ( (sent.size() + 1) <= OUTGOING_SMS_MAX_COUNT) if ( (sent.size() + numParts) <= OUTGOING_SMS_MAX_COUNT)
{
// each part counts towards message limit
for (int j = 0; j < numParts; j++)
{ {
sent.add(ct); sent.add(ct);
}
return packageName; return packageName;
} }
} }
@ -422,13 +430,19 @@ 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); OutgoingMessage sms = outgoingMessages.get(uri);
if (sms == null) { if (sms == null) {
return; return;
} }
if (partIndex != 0)
{
// TODO: process message status for parts other than the first one
return;
}
switch (resultCode) { switch (resultCode) {
case Activity.RESULT_OK: case Activity.RESULT_OK:
this.notifyStatus(sms, App.STATUS_SENT, ""); this.notifyStatus(sms, App.STATUS_SENT, "");
@ -466,15 +480,31 @@ public final class App extends Application {
public synchronized void sendOutgoingMessage(OutgoingMessage sms) { 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 // this is mostly to prevent accidentally sending real messages to
// random people while testing... // random people while testing...
log("Ignoring outgoing SMS to " + sms.getTo()); log("Ignoring outgoing SMS to " + to);
return; return;
} }
String messageBody = sms.getMessageBody();
if (messageBody == null || messageBody.length() == 0)
{
log("Ignoring outgoing SMS; message body is empty");
return;
}
Uri uri = sms.getUri(); Uri uri = sms.getUri();
if (outgoingMessages.containsKey(uri)) { if (outgoingMessages.containsKey(uri)) {
log("Duplicate outgoing " + sms.getLogName() + ", skipping"); log("Duplicate outgoing " + sms.getLogName() + ", skipping");

View File

@ -3,6 +3,8 @@ package org.envaya.sms;
import android.net.Uri; import android.net.Uri;
import android.telephony.SmsMessage; import android.telephony.SmsMessage;
import java.security.InvalidParameterException;
import java.util.List;
import org.apache.http.message.BasicNameValuePair; import org.apache.http.message.BasicNameValuePair;
import org.envaya.sms.task.ForwarderTask; import org.envaya.sms.task.ForwarderTask;
@ -13,10 +15,27 @@ public class IncomingSms extends IncomingMessage {
protected long timestampMillis; protected long timestampMillis;
// constructor for SMS retrieved from android.provider.Telephony.SMS_RECEIVED intent // constructor for SMS retrieved from android.provider.Telephony.SMS_RECEIVED intent
public IncomingSms(App app, SmsMessage sms) { public IncomingSms(App app, List<SmsMessage> smsParts) throws InvalidParameterException {
super(app, sms.getOriginatingAddress()); super(app, smsParts.get(0).getOriginatingAddress());
this.message = sms.getMessageBody();
this.timestampMillis = sms.getTimestampMillis(); 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 // constructor for SMS retrieved from Messaging inbox

View File

@ -4,6 +4,8 @@ package org.envaya.sms;
import org.envaya.sms.receiver.OutgoingMessageRetry; import org.envaya.sms.receiver.OutgoingMessageRetry;
import android.content.Intent; import android.content.Intent;
import android.net.Uri; import android.net.Uri;
import android.telephony.SmsManager;
import java.util.ArrayList;
public class OutgoingMessage extends QueuedMessage { public class OutgoingMessage extends QueuedMessage {
@ -13,9 +15,10 @@ public class OutgoingMessage extends QueuedMessage {
private String to; private String to;
private String localId; private String localId;
private static int nextLocalId = 1; private static int nextLocalId = 1;
public OutgoingMessage(App app) public OutgoingMessage(App app)
{ {
super(app); super(app);
@ -84,7 +87,10 @@ public class OutgoingMessage extends QueuedMessage {
public void trySend() public void trySend()
{ {
String packageName = app.chooseOutgoingSmsPackage(); SmsManager smgr = SmsManager.getDefault();
ArrayList<String> bodyParts = smgr.divideMessage(getMessageBody());
String packageName = app.chooseOutgoingSmsPackage(bodyParts.size());
if (packageName == null) 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 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_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"); app.sendBroadcast(intent, "android.permission.SEND_SMS");
} }

View File

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

View File

@ -8,6 +8,7 @@ import android.content.BroadcastReceiver;
import android.content.Context; import android.content.Context;
import android.content.Intent; import android.content.Intent;
import android.net.Uri; import android.net.Uri;
import android.os.Bundle;
import org.envaya.sms.App; import org.envaya.sms.App;
public class MessageStatusNotifier extends BroadcastReceiver { public class MessageStatusNotifier extends BroadcastReceiver {
@ -17,6 +18,10 @@ public class MessageStatusNotifier extends BroadcastReceiver {
App app = (App) context.getApplicationContext(); App app = (App) context.getApplicationContext();
Uri uri = intent.getData(); 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(); int resultCode = getResultCode();
// uncomment to test retry on outgoing message failure // uncomment to test retry on outgoing message failure
@ -27,6 +32,6 @@ public class MessageStatusNotifier extends BroadcastReceiver {
} }
*/ */
app.notifyOutgoingMessageStatus(uri, resultCode); app.notifyOutgoingMessageStatus(uri, resultCode, index, numParts);
} }
} }

View File

@ -1,12 +1,13 @@
package org.envaya.sms.receiver; package org.envaya.sms.receiver;
import org.envaya.sms.App;
import android.app.PendingIntent; import android.app.PendingIntent;
import android.content.BroadcastReceiver; import android.content.BroadcastReceiver;
import android.content.Context; import android.content.Context;
import android.content.Intent; import android.content.Intent;
import android.os.Bundle; import android.os.Bundle;
import android.telephony.SmsManager; import android.telephony.SmsManager;
import org.envaya.sms.App; import java.util.ArrayList;
public class OutgoingSmsReceiver extends BroadcastReceiver { public class OutgoingSmsReceiver extends BroadcastReceiver {
@Override @Override
@ -14,18 +15,38 @@ public class OutgoingSmsReceiver extends BroadcastReceiver {
{ {
Bundle extras = intent.getExtras(); Bundle extras = intent.getExtras();
String to = extras.getString(App.OUTGOING_SMS_EXTRA_TO); String to = extras.getString(App.OUTGOING_SMS_EXTRA_TO);
String body = extras.getString(App.OUTGOING_SMS_EXTRA_BODY); ArrayList<String> bodyParts = extras.getStringArrayList(App.OUTGOING_SMS_EXTRA_BODY);
SmsManager smgr = SmsManager.getDefault(); SmsManager smgr = SmsManager.getDefault();
Intent statusIntent = new Intent(App.MESSAGE_STATUS_INTENT, intent.getData()); ArrayList<PendingIntent> sentIntents = new ArrayList<PendingIntent>();
ArrayList<PendingIntent> deliveryIntents = new ArrayList<PendingIntent>();
PendingIntent sentIntent = PendingIntent.getBroadcast( 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, context,
0, 0,
statusIntent, 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);
} }
} }

View File

@ -6,7 +6,9 @@ import android.content.Intent;
import android.os.Bundle; import android.os.Bundle;
import android.telephony.SmsMessage; import android.telephony.SmsMessage;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map;
import org.envaya.sms.App; import org.envaya.sms.App;
import org.envaya.sms.IncomingMessage; import org.envaya.sms.IncomingMessage;
import org.envaya.sms.IncomingSms; import org.envaya.sms.IncomingSms;
@ -17,7 +19,6 @@ public class SmsReceiver extends BroadcastReceiver {
private App app; private App app;
@Override @Override
// source: http://www.devx.com/wireless/Article/39495/1954
public void onReceive(Context context, Intent intent) { public void onReceive(Context context, Intent intent) {
app = (App) context.getApplicationContext(); app = (App) context.getApplicationContext();
@ -27,42 +28,46 @@ public class SmsReceiver extends BroadcastReceiver {
} }
try { try {
boolean hasUnhandledMessage = false; IncomingMessage sms = getMessageFromIntent(intent);
for (IncomingMessage sms : getMessagesFromIntent(intent)) {
if (sms.isForwardable()) if (sms.isForwardable())
{ {
app.forwardToServer(sms); app.forwardToServer(sms);
if (!app.getKeepInInbox())
{
this.abortBroadcast();
}
} }
else else
{ {
app.log("Ignoring incoming SMS from " + sms.getFrom()); app.log("Ignoring incoming SMS from " + sms.getFrom());
hasUnhandledMessage = true;
}
}
if (!hasUnhandledMessage && !app.getKeepInInbox())
{
this.abortBroadcast();
} }
} catch (Throwable ex) { } catch (Throwable ex) {
app.logError("Unexpected error in SmsReceiver", ex, true); app.logError("Unexpected error in SmsReceiver", ex, true);
} }
} }
// from http://github.com/dimagi/rapidandroid private IncomingMessage getMessageFromIntent(Intent intent)
// source: http://www.devx.com/wireless/Article/39495/1954
private List<IncomingMessage> getMessagesFromIntent(Intent intent)
{ {
Bundle bundle = intent.getExtras(); Bundle bundle = intent.getExtras();
List<IncomingMessage> messages = new ArrayList<IncomingMessage>();
// 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<SmsMessage> smsParts = new ArrayList<SmsMessage>();
for (Object pdu : (Object[]) bundle.get("pdus")) for (Object pdu : (Object[]) bundle.get("pdus"))
{ {
SmsMessage sms = SmsMessage.createFromPdu((byte[]) pdu); smsParts.add(SmsMessage.createFromPdu((byte[]) pdu));
messages.add(new IncomingSms(app, sms));
} }
return messages;
return new IncomingSms(app, smsParts);
} }
} }

View File

@ -217,7 +217,6 @@ public class HttpTask extends AsyncTask<String, Void, HttpResponse> {
try try
{ {
handleResponse(response); handleResponse(response);
response.getEntity().consumeContent();
} }
catch (Throwable ex) catch (Throwable ex)
{ {
@ -225,6 +224,13 @@ public class HttpTask extends AsyncTask<String, Void, HttpResponse> {
app.logError("Error processing server response", ex); app.logError("Error processing server response", ex);
handleFailure(); handleFailure();
} }
try
{
response.getEntity().consumeContent();
}
catch (IOException ex)
{
}
} }
else else
{ {