Changed arrivals to departures.

Added handling for WHITE and SPCL

--HG--
rename : res/layout/arrival_listing.xml => res/layout/departure_listing.xml
rename : src/com/dougkeen/bart/ArrivalArrayAdapter.java => src/com/dougkeen/bart/DepartureArrayAdapter.java
rename : src/com/dougkeen/bart/GetRealTimeArrivalsTask.java => src/com/dougkeen/bart/GetRealTimeDeparturesTask.java
rename : src/com/dougkeen/bart/ViewArrivalsActivity.java => src/com/dougkeen/bart/ViewDeparturesActivity.java
rename : src/com/dougkeen/bart/data/Arrival.java => src/com/dougkeen/bart/data/Departure.java
rename : src/com/dougkeen/bart/data/RealTimeArrivals.java => src/com/dougkeen/bart/data/RealTimeDepartures.java
This commit is contained in:
dkeen 2011-06-05 11:13:21 -07:00
parent 67e6f5347d
commit ca8f07b30a
13 changed files with 271 additions and 188 deletions

View File

@ -35,7 +35,7 @@
<data android:mimeType="vnd.android.cursor.dir/com.dougkeen.bart.favorite" /> <data android:mimeType="vnd.android.cursor.dir/com.dougkeen.bart.favorite" />
</intent-filter> </intent-filter>
</activity> </activity>
<activity android:name=".ViewArrivalsActivity" <activity android:name="ViewDeparturesActivity"
android:label="@string/app_name"> android:label="@string/app_name">
<intent-filter> <intent-filter>
<action android:name="android.intent.action.VIEW" /> <action android:name="android.intent.action.VIEW" />

View File

@ -8,20 +8,20 @@
android:layout_width="fill_parent" android:layout_height="wrap_content" android:layout_width="fill_parent" android:layout_height="wrap_content"
android:layout_toRightOf="@id/destinationColorBar" android:layout_toRightOf="@id/destinationColorBar"
android:layout_alignParentTop="true"> android:layout_alignParentTop="true">
<TextView android:id="@+id/destinationText" style="@style/ArrivalDestinationText" <TextView android:id="@+id/destinationText" style="@style/DepartureDestinationText"
android:layout_weight="1" android:singleLine="true" android:layout_weight="1" android:singleLine="true"
android:ellipsize="marquee" /> android:ellipsize="marquee" />
<ImageView android:id="@+id/bikeIcon" android:src="@drawable/bike" <ImageView android:id="@+id/bikeIcon" android:src="@drawable/bike"
style="@style/BikeIcon" /> style="@style/BikeIcon" />
<TextView android:id="@+id/countdown" style="@style/ArrivalCountdownText" <TextView android:id="@+id/countdown" style="@style/DepartureCountdownText"
android:gravity="right" /> android:gravity="right" />
</LinearLayout> </LinearLayout>
<ImageView android:id="@+id/xferIcon" android:src="@drawable/xfer" <ImageView android:id="@+id/xferIcon" android:src="@drawable/xfer"
android:layout_below="@id/topRow" android:layout_alignParentRight="true" android:layout_below="@id/topRow" android:layout_alignParentRight="true"
style="@style/XferIcon" /> style="@style/XferIcon" />
<TextView android:id="@+id/trainLengthText" style="@style/ArrivalTrainLengthText" <TextView android:id="@+id/trainLengthText" style="@style/DepartureTrainLengthText"
android:layout_toRightOf="@id/destinationColorBar" android:layout_toRightOf="@id/destinationColorBar"
android:layout_below="@id/topRow" /> android:layout_below="@id/topRow" />
<TextView android:layout_alignParentRight="true" android:id="@+id/uncertainty" <TextView android:layout_alignParentRight="true" android:id="@+id/uncertainty"
android:layout_below="@id/topRow" style="@style/ArrivalUncertaintyText" /> android:layout_below="@id/topRow" style="@style/DepartureUncertaintyText" />
</RelativeLayout> </RelativeLayout>

View File

@ -1,5 +1,5 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android"> <menu xmlns:android="http://schemas.android.com/apk/res/android">
<item android:title="@string/view_arrivals" android:id="@+id/view"></item> <item android:title="@string/view_departures" android:id="@+id/view"></item>
<item android:title="@string/delete" android:id="@+id/delete"></item> <item android:title="@string/delete" android:id="@+id/delete"></item>
</menu> </menu>

View File

@ -2,7 +2,8 @@
<resources> <resources>
<string name="app_name">BART Catcher</string> <string name="app_name">BART Catcher</string>
<string name="favorite_routes">Favorite Routes</string> <string name="favorite_routes">Favorite Routes</string>
<string name="empty_favorites_list_message">Press the menu button and select \"Add route\" to add <string name="empty_favorites_list_message">Press the menu button and select \"Add route\" to
add
a route</string> a route</string>
<string name="add_route">Add a route</string> <string name="add_route">Add a route</string>
<string name="origin">Origin</string> <string name="origin">Origin</string>
@ -12,15 +13,17 @@
different</string> different</string>
<string name="error_null_destination">You must select a destination station</string> <string name="error_null_destination">You must select a destination station</string>
<string name="error_null_origin">You must select an origin station</string> <string name="error_null_origin">You must select an origin station</string>
<string name="arrival_wait_message">Please wait while real time arrival data is <string name="departure_wait_message">Please wait while real time departure data is
loaded...</string> loaded...</string>
<string name="no_data_message">No arrival data is currently available for this <string name="no_data_message">No departure data is currently available for this
route</string> route. Note that there may be service advisories posted at
http://m.bart.gov/schedules/advisories/</string>
<string name="view">View</string> <string name="view">View</string>
<string name="view_arrivals">View arrivals</string> <string name="view_departures">View departures</string>
<string name="delete">Delete</string> <string name="delete">Delete</string>
<string name="yes">Yes</string> <string name="yes">Yes</string>
<string name="cancel">Cancel</string> <string name="cancel">Cancel</string>
<string name="view_on_bart_site">View details on BART site</string> <string name="view_on_bart_site">View details on BART site</string>
<string name="could_not_connect">Could not connect to BART services. Please try again later.</string> <string name="could_not_connect">Could not connect to BART services. Please try
again later.</string>
</resources> </resources>

