diff --git a/.settings/org.eclipse.core.resources.prefs b/.settings/org.eclipse.core.resources.prefs new file mode 100644 index 0000000..99f26c0 --- /dev/null +++ b/.settings/org.eclipse.core.resources.prefs @@ -0,0 +1,2 @@ +eclipse.preferences.version=1 +encoding/=UTF-8 diff --git a/AndroidManifest.xml b/AndroidManifest.xml index 7aefb2d..81dd02b 100644 --- a/AndroidManifest.xml +++ b/AndroidManifest.xml @@ -18,7 +18,8 @@ android:label="@string/app_name" android:theme="@style/AppTheme" > @@ -43,7 +44,7 @@ @@ -55,7 +56,7 @@ @@ -66,14 +67,14 @@ android:label="BartRunner data provider" /> diff --git a/res/layout/favorite_listing.xml b/res/layout/favorite_listing.xml index 9ee5f69..fe0e94e 100644 --- a/res/layout/favorite_listing.xml +++ b/res/layout/favorite_listing.xml @@ -1,23 +1,33 @@ - + android:layout_alignParentTop="true" + android:gravity="right" + android:width="90dp" + bart:tickInterval="1" /> + + diff --git a/res/layout/main.xml b/res/layout/main.xml index 82ac56d..1bcd9f7 100644 --- a/res/layout/main.xml +++ b/res/layout/main.xml @@ -17,6 +17,14 @@ android:layout_weight="1" android:visibility="gone" /> + + + \ No newline at end of file diff --git a/res/values/strings.xml b/res/values/strings.xml index 52e1ee3..b789695 100644 --- a/res/values/strings.xml +++ b/res/values/strings.xml @@ -4,6 +4,7 @@ BART Runner Favorite routes No favorite routes have been added yet + Loading, please wait... Add a route Origin Destination diff --git a/src/com/dougkeen/bart/BartRunnerApplication.java b/src/com/dougkeen/bart/BartRunnerApplication.java index e7d795b..b6591d1 100644 --- a/src/com/dougkeen/bart/BartRunnerApplication.java +++ b/src/com/dougkeen/bart/BartRunnerApplication.java @@ -1,12 +1,27 @@ package com.dougkeen.bart; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.InputStream; + +import org.apache.commons.io.IOUtils; +import org.apache.commons.lang3.ObjectUtils; + import android.app.Application; import android.media.MediaPlayer; +import android.os.Parcel; +import android.util.Log; +import com.dougkeen.bart.model.Constants; import com.dougkeen.bart.model.Departure; import com.dougkeen.util.Observable; public class BartRunnerApplication extends Application { + private static final int FIVE_MINUTES = 5 * 60 * 1000; + + private static final String CACHE_FILE_NAME = "lastBoardedDeparture"; + private Departure mBoardedDeparture; private Observable mAlarmPending = new Observable(false); @@ -26,11 +41,73 @@ public class BartRunnerApplication extends Application { } public Departure getBoardedDeparture() { + if (mBoardedDeparture == null) { + // see if there's a saved one + File cachedDepartureFile = new File(getCacheDir(), CACHE_FILE_NAME); + if (cachedDepartureFile.exists()) { + InputStream inputStream = null; + try { + inputStream = new FileInputStream(cachedDepartureFile); + byte[] byteArray = IOUtils.toByteArray(inputStream); + Parcel parcel = Parcel.obtain(); + parcel.unmarshall(byteArray, 0, byteArray.length); + parcel.setDataPosition(0); + Departure lastBoardedDeparture = Departure.CREATOR + .createFromParcel(parcel); + + /* + * Check if the cached one is relatively recent. If so, + * restore that to the application context + */ + long now = System.currentTimeMillis(); + if (lastBoardedDeparture.getEstimatedArrivalTime() >= now + - FIVE_MINUTES + || lastBoardedDeparture.getMeanEstimate() >= now + - 2 * FIVE_MINUTES) { + mBoardedDeparture = lastBoardedDeparture; + } + } catch (Exception e) { + Log.w(Constants.TAG, + "Couldn't read or unmarshal lastBoardedDeparture file", + e); + try { + cachedDepartureFile.delete(); + } catch (SecurityException anotherException) { + Log.w(Constants.TAG, + "Couldn't delete lastBoardedDeparture file", + anotherException); + } + } finally { + IOUtils.closeQuietly(inputStream); + } + } + } return mBoardedDeparture; } public void setBoardedDeparture(Departure boardedDeparture) { - this.mBoardedDeparture = boardedDeparture; + if (!ObjectUtils.equals(boardedDeparture, mBoardedDeparture) + || ObjectUtils.compare(mBoardedDeparture, boardedDeparture) != 0) { + this.mBoardedDeparture = boardedDeparture; + + if (mBoardedDeparture != null) { + File cachedDepartureFile = new File(getCacheDir(), + CACHE_FILE_NAME); + FileOutputStream fileOutputStream = null; + try { + fileOutputStream = new FileOutputStream(cachedDepartureFile); + Parcel parcel = Parcel.obtain(); + mBoardedDeparture.writeToParcel(parcel, 0); + fileOutputStream.write(parcel.marshall()); + } catch (Exception e) { + Log.w(Constants.TAG, + "Couldn't write last boarded departure cache file", + e); + } finally { + IOUtils.closeQuietly(fileOutputStream); + } + } + } } public boolean isAlarmSounding() { diff --git a/src/com/dougkeen/bart/AbstractRouteSelectionFragment.java b/src/com/dougkeen/bart/activities/AbstractRouteSelectionFragment.java similarity index 96% rename from src/com/dougkeen/bart/AbstractRouteSelectionFragment.java rename to src/com/dougkeen/bart/activities/AbstractRouteSelectionFragment.java index 580678f..8936677 100644 --- a/src/com/dougkeen/bart/AbstractRouteSelectionFragment.java +++ b/src/com/dougkeen/bart/activities/AbstractRouteSelectionFragment.java @@ -1,131 +1,132 @@ -package com.dougkeen.bart; - -import android.app.Dialog; -import android.content.Context; -import android.content.DialogInterface; -import android.content.SharedPreferences; -import android.content.SharedPreferences.Editor; -import android.os.Bundle; -import android.support.v4.app.DialogFragment; -import android.support.v4.app.FragmentActivity; -import android.widget.ArrayAdapter; -import android.widget.Spinner; -import android.widget.Toast; - -import com.WazaBe.HoloEverywhere.AlertDialog; -import com.dougkeen.bart.model.Station; - -public abstract class AbstractRouteSelectionFragment extends DialogFragment { - - private static final String KEY_LAST_SELECTED_DESTINATION = "lastSelectedDestination"; - private static final String KEY_LAST_SELECTED_ORIGIN = "lastSelectedOrigin"; - protected String mTitle; - - public AbstractRouteSelectionFragment(String title) { - super(); - mTitle = title; - } - - @Override - public void onStart() { - super.onStart(); - - SharedPreferences preferences = getActivity().getPreferences( - Context.MODE_PRIVATE); - - final int lastSelectedOriginPosition = preferences.getInt( - KEY_LAST_SELECTED_ORIGIN, 0); - final int lastSelectedDestinationPosition = preferences.getInt( - KEY_LAST_SELECTED_DESTINATION, 1); - - final Dialog dialog = getDialog(); - final FragmentActivity activity = getActivity(); - ArrayAdapter originSpinnerAdapter = new ArrayAdapter( - activity, android.R.layout.simple_spinner_item, - Station.getStationList()); - originSpinnerAdapter - .setDropDownViewResource(R.layout.simple_spinner_dropdown_item); - final Spinner originSpinner = (Spinner) dialog - .findViewById(R.id.origin_spinner); - originSpinner.setAdapter(originSpinnerAdapter); - originSpinner.setSelection(lastSelectedOriginPosition); - - ArrayAdapter destinationSpinnerAdapter = new ArrayAdapter( - activity, android.R.layout.simple_spinner_item, - Station.getStationList()); - destinationSpinnerAdapter - .setDropDownViewResource(R.layout.simple_spinner_dropdown_item); - - final Spinner destinationSpinner = (Spinner) dialog - .findViewById(R.id.destination_spinner); - destinationSpinner.setAdapter(destinationSpinnerAdapter); - destinationSpinner.setSelection(lastSelectedDestinationPosition); - } - - @Override - public Dialog onCreateDialog(Bundle savedInstanceState) { - final FragmentActivity activity = getActivity(); - - return new AlertDialog.Builder(activity) - .setTitle(mTitle) - .setCancelable(true) - .setView(R.layout.route_form) - .setPositiveButton(R.string.ok, - new DialogInterface.OnClickListener() { - @Override - public void onClick(DialogInterface dialog, - int which) { - handleOkClick(); - } - }) - .setNegativeButton(R.string.cancel, - new DialogInterface.OnClickListener() { - public void onClick(DialogInterface dialog, - int whichButton) { - dialog.cancel(); - } - }).create(); - } - - protected void handleOkClick() { - final Dialog dialog = getDialog(); - final Spinner originSpinner = (Spinner) dialog - .findViewById(R.id.origin_spinner); - final Spinner destinationSpinner = (Spinner) dialog - .findViewById(R.id.destination_spinner); - - Station origin = (Station) originSpinner.getSelectedItem(); - Station destination = (Station) destinationSpinner.getSelectedItem(); - if (origin == null) { - Toast.makeText(dialog.getContext(), - com.dougkeen.bart.R.string.error_null_origin, - Toast.LENGTH_LONG).show(); - return; - } - if (destination == null) { - Toast.makeText(dialog.getContext(), - com.dougkeen.bart.R.string.error_null_destination, - Toast.LENGTH_LONG).show(); - return; - } - if (origin.equals(destination)) { - Toast.makeText( - dialog.getContext(), - com.dougkeen.bart.R.string.error_matching_origin_and_destination, - Toast.LENGTH_LONG).show(); - return; - } - - final Editor prefsEditor = getActivity().getPreferences( - Context.MODE_PRIVATE).edit(); - prefsEditor.putInt(KEY_LAST_SELECTED_ORIGIN, - originSpinner.getSelectedItemPosition()); - prefsEditor.putInt(KEY_LAST_SELECTED_DESTINATION, - destinationSpinner.getSelectedItemPosition()); - prefsEditor.commit(); - - onOkButtonClick(origin, destination); - } - - abstract protected void onOkButtonClick(Station origin, Station destination); +package com.dougkeen.bart.activities; + +import android.app.Dialog; +import android.content.Context; +import android.content.DialogInterface; +import android.content.SharedPreferences; +import android.content.SharedPreferences.Editor; +import android.os.Bundle; +import android.support.v4.app.DialogFragment; +import android.support.v4.app.FragmentActivity; +import android.widget.ArrayAdapter; +import android.widget.Spinner; +import android.widget.Toast; + +import com.WazaBe.HoloEverywhere.AlertDialog; +import com.dougkeen.bart.R; +import com.dougkeen.bart.model.Station; + +public abstract class AbstractRouteSelectionFragment extends DialogFragment { + + private static final String KEY_LAST_SELECTED_DESTINATION = "lastSelectedDestination"; + private static final String KEY_LAST_SELECTED_ORIGIN = "lastSelectedOrigin"; + protected String mTitle; + + public AbstractRouteSelectionFragment(String title) { + super(); + mTitle = title; + } + + @Override + public void onStart() { + super.onStart(); + + SharedPreferences preferences = getActivity().getPreferences( + Context.MODE_PRIVATE); + + final int lastSelectedOriginPosition = preferences.getInt( + KEY_LAST_SELECTED_ORIGIN, 0); + final int lastSelectedDestinationPosition = preferences.getInt( + KEY_LAST_SELECTED_DESTINATION, 1); + + final Dialog dialog = getDialog(); + final FragmentActivity activity = getActivity(); + ArrayAdapter originSpinnerAdapter = new ArrayAdapter( + activity, android.R.layout.simple_spinner_item, + Station.getStationList()); + originSpinnerAdapter + .setDropDownViewResource(R.layout.simple_spinner_dropdown_item); + final Spinner originSpinner = (Spinner) dialog + .findViewById(R.id.origin_spinner); + originSpinner.setAdapter(originSpinnerAdapter); + originSpinner.setSelection(lastSelectedOriginPosition); + + ArrayAdapter destinationSpinnerAdapter = new ArrayAdapter( + activity, android.R.layout.simple_spinner_item, + Station.getStationList()); + destinationSpinnerAdapter + .setDropDownViewResource(R.layout.simple_spinner_dropdown_item); + + final Spinner destinationSpinner = (Spinner) dialog + .findViewById(R.id.destination_spinner); + destinationSpinner.setAdapter(destinationSpinnerAdapter); + destinationSpinner.setSelection(lastSelectedDestinationPosition); + } + + @Override + public Dialog onCreateDialog(Bundle savedInstanceState) { + final FragmentActivity activity = getActivity(); + + return new AlertDialog.Builder(activity) + .setTitle(mTitle) + .setCancelable(true) + .setView(R.layout.route_form) + .setPositiveButton(R.string.ok, + new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, + int which) { + handleOkClick(); + } + }) + .setNegativeButton(R.string.cancel, + new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, + int whichButton) { + dialog.cancel(); + } + }).create(); + } + + protected void handleOkClick() { + final Dialog dialog = getDialog(); + final Spinner originSpinner = (Spinner) dialog + .findViewById(R.id.origin_spinner); + final Spinner destinationSpinner = (Spinner) dialog + .findViewById(R.id.destination_spinner); + + Station origin = (Station) originSpinner.getSelectedItem(); + Station destination = (Station) destinationSpinner.getSelectedItem(); + if (origin == null) { + Toast.makeText(dialog.getContext(), + com.dougkeen.bart.R.string.error_null_origin, + Toast.LENGTH_LONG).show(); + return; + } + if (destination == null) { + Toast.makeText(dialog.getContext(), + com.dougkeen.bart.R.string.error_null_destination, + Toast.LENGTH_LONG).show(); + return; + } + if (origin.equals(destination)) { + Toast.makeText( + dialog.getContext(), + com.dougkeen.bart.R.string.error_matching_origin_and_destination, + Toast.LENGTH_LONG).show(); + return; + } + + final Editor prefsEditor = getActivity().getPreferences( + Context.MODE_PRIVATE).edit(); + prefsEditor.putInt(KEY_LAST_SELECTED_ORIGIN, + originSpinner.getSelectedItemPosition()); + prefsEditor.putInt(KEY_LAST_SELECTED_DESTINATION, + destinationSpinner.getSelectedItemPosition()); + prefsEditor.commit(); + + onOkButtonClick(origin, destination); + } + + abstract protected void onOkButtonClick(Station origin, Station destination); } \ No newline at end of file diff --git a/src/com/dougkeen/bart/AddRouteDialogFragment.java b/src/com/dougkeen/bart/activities/AddRouteDialogFragment.java similarity index 95% rename from src/com/dougkeen/bart/AddRouteDialogFragment.java rename to src/com/dougkeen/bart/activities/AddRouteDialogFragment.java index 97279f5..20ed96c 100644 --- a/src/com/dougkeen/bart/AddRouteDialogFragment.java +++ b/src/com/dougkeen/bart/activities/AddRouteDialogFragment.java @@ -1,55 +1,56 @@ -package com.dougkeen.bart; - -import android.content.ContentValues; -import android.view.View; -import android.widget.CheckBox; - -import com.dougkeen.bart.data.RoutesColumns; -import com.dougkeen.bart.model.Constants; -import com.dougkeen.bart.model.Station; - -public class AddRouteDialogFragment extends AbstractRouteSelectionFragment { - public AddRouteDialogFragment(String title) { - super(title); - } - - @Override - public void onStart() { - super.onStart(); - final View checkboxText = getDialog().findViewById( - R.id.return_checkbox_text); - final View checkbox = getDialog().findViewById(R.id.return_checkbox); - checkboxText.setVisibility(View.VISIBLE); - checkbox.setVisibility(View.VISIBLE); - checkboxText.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View view) { - checkbox.performClick(); - } - }); - } - - @Override - protected void onOkButtonClick(Station origin, Station destination) { - ContentValues values = new ContentValues(); - values.put(RoutesColumns.FROM_STATION.string, origin.abbreviation); - values.put(RoutesColumns.TO_STATION.string, destination.abbreviation); - - getActivity().getContentResolver().insert( - Constants.FAVORITE_CONTENT_URI, values); - - if (((CheckBox) getDialog().findViewById(R.id.return_checkbox)) - .isChecked()) { - values = new ContentValues(); - 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(); - } - -} +package com.dougkeen.bart.activities; + +import android.content.ContentValues; +import android.view.View; +import android.widget.CheckBox; + +import com.dougkeen.bart.R; +import com.dougkeen.bart.data.RoutesColumns; +import com.dougkeen.bart.model.Constants; +import com.dougkeen.bart.model.Station; + +public class AddRouteDialogFragment extends AbstractRouteSelectionFragment { + public AddRouteDialogFragment(String title) { + super(title); + } + + @Override + public void onStart() { + super.onStart(); + final View checkboxText = getDialog().findViewById( + R.id.return_checkbox_text); + final View checkbox = getDialog().findViewById(R.id.return_checkbox); + checkboxText.setVisibility(View.VISIBLE); + checkbox.setVisibility(View.VISIBLE); + checkboxText.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + checkbox.performClick(); + } + }); + } + + @Override + protected void onOkButtonClick(Station origin, Station destination) { + ContentValues values = new ContentValues(); + values.put(RoutesColumns.FROM_STATION.string, origin.abbreviation); + values.put(RoutesColumns.TO_STATION.string, destination.abbreviation); + + getActivity().getContentResolver().insert( + Constants.FAVORITE_CONTENT_URI, values); + + if (((CheckBox) getDialog().findViewById(R.id.return_checkbox)) + .isChecked()) { + values = new ContentValues(); + 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(); + } + +} diff --git a/src/com/dougkeen/bart/QuickRouteDialogFragment.java b/src/com/dougkeen/bart/activities/QuickRouteDialogFragment.java similarity index 92% rename from src/com/dougkeen/bart/QuickRouteDialogFragment.java rename to src/com/dougkeen/bart/activities/QuickRouteDialogFragment.java index 42f1558..bda8654 100644 --- a/src/com/dougkeen/bart/QuickRouteDialogFragment.java +++ b/src/com/dougkeen/bart/activities/QuickRouteDialogFragment.java @@ -1,21 +1,21 @@ -package com.dougkeen.bart; - -import android.content.Intent; - -import com.dougkeen.bart.model.Constants; -import com.dougkeen.bart.model.Station; - -public class QuickRouteDialogFragment extends AbstractRouteSelectionFragment { - - public QuickRouteDialogFragment(String title) { - super(title); - } - - @Override - protected void onOkButtonClick(Station origin, Station destination) { - startActivity(new Intent(Intent.ACTION_VIEW, - Constants.ARBITRARY_ROUTE_CONTENT_URI_ROOT.buildUpon() - .appendPath(origin.abbreviation) - .appendPath(destination.abbreviation).build())); - } -} +package com.dougkeen.bart.activities; + +import android.content.Intent; + +import com.dougkeen.bart.model.Constants; +import com.dougkeen.bart.model.Station; + +public class QuickRouteDialogFragment extends AbstractRouteSelectionFragment { + + public QuickRouteDialogFragment(String title) { + super(title); + } + + @Override + protected void onOkButtonClick(Station origin, Station destination) { + startActivity(new Intent(Intent.ACTION_VIEW, + Constants.ARBITRARY_ROUTE_CONTENT_URI_ROOT.buildUpon() + .appendPath(origin.abbreviation) + .appendPath(destination.abbreviation).build())); + } +} diff --git a/src/com/dougkeen/bart/RoutesListActivity.java b/src/com/dougkeen/bart/activities/RoutesListActivity.java similarity index 71% rename from src/com/dougkeen/bart/RoutesListActivity.java rename to src/com/dougkeen/bart/activities/RoutesListActivity.java index f1cfd55..cf2c0b9 100644 --- a/src/com/dougkeen/bart/RoutesListActivity.java +++ b/src/com/dougkeen/bart/activities/RoutesListActivity.java @@ -1,4 +1,4 @@ -package com.dougkeen.bart; +package com.dougkeen.bart.activities; import java.util.Calendar; import java.util.TimeZone; @@ -8,17 +8,16 @@ import android.content.ContentValues; import android.content.DialogInterface; import android.content.Intent; import android.database.Cursor; -import android.database.CursorWrapper; import android.net.Uri; import android.os.Bundle; import android.support.v4.app.DialogFragment; +import android.support.v4.app.LoaderManager.LoaderCallbacks; +import android.support.v4.content.CursorLoader; +import android.support.v4.content.Loader; import android.view.View; import android.widget.AdapterView; import android.widget.Button; -import android.widget.CursorAdapter; import android.widget.ListAdapter; -import android.widget.SimpleCursorAdapter; -import android.widget.SimpleCursorAdapter.ViewBinder; import android.widget.TextView; import com.WazaBe.HoloEverywhere.AlertDialog; @@ -27,18 +26,23 @@ import com.actionbarsherlock.view.ActionMode; import com.actionbarsherlock.view.Menu; import com.actionbarsherlock.view.MenuInflater; import com.actionbarsherlock.view.MenuItem; +import com.dougkeen.bart.R; +import com.dougkeen.bart.controls.Ticker; import com.dougkeen.bart.data.CursorUtils; +import com.dougkeen.bart.data.FavoritesArrayAdapter; import com.dougkeen.bart.data.RoutesColumns; import com.dougkeen.bart.model.Constants; import com.dougkeen.bart.model.Station; +import com.dougkeen.bart.model.StationPair; import com.dougkeen.bart.networktasks.GetRouteFareTask; -public class RoutesListActivity extends SherlockFragmentActivity { +public class RoutesListActivity extends SherlockFragmentActivity implements + LoaderCallbacks { + private static final int FAVORITES_LOADER_ID = 0; + private static final TimeZone PACIFIC_TIME = TimeZone .getTimeZone("America/Los_Angeles"); - protected Cursor mQuery; - private Uri mCurrentlySelectedUri; private Station mCurrentlySelectedOrigin; @@ -46,6 +50,8 @@ public class RoutesListActivity extends SherlockFragmentActivity { private ActionMode mActionMode; + private FavoritesArrayAdapter mRoutesAdapter; + /** Called when the activity is first created. */ @Override public void onCreate(Bundle savedInstanceState) { @@ -53,51 +59,24 @@ public class RoutesListActivity extends SherlockFragmentActivity { setContentView(R.layout.main); setTitle(R.string.favorite_routes); - mQuery = managedQuery(Constants.FAVORITE_CONTENT_URI, new String[] { - RoutesColumns._ID.string, RoutesColumns.FROM_STATION.string, - RoutesColumns.TO_STATION.string, RoutesColumns.FARE.string, - RoutesColumns.FARE_LAST_UPDATED.string, - RoutesColumns.AVERAGE_TRIP_SAMPLE_COUNT.string, - RoutesColumns.AVERAGE_TRIP_LENGTH.string }, null, null, - RoutesColumns._ID.string); + mRoutesAdapter = new FavoritesArrayAdapter(this, + R.layout.favorite_listing); - refreshFares(); + getSupportLoaderManager().initLoader(FAVORITES_LOADER_ID, null, this); - SimpleCursorAdapter adapter = new SimpleCursorAdapter(this, - R.layout.favorite_listing, mQuery, new String[] { - RoutesColumns.FROM_STATION.string, - RoutesColumns.TO_STATION.string, - RoutesColumns.FARE.string }, new int[] { - R.id.originText, R.id.destinationText, R.id.fareText }); - adapter.setViewBinder(new ViewBinder() { - public boolean setViewValue(View view, Cursor cursor, - int columnIndex) { - if (view.getId() == R.id.fareText) { - String fare = cursor.getString(columnIndex); - if (fare != null) { - ((TextView) view).setSingleLine(false); - ((TextView) view).setText("Fare:\n" + fare); - } - } else { - ((TextView) view).setText(Station.getByAbbreviation(cursor - .getString(columnIndex)).name); - } - return true; - } - }); - - setListAdapter(adapter); + setListAdapter(mRoutesAdapter); getListView().setOnItemClickListener( new AdapterView.OnItemClickListener() { - @Override public void onItemClick(AdapterView l, View v, int position, long id) { + ; startActivity(new Intent(Intent.ACTION_VIEW, ContentUris.withAppendedId( - Constants.FAVORITE_CONTENT_URI, id))); + Constants.FAVORITE_CONTENT_URI, + getListAdapter().getItem(position) + .getId()))); } - }); getListView().setEmptyView(findViewById(android.R.id.empty)); getListView().setOnItemLongClickListener( @@ -109,17 +88,13 @@ public class RoutesListActivity extends SherlockFragmentActivity { mActionMode.finish(); } - mCurrentlySelectedUri = ContentUris.withAppendedId( - Constants.FAVORITE_CONTENT_URI, id); + StationPair item = getListAdapter().getItem(position); - CursorWrapper item = (CursorWrapper) getListAdapter() - .getItem(position); - Station orig = Station.getByAbbreviation(CursorUtils - .getString(item, RoutesColumns.FROM_STATION)); - Station dest = Station.getByAbbreviation(CursorUtils - .getString(item, RoutesColumns.TO_STATION)); - mCurrentlySelectedOrigin = orig; - mCurrentlySelectedDestination = dest; + mCurrentlySelectedUri = ContentUris.withAppendedId( + Constants.FAVORITE_CONTENT_URI, item.getId()); + + mCurrentlySelectedOrigin = item.getOrigin(); + mCurrentlySelectedDestination = item.getDestination(); startContextualActionMode(); return true; @@ -157,31 +132,58 @@ public class RoutesListActivity extends SherlockFragmentActivity { } } + @Override + public Loader 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 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 loader) { + // Nothing to do + } + @SuppressWarnings("unchecked") private AdapterView getListView() { return (AdapterView) findViewById(android.R.id.list); } - private CursorAdapter mListAdapter; - - protected CursorAdapter getListAdapter() { - return mListAdapter; + protected FavoritesArrayAdapter getListAdapter() { + return mRoutesAdapter; } - protected void setListAdapter(SimpleCursorAdapter adapter) { - mListAdapter = adapter; - getListView().setAdapter(mListAdapter); + protected void setListAdapter(FavoritesArrayAdapter adapter) { + mRoutesAdapter = adapter; + getListView().setAdapter(mRoutesAdapter); } - private void refreshFares() { - if (mQuery.moveToFirst()) { + private void refreshFares(Cursor cursor) { + if (cursor.moveToFirst()) { do { final Station orig = Station.getByAbbreviation(CursorUtils - .getString(mQuery, RoutesColumns.FROM_STATION)); + .getString(cursor, RoutesColumns.FROM_STATION)); final Station dest = Station.getByAbbreviation(CursorUtils - .getString(mQuery, RoutesColumns.TO_STATION)); - final Long id = CursorUtils.getLong(mQuery, RoutesColumns._ID); - final Long lastUpdateMillis = CursorUtils.getLong(mQuery, + .getString(cursor, RoutesColumns.TO_STATION)); + final Long id = CursorUtils.getLong(cursor, RoutesColumns._ID); + final Long lastUpdateMillis = CursorUtils.getLong(cursor, RoutesColumns.FARE_LAST_UPDATED); Calendar now = Calendar.getInstance(); @@ -215,7 +217,7 @@ public class RoutesListActivity extends SherlockFragmentActivity { }; fareTask.execute(new GetRouteFareTask.Params(orig, dest)); } - } while (mQuery.moveToNext()); + } while (cursor.moveToNext()); } } @@ -235,9 +237,41 @@ public class RoutesListActivity extends SherlockFragmentActivity { @Override protected void onResume() { super.onResume(); - ((TextView) findViewById(android.R.id.empty)) - .setText(R.string.empty_favorites_list_message); - refreshFares(); + Ticker.getInstance().startTicking(this); + if (mRoutesAdapter != null && !mRoutesAdapter.isEmpty() + && !mRoutesAdapter.areEtdListenersActive()) { + mRoutesAdapter.setUpEtdListeners(); + } + } + + @Override + protected void onPause() { + super.onPause(); + if (mRoutesAdapter != null && mRoutesAdapter.areEtdListenersActive()) { + mRoutesAdapter.clearEtdListeners(); + } + } + + @Override + protected void onStop() { + super.onStop(); + Ticker.getInstance().stopTicking(this); + } + + @Override + protected void onDestroy() { + super.onDestroy(); + if (mRoutesAdapter != null) { + mRoutesAdapter.close(); + } + } + + @Override + public void onWindowFocusChanged(boolean hasFocus) { + super.onWindowFocusChanged(hasFocus); + if (hasFocus) { + Ticker.getInstance().startTicking(this); + } } public boolean onCreateOptionsMenu(Menu menu) { diff --git a/src/com/dougkeen/bart/TrainAlertDialogFragment.java b/src/com/dougkeen/bart/activities/TrainAlertDialogFragment.java similarity index 94% rename from src/com/dougkeen/bart/TrainAlertDialogFragment.java rename to src/com/dougkeen/bart/activities/TrainAlertDialogFragment.java index 644478d..428b0b2 100644 --- a/src/com/dougkeen/bart/TrainAlertDialogFragment.java +++ b/src/com/dougkeen/bart/activities/TrainAlertDialogFragment.java @@ -1,4 +1,4 @@ -package com.dougkeen.bart; +package com.dougkeen.bart.activities; import net.simonvt.widget.NumberPicker; import android.app.Dialog; @@ -12,6 +12,9 @@ import android.support.v4.app.DialogFragment; import android.support.v4.app.FragmentActivity; import com.WazaBe.HoloEverywhere.AlertDialog; +import com.dougkeen.bart.BartRunnerApplication; +import com.dougkeen.bart.R; +import com.dougkeen.bart.services.NotificationService; public class TrainAlertDialogFragment extends DialogFragment { diff --git a/src/com/dougkeen/bart/ViewDeparturesActivity.java b/src/com/dougkeen/bart/activities/ViewDeparturesActivity.java similarity index 94% rename from src/com/dougkeen/bart/ViewDeparturesActivity.java rename to src/com/dougkeen/bart/activities/ViewDeparturesActivity.java index c9818f3..3a48ed0 100644 --- a/src/com/dougkeen/bart/ViewDeparturesActivity.java +++ b/src/com/dougkeen/bart/activities/ViewDeparturesActivity.java @@ -1,625 +1,631 @@ -package com.dougkeen.bart; - -import java.util.List; - -import android.app.AlertDialog; -import android.app.AlertDialog.Builder; -import android.content.ComponentName; -import android.content.Context; -import android.content.DialogInterface; -import android.content.Intent; -import android.content.ServiceConnection; -import android.database.Cursor; -import android.graphics.Color; -import android.graphics.drawable.GradientDrawable; -import android.media.MediaPlayer; -import android.media.RingtoneManager; -import android.net.Uri; -import android.os.Bundle; -import android.os.Handler; -import android.os.IBinder; -import android.os.Parcelable; -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.util.Linkify; -import android.util.Log; -import android.view.View; -import android.view.WindowManager; -import android.widget.AdapterView; -import android.widget.ImageView; -import android.widget.ListAdapter; -import android.widget.ProgressBar; -import android.widget.TextView; -import android.widget.Toast; - -import com.actionbarsherlock.app.SherlockFragmentActivity; -import com.actionbarsherlock.view.ActionMode; -import com.actionbarsherlock.view.Menu; -import com.actionbarsherlock.view.MenuInflater; -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.Ticker; -import com.dougkeen.bart.data.RoutesColumns; -import com.dougkeen.bart.model.Constants; -import com.dougkeen.bart.model.Departure; -import com.dougkeen.bart.model.Station; -import com.dougkeen.bart.model.StationPair; -import com.dougkeen.bart.model.TextProvider; -import com.dougkeen.util.Observer; - -public class ViewDeparturesActivity extends SherlockFragmentActivity implements - EtdServiceListener { - - private static final int LOADER_ID = 123; - - private Uri mUri; - - private Station mOrigin; - private Station mDestination; - - private Departure mSelectedDeparture; - - private DepartureArrayAdapter mDeparturesAdapter; - - private TextView mEmptyView; - private ProgressBar mProgress; - - private ActionMode mActionMode; - - private EtdService mEtdService; - - private Handler mHandler = new Handler(); - - private boolean mBound = false; - - @Override - protected void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - - setContentView(R.layout.departures); - - final Intent intent = getIntent(); - - String action = intent.getAction(); - - if (Intent.ACTION_VIEW.equals(action)) { - mUri = intent.getData(); - } - - final Uri uri = mUri; - - 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() { - @Override - public Loader 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 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(); - setListTitle(); - if (mBound && mEtdService != null) - mEtdService - .registerListener(ViewDeparturesActivity.this); - refreshBoardedDeparture(); - - getSupportLoaderManager().destroyLoader(LOADER_ID); - } - - @Override - public void onLoaderReset(Loader loader) { - // ignore - } - }); - } - - mEmptyView = (TextView) findViewById(android.R.id.empty); - mEmptyView.setText(R.string.departure_wait_message); - - mProgress = (ProgressBar) findViewById(android.R.id.progress); - - mDeparturesAdapter = new DepartureArrayAdapter(this, - R.layout.departure_listing); - - if (savedInstanceState != null) { - if (savedInstanceState.containsKey("departures")) { - for (Parcelable departure : savedInstanceState - .getParcelableArray("departures")) { - mDeparturesAdapter.add((Departure) departure); - } - mDeparturesAdapter.notifyDataSetChanged(); - } - if (savedInstanceState.containsKey("selectedDeparture")) { - mSelectedDeparture = (Departure) savedInstanceState - .getParcelable("selectedDeparture"); - } - if (savedInstanceState.getBoolean("hasActionMode") - && mSelectedDeparture != null) { - startDepartureActionMode(); - } - } - setListAdapter(mDeparturesAdapter); - getListView().setEmptyView(findViewById(android.R.id.empty)); - getListView().setOnItemClickListener( - new AdapterView.OnItemClickListener() { - @Override - public void onItemClick(AdapterView adapterView, - View view, int position, long id) { - mSelectedDeparture = (Departure) getListAdapter() - .getItem(position); - if (mActionMode != null) { - mActionMode.finish(); - } - startDepartureActionMode(); - } - }); - - findViewById(R.id.missingDepartureText).setVisibility(View.VISIBLE); - - refreshBoardedDeparture(); - - getSupportActionBar().setHomeButtonEnabled(true); - getSupportActionBar().setDisplayHomeAsUpEnabled(true); - - final BartRunnerApplication bartRunnerApplication = (BartRunnerApplication) getApplication(); - if (bartRunnerApplication.shouldPlayAlarmRingtone()) { - soundTheAlarm(); - } - - if (bartRunnerApplication.isAlarmSounding()) { - Builder builder = new AlertDialog.Builder(this); - builder.setMessage(R.string.train_alert_text) - .setCancelable(false) - .setNeutralButton(R.string.silence_alarm, - new DialogInterface.OnClickListener() { - @Override - public void onClick(DialogInterface dialog, - int which) { - silenceAlarm(); - dialog.dismiss(); - } - }).show(); - } - } - - private void soundTheAlarm() { - final BartRunnerApplication application = (BartRunnerApplication) getApplication(); - - Uri alertSound = RingtoneManager - .getDefaultUri(RingtoneManager.TYPE_ALARM); - - if (alertSound == null || !tryToPlayRingtone(alertSound)) { - alertSound = RingtoneManager - .getDefaultUri(RingtoneManager.TYPE_NOTIFICATION); - if (alertSound == null || !tryToPlayRingtone(alertSound)) { - alertSound = RingtoneManager - .getDefaultUri(RingtoneManager.TYPE_RINGTONE); - } - } - if (application.getAlarmMediaPlayer() == null) { - tryToPlayRingtone(alertSound); - } - mHandler.postDelayed(new Runnable() { - @Override - public void run() { - silenceAlarm(); - } - }, 20000); - - application.setPlayAlarmRingtone(false); - application.setAlarmSounding(true); - } - - private boolean tryToPlayRingtone(Uri alertSound) { - MediaPlayer mediaPlayer = MediaPlayer.create(this, alertSound); - if (mediaPlayer == null) - return false; - mediaPlayer.setLooping(true); - mediaPlayer.start(); - ((BartRunnerApplication) getApplication()) - .setAlarmMediaPlayer(mediaPlayer); - return true; - } - - private void silenceAlarm() { - final BartRunnerApplication application = (BartRunnerApplication) getApplication(); - final MediaPlayer mediaPlayer = application.getAlarmMediaPlayer(); - application.setAlarmSounding(false); - application.setAlarmMediaPlayer(null); - try { - if (mediaPlayer != null && mediaPlayer.isPlaying()) { - mediaPlayer.stop(); - mediaPlayer.release(); - } - } catch (IllegalStateException e) { - Log.e(Constants.TAG, - "Couldn't stop media player; It was in an invalid state", e); - } - } - - private void setListTitle() { - ((TextView) findViewById(R.id.listTitle)).setText(mOrigin.name + " to " - + mDestination.name); - } - - @SuppressWarnings("unchecked") - private AdapterView getListView() { - return (AdapterView) findViewById(android.R.id.list); - } - - 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); - } - } - }; - - private Observer mAlarmPendingObserver; - - protected DepartureArrayAdapter getListAdapter() { - return mDeparturesAdapter; - } - - protected void setListAdapter(DepartureArrayAdapter adapter) { - mDeparturesAdapter = adapter; - getListView().setAdapter(mDeparturesAdapter); - } - - @Override - protected void onStop() { - super.onStop(); - if (mEtdService != null) - mEtdService.unregisterListener(this); - if (mAlarmPendingObserver != null) - ((BartRunnerApplication) getApplication()) - .getAlarmPendingObservable().unregisterObserver( - mAlarmPendingObserver); - if (mBound) - unbindService(mConnection); - Ticker.getInstance().stopTicking(); - WakeLocker.release(); - } - - @Override - protected void onSaveInstanceState(Bundle outState) { - super.onSaveInstanceState(outState); - if (mOrigin != null || mDestination != null) { - /* - * If origin or destination are null, this thing was never - * initialized in the first place, so there's really nothing to save - */ - Departure[] departures = new Departure[mDeparturesAdapter - .getCount()]; - for (int i = mDeparturesAdapter.getCount() - 1; i >= 0; i--) { - departures[i] = mDeparturesAdapter.getItem(i); - } - outState.putParcelableArray("departures", departures); - outState.putParcelable("selectedDeparture", mSelectedDeparture); - outState.putBoolean("hasActionMode", mActionMode != null); - outState.putString("origin", mOrigin.abbreviation); - outState.putString("destination", mDestination.abbreviation); - } - } - - @Override - protected void onStart() { - super.onStart(); - bindService(new Intent(this, EtdService.class), mConnection, - Context.BIND_AUTO_CREATE); - Ticker.getInstance().startTicking(); - } - - @Override - public void onWindowFocusChanged(boolean hasFocus) { - super.onWindowFocusChanged(hasFocus); - if (hasFocus) { - getWindow() - .addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); - Ticker.getInstance().startTicking(); - refreshBoardedDeparture(); - } - } - - @Override - public boolean onCreateOptionsMenu(Menu menu) { - MenuInflater inflater = getSupportMenuInflater(); - inflater.inflate(R.menu.route_menu, menu); - final MenuItem cancelAlarmButton = menu - .findItem(R.id.cancel_alarm_button); - final BartRunnerApplication application = (BartRunnerApplication) getApplication(); - if (application.isAlarmPending()) { - cancelAlarmButton.setVisible(true); - } - mAlarmPendingObserver = new Observer() { - @Override - public void onUpdate(final Boolean newValue) { - runOnUiThread(new Runnable() { - @Override - public void run() { - cancelAlarmButton.setVisible(newValue); - } - }); - } - }; - application.getAlarmPendingObservable().registerObserver( - mAlarmPendingObserver); - return true; - } - - @Override - public boolean onOptionsItemSelected(MenuItem item) { - int itemId = item.getItemId(); - if (itemId == android.R.id.home) { - Intent intent = new Intent(Intent.ACTION_VIEW, - Constants.FAVORITE_CONTENT_URI); - intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP); - startActivity(intent); - return true; - } else if (itemId == R.id.cancel_alarm_button) { - Intent intent = new Intent(this, NotificationService.class); - intent.putExtra("cancelAlarm", true); - startService(intent); - return true; - } else if (itemId == R.id.view_on_bart_site_button) { - startActivity(new Intent( - Intent.ACTION_VIEW, - Uri.parse("http://m.bart.gov/schedules/qp_results.aspx?type=departure&date=today&time=" - + DateFormat.format("h:mmaa", - System.currentTimeMillis()) - + "&orig=" - + mOrigin.abbreviation - + "&dest=" - + mDestination.abbreviation))); - return true; - } else if (itemId == R.id.view_system_map_button) { - startActivity(new Intent(this, ViewMapActivity.class)); - return true; - } else { - return super.onOptionsItemSelected(item); - } - } - - private void refreshBoardedDeparture() { - final Departure boardedDeparture = ((BartRunnerApplication) getApplication()) - .getBoardedDeparture(); - if (boardedDeparture == null - || boardedDeparture.getStationPair() == null - || !boardedDeparture.getStationPair().equals(getStationPair())) { - findViewById(R.id.yourTrainSection).setVisibility(View.GONE); - return; - } - - findViewById(R.id.yourTrainSection).setVisibility(View.VISIBLE); - ((TextView) findViewById(R.id.yourTrainDestinationText)) - .setText(boardedDeparture.getTrainDestination().toString()); - - ((TextView) findViewById(R.id.yourTrainTrainLengthText)) - .setText(boardedDeparture.getTrainLengthText()); - - ImageView colorBar = (ImageView) findViewById(R.id.yourTrainDestinationColorBar); - ((GradientDrawable) colorBar.getDrawable()).setColor(Color - .parseColor(boardedDeparture.getTrainDestinationColor())); - if (boardedDeparture.isBikeAllowed()) { - ((ImageView) findViewById(R.id.yourTrainBikeIcon)) - .setVisibility(View.VISIBLE); - } else { - ((ImageView) findViewById(R.id.yourTrainBikeIcon)) - .setVisibility(View.INVISIBLE); - } - if (boardedDeparture.getRequiresTransfer()) { - ((ImageView) findViewById(R.id.yourTrainXferIcon)) - .setVisibility(View.VISIBLE); - } else { - ((ImageView) findViewById(R.id.yourTrainXferIcon)) - .setVisibility(View.INVISIBLE); - } - CountdownTextView departureCountdown = (CountdownTextView) findViewById(R.id.yourTrainDepartureCountdown); - CountdownTextView arrivalCountdown = (CountdownTextView) findViewById(R.id.yourTrainArrivalCountdown); - - departureCountdown.setText("Leaves in " - + boardedDeparture.getCountdownText() + " " - + boardedDeparture.getUncertaintyText()); - departureCountdown.setTextProvider(new TextProvider() { - @Override - public String getText(long tickNumber) { - if (boardedDeparture.hasDeparted()) { - return "Departed"; - } else { - return "Leaves in " + boardedDeparture.getCountdownText() - + " " + boardedDeparture.getUncertaintyText(); - } - } - }); - - arrivalCountdown.setText(boardedDeparture - .getEstimatedArrivalMinutesLeftText(this)); - arrivalCountdown.setTextProvider(new TextProvider() { - @Override - public String getText(long tickNumber) { - return boardedDeparture - .getEstimatedArrivalMinutesLeftText(ViewDeparturesActivity.this); - } - }); - } - - private void startDepartureActionMode() { - mActionMode = startActionMode(new DepartureActionMode()); - mActionMode.setTitle(mSelectedDeparture.getTrainDestinationName()); - mActionMode.setSubtitle(mSelectedDeparture.getTrainLengthText()); - } - - private class DepartureActionMode implements ActionMode.Callback { - - @Override - public boolean onCreateActionMode(ActionMode mode, Menu menu) { - mode.getMenuInflater().inflate(R.menu.departure_context_menu, menu); - return true; - } - - @Override - public boolean onPrepareActionMode(ActionMode mode, Menu menu) { - return false; - } - - @Override - public boolean onActionItemClicked(ActionMode mode, MenuItem item) { - if (item.getItemId() == R.id.boardTrain) { - final BartRunnerApplication application = (BartRunnerApplication) getApplication(); - mSelectedDeparture.setPassengerDestination(mDestination); - application.setBoardedDeparture(mSelectedDeparture); - refreshBoardedDeparture(); - - // Stop the notification service - stopService(new Intent(ViewDeparturesActivity.this, - NotificationService.class)); - - // Don't prompt for alert if train is about to leave - if (mSelectedDeparture.getMeanSecondsLeft() / 60 > 1) { - new TrainAlertDialogFragment().show( - getSupportFragmentManager(), "dialog"); - } - - mode.finish(); - return true; - } - return false; - } - - @Override - public void onDestroyActionMode(ActionMode mode) { - mActionMode = null; - } - - } - - @Override - public void onETDChanged(final List 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(); - for (Departure departure : departures) { - listAdapter.add(departure); - } - } - - refreshBoardedDeparture(); - - 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); - } -} +package com.dougkeen.bart.activities; + +import java.util.List; + +import android.app.AlertDialog; +import android.app.AlertDialog.Builder; +import android.content.ComponentName; +import android.content.Context; +import android.content.DialogInterface; +import android.content.Intent; +import android.content.ServiceConnection; +import android.database.Cursor; +import android.graphics.Color; +import android.graphics.drawable.GradientDrawable; +import android.media.MediaPlayer; +import android.media.RingtoneManager; +import android.net.Uri; +import android.os.Bundle; +import android.os.Handler; +import android.os.IBinder; +import android.os.Parcelable; +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.util.Linkify; +import android.util.Log; +import android.view.View; +import android.view.WindowManager; +import android.widget.AdapterView; +import android.widget.ImageView; +import android.widget.ListAdapter; +import android.widget.ProgressBar; +import android.widget.TextView; +import android.widget.Toast; + +import com.actionbarsherlock.app.SherlockFragmentActivity; +import com.actionbarsherlock.view.ActionMode; +import com.actionbarsherlock.view.Menu; +import com.actionbarsherlock.view.MenuInflater; +import com.actionbarsherlock.view.MenuItem; +import com.dougkeen.bart.BartRunnerApplication; +import com.dougkeen.bart.R; +import com.dougkeen.bart.controls.CountdownTextView; +import com.dougkeen.bart.controls.Ticker; +import com.dougkeen.bart.data.DepartureArrayAdapter; +import com.dougkeen.bart.data.RoutesColumns; +import com.dougkeen.bart.model.Constants; +import com.dougkeen.bart.model.Departure; +import com.dougkeen.bart.model.Station; +import com.dougkeen.bart.model.StationPair; +import com.dougkeen.bart.model.TextProvider; +import com.dougkeen.bart.services.EtdService; +import com.dougkeen.bart.services.EtdService.EtdServiceBinder; +import com.dougkeen.bart.services.EtdService.EtdServiceListener; +import com.dougkeen.bart.services.NotificationService; +import com.dougkeen.util.Observer; +import com.dougkeen.util.WakeLocker; + +public class ViewDeparturesActivity extends SherlockFragmentActivity implements + EtdServiceListener { + + private static final int LOADER_ID = 123; + + private Uri mUri; + + private Station mOrigin; + private Station mDestination; + + private Departure mSelectedDeparture; + + private DepartureArrayAdapter mDeparturesAdapter; + + private TextView mEmptyView; + private ProgressBar mProgress; + + private ActionMode mActionMode; + + private EtdService mEtdService; + + private Handler mHandler = new Handler(); + + private boolean mBound = false; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + setContentView(R.layout.departures); + + final Intent intent = getIntent(); + + String action = intent.getAction(); + + if (Intent.ACTION_VIEW.equals(action)) { + mUri = intent.getData(); + } + + final Uri uri = mUri; + + 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() { + @Override + public Loader 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 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(); + + getSupportLoaderManager().destroyLoader(LOADER_ID); + } + + @Override + public void onLoaderReset(Loader loader) { + // ignore + } + }); + } + + mEmptyView = (TextView) findViewById(android.R.id.empty); + mEmptyView.setText(R.string.departure_wait_message); + + mProgress = (ProgressBar) findViewById(android.R.id.progress); + + mDeparturesAdapter = new DepartureArrayAdapter(this, + R.layout.departure_listing); + + if (savedInstanceState != null) { + if (savedInstanceState.containsKey("departures")) { + for (Parcelable departure : savedInstanceState + .getParcelableArray("departures")) { + mDeparturesAdapter.add((Departure) departure); + } + mDeparturesAdapter.notifyDataSetChanged(); + } + if (savedInstanceState.containsKey("selectedDeparture")) { + mSelectedDeparture = (Departure) savedInstanceState + .getParcelable("selectedDeparture"); + } + if (savedInstanceState.getBoolean("hasActionMode") + && mSelectedDeparture != null) { + startDepartureActionMode(); + } + } + setListAdapter(mDeparturesAdapter); + getListView().setEmptyView(findViewById(android.R.id.empty)); + getListView().setOnItemClickListener( + new AdapterView.OnItemClickListener() { + @Override + public void onItemClick(AdapterView adapterView, + View view, int position, long id) { + mSelectedDeparture = (Departure) getListAdapter() + .getItem(position); + if (mActionMode != null) { + mActionMode.finish(); + } + startDepartureActionMode(); + } + }); + + findViewById(R.id.missingDepartureText).setVisibility(View.VISIBLE); + + refreshBoardedDeparture(); + + getSupportActionBar().setHomeButtonEnabled(true); + getSupportActionBar().setDisplayHomeAsUpEnabled(true); + + final BartRunnerApplication bartRunnerApplication = (BartRunnerApplication) getApplication(); + if (bartRunnerApplication.shouldPlayAlarmRingtone()) { + soundTheAlarm(); + } + + if (bartRunnerApplication.isAlarmSounding()) { + Builder builder = new AlertDialog.Builder(this); + builder.setMessage(R.string.train_alert_text) + .setCancelable(false) + .setNeutralButton(R.string.silence_alarm, + new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, + int which) { + silenceAlarm(); + dialog.dismiss(); + } + }).show(); + } + } + + private void soundTheAlarm() { + final BartRunnerApplication application = (BartRunnerApplication) getApplication(); + + Uri alertSound = RingtoneManager + .getDefaultUri(RingtoneManager.TYPE_ALARM); + + if (alertSound == null || !tryToPlayRingtone(alertSound)) { + alertSound = RingtoneManager + .getDefaultUri(RingtoneManager.TYPE_NOTIFICATION); + if (alertSound == null || !tryToPlayRingtone(alertSound)) { + alertSound = RingtoneManager + .getDefaultUri(RingtoneManager.TYPE_RINGTONE); + } + } + if (application.getAlarmMediaPlayer() == null) { + tryToPlayRingtone(alertSound); + } + mHandler.postDelayed(new Runnable() { + @Override + public void run() { + silenceAlarm(); + } + }, 20000); + + application.setPlayAlarmRingtone(false); + application.setAlarmSounding(true); + } + + private boolean tryToPlayRingtone(Uri alertSound) { + MediaPlayer mediaPlayer = MediaPlayer.create(this, alertSound); + if (mediaPlayer == null) + return false; + mediaPlayer.setLooping(true); + mediaPlayer.start(); + ((BartRunnerApplication) getApplication()) + .setAlarmMediaPlayer(mediaPlayer); + return true; + } + + private void silenceAlarm() { + final BartRunnerApplication application = (BartRunnerApplication) getApplication(); + final MediaPlayer mediaPlayer = application.getAlarmMediaPlayer(); + application.setAlarmSounding(false); + application.setAlarmMediaPlayer(null); + try { + if (mediaPlayer != null && mediaPlayer.isPlaying()) { + mediaPlayer.stop(); + mediaPlayer.release(); + } + } catch (IllegalStateException e) { + Log.e(Constants.TAG, + "Couldn't stop media player; It was in an invalid state", e); + } + } + + private void setListTitle() { + ((TextView) findViewById(R.id.listTitle)).setText(mOrigin.name + " to " + + mDestination.name); + } + + @SuppressWarnings("unchecked") + private AdapterView getListView() { + return (AdapterView) findViewById(android.R.id.list); + } + + 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, false); + } + } + }; + + private Observer mAlarmPendingObserver; + + protected DepartureArrayAdapter getListAdapter() { + return mDeparturesAdapter; + } + + protected void setListAdapter(DepartureArrayAdapter adapter) { + mDeparturesAdapter = adapter; + getListView().setAdapter(mDeparturesAdapter); + } + + @Override + protected void onStop() { + super.onStop(); + if (mEtdService != null) + mEtdService.unregisterListener(this); + if (mAlarmPendingObserver != null) + ((BartRunnerApplication) getApplication()) + .getAlarmPendingObservable().unregisterObserver( + mAlarmPendingObserver); + if (mBound) + unbindService(mConnection); + Ticker.getInstance().stopTicking(this); + WakeLocker.release(); + } + + @Override + protected void onSaveInstanceState(Bundle outState) { + super.onSaveInstanceState(outState); + if (mOrigin != null || mDestination != null) { + /* + * If origin or destination are null, this thing was never + * initialized in the first place, so there's really nothing to save + */ + Departure[] departures = new Departure[mDeparturesAdapter + .getCount()]; + for (int i = mDeparturesAdapter.getCount() - 1; i >= 0; i--) { + departures[i] = mDeparturesAdapter.getItem(i); + } + outState.putParcelableArray("departures", departures); + outState.putParcelable("selectedDeparture", mSelectedDeparture); + outState.putBoolean("hasActionMode", mActionMode != null); + outState.putString("origin", mOrigin.abbreviation); + outState.putString("destination", mDestination.abbreviation); + } + } + + @Override + protected void onStart() { + super.onStart(); + bindService(new Intent(this, EtdService.class), mConnection, + Context.BIND_AUTO_CREATE); + Ticker.getInstance().startTicking(this); + } + + @Override + public void onWindowFocusChanged(boolean hasFocus) { + super.onWindowFocusChanged(hasFocus); + if (hasFocus) { + getWindow() + .addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); + Ticker.getInstance().startTicking(this); + refreshBoardedDeparture(); + } + } + + @Override + public boolean onCreateOptionsMenu(Menu menu) { + MenuInflater inflater = getSupportMenuInflater(); + inflater.inflate(R.menu.route_menu, menu); + final MenuItem cancelAlarmButton = menu + .findItem(R.id.cancel_alarm_button); + final BartRunnerApplication application = (BartRunnerApplication) getApplication(); + if (application.isAlarmPending()) { + cancelAlarmButton.setVisible(true); + } + mAlarmPendingObserver = new Observer() { + @Override + public void onUpdate(final Boolean newValue) { + runOnUiThread(new Runnable() { + @Override + public void run() { + cancelAlarmButton.setVisible(newValue); + } + }); + } + }; + application.getAlarmPendingObservable().registerObserver( + mAlarmPendingObserver); + return true; + } + + @Override + public boolean onOptionsItemSelected(MenuItem item) { + int itemId = item.getItemId(); + if (itemId == android.R.id.home) { + Intent intent = new Intent(Intent.ACTION_VIEW, + Constants.FAVORITE_CONTENT_URI); + intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP); + startActivity(intent); + return true; + } else if (itemId == R.id.cancel_alarm_button) { + Intent intent = new Intent(this, NotificationService.class); + intent.putExtra("cancelAlarm", true); + startService(intent); + return true; + } else if (itemId == R.id.view_on_bart_site_button) { + startActivity(new Intent( + Intent.ACTION_VIEW, + Uri.parse("http://m.bart.gov/schedules/qp_results.aspx?type=departure&date=today&time=" + + DateFormat.format("h:mmaa", + System.currentTimeMillis()) + + "&orig=" + + mOrigin.abbreviation + + "&dest=" + + mDestination.abbreviation))); + return true; + } else if (itemId == R.id.view_system_map_button) { + startActivity(new Intent(this, ViewMapActivity.class)); + return true; + } else { + return super.onOptionsItemSelected(item); + } + } + + private void refreshBoardedDeparture() { + final Departure boardedDeparture = ((BartRunnerApplication) getApplication()) + .getBoardedDeparture(); + if (boardedDeparture == null + || boardedDeparture.getStationPair() == null + || !boardedDeparture.getStationPair().equals(getStationPair())) { + findViewById(R.id.yourTrainSection).setVisibility(View.GONE); + return; + } + + findViewById(R.id.yourTrainSection).setVisibility(View.VISIBLE); + ((TextView) findViewById(R.id.yourTrainDestinationText)) + .setText(boardedDeparture.getTrainDestination().toString()); + + ((TextView) findViewById(R.id.yourTrainTrainLengthText)) + .setText(boardedDeparture.getTrainLengthText()); + + ImageView colorBar = (ImageView) findViewById(R.id.yourTrainDestinationColorBar); + ((GradientDrawable) colorBar.getDrawable()).setColor(Color + .parseColor(boardedDeparture.getTrainDestinationColor())); + if (boardedDeparture.isBikeAllowed()) { + ((ImageView) findViewById(R.id.yourTrainBikeIcon)) + .setVisibility(View.VISIBLE); + } else { + ((ImageView) findViewById(R.id.yourTrainBikeIcon)) + .setVisibility(View.INVISIBLE); + } + if (boardedDeparture.getRequiresTransfer()) { + ((ImageView) findViewById(R.id.yourTrainXferIcon)) + .setVisibility(View.VISIBLE); + } else { + ((ImageView) findViewById(R.id.yourTrainXferIcon)) + .setVisibility(View.INVISIBLE); + } + CountdownTextView departureCountdown = (CountdownTextView) findViewById(R.id.yourTrainDepartureCountdown); + CountdownTextView arrivalCountdown = (CountdownTextView) findViewById(R.id.yourTrainArrivalCountdown); + + departureCountdown.setText("Leaves in " + + boardedDeparture.getCountdownText() + " " + + boardedDeparture.getUncertaintyText()); + departureCountdown.setTextProvider(new TextProvider() { + @Override + public String getText(long tickNumber) { + if (boardedDeparture.hasDeparted()) { + return "Departed"; + } else { + return "Leaves in " + boardedDeparture.getCountdownText() + + " " + boardedDeparture.getUncertaintyText(); + } + } + }); + + arrivalCountdown.setText(boardedDeparture + .getEstimatedArrivalMinutesLeftText(this)); + arrivalCountdown.setTextProvider(new TextProvider() { + @Override + public String getText(long tickNumber) { + return boardedDeparture + .getEstimatedArrivalMinutesLeftText(ViewDeparturesActivity.this); + } + }); + } + + private void startDepartureActionMode() { + mActionMode = startActionMode(new DepartureActionMode()); + mActionMode.setTitle(mSelectedDeparture.getTrainDestinationName()); + mActionMode.setSubtitle(mSelectedDeparture.getTrainLengthText()); + } + + private class DepartureActionMode implements ActionMode.Callback { + + @Override + public boolean onCreateActionMode(ActionMode mode, Menu menu) { + mode.getMenuInflater().inflate(R.menu.departure_context_menu, menu); + return true; + } + + @Override + public boolean onPrepareActionMode(ActionMode mode, Menu menu) { + return false; + } + + @Override + public boolean onActionItemClicked(ActionMode mode, MenuItem item) { + if (item.getItemId() == R.id.boardTrain) { + final BartRunnerApplication application = (BartRunnerApplication) getApplication(); + mSelectedDeparture.setPassengerDestination(mDestination); + application.setBoardedDeparture(mSelectedDeparture); + refreshBoardedDeparture(); + + // Stop the notification service + stopService(new Intent(ViewDeparturesActivity.this, + NotificationService.class)); + + // Don't prompt for alert if train is about to leave + if (mSelectedDeparture.getMeanSecondsLeft() / 60 > 1) { + new TrainAlertDialogFragment().show( + getSupportFragmentManager(), "dialog"); + } + + mode.finish(); + return true; + } + return false; + } + + @Override + public void onDestroyActionMode(ActionMode mode) { + mActionMode = null; + } + + } + + @Override + public void onETDChanged(final List 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(); + for (Departure departure : departures) { + listAdapter.add(departure); + } + } + + refreshBoardedDeparture(); + + 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); + } +} diff --git a/src/com/dougkeen/bart/ViewMapActivity.java b/src/com/dougkeen/bart/activities/ViewMapActivity.java similarity index 93% rename from src/com/dougkeen/bart/ViewMapActivity.java rename to src/com/dougkeen/bart/activities/ViewMapActivity.java index ac9abd7..e25a3e1 100644 --- a/src/com/dougkeen/bart/ViewMapActivity.java +++ b/src/com/dougkeen/bart/activities/ViewMapActivity.java @@ -1,45 +1,46 @@ -package com.dougkeen.bart; - -import android.os.Bundle; -import android.webkit.WebView; - -import com.actionbarsherlock.app.SherlockActivity; -import com.actionbarsherlock.view.Menu; -import com.actionbarsherlock.view.MenuInflater; -import com.actionbarsherlock.view.MenuItem; - -public class ViewMapActivity extends SherlockActivity { - - @Override - protected void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - - WebView webview = new WebView(this); - setContentView(webview); - - webview.getSettings().setBuiltInZoomControls(true); - webview.getSettings().setSupportZoom(true); - - webview.loadUrl("file:///android_res/drawable/map.png"); - - getSupportActionBar().setHomeButtonEnabled(true); - getSupportActionBar().setDisplayHomeAsUpEnabled(true); - } - - @Override - public boolean onCreateOptionsMenu(Menu menu) { - MenuInflater inflater = getSupportMenuInflater(); - inflater.inflate(R.menu.system_map_menu, menu); - return true; - } - - @Override - public boolean onOptionsItemSelected(MenuItem item) { - if (item.getItemId() == android.R.id.home) { - finish(); - return true; - } - return super.onOptionsItemSelected(item); - } - -} +package com.dougkeen.bart.activities; + +import android.os.Bundle; +import android.webkit.WebView; + +import com.actionbarsherlock.app.SherlockActivity; +import com.actionbarsherlock.view.Menu; +import com.actionbarsherlock.view.MenuInflater; +import com.actionbarsherlock.view.MenuItem; +import com.dougkeen.bart.R; + +public class ViewMapActivity extends SherlockActivity { + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + WebView webview = new WebView(this); + setContentView(webview); + + webview.getSettings().setBuiltInZoomControls(true); + webview.getSettings().setSupportZoom(true); + + webview.loadUrl("file:///android_res/drawable/map.png"); + + getSupportActionBar().setHomeButtonEnabled(true); + getSupportActionBar().setDisplayHomeAsUpEnabled(true); + } + + @Override + public boolean onCreateOptionsMenu(Menu menu) { + MenuInflater inflater = getSupportMenuInflater(); + inflater.inflate(R.menu.system_map_menu, menu); + return true; + } + + @Override + public boolean onOptionsItemSelected(MenuItem item) { + if (item.getItemId() == android.R.id.home) { + finish(); + return true; + } + return super.onOptionsItemSelected(item); + } + +} diff --git a/src/com/dougkeen/bart/controls/CountdownTextView.java b/src/com/dougkeen/bart/controls/CountdownTextView.java index 447bfee..e573567 100644 --- a/src/com/dougkeen/bart/controls/CountdownTextView.java +++ b/src/com/dougkeen/bart/controls/CountdownTextView.java @@ -37,7 +37,7 @@ public class CountdownTextView extends TextView implements public void setTextProvider(TextProvider provider) { mTextProvider = provider; - Ticker.getInstance().addSubscriber(this); + Ticker.getInstance().addSubscriber(this, getContext()); } @Override diff --git a/src/com/dougkeen/bart/controls/Ticker.java b/src/com/dougkeen/bart/controls/Ticker.java index 8486fbd..da2c052 100644 --- a/src/com/dougkeen/bart/controls/Ticker.java +++ b/src/com/dougkeen/bart/controls/Ticker.java @@ -3,6 +3,7 @@ package com.dougkeen.bart.controls; import java.util.Iterator; import java.util.WeakHashMap; +import android.content.Context; import android.os.Handler; public class Ticker { @@ -16,6 +17,8 @@ public class Ticker { private WeakHashMap mSubscribers; + private WeakHashMap mTickerHosts; + private TickerEngine mEngine; private static class TickerEngine implements Runnable { @@ -84,26 +87,28 @@ public class Ticker { return sInstance; } - public void addSubscriber(TickSubscriber subscriber) { + public void addSubscriber(TickSubscriber subscriber, Context host) { if (!mSubscribers.containsKey(subscriber) && subscriber != null) { mSubscribers.put(subscriber, null); - startTicking(); + startTicking(host); } } private Ticker() { mSubscribers = new WeakHashMap(); + mTickerHosts = new WeakHashMap(); mEngine = new TickerEngine(this); } - public void startTicking() { + public void startTicking(Context host) { + mTickerHosts.put(host, true); if (!mEngine.isOn()) mEngine.run(); } - public void stopTicking() { - if (mEngine.isOn()) + public void stopTicking(Context host) { + mTickerHosts.remove(host); + if (mEngine.isOn() && mTickerHosts.isEmpty()) mEngine.stop(); } - } diff --git a/src/com/dougkeen/bart/controls/TimedTextSwitcher.java b/src/com/dougkeen/bart/controls/TimedTextSwitcher.java index 209c3ce..2736e2a 100644 --- a/src/com/dougkeen/bart/controls/TimedTextSwitcher.java +++ b/src/com/dougkeen/bart/controls/TimedTextSwitcher.java @@ -43,7 +43,7 @@ public class TimedTextSwitcher extends TextSwitcher implements public void setTextProvider(TextProvider textProvider) { mTextProvider = textProvider; - Ticker.getInstance().addSubscriber(this); + Ticker.getInstance().addSubscriber(this, getContext()); } private CharSequence mLastText; diff --git a/src/com/dougkeen/bart/DepartureArrayAdapter.java b/src/com/dougkeen/bart/data/DepartureArrayAdapter.java similarity index 96% rename from src/com/dougkeen/bart/DepartureArrayAdapter.java rename to src/com/dougkeen/bart/data/DepartureArrayAdapter.java index 9d8bc1d..32a2b73 100644 --- a/src/com/dougkeen/bart/DepartureArrayAdapter.java +++ b/src/com/dougkeen/bart/data/DepartureArrayAdapter.java @@ -1,148 +1,149 @@ -package com.dougkeen.bart; - -import java.util.List; - -import org.apache.commons.lang3.StringUtils; - -import android.content.Context; -import android.graphics.Color; -import android.graphics.drawable.GradientDrawable; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; -import android.view.animation.AnimationUtils; -import android.widget.ArrayAdapter; -import android.widget.ImageView; -import android.widget.RelativeLayout; -import android.widget.TextSwitcher; -import android.widget.TextView; -import android.widget.ViewSwitcher.ViewFactory; - -import com.dougkeen.bart.controls.CountdownTextView; -import com.dougkeen.bart.controls.TimedTextSwitcher; -import com.dougkeen.bart.model.Departure; -import com.dougkeen.bart.model.TextProvider; - -public class DepartureArrayAdapter extends ArrayAdapter { - - public DepartureArrayAdapter(Context context, int textViewResourceId, - Departure[] objects) { - super(context, textViewResourceId, objects); - } - - public DepartureArrayAdapter(Context context, int resource, - int textViewResourceId, Departure[] objects) { - super(context, resource, textViewResourceId, objects); - } - - public DepartureArrayAdapter(Context context, int resource, - int textViewResourceId, List objects) { - super(context, resource, textViewResourceId, objects); - } - - public DepartureArrayAdapter(Context context, int resource, - int textViewResourceId) { - super(context, resource, textViewResourceId); - } - - public DepartureArrayAdapter(Context context, int textViewResourceId, - List objects) { - super(context, textViewResourceId, objects); - } - - public DepartureArrayAdapter(Context context, int textViewResourceId) { - super(context, textViewResourceId); - } - - @Override - public View getView(int position, View convertView, ViewGroup parent) { - View view; - if (convertView != null && convertView instanceof RelativeLayout) { - view = convertView; - } else { - LayoutInflater inflater = LayoutInflater.from(getContext()); - view = inflater.inflate(R.layout.departure_listing, parent, false); - } - - final Departure departure = getItem(position); - ((TextView) view.findViewById(R.id.destinationText)).setText(departure - .getTrainDestination().toString()); - - TimedTextSwitcher textSwitcher = (TimedTextSwitcher) view - .findViewById(R.id.trainLengthText); - initTextSwitcher(textSwitcher); - - final String estimatedArrivalTimeText = departure - .getEstimatedArrivalTimeText(getContext()); - if (!StringUtils.isBlank(estimatedArrivalTimeText)) { - textSwitcher.setCurrentText("Est. arrival " - + estimatedArrivalTimeText); - } else { - textSwitcher.setCurrentText(departure.getTrainLengthText()); - } - textSwitcher.setTextProvider(new TextProvider() { - @Override - public String getText(long tickNumber) { - if (tickNumber % 4 == 0) { - return departure.getTrainLengthText(); - } else { - final String estimatedArrivalTimeText = departure - .getEstimatedArrivalTimeText(getContext()); - if (StringUtils.isBlank(estimatedArrivalTimeText)) { - return ""; - } else { - return "Est. arrival " + estimatedArrivalTimeText; - } - } - } - }); - - ImageView colorBar = (ImageView) view - .findViewById(R.id.destinationColorBar); - ((GradientDrawable) colorBar.getDrawable()).setColor(Color - .parseColor(departure.getTrainDestinationColor())); - CountdownTextView countdownTextView = (CountdownTextView) view - .findViewById(R.id.countdown); - countdownTextView.setText(departure.getCountdownText()); - countdownTextView.setTextProvider(new TextProvider() { - @Override - public String getText(long tickNumber) { - return departure.getCountdownText(); - } - }); - ((TextView) view.findViewById(R.id.uncertainty)).setText(departure - .getUncertaintyText()); - if (departure.isBikeAllowed()) { - ((ImageView) view.findViewById(R.id.bikeIcon)) - .setVisibility(View.VISIBLE); - } else { - ((ImageView) view.findViewById(R.id.bikeIcon)) - .setVisibility(View.INVISIBLE); - } - if (departure.getRequiresTransfer()) { - ((ImageView) view.findViewById(R.id.xferIcon)) - .setVisibility(View.VISIBLE); - } else { - ((ImageView) view.findViewById(R.id.xferIcon)) - .setVisibility(View.INVISIBLE); - } - - return view; - } - - private void initTextSwitcher(TextSwitcher textSwitcher) { - if (textSwitcher.getInAnimation() == null) { - textSwitcher.setFactory(new ViewFactory() { - public View makeView() { - return LayoutInflater.from(getContext()).inflate( - R.layout.train_length_arrival_textview, null); - } - }); - - textSwitcher.setInAnimation(AnimationUtils.loadAnimation( - getContext(), android.R.anim.slide_in_left)); - textSwitcher.setOutAnimation(AnimationUtils.loadAnimation( - getContext(), android.R.anim.slide_out_right)); - } - } -} +package com.dougkeen.bart.data; + +import java.util.List; + +import org.apache.commons.lang3.StringUtils; + +import android.content.Context; +import android.graphics.Color; +import android.graphics.drawable.GradientDrawable; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.view.animation.AnimationUtils; +import android.widget.ArrayAdapter; +import android.widget.ImageView; +import android.widget.RelativeLayout; +import android.widget.TextSwitcher; +import android.widget.TextView; +import android.widget.ViewSwitcher.ViewFactory; + +import com.dougkeen.bart.R; +import com.dougkeen.bart.controls.CountdownTextView; +import com.dougkeen.bart.controls.TimedTextSwitcher; +import com.dougkeen.bart.model.Departure; +import com.dougkeen.bart.model.TextProvider; + +public class DepartureArrayAdapter extends ArrayAdapter { + + public DepartureArrayAdapter(Context context, int textViewResourceId, + Departure[] objects) { + super(context, textViewResourceId, objects); + } + + public DepartureArrayAdapter(Context context, int resource, + int textViewResourceId, Departure[] objects) { + super(context, resource, textViewResourceId, objects); + } + + public DepartureArrayAdapter(Context context, int resource, + int textViewResourceId, List objects) { + super(context, resource, textViewResourceId, objects); + } + + public DepartureArrayAdapter(Context context, int resource, + int textViewResourceId) { + super(context, resource, textViewResourceId); + } + + public DepartureArrayAdapter(Context context, int textViewResourceId, + List objects) { + super(context, textViewResourceId, objects); + } + + public DepartureArrayAdapter(Context context, int textViewResourceId) { + super(context, textViewResourceId); + } + + @Override + public View getView(int position, View convertView, ViewGroup parent) { + View view; + if (convertView != null && convertView instanceof RelativeLayout) { + view = convertView; + } else { + LayoutInflater inflater = LayoutInflater.from(getContext()); + view = inflater.inflate(R.layout.departure_listing, parent, false); + } + + final Departure departure = getItem(position); + ((TextView) view.findViewById(R.id.destinationText)).setText(departure + .getTrainDestination().toString()); + + TimedTextSwitcher textSwitcher = (TimedTextSwitcher) view + .findViewById(R.id.trainLengthText); + initTextSwitcher(textSwitcher); + + final String estimatedArrivalTimeText = departure + .getEstimatedArrivalTimeText(getContext()); + if (!StringUtils.isBlank(estimatedArrivalTimeText)) { + textSwitcher.setCurrentText("Est. arrival " + + estimatedArrivalTimeText); + } else { + textSwitcher.setCurrentText(departure.getTrainLengthText()); + } + textSwitcher.setTextProvider(new TextProvider() { + @Override + public String getText(long tickNumber) { + if (tickNumber % 4 == 0) { + return departure.getTrainLengthText(); + } else { + final String estimatedArrivalTimeText = departure + .getEstimatedArrivalTimeText(getContext()); + if (StringUtils.isBlank(estimatedArrivalTimeText)) { + return ""; + } else { + return "Est. arrival " + estimatedArrivalTimeText; + } + } + } + }); + + ImageView colorBar = (ImageView) view + .findViewById(R.id.destinationColorBar); + ((GradientDrawable) colorBar.getDrawable()).setColor(Color + .parseColor(departure.getTrainDestinationColor())); + CountdownTextView countdownTextView = (CountdownTextView) view + .findViewById(R.id.countdown); + countdownTextView.setText(departure.getCountdownText()); + countdownTextView.setTextProvider(new TextProvider() { + @Override + public String getText(long tickNumber) { + return departure.getCountdownText(); + } + }); + ((TextView) view.findViewById(R.id.uncertainty)).setText(departure + .getUncertaintyText()); + if (departure.isBikeAllowed()) { + ((ImageView) view.findViewById(R.id.bikeIcon)) + .setVisibility(View.VISIBLE); + } else { + ((ImageView) view.findViewById(R.id.bikeIcon)) + .setVisibility(View.INVISIBLE); + } + if (departure.getRequiresTransfer()) { + ((ImageView) view.findViewById(R.id.xferIcon)) + .setVisibility(View.VISIBLE); + } else { + ((ImageView) view.findViewById(R.id.xferIcon)) + .setVisibility(View.INVISIBLE); + } + + return view; + } + + private void initTextSwitcher(TextSwitcher textSwitcher) { + if (textSwitcher.getInAnimation() == null) { + textSwitcher.setFactory(new ViewFactory() { + public View makeView() { + return LayoutInflater.from(getContext()).inflate( + R.layout.train_length_arrival_textview, null); + } + }); + + textSwitcher.setInAnimation(AnimationUtils.loadAnimation( + getContext(), android.R.anim.slide_in_left)); + textSwitcher.setOutAnimation(AnimationUtils.loadAnimation( + getContext(), android.R.anim.slide_out_right)); + } + } +} diff --git a/src/com/dougkeen/bart/data/FavoritesArrayAdapter.java b/src/com/dougkeen/bart/data/FavoritesArrayAdapter.java new file mode 100644 index 0000000..65af46d --- /dev/null +++ b/src/com/dougkeen/bart/data/FavoritesArrayAdapter.java @@ -0,0 +1,276 @@ +package com.dougkeen.bart.data; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import org.apache.commons.lang3.StringUtils; + +import android.app.Activity; +import android.content.ComponentName; +import android.content.Context; +import android.content.Intent; +import android.content.ServiceConnection; +import android.database.Cursor; +import android.os.IBinder; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.view.animation.AnimationUtils; +import android.widget.ArrayAdapter; +import android.widget.RelativeLayout; +import android.widget.TextSwitcher; +import android.widget.TextView; +import android.widget.ViewSwitcher.ViewFactory; + +import com.dougkeen.bart.R; +import com.dougkeen.bart.controls.CountdownTextView; +import com.dougkeen.bart.controls.TimedTextSwitcher; +import com.dougkeen.bart.model.Departure; +import com.dougkeen.bart.model.StationPair; +import com.dougkeen.bart.model.TextProvider; +import com.dougkeen.bart.services.EtdService; +import com.dougkeen.bart.services.EtdService.EtdServiceBinder; +import com.dougkeen.bart.services.EtdService.EtdServiceListener; + +public class FavoritesArrayAdapter extends ArrayAdapter { + + private boolean mBound = false; + + private EtdService mEtdService; + + private Activity mHostActivity; + + private Map mEtdListeners = new HashMap(); + + 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 (!isEmpty()) { + setUpEtdListeners(); + } + } + }; + + public void setUpEtdListeners() { + if (mBound && mEtdService != null) { + for (int i = getCount() - 1; i >= 0; i--) { + final StationPair item = getItem(i); + mEtdListeners.put(item, new EtdListener(item, mEtdService)); + } + } + } + + public void clearEtdListeners() { + if (mBound && mEtdService != null) { + for (EtdListener listener : mEtdListeners.values()) { + listener.close(mEtdService); + } + mEtdListeners.clear(); + } + } + + public boolean areEtdListenersActive() { + return !mEtdListeners.isEmpty(); + } + + public FavoritesArrayAdapter(Context context, int textViewResourceId) { + super(context, textViewResourceId); + mHostActivity = (Activity) context; + mHostActivity.bindService(new Intent(mHostActivity, EtdService.class), + mConnection, Context.BIND_AUTO_CREATE); + } + + public void close() { + if (mBound) { + mHostActivity.unbindService(mConnection); + } + } + + @Override + public void add(StationPair object) { + super.add(object); + if (mEtdService != null && mBound) { + mEtdListeners.put(object, new EtdListener(object, mEtdService)); + } + } + + @Override + public void remove(StationPair object) { + super.remove(object); + if (mEtdListeners.containsKey(object) && mEtdService != null & mBound) { + mEtdListeners.get(object).close(mEtdService); + mEtdListeners.remove(object); + } + } + + @Override + public void clear() { + super.clear(); + clearEtdListeners(); + } + + public void updateFromCursor(Cursor cursor) { + if (!cursor.moveToFirst()) { + clear(); + } + for (int i = 0; i < getCount(); i++) { + StationPair adapterItem = getItem(i); + 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 + public View getView(int position, View convertView, ViewGroup parent) { + View view; + if (convertView != null && convertView instanceof RelativeLayout) { + view = convertView; + } else { + LayoutInflater inflater = LayoutInflater.from(getContext()); + view = inflater.inflate(R.layout.favorite_listing, parent, false); + } + + final StationPair pair = getItem(position); + + final EtdListener etdListener = mEtdListeners.get(pair); + + final TimedTextSwitcher uncertaintyTextSwitcher = (TimedTextSwitcher) view + .findViewById(R.id.uncertainty); + initTextSwitcher(uncertaintyTextSwitcher); + + if (etdListener == null || etdListener.getFirstDeparture() == null) { + uncertaintyTextSwitcher.setCurrentText(pair.getFare()); + } else { + CountdownTextView countdownTextView = (CountdownTextView) view + .findViewById(R.id.countdownText); + countdownTextView.setText(etdListener.getFirstDeparture() + .getCountdownText()); + countdownTextView.setTextProvider(new TextProvider() { + @Override + public String getText(long tickNumber) { + return etdListener.getFirstDeparture().getCountdownText(); + } + }); + + final String uncertaintyText = etdListener.getFirstDeparture() + .getUncertaintyText(); + if (!StringUtils.isBlank(uncertaintyText)) { + uncertaintyTextSwitcher.setCurrentText(uncertaintyText); + } else { + uncertaintyTextSwitcher.setCurrentText(pair.getFare()); + } + uncertaintyTextSwitcher.setTextProvider(new TextProvider() { + @Override + public String getText(long tickNumber) { + if (tickNumber % 4 <= 1) { + return pair.getFare(); + } else { + return etdListener.getFirstDeparture() + .getUncertaintyText(); + } + } + }); + } + + ((TextView) view.findViewById(R.id.originText)).setText(pair + .getOrigin().name); + ((TextView) view.findViewById(R.id.destinationText)).setText(pair + .getDestination().name); + + return view; + } + + private void initTextSwitcher(TextSwitcher textSwitcher) { + if (textSwitcher.getInAnimation() == null) { + textSwitcher.setFactory(new ViewFactory() { + public View makeView() { + return LayoutInflater.from(getContext()).inflate( + R.layout.uncertainty_textview, null); + } + }); + + textSwitcher.setInAnimation(AnimationUtils.loadAnimation( + getContext(), android.R.anim.slide_in_left)); + textSwitcher.setOutAnimation(AnimationUtils.loadAnimation( + getContext(), android.R.anim.slide_out_right)); + } + } + + private class EtdListener implements EtdServiceListener { + + private final StationPair mStationPair; + + private Departure firstDeparture; + + protected EtdListener(StationPair mStationPair, EtdService etdService) { + super(); + this.mStationPair = mStationPair; + etdService.registerListener(this, true); + } + + protected void close(EtdService etdService) { + etdService.unregisterListener(this); + } + + @Override + public void onETDChanged(List departures) { + for (Departure departure : departures) { + if (!departure.hasDeparted()) { + if (!departure.equals(firstDeparture)) { + firstDeparture = departure; + FavoritesArrayAdapter.this.notifyDataSetChanged(); + } + return; + } + } + } + + @Override + public void onError(String errorMessage) { + } + + @Override + public void onRequestStarted() { + } + + @Override + public void onRequestEnded() { + } + + @Override + public StationPair getStationPair() { + return mStationPair; + } + + public Departure getFirstDeparture() { + return firstDeparture; + } + } +} diff --git a/src/com/dougkeen/bart/model/Departure.java b/src/com/dougkeen/bart/model/Departure.java index 6d6b22d..4c84ae0 100644 --- a/src/com/dougkeen/bart/model/Departure.java +++ b/src/com/dougkeen/bart/model/Departure.java @@ -496,8 +496,10 @@ public class Departure implements Parcelable, Comparable { dest.writeLong(arrivalTimeOverride); dest.writeInt(estimatedTripTime); dest.writeInt(line.ordinal()); - dest.writeBooleanArray(new boolean[] { beganAsDeparted, bikeAllowed, - requiresTransfer, transferScheduled }); + dest.writeByte(beganAsDeparted ? (byte) 1 : (byte) 0); + dest.writeByte(bikeAllowed ? (byte) 1 : (byte) 0); + dest.writeByte(requiresTransfer ? (byte) 1 : (byte) 0); + dest.writeByte(transferScheduled ? (byte) 1 : (byte) 0); } private void readFromParcel(Parcel in) { @@ -516,12 +518,10 @@ public class Departure implements Parcelable, Comparable { arrivalTimeOverride = in.readLong(); estimatedTripTime = in.readInt(); line = Line.values()[in.readInt()]; - boolean[] bools = new boolean[4]; - in.readBooleanArray(bools); - beganAsDeparted = bools[0]; - bikeAllowed = bools[1]; - requiresTransfer = bools[2]; - transferScheduled = bools[3]; + beganAsDeparted = in.readByte() == (byte) 1; + bikeAllowed = in.readByte() == (byte) 1; + requiresTransfer = in.readByte() == (byte) 1; + transferScheduled = in.readByte() == (byte) 1; } public static final Parcelable.Creator CREATOR = new Parcelable.Creator() { diff --git a/src/com/dougkeen/bart/model/Route.java b/src/com/dougkeen/bart/model/Route.java index 6cd7f53..020557c 100644 --- a/src/com/dougkeen/bart/model/Route.java +++ b/src/com/dougkeen/bart/model/Route.java @@ -10,8 +10,6 @@ public class Route { private boolean requiresTransfer; private Station transferStation; private String direction; - private String fare; - private Long fareLastUpdated; public Station getOrigin() { return origin; @@ -69,22 +67,6 @@ public class Route { this.direction = direction; } - public String getFare() { - return fare; - } - - public void setFare(String fare) { - this.fare = fare; - } - - public Long getFareLastUpdated() { - return fareLastUpdated; - } - - public void setFareLastUpdated(Long fareLastUpdated) { - this.fareLastUpdated = fareLastUpdated; - } - @Override public String toString() { StringBuilder builder = new StringBuilder(); @@ -100,10 +82,6 @@ public class Route { builder.append(transferStation); builder.append(", direction="); builder.append(direction); - builder.append(", fare="); - builder.append(fare); - builder.append(", fareLastUpdated="); - builder.append(fareLastUpdated); builder.append("]"); return builder.toString(); } diff --git a/src/com/dougkeen/bart/model/StationPair.java b/src/com/dougkeen/bart/model/StationPair.java index 01e0180..fbdc16b 100644 --- a/src/com/dougkeen/bart/model/StationPair.java +++ b/src/com/dougkeen/bart/model/StationPair.java @@ -1,5 +1,11 @@ package com.dougkeen.bart.model; +import org.apache.commons.lang3.ObjectUtils; + +import com.dougkeen.bart.data.CursorUtils; +import com.dougkeen.bart.data.RoutesColumns; + +import android.database.Cursor; import android.net.Uri; import android.os.Parcel; import android.os.Parcelable; @@ -11,12 +17,39 @@ public class StationPair implements Parcelable { 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) { readFromParcel(in); } + public static StationPair createFromCursor(Cursor cursor) { + StationPair pair = new StationPair( + Station.getByAbbreviation(CursorUtils.getString(cursor, + RoutesColumns.FROM_STATION)), + Station.getByAbbreviation(CursorUtils.getString(cursor, + RoutesColumns.TO_STATION))); + pair.id = CursorUtils.getLong(cursor, RoutesColumns._ID); + pair.fare = CursorUtils.getString(cursor, RoutesColumns.FARE); + pair.fareLastUpdated = CursorUtils.getLong(cursor, + RoutesColumns.FARE_LAST_UPDATED); + return pair; + } + + private Long id; private Station origin; private Station destination; + private String fare; + private Long fareLastUpdated; + + public Long getId() { + return id; + } public Station getOrigin() { return origin; @@ -26,6 +59,22 @@ public class StationPair implements Parcelable { return destination; } + public String getFare() { + return fare; + } + + public void setFare(String fare) { + this.fare = fare; + } + + public Long getFareLastUpdated() { + return fareLastUpdated; + } + + public void setFareLastUpdated(Long fareLastUpdated) { + this.fareLastUpdated = fareLastUpdated; + } + public boolean isBetweenStations(Station station1, Station station2) { return (origin.equals(station1) && destination.equals(station2)) || (origin.equals(station2) && destination.equals(station1)); @@ -51,6 +100,14 @@ public class StationPair implements Parcelable { return result; } + public boolean fareEquals(StationPair other) { + if (other == null) + return false; + return ObjectUtils.equals(getFare(), other.getFare()) + && ObjectUtils.equals(getFareLastUpdated(), + other.getFareLastUpdated()); + } + @Override public boolean equals(Object obj) { if (this == obj) diff --git a/src/com/dougkeen/bart/AlarmBroadcastReceiver.java b/src/com/dougkeen/bart/receivers/AlarmBroadcastReceiver.java similarity index 84% rename from src/com/dougkeen/bart/AlarmBroadcastReceiver.java rename to src/com/dougkeen/bart/receivers/AlarmBroadcastReceiver.java index 85bbb58..ef9b5b1 100644 --- a/src/com/dougkeen/bart/AlarmBroadcastReceiver.java +++ b/src/com/dougkeen/bart/receivers/AlarmBroadcastReceiver.java @@ -1,4 +1,7 @@ -package com.dougkeen.bart; +package com.dougkeen.bart.receivers; + +import com.dougkeen.bart.BartRunnerApplication; +import com.dougkeen.util.WakeLocker; import android.content.BroadcastReceiver; import android.content.Context; diff --git a/src/com/dougkeen/bart/EtdService.java b/src/com/dougkeen/bart/services/EtdService.java similarity index 96% rename from src/com/dougkeen/bart/EtdService.java rename to src/com/dougkeen/bart/services/EtdService.java index 14b131f..b80d2c1 100644 --- a/src/com/dougkeen/bart/EtdService.java +++ b/src/com/dougkeen/bart/services/EtdService.java @@ -1,4 +1,4 @@ -package com.dougkeen.bart; +package com.dougkeen.bart.services; import java.util.ArrayList; import java.util.Collections; @@ -21,6 +21,8 @@ import android.os.IBinder; import android.support.v4.content.CursorLoader; import android.util.Log; +import com.dougkeen.bart.BartRunnerApplication; +import com.dougkeen.bart.R; import com.dougkeen.bart.data.RoutesColumns; import com.dougkeen.bart.model.Constants; import com.dougkeen.bart.model.Departure; @@ -44,7 +46,8 @@ public class EtdService extends Service { mServiceEngineMap = new HashMap(); } - public void registerListener(EtdServiceListener listener) { + public void registerListener(EtdServiceListener listener, + boolean limitToFirstNonDeparted) { StationPair stationPair = getStationPairFromListener(listener); if (stationPair == null) return; @@ -53,7 +56,8 @@ public class EtdService extends Service { mServiceEngineMap.put(stationPair, new EtdServiceEngine(stationPair)); } - mServiceEngineMap.get(stationPair).registerListener(listener); + mServiceEngineMap.get(stationPair).registerListener(listener, + limitToFirstNonDeparted); } private StationPair getStationPairFromListener(EtdServiceListener listener) { @@ -116,6 +120,8 @@ public class EtdService extends Service { // We'll only use the keys private WeakHashMap mListeners; + private boolean mLimitToFirstNonDeparted = true; + private List mLatestDepartures; private ScheduleInformation mLatestScheduleInfo; @@ -148,8 +154,11 @@ public class EtdService extends Service { cursor.close(); } - protected void registerListener(EtdServiceListener listener) { + protected void registerListener(EtdServiceListener listener, + boolean limitToFirstNonDeparted) { mListeners.put(listener, true); + if (!limitToFirstNonDeparted) + mLimitToFirstNonDeparted = false; if (!mPendingEtdRequest) { mStarted = true; fetchLatestDepartures(); @@ -439,6 +448,9 @@ public class EtdService extends Service { if (departure.equals(boardedDeparture)) { boardedDeparture.mergeEstimate(departure); } + if (!departure.hasDeparted() && mLimitToFirstNonDeparted) { + break; + } } /* @@ -504,6 +516,10 @@ public class EtdService extends Service { if (departure.equals(boardedDeparture)) { boardedDeparture.mergeEstimate(departure); } + + if (!departure.hasDeparted() && mLimitToFirstNonDeparted) { + break; + } } } Collections.sort(mLatestDepartures); diff --git a/src/com/dougkeen/bart/NotificationService.java b/src/com/dougkeen/bart/services/NotificationService.java similarity index 95% rename from src/com/dougkeen/bart/NotificationService.java rename to src/com/dougkeen/bart/services/NotificationService.java index 17840f3..98f41a2 100644 --- a/src/com/dougkeen/bart/NotificationService.java +++ b/src/com/dougkeen/bart/services/NotificationService.java @@ -1,4 +1,4 @@ -package com.dougkeen.bart; +package com.dougkeen.bart.services; import java.util.List; @@ -18,17 +18,19 @@ import android.os.HandlerThread; import android.os.IBinder; import android.os.Looper; import android.os.Message; +import android.os.Parcelable; import android.os.SystemClock; import android.support.v4.app.NotificationCompat; import android.support.v4.app.NotificationCompat.Builder; import android.util.Log; -import android.util.TimeFormatException; -import com.dougkeen.bart.EtdService.EtdServiceBinder; -import com.dougkeen.bart.EtdService.EtdServiceListener; +import com.dougkeen.bart.BartRunnerApplication; +import com.dougkeen.bart.R; import com.dougkeen.bart.model.Constants; import com.dougkeen.bart.model.Departure; import com.dougkeen.bart.model.StationPair; +import com.dougkeen.bart.services.EtdService.EtdServiceBinder; +import com.dougkeen.bart.services.EtdService.EtdServiceListener; public class NotificationService extends Service implements EtdServiceListener { @@ -74,7 +76,7 @@ public class NotificationService extends Service implements EtdServiceListener { public void onServiceConnected(ComponentName name, IBinder service) { mEtdService = ((EtdServiceBinder) service).getService(); if (getStationPair() != null) { - mEtdService.registerListener(NotificationService.this); + mEtdService.registerListener(NotificationService.this, false); } mBound = true; } @@ -142,7 +144,7 @@ public class NotificationService extends Service implements EtdServiceListener { } if (getStationPair() != null && mEtdService != null) { - mEtdService.registerListener(this); + mEtdService.registerListener(this, false); } Intent targetIntent = new Intent(Intent.ACTION_VIEW, @@ -163,7 +165,7 @@ public class NotificationService extends Service implements EtdServiceListener { getStationPair().getUri()); final Departure boardedDeparture = ((BartRunnerApplication) getApplication()) .getBoardedDeparture(); - alarmIntent.putExtra("departure", boardedDeparture); + alarmIntent.putExtra("departure", (Parcelable) boardedDeparture); mAlarmPendingIntent = PendingIntent.getBroadcast(this, 0, alarmIntent, PendingIntent.FLAG_UPDATE_CURRENT); } @@ -317,7 +319,7 @@ public class NotificationService extends Service implements EtdServiceListener { mStationPair.getOrigin().shortName + " to " + mStationPair.getDestination().shortName) .setContentIntent(mNotificationIntent).setWhen(0); - if (android.os.Build.VERSION.SDK_INT > 16) { + if (android.os.Build.VERSION.SDK_INT >= 16) { notificationBuilder .setPriority(NotificationCompat.PRIORITY_HIGH) .addAction( diff --git a/src/com/dougkeen/bart/WakeLocker.java b/src/com/dougkeen/util/WakeLocker.java similarity index 95% rename from src/com/dougkeen/bart/WakeLocker.java rename to src/com/dougkeen/util/WakeLocker.java index d12aee0..501a5b1 100644 --- a/src/com/dougkeen/bart/WakeLocker.java +++ b/src/com/dougkeen/util/WakeLocker.java @@ -1,4 +1,4 @@ -package com.dougkeen.bart; +package com.dougkeen.util; import android.content.Context; import android.os.PowerManager;