Merge with estarrival

This commit is contained in:
dkeen@dkeen-laptop 2012-04-13 15:10:49 -07:00
commit 5db3a5593b
27 changed files with 868 additions and 112 deletions

View File

@ -5,6 +5,7 @@
<classpathentry kind="con" path="com.android.ide.eclipse.adt.ANDROID_FRAMEWORK"/> <classpathentry kind="con" path="com.android.ide.eclipse.adt.ANDROID_FRAMEWORK"/>
<classpathentry kind="lib" path="libs/commons-io-2.0.1.jar"/> <classpathentry kind="lib" path="libs/commons-io-2.0.1.jar"/>
<classpathentry kind="lib" path="libs/commons-lang3-3.1.jar"/> <classpathentry kind="lib" path="libs/commons-lang3-3.1.jar"/>
<classpathentry kind="con" path="com.android.ide.eclipse.adt.LIBRARIES"/> <classpathentry kind="lib" path="libs/android-support-v4.jar"/>
<classpathentry exported="true" kind="con" path="com.android.ide.eclipse.adt.LIBRARIES"/>
<classpathentry kind="output" path="bin/classes"/> <classpathentry kind="output" path="bin/classes"/>
</classpath> </classpath>

BIN
libs/android-support-v4.jar Normal file

Binary file not shown.

View File

@ -43,9 +43,10 @@
android:layout_below="@id/topRow" android:layout_below="@id/topRow"
android:src="@drawable/xfer" /> android:src="@drawable/xfer" />
<TextView <TextSwitcher
android:id="@+id/trainLengthText" android:id="@+id/trainLengthText"
style="@style/DepartureTrainLengthText" android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@id/topRow" android:layout_below="@id/topRow"
android:layout_toRightOf="@id/destinationColorBar" /> android:layout_toRightOf="@id/destinationColorBar" />

View File

@ -0,0 +1,3 @@
<?xml version="1.0" encoding="utf-8"?>
<TextView xmlns:android="http://schemas.android.com/apk/res/android"
style="@style/DepartureTrainLengthText" />

View File

@ -1,6 +1,8 @@
package com.dougkeen.bart; package com.dougkeen.bart;
import com.dougkeen.bart.data.RoutesColumns; import com.dougkeen.bart.data.RoutesColumns;
import com.dougkeen.bart.model.Constants;
import com.dougkeen.bart.model.Station;
import android.app.Activity; import android.app.Activity;
import android.content.ContentValues; import android.content.ContentValues;

View File

