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) {