Strong references in ETD listener, replaced SQLite junk with simple JSON file, annotation conversion

This commit is contained in:
Doug Keen 2013-03-24 16:11:43 -07:00
parent aaefc58702
commit 601d8516e6
21 changed files with 350 additions and 728 deletions

View File

@ -14,7 +14,7 @@
android:targetSdkVersion="14" /> android:targetSdkVersion="14" />
<application <application
android:name=".BartRunnerApplication" android:name=".BartRunnerApplication_"
android:icon="@drawable/icon" android:icon="@drawable/icon"
android:label="@string/app_name" android:label="@string/app_name"
android:theme="@style/AppTheme" > android:theme="@style/AppTheme" >
@ -61,17 +61,11 @@
android:label="@string/system_map" > android:label="@string/system_map" >
</activity> </activity>
<provider
android:name=".data.BartContentProvider"
android:authorities="com.dougkeen.bart.dataprovider"
android:exported="false"
android:label="BartRunner data provider" />
<service <service
android:name=".services.BoardedDepartureService" android:name=".services.BoardedDepartureService"
android:exported="false" /> android:exported="false" />
<service <service
android:name=".services.EtdService" android:name=".services.EtdService_"
android:exported="false" /> android:exported="false" />
<receiver <receiver

Binary file not shown.

BIN
libs/jackson-core-2.1.2.jar Normal file

Binary file not shown.

Binary file not shown.

View File

@ -16,21 +16,9 @@
android:layout_width="fill_parent" android:layout_width="fill_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_weight="1" android:layout_weight="1"
android:text="@string/empty_favorites_list_message"
android:visibility="gone" /> android:visibility="gone" />
<FrameLayout
android:id="@+id/progress"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:layout_weight="1000" >
<ProgressBar
style="?android:attr/progressBarStyleLarge"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center" />
</FrameLayout>
<TextView <TextView
android:id="@+id/alertMessages" android:id="@+id/alertMessages"
android:layout_width="wrap_content" android:layout_width="wrap_content"

View File

