mirror of
https://github.com/cwinfo/envayasms.git
synced 2024-12-04 12:35:32 +00:00
add PendingMessages activity for viewing/retrying/deleting pending messages; clean up UI for ForwardInbox; create Inbox and Outbox class to simplify App class
This commit is contained in:
parent
1081f57580
commit
d7f803e60e
@ -1,8 +1,8 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
package="org.envaya.sms"
|
||||
android:versionCode="12"
|
||||
android:versionName="2.0-rc2">
|
||||
android:versionCode="13"
|
||||
android:versionName="2.0">
|
||||
|
||||
<uses-sdk android:minSdkVersion="4" />
|
||||
|
||||
@ -35,8 +35,11 @@
|
||||
<activity android:name=".ui.TestPhoneNumbers" android:label="EnvayaSMS : Test Phone Numbers">
|
||||
</activity>
|
||||
|
||||
<activity android:name=".ui.ForwardInbox" android:label="EnvayaSMS : Forward Inbox">
|
||||
<activity android:name=".ui.MessagingInbox" android:label="EnvayaSMS : Forward Inbox">
|
||||
</activity>
|
||||
|
||||
<activity android:name=".ui.PendingMessages" android:label="EnvayaSMS : Pending Messages">
|
||||
</activity>
|
||||
|
||||
<activity android:name=".ui.Prefs" android:label="EnvayaSMS : Settings">
|
||||
</activity>
|
||||
|
4
LICENSE
4
LICENSE
@ -34,10 +34,6 @@ libs/httpmime-4.1.2.jar is (c) Apache Software Foundation
|
||||
org.envaya.sms.Base64Coder is (c) 2003-2010 Christian d'Heureuse,
|
||||
released under MIT License (and others)
|
||||
|
||||
org.envaya.sms.ui.InertCheckBox and org.envaya.sms.ui.CheckableRelativeLayout
|
||||
is (c) C<>dric Caron, released presumably into the public domain at
|
||||
http://www.marvinlabs.com/2010/10/custom-listview-ability-check-items/
|
||||
|
||||
org.envaya.sms.App.chooseOutgoingSmsPackage and
|
||||
org.envaya.sms.ForegroundService include code from Android,
|
||||
Copyright 2005-2009 The Android Open Source Project
|
||||
|
@ -11,7 +11,6 @@
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="fill_parent"
|
||||
android:id="@+id/help"
|
||||
android:textSize="16sp"
|
||||
android:autoLink="web"
|
||||
android:textColor="#FFFFFF"
|
||||
android:layout_margin="5px">
|
||||
|
@ -9,10 +9,11 @@
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="fill_parent"
|
||||
android:layout_weight="1"
|
||||
android:choiceMode="multipleChoice" />
|
||||
<Button
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_width="wrap_content"
|
||||
android:text="Forward selected"
|
||||
android:onClick="forwardSelected" />
|
||||
/>
|
||||
<TextView android:id="@android:id/empty"
|
||||
android:text="The inbox is empty."
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="fill_parent"
|
||||
android:layout_weight="1"
|
||||
/>
|
||||
</LinearLayout>
|
@ -1,34 +1,23 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<org.envaya.sms.ui.CheckableRelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:orientation="horizontal"
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:orientation="vertical"
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="#cccccc">
|
||||
<org.envaya.sms.ui.InertCheckBox android:id="@+id/inbox_checkbox"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:focusable="false"
|
||||
/>
|
||||
android:background="#333333">
|
||||
<TextView
|
||||
android:id="@+id/inbox_address"
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_toRightOf="@id/inbox_checkbox"
|
||||
android:layout_alignTop="@id/inbox_checkbox"
|
||||
android:textColor="#333333"
|
||||
android:textColor="#FFFFFF"
|
||||
android:layout_marginTop="4sp"
|
||||
android:layout_marginLeft="6sp"
|
||||
android:focusable="false"
|
||||
android:layout_marginLeft="6sp"
|
||||
android:textSize="14sp"></TextView>
|
||||
<TextView
|
||||
android:id="@+id/inbox_body"
|
||||
android:layout_below="@id/inbox_address"
|
||||
android:layout_alignLeft="@id/inbox_address"
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:textColor="#666666"
|
||||
android:textColor="#CCCCCC"
|
||||
android:layout_marginLeft="6sp"
|
||||
android:layout_marginBottom="6sp"
|
||||
android:focusable="false"
|
||||
android:paddingBottom="6sp"
|
||||
android:textSize="14sp"></TextView>
|
||||
</org.envaya.sms.ui.CheckableRelativeLayout>
|
||||
</LinearLayout>
|
35
res/layout/pending_message.xml
Executable file
35
res/layout/pending_message.xml
Executable file
@ -0,0 +1,35 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:orientation="vertical"
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="#333333">
|
||||
<TextView
|
||||
android:id="@+id/pending_address"
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:textColor="#FFFFFF"
|
||||
android:layout_marginTop="4sp"
|
||||
android:layout_marginLeft="6sp"
|
||||
android:focusable="false"
|
||||
android:textSize="14sp"></TextView>
|
||||
<TextView
|
||||
android:id="@+id/pending_time"
|
||||
android:layout_below="@id/pending_address"
|
||||
android:layout_alignLeft="@id/pending_address"
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginLeft="6sp"
|
||||
android:textColor="#CCCCCC"
|
||||
android:focusable="false"
|
||||
android:textSize="14sp"></TextView>
|
||||
<TextView
|
||||
android:id="@+id/pending_status"
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:textColor="#CCCCCC"
|
||||
android:focusable="false"
|
||||
android:paddingBottom="6sp"
|
||||
android:layout_marginLeft="6sp"
|
||||
android:textSize="14sp"></TextView>
|
||||
</LinearLayout>
|
19
res/layout/pending_messages.xml
Executable file
19
res/layout/pending_messages.xml
Executable file
@ -0,0 +1,19 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:orientation="vertical"
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="fill_parent"
|
||||
android:paddingLeft="8dp"
|
||||
android:paddingRight="8dp">
|
||||
<ListView android:id="@android:id/list"
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="fill_parent"
|
||||
android:layout_weight="1"
|
||||
/>
|
||||
<TextView android:id="@android:id/empty"
|
||||
android:text="There are no pending messages."
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="fill_parent"
|
||||
android:layout_weight="1"
|
||||
/>
|
||||
</LinearLayout>
|
5
res/menu/inbox.xml
Executable file
5
res/menu/inbox.xml
Executable file
@ -0,0 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<menu xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<item android:id="@+id/forward_all"
|
||||
android:title="Forward all" />
|
||||
</menu>
|
@ -15,8 +15,8 @@
|
||||
<item android:id="@+id/retry_now"
|
||||
android:icon="@drawable/ic_menu_magnet"
|
||||
android:title="@string/retry_now" />
|
||||
<item android:id="@+id/help"
|
||||
android:icon="@drawable/ic_menu_puzzle"
|
||||
android:title="@string/help" />
|
||||
<item android:id="@+id/pending"
|
||||
android:icon="@drawable/ic_menu_dialog"
|
||||
android:title="@string/pending" />
|
||||
|
||||
</menu>
|
7
res/menu/pending_messages.xml
Executable file
7
res/menu/pending_messages.xml
Executable file
@ -0,0 +1,7 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<menu xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<item android:id="@+id/retry_all"
|
||||
android:title="Retry all" />
|
||||
<item android:id="@+id/delete_all"
|
||||
android:title="Delete all" />
|
||||
</menu>
|
@ -5,6 +5,7 @@
|
||||
<string name="test">Test Connection</string>
|
||||
<string name="check_now">Check Messages</string>
|
||||
<string name="help">Help</string>
|
||||
<string name="pending">Pending Msgs...</string>
|
||||
<string name="retry_now">Retry</string>
|
||||
<string name="forward_inbox">Fwd Inbox...</string>
|
||||
<string name='service_started'>New SMS will be forwarded to server</string>
|
||||
|
@ -80,4 +80,14 @@
|
||||
android:targetClass="org.envaya.sms.ui.TestPhoneNumbers" />
|
||||
</PreferenceScreen>
|
||||
|
||||
<PreferenceScreen
|
||||
android:key="help"
|
||||
android:title="About EnvayaSMS"
|
||||
>
|
||||
<intent
|
||||
android:action="android.intent.action.MAIN"
|
||||
android:targetPackage="org.envaya.sms"
|
||||
android:targetClass="org.envaya.sms.ui.Help" />
|
||||
</PreferenceScreen>
|
||||
|
||||
</PreferenceScreen>
|
@ -17,7 +17,6 @@ import android.net.wifi.WifiManager;
|
||||
import android.os.Bundle;
|
||||
import android.os.SystemClock;
|
||||
import android.preference.PreferenceManager;
|
||||
import android.telephony.SmsManager;
|
||||
import android.text.Html;
|
||||
import android.text.SpannableStringBuilder;
|
||||
import android.util.Log;
|
||||
@ -25,12 +24,10 @@ import java.io.IOException;
|
||||
import java.net.InetAddress;
|
||||
import java.text.DateFormat;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Comparator;
|
||||
import java.util.Date;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.PriorityQueue;
|
||||
import org.apache.http.client.HttpClient;
|
||||
import org.apache.http.conn.scheme.PlainSocketFactory;
|
||||
import org.apache.http.conn.scheme.Scheme;
|
||||
@ -38,15 +35,12 @@ import org.apache.http.conn.scheme.SchemeRegistry;
|
||||
import org.apache.http.conn.ssl.SSLSocketFactory;
|
||||
import org.apache.http.impl.client.DefaultHttpClient;
|
||||
import org.apache.http.impl.conn.tsccm.ThreadSafeClientConnManager;
|
||||
import org.apache.http.message.BasicNameValuePair;
|
||||
import org.apache.http.params.BasicHttpParams;
|
||||
import org.apache.http.params.HttpConnectionParams;
|
||||
import org.apache.http.params.HttpParams;
|
||||
import org.apache.http.params.HttpProtocolParams;
|
||||
import org.envaya.sms.receiver.DequeueOutgoingMessageReceiver;
|
||||
import org.envaya.sms.receiver.OutgoingMessagePoller;
|
||||
import org.envaya.sms.receiver.ReenableWifiReceiver;
|
||||
import org.envaya.sms.task.HttpTask;
|
||||
import org.envaya.sms.task.PollerTask;
|
||||
import org.json.JSONArray;
|
||||
import org.json.JSONException;
|
||||
@ -68,7 +62,11 @@ public final class App extends Application {
|
||||
public static final String LOG_NAME = "EnvayaSMS";
|
||||
|
||||
// intent to signal to Main activity (if open) that log has changed
|
||||
public static final String LOG_INTENT = "org.envaya.sms.LOG";
|
||||
public static final String LOG_CHANGED_INTENT = "org.envaya.sms.LOG_CHANGED";
|
||||
|
||||
// signal to PendingMessages activity (if open) that inbox/outbox has changed
|
||||
public static final String INBOX_CHANGED_INTENT = "org.envaya.sms.INBOX_CHANGED";
|
||||
public static final String OUTBOX_CHANGED_INTENT = "org.envaya.sms.OUTBOX_CHANGED";
|
||||
|
||||
public static final String QUERY_EXPANSION_PACKS_INTENT = "org.envaya.sms.QUERY_EXPANSION_PACKS";
|
||||
public static final String QUERY_EXPANSION_PACKS_EXTRA_PACKAGES = "packages";
|
||||
@ -112,30 +110,9 @@ public final class App extends Application {
|
||||
// between when we prepare messages and when SMSDispatcher receives them
|
||||
public static final int OUTGOING_SMS_CHECK_PERIOD = 3605000; // one hour plus 5 sec (in ms)
|
||||
public static final int OUTGOING_SMS_MAX_COUNT = 100;
|
||||
|
||||
private Map<Uri, IncomingMessage> incomingMessages = new HashMap<Uri, IncomingMessage>();
|
||||
private Map<Uri, OutgoingMessage> outgoingMessages = new HashMap<Uri, OutgoingMessage>();
|
||||
|
||||
private int numPendingOutgoingMessages = 0;
|
||||
private PriorityQueue<OutgoingMessage> outgoingQueue = new PriorityQueue<OutgoingMessage>(10,
|
||||
new Comparator<OutgoingMessage>() {
|
||||
public int compare(OutgoingMessage t1, OutgoingMessage t2)
|
||||
{
|
||||
int pri2 = t2.getPriority();
|
||||
int pri1 = t1.getPriority();
|
||||
|
||||
if (pri1 != pri2)
|
||||
{
|
||||
return pri2 - pri1;
|
||||
}
|
||||
|
||||
int order2 = t2.getLocalId();
|
||||
int order1 = t1.getLocalId();
|
||||
|
||||
return order1 - order2;
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
public final Inbox inbox = new Inbox(this);
|
||||
public final Outbox outbox = new Outbox(this);
|
||||
|
||||
private SharedPreferences settings;
|
||||
private MmsObserver mmsObserver;
|
||||
@ -147,16 +124,14 @@ public final class App extends Application {
|
||||
// list of package names (e.g. org.envaya.sms, or org.envaya.sms.packXX)
|
||||
// for this package and all expansion packs
|
||||
private List<String> outgoingMessagePackages = new ArrayList<String>();
|
||||
|
||||
// map of package name => sorted list of timestamps of outgoing messages
|
||||
private HashMap<String, ArrayList<Long>> outgoingTimestamps
|
||||
= new HashMap<String, ArrayList<Long>>();
|
||||
|
||||
// count to provide round-robin selection of expansion packs
|
||||
private int outgoingMessageCount = -1;
|
||||
|
||||
private long nextValidOutgoingTime;
|
||||
|
||||
// map of package name => sorted list of timestamps of outgoing messages
|
||||
private HashMap<String, ArrayList<Long>> outgoingTimestamps
|
||||
= new HashMap<String, ArrayList<Long>>();
|
||||
|
||||
private MmsUtils mmsUtils;
|
||||
|
||||
@Override
|
||||
@ -226,7 +201,7 @@ public final class App extends Application {
|
||||
return packageInfo;
|
||||
}
|
||||
|
||||
private synchronized String chooseOutgoingSmsPackage(int numParts)
|
||||
public synchronized String chooseOutgoingSmsPackage(int numParts)
|
||||
{
|
||||
outgoingMessageCount++;
|
||||
|
||||
@ -280,7 +255,7 @@ public final class App extends Application {
|
||||
* outgoing SMS with numParts parts. Only valid immediately after
|
||||
* chooseOutgoingSmsPackage returns null.
|
||||
*/
|
||||
private synchronized long getNextValidOutgoingTime(int numParts)
|
||||
public synchronized long getNextValidOutgoingTime(int numParts)
|
||||
{
|
||||
long minTime = System.currentTimeMillis() + OUTGOING_SMS_CHECK_PERIOD;
|
||||
|
||||
@ -449,302 +424,15 @@ public final class App extends Application {
|
||||
return settings.getString("password", "");
|
||||
}
|
||||
|
||||
private void notifyStatus(OutgoingMessage 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)) {
|
||||
logMessage = "could not be sent (" + errorMessage + ")";
|
||||
} else {
|
||||
logMessage = "queued";
|
||||
}
|
||||
String smsDesc = sms.getLogName();
|
||||
|
||||
if (serverId != null) {
|
||||
log("Notifying server " + smsDesc + " " + logMessage);
|
||||
|
||||
new HttpTask(this,
|
||||
new BasicNameValuePair("id", serverId),
|
||||
new BasicNameValuePair("status", status),
|
||||
new BasicNameValuePair("error", errorMessage),
|
||||
new BasicNameValuePair("action", App.ACTION_SEND_STATUS)
|
||||
).execute();
|
||||
} else {
|
||||
log(smsDesc + " " + logMessage);
|
||||
}
|
||||
public synchronized void retryStuckMessages() {
|
||||
outbox.retryAll();
|
||||
inbox.retryAll();
|
||||
}
|
||||
|
||||
public synchronized void retryStuckMessages() {
|
||||
|
||||
this.nextValidOutgoingTime = 0;
|
||||
|
||||
retryStuckOutgoingMessages();
|
||||
retryStuckIncomingMessages();
|
||||
}
|
||||
|
||||
public synchronized int getStuckMessageCount() {
|
||||
return outgoingMessages.size() + incomingMessages.size();
|
||||
}
|
||||
|
||||
public synchronized void retryStuckOutgoingMessages() {
|
||||
for (OutgoingMessage sms : outgoingMessages.values()) {
|
||||
|
||||
OutgoingMessage.ProcessingState state = sms.getProcessingState();
|
||||
|
||||
if (state != OutgoingMessage.ProcessingState.Queued
|
||||
&& state != OutgoingMessage.ProcessingState.Sending)
|
||||
{
|
||||
enqueueOutgoingMessage(sms);
|
||||
}
|
||||
}
|
||||
maybeDequeueOutgoingMessage();
|
||||
}
|
||||
|
||||
public synchronized void retryStuckIncomingMessages() {
|
||||
for (IncomingMessage sms : incomingMessages.values()) {
|
||||
IncomingMessage.ProcessingState state = sms.getProcessingState();
|
||||
if (state != IncomingMessage.ProcessingState.Forwarding)
|
||||
{
|
||||
enqueueIncomingMessage(sms);
|
||||
}
|
||||
}
|
||||
public synchronized int getPendingMessageCount() {
|
||||
return outbox.size() + inbox.size();
|
||||
}
|
||||
|
||||
public synchronized void setIncomingMessageStatus(IncomingMessage message, boolean success) {
|
||||
|
||||
message.setProcessingState(IncomingMessage.ProcessingState.None);
|
||||
|
||||
Uri uri = message.getUri();
|
||||
if (success)
|
||||
{
|
||||
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 (message.scheduleRetry())
|
||||
{
|
||||
message.setProcessingState(IncomingMessage.ProcessingState.Scheduled);
|
||||
}
|
||||
else
|
||||
{
|
||||
incomingMessages.remove(uri);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public synchronized void notifyOutgoingMessageStatus(Uri uri, int resultCode, int partIndex, int numParts) {
|
||||
OutgoingMessage sms = outgoingMessages.get(uri);
|
||||
|
||||
if (sms == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (partIndex != 0)
|
||||
{
|
||||
// TODO: process message status for parts other than the first one
|
||||
return;
|
||||
}
|
||||
|
||||
switch (resultCode) {
|
||||
case Activity.RESULT_OK:
|
||||
this.notifyStatus(sms, App.STATUS_SENT, "");
|
||||
break;
|
||||
case SmsManager.RESULT_ERROR_GENERIC_FAILURE:
|
||||
this.notifyStatus(sms, App.STATUS_FAILED, "generic failure");
|
||||
break;
|
||||
case SmsManager.RESULT_ERROR_RADIO_OFF:
|
||||
this.notifyStatus(sms, App.STATUS_FAILED, "radio off");
|
||||
break;
|
||||
case SmsManager.RESULT_ERROR_NO_SERVICE:
|
||||
this.notifyStatus(sms, App.STATUS_FAILED, "no service");
|
||||
break;
|
||||
case SmsManager.RESULT_ERROR_NULL_PDU:
|
||||
this.notifyStatus(sms, App.STATUS_FAILED, "null PDU");
|
||||
break;
|
||||
default:
|
||||
this.notifyStatus(sms, App.STATUS_FAILED, "unknown error");
|
||||
break;
|
||||
}
|
||||
|
||||
sms.setProcessingState(OutgoingMessage.ProcessingState.None);
|
||||
|
||||
switch (resultCode) {
|
||||
case SmsManager.RESULT_ERROR_GENERIC_FAILURE:
|
||||
case SmsManager.RESULT_ERROR_RADIO_OFF:
|
||||
case SmsManager.RESULT_ERROR_NO_SERVICE:
|
||||
if (sms.scheduleRetry()) {
|
||||
sms.setProcessingState(OutgoingMessage.ProcessingState.Scheduled);
|
||||
}
|
||||
else {
|
||||
outgoingMessages.remove(uri);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
outgoingMessages.remove(uri);
|
||||
break;
|
||||
}
|
||||
|
||||
numPendingOutgoingMessages--;
|
||||
maybeDequeueOutgoingMessage();
|
||||
}
|
||||
|
||||
public synchronized void sendOutgoingMessage(OutgoingMessage sms) {
|
||||
|
||||
String to = sms.getTo();
|
||||
if (to == null || to.length() == 0)
|
||||
{
|
||||
notifyStatus(sms, App.STATUS_FAILED, "Destination address is empty");
|
||||
return;
|
||||
}
|
||||
|
||||
if (isTestMode() && !isTestPhoneNumber(to))
|
||||
{
|
||||
// this is mostly to prevent accidentally sending real messages to
|
||||
// random people while testing...
|
||||
notifyStatus(sms, App.STATUS_FAILED, "Destination number is not in list of test senders");
|
||||
return;
|
||||
}
|
||||
|
||||
String messageBody = sms.getMessageBody();
|
||||
|
||||
if (messageBody == null || messageBody.length() == 0)
|
||||
{
|
||||
notifyStatus(sms, App.STATUS_FAILED, "Message body is empty");
|
||||
return;
|
||||
}
|
||||
|
||||
Uri uri = sms.getUri();
|
||||
if (outgoingMessages.containsKey(uri)) {
|
||||
debug("Duplicate outgoing " + sms.getLogName() + ", skipping");
|
||||
return;
|
||||
}
|
||||
|
||||
outgoingMessages.put(uri, sms);
|
||||
enqueueOutgoingMessage(sms);
|
||||
}
|
||||
|
||||
public synchronized void maybeDequeueOutgoingMessage()
|
||||
{
|
||||
long now = System.currentTimeMillis();
|
||||
if (nextValidOutgoingTime <= now && numPendingOutgoingMessages < 2)
|
||||
{
|
||||
OutgoingMessage sms = outgoingQueue.peek();
|
||||
|
||||
if (sms == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
SmsManager smgr = SmsManager.getDefault();
|
||||
ArrayList<String> bodyParts = smgr.divideMessage(sms.getMessageBody());
|
||||
|
||||
int numParts = bodyParts.size();
|
||||
|
||||
if (numParts > App.OUTGOING_SMS_MAX_COUNT)
|
||||
{
|
||||
outgoingQueue.poll();
|
||||
outgoingMessages.remove(sms.getUri());
|
||||
notifyStatus(sms, App.STATUS_FAILED, "Message has too many parts ("+(numParts)+")");
|
||||
return;
|
||||
}
|
||||
|
||||
String packageName = chooseOutgoingSmsPackage(numParts);
|
||||
|
||||
if (packageName == null)
|
||||
{
|
||||
nextValidOutgoingTime = getNextValidOutgoingTime(numParts);
|
||||
|
||||
if (nextValidOutgoingTime <= now) // should never happen
|
||||
{
|
||||
nextValidOutgoingTime = now + 2000;
|
||||
}
|
||||
|
||||
long diff = nextValidOutgoingTime - now;
|
||||
|
||||
log("Waiting for " + (diff/1000) + " seconds");
|
||||
|
||||
AlarmManager alarm = (AlarmManager) getSystemService(Context.ALARM_SERVICE);
|
||||
|
||||
Intent intent = new Intent(this, DequeueOutgoingMessageReceiver.class);
|
||||
|
||||
PendingIntent pendingIntent = PendingIntent.getBroadcast(this,
|
||||
0,
|
||||
intent,
|
||||
0);
|
||||
|
||||
alarm.set(
|
||||
AlarmManager.RTC_WAKEUP,
|
||||
nextValidOutgoingTime,
|
||||
pendingIntent);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
outgoingQueue.poll();
|
||||
numPendingOutgoingMessages++;
|
||||
|
||||
sms.setProcessingState(OutgoingMessage.ProcessingState.Sending);
|
||||
|
||||
sms.trySend(bodyParts, packageName);
|
||||
}
|
||||
}
|
||||
|
||||
public synchronized void enqueueOutgoingMessage(OutgoingMessage sms)
|
||||
{
|
||||
outgoingQueue.add(sms);
|
||||
sms.setProcessingState(OutgoingMessage.ProcessingState.Queued);
|
||||
maybeDequeueOutgoingMessage();
|
||||
}
|
||||
|
||||
public synchronized void forwardToServer(IncomingMessage message) {
|
||||
Uri uri = message.getUri();
|
||||
|
||||
if (incomingMessages.containsKey(uri)) {
|
||||
log("Duplicate incoming "+message.getDisplayType()+", skipping");
|
||||
return;
|
||||
}
|
||||
|
||||
incomingMessages.put(uri, message);
|
||||
|
||||
log("Received "+message.getDisplayType()+" from " + message.getFrom());
|
||||
|
||||
enqueueIncomingMessage(message);
|
||||
}
|
||||
|
||||
public synchronized void enqueueIncomingMessage(IncomingMessage message)
|
||||
{
|
||||
message.setProcessingState(IncomingMessage.ProcessingState.Forwarding);
|
||||
message.tryForwardToServer();
|
||||
}
|
||||
|
||||
public synchronized void retryIncomingMessage(Uri uri) {
|
||||
IncomingMessage message = incomingMessages.get(uri);
|
||||
if (message != null
|
||||
&& message.getProcessingState() == IncomingMessage.ProcessingState.Scheduled) {
|
||||
enqueueIncomingMessage(message);
|
||||
}
|
||||
}
|
||||
|
||||
public synchronized void retryOutgoingMessage(Uri uri) {
|
||||
OutgoingMessage sms = outgoingMessages.get(uri);
|
||||
if (sms != null
|
||||
&& sms.getProcessingState() == OutgoingMessage.ProcessingState.Scheduled) {
|
||||
enqueueOutgoingMessage(sms);
|
||||
}
|
||||
}
|
||||
|
||||
public void debug(String msg) {
|
||||
Log.d(LOG_NAME, msg);
|
||||
}
|
||||
@ -783,8 +471,7 @@ public final class App extends Application {
|
||||
displayedLog.append(msg);
|
||||
displayedLog.append("\n");
|
||||
|
||||
Intent broadcast = new Intent(App.LOG_INTENT);
|
||||
sendBroadcast(broadcast);
|
||||
sendBroadcast(new Intent(App.LOG_CHANGED_INTENT));
|
||||
}
|
||||
|
||||
public synchronized CharSequence getDisplayedLog()
|
||||
@ -1092,15 +779,14 @@ public final class App extends Application {
|
||||
asyncCheckConnectivity();
|
||||
}
|
||||
|
||||
public void onConnectivityRestored()
|
||||
private void onConnectivityRestored()
|
||||
{
|
||||
retryStuckIncomingMessages();
|
||||
inbox.retryAll();
|
||||
|
||||
if (getOutgoingPollSeconds() > 0)
|
||||
{
|
||||
checkOutgoingMessages();
|
||||
}
|
||||
|
||||
}
|
||||
// failed outgoing message status notifications are dropped...
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -45,7 +45,7 @@ public class CheckMmsInboxService extends IntentService
|
||||
|
||||
if (mms.isForwardable())
|
||||
{
|
||||
app.forwardToServer(mms);
|
||||
app.inbox.forwardMessage(mms);
|
||||
}
|
||||
else
|
||||
{
|
||||
|
@ -26,13 +26,12 @@ import android.util.Log;
|
||||
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.lang.reflect.Method;
|
||||
import org.envaya.sms.R;
|
||||
import org.envaya.sms.ui.Main;
|
||||
|
||||
/*
|
||||
* Service running in foreground to make sure App instance stays
|
||||
* in memory (otherwise we could lose timestamps of sent messages
|
||||
* which could cause us to exceed Android's SMS sending limit)
|
||||
* in memory (to avoid losing pending messages and timestamps of
|
||||
* sent messages).
|
||||
*
|
||||
* Also adds notification to status bar.
|
||||
*/
|
||||
|
112
src/org/envaya/sms/Inbox.java
Executable file
112
src/org/envaya/sms/Inbox.java
Executable file
@ -0,0 +1,112 @@
|
||||
package org.envaya.sms;
|
||||
|
||||
import android.content.Intent;
|
||||
import android.net.Uri;
|
||||
import java.util.Collection;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
public class Inbox {
|
||||
private Map<Uri, IncomingMessage> incomingMessages = new HashMap<Uri, IncomingMessage>();
|
||||
private App app;
|
||||
|
||||
public Inbox(App app)
|
||||
{
|
||||
this.app = app;
|
||||
}
|
||||
|
||||
public IncomingMessage getMessage(Uri uri)
|
||||
{
|
||||
return incomingMessages.get(uri);
|
||||
}
|
||||
|
||||
public synchronized void forwardMessage(IncomingMessage message) {
|
||||
Uri uri = message.getUri();
|
||||
|
||||
if (incomingMessages.containsKey(uri)) {
|
||||
app.log("Duplicate incoming "+message.getDisplayType()+", skipping");
|
||||
return;
|
||||
}
|
||||
|
||||
incomingMessages.put(uri, message);
|
||||
|
||||
app.log("Received "+message.getDisplayType()+" from " + message.getFrom());
|
||||
|
||||
message.setProcessingState(IncomingMessage.ProcessingState.Forwarding);
|
||||
message.tryForwardToServer();
|
||||
|
||||
notifyChanged();
|
||||
}
|
||||
|
||||
public synchronized void retryForwardMessage(IncomingMessage message)
|
||||
{
|
||||
IncomingMessage.ProcessingState state = message.getProcessingState();
|
||||
|
||||
if (state == IncomingMessage.ProcessingState.Scheduled
|
||||
|| state == IncomingMessage.ProcessingState.None)
|
||||
{
|
||||
message.setProcessingState(IncomingMessage.ProcessingState.Forwarding);
|
||||
message.tryForwardToServer();
|
||||
|
||||
notifyChanged();
|
||||
}
|
||||
}
|
||||
|
||||
public synchronized void deleteMessage(IncomingMessage message)
|
||||
{
|
||||
incomingMessages.remove(message.getUri());
|
||||
app.log("SMS from " + message.getFrom() + " deleted");
|
||||
notifyChanged();
|
||||
}
|
||||
|
||||
public synchronized void messageFailed(IncomingMessage message)
|
||||
{
|
||||
message.setProcessingState(IncomingMessage.ProcessingState.None);
|
||||
|
||||
if (message.scheduleRetry())
|
||||
{
|
||||
message.setProcessingState(IncomingMessage.ProcessingState.Scheduled);
|
||||
}
|
||||
notifyChanged();
|
||||
}
|
||||
|
||||
public synchronized void messageForwarded(IncomingMessage message) {
|
||||
|
||||
message.setProcessingState(IncomingMessage.ProcessingState.Forwarded);
|
||||
|
||||
Uri uri = message.getUri();
|
||||
incomingMessages.remove(uri);
|
||||
|
||||
notifyChanged();
|
||||
|
||||
if (message instanceof IncomingMms)
|
||||
{
|
||||
IncomingMms mms = (IncomingMms)message;
|
||||
if (!app.getKeepInInbox())
|
||||
{
|
||||
app.log("Deleting MMS " + mms.getId() + " from inbox...");
|
||||
app.getMmsUtils().deleteFromInbox(mms);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void notifyChanged()
|
||||
{
|
||||
app.sendBroadcast(new Intent(App.INBOX_CHANGED_INTENT));
|
||||
}
|
||||
|
||||
public synchronized void retryAll() {
|
||||
for (IncomingMessage message : incomingMessages.values()) {
|
||||
retryForwardMessage(message);
|
||||
}
|
||||
}
|
||||
|
||||
public synchronized int size() {
|
||||
return incomingMessages.size();
|
||||
}
|
||||
|
||||
public synchronized Collection<IncomingMessage> getMessages()
|
||||
{
|
||||
return incomingMessages.values();
|
||||
}
|
||||
}
|
@ -14,7 +14,8 @@ public abstract class IncomingMessage extends QueuedMessage {
|
||||
{
|
||||
None, // not doing anything with this sms now... just sitting around
|
||||
Forwarding, // currently sending to server
|
||||
Scheduled // waiting for a while before retrying after failure forwarding
|
||||
Scheduled, // waiting for a while before retrying after failure forwarding
|
||||
Forwarded
|
||||
}
|
||||
|
||||
public IncomingMessage(App app, String from, long timestamp)
|
||||
@ -38,10 +39,7 @@ public abstract class IncomingMessage extends QueuedMessage {
|
||||
{
|
||||
this.state = status;
|
||||
}
|
||||
|
||||
|
||||
public abstract String getDisplayType();
|
||||
|
||||
|
||||
public boolean isForwardable()
|
||||
{
|
||||
if (app.isTestMode() && !app.isTestPhoneNumber(from))
|
||||
@ -86,5 +84,23 @@ public abstract class IncomingMessage extends QueuedMessage {
|
||||
return intent;
|
||||
}
|
||||
|
||||
public String getStatusText()
|
||||
{
|
||||
switch (state)
|
||||
{
|
||||
case Scheduled:
|
||||
return "scheduled retry";
|
||||
case Forwarding:
|
||||
return "forwarding to server";
|
||||
default:
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
public String getDescription()
|
||||
{
|
||||
return getDisplayType() + " from " + getFrom();
|
||||
}
|
||||
|
||||
public abstract void tryForwardToServer();
|
||||
}
|
||||
|
@ -27,7 +27,7 @@ public class IncomingSms extends IncomingMessage {
|
||||
for (int i = 1; i < numParts; i++)
|
||||
{
|
||||
SmsMessage smsPart = smsParts.get(i);
|
||||
|
||||
|
||||
if (!smsPart.getOriginatingAddress().equals(from))
|
||||
{
|
||||
throw new InvalidParameterException(
|
||||
|
280
src/org/envaya/sms/Outbox.java
Executable file
280
src/org/envaya/sms/Outbox.java
Executable file
@ -0,0 +1,280 @@
|
||||
|
||||
package org.envaya.sms;
|
||||
|
||||
import android.app.AlarmManager;
|
||||
import android.app.PendingIntent;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.net.Uri;
|
||||
import android.telephony.SmsManager;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Comparator;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.PriorityQueue;
|
||||
import org.apache.http.message.BasicNameValuePair;
|
||||
import org.envaya.sms.receiver.DequeueOutgoingMessageReceiver;
|
||||
import org.envaya.sms.task.HttpTask;
|
||||
|
||||
public class Outbox {
|
||||
private Map<Uri, OutgoingMessage> outgoingMessages = new HashMap<Uri, OutgoingMessage>();
|
||||
|
||||
private App app;
|
||||
|
||||
// number of outgoing messages that are currently being sent and waiting for
|
||||
// messageSent or messageFailed to be called
|
||||
private int numSendingOutgoingMessages = 0;
|
||||
|
||||
// cache of next time we can send the first message in queue without
|
||||
// exceeding android sending limit
|
||||
private long nextValidOutgoingTime;
|
||||
|
||||
// enqueue outgoing messages in descending order by priority, ascending by local id
|
||||
// (order in which message was received)
|
||||
private PriorityQueue<OutgoingMessage> outgoingQueue = new PriorityQueue<OutgoingMessage>(10,
|
||||
new Comparator<OutgoingMessage>() {
|
||||
public int compare(OutgoingMessage t1, OutgoingMessage t2)
|
||||
{
|
||||
int pri2 = t2.getPriority();
|
||||
int pri1 = t1.getPriority();
|
||||
|
||||
if (pri1 != pri2)
|
||||
{
|
||||
return pri2 - pri1;
|
||||
}
|
||||
|
||||
int order2 = t2.getLocalId();
|
||||
int order1 = t1.getLocalId();
|
||||
|
||||
return order1 - order2;
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
public Outbox(App app)
|
||||
{
|
||||
this.app = app;
|
||||
}
|
||||
|
||||
private void notifyMessageStatus(OutgoingMessage 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)) {
|
||||
logMessage = "could not be sent (" + errorMessage + ")";
|
||||
} else {
|
||||
logMessage = "queued";
|
||||
}
|
||||
String smsDesc = sms.getLogName();
|
||||
|
||||
if (serverId != null) {
|
||||
app.log("Notifying server " + smsDesc + " " + logMessage);
|
||||
|
||||
new HttpTask(app,
|
||||
new BasicNameValuePair("id", serverId),
|
||||
new BasicNameValuePair("status", status),
|
||||
new BasicNameValuePair("error", errorMessage),
|
||||
new BasicNameValuePair("action", App.ACTION_SEND_STATUS)
|
||||
).execute();
|
||||
} else {
|
||||
app.log(smsDesc + " " + logMessage);
|
||||
}
|
||||
}
|
||||
|
||||
public synchronized void retryAll()
|
||||
{
|
||||
nextValidOutgoingTime = 0;
|
||||
|
||||
for (OutgoingMessage sms : outgoingMessages.values()) {
|
||||
enqueueMessage(sms);
|
||||
}
|
||||
maybeDequeueMessage();
|
||||
}
|
||||
|
||||
public OutgoingMessage getMessage(Uri uri)
|
||||
{
|
||||
return outgoingMessages.get(uri);
|
||||
}
|
||||
|
||||
public synchronized void messageSent(OutgoingMessage sms)
|
||||
{
|
||||
sms.setProcessingState(OutgoingMessage.ProcessingState.Sent);
|
||||
|
||||
notifyMessageStatus(sms, App.STATUS_SENT, "");
|
||||
|
||||
outgoingMessages.remove(sms.getUri());
|
||||
|
||||
notifyChanged();
|
||||
|
||||
numSendingOutgoingMessages--;
|
||||
maybeDequeueMessage();
|
||||
}
|
||||
|
||||
public synchronized void messageFailed(OutgoingMessage sms, String error)
|
||||
{
|
||||
if (sms.scheduleRetry())
|
||||
{
|
||||
sms.setProcessingState(OutgoingMessage.ProcessingState.Scheduled);
|
||||
}
|
||||
else
|
||||
{
|
||||
sms.setProcessingState(OutgoingMessage.ProcessingState.None);
|
||||
}
|
||||
notifyChanged();
|
||||
notifyMessageStatus(sms, App.STATUS_FAILED, error);
|
||||
|
||||
numSendingOutgoingMessages--;
|
||||
maybeDequeueMessage();
|
||||
}
|
||||
|
||||
public synchronized void sendMessage(OutgoingMessage sms) {
|
||||
|
||||
String to = sms.getTo();
|
||||
if (to == null || to.length() == 0)
|
||||
{
|
||||
notifyMessageStatus(sms, App.STATUS_FAILED,
|
||||
"Destination address is empty");
|
||||
return;
|
||||
}
|
||||
|
||||
if (app.isTestMode() && !app.isTestPhoneNumber(to))
|
||||
{
|
||||
// this is mostly to prevent accidentally sending real messages to
|
||||
// random people while testing...
|
||||
notifyMessageStatus(sms, App.STATUS_FAILED,
|
||||
"Destination number is not in list of test senders");
|
||||
return;
|
||||
}
|
||||
|
||||
String messageBody = sms.getMessageBody();
|
||||
|
||||
if (messageBody == null || messageBody.length() == 0)
|
||||
{
|
||||
notifyMessageStatus(sms, App.STATUS_FAILED,
|
||||
"Message body is empty");
|
||||
return;
|
||||
}
|
||||
|
||||
Uri uri = sms.getUri();
|
||||
if (outgoingMessages.containsKey(uri)) {
|
||||
app.debug("Duplicate outgoing " + sms.getLogName() + ", skipping");
|
||||
return;
|
||||
}
|
||||
|
||||
outgoingMessages.put(uri, sms);
|
||||
enqueueMessage(sms);
|
||||
}
|
||||
|
||||
public synchronized void deleteMessage(OutgoingMessage message)
|
||||
{
|
||||
outgoingMessages.remove(message.getUri());
|
||||
|
||||
if (message.getProcessingState() == OutgoingMessage.ProcessingState.Queued)
|
||||
{
|
||||
outgoingQueue.remove(message);
|
||||
}
|
||||
notifyMessageStatus(message, App.STATUS_FAILED,
|
||||
"deleted by user");
|
||||
app.log("SMS to " + message.getTo() + " deleted");
|
||||
notifyChanged();
|
||||
}
|
||||
|
||||
public synchronized void maybeDequeueMessage()
|
||||
{
|
||||
long now = System.currentTimeMillis();
|
||||
if (nextValidOutgoingTime <= now && numSendingOutgoingMessages < 2)
|
||||
{
|
||||
OutgoingMessage sms = outgoingQueue.peek();
|
||||
|
||||
if (sms == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
SmsManager smgr = SmsManager.getDefault();
|
||||
ArrayList<String> bodyParts = smgr.divideMessage(sms.getMessageBody());
|
||||
|
||||
int numParts = bodyParts.size();
|
||||
|
||||
if (numParts > App.OUTGOING_SMS_MAX_COUNT)
|
||||
{
|
||||
outgoingQueue.poll();
|
||||
outgoingMessages.remove(sms.getUri());
|
||||
notifyMessageStatus(sms, App.STATUS_FAILED,
|
||||
"Message has too many parts ("+(numParts)+")");
|
||||
return;
|
||||
}
|
||||
|
||||
String packageName = app.chooseOutgoingSmsPackage(numParts);
|
||||
|
||||
if (packageName == null)
|
||||
{
|
||||
nextValidOutgoingTime = app.getNextValidOutgoingTime(numParts);
|
||||
|
||||
if (nextValidOutgoingTime <= now) // should never happen
|
||||
{
|
||||
nextValidOutgoingTime = now + 2000;
|
||||
}
|
||||
|
||||
long diff = nextValidOutgoingTime - now;
|
||||
|
||||
app.log("Waiting for " + (diff/1000) + " seconds");
|
||||
|
||||
AlarmManager alarm = (AlarmManager) app.getSystemService(Context.ALARM_SERVICE);
|
||||
|
||||
Intent intent = new Intent(app, DequeueOutgoingMessageReceiver.class);
|
||||
|
||||
PendingIntent pendingIntent = PendingIntent.getBroadcast(app,
|
||||
0,
|
||||
intent,
|
||||
0);
|
||||
|
||||
alarm.set(
|
||||
AlarmManager.RTC_WAKEUP,
|
||||
nextValidOutgoingTime,
|
||||
pendingIntent);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
outgoingQueue.poll();
|
||||
numSendingOutgoingMessages++;
|
||||
|
||||
sms.setProcessingState(OutgoingMessage.ProcessingState.Sending);
|
||||
|
||||
sms.trySend(bodyParts, packageName);
|
||||
notifyChanged();
|
||||
}
|
||||
}
|
||||
|
||||
public synchronized void enqueueMessage(OutgoingMessage message)
|
||||
{
|
||||
OutgoingMessage.ProcessingState state = message.getProcessingState();
|
||||
|
||||
if (state == OutgoingMessage.ProcessingState.Scheduled
|
||||
|| state == OutgoingMessage.ProcessingState.None)
|
||||
{
|
||||
outgoingQueue.add(message);
|
||||
message.setProcessingState(OutgoingMessage.ProcessingState.Queued);
|
||||
notifyChanged();
|
||||
maybeDequeueMessage();
|
||||
}
|
||||
}
|
||||
|
||||
private void notifyChanged()
|
||||
{
|
||||
app.sendBroadcast(new Intent(App.OUTBOX_CHANGED_INTENT));
|
||||
}
|
||||
|
||||
public synchronized int size() {
|
||||
return outgoingMessages.size();
|
||||
}
|
||||
|
||||
public synchronized Collection<OutgoingMessage> getMessages()
|
||||
{
|
||||
return outgoingMessages.values();
|
||||
}
|
||||
}
|
@ -23,7 +23,8 @@ public class OutgoingMessage extends QueuedMessage {
|
||||
None, // not doing anything with this sms now... just sitting around
|
||||
Queued, // in the outgoing queue waiting to be sent
|
||||
Sending, // passed to an expansion pack, waiting for status notification
|
||||
Scheduled // waiting for a while before retrying after failure sending
|
||||
Scheduled, // waiting for a while before retrying after failure sending
|
||||
Sent
|
||||
}
|
||||
|
||||
public OutgoingMessage(App app)
|
||||
@ -143,4 +144,29 @@ public class OutgoingMessage extends QueuedMessage {
|
||||
intent.setData(this.getUri());
|
||||
return intent;
|
||||
}
|
||||
|
||||
public String getStatusText()
|
||||
{
|
||||
switch (state)
|
||||
{
|
||||
case Scheduled:
|
||||
return "scheduled retry";
|
||||
case Queued:
|
||||
return "queued to send";
|
||||
case Sending:
|
||||
return "sending";
|
||||
default:
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
public String getDescription()
|
||||
{
|
||||
return getDisplayType() + " to " + getTo();
|
||||
}
|
||||
|
||||
public String getDisplayType()
|
||||
{
|
||||
return "SMS";
|
||||
}
|
||||
}
|
||||
|
@ -6,18 +6,29 @@ import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.net.Uri;
|
||||
import android.os.SystemClock;
|
||||
import java.util.Date;
|
||||
|
||||
public abstract class QueuedMessage
|
||||
{
|
||||
protected long nextRetryTime = 0;
|
||||
protected int numRetries = 0;
|
||||
|
||||
protected Date dateCreated = new Date();
|
||||
public App app;
|
||||
|
||||
public QueuedMessage(App app)
|
||||
{
|
||||
this.app = app;
|
||||
}
|
||||
}
|
||||
|
||||
public Date getDateCreated()
|
||||
{
|
||||
return dateCreated;
|
||||
}
|
||||
|
||||
public int getNumRetries()
|
||||
{
|
||||
return numRetries;
|
||||
}
|
||||
|
||||
public boolean canRetryNow() {
|
||||
return (nextRetryTime > 0 && nextRetryTime < SystemClock.elapsedRealtime());
|
||||
@ -63,6 +74,10 @@ public abstract class QueuedMessage
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public abstract String getDisplayType();
|
||||
public abstract String getDescription();
|
||||
public abstract String getStatusText();
|
||||
|
||||
public abstract Uri getUri();
|
||||
|
||||
|
@ -4,8 +4,6 @@ package org.envaya.sms.receiver;
|
||||
import android.content.BroadcastReceiver;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.net.ConnectivityManager;
|
||||
import android.net.NetworkInfo;
|
||||
import org.envaya.sms.App;
|
||||
|
||||
public class ConnectivityChangeReceiver extends BroadcastReceiver {
|
||||
|
@ -16,6 +16,6 @@ public class DequeueOutgoingMessageReceiver extends BroadcastReceiver {
|
||||
return;
|
||||
}
|
||||
|
||||
app.maybeDequeueOutgoingMessage();
|
||||
app.outbox.maybeDequeueMessage();
|
||||
}
|
||||
}
|
||||
|
@ -5,6 +5,7 @@ import android.content.BroadcastReceiver;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import org.envaya.sms.App;
|
||||
import org.envaya.sms.IncomingMessage;
|
||||
|
||||
public class IncomingMessageRetry extends BroadcastReceiver
|
||||
{
|
||||
@ -17,6 +18,13 @@ public class IncomingMessageRetry extends BroadcastReceiver
|
||||
return;
|
||||
}
|
||||
|
||||
app.retryIncomingMessage(intent.getData());
|
||||
IncomingMessage message = app.inbox.getMessage(intent.getData());
|
||||
|
||||
if (message == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
app.inbox.retryForwardMessage(message);
|
||||
}
|
||||
}
|
||||
|
@ -4,12 +4,15 @@
|
||||
*/
|
||||
package org.envaya.sms.receiver;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.BroadcastReceiver;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.net.Uri;
|
||||
import android.os.Bundle;
|
||||
import android.telephony.SmsManager;
|
||||
import org.envaya.sms.App;
|
||||
import org.envaya.sms.OutgoingMessage;
|
||||
|
||||
public class MessageStatusNotifier extends BroadcastReceiver {
|
||||
|
||||
@ -20,18 +23,56 @@ public class MessageStatusNotifier extends BroadcastReceiver {
|
||||
|
||||
Bundle extras = intent.getExtras();
|
||||
int index = extras.getInt(App.STATUS_EXTRA_INDEX);
|
||||
int numParts = extras.getInt(App.STATUS_EXTRA_NUM_PARTS);
|
||||
//int numParts = extras.getInt(App.STATUS_EXTRA_NUM_PARTS);
|
||||
|
||||
OutgoingMessage sms = app.outbox.getMessage(uri);
|
||||
|
||||
if (sms == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (index != 0)
|
||||
{
|
||||
// TODO: process message status for parts other than the first one
|
||||
return;
|
||||
}
|
||||
|
||||
int resultCode = getResultCode();
|
||||
|
||||
// uncomment to test retry on outgoing message failure
|
||||
/*
|
||||
/*
|
||||
// uncomment to test retry on outgoing message failure
|
||||
if (Math.random() > 0.4)
|
||||
{
|
||||
resultCode = SmsManager.RESULT_ERROR_NO_SERVICE;
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
||||
app.notifyOutgoingMessageStatus(uri, resultCode, index, numParts);
|
||||
if (resultCode == Activity.RESULT_OK)
|
||||
{
|
||||
app.outbox.messageSent(sms);
|
||||
}
|
||||
else
|
||||
{
|
||||
app.outbox.messageFailed(sms, getErrorMessage(resultCode));
|
||||
}
|
||||
}
|
||||
|
||||
public String getErrorMessage(int resultCode)
|
||||
{
|
||||
switch (resultCode) {
|
||||
case Activity.RESULT_OK:
|
||||
return "";
|
||||
case SmsManager.RESULT_ERROR_GENERIC_FAILURE:
|
||||
return "generic failure";
|
||||
case SmsManager.RESULT_ERROR_RADIO_OFF:
|
||||
return "radio off";
|
||||
case SmsManager.RESULT_ERROR_NO_SERVICE:
|
||||
return "no service";
|
||||
case SmsManager.RESULT_ERROR_NULL_PDU:
|
||||
return "null PDU";
|
||||
default:
|
||||
return "unknown error";
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
@ -5,6 +5,7 @@ import android.content.BroadcastReceiver;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import org.envaya.sms.App;
|
||||
import org.envaya.sms.OutgoingMessage;
|
||||
|
||||
public class OutgoingMessageRetry extends BroadcastReceiver
|
||||
{
|
||||
@ -16,6 +17,13 @@ public class OutgoingMessageRetry extends BroadcastReceiver
|
||||
{
|
||||
return;
|
||||
}
|
||||
app.retryOutgoingMessage(intent.getData());
|
||||
|
||||
OutgoingMessage message = app.outbox.getMessage(intent.getData());
|
||||
if (message == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
app.outbox.enqueueMessage(message);
|
||||
}
|
||||
}
|
||||
|
@ -29,7 +29,7 @@ public class SmsReceiver extends BroadcastReceiver {
|
||||
|
||||
if (sms.isForwardable())
|
||||
{
|
||||
app.forwardToServer(sms);
|
||||
app.inbox.forwardMessage(sms);
|
||||
|
||||
if (!app.getKeepInInbox())
|
||||
{
|
||||
|
@ -28,14 +28,13 @@ public class ForwarderTask extends HttpTask {
|
||||
protected void handleResponse(HttpResponse response) throws Exception {
|
||||
|
||||
for (OutgoingMessage reply : parseResponseXML(response)) {
|
||||
app.sendOutgoingMessage(reply);
|
||||
app.outbox.sendMessage(reply);
|
||||
}
|
||||
|
||||
app.setIncomingMessageStatus(message, true);
|
||||
app.inbox.messageForwarded(message);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void handleFailure() {
|
||||
app.setIncomingMessageStatus(message, false);
|
||||
app.inbox.messageFailed(message);
|
||||
}
|
||||
}
|
||||
|
@ -15,7 +15,7 @@ public class PollerTask extends HttpTask {
|
||||
@Override
|
||||
protected void handleResponse(HttpResponse response) throws Exception {
|
||||
for (OutgoingMessage reply : parseResponseXML(response)) {
|
||||
app.sendOutgoingMessage(reply);
|
||||
app.outbox.sendMessage(reply);
|
||||
}
|
||||
}
|
||||
}
|
@ -1,105 +0,0 @@
|
||||
package org.envaya.sms.ui;
|
||||
|
||||
// from http://www.marvinlabs.com/2010/10/custom-listview-ability-check-items/
|
||||
// package fr.marvinlabs.widget;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import android.content.Context;
|
||||
import android.util.AttributeSet;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.Checkable;
|
||||
import android.widget.RelativeLayout;
|
||||
|
||||
/**
|
||||
* Extension of a relative layout to provide a checkable behavior
|
||||
*
|
||||
* @author marvinlabs
|
||||
*/
|
||||
public class CheckableRelativeLayout extends RelativeLayout implements
|
||||
Checkable {
|
||||
|
||||
private boolean isChecked;
|
||||
private List<Checkable> checkableViews;
|
||||
|
||||
public CheckableRelativeLayout(Context context, AttributeSet attrs,
|
||||
int defStyle) {
|
||||
super(context, attrs, defStyle);
|
||||
initialise(attrs);
|
||||
}
|
||||
|
||||
public CheckableRelativeLayout(Context context, AttributeSet attrs) {
|
||||
super(context, attrs);
|
||||
initialise(attrs);
|
||||
}
|
||||
|
||||
public CheckableRelativeLayout(Context context, int checkableId) {
|
||||
super(context);
|
||||
initialise(null);
|
||||
}
|
||||
|
||||
/*
|
||||
* @see android.widget.Checkable#isChecked()
|
||||
*/
|
||||
public boolean isChecked() {
|
||||
return isChecked;
|
||||
}
|
||||
|
||||
/*
|
||||
* @see android.widget.Checkable#setChecked(boolean)
|
||||
*/
|
||||
public void setChecked(boolean isChecked) {
|
||||
|
||||
this.isChecked = isChecked;
|
||||
for (Checkable c : checkableViews) {
|
||||
c.setChecked(isChecked);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* @see android.widget.Checkable#toggle()
|
||||
*/
|
||||
public void toggle() {
|
||||
this.isChecked = !this.isChecked;
|
||||
for (Checkable c : checkableViews) {
|
||||
c.toggle();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onFinishInflate() {
|
||||
super.onFinishInflate();
|
||||
|
||||
final int childCount = this.getChildCount();
|
||||
for (int i = 0; i < childCount; ++i) {
|
||||
findCheckableChildren(this.getChildAt(i));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Read the custom XML attributes
|
||||
*/
|
||||
private void initialise(AttributeSet attrs) {
|
||||
this.isChecked = false;
|
||||
this.checkableViews = new ArrayList<Checkable>(5);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add to our checkable list all the children of the view that implement the
|
||||
* interface Checkable
|
||||
*/
|
||||
private void findCheckableChildren(View v) {
|
||||
if (v instanceof Checkable) {
|
||||
this.checkableViews.add((Checkable) v);
|
||||
}
|
||||
|
||||
if (v instanceof ViewGroup) {
|
||||
final ViewGroup vg = (ViewGroup) v;
|
||||
final int childCount = vg.getChildCount();
|
||||
for (int i = 0; i < childCount; ++i) {
|
||||
findCheckableChildren(vg.getChildAt(i));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -1,86 +0,0 @@
|
||||
|
||||
package org.envaya.sms.ui;
|
||||
|
||||
import android.app.ListActivity;
|
||||
import android.database.Cursor;
|
||||
import android.net.Uri;
|
||||
import android.os.Bundle;
|
||||
import android.util.SparseBooleanArray;
|
||||
import android.view.View;
|
||||
import android.widget.ListView;
|
||||
import android.widget.SimpleCursorAdapter;
|
||||
import org.envaya.sms.App;
|
||||
import org.envaya.sms.IncomingMessage;
|
||||
import org.envaya.sms.IncomingSms;
|
||||
import org.envaya.sms.R;
|
||||
|
||||
|
||||
public class ForwardInbox extends ListActivity {
|
||||
|
||||
private App app;
|
||||
|
||||
private Cursor cur;
|
||||
|
||||
/** Called when the activity is first created. */
|
||||
@Override
|
||||
public void onCreate(Bundle icicle) {
|
||||
super.onCreate(icicle);
|
||||
|
||||
app = (App) getApplication();
|
||||
|
||||
setContentView(R.layout.inbox);
|
||||
|
||||
// undocumented API; see
|
||||
// core/java/android/provider/Telephony.java
|
||||
|
||||
Uri inboxUri = Uri.parse("content://sms/inbox");
|
||||
|
||||
cur = getContentResolver().query(inboxUri, null, null, null, null);
|
||||
|
||||
SimpleCursorAdapter adapter = new SimpleCursorAdapter(this,
|
||||
R.layout.inbox_item,
|
||||
cur,
|
||||
new String[] {"address","body"},
|
||||
new int[] {R.id.inbox_address, R.id.inbox_body});
|
||||
|
||||
setListAdapter(adapter);
|
||||
|
||||
ListView listView = getListView();
|
||||
|
||||
listView.setItemsCanFocus(false);
|
||||
}
|
||||
|
||||
public void forwardSelected(View view) {
|
||||
|
||||
ListView listView = getListView();
|
||||
|
||||
SparseBooleanArray checkedItems = listView.getCheckedItemPositions();
|
||||
|
||||
int checkedItemsCount = checkedItems.size();
|
||||
|
||||
int addressIndex = cur.getColumnIndex("address");
|
||||
int bodyIndex = cur.getColumnIndex("body");
|
||||
int dateIndex = cur.getColumnIndex("date");
|
||||
|
||||
for (int i = 0; i < checkedItemsCount; ++i)
|
||||
{
|
||||
int position = checkedItems.keyAt(i);
|
||||
boolean isChecked = checkedItems.valueAt(i);
|
||||
|
||||
if (isChecked)
|
||||
{
|
||||
cur.moveToPosition(position);
|
||||
|
||||
String address = cur.getString(addressIndex);
|
||||
String body = cur.getString(bodyIndex);
|
||||
long date = cur.getLong(dateIndex);
|
||||
|
||||
IncomingMessage sms = new IncomingSms(app, address, body, date);
|
||||
|
||||
app.forwardToServer(sms);
|
||||
}
|
||||
}
|
||||
|
||||
this.finish();
|
||||
}
|
||||
}
|
@ -1,72 +0,0 @@
|
||||
package org.envaya.sms.ui;
|
||||
|
||||
// from http://www.marvinlabs.com/2010/10/custom-listview-ability-check-items/
|
||||
// package fr.marvinlabs.widget;
|
||||
import android.content.Context;
|
||||
import android.util.AttributeSet;
|
||||
import android.view.KeyEvent;
|
||||
import android.view.MotionEvent;
|
||||
import android.widget.CheckBox;
|
||||
|
||||
/**
|
||||
* CheckBox that does not react to any user event in order to let the container handle them.
|
||||
*/
|
||||
public class InertCheckBox extends CheckBox {
|
||||
|
||||
// Provide the same constructors as the superclass
|
||||
public InertCheckBox(Context context, AttributeSet attrs, int defStyle) {
|
||||
super(context, attrs, defStyle);
|
||||
}
|
||||
|
||||
// Provide the same constructors as the superclass
|
||||
public InertCheckBox(Context context, AttributeSet attrs) {
|
||||
super(context, attrs);
|
||||
}
|
||||
|
||||
// Provide the same constructors as the superclass
|
||||
public InertCheckBox(Context context) {
|
||||
super(context);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onTouchEvent(MotionEvent event) {
|
||||
// Make the checkbox not respond to any user event
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onKeyDown(int keyCode, KeyEvent event) {
|
||||
// Make the checkbox not respond to any user event
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onKeyMultiple(int keyCode, int repeatCount, KeyEvent event) {
|
||||
// Make the checkbox not respond to any user event
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onKeyPreIme(int keyCode, KeyEvent event) {
|
||||
// Make the checkbox not respond to any user event
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onKeyShortcut(int keyCode, KeyEvent event) {
|
||||
// Make the checkbox not respond to any user event
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onKeyUp(int keyCode, KeyEvent event) {
|
||||
// Make the checkbox not respond to any user event
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onTrackballEvent(MotionEvent event) {
|
||||
// Make the checkbox not respond to any user event
|
||||
return false;
|
||||
}
|
||||
}
|
@ -15,12 +15,9 @@ import android.view.MenuItem;
|
||||
import android.view.View;
|
||||
import android.widget.ScrollView;
|
||||
import android.widget.TextView;
|
||||
import java.util.List;
|
||||
import org.apache.http.HttpResponse;
|
||||
import org.apache.http.message.BasicNameValuePair;
|
||||
import org.envaya.sms.App;
|
||||
import org.envaya.sms.IncomingMms;
|
||||
import org.envaya.sms.MmsUtils;
|
||||
import org.envaya.sms.R;
|
||||
|
||||
public class Main extends Activity {
|
||||
@ -76,7 +73,7 @@ public class Main extends Activity {
|
||||
updateLogView();
|
||||
|
||||
IntentFilter logReceiverFilter = new IntentFilter();
|
||||
logReceiverFilter.addAction(App.LOG_INTENT);
|
||||
logReceiverFilter.addAction(App.LOG_CHANGED_INTENT);
|
||||
registerReceiver(logReceiver, logReceiverFilter);
|
||||
}
|
||||
|
||||
@ -94,10 +91,10 @@ public class Main extends Activity {
|
||||
app.retryStuckMessages();
|
||||
return true;
|
||||
case R.id.forward_inbox:
|
||||
startActivity(new Intent(this, ForwardInbox.class));
|
||||
startActivity(new Intent(this, MessagingInbox.class));
|
||||
return true;
|
||||
case R.id.help:
|
||||
startActivity(new Intent(this, Help.class));
|
||||
case R.id.pending:
|
||||
startActivity(new Intent(this, PendingMessages.class));
|
||||
return true;
|
||||
case R.id.test:
|
||||
app.log("Testing server connection...");
|
||||
@ -119,10 +116,11 @@ public class Main extends Activity {
|
||||
|
||||
@Override
|
||||
public boolean onPrepareOptionsMenu(Menu menu) {
|
||||
MenuItem item = menu.findItem(R.id.retry_now);
|
||||
int stuckMessages = app.getStuckMessageCount();
|
||||
item.setEnabled(stuckMessages > 0);
|
||||
item.setTitle("Retry Fwd (" + stuckMessages + ")");
|
||||
MenuItem retryItem = menu.findItem(R.id.retry_now);
|
||||
int pendingMessages = app.getPendingMessageCount();
|
||||
retryItem.setEnabled(pendingMessages > 0);
|
||||
retryItem.setTitle("Retry All (" + pendingMessages + ")");
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
146
src/org/envaya/sms/ui/MessagingInbox.java
Executable file
146
src/org/envaya/sms/ui/MessagingInbox.java
Executable file
@ -0,0 +1,146 @@
|
||||
|
||||
package org.envaya.sms.ui;
|
||||
|
||||
import android.app.AlertDialog;
|
||||
import android.app.ListActivity;
|
||||
import android.content.DialogInterface;
|
||||
import android.content.DialogInterface.OnClickListener;
|
||||
import android.database.Cursor;
|
||||
import android.net.Uri;
|
||||
import android.os.Bundle;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuInflater;
|
||||
import android.view.MenuItem;
|
||||
import android.view.View;
|
||||
import android.widget.AdapterView;
|
||||
import android.widget.AdapterView.OnItemClickListener;
|
||||
import android.widget.ListView;
|
||||
import android.widget.SimpleCursorAdapter;
|
||||
import android.widget.Toast;
|
||||
import org.envaya.sms.App;
|
||||
import org.envaya.sms.IncomingMessage;
|
||||
import org.envaya.sms.IncomingSms;
|
||||
import org.envaya.sms.R;
|
||||
|
||||
|
||||
public class MessagingInbox extends ListActivity {
|
||||
|
||||
private App app;
|
||||
|
||||
private Cursor cur;
|
||||
|
||||
/** Called when the activity is first created. */
|
||||
@Override
|
||||
public void onCreate(Bundle icicle) {
|
||||
super.onCreate(icicle);
|
||||
|
||||
app = (App) getApplication();
|
||||
|
||||
setContentView(R.layout.inbox);
|
||||
|
||||
// undocumented API; see
|
||||
// core/java/android/provider/Telephony.java
|
||||
|
||||
Uri inboxUri = Uri.parse("content://sms/inbox");
|
||||
|
||||
cur = getContentResolver().query(inboxUri,
|
||||
new String[] { "_id", "address", "body", "date" }, null, null,
|
||||
"_id desc limit 50");
|
||||
|
||||
SimpleCursorAdapter adapter = new SimpleCursorAdapter(this,
|
||||
R.layout.inbox_item,
|
||||
cur,
|
||||
new String[] {"address","body"},
|
||||
new int[] {R.id.inbox_address, R.id.inbox_body});
|
||||
|
||||
setListAdapter(adapter);
|
||||
|
||||
ListView listView = getListView();
|
||||
|
||||
listView.setOnItemClickListener(new OnItemClickListener() {
|
||||
public void onItemClick(AdapterView<?> parent, View view,
|
||||
int position, long id)
|
||||
{
|
||||
final IncomingMessage message = getMessageAtPosition(position);
|
||||
|
||||
final CharSequence[] options = {"Forward", "Cancel"};
|
||||
|
||||
new AlertDialog.Builder(MessagingInbox.this)
|
||||
.setTitle(message.getDescription())
|
||||
.setItems(options, new OnClickListener() {
|
||||
public void onClick(DialogInterface dialog, int which)
|
||||
{
|
||||
if (which == 0)
|
||||
{
|
||||
app.inbox.forwardMessage(message);
|
||||
showToast("Forwarding " + message.getDescription());
|
||||
}
|
||||
dialog.dismiss();
|
||||
}
|
||||
})
|
||||
.show();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public void showToast(String text)
|
||||
{
|
||||
Toast.makeText(this, text, Toast.LENGTH_SHORT).show();
|
||||
}
|
||||
|
||||
public IncomingMessage getMessageAtPosition(int position)
|
||||
{
|
||||
int addressIndex = cur.getColumnIndex("address");
|
||||
int bodyIndex = cur.getColumnIndex("body");
|
||||
int dateIndex = cur.getColumnIndex("date");
|
||||
|
||||
cur.moveToPosition(position);
|
||||
|
||||
String address = cur.getString(addressIndex);
|
||||
String body = cur.getString(bodyIndex);
|
||||
long date = cur.getLong(dateIndex);
|
||||
|
||||
return new IncomingSms(app, address, body, date);
|
||||
}
|
||||
|
||||
public void forwardAllClicked() {
|
||||
final int count = cur.getCount();
|
||||
|
||||
for (int i = 0; i < count; ++i)
|
||||
{
|
||||
app.inbox.forwardMessage(getMessageAtPosition(i));
|
||||
}
|
||||
finish();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onOptionsItemSelected(MenuItem item) {
|
||||
// Handle item selection
|
||||
switch (item.getItemId()) {
|
||||
case R.id.forward_all:
|
||||
forwardAllClicked();
|
||||
return true;
|
||||
default:
|
||||
return super.onOptionsItemSelected(item);
|
||||
}
|
||||
}
|
||||
|
||||
// first time the Menu key is pressed
|
||||
@Override
|
||||
public boolean onCreateOptionsMenu(Menu menu) {
|
||||
MenuInflater inflater = getMenuInflater();
|
||||
inflater.inflate(R.menu.inbox, menu);
|
||||
return(true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onPrepareOptionsMenu(Menu menu) {
|
||||
MenuItem forwardItem = menu.findItem(R.id.forward_all);
|
||||
|
||||
int numMessages = cur.getCount();
|
||||
forwardItem.setEnabled(numMessages > 0);
|
||||
forwardItem.setTitle("Forward All (" + numMessages + ")");
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
274
src/org/envaya/sms/ui/PendingMessages.java
Executable file
274
src/org/envaya/sms/ui/PendingMessages.java
Executable file
@ -0,0 +1,274 @@
|
||||
|
||||
package org.envaya.sms.ui;
|
||||
|
||||
import android.app.AlertDialog;
|
||||
import android.app.ListActivity;
|
||||
import android.content.BroadcastReceiver;
|
||||
import android.content.Context;
|
||||
import android.content.DialogInterface;
|
||||
import android.content.DialogInterface.OnClickListener;
|
||||
import android.content.Intent;
|
||||
import android.content.IntentFilter;
|
||||
import android.os.Bundle;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuInflater;
|
||||
import android.view.MenuItem;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.AdapterView;
|
||||
import android.widget.AdapterView.OnItemClickListener;
|
||||
import android.widget.ArrayAdapter;
|
||||
import android.widget.Button;
|
||||
import android.widget.ListView;
|
||||
import android.widget.TextView;
|
||||
import java.text.DateFormat;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
import org.envaya.sms.App;
|
||||
import org.envaya.sms.IncomingMessage;
|
||||
import org.envaya.sms.OutgoingMessage;
|
||||
import org.envaya.sms.QueuedMessage;
|
||||
import org.envaya.sms.R;
|
||||
|
||||
|
||||
public class PendingMessages extends ListActivity {
|
||||
|
||||
private App app;
|
||||
|
||||
private List<QueuedMessage> displayedMessages;
|
||||
|
||||
private BroadcastReceiver refreshReceiver = new BroadcastReceiver() {
|
||||
@Override
|
||||
public void onReceive(Context context, Intent intent) {
|
||||
refreshMessages();
|
||||
}
|
||||
};
|
||||
|
||||
/** Called when the activity is first created. */
|
||||
@Override
|
||||
public void onCreate(Bundle icicle) {
|
||||
super.onCreate(icicle);
|
||||
|
||||
app = (App) getApplication();
|
||||
|
||||
setContentView(R.layout.pending_messages);
|
||||
|
||||
IntentFilter refreshReceiverFilter = new IntentFilter();
|
||||
refreshReceiverFilter.addAction(App.INBOX_CHANGED_INTENT);
|
||||
refreshReceiverFilter.addAction(App.OUTBOX_CHANGED_INTENT);
|
||||
registerReceiver(refreshReceiver, refreshReceiverFilter);
|
||||
|
||||
ListView listView = getListView();
|
||||
|
||||
listView.setOnItemClickListener(new OnItemClickListener() {
|
||||
public void onItemClick(AdapterView<?> parent, View view,
|
||||
int position, long id)
|
||||
{
|
||||
final QueuedMessage message = displayedMessages.get(position);
|
||||
final CharSequence[] options = {"Retry", "Delete", "Cancel"};
|
||||
|
||||
new AlertDialog.Builder(PendingMessages.this)
|
||||
.setTitle(message.getDescription())
|
||||
.setItems(options, new OnClickListener() {
|
||||
public void onClick(DialogInterface dialog, int which)
|
||||
{
|
||||
if (which == 0)
|
||||
{
|
||||
retryMessage(message);
|
||||
}
|
||||
else if (which == 1)
|
||||
{
|
||||
deleteMessage(message);
|
||||
}
|
||||
dialog.dismiss();
|
||||
}
|
||||
})
|
||||
.show();
|
||||
}
|
||||
});
|
||||
|
||||
refreshMessages();
|
||||
}
|
||||
|
||||
public void refreshMessages()
|
||||
{
|
||||
final ArrayList<QueuedMessage> messages = new ArrayList<QueuedMessage>();
|
||||
|
||||
synchronized(app.outbox)
|
||||
{
|
||||
for (OutgoingMessage message : app.outbox.getMessages())
|
||||
{
|
||||
messages.add(message);
|
||||
}
|
||||
}
|
||||
|
||||
synchronized(app.inbox)
|
||||
{
|
||||
for (IncomingMessage message : app.inbox.getMessages())
|
||||
{
|
||||
messages.add(message);
|
||||
}
|
||||
}
|
||||
|
||||
Collections.sort(messages, new Comparator<QueuedMessage>(){
|
||||
public int compare(QueuedMessage t1, QueuedMessage t2)
|
||||
{
|
||||
return t1.getDateCreated().compareTo(t2.getDateCreated());
|
||||
}
|
||||
});
|
||||
|
||||
displayedMessages = messages;
|
||||
|
||||
final LayoutInflater inflater = (LayoutInflater)getSystemService(Context.LAYOUT_INFLATER_SERVICE);
|
||||
final DateFormat longFormat = new SimpleDateFormat("dd MMM hh:mm:ss");
|
||||
final DateFormat shortFormat = new SimpleDateFormat("hh:mm:ss");
|
||||
final Date now = new Date();
|
||||
|
||||
ArrayAdapter<QueuedMessage> arrayAdapter = new ArrayAdapter<QueuedMessage>(this,
|
||||
R.layout.pending_message,
|
||||
messages.toArray(new QueuedMessage[]{})) {
|
||||
@Override
|
||||
public View getView(int position, View convertView, ViewGroup parent) {
|
||||
View v = convertView;
|
||||
if (v == null) {
|
||||
v = inflater.inflate(R.layout.pending_message, null);
|
||||
}
|
||||
QueuedMessage message = messages.get(position);
|
||||
if (message == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
TextView addr = (TextView) v.findViewById(R.id.pending_address);
|
||||
TextView time = (TextView) v.findViewById(R.id.pending_time);
|
||||
TextView status = (TextView) v.findViewById(R.id.pending_status);
|
||||
|
||||
addr.setText(message.getDescription());
|
||||
|
||||
String statusText = message.getStatusText();
|
||||
int numRetries = message.getNumRetries();
|
||||
if (numRetries > 0)
|
||||
{
|
||||
statusText = statusText + " (tries=" + numRetries + ")";
|
||||
}
|
||||
|
||||
status.setText(statusText);
|
||||
|
||||
Date date = message.getDateCreated();
|
||||
DateFormat format =
|
||||
(date.getDate() == now.getDate() && date.getMonth() == now.getMonth())
|
||||
? shortFormat : longFormat;
|
||||
|
||||
time.setText(format.format(date));
|
||||
return v;
|
||||
}
|
||||
};
|
||||
|
||||
setListAdapter(arrayAdapter);
|
||||
}
|
||||
|
||||
public void deleteMessage(QueuedMessage message)
|
||||
{
|
||||
if (message instanceof IncomingMessage)
|
||||
{
|
||||
app.inbox.deleteMessage((IncomingMessage)message);
|
||||
}
|
||||
else
|
||||
{
|
||||
app.outbox.deleteMessage((OutgoingMessage)message);
|
||||
}
|
||||
}
|
||||
|
||||
public void deleteAll()
|
||||
{
|
||||
for (QueuedMessage message : displayedMessages)
|
||||
{
|
||||
deleteMessage(message);
|
||||
}
|
||||
}
|
||||
|
||||
public void deleteAllClicked() {
|
||||
|
||||
new AlertDialog.Builder(this)
|
||||
.setTitle("Confirm Action")
|
||||
.setMessage("Do you want to delete all "+displayedMessages.size()+" pending messages?")
|
||||
.setPositiveButton("OK",
|
||||
new OnClickListener() {
|
||||
public void onClick(DialogInterface dialog, int which)
|
||||
{
|
||||
dialog.dismiss();
|
||||
deleteAll();
|
||||
}
|
||||
}
|
||||
)
|
||||
.setNegativeButton("Cancel",
|
||||
new OnClickListener() {
|
||||
public void onClick(DialogInterface dialog, int which)
|
||||
{
|
||||
dialog.dismiss();
|
||||
}
|
||||
}
|
||||
)
|
||||
.show();
|
||||
}
|
||||
|
||||
public void retryMessage(QueuedMessage message)
|
||||
{
|
||||
if (message instanceof IncomingMessage)
|
||||
{
|
||||
app.inbox.retryForwardMessage((IncomingMessage)message);
|
||||
}
|
||||
else
|
||||
{
|
||||
app.outbox.enqueueMessage((OutgoingMessage)message);
|
||||
}
|
||||
}
|
||||
|
||||
public void retryAllClicked()
|
||||
{
|
||||
for (QueuedMessage message : displayedMessages)
|
||||
{
|
||||
retryMessage(message);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onOptionsItemSelected(MenuItem item) {
|
||||
// Handle item selection
|
||||
switch (item.getItemId()) {
|
||||
case R.id.retry_all:
|
||||
retryAllClicked();
|
||||
return true;
|
||||
case R.id.delete_all:
|
||||
deleteAllClicked();
|
||||
return true;
|
||||
default:
|
||||
return super.onOptionsItemSelected(item);
|
||||
}
|
||||
}
|
||||
|
||||
// first time the Menu key is pressed
|
||||
@Override
|
||||
public boolean onCreateOptionsMenu(Menu menu) {
|
||||
MenuInflater inflater = getMenuInflater();
|
||||
inflater.inflate(R.menu.pending_messages, menu);
|
||||
return(true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onPrepareOptionsMenu(Menu menu) {
|
||||
MenuItem retryItem = menu.findItem(R.id.retry_all);
|
||||
MenuItem deleteItem = menu.findItem(R.id.delete_all);
|
||||
|
||||
int numMessages = displayedMessages.size();
|
||||
retryItem.setEnabled(numMessages > 0);
|
||||
deleteItem.setEnabled(numMessages > 0);
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
@ -16,11 +16,15 @@ import org.envaya.sms.R;
|
||||
|
||||
public class Prefs extends PreferenceActivity implements OnSharedPreferenceChangeListener {
|
||||
|
||||
private App app;
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
addPreferencesFromResource(R.xml.prefs);
|
||||
|
||||
app = (App) getApplication();
|
||||
|
||||
PreferenceScreen screen = this.getPreferenceScreen();
|
||||
int numPrefs = screen.getPreferenceCount();
|
||||
|
||||
@ -46,8 +50,7 @@ public class Prefs extends PreferenceActivity implements OnSharedPreferenceChang
|
||||
|
||||
public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) {
|
||||
|
||||
App app = (App) getApplication();
|
||||
|
||||
|
||||
if (key.equals("outgoing_interval"))
|
||||
{
|
||||
app.setOutgoingMessageAlarm();
|
||||
@ -109,7 +112,9 @@ public class Prefs extends PreferenceActivity implements OnSharedPreferenceChang
|
||||
|
||||
private void updatePrefSummary(Preference p)
|
||||
{
|
||||
if ("wifi_sleep_policy".equals(p.getKey()))
|
||||
String key = p.getKey();
|
||||
|
||||
if ("wifi_sleep_policy".equals(key))
|
||||
{
|
||||
int sleepPolicy;
|
||||
|
||||
@ -136,6 +141,10 @@ public class Prefs extends PreferenceActivity implements OnSharedPreferenceChang
|
||||
break;
|
||||
}
|
||||
}
|
||||
else if ("help".equals(key))
|
||||
{
|
||||
p.setSummary(app.getPackageInfo().versionName);
|
||||
}
|
||||
else if (p instanceof ListPreference) {
|
||||
p.setSummary(((ListPreference)p).getEntry());
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user