com.kentph.ttcnextbus.NetworkActivity.java Source code

Java tutorial

Introduction

Here is the source code for com.kentph.ttcnextbus.NetworkActivity.java

Source

/*
 * Copyright (C) 2012 The Android Open Source Project
 *
 * 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 com.kentph.ttcnextbus;

import android.app.Activity;
import android.app.Dialog;
import android.app.ProgressDialog;
import android.content.BroadcastReceiver;
import android.content.ContentValues;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.IntentSender;
import android.content.SharedPreferences;
import android.database.Cursor;
import android.database.DatabaseUtils;
import android.database.sqlite.SQLiteDatabase;
import android.location.Location;
import android.net.ConnectivityManager;
import android.net.NetworkInfo;
import android.os.AsyncTask;
import android.os.Bundle;
import android.preference.PreferenceManager;
import android.support.v4.app.DialogFragment;
import android.support.v4.app.NavUtils;
import android.util.Log;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.webkit.WebView;
import android.widget.Toast;

//import com.example.android.networkusage.StackOverflowXmlParser.Entry;
import com.google.android.gms.common.ConnectionResult;
import com.google.android.gms.common.GooglePlayServicesClient;
import com.google.android.gms.common.GooglePlayServicesUtil;
import com.google.android.gms.location.LocationClient;
import com.kentph.ttcnextbus.NextBusRouteListXmlParser.Route;
import com.kentph.ttcnextbus.NextBusRouteConfigXmlParser.RouteConfig;

import org.xmlpull.v1.XmlPullParserException;

import java.io.IOException;
import java.io.InputStream;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.Iterator;
import java.util.List;
import java.util.ListIterator;

import static android.database.DatabaseUtils.*;
import static com.kentph.ttcnextbus.NextBusPredictionsXmlParser.Prediction;
import static com.kentph.ttcnextbus.NextBusPredictionsXmlParser.RoutePredictions;
import static com.kentph.ttcnextbus.NextBusRouteConfigXmlParser.Path;
import static com.kentph.ttcnextbus.NextBusRouteConfigXmlParser.PathPoint;
import static com.kentph.ttcnextbus.RouteDbHelper.RouteDbContract.*;

/**
 * Main Activity for the sample application.
 *
 * This activity does the following:
 *
 * o Presents a WebView screen to users. This WebView has a list of HTML links to the latest
 *   questions tagged 'android' on stackoverflow.com.
 *
 * o Parses the StackOverflow XML feed using XMLPullParser.
 *
 * o Uses AsyncTask to download and process the XML feed.
 *
 * o Monitors preferences and the device's network connection to determine whether
 *   to refresh the WebView content.
 */
