From 5836ccb89c488e7033985d7e04f1d6d5ed59b199 Mon Sep 17 00:00:00 2001 From: Doug Keen Date: Wed, 10 Oct 2012 17:21:30 -0700 Subject: [PATCH] Added alarm indicator to Your Train card. Fixed mysterious "freeze" of notifications (turned out to be unexpected gc of weak references). Departure countdown now reads "Leaving" at the end of the countdown. --- res/layout/your_train.xml | 12 +++ res/values/strings.xml | 94 +++++++++---------- .../activities/ViewDeparturesActivity.java | 69 +++++++------- .../bart/controls/YourTrainLayout.java | 69 +++++++++++++- src/com/dougkeen/bart/model/Departure.java | 15 ++- .../services/BoardedDepartureService.java | 15 +++ 6 files changed, 192 insertions(+), 82 deletions(-) diff --git a/res/layout/your_train.xml b/res/layout/your_train.xml index a906d13..a1f54de 100644 --- a/res/layout/your_train.xml +++ b/res/layout/your_train.xml @@ -2,6 +2,18 @@ + + + - - - BART Runner - Favorite routes - No favorite routes have been added yet - Loading, please wait… - Add a route - Origin - Destination - Save - The origin and destination stations must be - different - You must select a destination station - You must select an origin station - Please wait while real time departure data is - loaded - No departure data is currently available for this - route. Note that this route may require a non-standard transfer due to - a temporary change in service. Check for service advisories posted at - http://m.bart.gov/schedules/advisories/ - View - View departures - Missing/inaccurate departure? Feature request? Please report to bartrunner@dougkeen.com - Delete - Yes - Cancel - View details on BART site - Could not connect to BART services. Please try - again later. - Also add return route - View system map - System map - Departures - OK - Quick departure lookup - I will board this train - Departure options - Your train - Set up departure alarm - Your train is leaving soon! - Silence alarm - Cancel alarm - Set alarm - Leaving - - \ No newline at end of file + + + + BART Runner + Favorite routes + No favorite routes have been added yet + Loading, please wait… + Add a route + Origin + Destination + Save + The origin and destination stations must be + different + You must select a destination station + You must select an origin station + Please wait while real time departure data is + loaded + No departure data is currently available for this + route. Note that this route may require a non-standard transfer due to + a temporary change in service. Check for service advisories posted at + http://m.bart.gov/schedules/advisories/ + View + View departures + Missing/inaccurate departure? Feature request? Please report to bartrunner@dougkeen.com + Delete + Yes + Cancel + View details on BART site + Could not connect to BART services. Please try + again later. + Also add return route + View system map + System map + Departures + OK + Quick departure lookup + I will board this train + Departure options + Your train + Set up departure alarm + Your train is leaving soon! + Silence alarm + Cancel alarm + Set alarm + Leaving + Departed + \ No newline at end of file diff --git a/src/com/dougkeen/bart/activities/ViewDeparturesActivity.java b/src/com/dougkeen/bart/activities/ViewDeparturesActivity.java index 93d7269..48286e4 100644 --- a/src/com/dougkeen/bart/activities/ViewDeparturesActivity.java +++ b/src/com/dougkeen/bart/activities/ViewDeparturesActivity.java @@ -176,7 +176,7 @@ public class ViewDeparturesActivity extends SActivity implements && mSelectedDeparture != null) { ((Checkable) findViewById(R.id.yourTrainSection)) .setChecked(true); - startYourTrainActionMode(bartRunnerApplication); + startYourTrainActionMode(); } } setListAdapter(mDeparturesAdapter); @@ -245,9 +245,7 @@ public class ViewDeparturesActivity extends SActivity implements tryToPlayRingtone(alarmSound); } final Vibrator vibrator = (Vibrator) getSystemService(VIBRATOR_SERVICE); - if (vibrator.hasVibrator()) { - vibrator.vibrate(new long[] { 0, 500, 500 }, 1); - } + vibrator.vibrate(new long[] { 0, 500, 500 }, 1); mHandler.postDelayed(new Runnable() { @Override public void run() { @@ -276,9 +274,7 @@ public class ViewDeparturesActivity extends SActivity implements application.setAlarmSounding(false); application.setAlarmMediaPlayer(null); final Vibrator vibrator = (Vibrator) getSystemService(VIBRATOR_SERVICE); - if (vibrator.hasVibrator()) { - vibrator.cancel(); - } + vibrator.cancel(); try { if (mediaPlayer != null && mediaPlayer.isPlaying()) { mediaPlayer.stop(); @@ -363,7 +359,7 @@ public class ViewDeparturesActivity extends SActivity implements @Override public void onClick(View v) { ((Checkable) v).setChecked(true); - startYourTrainActionMode((BartRunnerApplication) getApplication()); + startYourTrainActionMode(); } }; @@ -472,28 +468,28 @@ public class ViewDeparturesActivity extends SActivity implements } private void refreshBoardedDeparture(boolean animate) { - final Departure boardedDeparture = ((BartRunnerApplication) getApplication()) - .getBoardedDeparture(); + final Departure boardedDeparture = getBoardedDeparture(); int currentVisibility = mYourTrainSection.getVisibility(); - final boolean boardedDepartureDoesNotApply = boardedDeparture == null - || boardedDeparture.getStationPair() == null - || !boardedDeparture.getStationPair().equals(getStationPair()); - - if (boardedDepartureDoesNotApply) { + if (!doesDepartureApply(boardedDeparture)) { if (currentVisibility != View.GONE) { hideYourTrainSection(); } return; } - mYourTrainSection.updateFromDeparture(boardedDeparture); + mYourTrainSection.updateFromBoardedDeparture(); if (currentVisibility != View.VISIBLE) { showYourTrainSection(animate); } } + private boolean doesDepartureApply(final Departure departure) { + return departure != null && departure.getStationPair() != null + && departure.getStationPair().equals(getStationPair()); + } + private void setBoardedDeparture(Departure selectedDeparture) { final BartRunnerApplication application = (BartRunnerApplication) getApplication(); selectedDeparture.setPassengerDestination(mDestination); @@ -546,14 +542,13 @@ public class ViewDeparturesActivity extends SActivity implements } - private void startYourTrainActionMode(BartRunnerApplication application) { + private void startYourTrainActionMode() { 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(); + Departure boardedDeparture = getBoardedDeparture(); + if (boardedDeparture != null && boardedDeparture.isAlarmPending()) { + int leadTime = boardedDeparture.getAlarmLeadTimeMinutes(); mActionMode.setSubtitle(getAlarmSubtitle(leadTime)); } else { mActionMode.setSubtitle(null); @@ -579,9 +574,7 @@ public class ViewDeparturesActivity extends SActivity implements .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(); + final Departure boardedDeparture = getBoardedDeparture(); if (boardedDeparture == null) { mode.finish(); @@ -652,10 +645,8 @@ public class ViewDeparturesActivity extends SActivity implements 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) { + if (getBoardedDeparture().getMeanSecondsLeft() > 60) { new TrainAlarmDialogFragment() .show(getSupportFragmentManager() .beginTransaction()); @@ -680,9 +671,7 @@ public class ViewDeparturesActivity extends SActivity implements public void onDestroyActionMode(ActionMode mode) { ((Checkable) findViewById(R.id.yourTrainSection)).setChecked(false); - final BartRunnerApplication application = (BartRunnerApplication) getApplication(); - final Departure boardedDeparture = application - .getBoardedDeparture(); + final Departure boardedDeparture = getBoardedDeparture(); if (boardedDeparture != null) { boardedDeparture.getAlarmPendingObservable() .unregisterObserver(mAlarmPendingObserver); @@ -719,10 +708,18 @@ public class ViewDeparturesActivity extends SActivity implements Ticker.getInstance().startTicking( ViewDeparturesActivity.this); + Departure boardedDeparture = getBoardedDeparture(); + boolean boardedDepartureFound = false; + // Merge lists if (mDeparturesAdapter.getCount() > 0) { int adapterIndex = -1; for (Departure departure : departures) { + if (!boardedDepartureFound + && departure.equals(boardedDeparture)) { + boardedDepartureFound = true; + } + adapterIndex++; Departure existingDeparture = null; if (adapterIndex < mDeparturesAdapter.getCount()) { @@ -755,6 +752,11 @@ public class ViewDeparturesActivity extends SActivity implements } } + if (doesDepartureApply(boardedDeparture) + && !boardedDepartureFound) { + boardedDeparture.setListedInETDs(false); + } + refreshBoardedDeparture(true); getListAdapter().notifyDataSetChanged(); @@ -771,8 +773,7 @@ public class ViewDeparturesActivity extends SActivity implements if (isDepartureActionModeActive() && mSelectedDeparture != null) { targetDeparture = mSelectedDeparture; } else { - targetDeparture = ((BartRunnerApplication) getApplication()) - .getBoardedDeparture(); + targetDeparture = getBoardedDeparture(); } for (int i = getListAdapter().getCount() - 1; i >= 0; i--) { if (getListAdapter().getItem(i).equals(targetDeparture)) { @@ -851,4 +852,8 @@ public class ViewDeparturesActivity extends SActivity implements && !mActionMode.getTitle().equals( getString(R.string.your_train)); } + + private Departure getBoardedDeparture() { + return ((BartRunnerApplication) getApplication()).getBoardedDeparture(); + } } diff --git a/src/com/dougkeen/bart/controls/YourTrainLayout.java b/src/com/dougkeen/bart/controls/YourTrainLayout.java index 2b7a748..4cfa2ca 100644 --- a/src/com/dougkeen/bart/controls/YourTrainLayout.java +++ b/src/com/dougkeen/bart/controls/YourTrainLayout.java @@ -1,5 +1,6 @@ package com.dougkeen.bart.controls; +import android.app.Activity; import android.content.Context; import android.graphics.Color; import android.graphics.drawable.GradientDrawable; @@ -11,9 +12,11 @@ import android.widget.ImageView; import android.widget.RelativeLayout; import android.widget.TextView; +import com.dougkeen.bart.BartRunnerApplication; import com.dougkeen.bart.R; import com.dougkeen.bart.model.Departure; import com.dougkeen.bart.model.TextProvider; +import com.dougkeen.util.Observer; public class YourTrainLayout extends RelativeLayout implements Checkable { @@ -38,6 +41,28 @@ public class YourTrainLayout extends RelativeLayout implements Checkable { private boolean mChecked; + private Departure mDeparture; + + private final Observer mAlarmLeadObserver = new Observer() { + @Override + public void onUpdate(Integer newValue) { + final Activity context = (Activity) getContext(); + if (context != null) { + context.runOnUiThread(mUpdateAlarmIndicatorRunnable); + } + } + }; + + private final Observer mAlarmPendingObserver = new Observer() { + @Override + public void onUpdate(Boolean newValue) { + final Activity context = (Activity) getContext(); + if (context != null) { + context.runOnUiThread(mUpdateAlarmIndicatorRunnable); + } + } + }; + @Override public boolean isChecked() { return mChecked; @@ -64,7 +89,27 @@ public class YourTrainLayout extends RelativeLayout implements Checkable { setChecked(!isChecked()); } - public void updateFromDeparture(final Departure boardedDeparture) { + public void updateFromBoardedDeparture() { + final Departure boardedDeparture = ((BartRunnerApplication) ((Activity) getContext()) + .getApplication()).getBoardedDeparture(); + if (boardedDeparture == null) + return; + + if (!boardedDeparture.equals(mDeparture)) { + if (mDeparture != null) { + mDeparture.getAlarmLeadTimeMinutesObservable() + .unregisterObserver(mAlarmLeadObserver); + mDeparture.getAlarmPendingObservable().unregisterObserver( + mAlarmPendingObserver); + } + boardedDeparture.getAlarmLeadTimeMinutesObservable() + .registerObserver(mAlarmLeadObserver); + boardedDeparture.getAlarmPendingObservable().registerObserver( + mAlarmPendingObserver); + } + + mDeparture = boardedDeparture; + ((TextView) findViewById(R.id.yourTrainDestinationText)) .setText(boardedDeparture.getTrainDestination().toString()); @@ -88,6 +133,9 @@ public class YourTrainLayout extends RelativeLayout implements Checkable { ((ImageView) findViewById(R.id.yourTrainXferIcon)) .setVisibility(View.INVISIBLE); } + + updateAlarmIndicator(); + CountdownTextView departureCountdown = (CountdownTextView) findViewById(R.id.yourTrainDepartureCountdown); CountdownTextView arrivalCountdown = (CountdownTextView) findViewById(R.id.yourTrainArrivalCountdown); @@ -95,7 +143,7 @@ public class YourTrainLayout extends RelativeLayout implements Checkable { @Override public String getText(long tickNumber) { if (boardedDeparture.hasDeparted()) { - return getContext().getString(R.string.leaving); + return boardedDeparture.getCountdownText(); } else { return "Leaves in " + boardedDeparture.getCountdownText() + " " + boardedDeparture.getUncertaintyText(); @@ -117,4 +165,21 @@ public class YourTrainLayout extends RelativeLayout implements Checkable { setBackground(); } + + private void updateAlarmIndicator() { + if (!mDeparture.isAlarmPending()) { + findViewById(R.id.alarmText).setVisibility(GONE); + } else { + findViewById(R.id.alarmText).setVisibility(VISIBLE); + ((TextView) findViewById(R.id.alarmText)).setText(String + .valueOf(mDeparture.getAlarmLeadTimeMinutes())); + } + } + + private final Runnable mUpdateAlarmIndicatorRunnable = new Runnable() { + @Override + public void run() { + updateAlarmIndicator(); + } + }; } diff --git a/src/com/dougkeen/bart/model/Departure.java b/src/com/dougkeen/bart/model/Departure.java index 794aa9c..fc99f5f 100644 --- a/src/com/dougkeen/bart/model/Departure.java +++ b/src/com/dougkeen/bart/model/Departure.java @@ -74,6 +74,8 @@ public class Departure implements Parcelable, Comparable { 0); private Observable alarmPending = new Observable(false); + private boolean listedInETDs = true; + public Station getOrigin() { return origin; } @@ -463,9 +465,12 @@ public class Departure implements Parcelable, Comparable { if (hasDeparted()) { if (origin != null && origin.longStationLinger && beganAsDeparted) { builder.append("At station"); - } else { + } else if (isListedInETDs()) { builder.append(BartRunnerApplication.getAppContext().getString( R.string.leaving)); + } else { + builder.append(BartRunnerApplication.getAppContext().getString( + R.string.departed)); } } else { builder.append(secondsLeft / 60); @@ -484,6 +489,14 @@ public class Departure implements Parcelable, Comparable { } } + public boolean isListedInETDs() { + return listedInETDs; + } + + public void setListedInETDs(boolean listedInETDs) { + this.listedInETDs = listedInETDs; + } + public int getAlarmLeadTimeMinutes() { return alarmLeadTimeMinutes.getValue(); } diff --git a/src/com/dougkeen/bart/services/BoardedDepartureService.java b/src/com/dougkeen/bart/services/BoardedDepartureService.java index 733e3f3..c7c766d 100644 --- a/src/com/dougkeen/bart/services/BoardedDepartureService.java +++ b/src/com/dougkeen/bart/services/BoardedDepartureService.java @@ -136,6 +136,8 @@ public class BoardedDepartureService extends Service implements if (intent.getBooleanExtra("clearBoardedDeparture", false)) { application.setBoardedDeparture(null); shutDown(false); + } else { + updateNotification(); } return; } @@ -232,6 +234,19 @@ public class BoardedDepartureService extends Service implements return; } + if (mEtdService != null) { + /* + * Make sure we're still listening for ETD changes (in case weak ref + * was garbage collected). Not a huge fan of this approach, but I + * think I'd rather keep the weak references to avoid memory leaks + * than move to soft references or some other form of stronger + * reference. Besides, registerListener() should only result in a + * few constant-time map operations, so there shouldn't be a big + * performance hit. + */ + mEtdService.registerListener(this, false); + } + boardedDeparture.updateAlarm(getApplicationContext(), mAlarmManager); updateNotification();