Java tutorial
/** * 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 } } }