5
0
mirror of https://github.com/cwinfo/envayasms.git synced 2024-09-16 12:39:35 +00:00

support for incoming MMS

This commit is contained in:
Jesse Young 2011-09-17 18:38:16 -07:00
parent 60d49414e1
commit d994b10c35
23 changed files with 787 additions and 159 deletions

View File

@ -33,19 +33,12 @@
android:label="@string/app_name">
</activity>
<receiver android:name=".receiver.SMSReceiver">
<receiver android:name=".receiver.SmsReceiver">
<intent-filter android:priority="101">
<action android:name="android.provider.Telephony.SMS_RECEIVED" />
</intent-filter>
</receiver>
<receiver android:name=".receiver.MMSReceiver">
<intent-filter android:priority="101">
<action android:name="android.provider.Telephony.WAP_PUSH_RECEIVED" />
<data android:mimeType="application/vnd.wap.mms-message" />
</intent-filter>
</receiver>
</receiver>
<receiver android:name=".receiver.MessageStatusNotifier">
</receiver>
@ -63,5 +56,8 @@
<action android:name="android.intent.action.BOOT_COMPLETED" />
</intent-filter>
</receiver>
<service android:name=".CheckMmsInboxService">
</service>
</application>
</manifest>

BIN
libs/httpmime-4.1.2.jar Executable file

Binary file not shown.

View File

@ -41,5 +41,6 @@
android:title="Keep new messages?"
android:summaryOff="Incoming SMS will not be stored in Messaging inbox"
android:summaryOn="Incoming SMS will be stored in Messaging inbox"
></CheckBoxPreference>
></CheckBoxPreference>
</PreferenceScreen>

View File

