Android Open Source - BART Usher Service






From Project

Back to project page BART.

License

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.

Java Source Code

/*
 *  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);
    }
}




Java Source Code List

pro.dbro.bart.BART.java
pro.dbro.bart.BartLinearLayout.java
pro.dbro.bart.BartRouteParser.java
pro.dbro.bart.BartStationEtdParser.java
pro.dbro.bart.DeviceLocation.java
pro.dbro.bart.LocalPersistence.java
pro.dbro.bart.MapActivity.java
pro.dbro.bart.RequestTask.java
pro.dbro.bart.StationSuggestion.java
pro.dbro.bart.TextPlusIconArrayAdapter.java
pro.dbro.bart.TheActivity.java
pro.dbro.bart.UsherService.java
pro.dbro.bart.ViewCountDownTimer.java
pro.dbro.bart.etdResponse.java
pro.dbro.bart.etd.java
pro.dbro.bart.leg.java
pro.dbro.bart.routeResponse.java
pro.dbro.bart.route.java