mirror of
https://github.com/cwinfo/envayasms.git
synced 2024-11-09 10:20:25 +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"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
package="org.envaya.sms"
|
package="org.envaya.sms"
|
||||||
android:versionCode="12"
|
android:versionCode="13"
|
||||||
android:versionName="2.0-rc2">
|
android:versionName="2.0">
|
||||||
|
|
||||||
<uses-sdk android:minSdkVersion="4" />
|
<uses-sdk android:minSdkVersion="4" />
|
||||||
|
|
||||||
@ -35,7 +35,10 @@
|
|||||||
<activity android:name=".ui.TestPhoneNumbers" android:label="EnvayaSMS : Test Phone Numbers">
|
<activity android:name=".ui.TestPhoneNumbers" android:label="EnvayaSMS : Test Phone Numbers">
|
||||||
</activity>
|
</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>
|
||||||
|
|
||||||
<activity android:name=".ui.Prefs" android:label="EnvayaSMS : Settings">
|
<activity android:name=".ui.Prefs" android:label="EnvayaSMS : Settings">
|
||||||
|
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,
|
org.envaya.sms.Base64Coder is (c) 2003-2010 Christian d'Heureuse,
|
||||||
released under MIT License (and others)
|
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.App.chooseOutgoingSmsPackage and
|
||||||
org.envaya.sms.ForegroundService include code from Android,
|
org.envaya.sms.ForegroundService include code from Android,
|
||||||
Copyright 2005-2009 The Android Open Source Project
|
Copyright 2005-2009 The Android Open Source Project
|
||||||
|
@ -11,7 +11,6 @@
|
|||||||
android:layout_width="fill_parent"
|
android:layout_width="fill_parent"
|
||||||
android:layout_height="fill_parent"
|
android:layout_height="fill_parent"
|
||||||
android:id="@+id/help"
|
android:id="@+id/help"
|
||||||
android:textSize="16sp"
|
|
||||||
android:autoLink="web"
|
android:autoLink="web"
|
||||||
android:textColor="#FFFFFF"
|
android:textColor="#FFFFFF"
|
||||||
android:layout_margin="5px">
|
android:layout_margin="5px">
|
||||||
|
@ -9,10 +9,11 @@
|
|||||||
android:layout_width="fill_parent"
|
android:layout_width="fill_parent"
|
||||||
android:layout_height="fill_parent"
|
android:layout_height="fill_parent"
|
||||||
android:layout_weight="1"
|
android:layout_weight="1"
|
||||||
android:choiceMode="multipleChoice" />
|
/>
|
||||||
<Button
|
<TextView android:id="@android:id/empty"
|
||||||
android:layout_height="wrap_content"
|
android:text="The inbox is empty."
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="fill_parent"
|
||||||
android:text="Forward selected"
|
android:layout_height="fill_parent"
|
||||||
android:onClick="forwardSelected" />
|
android:layout_weight="1"
|
||||||
|
/>
|
||||||
</LinearLayout>
|
</LinearLayout>
|
@ -1,34 +1,23 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<org.envaya.sms.ui.CheckableRelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
android:orientation="horizontal"
|
android:orientation="vertical"
|
||||||
android:layout_width="fill_parent"
|
android:layout_width="fill_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:background="#cccccc">
|
android:background="#333333">
|
||||||
<org.envaya.sms.ui.InertCheckBox android:id="@+id/inbox_checkbox"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:focusable="false"
|
|
||||||
/>
|
|
||||||
<TextView
|
<TextView
|
||||||
android:id="@+id/inbox_address"
|
android:id="@+id/inbox_address"
|
||||||
android:layout_width="fill_parent"
|
android:layout_width="fill_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_toRightOf="@id/inbox_checkbox"
|
android:textColor="#FFFFFF"
|
||||||
android:layout_alignTop="@id/inbox_checkbox"
|
|
||||||
android:textColor="#333333"
|
|
||||||
android:layout_marginTop="4sp"
|
android:layout_marginTop="4sp"
|
||||||
android:layout_marginLeft="6sp"
|
android:layout_marginLeft="6sp"
|
||||||
android:focusable="false"
|
|
||||||
android:textSize="14sp"></TextView>
|
android:textSize="14sp"></TextView>
|
||||||
<TextView
|
<TextView
|
||||||
android:id="@+id/inbox_body"
|
android:id="@+id/inbox_body"
|
||||||
android:layout_below="@id/inbox_address"
|
|
||||||
android:layout_alignLeft="@id/inbox_address"
|
|
||||||
android:layout_width="fill_parent"
|
android:layout_width="fill_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:textColor="#666666"
|
android:textColor="#CCCCCC"
|
||||||
|
android:layout_marginLeft="6sp"
|
||||||
android:layout_marginBottom="6sp"
|
android:layout_marginBottom="6sp"
|
||||||
android:focusable="false"
|
|
||||||
android:paddingBottom="6sp"
|
|
||||||
android:textSize="14sp"></TextView>
|
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"
|
<item android:id="@+id/retry_now"
|
||||||
android:icon="@drawable/ic_menu_magnet"
|
android:icon="@drawable/ic_menu_magnet"
|
||||||
android:title="@string/retry_now" />
|
android:title="@string/retry_now" />
|
||||||
<item android:id="@+id/help"
|
<item android:id="@+id/pending"
|
||||||
android:icon="@drawable/ic_menu_puzzle"
|
android:icon="@drawable/ic_menu_dialog"
|
||||||
android:title="@string/help" />
|
android:title="@string/pending" />
|
||||||
|
|
||||||
</menu>
|
</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="test">Test Connection</string>
|
||||||
<string name="check_now">Check Messages</string>
|
<string name="check_now">Check Messages</string>
|
||||||
<string name="help">Help</string>
|
<string name="help">Help</string>
|
||||||
|
<string name="pending">Pending Msgs...</string>
|
||||||
<string name="retry_now">Retry</string>
|
<string name="retry_now">Retry</string>
|
||||||
<string name="forward_inbox">Fwd Inbox...</string>
|
<string name="forward_inbox">Fwd Inbox...</string>
|
||||||
<string name='service_started'>New SMS will be forwarded to server</string>
|
<string name='service_started'>New SMS will be forwarded to server</string>
|
||||||
|
@ -80,4 +80,14 @@
|
|||||||
android:targetClass="org.envaya.sms.ui.TestPhoneNumbers" />
|
android:targetClass="org.envaya.sms.ui.TestPhoneNumbers" />
|
||||||
</PreferenceScreen>
|
</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>
|
</PreferenceScreen>
|
@ -17,7 +17,6 @@ import android.net.wifi.WifiManager;
|
|||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.os.SystemClock;
|
import android.os.SystemClock;
|
||||||
import android.preference.PreferenceManager;
|
import android.preference.PreferenceManager;
|
||||||
import android.telephony.SmsManager;
|
|
||||||
import android.text.Html;
|
import android.text.Html;
|
||||||
import android.text.SpannableStringBuilder;
|
import android.text.SpannableStringBuilder;
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
@ -25,12 +24,10 @@ import java.io.IOException;
|
|||||||
import java.net.InetAddress;
|
import java.net.InetAddress;
|
||||||
import java.text.DateFormat;
|
import java.text.DateFormat;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Comparator;
|
|
||||||
import java.util.Date;
|
import java.util.Date;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.PriorityQueue;
|
|
||||||
import org.apache.http.client.HttpClient;
|
import org.apache.http.client.HttpClient;
|
||||||
import org.apache.http.conn.scheme.PlainSocketFactory;
|
import org.apache.http.conn.scheme.PlainSocketFactory;
|
||||||
import org.apache.http.conn.scheme.Scheme;
|
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.conn.ssl.SSLSocketFactory;
|
||||||
import org.apache.http.impl.client.DefaultHttpClient;
|
import org.apache.http.impl.client.DefaultHttpClient;
|
||||||
import org.apache.http.impl.conn.tsccm.ThreadSafeClientConnManager;
|
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.BasicHttpParams;
|
||||||
import org.apache.http.params.HttpConnectionParams;
|
import org.apache.http.params.HttpConnectionParams;
|
||||||
import org.apache.http.params.HttpParams;
|
import org.apache.http.params.HttpParams;
|
||||||
import org.apache.http.params.HttpProtocolParams;
|
import org.apache.http.params.HttpProtocolParams;
|
||||||
import org.envaya.sms.receiver.DequeueOutgoingMessageReceiver;
|
|
||||||
import org.envaya.sms.receiver.OutgoingMessagePoller;
|
import org.envaya.sms.receiver.OutgoingMessagePoller;
|
||||||
import org.envaya.sms.receiver.ReenableWifiReceiver;
|
import org.envaya.sms.receiver.ReenableWifiReceiver;
|
||||||
import org.envaya.sms.task.HttpTask;
|
|
||||||
import org.envaya.sms.task.PollerTask;
|
import org.envaya.sms.task.PollerTask;
|
||||||
import org.json.JSONArray;
|
import org.json.JSONArray;
|
||||||
import org.json.JSONException;
|
import org.json.JSONException;
|
||||||
@ -68,7 +62,11 @@ public final class App extends Application {
|
|||||||
public static final String LOG_NAME = "EnvayaSMS";
|
public static final String LOG_NAME = "EnvayaSMS";
|
||||||
|
|
||||||
// intent to signal to Main activity (if open) that log has changed
|
// 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_INTENT = "org.envaya.sms.QUERY_EXPANSION_PACKS";
|
||||||
public static final String QUERY_EXPANSION_PACKS_EXTRA_PACKAGES = "packages";
|
public static final String QUERY_EXPANSION_PACKS_EXTRA_PACKAGES = "packages";
|
||||||
@ -113,29 +111,8 @@ public final class App extends Application {
|
|||||||
public static final int OUTGOING_SMS_CHECK_PERIOD = 3605000; // one hour plus 5 sec (in ms)
|
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;
|
public static final int OUTGOING_SMS_MAX_COUNT = 100;
|
||||||
|
|
||||||
private Map<Uri, IncomingMessage> incomingMessages = new HashMap<Uri, IncomingMessage>();
|
public final Inbox inbox = new Inbox(this);
|
||||||
private Map<Uri, OutgoingMessage> outgoingMessages = new HashMap<Uri, OutgoingMessage>();
|
public final Outbox outbox = new Outbox(this);
|
||||||
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
private SharedPreferences settings;
|
private SharedPreferences settings;
|
||||||
private MmsObserver mmsObserver;
|
private MmsObserver mmsObserver;
|
||||||
@ -148,15 +125,13 @@ public final class App extends Application {
|
|||||||
// for this package and all expansion packs
|
// for this package and all expansion packs
|
||||||
private List<String> outgoingMessagePackages = new ArrayList<String>();
|
private List<String> outgoingMessagePackages = new ArrayList<String>();
|
||||||
|
|
||||||
// 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
|
// map of package name => sorted list of timestamps of outgoing messages
|
||||||
private HashMap<String, ArrayList<Long>> outgoingTimestamps
|
private HashMap<String, ArrayList<Long>> outgoingTimestamps
|
||||||
= new HashMap<String, ArrayList<Long>>();
|
= new HashMap<String, ArrayList<Long>>();
|
||||||
|
|
||||||
|
// count to provide round-robin selection of expansion packs
|
||||||
|
private int outgoingMessageCount = -1;
|
||||||
|
|
||||||
private MmsUtils mmsUtils;
|
private MmsUtils mmsUtils;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -226,7 +201,7 @@ public final class App extends Application {
|
|||||||
return packageInfo;
|
return packageInfo;
|
||||||
}
|
}
|
||||||
|
|
||||||
private synchronized String chooseOutgoingSmsPackage(int numParts)
|
public synchronized String chooseOutgoingSmsPackage(int numParts)
|
||||||
{
|
{
|
||||||
outgoingMessageCount++;
|
outgoingMessageCount++;
|
||||||
|
|
||||||
@ -280,7 +255,7 @@ public final class App extends Application {
|
|||||||
* outgoing SMS with numParts parts. Only valid immediately after
|
* outgoing SMS with numParts parts. Only valid immediately after
|
||||||
* chooseOutgoingSmsPackage returns null.
|
* chooseOutgoingSmsPackage returns null.
|
||||||
*/
|
*/
|
||||||
private synchronized long getNextValidOutgoingTime(int numParts)
|
public synchronized long getNextValidOutgoingTime(int numParts)
|
||||||
{
|
{
|
||||||
long minTime = System.currentTimeMillis() + OUTGOING_SMS_CHECK_PERIOD;
|
long minTime = System.currentTimeMillis() + OUTGOING_SMS_CHECK_PERIOD;
|
||||||
|
|
||||||
@ -449,300 +424,13 @@ public final class App extends Application {
|
|||||||
return settings.getString("password", "");
|
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() {
|
public synchronized void retryStuckMessages() {
|
||||||
|
outbox.retryAll();
|
||||||
this.nextValidOutgoingTime = 0;
|
inbox.retryAll();
|
||||||
|
|
||||||
retryStuckOutgoingMessages();
|
|
||||||
retryStuckIncomingMessages();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public synchronized int getStuckMessageCount() {
|
public synchronized int getPendingMessageCount() {
|
||||||
return outgoingMessages.size() + incomingMessages.size();
|
return outbox.size() + inbox.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 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) {
|
public void debug(String msg) {
|
||||||
@ -783,8 +471,7 @@ public final class App extends Application {
|
|||||||
displayedLog.append(msg);
|
displayedLog.append(msg);
|
||||||
displayedLog.append("\n");
|
displayedLog.append("\n");
|
||||||
|
|
||||||
Intent broadcast = new Intent(App.LOG_INTENT);
|
sendBroadcast(new Intent(App.LOG_CHANGED_INTENT));
|
||||||
sendBroadcast(broadcast);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public synchronized CharSequence getDisplayedLog()
|
public synchronized CharSequence getDisplayedLog()
|
||||||
@ -1092,15 +779,14 @@ public final class App extends Application {
|
|||||||
asyncCheckConnectivity();
|
asyncCheckConnectivity();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void onConnectivityRestored()
|
private void onConnectivityRestored()
|
||||||
{
|
{
|
||||||
retryStuckIncomingMessages();
|
inbox.retryAll();
|
||||||
|
|
||||||
if (getOutgoingPollSeconds() > 0)
|
if (getOutgoingPollSeconds() > 0)
|
||||||
{
|
{
|
||||||
checkOutgoingMessages();
|
checkOutgoingMessages();
|
||||||
}
|
}
|
||||||
|
|
||||||
// failed outgoing message status notifications are dropped...
|
// failed outgoing message status notifications are dropped...
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -45,7 +45,7 @@ public class CheckMmsInboxService extends IntentService
|
|||||||
|
|
||||||
if (mms.isForwardable())
|
if (mms.isForwardable())
|
||||||
{
|
{
|
||||||
app.forwardToServer(mms);
|
app.inbox.forwardMessage(mms);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
@ -26,13 +26,12 @@ import android.util.Log;
|
|||||||
|
|
||||||
import java.lang.reflect.InvocationTargetException;
|
import java.lang.reflect.InvocationTargetException;
|
||||||
import java.lang.reflect.Method;
|
import java.lang.reflect.Method;
|
||||||
import org.envaya.sms.R;
|
|
||||||
import org.envaya.sms.ui.Main;
|
import org.envaya.sms.ui.Main;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Service running in foreground to make sure App instance stays
|
* Service running in foreground to make sure App instance stays
|
||||||
* in memory (otherwise we could lose timestamps of sent messages
|
* in memory (to avoid losing pending messages and timestamps of
|
||||||
* which could cause us to exceed Android's SMS sending limit)
|
* sent messages).
|
||||||
*
|
*
|
||||||
* Also adds notification to status bar.
|
* 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
|
None, // not doing anything with this sms now... just sitting around
|
||||||
Forwarding, // currently sending to server
|
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)
|
public IncomingMessage(App app, String from, long timestamp)
|
||||||
@ -39,9 +40,6 @@ public abstract class IncomingMessage extends QueuedMessage {
|
|||||||
this.state = status;
|
this.state = status;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public abstract String getDisplayType();
|
|
||||||
|
|
||||||
public boolean isForwardable()
|
public boolean isForwardable()
|
||||||
{
|
{
|
||||||
if (app.isTestMode() && !app.isTestPhoneNumber(from))
|
if (app.isTestMode() && !app.isTestPhoneNumber(from))
|
||||||
@ -86,5 +84,23 @@ public abstract class IncomingMessage extends QueuedMessage {
|
|||||||
return intent;
|
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();
|
public abstract void tryForwardToServer();
|
||||||
}
|
}
|
||||||
|
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
|
None, // not doing anything with this sms now... just sitting around
|
||||||
Queued, // in the outgoing queue waiting to be sent
|
Queued, // in the outgoing queue waiting to be sent
|
||||||
Sending, // passed to an expansion pack, waiting for status notification
|
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)
|
public OutgoingMessage(App app)
|
||||||
@ -143,4 +144,29 @@ public class OutgoingMessage extends QueuedMessage {
|
|||||||
intent.setData(this.getUri());
|
intent.setData(this.getUri());
|
||||||
return intent;
|
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,12 +6,13 @@ import android.content.Context;
|
|||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
import android.os.SystemClock;
|
import android.os.SystemClock;
|
||||||
|
import java.util.Date;
|
||||||
|
|
||||||
public abstract class QueuedMessage
|
public abstract class QueuedMessage
|
||||||
{
|
{
|
||||||
protected long nextRetryTime = 0;
|
protected long nextRetryTime = 0;
|
||||||
protected int numRetries = 0;
|
protected int numRetries = 0;
|
||||||
|
protected Date dateCreated = new Date();
|
||||||
public App app;
|
public App app;
|
||||||
|
|
||||||
public QueuedMessage(App app)
|
public QueuedMessage(App app)
|
||||||
@ -19,6 +20,16 @@ public abstract class QueuedMessage
|
|||||||
this.app = app;
|
this.app = app;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Date getDateCreated()
|
||||||
|
{
|
||||||
|
return dateCreated;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getNumRetries()
|
||||||
|
{
|
||||||
|
return numRetries;
|
||||||
|
}
|
||||||
|
|
||||||
public boolean canRetryNow() {
|
public boolean canRetryNow() {
|
||||||
return (nextRetryTime > 0 && nextRetryTime < SystemClock.elapsedRealtime());
|
return (nextRetryTime > 0 && nextRetryTime < SystemClock.elapsedRealtime());
|
||||||
}
|
}
|
||||||
@ -64,6 +75,10 @@ public abstract class QueuedMessage
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public abstract String getDisplayType();
|
||||||
|
public abstract String getDescription();
|
||||||
|
public abstract String getStatusText();
|
||||||
|
|
||||||
public abstract Uri getUri();
|
public abstract Uri getUri();
|
||||||
|
|
||||||
protected abstract Intent getRetryIntent();
|
protected abstract Intent getRetryIntent();
|
||||||
|
@ -4,8 +4,6 @@ package org.envaya.sms.receiver;
|
|||||||
import android.content.BroadcastReceiver;
|
import android.content.BroadcastReceiver;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
import android.net.ConnectivityManager;
|
|
||||||
import android.net.NetworkInfo;
|
|
||||||
import org.envaya.sms.App;
|
import org.envaya.sms.App;
|
||||||
|
|
||||||
public class ConnectivityChangeReceiver extends BroadcastReceiver {
|
public class ConnectivityChangeReceiver extends BroadcastReceiver {
|
||||||
|
@ -16,6 +16,6 @@ public class DequeueOutgoingMessageReceiver extends BroadcastReceiver {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
app.maybeDequeueOutgoingMessage();
|
app.outbox.maybeDequeueMessage();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -5,6 +5,7 @@ import android.content.BroadcastReceiver;
|
|||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
import org.envaya.sms.App;
|
import org.envaya.sms.App;
|
||||||
|
import org.envaya.sms.IncomingMessage;
|
||||||
|
|
||||||
public class IncomingMessageRetry extends BroadcastReceiver
|
public class IncomingMessageRetry extends BroadcastReceiver
|
||||||
{
|
{
|
||||||
@ -17,6 +18,13 @@ public class IncomingMessageRetry extends BroadcastReceiver
|
|||||||
return;
|
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;
|
package org.envaya.sms.receiver;
|
||||||
|
|
||||||
|
import android.app.Activity;
|
||||||
import android.content.BroadcastReceiver;
|
import android.content.BroadcastReceiver;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
|
import android.telephony.SmsManager;
|
||||||
import org.envaya.sms.App;
|
import org.envaya.sms.App;
|
||||||
|
import org.envaya.sms.OutgoingMessage;
|
||||||
|
|
||||||
public class MessageStatusNotifier extends BroadcastReceiver {
|
public class MessageStatusNotifier extends BroadcastReceiver {
|
||||||
|
|
||||||
@ -20,18 +23,56 @@ public class MessageStatusNotifier extends BroadcastReceiver {
|
|||||||
|
|
||||||
Bundle extras = intent.getExtras();
|
Bundle extras = intent.getExtras();
|
||||||
int index = extras.getInt(App.STATUS_EXTRA_INDEX);
|
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();
|
int resultCode = getResultCode();
|
||||||
|
|
||||||
// uncomment to test retry on outgoing message failure
|
|
||||||
/*
|
/*
|
||||||
|
// uncomment to test retry on outgoing message failure
|
||||||
if (Math.random() > 0.4)
|
if (Math.random() > 0.4)
|
||||||
{
|
{
|
||||||
resultCode = SmsManager.RESULT_ERROR_NO_SERVICE;
|
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.Context;
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
import org.envaya.sms.App;
|
import org.envaya.sms.App;
|
||||||
|
import org.envaya.sms.OutgoingMessage;
|
||||||
|
|
||||||
public class OutgoingMessageRetry extends BroadcastReceiver
|
public class OutgoingMessageRetry extends BroadcastReceiver
|
||||||
{
|
{
|
||||||
@ -16,6 +17,13 @@ public class OutgoingMessageRetry extends BroadcastReceiver
|
|||||||
{
|
{
|
||||||
return;
|
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())
|
if (sms.isForwardable())
|
||||||
{
|
{
|
||||||
app.forwardToServer(sms);
|
app.inbox.forwardMessage(sms);
|
||||||
|
|
||||||
if (!app.getKeepInInbox())
|
if (!app.getKeepInInbox())
|
||||||
{
|
{
|
||||||
|
@ -28,14 +28,13 @@ public class ForwarderTask extends HttpTask {
|
|||||||
protected void handleResponse(HttpResponse response) throws Exception {
|
protected void handleResponse(HttpResponse response) throws Exception {
|
||||||
|
|
||||||
for (OutgoingMessage reply : parseResponseXML(response)) {
|
for (OutgoingMessage reply : parseResponseXML(response)) {
|
||||||
app.sendOutgoingMessage(reply);
|
app.outbox.sendMessage(reply);
|
||||||
}
|
}
|
||||||
|
app.inbox.messageForwarded(message);
|
||||||
app.setIncomingMessageStatus(message, true);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void handleFailure() {
|
protected void handleFailure() {
|
||||||
app.setIncomingMessageStatus(message, false);
|
app.inbox.messageFailed(message);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -15,7 +15,7 @@ public class PollerTask extends HttpTask {
|
|||||||
@Override
|
@Override
|
||||||
protected void handleResponse(HttpResponse response) throws Exception {
|
protected void handleResponse(HttpResponse response) throws Exception {
|
||||||
for (OutgoingMessage reply : parseResponseXML(response)) {
|
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.view.View;
|
||||||
import android.widget.ScrollView;
|
import android.widget.ScrollView;
|
||||||
import android.widget.TextView;
|
import android.widget.TextView;
|
||||||
import java.util.List;
|
|
||||||
import org.apache.http.HttpResponse;
|
import org.apache.http.HttpResponse;
|
||||||
import org.apache.http.message.BasicNameValuePair;
|
import org.apache.http.message.BasicNameValuePair;
|
||||||
import org.envaya.sms.App;
|
import org.envaya.sms.App;
|
||||||
import org.envaya.sms.IncomingMms;
|
|
||||||
import org.envaya.sms.MmsUtils;
|
|
||||||
import org.envaya.sms.R;
|
import org.envaya.sms.R;
|
||||||
|
|
||||||
public class Main extends Activity {
|
public class Main extends Activity {
|
||||||
@ -76,7 +73,7 @@ public class Main extends Activity {
|
|||||||
updateLogView();
|
updateLogView();
|
||||||
|
|
||||||
IntentFilter logReceiverFilter = new IntentFilter();
|
IntentFilter logReceiverFilter = new IntentFilter();
|
||||||
logReceiverFilter.addAction(App.LOG_INTENT);
|
logReceiverFilter.addAction(App.LOG_CHANGED_INTENT);
|
||||||
registerReceiver(logReceiver, logReceiverFilter);
|
registerReceiver(logReceiver, logReceiverFilter);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -94,10 +91,10 @@ public class Main extends Activity {
|
|||||||
app.retryStuckMessages();
|
app.retryStuckMessages();
|
||||||
return true;
|
return true;
|
||||||
case R.id.forward_inbox:
|
case R.id.forward_inbox:
|
||||||
startActivity(new Intent(this, ForwardInbox.class));
|
startActivity(new Intent(this, MessagingInbox.class));
|
||||||
return true;
|
return true;
|
||||||
case R.id.help:
|
case R.id.pending:
|
||||||
startActivity(new Intent(this, Help.class));
|
startActivity(new Intent(this, PendingMessages.class));
|
||||||
return true;
|
return true;
|
||||||
case R.id.test:
|
case R.id.test:
|
||||||
app.log("Testing server connection...");
|
app.log("Testing server connection...");
|
||||||
@ -119,10 +116,11 @@ public class Main extends Activity {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean onPrepareOptionsMenu(Menu menu) {
|
public boolean onPrepareOptionsMenu(Menu menu) {
|
||||||
MenuItem item = menu.findItem(R.id.retry_now);
|
MenuItem retryItem = menu.findItem(R.id.retry_now);
|
||||||
int stuckMessages = app.getStuckMessageCount();
|
int pendingMessages = app.getPendingMessageCount();
|
||||||
item.setEnabled(stuckMessages > 0);
|
retryItem.setEnabled(pendingMessages > 0);
|
||||||
item.setTitle("Retry Fwd (" + stuckMessages + ")");
|
retryItem.setTitle("Retry All (" + pendingMessages + ")");
|
||||||
|
|
||||||
return true;
|
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 {
|
public class Prefs extends PreferenceActivity implements OnSharedPreferenceChangeListener {
|
||||||
|
|
||||||
|
private App app;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onCreate(Bundle savedInstanceState) {
|
public void onCreate(Bundle savedInstanceState) {
|
||||||
super.onCreate(savedInstanceState);
|
super.onCreate(savedInstanceState);
|
||||||
addPreferencesFromResource(R.xml.prefs);
|
addPreferencesFromResource(R.xml.prefs);
|
||||||
|
|
||||||
|
app = (App) getApplication();
|
||||||
|
|
||||||
PreferenceScreen screen = this.getPreferenceScreen();
|
PreferenceScreen screen = this.getPreferenceScreen();
|
||||||
int numPrefs = screen.getPreferenceCount();
|
int numPrefs = screen.getPreferenceCount();
|
||||||
|
|
||||||
@ -46,7 +50,6 @@ public class Prefs extends PreferenceActivity implements OnSharedPreferenceChang
|
|||||||
|
|
||||||
public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) {
|
public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) {
|
||||||
|
|
||||||
App app = (App) getApplication();
|
|
||||||
|
|
||||||
if (key.equals("outgoing_interval"))
|
if (key.equals("outgoing_interval"))
|
||||||
{
|
{
|
||||||
@ -109,7 +112,9 @@ public class Prefs extends PreferenceActivity implements OnSharedPreferenceChang
|
|||||||
|
|
||||||
private void updatePrefSummary(Preference p)
|
private void updatePrefSummary(Preference p)
|
||||||
{
|
{
|
||||||
if ("wifi_sleep_policy".equals(p.getKey()))
|
String key = p.getKey();
|
||||||
|
|
||||||
|
if ("wifi_sleep_policy".equals(key))
|
||||||
{
|
{
|
||||||
int sleepPolicy;
|
int sleepPolicy;
|
||||||
|
|
||||||
@ -136,6 +141,10 @@ public class Prefs extends PreferenceActivity implements OnSharedPreferenceChang
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
else if ("help".equals(key))
|
||||||
|
{
|
||||||
|
p.setSummary(app.getPackageInfo().versionName);
|
||||||
|
}
|
||||||
else if (p instanceof ListPreference) {
|
else if (p instanceof ListPreference) {
|
||||||
p.setSummary(((ListPreference)p).getEntry());
|
p.setSummary(((ListPreference)p).getEntry());
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user