View File

@ -18,7 +18,7 @@
<item name="android:singleLine">true</item> <item name="android:singleLine">true</item>
</style> </style>
<style name="ArrivalDestinationText"> <style name="DepartureDestinationText">
<item name="android:layout_width">wrap_content</item> <item name="android:layout_width">wrap_content</item>
<item name="android:layout_height">wrap_content</item> <item name="android:layout_height">wrap_content</item>
<item name="android:textSize">22dp</item> <item name="android:textSize">22dp</item>
@ -27,7 +27,7 @@
<item name="android:layout_marginLeft">3dp</item> <item name="android:layout_marginLeft">3dp</item>
</style> </style>
<style name="ArrivalTrainLengthText"> <style name="DepartureTrainLengthText">
<item name="android:layout_width">wrap_content</item> <item name="android:layout_width">wrap_content</item>
<item name="android:layout_height">wrap_content</item> <item name="android:layout_height">wrap_content</item>
<item name="android:textSize">18dp</item> <item name="android:textSize">18dp</item>
@ -35,14 +35,14 @@
<item name="android:layout_marginLeft">3dp</item> <item name="android:layout_marginLeft">3dp</item>
</style> </style>
<style name="ArrivalCountdownText"> <style name="DepartureCountdownText">
<item name="android:layout_width">wrap_content</item> <item name="android:layout_width">wrap_content</item>
<item name="android:layout_height">wrap_content</item> <item name="android:layout_height">wrap_content</item>
<item name="android:textSize">20dp</item> <item name="android:textSize">20dp</item>
<item name="android:singleLine">true</item> <item name="android:singleLine">true</item>
<item name="android:width">90dp</item> <item name="android:width">90dp</item>
</style> </style>
<style name="ArrivalUncertaintyText"> <style name="DepartureUncertaintyText">
<item name="android:layout_width">wrap_content</item> <item name="android:layout_width">wrap_content</item>
<item name="android:layout_height">wrap_content</item> <item name="android:layout_height">wrap_content</item>
<item name="android:textSize">16dp</item> <item name="android:textSize">16dp</item>

View File

