diff --git a/AndroidManifest.xml b/AndroidManifest.xml index 4d98e82..2e41cf2 100755 --- a/AndroidManifest.xml +++ b/AndroidManifest.xml @@ -24,7 +24,7 @@ - + diff --git a/res/xml/prefs.xml b/res/xml/prefs.xml index 7ff56d5..478996c 100755 --- a/res/xml/prefs.xml +++ b/res/xml/prefs.xml @@ -30,5 +30,14 @@ + + \ No newline at end of file diff --git a/src/org/envaya/kalsms/App.java b/src/org/envaya/kalsms/App.java index 98c1ef5..4e8afe2 100755 --- a/src/org/envaya/kalsms/App.java +++ b/src/org/envaya/kalsms/App.java @@ -7,9 +7,11 @@ package org.envaya.kalsms; import android.app.Activity; import android.app.AlarmManager; import android.app.PendingIntent; +import android.content.ContentResolver; import android.content.Context; import android.content.Intent; import android.content.SharedPreferences; +import android.database.Cursor; import android.net.Uri; import android.os.SystemClock; import android.preference.PreferenceManager; @@ -22,349 +24,289 @@ import org.apache.http.HttpResponse; import org.apache.http.message.BasicNameValuePair; public class App { - + 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 LOG_NAME = "KALSMS"; public static final String LOG_INTENT = "org.envaya.kalsms.LOG"; - - private static App app; - + private static App app; private Map incomingSmsMap = new HashMap(); - private Map outgoingSmsMap = new HashMap(); - + private Map outgoingSmsMap = new HashMap(); public Context context; public SharedPreferences settings; - - private abstract class QueuedMessage - { + + private abstract class QueuedMessage { + public T sms; public long nextAttemptTime = 0; - public int numAttempts = 0; - - public boolean canAttemptNow() - { + public int numAttempts = 0; + + public boolean canAttemptNow() { return (nextAttemptTime > 0 && nextAttemptTime < SystemClock.elapsedRealtime()); } - public boolean scheduleNextAttempt() - { + public boolean scheduleNextAttempt() { long now = SystemClock.elapsedRealtime(); - numAttempts++; - - if (numAttempts > 4) - { + numAttempts++; + + if (numAttempts > 4) { log("5th failure: giving up"); return false; } - - int second = 1000; - int minute = second * 60; - - if (numAttempts == 1) - { + + int second = 1000; + int minute = second * 60; + + if (numAttempts == 1) { log("1st failure; retry in 1 minute"); - nextAttemptTime = now + 1 * minute; - } - else if (numAttempts == 2) - { + nextAttemptTime = now + 1 * minute; + } else if (numAttempts == 2) { log("2nd failure; retry in 10 minutes"); - nextAttemptTime = now + 10 * minute; - } - else if (numAttempts == 3) - { + nextAttemptTime = now + 10 * minute; + } else if (numAttempts == 3) { log("3rd failure; retry in 1 hour"); - nextAttemptTime = now + 60 * minute; - } - else - { + nextAttemptTime = now + 60 * minute; + } else { log("4th failure: retry in 1 day"); - nextAttemptTime = now + 24 * 60 * minute; - } - - AlarmManager alarm = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE); - + nextAttemptTime = now + 24 * 60 * minute; + } + + AlarmManager alarm = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE); + PendingIntent pendingIntent = PendingIntent.getBroadcast(context, - 0, - getAttemptIntent(), - 0); + 0, + getAttemptIntent(), + 0); alarm.set( - AlarmManager.ELAPSED_REALTIME_WAKEUP, - nextAttemptTime, - pendingIntent); - - return true; + AlarmManager.ELAPSED_REALTIME_WAKEUP, + nextAttemptTime, + pendingIntent); + + return true; } - + public abstract void attemptNow(); - protected abstract Intent getAttemptIntent(); - } - + + protected abstract Intent getAttemptIntent(); + } + private class QueuedIncomingSms extends QueuedMessage { - public QueuedIncomingSms(SmsMessage sms) - { + + public QueuedIncomingSms(SmsMessage sms) { this.sms = sms; } - - public void attemptNow() - { - log("Retrying forwarding SMS from " + sms.getOriginatingAddress()); + + public void attemptNow() { + log("Retrying forwarding SMS from " + sms.getOriginatingAddress()); trySendMessageToServer(sms); } - - protected Intent getAttemptIntent() - { + + protected Intent getAttemptIntent() { Intent intent = new Intent(context, IncomingMessageRetry.class); intent.setData(Uri.parse("kalsms://incoming/" + getSmsId(sms))); return intent; } - } - + } + private class QueuedOutgoingSms extends QueuedMessage { - public QueuedOutgoingSms(OutgoingSmsMessage sms) - { + + public QueuedOutgoingSms(OutgoingSmsMessage sms) { this.sms = sms; } - public void attemptNow() - { - log("Retrying sending " +sms.getLogName() + " to " + sms.getTo()); - trySendSMS(sms); + public void attemptNow() { + log("Retrying sending " + sms.getLogName() + " to " + sms.getTo()); + trySendSMS(sms); } - - protected Intent getAttemptIntent() - { + + protected Intent getAttemptIntent() { Intent intent = new Intent(context, OutgoingMessageRetry.class); intent.setData(Uri.parse("kalsms://outgoing/" + sms.getId())); log("id=" + sms.getId()); return intent; - } - } - - protected App(Context context) - { + } + } + + protected App(Context context) { this.context = context; this.settings = PreferenceManager.getDefaultSharedPreferences(context); } - - public static App getInstance(Context context) - { - if (app == null) - { + + public static App getInstance(Context context) { + if (app == null) { app = new App(context); } return app; } - - public void debug(String msg) - { - Log.d(LOG_NAME, msg); - } - - public void log(String msg) - { + + public void debug(String msg) { Log.d(LOG_NAME, msg); - + } + + public void log(String msg) { + Log.d(LOG_NAME, msg); + Intent broadcast = new Intent(App.LOG_INTENT); broadcast.putExtra("message", msg); context.sendBroadcast(broadcast); } - public void checkOutgoingMessages() - { + public void checkOutgoingMessages() { String serverUrl = getServerUrl(); - if (serverUrl.length() > 0) - { + if (serverUrl.length() > 0) { log("Checking for outgoing messages"); - new PollerTask().execute( - new BasicNameValuePair("action", App.ACTION_OUTGOING) - ); - } - else - { + new PollerTask().execute( + new BasicNameValuePair("action", App.ACTION_OUTGOING)); + } else { log("Can't check outgoing messages; server URL not set"); } } - - public void setOutgoingMessageAlarm() - { - AlarmManager alarm = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE); - + + public void setOutgoingMessageAlarm() { + AlarmManager alarm = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE); + PendingIntent pendingIntent = PendingIntent.getBroadcast(context, 0, - new Intent(context, OutgoingMessagePoller.class), + new Intent(context, OutgoingMessagePoller.class), 0); - - alarm.cancel(pendingIntent); - + + alarm.cancel(pendingIntent); + int pollSeconds = getOutgoingPollSeconds(); - - if (pollSeconds > 0) - { + + if (pollSeconds > 0) { alarm.setRepeating( - AlarmManager.ELAPSED_REALTIME_WAKEUP, - SystemClock.elapsedRealtime(), - pollSeconds * 1000, - pendingIntent); - log("Checking for outgoing messages every " + pollSeconds + " sec"); - } - else - { + AlarmManager.ELAPSED_REALTIME_WAKEUP, + SystemClock.elapsedRealtime(), + pollSeconds * 1000, + pendingIntent); + log("Checking for outgoing messages every " + pollSeconds + " sec"); + } else { log("Not checking for outgoing messages."); } } - - public void logError(Throwable ex) - { + + public void logError(Throwable ex) { logError("ERROR", ex); - } - - public void logError(String msg, Throwable ex) - { + } + + public void logError(String msg, Throwable ex) { logError(msg, ex, false); } - - public void logError(String msg, Throwable ex, boolean detail) - { + + public void logError(String msg, Throwable ex, boolean detail) { log(msg + ": " + ex.getClass().getName() + ": " + ex.getMessage()); - - if (detail) - { - for (StackTraceElement elem : ex.getStackTrace()) - { + + if (detail) { + for (StackTraceElement elem : ex.getStackTrace()) { log(elem.getClassName() + ":" + elem.getMethodName() + ":" + elem.getLineNumber()); } Throwable innerEx = ex.getCause(); - if (innerEx != null) - { + if (innerEx != null) { logError("Inner exception:", innerEx, true); } } - } - - public String getDisplayString(String str) - { - if (str.length() == 0) - { - return "(not set)"; - } - else - { - return str; - } } - - public String getServerUrl() - { + + public String getDisplayString(String str) { + if (str.length() == 0) { + return "(not set)"; + } else { + return str; + } + } + + public String getServerUrl() { return settings.getString("server_url", ""); } - public String getPhoneNumber() - { + public String getPhoneNumber() { return settings.getString("phone_number", ""); } + + public int getOutgoingPollSeconds() { + return Integer.parseInt(settings.getString("outgoing_interval", "0")); + } + + public boolean getLaunchOnBoot() { + return settings.getBoolean("launch_on_boot", false); + } - public int getOutgoingPollSeconds() - { - return Integer.parseInt(settings.getString("outgoing_interval", "0")); - } - - public boolean getLaunchOnBoot() - { - return settings.getBoolean("launch_on_boot", true); - } - - public String getPassword() + public boolean getKeepInInbox() { + return settings.getBoolean("keep_in_inbox", false); + } + + public String getPassword() { return settings.getString("password", ""); - } - - private void notifyStatus(OutgoingSmsMessage sms, String status, String errorMessage) - { + } + + private void notifyStatus(OutgoingSmsMessage sms, String status, String errorMessage) { String serverId = sms.getServerId(); - + String logMessage; - if (status.equals(App.STATUS_SENT)) - { - logMessage = "sent successfully"; - } - else if (status.equals(App.STATUS_FAILED)) - { + if (status.equals(App.STATUS_SENT)) { + logMessage = "sent successfully"; + } else if (status.equals(App.STATUS_FAILED)) { logMessage = "could not be sent (" + errorMessage + ")"; - } - else - { + } else { logMessage = "queued"; } String smsDesc = sms.getLogName(); - - if (serverId != null) - { - app.log("Notifying server " + smsDesc + " " + logMessage); + + if (serverId != null) { + app.log("Notifying server " + smsDesc + " " + logMessage); new HttpTask(app).execute( - new BasicNameValuePair("id", serverId), - new BasicNameValuePair("status", status), - new BasicNameValuePair("error", errorMessage), - new BasicNameValuePair("action", App.ACTION_SEND_STATUS) - ); - } - else - { + new BasicNameValuePair("id", serverId), + new BasicNameValuePair("status", status), + new BasicNameValuePair("error", errorMessage), + new BasicNameValuePair("action", App.ACTION_SEND_STATUS)); + } else { app.log(smsDesc + " " + logMessage); } } - - public synchronized void retryStuckMessages() - { + + public synchronized void retryStuckMessages() { retryStuckOutgoingMessages(); retryStuckIncomingMessages(); } - - public synchronized int getStuckMessageCount() - { + + public synchronized int getStuckMessageCount() { return outgoingSmsMap.size() + incomingSmsMap.size(); } - - public synchronized void retryStuckOutgoingMessages() - { - for (QueuedOutgoingSms queuedSms : outgoingSmsMap.values()) - { + + public synchronized void retryStuckOutgoingMessages() { + for (QueuedOutgoingSms queuedSms : outgoingSmsMap.values()) { queuedSms.attemptNow(); } } - - public synchronized void retryStuckIncomingMessages() - { - for (QueuedIncomingSms queuedSms : incomingSmsMap.values()) - { + + public synchronized void retryStuckIncomingMessages() { + for (QueuedIncomingSms queuedSms : incomingSmsMap.values()) { queuedSms.attemptNow(); - } + } } - - public synchronized void notifyOutgoingMessageStatus(String id, int resultCode) - { + + public synchronized void notifyOutgoingMessageStatus(String id, int resultCode) { QueuedOutgoingSms queuedSms = outgoingSmsMap.get(id); - - if (queuedSms == null) - { + + if (queuedSms == null) { return; } - - OutgoingSmsMessage sms = queuedSms.sms; - + + OutgoingSmsMessage sms = queuedSms.sms; + switch (resultCode) { case Activity.RESULT_OK: this.notifyStatus(sms, App.STATUS_SENT, ""); break; - case SmsManager.RESULT_ERROR_GENERIC_FAILURE: + case SmsManager.RESULT_ERROR_GENERIC_FAILURE: this.notifyStatus(sms, App.STATUS_FAILED, "generic failure"); break; case SmsManager.RESULT_ERROR_RADIO_OFF: @@ -373,7 +315,7 @@ public class App { case SmsManager.RESULT_ERROR_NO_SERVICE: this.notifyStatus(sms, App.STATUS_FAILED, "no service"); break; - case SmsManager.RESULT_ERROR_NULL_PDU: + case SmsManager.RESULT_ERROR_NULL_PDU: this.notifyStatus(sms, App.STATUS_FAILED, "null PDU"); break; default: @@ -381,69 +323,63 @@ public class App { break; } - switch (resultCode) { - case SmsManager.RESULT_ERROR_GENERIC_FAILURE: + switch (resultCode) { + case SmsManager.RESULT_ERROR_GENERIC_FAILURE: case SmsManager.RESULT_ERROR_RADIO_OFF: case SmsManager.RESULT_ERROR_NO_SERVICE: - if (!queuedSms.scheduleNextAttempt()) - { + if (!queuedSms.scheduleNextAttempt()) { outgoingSmsMap.remove(id); } break; default: outgoingSmsMap.remove(id); break; - } - + } + } - - public synchronized void sendSMS(OutgoingSmsMessage sms) - { + + public synchronized void sendSMS(OutgoingSmsMessage sms) { String id = sms.getId(); - if (outgoingSmsMap.containsKey(id)) - { + if (outgoingSmsMap.containsKey(id)) { log(sms.getLogName() + " already sent, skipping"); return; } QueuedOutgoingSms queueEntry = new QueuedOutgoingSms(sms); outgoingSmsMap.put(id, queueEntry); - - log("Sending " +sms.getLogName() + " to " + sms.getTo()); + + log("Sending " + sms.getLogName() + " to " + sms.getTo()); trySendSMS(sms); } - - private void trySendSMS(OutgoingSmsMessage sms) - { + + private void trySendSMS(OutgoingSmsMessage sms) { SmsManager smgr = SmsManager.getDefault(); - + Intent intent = new Intent(context, MessageStatusNotifier.class); intent.setData(Uri.parse("kalsms://outgoing/" + sms.getId())); - + PendingIntent sentIntent = PendingIntent.getBroadcast( this.context, 0, intent, PendingIntent.FLAG_ONE_SHOT); - - smgr.sendTextMessage(sms.getTo(), null, sms.getMessage(), sentIntent, null); + + smgr.sendTextMessage(sms.getTo(), null, sms.getMessage(), sentIntent, null); } - + private class PollerTask extends HttpTask { - public PollerTask() - { + public PollerTask() { super(app); } - + @Override protected void handleResponse(HttpResponse response) throws Exception { for (OutgoingSmsMessage reply : parseResponseXML(response)) { app.sendSMS(reply); - } - } + } + } } - private class ForwarderTask extends HttpTask { @@ -455,94 +391,79 @@ public class App { } @Override - protected String getDefaultToAddress() - { + protected String getDefaultToAddress() { return originalSms.getOriginatingAddress(); - } - + } + @Override protected void handleResponse(HttpResponse response) throws Exception { - + for (OutgoingSmsMessage reply : parseResponseXML(response)) { app.sendSMS(reply); - } - - app.notifyIncomingMessageStatus(originalSms, true); + } + + app.notifyIncomingMessageStatus(originalSms, true); } - + @Override - protected void handleFailure() - { + protected void handleFailure() { app.notifyIncomingMessageStatus(originalSms, false); - } - } - - private String getSmsId(SmsMessage sms) - { + } + } + + private String getSmsId(SmsMessage sms) { return sms.getOriginatingAddress() + ":" + sms.getMessageBody() + ":" + sms.getTimestampMillis(); } - - public synchronized void sendMessageToServer(SmsMessage sms) - { + + public synchronized void sendMessageToServer(SmsMessage sms) { String id = getSmsId(sms); - if (incomingSmsMap.containsKey(id)) - { + if (incomingSmsMap.containsKey(id)) { log("Duplicate incoming SMS, skipping"); return; - } - - QueuedIncomingSms queuedSms = new QueuedIncomingSms(sms); + } + + QueuedIncomingSms queuedSms = new QueuedIncomingSms(sms); incomingSmsMap.put(id, queuedSms); - + app.log("Received SMS from " + sms.getOriginatingAddress()); - + trySendMessageToServer(sms); - } - - public void trySendMessageToServer(SmsMessage sms) - { + } + + public void trySendMessageToServer(SmsMessage sms) { String message = sms.getMessageBody(); String sender = sms.getOriginatingAddress(); - + new ForwarderTask(sms).execute( - new BasicNameValuePair("from", sender), - new BasicNameValuePair("message", message), - new BasicNameValuePair("action", App.ACTION_INCOMING) - ); + new BasicNameValuePair("from", sender), + new BasicNameValuePair("message", message), + new BasicNameValuePair("action", App.ACTION_INCOMING)); } - - private synchronized void notifyIncomingMessageStatus(SmsMessage sms, boolean success) - { + + private synchronized void notifyIncomingMessageStatus(SmsMessage sms, boolean success) { String id = getSmsId(sms); QueuedIncomingSms queuedSms = incomingSmsMap.get(id); - - if (queuedSms != null) - { - if (success || !queuedSms.scheduleNextAttempt()) - { + + if (queuedSms != null) { + if (success || !queuedSms.scheduleNextAttempt()) { incomingSmsMap.remove(id); - } + } } } - - public synchronized void retryIncomingMessage(String id) - { + + public synchronized void retryIncomingMessage(String id) { QueuedIncomingSms queuedSms = incomingSmsMap.get(id); - if (queuedSms != null) - { + if (queuedSms != null) { queuedSms.attemptNow(); - } + } } - - public synchronized void retryOutgoingMessage(String id) - { + + public synchronized void retryOutgoingMessage(String id) { QueuedOutgoingSms queuedSms = outgoingSmsMap.get(id); - if (queuedSms != null) - { + if (queuedSms != null) { queuedSms.attemptNow(); - } + } } - } diff --git a/src/org/envaya/kalsms/HttpTask.java b/src/org/envaya/kalsms/HttpTask.java index 7ae3797..938a581 100755 --- a/src/org/envaya/kalsms/HttpTask.java +++ b/src/org/envaya/kalsms/HttpTask.java @@ -7,7 +7,9 @@ package org.envaya.kalsms; import android.os.AsyncTask; import java.io.IOException; import java.io.InputStream; +import java.io.UnsupportedEncodingException; import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; @@ -50,41 +52,35 @@ public class HttpTask extends AsyncTask } private String getSignature(String url, List params) + throws NoSuchAlgorithmException, UnsupportedEncodingException { - try { - Collections.sort(params, new Comparator() { - public int compare(Object o1, Object o2) - { - return ((BasicNameValuePair)o1).getName().compareTo(((BasicNameValuePair)o2).getName()); - } - }); - - StringBuilder builder = new StringBuilder(); - builder.append(url); - for (BasicNameValuePair param : params) + Collections.sort(params, new Comparator() { + public int compare(Object o1, Object o2) { - builder.append(","); - builder.append(param.getName()); - builder.append("="); - builder.append(param.getValue()); + return ((BasicNameValuePair)o1).getName().compareTo(((BasicNameValuePair)o2).getName()); } + }); + + StringBuilder builder = new StringBuilder(); + builder.append(url); + for (BasicNameValuePair param : params) + { builder.append(","); - builder.append(app.getPassword()); - - String value = builder.toString(); - - MessageDigest md = MessageDigest.getInstance("SHA-1"); - - md.update(value.getBytes("utf-8")); - - byte[] digest = md.digest(); - - return new String(Base64Coder.encode(digest)); - - } catch (Exception ex) { - app.logError("Error computing signature", ex); + builder.append(param.getName()); + builder.append("="); + builder.append(param.getValue()); } - return ""; + builder.append(","); + builder.append(app.getPassword()); + + String value = builder.toString(); + + MessageDigest md = MessageDigest.getInstance("SHA-1"); + md.update(value.getBytes("utf-8")); + + byte[] digest = md.digest(); + + return new String(Base64Coder.encode(digest)); } protected HttpResponse doInBackground(BasicNameValuePair... params) { diff --git a/src/org/envaya/kalsms/IncomingMessageForwarder.java b/src/org/envaya/kalsms/IncomingMessageForwarder.java index 54c19e9..adf80d6 100755 --- a/src/org/envaya/kalsms/IncomingMessageForwarder.java +++ b/src/org/envaya/kalsms/IncomingMessageForwarder.java @@ -13,18 +13,21 @@ public class IncomingMessageForwarder extends BroadcastReceiver { @Override // source: http://www.devx.com/wireless/Article/39495/1954 - public void onReceive(Context context, Intent intent) { + public void onReceive(Context context, Intent intent) { + app = App.getInstance(context.getApplicationContext()); + try { - this.app = App.getInstance(context.getApplicationContext()); - String action = intent.getAction(); if (action.equals("android.provider.Telephony.SMS_RECEIVED")) { for (SmsMessage sms : getMessagesFromIntent(intent)) { app.sendMessageToServer(sms); - - //DeleteSMSFromInbox(context, mesg); + } + + if (!app.getKeepInInbox()) + { + this.abortBroadcast(); } } } catch (Throwable ex) { @@ -32,28 +35,6 @@ public class IncomingMessageForwarder extends BroadcastReceiver { } } - /* - private void DeleteSMSFromInbox(Context context, SmsMessage mesg) { - Log.d("KALSMS", "try to delete SMS"); - try { - Uri uriSms = Uri.parse("content://sms/inbox"); - StringBuilder sb = new StringBuilder(); - sb.append("address='" + mesg.getOriginatingAddress() + "' AND "); - sb.append("body='" + mesg.getMessageBody() + "'"); - Cursor c = context.getContentResolver().query(uriSms, null, sb.toString(), null, null); - c.moveToFirst(); - int thread_id = c.getInt(1); - context.getContentResolver().delete(Uri.parse("content://sms/conversations/" + thread_id), null, null); - c.close(); - } catch (Exception ex) { - // deletions don't work most of the time since the timing of the - // receipt and saving to the inbox - // makes it difficult to match up perfectly. the SMS might not be in - // the inbox yet when this receiver triggers! - Log.d("SmsReceiver", "Error deleting sms from inbox: " + ex.getMessage()); - } - } - */ // from http://github.com/dimagi/rapidandroid // source: http://www.devx.com/wireless/Article/39495/1954 private SmsMessage[] getMessagesFromIntent(Intent intent) {