edu.auburn.ppl.cyclecolumbus.TripUploader.java Source code

Java tutorial

Introduction

Here is the source code for edu.auburn.ppl.cyclecolumbus.TripUploader.java

Source

/**
 *   @author Ken Streit, Auburn University
 *   @author Christopher Le Dantec <ledantec@gatech.edu>
 *   @author Anhong Guo <guoanhong15@gmail.com>
 *
 *   Updated/Modified for Columbus, GA. Added more features.
 *   Based on the CycleTracks codebase for SFCTA.
 *
 *   CycleTracks, Copyright 2009,2010 San Francisco County Transportation Authority
 *                                    San Francisco, CA, USA
 *
 *     @author Billy Charlton <billy.charlton@sfcta.org>
 *
 *   This file is part of CycleTracks.
 *
 *   CycleTracks 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.
 *
 *   CycleTracks 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 CycleTracks.  If not, see <http://www.gnu.org/licenses/>.
 */

package edu.auburn.ppl.cyclecolumbus;

import android.content.Context;
import android.content.SharedPreferences;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.database.Cursor;
import android.os.AsyncTask;
import android.os.Build;
import android.provider.Settings.System;
import android.util.Log;
import android.widget.ListView;
import android.widget.Toast;

import org.apache.http.HttpResponse;
import org.apache.http.NameValuePair;
import org.apache.http.client.HttpClient;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.entity.ByteArrayEntity;
import org.apache.http.impl.client.DefaultHttpClient;
import org.apache.http.message.BasicNameValuePair;
import org.json.JSONException;
import org.json.JSONObject;

import java.io.BufferedReader;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.text.DecimalFormat;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Vector;
import java.util.zip.GZIPOutputStream;

public class TripUploader extends AsyncTask<Long, Integer, Boolean> {
    Context mCtx;
    DbAdapter mDb;

    public static final int kSaveProtocolVersion = 3;

    // Saving protocol version 3
    public static final String TRIP_COORDS_TIME = "r";
    public static final String TRIP_COORDS_LAT = "l";
    public static final String TRIP_COORDS_LON = "n";
    public static final String TRIP_COORDS_ALT = "a";
    public static final String TRIP_COORDS_SPEED = "s";
    public static final String TRIP_COORDS_HACCURACY = "h";
    public static final String TRIP_COORDS_VACCURACY = "v";

    public static final String USER_AGE = "age";
    public static final String USER_EMAIL = "email";
    public static final String USER_GENDER = "gender";
    public static final String USER_ZIP_HOME = "homeZIP";
    public static final String USER_ZIP_WORK = "workZIP";
    public static final String USER_ZIP_SCHOOL = "schoolZIP";
    public static final String USER_CYCLING_FREQUENCY = "cyclingFreq";
    public static final String APP_VERSION = "app_version";
    public static final String USER_AGREE = "agree";

    public static final String USER_ETHNICITY = "ethnicity";
    public static final String USER_INCOME = "income";
    public static final String USER_RIDERTYPE = "rider_type";
    public static final String USER_RIDERHISTORY = "rider_history";

    private String kcal = "";
    private String co2 = "";
    private String distance = "";
    private String avgCost = "";
    private String agreement = "Disagree";

    public TripUploader(Context ctx) {
        super();
        this.mCtx = ctx;
        this.mDb = new DbAdapter(this.mCtx);
    }