public class NetworkActivity extends Activity implements GooglePlayServicesClient.ConnectionCallbacks,
        GooglePlayServicesClient.OnConnectionFailedListener {

    public static final String WIFI = "Wi-Fi";
    public static final String ANY = "Any";
    //    private static String URL =
    //            "http://webservices.nextbus.com/service/publicXMLFeed?command=routeConfig&a=ttc&r=";
    //            "http://stackoverflow.com/feeds/tag?tagnames=android&sort=newest";

    // Whether there is a Wi-Fi connection.
    private static boolean wifiConnected = false;
    // Whether there is a mobile connection.
    private static boolean mobileConnected = false;
    // Whether the display should be refreshed.
    public static boolean refreshDisplay = true;

    // The user's current network preference setting.
    public static String sPref = null;

    // The BroadcastReceiver that tracks network connectivity changes.
    private NetworkReceiver receiver = new NetworkReceiver();

    // Global constants
    /*
     * Define a request code to send to Google Play services
     * This code is returned in Activity.onActivityResult
     */
    private final static int CONNECTION_FAILURE_RESOLUTION_REQUEST = 9000;

    /**
     * Show a dialog returned by Google Play services for the
     * connection error code
     *
     * @param errorCode An error code returned from onConnectionFailed
     */
    private void showErrorDialog(int errorCode) {

        // Get the error dialog from Google Play services
        Dialog errorDialog = GooglePlayServicesUtil.getErrorDialog(errorCode, this,
                LocationUtils.CONNECTION_FAILURE_RESOLUTION_REQUEST);

        // If Google Play services can provide an error dialog
        if (errorDialog != null) {

            // Create a new DialogFragment in which to show the error dialog
            ErrorDialogFragment errorFragment = new ErrorDialogFragment();

            // Set the dialog in the DialogFragment
            errorFragment.setDialog(errorDialog);

            // Show the error dialog in the DialogFragment
            //errorFragment.show(getFragmentManager(), LocationUtils.APPTAG);
        }
    }

    // Define a DialogFragment that displays the error dialog
    public static class ErrorDialogFragment extends DialogFragment {
        // Global field to contain the error dialog
        private Dialog mDialog;

        // Default constructor. Sets the dialog field to null
        public ErrorDialogFragment() {
            super();
            mDialog = null;
        }

        // Set the dialog to display
        public void setDialog(Dialog dialog) {
            mDialog = dialog;
        }

        // Return a Dialog to the DialogFragment.
        @Override
        public Dialog onCreateDialog(Bundle savedInstanceState) {
            return mDialog;
        }
    }

    private boolean servicesConnected() {
        // Check that Google Play services is available
        int resultCode = GooglePlayServicesUtil.isGooglePlayServicesAvailable(this);
        // If Google Play services is available
        if (ConnectionResult.SUCCESS == resultCode) {
            // In debug mode, log the status
            Log.d("Location Updates", "Google Play services is available.");
            // Continue
            return true;
            // Google Play services was not available for some reason
        } else {
            // Display an error dialog
            Dialog dialog = GooglePlayServicesUtil.getErrorDialog(resultCode, this, 0);
            if (dialog != null) {
                ErrorDialogFragment errorFragment = new ErrorDialogFragment();
                errorFragment.setDialog(dialog);
                //errorFragment.show(getSupportManager(), LocationUtils.APPTAG);
            }
            return false;
        }
    }

    /*
     * Called by Location Services when the request to connect the
     * client finishes successfully. At this point, you can
     * request the current location or start periodic updates
     */
    @Override
    public void onConnected(Bundle dataBundle) {
        // Display the connection status
        Toast.makeText(this, "Connected", Toast.LENGTH_SHORT).show();

        // Only loads the page if refreshDisplay is true. Otherwise, keeps previous
        // display. For example, if the user has set "Wi-Fi only" in prefs and the
        // device loses its Wi-Fi connection midway through the user using the app,
        // you don't want to refresh the display--this would force the display of
        // an error page instead of stackoverflow.com content.
        if (refreshDisplay) {
            loadPage();
        }
    }

    /*
     * Called by Location Services if the connection to the
     * location client drops because of an error.
     */
    @Override
    public void onDisconnected() {
        // Display the connection status
        Toast.makeText(this, "Disconnected. Please re-connect.", Toast.LENGTH_SHORT).show();
    }

    /*
     * Called by Location Services if the attempt to
     * Location Services fails.
     */
    @Override
    public void onConnectionFailed(ConnectionResult connectionResult) {
        /*
         * Google Play services can resolve some errors it detects.
         * If the error has a resolution, try sending an Intent to
         * start a Google Play services activity that can resolve
         * error.
         */
        if (connectionResult.hasResolution()) {
            try {
                // Start an Activity that tries to resolve the error
                connectionResult.startResolutionForResult(this, CONNECTION_FAILURE_RESOLUTION_REQUEST);
                /*
                 * Thrown if Google Play services canceled the original
                 * PendingIntent
                 */
            } catch (IntentSender.SendIntentException e) {
                // Log the error
                e.printStackTrace();
            }
        } else {
            /*
             * If no resolution is available, display a dialog to the
             * user with the error.
             */
            showErrorDialog(connectionResult.getErrorCode());
        }
    }

    // Stores the current instantiation of the location client in this object
    private LocationClient mLocationClient;

    // Global variable to hold the current location
    Location mCurrentLocation;

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        // Register BroadcastReceiver to track connection changes.
        IntentFilter filter = new IntentFilter(ConnectivityManager.CONNECTIVITY_ACTION);
        receiver = new NetworkReceiver();
        this.registerReceiver(receiver, filter);

        /*
         * Create a new location client, using the enclosing class to
         * handle callbacks.
         */
        mLocationClient = new LocationClient(this, this, this);
    }

    // Refreshes the display if the network connection and the
    // pref settings allow it.
    @Override
    public void onStart() {
        super.onStart();

        // Gets the user's network preference settings
        SharedPreferences sharedPrefs = PreferenceManager.getDefaultSharedPreferences(this);

        // Retrieves a string value for the preferences. The second parameter
        // is the default value to use if a preference value is not found.
        sPref = sharedPrefs.getString("listPref", "Wi-Fi");

        updateConnectedFlags();

        // Connect the client.
        mLocationClient.connect();

        //        // Only loads the page if refreshDisplay is true. Otherwise, keeps previous
        //        // display. For example, if the user has set "Wi-Fi only" in prefs and the
        //        // device loses its Wi-Fi connection midway through the user using the app,
        //        // you don't want to refresh the display--this would force the display of
        //        // an error page instead of stackoverflow.com content.
        //        if (refreshDisplay) {
        //            loadPage();
        //        }
    }

    /*
     * Called when the Activity is no longer visible.
     */
    @Override
    protected void onStop() {
        // Disconnecting the client invalidates it.
        mLocationClient.disconnect();
        super.onStop();
    }

    @Override
    public void onDestroy() {
        super.onDestroy();
        if (receiver != null) {
            this.unregisterReceiver(receiver);
        }
    }

    // Checks the network connection and sets the wifiConnected and mobileConnected
    // variables accordingly.
    private void updateConnectedFlags() {
        ConnectivityManager connMgr = (ConnectivityManager) getSystemService(Context.CONNECTIVITY_SERVICE);

        NetworkInfo activeInfo = connMgr.getActiveNetworkInfo();
        if (activeInfo != null && activeInfo.isConnected()) {
            wifiConnected = activeInfo.getType() == ConnectivityManager.TYPE_WIFI;
            mobileConnected = activeInfo.getType() == ConnectivityManager.TYPE_MOBILE;
        } else {
            wifiConnected = false;
            mobileConnected = false;
        }
    }

    // Uses AsyncTask subclass to download the XML feed from stackoverflow.com.
    // This avoids UI lock up. To prevent network operations from
    // causing a delay that results in a poor user experience, always perform
    // network operations on a separate thread from the UI.
    private void loadPage() {
        if (((sPref.equals(ANY)) && (wifiConnected || mobileConnected))
                || ((sPref.equals(WIFI)) && (wifiConnected))) {
            // AsyncTask subclass
            new DownloadXmlTask().execute();
        } else {
            showErrorPage();
        }
    }

    // Displays an error if the app is unable to load content.
    private void showErrorPage() {
        setContentView(R.layout.activity_network);

        // The specified network connection is not available. Displays error message.
        WebView myWebView = (WebView) findViewById(R.id.webview);
        myWebView.loadData(getResources().getString(R.string.connection_error), "text/html", null);
        //        TextView textView = (TextView) findViewById(R.id.text);
        //        textView.setText(getResources().getString(R.string.connection_error));
    }

    // Populates the activity's options menu.
    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        MenuInflater inflater = getMenuInflater();
        inflater.inflate(R.menu.mainmenu, menu);
        return true;
    }

    // Handles the user's menu selection.
    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        switch (item.getItemId()) {
        case R.id.settings:
            Intent settingsActivity = new Intent(getBaseContext(), SettingsActivity.class);
            startActivity(settingsActivity);
            return true;
        case R.id.refresh:
            loadPage();
            return true;
        case android.R.id.home:
            // This ID represents the Home or Up button. In the case of this
            // activity, the Up button is shown. Use NavUtils to allow users
            // to navigate up one level in the application structure. For
            // more details, see the Navigation pattern on Android Design:
            //
            // http://developer.android.com/design/patterns/navigation.html#up-vs-back
            //
            NavUtils.navigateUpFromSameTask(this);
            return true;
        default:
            return super.onOptionsItemSelected(item);
        }
    }

    // Location subclass that stores stopId
    public static class StopLocation extends Location {
        public final String stopId;
        public float distance;

        public StopLocation(String stopId, double lat, double lon) {
            super("");
            this.stopId = stopId;
            this.setLatitude(lat);
            this.setLongitude(lon);
            this.distance = -1;
        }
    }

    // Implementation of AsyncTask used to download XML feed from stackoverflow.com.
    private class DownloadXmlTask extends AsyncTask<Void, String, String> {
        //        private ProgressDialog pd;
        @Override
        protected void onPreExecute() {
            super.onPreExecute();
            //            pd = new ProgressDialog(getApplicationContext());
            //            pd.setMessage("Starting...");
            //            pd.show();
        }

        @Override
        protected String doInBackground(Void... params) {
            RouteDbAssetHelper mDbHelper = new RouteDbAssetHelper(getApplicationContext());
            // Gets the data repository in write mode
            SQLiteDatabase db = mDbHelper.getWritableDatabase();

            // get current location in lat/lon
            if (servicesConnected())
                mCurrentLocation = mLocationClient.getLastLocation();
            int gridLat = (int) (mCurrentLocation.getLatitude() / 0.004);
            int gridLon = (int) (mCurrentLocation.getLongitude() / 0.004);

            // get all stops withing a 3x3 grid of current locations grid
            String[] whereArgs = new String[] { "" + (gridLat - 1), "" + (gridLat + 1), "" + (gridLon + 1),
                    "" + (gridLon - 1) };

            // construct sql select query
            Cursor cursor = db.query("stops", new String[] { "stopid", "lat", "lon" },
                    "(gridlat BETWEEN ? AND ?) " + "AND (gridlon BETWEEN ? AND ?)", whereArgs, null, null, null,
                    null);

            int rows = cursor.getCount() / 2; //TODO fix why doubled cursor
            int count = 0;

            // get column indices
            int stopIdColumn = cursor.getColumnIndex("stopid");
            int latColumn = cursor.getColumnIndex("lat");
            int lonColumn = cursor.getColumnIndex("lon");

            List<StopLocation> stopLocations = new ArrayList<StopLocation>();

            // fill stopLocations list with each stop obtained in sql query
            if (cursor.moveToFirst())
                while (count < rows) {
                    StopLocation stopLocation = new StopLocation(cursor.getString(stopIdColumn),
                            cursor.getDouble(latColumn), cursor.getDouble(lonColumn));
                    // calculate distance of stop from current loc and save
                    stopLocation.distance = mCurrentLocation.distanceTo(stopLocation);
                    stopLocations.add(stopLocation);
                    cursor.moveToNext(); // brute force chop list in half
                    count++;
                }

            // sort stops by distance
            Collections.sort(stopLocations, new Comparator<StopLocation>() {
                public int compare(StopLocation a, StopLocation b) {
                    float diff = a.distance - b.distance;
                    if (diff > 0)
                        return 1;
                    else if (diff < 0)
                        return -1;
                    else
                        return 0;
                }
            });

            try {
                InputStream stream = null;
                NextBusPredictionsXmlParser nextBusPredictionsXmlParser = new NextBusPredictionsXmlParser();

                //        String routeNumber = null; // Split routeTitle into Number and Name
                //        String routeName = null;
                //        String stopTitle = null;  // Otherwise match output attrs exactly
                //        // attrs from direction tag
                //        String direction = null;
                //        String terminal = null;
                //
                List<List<RoutePredictions>> listOfRoutePredictions = new ArrayList<List<RoutePredictions>>();

                StringBuilder htmlString = new StringBuilder();

                // get predictions from nextbus
                try {
                    for (StopLocation stopLocation : stopLocations.subList(0, 5)) { // TODO make user settable
                        stream = downloadUrl(
                                "http://webservices.nextbus.com/service/publicXMLFeed?command=predictions&a=ttc&stopId="
                                        + stopLocation.stopId);
                        listOfRoutePredictions.add(nextBusPredictionsXmlParser.parse(stream));
                    }

                    // Makes sure that the InputStream is closed after the app is
                    // finished using it.
                } finally {
                    if (stream != null) {
                        stream.close();
                    }
                }

                // print predictions
                count = 0;
                for (List<RoutePredictions> routePredictions : listOfRoutePredictions) {
                    htmlString.append("<h1>Stop ");
                    htmlString.append(stopLocations.get(count).stopId);
                    htmlString.append("</h1>");
                    count++;
                    for (RoutePredictions routePrediction : routePredictions) {
                        htmlString.append("<h4>Route number: ");
                        htmlString.append(routePrediction.routeNumber);
                        //                        htmlString.append("</h1>");
                        htmlString.append(" | Route name: ");
                        htmlString.append(routePrediction.routeName);
                        //                        htmlString.append("</h4>");
                        htmlString.append(" | Stop title: ");
                        htmlString.append(routePrediction.stopTitle);
                        //                        htmlString.append("</h4>");
                        htmlString.append(" | Direction: ");
                        htmlString.append(routePrediction.direction);
                        //                        htmlString.append("</h4>");
                        htmlString.append(" | Terminal: ");
                        htmlString.append(routePrediction.terminal);
                        htmlString.append("</h4>");

                        for (Prediction prediction : routePrediction.listOfPredictions) {
                            //                            htmlString.append("<p>seconds: ");
                            //                            htmlString.append(prediction.seconds);
                            //                            htmlString.append("</p>");
                            htmlString.append("<p>minutes: ");
                            htmlString.append(prediction.minutes);
                            //                            htmlString.append("</p>");
                            //                            htmlString.append("<p>Departure: ");
                            //                            htmlString.append(prediction.isDeparture);
                            //                            htmlString.append("</p>");
                            //                            htmlString.append("<p>Layover: ");
                            //                            htmlString.append(prediction.affectedByLayover);
                            //                            htmlString.append("</p>");
                            //                            htmlString.append("<p>Branch: ");
                            //                            htmlString.append(prediction.branch);
                            //                            htmlString.append("</p>");
                            //                            htmlString.append("<p>Direction tag: ");
                            //                            htmlString.append(prediction.dirTag);
                            //                            htmlString.append("</p>");
                            //                            htmlString.append("<p>Vehicle: ");
                            //                            htmlString.append(prediction.vehicle);
                            //                            htmlString.append("</p>");
                            //                            htmlString.append("<p>Block: ");
                            //                            htmlString.append(prediction.block);
                            //                            htmlString.append("</p>");
                            //                            htmlString.append("<p>Trip tag: ");
                            //                            htmlString.append(prediction.tripTag);
                            //                            htmlString.append("</p>");
                        }
                    }
                }

                htmlString.append("<p>Done</p>");
                return htmlString.toString();
            } catch (IOException e) {
                return getResources().getString(R.string.connection_error);
            } catch (XmlPullParserException e) {
                return getResources().getString(R.string.xml_error);
            }
        }

        @Override
        protected void onProgressUpdate(String... values) {
            //            pd.setMessage("Getting route " + values[0]);
            //            pd.show();
        }

        @Override
        protected void onPostExecute(String result) {
            setContentView(R.layout.activity_network);
            // Displays the HTML string in the UI via a WebView
            WebView myWebView = (WebView) findViewById(R.id.webview);
            myWebView.loadData(result, "text/html", null);
            //            pd.setMessage("Done!");
            //            pd.show();
        }
    }

    // Given a string representation of a URL, sets up a connection and gets
    // an input stream.
    private InputStream downloadUrl(String urlString) throws IOException {
        URL url = new URL(urlString);
        HttpURLConnection conn = (HttpURLConnection) url.openConnection();
        conn.setReadTimeout(10000 /* milliseconds */);
        conn.setConnectTimeout(15000 /* milliseconds */);
        conn.setRequestMethod("GET");
        conn.setDoInput(true);
        // Starts the query
        conn.connect();
        InputStream stream = conn.getInputStream();
        return stream;
    }

    /**
     *
     * This BroadcastReceiver intercepts the android.net.ConnectivityManager.CONNECTIVITY_ACTION,
     * which indicates a connection change. It checks whether the type is TYPE_WIFI.
     * If it is, it checks whether Wi-Fi is connected and sets the wifiConnected flag in the
     * main activity accordingly.
     *
     */
    public class NetworkReceiver extends BroadcastReceiver {

        @Override
        public void onReceive(Context context, Intent intent) {
            ConnectivityManager connMgr = (ConnectivityManager) context
                    .getSystemService(Context.CONNECTIVITY_SERVICE);
            NetworkInfo networkInfo = connMgr.getActiveNetworkInfo();

            // Checks the user prefs and the network connection. Based on the result, decides
            // whether
            // to refresh the display or keep the current display.
            // If the userpref is Wi-Fi only, checks to see if the device has a Wi-Fi connection.
            if (WIFI.equals(sPref) && networkInfo != null
                    && networkInfo.getType() == ConnectivityManager.TYPE_WIFI) {
                // If device has its Wi-Fi connection, sets refreshDisplay
                // to true. This causes the display to be refreshed when the user
                // returns to the app.
                refreshDisplay = true;
                Toast.makeText(context, R.string.wifi_connected, Toast.LENGTH_SHORT).show();

                // If the setting is ANY network and there is a network connection
                // (which by process of elimination would be mobile), sets refreshDisplay to true.
            } else if (ANY.equals(sPref) && networkInfo != null) {
                refreshDisplay = true;

                // Otherwise, the app can't download content--either because there is no network
                // connection (mobile or Wi-Fi), or because the pref setting is WIFI, and there
                // is no Wi-Fi connection.
                // Sets refreshDisplay to false.
            } else {
                refreshDisplay = false;
                Toast.makeText(context, R.string.lost_connection, Toast.LENGTH_SHORT).show();
            }
        }
    }
}