/** * 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.BufferedOutputStream; import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.text.SimpleDateFormat; import android.content.ContentValues; import android.content.Context; import android.database.Cursor; import android.database.SQLException; import android.database.sqlite.SQLiteDatabase; import android.database.sqlite.SQLiteOpenHelper; import android.util.Log; /** * Simple database access helper class. Defines the basic CRUD operations, and * gives the ability to list all trips as well as retrieve or modify a specific * trip. * * This has been improved from the first version of this tutorial through the * addition of better error handling and also using returning a Cursor instead * of using a collection of inner classes (which is less scalable and not * recommended). * * **This code borrows heavily from Google demo app "Notepad" in the Android * SDK** */ public class DbAdapter { private static final String NOTE_IMAGES_DIR_NAME = "note_images"; // Database versions private static final int DATABASE_VERSION_PAUSES = 22; private static final int DATABASE_VERSION_SEGMENTS = 23; private static final int DATABASE_VERSION_NOTES_V3 = 25; private static final int DATABASE_VERSION_ANSWERS = 26; private static final int DATABASE_VERSION_NOTE_ANSWERS = 27; private static final int DATABASE_VERSION_NOTE_SEVERITY = 28; private static final int DATABASE_VERSION_REMINDERS = 29; private static final int DATABASE_VERSION_REPORT_DATES = 30; private static final int DATABASE_VERSION_NOTE_EMAILS = 31; private static final int DATABASE_VERSION_SENSOR_VALUES_TABLE = 32; private static final int DATABASE_VERSION = DATABASE_VERSION_SENSOR_VALUES_TABLE; private static final String DATA_TABLE_SENSOR_VALUES = "sensor_values"; // Trips Table columns public static final String K_TRIP_ROWID = "_id"; public static final String K_TRIP_PURP = "purp"; public static final String K_TRIP_START = "start"; public static final String K_TRIP_END = "endtime"; public static final String K_TRIP_FANCYSTART = "fancystart"; public static final String K_TRIP_FANCYINFO = "fancyinfo"; public static final String K_TRIP_NOTE = "note"; public static final String K_TRIP_DISTANCE = "distance"; public static final String K_TRIP_LATHI = "lathi"; public static final String K_TRIP_LATLO = "latlo"; public static final String K_TRIP_LGTHI = "lgthi"; public static final String K_TRIP_LGTLO = "lgtlo"; public static final String K_TRIP_STATUS = "status"; // Coords Table columns public static final String K_POINT_ROWID = "_id"; public static final String K_POINT_TRIP = "trip"; public static final String K_POINT_TIME = "time"; public static final String K_POINT_LAT = "lat"; public static final String K_POINT_LGT = "lgt"; public static final String K_POINT_ACC = "acc"; public static final String K_POINT_ALT = "alt"; public static final String K_POINT_SPEED = "speed"; // Note Table columns public static final String K_NOTE_ROWID = "_id"; public static final String K_NOTE_TRIP_ID = "tripid"; public static final String K_NOTE_RECORDED = "noterecorded"; public static final String K_NOTE_FANCYSTART = "notefancystart"; public static final String K_NOTE_LAT = "notelat"; public static final String K_NOTE_LGT = "notelgt"; public static final String K_NOTE_ACC = "noteacc"; public static final String K_NOTE_ALT = "notealt"; public static final String K_NOTE_SPEED = "notespeed"; public static final String K_NOTE_SEVERITY = "noteseverity"; public static final String K_NOTE_DETAILS = "notedetails"; public static final String K_NOTE_IMGURL = "noteimageurl"; public static final String K_NOTE_STATUS = "notestatus"; public static final String K_NOTE_REPORT_DATE = "notereportdate"; public static final String K_NOTE_EMAIL_SENT = "emailsent"; // Pauses Table columns public static final String K_PAUSE_TRIP_ID = "_id"; public static final String K_PAUSE_START_TIME = "starttime"; public static final String K_PAUSE_END_TIME = "endtime"; // Sensor table columns public static final String K_SENSOR_TIME = "time"; public static final String K_SENSOR_ID = "id"; public static final String K_SENSOR_TYPE = "type"; public static final String K_SENSOR_SAMPLES = "samples"; public static final String K_SENSOR_NUM_VALS = "numvals"; public static final String K_SENSOR_AVG_0 = "avg0"; public static final String K_SENSOR_AVG_1 = "avg1"; public static final String K_SENSOR_AVG_2 = "avg2"; public static final String K_SENSOR_SSD_0 = "ssd0"; public static final String K_SENSOR_SSD_1 = "ssd1"; public static final String K_SENSOR_SSD_2 = "ssd2"; // Trip Answers Table columns public static final String K_ANSWER_TRIP_ID = "trip_id"; public static final String K_ANSWER_QUESTION_ID = "question_id"; public static final String K_ANSWER_ANSWER_ID = "answer_id"; public static final String K_ANSWER_OTHER_TEXT = "other_text"; // Note Answers Table columns public static final String K_NOTE_ANSWER_NOTE_ID = "note_id"; public static final String K_NOTE_ANSWER_QUESTION_ID = "question_id"; public static final String K_NOTE_ANSWER_ANSWER_ID = "answer_id"; public static final String K_NOTE_ANSWER_OTHER_TEXT = "other_text"; // Segments Table columns public static final String K_SEGMENT_ID = "_id"; public static final String K_SEGMENT_TRIP_ID = "tripid"; public static final String K_SEGMENT_RATING = "rating"; public static final String K_SEGMENT_DETAILS = "details"; public static final String K_SEGMENT_START_INDEX = "startindex"; public static final String K_SEGMENT_END_INDEX = "endindex"; public static final String K_SEGMENT_STATUS = "status"; // Reminders Table columns public static final String K_REMINDER_ID = "_id"; public static final String K_REMINDER_NAME = "name"; public static final String K_REMINDER_DAYS = "days"; public static final String K_REMINDER_HOURS = "hours"; public static final String K_REMINDER_MINUTES = "minutes"; public static final String K_REMINDER_ENABLED = "enabled"; private static final String MODULE_TAG = "DbAdapter"; private DatabaseHelper mDbHelper; private SQLiteDatabase mDb; /** * Database creation sql statement */ private static final String TABLE_CREATE_TRIPS = "create table trips " + "(_id integer primary key autoincrement, purp text, start double, endtime double, " + "fancystart text, fancyinfo text, distance float, note text," + "lathi integer, latlo integer, lgthi integer, lgtlo integer, status integer);"; private static final String TABLE_CREATE_COORDS = "create table coords " + "(_id integer primary key autoincrement, " + "trip integer, lat int, lgt int, " + "time double, acc float, alt double, speed float);"; private static final String TABLE_CREATE_NOTES = "create table notes " + "(_id integer primary key autoincrement, tripid int, notetype integer, noteseverity integer, noterecorded double, " + "notefancystart text, notedetails text, noteimageurl text, " + "notelat int, notelgt int, noteacc float, notealt double, notespeed float, notestatus integer, " + "notereportdate double DEFAULT 0, emailsent int DEFAULT 0);"; private static final String TABLE_CREATE_PAUSES = "create table pauses " + "(_id integer, starttime double, endtime double, " + "PRIMARY KEY(_id, startTime), " + "FOREIGN KEY(_id) REFERENCES TRIPS(_id));"; private static final String SQL_CREATE_TABLE_CMD = "create table"; private static final String TABLE_CREATE_SENSOR_VALUES = SQL_CREATE_TABLE_CMD + " " + DATA_TABLE_SENSOR_VALUES + " (" + K_SENSOR_TIME + " double, " + K_SENSOR_ID + " text, " + K_SENSOR_TYPE + " integer, " + K_SENSOR_SAMPLES + " integer, " + K_SENSOR_NUM_VALS + " integer, " + K_SENSOR_AVG_0 + " double, " + K_SENSOR_AVG_1 + " double, " + K_SENSOR_AVG_2 + " double, " + K_SENSOR_SSD_0 + " double, " + K_SENSOR_SSD_1 + " double, " + K_SENSOR_SSD_2 + " double, " + "PRIMARY KEY(" + K_SENSOR_TIME + ", " + K_SENSOR_ID + "));"; private static final String TABLE_CREATE_SEGMENTS = "create table segments " + "(_id integer primary key autoincrement, tripid int, rating int, " + "details text, startindex int, endindex int, status int, " + "FOREIGN KEY(tripid) REFERENCES TRIPS(_id));"; private static final String TABLE_CREATE_ANSWERS = "create table answers " + "(trip_id integer, question_id integer, answer_id integer, other_text text, " + "FOREIGN KEY(trip_id) REFERENCES TRIPS(_id));"; private static final String TABLE_CREATE_NOTE_ANSWERS = "create table note_answers " + "(note_id integer, question_id integer, answer_id integer, other_text text, " + "FOREIGN KEY(note_id) REFERENCES NOTES(_id));"; private static final String TABLE_CREATE_REMINDERS = "create table reminders " + "(_id integer primary key autoincrement, name text, days int, " + "hours int, minutes int, enabled integer);"; private static final String TABLE_DROP_NOTES = "drop table notes;"; private static final String NOTES_TABLE_ADD_COLUMN_REPORT_DATE = "ALTER TABLE notes ADD COLUMN notereportdate double DEFAULT 0;"; private static final String NOTES_TABLE_ADD_COLUMN_NOTE_EMAIL = "ALTER TABLE notes ADD COLUMN emailsent int DEFAULT 0;"; private static final String DATABASE_NAME = "data"; private static final String DATA_TABLE_TRIPS = "trips"; private static final String DATA_TABLE_COORDS = "coords"; private static final String DATA_TABLE_NOTES = "notes"; private static final String DATA_TABLE_PAUSES = "pauses"; private static final String DATA_TABLE_SEGMENTS = "segments"; private static final String DATA_TABLE_ANSWERS = "answers"; private static final String DATA_TABLE_NOTE_ANSWERS = "note_answers"; private static final String DATA_TABLE_REMINDERS = "reminders"; private final Context mCtx; private String noteImagesDirName = null; // ************************************************************************ // * DatabaseHelper // ************************************************************************ private static class DatabaseHelper extends SQLiteOpenHelper { DatabaseHelper(Context context) { super(context, DATABASE_NAME, null, DATABASE_VERSION); } @Override public void onCreate(SQLiteDatabase db) { db.execSQL(TABLE_CREATE_TRIPS); db.execSQL(TABLE_CREATE_COORDS); db.execSQL(TABLE_CREATE_NOTES); db.execSQL(TABLE_CREATE_PAUSES); db.execSQL(TABLE_CREATE_SEGMENTS); db.execSQL(TABLE_CREATE_ANSWERS); db.execSQL(TABLE_CREATE_NOTE_ANSWERS); db.execSQL(TABLE_CREATE_REMINDERS); } @Override public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { Log.w(MODULE_TAG, "Upgrading database from version " + oldVersion + " to " + newVersion); if (oldVersion < DATABASE_VERSION_PAUSES) try { db.execSQL(TABLE_CREATE_PAUSES); } catch(Exception ex) { Log.e(MODULE_TAG, ex.getMessage()); } if (oldVersion < DATABASE_VERSION_SEGMENTS) try { db.execSQL(TABLE_CREATE_SEGMENTS); } catch(Exception ex) { Log.e(MODULE_TAG, ex.getMessage()); } if (oldVersion < DATABASE_VERSION_NOTES_V3) { try { db.execSQL(TABLE_DROP_NOTES); } catch(Exception ex) { Log.e(MODULE_TAG, ex.getMessage()); } try { db.execSQL(TABLE_CREATE_NOTES); } catch(Exception ex) { Log.e(MODULE_TAG, ex.getMessage()); } } if (oldVersion < DATABASE_VERSION_ANSWERS) { try { db.execSQL(TABLE_CREATE_ANSWERS); } catch(Exception ex) { Log.e(MODULE_TAG, ex.getMessage()); } } if (oldVersion < DATABASE_VERSION_NOTE_ANSWERS) { try { db.execSQL(TABLE_CREATE_NOTE_ANSWERS); } catch(Exception ex) { Log.e(MODULE_TAG, ex.getMessage()); } } if (oldVersion < DATABASE_VERSION_NOTE_SEVERITY) { try { db.execSQL(TABLE_DROP_NOTES); db.execSQL(TABLE_CREATE_NOTES); } catch(Exception ex) { Log.e(MODULE_TAG, ex.getMessage()); } } if (oldVersion < DATABASE_VERSION_REMINDERS) { try { db.execSQL(TABLE_CREATE_REMINDERS); } catch(Exception ex) { Log.e(MODULE_TAG, ex.getMessage()); } } if (oldVersion < DATABASE_VERSION_REPORT_DATES) { try { db.execSQL(NOTES_TABLE_ADD_COLUMN_REPORT_DATE); } catch(Exception ex) { Log.e(MODULE_TAG, ex.getMessage()); } } if (oldVersion < DATABASE_VERSION_NOTE_EMAILS) { try { db.execSQL(NOTES_TABLE_ADD_COLUMN_NOTE_EMAIL); } catch(Exception ex) { Log.e(MODULE_TAG, ex.getMessage()); } } // Create table for holding sensor data if (oldVersion < DATABASE_VERSION_SENSOR_VALUES_TABLE) { try { db.execSQL(TABLE_CREATE_SENSOR_VALUES); } catch(Exception ex) { Log.e(MODULE_TAG, ex.getMessage()); } } } @Override public void onDowngrade(SQLiteDatabase db, int oldVersion, int newVersion) { } } // ************************************************************************ // * DbAdapter table methods // ************************************************************************ /** * Constructor - takes the context to allow the database to be * opened/created * * @param ctx * the Context within which to work */ public DbAdapter(Context ctx) { mCtx = ctx; try { noteImagesDirName = makeNoteImageDirectory(); } catch(Exception ex) { Log.e(MODULE_TAG, ex.getMessage()); } } private String makeNoteImageDirectory() throws Exception { File dir; if (null == (dir = mCtx.getFilesDir())) { throw new Exception("Unable to retrieve root directory for storing note images"); } String dirName = dir.getAbsolutePath() + "/"+ NOTE_IMAGES_DIR_NAME; File imagesDir = new File(dirName); if (!imagesDir.exists()) if (!imagesDir.mkdirs()) throw new Exception("Unable to create directory for storing note images"); return dirName; } public String getNoteImageDirectory() { return noteImagesDirName; } /** * Open the database. If it cannot be opened, try to create a new instance * of the database. If it cannot be created, throw an exception to signal * the failure * * @return this (self reference, allowing this to be chained in an * initialization call) * @throws SQLException * if the database could be neither opened or created */ public DbAdapter open() throws SQLException { mDbHelper = new DatabaseHelper(mCtx); mDb = mDbHelper.getWritableDatabase(); return this; } public DbAdapter openReadOnly() throws SQLException { mDbHelper = new DatabaseHelper(mCtx); mDb = mDbHelper.getReadableDatabase(); return this; } public void close() { mDbHelper.close(); } // ************************************************************************ // * Coordinate table methods // ************************************************************************ public boolean addCoordToTrip(long tripid, CyclePoint pt) { // Add the latest point ContentValues coordValues = new ContentValues(); coordValues.put(K_POINT_TRIP, tripid); coordValues.put(K_POINT_LAT, pt.latitude); coordValues.put(K_POINT_LGT, pt.longitude); coordValues.put(K_POINT_TIME, pt.time); coordValues.put(K_POINT_ACC, pt.accuracy); coordValues.put(K_POINT_ALT, pt.altitude); coordValues.put(K_POINT_SPEED, pt.speed); long rowId = mDb.insert(DATA_TABLE_COORDS, null, coordValues); if (rowId == -1) { Log.e(MODULE_TAG, "Insert " + DATA_TABLE_COORDS + ": failed"); } else { /* Log.i(MODULE_TAG, "Insert " + DATA_TABLE_COORDS + "[" + String.valueOf(rowId) + "](" + K_POINT_TRIP + ", " + K_POINT_LAT + ", " + K_POINT_LGT + ", " + K_POINT_TIME + ", " + K_POINT_ACC + ", " + K_POINT_ALT + ", " + K_POINT_SPEED +")"); */ } // And update the trip stats ContentValues tripValues = new ContentValues(); tripValues.put(K_TRIP_END, pt.time); int numRows = mDb.update(DATA_TABLE_TRIPS, tripValues, K_TRIP_ROWID + "=" + tripid, null); boolean success = ((rowId != -1) && (numRows > 0)); return success; } public boolean deleteAllCoordsForTrip(long tripid) { int numRows = mDb.delete(DATA_TABLE_COORDS, K_POINT_TRIP + "=" + tripid, null); Log.i(MODULE_TAG, "Deleted " + DATA_TABLE_COORDS + "[" + String.valueOf(tripid) +"]: " + String.valueOf(numRows) + " rows."); return numRows > 0; } public Cursor fetchAllCoordsForTrip(long tripid) { try { Cursor mCursor = mDb.query(true, DATA_TABLE_COORDS, new String[] { K_POINT_LAT, K_POINT_LGT, K_POINT_TIME, K_POINT_ACC, K_POINT_ALT, K_POINT_SPEED }, K_POINT_TRIP + "=" + tripid, null, null, null, K_POINT_TIME, null); if (mCursor != null) { mCursor.moveToFirst(); } return mCursor; } catch (Exception e) { Log.e(MODULE_TAG, e.toString()); return null; } } // ************************************************************************ // * Sensor table methods // ************************************************************************ public void addSensorReadings(double currentTime, String sensorName, int sensorType, int numSamples, float[] averageValues, float[] sumSquareDifferences) { // Add the latest point ContentValues cv = new ContentValues(); cv.put(K_SENSOR_TIME, currentTime); cv.put(K_SENSOR_ID, sensorName); cv.put(K_SENSOR_TYPE, sensorType); cv.put(K_SENSOR_SAMPLES, numSamples); cv.put(K_SENSOR_NUM_VALS, averageValues.length); switch(averageValues.length) { case 1: cv.put(K_SENSOR_AVG_0, averageValues[0]); cv.put(K_SENSOR_SSD_0, sumSquareDifferences[0]); break; case 3: cv.put(K_SENSOR_AVG_0, averageValues[0]); cv.put(K_SENSOR_AVG_1, averageValues[1]); cv.put(K_SENSOR_AVG_2, averageValues[2]); cv.put(K_SENSOR_SSD_0, sumSquareDifferences[0]); cv.put(K_SENSOR_SSD_1, sumSquareDifferences[1]); cv.put(K_SENSOR_SSD_2, sumSquareDifferences[2]); break; default: Log.e(MODULE_TAG, "addSensorReadings failed: invalid number of values encountered"); return; } if (-1 == mDb.insert(DATA_TABLE_SENSOR_VALUES, null, cv)) { Log.e(MODULE_TAG, "Insert " + DATA_TABLE_SENSOR_VALUES + ": failed"); } } public Cursor fetchSensorValues(double time) { try { String[] columns = new String[] { K_SENSOR_ID, K_SENSOR_TYPE, K_SENSOR_SAMPLES, K_SENSOR_NUM_VALS, K_SENSOR_AVG_0, K_SENSOR_AVG_1, K_SENSOR_AVG_2, K_SENSOR_SSD_0, K_SENSOR_SSD_1, K_SENSOR_SSD_2}; Cursor cursor = mDb.query(true, DATA_TABLE_SENSOR_VALUES, columns, K_SENSOR_TIME + "=" + time, null, null, null, null, null); if (cursor != null) { cursor.moveToFirst(); } return cursor; } catch (Exception e) { Log.e(MODULE_TAG, e.toString()); return null; } } // ************************************************************************ // * Trip table methods // ************************************************************************ /** * Create a new trip using the data provided. If the trip is successfully * created return the new rowId for that trip, otherwise return a -1 to * indicate failure. */ public long createTrip(String purp, double starttime, String fancystart, String note) { ContentValues initialValues = new ContentValues(); initialValues.put(K_TRIP_PURP, purp); initialValues.put(K_TRIP_START, starttime); initialValues.put(K_TRIP_FANCYSTART, fancystart); initialValues.put(K_TRIP_NOTE, note); initialValues.put(K_TRIP_STATUS, TripData.STATUS_INCOMPLETE); Long rowId = mDb.insert(DATA_TABLE_TRIPS, null, initialValues); Log.i(MODULE_TAG, "Insert " + DATA_TABLE_TRIPS + "[" + String.valueOf(rowId) +"]"); return rowId; } public long createTrip() { return createTrip("", System.currentTimeMillis(), "", ""); } /** * Delete the trip with the given rowId * * @param rowId * id of note to delete * @return true if deleted, false otherwise */ public boolean deleteTrip(long rowId) { int numRows = mDb.delete(DATA_TABLE_TRIPS, K_TRIP_ROWID + "=" + rowId, null); Log.i(MODULE_TAG, "Deleted " + DATA_TABLE_TRIPS + "[" + String.valueOf(rowId) +"]: " + String.valueOf(numRows) + " rows."); return numRows > 0; } /** * Return a Cursor over the list of all notes in the database * * @return Cursor over all trips */ public Cursor fetchAllTrips() { Cursor c = mDb.query(DATA_TABLE_TRIPS, new String[] { K_TRIP_ROWID, K_TRIP_PURP, K_TRIP_START, K_TRIP_FANCYSTART, K_TRIP_NOTE, K_TRIP_FANCYINFO, K_TRIP_END, K_TRIP_DISTANCE, K_TRIP_STATUS }, null, null, null, null, "-" + K_TRIP_START); if (c != null && c.getCount() > 0) { c.moveToFirst(); } return c; } public Cursor fetchUnsentTrips() { Cursor c = mDb.query(DATA_TABLE_TRIPS, new String[] { K_TRIP_ROWID }, K_TRIP_STATUS + "=" + TripData.STATUS_COMPLETE, null, null, null, null); if (c != null && c.getCount() > 0) { c.moveToFirst(); } return c; } public int cleanTripsCoordsTables() { int badTrips = 0; Cursor c = mDb.query(DATA_TABLE_TRIPS, new String[] { K_TRIP_ROWID, K_TRIP_STATUS }, K_TRIP_STATUS + "=" + TripData.STATUS_INCOMPLETE, null, null, null, null); if (c != null && c.getCount() > 0) { c.moveToFirst(); badTrips = c.getCount(); while (!c.isAfterLast()) { long tripid = c.getInt(0); deleteAllCoordsForTrip(tripid); deletePauses(tripid); deleteAnswers(tripid); c.moveToNext(); } } c.close(); if (badTrips > 0) { mDb.delete(DATA_TABLE_TRIPS, K_TRIP_STATUS + "=" + TripData.STATUS_INCOMPLETE, null); } return badTrips; } /** * Return a Cursor positioned at the trip that matches the given rowId * * @param rowId * id of trip to retrieve * @return Cursor positioned to matching trip, if found * @throws SQLException * if trip could not be found/retrieved */ public Cursor fetchTrip(long rowId) throws SQLException { Cursor mCursor = mDb.query(true, DATA_TABLE_TRIPS, new String[] { K_TRIP_ROWID, K_TRIP_PURP, K_TRIP_START, K_TRIP_FANCYSTART, K_TRIP_NOTE, K_TRIP_LATHI, K_TRIP_LATLO, K_TRIP_LGTHI, K_TRIP_LGTLO, K_TRIP_STATUS, K_TRIP_END, K_TRIP_FANCYINFO, K_TRIP_DISTANCE }, K_TRIP_ROWID + "=" + rowId, null, null, null, null, null); if (mCursor != null) { mCursor.moveToFirst(); } return mCursor; } public boolean updateTrip(long tripid, String fancystart, String fancyinfo, String note) { int numRows; ContentValues contentValues = new ContentValues(); contentValues.put(K_TRIP_FANCYSTART, fancystart); contentValues.put(K_TRIP_FANCYINFO, fancyinfo); contentValues.put(K_TRIP_NOTE, note); numRows = mDb.update(DATA_TABLE_TRIPS, contentValues, K_TRIP_ROWID + "=" + tripid, null); /* Log.i(MODULE_TAG, "Updated " + DATA_TABLE_TRIPS + "[" + String.valueOf(tripid) + "](" + K_TRIP_FANCYSTART + ", " + K_TRIP_FANCYINFO + ", " + K_TRIP_NOTE +"): " + String.valueOf(numRows) + " rows."); */ return numRows > 0; } public void updateTripPurpose(long tripid, String purpose) { int numRows; ContentValues contentValues = new ContentValues(); contentValues.put(K_TRIP_PURP, purpose); numRows = mDb.update(DATA_TABLE_TRIPS, contentValues, K_TRIP_ROWID + "=" + tripid, null); /* Log.i(MODULE_TAG, "Updated " + DATA_TABLE_TRIPS + "[" + String.valueOf(tripid) + "](" + K_TRIP_PURP + "): " + String.valueOf(numRows) + " rows."); */ return; } public boolean updateTrip(long tripid, int lathigh, int latlow, int lgthigh, int lgtlow, float distance) { int numRows; ContentValues contentValues = new ContentValues(); contentValues.put(K_TRIP_LATHI, lathigh); contentValues.put(K_TRIP_LATLO, latlow); contentValues.put(K_TRIP_LGTHI, lgthigh); contentValues.put(K_TRIP_LGTLO, lgtlow); contentValues.put(K_TRIP_DISTANCE, distance); numRows = mDb.update(DATA_TABLE_TRIPS, contentValues, K_TRIP_ROWID + "=" + tripid, null); /* Log.i(MODULE_TAG, "Updated " + DATA_TABLE_TRIPS + "[" + String.valueOf(tripid) + "](" + K_TRIP_LATHI + ", " + K_TRIP_LATLO + ", " + K_TRIP_LGTHI + ", " + K_TRIP_LGTLO + ", " + K_TRIP_DISTANCE +"): " + String.valueOf(numRows) + " rows."); */ return numRows > 0; } public boolean updateTripStatus(long tripid, int tripStatus) { int numRows; ContentValues initialValues = new ContentValues(); initialValues.put(K_TRIP_STATUS, tripStatus); numRows = mDb.update(DATA_TABLE_TRIPS, initialValues, K_TRIP_ROWID + "=" + tripid, null); Log.i(MODULE_TAG, "Updated " + DATA_TABLE_TRIPS + "[" + String.valueOf(tripid) + "](" + K_TRIP_STATUS +"): " + String.valueOf(numRows) + " rows."); return numRows > 0; } // ************************************************************************ // * Notes table methods // ************************************************************************ /** * Create a new note using the data provided. If the note is successfully * created return the new rowId for that note, otherwise return a -1 to * indicate failure. */ public long createNote(long tripId, long recorded) { int severity = -1; String fancyStartTime = (new SimpleDateFormat("MMMM d, y HH:mm a")).format(recorded); String details = ""; String imgUrl = ""; ContentValues initialValues = new ContentValues(); initialValues.put(K_NOTE_TRIP_ID, tripId); initialValues.put(K_NOTE_SEVERITY, severity); initialValues.put(K_NOTE_RECORDED, (double) recorded); initialValues.put(K_NOTE_FANCYSTART, fancyStartTime); initialValues.put(K_NOTE_DETAILS, details); initialValues.put(K_NOTE_IMGURL, imgUrl); initialValues.put(K_NOTE_LAT, 0); initialValues.put(K_NOTE_LGT, 0); initialValues.put(K_NOTE_ACC, 0); initialValues.put(K_NOTE_ALT, 0); initialValues.put(K_NOTE_SPEED, 0); initialValues.put(K_NOTE_REPORT_DATE, 0); initialValues.put(K_NOTE_EMAIL_SENT, 0); initialValues.put(K_NOTE_STATUS, NoteData.STATUS_INCOMPLETE); return mDb.insert(DATA_TABLE_NOTES, null, initialValues); } /** * Delete the note with the given rowId * * @param rowId * id of note to delete * @return true if deleted, false otherwise */ public boolean deleteNote(long rowId) { deleteNoteImageFile(rowId); return mDb.delete(DATA_TABLE_NOTES, K_NOTE_ROWID + "=" + rowId, null) > 0; } /** * Return a Cursor over the list of all notes in the database * * @return Cursor over all notes */ public Cursor fetchAllNotes() { Cursor cursor = mDb.query(DATA_TABLE_NOTES, new String[] { K_NOTE_ROWID, K_NOTE_TRIP_ID, K_NOTE_SEVERITY, K_NOTE_RECORDED, K_NOTE_FANCYSTART, K_NOTE_DETAILS, K_NOTE_IMGURL, K_NOTE_LAT, K_NOTE_LGT, K_NOTE_ACC, K_NOTE_ALT, K_NOTE_SPEED, K_NOTE_STATUS }, null, null, null, null, "-" + K_NOTE_RECORDED); if ((cursor != null) && (cursor.getCount() > 0)) { cursor.moveToFirst(); } return cursor; } public Cursor fetchUnsentNotes() { Cursor c = mDb.query(DATA_TABLE_NOTES, new String[] { K_NOTE_ROWID }, K_NOTE_STATUS + "=" + NoteData.STATUS_COMPLETE, null, null, null, null); if (c != null && c.getCount() > 0) { c.moveToFirst(); } return c; } public int cleanNoteTables() { int badNotes = 0; Cursor c = mDb.query(DATA_TABLE_NOTES, new String[] { K_NOTE_ROWID, K_NOTE_STATUS }, K_NOTE_STATUS + "=" + NoteData.STATUS_INCOMPLETE, null, null, null, null); if (c != null && c.getCount() > 0) { c.moveToFirst(); badNotes = c.getCount(); while (!c.isAfterLast()) { c.moveToNext(); } } c.close(); if (badNotes > 0) { mDb.delete(DATA_TABLE_NOTES, K_NOTE_STATUS + "=" + NoteData.STATUS_INCOMPLETE, null); } return badNotes; } /** * Return a Cursor positioned at the note that matches the given rowId * * @param rowId * id of note to retrieve * @return Cursor positioned to matching note, if found * @throws SQLException * if note could not be found/retrieved */ public Cursor fetchNote(long rowId) throws SQLException { Cursor mCursor = mDb.query(true, DATA_TABLE_NOTES, new String[] { K_NOTE_ROWID, K_NOTE_TRIP_ID, K_NOTE_SEVERITY, K_NOTE_RECORDED, K_NOTE_FANCYSTART, K_NOTE_DETAILS, K_NOTE_IMGURL, K_NOTE_LAT, K_NOTE_LGT, K_NOTE_ACC, K_NOTE_ALT, K_NOTE_SPEED, K_NOTE_STATUS, K_NOTE_REPORT_DATE, K_NOTE_EMAIL_SENT }, K_NOTE_ROWID + "=" + rowId, null, null, null, null, null); if (mCursor != null) { mCursor.moveToFirst(); } return mCursor; } public int getNoteStatus(long noteId) { Cursor cursor = mDb.query(true, DATA_TABLE_NOTES, new String[] { K_NOTE_STATUS }, K_NOTE_ROWID + "=" + noteId, null, null, null, null, null); if ((cursor != null) && (cursor.getCount() > 0)) { cursor.moveToFirst(); int noteStatus = cursor.getInt(cursor.getColumnIndex(DbAdapter.K_NOTE_STATUS)); return noteStatus; } return -1; } public boolean updateNote(long noteid, int latitude, int longitude, float accuracy, double altitude, float speed) { ContentValues contentValues = new ContentValues(); contentValues.put(K_NOTE_LAT, latitude); contentValues.put(K_NOTE_LGT, longitude); contentValues.put(K_NOTE_ACC, accuracy); contentValues.put(K_NOTE_ALT, altitude); contentValues.put(K_NOTE_SPEED, speed); int numRows = mDb.update(DATA_TABLE_NOTES, contentValues, K_NOTE_ROWID + "=" + noteid, null); /* Log.i(MODULE_TAG, "Updated " + DATA_TABLE_NOTES + "[" + String.valueOf(noteid) + "](" + K_NOTE_LAT + ", " + K_NOTE_LGT + ", " + K_NOTE_ACC + ", " + K_NOTE_ALT + ", " + K_NOTE_SPEED +"): " + String.valueOf(numRows) + " rows."); */ return numRows > 0; } public boolean updateNote(long noteId, String noteDetails, byte[] imageBytes) { ContentValues contentValues = new ContentValues(); contentValues.put(K_NOTE_DETAILS, noteDetails); if (null == imageBytes) { contentValues.put(K_NOTE_IMGURL, ""); } else { contentValues.put(K_NOTE_IMGURL, getNoteImageFileName(noteId)); saveToFile(noteId, imageBytes); } int numRows = mDb.update(DATA_TABLE_NOTES, contentValues, K_NOTE_ROWID + "=" + noteId, null); return numRows > 0; } public boolean updateNoteSeverity(long noteId, int noteSeverity) { ContentValues contentValues = new ContentValues(); contentValues.put(K_NOTE_SEVERITY, noteSeverity); int numRows = mDb.update(DATA_TABLE_NOTES, contentValues, K_NOTE_ROWID + "=" + noteId, null); return numRows > 0; } public boolean updateNoteReportDate(long noteId, long reportDate) { ContentValues contentValues = new ContentValues(); contentValues.put(K_NOTE_REPORT_DATE, reportDate); int numRows = mDb.update(DATA_TABLE_NOTES, contentValues, K_NOTE_ROWID + "=" + noteId, null); return numRows > 0; } public boolean updateNoteEmailSent(long noteId, boolean value) { ContentValues contentValues = new ContentValues(); contentValues.put(K_NOTE_EMAIL_SENT, value == false ? 0 : 1); int numRows = mDb.update(DATA_TABLE_NOTES, contentValues, K_NOTE_ROWID + "=" + noteId, null); return numRows > 0; } public String getNoteImageFileName(long noteId) { return noteImagesDirName + "/note_image_" + noteId + ".jpg"; } private void saveToFile(long noteId, byte[] imageBytes) { String fileName = getNoteImageFileName(noteId); File file = new File(fileName); OutputStream out = null; try { out = new BufferedOutputStream(new FileOutputStream(file)); out.write(imageBytes); } catch(Exception ex) { Log.e(MODULE_TAG, ex.getMessage()); } finally { if (out != null) { try { out.close(); } catch(Exception ex) { Log.e(MODULE_TAG, ex.getMessage()); } } } } private void deleteNoteImageFile(long noteId) { String fileName = getNoteImageFileName(noteId); File file; try { file = new File(fileName); if (file.exists()) { file.delete(); } } catch(Exception ex) { Log.e(MODULE_TAG, ex.getMessage()); } } public byte[] getNoteImageData(long noteId) { String fileName = getNoteImageFileName(noteId); File file = new File(fileName); InputStream in = null; byte[] bytes = null; try { if (file.exists()) { in = new FileInputStream(file); bytes = new byte[(int) file.length()]; in.read(bytes); } } catch(Exception ex) { Log.e(MODULE_TAG, ex.getMessage()); bytes = null; } finally { if (in != null) { try { in.close(); } catch (IOException ex) { Log.e(MODULE_TAG, ex.getMessage()); } } } return bytes; } public boolean updateNoteStatus(long noteid, int noteStatus) { ContentValues initialValues = new ContentValues(); initialValues.put(K_NOTE_STATUS, noteStatus); int numRows = mDb.update(DATA_TABLE_NOTES, initialValues, K_NOTE_ROWID + "=" + noteid, null); Log.v(MODULE_TAG, "updateNoteStatus[" + String.valueOf(noteid) + "](" + String.valueOf(noteStatus) +"): " + String.valueOf(numRows) + " rows."); return numRows > 0; } // ************************************************************************ // * Pauses table methods // ************************************************************************ /** * Insert a row into the 'pauses' table * @param tripId Trip ID of associated trip * @param startTime Starting date-time of the pause * @param endTime Ending date-time of the pause * @throws SQLException */ public void addPauseToTrip(long tripId, double startTime, double endTime) throws SQLException{ // Assemble row data ContentValues rowValues = new ContentValues(); rowValues.put(K_PAUSE_TRIP_ID, tripId); rowValues.put(K_PAUSE_START_TIME, startTime); rowValues.put(K_PAUSE_END_TIME, endTime); // Insert row in table mDb.insertOrThrow(DATA_TABLE_PAUSES, null, rowValues); } /** * Delete pauses with the given trip ID * @param tripId id of the pauses to delete */ public void deletePauses(long tripId) { mDb.delete(DATA_TABLE_PAUSES, K_PAUSE_TRIP_ID + "=" + tripId, null); } /** * Return a Cursor positioned at the pause that matches the given rowId * * @param rowId * id of trip to retrieve * @return Cursor positioned to matching trip, if found * @throws SQLException * if trip could not be found/retrieved */ public Cursor fetchPauses(long tripId) throws SQLException { Cursor cursor; String[] columns = new String[] { K_PAUSE_START_TIME, K_PAUSE_END_TIME }; String whereClause = K_PAUSE_TRIP_ID + "=" + tripId; if (null != (cursor = mDb.query(true, DATA_TABLE_PAUSES, columns, whereClause, null, null, null, null, null))) { cursor.moveToFirst(); } return cursor; } // ************************************************************************ // * Answers table methods // ************************************************************************ /** * Insert a row into the 'answers' table, other is set to blank * @param trip_id Trip ID of associated trip * @param question_id ID of question being answered * @param answer_id ID of answer * @throws SQLException */ public void addAnswerToTrip(long trip_id, int question_id, int answer_id) throws SQLException{ addAnswerToTrip(trip_id, question_id, answer_id, ""); } /** * Insert a row into the 'answers' table * @param trip_id Trip ID of associated trip * @param question_id ID of question being answered * @param answer_id ID of answer * @param other_text value of other * @throws SQLException */ public void addAnswerToTrip(long trip_id, int question_id, int answer_id, String other_text) throws SQLException{ // Assemble row data ContentValues rowValues = new ContentValues(); rowValues.put(K_ANSWER_TRIP_ID, trip_id); rowValues.put(K_ANSWER_QUESTION_ID, question_id); rowValues.put(K_ANSWER_ANSWER_ID, answer_id); rowValues.put(K_ANSWER_OTHER_TEXT, other_text); // Insert row in table mDb.insertOrThrow(DATA_TABLE_ANSWERS, null, rowValues); } /** * Delete answers with the given trip ID * @param trip_id id of the pauses to delete */ public void deleteAnswers(long trip_id) { mDb.delete(DATA_TABLE_ANSWERS, K_ANSWER_TRIP_ID + "=" + trip_id, null); } /** * Return a Cursor positioned at the answers that matches the given trip_id * * @param trip_id ID of trip to retrieve * @return Cursor positioned to matching trip, if found * @throws SQLException if trip could not be found/retrieved */ public Cursor fetchTripAnswers(long trip_id) throws SQLException { Cursor cursor; String[] columns = new String[] { K_ANSWER_QUESTION_ID, K_ANSWER_ANSWER_ID, K_ANSWER_OTHER_TEXT }; String whereClause = K_ANSWER_TRIP_ID + "=" + trip_id; if (null != (cursor = mDb.query(true, DATA_TABLE_ANSWERS, columns, whereClause, null, null, null, null, null))) { cursor.moveToFirst(); } return cursor; } // ************************************************************************ // * Note Answers table methods // ************************************************************************ /** * Insert a row into the 'answers' table * @param note_id Note ID of associated trip * @param question_id ID of question being answered * @param answer_id ID of answer * @throws SQLException */ public void addAnswerToNote(long note_id, int question_id, int answer_id) throws SQLException{ addAnswerToNote(note_id, question_id, answer_id, ""); } /** * Insert a row into the 'answers' table * @param note_id Note ID of associated trip * @param question_id ID of question being answered * @param answer_id ID of answer * @param other_text value of other * @throws SQLException */ public void addAnswerToNote(long note_id, int question_id, int answer_id, String other_text) throws SQLException{ // Assemble row data ContentValues rowValues = new ContentValues(); rowValues.put(K_NOTE_ANSWER_NOTE_ID, note_id); rowValues.put(K_NOTE_ANSWER_QUESTION_ID, question_id); rowValues.put(K_NOTE_ANSWER_ANSWER_ID, answer_id); rowValues.put(K_NOTE_ANSWER_OTHER_TEXT, other_text); // Insert row in table mDb.insertOrThrow(DATA_TABLE_NOTE_ANSWERS, null, rowValues); } /** * Delete answers with the given note ID * @param note_id id of the pauses to delete */ public void deleteNoteAnswers(long note_id) { mDb.delete(DATA_TABLE_NOTE_ANSWERS, K_NOTE_ANSWER_NOTE_ID + "=" + note_id, null); } /** * Return a Cursor positioned at the answers that matches the given note_id * * @param note_id ID of trip to retrieve * @return Cursor positioned to matching trip, if found * @throws SQLException if trip could not be found/retrieved */ public Cursor fetchNoteAnswers(long note_id) throws SQLException { Cursor cursor; String[] columns = new String[] { K_NOTE_ANSWER_QUESTION_ID, K_NOTE_ANSWER_ANSWER_ID, K_NOTE_ANSWER_OTHER_TEXT }; String whereClause = K_NOTE_ANSWER_NOTE_ID + "=" + note_id; if (null != (cursor = mDb.query(true, DATA_TABLE_NOTE_ANSWERS, columns, whereClause, null, null, null, null, null))) { cursor.moveToFirst(); } return cursor; } // ************************************************************************ // * Segments table methods // ************************************************************************ /** * Return a Cursor positioned at the Segment that matches the given segmentId * * @param segmentId id of note to retrieve * @return Cursor positioned to matching note, if found * @throws SQLException * if note could not be found/retrieved */ public Cursor fetchSegment(long segmentId) throws SQLException { Cursor mCursor = mDb.query(true, DATA_TABLE_SEGMENTS, new String[] { K_SEGMENT_ID, K_SEGMENT_TRIP_ID, K_SEGMENT_RATING, K_SEGMENT_DETAILS, K_SEGMENT_START_INDEX, K_SEGMENT_END_INDEX, K_SEGMENT_STATUS}, K_SEGMENT_ID + "=" + segmentId, null, null, null, null, null); if (mCursor != null) { mCursor.moveToFirst(); } return mCursor; } public long createSegment() { return createSegment(-1, -1, "", -1, -1); } /** * Create a new segment using the data provided. If the segment is successfully * created return the new rowId for that segment, otherwise return a -1 to * indicate failure. */ public long createSegment(long tripId, int rating, String details, int startIndex, int endIndex) { ContentValues initialValues = new ContentValues(); initialValues.put(K_SEGMENT_TRIP_ID, tripId); initialValues.put(K_SEGMENT_RATING, rating); initialValues.put(K_SEGMENT_DETAILS, details); initialValues.put(K_SEGMENT_START_INDEX, startIndex); initialValues.put(K_SEGMENT_END_INDEX, endIndex); initialValues.put(K_SEGMENT_STATUS, SegmentData.STATUS_INCOMPLETE); long segmentId = mDb.insert(DATA_TABLE_SEGMENTS, null, initialValues); return segmentId; } public boolean updateSegmentStatus(long segmentId, int status) { ContentValues initialValues = new ContentValues(); initialValues.put(K_SEGMENT_STATUS, status); return mDb.update(DATA_TABLE_SEGMENTS, initialValues, K_SEGMENT_ID + "=" + segmentId, null) > 0; } public boolean updateSegment(long segmentId, long tripId, int rating, String details, int startIndex, int endIndex) { ContentValues initialValues = new ContentValues(); initialValues.put(K_SEGMENT_TRIP_ID, tripId); initialValues.put(K_SEGMENT_RATING , rating); initialValues.put(K_SEGMENT_DETAILS, details); initialValues.put(K_SEGMENT_START_INDEX, startIndex); initialValues.put(K_SEGMENT_END_INDEX, endIndex); return mDb.update(DATA_TABLE_SEGMENTS, initialValues, K_SEGMENT_ID + "=" + segmentId, null) > 0; } public Cursor fetchUnsentSegmentIds() { Cursor c = mDb.query(DATA_TABLE_SEGMENTS, new String[] { K_SEGMENT_ID }, K_SEGMENT_STATUS + "=" + SegmentData.STATUS_COMPLETE, null, null, null, null); if (c != null && c.getCount() > 0) { c.moveToFirst(); } return c; } // ************************************************************************ // * Reminders table methods // ************************************************************************ /** * Return a Cursor over the list of all reminders in the database * * @return Cursor over all notes */ public Cursor fetchAllReminders() { Cursor cursor = mDb.query(DATA_TABLE_REMINDERS, new String[] { K_REMINDER_ID, K_REMINDER_NAME, K_REMINDER_DAYS, K_REMINDER_HOURS, K_REMINDER_MINUTES, K_REMINDER_ENABLED }, null, null, null, null, K_REMINDER_ID); if ((cursor != null) && (cursor.getCount() > 0)) { cursor.moveToFirst(); } return cursor; } /** * Delete the reminder with the given ID * * @param id * id of note to delete * @return true if deleted, false otherwise */ public boolean deleteReminder(long id) { return mDb.delete(DATA_TABLE_REMINDERS, K_REMINDER_ID + "=" + id, null) > 0; } public long createReminder(String name, int days, int hours, int minutes, boolean enabled) { ContentValues cv = new ContentValues(); cv.put(K_REMINDER_NAME, name); cv.put(K_REMINDER_DAYS, days); cv.put(K_REMINDER_HOURS, hours); cv.put(K_REMINDER_MINUTES, minutes); cv.put(K_REMINDER_ENABLED, enabled ? 1 : 0); return mDb.insert(DATA_TABLE_REMINDERS, null, cv); } public boolean updateReminder(long id, String name, int days, int hours, int minutes, boolean enabled) { ContentValues cv = new ContentValues(); cv.put(K_REMINDER_NAME, name); cv.put(K_REMINDER_DAYS, days); cv.put(K_REMINDER_HOURS, hours); cv.put(K_REMINDER_MINUTES, minutes); cv.put(K_REMINDER_ENABLED, enabled ? 1 : 0); return mDb.update(DATA_TABLE_REMINDERS, cv, K_REMINDER_ID + "=" + id, null) > 0; } /** * Return a Cursor positioned at the reminder that matches the given reminderId * * @param rowId * id of note to retrieve * @return Cursor positioned to matching note, if found * @throws SQLException * if note could not be found/retrieved */ public Cursor fetchReminder(long reminderID) throws SQLException { // Columns to retrieve String[] columns = new String[] {K_REMINDER_NAME, K_REMINDER_DAYS, K_REMINDER_HOURS, K_REMINDER_MINUTES, K_REMINDER_ENABLED}; String whereClause = K_REMINDER_ID + "=" + reminderID; Cursor mCursor = mDb.query(true, DATA_TABLE_REMINDERS, columns, whereClause, null, null, null, null, null); if (mCursor != null) { mCursor.moveToFirst(); } return mCursor; } }