edu.pdx.cecs.orcycle.NoteUploader.java Source code

Java tutorial

Introduction

Here is the source code for edu.pdx.cecs.orcycle.NoteUploader.java

Source

/**
 *  ORcycle, Copyright 2014, 2015, PSU Transportation, Technology, and People Lab.
 *
 *  ORcycle 2.2.0 has introduced new app features: safety focus with new buttons
 *  to report safety issues and crashes (new questionnaires), expanded trip
 *  questionnaire (adding questions besides trip purpose), app utilization
 *  reminders, app tutorial, and updated font and color schemes.
 *
 *  @author Bryan.Blanc <bryanpblanc@gmail.com>    (code)
 *  @author Miguel Figliozzi <figliozzi@pdx.edu> and ORcycle team (general app
 *  design and features, report questionnaires and new ORcycle features)
 *
 *  For more information on the project, go to
 *  http://www.pdx.edu/transportation-lab/orcycle and http://www.pdx.edu/transportation-lab/app-development
 *
 *  Updated/modified for Oregon pilot study and app deployment.
 *
 *  ORcycle 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 any later version.
 *  ORcycle 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
 *  ORcycle. If not, see <http://www.gnu.org/licenses/>.
 *
 *************************************************************************************
 *
 *    Cycle Altanta, Copyright 2012 Georgia Institute of Technology
 *                                    Atlanta, GA. USA
 *
 *   @author Christopher Le Dantec <ledantec@gatech.edu>
 *   @author Anhong Guo <guoanhong15@gmail.com>
 *
 *   Updated/Modified for Atlanta's app deployment. 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.pdx.cecs.orcycle;

import java.io.DataOutputStream;
import java.io.IOException;
import java.net.HttpURLConnection;
import java.net.URL;
import java.text.SimpleDateFormat;
import java.util.HashMap;
import java.util.Locale;
import java.util.Map;
import java.util.Vector;

import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;

import android.content.Context;
import android.database.Cursor;
import android.os.AsyncTask;
import android.util.Log;
import android.widget.ListView;
import android.widget.Toast;

public class NoteUploader extends AsyncTask<Long, Integer, Boolean> {

    private byte[] imageData;

    private static final String MODULE_TAG = "NoteUploader";
    private static final int kSaveNoteProtocolVersion = 4;

    private static final String NOTE_TRIP_ID = "p";
    private static final String NOTE_RECORDED = "r";
    private static final String NOTE_LAT = "l";
    private static final String NOTE_LGT = "n";
    private static final String NOTE_HACC = "h";
    private static final String NOTE_VACC = "v";
    private static final String NOTE_ALT = "a";
    private static final String NOTE_SPEED = "s";
    private static final String NOTE_TYPE = "t";
    private static final String NOTE_DETAILS = "d";
    private static final String NOTE_IMGURL = "i";
    private static final String NOTE_REPORT_DATE = "reportDate";
    private static final String NOTE_EMAIL_SENT = "e";

    private static final String FID_QUESTION_ID = "question_id";
    private static final String FID_ANSWER_ID = "answer_id";
    private static final String FID_ANSWER_OTHER_TEXT = "other_text";

    private static final String twoHyphens = "--";
    private static final String boundary = "cycle*******notedata*******atlanta";
    private static final String lineEnd = "\r\n";
    private static final String notesep = "--cycle*******notedata*******atlanta\r\n";

    private static final Map<Long, Boolean> pendingUploads = new HashMap<Long, Boolean>();

    private final SimpleDateFormat reportDateFormatter = new SimpleDateFormat("yyyy-MM-dd", Locale.US);

    private final Context mCtx;
    private final String userId;
    private final DbAdapter mDb;

    public NoteUploader(Context ctx, String userId) {
        super();
        this.mCtx = ctx;
        this.userId = userId;
        this.mDb = new DbAdapter(this.mCtx);
    }

    public static void setPending(long noteId, boolean value) {
        Log.v(MODULE_TAG, "setPending: [" + String.valueOf(noteId) + "] = " + String.valueOf(value));
        if (value == false) {
            if (pendingUploads.containsKey(noteId)) {
                pendingUploads.remove(noteId);
            }
        } else {
            pendingUploads.put(noteId, true);
        }
    }

    public static boolean isPending(long noteId) {

        boolean value = false;
        if (pendingUploads.containsKey(noteId)) {
            value = pendingUploads.get(noteId);
        }
        return value;
    }

    private JSONObject getNoteJSON(long noteId) throws JSONException {
        try {
            SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
            String noteImageFileName;

            mDb.openReadOnly();
            try {
                Cursor noteCursor = mDb.fetchNote(noteId);

                try {
                    Map<String, Integer> fieldMap = new HashMap<String, Integer>();
                    fieldMap.put(NOTE_TRIP_ID, noteCursor.getColumnIndex(DbAdapter.K_NOTE_TRIP_ID));
                    fieldMap.put(NOTE_RECORDED, noteCursor.getColumnIndex(DbAdapter.K_NOTE_RECORDED));
                    fieldMap.put(NOTE_LAT, noteCursor.getColumnIndex(DbAdapter.K_NOTE_LAT));
                    fieldMap.put(NOTE_LGT, noteCursor.getColumnIndex(DbAdapter.K_NOTE_LGT));
                    fieldMap.put(NOTE_HACC, noteCursor.getColumnIndex(DbAdapter.K_NOTE_ACC));
                    fieldMap.put(NOTE_VACC, noteCursor.getColumnIndex(DbAdapter.K_NOTE_ACC));
                    fieldMap.put(NOTE_ALT, noteCursor.getColumnIndex(DbAdapter.K_NOTE_ALT));
                    fieldMap.put(NOTE_SPEED, noteCursor.getColumnIndex(DbAdapter.K_NOTE_SPEED));
                    fieldMap.put(NOTE_DETAILS, noteCursor.getColumnIndex(DbAdapter.K_NOTE_DETAILS));
                    fieldMap.put(NOTE_IMGURL, noteCursor.getColumnIndex(DbAdapter.K_NOTE_IMGURL));
                    fieldMap.put(NOTE_REPORT_DATE, noteCursor.getColumnIndex(DbAdapter.K_NOTE_REPORT_DATE));
                    fieldMap.put(NOTE_EMAIL_SENT, noteCursor.getColumnIndex(DbAdapter.K_NOTE_EMAIL_SENT));

                    JSONObject note = new JSONObject();

                    note.put(NOTE_TRIP_ID, noteCursor.getInt(fieldMap.get(NOTE_TRIP_ID)));
                    note.put(NOTE_RECORDED, df.format(noteCursor.getDouble(fieldMap.get(NOTE_RECORDED))));
                    note.put(NOTE_LAT, noteCursor.getDouble(fieldMap.get(NOTE_LAT)) / 1E6);
                    note.put(NOTE_LGT, noteCursor.getDouble(fieldMap.get(NOTE_LGT)) / 1E6);
                    note.put(NOTE_HACC, noteCursor.getDouble(fieldMap.get(NOTE_HACC)));
                    note.put(NOTE_VACC, noteCursor.getDouble(fieldMap.get(NOTE_VACC)));
                    note.put(NOTE_ALT, noteCursor.getDouble(fieldMap.get(NOTE_ALT)));
                    note.put(NOTE_SPEED, noteCursor.getDouble(fieldMap.get(NOTE_SPEED)));
                    note.put(NOTE_DETAILS, noteCursor.getString(fieldMap.get(NOTE_DETAILS)));
                    note.put(NOTE_IMGURL, noteImageFileName = noteCursor.getString(fieldMap.get(NOTE_IMGURL)));

                    long reportDate = noteCursor.getLong(fieldMap.get(NOTE_REPORT_DATE));
                    String formattedDate = reportDateFormatter.format(reportDate);
                    note.put(NOTE_REPORT_DATE, formattedDate);

                    boolean emailSent = (noteCursor.getInt(fieldMap.get(NOTE_EMAIL_SENT)) == 1 ? true : false);
                    note.put(NOTE_EMAIL_SENT, emailSent);

                    if ((null != noteImageFileName) && (!noteImageFileName.equals("")))
                        imageData = mDb.getNoteImageData(noteId);
                    else
                        imageData = null;

                    return note;
                } catch (Exception ex) {
                    Log.e(MODULE_TAG, ex.getMessage());
                } finally {
                    noteCursor.close();
                }
                return null;
            } catch (Exception ex) {
                Log.e(MODULE_TAG, ex.getMessage());
            } finally {
                mDb.close();
            }
            return null;
        } catch (Exception ex) {
            Log.e(MODULE_TAG, ex.getMessage());
        }
        return null;
    }

    private JSONArray getNoteResponsesJSON(long noteId) throws JSONException {

        // Create a JSON array to hold all of the answers
        JSONArray jsonAnswers = new JSONArray();

        mDb.openReadOnly();
        try {
            Cursor answers = mDb.fetchNoteAnswers(noteId);

            int questionId = answers.getColumnIndex(DbAdapter.K_NOTE_ANSWER_QUESTION_ID);
            int answerId = answers.getColumnIndex(DbAdapter.K_NOTE_ANSWER_ANSWER_ID);
            int otherText = answers.getColumnIndex(DbAdapter.K_NOTE_ANSWER_OTHER_TEXT);
            String text;

            // Cycle thru the database entries
            while (!answers.isAfterLast()) {

                // For each row, construct a JSON object
                JSONObject json = new JSONObject();

                try {
                    // Place values into the JSON object
                    json.put(FID_QUESTION_ID, answers.getInt(questionId));
                    json.put(FID_ANSWER_ID, answers.getInt(answerId));

                    if (null != (text = answers.getString(otherText))) {
                        text = text.trim();
                        if (!text.equals("")) {
                            json.put(FID_ANSWER_OTHER_TEXT, text);
                        }
                    }
                    // Place JSON objects into the JSON array
                    jsonAnswers.put(json);
                } catch (Exception ex) {
                    Log.e(MODULE_TAG, ex.getMessage());
                }
                // Move to next row
                answers.moveToNext();
            }
            answers.close();
        } catch (Exception ex) {
            Log.e(MODULE_TAG, ex.getMessage());
        } finally {
            mDb.close();
        }
        return jsonAnswers;
    }

    boolean uploadOneNote(long noteId) {
        boolean result = false;
        final String postUrl = mCtx.getResources().getString(R.string.post_url);

        try {
            JSONArray jsonNoteResponses = getNoteResponsesJSON(noteId);

            URL url = new URL(postUrl);

            HttpURLConnection conn = (HttpURLConnection) url.openConnection();
            conn.setDoInput(true); // Allow Inputs
            conn.setDoOutput(true); // Allow Outputs
            conn.setUseCaches(false); // Don't use a Cached Copy
            conn.setRequestMethod("POST");
            conn.setRequestProperty("Connection", "Keep-Alive");
            conn.setRequestProperty("ENCTYPE", "multipart/form-data");
            conn.setRequestProperty("Content-Type", "multipart/form-data; boundary=" + boundary);
            conn.setRequestProperty("Cycleatl-Protocol-Version", "4");

            DataOutputStream dos = new DataOutputStream(conn.getOutputStream());
            JSONObject jsonNote;
            if (null != (jsonNote = getNoteJSON(noteId))) {
                try {
                    String deviceId = userId;

                    dos.writeBytes(notesep + ContentField("note") + jsonNote.toString() + "\r\n");
                    dos.writeBytes(
                            notesep + ContentField("version") + String.valueOf(kSaveNoteProtocolVersion) + "\r\n");
                    dos.writeBytes(notesep + ContentField("device") + deviceId + "\r\n");
                    dos.writeBytes(notesep + ContentField("noteResponses") + jsonNoteResponses.toString() + "\r\n");

                    if (null != imageData) {
                        dos.writeBytes(notesep + "Content-Disposition: form-data; name=\"file\"; filename=\""
                                + deviceId + ".jpg\"\r\n" + "Content-Type: image/jpeg\r\n\r\n");
                        dos.write(imageData);
                        dos.writeBytes("\r\n");
                    }

                    dos.writeBytes(notesep);
                    dos.flush();
                } catch (Exception ex) {
                    Log.e(MODULE_TAG, ex.getMessage());
                    return false;
                } finally {
                    dos.close();
                }
                int serverResponseCode = conn.getResponseCode();
                String serverResponseMessage = conn.getResponseMessage();
                // JSONObject responseData = new JSONObject(serverResponseMessage);
                Log.v("Jason", "HTTP Response is : " + serverResponseMessage + ": " + serverResponseCode);
                if (serverResponseCode == 201 || serverResponseCode == 202) {
                    mDb.open();
                    mDb.updateNoteStatus(noteId, NoteData.STATUS_SENT);
                    mDb.close();
                    result = true;
                }
            } else {
                result = false;
            }
        } catch (IllegalStateException ex) {
            Log.e(MODULE_TAG, ex.getMessage());
            return false;
        } catch (IOException ex) {
            Log.e(MODULE_TAG, ex.getMessage());
            return false;
        } catch (JSONException ex) {
            Log.e(MODULE_TAG, ex.getMessage());
            return false;
        } finally {
            NoteUploader.setPending(noteId, false);
        }
        return result;
    }

    private String ContentField(String type) {
        return "Content-Disposition: form-data; name=\"" + type + "\"\r\n\r\n";
    }

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

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

        mDb.openReadOnly();
        Cursor cur = mDb.fetchUnsentNotes();
        if (cur != null && cur.getCount() > 0) {
            // pd.setMessage("Sent. You have previously unsent notes; submitting those now.");
            while (!cur.isAfterLast()) {
                unsentNotes.add(Long.valueOf(cur.getLong(0)));
                cur.moveToNext();
            }
            cur.close();
        }
        mDb.close();

        for (Long note : unsentNotes) {
            result &= uploadOneNote(note);
        }
        return result;
    }

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

    private SavedNotesAdapter mSavedNotesAdapter;

    public SavedNotesAdapter setSavedNotesAdapter(SavedNotesAdapter mSavedNotesAdapter) {
        this.mSavedNotesAdapter = mSavedNotesAdapter;
        return mSavedNotesAdapter;
    }

    private FragmentSavedNotesSection fragmentSavedNotesSection;

    public FragmentSavedNotesSection setFragmentSavedNotesSection(
            FragmentSavedNotesSection fragmentSavedNotesSection) {
        this.fragmentSavedNotesSection = fragmentSavedNotesSection;
        return fragmentSavedNotesSection;
    }

    private ListView listSavedNotes;

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

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

            if (fragmentSavedNotesSection != null) {
                listSavedNotes.invalidate();
                fragmentSavedNotesSection.populateNoteList(listSavedNotes);
            }

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