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: