Now uses service to grab departure estimates (in preparation for a notification service that will share that EtdService)
This commit is contained in:
parent
64d5eda3b5
commit
7a4e8da4f0
3
.gitignore
vendored
Normal file
3
.gitignore
vendored
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
|
||||||
|
/bin
|
||||||
|
/gen
|
@ -2,14 +2,14 @@
|
|||||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
package="com.dougkeen.bart"
|
package="com.dougkeen.bart"
|
||||||
android:installLocation="auto"
|
android:installLocation="auto"
|
||||||
android:versionCode="20"
|
android:versionCode="21"
|
||||||
android:versionName="2.0.2" >
|
android:versionName="2.1.0" >
|
||||||
|
|
||||||
<uses-permission android:name="android.permission.INTERNET" />
|
<uses-permission android:name="android.permission.INTERNET" />
|
||||||
<uses-permission android:name="android.permission.WAKE_LOCK" />
|
<uses-permission android:name="android.permission.WAKE_LOCK" />
|
||||||
|
|
||||||
<uses-sdk
|
<uses-sdk
|
||||||
android:minSdkVersion="7"
|
android:minSdkVersion="8"
|
||||||
android:targetSdkVersion="14" />
|
android:targetSdkVersion="14" />
|
||||||
|
|
||||||
<application
|
<application
|
||||||
@ -62,6 +62,9 @@
|
|||||||
android:name=".data.BartContentProvider"
|
android:name=".data.BartContentProvider"
|
||||||
android:authorities="com.dougkeen.bart.dataprovider"
|
android:authorities="com.dougkeen.bart.dataprovider"
|
||||||
android:label="BartRunner data provider" />
|
android:label="BartRunner data provider" />
|
||||||
|
|
||||||
|
<service android:name=".NotificationService" />
|
||||||
|
<service android:name=".EtdService" />
|
||||||
</application>
|
</application>
|
||||||
|
|
||||||
</manifest>
|
</manifest>
|
@ -1,14 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<TextView xmlns:android="http://schemas.android.com/apk/res/android"
|
|
||||||
android:id="@android:id/text1"
|
|
||||||
android:layout_width="fill_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:ellipsize="marquee"
|
|
||||||
android:textColor="#000"
|
|
||||||
android:paddingBottom="10dip"
|
|
||||||
android:paddingLeft="5dip"
|
|
||||||
android:paddingRight="5dip"
|
|
||||||
android:paddingTop="10dip"
|
|
||||||
android:singleLine="true" >
|
|
||||||
|
|
||||||
</TextView>
|
|
@ -1,7 +1,6 @@
|
|||||||
package com.dougkeen.bart;
|
package com.dougkeen.bart;
|
||||||
|
|
||||||
import android.content.ContentValues;
|
import android.content.ContentValues;
|
||||||
import android.net.Uri;
|
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.widget.CheckBox;
|
import android.widget.CheckBox;
|
||||||
|
|
||||||
@ -36,7 +35,7 @@ public class AddRouteDialogFragment extends AbstractRouteSelectionFragment {
|
|||||||
values.put(RoutesColumns.FROM_STATION.string, origin.abbreviation);
|
values.put(RoutesColumns.FROM_STATION.string, origin.abbreviation);
|
||||||
values.put(RoutesColumns.TO_STATION.string, destination.abbreviation);
|
values.put(RoutesColumns.TO_STATION.string, destination.abbreviation);
|
||||||
|
|
||||||
Uri newUri = getActivity().getContentResolver().insert(
|
getActivity().getContentResolver().insert(
|
||||||
Constants.FAVORITE_CONTENT_URI, values);
|
Constants.FAVORITE_CONTENT_URI, values);
|
||||||
|
|
||||||
if (((CheckBox) getDialog().findViewById(R.id.return_checkbox))
|
if (((CheckBox) getDialog().findViewById(R.id.return_checkbox))
|
||||||
|
@ -72,7 +72,14 @@ public class DepartureArrayAdapter extends ArrayAdapter<Departure> {
|
|||||||
.findViewById(R.id.trainLengthText);
|
.findViewById(R.id.trainLengthText);
|
||||||
initTextSwitcher(textSwitcher);
|
initTextSwitcher(textSwitcher);
|
||||||
|
|
||||||
textSwitcher.setCurrentText(departure.getTrainLengthText());
|
final String estimatedArrivalTimeText = departure
|
||||||
|
.getEstimatedArrivalTimeText(getContext());
|
||||||
|
if (!StringUtils.isBlank(estimatedArrivalTimeText)) {
|
||||||
|
textSwitcher.setCurrentText("Est. arrival "
|
||||||
|
+ estimatedArrivalTimeText);
|
||||||
|
} else {
|
||||||
|
textSwitcher.setCurrentText(departure.getTrainLengthText());
|
||||||
|
}
|
||||||
textSwitcher.setTextProvider(new TextProvider() {
|
textSwitcher.setTextProvider(new TextProvider() {
|
||||||
@Override
|
@Override
|
||||||
public String getText(long tickNumber) {
|
public String getText(long tickNumber) {
|
||||||
|
580
src/com/dougkeen/bart/EtdService.java
Normal file
580
src/com/dougkeen/bart/EtdService.java
Normal file
@ -0,0 +1,580 @@
|
|||||||
|
package com.dougkeen.bart;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.WeakHashMap;
|
||||||
|
|
||||||
|
import org.apache.commons.lang3.math.NumberUtils;
|
||||||
|
|
||||||
|
import android.app.Service;
|
||||||
|
import android.content.ContentValues;
|
||||||
|
import android.content.Intent;
|
||||||
|
import android.database.Cursor;
|
||||||
|
import android.net.Uri;
|
||||||
|
import android.os.AsyncTask;
|
||||||
|
import android.os.Binder;
|
||||||
|
import android.os.Handler;
|
||||||
|
import android.os.IBinder;
|
||||||
|
import android.support.v4.content.CursorLoader;
|
||||||
|
import android.util.Log;
|
||||||
|
|
||||||
|
import com.dougkeen.bart.data.RoutesColumns;
|
||||||
|
import com.dougkeen.bart.model.Constants;
|
||||||
|
import com.dougkeen.bart.model.Departure;
|
||||||
|
import com.dougkeen.bart.model.RealTimeDepartures;
|
||||||
|
import com.dougkeen.bart.model.ScheduleInformation;
|
||||||
|
import com.dougkeen.bart.model.ScheduleItem;
|
||||||
|
import com.dougkeen.bart.model.Station;
|
||||||
|
import com.dougkeen.bart.model.StationPair;
|
||||||
|
import com.dougkeen.bart.networktasks.GetRealTimeDeparturesTask;
|
||||||
|
import com.dougkeen.bart.networktasks.GetScheduleInformationTask;
|
||||||
|
|
||||||
|
public class EtdService extends Service {
|
||||||
|
|
||||||
|
private IBinder mBinder;
|
||||||
|
|
||||||
|
private Map<StationPair, EtdServiceEngine> mServiceEngineMap;
|
||||||
|
|
||||||
|
public EtdService() {
|
||||||
|
super();
|
||||||
|
mBinder = new EtdServiceBinder();
|
||||||
|
mServiceEngineMap = new HashMap<StationPair, EtdServiceEngine>();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void registerListener(EtdServiceListener listener) {
|
||||||
|
StationPair stationPair = getStationPairFromListener(listener);
|
||||||
|
if (stationPair == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (!mServiceEngineMap.containsKey(stationPair)) {
|
||||||
|
mServiceEngineMap.put(stationPair,
|
||||||
|
new EtdServiceEngine(stationPair));
|
||||||
|
}
|
||||||
|
mServiceEngineMap.get(stationPair).registerListener(listener);
|
||||||
|
}
|
||||||
|
|
||||||
|
private StationPair getStationPairFromListener(EtdServiceListener listener) {
|
||||||
|
StationPair route = listener.getStationPair();
|
||||||
|
if (route == null) {
|
||||||
|
Log.wtf(Constants.TAG,
|
||||||
|
"Somehow we got a listener that's returning a null route O_o");
|
||||||
|
}
|
||||||
|
return route;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void unregisterListener(EtdServiceListener listener) {
|
||||||
|
StationPair stationPair = getStationPairFromListener(listener);
|
||||||
|
if (stationPair == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (mServiceEngineMap.containsKey(stationPair)) {
|
||||||
|
mServiceEngineMap.get(stationPair).unregisterListener(listener);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public IBinder onBind(Intent intent) {
|
||||||
|
return mBinder;
|
||||||
|
}
|
||||||
|
|
||||||
|
public interface EtdServiceListener {
|
||||||
|
void onETDChanged(final List<Departure> departures);
|
||||||
|
|
||||||
|
void onError(String errorMessage);
|
||||||
|
|
||||||
|
void onRequestStarted();
|
||||||
|
|
||||||
|
void onRequestEnded();
|
||||||
|
|
||||||
|
StationPair getStationPair();
|
||||||
|
}
|
||||||
|
|
||||||
|
public class EtdServiceBinder extends Binder {
|
||||||
|
|
||||||
|
public EtdService getService() {
|
||||||
|
return EtdService.this;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private class EtdServiceEngine {
|
||||||
|
private static final int UNCERTAINTY_THRESHOLD = 17;
|
||||||
|
|
||||||
|
private Uri mUri;
|
||||||
|
|
||||||
|
private final StationPair mStationPair;
|
||||||
|
|
||||||
|
private boolean mIgnoreDepartureDirection = false;
|
||||||
|
|
||||||
|
private boolean mPendingEtdRequest = false;
|
||||||
|
|
||||||
|
private int mAverageTripLength;
|
||||||
|
private int mAverageTripSampleCount;
|
||||||
|
|
||||||
|
// We'll only use the keys
|
||||||
|
private WeakHashMap<EtdServiceListener, Boolean> mListeners;
|
||||||
|
|
||||||
|
private List<Departure> mLatestDepartures;
|
||||||
|
private ScheduleInformation mLatestScheduleInfo;
|
||||||
|
|
||||||
|
private AsyncTask<StationPair, Integer, RealTimeDepartures> mGetDeparturesTask;
|
||||||
|
private AsyncTask<StationPair, Integer, ScheduleInformation> mGetScheduleInformationTask;
|
||||||
|
|
||||||
|
private Handler mRunnableQueue;
|
||||||
|
|
||||||
|
private boolean mStarted = false;
|
||||||
|
|
||||||
|
public EtdServiceEngine(final StationPair route) {
|
||||||
|
mStationPair = route;
|
||||||
|
mListeners = new WeakHashMap<EtdService.EtdServiceListener, Boolean>();
|
||||||
|
mRunnableQueue = new Handler();
|
||||||
|
mLatestDepartures = new ArrayList<Departure>();
|
||||||
|
|
||||||
|
mUri = Constants.ARBITRARY_ROUTE_CONTENT_URI_ROOT.buildUpon()
|
||||||
|
.appendPath(mStationPair.getOrigin().abbreviation)
|
||||||
|
.appendPath(mStationPair.getDestination().abbreviation)
|
||||||
|
.build();
|
||||||
|
|
||||||
|
Cursor cursor = new CursorLoader(EtdService.this, mUri,
|
||||||
|
new String[] { RoutesColumns.AVERAGE_TRIP_LENGTH.string,
|
||||||
|
RoutesColumns.AVERAGE_TRIP_SAMPLE_COUNT.string },
|
||||||
|
null, null, null).loadInBackground();
|
||||||
|
if (cursor.moveToFirst()) {
|
||||||
|
mAverageTripLength = cursor.getInt(0);
|
||||||
|
mAverageTripSampleCount = cursor.getInt(1);
|
||||||
|
}
|
||||||
|
cursor.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void registerListener(EtdServiceListener listener) {
|
||||||
|
mListeners.put(listener, true);
|
||||||
|
if (!mPendingEtdRequest) {
|
||||||
|
mStarted = true;
|
||||||
|
fetchLatestDepartures();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void unregisterListener(EtdServiceListener listener) {
|
||||||
|
mListeners.remove(listener);
|
||||||
|
if (mListeners.isEmpty()) {
|
||||||
|
if (mGetDeparturesTask != null
|
||||||
|
&& mGetDeparturesTask.getStatus().equals(
|
||||||
|
AsyncTask.Status.RUNNING)) {
|
||||||
|
mGetDeparturesTask.cancel(true);
|
||||||
|
}
|
||||||
|
if (mGetScheduleInformationTask != null
|
||||||
|
&& mGetScheduleInformationTask.getStatus().equals(
|
||||||
|
AsyncTask.Status.RUNNING)) {
|
||||||
|
mGetScheduleInformationTask.cancel(true);
|
||||||
|
}
|
||||||
|
mStarted = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void notifyListenersOfETDChange() {
|
||||||
|
for (EtdServiceListener listener : mListeners.keySet()) {
|
||||||
|
listener.onETDChanged(mLatestDepartures);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void notifyListenersOfError(String errorMessage) {
|
||||||
|
for (EtdServiceListener listener : mListeners.keySet()) {
|
||||||
|
listener.onError(errorMessage);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void notifyListenersOfRequestStart() {
|
||||||
|
for (EtdServiceListener listener : mListeners.keySet()) {
|
||||||
|
listener.onRequestStarted();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void notifyListenersOfRequestEnd() {
|
||||||
|
for (EtdServiceListener listener : mListeners.keySet()) {
|
||||||
|
listener.onRequestEnded();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void fetchLatestDepartures() {
|
||||||
|
if (mGetDeparturesTask != null
|
||||||
|
&& mGetDeparturesTask.equals(AsyncTask.Status.RUNNING)) {
|
||||||
|
// Don't overlap fetches
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!mStarted)
|
||||||
|
return;
|
||||||
|
|
||||||
|
GetRealTimeDeparturesTask task = new GetRealTimeDeparturesTask(
|
||||||
|
mIgnoreDepartureDirection) {
|
||||||
|
@Override
|
||||||
|
public void onResult(RealTimeDepartures result) {
|
||||||
|
Log.d(Constants.TAG, "Processing data from server");
|
||||||
|
processLatestDepartures(result);
|
||||||
|
Log.d(Constants.TAG, "Done processing data from server");
|
||||||
|
notifyListenersOfRequestEnd();
|
||||||
|
mPendingEtdRequest = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onError(Exception e) {
|
||||||
|
Log.w(Constants.TAG, e.getMessage(), e);
|
||||||
|
notifyListenersOfError(getString(R.string.could_not_connect));
|
||||||
|
// Try again in 60s
|
||||||
|
scheduleDepartureFetch(60000);
|
||||||
|
notifyListenersOfRequestEnd();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
mGetDeparturesTask = task;
|
||||||
|
Log.d(Constants.TAG, "Fetching data from server");
|
||||||
|
task.execute(new StationPair(mStationPair.getOrigin(), mStationPair
|
||||||
|
.getDestination()));
|
||||||
|
notifyListenersOfRequestStart();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void fetchLatestSchedule() {
|
||||||
|
if (mGetScheduleInformationTask != null
|
||||||
|
&& mGetScheduleInformationTask.getStatus().equals(
|
||||||
|
AsyncTask.Status.RUNNING)) {
|
||||||
|
// Don't overlap fetches
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
GetScheduleInformationTask task = new GetScheduleInformationTask() {
|
||||||
|
@Override
|
||||||
|
public void onResult(ScheduleInformation result) {
|
||||||
|
Log.d(Constants.TAG, "Processing data from server");
|
||||||
|
mLatestScheduleInfo = result;
|
||||||
|
applyScheduleInformation(result);
|
||||||
|
Log.d(Constants.TAG, "Done processing data from server");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onError(Exception e) {
|
||||||
|
Log.w(Constants.TAG, e.getMessage(), e);
|
||||||
|
notifyListenersOfError(getString(R.string.could_not_connect));
|
||||||
|
|
||||||
|
// Try again in 60s
|
||||||
|
scheduleScheduleInfoFetch(60000);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
Log.i(Constants.TAG, "Fetching data from server");
|
||||||
|
mGetScheduleInformationTask = task;
|
||||||
|
task.execute(new StationPair(mStationPair.getOrigin(), mStationPair
|
||||||
|
.getDestination()));
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void applyScheduleInformation(ScheduleInformation result) {
|
||||||
|
int localAverageLength = mLatestScheduleInfo.getAverageTripLength();
|
||||||
|
|
||||||
|
int departuresCount = mLatestDepartures.size();
|
||||||
|
|
||||||
|
// Let's get smallest interval between departures
|
||||||
|
int smallestDepartureInterval = 0;
|
||||||
|
long previousDepartureTime = 0;
|
||||||
|
for (int departureIndex = 0; departureIndex < departuresCount; departureIndex++) {
|
||||||
|
Departure departure = mLatestDepartures.get(departureIndex);
|
||||||
|
if (previousDepartureTime == 0) {
|
||||||
|
previousDepartureTime = departure.getMeanEstimate();
|
||||||
|
} else if (smallestDepartureInterval == 0) {
|
||||||
|
smallestDepartureInterval = (int) (departure
|
||||||
|
.getMeanEstimate() - previousDepartureTime);
|
||||||
|
} else {
|
||||||
|
smallestDepartureInterval = Math
|
||||||
|
.min(smallestDepartureInterval,
|
||||||
|
(int) (departure.getMeanEstimate() - previousDepartureTime));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Match scheduled departures with real time departures in adapter
|
||||||
|
int lastSearchIndex = 0;
|
||||||
|
int tripCount = mLatestScheduleInfo.getTrips().size();
|
||||||
|
boolean departureUpdated = false;
|
||||||
|
Departure lastUnestimatedTransfer = null;
|
||||||
|
int departuresWithoutEstimates = 0;
|
||||||
|
for (int departureIndex = 0; departureIndex < departuresCount; departureIndex++) {
|
||||||
|
Departure departure = mLatestDepartures.get(departureIndex);
|
||||||
|
for (int i = lastSearchIndex; i < tripCount; i++) {
|
||||||
|
ScheduleItem trip = mLatestScheduleInfo.getTrips().get(i);
|
||||||
|
// Definitely not a match if they have different
|
||||||
|
// destinations
|
||||||
|
if (!departure.getDestination().abbreviation.equals(trip
|
||||||
|
.getTrainHeadStation())) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
long departTimeDiff = Math.abs(trip.getDepartureTime()
|
||||||
|
- departure.getMeanEstimate());
|
||||||
|
final long millisUntilTripDeparture = trip
|
||||||
|
.getDepartureTime() - System.currentTimeMillis();
|
||||||
|
final int equalityTolerance = (departure.getOrigin() != null) ? NumberUtils
|
||||||
|
.max(departure.getOrigin().departureEqualityTolerance,
|
||||||
|
ScheduleItem.SCHEDULE_ITEM_DEPARTURE_EQUALS_TOLERANCE,
|
||||||
|
smallestDepartureInterval)
|
||||||
|
: ScheduleItem.SCHEDULE_ITEM_DEPARTURE_EQUALS_TOLERANCE;
|
||||||
|
if (departure.getOrigin() != null
|
||||||
|
&& departure.getOrigin().longStationLinger
|
||||||
|
&& departure.hasDeparted()
|
||||||
|
&& millisUntilTripDeparture > 0
|
||||||
|
&& millisUntilTripDeparture < equalityTolerance) {
|
||||||
|
departure.setArrivalTimeOverride(trip.getArrivalTime());
|
||||||
|
lastSearchIndex = i;
|
||||||
|
departureUpdated = true;
|
||||||
|
if (lastUnestimatedTransfer != null) {
|
||||||
|
lastUnestimatedTransfer.setArrivalTimeOverride(trip
|
||||||
|
.getArrivalTime());
|
||||||
|
departuresWithoutEstimates--;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
} else if (departTimeDiff <= (equalityTolerance + departure
|
||||||
|
.getUncertaintySeconds() * 1000)
|
||||||
|
&& departure.getEstimatedTripTime() != trip
|
||||||
|
.getTripLength()
|
||||||
|
&& !(departure.getOrigin().longStationLinger && departure
|
||||||
|
.hasDeparted())) {
|
||||||
|
departure.setEstimatedTripTime(trip.getTripLength());
|
||||||
|
lastSearchIndex = i;
|
||||||
|
departureUpdated = true;
|
||||||
|
if (lastUnestimatedTransfer != null) {
|
||||||
|
lastUnestimatedTransfer.setArrivalTimeOverride(trip
|
||||||
|
.getArrivalTime());
|
||||||
|
departuresWithoutEstimates--;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Don't estimate for non-scheduled transfers
|
||||||
|
if (!departure.getRequiresTransfer()) {
|
||||||
|
if (!departure.hasEstimatedTripTime()
|
||||||
|
&& localAverageLength > 0) {
|
||||||
|
// Use the average we just calculated if available
|
||||||
|
departure.setEstimatedTripTime(localAverageLength);
|
||||||
|
} else if (!departure.hasEstimatedTripTime()) {
|
||||||
|
// Otherwise just assume the global average
|
||||||
|
departure.setEstimatedTripTime(mAverageTripLength);
|
||||||
|
}
|
||||||
|
} else if (departure.getRequiresTransfer()
|
||||||
|
&& !departure.hasAnyArrivalEstimate()) {
|
||||||
|
lastUnestimatedTransfer = departure;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!departure.hasAnyArrivalEstimate()) {
|
||||||
|
departuresWithoutEstimates++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (departureUpdated) {
|
||||||
|
notifyListenersOfETDChange();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* If we still have some departures without estimates, try again
|
||||||
|
* later
|
||||||
|
*/
|
||||||
|
if (departuresWithoutEstimates > 0) {
|
||||||
|
scheduleScheduleInfoFetch(20000);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void processLatestDepartures(RealTimeDepartures result) {
|
||||||
|
if (result.getDepartures().isEmpty()) {
|
||||||
|
result.includeTransferRoutes();
|
||||||
|
}
|
||||||
|
if (result.getDepartures().isEmpty()) {
|
||||||
|
result.includeDoubleTransferRoutes();
|
||||||
|
}
|
||||||
|
if (result.getDepartures().isEmpty()
|
||||||
|
&& mStationPair.isBetweenStations(Station.MLBR,
|
||||||
|
Station.SFIA)) {
|
||||||
|
/*
|
||||||
|
* Let's try again, ignoring direction (this sometimes comes up
|
||||||
|
* when you travel between Millbrae and SFO... sometimes you
|
||||||
|
* need to travel north and transfer, sometimes you can travel
|
||||||
|
* south for a direct line)
|
||||||
|
*/
|
||||||
|
mIgnoreDepartureDirection = true;
|
||||||
|
scheduleDepartureFetch(50);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
boolean needsBetterAccuracy = false;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Keep track of first departure, since we'll request another quick
|
||||||
|
* refresh if it has departed.
|
||||||
|
*/
|
||||||
|
Departure firstDeparture = null;
|
||||||
|
|
||||||
|
final List<Departure> departures = result.getDepartures();
|
||||||
|
if (mLatestDepartures.isEmpty()) {
|
||||||
|
// Just copy everything to the departure list
|
||||||
|
for (Departure departure : departures) {
|
||||||
|
if (firstDeparture == null) {
|
||||||
|
firstDeparture = departure;
|
||||||
|
}
|
||||||
|
mLatestDepartures.add(departure);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Since all the departures are new, we'll definitely need
|
||||||
|
* better accuracy
|
||||||
|
*/
|
||||||
|
needsBetterAccuracy = true;
|
||||||
|
} else {
|
||||||
|
/*
|
||||||
|
* Let's merge the latest departure list with the instance
|
||||||
|
* departure list
|
||||||
|
*/
|
||||||
|
int instanceListIndex = -1;
|
||||||
|
for (Departure departure : departures) {
|
||||||
|
instanceListIndex++;
|
||||||
|
Departure existingDeparture = null;
|
||||||
|
if (instanceListIndex < mLatestDepartures.size()) {
|
||||||
|
existingDeparture = mLatestDepartures
|
||||||
|
.get(instanceListIndex);
|
||||||
|
}
|
||||||
|
/*
|
||||||
|
* Looks for departures at the beginning of the adapter that
|
||||||
|
* aren't in the latest list of departures
|
||||||
|
*/
|
||||||
|
while (existingDeparture != null
|
||||||
|
&& !departure.equals(existingDeparture)) {
|
||||||
|
// Remove old departure
|
||||||
|
mLatestDepartures.remove(existingDeparture);
|
||||||
|
if (instanceListIndex < mLatestDepartures.size()) {
|
||||||
|
/*
|
||||||
|
* Try again with next departure (keep in mind the
|
||||||
|
* next departure is now at the current index, since
|
||||||
|
* we removed a member)
|
||||||
|
*/
|
||||||
|
existingDeparture = mLatestDepartures
|
||||||
|
.get(instanceListIndex);
|
||||||
|
} else {
|
||||||
|
// Reached the end of the list... give up
|
||||||
|
existingDeparture = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/*
|
||||||
|
* Merge the estimate if we found a matching departure,
|
||||||
|
* otherwise add a new one to the adapter
|
||||||
|
*/
|
||||||
|
if (existingDeparture != null) {
|
||||||
|
existingDeparture.mergeEstimate(departure);
|
||||||
|
} else {
|
||||||
|
mLatestDepartures.add(departure);
|
||||||
|
existingDeparture = departure;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set first departure
|
||||||
|
if (firstDeparture == null) {
|
||||||
|
firstDeparture = existingDeparture;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if estimate is accurate enough
|
||||||
|
if (existingDeparture.getUncertaintySeconds() > UNCERTAINTY_THRESHOLD) {
|
||||||
|
needsBetterAccuracy = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Collections.sort(mLatestDepartures);
|
||||||
|
notifyListenersOfETDChange();
|
||||||
|
requestScheduleIfNecessary();
|
||||||
|
|
||||||
|
if (firstDeparture != null) {
|
||||||
|
if (needsBetterAccuracy || firstDeparture.hasDeparted()) {
|
||||||
|
// Get more data in 20s
|
||||||
|
scheduleDepartureFetch(20000);
|
||||||
|
} else {
|
||||||
|
/*
|
||||||
|
* Get more 90 seconds before next train arrives, right when
|
||||||
|
* next train arrives, or 3 minutes, whichever is sooner
|
||||||
|
*/
|
||||||
|
final int intervalUntilNextDeparture = firstDeparture
|
||||||
|
.getMinSecondsLeft() * 1000;
|
||||||
|
final int alternativeInterval = 3 * 60 * 1000;
|
||||||
|
|
||||||
|
int interval = intervalUntilNextDeparture;
|
||||||
|
if (intervalUntilNextDeparture > 95000
|
||||||
|
&& intervalUntilNextDeparture < alternativeInterval) {
|
||||||
|
interval = interval - 90 * 1000;
|
||||||
|
} else if (intervalUntilNextDeparture > alternativeInterval) {
|
||||||
|
interval = alternativeInterval;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (interval < 0) {
|
||||||
|
interval = 20000;
|
||||||
|
}
|
||||||
|
|
||||||
|
scheduleDepartureFetch(interval);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void requestScheduleIfNecessary() {
|
||||||
|
// Bail if there's nothing to match schedules to
|
||||||
|
if (mLatestDepartures.isEmpty()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fetch if we don't have anything at all
|
||||||
|
if (mLatestScheduleInfo == null) {
|
||||||
|
fetchLatestSchedule();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Otherwise, check if the latest departure doesn't have schedule
|
||||||
|
* info... if not, fetch
|
||||||
|
*/
|
||||||
|
Departure lastDeparture = mLatestDepartures.get(mLatestDepartures
|
||||||
|
.size() - 1);
|
||||||
|
if (mLatestScheduleInfo.getLatestDepartureTime() < lastDeparture
|
||||||
|
.getMeanEstimate()) {
|
||||||
|
fetchLatestSchedule();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void scheduleDepartureFetch(int millisUntilExecute) {
|
||||||
|
mPendingEtdRequest = true;
|
||||||
|
mRunnableQueue.postDelayed(new Runnable() {
|
||||||
|
public void run() {
|
||||||
|
fetchLatestDepartures();
|
||||||
|
}
|
||||||
|
}, millisUntilExecute);
|
||||||
|
Log.d(Constants.TAG, "Scheduled another departure fetch in "
|
||||||
|
+ millisUntilExecute / 1000 + "s");
|
||||||
|
}
|
||||||
|
|
||||||
|
private void scheduleScheduleInfoFetch(int millisUntilExecute) {
|
||||||
|
mRunnableQueue.postDelayed(new Runnable() {
|
||||||
|
public void run() {
|
||||||
|
fetchLatestSchedule();
|
||||||
|
}
|
||||||
|
}, millisUntilExecute);
|
||||||
|
Log.d(Constants.TAG, "Scheduled another schedule fetch in "
|
||||||
|
+ millisUntilExecute / 1000 + "s");
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
@ -3,7 +3,6 @@ package com.dougkeen.bart;
|
|||||||
import java.util.Calendar;
|
import java.util.Calendar;
|
||||||
import java.util.TimeZone;
|
import java.util.TimeZone;
|
||||||
|
|
||||||
import android.app.Dialog;
|
|
||||||
import android.content.ContentUris;
|
import android.content.ContentUris;
|
||||||
import android.content.ContentValues;
|
import android.content.ContentValues;
|
||||||
import android.content.DialogInterface;
|
import android.content.DialogInterface;
|
||||||
@ -18,13 +17,11 @@ import android.widget.AdapterView;
|
|||||||
import android.widget.Button;
|
import android.widget.Button;
|
||||||
import android.widget.CursorAdapter;
|
import android.widget.CursorAdapter;
|
||||||
import android.widget.ListAdapter;
|
import android.widget.ListAdapter;
|
||||||
import android.widget.ListView;
|
|
||||||
import android.widget.SimpleCursorAdapter;
|
import android.widget.SimpleCursorAdapter;
|
||||||
import android.widget.SimpleCursorAdapter.ViewBinder;
|
import android.widget.SimpleCursorAdapter.ViewBinder;
|
||||||
import android.widget.TextView;
|
import android.widget.TextView;
|
||||||
|
|
||||||
import com.WazaBe.HoloEverywhere.AlertDialog;
|
import com.WazaBe.HoloEverywhere.AlertDialog;
|
||||||
import com.WazaBe.HoloEverywhere.AlertDialog.Builder;
|
|
||||||
import com.actionbarsherlock.app.SherlockFragmentActivity;
|
import com.actionbarsherlock.app.SherlockFragmentActivity;
|
||||||
import com.actionbarsherlock.view.ActionMode;
|
import com.actionbarsherlock.view.ActionMode;
|
||||||
import com.actionbarsherlock.view.Menu;
|
import com.actionbarsherlock.view.Menu;
|
||||||
@ -40,8 +37,6 @@ public class RoutesListActivity extends SherlockFragmentActivity {
|
|||||||
private static final TimeZone PACIFIC_TIME = TimeZone
|
private static final TimeZone PACIFIC_TIME = TimeZone
|
||||||
.getTimeZone("America/Los_Angeles");
|
.getTimeZone("America/Los_Angeles");
|
||||||
|
|
||||||
private static final int DIALOG_DELETE_ROUTE = 0;
|
|
||||||
|
|
||||||
protected Cursor mQuery;
|
protected Cursor mQuery;
|
||||||
|
|
||||||
private Uri mCurrentlySelectedUri;
|
private Uri mCurrentlySelectedUri;
|
||||||
|
@ -2,19 +2,21 @@ package com.dougkeen.bart;
|
|||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
import org.apache.commons.lang3.math.NumberUtils;
|
import android.content.ComponentName;
|
||||||
|
|
||||||
import android.content.ContentValues;
|
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
|
import android.content.ServiceConnection;
|
||||||
import android.database.Cursor;
|
import android.database.Cursor;
|
||||||
import android.graphics.Color;
|
import android.graphics.Color;
|
||||||
import android.graphics.drawable.GradientDrawable;
|
import android.graphics.drawable.GradientDrawable;
|
||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
import android.os.AsyncTask;
|
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
|
import android.os.IBinder;
|
||||||
import android.os.Parcelable;
|
import android.os.Parcelable;
|
||||||
import android.os.PowerManager;
|
import android.os.PowerManager;
|
||||||
|
import android.support.v4.app.LoaderManager.LoaderCallbacks;
|
||||||
|
import android.support.v4.content.CursorLoader;
|
||||||
|
import android.support.v4.content.Loader;
|
||||||
import android.text.format.DateFormat;
|
import android.text.format.DateFormat;
|
||||||
import android.text.util.Linkify;
|
import android.text.util.Linkify;
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
@ -31,51 +33,43 @@ import com.actionbarsherlock.view.ActionMode;
|
|||||||
import com.actionbarsherlock.view.Menu;
|
import com.actionbarsherlock.view.Menu;
|
||||||
import com.actionbarsherlock.view.MenuInflater;
|
import com.actionbarsherlock.view.MenuInflater;
|
||||||
import com.actionbarsherlock.view.MenuItem;
|
import com.actionbarsherlock.view.MenuItem;
|
||||||
|
import com.dougkeen.bart.EtdService.EtdServiceBinder;
|
||||||
|
import com.dougkeen.bart.EtdService.EtdServiceListener;
|
||||||
import com.dougkeen.bart.controls.CountdownTextView;
|
import com.dougkeen.bart.controls.CountdownTextView;
|
||||||
import com.dougkeen.bart.controls.Ticker;
|
import com.dougkeen.bart.controls.Ticker;
|
||||||
import com.dougkeen.bart.data.RoutesColumns;
|
import com.dougkeen.bart.data.RoutesColumns;
|
||||||
import com.dougkeen.bart.model.Constants;
|
import com.dougkeen.bart.model.Constants;
|
||||||
import com.dougkeen.bart.model.Departure;
|
import com.dougkeen.bart.model.Departure;
|
||||||
import com.dougkeen.bart.model.RealTimeDepartures;
|
|
||||||
import com.dougkeen.bart.model.ScheduleInformation;
|
|
||||||
import com.dougkeen.bart.model.ScheduleItem;
|
|
||||||
import com.dougkeen.bart.model.Station;
|
import com.dougkeen.bart.model.Station;
|
||||||
import com.dougkeen.bart.model.StationPair;
|
import com.dougkeen.bart.model.StationPair;
|
||||||
import com.dougkeen.bart.model.TextProvider;
|
import com.dougkeen.bart.model.TextProvider;
|
||||||
import com.dougkeen.bart.networktasks.GetRealTimeDeparturesTask;
|
|
||||||
import com.dougkeen.bart.networktasks.GetScheduleInformationTask;
|
|
||||||
|
|
||||||
public class ViewDeparturesActivity extends SherlockFragmentActivity {
|
public class ViewDeparturesActivity extends SherlockFragmentActivity implements
|
||||||
|
EtdServiceListener {
|
||||||
|
|
||||||
private static final int UNCERTAINTY_THRESHOLD = 17;
|
private static final int LOADER_ID = 123;
|
||||||
|
|
||||||
private Uri mUri;
|
private Uri mUri;
|
||||||
|
|
||||||
private Station mOrigin;
|
private Station mOrigin;
|
||||||
private Station mDestination;
|
private Station mDestination;
|
||||||
private int mAverageTripLength;
|
|
||||||
private int mAverageTripSampleCount;
|
|
||||||
|
|
||||||
private Departure mSelectedDeparture;
|
private Departure mSelectedDeparture;
|
||||||
private Departure mBoardedDeparture;
|
private Departure mBoardedDeparture;
|
||||||
|
|
||||||
private DepartureArrayAdapter mDeparturesAdapter;
|
private DepartureArrayAdapter mDeparturesAdapter;
|
||||||
|
|
||||||
private ScheduleInformation mLatestScheduleInfo;
|
|
||||||
|
|
||||||
private TextView mEmptyView;
|
private TextView mEmptyView;
|
||||||
private ProgressBar mProgress;
|
private ProgressBar mProgress;
|
||||||
|
|
||||||
private AsyncTask<StationPair, Integer, RealTimeDepartures> mGetDeparturesTask;
|
|
||||||
private AsyncTask<StationPair, Integer, ScheduleInformation> mGetScheduleInformationTask;
|
|
||||||
|
|
||||||
private PowerManager.WakeLock mWakeLock;
|
private PowerManager.WakeLock mWakeLock;
|
||||||
|
|
||||||
private boolean mDepartureFetchIsPending;
|
|
||||||
private boolean mScheduleFetchIsPending;
|
|
||||||
|
|
||||||
private ActionMode mActionMode;
|
private ActionMode mActionMode;
|
||||||
|
|
||||||
|
private EtdService mEtdService;
|
||||||
|
|
||||||
|
private boolean mBound = false;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void onCreate(Bundle savedInstanceState) {
|
protected void onCreate(Bundle savedInstanceState) {
|
||||||
super.onCreate(savedInstanceState);
|
super.onCreate(savedInstanceState);
|
||||||
@ -89,23 +83,56 @@ public class ViewDeparturesActivity extends SherlockFragmentActivity {
|
|||||||
mUri = intent.getData();
|
mUri = intent.getData();
|
||||||
}
|
}
|
||||||
|
|
||||||
Cursor cursor = managedQuery(mUri, new String[] {
|
final Uri uri = mUri;
|
||||||
RoutesColumns.FROM_STATION.string,
|
|
||||||
RoutesColumns.TO_STATION.string,
|
|
||||||
RoutesColumns.AVERAGE_TRIP_LENGTH.string,
|
|
||||||
RoutesColumns.AVERAGE_TRIP_SAMPLE_COUNT.string }, null, null,
|
|
||||||
null);
|
|
||||||
|
|
||||||
if (!cursor.moveToFirst()) {
|
if (savedInstanceState != null
|
||||||
throw new IllegalStateException("URI not found: " + mUri.toString());
|
&& savedInstanceState.containsKey("origin")
|
||||||
|
&& savedInstanceState.containsKey("destination")) {
|
||||||
|
mOrigin = Station.getByAbbreviation(savedInstanceState
|
||||||
|
.getString("origin"));
|
||||||
|
mDestination = Station.getByAbbreviation(savedInstanceState
|
||||||
|
.getString("destination"));
|
||||||
|
} else {
|
||||||
|
getSupportLoaderManager().initLoader(LOADER_ID, null,
|
||||||
|
new LoaderCallbacks<Cursor>() {
|
||||||
|
@Override
|
||||||
|
public Loader<Cursor> onCreateLoader(int id, Bundle args) {
|
||||||
|
return new CursorLoader(
|
||||||
|
ViewDeparturesActivity.this, uri,
|
||||||
|
new String[] {
|
||||||
|
RoutesColumns.FROM_STATION.string,
|
||||||
|
RoutesColumns.TO_STATION.string },
|
||||||
|
null, null, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onLoadFinished(Loader<Cursor> loader,
|
||||||
|
Cursor cursor) {
|
||||||
|
if (!cursor.moveToFirst()) {
|
||||||
|
Log.wtf(Constants.TAG,
|
||||||
|
"Couldn't find Route record for the current Activity");
|
||||||
|
}
|
||||||
|
mOrigin = Station.getByAbbreviation(cursor
|
||||||
|
.getString(0));
|
||||||
|
mDestination = Station.getByAbbreviation(cursor
|
||||||
|
.getString(1));
|
||||||
|
cursor.close();
|
||||||
|
((TextView) findViewById(R.id.listTitle))
|
||||||
|
.setText(mOrigin.name + " to "
|
||||||
|
+ mDestination.name);
|
||||||
|
if (mBound && mEtdService != null)
|
||||||
|
mEtdService
|
||||||
|
.registerListener(ViewDeparturesActivity.this);
|
||||||
|
|
||||||
|
getSupportLoaderManager().destroyLoader(LOADER_ID);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onLoaderReset(Loader<Cursor> loader) {
|
||||||
|
// ignore
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
mOrigin = Station.getByAbbreviation(cursor.getString(0));
|
|
||||||
mDestination = Station.getByAbbreviation(cursor.getString(1));
|
|
||||||
mAverageTripLength = cursor.getInt(2);
|
|
||||||
mAverageTripSampleCount = cursor.getInt(3);
|
|
||||||
|
|
||||||
((TextView) findViewById(R.id.listTitle)).setText(mOrigin.name + " to "
|
|
||||||
+ mDestination.name);
|
|
||||||
|
|
||||||
mEmptyView = (TextView) findViewById(android.R.id.empty);
|
mEmptyView = (TextView) findViewById(android.R.id.empty);
|
||||||
mEmptyView.setText(R.string.departure_wait_message);
|
mEmptyView.setText(R.string.departure_wait_message);
|
||||||
@ -115,13 +142,12 @@ public class ViewDeparturesActivity extends SherlockFragmentActivity {
|
|||||||
mDeparturesAdapter = new DepartureArrayAdapter(this,
|
mDeparturesAdapter = new DepartureArrayAdapter(this,
|
||||||
R.layout.departure_listing);
|
R.layout.departure_listing);
|
||||||
if (savedInstanceState != null) {
|
if (savedInstanceState != null) {
|
||||||
|
|
||||||
if (savedInstanceState.containsKey("departures")) {
|
if (savedInstanceState.containsKey("departures")) {
|
||||||
for (Parcelable departure : savedInstanceState
|
for (Parcelable departure : savedInstanceState
|
||||||
.getParcelableArray("departures")) {
|
.getParcelableArray("departures")) {
|
||||||
mDeparturesAdapter.add((Departure) departure);
|
mDeparturesAdapter.add((Departure) departure);
|
||||||
mDeparturesAdapter.notifyDataSetChanged();
|
|
||||||
}
|
}
|
||||||
|
mDeparturesAdapter.notifyDataSetChanged();
|
||||||
}
|
}
|
||||||
if (savedInstanceState.containsKey("boardedDeparture")) {
|
if (savedInstanceState.containsKey("boardedDeparture")) {
|
||||||
mBoardedDeparture = (Departure) savedInstanceState
|
mBoardedDeparture = (Departure) savedInstanceState
|
||||||
@ -165,39 +191,40 @@ public class ViewDeparturesActivity extends SherlockFragmentActivity {
|
|||||||
return (AdapterView<ListAdapter>) findViewById(android.R.id.list);
|
return (AdapterView<ListAdapter>) findViewById(android.R.id.list);
|
||||||
}
|
}
|
||||||
|
|
||||||
private DepartureArrayAdapter mListAdapter;
|
private final ServiceConnection mConnection = new ServiceConnection() {
|
||||||
|
@Override
|
||||||
|
public void onServiceDisconnected(ComponentName name) {
|
||||||
|
mEtdService = null;
|
||||||
|
mBound = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onServiceConnected(ComponentName name, IBinder service) {
|
||||||
|
mEtdService = ((EtdServiceBinder) service).getService();
|
||||||
|
mBound = true;
|
||||||
|
if (getStationPair() != null) {
|
||||||
|
mEtdService.registerListener(ViewDeparturesActivity.this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
protected DepartureArrayAdapter getListAdapter() {
|
protected DepartureArrayAdapter getListAdapter() {
|
||||||
return mListAdapter;
|
return mDeparturesAdapter;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void setListAdapter(DepartureArrayAdapter adapter) {
|
protected void setListAdapter(DepartureArrayAdapter adapter) {
|
||||||
mListAdapter = adapter;
|
mDeparturesAdapter = adapter;
|
||||||
getListView().setAdapter(mListAdapter);
|
getListView().setAdapter(mDeparturesAdapter);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void onPause() {
|
protected void onStop() {
|
||||||
cancelDataFetch();
|
super.onStop();
|
||||||
|
if (mEtdService != null)
|
||||||
|
mEtdService.unregisterListener(this);
|
||||||
|
if (mBound)
|
||||||
|
unbindService(mConnection);
|
||||||
Ticker.getInstance().stopTicking();
|
Ticker.getInstance().stopTicking();
|
||||||
super.onPause();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void onDestroy() {
|
|
||||||
cancelDataFetch();
|
|
||||||
super.onDestroy();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void cancelDataFetch() {
|
|
||||||
if (mGetDeparturesTask != null) {
|
|
||||||
mGetDeparturesTask.cancel(true);
|
|
||||||
mDepartureFetchIsPending = false;
|
|
||||||
}
|
|
||||||
if (mGetScheduleInformationTask != null) {
|
|
||||||
mGetScheduleInformationTask.cancel(true);
|
|
||||||
mScheduleFetchIsPending = false;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -211,11 +238,15 @@ public class ViewDeparturesActivity extends SherlockFragmentActivity {
|
|||||||
outState.putParcelable("boardedDeparture", mBoardedDeparture);
|
outState.putParcelable("boardedDeparture", mBoardedDeparture);
|
||||||
outState.putParcelable("selectedDeparture", mSelectedDeparture);
|
outState.putParcelable("selectedDeparture", mSelectedDeparture);
|
||||||
outState.putBoolean("hasActionMode", mActionMode != null);
|
outState.putBoolean("hasActionMode", mActionMode != null);
|
||||||
|
outState.putString("origin", mOrigin.abbreviation);
|
||||||
|
outState.putString("destination", mDestination.abbreviation);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void onResume() {
|
protected void onStart() {
|
||||||
super.onResume();
|
super.onStart();
|
||||||
|
bindService(new Intent(this, EtdService.class), mConnection,
|
||||||
|
Context.BIND_AUTO_CREATE);
|
||||||
Ticker.getInstance().startTicking();
|
Ticker.getInstance().startTicking();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -223,9 +254,6 @@ public class ViewDeparturesActivity extends SherlockFragmentActivity {
|
|||||||
public void onWindowFocusChanged(boolean hasFocus) {
|
public void onWindowFocusChanged(boolean hasFocus) {
|
||||||
super.onWindowFocusChanged(hasFocus);
|
super.onWindowFocusChanged(hasFocus);
|
||||||
if (hasFocus) {
|
if (hasFocus) {
|
||||||
if (!mDepartureFetchIsPending) {
|
|
||||||
fetchLatestDepartures();
|
|
||||||
}
|
|
||||||
PowerManager powerManager = (PowerManager) getSystemService(Context.POWER_SERVICE);
|
PowerManager powerManager = (PowerManager) getSystemService(Context.POWER_SERVICE);
|
||||||
mWakeLock = powerManager
|
mWakeLock = powerManager
|
||||||
.newWakeLock(PowerManager.SCREEN_DIM_WAKE_LOCK,
|
.newWakeLock(PowerManager.SCREEN_DIM_WAKE_LOCK,
|
||||||
@ -237,381 +265,6 @@ public class ViewDeparturesActivity extends SherlockFragmentActivity {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean mIgnoreDepartureDirection = false;
|
|
||||||
|
|
||||||
private void fetchLatestDepartures() {
|
|
||||||
if (!hasWindowFocus())
|
|
||||||
return;
|
|
||||||
if (mGetDeparturesTask != null
|
|
||||||
&& mGetDeparturesTask.getStatus().equals(
|
|
||||||
AsyncTask.Status.RUNNING)) {
|
|
||||||
// Don't overlap fetches
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
mGetDeparturesTask = new GetRealTimeDeparturesTask(
|
|
||||||
mIgnoreDepartureDirection) {
|
|
||||||
@Override
|
|
||||||
public void onResult(RealTimeDepartures result) {
|
|
||||||
mDepartureFetchIsPending = false;
|
|
||||||
Log.i(Constants.TAG, "Processing data from server");
|
|
||||||
mProgress.setVisibility(View.INVISIBLE);
|
|
||||||
processLatestDepartures(result);
|
|
||||||
Log.i(Constants.TAG, "Done processing data from server");
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onError(Exception e) {
|
|
||||||
mDepartureFetchIsPending = false;
|
|
||||||
Log.w(Constants.TAG, e.getMessage(), e);
|
|
||||||
Toast.makeText(ViewDeparturesActivity.this,
|
|
||||||
R.string.could_not_connect, Toast.LENGTH_LONG).show();
|
|
||||||
mEmptyView.setText(R.string.could_not_connect);
|
|
||||||
mProgress.setVisibility(View.INVISIBLE);
|
|
||||||
// Try again in 60s
|
|
||||||
scheduleDepartureFetch(60000);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
Log.i(Constants.TAG, "Fetching data from server");
|
|
||||||
mGetDeparturesTask.execute(new StationPair(mOrigin, mDestination));
|
|
||||||
mProgress.setVisibility(View.VISIBLE);
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
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();
|
|
||||||
mEmptyView.setText(R.string.could_not_connect);
|
|
||||||
mProgress.setVisibility(View.GONE);
|
|
||||||
|
|
||||||
// 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) {
|
|
||||||
if (result.getDepartures().isEmpty()) {
|
|
||||||
result.includeTransferRoutes();
|
|
||||||
}
|
|
||||||
if (result.getDepartures().isEmpty()) {
|
|
||||||
result.includeDoubleTransferRoutes();
|
|
||||||
}
|
|
||||||
if (result.getDepartures().isEmpty() && !mIgnoreDepartureDirection) {
|
|
||||||
// Let's try again, ignoring direction (this sometimes comes up when
|
|
||||||
// you travel between Millbrae and SFO)
|
|
||||||
mIgnoreDepartureDirection = true;
|
|
||||||
scheduleDepartureFetch(50);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (result.getDepartures().isEmpty()) {
|
|
||||||
final TextView textView = mEmptyView;
|
|
||||||
textView.setText(R.string.no_data_message);
|
|
||||||
mProgress.setVisibility(View.GONE);
|
|
||||||
Linkify.addLinks(textView, Linkify.WEB_URLS);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
boolean needsBetterAccuracy = false;
|
|
||||||
|
|
||||||
// Keep track of first departure, since we'll request another quick
|
|
||||||
// refresh if it has departed.
|
|
||||||
Departure firstDeparture = null;
|
|
||||||
|
|
||||||
final List<Departure> departures = result.getDepartures();
|
|
||||||
if (mDeparturesAdapter.isEmpty()) {
|
|
||||||
// Just copy everything to the adapter
|
|
||||||
for (Departure departure : departures) {
|
|
||||||
if (firstDeparture == null) {
|
|
||||||
firstDeparture = departure;
|
|
||||||
}
|
|
||||||
mDeparturesAdapter.add(departure);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Since all the departures are new, we'll definitely need better
|
|
||||||
// accuracy
|
|
||||||
needsBetterAccuracy = true;
|
|
||||||
} else {
|
|
||||||
// Let's merge the latest departure list with the adapter
|
|
||||||
int adapterIndex = -1;
|
|
||||||
for (Departure departure : departures) {
|
|
||||||
adapterIndex++;
|
|
||||||
Departure existingDeparture = null;
|
|
||||||
if (adapterIndex < mDeparturesAdapter.getCount()) {
|
|
||||||
existingDeparture = mDeparturesAdapter
|
|
||||||
.getItem(adapterIndex);
|
|
||||||
}
|
|
||||||
// Looks for departures at the beginning of the adapter that
|
|
||||||
// aren't in the latest list of departures
|
|
||||||
while (existingDeparture != null
|
|
||||||
&& !departure.equals(existingDeparture)) {
|
|
||||||
// Remove old departure
|
|
||||||
mDeparturesAdapter.remove(existingDeparture);
|
|
||||||
if (adapterIndex < mDeparturesAdapter.getCount()) {
|
|
||||||
// Try again with next departure (keep in mind the next
|
|
||||||
// departure is now at the current index, since we
|
|
||||||
// removed a member)
|
|
||||||
existingDeparture = mDeparturesAdapter
|
|
||||||
.getItem(adapterIndex);
|
|
||||||
} else {
|
|
||||||
// Reached the end of the adapter... give up
|
|
||||||
existingDeparture = null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Merge the estimate if we found a matching departure,
|
|
||||||
// otherwise add a new one to the adapter
|
|
||||||
if (existingDeparture != null) {
|
|
||||||
existingDeparture.mergeEstimate(departure);
|
|
||||||
} else {
|
|
||||||
mDeparturesAdapter.add(departure);
|
|
||||||
existingDeparture = departure;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Set first departure
|
|
||||||
if (firstDeparture == null) {
|
|
||||||
firstDeparture = existingDeparture;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check if estimate is accurate enough
|
|
||||||
if (existingDeparture.getUncertaintySeconds() > UNCERTAINTY_THRESHOLD) {
|
|
||||||
needsBetterAccuracy = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
mDeparturesAdapter.notifyDataSetChanged();
|
|
||||||
requestScheduleIfNecessary();
|
|
||||||
|
|
||||||
if (hasWindowFocus() && firstDeparture != null) {
|
|
||||||
if (needsBetterAccuracy || firstDeparture.hasDeparted()) {
|
|
||||||
// Get more data in 20s
|
|
||||||
scheduleDepartureFetch(20000);
|
|
||||||
} else {
|
|
||||||
// Get more 90 seconds before next train arrives, right when
|
|
||||||
// next train arrives, or 3 minutes, whichever is sooner
|
|
||||||
final int intervalUntilNextDeparture = firstDeparture
|
|
||||||
.getMinSecondsLeft() * 1000;
|
|
||||||
final int alternativeInterval = 3 * 60 * 1000;
|
|
||||||
|
|
||||||
int interval = intervalUntilNextDeparture;
|
|
||||||
if (intervalUntilNextDeparture > 95000
|
|
||||||
&& intervalUntilNextDeparture < alternativeInterval) {
|
|
||||||
interval = interval - 90 * 1000;
|
|
||||||
} else if (intervalUntilNextDeparture > alternativeInterval) {
|
|
||||||
interval = alternativeInterval;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (interval < 0) {
|
|
||||||
interval = 20000;
|
|
||||||
}
|
|
||||||
|
|
||||||
scheduleDepartureFetch(interval);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void requestScheduleIfNecessary() {
|
|
||||||
// Bail if there's nothing to match schedules to
|
|
||||||
if (mDeparturesAdapter.getCount() == 0) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Fetch if we don't have anything at all
|
|
||||||
if (mLatestScheduleInfo == null) {
|
|
||||||
fetchLatestSchedule();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Otherwise, check if the latest departure doesn't have schedule
|
|
||||||
// info... if not, fetch
|
|
||||||
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();
|
|
||||||
|
|
||||||
// Let's get smallest interval between departures
|
|
||||||
int smallestDepartureInterval = 0;
|
|
||||||
long previousDepartureTime = 0;
|
|
||||||
for (int departureIndex = 0; departureIndex < departuresCount; departureIndex++) {
|
|
||||||
Departure departure = mDeparturesAdapter.getItem(departureIndex);
|
|
||||||
if (previousDepartureTime == 0) {
|
|
||||||
previousDepartureTime = departure.getMeanEstimate();
|
|
||||||
} else if (smallestDepartureInterval == 0) {
|
|
||||||
smallestDepartureInterval = (int) (departure.getMeanEstimate() - previousDepartureTime);
|
|
||||||
} else {
|
|
||||||
smallestDepartureInterval = Math
|
|
||||||
.min(smallestDepartureInterval,
|
|
||||||
(int) (departure.getMeanEstimate() - previousDepartureTime));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Match scheduled departures with real time departures in adapter
|
|
||||||
int lastSearchIndex = 0;
|
|
||||||
int tripCount = mLatestScheduleInfo.getTrips().size();
|
|
||||||
boolean departureUpdated = false;
|
|
||||||
Departure lastUnestimatedTransfer = null;
|
|
||||||
int departuresWithoutEstimates = 0;
|
|
||||||
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);
|
|
||||||
// Definitely not a match if they have different destinations
|
|
||||||
if (!departure.getDestination().abbreviation.equals(trip
|
|
||||||
.getTrainHeadStation())) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
long departTimeDiff = Math.abs(trip.getDepartureTime()
|
|
||||||
- departure.getMeanEstimate());
|
|
||||||
final long millisUntilTripDeparture = trip.getDepartureTime()
|
|
||||||
- System.currentTimeMillis();
|
|
||||||
final int equalityTolerance = (departure.getOrigin() != null) ? NumberUtils
|
|
||||||
.max(departure.getOrigin().departureEqualityTolerance,
|
|
||||||
ScheduleItem.SCHEDULE_ITEM_DEPARTURE_EQUALS_TOLERANCE,
|
|
||||||
smallestDepartureInterval)
|
|
||||||
: ScheduleItem.SCHEDULE_ITEM_DEPARTURE_EQUALS_TOLERANCE;
|
|
||||||
if (departure.getOrigin() != null
|
|
||||||
&& departure.getOrigin().longStationLinger
|
|
||||||
&& departure.hasDeparted()
|
|
||||||
&& millisUntilTripDeparture > 0
|
|
||||||
&& millisUntilTripDeparture < equalityTolerance) {
|
|
||||||
departure.setArrivalTimeOverride(trip.getArrivalTime());
|
|
||||||
lastSearchIndex = i;
|
|
||||||
departureUpdated = true;
|
|
||||||
if (lastUnestimatedTransfer != null) {
|
|
||||||
lastUnestimatedTransfer.setArrivalTimeOverride(trip
|
|
||||||
.getArrivalTime());
|
|
||||||
departuresWithoutEstimates--;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
} else if (departTimeDiff <= (equalityTolerance + departure
|
|
||||||
.getUncertaintySeconds() * 1000)
|
|
||||||
&& departure.getEstimatedTripTime() != trip
|
|
||||||
.getTripLength()
|
|
||||||
&& !(departure.getOrigin().longStationLinger && departure
|
|
||||||
.hasDeparted())) {
|
|
||||||
departure.setEstimatedTripTime(trip.getTripLength());
|
|
||||||
lastSearchIndex = i;
|
|
||||||
departureUpdated = true;
|
|
||||||
if (lastUnestimatedTransfer != null) {
|
|
||||||
lastUnestimatedTransfer.setArrivalTimeOverride(trip
|
|
||||||
.getArrivalTime());
|
|
||||||
departuresWithoutEstimates--;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Don't estimate for non-scheduled transfers
|
|
||||||
if (!departure.getRequiresTransfer()) {
|
|
||||||
if (!departure.hasEstimatedTripTime() && localAverageLength > 0) {
|
|
||||||
// Use the average we just calculated if available
|
|
||||||
departure.setEstimatedTripTime(localAverageLength);
|
|
||||||
} else if (!departure.hasEstimatedTripTime()) {
|
|
||||||
// Otherwise just assume the global average
|
|
||||||
departure.setEstimatedTripTime(mAverageTripLength);
|
|
||||||
}
|
|
||||||
} else if (departure.getRequiresTransfer()
|
|
||||||
&& !departure.hasAnyArrivalEstimate()) {
|
|
||||||
lastUnestimatedTransfer = departure;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!departure.hasAnyArrivalEstimate()) {
|
|
||||||
departuresWithoutEstimates++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
|
|
||||||
// If we still have some departures without estimates, try again later
|
|
||||||
if (departuresWithoutEstimates > 0) {
|
|
||||||
scheduleScheduleInfoFetch(20000);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void scheduleDepartureFetch(int millisUntilExecute) {
|
|
||||||
if (!mDepartureFetchIsPending) {
|
|
||||||
postDelayed(new Runnable() {
|
|
||||||
public void run() {
|
|
||||||
fetchLatestDepartures();
|
|
||||||
}
|
|
||||||
}, millisUntilExecute);
|
|
||||||
mDepartureFetchIsPending = true;
|
|
||||||
Log.i(Constants.TAG, "Scheduled another departure fetch in "
|
|
||||||
+ millisUntilExecute / 1000 + "s");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void scheduleScheduleInfoFetch(int millisUntilExecute) {
|
|
||||||
if (!mScheduleFetchIsPending) {
|
|
||||||
postDelayed(new Runnable() {
|
|
||||||
public void run() {
|
|
||||||
fetchLatestSchedule();
|
|
||||||
}
|
|
||||||
}, millisUntilExecute);
|
|
||||||
mScheduleFetchIsPending = true;
|
|
||||||
Log.i(Constants.TAG, "Scheduled another schedule fetch in "
|
|
||||||
+ millisUntilExecute / 1000 + "s");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private boolean postDelayed(Runnable runnable, long delayMillis) {
|
|
||||||
return mEmptyView.postDelayed(runnable, delayMillis);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean onCreateOptionsMenu(Menu menu) {
|
public boolean onCreateOptionsMenu(Menu menu) {
|
||||||
MenuInflater inflater = getSupportMenuInflater();
|
MenuInflater inflater = getSupportMenuInflater();
|
||||||
@ -744,4 +397,105 @@ public class ViewDeparturesActivity extends SherlockFragmentActivity {
|
|||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onETDChanged(final List<Departure> departures) {
|
||||||
|
runOnUiThread(new Runnable() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
if (departures.isEmpty()) {
|
||||||
|
final TextView textView = mEmptyView;
|
||||||
|
textView.setText(R.string.no_data_message);
|
||||||
|
mProgress.setVisibility(View.INVISIBLE);
|
||||||
|
Linkify.addLinks(textView, Linkify.WEB_URLS);
|
||||||
|
} else {
|
||||||
|
// Merge lists
|
||||||
|
if (mDeparturesAdapter.getCount() > 0) {
|
||||||
|
int adapterIndex = -1;
|
||||||
|
for (Departure departure : departures) {
|
||||||
|
adapterIndex++;
|
||||||
|
Departure existingDeparture = null;
|
||||||
|
if (adapterIndex < mDeparturesAdapter.getCount()) {
|
||||||
|
existingDeparture = mDeparturesAdapter
|
||||||
|
.getItem(adapterIndex);
|
||||||
|
}
|
||||||
|
while (existingDeparture != null
|
||||||
|
&& !departure.equals(existingDeparture)) {
|
||||||
|
mDeparturesAdapter.remove(existingDeparture);
|
||||||
|
if (adapterIndex < mDeparturesAdapter
|
||||||
|
.getCount()) {
|
||||||
|
existingDeparture = mDeparturesAdapter
|
||||||
|
.getItem(adapterIndex);
|
||||||
|
} else {
|
||||||
|
existingDeparture = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (existingDeparture != null) {
|
||||||
|
existingDeparture.mergeEstimate(departure);
|
||||||
|
} else {
|
||||||
|
mDeparturesAdapter.add(departure);
|
||||||
|
existingDeparture = departure;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
final DepartureArrayAdapter listAdapter = getListAdapter();
|
||||||
|
listAdapter.clear();
|
||||||
|
// addAll() method isn't available until API level 11
|
||||||
|
for (Departure departure : departures) {
|
||||||
|
listAdapter.add(departure);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (mBoardedDeparture != null) {
|
||||||
|
for (Departure departure : departures) {
|
||||||
|
if (departure.equals(mBoardedDeparture)) {
|
||||||
|
mBoardedDeparture = departure;
|
||||||
|
refreshBoardedDeparture();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
getListAdapter().notifyDataSetChanged();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onError(final String errorMessage) {
|
||||||
|
runOnUiThread(new Runnable() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
Toast.makeText(ViewDeparturesActivity.this, errorMessage,
|
||||||
|
Toast.LENGTH_LONG).show();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onRequestStarted() {
|
||||||
|
runOnUiThread(new Runnable() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
mProgress.setVisibility(View.VISIBLE);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onRequestEnded() {
|
||||||
|
runOnUiThread(new Runnable() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
mProgress.setVisibility(View.INVISIBLE);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public StationPair getStationPair() {
|
||||||
|
if (mOrigin == null || mDestination == null)
|
||||||
|
return null;
|
||||||
|
return new StationPair(mOrigin, mDestination);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
package com.dougkeen.bart;
|
package com.dougkeen.bart;
|
||||||
|
|
||||||
import android.content.Intent;
|
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.webkit.WebView;
|
import android.webkit.WebView;
|
||||||
|
|
||||||
@ -8,7 +7,6 @@ import com.actionbarsherlock.app.SherlockActivity;
|
|||||||
import com.actionbarsherlock.view.Menu;
|
import com.actionbarsherlock.view.Menu;
|
||||||
import com.actionbarsherlock.view.MenuInflater;
|
import com.actionbarsherlock.view.MenuInflater;
|
||||||
import com.actionbarsherlock.view.MenuItem;
|
import com.actionbarsherlock.view.MenuItem;
|
||||||
import com.dougkeen.bart.model.Constants;
|
|
||||||
|
|
||||||
public class ViewMapActivity extends SherlockActivity {
|
public class ViewMapActivity extends SherlockActivity {
|
||||||
|
|
||||||
|
@ -46,7 +46,13 @@ public class TimedTextSwitcher extends TextSwitcher implements
|
|||||||
Ticker.getInstance().addSubscriber(this);
|
Ticker.getInstance().addSubscriber(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
private String mLastText;
|
private CharSequence mLastText;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setCurrentText(CharSequence text) {
|
||||||
|
mLastText = text;
|
||||||
|
super.setCurrentText(text);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onTick(long tickNumber) {
|
public void onTick(long tickNumber) {
|
||||||
|
@ -2,8 +2,6 @@ package com.dougkeen.bart.data;
|
|||||||
|
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
|
|
||||||
import com.dougkeen.bart.model.Constants;
|
|
||||||
|
|
||||||
import android.content.ContentProvider;
|
import android.content.ContentProvider;
|
||||||
import android.content.ContentUris;
|
import android.content.ContentUris;
|
||||||
import android.content.ContentValues;
|
import android.content.ContentValues;
|
||||||
@ -17,6 +15,8 @@ import android.database.sqlite.SQLiteQueryBuilder;
|
|||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
import android.text.TextUtils;
|
import android.text.TextUtils;
|
||||||
|
|
||||||
|
import com.dougkeen.bart.model.Constants;
|
||||||
|
|
||||||
public class BartContentProvider extends ContentProvider {
|
public class BartContentProvider extends ContentProvider {
|
||||||
|
|
||||||
private static final UriMatcher sUriMatcher;
|
private static final UriMatcher sUriMatcher;
|
||||||
@ -95,14 +95,27 @@ public class BartContentProvider extends ContentProvider {
|
|||||||
int match = sUriMatcher.match(uri);
|
int match = sUriMatcher.match(uri);
|
||||||
|
|
||||||
if (match == ARBITRARY_ROUTE) {
|
if (match == ARBITRARY_ROUTE) {
|
||||||
|
final String origin = uri.getPathSegments().get(1);
|
||||||
|
final String destination = uri.getPathSegments().get(2);
|
||||||
|
|
||||||
|
qb.setTables(DatabaseHelper.FAVORITES_TABLE_NAME);
|
||||||
|
qb.setProjectionMap(sFavoritesProjectionMap);
|
||||||
|
qb.appendWhere(String.format("%s = '%s' AND %s = '%s'",
|
||||||
|
RoutesColumns.FROM_STATION, origin,
|
||||||
|
RoutesColumns.TO_STATION, destination));
|
||||||
|
Cursor query = qb.query(db, projection, selection, selectionArgs,
|
||||||
|
null, null, sortOrder);
|
||||||
|
if (query.getCount() > 0)
|
||||||
|
return query;
|
||||||
|
|
||||||
MatrixCursor returnCursor = new MatrixCursor(projection);
|
MatrixCursor returnCursor = new MatrixCursor(projection);
|
||||||
RowBuilder newRow = returnCursor.newRow();
|
RowBuilder newRow = returnCursor.newRow();
|
||||||
|
|
||||||
for (String column : projection) {
|
for (String column : projection) {
|
||||||
if (column.equals(RoutesColumns.FROM_STATION.string)) {
|
if (column.equals(RoutesColumns.FROM_STATION.string)) {
|
||||||
newRow.add(uri.getPathSegments().get(1));
|
newRow.add(origin);
|
||||||
} else if (column.equals(RoutesColumns.TO_STATION.string)) {
|
} else if (column.equals(RoutesColumns.TO_STATION.string)) {
|
||||||
newRow.add(uri.getPathSegments().get(2));
|
newRow.add(destination);
|
||||||
} else {
|
} else {
|
||||||
newRow.add(null);
|
newRow.add(null);
|
||||||
}
|
}
|
||||||
@ -204,6 +217,24 @@ public class BartContentProvider extends ContentProvider {
|
|||||||
+ ')' : ""), whereArgs);
|
+ ')' : ""), whereArgs);
|
||||||
getContext().getContentResolver().notifyChange(uri, null);
|
getContext().getContentResolver().notifyChange(uri, null);
|
||||||
return count;
|
return count;
|
||||||
|
} else if (match == ARBITRARY_ROUTE) {
|
||||||
|
// Get the route with the origin and destination provided, and
|
||||||
|
// simply delegate to the previous log branch. If the given route
|
||||||
|
// doesn't exist, do nothing.
|
||||||
|
String origin = uri.getPathSegments().get(1);
|
||||||
|
String destination = uri.getPathSegments().get(2);
|
||||||
|
|
||||||
|
Cursor query = db.query(DatabaseHelper.FAVORITES_TABLE_NAME,
|
||||||
|
new String[] { RoutesColumns._ID.string },
|
||||||
|
RoutesColumns.FROM_STATION.string + "=? AND "
|
||||||
|
+ RoutesColumns.TO_STATION.string + "=?",
|
||||||
|
new String[] { origin, destination }, null, null, null);
|
||||||
|
|
||||||
|
if (query.moveToFirst()) {
|
||||||
|
return update(ContentUris.withAppendedId(
|
||||||
|
Constants.FAVORITE_CONTENT_URI, query.getLong(0)),
|
||||||
|
values, where, whereArgs);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
@ -302,11 +302,17 @@ public class Departure implements Parcelable, Comparable<Departure> {
|
|||||||
setMinEstimate(newMin);
|
setMinEstimate(newMin);
|
||||||
setMaxEstimate(newMax);
|
setMaxEstimate(newMax);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!hasAnyArrivalEstimate() && departure.hasAnyArrivalEstimate()) {
|
||||||
|
setArrivalTimeOverride(departure.getArrivalTimeOverride());
|
||||||
|
setEstimatedTripTime(departure.getEstimatedTripTime());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public int compareTo(Departure another) {
|
public int compareTo(Departure another) {
|
||||||
return (this.getMinutes() > another.getMinutes()) ? 1 : ((this
|
return (this.getMeanSecondsLeft() > another.getMeanSecondsLeft()) ? 1
|
||||||
.getMinutes() == another.getMinutes()) ? 0 : -1);
|
: ((this.getMeanSecondsLeft() == another.getMeanSecondsLeft()) ? 0
|
||||||
|
: -1);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -117,7 +117,8 @@ public class Route {
|
|||||||
} else if (routeLine.transferLine2 != null
|
} else if (routeLine.transferLine2 != null
|
||||||
&& viaLine.equals(routeLine.transferLine2)) {
|
&& viaLine.equals(routeLine.transferLine2)) {
|
||||||
return true;
|
return true;
|
||||||
} else if (requiresTransfer && transferLines != null && !transferLines.isEmpty()) {
|
} else if (requiresTransfer && transferLines != null
|
||||||
|
&& !transferLines.isEmpty()) {
|
||||||
return transferLines.contains(viaLine);
|
return transferLines.contains(viaLine);
|
||||||
} else {
|
} else {
|
||||||
int originIndex = viaLine.stations.indexOf(origin);
|
int originIndex = viaLine.stations.indexOf(origin);
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
package com.dougkeen.bart.model;
|
package com.dougkeen.bart.model;
|
||||||
|
|
||||||
|
|
||||||
public class StationPair {
|
public class StationPair {
|
||||||
public StationPair(Station origin, Station destination) {
|
public StationPair(Station origin, Station destination) {
|
||||||
super();
|
super();
|
||||||
@ -18,4 +17,36 @@ public class StationPair {
|
|||||||
public Station getDestination() {
|
public Station getDestination() {
|
||||||
return destination;
|
return destination;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public boolean isBetweenStations(Station station1, Station station2) {
|
||||||
|
return (origin.equals(station1) && destination.equals(station2))
|
||||||
|
|| (origin.equals(station2) && destination.equals(station1));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int hashCode() {
|
||||||
|
final int prime = 31;
|
||||||
|
int result = 1;
|
||||||
|
result = prime * result
|
||||||
|
+ ((destination == null) ? 0 : destination.hashCode());
|
||||||
|
result = prime * result + ((origin == null) ? 0 : origin.hashCode());
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object obj) {
|
||||||
|
if (this == obj)
|
||||||
|
return true;
|
||||||
|
if (obj == null)
|
||||||
|
return false;
|
||||||
|
if (getClass() != obj.getClass())
|
||||||
|
return false;
|
||||||
|
StationPair other = (StationPair) obj;
|
||||||
|
if (destination != other.destination)
|
||||||
|
return false;
|
||||||
|
if (origin != other.origin)
|
||||||
|
return false;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
@ -1,7 +1,6 @@
|
|||||||
package com.dougkeen.bart.networktasks;
|
package com.dougkeen.bart.networktasks;
|
||||||
|
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.Calendar;
|
|
||||||
import java.util.Date;
|
import java.util.Date;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user