@ -13,36 +13,36 @@ import android.widget.ImageView;
import android.widget.RelativeLayout; import android.widget.RelativeLayout;
import android.widget.TextView; import android.widget.TextView;
import com.dougkeen.bart.data.Arrival; import com.dougkeen.bart.data.Departure;
public class ArrivalArrayAdapter extends ArrayAdapter<Arrival> { public class DepartureArrayAdapter extends ArrayAdapter<Departure> {
public ArrivalArrayAdapter(Context context, int textViewResourceId, public DepartureArrayAdapter(Context context, int textViewResourceId,
Arrival[] objects) { Departure[] objects) {
super(context, textViewResourceId, objects); super(context, textViewResourceId, objects);
} }
public ArrivalArrayAdapter(Context context, int resource, public DepartureArrayAdapter(Context context, int resource,
int textViewResourceId, Arrival[] objects) { int textViewResourceId, Departure[] objects) {
super(context, resource, textViewResourceId, objects); super(context, resource, textViewResourceId, objects);
} }
public ArrivalArrayAdapter(Context context, int resource, public DepartureArrayAdapter(Context context, int resource,
int textViewResourceId, List<Arrival> objects) { int textViewResourceId, List<Departure> objects) {
super(context, resource, textViewResourceId, objects); super(context, resource, textViewResourceId, objects);
} }
public ArrivalArrayAdapter(Context context, int resource, public DepartureArrayAdapter(Context context, int resource,
int textViewResourceId) { int textViewResourceId) {
super(context, resource, textViewResourceId); super(context, resource, textViewResourceId);
} }
public ArrivalArrayAdapter(Context context, int textViewResourceId, public DepartureArrayAdapter(Context context, int textViewResourceId,
List<Arrival> objects) { List<Departure> objects) {
super(context, textViewResourceId, objects); super(context, textViewResourceId, objects);
} }
public ArrivalArrayAdapter(Context context, int textViewResourceId) { public DepartureArrayAdapter(Context context, int textViewResourceId) {
super(context, textViewResourceId); super(context, textViewResourceId);
} }
@ -53,30 +53,30 @@ public class ArrivalArrayAdapter extends ArrayAdapter<Arrival> {
view = convertView; view = convertView;
} else { } else {
LayoutInflater inflater = LayoutInflater.from(getContext()); LayoutInflater inflater = LayoutInflater.from(getContext());
view = inflater.inflate(R.layout.arrival_listing, parent, false); view = inflater.inflate(R.layout.departure_listing, parent, false);
} }
Arrival arrival = getItem(position); Departure departure = getItem(position);
((TextView) view.findViewById(R.id.destinationText)).setText(arrival ((TextView) view.findViewById(R.id.destinationText)).setText(departure
.getDestination().toString()); .getDestination().toString());
((TextView) view.findViewById(R.id.trainLengthText)).setText(arrival ((TextView) view.findViewById(R.id.trainLengthText)).setText(departure
.getTrainLengthText()); .getTrainLengthText());
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
.parseColor(arrival.getDestinationColor())); .parseColor(departure.getDestinationColor()));
((TextView) view.findViewById(R.id.countdown)).setText(arrival ((TextView) view.findViewById(R.id.countdown)).setText(departure
.getCountdownText()); .getCountdownText());
((TextView) view.findViewById(R.id.uncertainty)).setText(arrival ((TextView) view.findViewById(R.id.uncertainty)).setText(departure
.getUncertaintyText()); .getUncertaintyText());
if (arrival.isBikeAllowed()) { if (departure.isBikeAllowed()) {
((ImageView) view.findViewById(R.id.bikeIcon)) ((ImageView) view.findViewById(R.id.bikeIcon))
.setVisibility(View.VISIBLE); .setVisibility(View.VISIBLE);
} else { } else {
((ImageView) view.findViewById(R.id.bikeIcon)) ((ImageView) view.findViewById(R.id.bikeIcon))
.setVisibility(View.INVISIBLE); .setVisibility(View.INVISIBLE);
} }
if (arrival.getRequiresTransfer()) { if (departure.getRequiresTransfer()) {
((ImageView) view.findViewById(R.id.xferIcon)) ((ImageView) view.findViewById(R.id.xferIcon))
.setVisibility(View.VISIBLE); .setVisibility(View.VISIBLE);
} else { } else {

View File

@ -10,32 +10,34 @@ import org.xml.sax.helpers.DefaultHandler;
import android.util.Log; import android.util.Log;
import com.dougkeen.bart.data.Arrival; import com.dougkeen.bart.data.Departure;
import com.dougkeen.bart.data.RealTimeArrivals; import com.dougkeen.bart.data.RealTimeDepartures;
public class EtdContentHandler extends DefaultHandler { public class EtdContentHandler extends DefaultHandler {
public EtdContentHandler(Station origin, Station destination, public EtdContentHandler(Station origin, Station destination,
List<Route> routes) { List<Route> routes) {
super(); super();
realTimeArrivals = new RealTimeArrivals(origin, destination, routes); realTimeDepartures = new RealTimeDepartures(origin, destination, routes);
} }
private final static List<String> TAGS = Arrays.asList("date", "time", private final static List<String> TAGS = Arrays.asList("date", "time",
"abbreviation", "minutes", "platform", "direction", "length", "abbreviation", "minutes", "platform", "direction", "length",
"color", "hexcolor", "bikeflag"); "color", "hexcolor", "bikeflag");
private RealTimeArrivals realTimeArrivals; private RealTimeDepartures realTimeDepartures;
public RealTimeArrivals getRealTimeArrivals() { public RealTimeDepartures getRealTimeDepartures() {
return realTimeArrivals; return realTimeDepartures;
} }
private String date; private String date;
private String currentDestination; private String currentDestination;
private String currentValue; private String currentValue;
private Arrival currentArrival; private Departure currentDeparture;
private boolean isParsingTag; private boolean isParsingTag;
private boolean getDestinationFromLine;
@Override @Override
public void characters(char[] ch, int start, int length) public void characters(char[] ch, int start, int length)
throws SAXException { throws SAXException {
@ -51,11 +53,15 @@ public class EtdContentHandler extends DefaultHandler {
isParsingTag = true; isParsingTag = true;
} }
if (localName.equals("estimate")) { if (localName.equals("estimate")) {
currentArrival = new Arrival(); currentDeparture = new Departure();
currentArrival.setDestination(Station if (currentDestination.equalsIgnoreCase("SPCL")) {
getDestinationFromLine = true;
} else {
currentDeparture.setDestination(Station
.getByAbbreviation(currentDestination)); .getByAbbreviation(currentDestination));
} }
} }
}
@Override @Override
public void endElement(String uri, String localName, String qName) public void endElement(String uri, String localName, String qName)
@ -63,40 +69,60 @@ public class EtdContentHandler extends DefaultHandler {
if (localName.equals("date")) { if (localName.equals("date")) {
date = currentValue; date = currentValue;
} else if (localName.equals("time")) { } else if (localName.equals("time")) {
realTimeArrivals.setTime(Date.parse(date + " " + currentValue)); realTimeDepartures.setTime(Date.parse(date + " " + currentValue));
} else if (localName.equals("abbreviation")) { } else if (localName.equals("abbreviation")) {
currentDestination = currentValue; currentDestination = currentValue;
} else if (localName.equals("minutes")) { } else if (localName.equals("minutes")) {
if (currentValue.equalsIgnoreCase("arrived")) { if (currentValue.equalsIgnoreCase("arrived")) {
currentArrival.setMinutes(0); currentDeparture.setMinutes(0);
} else { } else {
currentArrival.setMinutes(Integer.parseInt(currentValue)); currentDeparture.setMinutes(Integer.parseInt(currentValue));
} }
} else if (localName.equals("platform")) { } else if (localName.equals("platform")) {
currentArrival.setPlatform(currentValue); currentDeparture.setPlatform(currentValue);
} else if (localName.equals("direction")) { } else if (localName.equals("direction")) {
currentArrival.setDirection(currentValue); currentDeparture.setDirection(currentValue);
} else if (localName.equals("length")) { } else if (localName.equals("length")) {
currentArrival.setTrainLength(Integer.parseInt(currentValue)); currentDeparture.setTrainLength(Integer.parseInt(currentValue));
} else if (localName.equals("color")) { } else if (localName.equals("color")) {
try { try {
currentArrival.setLine(Line.valueOf(currentValue)); if (getDestinationFromLine) {
final Line line = Line.valueOf(currentValue);
currentDeparture.setLine(line);
currentDeparture.setDestination(line
.getUsualTerminusForDirectionAndOrigin(
currentDeparture.getDirection(),
realTimeDepartures.getOrigin()));
} else if (currentValue.equalsIgnoreCase("WHITE")) {
for (Line line : Line.values()) {
if (line.stations.indexOf(currentDeparture
.getDestination()) >= 0
&& line.stations.indexOf(realTimeDepartures
.getDestination()) >= 0) {
currentDeparture.setLine(line);
break;
}
}
} else {
currentDeparture.setLine(Line.valueOf(currentValue));
}
} catch (IllegalArgumentException e) { } catch (IllegalArgumentException e) {
Log.w("BartApp", "There is no line called '" + currentValue Log.w(Constants.TAG, "There is no line called '" + currentValue
+ "'"); + "'");
} }
} else if (localName.equals("hexcolor")) { } else if (localName.equals("hexcolor")) {
currentArrival.setDestinationColor("#ff" currentDeparture.setDestinationColor("#ff"
+ currentValue.substring(1)); + currentValue.substring(1));
} else if (localName.equals("bikeflag")) { } else if (localName.equals("bikeflag")) {
currentArrival.setBikeAllowed(currentValue.equalsIgnoreCase("1")); currentDeparture.setBikeAllowed(currentValue.equalsIgnoreCase("1"));
} else if (localName.equals("estimate")) { } else if (localName.equals("estimate")) {
realTimeArrivals.addArrival(currentArrival); realTimeDepartures.addDeparture(currentDeparture);
currentArrival = null; currentDeparture = null;
getDestinationFromLine = false;
} else if (localName.equals("etd")) { } else if (localName.equals("etd")) {
currentDestination = null; currentDestination = null;
} else if (localName.equals("station")) { } else if (localName.equals("station")) {
realTimeArrivals.sortArrivals(); realTimeDepartures.sortDepartures();
} }
isParsingTag = false; isParsingTag = false;
currentValue = null; currentValue = null;

View File

@ -9,13 +9,13 @@ import java.util.List;
import org.xml.sax.SAXException; import org.xml.sax.SAXException;
import com.dougkeen.bart.data.RealTimeArrivals; import com.dougkeen.bart.data.RealTimeDepartures;
import android.os.AsyncTask; import android.os.AsyncTask;
import android.util.Xml; import android.util.Xml;
public abstract class GetRealTimeArrivalsTask extends public abstract class GetRealTimeDeparturesTask extends
AsyncTask<GetRealTimeArrivalsTask.Params, Integer, RealTimeArrivals> { AsyncTask<GetRealTimeDeparturesTask.Params, Integer, RealTimeDepartures> {
private static final int CONNECTION_TIMEOUT_MILLIS = 10000; private static final int CONNECTION_TIMEOUT_MILLIS = 10000;
private final static String API_KEY = "5LD9-IAYI-TRAT-MHHW"; private final static String API_KEY = "5LD9-IAYI-TRAT-MHHW";
@ -28,20 +28,20 @@ public abstract class GetRealTimeArrivalsTask extends
private List<Route> mRoutes; private List<Route> mRoutes;
@Override @Override
protected RealTimeArrivals doInBackground(Params... paramsArray) { protected RealTimeDepartures doInBackground(Params... paramsArray) {
// Always expect one param // Always expect one param
Params params = paramsArray[0]; Params params = paramsArray[0];
mRoutes = params.origin.getRoutesForDestination(params.destination); mRoutes = params.origin.getRoutesForDestination(params.destination);
if (!isCancelled()) { if (!isCancelled()) {
return getArrivalsFromNetwork(params, 0); return getDeparturesFromNetwork(params, 0);
} else { } else {
return null; return null;
} }
} }
private RealTimeArrivals getArrivalsFromNetwork(Params params, private RealTimeDepartures getDeparturesFromNetwork(Params params,
int attemptNumber) { int attemptNumber) {
try { try {
URL sourceUrl = new URL(String.format(API_URL, URL sourceUrl = new URL(String.format(API_URL,
@ -57,9 +57,9 @@ public abstract class GetRealTimeArrivalsTask extends
Xml.parse(connection.getInputStream(), Xml.parse(connection.getInputStream(),
Xml.findEncodingByName("UTF-8"), Xml.findEncodingByName("UTF-8"),
handler); handler);
final RealTimeArrivals realTimeArrivals = handler final RealTimeDepartures realTimeDepartures = handler
.getRealTimeArrivals(); .getRealTimeDepartures();
return realTimeArrivals; return realTimeDepartures;
} catch (MalformedURLException e) { } catch (MalformedURLException e) {
throw new RuntimeException(e); throw new RuntimeException(e);
} catch (UnsupportedEncodingException e) { } catch (UnsupportedEncodingException e) {
@ -71,7 +71,7 @@ public abstract class GetRealTimeArrivalsTask extends
} catch (InterruptedException interrupt) { } catch (InterruptedException interrupt) {
// Ignore... just go on to next attempt // Ignore... just go on to next attempt
} }
return getArrivalsFromNetwork(params, attemptNumber + 1); return getDeparturesFromNetwork(params, attemptNumber + 1);
} else { } else {
mIOException = e; mIOException = e;
return null; return null;
@ -101,7 +101,7 @@ public abstract class GetRealTimeArrivalsTask extends
} }
@Override @Override
protected void onPostExecute(RealTimeArrivals result) { protected void onPostExecute(RealTimeDepartures result) {
if (result != null) { if (result != null) {
onResult(result); onResult(result);
} else { } else {
@ -109,7 +109,7 @@ public abstract class GetRealTimeArrivalsTask extends
} }
} }
public abstract void onResult(RealTimeArrivals result); public abstract void onResult(RealTimeDepartures result);
public abstract void onNetworkError(IOException e); public abstract void onNetworkError(IOException e);
} }

View File

@ -92,4 +92,22 @@ public enum Line {
return destinations; return destinations;
} }
public Station getUsualTerminusForDirectionAndOrigin(String direction,
Station origin) {
boolean isNorth = false;
if (direction.toLowerCase().startsWith("s") && directionMayInvert
&& origin.invertDirection) {
isNorth = true;
} else if (direction.toLowerCase().startsWith("n")
&& !(directionMayInvert && origin.invertDirection)) {
isNorth = true;
}
if (isNorth) {
return stations.get(stations.size() - 1);
} else {
return stations.get(0);
}
}
} }

View File

@ -47,7 +47,8 @@ public enum Station {
UCTY("ucty", "Union City", true, "bayf"), UCTY("ucty", "Union City", true, "bayf"),
WCRK("wcrk", "Walnut Creek", false, "mcar"), WCRK("wcrk", "Walnut Creek", false, "mcar"),
WDUB("wdub", "West Dublin/Pleasanton", false, "bayf"), WDUB("wdub", "West Dublin/Pleasanton", false, "bayf"),
WOAK("woak", "West Oakland", false); WOAK("woak", "West Oakland", false),
SPCL("spcl", "Special", false);
public final String abbreviation; public final String abbreviation;
public final String name; public final String name;

View File

@ -13,6 +13,7 @@ import android.os.Bundle;
import android.os.Parcelable; import android.os.Parcelable;
import android.os.PowerManager; import android.os.PowerManager;
import android.text.format.DateFormat; import android.text.format.DateFormat;
import android.text.util.Linkify;
import android.util.Log; import android.util.Log;
import android.view.Menu; import android.view.Menu;
import android.view.MenuInflater; import android.view.MenuInflater;
@ -21,12 +22,12 @@ import android.widget.ArrayAdapter;
import android.widget.TextView; import android.widget.TextView;
import android.widget.Toast; import android.widget.Toast;
import com.dougkeen.bart.GetRealTimeArrivalsTask.Params; import com.dougkeen.bart.GetRealTimeDeparturesTask.Params;
import com.dougkeen.bart.data.Arrival; import com.dougkeen.bart.data.Departure;
import com.dougkeen.bart.data.RealTimeArrivals; import com.dougkeen.bart.data.RealTimeDepartures;
import com.dougkeen.bart.data.RoutesColumns; import com.dougkeen.bart.data.RoutesColumns;
public class ViewArrivalsActivity extends ListActivity { public class ViewDeparturesActivity extends ListActivity {
private static final int UNCERTAINTY_THRESHOLD = 17; private static final int UNCERTAINTY_THRESHOLD = 17;
@ -35,11 +36,11 @@ public class ViewArrivalsActivity extends ListActivity {
private Station mOrigin; private Station mOrigin;
private Station mDestination; private Station mDestination;
private ArrayAdapter<Arrival> mArrivalsAdapter; private ArrayAdapter<Departure> mDeparturesAdapter;
private TextView mListTitleView; private TextView mListTitleView;
private AsyncTask<Params, Integer, RealTimeArrivals> mGetArrivalsTask; private AsyncTask<Params, Integer, RealTimeDepartures> mGetDeparturesTask;
private boolean mIsAutoUpdating = false; private boolean mIsAutoUpdating = false;
@ -52,7 +53,7 @@ public class ViewArrivalsActivity extends ListActivity {
private PowerManager.WakeLock mWakeLock; private PowerManager.WakeLock mWakeLock;
private boolean mFetchArrivalsOnNextFocus; private boolean mFetchDeparturesOnNextFocus;
@Override @Override
protected void onCreate(Bundle savedInstanceState) { protected void onCreate(Bundle savedInstanceState) {
@ -77,31 +78,32 @@ public class ViewArrivalsActivity extends ListActivity {
mOrigin = Station.getByAbbreviation(cursor.getString(0)); mOrigin = Station.getByAbbreviation(cursor.getString(0));
mDestination = Station.getByAbbreviation(cursor.getString(1)); mDestination = Station.getByAbbreviation(cursor.getString(1));
String header = mOrigin.name + " to " + mDestination.name; String header = "Departures:\n" + mOrigin.name + " to "
+ mDestination.name;
mListTitleView = (TextView) findViewById(R.id.listTitle); mListTitleView = (TextView) findViewById(R.id.listTitle);
mListTitleView.setText(header); mListTitleView.setText(header);
((TextView) findViewById(android.R.id.empty)) ((TextView) findViewById(android.R.id.empty))
.setText(R.string.arrival_wait_message); .setText(R.string.departure_wait_message);
mArrivalsAdapter = new ArrivalArrayAdapter(this, mDeparturesAdapter = new DepartureArrayAdapter(this,
R.layout.arrival_listing); R.layout.departure_listing);
if (savedInstanceState != null if (savedInstanceState != null
&& savedInstanceState.containsKey("arrivals")) { && savedInstanceState.containsKey("departures")) {
for (Parcelable arrival : savedInstanceState for (Parcelable departure : savedInstanceState
.getParcelableArray("arrivals")) { .getParcelableArray("departures")) {
mArrivalsAdapter.add((Arrival) arrival); mDeparturesAdapter.add((Departure) departure);
} }
} }
setListAdapter(mArrivalsAdapter); setListAdapter(mDeparturesAdapter);
mFetchArrivalsOnNextFocus = true; mFetchDeparturesOnNextFocus = true;
} }
@Override @Override
protected void onDestroy() { protected void onDestroy() {
if (mGetArrivalsTask != null) { if (mGetDeparturesTask != null) {
mGetArrivalsTask.cancel(true); mGetDeparturesTask.cancel(true);
} }
super.onDestroy(); super.onDestroy();
} }
@ -109,26 +111,28 @@ public class ViewArrivalsActivity extends ListActivity {
@Override @Override
protected void onSaveInstanceState(Bundle outState) { protected void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState); super.onSaveInstanceState(outState);
Arrival[] arrivals = new Arrival[mArrivalsAdapter.getCount()]; Departure[] departures = new Departure[mDeparturesAdapter.getCount()];
for (int i = mArrivalsAdapter.getCount() - 1; i >= 0; i--) { for (int i = mDeparturesAdapter.getCount() - 1; i >= 0; i--) {
arrivals[i] = mArrivalsAdapter.getItem(i); departures[i] = mDeparturesAdapter.getItem(i);
} }
outState.putParcelableArray("arrivals", arrivals); outState.putParcelableArray("departures", departures);
} }
@Override @Override
public void onWindowFocusChanged(boolean hasFocus) { public void onWindowFocusChanged(boolean hasFocus) {
super.onWindowFocusChanged(hasFocus); super.onWindowFocusChanged(hasFocus);
if (hasFocus) { if (hasFocus) {
if (mFetchArrivalsOnNextFocus) { if (mFetchDeparturesOnNextFocus) {
fetchLatestArrivals(); fetchLatestDepartures();
mFetchArrivalsOnNextFocus = false; mFetchDeparturesOnNextFocus = false;
} }
PowerManager powerManaer = (PowerManager) getSystemService(Context.POWER_SERVICE); PowerManager powerManaer = (PowerManager) getSystemService(Context.POWER_SERVICE);
mWakeLock = powerManaer.newWakeLock( mWakeLock = powerManaer
PowerManager.SCREEN_DIM_WAKE_LOCK, "ViewArrivalsActivity"); .newWakeLock(
PowerManager.SCREEN_DIM_WAKE_LOCK,
"ViewDeparturesActivity");
mWakeLock.acquire(); mWakeLock.acquire();
if (mArrivalsAdapter != null && !mArrivalsAdapter.isEmpty()) { if (mDeparturesAdapter != null && !mDeparturesAdapter.isEmpty()) {
mIsAutoUpdating = true; mIsAutoUpdating = true;
} }
runAutoUpdate(); runAutoUpdate();
@ -137,28 +141,28 @@ public class ViewArrivalsActivity extends ListActivity {
} }
} }
private void fetchLatestArrivals() { private void fetchLatestDepartures() {
if (!hasWindowFocus()) if (!hasWindowFocus())
return; return;
if (mGetArrivalsTask != null if (mGetDeparturesTask != null
&& mGetArrivalsTask.getStatus() && mGetDeparturesTask.getStatus()
.equals(AsyncTask.Status.RUNNING)) { .equals(AsyncTask.Status.RUNNING)) {
// Don't overlap fetches // Don't overlap fetches
return; return;
} }
mGetArrivalsTask = new GetRealTimeArrivalsTask() { mGetDeparturesTask = new GetRealTimeDeparturesTask() {
@Override @Override
public void onResult(RealTimeArrivals result) { public void onResult(RealTimeDepartures result) {
Log.i(Constants.TAG, "Processing data from server"); Log.i(Constants.TAG, "Processing data from server");
processLatestArrivals(result); processLatestDepartures(result);
Log.i(Constants.TAG, "Done processing data from server"); Log.i(Constants.TAG, "Done processing data from server");
} }
@Override @Override
public void onNetworkError(IOException e) { public void onNetworkError(IOException e) {
Log.w(Constants.TAG, e.getMessage()); Log.w(Constants.TAG, e.getMessage());
Toast.makeText(ViewArrivalsActivity.this, Toast.makeText(ViewDeparturesActivity.this,
R.string.could_not_connect, R.string.could_not_connect,
Toast.LENGTH_LONG).show(); Toast.LENGTH_LONG).show();
((TextView) findViewById(android.R.id.empty)) ((TextView) findViewById(android.R.id.empty))
@ -167,80 +171,96 @@ public class ViewArrivalsActivity extends ListActivity {
} }
}; };
Log.i(Constants.TAG, "Fetching data from server"); Log.i(Constants.TAG, "Fetching data from server");
mGetArrivalsTask.execute(new GetRealTimeArrivalsTask.Params(mOrigin, mGetDeparturesTask.execute(new GetRealTimeDeparturesTask.Params(
mOrigin,
mDestination)); mDestination));
} }
protected void processLatestArrivals(RealTimeArrivals result) { protected void processLatestDepartures(RealTimeDepartures result) {
if (result.getArrivals().isEmpty()) { if (result.getDepartures().isEmpty()) {
((TextView) findViewById(android.R.id.empty)) final TextView textView = (TextView) findViewById(android.R.id.empty);
.setText(R.string.no_data_message); textView.setText(R.string.no_data_message);
Linkify.addLinks(textView, Linkify.WEB_URLS);
return; return;
} }
boolean needsBetterAccuracy = false; boolean needsBetterAccuracy = false;
Arrival firstArrival = null; Departure firstDeparture = null;
final List<Arrival> arrivals = result.getArrivals(); final List<Departure> departures = result.getDepartures();
if (mArrivalsAdapter.getCount() > 0) { if (mDeparturesAdapter.getCount() > 0) {
int adapterIndex = -1; int adapterIndex = -1;
for (Arrival arrival : arrivals) { for (Departure departure : departures) {
adapterIndex++; adapterIndex++;
Arrival existingArrival = null; Departure existingDeparture = null;
if (adapterIndex < mArrivalsAdapter.getCount()) { if (adapterIndex < mDeparturesAdapter.getCount()) {
existingArrival = mArrivalsAdapter.getItem(adapterIndex); existingDeparture = mDeparturesAdapter
.getItem(adapterIndex);
} }
while (existingArrival != null while (existingDeparture != null
&& !arrival.equals(existingArrival)) { && !departure.equals(existingDeparture)) {
mArrivalsAdapter.remove(existingArrival); mDeparturesAdapter.remove(existingDeparture);
if (adapterIndex < mArrivalsAdapter.getCount()) { if (adapterIndex < mDeparturesAdapter.getCount()) {
existingArrival = mArrivalsAdapter existingDeparture = mDeparturesAdapter
.getItem(adapterIndex); .getItem(adapterIndex);
} else { } else {
existingArrival = null; existingDeparture = null;
} }
} }
if (existingArrival != null) { if (existingDeparture != null) {
existingArrival.mergeEstimate(arrival); existingDeparture.mergeEstimate(departure);
} else { } else {
mArrivalsAdapter.add(arrival); mDeparturesAdapter.add(departure);
existingArrival = arrival; existingDeparture = departure;
} }
if (firstArrival == null) { if (firstDeparture == null) {
firstArrival = existingArrival; firstDeparture = existingDeparture;
} }
if (existingArrival.getUncertaintySeconds() > UNCERTAINTY_THRESHOLD) { if (existingDeparture.getUncertaintySeconds() > UNCERTAINTY_THRESHOLD) {
needsBetterAccuracy = true; needsBetterAccuracy = true;
} }
} }
} else { } else {
for (Arrival arrival : arrivals) { for (Departure departure : departures) {
if (firstArrival == null) { if (firstDeparture == null) {
firstArrival = arrival; firstDeparture = departure;
} }
mArrivalsAdapter.add(arrival); mDeparturesAdapter.add(departure);
} }
needsBetterAccuracy = true; needsBetterAccuracy = true;
} }
mArrivalsAdapter.notifyDataSetChanged(); mDeparturesAdapter.notifyDataSetChanged();
if (hasWindowFocus() && firstArrival != null) { if (hasWindowFocus() && firstDeparture != null) {
if (needsBetterAccuracy if (needsBetterAccuracy
|| firstArrival.hasArrived()) { || firstDeparture.hasDeparted()) {
// Get more data in 20s // Get more data in 20s
mListTitleView.postDelayed(new Runnable() { mListTitleView.postDelayed(new Runnable() {
@Override @Override
public void run() { public void run() {
fetchLatestArrivals(); fetchLatestDepartures();
} }
}, 20000); }, 20000);
Log.i(Constants.TAG, "Scheduled another data fetch in 20s"); Log.i(Constants.TAG, "Scheduled another data fetch in 20s");
} else { } else {
// Get more when next train arrives // Get more 90 seconds before next train arrives, right when
final int interval = firstArrival.getMinSecondsLeft() * 1000; // next train arrives, or 3 minutes, whichever is sooner
final long now = System.currentTimeMillis();
final long nextDepartureMillis = firstDeparture
.getMinSecondsLeft() * 1000L;
final int intervalUntilNextDeparture = (int) (nextDepartureMillis - now);
final int alternativeInterval = 3 * 60 * 1000;
int interval = intervalUntilNextDeparture;
if (intervalUntilNextDeparture > 95000
&& intervalUntilNextDeparture < alternativeInterval) {
interval = interval - 90 * 1000;
} else if (intervalUntilNextDeparture > alternativeInterval) {
interval = alternativeInterval;
}
mListTitleView.postDelayed(new Runnable() { mListTitleView.postDelayed(new Runnable() {
@Override @Override
public void run() { public void run() {
fetchLatestArrivals(); fetchLatestDepartures();
} }
}, interval); }, interval);
Log.i(Constants.TAG, "Scheduled another data fetch in " Log.i(Constants.TAG, "Scheduled another data fetch in "
@ -256,8 +276,8 @@ public class ViewArrivalsActivity extends ListActivity {
} }
private void runAutoUpdate() { private void runAutoUpdate() {
if (mIsAutoUpdating && mArrivalsAdapter != null) { if (mIsAutoUpdating && mDeparturesAdapter != null) {
mArrivalsAdapter.notifyDataSetChanged(); mDeparturesAdapter.notifyDataSetChanged();
} }
if (hasWindowFocus()) { if (hasWindowFocus()) {
mListTitleView.postDelayed(AUTO_UPDATE_RUNNABLE, 1000); mListTitleView.postDelayed(AUTO_UPDATE_RUNNABLE, 1000);
@ -285,7 +305,7 @@ public class ViewArrivalsActivity extends ListActivity {
+ mOrigin.abbreviation + mOrigin.abbreviation
+ "&dest=" + "&dest="
+ mDestination.abbreviation))); + mDestination.abbreviation)));
mFetchArrivalsOnNextFocus = true; mFetchDeparturesOnNextFocus = true;
return true; return true;
} else { } else {
return super.onOptionsItemSelected(item); return super.onOptionsItemSelected(item);

View File

@ -6,12 +6,14 @@ import android.os.Parcelable;
import com.dougkeen.bart.Line; import com.dougkeen.bart.Line;
import com.dougkeen.bart.Station; import com.dougkeen.bart.Station;
public class Arrival implements Parcelable, Comparable<Arrival> { public class Departure implements Parcelable, Comparable<Departure> {
public Arrival() { private static final int MINIMUM_MERGE_OVERLAP_MILLIS = 10000;
public Departure() {
super(); super();
} }
public Arrival(String destinationAbbr, String destinationColor, public Departure(String destinationAbbr, String destinationColor,
String platform, String direction, boolean bikeAllowed, String platform, String direction, boolean bikeAllowed,
int trainLength, int minutes) { int trainLength, int minutes) {
super(); super();
@ -24,7 +26,7 @@ public class Arrival implements Parcelable, Comparable<Arrival> {
this.minutes = minutes; this.minutes = minutes;
} }
public Arrival(Parcel in) { public Departure(Parcel in) {
readFromParcel(in); readFromParcel(in);
} }
@ -163,20 +165,30 @@ public class Arrival implements Parcelable, Comparable<Arrival> {
.currentTimeMillis()) / 1000); .currentTimeMillis()) / 1000);
} }
public boolean hasArrived() { public boolean hasDeparted() {
return getMinutes() == 0 || getMeanSecondsLeft() < 0; return getMinutes() == 0 || getMeanSecondsLeft() < 0;
} }
public void calculateEstimates(long originalEstimateTime) { public void calculateEstimates(long originalEstimateTime) {
setMinEstimate(originalEstimateTime + (getMinutes() * 60 * 1000)); setMinEstimate(originalEstimateTime + (getMinutes() * 60 * 1000)
setMaxEstimate(getMinEstimate() + (59 * 1000)); - (30000));
setMaxEstimate(getMinEstimate() + 60000);
}
public void mergeEstimate(Departure departure) {
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
// values
setMinEstimate(departure.getMinEstimate());
setMaxEstimate(departure.getMaxEstimate());
return;
} }
public void mergeEstimate(Arrival arrival) {
final long newMin = Math final long newMin = Math
.max(getMinEstimate(), arrival.getMinEstimate()); .max(getMinEstimate(), departure.getMinEstimate());
final long newMax = Math final long newMax = Math
.min(getMaxEstimate(), arrival.getMaxEstimate()); .min(getMaxEstimate(), departure.getMaxEstimate());
if (newMax > newMin) { // We can never have 0 or negative uncertainty if (newMax > newMin) { // We can never have 0 or negative uncertainty
setMinEstimate(newMin); setMinEstimate(newMin);
setMaxEstimate(newMax); setMaxEstimate(newMax);
@ -184,7 +196,7 @@ public class Arrival implements Parcelable, Comparable<Arrival> {
} }
@Override @Override
public int compareTo(Arrival another) { public int compareTo(Departure another) {
return (this.getMinutes() > another.getMinutes()) ? 1 : ( return (this.getMinutes() > another.getMinutes()) ? 1 : (
(this.getMinutes() == another.getMinutes()) ? 0 : -1); (this.getMinutes() == another.getMinutes()) ? 0 : -1);
} }
@ -197,7 +209,7 @@ public class Arrival implements Parcelable, Comparable<Arrival> {
return false; return false;
if (getClass() != obj.getClass()) if (getClass() != obj.getClass())
return false; return false;
Arrival other = (Arrival) obj; Departure other = (Departure) obj;
if (bikeAllowed != other.bikeAllowed) if (bikeAllowed != other.bikeAllowed)
return false; return false;
if (destination != other.destination) if (destination != other.destination)
@ -227,8 +239,8 @@ public class Arrival implements Parcelable, Comparable<Arrival> {
public String getCountdownText() { public String getCountdownText() {
StringBuilder builder = new StringBuilder(); StringBuilder builder = new StringBuilder();
int secondsLeft = getMeanSecondsLeft(); int secondsLeft = getMeanSecondsLeft();
if (hasArrived()) { if (hasDeparted()) {
builder.append("Arrived"); builder.append("Departed");
} else { } else {
builder.append(secondsLeft / 60); builder.append(secondsLeft / 60);
builder.append("m, "); builder.append("m, ");
@ -239,7 +251,7 @@ public class Arrival implements Parcelable, Comparable<Arrival> {
} }
public String getUncertaintyText() { public String getUncertaintyText() {
if (hasArrived()) { if (hasDeparted()) {
return ""; return "";
} else { } else {
return "" + getUncertaintySeconds() + "s)"; return "" + getUncertaintySeconds() + "s)";
@ -290,13 +302,13 @@ public class Arrival implements Parcelable, Comparable<Arrival> {
maxEstimate = in.readLong(); maxEstimate = in.readLong();
} }
public static final Parcelable.Creator<Arrival> CREATOR = new Parcelable.Creator<Arrival>() { public static final Parcelable.Creator<Departure> CREATOR = new Parcelable.Creator<Departure>() {
public Arrival createFromParcel(Parcel in) { public Departure createFromParcel(Parcel in) {
return new Arrival(in); return new Departure(in);
} }
public Arrival[] newArray(int size) { public Departure[] newArray(int size) {
return new Arrival[size]; return new Departure[size];
} }
}; };
} }

View File

@ -7,8 +7,8 @@ import java.util.List;
import com.dougkeen.bart.Route; import com.dougkeen.bart.Route;
import com.dougkeen.bart.Station; import com.dougkeen.bart.Station;
public class RealTimeArrivals { public class RealTimeDepartures {
public RealTimeArrivals(Station origin, Station destination, public RealTimeDepartures(Station origin, Station destination,
List<Route> routes) { List<Route> routes) {
this.origin = origin; this.origin = origin;
this.destination = destination; this.destination = destination;
@ -19,7 +19,7 @@ public class RealTimeArrivals {
private Station destination; private Station destination;
private long time; private long time;
private List<Arrival> arrivals; private List<Departure> departures;
private List<Route> routes; private List<Route> routes;
@ -47,32 +47,35 @@ public class RealTimeArrivals {
this.time = time; this.time = time;
} }
public List<Arrival> getArrivals() { public List<Departure> getDepartures() {
if (arrivals == null) { if (departures == null) {
arrivals = new ArrayList<Arrival>(); departures = new ArrayList<Departure>();
} }
return arrivals; return departures;
} }
public void setArrivals(List<Arrival> arrivals) { public void setDepartures(List<Departure> departures) {
this.arrivals = arrivals; this.departures = departures;
} }
public void addArrival(Arrival arrival) { public void addDeparture(Departure departure) {
Station destination = Station.getByAbbreviation(arrival Station destination = Station.getByAbbreviation(departure
.getDestinationAbbreviation()); .getDestinationAbbreviation());
if (departure.getLine() == null)
return;
for (Route route : routes) { for (Route route : routes) {
if (route.trainDestinationIsApplicable(destination, arrival.getLine())) { if (route.trainDestinationIsApplicable(destination,
arrival.setRequiresTransfer(route.hasTransfer()); departure.getLine())) {
getArrivals().add(arrival); departure.setRequiresTransfer(route.hasTransfer());
arrival.calculateEstimates(time); getDepartures().add(departure);
departure.calculateEstimates(time);
return; return;
} }
} }
} }
public void sortArrivals() { public void sortDepartures() {
Collections.sort(getArrivals()); Collections.sort(getDepartures());
} }
public List<Route> getRoutes() { public List<Route> getRoutes() {
@ -86,14 +89,14 @@ public class RealTimeArrivals {
@Override @Override
public String toString() { public String toString() {
StringBuilder builder = new StringBuilder(); StringBuilder builder = new StringBuilder();
builder.append("RealTimeArrivals [origin="); builder.append("RealTimeDepartures [origin=");
builder.append(origin); builder.append(origin);
builder.append(", destination="); builder.append(", destination=");
builder.append(destination); builder.append(destination);
builder.append(", time="); builder.append(", time=");
builder.append(time); builder.append(time);
builder.append(", arrivals="); builder.append(", departures=");
builder.append(arrivals); builder.append(departures);
builder.append("]"); builder.append("]");
return builder.toString(); return builder.toString();
} }