Android Open Source - cicada Tube Status






From Project

Back to project page cicada.

License

The source code is released under:

Apache License

If you think the Android project cicada 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) 2011 Cicada contributors
////w w w .  ja v a 2 s  .  c  om
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package org.cicadasong.samples.tubestatus;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.cicadasong.cicadalib.CicadaApp;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;

import android.text.TextUtils;

import android.content.SharedPreferences;
import android.content.SharedPreferences.OnSharedPreferenceChangeListener;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.Typeface;
import android.os.AsyncTask;
import android.os.Handler;
import android.util.Log;

/**
 * An example app that fetches the current status of a particular London Underground line.
 * If this was a real app, it would also provide a phone-side setup screen to pick the train
 * lines to show.
 * 
 * It uses the tubeupdates.com site to get the information which currently scrapes information from TFL.
 * Api documentation is here: http://tubeupdates.com/documentation/
 */
public class TubeStatus extends CicadaApp implements OnSharedPreferenceChangeListener {
  public static final String TAG = TubeStatus.class.getSimpleName();

  protected enum TubeLine {
    ALL("All lines", "all"),
    TUBE("Tube only", "tube"), // excludes DLR and Overground
    OVERGROUND("Overground", "overground"),
    DLR("DLR", "docklands"),

    BAKERLOO("Bakerloo", "bakerloo"),
    CENTRAL("Central", "central"),
    CIRCLE("Circle", "circle"),
    DISTRICT("District", "district"),
    HAMMERSMITH_AND_CITY("Hammersmith & City", "hammersmithcity"),
    JUBILEE("Jubilee", "jubilee"),
    METROPOLITAN("Metropolitan", "metropolitan"),
    NORTHERN("Northern", "northern"),
    PICCADILLY("Piccadilly", "piccadilly"),
    VICTORIA("Victoria", "victoria"),
    WATERLOO_AND_CITY("Waterloo & City", "waterloocity");

    public String name;
    public String lineIdentifier;

    TubeLine(String name, String lineIdentifier) {
      this.name = name;
      this.lineIdentifier = lineIdentifier;
    }

    protected static List<TubeLine> allLines = new ArrayList<TubeLine>(Arrays.asList(
      // wildcards
      TubeLine.ALL,
      TubeLine.TUBE,
      
      // sprawling transport systems with seperate lines
      TubeLine.OVERGROUND,
      TubeLine.DLR,
      
      // alphabetically ordered list of tube lines
      TubeLine.BAKERLOO,
      TubeLine.CENTRAL,
      TubeLine.CIRCLE,
      TubeLine.DISTRICT,
      TubeLine.HAMMERSMITH_AND_CITY,
      TubeLine.JUBILEE,
      TubeLine.METROPOLITAN,
      TubeLine.NORTHERN,
      TubeLine.PICCADILLY,
      TubeLine.VICTORIA,
      TubeLine.WATERLOO_AND_CITY
    ));
    
    public static int indexOf(String lineIdentifier) {
      for (int index = 0; index < allLines.size(); index++) {
        if (allLines.get(index).lineIdentifier.compareToIgnoreCase(lineIdentifier) == 0) {
          return index;
        }
      }
      return -1;
    }
  }
  
  
  private int selectionIndex;
  private String status;
  private Runnable updateStatusTask;
  private Handler handler;
  private int updateIntervalMsec;
  
  private static final String STATUS_GOOD = "good service";
  private static final String STATUS_MINOR_DELAYS = "minor delays";
  private static final String STATUS_SEVERE_DELAYS = "severe delays";
  private static final String STATUS_PART_CLOSURE = "part closure";
  private static final String STATUS_PART_SUSPENDED = "part suspended";
  private static final String STATUS_SUSPENDED = "suspended";
  private static final String STATUS_OTHER = "other"; // not returned via API, used only as map key
    
  private static final Map <String, String> messageToCodeMap = createMessageToCodeMap();
  private static Map<String, String> createMessageToCodeMap() {
    Map<String, String> result = new HashMap<String, String>();
    result.put(STATUS_GOOD, "OK");
    result.put(STATUS_MINOR_DELAYS, "MD");
    result.put(STATUS_SEVERE_DELAYS, "SD");
    result.put(STATUS_PART_CLOSURE, "PC");
    result.put(STATUS_PART_SUSPENDED, "PS");
    result.put(STATUS_SUSPENDED, "S");
    return Collections.unmodifiableMap(result);
  }
  
  @Override
  public void onCreate() {
    Preferences.getSharedPreferences(this).registerOnSharedPreferenceChangeListener(this);
    readPreferences();
    refreshStatus();
  }
  
  private void readPreferences() {
    Log.v(TAG, "Reading preferences");
    updateSelection(TubeLine.indexOf(Preferences.getPreferredLine(this)));
    updateIntervalMsec = Preferences.getUpdateFrequency(this) * 60 * 1000;
  }
  
  private void updateSelection(int index) {
    if (index < 0) {
      index = TubeLine.allLines.size() - 1;
    }
    if (index >= TubeLine.allLines.size()) {
      index = 0;
    }
    selectionIndex = index;
  }

