diff --git a/AndroidManifest.xml b/AndroidManifest.xml index 957994e..246e776 100755 --- a/AndroidManifest.xml +++ b/AndroidManifest.xml @@ -33,19 +33,12 @@ android:label="@string/app_name"> - + - - - - - - - - - + + @@ -63,5 +56,8 @@ + + + \ No newline at end of file diff --git a/libs/httpmime-4.1.2.jar b/libs/httpmime-4.1.2.jar new file mode 100755 index 0000000..eea3b3f Binary files /dev/null and b/libs/httpmime-4.1.2.jar differ diff --git a/res/xml/prefs.xml b/res/xml/prefs.xml index 68d0d66..5539904 100755 --- a/res/xml/prefs.xml +++ b/res/xml/prefs.xml @@ -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" - > + > + \ No newline at end of file diff --git a/src/org/envaya/kalsms/App.java b/src/org/envaya/kalsms/App.java index aebcad5..651019e 100755 --- a/src/org/envaya/kalsms/App.java +++ b/src/org/envaya/kalsms/App.java @@ -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 incomingSmsMap = new HashMap(); - private Map outgoingSmsMap = new HashMap(); + // 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 incomingMessages = new HashMap(); + private Map outgoingMessages = new HashMap(); - 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() ? "SMS gateway running." : "SMS gateway disabled.")); + + 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; } - } diff --git a/src/org/envaya/kalsms/CheckMmsInboxService.java b/src/org/envaya/kalsms/CheckMmsInboxService.java new file mode 100755 index 0000000..e808bff --- /dev/null +++ b/src/org/envaya/kalsms/CheckMmsInboxService.java @@ -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 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); + } + } + } +} \ No newline at end of file diff --git a/src/org/envaya/kalsms/IncomingMessage.java b/src/org/envaya/kalsms/IncomingMessage.java index 5c47c6a..85815c5 100755 --- a/src/org/envaya/kalsms/IncomingMessage.java +++ b/src/org/envaya/kalsms/IncomingMessage.java @@ -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(); } diff --git a/src/org/envaya/kalsms/IncomingMms.java b/src/org/envaya/kalsms/IncomingMms.java new file mode 100755 index 0000000..36b090e --- /dev/null +++ b/src/org/envaya/kalsms/IncomingMms.java @@ -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 parts; + long id; + String contentLocation; + + public IncomingMms(App app, String from, long id) + { + super(app, from); + this.parts = new ArrayList(); + this.id = id; + } + + public String getDisplayType() + { + return "MMS"; + } + + public List 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 formParts = new ArrayList(); + + 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); + } +} diff --git a/src/org/envaya/kalsms/IncomingSms.java b/src/org/envaya/kalsms/IncomingSms.java new file mode 100755 index 0000000..e2e9144 --- /dev/null +++ b/src/org/envaya/kalsms/IncomingSms.java @@ -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(); + } + +} diff --git a/src/org/envaya/kalsms/MmsObserver.java b/src/org/envaya/kalsms/MmsObserver.java new file mode 100755 index 0000000..713647d --- /dev/null +++ b/src/org/envaya/kalsms/MmsObserver.java @@ -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 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)); + } + } +} diff --git a/src/org/envaya/kalsms/MmsPart.java b/src/org/envaya/kalsms/MmsPart.java new file mode 100755 index 0000000..fe38bae --- /dev/null +++ b/src/org/envaya/kalsms/MmsPart.java @@ -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 . 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); + } + +} diff --git a/src/org/envaya/kalsms/MmsUtils.java b/src/org/envaya/kalsms/MmsUtils.java new file mode 100755 index 0000000..6027d84 --- /dev/null +++ b/src/org/envaya/kalsms/MmsUtils.java @@ -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 seenMmsContentLocations = new HashSet(); + + private App app; + private ContentResolver contentResolver; + + public MmsUtils(App app) + { + this.app = app; + this.contentResolver = app.getContentResolver(); + } + + private List 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 parts = new ArrayList(); + + 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 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 messages = new ArrayList(); + + 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); + } +} diff --git a/src/org/envaya/kalsms/OutgoingMessage.java b/src/org/envaya/kalsms/OutgoingMessage.java index 67ae0d1..98c5e4f 100755 --- a/src/org/envaya/kalsms/OutgoingMessage.java +++ b/src/org/envaya/kalsms/OutgoingMessage.java @@ -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; } } diff --git a/src/org/envaya/kalsms/QueuedMessage.java b/src/org/envaya/kalsms/QueuedMessage.java index 1f02c96..59915c4 100755 --- a/src/org/envaya/kalsms/QueuedMessage.java +++ b/src/org/envaya/kalsms/QueuedMessage.java @@ -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(); diff --git a/src/org/envaya/kalsms/receiver/BootReceiver.java b/src/org/envaya/kalsms/receiver/BootReceiver.java index 4c3c527..f78327d 100755 --- a/src/org/envaya/kalsms/receiver/BootReceiver.java +++ b/src/org/envaya/kalsms/receiver/BootReceiver.java @@ -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 } } diff --git a/src/org/envaya/kalsms/receiver/IncomingMessageRetry.java b/src/org/envaya/kalsms/receiver/IncomingMessageRetry.java index ad1a91b..08bfad5 100755 --- a/src/org/envaya/kalsms/receiver/IncomingMessageRetry.java +++ b/src/org/envaya/kalsms/receiver/IncomingMessageRetry.java @@ -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()); } } diff --git a/src/org/envaya/kalsms/receiver/MMSReceiver.java b/src/org/envaya/kalsms/receiver/MMSReceiver.java deleted file mode 100755 index a97e5a4..0000000 --- a/src/org/envaya/kalsms/receiver/MMSReceiver.java +++ /dev/null @@ -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"); - } -} \ No newline at end of file diff --git a/src/org/envaya/kalsms/receiver/MessageStatusNotifier.java b/src/org/envaya/kalsms/receiver/MessageStatusNotifier.java index ff31663..ca46363 100755 --- a/src/org/envaya/kalsms/receiver/MessageStatusNotifier.java +++ b/src/org/envaya/kalsms/receiver/MessageStatusNotifier.java @@ -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); } } diff --git a/src/org/envaya/kalsms/receiver/OutgoingMessageRetry.java b/src/org/envaya/kalsms/receiver/OutgoingMessageRetry.java index ed48c46..8abc431 100755 --- a/src/org/envaya/kalsms/receiver/OutgoingMessageRetry.java +++ b/src/org/envaya/kalsms/receiver/OutgoingMessageRetry.java @@ -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()); } } diff --git a/src/org/envaya/kalsms/receiver/SMSReceiver.java b/src/org/envaya/kalsms/receiver/SMSReceiver.java index faf9e06..d95ba83 100755 --- a/src/org/envaya/kalsms/receiver/SMSReceiver.java +++ b/src/org/envaya/kalsms/receiver/SMSReceiver.java @@ -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; } diff --git a/src/org/envaya/kalsms/task/ForwarderTask.java b/src/org/envaya/kalsms/task/ForwarderTask.java index cdb473c..ccff314 100755 --- a/src/org/envaya/kalsms/task/ForwarderTask.java +++ b/src/org/envaya/kalsms/task/ForwarderTask.java @@ -16,7 +16,7 @@ public class ForwarderTask extends HttpTask { params.add(new BasicNameValuePair("action", App.ACTION_INCOMING)); } - + @Override protected String getDefaultToAddress() { return originalSms.getFrom(); diff --git a/src/org/envaya/kalsms/task/HttpTask.java b/src/org/envaya/kalsms/task/HttpTask.java index c922c54..acb50a3 100755 --- a/src/org/envaya/kalsms/task/HttpTask.java +++ b/src/org/envaya/kalsms/task/HttpTask.java @@ -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 { protected String url; protected List params = new ArrayList(); + + private List formParts; + private boolean useMultipartPost = false; public HttpTask(App app, BasicNameValuePair... paramsArr) { @@ -52,7 +62,13 @@ public class HttpTask extends AsyncTask { params.add(new BasicNameValuePair("version", "2")); params.add(new BasicNameValuePair("phone_number", app.getPhoneNumber())); } - + + public void setFormParts(List formParts) + { + useMultipartPost = true; + this.formParts = formParts; + } + public HttpClient getHttpClient() { HttpParams httpParameters = new BasicHttpParams(); @@ -102,10 +118,29 @@ public class HttpTask extends AsyncTask { } 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); diff --git a/src/org/envaya/kalsms/ui/ForwardInbox.java b/src/org/envaya/kalsms/ui/ForwardInbox.java index b9d96af..ce74d8a 100755 --- a/src/org/envaya/kalsms/ui/ForwardInbox.java +++ b/src/org/envaya/kalsms/ui/ForwardInbox.java @@ -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); } diff --git a/src/org/envaya/kalsms/ui/Main.java b/src/org/envaya/kalsms/ui/Main.java index da3c1e7..fbee98a 100755 --- a/src/org/envaya/kalsms/ui/Main.java +++ b/src/org/envaya/kalsms/ui/Main.java @@ -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() ? "SMS gateway running." : "SMS gateway disabled.")); - - app.log("Server URL is: " + app.getDisplayString(app.getServerUrl())); - app.log("Your phone number is: " + app.getDisplayString(app.getPhoneNumber()) ); app.log(Html.fromHtml("Press Menu to edit settings.")); - - 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: