Java tutorial
// Copyright (C) 2011 Cicada contributors // // 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.nextbuses; 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.Collections; import java.util.HashSet; import java.util.List; import java.util.Set; import org.cicadasong.cicadalib.CicadaApp; import org.json.JSONArray; import org.json.JSONException; import org.json.JSONObject; 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 displays the next several bus times at a given stop. At present, the * stop is hard-coded; a real app would offer a phone-side setup UI to pick the stop, and/or * possibly autodetect the nearest stop(s) based on the phone's current location. */ public class NextBuses extends CicadaApp { public static final String TAG = NextBuses.class.getSimpleName(); // Update every 2 minutes public static final int STATUS_UPDATE_INTERVAL_MSEC = 2 * 60 * 1000; private String stopName = "3rd St & Howard St"; private Runnable updateStatusTask; private Handler handler; private PredictionSet predictions = null; private boolean inInitialFetch = true; // Paint objects for different fonts private Paint metawatch11px; private Paint metawatch7px; private Paint metawatch5px; private Paint default10pt; @Override public void onCreate() { createFontPainters(); super.onCreate(); } private void createFontPainters() { Typeface fontMetawatch11px = Typeface.createFromAsset(getAssets(), "fonts/metawatch_16pt_11pxl_proto1.ttf"); metawatch11px = new Paint(); metawatch11px.setTypeface(fontMetawatch11px); metawatch11px.setTextSize(16); Typeface fontMetawatch7px = Typeface.createFromAsset(getAssets(), "fonts/metawatch_8pt_7pxl_CAPS_proto1.ttf"); metawatch7px = new Paint(); metawatch7px.setTypeface(fontMetawatch7px); metawatch7px.setTextSize(8); Typeface fontMetawatch5px = Typeface.createFromAsset(getAssets(), "fonts/metawatch_8pt_5pxl_CAPS_proto1.ttf"); metawatch5px = new Paint(); metawatch5px.setTypeface(fontMetawatch5px); metawatch5px.setTextSize(8); default10pt = new Paint(); default10pt.setTextSize(10); } @Override protected void onResume() { Log.v(TAG, "Next Buses activated in mode: " + getCurrentMode()); if (updateStatusTask == null) { updateStatusTask = new Runnable() { @Override public void run() { if (!isActive()) return; (new GetTimesTask()).execute(); } }; } if (handler == null) { handler = new Handler(); } handler.removeCallbacks(updateStatusTask); handler.post(updateStatusTask); } @Override protected void onPause() { handler.removeCallbacks(updateStatusTask); } @Override protected void onButtonPress(WatchButton button) { } protected void onDraw(Canvas canvas) { // Set font for heading Paint paint = isWidget() ? metawatch5px : metawatch7px; int x = 2; int y = 2 + (int) -paint.ascent(); canvas.drawText("Next buses at...", x, y, paint); y += (int) paint.descent() + 2; // Set font for stop name paint = isWidget() ? metawatch7px : metawatch11px; y += (int) -paint.ascent(); canvas.drawText(stopName, x, y, paint); y += (int) paint.descent() + 2; // Set font for "body" paint = isWidget() ? metawatch5px : default10pt; y += (int) -paint.ascent(); if (inInitialFetch) { canvas.drawText("Fetching...", x, y, paint); } else if (predictions == null) { canvas.drawText("Network Error", x, y, paint); } else if (isWidget()) { String singleLineResult = ""; List<Prediction> allPredictions = predictions.getAllPredictions(); StringBuilder resultBuilder = new StringBuilder(); for (Prediction prediction : allPredictions) { if (resultBuilder.length() > 0) { resultBuilder.append(" "); } resultBuilder.append(prediction.route); resultBuilder.append("~"); resultBuilder.append(prediction.minutes); resultBuilder.append("m"); } singleLineResult = resultBuilder.toString(); canvas.drawText(singleLineResult, x, y, paint); } else { // We're in app mode, so we have more screen space to work with for (String route : predictions.getRoutes()) { StringBuilder resultBuilder = new StringBuilder(); resultBuilder.append(route); resultBuilder.append(":"); for (Prediction prediction : predictions.getPredictionsForRoute(route)) { resultBuilder.append(" "); resultBuilder.append(prediction.minutes); resultBuilder.append("m"); } canvas.drawText(resultBuilder.toString(), x, y, paint); y += paint.getFontSpacing(); } } } private void processStatusUpdate(PredictionSet newPredictions) { if (!isActive()) { return; } inInitialFetch = false; predictions = newPredictions; invalidate(); handler.postDelayed(updateStatusTask, STATUS_UPDATE_INTERVAL_MSEC); } private class GetTimesTask extends AsyncTask<Void, Void, PredictionSet> { private static final String STOP_URL = "http://proximobus.appspot.com/agencies/sf-muni/stops/13128/predictions.json"; @Override protected void onPostExecute(PredictionSet result) { processStatusUpdate(result); } @Override protected PredictionSet doInBackground(Void... params) { HttpURLConnection connection = null; PredictionSet resultSet = null; try { URL url = new URL(STOP_URL); connection = (HttpURLConnection) url.openConnection(); if (connection.getResponseCode() == HttpURLConnection.HTTP_OK) { String response = convertStreamToString(connection.getInputStream()); try { resultSet = new PredictionSet(); JSONObject responseObj = new JSONObject(response); JSONArray predArray = responseObj.getJSONArray("items"); for (int i = 0; i < predArray.length(); i++) { JSONObject predObj = predArray.getJSONObject(i); resultSet.addPrediction(predObj.getString("route_id"), predObj.getInt("minutes")); } } 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 resultSet; } } 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(); } private static class Prediction { public String route; public int minutes; } private static class PredictionSet { List<Prediction> predictions = new ArrayList<Prediction>(); public void addPrediction(String route, int minutes) { Prediction prediction = new Prediction(); prediction.route = route; prediction.minutes = minutes; predictions.add(prediction); } public List<String> getRoutes() { Set<String> routeSet = new HashSet<String>(); for (Prediction prediction : predictions) { routeSet.add(prediction.route); } List<String> result = new ArrayList<String>(routeSet); Collections.sort(result); return result; } public List<Prediction> getAllPredictions() { return predictions; } public List<Prediction> getPredictionsForRoute(String route) { List<Prediction> result = new ArrayList<Prediction>(); for (Prediction prediction : predictions) { if (prediction.route.equals(route)) { result.add(prediction); } } return result; } } }