@ -4,6 +4,7 @@ import java.io.File;
import java.io.FileInputStream; import java.io.FileInputStream;
import java.io.FileOutputStream; import java.io.FileOutputStream;
import java.io.InputStream; import java.io.InputStream;
import java.util.List;
import org.apache.commons.io.IOUtils; import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.ObjectUtils; import org.apache.commons.lang3.ObjectUtils;
@ -15,9 +16,16 @@ import android.media.MediaPlayer;
import android.os.Parcel; import android.os.Parcel;
import android.util.Log; import android.util.Log;
import com.dougkeen.bart.data.DatabaseHelper;
import com.dougkeen.bart.data.FavoritesPersistence;
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.Station;
import com.dougkeen.bart.model.StationPair;
import com.googlecode.androidannotations.annotations.Bean;
import com.googlecode.androidannotations.annotations.EApplication;
@EApplication
public class BartRunnerApplication extends Application { public class BartRunnerApplication extends Application {
private static final int FIVE_MINUTES = 5 * 60 * 1000; private static final int FIVE_MINUTES = 5 * 60 * 1000;
@ -33,6 +41,53 @@ public class BartRunnerApplication extends Application {
private static Context context; private static Context context;
@Bean
FavoritesPersistence favoritesPersistenceContext;
private List<StationPair> favorites;
public void saveFavorites() {
if (favorites != null) {
favoritesPersistenceContext.persist(favorites);
}
}
public List<StationPair> getFavorites() {
if (favorites == null) {
favorites = favoritesPersistenceContext.restore();
if (favorites.isEmpty()) {
// Upgrade database, in case favorites are still in there
new DatabaseHelper(this).getReadableDatabase().close();
favorites = favoritesPersistenceContext.restore();
}
}
return favorites;
}
public void setFavorites(List<StationPair> favorites) {
this.favorites = favorites;
}
public StationPair getFavorite(Station origin, Station destination) {
for (StationPair favorite : getFavorites()) {
if (origin.equals(favorite.getOrigin())
&& destination.equals(favorite.getDestination())) {
return favorite;
}
}
return null;
}
public void addFavorite(StationPair favorite) {
getFavorites().add(favorite);
saveFavorites();
}
public void removeFavorite(StationPair favorite) {
getFavorites().remove(favorite);
saveFavorites();
}
@Override @Override
public void onCreate() { public void onCreate() {
super.onCreate(); super.onCreate();
@ -59,12 +114,13 @@ public class BartRunnerApplication extends Application {
InputStream inputStream = null; InputStream inputStream = null;
try { try {
inputStream = new FileInputStream(cachedDepartureFile); inputStream = new FileInputStream(cachedDepartureFile);
byte[] byteArray = IOUtils.toByteArray(inputStream); final byte[] byteArray = IOUtils.toByteArray(inputStream);
Parcel parcel = Parcel.obtain(); final Parcel parcel = Parcel.obtain();
parcel.unmarshall(byteArray, 0, byteArray.length); parcel.unmarshall(byteArray, 0, byteArray.length);
parcel.setDataPosition(0); parcel.setDataPosition(0);
Departure lastBoardedDeparture = Departure.CREATOR Departure lastBoardedDeparture = Departure.CREATOR
.createFromParcel(parcel); .createFromParcel(parcel);
parcel.recycle();
/* /*
* Check if the cached one is relatively recent. If so, * Check if the cached one is relatively recent. If so,

View File

@ -29,6 +29,13 @@ public abstract class AbstractRouteSelectionFragment extends DialogFragment {
mTitle = title; mTitle = title;
} }
@Override
public void setArguments(Bundle args) {
super.setArguments(args);
if (args.containsKey("title"))
mTitle = args.getString("title");
}
@Override @Override
public void onCreate(Bundle savedInstanceState) { public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState); super.onCreate(savedInstanceState);

View File

@ -1,14 +1,12 @@
package com.dougkeen.bart.activities; package com.dougkeen.bart.activities;
import android.content.ContentValues;
import android.view.View; import android.view.View;
import android.widget.CheckBox; import android.widget.CheckBox;
import com.dougkeen.bart.BartRunnerApplication; import com.dougkeen.bart.BartRunnerApplication;
import com.dougkeen.bart.R; import com.dougkeen.bart.R;
import com.dougkeen.bart.data.RoutesColumns;
import com.dougkeen.bart.model.Constants;
import com.dougkeen.bart.model.Station; import com.dougkeen.bart.model.Station;
import com.dougkeen.bart.model.StationPair;
public class AddRouteDialogFragment extends AbstractRouteSelectionFragment { public class AddRouteDialogFragment extends AbstractRouteSelectionFragment {
public AddRouteDialogFragment() { public AddRouteDialogFragment() {
@ -16,10 +14,6 @@ public class AddRouteDialogFragment extends AbstractRouteSelectionFragment {
R.string.add_route)); R.string.add_route));
} }
public AddRouteDialogFragment(String title) {
super(title);
}
@Override @Override
public void onStart() { public void onStart() {
super.onStart(); super.onStart();
@ -29,22 +23,12 @@ public class AddRouteDialogFragment extends AbstractRouteSelectionFragment {
@Override @Override
protected void onOkButtonClick(Station origin, Station destination) { protected void onOkButtonClick(Station origin, Station destination) {
ContentValues values = new ContentValues(); RoutesListActivity activity = (RoutesListActivity) getActivity();
values.put(RoutesColumns.FROM_STATION.string, origin.abbreviation); activity.addFavorite(new StationPair(origin, destination));
values.put(RoutesColumns.TO_STATION.string, destination.abbreviation);
getActivity().getContentResolver().insert(
Constants.FAVORITE_CONTENT_URI, values);
if (((CheckBox) getDialog().findViewById(R.id.return_checkbox)) if (((CheckBox) getDialog().findViewById(R.id.return_checkbox))
.isChecked()) { .isChecked()) {
values = new ContentValues(); activity.addFavorite(new StationPair(destination, origin));
values.put(RoutesColumns.FROM_STATION.string,
destination.abbreviation);
values.put(RoutesColumns.TO_STATION.string, origin.abbreviation);
getActivity().getContentResolver().insert(
Constants.FAVORITE_CONTENT_URI, values);
} }
dismiss(); dismiss();

View File

@ -14,10 +14,6 @@ public class QuickRouteDialogFragment extends AbstractRouteSelectionFragment {
R.string.quick_departure_lookup)); R.string.quick_departure_lookup));
} }
public QuickRouteDialogFragment(String title) {
super(title);
}
@Override @Override
protected void onOkButtonClick(Station origin, Station destination) { protected void onOkButtonClick(Station origin, Station destination) {
startActivity(new Intent(Intent.ACTION_VIEW, startActivity(new Intent(Intent.ACTION_VIEW,

View File

@ -3,16 +3,10 @@ package com.dougkeen.bart.activities;
import java.util.Calendar; import java.util.Calendar;
import java.util.TimeZone; import java.util.TimeZone;
import android.content.ContentUris;
import android.content.ContentValues;
import android.content.DialogInterface; import android.content.DialogInterface;
import android.content.Intent; import android.content.Intent;
import android.database.Cursor;
import android.net.Uri;
import android.os.Bundle; import android.os.Bundle;
import android.support.v4.app.LoaderManager.LoaderCallbacks; import android.support.v4.app.ActivityCompat;
import android.support.v4.content.CursorLoader;
import android.support.v4.content.Loader;
import android.util.Log; import android.util.Log;
import android.view.View; import android.view.View;
import android.widget.AdapterView; import android.widget.AdapterView;
@ -29,24 +23,24 @@ 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.BartRunnerApplication;
import com.dougkeen.bart.R; import com.dougkeen.bart.R;
import com.dougkeen.bart.controls.Ticker; import com.dougkeen.bart.controls.Ticker;
import com.dougkeen.bart.controls.Ticker.TickSubscriber; import com.dougkeen.bart.controls.Ticker.TickSubscriber;
import com.dougkeen.bart.data.CursorUtils;
import com.dougkeen.bart.data.FavoritesArrayAdapter; import com.dougkeen.bart.data.FavoritesArrayAdapter;
import com.dougkeen.bart.data.RoutesColumns;
import com.dougkeen.bart.model.Alert; import com.dougkeen.bart.model.Alert;
import com.dougkeen.bart.model.Alert.AlertList; import com.dougkeen.bart.model.Alert.AlertList;
import com.dougkeen.bart.model.Constants; import com.dougkeen.bart.model.Constants;
import com.dougkeen.bart.model.Station;
import com.dougkeen.bart.model.StationPair; import com.dougkeen.bart.model.StationPair;
import com.dougkeen.bart.networktasks.AlertsClient; import com.dougkeen.bart.networktasks.AlertsClient;
import com.dougkeen.bart.networktasks.ElevatorClient; import com.dougkeen.bart.networktasks.ElevatorClient;
import com.dougkeen.bart.networktasks.GetRouteFareTask; import com.dougkeen.bart.networktasks.GetRouteFareTask;
import com.googlecode.androidannotations.annotations.AfterViews; import com.googlecode.androidannotations.annotations.AfterViews;
import com.googlecode.androidannotations.annotations.App;
import com.googlecode.androidannotations.annotations.Background; import com.googlecode.androidannotations.annotations.Background;
import com.googlecode.androidannotations.annotations.Click; import com.googlecode.androidannotations.annotations.Click;
import com.googlecode.androidannotations.annotations.EActivity; import com.googlecode.androidannotations.annotations.EActivity;
import com.googlecode.androidannotations.annotations.InstanceState;
import com.googlecode.androidannotations.annotations.ItemClick; import com.googlecode.androidannotations.annotations.ItemClick;
import com.googlecode.androidannotations.annotations.ItemLongClick; import com.googlecode.androidannotations.annotations.ItemLongClick;
import com.googlecode.androidannotations.annotations.UiThread; import com.googlecode.androidannotations.annotations.UiThread;
@ -54,24 +48,25 @@ import com.googlecode.androidannotations.annotations.ViewById;
import com.googlecode.androidannotations.annotations.rest.RestService; import com.googlecode.androidannotations.annotations.rest.RestService;
@EActivity(R.layout.main) @EActivity(R.layout.main)
public class RoutesListActivity extends SActivity implements public class RoutesListActivity extends SActivity implements TickSubscriber {
LoaderCallbacks<Cursor>, TickSubscriber {
private static final String NO_DELAYS_REPORTED = "No delays reported"; private static final String NO_DELAYS_REPORTED = "No delays reported";
private static final int FAVORITES_LOADER_ID = 0;
private static final TimeZone PACIFIC_TIME = TimeZone private static final TimeZone PACIFIC_TIME = TimeZone
.getTimeZone("America/Los_Angeles"); .getTimeZone("America/Los_Angeles");
private Uri mCurrentlySelectedUri; @InstanceState
StationPair mCurrentlySelectedStationPair;
private Station mCurrentlySelectedOrigin; @InstanceState
private Station mCurrentlySelectedDestination; String mCurrentAlerts;
private ActionMode mActionMode; private ActionMode mActionMode;
private FavoritesArrayAdapter mRoutesAdapter; private FavoritesArrayAdapter mRoutesAdapter;
@App
BartRunnerApplication app;
@RestService @RestService
AlertsClient alertsClient; AlertsClient alertsClient;
@ -89,16 +84,16 @@ public class RoutesListActivity extends SActivity implements
@Click(R.id.quickLookupButton) @Click(R.id.quickLookupButton)
void quickLookupButtonClick() { void quickLookupButtonClick() {
DialogFragment dialog = new QuickRouteDialogFragment( DialogFragment dialog = new QuickRouteDialogFragment();
getString(R.string.quick_departure_lookup));
dialog.show(getSupportFragmentManager().beginTransaction()); dialog.show(getSupportFragmentManager().beginTransaction());
} }
@ItemClick(android.R.id.list) @ItemClick(android.R.id.list)
void listItemClicked(StationPair item) { void listItemClicked(StationPair item) {
startActivity(new Intent(Intent.ACTION_VIEW, Intent intent = new Intent(RoutesListActivity.this,
ContentUris.withAppendedId(Constants.FAVORITE_CONTENT_URI, ViewDeparturesActivity.class);
item.getId()))); intent.putExtra(Constants.STATION_PAIR_EXTRA, item);
startActivity(intent);
} }
@ItemLongClick(android.R.id.list) @ItemLongClick(android.R.id.list)
@ -107,11 +102,7 @@ public class RoutesListActivity extends SActivity implements
mActionMode.finish(); mActionMode.finish();
} }
mCurrentlySelectedUri = ContentUris.withAppendedId( mCurrentlySelectedStationPair = item;
Constants.FAVORITE_CONTENT_URI, item.getId());
mCurrentlySelectedOrigin = item.getOrigin();
mCurrentlySelectedDestination = item.getDestination();
startContextualActionMode(); startContextualActionMode();
} }
@ -121,9 +112,7 @@ public class RoutesListActivity extends SActivity implements
setTitle(R.string.favorite_routes); setTitle(R.string.favorite_routes);
mRoutesAdapter = new FavoritesArrayAdapter(this, mRoutesAdapter = new FavoritesArrayAdapter(this,
R.layout.favorite_listing); R.layout.favorite_listing, app.getFavorites());
getSupportLoaderManager().initLoader(FAVORITES_LOADER_ID, null, this);
setListAdapter(mRoutesAdapter); setListAdapter(mRoutesAdapter);
@ -132,6 +121,9 @@ public class RoutesListActivity extends SActivity implements
if (mCurrentAlerts != null) { if (mCurrentAlerts != null) {
showAlertMessage(mCurrentAlerts); showAlertMessage(mCurrentAlerts);
} }
startEtdListeners();
refreshFares();
} }
/** Called when the activity is first created. */ /** Called when the activity is first created. */
@ -140,58 +132,14 @@ public class RoutesListActivity extends SActivity implements
super.onCreate(savedInstanceState); super.onCreate(savedInstanceState);
if (savedInstanceState != null) { if (savedInstanceState != null) {
if (savedInstanceState.getString("currentlySelectedOrigin") != null) {
mCurrentlySelectedOrigin = Station
.getByAbbreviation(savedInstanceState
.getString("currentlySelectedOrigin"));
}
if (savedInstanceState.getString("currentlySelectedDestination") != null) {
mCurrentlySelectedDestination = Station
.getByAbbreviation(savedInstanceState
.getString("currentlySelectedDestination"));
}
if (savedInstanceState.getParcelable("currentlySelectedUri") != null) {
mCurrentlySelectedUri = (Uri) savedInstanceState
.getParcelable("currentlySelectedUri");
}
if (savedInstanceState.getBoolean("hasActionMode")) { if (savedInstanceState.getBoolean("hasActionMode")) {
startContextualActionMode(); startContextualActionMode();
} }
mCurrentAlerts = savedInstanceState.getString("currentAlerts");
} }
Ticker.getInstance().addSubscriber(this, getApplicationContext()); Ticker.getInstance().addSubscriber(this, getApplicationContext());
} }
@Override
public Loader<Cursor> onCreateLoader(int id, Bundle args) {
return new CursorLoader(this, Constants.FAVORITE_CONTENT_URI,
new String[] { RoutesColumns._ID.string,
RoutesColumns.FROM_STATION.string,
RoutesColumns.TO_STATION.string,
RoutesColumns.FARE.string,
RoutesColumns.FARE_LAST_UPDATED.string,
RoutesColumns.AVERAGE_TRIP_SAMPLE_COUNT.string,
RoutesColumns.AVERAGE_TRIP_LENGTH.string }, null, null,
RoutesColumns._ID.string);
}
@Override
public void onLoadFinished(Loader<Cursor> loader, Cursor cursor) {
if (cursor.getCount() == 0) {
((TextView) findViewById(android.R.id.empty))
.setText(R.string.empty_favorites_list_message);
}
mRoutesAdapter.updateFromCursor(cursor);
refreshFares(cursor);
findViewById(R.id.progress).setVisibility(View.GONE);
}
@Override
public void onLoaderReset(Loader<Cursor> loader) {
// Nothing to do
}
private AdapterView<ListAdapter> getListView() { private AdapterView<ListAdapter> getListView() {
return listView; return listView;
} }
@ -205,39 +153,32 @@ public class RoutesListActivity extends SActivity implements
getListView().setAdapter(mRoutesAdapter); getListView().setAdapter(mRoutesAdapter);
} }
private void refreshFares(Cursor cursor) { void addFavorite(StationPair pair) {
if (cursor.moveToFirst()) { mRoutesAdapter.add(pair);
do { }
final Station orig = Station.getByAbbreviation(CursorUtils
.getString(cursor, RoutesColumns.FROM_STATION)); private void refreshFares() {
final Station dest = Station.getByAbbreviation(CursorUtils for (int i = getListAdapter().getCount() - 1; i >= 0; i--) {
.getString(cursor, RoutesColumns.TO_STATION)); final StationPair stationPair = getListAdapter().getItem(i);
final Long id = CursorUtils.getLong(cursor, RoutesColumns._ID);
final Long lastUpdateMillis = CursorUtils.getLong(cursor,
RoutesColumns.FARE_LAST_UPDATED);
Calendar now = Calendar.getInstance(); Calendar now = Calendar.getInstance();
Calendar lastUpdate = Calendar.getInstance(); Calendar lastUpdate = Calendar.getInstance();
lastUpdate.setTimeInMillis(lastUpdateMillis); lastUpdate.setTimeInMillis(stationPair.getFareLastUpdated());
now.setTimeZone(PACIFIC_TIME); now.setTimeZone(PACIFIC_TIME);
lastUpdate.setTimeZone(PACIFIC_TIME); lastUpdate.setTimeZone(PACIFIC_TIME);
// Update every day // Update every day
if (now.get(Calendar.DAY_OF_YEAR) != lastUpdate if (now.get(Calendar.DAY_OF_YEAR) != lastUpdate
.get(Calendar.DAY_OF_YEAR)) { .get(Calendar.DAY_OF_YEAR)
|| now.get(Calendar.YEAR) != lastUpdate.get(Calendar.YEAR)) {
GetRouteFareTask fareTask = new GetRouteFareTask() { GetRouteFareTask fareTask = new GetRouteFareTask() {
@Override @Override
public void onResult(String fare) { public void onResult(String fare) {
ContentValues values = new ContentValues(); stationPair.setFare(fare);
values.put(RoutesColumns.FARE.string, fare); stationPair.setFareLastUpdated(System
values.put(RoutesColumns.FARE_LAST_UPDATED.string, .currentTimeMillis());
System.currentTimeMillis()); getListAdapter().notifyDataSetChanged();
getContentResolver()
.update(ContentUris.withAppendedId(
Constants.FAVORITE_CONTENT_URI, id),
values, null, null);
} }
@Override @Override
@ -245,30 +186,26 @@ public class RoutesListActivity extends SActivity implements
// Ignore... we can do this later // Ignore... we can do this later
} }
}; };
fareTask.execute(new GetRouteFareTask.Params(orig, dest)); fareTask.execute(new GetRouteFareTask.Params(stationPair
.getOrigin(), stationPair.getDestination()));
} }
} while (cursor.moveToNext());
} }
} }
@Override @Override
protected void onSaveInstanceState(Bundle outState) { protected void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState); super.onSaveInstanceState(outState);
if (mCurrentlySelectedOrigin != null)
outState.putString("currentlySelectedOrigin",
mCurrentlySelectedOrigin.abbreviation);
if (mCurrentlySelectedDestination != null)
outState.putString("currentlySelectedDestination",
mCurrentlySelectedDestination.abbreviation);
outState.putParcelable("currentlySelectedUri", mCurrentlySelectedUri);
outState.putBoolean("hasActionMode", mActionMode != null); outState.putBoolean("hasActionMode", mActionMode != null);
outState.putString("currentAlerts", mCurrentAlerts);
} }
@Override @Override
protected void onResume() { protected void onResume() {
super.onResume(); super.onResume();
Ticker.getInstance().startTicking(this); Ticker.getInstance().startTicking(this);
startEtdListeners();
}
private void startEtdListeners() {
if (mRoutesAdapter != null && !mRoutesAdapter.isEmpty() if (mRoutesAdapter != null && !mRoutesAdapter.isEmpty()
&& !mRoutesAdapter.areEtdListenersActive()) { && !mRoutesAdapter.areEtdListenersActive()) {
mRoutesAdapter.setUpEtdListeners(); mRoutesAdapter.setUpEtdListeners();
@ -287,6 +224,8 @@ public class RoutesListActivity extends SActivity implements
protected void onStop() { protected void onStop() {
super.onStop(); super.onStop();
Ticker.getInstance().stopTicking(this); Ticker.getInstance().stopTicking(this);
app.saveFavorites();
} }
@Override @Override
@ -314,13 +253,11 @@ public class RoutesListActivity extends SActivity implements
private MenuItem elevatorMenuItem; private MenuItem elevatorMenuItem;
private View origElevatorActionView; private View origElevatorActionView;
private String mCurrentAlerts;
public boolean onOptionsItemSelected(MenuItem item) { public boolean onOptionsItemSelected(MenuItem item) {
int itemId = item.getItemId(); int itemId = item.getItemId();
if (itemId == R.id.add_favorite_menu_button) { if (itemId == R.id.add_favorite_menu_button) {
new AddRouteDialogFragment(getString(R.string.add_route)) new AddRouteDialogFragment().show(getSupportFragmentManager()
.show(getSupportFragmentManager().beginTransaction()); .beginTransaction());
return true; return true;
} else if (itemId == R.id.view_system_map_button) { } else if (itemId == R.id.view_system_map_button) {
startActivity(new Intent(this, ViewMapActivity.class)); startActivity(new Intent(this, ViewMapActivity.class));
@ -393,7 +330,7 @@ public class RoutesListActivity extends SActivity implements
@UiThread @UiThread
void resetElevatorMenuGraphic() { void resetElevatorMenuGraphic() {
invalidateOptionsMenu(); ActivityCompat.invalidateOptionsMenu(this);
elevatorMenuItem.setActionView(origElevatorActionView); elevatorMenuItem.setActionView(origElevatorActionView);
} }
@ -407,8 +344,9 @@ public class RoutesListActivity extends SActivity implements
private void startContextualActionMode() { private void startContextualActionMode() {
mActionMode = startActionMode(new RouteActionMode()); mActionMode = startActionMode(new RouteActionMode());
mActionMode.setTitle(mCurrentlySelectedOrigin.name); mActionMode.setTitle(mCurrentlySelectedStationPair.getOrigin().name);
mActionMode.setSubtitle("to " + mCurrentlySelectedDestination.name); mActionMode.setSubtitle("to "
+ mCurrentlySelectedStationPair.getDestination().name);
} }
private final class RouteActionMode implements ActionMode.Callback { private final class RouteActionMode implements ActionMode.Callback {
@ -426,8 +364,11 @@ public class RoutesListActivity extends SActivity implements
@Override @Override
public boolean onActionItemClicked(ActionMode mode, MenuItem item) { public boolean onActionItemClicked(ActionMode mode, MenuItem item) {
if (item.getItemId() == R.id.view) { if (item.getItemId() == R.id.view) {
startActivity(new Intent(Intent.ACTION_VIEW, Intent intent = new Intent(RoutesListActivity.this,
mCurrentlySelectedUri)); ViewDeparturesActivity.class);
intent.putExtra(Constants.STATION_PAIR_EXTRA,
mCurrentlySelectedStationPair);
startActivity(intent);
mode.finish(); mode.finish();
return true; return true;
} else if (item.getItemId() == R.id.delete) { } else if (item.getItemId() == R.id.delete) {
@ -439,11 +380,9 @@ public class RoutesListActivity extends SActivity implements
new DialogInterface.OnClickListener() { new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, public void onClick(DialogInterface dialog,
int which) { int which) {
getContentResolver().delete( getListAdapter().remove(
mCurrentlySelectedUri, null, null); mCurrentlySelectedStationPair);
mCurrentlySelectedUri = null; mCurrentlySelectedStationPair = null;
mCurrentlySelectedOrigin = null;
mCurrentlySelectedDestination = null;
mActionMode.finish(); mActionMode.finish();
dialog.dismiss(); dialog.dismiss();
} }

View File

@ -9,7 +9,6 @@ import android.content.Context;
import android.content.DialogInterface; import android.content.DialogInterface;
import android.content.Intent; import android.content.Intent;
import android.content.ServiceConnection; import android.content.ServiceConnection;
import android.database.Cursor;
import android.media.MediaPlayer; import android.media.MediaPlayer;
import android.media.RingtoneManager; import android.media.RingtoneManager;
import android.net.Uri; import android.net.Uri;
@ -18,9 +17,6 @@ import android.os.Handler;
import android.os.IBinder; import android.os.IBinder;
import android.os.Parcelable; import android.os.Parcelable;
import android.os.Vibrator; import android.os.Vibrator;
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;
@ -44,27 +40,21 @@ import com.dougkeen.bart.controls.SwipeHelper;
import com.dougkeen.bart.controls.Ticker; import com.dougkeen.bart.controls.Ticker;
import com.dougkeen.bart.controls.YourTrainLayout; import com.dougkeen.bart.controls.YourTrainLayout;
import com.dougkeen.bart.data.DepartureArrayAdapter; import com.dougkeen.bart.data.DepartureArrayAdapter;
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.Station;
import com.dougkeen.bart.model.StationPair; import com.dougkeen.bart.model.StationPair;
import com.dougkeen.bart.services.BoardedDepartureService; import com.dougkeen.bart.services.BoardedDepartureService;
import com.dougkeen.bart.services.EtdService; import com.dougkeen.bart.services.EtdService;
import com.dougkeen.bart.services.EtdService.EtdServiceBinder; import com.dougkeen.bart.services.EtdService.EtdServiceBinder;
import com.dougkeen.bart.services.EtdService.EtdServiceListener; import com.dougkeen.bart.services.EtdService.EtdServiceListener;
import com.dougkeen.bart.services.EtdService_;
import com.dougkeen.util.Observer; import com.dougkeen.util.Observer;
import com.dougkeen.util.WakeLocker; import com.dougkeen.util.WakeLocker;
public class ViewDeparturesActivity extends SActivity implements public class ViewDeparturesActivity extends SActivity implements
EtdServiceListener { EtdServiceListener {
private static final int LOADER_ID = 123; private StationPair mStationPair;
private Uri mUri;
private Station mOrigin;
private Station mDestination;
private Departure mSelectedDeparture; private Departure mSelectedDeparture;
@ -89,64 +79,8 @@ public class ViewDeparturesActivity extends SActivity implements
final Intent intent = getIntent(); final Intent intent = getIntent();
String action = intent.getAction();
if (Intent.ACTION_VIEW.equals(action)) {
mUri = intent.getData();
}
final Uri uri = mUri;
final BartRunnerApplication bartRunnerApplication = (BartRunnerApplication) getApplication(); final BartRunnerApplication bartRunnerApplication = (BartRunnerApplication) getApplication();
if (savedInstanceState != null
&& savedInstanceState.containsKey("origin")
&& savedInstanceState.containsKey("destination")) {
mOrigin = Station.getByAbbreviation(savedInstanceState
.getString("origin"));
mDestination = Station.getByAbbreviation(savedInstanceState
.getString("destination"));
setListTitle();
} 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));
setListTitle();
if (mBound && mEtdService != null)
mEtdService.registerListener(
ViewDeparturesActivity.this, false);
refreshBoardedDeparture(false);
getSupportLoaderManager().destroyLoader(LOADER_ID);
}
@Override
public void onLoaderReset(Loader<Cursor> loader) {
// ignore
}
});
}
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);
@ -155,6 +89,43 @@ public class ViewDeparturesActivity extends SActivity implements
mDeparturesAdapter = new DepartureArrayAdapter(this, mDeparturesAdapter = new DepartureArrayAdapter(this,
R.layout.departure_listing); R.layout.departure_listing);
setListAdapter(mDeparturesAdapter);
final ListView listView = getListView();
listView.setEmptyView(findViewById(android.R.id.empty));
listView.setOnItemClickListener(mListItemClickListener);
listView.setOnItemLongClickListener(mListItemLongClickListener);
mMissingDepartureText = findViewById(R.id.missingDepartureText);
mMissingDepartureText.setVisibility(View.VISIBLE);
mYourTrainSection = (YourTrainLayout) findViewById(R.id.yourTrainSection);
mYourTrainSection.setOnClickListener(mYourTrainSectionClickListener);
mSwipeHelper = new SwipeHelper(mYourTrainSection, null,
new SwipeHelper.OnDismissCallback() {
@Override
public void onDismiss(View view, Object token) {
dismissYourTrainSelection();
if (mActionMode != null) {
mActionMode.finish();
}
}
});
mYourTrainSection.setOnTouchListener(mSwipeHelper);
if (savedInstanceState != null
&& savedInstanceState.containsKey("stationPair")) {
mStationPair = savedInstanceState.getParcelable("stationPair");
setListTitle();
} else {
mStationPair = intent.getExtras().getParcelable(
Constants.STATION_PAIR_EXTRA);
setListTitle();
if (mBound && mEtdService != null)
mEtdService
.registerListener(ViewDeparturesActivity.this, false);
refreshBoardedDeparture(false);
}
if (savedInstanceState != null) { if (savedInstanceState != null) {
if (savedInstanceState.containsKey("departures")) { if (savedInstanceState.containsKey("departures")) {
for (Parcelable departure : savedInstanceState for (Parcelable departure : savedInstanceState
@ -178,29 +149,6 @@ public class ViewDeparturesActivity extends SActivity implements
startYourTrainActionMode(); startYourTrainActionMode();
} }
} }
setListAdapter(mDeparturesAdapter);
final ListView listView = getListView();
listView.setEmptyView(findViewById(android.R.id.empty));
listView.setOnItemClickListener(mListItemClickListener);
listView.setOnItemLongClickListener(mListItemLongClickListener);
mMissingDepartureText = findViewById(R.id.missingDepartureText);
mMissingDepartureText.setVisibility(View.VISIBLE);
mYourTrainSection = (YourTrainLayout) findViewById(R.id.yourTrainSection);
mYourTrainSection.setOnClickListener(mYourTrainSectionClickListener);
mSwipeHelper = new SwipeHelper(mYourTrainSection, null,
new SwipeHelper.OnDismissCallback() {
@Override
public void onDismiss(View view, Object token) {
dismissYourTrainSelection();
if (mActionMode != null) {
mActionMode.finish();
}
}
});
mYourTrainSection.setOnTouchListener(mSwipeHelper);
refreshBoardedDeparture(false); refreshBoardedDeparture(false);
getSupportActionBar().setHomeButtonEnabled(true); getSupportActionBar().setHomeButtonEnabled(true);
@ -297,8 +245,9 @@ public class ViewDeparturesActivity extends SActivity implements
} }
private void setListTitle() { private void setListTitle() {
((TextView) findViewById(R.id.listTitle)).setText(mOrigin.name + " to " ((TextView) findViewById(R.id.listTitle))
+ mDestination.name); .setText(mStationPair.getOrigin().name + " to "
+ mStationPair.getDestination().name);
} }
private ListView getListView() { private ListView getListView() {
@ -399,7 +348,7 @@ public class ViewDeparturesActivity extends SActivity implements
@Override @Override
protected void onSaveInstanceState(Bundle outState) { protected void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState); super.onSaveInstanceState(outState);
if (mOrigin != null || mDestination != null) { if (mStationPair != null) {
/* /*
* If origin or destination are null, this thing was never * If origin or destination are null, this thing was never
* initialized in the first place, so there's really nothing to save * initialized in the first place, so there's really nothing to save
@ -415,15 +364,14 @@ public class ViewDeparturesActivity extends SActivity implements
isDepartureActionModeActive()); isDepartureActionModeActive());
outState.putBoolean("hasYourTrainActionMode", outState.putBoolean("hasYourTrainActionMode",
isYourTrainActionModeActive()); isYourTrainActionModeActive());
outState.putString("origin", mOrigin.abbreviation); outState.putParcelable("stationPair", mStationPair);
outState.putString("destination", mDestination.abbreviation);
} }
} }
@Override @Override
protected void onStart() { protected void onStart() {
super.onStart(); super.onStart();
bindService(new Intent(this, EtdService.class), mConnection, bindService(EtdService_.intent(this).get(), mConnection,
Context.BIND_AUTO_CREATE); Context.BIND_AUTO_CREATE);
Ticker.getInstance().startTicking(this); Ticker.getInstance().startTicking(this);
} }
@ -459,10 +407,8 @@ public class ViewDeparturesActivity extends SActivity implements
public boolean onOptionsItemSelected(MenuItem item) { public boolean onOptionsItemSelected(MenuItem item) {
int itemId = item.getItemId(); int itemId = item.getItemId();
if (itemId == android.R.id.home) { if (itemId == android.R.id.home) {
Intent intent = new Intent(Intent.ACTION_VIEW, RoutesListActivity_.intent(this)
Constants.FAVORITE_CONTENT_URI); .flags(Intent.FLAG_ACTIVITY_CLEAR_TOP).start();
intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
startActivity(intent);
return true; return true;
} else if (itemId == R.id.view_on_bart_site_button) { } else if (itemId == R.id.view_on_bart_site_button) {
startActivity(new Intent( startActivity(new Intent(
@ -471,9 +417,9 @@ public class ViewDeparturesActivity extends SActivity implements
+ DateFormat.format("h:mmaa", + DateFormat.format("h:mmaa",
System.currentTimeMillis()) System.currentTimeMillis())
+ "&orig=" + "&orig="
+ mOrigin.abbreviation + mStationPair.getOrigin().abbreviation
+ "&dest=" + "&dest="
+ mDestination.abbreviation))); + mStationPair.getDestination().abbreviation)));
return true; return true;
} else if (itemId == R.id.view_system_map_button) { } else if (itemId == R.id.view_system_map_button) {
startActivity(new Intent(this, ViewMapActivity.class)); startActivity(new Intent(this, ViewMapActivity.class));
@ -508,13 +454,16 @@ public class ViewDeparturesActivity extends SActivity implements
private void setBoardedDeparture(Departure selectedDeparture) { private void setBoardedDeparture(Departure selectedDeparture) {
final BartRunnerApplication application = (BartRunnerApplication) getApplication(); final BartRunnerApplication application = (BartRunnerApplication) getApplication();
selectedDeparture.setPassengerDestination(mDestination); selectedDeparture
.setPassengerDestination(mStationPair.getDestination());
application.setBoardedDeparture(selectedDeparture); application.setBoardedDeparture(selectedDeparture);
refreshBoardedDeparture(true); refreshBoardedDeparture(true);
// Start the notification service // Start the notification service
startService(new Intent(ViewDeparturesActivity.this, final Intent intent = new Intent(ViewDeparturesActivity.this,
BoardedDepartureService.class)); BoardedDepartureService.class);
intent.putExtra("departure", selectedDeparture);
startService(intent);
} }
private void startDepartureActionMode() { private void startDepartureActionMode() {
@ -815,9 +764,7 @@ public class ViewDeparturesActivity extends SActivity implements
@Override @Override
public StationPair getStationPair() { public StationPair getStationPair() {
if (mOrigin == null || mDestination == null) return mStationPair;
return null;
return new StationPair(mOrigin, mDestination);
} }
private void hideYourTrainSection() { private void hideYourTrainSection() {

View File

@ -1,271 +0,0 @@
package com.dougkeen.bart.data;
import java.util.HashMap;
import android.content.ContentProvider;
import android.content.ContentUris;
import android.content.ContentValues;
import android.content.UriMatcher;
import android.database.Cursor;
import android.database.MatrixCursor;
import android.database.MatrixCursor.RowBuilder;
import android.database.SQLException;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteQueryBuilder;
import android.net.Uri;
import android.text.TextUtils;
import com.dougkeen.bart.model.Constants;
public class BartContentProvider extends ContentProvider {
private static final UriMatcher sUriMatcher;
private static HashMap<String, String> sFavoritesProjectionMap;
private static final int FAVORITES = 1;
private static final int FAVORITE_ID = 2;
private static final int ARBITRARY_ROUTE = 3;
private static final int ARBITRARY_ROUTE_UNDEFINED = 4;
/**
* The default sort order for events
*/
private static final String DEFAULT_SORT_ORDER = RoutesColumns.FROM_STATION.string
+ " DESC";
static {
sUriMatcher = new UriMatcher(UriMatcher.NO_MATCH);
sUriMatcher.addURI(Constants.AUTHORITY, "favorites", FAVORITES);
sUriMatcher.addURI(Constants.AUTHORITY, "favorites/#", FAVORITE_ID);
sUriMatcher.addURI(Constants.AUTHORITY, "route/*/*", ARBITRARY_ROUTE);
sUriMatcher.addURI(Constants.AUTHORITY, "route",
ARBITRARY_ROUTE_UNDEFINED);
sFavoritesProjectionMap = new HashMap<String, String>();
sFavoritesProjectionMap.put(RoutesColumns._ID.string,
RoutesColumns._ID.string);
sFavoritesProjectionMap.put(RoutesColumns.FROM_STATION.string,
RoutesColumns.FROM_STATION.string);
sFavoritesProjectionMap.put(RoutesColumns.TO_STATION.string,
RoutesColumns.TO_STATION.string);
sFavoritesProjectionMap.put(RoutesColumns.FARE.string,
RoutesColumns.FARE.string);
sFavoritesProjectionMap.put(RoutesColumns.FARE_LAST_UPDATED.string,
RoutesColumns.FARE_LAST_UPDATED.string);
sFavoritesProjectionMap.put(
RoutesColumns.AVERAGE_TRIP_SAMPLE_COUNT.string,
RoutesColumns.AVERAGE_TRIP_SAMPLE_COUNT.string);
sFavoritesProjectionMap.put(RoutesColumns.AVERAGE_TRIP_LENGTH.string,
RoutesColumns.AVERAGE_TRIP_LENGTH.string);
}
private DatabaseHelper mDatabaseHelper;
@Override
public boolean onCreate() {
mDatabaseHelper = new DatabaseHelper(getContext());
return true;
}
@Override
public String getType(Uri uri) {
int match = sUriMatcher.match(uri);
if (match == FAVORITES) {
return Constants.FAVORITE_CONTENT_TYPE;
} else if (match == FAVORITE_ID) {
return Constants.FAVORITE_CONTENT_ITEM_TYPE;
} else if (match == ARBITRARY_ROUTE) {
return Constants.ARBITRARY_ROUTE_TYPE;
} else if (match == ARBITRARY_ROUTE_UNDEFINED) {
return Constants.ARBITRARY_ROUTE_UNDEFINED_TYPE;
} else {
throw new IllegalArgumentException("Unknown URI " + uri);
}
}
@Override
public Cursor query(Uri uri, String[] projection, String selection,
String[] selectionArgs, String sortOrder) {
SQLiteQueryBuilder qb = new SQLiteQueryBuilder();
SQLiteDatabase db = mDatabaseHelper.getReadableDatabase();
String orderBy = sortOrder;
int match = sUriMatcher.match(uri);
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);
RowBuilder newRow = returnCursor.newRow();
for (String column : projection) {
if (column.equals(RoutesColumns.FROM_STATION.string)) {
newRow.add(origin);
} else if (column.equals(RoutesColumns.TO_STATION.string)) {
newRow.add(destination);
} else {
newRow.add(null);
}
}
return returnCursor;
} else if (match == FAVORITE_ID) {
qb.setTables(DatabaseHelper.FAVORITES_TABLE_NAME);
qb.setProjectionMap(sFavoritesProjectionMap);
qb.appendWhere(RoutesColumns._ID + " = "
+ uri.getPathSegments().get(1));
} else if (match == FAVORITES) {
qb.setTables(DatabaseHelper.FAVORITES_TABLE_NAME);
qb.setProjectionMap(sFavoritesProjectionMap);
} else {
throw new IllegalArgumentException("Unknown URI " + uri);
}
// If no sort order is specified use the default
if (TextUtils.isEmpty(orderBy)) {
orderBy = DEFAULT_SORT_ORDER;
}
// Get the database and run the query
Cursor cursor = qb.query(db, projection, selection, selectionArgs,
null, null, orderBy);
// Tell the cursor what uri to watch, so it knows when its source data
// changes
cursor.setNotificationUri(getContext().getContentResolver(), uri);
return cursor;
}
@Override
public Uri insert(Uri uri, ContentValues initialValues) {
ContentValues values;
if (initialValues != null) {
values = new ContentValues(initialValues);
} else {
values = new ContentValues();
}
SQLiteDatabase db = mDatabaseHelper.getWritableDatabase();
// Validate the requested uri
int match = sUriMatcher.match(uri);
if (match == FAVORITES) {
long rowId = -1;
Cursor cursor = db
.query(DatabaseHelper.FAVORITES_TABLE_NAME,
new String[] { RoutesColumns._ID.string },
RoutesColumns.FROM_STATION + "=? AND "
+ RoutesColumns.TO_STATION + "=?",
new String[] {
values.getAsString(RoutesColumns.FROM_STATION.string),
values.getAsString(RoutesColumns.TO_STATION.string) },
null, null, null);
try {
if (cursor.moveToFirst()) {
rowId = cursor.getLong(0);
}
} finally {
CursorUtils.closeCursorQuietly(cursor);
}
if (rowId < 0) {
rowId = db.insert(DatabaseHelper.FAVORITES_TABLE_NAME,
RoutesColumns.FROM_STATION.string, values);
}
if (rowId > 0) {
Uri eventUri = ContentUris.withAppendedId(
Constants.FAVORITE_CONTENT_URI, rowId);
getContext().getContentResolver().notifyChange(eventUri, null,
false);
return eventUri;
}
} else {
throw new IllegalArgumentException("Unknown URI " + uri);
}
throw new SQLException("Failed to insert row into " + uri);
}
@Override
public int update(Uri uri, ContentValues values, String where,
String[] whereArgs) {
SQLiteDatabase db = mDatabaseHelper.getWritableDatabase();
// Validate the requested uri
int match = sUriMatcher.match(uri);
if (match == FAVORITE_ID) {
String favoriteId = uri.getPathSegments().get(1);
int count = db.update(
DatabaseHelper.FAVORITES_TABLE_NAME,
values,
RoutesColumns._ID
+ " = "
+ favoriteId
+ (!TextUtils.isEmpty(where) ? " AND (" + where
+ ')' : ""), whereArgs);
getContext().getContentResolver().notifyChange(uri, null);
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);
try {
if (query.moveToFirst()) {
return update(ContentUris.withAppendedId(
Constants.FAVORITE_CONTENT_URI, query.getLong(0)),
values, where, whereArgs);
}
} finally {
CursorUtils.closeCursorQuietly(query);
}
}
return 0;
}
@Override
public int delete(Uri uri, String where, String[] whereArgs) {
// TODO: Sync with REST service?
SQLiteDatabase db = mDatabaseHelper.getWritableDatabase();
int count;
int match = sUriMatcher.match(uri);
if (match == FAVORITES) {
count = db.delete(DatabaseHelper.FAVORITES_TABLE_NAME, where,
whereArgs);
} else if (match == FAVORITE_ID) {
String favoriteId = uri.getPathSegments().get(1);
count = db.delete(
DatabaseHelper.FAVORITES_TABLE_NAME,
RoutesColumns._ID
+ " = "
+ favoriteId
+ (!TextUtils.isEmpty(where) ? " AND (" + where
+ ')' : ""), whereArgs);
} else {
throw new IllegalArgumentException("Unknown URI " + uri);
}
getContext().getContentResolver().notifyChange(uri, null);
return count;
}
}

View File

@ -20,4 +20,8 @@ public final class CursorUtils {
public static final Long getLong(Cursor cursor, RoutesColumns column) { public static final Long getLong(Cursor cursor, RoutesColumns column) {
return cursor.getLong(cursor.getColumnIndex(column.string)); return cursor.getLong(cursor.getColumnIndex(column.string));
} }
public static final Integer getInteger(Cursor cursor, RoutesColumns column) {
return cursor.getInt(cursor.getColumnIndex(column.string));
}
} }

View File

@ -1,31 +1,32 @@
package com.dougkeen.bart.data; package com.dougkeen.bart.data;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays;
import java.util.List; import java.util.List;
import org.apache.commons.lang3.StringUtils;
import android.content.Context; import android.content.Context;
import android.database.Cursor; import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase; import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper; import android.database.sqlite.SQLiteOpenHelper;
import android.util.Log;
import com.dougkeen.bart.BartRunnerApplication;
import com.dougkeen.bart.model.StationPair;
public class DatabaseHelper extends SQLiteOpenHelper { public class DatabaseHelper extends SQLiteOpenHelper {
private static final String DATABASE_NAME = "bart.dougkeen.db"; private static final String DATABASE_NAME = "bart.dougkeen.db";
private static final int DATABASE_VERSION = 4; private static final int DATABASE_VERSION = 6;
public static final String FAVORITES_TABLE_NAME = "Favorites"; public static final String FAVORITES_TABLE_NAME = "Favorites";
private BartRunnerApplication app;
public DatabaseHelper(Context context) { public DatabaseHelper(Context context) {
super(context, DATABASE_NAME, null, DATABASE_VERSION); super(context, DATABASE_NAME, null, DATABASE_VERSION);
app = (BartRunnerApplication) context.getApplicationContext();
} }
@Override @Override
public void onCreate(SQLiteDatabase db) { public void onCreate(SQLiteDatabase db) {
createFavoritesTable(db);
} }
private void createFavoritesTable(SQLiteDatabase db) { private void createFavoritesTable(SQLiteDatabase db) {
@ -45,54 +46,24 @@ public class DatabaseHelper extends SQLiteOpenHelper {
try { try {
createFavoritesTable(db); createFavoritesTable(db);
List<String> columns = getColumns(db, FAVORITES_TABLE_NAME); Cursor query = db.query(FAVORITES_TABLE_NAME, RoutesColumns.all(),
null, null, null, null, null);
db.execSQL("ALTER TABLE " + FAVORITES_TABLE_NAME List<StationPair> favorites = new ArrayList<StationPair>();
+ " RENAME TO temp_" + FAVORITES_TABLE_NAME);
createFavoritesTable(db); while (query.moveToNext()) {
favorites.add(StationPair.createFromCursor(query));
}
columns.retainAll(getColumns(db, FAVORITES_TABLE_NAME)); query.close();
String cols = StringUtils.join(columns, ","); new FavoritesPersistence(app).persist(favorites);
db.execSQL(String.format(
"INSERT INTO %s (%s) SELECT %s from temp_%s",
FAVORITES_TABLE_NAME, cols, cols, FAVORITES_TABLE_NAME));
db.execSQL("DROP TABLE temp_" + FAVORITES_TABLE_NAME); db.execSQL("DROP TABLE " + FAVORITES_TABLE_NAME);
db.setTransactionSuccessful(); db.setTransactionSuccessful();
} finally { } finally {
db.endTransaction(); db.endTransaction();
} }
} }
public static List<String> getColumns(SQLiteDatabase db, String tableName) {
List<String> ar = null;
Cursor c = null;
try {
c = db.rawQuery("select * from " + tableName + " limit 1", null);
if (c != null) {
ar = new ArrayList<String>(Arrays.asList(c.getColumnNames()));
}
} catch (Exception e) {
Log.v(tableName, e.getMessage(), e);
e.printStackTrace();
} finally {
if (c != null)
c.close();
}
return ar;
}
public static String join(List<String> list, String delim) {
StringBuilder buf = new StringBuilder();
int num = list.size();
for (int i = 0; i < num; i++) {
if (i != 0)
buf.append(delim);
buf.append((String) list.get(i));
}
return buf.toString();
}
} }

View File

@ -9,9 +9,7 @@ import org.apache.commons.lang3.StringUtils;
import android.app.Activity; import android.app.Activity;
import android.content.ComponentName; import android.content.ComponentName;
import android.content.Context; import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection; import android.content.ServiceConnection;
import android.database.Cursor;
import android.os.IBinder; import android.os.IBinder;
import android.view.LayoutInflater; import android.view.LayoutInflater;
import android.view.View; import android.view.View;
@ -32,6 +30,7 @@ import com.dougkeen.bart.model.TextProvider;
import com.dougkeen.bart.services.EtdService; import com.dougkeen.bart.services.EtdService;
import com.dougkeen.bart.services.EtdService.EtdServiceBinder; import com.dougkeen.bart.services.EtdService.EtdServiceBinder;
import com.dougkeen.bart.services.EtdService.EtdServiceListener; import com.dougkeen.bart.services.EtdService.EtdServiceListener;
import com.dougkeen.bart.services.EtdService_;
public class FavoritesArrayAdapter extends ArrayAdapter<StationPair> { public class FavoritesArrayAdapter extends ArrayAdapter<StationPair> {
@ -82,10 +81,11 @@ public class FavoritesArrayAdapter extends ArrayAdapter<StationPair> {
return !mEtdListeners.isEmpty(); return !mEtdListeners.isEmpty();
} }
public FavoritesArrayAdapter(Context context, int textViewResourceId) { public FavoritesArrayAdapter(Context context, int textViewResourceId,
super(context, textViewResourceId); List<StationPair> objects) {
super(context, textViewResourceId, objects);
mHostActivity = (Activity) context; mHostActivity = (Activity) context;
mHostActivity.bindService(new Intent(mHostActivity, EtdService.class), mHostActivity.bindService(EtdService_.intent(mHostActivity).get(),
mConnection, Context.BIND_AUTO_CREATE); mConnection, Context.BIND_AUTO_CREATE);
} }
@ -118,42 +118,6 @@ public class FavoritesArrayAdapter extends ArrayAdapter<StationPair> {
clearEtdListeners(); clearEtdListeners();
} }
public void updateFromCursor(Cursor cursor) {
if (!cursor.moveToFirst()) {
clear();
}
for (int i = 0; i < getCount(); i++) {
StationPair adapterItem = getItem(i);
if (cursor.isAfterLast()) {
while (i < getCount()) {
remove(getItem(i));
}
} else {
StationPair cursorItem = StationPair.createFromCursor(cursor);
while (!cursorItem.equals(adapterItem)) {
remove(adapterItem);
if (i < getCount()) {
adapterItem = getItem(i);
} else {
break;
}
}
if (cursorItem.equals(adapterItem)
&& !cursorItem.fareEquals(adapterItem)) {
adapterItem.setFare(cursorItem.getFare());
adapterItem.setFareLastUpdated(cursorItem
.getFareLastUpdated());
notifyDataSetChanged();
}
cursor.moveToNext();
}
}
while (!cursor.isAfterLast()) {
add(StationPair.createFromCursor(cursor));
cursor.moveToNext();
}
}
@Override @Override
public View getView(int position, View convertView, ViewGroup parent) { public View getView(int position, View convertView, ViewGroup parent) {
View view; View view;

View File

@ -0,0 +1,63 @@
package com.dougkeen.bart.data;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.util.ArrayList;
import java.util.List;
import org.apache.commons.io.IOUtils;
import android.content.Context;
import android.util.Log;
import com.dougkeen.bart.BartRunnerApplication;
import com.dougkeen.bart.model.StationPair;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.googlecode.androidannotations.annotations.EBean;
@EBean
public class FavoritesPersistence {
private static final String TAG = "FavoritesPersistence";
private final ObjectMapper objectMapper = new ObjectMapper();
private BartRunnerApplication app;
public FavoritesPersistence(Context context) {
app = (BartRunnerApplication) context.getApplicationContext();
}
public void persist(List<StationPair> favorites) {
FileOutputStream outputStream = null;
try {
outputStream = app
.openFileOutput("favorites", Context.MODE_PRIVATE);
objectMapper.writeValue(outputStream, favorites);
} catch (Exception e) {
Log.e(TAG, "Could not write favorites file", e);
} finally {
IOUtils.closeQuietly(outputStream);
}
}
public List<StationPair> restore() {
for (String file : app.fileList()) {
if ("favorites".equals(file)) {
FileInputStream inputStream = null;
try {
inputStream = app.openFileInput("favorites");
return objectMapper.readValue(inputStream,
new TypeReference<ArrayList<StationPair>>() {
});
} catch (Exception e) {
Log.e(TAG, "Could not read favorites file", e);
} finally {
IOUtils.closeQuietly(inputStream);
}
}
}
return new ArrayList<StationPair>();
}
}

View File

@ -1,14 +1,11 @@
package com.dougkeen.bart.data; package com.dougkeen.bart.data;
public enum RoutesColumns { public enum RoutesColumns {
_ID("_id", "INTEGER", false), _ID("_id", "INTEGER", false), FROM_STATION("FROM_STATION", "TEXT", false), TO_STATION(
FROM_STATION("FROM_STATION", "TEXT", false), "TO_STATION", "TEXT", false), FARE("FARE", "TEXT", true), FARE_LAST_UPDATED(
TO_STATION("TO_STATION", "TEXT", false), "FARE_LAST_UPDATED", "INTEGER", true), AVERAGE_TRIP_SAMPLE_COUNT(
FARE("FARE", "TEXT", true), "AVE_TRIP_SAMPLE_COUNT", "INTEGER", true), AVERAGE_TRIP_LENGTH(
FARE_LAST_UPDATED("FARE_LAST_UPDATED", "INTEGER", true), "AVE_TRIP_LENGTH", "INTEGER", true);
AVERAGE_TRIP_SAMPLE_COUNT("AVE_TRIP_SAMPLE_COUNT", "INTEGER", true),
AVERAGE_TRIP_LENGTH("AVE_TRIP_LENGTH", "INTEGER", true);
// This class cannot be instantiated // This class cannot be instantiated
private RoutesColumns(String string, String type, Boolean nullable) { private RoutesColumns(String string, String type, Boolean nullable) {
@ -24,4 +21,13 @@ public enum RoutesColumns {
protected String getColumnDef() { protected String getColumnDef() {
return string + " " + sqliteType + (nullable ? "" : " NOT NULL"); return string + " " + sqliteType + (nullable ? "" : " NOT NULL");
} }
public static String[] all() {
final RoutesColumns[] values = RoutesColumns.values();
String[] returnArray = new String[values.length];
for (int i = values.length - 1; i >= 0; i--) {
returnArray[i] = values[i].string;
}
return returnArray;
}
} }

View File

@ -17,4 +17,5 @@ public class Constants {
public static final String TAG = "com.dougkeen.BartRunner"; public static final String TAG = "com.dougkeen.BartRunner";
public static final String API_KEY = "5LD9-IAYI-TRAT-MHHW"; public static final String API_KEY = "5LD9-IAYI-TRAT-MHHW";
public static final String ACTION_ALARM = "com.dougkeen.action.ALARM"; public static final String ACTION_ALARM = "com.dougkeen.action.ALARM";
public static final String STATION_PAIR_EXTRA = "StationPair";
} }

View File

@ -3,27 +3,23 @@ package com.dougkeen.bart.model;
import org.apache.commons.lang3.ObjectUtils; import org.apache.commons.lang3.ObjectUtils;
import android.database.Cursor; import android.database.Cursor;
import android.net.Uri;
import android.os.Parcel; import android.os.Parcel;
import android.os.Parcelable; import android.os.Parcelable;
import com.dougkeen.bart.data.CursorUtils; import com.dougkeen.bart.data.CursorUtils;
import com.dougkeen.bart.data.RoutesColumns; import com.dougkeen.bart.data.RoutesColumns;
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonProperty;
public class StationPair implements Parcelable { public class StationPair implements Parcelable {
public StationPair(Station origin, Station destination) { @JsonCreator
public StationPair(@JsonProperty("origin") Station origin,
@JsonProperty("destination") Station destination) {
super(); super();
this.origin = origin; this.origin = origin;
this.destination = destination; this.destination = destination;
} }
public StationPair(Long id, Station origin, Station destination) {
super();
this.origin = origin;
this.destination = destination;
this.id = id;
}
public StationPair(Parcel in) { public StationPair(Parcel in) {
readFromParcel(in); readFromParcel(in);
} }
@ -34,22 +30,22 @@ public class StationPair implements Parcelable {
RoutesColumns.FROM_STATION)), RoutesColumns.FROM_STATION)),
Station.getByAbbreviation(CursorUtils.getString(cursor, Station.getByAbbreviation(CursorUtils.getString(cursor,
RoutesColumns.TO_STATION))); RoutesColumns.TO_STATION)));
pair.id = CursorUtils.getLong(cursor, RoutesColumns._ID);
pair.fare = CursorUtils.getString(cursor, RoutesColumns.FARE); pair.fare = CursorUtils.getString(cursor, RoutesColumns.FARE);
pair.fareLastUpdated = CursorUtils.getLong(cursor, pair.fareLastUpdated = CursorUtils.getLong(cursor,
RoutesColumns.FARE_LAST_UPDATED); RoutesColumns.FARE_LAST_UPDATED);
pair.averageTripLength = CursorUtils.getInteger(cursor,
RoutesColumns.AVERAGE_TRIP_LENGTH);
pair.averageTripSampleCount = CursorUtils.getInteger(cursor,
RoutesColumns.AVERAGE_TRIP_SAMPLE_COUNT);
return pair; return pair;
} }
private Long id;
private Station origin; private Station origin;
private Station destination; private Station destination;
private String fare; private String fare;
private Long fareLastUpdated; private long fareLastUpdated;
private int averageTripLength;
public Long getId() { private int averageTripSampleCount;
return id;
}
public Station getOrigin() { public Station getOrigin() {
return origin; return origin;
@ -67,29 +63,35 @@ public class StationPair implements Parcelable {
this.fare = fare; this.fare = fare;
} }
public Long getFareLastUpdated() { public long getFareLastUpdated() {
return fareLastUpdated; return fareLastUpdated;
} }
public void setFareLastUpdated(Long fareLastUpdated) { public void setFareLastUpdated(long fareLastUpdated) {
this.fareLastUpdated = fareLastUpdated; this.fareLastUpdated = fareLastUpdated;
} }
public int getAverageTripLength() {
return averageTripLength;
}
public void setAverageTripLength(int averageTripLength) {
this.averageTripLength = averageTripLength;
}
public int getAverageTripSampleCount() {
return averageTripSampleCount;
}
public void setAverageTripSampleCount(int averageTripSampleCount) {
this.averageTripSampleCount = averageTripSampleCount;
}
public boolean isBetweenStations(Station station1, Station station2) { public boolean isBetweenStations(Station station1, Station station2) {
return (origin.equals(station1) && destination.equals(station2)) return (origin.equals(station1) && destination.equals(station2))
|| (origin.equals(station2) && destination.equals(station1)); || (origin.equals(station2) && destination.equals(station1));
} }
public Uri getUri() {
if (getOrigin() != null && getDestination() != null) {
return Constants.ARBITRARY_ROUTE_CONTENT_URI_ROOT.buildUpon()
.appendPath(getOrigin().abbreviation)
.appendPath(getDestination().abbreviation).build();
} else {
return null;
}
}
@Override @Override
public int hashCode() { public int hashCode() {
final int prime = 31; final int prime = 31;

View File

@ -89,7 +89,7 @@ public class BoardedDepartureService extends Service implements
mServiceLooper = thread.getLooper(); mServiceLooper = thread.getLooper();
mServiceHandler = new ServiceHandler(mServiceLooper, this); mServiceHandler = new ServiceHandler(mServiceLooper, this);
bindService(new Intent(this, EtdService.class), mConnection, bindService(EtdService_.intent(this).get(), mConnection,
Context.BIND_AUTO_CREATE); Context.BIND_AUTO_CREATE);
mNotificationManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE); mNotificationManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
mAlarmManager = (AlarmManager) getSystemService(Context.ALARM_SERVICE); mAlarmManager = (AlarmManager) getSystemService(Context.ALARM_SERVICE);

View File

@ -10,20 +10,15 @@ import java.util.WeakHashMap;
import org.apache.commons.lang3.math.NumberUtils; import org.apache.commons.lang3.math.NumberUtils;
import android.app.Service; import android.app.Service;
import android.content.ContentValues;
import android.content.Intent; import android.content.Intent;
import android.database.Cursor;
import android.net.Uri;
import android.os.AsyncTask; import android.os.AsyncTask;
import android.os.Binder; import android.os.Binder;
import android.os.Handler; import android.os.Handler;
import android.os.IBinder; import android.os.IBinder;
import android.support.v4.content.CursorLoader;
import android.util.Log; import android.util.Log;
import com.dougkeen.bart.BartRunnerApplication; import com.dougkeen.bart.BartRunnerApplication;
import com.dougkeen.bart.R; import com.dougkeen.bart.R;
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.RealTimeDepartures;
@ -33,7 +28,9 @@ import com.dougkeen.bart.model.Station;
import com.dougkeen.bart.model.StationPair; import com.dougkeen.bart.model.StationPair;
import com.dougkeen.bart.networktasks.GetRealTimeDeparturesTask; import com.dougkeen.bart.networktasks.GetRealTimeDeparturesTask;
import com.dougkeen.bart.networktasks.GetScheduleInformationTask; import com.dougkeen.bart.networktasks.GetScheduleInformationTask;
import com.googlecode.androidannotations.annotations.EService;
@EService
public class EtdService extends Service { public class EtdService extends Service {
private IBinder mBinder; private IBinder mBinder;
@ -106,19 +103,13 @@ public class EtdService extends Service {
private class EtdServiceEngine { private class EtdServiceEngine {
private static final int UNCERTAINTY_THRESHOLD = 17; private static final int UNCERTAINTY_THRESHOLD = 17;
private Uri mUri;
private final StationPair mStationPair; private final StationPair mStationPair;
private boolean mIgnoreDepartureDirection = false; private boolean mIgnoreDepartureDirection = false;
private boolean mPendingEtdRequest = false; private boolean mPendingEtdRequest = false;
private int mAverageTripLength; private Map<EtdServiceListener, Boolean> mListeners;
private int mAverageTripSampleCount;
// We'll only use the keys
private WeakHashMap<EtdServiceListener, Boolean> mListeners;
private boolean mLimitToFirstNonDeparted = true; private boolean mLimitToFirstNonDeparted = true;
@ -134,24 +125,9 @@ public class EtdService extends Service {
public EtdServiceEngine(final StationPair route) { public EtdServiceEngine(final StationPair route) {
mStationPair = route; mStationPair = route;
mListeners = new WeakHashMap<EtdService.EtdServiceListener, Boolean>(); mListeners = new HashMap<EtdService.EtdServiceListener, Boolean>();
mRunnableQueue = new Handler(); mRunnableQueue = new Handler();
mLatestDepartures = new ArrayList<Departure>(); 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, protected void registerListener(EtdServiceListener listener,
@ -238,8 +214,7 @@ public class EtdService extends Service {
}; };
mGetDeparturesTask = task; mGetDeparturesTask = task;
Log.v(Constants.TAG, "Fetching data from server"); Log.v(Constants.TAG, "Fetching data from server");
task.execute(new StationPair(mStationPair.getOrigin(), mStationPair task.execute(mStationPair);
.getDestination()));
notifyListenersOfRequestStart(); notifyListenersOfRequestStart();
} }
@ -271,8 +246,7 @@ public class EtdService extends Service {
}; };
Log.i(Constants.TAG, "Fetching data from server"); Log.i(Constants.TAG, "Fetching data from server");
mGetScheduleInformationTask = task; mGetScheduleInformationTask = task;
task.execute(new StationPair(mStationPair.getOrigin(), mStationPair task.execute(mStationPair);
.getDestination()));
} }
protected void applyScheduleInformation(ScheduleInformation result) { protected void applyScheduleInformation(ScheduleInformation result) {
@ -363,7 +337,8 @@ public class EtdService extends Service {
departure.setEstimatedTripTime(localAverageLength); departure.setEstimatedTripTime(localAverageLength);
} else if (!departure.hasEstimatedTripTime()) { } else if (!departure.hasEstimatedTripTime()) {
// Otherwise just assume the global average // Otherwise just assume the global average
departure.setEstimatedTripTime(mAverageTripLength); departure.setEstimatedTripTime(mStationPair
.getAverageTripLength());
} }
} else if (departure.getRequiresTransfer() } else if (departure.getRequiresTransfer()
&& !departure.hasAnyArrivalEstimate()) { && !departure.hasAnyArrivalEstimate()) {
@ -381,20 +356,16 @@ public class EtdService extends Service {
// Update global average // Update global average
if (mLatestScheduleInfo.getTripCountForAverage() > 0) { if (mLatestScheduleInfo.getTripCountForAverage() > 0) {
int newAverageSampleCount = mAverageTripSampleCount int newAverageSampleCount = mStationPair
.getAverageTripSampleCount()
+ mLatestScheduleInfo.getTripCountForAverage(); + mLatestScheduleInfo.getTripCountForAverage();
int newAverage = (mAverageTripLength * mAverageTripSampleCount + localAverageLength int newAverage = (mStationPair.getAverageTripLength()
* mStationPair.getAverageTripSampleCount() + localAverageLength
* mLatestScheduleInfo.getTripCountForAverage()) * mLatestScheduleInfo.getTripCountForAverage())
/ newAverageSampleCount; / newAverageSampleCount;
ContentValues contentValues = new ContentValues(); mStationPair.setAverageTripLength(newAverage);
contentValues.put(RoutesColumns.AVERAGE_TRIP_LENGTH.string, mStationPair.setAverageTripSampleCount(newAverageSampleCount);
newAverage);
contentValues.put(
RoutesColumns.AVERAGE_TRIP_SAMPLE_COUNT.string,
newAverageSampleCount);
getContentResolver().update(mUri, contentValues, null, null);
} }
/* /*