diff --git a/.classpath b/.classpath
index 6e9239f..53681c2 100644
--- a/.classpath
+++ b/.classpath
@@ -3,5 +3,6 @@
+
diff --git a/libs/commons-io-2.0.1.jar b/libs/commons-io-2.0.1.jar
new file mode 100644
index 0000000..5b64b7d
Binary files /dev/null and b/libs/commons-io-2.0.1.jar differ
diff --git a/src/com/dougkeen/bart/GetRealTimeDeparturesTask.java b/src/com/dougkeen/bart/GetRealTimeDeparturesTask.java
index 66a27ca..5d2d7c9 100644
--- a/src/com/dougkeen/bart/GetRealTimeDeparturesTask.java
+++ b/src/com/dougkeen/bart/GetRealTimeDeparturesTask.java
@@ -1,20 +1,34 @@
package com.dougkeen.bart;
import java.io.IOException;
+import java.io.StringWriter;
import java.io.UnsupportedEncodingException;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLConnection;
import java.util.List;
+import org.apache.commons.io.IOUtils;
+import org.apache.http.HttpResponse;
+import org.apache.http.HttpStatus;
+import org.apache.http.client.ClientProtocolException;
+import org.apache.http.client.HttpClient;
+import org.apache.http.client.methods.HttpGet;
+import org.apache.http.client.methods.HttpUriRequest;
+import org.apache.http.conn.params.ConnManagerParams;
+import org.apache.http.impl.client.DefaultHttpClient;
+import org.apache.http.params.HttpConnectionParams;
+import org.apache.http.params.HttpParams;
import org.xml.sax.SAXException;
+import android.os.AsyncTask;
+import android.util.Log;
+import android.util.Xml;
+
import com.dougkeen.bart.data.RealTimeDepartures;
-import android.os.AsyncTask;
-import android.util.Xml;
-
-public abstract class GetRealTimeDeparturesTask extends
+public abstract class GetRealTimeDeparturesTask
+ extends
AsyncTask {
private static final int CONNECTION_TIMEOUT_MILLIS = 10000;
@@ -23,7 +37,7 @@ public abstract class GetRealTimeDeparturesTask extends
+ API_KEY + "&orig=%1$s&dir=%2$s";
private final static int MAX_ATTEMPTS = 3;
- private IOException mIOException;
+ private Exception mException;
private List mRoutes;
@@ -43,8 +57,9 @@ public abstract class GetRealTimeDeparturesTask extends
private RealTimeDepartures getDeparturesFromNetwork(Params params,
int attemptNumber) {
+ String xml = null;
try {
- URL sourceUrl = new URL(String.format(API_URL,
+ HttpUriRequest request = new HttpGet(String.format(API_URL,
params.origin.abbreviation, mRoutes.get(0).getDirection()));
EtdContentHandler handler = new EtdContentHandler(params.origin,
@@ -52,11 +67,23 @@ public abstract class GetRealTimeDeparturesTask extends
if (isCancelled()) {
return null;
}
- URLConnection connection = sourceUrl.openConnection();
- connection.setConnectTimeout(CONNECTION_TIMEOUT_MILLIS);
- Xml.parse(connection.getInputStream(),
- Xml.findEncodingByName("UTF-8"),
- handler);
+
+ HttpResponse response = executeWithRecovery(request);
+
+ if (response.getStatusLine().getStatusCode() != HttpStatus.SC_OK) {
+ throw new IOException("Server returned "
+ + response.getStatusLine().toString());
+ }
+
+ StringWriter writer = new StringWriter();
+ IOUtils.copy(response.getEntity().getContent(), writer, "UTF-8");
+
+ xml = writer.toString();
+ if (xml.length() == 0) {
+ throw new IOException("Server returned blank xml document");
+ }
+
+ Xml.parse(xml, handler);
final RealTimeDepartures realTimeDepartures = handler
.getRealTimeDepartures();
return realTimeDepartures;
@@ -67,20 +94,45 @@ public abstract class GetRealTimeDeparturesTask extends
} catch (IOException e) {
if (attemptNumber < MAX_ATTEMPTS - 1) {
try {
+ Log.w(Constants.TAG,
+ "Attempt to contact server failed... retrying in 5s",
+ e);
Thread.sleep(5000);
} catch (InterruptedException interrupt) {
// Ignore... just go on to next attempt
}
return getDeparturesFromNetwork(params, attemptNumber + 1);
} else {
- mIOException = e;
+ mException = new Exception("Could not contact BART system", e);
return null;
}
} catch (SAXException e) {
- throw new RuntimeException(e);
+ mException = new Exception(
+ "Could not understand response from BART system: " + xml, e);
+ return null;
}
}
+ private static HttpResponse executeWithRecovery(final HttpUriRequest request)
+ throws IOException, ClientProtocolException {
+ try {
+ return getHttpClient().execute(request);
+ } catch (IllegalStateException e) {
+ // try again... this is a rare error
+ return getHttpClient().execute(request);
+ }
+ }
+
+ private static HttpClient getHttpClient() {
+ HttpClient client = new DefaultHttpClient();
+ final HttpParams params = client.getParams();
+ HttpConnectionParams.setConnectionTimeout(params,
+ CONNECTION_TIMEOUT_MILLIS);
+ HttpConnectionParams.setSoTimeout(params, CONNECTION_TIMEOUT_MILLIS);
+ ConnManagerParams.setTimeout(params, CONNECTION_TIMEOUT_MILLIS);
+ return client;
+ }
+
public static class Params {
public Params(Station origin, Station destination) {
super();
@@ -105,11 +157,11 @@ public abstract class GetRealTimeDeparturesTask extends
if (result != null) {
onResult(result);
} else {
- onNetworkError(mIOException);
+ onError(mException);
}
}
public abstract void onResult(RealTimeDepartures result);
- public abstract void onNetworkError(IOException e);
+ public abstract void onError(Exception exception);
}
diff --git a/src/com/dougkeen/bart/ViewDeparturesActivity.java b/src/com/dougkeen/bart/ViewDeparturesActivity.java
index e64f66b..3b5cd3b 100644
--- a/src/com/dougkeen/bart/ViewDeparturesActivity.java
+++ b/src/com/dougkeen/bart/ViewDeparturesActivity.java
@@ -1,6 +1,5 @@
package com.dougkeen.bart;
-import java.io.IOException;
import java.util.List;
import android.app.ListActivity;
@@ -53,7 +52,7 @@ public class ViewDeparturesActivity extends ListActivity {
private PowerManager.WakeLock mWakeLock;
- private boolean mFetchDeparturesOnNextFocus;
+ private boolean mDataFetchIsPending;
@Override
protected void onCreate(Bundle savedInstanceState) {
@@ -96,14 +95,13 @@ public class ViewDeparturesActivity extends ListActivity {
}
}
setListAdapter(mDeparturesAdapter);
-
- mFetchDeparturesOnNextFocus = true;
}
@Override
protected void onDestroy() {
if (mGetDeparturesTask != null) {
mGetDeparturesTask.cancel(true);
+ mDataFetchIsPending = false;
}
super.onDestroy();
}
@@ -122,9 +120,8 @@ public class ViewDeparturesActivity extends ListActivity {
public void onWindowFocusChanged(boolean hasFocus) {
super.onWindowFocusChanged(hasFocus);
if (hasFocus) {
- if (mFetchDeparturesOnNextFocus) {
+ if (!mDataFetchIsPending) {
fetchLatestDepartures();
- mFetchDeparturesOnNextFocus = false;
}
PowerManager powerManaer = (PowerManager) getSystemService(Context.POWER_SERVICE);
mWakeLock = powerManaer
@@ -154,20 +151,21 @@ public class ViewDeparturesActivity extends ListActivity {
mGetDeparturesTask = new GetRealTimeDeparturesTask() {
@Override
public void onResult(RealTimeDepartures result) {
+ mDataFetchIsPending = false;
Log.i(Constants.TAG, "Processing data from server");
processLatestDepartures(result);
Log.i(Constants.TAG, "Done processing data from server");
}
@Override
- public void onNetworkError(IOException e) {
- Log.w(Constants.TAG, e.getMessage());
+ public void onError(Exception e) {
+ mDataFetchIsPending = false;
+ Log.w(Constants.TAG, e.getMessage(), e);
Toast.makeText(ViewDeparturesActivity.this,
R.string.could_not_connect,
Toast.LENGTH_LONG).show();
((TextView) findViewById(android.R.id.empty))
.setText(R.string.could_not_connect);
-
}
};
Log.i(Constants.TAG, "Fetching data from server");
@@ -234,13 +232,7 @@ public class ViewDeparturesActivity extends ListActivity {
if (needsBetterAccuracy
|| firstDeparture.hasDeparted()) {
// Get more data in 20s
- mListTitleView.postDelayed(new Runnable() {
- @Override
- public void run() {
- fetchLatestDepartures();
- }
- }, 20000);
- Log.i(Constants.TAG, "Scheduled another data fetch in 20s");
+ scheduleDataFetch(20000);
} else {
// Get more 90 seconds before next train arrives, right when
// next train arrives, or 3 minutes, whichever is sooner
@@ -255,15 +247,12 @@ public class ViewDeparturesActivity extends ListActivity {
} else if (intervalUntilNextDeparture > alternativeInterval) {
interval = alternativeInterval;
}
- mListTitleView.postDelayed(new Runnable() {
- @Override
- public void run() {
- fetchLatestDepartures();
- }
- }, interval);
- Log.i(Constants.TAG, "Scheduled another data fetch in "
- + interval / 1000
- + "s");
+
+ if (interval < 0) {
+ interval = 20000;
+ }
+
+ scheduleDataFetch(interval);
}
if (!mIsAutoUpdating) {
mIsAutoUpdating = true;
@@ -273,6 +262,20 @@ public class ViewDeparturesActivity extends ListActivity {
}
}
+ private void scheduleDataFetch(int millisUntilExecute) {
+ if (!mDataFetchIsPending) {
+ mListTitleView.postDelayed(new Runnable() {
+ @Override
+ public void run() {
+ fetchLatestDepartures();
+ }
+ }, millisUntilExecute);
+ mDataFetchIsPending = true;
+ Log.i(Constants.TAG, "Scheduled another data fetch in "
+ + millisUntilExecute / 1000 + "s");
+ }
+ }
+
private void runAutoUpdate() {
if (mIsAutoUpdating && mDeparturesAdapter != null) {
mDeparturesAdapter.notifyDataSetChanged();
@@ -303,7 +306,6 @@ public class ViewDeparturesActivity extends ListActivity {
+ mOrigin.abbreviation
+ "&dest="
+ mDestination.abbreviation)));
- mFetchDeparturesOnNextFocus = true;
return true;
} else {
return super.onOptionsItemSelected(item);
diff --git a/src/com/dougkeen/bart/data/Departure.java b/src/com/dougkeen/bart/data/Departure.java
index 667ac02..6c61ced 100644
--- a/src/com/dougkeen/bart/data/Departure.java
+++ b/src/com/dougkeen/bart/data/Departure.java
@@ -166,7 +166,7 @@ public class Departure implements Parcelable, Comparable {
}
public boolean hasDeparted() {
- return getMinutes() == 0 || getMeanSecondsLeft() < 0;
+ return getMeanSecondsLeft() < 0;
}
public void calculateEstimates(long originalEstimateTime) {