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.
This commit is contained in:
Doug Keen 2012-10-10 17:21:30 -07:00
parent 699a40e93b
commit 5836ccb89c
6 changed files with 192 additions and 82 deletions

View File

@ -2,6 +2,18 @@
<merge xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:bart="http://schemas.android.com/apk/res/com.dougkeen.bart" >
<TextView
android:id="@+id/alarmText"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentRight="true"
android:layout_alignParentTop="true"
android:drawableLeft="@drawable/ic_action_alarm"
android:gravity="center_vertical"
android:textSize="16dp"
android:visibility="gone" >
</TextView>
<TextView
android:id="@+id/yourTrainHeader"
android:layout_width="wrap_content"

View File

@ -1,47 +1,47 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="app_name">BART Runner</string>
<string name="favorite_routes">Favorite routes</string>
<string name="empty_favorites_list_message">No favorite routes have been added yet</string>
<string name="loading_message">Loading, please wait&#8230;</string>
<string name="add_route">Add a route</string>
<string name="origin">Origin</string>
<string name="destination">Destination</string>
<string name="save">Save</string>
<string name="error_matching_origin_and_destination">The origin and destination stations must be
different</string>
<string name="error_null_destination">You must select a destination station</string>
<string name="error_null_origin">You must select an origin station</string>
<string name="departure_wait_message">Please wait while real time departure data is
loaded</string>
<string name="no_data_message">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/</string>
<string name="view">View</string>
<string name="view_departures">View departures</string>
<string name="missing_departure">Missing/inaccurate departure? Feature request? Please report to bartrunner@dougkeen.com</string>
<string name="delete">Delete</string>
<string name="yes">Yes</string>
<string name="cancel">Cancel</string>
<string name="view_on_bart_site">View details on BART site</string>
<string name="could_not_connect">Could not connect to BART services. Please try
again later.</string>
<string name="also_add_return_route">Also add return route</string>
<string name="view_system_map">View system map</string>
<string name="system_map">System map</string>
<string name="departures">Departures</string>
<string name="ok">OK</string>
<string name="quick_departure_lookup">Quick departure lookup</string>
<string name="getting_on_this_train">I will board this train</string>
<string name="departure_options">Departure options</string>
<string name="your_train">Your train</string>
<string name="set_up_departure_alarm">Set up departure alarm</string>
<string name="train_alarm_text">Your train is leaving soon!</string>
<string name="silence_alarm">Silence alarm</string>
<string name="cancel_alarm">Cancel alarm</string>
<string name="set_alarm">Set alarm</string>
<string name="leaving">Leaving</string>
</resources>
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="app_name">BART Runner</string>
<string name="favorite_routes">Favorite routes</string>
<string name="empty_favorites_list_message">No favorite routes have been added yet</string>
<string name="loading_message">Loading, please wait&#8230;</string>
<string name="add_route">Add a route</string>
<string name="origin">Origin</string>
<string name="destination">Destination</string>
<string name="save">Save</string>
<string name="error_matching_origin_and_destination">The origin and destination stations must be
different</string>
<string name="error_null_destination">You must select a destination station</string>
<string name="error_null_origin">You must select an origin station</string>
<string name="departure_wait_message">Please wait while real time departure data is
loaded</string>
<string name="no_data_message">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/</string>
<string name="view">View</string>
<string name="view_departures">View departures</string>
<string name="missing_departure">Missing/inaccurate departure? Feature request? Please report to bartrunner@dougkeen.com</string>
<string name="delete">Delete</string>
<string name="yes">Yes</string>
<string name="cancel">Cancel</string>
<string name="view_on_bart_site">View details on BART site</string>
<string name="could_not_connect">Could not connect to BART services. Please try
again later.</string>
<string name="also_add_return_route">Also add return route</string>
<string name="view_system_map">View system map</string>
<string name="system_map">System map</string>
<string name="departures">Departures</string>
<string name="ok">OK</string>
<string name="quick_departure_lookup">Quick departure lookup</string>
<string name="getting_on_this_train">I will board this train</string>
<string name="departure_options">Departure options</string>
<string name="your_train">Your train</string>
<string name="set_up_departure_alarm">Set up departure alarm</string>
<string name="train_alarm_text">Your train is leaving soon!</string>
<string name="silence_alarm">Silence alarm</string>
<string name="cancel_alarm">Cancel alarm</string>
<string name="set_alarm">Set alarm</string>
<string name="leaving">Leaving</string>
<string name="departed">Departed</string>
</resources>

View File

@ -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();
}
}

View File

@ -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<Integer> mAlarmLeadObserver = new Observer<Integer>() {
@Override
public void onUpdate(Integer newValue) {
final Activity context = (Activity) getContext();
if (context != null) {
context.runOnUiThread(mUpdateAlarmIndicatorRunnable);
}
}
};
private final Observer<Boolean> mAlarmPendingObserver = new Observer<Boolean>() {
@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();
}
};
}

View File

@ -74,6 +74,8 @@ public class Departure implements Parcelable, Comparable<Departure> {
0);
private Observable<Boolean> alarmPending = new Observable<Boolean>(false);
private boolean listedInETDs = true;
public Station getOrigin() {
return origin;
}
@ -463,9 +465,12 @@ public class Departure implements Parcelable, Comparable<Departure> {
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<Departure> {
}
}
public boolean isListedInETDs() {
return listedInETDs;
}
public void setListedInETDs(boolean listedInETDs) {
this.listedInETDs = listedInETDs;
}
public int getAlarmLeadTimeMinutes() {
return alarmLeadTimeMinutes.getValue();
}

View File

@ -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();