  @Override
  protected void onResume() {
    Log.v(TAG, "Tube Status activated in mode: " + getCurrentMode());

    if (updateStatusTask == null) {
      updateStatusTask = new Runnable() {

        @Override
        public void run() {
          if (!isActive()) return;

          (new GetStatusTask()).execute();
        }
      };
    }
    if (handler == null) {
      handler = new Handler();
    }
    handler.removeCallbacks(updateStatusTask);
    handler.post(updateStatusTask);
  }

  @Override
  protected void onPause() {
    Preferences.getSharedPreferences(this).unregisterOnSharedPreferenceChangeListener(this);
    handler.removeCallbacks(updateStatusTask);
  }

  @Override
  protected void onButtonPress(WatchButton button) {
    switch (button) {
      case TOP_RIGHT:
        updateSelection(selectionIndex - 1);
        refreshStatus();
        break;
      case MIDDLE_RIGHT:
        handler.post(updateStatusTask);
        refreshStatus();
        break;
      case BOTTOM_RIGHT:
        updateSelection(selectionIndex + 1);
        refreshStatus();
        break;
    }
  }

  protected void refreshStatus() {
    status = "Fetching...";
    if (!isActive()) {
      return;
    }
    if (handler != null) {
      handler.removeCallbacks(updateStatusTask); // ensure that any fetch that is already in process doesn't deliver updates to the UI after this new fetch
      handler.post(updateStatusTask);
    }
    invalidate();
  }
  
  protected void onDraw(Canvas canvas) {
    Paint paint = new Paint();
    paint.setTextAlign(Paint.Align.CENTER);
    paint.setTypeface(Typeface.DEFAULT_BOLD);
    
    // We've centered the output vertically, so it works with the reduced canvas height in
    // widget mode.
    int y = canvas.getHeight() / 2;
    int x = canvas.getWidth() / 2;

    paint.setTypeface(Typeface.DEFAULT);
    paint.setTextSize(11);
    canvas.drawText(TubeLine.allLines.get(selectionIndex).name, x, y - paint.descent() - 1, paint);

    paint.setTextSize(11); // TODO dynamically adjust font size depending on length of status string?
    canvas.drawText(status, x, y + (int)-paint.ascent() + 1, paint);
  }
  
  private void processStatusUpdate(String newStatus) {
    if (!isActive()) {
      return;
    }

    status = newStatus;

    invalidate();

    handler.postDelayed(updateStatusTask, updateIntervalMsec);
  }

  private class GetStatusTask extends AsyncTask<Void, Void, String> {
    private static final String TUBE_STATUS_URL =
        "http://api.tubeupdates.com/?method=get.status&lines={lineIdentifier}&return=status";
    protected void onPostExecute(String result) {
      processStatusUpdate(result);
    }
            
    protected String determineOverallStatus(JSONObject responseObj) throws JSONException {
      
      Map<String, Integer> counterMap = new HashMap<String, Integer>();
      counterMap.put(STATUS_GOOD, 0);
      counterMap.put(STATUS_MINOR_DELAYS, 0);
      counterMap.put(STATUS_SEVERE_DELAYS, 0);
      counterMap.put(STATUS_PART_CLOSURE, 0);
      counterMap.put(STATUS_PART_SUSPENDED, 0);
      counterMap.put(STATUS_SUSPENDED, 0);
      counterMap.put(STATUS_OTHER, 0);
      
      JSONArray lines = responseObj.getJSONObject("response").getJSONArray("lines");
      for (int index = 0; index < lines.length() ; index++) {
        JSONObject line = lines.getJSONObject(index);
        Log.d(TAG, "Line: " + line.toString());
        String status = line.getString("status");
        
        String[] statusMessages = status.split(",");
        for (String statusMessage : statusMessages) {
          if (messageToCodeMap.containsKey(statusMessage)) {
            counterMap.put(statusMessage, counterMap.get(statusMessage) + 1);
          } else {
            counterMap.put(STATUS_OTHER, counterMap.get(STATUS_OTHER) + 1);
          }
        }
      }
      
      ArrayList<String> codes = new ArrayList<String>();
      
      for (Map.Entry<String, Integer> entry : counterMap.entrySet()) {
        if (entry.getValue() == 0) {
          continue;
        }
        String codeAndCount = String.format("%s:%d", messageToCodeMap.get(entry.getKey()), entry.getValue());
        codes.add(codeAndCount);
      }
      return TextUtils.join("/", codes);
    }
    
    protected String determineSingleLineStatus(JSONObject responseObj) throws JSONException {
      String status = responseObj.getJSONObject("response").getJSONArray("lines")
          .getJSONObject(0).getString("status");
      
      String[] statusMessages = status.split(",");
      ArrayList<String> codes = new ArrayList<String>();
      for (String statusMessage : statusMessages) {
        if (messageToCodeMap.containsKey(statusMessage)) {
          codes.add(messageToCodeMap.get(statusMessage));
        } else {
          return "Unknown";
        }
      }
      return TextUtils.join("/", codes);
    }
    
