Back to project page BART.
The source code is released under:
GNU General Public License
If you think the Android project BART listed in this page is inappropriate, such as containing malicious code/tools or violating the copyright, please email info at java2s dot com, thanks.
/* * Copyright (C) 2012 David Brodsky//from w w w . ja va 2 s.co m * This file is part of Open BART. * * Open BART is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * Open BART is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with Open BART. If not, see <http://www.gnu.org/licenses/>. */ package pro.dbro.bart; import java.util.Date; import android.app.Notification; import android.app.NotificationManager; import android.app.PendingIntent; import android.app.Service; import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import android.os.Binder; import android.os.CountDownTimer; import android.os.IBinder; import android.os.Vibrator; import android.support.v4.app.NotificationCompat; import android.support.v4.content.LocalBroadcastManager; import android.util.Log; // TODO: Look at Intent to send on notification click to ENSURE stop service label is created in TheActivity public class UsherService extends Service { private NotificationManager mNM; private PendingIntent contentIntent; private Context c; private Notification notification; // keep an instance of the notification to update time text private int currentLeg; // keep track of which leg of the route we're currently on private boolean didBoard; // keep track of whether we've boarded the current leg, or are waiting for it to arrive private CountDownTimer timer; // keep track of current countdown for cancelling if new request comes // else we can get errors related to a timer expecting previous route private CountDownTimer reminderTimer; // timer separated from actual timer by REMINDER_PADDING_MS private route usherRoute; // the route to guide along. Updated with etd data private final long EVENT_PADDING_MS = 60*1000; // ms before an event (board, disembark) the usher should notify and issue next direction private final long REMINDER_PADDING_MS = 180*1000; // ms before an event (board, disembark) the usher should issue a reminder private final long UPDATE_INTERVAL_MS = 20*1000; // ms interval for updating notification // Unique Identification Number for the Notification. // We use it on Notification start, and to cancel it. private int NOTIFICATION = R.string.local_service_started; /** * Class for clients to access. Because we know this service always * runs in the same process as its clients, we don't need to deal with * IPC. */ public class LocalBinder extends Binder { UsherService getService() { return UsherService.this; } } @Override public void onCreate() { super.onCreate(); c = this; mNM = (NotificationManager)getSystemService(NOTIFICATION_SERVICE); //Log.v("Usher","OnCreate"); // LocalBroadCast Stuff LocalBroadcastManager.getInstance(this).registerReceiver(serviceDataMessageReceiver, new IntentFilter("service_status_change")); // Display a notification about us starting. We put an icon in the status bar. } @Override public int onStartCommand(Intent intent, int flags, int startId) { //TODO: Check if service-start should be sent OnCreate sendMessage(1); // send service-start message usherRoute = TheActivity.usherRoute; Log.i("UsherService", "Received start id " + startId + ": " + intent+ " route: "+usherRoute.toString()); if(timer != null){ timer.cancel(); } if(reminderTimer != null){ reminderTimer.cancel(); } // We want this service to continue running until it is explicitly // stopped, so return sticky. showNotification(); return START_STICKY; } @Override public void onDestroy(){ super.onDestroy(); sendMessage(0); // send service-stopped message Log.d("onDestroy","called"); if(timer != null) timer.cancel(); if(reminderTimer != null) reminderTimer.cancel(); // Cancel the persistent notification. mNM.cancel(NOTIFICATION); // Tell the user we stopped. //Toast.makeText(this, R.string.local_service_stopped, Toast.LENGTH_SHORT).show(); } @Override public IBinder onBind(Intent intent) { return mBinder; } // This is the object that receives interactions from clients. See // RemoteService for a more complete example. private final IBinder mBinder = new LocalBinder(); /** * Initialize first guidance timer and send initial notification */ private void showNotification() { // In this sample, we'll use the same text for the ticker and the expanded notification //CharSequence text = getText(R.string.local_service_started); String destinationStation = ""; try{ destinationStation = ((leg)usherRoute.legs.get(usherRoute.legs.size()-1)).disembarkStation; }catch(Throwable T){ Log.d("usherRoute null err", "catch it, bro"); } currentLeg = 0; didBoard = false; CharSequence tickerText = "Guiding to " + BART.REVERSE_STATION_MAP.get(destinationStation.toLowerCase()); // Set the info for the views that show in the notification panel. // BUGFIX: new Date() is not guaranteed to return in BART's locale Date now = new Date(); Log.d("UsherRouteEta",((leg)usherRoute.legs.get(0)).boardTime.toString()); long minutesUntilNext = ((((leg)usherRoute.legs.get(0)).boardTime.getTime() - now.getTime())); //minutesUntilNext is, for this brief moment, actually milliseconds. makeLegCountdownTimer(minutesUntilNext); // back to minutes minutesUntilNext = (minutesUntilNext)/(1000*60); //catch negative time state if(minutesUntilNext <= 0){ //Log.v("Negative ETA", "Catch me"); } CharSequence currentStepText = "At " + BART.REVERSE_STATION_MAP.get(((leg)usherRoute.legs.get(0)).boardStation.toLowerCase()); // display 0m as "<1m" CharSequence nextStepText = ""; if(minutesUntilNext == 0){ nextStepText = "Board "+ BART.REVERSE_STATION_MAP.get(((leg)usherRoute.legs.get(0)).trainHeadStation.toLowerCase()) + " train in <1m"; }else{ nextStepText = "Board "+ BART.REVERSE_STATION_MAP.get(((leg)usherRoute.legs.get(0)).trainHeadStation.toLowerCase()) + " train in " + String.valueOf(minutesUntilNext) + "m"; } // The PendingIntent to launch our activity if the user selects this notification setContentIntent(); // Create notification NotificationCompat.Builder builder = new NotificationCompat.Builder(c); builder.setContentIntent(contentIntent) .setSmallIcon(R.drawable.ic_launcher_notification) //.setLargeIcon(BitmapFactory.decodeResource(res, R.drawable.some_big_img)) .setTicker(tickerText) .setWhen(0) //.setAutoCancel(true) .setContentTitle(currentStepText) .setContentText(nextStepText) .setOngoing(true); notification = builder.getNotification(); mNM.notify(NOTIFICATION, notification); } //if newNotification is true, generate new notification with scroller text //else, simply update menu item text private void updateNotification(boolean newNotification){ Date now = new Date(); CharSequence nextStepText =""; CharSequence currentStepText = ""; if(didBoard){ // Notification text set for next disembark (Transfer or final destination) currentStepText = "On " + BART.REVERSE_STATION_MAP.get(((leg)usherRoute.legs.get(currentLeg)).trainHeadStation.toLowerCase())+ " train"; long minutesUntilNext = ((((leg)usherRoute.legs.get(currentLeg)).disembarkTime.getTime() - now.getTime())/(1000*60)); if(minutesUntilNext < 0){ //Log.v("Negative ETA", "Catch me"); } CharSequence actionText = ""; // If last leg, begin message with "Get off at", else "Transfer at" if(currentLeg+1 == usherRoute.legs.size()){ actionText = "Get off at "; } else{ actionText = "Transfer at "; } CharSequence timeText = ""; // If minutesUntilNext == 0, display as "<1m" if(minutesUntilNext == 0){ timeText = "<1m"; } else{ timeText = String.valueOf(minutesUntilNext)+"m"; } // Construct notification text (nextStepText) from actionText, usherRoute next station, and timeText nextStepText = actionText + BART.REVERSE_STATION_MAP.get(((leg)usherRoute.legs.get(currentLeg)).disembarkStation.toLowerCase()) + " in " + timeText; } else{ // Notification text set for next boarding long minutesUntilNext = ((((leg)usherRoute.legs.get(currentLeg)).boardTime.getTime() - now.getTime())/(1000*60)); if(minutesUntilNext < 0){ //Log.v("Negative ETA", "Catch me"); } nextStepText = "Board "+ BART.REVERSE_STATION_MAP.get(((leg)usherRoute.legs.get(currentLeg)).trainHeadStation.toLowerCase()) + " train in "; // Display 0m eta as "<1m" if(minutesUntilNext == 0){ nextStepText = nextStepText + "<1m"; } else{ nextStepText = nextStepText + String.valueOf(minutesUntilNext) + "m"; } currentStepText = "At " + BART.REVERSE_STATION_MAP.get(((leg)usherRoute.legs.get(currentLeg)).boardStation.toLowerCase()); } NotificationCompat.Builder builder = new NotificationCompat.Builder(c); if(contentIntent == null){ Log.d("ContentIntent","was null"); setContentIntent(); } builder.setContentIntent(contentIntent) .setSmallIcon(R.drawable.ic_launcher_notification) //.setLargeIcon(BitmapFactory.decodeResource(res, R.drawable.some_big_img)) .setWhen(0) .setContentTitle(currentStepText) .setContentText(nextStepText) .setOngoing(true); if(newNotification){ builder.setTicker(nextStepText); } notification = builder.getNotification(); mNM.notify(NOTIFICATION, notification); } private void makeLegCountdownTimer(long msUntilNext){ //make sure we don't leak any timers if(timer != null) timer.cancel(); // Make a new timer to expire EVENT_PADDING_MS before event timer = new CountDownTimer(msUntilNext - EVENT_PADDING_MS, UPDATE_INTERVAL_MS){ //new CountDownTimer(5000, 1000){ @Override public void onFinish() { // TODO Auto-generated method stub Vibrator v = (Vibrator) getSystemService(c.VIBRATOR_SERVICE); long[] vPattern = {0,400,300,400,300,400,300,400}; v.vibrate(vPattern,-1); //if(didBoard) // if we've boarded, we're handling the last leg // currentLeg ++; didBoard = !didBoard; // Our next move is departing the final leg train if ((usherRoute.legs.size() == currentLeg+1) && !didBoard){ NotificationCompat.Builder builder = new NotificationCompat.Builder(c); if(contentIntent == null){ Log.d("ContentIntent","was null"); setContentIntent(); } builder.setContentIntent(contentIntent) .setSmallIcon(R.drawable.ic_launcher_notification) //.setLargeIcon(BitmapFactory.decodeResource(res, R.drawable.some_big_img)) .setWhen(0) .setTicker("This is your stop! Take care!"); notification = builder.getNotification(); mNM.notify(NOTIFICATION, notification); //TheActivity.removeStopServiceText(); stopSelf(); } // Our next move is departing the current leg's train else if(didBoard){ //Set timer for this leg's disembark time Date now = new Date(); long msUntilNext = ((((leg)usherRoute.legs.get(currentLeg)).disembarkTime.getTime() - now.getTime())); //Log.v("UsherState","leg: "+ String.valueOf(currentLeg)+ " / "+String.valueOf(usherRoute.legs.size())+" Boarded. Next: "+String.valueOf(msUntilNext)+"ms"); updateNotification(true); makeLegCountdownTimer(msUntilNext); // this cancels current timer } // Our next move is boarding the next leg's train else{ currentLeg ++; Date now = new Date(); long msUntilNext = ((((leg)usherRoute.legs.get(currentLeg)).boardTime.getTime() - now.getTime())); //Log.v("UsherState","leg: "+ String.valueOf(currentLeg)+ " / "+String.valueOf(usherRoute.legs.size())+" Waiting. Next: "+String.valueOf(msUntilNext)+"ms"); updateNotification(true); makeLegCountdownTimer(msUntilNext); } } @Override public void onTick(long arg0) { updateNotification(false); } }.start(); //timer.start(); // Set Reminder timer REMINDER_PADDING_MS ms before event if(msUntilNext > (REMINDER_PADDING_MS+30*1000)){ // if next event is more than 30 seconds + REMINDER_PADDING_MS out, set reminder //avoid leaking timer if(reminderTimer != null) reminderTimer.cancel(); reminderTimer = new CountDownTimer(msUntilNext - REMINDER_PADDING_MS, msUntilNext - REMINDER_PADDING_MS){ @Override public void onFinish() { // TODO: Is this a good time for updating? //requestDataUpdate(); updateNotification(true); Vibrator v = (Vibrator) getSystemService(c.VIBRATOR_SERVICE); long[] vPattern = {0,400,300,400}; v.vibrate(vPattern,-1); } @Override public void onTick(long millisUntilFinished) { // TODO Auto-generated method stub } }.start(); } } private void sendMessage(int status) { // 0 = service stopped , 1 = service started Log.d("sender", "Broadcasting message"); Intent intent = new Intent("service_status_change"); // You can also include some extra data. intent.putExtra("status", status); LocalBroadcastManager.getInstance(this).sendBroadcast(intent); } private BroadcastReceiver serviceDataMessageReceiver = new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { // Get extra data included in the Intent int status = intent.getIntExtra("status", -1); if(status == 5){ // service stopped //update timers with fresh data updateTimersWithEtdResponse((etdResponse)intent.getSerializableExtra("etdResponse")); } } }; private void requestDataUpdate(){ String curStation; if( didBoard){ curStation = ((leg)usherRoute.legs.get(currentLeg)).disembarkStation.toLowerCase(); } else{ curStation = ((leg)usherRoute.legs.get(currentLeg)).boardStation.toLowerCase(); } new RequestTask("etd", false).execute(BART.API_ROOT+"etd.aspx?cmd=etd&orig="+curStation+"&key="+BART.API_KEY); } // TODO: I've gotten NullPointerException pointed here ? //04-11 18:24:42.420: E/AndroidRuntime(6698): java.lang.NullPointerException //04-11 18:24:42.420: E/AndroidRuntime(6698): at pro.dbro.bart.UsherService.updateTimersWithEtdResponse(UsherService.java:319) private void updateTimersWithEtdResponse(etdResponse response){ leg curLeg = (leg)usherRoute.legs.get(currentLeg); long curTargetTime; // time until next move according to schedule int etd = -1; /* for(int y=0;y<response.etds.size();y++){ // DEBUG try{ //Check that destination train is listed in terminal-station format. Ex: "Fremont" CounterEx: 'SFO/Milbrae' if (!BART.STATION_MAP.containsKey(((etd)response.etds.get(y)).destination)){ // If this is not a known silly-named train terminal station if (!BART.KNOWN_SILLY_TRAINS.containsKey(((etd)response.etds.get(y)).destination)){ // Let's try and guess what it is boolean station_guessed = false; for(int z = 0; z< BART.STATIONS.length; z++){ // Can we match a station name within the silly-train name? // haystack.indexOf(needle1); if ( (((etd)response.etds.get(y)).destination).indexOf(BART.STATIONS[z]) != -1){ // Set the etd destination to the guessed real station name ((etd)response.etds.get(y)).destination = BART.STATIONS[z]; station_guessed = true; } } if (!station_guessed){ break; //We have to give up on updating routes based on this utterly silly-named etd } } else{ // Set the etd destination station to the real station name ((etd)response.etds.get(y)).destination = KNOWN_SILLY_TRAINS.get(((etd)response.etds.get(y)).destination); //break; } } // end STATION_MAP silly-name train check and replace // Comparing BART station abbreviations if (BART.STATION_MAP.get(((etd)response.etds.get(y)).destination).compareTo(((leg)((route)input.routes.get(x)).legs.get(0)).trainHeadStation) == 0 ){ //If matching etd is not all ready matched to a route, match it to this one if (!routeToEtd.containsKey(x) && !routeToEtd.containsValue(y)){ routeToEtd.put(x, y); } else{ //if the etd is all ready claimed by a route, go to next etd break; } } else if (BART.STATION_MAP.get(((etd)currentEtdResponse.etds.get(y)).destination).compareTo(((leg)((route)input.routes.get(x)).legs.get(lastLeg)).trainHeadStation) == 0 ){ if (!routeToEtd.containsKey(x) && !routeToEtd.containsValue(y)){ routeToEtd.put(x, y); } else{ //if the etd is all ready claimed by a route, go to next etd break; } } }catch(Throwable T){ // Likely, a train with destination listed as a // special tuple and not an actual station name // was encountered Log.v("WTF", "Find me"); } }// end etd for loop */ for(int x=0;x<response.etds.size();x++){ //find the etd of response which matches current train //check this logic //crash here /* old method if(curLeg.trainHeadStation.compareTo(BART.STATION_MAP.get(((etd)response.etds.get(x)).destination)) == 0){ etd = x; break; } */ } //something went wrong if(etd == -1) return; long etdTargetTime = ((etd)response.etds.get(etd)).minutesToArrival*60*1000; // BUFIX: new Date() is not guaranteed to return time in BART's locale // instead, use date appended to etd response //Date now = new Date(); Date now = response.date; if(didBoard){ curTargetTime = curLeg.disembarkTime.getTime(); // for debug only // update the usherRoute disembarkTime by adding minutesToArrival to new Date() curLeg.disembarkTime = new Date(now.getTime() + ((etd)response.etds.get(etd)).minutesToArrival*60*1000); makeLegCountdownTimer(curLeg.disembarkTime.getTime()); } else{ curTargetTime = curLeg.boardTime.getTime(); // for debug only // update the usherRoute boardTime by adding minutesToArrival to new Date() curLeg.boardTime = new Date(now.getTime() + ((etd)response.etds.get(etd)).minutesToArrival*60*1000); makeLegCountdownTimer(curLeg.boardTime.getTime()); } //Log.v("USHER SYNC",String.valueOf((etdTargetTime-curTargetTime)/1000)); // s diff b/t current and etd } private void setContentIntent(){ Intent i = new Intent(this, TheActivity.class); i.putExtra("Service", true); i.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_SINGLE_TOP); contentIntent = PendingIntent.getActivity(this, 0, i, PendingIntent.FLAG_CANCEL_CURRENT); } }