    /******************************************************************************************
     * Gets each coordinate that was set during a trip and the info that goes along with it.
     ******************************************************************************************
     * @param tripId Unique trip ID
     * @return JSONObject representing all of the points set during trip
     * @throws JSONException
     ******************************************************************************************/
    private JSONObject getCoordsJSON(long tripId) throws JSONException {
        SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");

        mDb.openReadOnly();
        Cursor tripCoordsCursor = mDb.fetchAllCoordsForTrip(tripId);

        // Build the map between JSON fieldname and phone db fieldname:
        Map<String, Integer> fieldMap = new HashMap<String, Integer>();
        fieldMap.put(TRIP_COORDS_TIME, tripCoordsCursor.getColumnIndex(DbAdapter.K_POINT_TIME));
        fieldMap.put(TRIP_COORDS_LAT, tripCoordsCursor.getColumnIndex(DbAdapter.K_POINT_LAT));
        fieldMap.put(TRIP_COORDS_LON, tripCoordsCursor.getColumnIndex(DbAdapter.K_POINT_LGT));
        fieldMap.put(TRIP_COORDS_ALT, tripCoordsCursor.getColumnIndex(DbAdapter.K_POINT_ALT));
        fieldMap.put(TRIP_COORDS_SPEED, tripCoordsCursor.getColumnIndex(DbAdapter.K_POINT_SPEED));
        fieldMap.put(TRIP_COORDS_HACCURACY, tripCoordsCursor.getColumnIndex(DbAdapter.K_POINT_ACC));
        fieldMap.put(TRIP_COORDS_VACCURACY, tripCoordsCursor.getColumnIndex(DbAdapter.K_POINT_ACC));

        // Build JSON objects for each coordinate:
        JSONObject tripCoords = new JSONObject();
        while (!tripCoordsCursor.isAfterLast()) {
            JSONObject coord = new JSONObject();

            coord.put(TRIP_COORDS_TIME, df.format(tripCoordsCursor.getDouble(fieldMap.get(TRIP_COORDS_TIME))));
            coord.put(TRIP_COORDS_LAT, tripCoordsCursor.getDouble(fieldMap.get(TRIP_COORDS_LAT)) / 1E6);
            coord.put(TRIP_COORDS_LON, tripCoordsCursor.getDouble(fieldMap.get(TRIP_COORDS_LON)) / 1E6);
            coord.put(TRIP_COORDS_ALT, tripCoordsCursor.getDouble(fieldMap.get(TRIP_COORDS_ALT)));
            coord.put(TRIP_COORDS_SPEED, tripCoordsCursor.getDouble(fieldMap.get(TRIP_COORDS_SPEED)));
            coord.put(TRIP_COORDS_HACCURACY, tripCoordsCursor.getDouble(fieldMap.get(TRIP_COORDS_HACCURACY)));
            coord.put(TRIP_COORDS_VACCURACY, tripCoordsCursor.getDouble(fieldMap.get(TRIP_COORDS_VACCURACY)));

            tripCoords.put(coord.getString("r"), coord);
            tripCoordsCursor.moveToNext();
        }
        tripCoordsCursor.close();
        mDb.close();
        return tripCoords;
    }

    /******************************************************************************************
     * Gets all of the information the user filled out on the settings tab
     ******************************************************************************************
     * @return Returns the JSONObject form of the info the user filled out
     * @throws JSONException
     ******************************************************************************************/
    private JSONObject getUserJSON() throws JSONException {
        JSONObject user = new JSONObject();
        Map<String, Integer> fieldMap = new HashMap<String, Integer>();

        fieldMap.put(USER_EMAIL, Integer.valueOf(FragmentUserInfo.PREF_EMAIL));

        fieldMap.put(USER_ZIP_HOME, Integer.valueOf(FragmentUserInfo.PREF_ZIPHOME));
        fieldMap.put(USER_ZIP_WORK, Integer.valueOf(FragmentUserInfo.PREF_ZIPWORK));
        fieldMap.put(USER_ZIP_SCHOOL, Integer.valueOf(FragmentUserInfo.PREF_ZIPSCHOOL));

        SharedPreferences settings = this.mCtx.getSharedPreferences("PREFS", 0);
        for (Entry<String, Integer> entry : fieldMap.entrySet()) {
            user.put(entry.getKey(), settings.getString(entry.getValue().toString(), null));
        }
        user.put(USER_AGE, settings.getInt("" + FragmentUserInfo.PREF_AGE, 0));
        user.put(USER_GENDER, settings.getInt("" + FragmentUserInfo.PREF_GENDER, 0));
        user.put(USER_CYCLING_FREQUENCY, settings.getInt("" + FragmentUserInfo.PREF_GENDER, 0) / 100);
        // Integer.parseInt(settings.getString(""+UserInfoActivity.PREF_CYCLEFREQ,
        // "0"))
        user.put(USER_ETHNICITY, settings.getInt("" + FragmentUserInfo.PREF_ETHNICITY, 0));
        user.put(USER_INCOME, settings.getInt("" + FragmentUserInfo.PREF_INCOME, 0));
        user.put(USER_RIDERTYPE, settings.getInt("" + FragmentUserInfo.PREF_RIDERTYPE, 0));
        user.put(USER_RIDERHISTORY, settings.getInt("" + FragmentUserInfo.PREF_RIDERHISTORY, 0));

        user.put(APP_VERSION, getAppVersion());
        user.put(USER_AGREE, getUserAgreement());

        return user;
    }

