diff --git a/AndroidManifest.xml b/AndroidManifest.xml index 6bea803..75daa74 100644 --- a/AndroidManifest.xml +++ b/AndroidManifest.xml @@ -67,7 +67,7 @@ android:label="BartRunner data provider" /> - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/res/menu/route_menu.xml b/res/menu/route_menu.xml index 5539188..3b5d153 100644 --- a/res/menu/route_menu.xml +++ b/res/menu/route_menu.xml @@ -1,12 +1,6 @@ - + + + + + + + + \ No newline at end of file diff --git a/res/values/colors.xml b/res/values/colors.xml index ec67915..fefdd26 100644 --- a/res/values/colors.xml +++ b/res/values/colors.xml @@ -3,5 +3,6 @@ #FF000000 #FF2A7998 + #FF222222 \ No newline at end of file diff --git a/res/values/strings.xml b/res/values/strings.xml index b789695..41f894b 100644 --- a/res/values/strings.xml +++ b/res/values/strings.xml @@ -4,7 +4,7 @@ BART Runner Favorite routes No favorite routes have been added yet - Loading, please wait... + Loading, please wait… Add a route Origin Destination @@ -37,9 +37,10 @@ I will board this train Departure options Your train - Skip alert - Set up departure alert - Your train is leaving soon! + Set up departure alarm + Your train is leaving soon! Silence alarm + Cancel alarm + Set alarm \ No newline at end of file diff --git a/src/com/dougkeen/bart/BartRunnerApplication.java b/src/com/dougkeen/bart/BartRunnerApplication.java index 791dfae..ab73720 100644 --- a/src/com/dougkeen/bart/BartRunnerApplication.java +++ b/src/com/dougkeen/bart/BartRunnerApplication.java @@ -17,7 +17,6 @@ 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; @@ -26,8 +25,6 @@ public class BartRunnerApplication extends Application { private Departure mBoardedDeparture; - private Observable mAlarmPending = new Observable(false); - private boolean mPlayAlarmRingtone; private boolean mAlarmSounding; @@ -84,24 +81,42 @@ public class BartRunnerApplication extends Application { } } } + if (mBoardedDeparture != null && mBoardedDeparture.hasExpired()) { + setBoardedDeparture(null); + } return mBoardedDeparture; } public void setBoardedDeparture(Departure boardedDeparture) { if (!ObjectUtils.equals(boardedDeparture, mBoardedDeparture) || ObjectUtils.compare(mBoardedDeparture, boardedDeparture) != 0) { - // Cancel any pending alarms for the current departure - if (this.mBoardedDeparture != null - && this.mBoardedDeparture.isAlarmPending()) { - this.mBoardedDeparture.cancelAlarm(this, - (AlarmManager) getSystemService(Context.ALARM_SERVICE)); + if (this.mBoardedDeparture != null) { + this.mBoardedDeparture.getAlarmLeadTimeMinutesObservable() + .unregisterAllObservers(); + this.mBoardedDeparture.getAlarmPendingObservable() + .unregisterAllObservers(); + + // Cancel any pending alarms for the current departure + if (this.mBoardedDeparture.isAlarmPending()) { + this.mBoardedDeparture + .cancelAlarm( + this, + (AlarmManager) getSystemService(Context.ALARM_SERVICE)); + } } this.mBoardedDeparture = boardedDeparture; - if (mBoardedDeparture != null) { - File cachedDepartureFile = new File(getCacheDir(), - CACHE_FILE_NAME); + File cachedDepartureFile = new File(getCacheDir(), CACHE_FILE_NAME); + if (mBoardedDeparture == null) { + try { + cachedDepartureFile.delete(); + } catch (SecurityException anotherException) { + Log.w(Constants.TAG, + "Couldn't delete lastBoardedDeparture file", + anotherException); + } + } else { FileOutputStream fileOutputStream = null; try { fileOutputStream = new FileOutputStream(cachedDepartureFile); @@ -134,17 +149,4 @@ public class BartRunnerApplication extends Application { public void setAlarmMediaPlayer(MediaPlayer alarmMediaPlayer) { this.mAlarmMediaPlayer = alarmMediaPlayer; } - - public boolean isAlarmPending() { - return mAlarmPending.getValue(); - } - - public Observable getAlarmPendingObservable() { - return mAlarmPending; - } - - public void setAlarmPending(boolean alarmPending) { - this.mAlarmPending.setValue(alarmPending); - } - } \ No newline at end of file diff --git a/src/com/dougkeen/bart/activities/TrainAlertDialogFragment.java b/src/com/dougkeen/bart/activities/TrainAlarmDialogFragment.java similarity index 71% rename from src/com/dougkeen/bart/activities/TrainAlertDialogFragment.java rename to src/com/dougkeen/bart/activities/TrainAlarmDialogFragment.java index 4105560..5da3db2 100644 --- a/src/com/dougkeen/bart/activities/TrainAlertDialogFragment.java +++ b/src/com/dougkeen/bart/activities/TrainAlarmDialogFragment.java @@ -13,12 +13,13 @@ 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.model.Departure; -public class TrainAlertDialogFragment extends DialogFragment { +public class TrainAlarmDialogFragment extends DialogFragment { - private static final String KEY_LAST_ALERT_LEAD_TIME = "lastAlertLeadTime"; + private static final String KEY_LAST_ALARM_LEAD_TIME = "lastAlarmLeadTime"; - public TrainAlertDialogFragment() { + public TrainAlarmDialogFragment() { super(); } @@ -28,7 +29,7 @@ public class TrainAlertDialogFragment extends DialogFragment { SharedPreferences preferences = getActivity().getPreferences( Context.MODE_PRIVATE); - int lastAlertLeadTime = preferences.getInt(KEY_LAST_ALERT_LEAD_TIME, 5); + int lastAlarmLeadTime = preferences.getInt(KEY_LAST_ALARM_LEAD_TIME, 5); NumberPicker numberPicker = (NumberPicker) getDialog().findViewById( R.id.numberPicker); @@ -36,8 +37,8 @@ public class TrainAlertDialogFragment extends DialogFragment { BartRunnerApplication application = (BartRunnerApplication) getActivity() .getApplication(); - final int maxValue = application.getBoardedDeparture() - .getMeanSecondsLeft() / 60; + final Departure boardedDeparture = application.getBoardedDeparture(); + final int maxValue = boardedDeparture.getMeanSecondsLeft() / 60; String[] displayedValues = new String[maxValue]; for (int i = 1; i <= maxValue; i++) { @@ -47,8 +48,10 @@ public class TrainAlertDialogFragment extends DialogFragment { numberPicker.setMaxValue(maxValue); numberPicker.setDisplayedValues(displayedValues); - if (maxValue >= lastAlertLeadTime) { - numberPicker.setValue(lastAlertLeadTime); + if (boardedDeparture.isAlarmPending()) { + numberPicker.setValue(boardedDeparture.getAlarmLeadTimeMinutes()); + } else if (maxValue >= lastAlarmLeadTime) { + numberPicker.setValue(lastAlarmLeadTime); } else if (maxValue >= 5) { numberPicker.setValue(5); } else if (maxValue >= 3) { @@ -63,9 +66,9 @@ public class TrainAlertDialogFragment extends DialogFragment { final FragmentActivity activity = getActivity(); return new AlertDialog.Builder(activity) - .setTitle(R.string.set_up_departure_alert) + .setTitle(R.string.set_up_departure_alarm) .setCancelable(true) - .setView(R.layout.train_alert_dialog) + .setView(R.layout.train_alarm_dialog) .setPositiveButton(R.string.ok, new DialogInterface.OnClickListener() { @Override @@ -73,23 +76,23 @@ public class TrainAlertDialogFragment extends DialogFragment { int which) { NumberPicker numberPicker = (NumberPicker) getDialog() .findViewById(R.id.numberPicker); - final int alertLeadTime = numberPicker + final int alarmLeadTime = numberPicker .getValue(); // Save most recent selection Editor editor = getActivity().getPreferences( Context.MODE_PRIVATE).edit(); - editor.putInt(KEY_LAST_ALERT_LEAD_TIME, - alertLeadTime); + editor.putInt(KEY_LAST_ALARM_LEAD_TIME, + alarmLeadTime); editor.commit(); ((BartRunnerApplication) getActivity() .getApplication()) .getBoardedDeparture().setUpAlarm( - alertLeadTime); + alarmLeadTime); } }) - .setNegativeButton(R.string.skip_alert, + .setNegativeButton(R.string.cancel, new DialogInterface.OnClickListener() { public void onClick(DialogInterface dialog, int whichButton) { diff --git a/src/com/dougkeen/bart/activities/ViewDeparturesActivity.java b/src/com/dougkeen/bart/activities/ViewDeparturesActivity.java index 519ab56..d91cf10 100644 --- a/src/com/dougkeen/bart/activities/ViewDeparturesActivity.java +++ b/src/com/dougkeen/bart/activities/ViewDeparturesActivity.java @@ -10,8 +10,6 @@ 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; @@ -28,7 +26,7 @@ import android.util.Log; import android.view.View; import android.view.WindowManager; import android.widget.AdapterView; -import android.widget.ImageView; +import android.widget.Checkable; import android.widget.ListView; import android.widget.ProgressBar; import android.widget.TextView; @@ -41,19 +39,18 @@ 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.controls.YourTrainLayout; 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.BoardedDepartureService; 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; @@ -98,6 +95,8 @@ public class ViewDeparturesActivity extends SherlockFragmentActivity implements final Uri uri = mUri; + final BartRunnerApplication bartRunnerApplication = (BartRunnerApplication) getApplication(); + if (savedInstanceState != null && savedInstanceState.containsKey("origin") && savedInstanceState.containsKey("destination")) { @@ -166,41 +165,41 @@ public class ViewDeparturesActivity extends SherlockFragmentActivity implements mSelectedDeparture = (Departure) savedInstanceState .getParcelable("selectedDeparture"); } - if (savedInstanceState.getBoolean("hasActionMode") + if (savedInstanceState.getBoolean("hasDepartureActionMode") && mSelectedDeparture != null) { startDepartureActionMode(); } + if (savedInstanceState.getBoolean("hasYourTrainActionMode") + && mSelectedDeparture != null) { + ((Checkable) findViewById(R.id.yourTrainSection)) + .setChecked(true); + startYourTrainActionMode(bartRunnerApplication); + } } setListAdapter(mDeparturesAdapter); final ListView listView = getListView(); listView.setEmptyView(findViewById(android.R.id.empty)); listView.setChoiceMode(ListView.CHOICE_MODE_SINGLE); - listView.setOnItemClickListener(new AdapterView.OnItemClickListener() { - @Override - public void onItemClick(AdapterView adapterView, View view, - int position, long id) { - mSelectedDeparture = (Departure) getListAdapter().getItem( - position); - view.setSelected(true); - startDepartureActionMode(); - } - }); + listView.setOnItemClickListener(mListItemClickListener); + listView.setOnItemLongClickListener(mListItemLongClickListener); findViewById(R.id.missingDepartureText).setVisibility(View.VISIBLE); + findViewById(R.id.yourTrainSection).setOnClickListener( + mYourTrainSectionClickListener); + 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) + builder.setMessage(R.string.train_alarm_text) .setCancelable(false) .setNeutralButton(R.string.silence_alarm, new DialogInterface.OnClickListener() { @@ -217,19 +216,19 @@ public class ViewDeparturesActivity extends SherlockFragmentActivity implements private void soundTheAlarm() { final BartRunnerApplication application = (BartRunnerApplication) getApplication(); - Uri alertSound = RingtoneManager + Uri alarmSound = RingtoneManager .getDefaultUri(RingtoneManager.TYPE_ALARM); - if (alertSound == null || !tryToPlayRingtone(alertSound)) { - alertSound = RingtoneManager + if (alarmSound == null || !tryToPlayRingtone(alarmSound)) { + alarmSound = RingtoneManager .getDefaultUri(RingtoneManager.TYPE_NOTIFICATION); - if (alertSound == null || !tryToPlayRingtone(alertSound)) { - alertSound = RingtoneManager + if (alarmSound == null || !tryToPlayRingtone(alarmSound)) { + alarmSound = RingtoneManager .getDefaultUri(RingtoneManager.TYPE_RINGTONE); } } if (application.getAlarmMediaPlayer() == null) { - tryToPlayRingtone(alertSound); + tryToPlayRingtone(alarmSound); } mHandler.postDelayed(new Runnable() { @Override @@ -296,7 +295,54 @@ public class ViewDeparturesActivity extends SherlockFragmentActivity implements } }; - private Observer mAlarmPendingObserver; + private boolean mWasLongClick = false; + + private final AdapterView.OnItemClickListener mListItemClickListener = new AdapterView.OnItemClickListener() { + @Override + public void onItemClick(AdapterView adapterView, View view, + int position, long id) { + if (mWasLongClick) { + mWasLongClick = false; + return; + } + + if (mActionMode != null) { + /* + * If action mode is displayed, cancel out of that + */ + mActionMode.finish(); + getListView().clearChoices(); + } else { + /* + * Otherwise select the clicked departure as the one the user + * wants to board + */ + mSelectedDeparture = (Departure) getListAdapter().getItem( + position); + setBoardedDeparture(mSelectedDeparture); + } + } + }; + + private final AdapterView.OnItemLongClickListener mListItemLongClickListener = new AdapterView.OnItemLongClickListener() { + @Override + public boolean onItemLongClick(AdapterView adapterView, View view, + int position, long id) { + mWasLongClick = true; + mSelectedDeparture = (Departure) getListAdapter().getItem(position); + view.setSelected(true); + startDepartureActionMode(); + return false; + } + }; + + private final View.OnClickListener mYourTrainSectionClickListener = new View.OnClickListener() { + @Override + public void onClick(View v) { + ((Checkable) v).setChecked(true); + startYourTrainActionMode((BartRunnerApplication) getApplication()); + } + }; protected DepartureArrayAdapter getListAdapter() { return mDeparturesAdapter; @@ -312,10 +358,6 @@ public class ViewDeparturesActivity extends SherlockFragmentActivity implements super.onStop(); if (mEtdService != null) mEtdService.unregisterListener(this); - if (mAlarmPendingObserver != null) - ((BartRunnerApplication) getApplication()) - .getAlarmPendingObservable().unregisterObserver( - mAlarmPendingObserver); if (mBound) unbindService(mConnection); Ticker.getInstance().stopTicking(this); @@ -337,7 +379,10 @@ public class ViewDeparturesActivity extends SherlockFragmentActivity implements } outState.putParcelableArray("departures", departures); outState.putParcelable("selectedDeparture", mSelectedDeparture); - outState.putBoolean("hasActionMode", mActionMode != null); + outState.putBoolean("hasDepartureActionMode", + isDepartureActionModeActive()); + outState.putBoolean("hasYourTrainActionMode", + isYourTrainActionModeActive()); outState.putString("origin", mOrigin.abbreviation); outState.putString("destination", mDestination.abbreviation); } @@ -366,25 +411,6 @@ public class ViewDeparturesActivity extends SherlockFragmentActivity implements 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; } @@ -397,11 +423,6 @@ public class ViewDeparturesActivity extends SherlockFragmentActivity implements 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("cancelNotifications", true); - startService(intent); - return true; } else if (itemId == R.id.view_on_bart_site_button) { startActivity(new Intent( Intent.ACTION_VIEW, @@ -424,7 +445,7 @@ public class ViewDeparturesActivity extends SherlockFragmentActivity implements private void refreshBoardedDeparture() { final Departure boardedDeparture = ((BartRunnerApplication) getApplication()) .getBoardedDeparture(); - final View yourTrainSection = findViewById(R.id.yourTrainSection); + final YourTrainLayout yourTrainSection = (YourTrainLayout) findViewById(R.id.yourTrainSection); int currentVisibility = yourTrainSection.getVisibility(); final boolean boardedDepartureDoesNotApply = boardedDeparture == null @@ -433,65 +454,43 @@ public class ViewDeparturesActivity extends SherlockFragmentActivity implements if (boardedDepartureDoesNotApply) { if (currentVisibility != View.GONE) { - yourTrainSection.setVisibility(View.GONE); + hideYourTrainSection(); } return; } - ((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); - } - }); + yourTrainSection.updateFromDeparture(boardedDeparture); if (currentVisibility != View.VISIBLE) { - yourTrainSection.setVisibility(View.VISIBLE); + showYourTrainSection(yourTrainSection); } + + if (mActionMode == null) { + for (int i = getListAdapter().getCount() - 1; i >= 0; i--) { + if (getListAdapter().getItem(i).equals(boardedDeparture)) { + getListView().setSelection(i); + final Checkable listItem = (Checkable) getListView() + .getChildAt(i); + if (listItem != null) { + listItem.setChecked(true); + } + break; + } + } + getListView().requestLayout(); + } + + } + + private void setBoardedDeparture(Departure selectedDeparture) { + final BartRunnerApplication application = (BartRunnerApplication) getApplication(); + selectedDeparture.setPassengerDestination(mDestination); + application.setBoardedDeparture(selectedDeparture); + refreshBoardedDeparture(); + + // Start the notification service + startService(new Intent(ViewDeparturesActivity.this, + BoardedDepartureService.class)); } private void startDepartureActionMode() { @@ -511,26 +510,14 @@ public class ViewDeparturesActivity extends SherlockFragmentActivity implements @Override public boolean onPrepareActionMode(ActionMode mode, Menu menu) { + ((Checkable) findViewById(R.id.yourTrainSection)).setChecked(false); 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(); - - // Start the notification service - startService(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"); - } + setBoardedDeparture(mSelectedDeparture); mode.finish(); return true; @@ -547,6 +534,158 @@ public class ViewDeparturesActivity extends SherlockFragmentActivity implements } + private void startYourTrainActionMode(BartRunnerApplication application) { + if (mActionMode == null) + mActionMode = startActionMode(new YourTrainActionMode()); + mActionMode.setTitle(R.string.your_train); + if (application.getBoardedDeparture() != null + && application.getBoardedDeparture().isAlarmPending()) { + int leadTime = application.getBoardedDeparture() + .getAlarmLeadTimeMinutes(); + mActionMode.setSubtitle(getAlarmSubtitle(leadTime)); + } else { + mActionMode.setSubtitle(null); + } + } + + private String getAlarmSubtitle(int leadTime) { + if (leadTime == 0) + return null; + return "Alarm " + leadTime + " minute" + (leadTime != 1 ? "s" : "") + + " before departure"; + } + + private class YourTrainActionMode implements ActionMode.Callback { + private Observer mAlarmPendingObserver; + private Observer mAlarmLeadTimeObserver; + + @Override + public boolean onCreateActionMode(ActionMode mode, Menu menu) { + // Suppress new "your train" selections + getListView().setChoiceMode(ListView.CHOICE_MODE_NONE); + + mode.getMenuInflater() + .inflate(R.menu.your_train_context_menu, menu); + final MenuItem cancelAlarmButton = menu + .findItem(R.id.cancel_alarm_button); + final MenuItem setAlarmButton = menu + .findItem(R.id.set_alarm_button); + final BartRunnerApplication application = (BartRunnerApplication) getApplication(); + final Departure boardedDeparture = application + .getBoardedDeparture(); + if (boardedDeparture.isAlarmPending()) { + cancelAlarmButton.setVisible(true); + setAlarmButton.setIcon(R.drawable.ic_action_alarm); + } else if (boardedDeparture.getMeanSecondsLeft() > 60) { + setAlarmButton.setIcon(R.drawable.ic_action_add_alarm); + } + + // Don't allow alarm setting if train is about to leave + if (boardedDeparture.getMeanSecondsLeft() / 60 < 1) { + menu.findItem(R.id.set_alarm_button).setVisible(false); + } + + mAlarmPendingObserver = new Observer() { + @Override + public void onUpdate(final Boolean newValue) { + runOnUiThread(new Runnable() { + @Override + public void run() { + cancelAlarmButton.setVisible(newValue); + if (newValue) { + mActionMode + .setSubtitle(getAlarmSubtitle(boardedDeparture + .getAlarmLeadTimeMinutes())); + setAlarmButton + .setIcon(R.drawable.ic_action_alarm); + } else { + mActionMode.setSubtitle(null); + setAlarmButton + .setIcon(R.drawable.ic_action_add_alarm); + } + } + }); + } + }; + mAlarmLeadTimeObserver = new Observer() { + @Override + public void onUpdate(final Integer newValue) { + runOnUiThread(new Runnable() { + @Override + public void run() { + mActionMode.setSubtitle(getAlarmSubtitle(newValue)); + } + }); + } + }; + boardedDeparture.getAlarmPendingObservable().registerObserver( + mAlarmPendingObserver); + boardedDeparture.getAlarmLeadTimeMinutesObservable() + .registerObserver(mAlarmLeadTimeObserver); + return true; + } + + @Override + public boolean onPrepareActionMode(ActionMode mode, Menu menu) { + getListView().clearChoices(); + getListView().requestLayout(); + return false; + } + + @Override + public boolean onActionItemClicked(ActionMode mode, MenuItem item) { + final int itemId = item.getItemId(); + if (itemId == R.id.set_alarm_button) { + BartRunnerApplication application = (BartRunnerApplication) getApplication(); + + // Don't prompt for alarm if train is about to leave + if (application.getBoardedDeparture().getMeanSecondsLeft() > 60) { + new TrainAlarmDialogFragment().show( + getSupportFragmentManager(), "dialog"); + } + + return true; + } else if (itemId == R.id.cancel_alarm_button) { + Intent intent = new Intent(ViewDeparturesActivity.this, + BoardedDepartureService.class); + intent.putExtra("cancelNotifications", true); + startService(intent); + return true; + } else if (itemId == R.id.delete) { + Intent intent = new Intent(ViewDeparturesActivity.this, + BoardedDepartureService.class); + intent.putExtra("clearBoardedDeparture", true); + startService(intent); + hideYourTrainSection(); + mode.finish(); + return true; + } + return false; + } + + @Override + public void onDestroyActionMode(ActionMode mode) { + ((Checkable) findViewById(R.id.yourTrainSection)).setChecked(false); + + final BartRunnerApplication application = (BartRunnerApplication) getApplication(); + final Departure boardedDeparture = application + .getBoardedDeparture(); + if (boardedDeparture != null) { + boardedDeparture.getAlarmPendingObservable() + .unregisterObserver(mAlarmPendingObserver); + boardedDeparture.getAlarmLeadTimeMinutesObservable() + .unregisterObserver(mAlarmLeadTimeObserver); + } + + mAlarmPendingObserver = null; + mAlarmLeadTimeObserver = null; + mActionMode = null; + + // Enable new "your train" selections + getListView().setChoiceMode(ListView.CHOICE_MODE_SINGLE); + } + } + @Override public void onETDChanged(final List departures) { runOnUiThread(new Runnable() { @@ -558,6 +697,10 @@ public class ViewDeparturesActivity extends SherlockFragmentActivity implements mProgress.setVisibility(View.INVISIBLE); Linkify.addLinks(textView, Linkify.WEB_URLS); } else { + // TODO: Figure out why Ticker occasionally stops + Ticker.getInstance().startTicking( + ViewDeparturesActivity.this); + // Merge lists if (mDeparturesAdapter.getCount() > 0) { int adapterIndex = -1; @@ -639,4 +782,24 @@ public class ViewDeparturesActivity extends SherlockFragmentActivity implements return null; return new StationPair(mOrigin, mDestination); } + + private void hideYourTrainSection() { + findViewById(R.id.yourTrainSection).setVisibility(View.GONE); + } + + private void showYourTrainSection(final YourTrainLayout yourTrainSection) { + yourTrainSection.setVisibility(View.VISIBLE); + } + + private boolean isYourTrainActionModeActive() { + return mActionMode != null + && mActionMode.getTitle() + .equals(getString(R.string.your_train)); + } + + private boolean isDepartureActionModeActive() { + return mActionMode != null + && !mActionMode.getTitle().equals( + getString(R.string.your_train)); + } } diff --git a/src/com/dougkeen/bart/controls/DepartureListItemLayout.java b/src/com/dougkeen/bart/controls/DepartureListItemLayout.java index 79c30ae..b47624a 100644 --- a/src/com/dougkeen/bart/controls/DepartureListItemLayout.java +++ b/src/com/dougkeen/bart/controls/DepartureListItemLayout.java @@ -1,12 +1,12 @@ package com.dougkeen.bart.controls; -import com.dougkeen.bart.R; - import android.content.Context; import android.view.LayoutInflater; import android.widget.Checkable; import android.widget.RelativeLayout; +import com.dougkeen.bart.R; + public class DepartureListItemLayout extends RelativeLayout implements Checkable { diff --git a/src/com/dougkeen/bart/controls/TimedTextSwitcher.java b/src/com/dougkeen/bart/controls/TimedTextSwitcher.java index 2736e2a..a82c052 100644 --- a/src/com/dougkeen/bart/controls/TimedTextSwitcher.java +++ b/src/com/dougkeen/bart/controls/TimedTextSwitcher.java @@ -2,12 +2,12 @@ package com.dougkeen.bart.controls; import org.apache.commons.lang3.StringUtils; -import com.dougkeen.bart.model.TextProvider; - import android.content.Context; import android.util.AttributeSet; import android.widget.TextSwitcher; +import com.dougkeen.bart.model.TextProvider; + public class TimedTextSwitcher extends TextSwitcher implements Ticker.TickSubscriber { diff --git a/src/com/dougkeen/bart/controls/YourTrainLayout.java b/src/com/dougkeen/bart/controls/YourTrainLayout.java new file mode 100644 index 0000000..a042c61 --- /dev/null +++ b/src/com/dougkeen/bart/controls/YourTrainLayout.java @@ -0,0 +1,120 @@ +package com.dougkeen.bart.controls; + +import android.content.Context; +import android.graphics.Color; +import android.graphics.drawable.GradientDrawable; +import android.util.AttributeSet; +import android.view.LayoutInflater; +import android.view.View; +import android.widget.Checkable; +import android.widget.ImageView; +import android.widget.RelativeLayout; +import android.widget.TextView; + +import com.dougkeen.bart.R; +import com.dougkeen.bart.model.Departure; +import com.dougkeen.bart.model.TextProvider; + +public class YourTrainLayout extends RelativeLayout implements Checkable { + + public YourTrainLayout(Context context) { + super(context); + assignLayout(context); + } + + public YourTrainLayout(Context context, AttributeSet attrs, int defStyle) { + super(context, attrs, defStyle); + assignLayout(context); + } + + public YourTrainLayout(Context context, AttributeSet attrs) { + super(context, attrs); + assignLayout(context); + } + + public void assignLayout(Context context) { + LayoutInflater.from(context).inflate(R.layout.your_train, this, true); + } + + private boolean mChecked; + + @Override + public boolean isChecked() { + return mChecked; + } + + @Override + public void setChecked(boolean checked) { + mChecked = checked; + setBackground(); + } + + private void setBackground() { + if (isChecked()) { + setBackgroundDrawable(getContext().getResources().getDrawable( + R.color.blue_selection)); + } else { + setBackgroundDrawable(getContext().getResources().getDrawable( + R.color.gray)); + } + } + + @Override + public void toggle() { + setChecked(!isChecked()); + } + + public void updateFromDeparture(final Departure boardedDeparture) { + ((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); + + final TextProvider textProvider = new TextProvider() { + @Override + public String getText(long tickNumber) { + if (boardedDeparture.hasDeparted()) { + return "Departed"; + } else { + return "Leaves in " + boardedDeparture.getCountdownText() + + " " + boardedDeparture.getUncertaintyText(); + } + } + }; + departureCountdown.setText(textProvider.getText(0)); + departureCountdown.setTextProvider(textProvider); + + arrivalCountdown.setText(boardedDeparture + .getEstimatedArrivalMinutesLeftText(getContext())); + arrivalCountdown.setTextProvider(new TextProvider() { + @Override + public String getText(long tickNumber) { + return boardedDeparture + .getEstimatedArrivalMinutesLeftText(getContext()); + } + }); + + setBackground(); + } +} diff --git a/src/com/dougkeen/bart/data/DepartureArrayAdapter.java b/src/com/dougkeen/bart/data/DepartureArrayAdapter.java index 0af5679..5adf7e6 100644 --- a/src/com/dougkeen/bart/data/DepartureArrayAdapter.java +++ b/src/com/dougkeen/bart/data/DepartureArrayAdapter.java @@ -13,8 +13,6 @@ import android.view.ViewGroup; import android.view.animation.AnimationUtils; import android.widget.ArrayAdapter; import android.widget.ImageView; -import android.widget.ListView; -import android.widget.RelativeLayout; import android.widget.TextSwitcher; import android.widget.TextView; import android.widget.ViewSwitcher.ViewFactory; diff --git a/src/com/dougkeen/bart/data/RoutesColumns.java b/src/com/dougkeen/bart/data/RoutesColumns.java index e4ad2ee..e4d3097 100644 --- a/src/com/dougkeen/bart/data/RoutesColumns.java +++ b/src/com/dougkeen/bart/data/RoutesColumns.java @@ -1,5 +1,6 @@ package com.dougkeen.bart.data; + public enum RoutesColumns { _ID("_id", "INTEGER", false), FROM_STATION("FROM_STATION", "TEXT", false), diff --git a/src/com/dougkeen/bart/model/Departure.java b/src/com/dougkeen/bart/model/Departure.java index 4d1ebf5..52f5ce9 100644 --- a/src/com/dougkeen/bart/model/Departure.java +++ b/src/com/dougkeen/bart/model/Departure.java @@ -18,10 +18,12 @@ import android.text.format.DateFormat; import android.util.Log; import com.dougkeen.bart.R; -import com.dougkeen.bart.services.NotificationService; +import com.dougkeen.bart.services.BoardedDepartureService; +import com.dougkeen.util.Observable; public class Departure implements Parcelable, Comparable { - private static final int MINIMUM_MERGE_OVERLAP_MILLIS = 10000; + private static final int MINIMUM_MERGE_OVERLAP_MILLIS = 5000; + private static final int EXPIRE_MINUTES_AFTER_ARRIVAL = 1; public Departure() { super(); @@ -67,8 +69,9 @@ public class Departure implements Parcelable, Comparable { private long arrivalTimeOverride; - private int alarmLeadTimeMinutes; - private boolean alarmPending; + private Observable alarmLeadTimeMinutes = new Observable( + 0); + private Observable alarmPending = new Observable(false); public Station getOrigin() { return origin; @@ -308,7 +311,7 @@ public class Departure implements Parcelable, Comparable { } public boolean hasDeparted() { - return getMeanSecondsLeft() < 0; + return getMeanSecondsLeft() <= 0; } public void calculateEstimates(long originalEstimateTime) { @@ -333,40 +336,45 @@ public class Departure implements Parcelable, Comparable { setEstimatedTripTime(departure.getEstimatedTripTime()); } + long newMin = Math.max(getMinEstimate(), departure.getMinEstimate()); + long newMax = Math.min(getMaxEstimate(), departure.getMaxEstimate()); + if ((getMaxEstimate() - departure.getMinEstimate()) < MINIMUM_MERGE_OVERLAP_MILLIS || departure.getMaxEstimate() - getMinEstimate() < MINIMUM_MERGE_OVERLAP_MILLIS) { /* * The estimate must have changed... just use the latest incoming * values */ - setMinEstimate(departure.getMinEstimate()); - setMaxEstimate(departure.getMaxEstimate()); - return; + newMin = departure.getMinEstimate(); + newMax = departure.getMaxEstimate(); } - final long newMin = Math.max(getMinEstimate(), - departure.getMinEstimate()); - final long newMax = Math.min(getMaxEstimate(), - departure.getMaxEstimate()); - /* - * If the new departure would mark this as departed, and we have < 1 - * minute left on a fairly accurate local estimate, ignore the incoming + * If the new departure would mark this as departed, and we have < 60 + * seconds left on a fairly accurate local estimate, ignore the incoming * departure */ - if (!wasDeparted && getMeanSecondsLeft(newMin, newMax) < 0 + if (!wasDeparted && getMeanSecondsLeft(newMin, newMax) <= 0 && getMeanSecondsLeft() < 60 && getUncertaintySeconds() < 30) { Log.d(Constants.TAG, "Skipping estimate merge, since it would make this departure show as 'departed' prematurely"); return; } - if (newMax > newMin) { // We can never have 0 or negative uncertainty + if (newMax > newMin) { + // We must never have 0 or negative uncertainty setMinEstimate(newMin); setMaxEstimate(newMax); } } + public boolean hasExpired() { + final long now = System.currentTimeMillis(); + return getMaxEstimate() < now + && getEstimatedArrivalTime() + EXPIRE_MINUTES_AFTER_ARRIVAL + * 60000 < now; + } + public int compareTo(Departure another) { return (this.getMeanSecondsLeft() > another.getMeanSecondsLeft()) ? 1 : ((this.getMeanSecondsLeft() == another.getMeanSecondsLeft()) ? 0 @@ -475,10 +483,18 @@ public class Departure implements Parcelable, Comparable { } public int getAlarmLeadTimeMinutes() { + return alarmLeadTimeMinutes.getValue(); + } + + public Observable getAlarmLeadTimeMinutesObservable() { return alarmLeadTimeMinutes; } public boolean isAlarmPending() { + return alarmPending.getValue(); + } + + public Observable getAlarmPendingObservable() { return alarmPending; } @@ -489,7 +505,7 @@ public class Departure implements Parcelable, Comparable { } private long getAlarmClockTime() { - return getMeanEstimate() - alarmLeadTimeMinutes * 60 * 1000; + return getMeanEstimate() - alarmLeadTimeMinutes.getValue() * 60 * 1000; } public int getSecondsUntilAlarm() { @@ -497,8 +513,8 @@ public class Departure implements Parcelable, Comparable { } public void setUpAlarm(int leadTimeMinutes) { - this.alarmLeadTimeMinutes = leadTimeMinutes; - this.alarmPending = true; + this.alarmLeadTimeMinutes.setValue(leadTimeMinutes); + this.alarmPending.setValue(true); } public void updateAlarm(Context context, AlarmManager alarmManager) { @@ -509,20 +525,20 @@ public class Departure implements Parcelable, Comparable { final PendingIntent alarmIntent = getAlarmIntent(context); alarmManager.cancel(alarmIntent); - long alertTime = getAlarmClockTime(); + long alarmTime = getAlarmClockTime(); - alarmManager.set(AlarmManager.RTC_WAKEUP, alertTime, alarmIntent); + alarmManager.set(AlarmManager.RTC_WAKEUP, alarmTime, alarmIntent); if (Log.isLoggable(Constants.TAG, Log.VERBOSE)) Log.v(Constants.TAG, "Scheduling alarm for " - + DateFormatUtils.format(alertTime, "h:mm:ss")); + + DateFormatUtils.format(alarmTime, "h:mm:ss")); } } public void cancelAlarm(Context context, AlarmManager alarmManager) { alarmManager.cancel(getAlarmIntent(context)); - this.alarmPending = false; + this.alarmPending.setValue(false); } private PendingIntent notificationIntent; @@ -546,7 +562,7 @@ public class Departure implements Parcelable, Comparable { : "")); final Intent cancelAlarmIntent = new Intent(context, - NotificationService.class); + BoardedDepartureService.class); cancelAlarmIntent.putExtra("cancelNotifications", true); Builder notificationBuilder = new NotificationCompat.Builder(context) .setOngoing(true) @@ -564,7 +580,7 @@ public class Departure implements Parcelable, Comparable { "Cancel alarm", PendingIntent.getService(context, 0, cancelAlarmIntent, PendingIntent.FLAG_UPDATE_CURRENT)).setSubText( - "Alert " + getAlarmLeadTimeMinutes() + "Alarm " + getAlarmLeadTimeMinutes() + " minutes before departure"); } } else if (isAlarmPending()) { @@ -655,6 +671,6 @@ public class Departure implements Parcelable, Comparable { }; public void notifyAlarmHasBeenHandled() { - this.alarmPending = false; + this.alarmPending.setValue(false); } } \ No newline at end of file diff --git a/src/com/dougkeen/bart/model/StationPair.java b/src/com/dougkeen/bart/model/StationPair.java index fbdc16b..3496f0a 100644 --- a/src/com/dougkeen/bart/model/StationPair.java +++ b/src/com/dougkeen/bart/model/StationPair.java @@ -2,14 +2,14 @@ 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; +import com.dougkeen.bart.data.CursorUtils; +import com.dougkeen.bart.data.RoutesColumns; + public class StationPair implements Parcelable { public StationPair(Station origin, Station destination) { super(); diff --git a/src/com/dougkeen/bart/model/TextProvider.java b/src/com/dougkeen/bart/model/TextProvider.java index fe42318..7272567 100644 --- a/src/com/dougkeen/bart/model/TextProvider.java +++ b/src/com/dougkeen/bart/model/TextProvider.java @@ -1,5 +1,6 @@ package com.dougkeen.bart.model; + public interface TextProvider { String getText(long tickNumber); diff --git a/src/com/dougkeen/bart/networktasks/GetRouteFareTask.java b/src/com/dougkeen/bart/networktasks/GetRouteFareTask.java index 892dfd6..6b98bfe 100644 --- a/src/com/dougkeen/bart/networktasks/GetRouteFareTask.java +++ b/src/com/dougkeen/bart/networktasks/GetRouteFareTask.java @@ -12,13 +12,13 @@ import org.apache.http.client.methods.HttpGet; import org.apache.http.client.methods.HttpUriRequest; import org.xml.sax.SAXException; -import com.dougkeen.bart.model.Constants; -import com.dougkeen.bart.model.Station; - import android.os.AsyncTask; import android.util.Log; import android.util.Xml; +import com.dougkeen.bart.model.Constants; +import com.dougkeen.bart.model.Station; + public abstract class GetRouteFareTask extends AsyncTask { diff --git a/src/com/dougkeen/bart/services/NotificationService.java b/src/com/dougkeen/bart/services/BoardedDepartureService.java similarity index 76% rename from src/com/dougkeen/bart/services/NotificationService.java rename to src/com/dougkeen/bart/services/BoardedDepartureService.java index 911c806..733e3f3 100644 --- a/src/com/dougkeen/bart/services/NotificationService.java +++ b/src/com/dougkeen/bart/services/BoardedDepartureService.java @@ -1,38 +1,30 @@ package com.dougkeen.bart.services; +import java.lang.ref.WeakReference; import java.util.List; -import org.apache.commons.lang3.time.DateFormatUtils; - import android.app.AlarmManager; import android.app.NotificationManager; -import android.app.PendingIntent; import android.app.Service; import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.content.ServiceConnection; -import android.os.Bundle; import android.os.Handler; 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 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; +import com.dougkeen.util.Observer; -public class NotificationService extends Service implements EtdServiceListener { +public class BoardedDepartureService extends Service implements + EtdServiceListener { private static final int DEPARTURE_NOTIFICATION_ID = 123; @@ -44,22 +36,29 @@ public class NotificationService extends Service implements EtdServiceListener { private StationPair mStationPair; private NotificationManager mNotificationManager; private AlarmManager mAlarmManager; - private PendingIntent mNotificationIntent; private Handler mHandler; private boolean mHasShutDown = false; - public NotificationService() { + public BoardedDepartureService() { super(); } - private final class ServiceHandler extends Handler { - public ServiceHandler(Looper looper) { + private static final class ServiceHandler extends Handler { + private final WeakReference mServiceRef; + + public ServiceHandler(Looper looper, + BoardedDepartureService boardedDepartureService) { super(looper); + mServiceRef = new WeakReference( + boardedDepartureService); } @Override public void handleMessage(Message msg) { - onHandleIntent((Intent) msg.obj); + BoardedDepartureService service = mServiceRef.get(); + if (service != null) { + service.onHandleIntent((Intent) msg.obj); + } } } @@ -74,7 +73,8 @@ 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, false); + mEtdService.registerListener(BoardedDepartureService.this, + false); } mBound = true; } @@ -87,7 +87,7 @@ public class NotificationService extends Service implements EtdServiceListener { thread.start(); mServiceLooper = thread.getLooper(); - mServiceHandler = new ServiceHandler(mServiceLooper); + mServiceHandler = new ServiceHandler(mServiceLooper, this); bindService(new Intent(this, EtdService.class), mConnection, Context.BIND_AUTO_CREATE); @@ -122,17 +122,21 @@ public class NotificationService extends Service implements EtdServiceListener { } protected void onHandleIntent(Intent intent) { - final Departure boardedDeparture = ((BartRunnerApplication) getApplication()) - .getBoardedDeparture(); - if (boardedDeparture == null) { + final BartRunnerApplication application = (BartRunnerApplication) getApplication(); + final Departure boardedDeparture = application.getBoardedDeparture(); + if (boardedDeparture == null || intent == null) { // Nothing to notify about return; } - if (intent.getBooleanExtra("cancelNotifications", false)) { - // We want to cancel the alarm/notification + if (intent.getBooleanExtra("cancelNotifications", false) + || intent.getBooleanExtra("clearBoardedDeparture", false)) { + // We want to cancel the alarm boardedDeparture .cancelAlarm(getApplicationContext(), mAlarmManager); - shutDown(false); + if (intent.getBooleanExtra("clearBoardedDeparture", false)) { + application.setBoardedDeparture(null); + shutDown(false); + } return; } @@ -148,11 +152,20 @@ public class NotificationService extends Service implements EtdServiceListener { mEtdService.registerListener(this, false); } - Intent targetIntent = new Intent(Intent.ACTION_VIEW, - mStationPair.getUri()); - targetIntent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP); - mNotificationIntent = PendingIntent.getActivity(this, 0, targetIntent, - PendingIntent.FLAG_UPDATE_CURRENT); + boardedDeparture.getAlarmLeadTimeMinutesObservable().registerObserver( + new Observer() { + @Override + public void onUpdate(Integer newValue) { + updateNotification(); + } + }); + boardedDeparture.getAlarmPendingObservable().registerObserver( + new Observer() { + @Override + public void onUpdate(Boolean newValue) { + updateNotification(); + } + }); updateNotification(); @@ -179,6 +192,9 @@ public class NotificationService extends Service implements EtdServiceListener { .getUncertaintySeconds() != departure .getUncertaintySeconds())) { boardedDeparture.mergeEstimate(departure); + // Also merge back, in case boardedDeparture estimate is better + departure.mergeEstimate(boardedDeparture); + updateAlarm(); break; } @@ -211,8 +227,9 @@ public class NotificationService extends Service implements EtdServiceListener { final Departure boardedDeparture = ((BartRunnerApplication) getApplication()) .getBoardedDeparture(); - if (boardedDeparture.hasDeparted()) { + if (boardedDeparture == null || boardedDeparture.hasDeparted()) { shutDown(false); + return; } boardedDeparture.updateAlarm(getApplicationContext(), mAlarmManager); @@ -280,4 +297,5 @@ public class NotificationService extends Service implements EtdServiceListener { // Doesn't support binding return null; } + } diff --git a/src/com/dougkeen/util/Observable.java b/src/com/dougkeen/util/Observable.java index 4dd97bd..ef35799 100644 --- a/src/com/dougkeen/util/Observable.java +++ b/src/com/dougkeen/util/Observable.java @@ -36,6 +36,10 @@ public class Observable { listeners.remove(observer); } + public void unregisterAllObservers() { + listeners.clear(); + } + protected void notifyOfChange(T value) { for (Observer listener : listeners.keySet()) { if (listener != null) {