@ -2,21 +2,28 @@ package com.dougkeen.bart;
import java.util.List; import java.util.List;
import org.apache.commons.lang3.StringUtils;
import android.content.Context; import android.content.Context;
import android.graphics.Color; import android.graphics.Color;
import android.graphics.drawable.GradientDrawable; import android.graphics.drawable.GradientDrawable;
import android.view.LayoutInflater; import android.view.LayoutInflater;
import android.view.View; import android.view.View;
import android.view.ViewGroup; import android.view.ViewGroup;
import android.view.animation.AnimationUtils;
import android.widget.ArrayAdapter; import android.widget.ArrayAdapter;
import android.widget.ImageView; import android.widget.ImageView;
import android.widget.RelativeLayout; import android.widget.RelativeLayout;
import android.widget.TextSwitcher;
import android.widget.TextView; 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<Departure> { public class DepartureArrayAdapter extends ArrayAdapter<Departure> {
public static int refreshCounter = 0;
public DepartureArrayAdapter(Context context, int textViewResourceId, public DepartureArrayAdapter(Context context, int textViewResourceId,
Departure[] objects) { Departure[] objects) {
super(context, textViewResourceId, objects); super(context, textViewResourceId, objects);
@ -59,8 +66,30 @@ public class DepartureArrayAdapter extends ArrayAdapter<Departure> {
Departure departure = getItem(position); Departure departure = getItem(position);
((TextView) view.findViewById(R.id.destinationText)).setText(departure ((TextView) view.findViewById(R.id.destinationText)).setText(departure
.getDestination().toString()); .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 ImageView colorBar = (ImageView) view
.findViewById(R.id.destinationColorBar); .findViewById(R.id.destinationColorBar);
((GradientDrawable) colorBar.getDrawable()).setColor(Color ((GradientDrawable) colorBar.getDrawable()).setColor(Color
@ -87,4 +116,17 @@ public class DepartureArrayAdapter extends ArrayAdapter<Departure> {
return view; 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));
}
}
} }

View File

@ -29,6 +29,9 @@ import android.widget.TextView;
import com.dougkeen.bart.data.CursorUtils; import com.dougkeen.bart.data.CursorUtils;
import com.dougkeen.bart.data.RoutesColumns; 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 { public class RoutesListActivity extends ListActivity {
private static final TimeZone PACIFIC_TIME = TimeZone private static final TimeZone PACIFIC_TIME = TimeZone
@ -51,7 +54,9 @@ public class RoutesListActivity extends ListActivity {
mQuery = managedQuery(Constants.FAVORITE_CONTENT_URI, new String[] { mQuery = managedQuery(Constants.FAVORITE_CONTENT_URI, new String[] {
RoutesColumns._ID.string, RoutesColumns.FROM_STATION.string, RoutesColumns._ID.string, RoutesColumns.FROM_STATION.string,
RoutesColumns.TO_STATION.string, RoutesColumns.FARE.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); RoutesColumns._ID.string);
refreshFares(); refreshFares();

View File

@ -3,6 +3,7 @@ package com.dougkeen.bart;
import java.util.List; import java.util.List;
import android.app.ListActivity; import android.app.ListActivity;
import android.content.ContentValues;
import android.content.Context; import android.content.Context;
import android.content.Intent; import android.content.Intent;
import android.database.Cursor; import android.database.Cursor;
@ -22,10 +23,16 @@ import android.widget.ArrayAdapter;
import android.widget.TextView; import android.widget.TextView;
import android.widget.Toast; 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.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 { public class ViewDeparturesActivity extends ListActivity {
@ -35,12 +42,17 @@ public class ViewDeparturesActivity extends ListActivity {
private Station mOrigin; private Station mOrigin;
private Station mDestination; private Station mDestination;
private int mAverageTripLength;
private int mAverageTripSampleCount;
private ArrayAdapter<Departure> mDeparturesAdapter; private ArrayAdapter<Departure> mDeparturesAdapter;
private ScheduleInformation mLatestScheduleInfo;
private TextView mListTitleView; private TextView mListTitleView;
private AsyncTask<Params, Integer, RealTimeDepartures> mGetDeparturesTask; private AsyncTask<StationPair, Integer, RealTimeDepartures> mGetDeparturesTask;
private AsyncTask<StationPair, Integer, ScheduleInformation> mGetScheduleInformationTask;
private boolean mIsAutoUpdating = false; private boolean mIsAutoUpdating = false;
@ -52,7 +64,8 @@ public class ViewDeparturesActivity extends ListActivity {
private PowerManager.WakeLock mWakeLock; private PowerManager.WakeLock mWakeLock;
private boolean mDataFetchIsPending; private boolean mDepartureFetchIsPending;
private boolean mScheduleFetchIsPending;
@Override @Override
protected void onCreate(Bundle savedInstanceState) { protected void onCreate(Bundle savedInstanceState) {
@ -69,13 +82,18 @@ public class ViewDeparturesActivity extends ListActivity {
Cursor cursor = managedQuery(mUri, new String[] { Cursor cursor = managedQuery(mUri, new String[] {
RoutesColumns.FROM_STATION.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()) { if (!cursor.moveToFirst()) {
throw new IllegalStateException("URI not found: " + mUri.toString()); throw new IllegalStateException("URI not found: " + mUri.toString());
} }
mOrigin = Station.getByAbbreviation(cursor.getString(0)); mOrigin = Station.getByAbbreviation(cursor.getString(0));
mDestination = Station.getByAbbreviation(cursor.getString(1)); mDestination = Station.getByAbbreviation(cursor.getString(1));
mAverageTripLength = cursor.getInt(2);
mAverageTripSampleCount = cursor.getInt(3);
String header = "Departures:\n" + mOrigin.name + " to " String header = "Departures:\n" + mOrigin.name + " to "
+ mDestination.name; + mDestination.name;
@ -114,7 +132,11 @@ public class ViewDeparturesActivity extends ListActivity {
private void cancelDataFetch() { private void cancelDataFetch() {
if (mGetDeparturesTask != null) { if (mGetDeparturesTask != null) {
mGetDeparturesTask.cancel(true); 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) { public void onWindowFocusChanged(boolean hasFocus) {
super.onWindowFocusChanged(hasFocus); super.onWindowFocusChanged(hasFocus);
if (hasFocus) { if (hasFocus) {
if (!mDataFetchIsPending) { if (!mDepartureFetchIsPending) {
fetchLatestDepartures(); fetchLatestDepartures();
} }
PowerManager powerManager = (PowerManager) getSystemService(Context.POWER_SERVICE); PowerManager powerManager = (PowerManager) getSystemService(Context.POWER_SERVICE);
@ -162,7 +184,7 @@ public class ViewDeparturesActivity extends ListActivity {
mGetDeparturesTask = new GetRealTimeDeparturesTask() { mGetDeparturesTask = new GetRealTimeDeparturesTask() {
@Override @Override
public void onResult(RealTimeDepartures result) { public void onResult(RealTimeDepartures result) {
mDataFetchIsPending = false; mDepartureFetchIsPending = false;
Log.i(Constants.TAG, "Processing data from server"); Log.i(Constants.TAG, "Processing data from server");
processLatestDepartures(result); processLatestDepartures(result);
Log.i(Constants.TAG, "Done processing data from server"); Log.i(Constants.TAG, "Done processing data from server");
@ -170,19 +192,55 @@ public class ViewDeparturesActivity extends ListActivity {
@Override @Override
public void onError(Exception e) { public void onError(Exception e) {
mDataFetchIsPending = false; mDepartureFetchIsPending = false;
Log.w(Constants.TAG, e.getMessage(), e); Log.w(Constants.TAG, e.getMessage(), e);
Toast.makeText(ViewDeparturesActivity.this, Toast.makeText(ViewDeparturesActivity.this,
R.string.could_not_connect, Toast.LENGTH_LONG).show(); R.string.could_not_connect, Toast.LENGTH_LONG).show();
((TextView) findViewById(android.R.id.empty)) ((TextView) findViewById(android.R.id.empty))
.setText(R.string.could_not_connect); .setText(R.string.could_not_connect);
// Try again in 60s // Try again in 60s
scheduleDataFetch(60000); scheduleDepartureFetch(60000);
} }
}; };
Log.i(Constants.TAG, "Fetching data from server"); Log.i(Constants.TAG, "Fetching data from server");
mGetDeparturesTask.execute(new GetRealTimeDeparturesTask.Params( mGetDeparturesTask.execute(new StationPair(mOrigin, mDestination));
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) { protected void processLatestDepartures(RealTimeDepartures result) {
@ -238,11 +296,12 @@ public class ViewDeparturesActivity extends ListActivity {
needsBetterAccuracy = true; needsBetterAccuracy = true;
} }
mDeparturesAdapter.notifyDataSetChanged(); mDeparturesAdapter.notifyDataSetChanged();
requestScheduleIfNecessary();
if (hasWindowFocus() && firstDeparture != null) { if (hasWindowFocus() && firstDeparture != null) {
if (needsBetterAccuracy || firstDeparture.hasDeparted()) { if (needsBetterAccuracy || firstDeparture.hasDeparted()) {
// Get more data in 20s // Get more data in 20s
scheduleDataFetch(20000); scheduleDepartureFetch(20000);
} else { } else {
// Get more 90 seconds before next train arrives, right when // Get more 90 seconds before next train arrives, right when
// next train arrives, or 3 minutes, whichever is sooner // next train arrives, or 3 minutes, whichever is sooner
@ -262,7 +321,7 @@ public class ViewDeparturesActivity extends ListActivity {
interval = 20000; interval = 20000;
} }
scheduleDataFetch(interval); scheduleDepartureFetch(interval);
} }
if (!mIsAutoUpdating) { if (!mIsAutoUpdating) {
mIsAutoUpdating = true; mIsAutoUpdating = true;
@ -272,21 +331,106 @@ public class ViewDeparturesActivity extends ListActivity {
} }
} }
private void scheduleDataFetch(int millisUntilExecute) { private void requestScheduleIfNecessary() {
if (!mDataFetchIsPending) { 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() { mListTitleView.postDelayed(new Runnable() {
public void run() { public void run() {
fetchLatestDepartures(); fetchLatestDepartures();
} }
}, millisUntilExecute); }, millisUntilExecute);
mDataFetchIsPending = true; mDepartureFetchIsPending = true;
Log.i(Constants.TAG, "Scheduled another data fetch in " 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"); + millisUntilExecute / 1000 + "s");
} }
} }
private void runAutoUpdate() { private void runAutoUpdate() {
if (mIsAutoUpdating && mDeparturesAdapter != null) { if (mIsAutoUpdating && mDeparturesAdapter != null) {
DepartureArrayAdapter.refreshCounter++;
mDeparturesAdapter.notifyDataSetChanged(); mDeparturesAdapter.notifyDataSetChanged();
} }
if (hasWindowFocus()) { if (hasWindowFocus()) {

View File

@ -2,7 +2,7 @@ package com.dougkeen.bart.data;
import java.util.HashMap; import java.util.HashMap;
import com.dougkeen.bart.Constants; import com.dougkeen.bart.model.Constants;
import android.content.ContentProvider; import android.content.ContentProvider;
import android.content.ContentUris; import android.content.ContentUris;
@ -45,6 +45,10 @@ public class BartContentProvider extends ContentProvider {
RoutesColumns.FARE.string); RoutesColumns.FARE.string);
sFavoritesProjectionMap.put(RoutesColumns.FARE_LAST_UPDATED.string, sFavoritesProjectionMap.put(RoutesColumns.FARE_LAST_UPDATED.string,
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; private DatabaseHelper mDatabaseHelper;

View File

@ -15,7 +15,7 @@ import android.util.Log;
public class DatabaseHelper extends SQLiteOpenHelper { public class DatabaseHelper extends SQLiteOpenHelper {
private static final String DATABASE_NAME = "bart.dougkeen.db"; 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"; public static final String FAVORITES_TABLE_NAME = "Favorites";
@ -34,7 +34,9 @@ public class DatabaseHelper extends SQLiteOpenHelper {
+ RoutesColumns.FROM_STATION.getColumnDef() + ", " + RoutesColumns.FROM_STATION.getColumnDef() + ", "
+ RoutesColumns.TO_STATION.getColumnDef() + ", " + RoutesColumns.TO_STATION.getColumnDef() + ", "
+ RoutesColumns.FARE.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 @Override

View File

@ -5,7 +5,9 @@ public enum RoutesColumns {
FROM_STATION("FROM_STATION", "TEXT", false), FROM_STATION("FROM_STATION", "TEXT", false),
TO_STATION("TO_STATION", "TEXT", false), TO_STATION("TO_STATION", "TEXT", false),
FARE("FARE", "TEXT", true), 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 // This class cannot be instantiated

View File

@ -1,4 +1,4 @@
package com.dougkeen.bart; package com.dougkeen.bart.model;
import android.net.Uri; import android.net.Uri;
@ -10,6 +10,6 @@ public class Constants {
+ AUTHORITY + "/favorites"); + AUTHORITY + "/favorites");
public static final String MAP_URL = "http://m.bart.gov/images/global/system-map29.gif"; 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"; public static final String API_KEY = "5LD9-IAYI-TRAT-MHHW";
} }

View File

@ -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.Parcel;
import android.os.Parcelable; import android.os.Parcelable;
import android.text.format.DateFormat;
import com.dougkeen.bart.Line;
import com.dougkeen.bart.Station;
public class Departure implements Parcelable, Comparable<Departure> { public class Departure implements Parcelable, Comparable<Departure> {
private static final int ESTIMATE_EQUALS_TOLERANCE_MILLIS = 59999; 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; private static final int MINIMUM_MERGE_OVERLAP_MILLIS = 10000;
public Departure() { public Departure() {
@ -31,6 +33,7 @@ public class Departure implements Parcelable, Comparable<Departure> {
readFromParcel(in); readFromParcel(in);
} }
private Station origin;
private Station destination; private Station destination;
private Line line; private Line line;
private String destinationColor; private String destinationColor;
@ -45,6 +48,20 @@ public class Departure implements Parcelable, Comparable<Departure> {
private long minEstimate; private long minEstimate;
private long maxEstimate; 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() { public Station getDestination() {
return destination; return destination;
} }
@ -131,6 +148,9 @@ public class Departure implements Parcelable, Comparable<Departure> {
public void setMinutes(int minutes) { public void setMinutes(int minutes) {
this.minutes = minutes; this.minutes = minutes;
if (minutes == 0) {
beganAsDeparted = true;
}
} }
public long getMinEstimate() { public long getMinEstimate() {
@ -149,6 +169,18 @@ public class Departure implements Parcelable, Comparable<Departure> {
this.maxEstimate = maxEstimate; 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() { public int getUncertaintySeconds() {
return (int) (maxEstimate - minEstimate + 1000) / 2000; return (int) (maxEstimate - minEstimate + 1000) / 2000;
} }
@ -162,8 +194,27 @@ public class Departure implements Parcelable, Comparable<Departure> {
} }
public int getMeanSecondsLeft() { public int getMeanSecondsLeft() {
return (int) (((getMinEstimate() + getMaxEstimate()) / 2 - System return (int) ((getMeanEstimate() - System.currentTimeMillis()) / 1000);
.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() { public boolean hasDeparted() {
@ -177,6 +228,13 @@ public class Departure implements Parcelable, Comparable<Departure> {
} }
public void mergeEstimate(Departure departure) { 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 if ((getMaxEstimate() - departure.getMinEstimate()) < MINIMUM_MERGE_OVERLAP_MILLIS
|| departure.getMaxEstimate() - getMinEstimate() < MINIMUM_MERGE_OVERLAP_MILLIS) { || departure.getMaxEstimate() - getMinEstimate() < MINIMUM_MERGE_OVERLAP_MILLIS) {
// The estimate must have changed... just use the latest incoming // The estimate must have changed... just use the latest incoming
@ -250,7 +308,7 @@ public class Departure implements Parcelable, Comparable<Departure> {
return false; return false;
if (line != other.line) if (line != other.line)
return false; return false;
if (Math.abs(maxEstimate - other.maxEstimate) > ESTIMATE_EQUALS_TOLERANCE_MILLIS) if (Math.abs(maxEstimate - other.maxEstimate) > getEqualsTolerance())
return false; return false;
if (platform == null) { if (platform == null) {
if (other.platform != null) if (other.platform != null)
@ -267,11 +325,23 @@ public class Departure implements Parcelable, Comparable<Departure> {
return true; 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() { public String getCountdownText() {
StringBuilder builder = new StringBuilder(); StringBuilder builder = new StringBuilder();
int secondsLeft = getMeanSecondsLeft(); int secondsLeft = getMeanSecondsLeft();
if (hasDeparted()) { if (hasDeparted()) {
if (origin.longStationLinger && beganAsDeparted) {
builder.append("At station");
} else {
builder.append("Departed"); builder.append("Departed");
}
} else { } else {
builder.append(secondsLeft / 60); builder.append(secondsLeft / 60);
builder.append("m, "); builder.append("m, ");

View File

@ -1,4 +1,4 @@
package com.dougkeen.bart; package com.dougkeen.bart.model;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
@ -37,6 +37,12 @@ public enum Line {
Station.COLS, Station.SANL, Station.BAYF, Station.HAYW, Station.COLS, Station.SANL, Station.BAYF, Station.HAYW,
Station.SHAY, Station.UCTY, Station.FRMT), Station.SHAY, Station.UCTY, Station.FRMT),
YELLOW_ORANGE_SCHEDULED_TRANSFER(YELLOW, ORANGE, Station.MLBR, 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.SFIA, Station.SBRN, Station.SSAN, Station.COLM,
Station.DALY, Station.BALB, Station.GLEN, Station._24TH, Station.DALY, Station.BALB, Station.GLEN, Station._24TH,
Station._16TH, Station.CIVC, Station.POWL, Station.MONT, Station._16TH, Station.CIVC, Station.POWL, Station.MONT,

View File

@ -1,11 +1,9 @@
package com.dougkeen.bart.data; package com.dougkeen.bart.model;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collections; import java.util.Collections;
import java.util.List; import java.util.List;
import com.dougkeen.bart.Route;
import com.dougkeen.bart.Station;
public class RealTimeDepartures { public class RealTimeDepartures {
public RealTimeDepartures(Station origin, Station destination, public RealTimeDepartures(Station origin, Station destination,

View File

@ -1,4 +1,4 @@
package com.dougkeen.bart; package com.dougkeen.bart.model;
public class Route { public class Route {
private Station origin; private Station origin;

View File

@ -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<ScheduleItem> 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<ScheduleItem> getTrips() {
if (trips == null) {
trips = new ArrayList<ScheduleItem>();
}
return trips;
}
public void setTrips(List<ScheduleItem> 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;
}
}

View File

@ -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;
}
}

View File

@ -1,8 +1,10 @@
package com.dougkeen.bart; package com.dougkeen.bart.model;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import android.util.Log;
public enum Station { public enum Station {
_12TH("12th", "12th St./Oakland City Center", false, false, "bayf"), _12TH("12th", "12th St./Oakland City Center", false, false, "bayf"),
_16TH("16th", "16th St. Mission", false, false), _16TH("16th", "16th St. Mission", false, false),
@ -41,7 +43,7 @@ public enum Station {
ROCK("rock", "Rockridge", false, false, "mcar"), ROCK("rock", "Rockridge", false, false, "mcar"),
SBRN("sbrn", "San Bruno", false, false, "balb", "balb"), SBRN("sbrn", "San Bruno", false, false, "balb", "balb"),
SANL("sanl", "San Leandro", true, false, "mcar"), 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"), SHAY("shay", "South Hayward", true, false, "bayf"),
SSAN("ssan", "South San Francisco", false, false, "balb", "balb"), SSAN("ssan", "South San Francisco", false, false, "balb", "balb"),
UCTY("ucty", "Union City", true, false, "bayf"), UCTY("ucty", "Union City", true, false, "bayf"),
@ -56,37 +58,40 @@ public enum Station {
protected final String inboundTransferStation; protected final String inboundTransferStation;
protected final String outboundTransferStation; protected final String outboundTransferStation;
public final boolean endOfLine; public final boolean endOfLine;
public final boolean longStationLinger;
private Station(String abbreviation, String name, boolean invertDirection, boolean endOfLine) { private Station(String abbreviation, String name, boolean invertDirection,
this.abbreviation = abbreviation; boolean endOfLine) {
this.name = name; this(abbreviation, name, invertDirection, endOfLine, null, null, false);
this.invertDirection = invertDirection;
this.inboundTransferStation = null;
this.outboundTransferStation = null;
this.endOfLine = endOfLine;
} }
private Station(String abbreviation, String name, boolean invertDirection, boolean endOfLine, private Station(String abbreviation, String name, boolean invertDirection,
String transferStation) { boolean endOfLine, String transferStation) {
this.abbreviation = abbreviation; this(abbreviation, name, invertDirection, endOfLine, transferStation,
this.name = name; null, false);
this.invertDirection = invertDirection;
this.inboundTransferStation = transferStation;
this.outboundTransferStation = null;
this.endOfLine = endOfLine;
} }
private Station(String abbreviation, String name, boolean invertDirection, boolean endOfLine, private Station(String abbreviation, String name, boolean invertDirection,
String inboundTransferStation, String outboundTransferStation) { 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.abbreviation = abbreviation;
this.name = name; this.name = name;
this.invertDirection = invertDirection; this.invertDirection = invertDirection;
this.inboundTransferStation = inboundTransferStation; this.inboundTransferStation = inboundTransferStation;
this.outboundTransferStation = outboundTransferStation; this.outboundTransferStation = outboundTransferStation;
this.endOfLine = endOfLine; this.endOfLine = endOfLine;
this.longStationLinger = longStationLinger;
} }
public static Station getByAbbreviation(String abbr) { public static Station getByAbbreviation(String abbr) {
try {
if (abbr == null) { if (abbr == null) {
return null; return null;
} else if (Character.isDigit(abbr.charAt(0))) { } else if (Character.isDigit(abbr.charAt(0))) {
@ -94,6 +99,10 @@ public enum Station {
} else { } else {
return Station.valueOf(abbr.toUpperCase()); return Station.valueOf(abbr.toUpperCase());
} }
} catch (IllegalArgumentException e) {
Log.e(Constants.TAG, "Could not find station for '" + abbr + "'", e);
return null;
}
} }
public Station getInboundTransferStation() { public Station getInboundTransferStation() {
@ -155,8 +164,7 @@ public enum Station {
} }
if (isNorth == null) { if (isNorth == null) {
if (outboundTransferStation != null) { if (outboundTransferStation != null) {
returnList returnList.addAll(getOutboundTransferStation()
.addAll(getOutboundTransferStation()
.getRoutesForDestination(dest, .getRoutesForDestination(dest,
getOutboundTransferStation())); getOutboundTransferStation()));
} else if (dest.getInboundTransferStation() != null) { } else if (dest.getInboundTransferStation() != null) {

View File

@ -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;
}
}

View File

@ -1,4 +1,4 @@
package com.dougkeen.bart; package com.dougkeen.bart.networktasks;
import java.util.Arrays; import java.util.Arrays;
import java.util.Date; import java.util.Date;
@ -11,8 +11,12 @@ import org.xml.sax.helpers.DefaultHandler;
import android.util.Log; import android.util.Log;
import com.dougkeen.bart.data.Departure; import com.dougkeen.bart.model.Constants;
import com.dougkeen.bart.data.RealTimeDepartures; 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 class EtdContentHandler extends DefaultHandler {
public EtdContentHandler(Station origin, Station destination, public EtdContentHandler(Station origin, Station destination,
@ -55,6 +59,7 @@ public class EtdContentHandler extends DefaultHandler {
currentDeparture = new Departure(); currentDeparture = new Departure();
currentDeparture.setDestination(Station currentDeparture.setDestination(Station
.getByAbbreviation(currentDestination)); .getByAbbreviation(currentDestination));
currentDeparture.setOrigin(realTimeDepartures.getOrigin());
} }
} }

View File

@ -1,4 +1,4 @@
package com.dougkeen.bart; package com.dougkeen.bart.networktasks;
import org.xml.sax.Attributes; import org.xml.sax.Attributes;
import org.xml.sax.SAXException; import org.xml.sax.SAXException;

View File

@ -1,4 +1,4 @@
package com.dougkeen.bart; package com.dougkeen.bart.networktasks;
import java.io.IOException; import java.io.IOException;
import java.io.StringWriter; import java.io.StringWriter;
@ -17,11 +17,13 @@ import android.os.AsyncTask;
import android.util.Log; import android.util.Log;
import android.util.Xml; 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 public abstract class GetRealTimeDeparturesTask extends
extends AsyncTask<StationPair, Integer, RealTimeDepartures> {
AsyncTask<GetRealTimeDeparturesTask.Params, Integer, RealTimeDepartures> {
private final static String ETD_URL = "http://api.bart.gov/api/etd.aspx?cmd=etd&key=" 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"; + Constants.API_KEY + "&orig=%1$s&dir=%2$s";
@ -34,11 +36,12 @@ public abstract class GetRealTimeDeparturesTask
private List<Route> mRoutes; private List<Route> mRoutes;
@Override @Override
protected RealTimeDepartures doInBackground(Params... paramsArray) { protected RealTimeDepartures doInBackground(StationPair... paramsArray) {
// Always expect one param // 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()) { if (!isCancelled()) {
return getDeparturesFromNetwork(params, 0); return getDeparturesFromNetwork(params, 0);
@ -47,23 +50,23 @@ public abstract class GetRealTimeDeparturesTask
} }
} }
private RealTimeDepartures getDeparturesFromNetwork(Params params, private RealTimeDepartures getDeparturesFromNetwork(StationPair params,
int attemptNumber) { int attemptNumber) {
String xml = null; String xml = null;
try { try {
String url; String url;
if (params.origin.endOfLine) { if (params.getOrigin().endOfLine) {
url = String.format(ETD_URL_NO_DIRECTION, url = String.format(ETD_URL_NO_DIRECTION,
params.origin.abbreviation); params.getOrigin().abbreviation);
} else { } else {
url = String.format(ETD_URL, params.origin.abbreviation, url = String.format(ETD_URL, params.getOrigin().abbreviation,
mRoutes.get(0).getDirection()); mRoutes.get(0).getDirection());
} }
HttpUriRequest request = new HttpGet(url); HttpUriRequest request = new HttpGet(url);
EtdContentHandler handler = new EtdContentHandler(params.origin, EtdContentHandler handler = new EtdContentHandler(
params.destination, mRoutes); params.getOrigin(), params.getDestination(), mRoutes);
if (isCancelled()) { if (isCancelled()) {
return null; 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 @Override
protected void onPostExecute(RealTimeDepartures result) { protected void onPostExecute(RealTimeDepartures result) {
if (result != null) { if (result != null) {

View File

@ -1,4 +1,4 @@
package com.dougkeen.bart; package com.dougkeen.bart.networktasks;
import java.io.IOException; import java.io.IOException;
import java.io.StringWriter; import java.io.StringWriter;
@ -12,6 +12,9 @@ import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpUriRequest; import org.apache.http.client.methods.HttpUriRequest;
import org.xml.sax.SAXException; import org.xml.sax.SAXException;
import com.dougkeen.bart.model.Constants;
import com.dougkeen.bart.model.Station;
import android.os.AsyncTask; import android.os.AsyncTask;
import android.util.Log; import android.util.Log;
import android.util.Xml; import android.util.Xml;

View File

@ -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<StationPair, Integer, ScheduleInformation> {
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);
}

View File

@ -1,4 +1,4 @@
package com.dougkeen.bart; package com.dougkeen.bart.networktasks;
import java.io.IOException; import java.io.IOException;

View File

@ -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<String> 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);
}
}
}