From 82f637b041d5ef8fdcd8d21a809999f9617534e4 Mon Sep 17 00:00:00 2001 From: Doug Keen Date: Mon, 1 Oct 2012 21:03:08 -0700 Subject: [PATCH] Refactored boarded departure alarms/notifications Departure single click now selects boarded departure "Your train" now has its own context menu/action bar --- AndroidManifest.xml | 2 +- res/drawable-hdpi/ic_action_add_alarm.png | Bin 0 -> 1088 bytes res/drawable-hdpi/ic_action_alarm.png | Bin 0 -> 1493 bytes res/drawable-ldpi/ic_action_add_alarm.png | Bin 0 -> 472 bytes res/drawable-ldpi/ic_action_alarm.png | Bin 0 -> 608 bytes res/drawable-mdpi/ic_action_add_alarm.png | Bin 0 -> 671 bytes res/drawable-mdpi/ic_action_alarm.png | Bin 0 -> 874 bytes res/drawable-xhdpi/ic_action_add_alarm.png | Bin 0 -> 1531 bytes res/drawable-xhdpi/ic_action_alarm.png | Bin 0 -> 2081 bytes res/layout/departures.xml | 71 +-- ...lert_dialog.xml => train_alarm_dialog.xml} | 0 res/layout/your_train.xml | 71 +++ res/menu/route_menu.xml | 6 - res/menu/your_train_context_menu.xml | 22 + res/values/colors.xml | 1 + res/values/strings.xml | 9 +- .../dougkeen/bart/BartRunnerApplication.java | 50 +-- ...ent.java => TrainAlarmDialogFragment.java} | 33 +- .../activities/ViewDeparturesActivity.java | 407 ++++++++++++------ .../controls/DepartureListItemLayout.java | 4 +- .../bart/controls/TimedTextSwitcher.java | 4 +- .../bart/controls/YourTrainLayout.java | 120 ++++++ .../bart/data/DepartureArrayAdapter.java | 2 - src/com/dougkeen/bart/data/RoutesColumns.java | 1 + src/com/dougkeen/bart/model/Departure.java | 70 +-- src/com/dougkeen/bart/model/StationPair.java | 6 +- src/com/dougkeen/bart/model/TextProvider.java | 1 + .../bart/networktasks/GetRouteFareTask.java | 6 +- ...vice.java => BoardedDepartureService.java} | 80 ++-- src/com/dougkeen/util/Observable.java | 4 + 30 files changed, 659 insertions(+), 311 deletions(-) create mode 100644 res/drawable-hdpi/ic_action_add_alarm.png create mode 100644 res/drawable-hdpi/ic_action_alarm.png create mode 100644 res/drawable-ldpi/ic_action_add_alarm.png create mode 100644 res/drawable-ldpi/ic_action_alarm.png create mode 100644 res/drawable-mdpi/ic_action_add_alarm.png create mode 100644 res/drawable-mdpi/ic_action_alarm.png create mode 100644 res/drawable-xhdpi/ic_action_add_alarm.png create mode 100644 res/drawable-xhdpi/ic_action_alarm.png rename res/layout/{train_alert_dialog.xml => train_alarm_dialog.xml} (100%) create mode 100644 res/layout/your_train.xml create mode 100644 res/menu/your_train_context_menu.xml rename src/com/dougkeen/bart/activities/{TrainAlertDialogFragment.java => TrainAlarmDialogFragment.java} (71%) create mode 100644 src/com/dougkeen/bart/controls/YourTrainLayout.java rename src/com/dougkeen/bart/services/{NotificationService.java => BoardedDepartureService.java} (76%) 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" /> Kpcb#{gci!`!^PauiyVZH>(>dq&Jm);W@ALeg=lQ*FbW}Gl(cRqt zC{IA7XV9AgZwA^q0v8s0^0H@~j!4!drpTRn(?R4r_!%^Bk zck5_a1mH|RK9mbH;U%Qw)H~nDsgL3Luemd9vw6;ezEBLMPG-)jP{%WXBLn&95f~5W z;56v{xiAC9!XS`<_wW>M;_L(79m@SSquoWW2g4=8kpLX%p%8${u!qj8uub892MmWd z@CZIbEQ~bT4&Ztp^56sPhD0dh8ybLi7pDR?&{Hbp({VY@ui<_ZJc9ykkL3yW-o7=# zeNRjaU^A$nY^aArpcz|7+b8W+U@HJehR{icF~aFw7sD>7hJ85k%Wz&Je+w32Yw|h9 z`@^_D3<{$Z_yOghwIdOJYbI1dP6YTyTUUk16{v$uI{#yGR15qLBx3($cEnf%S~ssj zv$+cNy;R!D=DOGlFh)0yj$Yx2!e9v`@vN9`EryfVF|43YJCt9cejbJckOlWZ2TAJy zxK-dtfFYt6*D9!?zIB++BfJxX& zAx&FnAny>d6MH=M+KIwaLJ{B#fn_+LZPTyd+*AxFAe;Ic3jxX4Ghh~NzK26WC;}GJ zp-y4#GJf$dM~rjfB)2ypC(!OfnGa8Rdq+gVC8 z>NNwuJ6phDI@BUAr1SGuoiobXA~WF(?TXCps4L+abM*RfIInLw4k05< zjhFya2!54E@vp(cqRR+y6T3zz zMrIATb-3+&-C_oCQh&iDScsOrWgCS++h`qOcp=z;x!Q$7nE?cBr2X1Z-Z8bUMu1Xq z%hDMFCeZsB3lEh1^?_s17@*BtyTh`>1$euVP3(Cy;Bs-cyw)wYQEv@&@im~-T&2+} zCASOFh%RC~=bY4g0-TIeN4D{1pd&}u$+x{3a573A+2$V(5|=<0lTI)I0000!2G`vQr2|fsZ@4pYis+yk%6S|&h%SF|Fwgeg>4&LMYP490=c{t5N<$bFSdBAWol z_6%qDi4l8_`aX=U(HP%%qdE)d)S+Oj9!~tp`-k9lN4sz`$X-R;K;Vpi2ZrfDGXp(ehn|zEZwK3gFTs_? zw|Yc!2~0xC@xW{`2F3Hk=+V@jVD~Wc4fV_5ZLl1eog`qk*&ir>8rUo3f1nUKI5Uj(i+1B(M;`$5QU=kt8FD9q5x{jK!Tag= zc<4Qc`Zl0%(`cXGFaeD1O6O;QBlsq8fvu(8QM?oA{8Heod|Pb@85gF+C-KeE>{cQy z$Iy5}kT5Kt4HD`;6BvU1Y5%MiiMlg@k>PY442;xJMuM4Gpd6tL^ zYY1)c)p5*}dWZ7J&8{UEgW_)dBrpSwGs9tP2c8cDOY5zxf-gJ~9VA^!StpxqdMhOB%0+@sBxDN%PRE}b8c>) z#wi!FYru1p=c);{`XW1v@9y&d92!$3$`EKr+4Di#BgFeD<;8a#JB8;?;Pufvz(YOz zBI6O;ea1_TyZk#j`m+hB3?nnByv)CV0Z)wCg}#og8=9A+e~mET-Sj$uXP*mG`%GcD zECJJ~i`kQ72S&UiW*40FY&8Km+9T-i_0oayy81NmgzK@|P|GT13AoKa1r7!ufWa7( zOm@LpFEK-bhrHFitJAFJZOvWYKO7$4vq~ufF>Se}~eg3Jo(-46maVfQVTM|ya zm+{3p=@sX_yn9w^4`-&+&!c+j(lCJ_(KlwoTbpGCSODGv%W-PmZ7pth`|)U;pAKBi zmS4a`Gu^Kw+6t_K)4! zz&~?d{0*(@;tk6jU;LT-s9~?GI&Lrm-8`*Sw&UGg?_UMW&!CIZw%(v+qY&7j9wwBv v2>h3$-J7k}476sT$q{IUP?H1LboajjLP8Qx*^(;G00000NkvXXu0mjfYSYrX literal 0 HcmV?d00001 diff --git a/res/drawable-ldpi/ic_action_add_alarm.png b/res/drawable-ldpi/ic_action_add_alarm.png new file mode 100644 index 0000000000000000000000000000000000000000..d918b352fe9e8b0483b5c60747d7b23868a923ab GIT binary patch literal 472 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 literal 0 HcmV?d00001 diff --git a/res/drawable-xhdpi/ic_action_alarm.png b/res/drawable-xhdpi/ic_action_alarm.png new file mode 100644 index 0000000000000000000000000000000000000000..43adf6dc9794aa75cfc7b1a712a7a6c23451992f GIT binary patch literal 2081 zcmV++2;TRJP)>5;}prFP=#27`e5DUcK z5bP3r@49~9I`# zJ_WjX0MwIUeG1g4Ko>`#L#9AeQ`6euI=i8v;jgmrMd$kHn^l&+sHao_aB?jap91%Q z&%xnIWjKu9P0(`}JQcnMz3-KwR~@AQfP?Be3_cECHaIr{M+cYTDEbApchL14zoX$> z%80O<0l>M{aOMJdhlNALDvB z4*isMDCCCSdA2>gIlM$04*$+`NA^YdarDfr(6bo#r^1H>;C$_r7=-LoML;M703BKn zW4C8IZV--9Df(Glb}*BkS|{5F1-@ zeHpxBc=jE?=A5Jzq6h%jVQ9x-)KMCM(I+b$yC?&YudD|q!^%xX-nly$(W#|*xGmfr zb}5Y`tnAnDP})A7vE?+pJ|p)o*T*0u$hjy9033Agotx2o1;*~qIOK@i`9;Dr0U8hA zf!yp|9t9z6t-$5s@DZ?cU-}2Ws=}tzYdE}ckhzohCgxm}8vqzvss} zAF_0|^Plm481J8N&6AKs%_Y;2aXPe0$eA<;!R8am9CI~otOfpi(9~pNJSN;a`h9u- zMjixG0LZSz-=O>ej5N>TFduK^GLF1S%jN0&b~+aI8P@l==Pdf23YWq=XF#yhY@T|& zck~^c4Z{2Byz5&U02o*UWe>tJ%tm5hY%p%Z8wIZu4BGkry#Jv}r>@|AH`tJF8O?S8 za~ceV%@2}E&-vaQJ@_a%SHW>m`{Y>_azy~j&p@$rQF=QVZr2%ni zj+ps#5$!FTSAi$eZeXrP{{vvX@F?uBt3q#d6xt8e0>IH2J}vk(2?x$h?8Fr0-3&cL z+o&hE3IIF@;X^}%vFI3?yKzRa767sp;Sd~nB(DQFuU+S@WwLdE=LkuJ4il(}RGNtn zb5t_b0)Qo{A!zvTjRS6ja}my4`&$}I@6gtYFNwn1?=)B$b{cd?PuyGN)>Q}qjBC_N zP@09|gK}Hc+d12RO#A&(+DBRRWTViU;Pq5JMFH3hLw5#aQ!v~=#W0RHBJV07eM9@* zQrc$#IFIWH{@Lhh*_7qgR}_Hk{7XLj0|3{5>j25Ud=H%WH;K@bJuAEr9SThsegd!so{kFMPR9AuQ#{9Um%sa9cbJhzpy%F1Jp+)B+5cSHTLC6f z(FpKBb3(8*1H*AW&TSkJEP_VB9%@ML^*vu0lUE5ku0?jIAm6J?ljw)s%SCJ#20&vt;{1&#G)epVwwy(0&j5n?#q!#dolmp!$XGhZ&DWb; zAB{}Q_lOiAY6T#EwN;M^%&?uF;y`P%bkIuElU>grJOGVFlb|>9><-ujDxHxhX}0B4 zO#rkK1s{ab__+iSES7eKuaBb}HbCwGJ(-fs5$*vBjlK%X0KoO!5sAx@$KclGelaSI zLg)S5zE4o<_s3(gGouzGH3JZ$z%NCHtZTDWDhQ$Tehb_?)7e6`eT>0Qr<X2d2D4pj3baaPO z_16JNMXa;Q*CU{_jc96r>JgBNSZ9;3M?hyA(bWFbBOn#A&L;maAdd00000 LNkvXXu0mjf`@->o literal 0 HcmV?d00001 diff --git a/res/layout/departures.xml b/res/layout/departures.xml index ac8598f..9a654cc 100644 --- a/res/layout/departures.xml +++ b/res/layout/departures.xml @@ -14,81 +14,14 @@ android:paddingRight="5dp" android:textSize="24dp" /> - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + \ 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) {