    @Override
    protected String doInBackground(Void... params) {
      String result = "Network Error";
      HttpURLConnection connection = null;
      try {
        URL url = new URL(TUBE_STATUS_URL.replace("{lineIdentifier}", TubeLine.allLines.get(selectionIndex).lineIdentifier));
        connection = (HttpURLConnection) url.openConnection();
        if (connection.getResponseCode() == HttpURLConnection.HTTP_OK) {
          String response = convertStreamToString(connection.getInputStream());
          try {
            // Expected response format is something like:
            // {
            //   "response": {
            //     "lines": [{
            //       "name": "Victoria",
            //       "id": "victoria",
            //       "status": "good service",
            //       "messages": [],
            //       "status_starts": "Wed, 23 Jun 2010 10:09:04 +0100",
            //       "status_ends": "",
            //       "status_requested": "Wed, 23 Jun 2010 17:27:17 +0100"
            //     }]
            // }
            JSONObject responseObj = new JSONObject(response);
            
            int lineCount = responseObj.getJSONObject("response").getJSONArray("lines").length();
            if (lineCount > 1) {
              result = determineOverallStatus(responseObj);
            } else {
              result = determineSingleLineStatus(responseObj);
            }
          } catch (JSONException e) {
            Log.e(TAG, "Error decoding response: " + response);
          }
        }
      } catch (MalformedURLException e) {
        Log.e(TAG, "Malformed request URL: " + e);
      } catch (IOException e) {
        Log.e(TAG, "Connection error");
      } finally {
        if (connection != null) {
          connection.disconnect();
        }
      }

      return result;
    }
  }

  private static String convertStreamToString(InputStream is) {
    /*
     * To convert the InputStream to String we use the BufferedReader.readLine()
     * method. We iterate until the BufferedReader return null which means
     * there's no more data to read. Each line will appended to a StringBuilder
     * and returned as String.
     */
    BufferedReader reader = new BufferedReader(new InputStreamReader(is), 8192);
    StringBuilder sb = new StringBuilder();

    String line = null;
    try {
      while ((line = reader.readLine()) != null) {
        sb.append(line + "\n");
      }
    } catch (IOException e) {
      e.printStackTrace();
    } finally {
      try {
        is.close();
      } catch (IOException e) {
        e.printStackTrace();
      }
    }

    return sb.toString();
  }

  @Override
  public void onSharedPreferenceChanged(SharedPreferences sharedPreferences,
      String key) {
    readPreferences();
    handler.removeCallbacks(updateStatusTask);
    handler.post(updateStatusTask);
  }
}




Java Source Code List

com.hydraproductions.cicada.StopWatch.java
org.cicadasong.apollo.ApolloConfig.java
org.cicadasong.apollo.ApolloIntents.java
org.cicadasong.apollo.BitmapUtil.java
org.cicadasong.apollo.SimulatedDisplayView.java
org.cicadasong.cicada.AppDatabase.java
org.cicadasong.cicada.AppDescription.java
org.cicadasong.cicada.AppList.java
org.cicadasong.cicada.AppScanner.java
org.cicadasong.cicada.CicadaService.java
org.cicadasong.cicada.CicadaSettingsActivity.java
org.cicadasong.cicada.Cicada.java
org.cicadasong.cicada.DeviceService.java
org.cicadasong.cicada.HotkeySetupActivity.java
org.cicadasong.cicada.HotkeySetupEntry.java
org.cicadasong.cicada.MetaWatchConnection.java
org.cicadasong.cicada.NotificationRenderer.java
org.cicadasong.cicada.PackageMonitor.java
org.cicadasong.cicada.PackageUtil.java
org.cicadasong.cicada.PrefUtil.java
org.cicadasong.cicada.WidgetScreen.java
org.cicadasong.cicada.WidgetSetup.java
org.cicadasong.cicadalib.CicadaApp.java
org.cicadasong.cicadalib.CicadaIntents.java
org.cicadasong.cicadalib.CicadaNotificationManager.java
org.cicadasong.cicadalib.CicadaNotification.java
org.cicadasong.cicadalib.TextBlock.java
org.cicadasong.samples.bitly.Bitly.java
org.cicadasong.samples.digitalclock.DigitalClock.java
org.cicadasong.samples.hellocicada.HelloCicada.java
org.cicadasong.samples.nextbuses.NextBuses.java
org.cicadasong.samples.notificationdemo.NotificationDemo.java
org.cicadasong.samples.notifications.NotificationAccessibilityService.java
org.cicadasong.samples.notifications.Notifications.java
org.cicadasong.samples.quicktext.QuickTextSetup.java
org.cicadasong.samples.quicktext.QuickText.java
org.cicadasong.samples.smsnotifier.SMSBroadcastReceiver.java
org.cicadasong.samples.tubestatus.Preferences.java
org.cicadasong.samples.tubestatus.TubeStatusSettingsActivity.java
org.cicadasong.samples.tubestatus.TubeStatus.java
org.cicadasong.samples.webimageplayer.Preferences.java
org.cicadasong.samples.webimageplayer.WebImagePlayerSettingsActivity.java
org.cicadasong.samples.webimageplayer.WebImagePlayer.java