Merge with estarrival
This commit is contained in:
commit
5db3a5593b
@ -5,6 +5,7 @@
|
||||
<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-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"/>
|
||||
</classpath>
|
||||
|
BIN
libs/android-support-v4.jar
Normal file
BIN
libs/android-support-v4.jar
Normal file
Binary file not shown.
@ -43,9 +43,10 @@
|
||||
android:layout_below="@id/topRow"
|
||||
android:src="@drawable/xfer" />
|
||||
|
||||
<TextView
|
||||
<TextSwitcher
|
||||
android:id="@+id/trainLengthText"
|
||||
style="@style/DepartureTrainLengthText"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_below="@id/topRow"
|
||||
android:layout_toRightOf="@id/destinationColorBar" />
|
||||
|
||||
|
3
res/layout/train_length_arrival_textview.xml
Normal file
3
res/layout/train_length_arrival_textview.xml
Normal 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" />
|
@ -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;
|
||||
|
@ -2,21 +2,28 @@ package com.dougkeen.bart;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
|
||||
import android.content.Context;
|
||||
import android.graphics.Color;
|
||||
import android.graphics.drawable.GradientDrawable;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.view.animation.AnimationUtils;
|
||||
import android.widget.ArrayAdapter;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.RelativeLayout;
|
||||
import android.widget.TextSwitcher;
|
||||
import android.widget.TextView;
|
||||
import android.widget.ViewSwitcher.ViewFactory;
|
||||
|
||||
import com.dougkeen.bart.data.Departure;
|
||||
import com.dougkeen.bart.model.Departure;
|
||||
|
||||
public class DepartureArrayAdapter extends ArrayAdapter<Departure> {
|
||||
|
||||
public static int refreshCounter = 0;
|
||||
|
||||
public DepartureArrayAdapter(Context context, int textViewResourceId,
|
||||
Departure[] objects) {
|
||||
super(context, textViewResourceId, objects);
|
||||
@ -59,8 +66,30 @@ public class DepartureArrayAdapter extends ArrayAdapter<Departure> {
|
||||
Departure departure = getItem(position);
|
||||
((TextView) view.findViewById(R.id.destinationText)).setText(departure
|
||||
.getDestination().toString());
|
||||
((TextView) view.findViewById(R.id.trainLengthText)).setText(departure
|
||||
.getTrainLengthText());
|
||||
|
||||
TextSwitcher textSwitcher = (TextSwitcher) view
|
||||
.findViewById(R.id.trainLengthText);
|
||||
initTextSwitcher(textSwitcher);
|
||||
|
||||
final String estimatedArrivalTimeText = departure
|
||||
.getEstimatedArrivalTimeText(getContext());
|
||||
String arrivalText = "Est. arrival " + estimatedArrivalTimeText;
|
||||
if (StringUtils.isBlank(estimatedArrivalTimeText)) {
|
||||
textSwitcher.setCurrentText(departure.getTrainLengthText());
|
||||
} else if (refreshCounter % 6 < 3) {
|
||||
String trainLengthText = departure.getTrainLengthText();
|
||||
if (refreshCounter % 6 == 0) {
|
||||
textSwitcher.setText(trainLengthText);
|
||||
} else {
|
||||
textSwitcher.setCurrentText(trainLengthText);
|
||||
}
|
||||
} else {
|
||||
if (refreshCounter % 6 == 3) {
|
||||
textSwitcher.setText(arrivalText);
|
||||
} else {
|
||||
textSwitcher.setCurrentText(arrivalText);
|
||||
}
|
||||
}
|
||||
ImageView colorBar = (ImageView) view
|
||||
.findViewById(R.id.destinationColorBar);
|
||||
((GradientDrawable) colorBar.getDrawable()).setColor(Color
|
||||
@ -87,4 +116,17 @@ public class DepartureArrayAdapter extends ArrayAdapter<Departure> {
|
||||
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));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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();
|
||||
|
@ -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<Departure> mDeparturesAdapter;
|
||||
|
||||
private ScheduleInformation mLatestScheduleInfo;
|
||||
|
||||
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;
|
||||
|
||||
@ -52,7 +64,8 @@ public class ViewDeparturesActivity extends ListActivity {
|
||||
|
||||
private PowerManager.WakeLock mWakeLock;
|
||||
|
||||
private boolean mDataFetchIsPending;
|
||||
private boolean mDepartureFetchIsPending;
|
||||
private boolean mScheduleFetchIsPending;
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
@ -69,13 +82,18 @@ public class ViewDeparturesActivity extends ListActivity {
|
||||
|
||||
Cursor cursor = managedQuery(mUri, new String[] {
|
||||
RoutesColumns.FROM_STATION.string,
|
||||
RoutesColumns.TO_STATION.string }, null, null, null);
|
||||
RoutesColumns.TO_STATION.string,
|
||||
RoutesColumns.AVERAGE_TRIP_LENGTH.string,
|
||||
RoutesColumns.AVERAGE_TRIP_SAMPLE_COUNT.string }, null, null,
|
||||
null);
|
||||
|
||||
if (!cursor.moveToFirst()) {
|
||||
throw new IllegalStateException("URI not found: " + mUri.toString());
|
||||
}
|
||||
mOrigin = Station.getByAbbreviation(cursor.getString(0));
|
||||
mDestination = Station.getByAbbreviation(cursor.getString(1));
|
||||
mAverageTripLength = cursor.getInt(2);
|
||||
mAverageTripSampleCount = cursor.getInt(3);
|
||||
|
||||
String header = "Departures:\n" + mOrigin.name + " to "
|
||||
+ mDestination.name;
|
||||
@ -114,7 +132,11 @@ public class ViewDeparturesActivity extends ListActivity {
|
||||
private void cancelDataFetch() {
|
||||
if (mGetDeparturesTask != null) {
|
||||
mGetDeparturesTask.cancel(true);
|
||||
mDataFetchIsPending = false;
|
||||
mDepartureFetchIsPending = false;
|
||||
}
|
||||
if (mGetScheduleInformationTask != null) {
|
||||
mGetScheduleInformationTask.cancel(true);
|
||||
mScheduleFetchIsPending = false;
|
||||
}
|
||||
}
|
||||
|
||||
@ -132,7 +154,7 @@ public class ViewDeparturesActivity extends ListActivity {
|
||||
public void onWindowFocusChanged(boolean hasFocus) {
|
||||
super.onWindowFocusChanged(hasFocus);
|
||||
if (hasFocus) {
|
||||
if (!mDataFetchIsPending) {
|
||||
if (!mDepartureFetchIsPending) {
|
||||
fetchLatestDepartures();
|
||||
}
|
||||
PowerManager powerManager = (PowerManager) getSystemService(Context.POWER_SERVICE);
|
||||
@ -162,7 +184,7 @@ public class ViewDeparturesActivity extends ListActivity {
|
||||
mGetDeparturesTask = new GetRealTimeDeparturesTask() {
|
||||
@Override
|
||||
public void onResult(RealTimeDepartures result) {
|
||||
mDataFetchIsPending = false;
|
||||
mDepartureFetchIsPending = false;
|
||||
Log.i(Constants.TAG, "Processing data from server");
|
||||
processLatestDepartures(result);
|
||||
Log.i(Constants.TAG, "Done processing data from server");
|
||||
@ -170,19 +192,55 @@ public class ViewDeparturesActivity extends ListActivity {
|
||||
|
||||
@Override
|
||||
public void onError(Exception e) {
|
||||
mDataFetchIsPending = false;
|
||||
mDepartureFetchIsPending = false;
|
||||
Log.w(Constants.TAG, e.getMessage(), e);
|
||||
Toast.makeText(ViewDeparturesActivity.this,
|
||||
R.string.could_not_connect, Toast.LENGTH_LONG).show();
|
||||
((TextView) findViewById(android.R.id.empty))
|
||||
.setText(R.string.could_not_connect);
|
||||
// Try again in 60s
|
||||
scheduleDataFetch(60000);
|
||||
scheduleDepartureFetch(60000);
|
||||
}
|
||||
};
|
||||
Log.i(Constants.TAG, "Fetching data from server");
|
||||
mGetDeparturesTask.execute(new GetRealTimeDeparturesTask.Params(
|
||||
mOrigin, mDestination));
|
||||
mGetDeparturesTask.execute(new StationPair(mOrigin, mDestination));
|
||||
}
|
||||
|
||||
private void fetchLatestSchedule() {
|
||||
if (!hasWindowFocus())
|
||||
return;
|
||||
if (mGetScheduleInformationTask != null
|
||||
&& mGetScheduleInformationTask.getStatus().equals(
|
||||
AsyncTask.Status.RUNNING)) {
|
||||
// Don't overlap fetches
|
||||
return;
|
||||
}
|
||||
|
||||
mGetScheduleInformationTask = new GetScheduleInformationTask() {
|
||||
@Override
|
||||
public void onResult(ScheduleInformation result) {
|
||||
mScheduleFetchIsPending = false;
|
||||
Log.i(Constants.TAG, "Processing data from server");
|
||||
mLatestScheduleInfo = result;
|
||||
applyScheduleInformation();
|
||||
Log.i(Constants.TAG, "Done processing data from server");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onError(Exception e) {
|
||||
mScheduleFetchIsPending = false;
|
||||
Log.w(Constants.TAG, e.getMessage(), e);
|
||||
Toast.makeText(ViewDeparturesActivity.this,
|
||||
R.string.could_not_connect, Toast.LENGTH_LONG).show();
|
||||
((TextView) findViewById(android.R.id.empty))
|
||||
.setText(R.string.could_not_connect);
|
||||
// Try again in 60s
|
||||
scheduleScheduleInfoFetch(60000);
|
||||
}
|
||||
};
|
||||
Log.i(Constants.TAG, "Fetching data from server");
|
||||
mGetScheduleInformationTask.execute(new StationPair(mOrigin,
|
||||
mDestination));
|
||||
}
|
||||
|
||||
protected void processLatestDepartures(RealTimeDepartures result) {
|
||||
@ -238,11 +296,12 @@ public class ViewDeparturesActivity extends ListActivity {
|
||||
needsBetterAccuracy = true;
|
||||
}
|
||||
mDeparturesAdapter.notifyDataSetChanged();
|
||||
requestScheduleIfNecessary();
|
||||
|
||||
if (hasWindowFocus() && firstDeparture != null) {
|
||||
if (needsBetterAccuracy || firstDeparture.hasDeparted()) {
|
||||
// Get more data in 20s
|
||||
scheduleDataFetch(20000);
|
||||
scheduleDepartureFetch(20000);
|
||||
} else {
|
||||
// Get more 90 seconds before next train arrives, right when
|
||||
// next train arrives, or 3 minutes, whichever is sooner
|
||||
@ -262,7 +321,7 @@ public class ViewDeparturesActivity extends ListActivity {
|
||||
interval = 20000;
|
||||
}
|
||||
|
||||
scheduleDataFetch(interval);
|
||||
scheduleDepartureFetch(interval);
|
||||
}
|
||||
if (!mIsAutoUpdating) {
|
||||
mIsAutoUpdating = true;
|
||||
@ -272,21 +331,106 @@ public class ViewDeparturesActivity extends ListActivity {
|
||||
}
|
||||
}
|
||||
|
||||
private void scheduleDataFetch(int millisUntilExecute) {
|
||||
if (!mDataFetchIsPending) {
|
||||
private void requestScheduleIfNecessary() {
|
||||
if (mDeparturesAdapter.getCount() == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (mLatestScheduleInfo == null) {
|
||||
fetchLatestSchedule();
|
||||
return;
|
||||
}
|
||||
|
||||
Departure lastDeparture = mDeparturesAdapter.getItem(mDeparturesAdapter
|
||||
.getCount() - 1);
|
||||
if (mLatestScheduleInfo.getLatestDepartureTime() < lastDeparture
|
||||
.getMeanEstimate()) {
|
||||
fetchLatestSchedule();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
private void applyScheduleInformation() {
|
||||
int localAverageLength = mLatestScheduleInfo.getAverageTripLength();
|
||||
|
||||
int departuresCount = mDeparturesAdapter.getCount();
|
||||
int lastSearchIndex = 0;
|
||||
int tripCount = mLatestScheduleInfo.getTrips().size();
|
||||
boolean departureUpdated = false;
|
||||
for (int departureIndex = 0; departureIndex < departuresCount; departureIndex++) {
|
||||
Departure departure = mDeparturesAdapter.getItem(departureIndex);
|
||||
for (int i = lastSearchIndex; i < tripCount; i++) {
|
||||
ScheduleItem trip = mLatestScheduleInfo.getTrips().get(i);
|
||||
long departTimeDiff = Math.abs(trip.getDepartureTime()
|
||||
- departure.getMeanEstimate());
|
||||
if (departTimeDiff <= (60000 + departure
|
||||
.getUncertaintySeconds() * 1000)
|
||||
&& departure.getEstimatedTripTime() != trip
|
||||
.getTripLength()) {
|
||||
departure.setEstimatedTripTime(trip.getTripLength());
|
||||
lastSearchIndex = i;
|
||||
departureUpdated = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!departure.hasEstimatedTripTime() && localAverageLength > 0) {
|
||||
departure.setEstimatedTripTime(localAverageLength);
|
||||
} else if (!departure.hasEstimatedTripTime()) {
|
||||
departure.setEstimatedTripTime(mAverageTripLength);
|
||||
}
|
||||
}
|
||||
|
||||
if (departureUpdated) {
|
||||
mDeparturesAdapter.notifyDataSetChanged();
|
||||
}
|
||||
|
||||
// Update global average
|
||||
if (mLatestScheduleInfo.getTripCountForAverage() > 0) {
|
||||
int newAverageSampleCount = mAverageTripSampleCount
|
||||
+ mLatestScheduleInfo.getTripCountForAverage();
|
||||
int newAverage = (mAverageTripLength * mAverageTripSampleCount + localAverageLength
|
||||
* mLatestScheduleInfo.getTripCountForAverage())
|
||||
/ newAverageSampleCount;
|
||||
|
||||
ContentValues contentValues = new ContentValues();
|
||||
contentValues.put(RoutesColumns.AVERAGE_TRIP_LENGTH.string,
|
||||
newAverage);
|
||||
contentValues.put(RoutesColumns.AVERAGE_TRIP_SAMPLE_COUNT.string,
|
||||
newAverageSampleCount);
|
||||
|
||||
getContentResolver().update(mUri, contentValues, null, null);
|
||||
}
|
||||
}
|
||||
|
||||
private void scheduleDepartureFetch(int millisUntilExecute) {
|
||||
if (!mDepartureFetchIsPending) {
|
||||
mListTitleView.postDelayed(new Runnable() {
|
||||
public void run() {
|
||||
fetchLatestDepartures();
|
||||
}
|
||||
}, millisUntilExecute);
|
||||
mDataFetchIsPending = true;
|
||||
Log.i(Constants.TAG, "Scheduled another data fetch in "
|
||||
mDepartureFetchIsPending = true;
|
||||
Log.i(Constants.TAG, "Scheduled another departure fetch in "
|
||||
+ millisUntilExecute / 1000 + "s");
|
||||
}
|
||||
}
|
||||
|
||||
private void scheduleScheduleInfoFetch(int millisUntilExecute) {
|
||||
if (!mScheduleFetchIsPending) {
|
||||
mListTitleView.postDelayed(new Runnable() {
|
||||
public void run() {
|
||||
fetchLatestSchedule();
|
||||
}
|
||||
}, millisUntilExecute);
|
||||
mScheduleFetchIsPending = true;
|
||||
Log.i(Constants.TAG, "Scheduled another schedule fetch in "
|
||||
+ millisUntilExecute / 1000 + "s");
|
||||
}
|
||||
}
|
||||
|
||||
private void runAutoUpdate() {
|
||||
if (mIsAutoUpdating && mDeparturesAdapter != null) {
|
||||
DepartureArrayAdapter.refreshCounter++;
|
||||
mDeparturesAdapter.notifyDataSetChanged();
|
||||
}
|
||||
if (hasWindowFocus()) {
|
||||
|
@ -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;
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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";
|
||||
}
|
@ -1,13 +1,15 @@
|
||||
package com.dougkeen.bart.data;
|
||||
package com.dougkeen.bart.model;
|
||||
|
||||
import java.util.Date;
|
||||
|
||||
import android.content.Context;
|
||||
import android.os.Parcel;
|
||||
import android.os.Parcelable;
|
||||
|
||||
import com.dougkeen.bart.Line;
|
||||
import com.dougkeen.bart.Station;
|
||||
import android.text.format.DateFormat;
|
||||
|
||||
public class Departure implements Parcelable, Comparable<Departure> {
|
||||
private static final int ESTIMATE_EQUALS_TOLERANCE_MILLIS = 59999;
|
||||
private static final int ESTIMATE_EQUALS_TOLERANCE_LONG_LINGER_MILLIS = 719999;
|
||||
private static final int MINIMUM_MERGE_OVERLAP_MILLIS = 10000;
|
||||
|
||||
public Departure() {
|
||||
@ -31,6 +33,7 @@ public class Departure implements Parcelable, Comparable<Departure> {
|
||||
readFromParcel(in);
|
||||
}
|
||||
|
||||
private Station origin;
|
||||
private Station destination;
|
||||
private Line line;
|
||||
private String destinationColor;
|
||||
@ -45,6 +48,20 @@ public class Departure implements Parcelable, Comparable<Departure> {
|
||||
private long minEstimate;
|
||||
private long maxEstimate;
|
||||
|
||||
private int estimatedTripTime;
|
||||
|
||||
private boolean beganAsDeparted;
|
||||
|
||||
private long arrivalTimeOverride;
|
||||
|
||||
public Station getOrigin() {
|
||||
return origin;
|
||||
}
|
||||
|
||||
public void setOrigin(Station origin) {
|
||||
this.origin = origin;
|
||||
}
|
||||
|
||||
public Station getDestination() {
|
||||
return destination;
|
||||
}
|
||||
@ -131,6 +148,9 @@ public class Departure implements Parcelable, Comparable<Departure> {
|
||||
|
||||
public void setMinutes(int minutes) {
|
||||
this.minutes = minutes;
|
||||
if (minutes == 0) {
|
||||
beganAsDeparted = true;
|
||||
}
|
||||
}
|
||||
|
||||
public long getMinEstimate() {
|
||||
@ -149,6 +169,18 @@ public class Departure implements Parcelable, Comparable<Departure> {
|
||||
this.maxEstimate = maxEstimate;
|
||||
}
|
||||
|
||||
public int getEstimatedTripTime() {
|
||||
return estimatedTripTime;
|
||||
}
|
||||
|
||||
public void setEstimatedTripTime(int estimatedTripTime) {
|
||||
this.estimatedTripTime = estimatedTripTime;
|
||||
}
|
||||
|
||||
public boolean hasEstimatedTripTime() {
|
||||
return this.estimatedTripTime > 0;
|
||||
}
|
||||
|
||||
public int getUncertaintySeconds() {
|
||||
return (int) (maxEstimate - minEstimate + 1000) / 2000;
|
||||
}
|
||||
@ -162,8 +194,27 @@ public class Departure implements Parcelable, Comparable<Departure> {
|
||||
}
|
||||
|
||||
public int getMeanSecondsLeft() {
|
||||
return (int) (((getMinEstimate() + getMaxEstimate()) / 2 - System
|
||||
.currentTimeMillis()) / 1000);
|
||||
return (int) ((getMeanEstimate() - System.currentTimeMillis()) / 1000);
|
||||
}
|
||||
|
||||
public long getMeanEstimate() {
|
||||
return (getMinEstimate() + getMaxEstimate()) / 2;
|
||||
}
|
||||
|
||||
public long getEstimatedArrivalTime() {
|
||||
if (arrivalTimeOverride > 0) {
|
||||
return arrivalTimeOverride;
|
||||
}
|
||||
return getMeanEstimate() + getEstimatedTripTime();
|
||||
}
|
||||
|
||||
public String getEstimatedArrivalTimeText(Context context) {
|
||||
if (getEstimatedTripTime() > 0) {
|
||||
return DateFormat.getTimeFormat(context).format(
|
||||
new Date(getEstimatedArrivalTime()));
|
||||
} else {
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
public boolean hasDeparted() {
|
||||
@ -177,6 +228,13 @@ public class Departure implements Parcelable, Comparable<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
|
||||
|| departure.getMaxEstimate() - getMinEstimate() < MINIMUM_MERGE_OVERLAP_MILLIS) {
|
||||
// The estimate must have changed... just use the latest incoming
|
||||
@ -250,7 +308,7 @@ public class Departure implements Parcelable, Comparable<Departure> {
|
||||
return false;
|
||||
if (line != other.line)
|
||||
return false;
|
||||
if (Math.abs(maxEstimate - other.maxEstimate) > ESTIMATE_EQUALS_TOLERANCE_MILLIS)
|
||||
if (Math.abs(maxEstimate - other.maxEstimate) > getEqualsTolerance())
|
||||
return false;
|
||||
if (platform == null) {
|
||||
if (other.platform != null)
|
||||
@ -267,11 +325,23 @@ public class Departure implements Parcelable, Comparable<Departure> {
|
||||
return true;
|
||||
}
|
||||
|
||||
private int getEqualsTolerance() {
|
||||
if (origin.longStationLinger && hasDeparted()) {
|
||||
return ESTIMATE_EQUALS_TOLERANCE_LONG_LINGER_MILLIS;
|
||||
} else {
|
||||
return ESTIMATE_EQUALS_TOLERANCE_MILLIS;
|
||||
}
|
||||
}
|
||||
|
||||
public String getCountdownText() {
|
||||
StringBuilder builder = new StringBuilder();
|
||||
int secondsLeft = getMeanSecondsLeft();
|
||||
if (hasDeparted()) {
|
||||
if (origin.longStationLinger && beganAsDeparted) {
|
||||
builder.append("At station");
|
||||
} else {
|
||||
builder.append("Departed");
|
||||
}
|
||||
} else {
|
||||
builder.append(secondsLeft / 60);
|
||||
builder.append("m, ");
|
@ -1,4 +1,4 @@
|
||||
package com.dougkeen.bart;
|
||||
package com.dougkeen.bart.model;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
@ -37,6 +37,12 @@ public enum Line {
|
||||
Station.COLS, Station.SANL, Station.BAYF, Station.HAYW,
|
||||
Station.SHAY, Station.UCTY, Station.FRMT),
|
||||
YELLOW_ORANGE_SCHEDULED_TRANSFER(YELLOW, ORANGE, Station.MLBR,
|
||||
Station.SFIA, Station.SBRN, Station.SSAN, Station.COLM,
|
||||
Station.DALY, Station.BALB, Station.GLEN, Station._24TH,
|
||||
Station._16TH, Station.CIVC, Station.POWL, Station.MONT,
|
||||
Station.EMBR, Station.WOAK, Station.ASHB, Station.DBRK,
|
||||
Station.NBRK, Station.PLZA, Station.DELN, Station.RICH),
|
||||
YELLOW_RED_SCHEDULED_TRANSFER(YELLOW, RED, Station.MLBR,
|
||||
Station.SFIA, Station.SBRN, Station.SSAN, Station.COLM,
|
||||
Station.DALY, Station.BALB, Station.GLEN, Station._24TH,
|
||||
Station._16TH, Station.CIVC, Station.POWL, Station.MONT,
|
@ -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,
|
@ -1,4 +1,4 @@
|
||||
package com.dougkeen.bart;
|
||||
package com.dougkeen.bart.model;
|
||||
|
||||
public class Route {
|
||||
private Station origin;
|
91
src/com/dougkeen/bart/model/ScheduleInformation.java
Normal file
91
src/com/dougkeen/bart/model/ScheduleInformation.java
Normal 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;
|
||||
}
|
||||
}
|
86
src/com/dougkeen/bart/model/ScheduleItem.java
Normal file
86
src/com/dougkeen/bart/model/ScheduleItem.java
Normal 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;
|
||||
}
|
||||
}
|
@ -1,8 +1,10 @@
|
||||
package com.dougkeen.bart;
|
||||
package com.dougkeen.bart.model;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import android.util.Log;
|
||||
|
||||
public enum Station {
|
||||
_12TH("12th", "12th St./Oakland City Center", false, false, "bayf"),
|
||||
_16TH("16th", "16th St. Mission", false, false),
|
||||
@ -41,7 +43,7 @@ public enum Station {
|
||||
ROCK("rock", "Rockridge", false, false, "mcar"),
|
||||
SBRN("sbrn", "San Bruno", false, false, "balb", "balb"),
|
||||
SANL("sanl", "San Leandro", true, false, "mcar"),
|
||||
SFIA("sfia", "SFO Airport", false, false, "sbrn", "balb"),
|
||||
SFIA("sfia", "SFO Airport", false, false, "sbrn", "balb", true),
|
||||
SHAY("shay", "South Hayward", true, false, "bayf"),
|
||||
SSAN("ssan", "South San Francisco", false, false, "balb", "balb"),
|
||||
UCTY("ucty", "Union City", true, false, "bayf"),
|
||||
@ -56,37 +58,40 @@ public enum Station {
|
||||
protected final String inboundTransferStation;
|
||||
protected final String outboundTransferStation;
|
||||
public final boolean endOfLine;
|
||||
public final boolean longStationLinger;
|
||||
|
||||
private Station(String abbreviation, String name, boolean invertDirection, boolean endOfLine) {
|
||||
this.abbreviation = abbreviation;
|
||||
this.name = name;
|
||||
this.invertDirection = invertDirection;
|
||||
this.inboundTransferStation = null;
|
||||
this.outboundTransferStation = null;
|
||||
this.endOfLine = endOfLine;
|
||||
private Station(String abbreviation, String name, boolean invertDirection,
|
||||
boolean endOfLine) {
|
||||
this(abbreviation, name, invertDirection, endOfLine, null, null, false);
|
||||
}
|
||||
|
||||
private Station(String abbreviation, String name, boolean invertDirection, boolean endOfLine,
|
||||
String transferStation) {
|
||||
this.abbreviation = abbreviation;
|
||||
this.name = name;
|
||||
this.invertDirection = invertDirection;
|
||||
this.inboundTransferStation = transferStation;
|
||||
this.outboundTransferStation = null;
|
||||
this.endOfLine = endOfLine;
|
||||
private Station(String abbreviation, String name, boolean invertDirection,
|
||||
boolean endOfLine, String transferStation) {
|
||||
this(abbreviation, name, invertDirection, endOfLine, transferStation,
|
||||
null, false);
|
||||
}
|
||||
|
||||
private Station(String abbreviation, String name, boolean invertDirection, boolean endOfLine,
|
||||
String inboundTransferStation, String outboundTransferStation) {
|
||||
private Station(String abbreviation, String name, boolean invertDirection,
|
||||
boolean endOfLine, String inboundTransferStation,
|
||||
String outboundTransferStation) {
|
||||
this(abbreviation, name, invertDirection, endOfLine,
|
||||
inboundTransferStation, outboundTransferStation, false);
|
||||
}
|
||||
|
||||
private Station(String abbreviation, String name, boolean invertDirection,
|
||||
boolean endOfLine, String inboundTransferStation,
|
||||
String outboundTransferStation, boolean longStationLinger) {
|
||||
this.abbreviation = abbreviation;
|
||||
this.name = name;
|
||||
this.invertDirection = invertDirection;
|
||||
this.inboundTransferStation = inboundTransferStation;
|
||||
this.outboundTransferStation = outboundTransferStation;
|
||||
this.endOfLine = endOfLine;
|
||||
this.longStationLinger = longStationLinger;
|
||||
}
|
||||
|
||||
public static Station getByAbbreviation(String abbr) {
|
||||
try {
|
||||
if (abbr == null) {
|
||||
return null;
|
||||
} else if (Character.isDigit(abbr.charAt(0))) {
|
||||
@ -94,6 +99,10 @@ public enum Station {
|
||||
} else {
|
||||
return Station.valueOf(abbr.toUpperCase());
|
||||
}
|
||||
} catch (IllegalArgumentException e) {
|
||||
Log.e(Constants.TAG, "Could not find station for '" + abbr + "'", e);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public Station getInboundTransferStation() {
|
||||
@ -155,8 +164,7 @@ public enum Station {
|
||||
}
|
||||
if (isNorth == null) {
|
||||
if (outboundTransferStation != null) {
|
||||
returnList
|
||||
.addAll(getOutboundTransferStation()
|
||||
returnList.addAll(getOutboundTransferStation()
|
||||
.getRoutesForDestination(dest,
|
||||
getOutboundTransferStation()));
|
||||
} else if (dest.getInboundTransferStation() != null) {
|
21
src/com/dougkeen/bart/model/StationPair.java
Normal file
21
src/com/dougkeen/bart/model/StationPair.java
Normal 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;
|
||||
}
|
||||
}
|
@ -1,4 +1,4 @@
|
||||
package com.dougkeen.bart;
|
||||
package com.dougkeen.bart.networktasks;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Date;
|
||||
@ -11,8 +11,12 @@ import org.xml.sax.helpers.DefaultHandler;
|
||||
|
||||
import android.util.Log;
|
||||
|
||||
import com.dougkeen.bart.data.Departure;
|
||||
import com.dougkeen.bart.data.RealTimeDepartures;
|
||||
import com.dougkeen.bart.model.Constants;
|
||||
import com.dougkeen.bart.model.Departure;
|
||||
import com.dougkeen.bart.model.Line;
|
||||
import com.dougkeen.bart.model.RealTimeDepartures;
|
||||
import com.dougkeen.bart.model.Route;
|
||||
import com.dougkeen.bart.model.Station;
|
||||
|
||||
public class EtdContentHandler extends DefaultHandler {
|
||||
public EtdContentHandler(Station origin, Station destination,
|
||||
@ -55,6 +59,7 @@ public class EtdContentHandler extends DefaultHandler {
|
||||
currentDeparture = new Departure();
|
||||
currentDeparture.setDestination(Station
|
||||
.getByAbbreviation(currentDestination));
|
||||
currentDeparture.setOrigin(realTimeDepartures.getOrigin());
|
||||
}
|
||||
}
|
||||
|
@ -1,4 +1,4 @@
|
||||
package com.dougkeen.bart;
|
||||
package com.dougkeen.bart.networktasks;
|
||||
|
||||
import org.xml.sax.Attributes;
|
||||
import org.xml.sax.SAXException;
|
@ -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<GetRealTimeDeparturesTask.Params, Integer, RealTimeDepartures> {
|
||||
public abstract class GetRealTimeDeparturesTask extends
|
||||
AsyncTask<StationPair, Integer, RealTimeDepartures> {
|
||||
|
||||
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<Route> 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) {
|
@ -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;
|
@ -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);
|
||||
}
|
@ -1,4 +1,4 @@
|
||||
package com.dougkeen.bart;
|
||||
package com.dougkeen.bart.networktasks;
|
||||
|
||||
import java.io.IOException;
|
||||
|
160
src/com/dougkeen/bart/networktasks/ScheduleContentHandler.java
Normal file
160
src/com/dougkeen/bart/networktasks/ScheduleContentHandler.java
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user