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..041a410 100644 --- a/src/com/dougkeen/bart/DepartureArrayAdapter.java +++ b/src/com/dougkeen/bart/DepartureArrayAdapter.java @@ -8,12 +8,15 @@ 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 { @@ -46,6 +49,8 @@ public class DepartureArrayAdapter extends ArrayAdapter { super(context, textViewResourceId); } + private String currentViewSwitcherText; + @Override public View getView(int position, View convertView, ViewGroup parent) { View view; @@ -59,8 +64,27 @@ 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); + + if (System.currentTimeMillis() % 6000 > 3000) { + String trainLengthText = departure.getTrainLengthText(); + if (currentViewSwitcherText == null + || !currentViewSwitcherText.equals(trainLengthText)) { + textSwitcher.setText(trainLengthText); + currentViewSwitcherText = trainLengthText; + } + } else { + String arrivalText = "Est. arrival " + + departure.getEstimatedArrivalTimeText(getContext()); + if (currentViewSwitcherText == null + || !currentViewSwitcherText.equals(arrivalText)) { + textSwitcher.setText(arrivalText); + currentViewSwitcherText = arrivalText; + } + } ImageView colorBar = (ImageView) view .findViewById(R.id.destinationColorBar); ((GradientDrawable) colorBar.getDrawable()).setColor(Color @@ -87,4 +111,19 @@ 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.slide_in_left)); + textSwitcher.setOutAnimation(AnimationUtils.loadAnimation( + getContext(), android.R.anim.slide_out_right)); + } + } } 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..5f8c000 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,15 +331,99 @@ 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"); } } 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 86% rename from src/com/dougkeen/bart/data/Departure.java rename to src/com/dougkeen/bart/model/Departure.java index fd1a4eb..40564ce 100644 --- a/src/com/dougkeen/bart/data/Departure.java +++ b/src/com/dougkeen/bart/model/Departure.java @@ -1,10 +1,11 @@ -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; @@ -45,6 +46,8 @@ public class Departure implements Parcelable, Comparable { private long minEstimate; private long maxEstimate; + private int estimatedTripTime; + public Station getDestination() { return destination; } @@ -149,6 +152,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 +177,24 @@ 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() { + return getMeanEstimate() + getEstimatedTripTime(); + } + + public String getEstimatedArrivalTimeText(Context context) { + if (getEstimatedTripTime() > 0) { + return DateFormat.getTimeFormat(context).format( + new Date(getEstimatedArrivalTime())); + } else { + return ""; + } } public boolean hasDeparted() { diff --git a/src/com/dougkeen/bart/Line.java b/src/com/dougkeen/bart/model/Line.java similarity index 96% rename from src/com/dougkeen/bart/Line.java rename to src/com/dougkeen/bart/model/Line.java index 5fd79bb..035aef0 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; 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 52% rename from src/com/dougkeen/bart/Station.java rename to src/com/dougkeen/bart/model/Station.java index 55a65d0..e614b19 100644 --- a/src/com/dougkeen/bart/Station.java +++ b/src/com/dougkeen/bart/model/Station.java @@ -1,54 +1,48 @@ -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), - _19TH("19th", "19th St./Oakland", false, false, "bayf"), - _24TH("24th", "24th St. Mission", false, false), - ASHB("ashb", "Ashby", false, false, "mcar"), - BALB("balb", "Balboa Park", false, false), - BAYF("bayf", "Bay Fair", true, false, "mcar"), - CAST("cast", "Castro Valley", false, false, "bayf"), - CIVC("civc", "Civic Center", false, false), - COLS("cols", "Coliseum/Oakland Airport", true, false, "mcar"), - COLM("colm", "Colma", false, false, "balb", "balb"), - CONC("conc", "Concord", false, false, "mcar"), - DALY("daly", "Daly City", false, false), - DBRK("dbrk", "Downtown Berkeley", false, false, "mcar"), - DUBL("dubl", "Dublin/Pleasanton", false, true, "bayf"), - DELN("deln", "El Cerrito del Norte", false, false, "mcar"), - PLZA("plza", "El Cerrito Plaza", false, false, "mcar"), - EMBR("embr", "Embarcadero", false, false), - FRMT("frmt", "Fremont", true, false, "bayf"), - FTVL("ftvl", "Fruitvale", true, false, "mcar"), - GLEN("glen", "Glen Park", false, false), - HAYW("hayw", "Hayward", true, false, "bayf"), - LAFY("lafy", "Lafayette", false, false, "mcar"), - LAKE("lake", "Lake Merritt", true, false, "mcar"), - MCAR("mcar", "MacArthur", false, false, "bayf"), - MLBR("mlbr", "Millbrae", false, true, "balb", "balb"), - MONT("mont", "Montgomery St.", false, false), - NBRK("nbrk", "North Berkeley", false, false, "mcar"), - NCON("ncon", "North Concord/Martinez", false, false, "mcar"), - ORIN("orin", "Orinda", false, false, "mcar"), - PITT("pitt", "Pittsburg/Bay Point", false, true, "mcar"), - PHIL("phil", "Pleasant Hill", false, false, "mcar"), - POWL("powl", "Powell St.", false, false), - RICH("rich", "Richmond", false, true, "mcar"), - 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"), - SHAY("shay", "South Hayward", true, false, "bayf"), - SSAN("ssan", "South San Francisco", false, false, "balb", "balb"), - UCTY("ucty", "Union City", true, false, "bayf"), - WCRK("wcrk", "Walnut Creek", false, false, "mcar"), - WDUB("wdub", "West Dublin/Pleasanton", false, false, "bayf"), - WOAK("woak", "West Oakland", false, false), - SPCL("spcl", "Special", false, false); + _12TH("12th", "12th St./Oakland City Center", false, false, "bayf"), _16TH( + "16th", "16th St. Mission", false, false), _19TH("19th", + "19th St./Oakland", false, false, "bayf"), _24TH("24th", + "24th St. Mission", false, false), ASHB("ashb", "Ashby", false, + false, "mcar"), BALB("balb", "Balboa Park", false, false), BAYF( + "bayf", "Bay Fair", true, false, "mcar"), CAST("cast", + "Castro Valley", false, false, "bayf"), CIVC("civc", + "Civic Center", false, false), COLS("cols", + "Coliseum/Oakland Airport", true, false, "mcar"), COLM("colm", + "Colma", false, false, "balb", "balb"), CONC("conc", "Concord", + false, false, "mcar"), DALY("daly", "Daly City", false, false), DBRK( + "dbrk", "Downtown Berkeley", false, false, "mcar"), DUBL("dubl", + "Dublin/Pleasanton", false, true, "bayf"), DELN("deln", + "El Cerrito del Norte", false, false, "mcar"), PLZA("plza", + "El Cerrito Plaza", false, false, "mcar"), EMBR("embr", + "Embarcadero", false, false), FRMT("frmt", "Fremont", true, false, + "bayf"), FTVL("ftvl", "Fruitvale", true, false, "mcar"), GLEN( + "glen", "Glen Park", false, false), HAYW("hayw", "Hayward", true, + false, "bayf"), LAFY("lafy", "Lafayette", false, false, "mcar"), LAKE( + "lake", "Lake Merritt", true, false, "mcar"), MCAR("mcar", + "MacArthur", false, false, "bayf"), MLBR("mlbr", "Millbrae", false, + true, "balb", "balb"), MONT("mont", "Montgomery St.", false, false), NBRK( + "nbrk", "North Berkeley", false, false, "mcar"), NCON("ncon", + "North Concord/Martinez", false, false, "mcar"), ORIN("orin", + "Orinda", false, false, "mcar"), PITT("pitt", + "Pittsburg/Bay Point", false, true, "mcar"), PHIL("phil", + "Pleasant Hill", false, false, "mcar"), POWL("powl", "Powell St.", + false, false), RICH("rich", "Richmond", false, true, "mcar"), 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"), SHAY("shay", "South Hayward", true, + false, "bayf"), SSAN("ssan", "South San Francisco", false, false, + "balb", "balb"), UCTY("ucty", "Union City", true, false, "bayf"), WCRK( + "wcrk", "Walnut Creek", false, false, "mcar"), WDUB("wdub", + "West Dublin/Pleasanton", false, false, "bayf"), WOAK("woak", + "West Oakland", false, false), SPCL("spcl", "Special", false, false); public final String abbreviation; public final String name; @@ -57,7 +51,8 @@ public enum Station { protected final String outboundTransferStation; public final boolean endOfLine; - private Station(String abbreviation, String name, boolean invertDirection, boolean endOfLine) { + private Station(String abbreviation, String name, boolean invertDirection, + boolean endOfLine) { this.abbreviation = abbreviation; this.name = name; this.invertDirection = invertDirection; @@ -66,8 +61,8 @@ public enum Station { this.endOfLine = endOfLine; } - private Station(String abbreviation, String name, boolean invertDirection, boolean endOfLine, - String transferStation) { + private Station(String abbreviation, String name, boolean invertDirection, + boolean endOfLine, String transferStation) { this.abbreviation = abbreviation; this.name = name; this.invertDirection = invertDirection; @@ -76,8 +71,9 @@ public enum Station { this.endOfLine = endOfLine; } - 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 = abbreviation; this.name = name; this.invertDirection = invertDirection; @@ -87,12 +83,17 @@ public enum Station { } 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 +156,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 89% rename from src/com/dougkeen/bart/EtdContentHandler.java rename to src/com/dougkeen/bart/networktasks/EtdContentHandler.java index 87dadba..8bfd37b 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, 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); + } + } +}