diff --git a/.classpath b/.classpath
index b3366ba..1e34f60 100644
--- a/.classpath
+++ b/.classpath
@@ -5,6 +5,7 @@
-
+
+
diff --git a/libs/android-support-v4.jar b/libs/android-support-v4.jar
new file mode 100644
index 0000000..99e063b
Binary files /dev/null and b/libs/android-support-v4.jar differ
diff --git a/res/layout/departure_listing.xml b/res/layout/departure_listing.xml
index 9727b8a..c3ed530 100644
--- a/res/layout/departure_listing.xml
+++ b/res/layout/departure_listing.xml
@@ -43,9 +43,10 @@
android:layout_below="@id/topRow"
android:src="@drawable/xfer" />
-
diff --git a/res/layout/train_length_arrival_textview.xml b/res/layout/train_length_arrival_textview.xml
new file mode 100644
index 0000000..a580980
--- /dev/null
+++ b/res/layout/train_length_arrival_textview.xml
@@ -0,0 +1,3 @@
+
+
diff --git a/src/com/dougkeen/bart/AddRouteActivity.java b/src/com/dougkeen/bart/AddRouteActivity.java
index 5af8cfd..a02d7ea 100644
--- a/src/com/dougkeen/bart/AddRouteActivity.java
+++ b/src/com/dougkeen/bart/AddRouteActivity.java
@@ -1,6 +1,8 @@
package com.dougkeen.bart;
import com.dougkeen.bart.data.RoutesColumns;
+import com.dougkeen.bart.model.Constants;
+import com.dougkeen.bart.model.Station;
import android.app.Activity;
import android.content.ContentValues;
diff --git a/src/com/dougkeen/bart/DepartureArrayAdapter.java b/src/com/dougkeen/bart/DepartureArrayAdapter.java
index df9c8cb..a152f0e 100644
--- a/src/com/dougkeen/bart/DepartureArrayAdapter.java
+++ b/src/com/dougkeen/bart/DepartureArrayAdapter.java
@@ -2,21 +2,28 @@ package com.dougkeen.bart;
import java.util.List;
+import org.apache.commons.lang3.StringUtils;
+
import android.content.Context;
import android.graphics.Color;
import android.graphics.drawable.GradientDrawable;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
+import android.view.animation.AnimationUtils;
import android.widget.ArrayAdapter;
import android.widget.ImageView;
import android.widget.RelativeLayout;
+import android.widget.TextSwitcher;
import android.widget.TextView;
+import android.widget.ViewSwitcher.ViewFactory;
-import com.dougkeen.bart.data.Departure;
+import com.dougkeen.bart.model.Departure;
public class DepartureArrayAdapter extends ArrayAdapter {
+ public static int refreshCounter = 0;
+
public DepartureArrayAdapter(Context context, int textViewResourceId,
Departure[] objects) {
super(context, textViewResourceId, objects);
@@ -59,8 +66,30 @@ public class DepartureArrayAdapter extends ArrayAdapter {
Departure departure = getItem(position);
((TextView) view.findViewById(R.id.destinationText)).setText(departure
.getDestination().toString());
- ((TextView) view.findViewById(R.id.trainLengthText)).setText(departure
- .getTrainLengthText());
+
+ TextSwitcher textSwitcher = (TextSwitcher) view
+ .findViewById(R.id.trainLengthText);
+ initTextSwitcher(textSwitcher);
+
+ final String estimatedArrivalTimeText = departure
+ .getEstimatedArrivalTimeText(getContext());
+ String arrivalText = "Est. arrival " + estimatedArrivalTimeText;
+ if (StringUtils.isBlank(estimatedArrivalTimeText)) {
+ textSwitcher.setCurrentText(departure.getTrainLengthText());
+ } else if (refreshCounter % 6 < 3) {
+ String trainLengthText = departure.getTrainLengthText();
+ if (refreshCounter % 6 == 0) {
+ textSwitcher.setText(trainLengthText);
+ } else {
+ textSwitcher.setCurrentText(trainLengthText);
+ }
+ } else {
+ if (refreshCounter % 6 == 3) {
+ textSwitcher.setText(arrivalText);
+ } else {
+ textSwitcher.setCurrentText(arrivalText);
+ }
+ }
ImageView colorBar = (ImageView) view
.findViewById(R.id.destinationColorBar);
((GradientDrawable) colorBar.getDrawable()).setColor(Color
@@ -87,4 +116,17 @@ public class DepartureArrayAdapter extends ArrayAdapter {
return view;
}
+ private void initTextSwitcher(TextSwitcher textSwitcher) {
+ if (textSwitcher.getInAnimation() == null) {
+ textSwitcher.setFactory(new ViewFactory() {
+ public View makeView() {
+ return LayoutInflater.from(getContext()).inflate(
+ R.layout.train_length_arrival_textview, null);
+ }
+ });
+
+ textSwitcher.setInAnimation(AnimationUtils.loadAnimation(
+ getContext(), android.R.anim.fade_in));
+ }
+ }
}
diff --git a/src/com/dougkeen/bart/RoutesListActivity.java b/src/com/dougkeen/bart/RoutesListActivity.java
index fe6347f..d599736 100644
--- a/src/com/dougkeen/bart/RoutesListActivity.java
+++ b/src/com/dougkeen/bart/RoutesListActivity.java
@@ -29,6 +29,9 @@ import android.widget.TextView;
import com.dougkeen.bart.data.CursorUtils;
import com.dougkeen.bart.data.RoutesColumns;
+import com.dougkeen.bart.model.Constants;
+import com.dougkeen.bart.model.Station;
+import com.dougkeen.bart.networktasks.GetRouteFareTask;
public class RoutesListActivity extends ListActivity {
private static final TimeZone PACIFIC_TIME = TimeZone
@@ -51,7 +54,9 @@ public class RoutesListActivity extends ListActivity {
mQuery = managedQuery(Constants.FAVORITE_CONTENT_URI, new String[] {
RoutesColumns._ID.string, RoutesColumns.FROM_STATION.string,
RoutesColumns.TO_STATION.string, RoutesColumns.FARE.string,
- RoutesColumns.FARE_LAST_UPDATED.string }, null, null,
+ RoutesColumns.FARE_LAST_UPDATED.string,
+ RoutesColumns.AVERAGE_TRIP_SAMPLE_COUNT.string,
+ RoutesColumns.AVERAGE_TRIP_LENGTH.string }, null, null,
RoutesColumns._ID.string);
refreshFares();
diff --git a/src/com/dougkeen/bart/ViewDeparturesActivity.java b/src/com/dougkeen/bart/ViewDeparturesActivity.java
index 986af16..6ad519c 100644
--- a/src/com/dougkeen/bart/ViewDeparturesActivity.java
+++ b/src/com/dougkeen/bart/ViewDeparturesActivity.java
@@ -3,6 +3,7 @@ package com.dougkeen.bart;
import java.util.List;
import android.app.ListActivity;
+import android.content.ContentValues;
import android.content.Context;
import android.content.Intent;
import android.database.Cursor;
@@ -22,10 +23,16 @@ import android.widget.ArrayAdapter;
import android.widget.TextView;
import android.widget.Toast;
-import com.dougkeen.bart.GetRealTimeDeparturesTask.Params;
-import com.dougkeen.bart.data.Departure;
-import com.dougkeen.bart.data.RealTimeDepartures;
import com.dougkeen.bart.data.RoutesColumns;
+import com.dougkeen.bart.model.Constants;
+import com.dougkeen.bart.model.Departure;
+import com.dougkeen.bart.model.ScheduleInformation;
+import com.dougkeen.bart.model.ScheduleItem;
+import com.dougkeen.bart.model.StationPair;
+import com.dougkeen.bart.model.RealTimeDepartures;
+import com.dougkeen.bart.model.Station;
+import com.dougkeen.bart.networktasks.GetRealTimeDeparturesTask;
+import com.dougkeen.bart.networktasks.GetScheduleInformationTask;
public class ViewDeparturesActivity extends ListActivity {
@@ -35,12 +42,17 @@ public class ViewDeparturesActivity extends ListActivity {
private Station mOrigin;
private Station mDestination;
+ private int mAverageTripLength;
+ private int mAverageTripSampleCount;
private ArrayAdapter mDeparturesAdapter;
+ private ScheduleInformation mLatestScheduleInfo;
+
private TextView mListTitleView;
- private AsyncTask mGetDeparturesTask;
+ private AsyncTask mGetDeparturesTask;
+ private AsyncTask mGetScheduleInformationTask;
private boolean mIsAutoUpdating = false;
@@ -52,7 +64,8 @@ public class ViewDeparturesActivity extends ListActivity {
private PowerManager.WakeLock mWakeLock;
- private boolean mDataFetchIsPending;
+ private boolean mDepartureFetchIsPending;
+ private boolean mScheduleFetchIsPending;
@Override
protected void onCreate(Bundle savedInstanceState) {
@@ -69,13 +82,18 @@ public class ViewDeparturesActivity extends ListActivity {
Cursor cursor = managedQuery(mUri, new String[] {
RoutesColumns.FROM_STATION.string,
- RoutesColumns.TO_STATION.string }, null, null, null);
+ RoutesColumns.TO_STATION.string,
+ RoutesColumns.AVERAGE_TRIP_LENGTH.string,
+ RoutesColumns.AVERAGE_TRIP_SAMPLE_COUNT.string }, null, null,
+ null);
if (!cursor.moveToFirst()) {
throw new IllegalStateException("URI not found: " + mUri.toString());
}
mOrigin = Station.getByAbbreviation(cursor.getString(0));
mDestination = Station.getByAbbreviation(cursor.getString(1));
+ mAverageTripLength = cursor.getInt(2);
+ mAverageTripSampleCount = cursor.getInt(3);
String header = "Departures:\n" + mOrigin.name + " to "
+ mDestination.name;
@@ -114,7 +132,11 @@ public class ViewDeparturesActivity extends ListActivity {
private void cancelDataFetch() {
if (mGetDeparturesTask != null) {
mGetDeparturesTask.cancel(true);
- mDataFetchIsPending = false;
+ mDepartureFetchIsPending = false;
+ }
+ if (mGetScheduleInformationTask != null) {
+ mGetScheduleInformationTask.cancel(true);
+ mScheduleFetchIsPending = false;
}
}
@@ -132,7 +154,7 @@ public class ViewDeparturesActivity extends ListActivity {
public void onWindowFocusChanged(boolean hasFocus) {
super.onWindowFocusChanged(hasFocus);
if (hasFocus) {
- if (!mDataFetchIsPending) {
+ if (!mDepartureFetchIsPending) {
fetchLatestDepartures();
}
PowerManager powerManager = (PowerManager) getSystemService(Context.POWER_SERVICE);
@@ -162,7 +184,7 @@ public class ViewDeparturesActivity extends ListActivity {
mGetDeparturesTask = new GetRealTimeDeparturesTask() {
@Override
public void onResult(RealTimeDepartures result) {
- mDataFetchIsPending = false;
+ mDepartureFetchIsPending = false;
Log.i(Constants.TAG, "Processing data from server");
processLatestDepartures(result);
Log.i(Constants.TAG, "Done processing data from server");
@@ -170,19 +192,55 @@ public class ViewDeparturesActivity extends ListActivity {
@Override
public void onError(Exception e) {
- mDataFetchIsPending = false;
+ mDepartureFetchIsPending = 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);
// Try again in 60s
- scheduleDataFetch(60000);
+ scheduleDepartureFetch(60000);
}
};
Log.i(Constants.TAG, "Fetching data from server");
- mGetDeparturesTask.execute(new GetRealTimeDeparturesTask.Params(
- mOrigin, mDestination));
+ mGetDeparturesTask.execute(new StationPair(mOrigin, mDestination));
+ }
+
+ private void fetchLatestSchedule() {
+ if (!hasWindowFocus())
+ return;
+ if (mGetScheduleInformationTask != null
+ && mGetScheduleInformationTask.getStatus().equals(
+ AsyncTask.Status.RUNNING)) {
+ // Don't overlap fetches
+ return;
+ }
+
+ mGetScheduleInformationTask = new GetScheduleInformationTask() {
+ @Override
+ public void onResult(ScheduleInformation result) {
+ mScheduleFetchIsPending = false;
+ Log.i(Constants.TAG, "Processing data from server");
+ mLatestScheduleInfo = result;
+ applyScheduleInformation();
+ Log.i(Constants.TAG, "Done processing data from server");
+ }
+
+ @Override
+ public void onError(Exception e) {
+ mScheduleFetchIsPending = 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);
+ // Try again in 60s
+ scheduleScheduleInfoFetch(60000);
+ }
+ };
+ Log.i(Constants.TAG, "Fetching data from server");
+ mGetScheduleInformationTask.execute(new StationPair(mOrigin,
+ mDestination));
}
protected void processLatestDepartures(RealTimeDepartures result) {
@@ -238,11 +296,12 @@ public class ViewDeparturesActivity extends ListActivity {
needsBetterAccuracy = true;
}
mDeparturesAdapter.notifyDataSetChanged();
+ requestScheduleIfNecessary();
if (hasWindowFocus() && firstDeparture != null) {
if (needsBetterAccuracy || firstDeparture.hasDeparted()) {
// Get more data in 20s
- scheduleDataFetch(20000);
+ scheduleDepartureFetch(20000);
} else {
// Get more 90 seconds before next train arrives, right when
// next train arrives, or 3 minutes, whichever is sooner
@@ -262,7 +321,7 @@ public class ViewDeparturesActivity extends ListActivity {
interval = 20000;
}
- scheduleDataFetch(interval);
+ scheduleDepartureFetch(interval);
}
if (!mIsAutoUpdating) {
mIsAutoUpdating = true;
@@ -272,21 +331,106 @@ public class ViewDeparturesActivity extends ListActivity {
}
}
- private void scheduleDataFetch(int millisUntilExecute) {
- if (!mDataFetchIsPending) {
+ private void requestScheduleIfNecessary() {
+ if (mDeparturesAdapter.getCount() == 0) {
+ return;
+ }
+
+ if (mLatestScheduleInfo == null) {
+ fetchLatestSchedule();
+ return;
+ }
+
+ Departure lastDeparture = mDeparturesAdapter.getItem(mDeparturesAdapter
+ .getCount() - 1);
+ if (mLatestScheduleInfo.getLatestDepartureTime() < lastDeparture
+ .getMeanEstimate()) {
+ fetchLatestSchedule();
+ return;
+ }
+ }
+
+ private void applyScheduleInformation() {
+ int localAverageLength = mLatestScheduleInfo.getAverageTripLength();
+
+ int departuresCount = mDeparturesAdapter.getCount();
+ int lastSearchIndex = 0;
+ int tripCount = mLatestScheduleInfo.getTrips().size();
+ boolean departureUpdated = false;
+ for (int departureIndex = 0; departureIndex < departuresCount; departureIndex++) {
+ Departure departure = mDeparturesAdapter.getItem(departureIndex);
+ for (int i = lastSearchIndex; i < tripCount; i++) {
+ ScheduleItem trip = mLatestScheduleInfo.getTrips().get(i);
+ long departTimeDiff = Math.abs(trip.getDepartureTime()
+ - departure.getMeanEstimate());
+ if (departTimeDiff <= (60000 + departure
+ .getUncertaintySeconds() * 1000)
+ && departure.getEstimatedTripTime() != trip
+ .getTripLength()) {
+ departure.setEstimatedTripTime(trip.getTripLength());
+ lastSearchIndex = i;
+ departureUpdated = true;
+ break;
+ }
+ }
+ if (!departure.hasEstimatedTripTime() && localAverageLength > 0) {
+ departure.setEstimatedTripTime(localAverageLength);
+ } else if (!departure.hasEstimatedTripTime()) {
+ departure.setEstimatedTripTime(mAverageTripLength);
+ }
+ }
+
+ if (departureUpdated) {
+ mDeparturesAdapter.notifyDataSetChanged();
+ }
+
+ // Update global average
+ if (mLatestScheduleInfo.getTripCountForAverage() > 0) {
+ int newAverageSampleCount = mAverageTripSampleCount
+ + mLatestScheduleInfo.getTripCountForAverage();
+ int newAverage = (mAverageTripLength * mAverageTripSampleCount + localAverageLength
+ * mLatestScheduleInfo.getTripCountForAverage())
+ / newAverageSampleCount;
+
+ ContentValues contentValues = new ContentValues();
+ contentValues.put(RoutesColumns.AVERAGE_TRIP_LENGTH.string,
+ newAverage);
+ contentValues.put(RoutesColumns.AVERAGE_TRIP_SAMPLE_COUNT.string,
+ newAverageSampleCount);
+
+ getContentResolver().update(mUri, contentValues, null, null);
+ }
+ }
+
+ private void scheduleDepartureFetch(int millisUntilExecute) {
+ if (!mDepartureFetchIsPending) {
mListTitleView.postDelayed(new Runnable() {
public void run() {
fetchLatestDepartures();
}
}, millisUntilExecute);
- mDataFetchIsPending = true;
- Log.i(Constants.TAG, "Scheduled another data fetch in "
+ mDepartureFetchIsPending = true;
+ Log.i(Constants.TAG, "Scheduled another departure fetch in "
+ + millisUntilExecute / 1000 + "s");
+ }
+ }
+
+ private void scheduleScheduleInfoFetch(int millisUntilExecute) {
+ if (!mScheduleFetchIsPending) {
+ mListTitleView.postDelayed(new Runnable() {
+ public void run() {
+ fetchLatestSchedule();
+ }
+ }, millisUntilExecute);
+ mScheduleFetchIsPending = true;
+ Log.i(Constants.TAG, "Scheduled another schedule fetch in "
+ millisUntilExecute / 1000 + "s");
}
}
private void runAutoUpdate() {
if (mIsAutoUpdating && mDeparturesAdapter != null) {
+ DepartureArrayAdapter.refreshCounter++;
mDeparturesAdapter.notifyDataSetChanged();
}
if (hasWindowFocus()) {
diff --git a/src/com/dougkeen/bart/data/BartContentProvider.java b/src/com/dougkeen/bart/data/BartContentProvider.java
index 2344eed..9894a24 100644
--- a/src/com/dougkeen/bart/data/BartContentProvider.java
+++ b/src/com/dougkeen/bart/data/BartContentProvider.java
@@ -2,7 +2,7 @@ package com.dougkeen.bart.data;
import java.util.HashMap;
-import com.dougkeen.bart.Constants;
+import com.dougkeen.bart.model.Constants;
import android.content.ContentProvider;
import android.content.ContentUris;
@@ -45,6 +45,10 @@ public class BartContentProvider extends ContentProvider {
RoutesColumns.FARE.string);
sFavoritesProjectionMap.put(RoutesColumns.FARE_LAST_UPDATED.string,
RoutesColumns.FARE_LAST_UPDATED.string);
+ sFavoritesProjectionMap.put(RoutesColumns.AVERAGE_TRIP_SAMPLE_COUNT.string,
+ RoutesColumns.AVERAGE_TRIP_SAMPLE_COUNT.string);
+ sFavoritesProjectionMap.put(RoutesColumns.AVERAGE_TRIP_LENGTH.string,
+ RoutesColumns.AVERAGE_TRIP_LENGTH.string);
}
private DatabaseHelper mDatabaseHelper;
diff --git a/src/com/dougkeen/bart/data/DatabaseHelper.java b/src/com/dougkeen/bart/data/DatabaseHelper.java
index 5e633a9..d22383b 100644
--- a/src/com/dougkeen/bart/data/DatabaseHelper.java
+++ b/src/com/dougkeen/bart/data/DatabaseHelper.java
@@ -15,7 +15,7 @@ import android.util.Log;
public class DatabaseHelper extends SQLiteOpenHelper {
private static final String DATABASE_NAME = "bart.dougkeen.db";
- private static final int DATABASE_VERSION = 2;
+ private static final int DATABASE_VERSION = 4;
public static final String FAVORITES_TABLE_NAME = "Favorites";
@@ -34,7 +34,9 @@ public class DatabaseHelper extends SQLiteOpenHelper {
+ RoutesColumns.FROM_STATION.getColumnDef() + ", "
+ RoutesColumns.TO_STATION.getColumnDef() + ", "
+ RoutesColumns.FARE.getColumnDef() + ", "
- + RoutesColumns.FARE_LAST_UPDATED.getColumnDef() + ");");
+ + RoutesColumns.FARE_LAST_UPDATED.getColumnDef() + ", "
+ + RoutesColumns.AVERAGE_TRIP_SAMPLE_COUNT.getColumnDef() + ", "
+ + RoutesColumns.AVERAGE_TRIP_LENGTH.getColumnDef() + ");");
}
@Override
diff --git a/src/com/dougkeen/bart/data/RoutesColumns.java b/src/com/dougkeen/bart/data/RoutesColumns.java
index acf646e..e4ad2ee 100644
--- a/src/com/dougkeen/bart/data/RoutesColumns.java
+++ b/src/com/dougkeen/bart/data/RoutesColumns.java
@@ -5,7 +5,9 @@ public enum RoutesColumns {
FROM_STATION("FROM_STATION", "TEXT", false),
TO_STATION("TO_STATION", "TEXT", false),
FARE("FARE", "TEXT", true),
- FARE_LAST_UPDATED("FARE_LAST_UPDATED", "INTEGER", true);
+ FARE_LAST_UPDATED("FARE_LAST_UPDATED", "INTEGER", true),
+ AVERAGE_TRIP_SAMPLE_COUNT("AVE_TRIP_SAMPLE_COUNT", "INTEGER", true),
+ AVERAGE_TRIP_LENGTH("AVE_TRIP_LENGTH", "INTEGER", true);
// This class cannot be instantiated
diff --git a/src/com/dougkeen/bart/Constants.java b/src/com/dougkeen/bart/model/Constants.java
similarity index 86%
rename from src/com/dougkeen/bart/Constants.java
rename to src/com/dougkeen/bart/model/Constants.java
index 62ded3a..6134a31 100644
--- a/src/com/dougkeen/bart/Constants.java
+++ b/src/com/dougkeen/bart/model/Constants.java
@@ -1,4 +1,4 @@
-package com.dougkeen.bart;
+package com.dougkeen.bart.model;
import android.net.Uri;
@@ -10,6 +10,6 @@ public class Constants {
+ AUTHORITY + "/favorites");
public static final String MAP_URL = "http://m.bart.gov/images/global/system-map29.gif";
- public static final String TAG = "BartCatcher";
+ public static final String TAG = "BartRunner";
public static final String API_KEY = "5LD9-IAYI-TRAT-MHHW";
}
diff --git a/src/com/dougkeen/bart/data/Departure.java b/src/com/dougkeen/bart/model/Departure.java
similarity index 77%
rename from src/com/dougkeen/bart/data/Departure.java
rename to src/com/dougkeen/bart/model/Departure.java
index fd1a4eb..a1af043 100644
--- a/src/com/dougkeen/bart/data/Departure.java
+++ b/src/com/dougkeen/bart/model/Departure.java
@@ -1,13 +1,15 @@
-package com.dougkeen.bart.data;
+package com.dougkeen.bart.model;
+import java.util.Date;
+
+import android.content.Context;
import android.os.Parcel;
import android.os.Parcelable;
-
-import com.dougkeen.bart.Line;
-import com.dougkeen.bart.Station;
+import android.text.format.DateFormat;
public class Departure implements Parcelable, Comparable {
private static final int ESTIMATE_EQUALS_TOLERANCE_MILLIS = 59999;
+ private static final int ESTIMATE_EQUALS_TOLERANCE_LONG_LINGER_MILLIS = 719999;
private static final int MINIMUM_MERGE_OVERLAP_MILLIS = 10000;
public Departure() {
@@ -31,6 +33,7 @@ public class Departure implements Parcelable, Comparable {
readFromParcel(in);
}
+ private Station origin;
private Station destination;
private Line line;
private String destinationColor;
@@ -45,6 +48,20 @@ public class Departure implements Parcelable, Comparable {
private long minEstimate;
private long maxEstimate;
+ private int estimatedTripTime;
+
+ private boolean beganAsDeparted;
+
+ private long arrivalTimeOverride;
+
+ public Station getOrigin() {
+ return origin;
+ }
+
+ public void setOrigin(Station origin) {
+ this.origin = origin;
+ }
+
public Station getDestination() {
return destination;
}
@@ -131,6 +148,9 @@ public class Departure implements Parcelable, Comparable {
public void setMinutes(int minutes) {
this.minutes = minutes;
+ if (minutes == 0) {
+ beganAsDeparted = true;
+ }
}
public long getMinEstimate() {
@@ -149,6 +169,18 @@ public class Departure implements Parcelable, Comparable {
this.maxEstimate = maxEstimate;
}
+ public int getEstimatedTripTime() {
+ return estimatedTripTime;
+ }
+
+ public void setEstimatedTripTime(int estimatedTripTime) {
+ this.estimatedTripTime = estimatedTripTime;
+ }
+
+ public boolean hasEstimatedTripTime() {
+ return this.estimatedTripTime > 0;
+ }
+
public int getUncertaintySeconds() {
return (int) (maxEstimate - minEstimate + 1000) / 2000;
}
@@ -162,8 +194,27 @@ public class Departure implements Parcelable, Comparable {
}
public int getMeanSecondsLeft() {
- return (int) (((getMinEstimate() + getMaxEstimate()) / 2 - System
- .currentTimeMillis()) / 1000);
+ return (int) ((getMeanEstimate() - System.currentTimeMillis()) / 1000);
+ }
+
+ public long getMeanEstimate() {
+ return (getMinEstimate() + getMaxEstimate()) / 2;
+ }
+
+ public long getEstimatedArrivalTime() {
+ if (arrivalTimeOverride > 0) {
+ return arrivalTimeOverride;
+ }
+ return getMeanEstimate() + getEstimatedTripTime();
+ }
+
+ public String getEstimatedArrivalTimeText(Context context) {
+ if (getEstimatedTripTime() > 0) {
+ return DateFormat.getTimeFormat(context).format(
+ new Date(getEstimatedArrivalTime()));
+ } else {
+ return "";
+ }
}
public boolean hasDeparted() {
@@ -177,6 +228,13 @@ public class Departure implements Parcelable, Comparable {
}
public void mergeEstimate(Departure departure) {
+ if (departure.hasDeparted() && origin.longStationLinger
+ && getMinEstimate() > 0 && !beganAsDeparted) {
+ // This is probably not a true departure, but an indication that
+ // the train is in the station. Don't update the estimates.
+ return;
+ }
+
if ((getMaxEstimate() - departure.getMinEstimate()) < MINIMUM_MERGE_OVERLAP_MILLIS
|| departure.getMaxEstimate() - getMinEstimate() < MINIMUM_MERGE_OVERLAP_MILLIS) {
// The estimate must have changed... just use the latest incoming
@@ -250,7 +308,7 @@ public class Departure implements Parcelable, Comparable {
return false;
if (line != other.line)
return false;
- if (Math.abs(maxEstimate - other.maxEstimate) > ESTIMATE_EQUALS_TOLERANCE_MILLIS)
+ if (Math.abs(maxEstimate - other.maxEstimate) > getEqualsTolerance())
return false;
if (platform == null) {
if (other.platform != null)
@@ -267,11 +325,23 @@ public class Departure implements Parcelable, Comparable {
return true;
}
+ private int getEqualsTolerance() {
+ if (origin.longStationLinger && hasDeparted()) {
+ return ESTIMATE_EQUALS_TOLERANCE_LONG_LINGER_MILLIS;
+ } else {
+ return ESTIMATE_EQUALS_TOLERANCE_MILLIS;
+ }
+ }
+
public String getCountdownText() {
StringBuilder builder = new StringBuilder();
int secondsLeft = getMeanSecondsLeft();
if (hasDeparted()) {
- builder.append("Departed");
+ if (origin.longStationLinger && beganAsDeparted) {
+ builder.append("At station");
+ } else {
+ builder.append("Departed");
+ }
} else {
builder.append(secondsLeft / 60);
builder.append("m, ");
diff --git a/src/com/dougkeen/bart/Line.java b/src/com/dougkeen/bart/model/Line.java
similarity index 87%
rename from src/com/dougkeen/bart/Line.java
rename to src/com/dougkeen/bart/model/Line.java
index 5fd79bb..a37f58e 100644
--- a/src/com/dougkeen/bart/Line.java
+++ b/src/com/dougkeen/bart/model/Line.java
@@ -1,4 +1,4 @@
-package com.dougkeen.bart;
+package com.dougkeen.bart.model;
import java.util.ArrayList;
import java.util.Arrays;
@@ -37,6 +37,12 @@ public enum Line {
Station.COLS, Station.SANL, Station.BAYF, Station.HAYW,
Station.SHAY, Station.UCTY, Station.FRMT),
YELLOW_ORANGE_SCHEDULED_TRANSFER(YELLOW, ORANGE, Station.MLBR,
+ Station.SFIA, Station.SBRN, Station.SSAN, Station.COLM,
+ Station.DALY, Station.BALB, Station.GLEN, Station._24TH,
+ Station._16TH, Station.CIVC, Station.POWL, Station.MONT,
+ Station.EMBR, Station.WOAK, Station.ASHB, Station.DBRK,
+ Station.NBRK, Station.PLZA, Station.DELN, Station.RICH),
+ YELLOW_RED_SCHEDULED_TRANSFER(YELLOW, RED, Station.MLBR,
Station.SFIA, Station.SBRN, Station.SSAN, Station.COLM,
Station.DALY, Station.BALB, Station.GLEN, Station._24TH,
Station._16TH, Station.CIVC, Station.POWL, Station.MONT,
diff --git a/src/com/dougkeen/bart/data/RealTimeDepartures.java b/src/com/dougkeen/bart/model/RealTimeDepartures.java
similarity index 91%
rename from src/com/dougkeen/bart/data/RealTimeDepartures.java
rename to src/com/dougkeen/bart/model/RealTimeDepartures.java
index a7ec600..0d62bd4 100644
--- a/src/com/dougkeen/bart/data/RealTimeDepartures.java
+++ b/src/com/dougkeen/bart/model/RealTimeDepartures.java
@@ -1,11 +1,9 @@
-package com.dougkeen.bart.data;
+package com.dougkeen.bart.model;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
-import com.dougkeen.bart.Route;
-import com.dougkeen.bart.Station;
public class RealTimeDepartures {
public RealTimeDepartures(Station origin, Station destination,
diff --git a/src/com/dougkeen/bart/Route.java b/src/com/dougkeen/bart/model/Route.java
similarity index 94%
rename from src/com/dougkeen/bart/Route.java
rename to src/com/dougkeen/bart/model/Route.java
index f063411..7ce4d4c 100644
--- a/src/com/dougkeen/bart/Route.java
+++ b/src/com/dougkeen/bart/model/Route.java
@@ -1,4 +1,4 @@
-package com.dougkeen.bart;
+package com.dougkeen.bart.model;
public class Route {
private Station origin;
diff --git a/src/com/dougkeen/bart/model/ScheduleInformation.java b/src/com/dougkeen/bart/model/ScheduleInformation.java
new file mode 100644
index 0000000..25e635a
--- /dev/null
+++ b/src/com/dougkeen/bart/model/ScheduleInformation.java
@@ -0,0 +1,91 @@
+package com.dougkeen.bart.model;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import android.R.integer;
+
+public class ScheduleInformation {
+
+ public ScheduleInformation(Station origin, Station destination) {
+ super();
+ this.origin = origin;
+ this.destination = destination;
+ }
+
+ private Station origin;
+ private Station destination;
+ private long date;
+ private List trips;
+
+ public Station getOrigin() {
+ return origin;
+ }
+
+ public void setOrigin(Station origin) {
+ this.origin = origin;
+ }
+
+ public Station getDestination() {
+ return destination;
+ }
+
+ public void setDestination(Station destination) {
+ this.destination = destination;
+ }
+
+ public long getDate() {
+ return date;
+ }
+
+ public void setDate(long date) {
+ this.date = date;
+ }
+
+ public List getTrips() {
+ if (trips == null) {
+ trips = new ArrayList();
+ }
+ return trips;
+ }
+
+ public void setTrips(List trips) {
+ this.trips = trips;
+ }
+
+ public void addTrip(ScheduleItem trip) {
+ getTrips().add(trip);
+ }
+
+ public long getLatestDepartureTime() {
+ if (getTrips().isEmpty())
+ return -1;
+ else
+ return getTrips().get(getTrips().size() - 1).getDepartureTime();
+ }
+
+ private int aveTripLength = -1;
+ private int tripCount = 0;
+
+ public int getAverageTripLength() {
+ if (aveTripLength < 0) {
+ int sum = 0;
+ for (ScheduleItem trip : getTrips()) {
+ int tripLength = trip.getTripLength();
+ if (tripLength > 0) {
+ sum += tripLength;
+ tripCount++;
+ }
+ }
+ if (tripCount > 0) {
+ aveTripLength = sum / tripCount;
+ }
+ }
+ return aveTripLength;
+ }
+
+ public int getTripCountForAverage() {
+ getAverageTripLength();
+ return tripCount;
+ }
+}
\ No newline at end of file
diff --git a/src/com/dougkeen/bart/model/ScheduleItem.java b/src/com/dougkeen/bart/model/ScheduleItem.java
new file mode 100644
index 0000000..ac18840
--- /dev/null
+++ b/src/com/dougkeen/bart/model/ScheduleItem.java
@@ -0,0 +1,86 @@
+package com.dougkeen.bart.model;
+
+public class ScheduleItem {
+
+ public ScheduleItem() {
+ super();
+ }
+
+ public ScheduleItem(Station origin, Station destination) {
+ super();
+ this.origin = origin;
+ this.destination = destination;
+ }
+
+ private Station origin;
+ private Station destination;
+ private String fare;
+ private long departureTime;
+ private long arrivalTime;
+ private boolean bikesAllowed;
+ private String trainHeadStation;
+
+ public Station getOrigin() {
+ return origin;
+ }
+
+ public void setOrigin(Station origin) {
+ this.origin = origin;
+ }
+
+ public Station getDestination() {
+ return destination;
+ }
+
+ public void setDestination(Station destination) {
+ this.destination = destination;
+ }
+
+ public String getFare() {
+ return fare;
+ }
+
+ public void setFare(String fare) {
+ this.fare = fare;
+ }
+
+ public long getDepartureTime() {
+ return departureTime;
+ }
+
+ public void setDepartureTime(long departureTime) {
+ this.departureTime = departureTime;
+ }
+
+ public long getArrivalTime() {
+ return arrivalTime;
+ }
+
+ public void setArrivalTime(long arrivalTime) {
+ this.arrivalTime = arrivalTime;
+ }
+
+ public int getTripLength() {
+ if (departureTime <= 0 || arrivalTime <= 0) {
+ return 0;
+ } else {
+ return (int) (arrivalTime - departureTime);
+ }
+ }
+
+ public boolean isBikesAllowed() {
+ return bikesAllowed;
+ }
+
+ public void setBikesAllowed(boolean bikesAllowed) {
+ this.bikesAllowed = bikesAllowed;
+ }
+
+ public String getTrainHeadStation() {
+ return trainHeadStation;
+ }
+
+ public void setTrainHeadStation(String trainHeadStation) {
+ this.trainHeadStation = trainHeadStation;
+ }
+}
diff --git a/src/com/dougkeen/bart/Station.java b/src/com/dougkeen/bart/model/Station.java
similarity index 77%
rename from src/com/dougkeen/bart/Station.java
rename to src/com/dougkeen/bart/model/Station.java
index 55a65d0..76ca947 100644
--- a/src/com/dougkeen/bart/Station.java
+++ b/src/com/dougkeen/bart/model/Station.java
@@ -1,8 +1,10 @@
-package com.dougkeen.bart;
+package com.dougkeen.bart.model;
import java.util.ArrayList;
import java.util.List;
+import android.util.Log;
+
public enum Station {
_12TH("12th", "12th St./Oakland City Center", false, false, "bayf"),
_16TH("16th", "16th St. Mission", false, false),
@@ -41,7 +43,7 @@ public enum Station {
ROCK("rock", "Rockridge", false, false, "mcar"),
SBRN("sbrn", "San Bruno", false, false, "balb", "balb"),
SANL("sanl", "San Leandro", true, false, "mcar"),
- SFIA("sfia", "SFO Airport", false, false, "sbrn", "balb"),
+ SFIA("sfia", "SFO Airport", false, false, "sbrn", "balb", true),
SHAY("shay", "South Hayward", true, false, "bayf"),
SSAN("ssan", "South San Francisco", false, false, "balb", "balb"),
UCTY("ucty", "Union City", true, false, "bayf"),
@@ -56,43 +58,50 @@ public enum Station {
protected final String inboundTransferStation;
protected final String outboundTransferStation;
public final boolean endOfLine;
+ public final boolean longStationLinger;
- private Station(String abbreviation, String name, boolean invertDirection, boolean endOfLine) {
- this.abbreviation = abbreviation;
- this.name = name;
- this.invertDirection = invertDirection;
- this.inboundTransferStation = null;
- this.outboundTransferStation = null;
- this.endOfLine = endOfLine;
+ private Station(String abbreviation, String name, boolean invertDirection,
+ boolean endOfLine) {
+ this(abbreviation, name, invertDirection, endOfLine, null, null, false);
}
- private Station(String abbreviation, String name, boolean invertDirection, boolean endOfLine,
- String transferStation) {
- this.abbreviation = abbreviation;
- this.name = name;
- this.invertDirection = invertDirection;
- this.inboundTransferStation = transferStation;
- this.outboundTransferStation = null;
- this.endOfLine = endOfLine;
+ private Station(String abbreviation, String name, boolean invertDirection,
+ boolean endOfLine, String transferStation) {
+ this(abbreviation, name, invertDirection, endOfLine, transferStation,
+ null, false);
}
- private Station(String abbreviation, String name, boolean invertDirection, boolean endOfLine,
- String inboundTransferStation, String outboundTransferStation) {
+ private Station(String abbreviation, String name, boolean invertDirection,
+ boolean endOfLine, String inboundTransferStation,
+ String outboundTransferStation) {
+ this(abbreviation, name, invertDirection, endOfLine,
+ inboundTransferStation, outboundTransferStation, false);
+ }
+
+ private Station(String abbreviation, String name, boolean invertDirection,
+ boolean endOfLine, String inboundTransferStation,
+ String outboundTransferStation, boolean longStationLinger) {
this.abbreviation = abbreviation;
this.name = name;
this.invertDirection = invertDirection;
this.inboundTransferStation = inboundTransferStation;
this.outboundTransferStation = outboundTransferStation;
this.endOfLine = endOfLine;
+ this.longStationLinger = longStationLinger;
}
public static Station getByAbbreviation(String abbr) {
- if (abbr == null) {
+ try {
+ if (abbr == null) {
+ return null;
+ } else if (Character.isDigit(abbr.charAt(0))) {
+ return Station.valueOf("_" + abbr.toUpperCase());
+ } else {
+ return Station.valueOf(abbr.toUpperCase());
+ }
+ } catch (IllegalArgumentException e) {
+ Log.e(Constants.TAG, "Could not find station for '" + abbr + "'", e);
return null;
- } else if (Character.isDigit(abbr.charAt(0))) {
- return Station.valueOf("_" + abbr.toUpperCase());
- } else {
- return Station.valueOf(abbr.toUpperCase());
}
}
@@ -155,10 +164,9 @@ public enum Station {
}
if (isNorth == null) {
if (outboundTransferStation != null) {
- returnList
- .addAll(getOutboundTransferStation()
- .getRoutesForDestination(dest,
- getOutboundTransferStation()));
+ returnList.addAll(getOutboundTransferStation()
+ .getRoutesForDestination(dest,
+ getOutboundTransferStation()));
} else if (dest.getInboundTransferStation() != null) {
final List routesForDestination = getRoutesForDestination(
dest.getInboundTransferStation(),
diff --git a/src/com/dougkeen/bart/model/StationPair.java b/src/com/dougkeen/bart/model/StationPair.java
new file mode 100644
index 0000000..23f7a3e
--- /dev/null
+++ b/src/com/dougkeen/bart/model/StationPair.java
@@ -0,0 +1,21 @@
+package com.dougkeen.bart.model;
+
+
+public class StationPair {
+ public StationPair(Station origin, Station destination) {
+ super();
+ this.origin = origin;
+ this.destination = destination;
+ }
+
+ private Station origin;
+ private Station destination;
+
+ public Station getOrigin() {
+ return origin;
+ }
+
+ public Station getDestination() {
+ return destination;
+ }
+}
\ No newline at end of file
diff --git a/src/com/dougkeen/bart/EtdContentHandler.java b/src/com/dougkeen/bart/networktasks/EtdContentHandler.java
similarity index 88%
rename from src/com/dougkeen/bart/EtdContentHandler.java
rename to src/com/dougkeen/bart/networktasks/EtdContentHandler.java
index 87dadba..5345733 100644
--- a/src/com/dougkeen/bart/EtdContentHandler.java
+++ b/src/com/dougkeen/bart/networktasks/EtdContentHandler.java
@@ -1,4 +1,4 @@
-package com.dougkeen.bart;
+package com.dougkeen.bart.networktasks;
import java.util.Arrays;
import java.util.Date;
@@ -11,8 +11,12 @@ import org.xml.sax.helpers.DefaultHandler;
import android.util.Log;
-import com.dougkeen.bart.data.Departure;
-import com.dougkeen.bart.data.RealTimeDepartures;
+import com.dougkeen.bart.model.Constants;
+import com.dougkeen.bart.model.Departure;
+import com.dougkeen.bart.model.Line;
+import com.dougkeen.bart.model.RealTimeDepartures;
+import com.dougkeen.bart.model.Route;
+import com.dougkeen.bart.model.Station;
public class EtdContentHandler extends DefaultHandler {
public EtdContentHandler(Station origin, Station destination,
@@ -55,6 +59,7 @@ public class EtdContentHandler extends DefaultHandler {
currentDeparture = new Departure();
currentDeparture.setDestination(Station
.getByAbbreviation(currentDestination));
+ currentDeparture.setOrigin(realTimeDepartures.getOrigin());
}
}
diff --git a/src/com/dougkeen/bart/FareContentHandler.java b/src/com/dougkeen/bart/networktasks/FareContentHandler.java
similarity index 91%
rename from src/com/dougkeen/bart/FareContentHandler.java
rename to src/com/dougkeen/bart/networktasks/FareContentHandler.java
index 19a0e78..fa1c0fb 100644
--- a/src/com/dougkeen/bart/FareContentHandler.java
+++ b/src/com/dougkeen/bart/networktasks/FareContentHandler.java
@@ -1,4 +1,4 @@
-package com.dougkeen.bart;
+package com.dougkeen.bart.networktasks;
import org.xml.sax.Attributes;
import org.xml.sax.SAXException;
diff --git a/src/com/dougkeen/bart/GetRealTimeDeparturesTask.java b/src/com/dougkeen/bart/networktasks/GetRealTimeDeparturesTask.java
similarity index 71%
rename from src/com/dougkeen/bart/GetRealTimeDeparturesTask.java
rename to src/com/dougkeen/bart/networktasks/GetRealTimeDeparturesTask.java
index 64a4df0..2eb5e1e 100644
--- a/src/com/dougkeen/bart/GetRealTimeDeparturesTask.java
+++ b/src/com/dougkeen/bart/networktasks/GetRealTimeDeparturesTask.java
@@ -1,4 +1,4 @@
-package com.dougkeen.bart;
+package com.dougkeen.bart.networktasks;
import java.io.IOException;
import java.io.StringWriter;
@@ -17,11 +17,13 @@ import android.os.AsyncTask;
import android.util.Log;
import android.util.Xml;
-import com.dougkeen.bart.data.RealTimeDepartures;
+import com.dougkeen.bart.model.Constants;
+import com.dougkeen.bart.model.StationPair;
+import com.dougkeen.bart.model.RealTimeDepartures;
+import com.dougkeen.bart.model.Route;
-public abstract class GetRealTimeDeparturesTask
- extends
- AsyncTask {
+public abstract class GetRealTimeDeparturesTask extends
+ AsyncTask {
private final static String ETD_URL = "http://api.bart.gov/api/etd.aspx?cmd=etd&key="
+ Constants.API_KEY + "&orig=%1$s&dir=%2$s";
@@ -34,11 +36,12 @@ public abstract class GetRealTimeDeparturesTask
private List mRoutes;
@Override
- protected RealTimeDepartures doInBackground(Params... paramsArray) {
+ protected RealTimeDepartures doInBackground(StationPair... paramsArray) {
// Always expect one param
- Params params = paramsArray[0];
+ StationPair params = paramsArray[0];
- mRoutes = params.origin.getRoutesForDestination(params.destination);
+ mRoutes = params.getOrigin().getRoutesForDestination(
+ params.getDestination());
if (!isCancelled()) {
return getDeparturesFromNetwork(params, 0);
@@ -47,23 +50,23 @@ public abstract class GetRealTimeDeparturesTask
}
}
- private RealTimeDepartures getDeparturesFromNetwork(Params params,
+ private RealTimeDepartures getDeparturesFromNetwork(StationPair params,
int attemptNumber) {
String xml = null;
try {
String url;
- if (params.origin.endOfLine) {
+ if (params.getOrigin().endOfLine) {
url = String.format(ETD_URL_NO_DIRECTION,
- params.origin.abbreviation);
+ params.getOrigin().abbreviation);
} else {
- url = String.format(ETD_URL, params.origin.abbreviation,
+ url = String.format(ETD_URL, params.getOrigin().abbreviation,
mRoutes.get(0).getDirection());
}
HttpUriRequest request = new HttpGet(url);
- EtdContentHandler handler = new EtdContentHandler(params.origin,
- params.destination, mRoutes);
+ EtdContentHandler handler = new EtdContentHandler(
+ params.getOrigin(), params.getDestination(), mRoutes);
if (isCancelled()) {
return null;
}
@@ -113,25 +116,6 @@ public abstract class GetRealTimeDeparturesTask
}
}
- public static class Params {
- public Params(Station origin, Station destination) {
- super();
- this.origin = origin;
- this.destination = destination;
- }
-
- private Station origin;
- private Station destination;
-
- public Station getOrigin() {
- return origin;
- }
-
- public Station getDestination() {
- return destination;
- }
- }
-
@Override
protected void onPostExecute(RealTimeDepartures result) {
if (result != null) {
diff --git a/src/com/dougkeen/bart/GetRouteFareTask.java b/src/com/dougkeen/bart/networktasks/GetRouteFareTask.java
similarity index 92%
rename from src/com/dougkeen/bart/GetRouteFareTask.java
rename to src/com/dougkeen/bart/networktasks/GetRouteFareTask.java
index 4499eb2..892dfd6 100644
--- a/src/com/dougkeen/bart/GetRouteFareTask.java
+++ b/src/com/dougkeen/bart/networktasks/GetRouteFareTask.java
@@ -1,4 +1,4 @@
-package com.dougkeen.bart;
+package com.dougkeen.bart.networktasks;
import java.io.IOException;
import java.io.StringWriter;
@@ -12,6 +12,9 @@ import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpUriRequest;
import org.xml.sax.SAXException;
+import com.dougkeen.bart.model.Constants;
+import com.dougkeen.bart.model.Station;
+
import android.os.AsyncTask;
import android.util.Log;
import android.util.Xml;
diff --git a/src/com/dougkeen/bart/networktasks/GetScheduleInformationTask.java b/src/com/dougkeen/bart/networktasks/GetScheduleInformationTask.java
new file mode 100644
index 0000000..f8eb974
--- /dev/null
+++ b/src/com/dougkeen/bart/networktasks/GetScheduleInformationTask.java
@@ -0,0 +1,118 @@
+package com.dougkeen.bart.networktasks;
+
+import java.io.IOException;
+import java.io.StringWriter;
+import java.io.UnsupportedEncodingException;
+import java.net.MalformedURLException;
+
+import org.apache.commons.io.IOUtils;
+import org.apache.http.HttpResponse;
+import org.apache.http.HttpStatus;
+import org.apache.http.client.methods.HttpGet;
+import org.apache.http.client.methods.HttpUriRequest;
+import org.xml.sax.SAXException;
+
+import android.os.AsyncTask;
+import android.util.Log;
+import android.util.Xml;
+
+import com.dougkeen.bart.model.Constants;
+import com.dougkeen.bart.model.ScheduleInformation;
+import com.dougkeen.bart.model.StationPair;
+
+public abstract class GetScheduleInformationTask extends
+ AsyncTask {
+
+ private final static String SCHED_URL = "http://api.bart.gov/api/sched.aspx?cmd=depart&key="
+ + Constants.API_KEY + "&orig=%1$s&dest=%2$s&b=1&a=5";
+
+ private final static int MAX_ATTEMPTS = 5;
+
+ private Exception mException;
+
+ @Override
+ protected ScheduleInformation doInBackground(StationPair... paramsArray) {
+ // Always expect one param
+ StationPair params = paramsArray[0];
+
+ if (!isCancelled()) {
+ return getScheduleFromNetwork(params, 0);
+ } else {
+ return null;
+ }
+ }
+
+ private ScheduleInformation getScheduleFromNetwork(StationPair params,
+ int attemptNumber) {
+ String xml = null;
+ try {
+ String url = String.format(SCHED_URL,
+ params.getOrigin().abbreviation,
+ params.getDestination().abbreviation);
+
+ HttpUriRequest request = new HttpGet(url);
+
+ if (isCancelled()) {
+ return null;
+ }
+
+ ScheduleContentHandler handler = new ScheduleContentHandler(
+ params.getOrigin(), params.getDestination());
+
+ HttpResponse response = NetworkUtils.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 ScheduleInformation schedule = handler.getSchedule();
+ return schedule;
+ } catch (MalformedURLException e) {
+ throw new RuntimeException(e);
+ } catch (UnsupportedEncodingException e) {
+ throw new RuntimeException(e);
+ } catch (IOException e) {
+ if (attemptNumber < MAX_ATTEMPTS - 1) {
+ try {
+ Log.w(Constants.TAG,
+ "Attempt to contact server failed... retrying in 3s",
+ e);
+ Thread.sleep(3000);
+ } catch (InterruptedException interrupt) {
+ // Ignore... just go on to next attempt
+ }
+ return getScheduleFromNetwork(params, attemptNumber + 1);
+ } else {
+ mException = new Exception("Could not contact BART system", e);
+ return null;
+ }
+ } catch (SAXException e) {
+ mException = new Exception(
+ "Could not understand response from BART system: " + xml, e);
+ return null;
+ }
+ }
+
+ @Override
+ protected void onPostExecute(ScheduleInformation result) {
+ if (result != null) {
+ onResult(result);
+ } else {
+ onError(mException);
+ }
+ }
+
+ public abstract void onResult(ScheduleInformation result);
+
+ public abstract void onError(Exception exception);
+}
diff --git a/src/com/dougkeen/bart/NetworkUtils.java b/src/com/dougkeen/bart/networktasks/NetworkUtils.java
similarity index 94%
rename from src/com/dougkeen/bart/NetworkUtils.java
rename to src/com/dougkeen/bart/networktasks/NetworkUtils.java
index 417d937..6d06981 100644
--- a/src/com/dougkeen/bart/NetworkUtils.java
+++ b/src/com/dougkeen/bart/networktasks/NetworkUtils.java
@@ -1,4 +1,4 @@
-package com.dougkeen.bart;
+package com.dougkeen.bart.networktasks;
import java.io.IOException;
diff --git a/src/com/dougkeen/bart/networktasks/ScheduleContentHandler.java b/src/com/dougkeen/bart/networktasks/ScheduleContentHandler.java
new file mode 100644
index 0000000..b18b4a8
--- /dev/null
+++ b/src/com/dougkeen/bart/networktasks/ScheduleContentHandler.java
@@ -0,0 +1,160 @@
+package com.dougkeen.bart.networktasks;
+
+import java.text.DateFormat;
+import java.text.ParseException;
+import java.text.SimpleDateFormat;
+import java.util.Arrays;
+import java.util.Date;
+import java.util.List;
+import java.util.TimeZone;
+
+import org.apache.commons.lang3.StringUtils;
+import org.xml.sax.Attributes;
+import org.xml.sax.SAXException;
+import org.xml.sax.helpers.DefaultHandler;
+
+import android.util.Log;
+
+import com.dougkeen.bart.model.Constants;
+import com.dougkeen.bart.model.Departure;
+import com.dougkeen.bart.model.Line;
+import com.dougkeen.bart.model.RealTimeDepartures;
+import com.dougkeen.bart.model.Route;
+import com.dougkeen.bart.model.ScheduleInformation;
+import com.dougkeen.bart.model.ScheduleItem;
+import com.dougkeen.bart.model.Station;
+
+public class ScheduleContentHandler extends DefaultHandler {
+ public ScheduleContentHandler(Station origin, Station destination) {
+ super();
+ schedule = new ScheduleInformation(origin, destination);
+ }
+
+ private final static List TAGS = Arrays.asList("date", "time",
+ "trip");
+
+ private final static DateFormat TRIP_DATE_FORMAT;
+ private final static DateFormat REQUEST_DATE_FORMAT;
+
+ private final static TimeZone PACIFIC_TIME = TimeZone
+ .getTimeZone("America/Los_Angeles");
+
+ static {
+ TRIP_DATE_FORMAT = new SimpleDateFormat("MM/dd/yyyy h:mm a");
+ REQUEST_DATE_FORMAT = new SimpleDateFormat("MMM d, yyyy h:mm a");
+
+ TRIP_DATE_FORMAT.setTimeZone(PACIFIC_TIME);
+ REQUEST_DATE_FORMAT.setTimeZone(PACIFIC_TIME);
+ }
+
+ private ScheduleInformation schedule;
+
+ public ScheduleInformation getSchedule() {
+ return schedule;
+ }
+
+ private String currentValue;
+ private boolean isParsingTag;
+
+ private String requestDate;
+ private String requestTime;
+
+ @Override
+ public void characters(char[] ch, int start, int length)
+ throws SAXException {
+ if (isParsingTag) {
+ currentValue = new String(ch, start, length);
+ }
+ }
+
+ @Override
+ public void startElement(String uri, String localName, String qName,
+ Attributes attributes) throws SAXException {
+ if (TAGS.contains(localName)) {
+ isParsingTag = true;
+ }
+ if (localName.equals("trip")) {
+ ScheduleItem trip = new ScheduleItem();
+ String originDate = null;
+ String originTime = null;
+ String destinationDate = null;
+ String destinationTime = null;
+ for (int i = attributes.getLength() - 1; i >= 0; i--) {
+ if (attributes.getLocalName(i).equalsIgnoreCase("origin")) {
+ trip.setOrigin(Station.getByAbbreviation(attributes
+ .getValue(i)));
+ } else if (attributes.getLocalName(i).equalsIgnoreCase(
+ "destination")) {
+ trip.setDestination(Station.getByAbbreviation(attributes
+ .getValue(i)));
+ } else if (attributes.getLocalName(i).equalsIgnoreCase("fare")) {
+ trip.setFare(attributes.getValue(i));
+ } else if (attributes.getLocalName(i).equalsIgnoreCase(
+ "origTimeMin")) {
+ originTime = attributes.getValue(i);
+ } else if (attributes.getLocalName(i).equalsIgnoreCase(
+ "origTimeDate")) {
+ originDate = attributes.getValue(i);
+ } else if (attributes.getLocalName(i).equalsIgnoreCase(
+ "destTimeMin")) {
+ destinationTime = attributes.getValue(i);
+ } else if (attributes.getLocalName(i).equalsIgnoreCase(
+ "destTimeDate")) {
+ destinationDate = attributes.getValue(i);
+ } else if (attributes.getLocalName(i).equalsIgnoreCase(
+ "bikeFlag")) {
+ trip.setBikesAllowed(attributes.getValue(i).equals("1"));
+ } else if (attributes.getLocalName(i).equalsIgnoreCase(
+ "trainHeadStation")) {
+ trip.setTrainHeadStation(attributes.getValue(i));
+ }
+ }
+
+ long departTime = parseDate(TRIP_DATE_FORMAT, originDate,
+ originTime);
+ if (departTime > 0)
+ trip.setDepartureTime(departTime);
+
+ long arriveTime = parseDate(TRIP_DATE_FORMAT, destinationDate,
+ destinationTime);
+ if (arriveTime > 0)
+ trip.setArrivalTime(arriveTime);
+
+ schedule.addTrip(trip);
+ }
+ }
+
+ private long parseDate(DateFormat format, String dateString,
+ String timeString) {
+ if (dateString == null || timeString == null) {
+ return -1;
+ }
+ try {
+ return format.parse(dateString + " " + timeString).getTime();
+ } catch (ParseException e) {
+ Log.e(Constants.TAG, "Unable to parse datetime '" + dateString
+ + " " + timeString + "'", e);
+ return -1;
+ }
+ }
+
+ @Override
+ public void endElement(String uri, String localName, String qName)
+ throws SAXException {
+ if (localName.equals("date")) {
+ requestDate = currentValue;
+ } else if (localName.equals("time")) {
+ requestTime = currentValue;
+ }
+ isParsingTag = false;
+ currentValue = null;
+ }
+
+ @Override
+ public void endDocument() {
+ long date = parseDate(REQUEST_DATE_FORMAT, requestDate, requestTime);
+ if (date > 0) {
+ schedule.setDate(date);
+ }
+ }
+}