    /******************************************************************************************
     * Puts the data from a trip in database into vector
     ******************************************************************************************
     * @param tripId Unique ID of each trip
     * @return Vector of the trip
     ******************************************************************************************/
    private Vector<String> getTripData(long tripId) {
        Vector<String> tripData = new Vector<String>();
        mDb.openReadOnly();
        Cursor tripCursor = mDb.fetchTrip(tripId);

        setKcalCo2Savings(tripCursor);

        String note = tripCursor.getString(tripCursor.getColumnIndex(DbAdapter.K_TRIP_NOTE));
        String purpose = tripCursor.getString(tripCursor.getColumnIndex(DbAdapter.K_TRIP_PURP));
        Double startTime = tripCursor.getDouble(tripCursor.getColumnIndex(DbAdapter.K_TRIP_START));
        Double endTime = tripCursor.getDouble(tripCursor.getColumnIndex(DbAdapter.K_TRIP_END));
        tripCursor.close();
        mDb.close();

        SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        tripData.add(note);
        tripData.add(purpose);
        tripData.add(df.format(startTime));
        tripData.add(df.format(endTime));

        return tripData;
    }

    /******************************************************************************************
     * Gets the unique app device ID
     * Since server looks for length 32 (to match iOS), add androidDeviceId to it
     ******************************************************************************************
     * @return String form of device id
     ******************************************************************************************/
    public String getDeviceId() {
        String androidId = System.getString(this.mCtx.getContentResolver(), System.ANDROID_ID);
        String androidBase = "androidDeviceId-";

        if (androidId == null) { // This happens when running in the Emulator
            final String emulatorId = "android-RunningAsTestingDeleteMe";
            return emulatorId;
        }
        String deviceId = androidBase.concat(androidId);

        // Fix String Length
        int a = deviceId.length();
        if (a < 32) {
            for (int i = 0; i < 32 - a; i++) {
                deviceId = deviceId.concat("0");
            }
        } else {
            deviceId = deviceId.substring(0, 32);
        }

        return deviceId;
    }

    /******************************************************************************************
     * Retrieves the app version of the phone
     ******************************************************************************************
     * @return app version
     ******************************************************************************************/
    public String getAppVersion() {
        String versionName = "";
        int versionCode = 0;

        try {
            PackageInfo pInfo = mCtx.getPackageManager().getPackageInfo(mCtx.getPackageName(), 0);
            versionName = pInfo.versionName;
            versionCode = pInfo.versionCode;
        } catch (PackageManager.NameNotFoundException e) {
            e.printStackTrace();
        }

        String systemVersion = Build.VERSION.RELEASE;

        String manufacturer = Build.MANUFACTURER;
        String model = Build.MODEL;
        if (model.startsWith(manufacturer)) {
            return versionName + " (" + versionCode + ") on Android " + systemVersion + " " + capitalize(model);
        } else {
            return versionName + " (" + versionCode + ") on Android " + systemVersion + " "
                    + capitalize(manufacturer) + " " + model;
        }
    }

    private String capitalize(String s) {
        if (s == null || s.length() == 0) {
            return "";
        }
        char first = s.charAt(0);
        if (Character.isUpperCase(first)) {
            return s;
        } else {
            return Character.toUpperCase(first) + s.substring(1);
        }
    }

    /******************************************************************************************
     * Generates information to send to the server
     * Server looks for name-value pairs to retrieve data
     ******************************************************************************************
     * @param tripId Unique ID for each trip sent.  Unique to ALL RIDERS
     * @return Returns the data the server reads and stores
     * @throws JSONException
     ******************************************************************************************/
    private String getPostData(long tripId) throws JSONException {
        JSONObject coords = getCoordsJSON(tripId);
        JSONObject user = getUserJSON();
        String deviceId = getDeviceId();
        Vector<String> tripData = getTripData(tripId);
        String notes = tripData.get(0);
        String purpose = tripData.get(1);
        String startTime = tripData.get(2);
        // String endTime = tripData.get(3);
        Log.d("KENNY", "USER AGREEMENT: " + agreement);

        List<NameValuePair> nameValuePairs = new ArrayList<NameValuePair>(2);
        nameValuePairs.add(new BasicNameValuePair("coords", coords.toString()));
        nameValuePairs.add(new BasicNameValuePair("user", user.toString()));
        nameValuePairs.add(new BasicNameValuePair("device", deviceId));
        nameValuePairs.add(new BasicNameValuePair("notes", notes));
        nameValuePairs.add(new BasicNameValuePair("purpose", purpose));
        nameValuePairs.add(new BasicNameValuePair("start", startTime));
        // nameValuePairs.add(new BasicNameValuePair("end", endTime));
        nameValuePairs.add(new BasicNameValuePair("version", "" + kSaveProtocolVersion));

        double distDouble = Double.parseDouble(distance);

        String codedPostData = "purpose=" + purpose + "&user=" + user.toString() + "&notes=" + notes + "&coords="
                + coords.toString() + "&version=" + String.valueOf(kSaveProtocolVersion) + "&start=" + startTime
                + "&device=" + deviceId + "&distance=" + distance + "&cotwo=" + co2 + "&kcal=" + kcal + "&avgcost="
                + avgCost + "&score=" + (distDouble * 10);

        return codedPostData;
    }

    /******************************************************************************************
     * Used to see what the server sent back
     * Usually a "success" or "error" message
     ******************************************************************************************
     * @param is Input stream sent back from server
     * @return Returns a human-readable string of server response
     ******************************************************************************************/
    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));
        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();
    }

    /******************************************************************************************
     * Compresses the string to send to the server into byte array
     ******************************************************************************************
     * @param string String to compress
     * @return Byte array to send to server
     * @throws IOException
     ******************************************************************************************/
    public static byte[] compress(String string) throws IOException {
        ByteArrayOutputStream os = new ByteArrayOutputStream(string.length());
        GZIPOutputStream gos = new GZIPOutputStream(os);
        gos.write(string.getBytes());
        gos.close();
        byte[] compressed = os.toByteArray();
        os.close();
        return compressed;
    }

    boolean uploadOneTrip(long currentTripId) {
        boolean result = false;

        byte[] postBodyDataZipped;

        String postBodyData;
        try {
            postBodyData = getPostData(currentTripId);
        } catch (JSONException e) {
            e.printStackTrace();
            return result;
        }

        HttpClient client = new DefaultHttpClient();
        // TODO: Server URL
        final String postUrl = "http://FountainCityCycling.org/post/";
        HttpPost postRequest = new HttpPost(postUrl);

        try {
            // Zip Upload!!!
            Log.v("KENNY", "postBodyData: " + postBodyData.toString());
            Log.v("KENNY", "postBodyData Length: " + postBodyData.length());

            postBodyDataZipped = compress(postBodyData);

            Log.v("KENNY", "postBodyDataZipped: " + postBodyDataZipped);
            Log.v("KENNY", "postBodyDataZipped Length: " + String.valueOf(postBodyDataZipped.length));

            Log.v("KENNY", "Initializing HTTP POST request to " + postUrl + " of size "
                    + String.valueOf(postBodyDataZipped.length) + " orig size " + postBodyData.length());

            postRequest.setHeader("Cycleatl-Protocol-Version", "3");
            postRequest.setHeader("Content-Encoding", "gzip");
            postRequest.setHeader("Content-Type", "application/vnd.cycleatl.trip-v3+form");

            postRequest.setEntity(new ByteArrayEntity(postBodyDataZipped));

            HttpResponse response = client.execute(postRequest);
            String responseString = convertStreamToString(response.getEntity().getContent());
            Log.v("KENNY", "Trip Response: " + responseString);
            JSONObject responseData = new JSONObject(responseString);
            if (responseData.getString("status").equals("success")) {
                mDb.open();
                mDb.updateTripStatus(currentTripId, TripData.STATUS_SENT);
                mDb.close();
                result = true;
            }
        } catch (IllegalStateException e) {
            Log.d("KENNY", "Trip Catch: Illegal State Exception: " + e);
            e.printStackTrace();
            return false;
        } catch (IOException e) {
            Log.d("KENNY", "Trip Catch: IOException: " + e);
            e.printStackTrace();
            return false;
        } catch (JSONException e) {
            Log.d("KENNY", "Trip Catch: JSONException: " + e);
            e.printStackTrace();
            return false;
        }
        return result;
    }

    @Override
    protected Boolean doInBackground(Long... tripid) {
        // First, send the trip user asked for:
        Boolean result = true;
        if (tripid.length != 0) {
            result = uploadOneTrip(tripid[0]);
        }

        // Then, automatically try and send previously-completed trips
        // that were not sent successfully.
        Vector<Long> unsentTrips = new Vector<Long>();

        mDb.openReadOnly();
        Cursor cur = mDb.fetchUnsentTrips();
        if (cur != null && cur.getCount() > 0) {
            // Reades all unsent trips
            while (!cur.isAfterLast()) {
                unsentTrips.add(Long.valueOf(cur.getLong(0)));
                cur.moveToNext();
            }
            cur.close();
        }
        mDb.close();

        for (Long trip : unsentTrips) {
            result &= uploadOneTrip(trip);
        }
        return result;
    }

    @Override
    protected void onPreExecute() {
        Toast.makeText(mCtx.getApplicationContext(), "Submitting. Thanks for using Fountain City Cycling!",
                Toast.LENGTH_LONG).show();
    }

    private SavedTripsAdapter mSavedTripsAdapter;

    public SavedTripsAdapter setSavedTripsAdapter(SavedTripsAdapter mSavedTripsAdapter) {
        this.mSavedTripsAdapter = mSavedTripsAdapter;
        return mSavedTripsAdapter;
    }

    private FragmentSavedTripsSection fragmentSavedTripsSection;

    public FragmentSavedTripsSection setFragmentSavedTripsSection(
            FragmentSavedTripsSection fragmentSavedTripsSection) {
        this.fragmentSavedTripsSection = fragmentSavedTripsSection;
        return fragmentSavedTripsSection;
    }

    private ListView listSavedTrips;

    public ListView setListView(ListView listSavedTrips) {
        this.listSavedTrips = listSavedTrips;
        return listSavedTrips;
    }

    @Override
    protected void onPostExecute(Boolean result) {
        try {
            if (mSavedTripsAdapter != null) {
                mSavedTripsAdapter.notifyDataSetChanged();
            }

            if (fragmentSavedTripsSection != null) {
                listSavedTrips.invalidate();
                fragmentSavedTripsSection.populateTripList(listSavedTrips);
            }

            if (result) {
                Toast.makeText(mCtx.getApplicationContext(), "Trip uploaded successfully.", Toast.LENGTH_SHORT)
                        .show();
            } else {
                Toast.makeText(mCtx.getApplicationContext(),
                        "Fountain City Cycling couldn't upload the trip, and will retry when your next trip is completed.",
                        Toast.LENGTH_LONG).show();
            }
        } catch (Exception e) {
            // Just don't toast if the view has gone out of context
        }
    }

    /******************************************************************************************
     * Generates and uploads KCal, CO2 savings, Avg Savings from biking vs riding, and distance.
     ******************************************************************************************
     * @param cursor Uses this to access SQLite database
     ******************************************************************************************/
    private void setKcalCo2Savings(Cursor cursor) {

        // Set co2
        Double CO2 = cursor.getFloat(cursor.getColumnIndex("distance")) * 0.0006212 * 0.93;
        DecimalFormat df = new DecimalFormat("0.#");
        co2 = df.format(CO2);

        // Set kcal
        Double calory = cursor.getFloat(cursor.getColumnIndex("distance")) * 0.0006212 * 49 - 1.69;
        if (calory <= 0) {
            kcal = "0";
        } else {
            kcal = df.format(calory);
        }

        // Set Average Savings
        float savings = cursor.getFloat(cursor.getColumnIndex("distance")) * 0.0006212f;
        Double dSavings = savings * .592;
        Log.d("KENNY", "Distance: " + cursor.getFloat(cursor.getColumnIndex("distance")));
        DecimalFormat df2 = new DecimalFormat("$#,###,##0.00");
        avgCost = df2.format(dSavings);

        // Set Distance
        float dist = cursor.getFloat(cursor.getColumnIndex("distance")) * 0.0006212f;
        DecimalFormat dfDistance = new DecimalFormat("0.0#");
        distance = String.valueOf(dfDistance.format(dist));
    }

    /******************************************************************************************
     * Retrieves the user agreement that was JSON encoded into app memory
     ******************************************************************************************
     * @return String form of user agreement (Agree or Disagree)
     ******************************************************************************************/
    private String getUserAgreement() {

        // See if user agreed to contest terms
        JsonStorage jsonStorage = new JsonStorage(mCtx);
        try {
            String stringJSON = jsonStorage.readJSON(); // Get the string stored in text file
            JSONObject jsonObject = new JSONObject(stringJSON); // Convert it to JSON form
            agreement = jsonObject.getString("agree");
            Log.d("KENNY", "Read JSON, the user " + agreement);
        } catch (IOException e) {
            Log.d("KENNY", "Something went wrong in I/O");
            e.printStackTrace();
        } catch (JSONException ej) {
            Log.d("KENNY", "Something went wrong with JSON, set agreement to \"false\"");
            agreement = "Disagree";
            ej.printStackTrace();
        }
        return agreement;
    }
}