@ -10,9 +10,11 @@ import android.app.PendingIntent;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.net.Uri;
import android.os.SystemClock;
import android.preference.PreferenceManager;
import android.telephony.SmsManager;
import android.text.Html;
import android.text.SpannableStringBuilder;
import android.util.Log;
import java.text.DateFormat;
@ -26,24 +28,56 @@ public final class App extends Application {
public static final String ACTION_OUTGOING = "outgoing";
public static final String ACTION_INCOMING = "incoming";
public static final String ACTION_SEND_STATUS = "send_status";
public static final String STATUS_QUEUED = "queued";
public static final String STATUS_FAILED = "failed";
public static final String STATUS_SENT = "sent";
public static final String MESSAGE_TYPE_MMS = "mms";
public static final String MESSAGE_TYPE_SMS = "sms";
public static final String LOG_NAME = "KALSMS";
public static final String LOG_INTENT = "org.envaya.kalsms.LOG";
public static final int MAX_DISPLAYED_LOG = 15000;
public static final int LOG_TIMESTAMP_INTERVAL = 60000;
private long lastLogTime = 0;
private SpannableStringBuilder displayedLog = new SpannableStringBuilder();
private Map<String, IncomingMessage> incomingSmsMap = new HashMap<String, IncomingMessage>();
private Map<String, OutgoingMessage> outgoingSmsMap = new HashMap<String, OutgoingMessage>();
// Each QueuedMessage is identified within our internal Map by its Uri.
// Currently QueuedMessage instances are only available within KalSMS,
// (but they could be made available to other applications later via a ContentProvider)
public static final Uri CONTENT_URI = Uri.parse("content://org.envaya.kalsms");
public static final Uri INCOMING_URI = Uri.withAppendedPath(CONTENT_URI, "incoming");
public static final Uri OUTGOING_URI = Uri.withAppendedPath(CONTENT_URI, "outgoing");
private Map<Uri, IncomingMessage> incomingMessages = new HashMap<Uri, IncomingMessage>();
private Map<Uri, OutgoingMessage> outgoingMessages = new HashMap<Uri, OutgoingMessage>();
public SharedPreferences getSettings()
private SharedPreferences settings;
private MmsObserver mmsObserver;
private SpannableStringBuilder displayedLog = new SpannableStringBuilder();
private long lastLogTime;
private MmsUtils mmsUtils;
@Override
public void onCreate()
{
return PreferenceManager.getDefaultSharedPreferences(this);
}
super.onCreate();
settings = PreferenceManager.getDefaultSharedPreferences(this);
mmsUtils = new MmsUtils(this);
log(Html.fromHtml(
isEnabled() ? "<b>SMS gateway running.</b>" : "<b>SMS gateway disabled.</b>"));
log("Server URL is: " + getDisplayString(getServerUrl()));
log("Your phone number is: " + getDisplayString(getPhoneNumber()));
mmsObserver = new MmsObserver(this);
mmsObserver.register();
setOutgoingMessageAlarm();
}
public void checkOutgoingMessages()
{
@ -92,29 +126,29 @@ public final class App extends Application {
}
public String getServerUrl() {
return getSettings().getString("server_url", "");
return settings.getString("server_url", "");
}
public String getPhoneNumber() {
return getSettings().getString("phone_number", "");
return settings.getString("phone_number", "");
}
public int getOutgoingPollSeconds() {
return Integer.parseInt(getSettings().getString("outgoing_interval", "0"));
return Integer.parseInt(settings.getString("outgoing_interval", "0"));
}
public boolean isEnabled()
{
return getSettings().getBoolean("enabled", false);
return settings.getBoolean("enabled", false);
}
public boolean getKeepInInbox()
{
return getSettings().getBoolean("keep_in_inbox", false);
return settings.getBoolean("keep_in_inbox", false);
}
public String getPassword() {
return getSettings().getString("password", "");
return settings.getString("password", "");
}
private void notifyStatus(OutgoingMessage sms, String status, String errorMessage) {
@ -150,35 +184,45 @@ public final class App extends Application {
}
public synchronized int getStuckMessageCount() {
return outgoingSmsMap.size() + incomingSmsMap.size();
return outgoingMessages.size() + incomingMessages.size();
}
public synchronized void retryStuckOutgoingMessages() {
for (OutgoingMessage sms : outgoingSmsMap.values()) {
for (OutgoingMessage sms : outgoingMessages.values()) {
sms.retryNow();
}
}
public synchronized void retryStuckIncomingMessages() {
for (IncomingMessage sms : incomingSmsMap.values()) {
for (IncomingMessage sms : incomingMessages.values()) {
sms.retryNow();
}
}
public synchronized void setIncomingMessageStatus(IncomingMessage sms, boolean success) {
String id = sms.getId();
public synchronized void setIncomingMessageStatus(IncomingMessage message, boolean success) {
Uri uri = message.getUri();
if (success)
{
incomingSmsMap.remove(id);
incomingMessages.remove(uri);
if (message instanceof IncomingMms)
{
IncomingMms mms = (IncomingMms)message;
if (!getKeepInInbox())
{
log("Deleting MMS " + mms.getId() + " from inbox...");
mmsUtils.deleteFromInbox(mms);
}
}
}
else if (!sms.scheduleRetry())
else if (!message.scheduleRetry())
{
incomingSmsMap.remove(id);
incomingMessages.remove(uri);
}
}
public synchronized void notifyOutgoingMessageStatus(String id, int resultCode) {
OutgoingMessage sms = outgoingSmsMap.get(id);
public synchronized void notifyOutgoingMessageStatus(Uri uri, int resultCode) {
OutgoingMessage sms = outgoingMessages.get(uri);
if (sms == null) {
return;
@ -210,52 +254,52 @@ public final class App extends Application {
case SmsManager.RESULT_ERROR_RADIO_OFF:
case SmsManager.RESULT_ERROR_NO_SERVICE:
if (!sms.scheduleRetry()) {
outgoingSmsMap.remove(id);
outgoingMessages.remove(uri);
}
break;
default:
outgoingSmsMap.remove(id);
outgoingMessages.remove(uri);
break;
}
}
public synchronized void sendOutgoingMessage(OutgoingMessage sms) {
String id = sms.getId();
if (outgoingSmsMap.containsKey(id)) {
Uri uri = sms.getUri();
if (outgoingMessages.containsKey(uri)) {
log(sms.getLogName() + " already sent, skipping");
return;
}
outgoingSmsMap.put(id, sms);
outgoingMessages.put(uri, sms);
log("Sending " + sms.getLogName() + " to " + sms.getTo());
sms.trySend();
}
public synchronized void forwardToServer(IncomingMessage sms) {
String id = sms.getId();
public synchronized void forwardToServer(IncomingMessage message) {
Uri uri = message.getUri();
if (incomingSmsMap.containsKey(id)) {
log("Duplicate incoming SMS, skipping");
if (incomingMessages.containsKey(uri)) {
log("Duplicate incoming "+message.getDisplayType()+", skipping");
return;
}
incomingSmsMap.put(id, sms);
incomingMessages.put(uri, message);
log("Received SMS from " + sms.getFrom());
log("Received "+message.getDisplayType()+" from " + message.getFrom());
sms.tryForwardToServer();
message.tryForwardToServer();
}
public synchronized void retryIncomingMessage(String id) {
IncomingMessage sms = incomingSmsMap.get(id);
if (sms != null) {
sms.retryNow();
public synchronized void retryIncomingMessage(Uri uri) {
IncomingMessage message = incomingMessages.get(uri);
if (message != null) {
message.retryNow();
}
}
public synchronized void retryOutgoingMessage(String id) {
OutgoingMessage sms = outgoingSmsMap.get(id);
public synchronized void retryOutgoingMessage(Uri uri) {
OutgoingMessage sms = outgoingMessages.get(uri);
if (sms != null) {
sms.retryNow();
}
@ -265,7 +309,7 @@ public final class App extends Application {
Log.d(LOG_NAME, msg);
}
public void log(CharSequence msg)
public synchronized void log(CharSequence msg)
{
Log.d(LOG_NAME, msg.toString());
@ -303,7 +347,7 @@ public final class App extends Application {
sendBroadcast(broadcast);
}
public CharSequence getDisplayedLog()
public synchronized CharSequence getDisplayedLog()
{
return displayedLog;
}
@ -328,6 +372,10 @@ public final class App extends Application {
logError("Inner exception:", innerEx, true);
}
}
}
public MmsUtils getMmsUtils()
{
return mmsUtils;
}
}

View File

@ -0,0 +1,49 @@
package org.envaya.kalsms;
import android.app.IntentService;
import android.content.Intent;
import java.util.List;
public class CheckMmsInboxService extends IntentService
{
private App app;
private MmsUtils mmsUtils;
public CheckMmsInboxService(String name)
{
super(name);
}
public CheckMmsInboxService()
{
this("CheckMmsInboxService");
}
@Override
public void onCreate() {
super.onCreate();
app = (App)this.getApplicationContext();
mmsUtils = app.getMmsUtils();
}
@Override
protected void onHandleIntent(Intent intent)
{
List<IncomingMms> messages = mmsUtils.getMessagesInInbox();
for (IncomingMms mms : messages)
{
if (mmsUtils.isNewMms(mms))
{
// prevent forwarding MMS messages that existed in inbox
// before KalSMS started, or re-forwarding MMS multiple
// times if we don't delete them.
mmsUtils.markOldMms(mms);
app.forwardToServer(mms);
}
}
}
}

View File

@ -1,32 +1,21 @@
package org.envaya.kalsms;
import org.envaya.kalsms.task.ForwarderTask;
import org.envaya.kalsms.receiver.IncomingMessageRetry;
import android.content.Intent;
import android.net.Uri;
import android.telephony.SmsMessage;
import org.apache.http.message.BasicNameValuePair;
import org.envaya.kalsms.receiver.IncomingMessageRetry;
public class IncomingMessage extends QueuedMessage {
public abstract class IncomingMessage extends QueuedMessage {
public String from;
public String message;
public long timestampMillis;
public IncomingMessage(App app, SmsMessage sms) {
super(app);
this.from = sms.getOriginatingAddress();
this.message = sms.getMessageBody();
this.timestampMillis = sms.getTimestampMillis();
}
protected String from;
public IncomingMessage(App app, String from, String message, long timestampMillis) {
public IncomingMessage(App app, String from)
{
super(app);
this.from = from;
this.message = message;
this.timestampMillis = timestampMillis;
}
}
public abstract String getDisplayType();
public boolean isForwardable()
{
/*
@ -36,37 +25,21 @@ public class IncomingMessage extends QueuedMessage {
return from.length() > 5;
}
public String getMessageBody()
{
return message;
}
public String getFrom()
{
return from;
}
public String getId()
{
return from + ":" + message + ":" + timestampMillis;
}
public void retryNow() {
app.log("Retrying forwarding SMS from " + from);
app.log("Retrying forwarding message from " + from);
tryForwardToServer();
}
public void tryForwardToServer() {
new ForwarderTask(this,
new BasicNameValuePair("from", getFrom()),
new BasicNameValuePair("message", getMessageBody())
).execute();
}
}
protected Intent getRetryIntent() {
Intent intent = new Intent(app, IncomingMessageRetry.class);
intent.setData(Uri.parse("kalsms://incoming/" + this.getId()));
intent.setData(this.getUri());
return intent;
}
}
public abstract void tryForwardToServer();
}

View File

@ -0,0 +1,163 @@
package org.envaya.kalsms;
import android.net.Uri;
import java.io.IOException;
import java.util.ArrayList;
import org.json.*;
import java.util.List;
import org.apache.http.entity.mime.FormBodyPart;
import org.apache.http.entity.mime.content.ByteArrayBody;
import org.apache.http.entity.mime.content.ContentBody;
import org.apache.http.entity.mime.content.InputStreamBody;
import org.apache.http.message.BasicNameValuePair;
import org.envaya.kalsms.task.ForwarderTask;
public class IncomingMms extends IncomingMessage {
List<MmsPart> parts;
long id;
String contentLocation;
public IncomingMms(App app, String from, long id)
{
super(app, from);
this.parts = new ArrayList<MmsPart>();
this.id = id;
}
public String getDisplayType()
{
return "MMS";
}
public List<MmsPart> getParts()
{
return parts;
}
public void addPart(MmsPart part)
{
parts.add(part);
}
public long getId()
{
return id;
}
public String getContentLocation()
{
return contentLocation;
}
public void setContentLocation(String contentLocation)
{
this.contentLocation = contentLocation;
}
@Override
public String toString()
{
StringBuilder builder = new StringBuilder();
builder.append("MMS id=");
builder.append(id);
builder.append(" from=");
builder.append(from);
builder.append(":\n");
for (MmsPart part : parts)
{
builder.append(" ");
builder.append(part.toString());
builder.append("\n");
}
return builder.toString();
}
public void tryForwardToServer()
{
app.log("Forwarding MMS to server...");
List<FormBodyPart> formParts = new ArrayList<FormBodyPart>();
int i = 0;
String message = "";
JSONArray partsMetadata = new JSONArray();
for (MmsPart part : parts)
{
String formFieldName = "part" + i;
String text = part.getText();
String contentType = part.getContentType();
String partName = part.getName();
if ("text/plain".equals(contentType))
{
message = text;
}
ContentBody body;
if (text != null)
{
if (contentType != null)
{
contentType += "; charset=utf-8";
}
body = new ByteArrayBody(text.getBytes(), contentType, partName);
}
else
{
try
{
body = new InputStreamBody(part.openInputStream(),
contentType, partName);
}
catch (IOException ex)
{
app.logError("Error opening data for " + part.toString(), ex);
continue;
}
}
try
{
JSONObject partMetadata = new JSONObject();
partMetadata.put("name", formFieldName);
partMetadata.put("cid", part.getContentId());
partMetadata.put("type", part.getContentType());
partMetadata.put("filename", part.getName());
partsMetadata.put(partMetadata);
}
catch (JSONException ex)
{
app.logError("Error encoding MMS part metadata for " + part.toString(), ex);
continue;
}
formParts.add(new FormBodyPart(formFieldName, body));
i++;
}
ForwarderTask task = new ForwarderTask(this,
new BasicNameValuePair("from", getFrom()),
new BasicNameValuePair("message", message),
new BasicNameValuePair("message_type", App.MESSAGE_TYPE_MMS),
new BasicNameValuePair("mms_parts", partsMetadata.toString())
);
task.setFormParts(formParts);
task.execute();
}
public Uri getUri()
{
return Uri.withAppendedPath(App.INCOMING_URI, "mms/" + id);
}
}

View File

@ -0,0 +1,56 @@
package org.envaya.kalsms;
import android.net.Uri;
import android.telephony.SmsMessage;
import org.apache.http.message.BasicNameValuePair;
import org.envaya.kalsms.task.ForwarderTask;
public class IncomingSms extends IncomingMessage {
protected String message;
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();
}
// constructor for SMS retrieved from Messaging inbox
public IncomingSms(App app, String from, String message, long timestampMillis) {
super(app, from);
this.message = message;
this.timestampMillis = timestampMillis;
}
public String getMessageBody()
{
return message;
}
public String getDisplayType()
{
return "SMS";
}
public Uri getUri()
{
return Uri.withAppendedPath(App.INCOMING_URI,
"sms/" +
Uri.encode(from) + "/"
+ timestampMillis + "/" +
Uri.encode(message));
}
public void tryForwardToServer() {
new ForwarderTask(this,
new BasicNameValuePair("from", getFrom()),
new BasicNameValuePair("message_type", App.MESSAGE_TYPE_SMS),
new BasicNameValuePair("message", getMessageBody())
).execute();
}
}

View File

@ -0,0 +1,51 @@
package org.envaya.kalsms;
import android.app.IntentService;
import android.content.Intent;
import android.database.ContentObserver;
import android.os.Handler;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
final class MmsObserver extends ContentObserver {
private App app;
public MmsObserver(App app) {
super(new Handler());
this.app = app;
}
public void register()
{
/*
* Content observers can watch the MMS inbox URI for changes;
* This is the URL passed to PduPersister.persist by
* com.android.mms.transaction.RetrieveTransaction.run
*/
app.getContentResolver().registerContentObserver(
MmsUtils.OBSERVER_URI, true, this);
app.log("MMS content observer registered");
MmsUtils mmsUtils = app.getMmsUtils();
List<IncomingMms> messages = mmsUtils.getMessagesInInbox();
for (IncomingMms mms : messages)
{
mmsUtils.markOldMms(mms);
}
}
@Override
public void onChange(final boolean selfChange) {
super.onChange(selfChange);
if (!selfChange)
{
// check MMS inbox in an IntentService since it may be slow
// and we only want to do one check at a time
app.startService(new Intent(app, CheckMmsInboxService.class));
}
}
}

View File

@ -0,0 +1,113 @@
package org.envaya.kalsms;
import android.net.Uri;
import java.io.FileNotFoundException;
import java.io.InputStream;
public class MmsPart {
private App app;
private long partId;
private String contentType;
private String name;
private String text;
private String cid;
public MmsPart(App app, long partId)
{
this.app = app;
this.partId = partId;
}
/*
* The part id is the local value of the _id column in the MMS part table
* (see android.provider.Telephony.Part)
*/
public long getPartId()
{
return partId;
}
/*
* The content id of a MMS part is used to resolve references in SMIL
* like <img region="Image" src="cid:805"/> . Telephony.java claims
* that the cid column is an integer, but it is actually a string
* like "<0000>" or "<83>".
*/
public void setContentId(String cid)
{
this.cid = cid;
}
public String getContentId()
{
return cid;
}
/*
* Common Content-Type values for MMS parts include:
* application/smil
* text/plain
* image/jpeg
*/
public void setContentType(String contentType)
{
this.contentType = contentType;
}
public String getContentType()
{
return contentType;
}
/*
* The name of an MMS part is the filename of the original file sent
* (e.g. Image001.jpg). For text/SMIL parts, the filename is generated by the
* sending phone and can usually be ignored.
*/
public void setName(String name)
{
this.name = name;
}
public String getName()
{
return name;
}
/*
* The text is the content of text-based MMS parts (application/smil,
* text/plain, or text/html), and is null for multimedia parts.
*/
public void setText(String text)
{
this.text = text;
}
public String getText()
{
return text;
}
/*
* For multimedia parts, the _data column of the MMS Parts table contains the
* path on the Android filesystem containing that file, and openInputStream
* returns an InputStream for this file.
*/
public InputStream openInputStream() throws FileNotFoundException
{
return app.getContentResolver().openInputStream(getContentUri());
}
@Override
public String toString()
{
return "part " + partId + ": " + contentType + "; name=" + name + "; cid=" + cid;
}
public Uri getContentUri()
{
return Uri.parse("content://mms/part/" + partId);
}
}

View File

@ -0,0 +1,176 @@
package org.envaya.kalsms;
import android.content.ContentResolver;
import android.database.Cursor;
import android.net.Uri;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
/*
* Utilities for parsing IncomingMms from the MMS content provider tables,
* as defined by android.provider.Telephony
*
* Analogous to com.google.android.mms.pdu.PduPersister from
* core/java/com/google/android/mms/pdu in the base Android framework
* (https://github.com/android/platform_frameworks_base)
*/
public class MmsUtils
{
// constants from android.provider.Telephony
public static final Uri OBSERVER_URI = Uri.parse("content://mms-sms/");
public static final Uri INBOX_URI = Uri.parse("content://mms/inbox");
public static final Uri PART_URI = Uri.parse("content://mms/part");
// constants from com.google.android.mms.pdu.PduHeaders
private static final int PDU_HEADER_FROM = 0x89;
private static final int MESSAGE_TYPE_RETRIEVE_CONF = 0x84;
// todo -- prevent unbounded growth?
private final Set<String> seenMmsContentLocations = new HashSet<String>();
private App app;
private ContentResolver contentResolver;
public MmsUtils(App app)
{
this.app = app;
this.contentResolver = app.getContentResolver();
}
private List<MmsPart> getMmsParts(long id)
{
Cursor cur = contentResolver.query(PART_URI, new String[] {
"_id", "ct", "name", "text", "cid"
}, "mid = ?", new String[] { "" + id }, null);
// assume that if there is at least one part saved in database
// then MMS is fully delivered (this seems to be true in practice)
List<MmsPart> parts = new ArrayList<MmsPart>();
while (cur.moveToNext())
{
long partId = cur.getLong(0);
MmsPart part = new MmsPart(app, partId);
part.setContentType(cur.getString(1));
part.setName(cur.getString(2));
// todo interpret charset like com.google.android.mms.pdu.EncodedStringValue
part.setText(cur.getString(3));
part.setContentId(cur.getString(4));
parts.add(part);
}
cur.close();
return parts;
}
/*
* see com.google.android.mms.pdu.PduPersister.loadAddress
*/
private String getSenderNumber(long mmsId) {
Uri uri = Uri.parse("content://mms/"+mmsId+"/addr");
Cursor cur = contentResolver.query(uri,
new String[] { "address", "charset", "type" },
null, null, null);
String address = null;
while (cur.moveToNext())
{
int addrType = cur.getInt(2);
if (addrType == PDU_HEADER_FROM)
{
// todo interpret charset like com.google.android.mms.pdu.EncodedStringValue
address = cur.getString(0);
}
}
cur.close();
return address;
}
public List<IncomingMms> getMessagesInInbox()
{
// the M-Retrieve.conf messages are the 'actual' MMS messages
String m_type = "" + MESSAGE_TYPE_RETRIEVE_CONF;
Cursor c = contentResolver.query(INBOX_URI,
new String[] {"_id", "ct_l"},
"m_type = ? AND ct_l is not NULL", new String[] { m_type }, null);
List<IncomingMms> messages = new ArrayList<IncomingMms>();
while (c.moveToNext())
{
long id = c.getLong(0);
IncomingMms mms = new IncomingMms(app, getSenderNumber(id), id);
mms.setContentLocation(c.getString(1));
for (MmsPart part : getMmsParts(id))
{
mms.addPart(part);
}
messages.add(mms);
}
c.close();
return messages;
}
public boolean deleteFromInbox(IncomingMms mms)
{
String contentLocation = mms.getContentLocation();
int res;
if (contentLocation != null)
{
Uri uri = Uri.parse("content://mms/inbox");
/*
* Delete by content location (ct_l) rather than _id so that
* M-Notification.ind and M-Retrieve.conf messages are both deleted
* (otherwise it would remain in Messaging inbox with a Download button)
*/
res = contentResolver.delete(uri,
"ct_l = ?",
new String[] { contentLocation });
}
else
{
app.log("mms has no content-location");
Uri uri = Uri.parse("content://mms/inbox/" + mms.getId());
res = contentResolver.delete(uri, null, null);
}
app.log(res + " rows deleted");
return res > 0;
}
public synchronized void markOldMms(IncomingMms mms)
{
String contentLocation = mms.getContentLocation();
if (contentLocation != null)
{
seenMmsContentLocations.add(contentLocation);
}
}
public synchronized boolean isNewMms(IncomingMms mms)
{
String contentLocation = mms.getContentLocation();
return contentLocation != null && !seenMmsContentLocations.contains(contentLocation);
}
}

View File

@ -30,9 +30,9 @@ public class OutgoingMessage extends QueuedMessage {
return nextLocalId++;
}
public String getId()
public Uri getUri()
{
return (serverId == null) ? localId : serverId;
return Uri.withAppendedPath(App.OUTGOING_URI, ((serverId == null) ? localId : serverId));
}
public String getLogName()
@ -90,7 +90,7 @@ public class OutgoingMessage extends QueuedMessage {
SmsManager smgr = SmsManager.getDefault();
Intent intent = new Intent(app, MessageStatusNotifier.class);
intent.setData(Uri.parse("kalsms://outgoing/" + getId()));
intent.setData(this.getUri());
PendingIntent sentIntent = PendingIntent.getBroadcast(
app,
@ -103,7 +103,7 @@ public class OutgoingMessage extends QueuedMessage {
protected Intent getRetryIntent() {
Intent intent = new Intent(app, OutgoingMessageRetry.class);
intent.setData(Uri.parse("kalsms://outgoing/" + getId()));
intent.setData(this.getUri());
return intent;
}
}

View File

@ -4,6 +4,7 @@ import android.app.AlarmManager;
import android.app.PendingIntent;
import android.content.Context;
import android.content.Intent;
import android.net.Uri;
import android.os.SystemClock;
public abstract class QueuedMessage
@ -62,6 +63,8 @@ public abstract class QueuedMessage
return true;
}
public abstract Uri getUri();
public abstract void retryNow();

View File

@ -4,19 +4,12 @@ package org.envaya.kalsms.receiver;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import org.envaya.kalsms.App;
public class BootReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent)
{
App app = (App)context.getApplicationContext();
if (!app.isEnabled())
{
return;
}
app.setOutgoingMessageAlarm();
// just want to initialize App class to start outgoing message poll timer
}
}

View File

@ -12,6 +12,6 @@ public class IncomingMessageRetry extends BroadcastReceiver
public void onReceive(Context context, Intent intent)
{
App app = (App) context.getApplicationContext();
app.retryIncomingMessage(intent.getData().getLastPathSegment());
app.retryIncomingMessage(intent.getData());
}
}

View File

@ -1,27 +0,0 @@
/*
* Based on http://code.google.com/p/android-notifier/, copyright 2011 Rodrigo Damazio
* Licensed under the Apache License, Version 2.0
*/
package org.envaya.kalsms.receiver;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import org.envaya.kalsms.App;
public class MMSReceiver extends BroadcastReceiver {
private App app;
@Override
public void onReceive(Context context, Intent intent) {
app = (App) context.getApplicationContext();
if (!app.isEnabled()) {
return;
}
app.log("WAP Push received");
}
}

View File

@ -7,6 +7,7 @@ package org.envaya.kalsms.receiver;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.net.Uri;
import org.envaya.kalsms.App;
public class MessageStatusNotifier extends BroadcastReceiver {
@ -14,7 +15,7 @@ public class MessageStatusNotifier extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
App app = (App) context.getApplicationContext();
String id = intent.getData().getLastPathSegment();
Uri uri = intent.getData();
int resultCode = getResultCode();
@ -26,6 +27,6 @@ public class MessageStatusNotifier extends BroadcastReceiver {
}
*/
app.notifyOutgoingMessageStatus(id, resultCode);
app.notifyOutgoingMessageStatus(uri, resultCode);
}
}

View File

@ -12,6 +12,6 @@ public class OutgoingMessageRetry extends BroadcastReceiver
public void onReceive(Context context, Intent intent)
{
App app = (App) context.getApplicationContext();
app.retryOutgoingMessage(intent.getData().getLastPathSegment());
app.retryOutgoingMessage(intent.getData());
}
}

View File

@ -9,9 +9,10 @@ import java.util.ArrayList;
import java.util.List;
import org.envaya.kalsms.App;
import org.envaya.kalsms.IncomingMessage;
import org.envaya.kalsms.IncomingSms;
public class SMSReceiver extends BroadcastReceiver {
public class SmsReceiver extends BroadcastReceiver {
private App app;
@ -64,7 +65,7 @@ public class SMSReceiver extends BroadcastReceiver {
for (Object pdu : (Object[]) bundle.get("pdus"))
{
SmsMessage sms = SmsMessage.createFromPdu((byte[]) pdu);
messages.add(new IncomingMessage(app, sms));
messages.add(new IncomingSms(app, sms));
}
return messages;
}

View File

@ -16,7 +16,7 @@ public class ForwarderTask extends HttpTask {
params.add(new BasicNameValuePair("action", App.ACTION_INCOMING));
}
@Override
protected String getDefaultToAddress() {
return originalSms.getFrom();

View File

@ -14,7 +14,9 @@ import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
@ -22,6 +24,11 @@ import org.apache.http.HttpResponse;
import org.apache.http.client.HttpClient;
import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.entity.mime.FormBodyPart;
import org.apache.http.entity.mime.HttpMultipartMode;
import org.apache.http.entity.mime.MultipartEntity;
import org.apache.http.entity.mime.content.ContentBody;
import org.apache.http.entity.mime.content.StringBody;
import org.apache.http.impl.client.DefaultHttpClient;
import org.apache.http.message.BasicNameValuePair;
import org.apache.http.params.BasicHttpParams;
@ -42,6 +49,9 @@ public class HttpTask extends AsyncTask<String, Void, HttpResponse> {
protected String url;
protected List<BasicNameValuePair> params = new ArrayList<BasicNameValuePair>();
private List<FormBodyPart> formParts;
private boolean useMultipartPost = false;
public HttpTask(App app, BasicNameValuePair... paramsArr)
{
@ -52,7 +62,13 @@ public class HttpTask extends AsyncTask<String, Void, HttpResponse> {
params.add(new BasicNameValuePair("version", "2"));
params.add(new BasicNameValuePair("phone_number", app.getPhoneNumber()));
}
public void setFormParts(List<FormBodyPart> formParts)
{
useMultipartPost = true;
this.formParts = formParts;
}
public HttpClient getHttpClient()
{
HttpParams httpParameters = new BasicHttpParams();
@ -102,10 +118,29 @@ public class HttpTask extends AsyncTask<String, Void, HttpResponse> {
}
HttpPost post = new HttpPost(url);
post.setEntity(new UrlEncodedFormEntity(params));
if (useMultipartPost)
{
MultipartEntity entity = new MultipartEntity();//HttpMultipartMode.BROWSER_COMPATIBLE);
for (BasicNameValuePair param : params)
{
entity.addPart(param.getName(), new StringBody(param.getValue()));
}
for (FormBodyPart formPart : formParts)
{
entity.addPart(formPart);
}
post.setEntity(entity);
}
else
{
post.setEntity(new UrlEncodedFormEntity(params));
}
String signature = getSignature();
String signature = getSignature();
post.setHeader("X-Kalsms-Signature", signature);

View File

@ -11,6 +11,7 @@ import android.widget.ListView;
import android.widget.SimpleCursorAdapter;
import org.envaya.kalsms.App;
import org.envaya.kalsms.IncomingMessage;
import org.envaya.kalsms.IncomingSms;
import org.envaya.kalsms.R;
@ -74,7 +75,7 @@ public class ForwardInbox extends ListActivity {
String body = cur.getString(bodyIndex);
long date = cur.getLong(dateIndex);
IncomingMessage sms = new IncomingMessage(app, address, body, date);
IncomingMessage sms = new IncomingSms(app, address, body, date);
app.forwardToServer(sms);
}

View File

@ -3,9 +3,11 @@ package org.envaya.kalsms.ui;
import org.envaya.kalsms.task.HttpTask;
import android.app.Activity;
import android.content.BroadcastReceiver;
import android.content.ContentValues;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.net.Uri;
import android.os.Bundle;
import android.preference.PreferenceManager;
import android.text.Html;
@ -19,6 +21,8 @@ import android.widget.TextView;
import org.apache.http.HttpResponse;
import org.apache.http.message.BasicNameValuePair;
import org.envaya.kalsms.App;
import org.envaya.kalsms.IncomingMms;
import org.envaya.kalsms.MmsUtils;
import org.envaya.kalsms.R;
public class Main extends Activity {
@ -45,8 +49,6 @@ public class Main extends Activity {
app.log("Server connection OK!");
}
}
private long lastLogTime = 0;
public void updateLogView()
{
@ -81,14 +83,7 @@ public class Main extends Activity {
if (savedInstanceState == null)
{
app.log(Html.fromHtml(
app.isEnabled() ? "<b>SMS gateway running.</b>" : "<b>SMS gateway disabled.</b>"));
app.log("Server URL is: " + app.getDisplayString(app.getServerUrl()));
app.log("Your phone number is: " + app.getDisplayString(app.getPhoneNumber()) );
app.log(Html.fromHtml("<b>Press Menu to edit settings.</b>"));
app.setOutgoingMessageAlarm();
}
}
@ -99,7 +94,7 @@ public class Main extends Activity {
case R.id.settings:
startActivity(new Intent(this, Prefs.class));
return true;
case R.id.check_now:
case R.id.check_now:
app.checkOutgoingMessages();
return true;
case R.id.retry_now: