package ca.cumulonimbus.pressurenetsdk; import java.util.ArrayList; import java.util.Random; import android.content.ContentValues; import android.content.Context; import android.content.pm.PackageManager; import android.content.pm.PackageManager.NameNotFoundException; import android.database.Cursor; import android.database.DatabaseUtils; import android.database.SQLException; import android.database.sqlite.SQLiteDatabase; import android.database.sqlite.SQLiteDatabaseLockedException; import android.database.sqlite.SQLiteOpenHelper; import android.database.sqlite.SQLiteStatement; import android.location.Location; /** * Keep track of app settings, as this SDK may be used by more than one app on a * device. This allows an empty Intent to start the Service generically, and we * can then read saved settings for each registered app to act accordingly. * * @author jacob * */ public class CbDb { // Tables public static final String SETTINGS_TABLE = "cb_settings"; public static final String OBSERVATIONS_TABLE = "cb_observations"; public static final String CURRENT_CONDITIONS_TABLE = "cb_current_conditions"; public static final String API_LIST_TABLE = "cb_api_list"; public static final String APP_REGISTRATION_TABLE = "cb_registration"; // Settings Fields public static final String KEY_ROW_ID = "_id"; public static final String KEY_APP_ID = "app_id"; public static final String KEY_DATA_COLLECTION_FREQUENCY = "data_frequency"; public static final String KEY_SERVER_URL = "server_url"; public static final String KEY_ONLY_WHEN_CHARGING = "only_when_charging"; public static final String KEY_COLLECTING_DATA = "collecting_data"; public static final String KEY_SHARING_DATA = "sharing_data"; public static final String KEY_SHARE_LEVEL = "share_level"; public static final String KEY_SEND_NOTIFICATIONS = "send_notifications"; // Observation Fields public static final String KEY_LATITUDE = "latitude"; public static final String KEY_LONGITUDE = "longitude"; public static final String KEY_ALTITUDE = "altitude"; public static final String KEY_ACCURACY = "accuracy"; public static final String KEY_PROVIDER = "provider"; public static final String KEY_OBSERVATION_TYPE = "observation_type"; public static final String KEY_OBSERVATION_UNIT = "observation_unit"; public static final String KEY_OBSERVATION_VALUE = "observation_value"; public static final String KEY_SHARING = "sharing"; public static final String KEY_TIME = "time"; public static final String KEY_TIMEZONE = "timezone"; public static final String KEY_USERID = "user_id"; public static final String KEY_SENSOR_NAME = "sensor_name"; public static final String KEY_SENSOR_TYPE = "sensor_type"; public static final String KEY_SENSOR_VENDOR = "sensor_vendor"; public static final String KEY_SENSOR_RESOLUTION = "sensor_resolution"; public static final String KEY_SENSOR_VERSION = "sensor_version"; public static final String KEY_OBSERVATION_TREND = "observation_trend"; // Current Conditions Fields // + KEY_LATITUDE, KEY_LONGITUDE, KEY_ALTITUDE, KEY_ACCURACY, KEY_PROVIDER, // KEY_TIME, KEY_TIMEZONE, KEY_SHARING, KEY_USERID, public static final String KEY_GENERAL_CONDITION = "general_condition"; public static final String KEY_WINDY = "windy"; public static final String KEY_FOGGY = "foggy"; public static final String KEY_CLOUD_TYPE = "cloud_type"; public static final String KEY_PRECIPITATION_TYPE = "precipitation_type"; public static final String KEY_PRECIPITATION_AMOUNT = "precipitation_amount"; public static final String KEY_PRECIPITATION_UNIT = "precipitation_unit"; public static final String KEY_THUNDERSTORM_INTENSITY = "thunderstorm_intensity"; public static final String KEY_USER_COMMENT = "user_comment"; // Observation /list/ public static final String KEY_MIN_LAT = "min_lat"; public static final String KEY_MAX_LAT = "max_lat"; public static final String KEY_MIN_LON = "min_lon"; public static final String KEY_MAX_LON = "max_lon"; public static final String KEY_USE_GPS = "use_gps"; // APP_REGISTRATION_TABLE public static final String KEY_REGISTRATION_TIME = "registration_time"; public static final String KEY_PACKAGE_NAME = "package_name"; private Context mContext; private DatabaseHelper mDbHelper; private SQLiteDatabase mDB; private static final String SETTINGS_TABLE_CREATE = "create table " + SETTINGS_TABLE + " (_id integer primary key autoincrement, " + KEY_APP_ID + " text not null, " + KEY_DATA_COLLECTION_FREQUENCY + " real not null, " + KEY_SERVER_URL + " text not null, " + KEY_ONLY_WHEN_CHARGING + " text, " + KEY_COLLECTING_DATA + " text, " + KEY_SHARING_DATA + " text," + KEY_SHARE_LEVEL + " text," + KEY_SEND_NOTIFICATIONS + " text," + KEY_USE_GPS + " text)"; private static final String OBSERVATIONS_TABLE_CREATE = "create table " + OBSERVATIONS_TABLE + " (_id integer primary key autoincrement, " + KEY_LATITUDE + " real not null, " + KEY_LONGITUDE + " real not null, " + KEY_ALTITUDE + " real not null, " + KEY_ACCURACY + " real not null, " + KEY_PROVIDER + " text not null, " + KEY_OBSERVATION_TYPE + " text not null, " + KEY_OBSERVATION_UNIT + " text not null, " + KEY_OBSERVATION_VALUE + " real not null, " + KEY_SHARING + " text not null, " + KEY_TIME + " real not null, " + KEY_TIMEZONE + " real not null, " + KEY_USERID + " text not null, " + KEY_SENSOR_NAME + " text , " + KEY_SENSOR_TYPE + " real , " + KEY_SENSOR_VENDOR + " text , " + KEY_SENSOR_RESOLUTION + " real , " + KEY_SENSOR_VERSION + " real ," + KEY_OBSERVATION_TREND + " text," + "UNIQUE (" + KEY_LATITUDE + ", " + KEY_LONGITUDE + "," + KEY_TIME + ", " + KEY_USERID + "," + KEY_OBSERVATION_VALUE + ") ON CONFLICT REPLACE)"; private static final String OBSERVATIONS_TABLE_IDX = "observations_table_idx"; private static final String API_LIST_IDX = "api_list_idx"; private static final String CONDITIONS_IDX = "conditions_idx"; private static final String API_LIST_TABLE_CREATE = "create table " + API_LIST_TABLE + " (_id integer primary key autoincrement, " + KEY_LATITUDE + " real not null, " + KEY_LONGITUDE + " real not null, " + KEY_ALTITUDE + " real not null, " + KEY_TIME + " real not null, " + KEY_OBSERVATION_VALUE + " real not null, UNIQUE (" + KEY_OBSERVATION_VALUE +", " + KEY_TIME + ") ON CONFLICT REPLACE)"; private static final String CURRENT_CONDITIONS_TABLE_CREATE = "create table " + CURRENT_CONDITIONS_TABLE + " (_id integer primary key autoincrement, " + KEY_LATITUDE + " real not null, " + KEY_LONGITUDE + " real not null, " + KEY_ALTITUDE + " real not null, " + KEY_ACCURACY + " real not null, " + KEY_PROVIDER + " text not null, " + KEY_SHARING + " text not null, " + KEY_TIME + " real not null, " + KEY_TIMEZONE + " real not null, " + KEY_USERID + " text not null, " + KEY_GENERAL_CONDITION + " text not null, " + KEY_WINDY + " text not null, " + KEY_FOGGY + " text not null, " + KEY_CLOUD_TYPE + " text not null, " + KEY_PRECIPITATION_TYPE + " text not null, " + KEY_PRECIPITATION_AMOUNT + " real not null, " + KEY_PRECIPITATION_UNIT + " text not null, " + KEY_THUNDERSTORM_INTENSITY + " real not null, " + KEY_USER_COMMENT + " text not null, " + "UNIQUE (" + KEY_LATITUDE + ", " + KEY_LONGITUDE + "," + KEY_TIME + "," + KEY_GENERAL_CONDITION + ") ON CONFLICT IGNORE)"; private static final String APP_REGISTRATION_TABLE_CREATE = "create table " + APP_REGISTRATION_TABLE + " (_id integer primary key autoincrement, " + KEY_PACKAGE_NAME + " text not null, " + KEY_REGISTRATION_TIME + " real not null, UNIQUE ( " + KEY_PACKAGE_NAME + ") ON CONFLICT IGNORE)"; private static final String DATABASE_NAME = "CbDb"; private static final int DATABASE_VERSION = 50; // 40 = 4.2.7 // 41+ = 4.3.0 // 49-50 = 4.4 private static class DatabaseHelper extends SQLiteOpenHelper { DatabaseHelper(Context context) { super(context, DATABASE_NAME, null, DATABASE_VERSION); } @Override public void onCreate(SQLiteDatabase db) { db.execSQL(SETTINGS_TABLE_CREATE); db.execSQL(OBSERVATIONS_TABLE_CREATE); db.execSQL(CURRENT_CONDITIONS_TABLE_CREATE); db.execSQL(API_LIST_TABLE_CREATE); db.execSQL(APP_REGISTRATION_TABLE_CREATE); createIndex(db); } private void createIndex(SQLiteDatabase db) { String indexObs = "Create Index IF NOT EXISTS " + OBSERVATIONS_TABLE_IDX + " ON " + OBSERVATIONS_TABLE + "(" + KEY_TIME + ")"; String indexApi = "Create Index IF NOT EXISTS " + API_LIST_IDX + " ON " + API_LIST_TABLE + "(" + KEY_TIME + ", " + KEY_LATITUDE + ", " + KEY_LONGITUDE + ")"; String indexConditions = "Create Index IF NOT EXISTS " + CONDITIONS_IDX + " ON " + CURRENT_CONDITIONS_TABLE + "(" + KEY_TIME + ", " + KEY_LATITUDE + ", " + KEY_LONGITUDE + ")"; String indexApiAltitude = "Create Index IF NOT EXISTS " + API_LIST_IDX + " ON " + API_LIST_TABLE + "(" + KEY_ALTITUDE + ")"; db.execSQL(indexObs); db.execSQL(indexApi); db.execSQL(indexConditions); db.execSQL(indexApiAltitude); } @Override public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { // TODO: Build upgrade mechanism /* db.execSQL("DROP TABLE IF EXISTS " + SETTINGS_TABLE); db.execSQL("DROP TABLE IF EXISTS " + OBSERVATIONS_TABLE); db.execSQL("DROP TABLE IF EXISTS " + CURRENT_CONDITIONS_TABLE); db.execSQL("DROP TABLE IF EXISTS " + API_LIST_TABLE); onCreate(db); */ // TODO: Only add this after checking oldVersion and newVersion db.execSQL("DROP TABLE IF EXISTS " + APP_REGISTRATION_TABLE); db.execSQL(APP_REGISTRATION_TABLE_CREATE); // Add support for API List Altitudes if (oldVersion < 50 ) { db.execSQL("DROP TABLE IF EXISTS " + API_LIST_TABLE); db.execSQL(API_LIST_TABLE_CREATE); } // Add support for API List Altitudes if (oldVersion < 41 ) { db.execSQL("DROP TABLE IF EXISTS " + CURRENT_CONDITIONS_TABLE); db.execSQL(CURRENT_CONDITIONS_TABLE_CREATE); } // Add indexes createIndex(db); } } /** * Check the oldest registered package name * and compare to our package name. * @return */ public boolean isPrimaryApp() { String localPackageName = mContext.getPackageName(); Cursor cursor = mDB.query(APP_REGISTRATION_TABLE, new String[] {KEY_PACKAGE_NAME}, null, null, null, null, KEY_REGISTRATION_TIME + " ASC"); String packageName = ""; if(cursor.moveToFirst()) { packageName = cursor.getString(0); } log("SDKTESTS: checking primary app " + packageName.equals(localPackageName)); return packageName.equals(localPackageName); } private Cursor getAppsList() { return mDB.query(APP_REGISTRATION_TABLE, new String[] {KEY_PACKAGE_NAME, KEY_REGISTRATION_TIME}, null, null, null, null, KEY_REGISTRATION_TIME + " ASC"); } private boolean isPackageInstalled(String packagename, Context context) { PackageManager pm = context.getPackageManager(); try { pm.getPackageInfo(packagename, PackageManager.GET_ACTIVITIES); return true; } catch (NameNotFoundException e) { return false; } } private void log(String message) { if(CbConfiguration.DEBUG_MODE) { System.out.println(message); } } /** * If an SDK App has not been active for the last * X days, remove it from the list and allow other * apps to take precedence */ public void removeOldSDKApps(int days) { // remove old apps long timeAgo = System.currentTimeMillis() - (days * 24 * 60 * 60 * 1000); mDB.execSQL("delete from " + APP_REGISTRATION_TABLE + " WHERE " + KEY_REGISTRATION_TIME + " < " + timeAgo); // Remove uninstalled apps Cursor c = getAppsList(); ArrayList<CbRegisteredApp> apps = new ArrayList<CbRegisteredApp>(); while(c.moveToNext()) { CbRegisteredApp app = new CbRegisteredApp(); // TODO: fix these constants app.setPackageName(c.getString(0)); app.setRegistrationTime(c.getLong(1)); apps.add(app); } for(CbRegisteredApp app : apps) { boolean installed = isPackageInstalled(app.getPackageName(), mContext); log("SDKTESTS: " + app.getPackageName() + " installed? " + installed); if(!installed) { // TODO: fix poor SQL practices mDB.execSQL("delete from " + APP_REGISTRATION_TABLE + " WHERE " + KEY_PACKAGE_NAME + " = '" + app.getPackageName() + "'"); log("SDKTESTS: removed uninstalled app " + app.getPackageName()); } } } /** * Get all-time pressure count * @return */ public long getAllTimePressureCount() { return DatabaseUtils.queryNumEntries(mDB, OBSERVATIONS_TABLE, null,null); } /** * Get last day pressure count * @return */ public long getLast24hPressureCount() { return DatabaseUtils.queryNumEntries(mDB, OBSERVATIONS_TABLE, "time > ?", new String[] {System.currentTimeMillis() - (1000 * 60 * 60 * 24) + ""}); } /** * Get last week pressure count * @return */ public long getLast7dPressureCount() { return DatabaseUtils.queryNumEntries(mDB, OBSERVATIONS_TABLE, KEY_TIME + " > ?", new String[] {System.currentTimeMillis() - (1000 * 60 * 60 * 24 * 7) + ""}); } /** * Get last week current condition count * @return */ public long getLast7dConditionCount(String id) { return DatabaseUtils.queryNumEntries(mDB, CURRENT_CONDITIONS_TABLE, KEY_TIME + " > ? and " + KEY_USERID + " LIKE ?", new String[] {System.currentTimeMillis() - (1000 * 60 * 60 * 24 * 7) + "", "%" + id + "%"}); } /** * Get last day's current condition count * @return */ public long getLastDayConditionCount(String id) { return DatabaseUtils.queryNumEntries(mDB, CURRENT_CONDITIONS_TABLE, KEY_TIME + " > ? and " + KEY_USERID + " LIKE ?", new String[] {System.currentTimeMillis() - (1000 * 60 * 60 * 24) + "", "%" + id + "%"}); } /** * Get last week current condition count * @return */ public long getAllTimeConditionCount(String id) { log("all time conditions id " + id); return DatabaseUtils.queryNumEntries(mDB, CURRENT_CONDITIONS_TABLE, KEY_USERID + " = ?", new String[] {id}); } /** * Return the total number of recorded and stored measurements * that came directly from this device * @return */ public long getUserDataCount() { return DatabaseUtils.queryNumEntries(mDB, OBSERVATIONS_TABLE, null,null); } /** * This service caches data from the PressureNet API to improve app performance. * Return the number of cached measurements. * @return */ public long getDataCacheCount() { return DatabaseUtils.queryNumEntries(mDB, API_LIST_TABLE, null,null) + DatabaseUtils.queryNumEntries(mDB, CURRENT_CONDITIONS_TABLE, null,null); } /** * Keep the database cache clean */ public void deleteOldCacheData() { long hoursAgo = 24*3; long timeAgo = System.currentTimeMillis() - (1000 * 60 * 60 * hoursAgo); mDB.execSQL("delete from " + API_LIST_TABLE + " WHERE " + KEY_TIME + " < " + timeAgo); } /** * Get local current conditions * * @return */ public Cursor getCurrentConditions(double min_lat, double max_lat, double min_lon, double max_lon, long start_time, long end_time, double limit) { Cursor cursor = mDB.query(false, CURRENT_CONDITIONS_TABLE, new String[] { KEY_ROW_ID, KEY_LATITUDE, KEY_LONGITUDE, KEY_ALTITUDE, KEY_ACCURACY, KEY_PROVIDER, KEY_SHARING, KEY_TIME, KEY_TIMEZONE, KEY_USERID, KEY_GENERAL_CONDITION, KEY_WINDY, KEY_FOGGY, KEY_CLOUD_TYPE, KEY_PRECIPITATION_TYPE, KEY_PRECIPITATION_AMOUNT, KEY_PRECIPITATION_UNIT, KEY_THUNDERSTORM_INTENSITY, KEY_USER_COMMENT }, KEY_LATITUDE + " > ? and " + KEY_LATITUDE + " < ? and " + KEY_LONGITUDE + " > ? and " + KEY_LONGITUDE + " < ? and " + KEY_TIME + " > ? and " + KEY_TIME + " < ? ", new String[] { min_lat + "", max_lat + "", min_lon + "", max_lon + "", start_time + "", end_time + "" }, null, null, null, null); return cursor; } /** * Get my local current conditions * * @return */ public Cursor getMyCurrentConditions(double min_lat, double max_lat, double min_lon, double max_lon, long start_time, long end_time, double limit, String id) { Cursor cursor = mDB.query(false, CURRENT_CONDITIONS_TABLE, new String[] { KEY_ROW_ID, KEY_LATITUDE, KEY_LONGITUDE, KEY_ALTITUDE, KEY_ACCURACY, KEY_PROVIDER, KEY_SHARING, KEY_TIME, KEY_TIMEZONE, KEY_USERID, KEY_GENERAL_CONDITION, KEY_WINDY, KEY_FOGGY, KEY_CLOUD_TYPE, KEY_PRECIPITATION_TYPE, KEY_PRECIPITATION_AMOUNT, KEY_PRECIPITATION_UNIT, KEY_THUNDERSTORM_INTENSITY, KEY_USER_COMMENT }, KEY_LATITUDE + " > ? and " + KEY_LATITUDE + " < ? and " + KEY_LONGITUDE + " > ? and " + KEY_LONGITUDE + " < ? and " + KEY_TIME + " > ? and " + KEY_TIME + " < ? and " + KEY_USERID + "=?", new String[] { min_lat + "", max_lat + "", min_lon + "", max_lon + "", start_time + "", end_time + "", id}, null, null, null, null); return cursor; } /** * Run an API call against the API cache * * @return */ public Cursor runAPICacheCall(double min_lat, double max_lat, double min_lon, double max_lon, long start_time, long end_time, double limit) { Cursor cursor = mDB.query(false, API_LIST_TABLE, new String[] { KEY_ROW_ID, KEY_LATITUDE, KEY_LONGITUDE, KEY_ALTITUDE, KEY_OBSERVATION_VALUE, KEY_TIME }, KEY_LATITUDE + " > ? and " + KEY_LATITUDE + " < ? and " + KEY_LONGITUDE + " > ? and " + KEY_LONGITUDE + " < ? and " + KEY_TIME + " > ? and " + KEY_TIME + " < ? ", new String[] { min_lat + "", max_lat + "", min_lon + "", max_lon + "", start_time + "", end_time + "" }, null, null, null, null); return cursor; } /** * Run an "API call" against the local database * * @return */ public Cursor runLocalAPICall(double min_lat, double max_lat, double min_lon, double max_lon, long start_time, long end_time, double limit) { Cursor cursor = mDB.query(false, OBSERVATIONS_TABLE, new String[] { KEY_ROW_ID, KEY_LATITUDE, KEY_LONGITUDE, KEY_ALTITUDE, KEY_ACCURACY, KEY_PROVIDER, KEY_OBSERVATION_TYPE, KEY_OBSERVATION_UNIT, KEY_OBSERVATION_VALUE, KEY_SHARING, KEY_TIME, KEY_TIMEZONE, KEY_USERID, KEY_SENSOR_NAME, KEY_SENSOR_TYPE, KEY_SENSOR_VENDOR, KEY_SENSOR_RESOLUTION, KEY_SENSOR_VERSION, KEY_OBSERVATION_TREND }, KEY_LATITUDE + " > ? and " + KEY_LATITUDE + " < ? and " + KEY_LONGITUDE + " > ? and " + KEY_LONGITUDE + " < ? and " + KEY_TIME + " > ? and " + KEY_TIME + " < ? ", new String[] { min_lat + "", max_lat + "", min_lon + "", max_lon + "", start_time + "", end_time + "" }, null, null, KEY_TIME, null); return cursor; } /** * Get a single observation * * @param rowId * @return * @throws SQLException * */ public Cursor fetchObservation(long rowId) throws SQLException { Cursor mCursor = mDB.query(true, OBSERVATIONS_TABLE, new String[] { KEY_ROW_ID, KEY_LATITUDE, KEY_LONGITUDE, KEY_ALTITUDE, KEY_ACCURACY, KEY_PROVIDER, KEY_OBSERVATION_TYPE, KEY_OBSERVATION_UNIT, KEY_OBSERVATION_VALUE, KEY_SHARING, KEY_TIME, KEY_TIMEZONE, KEY_USERID, KEY_SENSOR_NAME, KEY_SENSOR_TYPE, KEY_SENSOR_VENDOR, KEY_SENSOR_RESOLUTION, KEY_SENSOR_VERSION, KEY_OBSERVATION_TREND }, KEY_ROW_ID + "=" + rowId, null, null, null, null, null); if (mCursor != null) { mCursor.moveToFirst(); } return mCursor; } /** * How many observations are there? * * @param rowId * @return * @throws SQLException * */ public long fetchObservationMaxID() throws SQLException { open(); Cursor mCount = mDB.rawQuery("SELECT COUNT(*) FROM " + OBSERVATIONS_TABLE, null); mCount.moveToFirst(); long rowId = mCount.getInt(0); mCount.close(); return rowId; } /** * Get a single application's settings by row id * * @param rowId * @return * @throws SQLException */ public Cursor fetchSetting(long rowId) throws SQLException { Cursor mCursor = mDB.query(true, SETTINGS_TABLE, new String[] { KEY_ROW_ID, KEY_APP_ID, KEY_DATA_COLLECTION_FREQUENCY, KEY_SERVER_URL, KEY_ONLY_WHEN_CHARGING, KEY_COLLECTING_DATA, KEY_SHARING_DATA, KEY_SHARE_LEVEL, KEY_SEND_NOTIFICATIONS, KEY_USE_GPS }, KEY_ROW_ID + "=" + rowId, null, null, null, null, null); if (mCursor != null) { mCursor.moveToFirst(); } return mCursor; } /** * Clear the local measurements from the device * * @return */ public void clearLocalCache() { mDB.execSQL("delete from " + OBSERVATIONS_TABLE); } /** * Clear the API cache from the device * * @return */ public void clearAPICache() { mDB.execSQL("delete from " + API_LIST_TABLE); } /** * Fetch every application setting. * * @return */ public Cursor fetchAllSettings() { return mDB.query(SETTINGS_TABLE, new String[] { KEY_ROW_ID, KEY_APP_ID, KEY_DATA_COLLECTION_FREQUENCY, KEY_SERVER_URL, KEY_ONLY_WHEN_CHARGING, KEY_COLLECTING_DATA, KEY_SHARING_DATA, KEY_SHARE_LEVEL, KEY_SEND_NOTIFICATIONS, KEY_USE_GPS}, null, null, null, null, null); } /** * Fetch every stored current condition * * @return */ public Cursor fetchAllConditions() { return mDB.query(CURRENT_CONDITIONS_TABLE, new String[] { KEY_ROW_ID, KEY_LATITUDE, KEY_LONGITUDE, KEY_ALTITUDE, KEY_ACCURACY, KEY_PROVIDER, KEY_SHARING, KEY_TIME, KEY_TIMEZONE, KEY_USERID, KEY_GENERAL_CONDITION, KEY_WINDY, KEY_FOGGY, KEY_CLOUD_TYPE, KEY_PRECIPITATION_TYPE, KEY_PRECIPITATION_AMOUNT, KEY_PRECIPITATION_UNIT, KEY_THUNDERSTORM_INTENSITY, KEY_USER_COMMENT }, null, null, null, null, null); } /** * Fetch every stored local observation * * @return */ public Cursor fetchAllObservations() { return mDB.query(OBSERVATIONS_TABLE, new String[] { KEY_ROW_ID, KEY_LATITUDE, KEY_LONGITUDE, KEY_ALTITUDE, KEY_ACCURACY, KEY_PROVIDER, KEY_OBSERVATION_TYPE, KEY_OBSERVATION_UNIT, KEY_OBSERVATION_VALUE, KEY_SHARING, KEY_TIME, KEY_TIMEZONE, KEY_USERID, KEY_SENSOR_NAME, KEY_SENSOR_TYPE, KEY_SENSOR_VENDOR, KEY_SENSOR_RESOLUTION, KEY_SENSOR_VERSION, KEY_OBSERVATION_TREND }, null, null, null, null, null); } /** * Get a single application's settings by app id * * @param rowId * @return * @throws SQLException */ public Cursor fetchSettingByApp(String appID) throws SQLException { Cursor mCursor = mDB.query(true, SETTINGS_TABLE, new String[] { KEY_ROW_ID, KEY_APP_ID, KEY_DATA_COLLECTION_FREQUENCY, KEY_SERVER_URL, KEY_SEND_NOTIFICATIONS, KEY_USE_GPS, KEY_ONLY_WHEN_CHARGING, KEY_SHARING_DATA, KEY_COLLECTING_DATA, KEY_SHARE_LEVEL}, KEY_APP_ID + "='" + appID + "'", null, null, null, null, null); if (mCursor != null) { mCursor.moveToFirst(); } return mCursor; } /** * Update existing settings for an application * * @param appID * @param dataCollectionFrequency * @return */ public long updateSetting(String appID, long dataCollectionFrequency, String serverURL, boolean onlyWhenCharging, boolean collectingData, boolean sharingData, String shareLevel, boolean sendNotifications, boolean useGPS) { ContentValues newValues = new ContentValues(); newValues.put(KEY_APP_ID, appID); newValues.put(KEY_DATA_COLLECTION_FREQUENCY, dataCollectionFrequency); newValues.put(KEY_SERVER_URL, serverURL); newValues.put(KEY_ONLY_WHEN_CHARGING, onlyWhenCharging); newValues.put(KEY_COLLECTING_DATA, collectingData); newValues.put(KEY_SHARING_DATA, sharingData); newValues.put(KEY_SHARE_LEVEL, shareLevel); newValues.put(KEY_SEND_NOTIFICATIONS, sendNotifications); newValues.put(KEY_USE_GPS, useGPS); return mDB.update(SETTINGS_TABLE, newValues, KEY_APP_ID + "='" + appID + "'", null); } public ArrayList<CbWeather> fudgeGPSData(ArrayList<CbWeather> readings) { ArrayList<CbWeather> fudgedReadings = new ArrayList<CbWeather>(); for(CbWeather obWeather : readings) { CbObservation ob = (CbObservation) obWeather; double longitude = ob.getLocation().getLongitude(); double latitude = ob.getLocation().getLatitude(); double range = .01; Random lat = new Random(System.currentTimeMillis()); Random lon = new Random(System.currentTimeMillis()); latitude = (latitude - range) + (int)(lat.nextDouble() * ((2 * range) + 1)); longitude = (longitude - range) + (int)(lon.nextDouble() * ((2 * range) + 1)); ob.getLocation().setLatitude(latitude); ob.getLocation().setLongitude(longitude); fudgedReadings.add(ob); } return fudgedReadings; } public boolean addWeatherArrayList(ArrayList<CbWeather> results, CbApiCall api) { if (results.get(0).getClass() == (CbObservation.class)) { results = fudgeGPSData(results); addObservationArrayList(results, api); } else { // TODO: fudge current conditions locations too addCurrentConditionArrayList(results); } return true; } public boolean addCurrentConditionArrayList(ArrayList<CbWeather> weather) { try { mDB.beginTransaction(); } catch(SQLiteDatabaseLockedException sqldble) { // This try/catch block is a bad hack. Refactor db usaage to use only one lock // regardless of the thread } String insertSQL = "INSERT INTO " + CURRENT_CONDITIONS_TABLE + " (" + KEY_LATITUDE + ", " + KEY_LONGITUDE + ", " + KEY_ALTITUDE + ", " + KEY_ACCURACY + ", " + KEY_PROVIDER + ", " + KEY_SHARING + ", " + KEY_TIME + ", " + KEY_TIMEZONE + ", " + KEY_USERID + ", " + KEY_GENERAL_CONDITION + ", " + KEY_WINDY + ", " + KEY_FOGGY + ", " + KEY_CLOUD_TYPE + ", " + KEY_PRECIPITATION_TYPE + ", " + KEY_PRECIPITATION_AMOUNT + ", " + KEY_PRECIPITATION_UNIT + ", " + KEY_THUNDERSTORM_INTENSITY + ", " + KEY_USER_COMMENT + ") values (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)"; try { SQLiteStatement insert = mDB.compileStatement(insertSQL); for (CbWeather weatherItem : weather) { CbCurrentCondition condition = (CbCurrentCondition) weatherItem; insert.bindDouble(1, condition.getLocation().getLatitude()); insert.bindDouble(2, condition.getLocation().getLongitude()); insert.bindDouble(3, condition.getLocation().getAltitude()); insert.bindDouble(4, condition.getLocation().getAccuracy()); insert.bindString(5, condition.getLocation().getProvider()); insert.bindString(6, condition.getSharing_policy()); insert.bindLong(7, condition.getTime()); insert.bindLong(8, condition.getTzoffset()); insert.bindString(9, condition.getUser_id()); insert.bindString(10, condition.getGeneral_condition()); insert.bindString(11, condition.getWindy()); insert.bindString(12, condition.getFog_thickness()); insert.bindString(13, condition.getCloud_type()); insert.bindString(14, condition.getPrecipitation_type()); insert.bindDouble(15, condition.getPrecipitation_amount()); insert.bindString(16, condition.getPrecipitation_unit()); insert.bindString(17, condition.getThunderstorm_intensity()); insert.bindString(18, condition.getUser_comment()); insert.executeInsert(); } mDB.setTransactionSuccessful(); } catch (SQLException sqle) { sqle.printStackTrace(); } finally { mDB.endTransaction(); } return true; } /** * Add a new Observations in an ArrayList * * @return */ public boolean addObservationArrayList(ArrayList<CbWeather> weather, CbApiCall api) { mDB.beginTransaction(); String insertSQL = "INSERT INTO " + API_LIST_TABLE + " (" + KEY_LATITUDE + ", " + KEY_LONGITUDE + ", " + KEY_ALTITUDE + ", " + KEY_TIME + ", " + KEY_OBSERVATION_VALUE + " " + ") values (?, ?, ?, ?, ?)"; try { SQLiteStatement insert = mDB.compileStatement(insertSQL); for (CbWeather weatherItem : weather) { CbObservation ob = (CbObservation) weatherItem; double latitude = ob.getLocation().getLatitude(); double longitude = ob.getLocation().getLongitude(); double altitude = ob.getLocation().getAltitude(); insert.bindDouble(1, latitude); insert.bindDouble(2, longitude); insert.bindDouble(3, altitude); insert.bindLong(4, ob.getTime()); insert.bindDouble(5, ob.getObservationValue()); insert.executeInsert(); } mDB.setTransactionSuccessful(); } catch (SQLException sqle) { sqle.printStackTrace(); } finally { mDB.endTransaction(); } return true; } /** * Add a new current condition * * @return */ public long addCondition(CbCurrentCondition cc) { ContentValues initialValues = new ContentValues(); initialValues.put(KEY_LATITUDE, cc.getLocation().getLatitude()); initialValues.put(KEY_LONGITUDE, cc.getLocation().getLongitude()); initialValues.put(KEY_ALTITUDE, cc.getLocation().getAltitude()); initialValues.put(KEY_ACCURACY, cc.getLocation().getAccuracy()); initialValues.put(KEY_PROVIDER, cc.getLocation().getProvider()); initialValues.put(KEY_SHARING, "default"); initialValues.put(KEY_TIME, cc.getTime()); initialValues.put(KEY_TIMEZONE, cc.getTzoffset()); initialValues.put(KEY_USERID, cc.getUser_id()); initialValues.put(KEY_GENERAL_CONDITION, cc.getGeneral_condition()); initialValues.put(KEY_WINDY, cc.getWindy()); initialValues.put(KEY_FOGGY, cc.getFog_thickness()); initialValues.put(KEY_CLOUD_TYPE, cc.getCloud_type()); initialValues.put(KEY_PRECIPITATION_TYPE, cc.getPrecipitation_type()); initialValues.put(KEY_PRECIPITATION_AMOUNT, cc.getPrecipitation_amount()); initialValues.put(KEY_PRECIPITATION_UNIT, cc.getPrecipitation_unit()); initialValues.put(KEY_THUNDERSTORM_INTENSITY, cc.getThunderstorm_intensity()); initialValues.put(KEY_USER_COMMENT, cc.getUser_comment()); return mDB.insert(CURRENT_CONDITIONS_TABLE, null, initialValues); } /** * Add a new observation * * @return */ public long addObservation(CbObservation observation) { ContentValues initialValues = new ContentValues(); //ContentValues listValues = new ContentValues(); try { initialValues .put(KEY_LATITUDE, observation.getLocation().getLatitude()); initialValues.put(KEY_LONGITUDE, observation.getLocation() .getLongitude()); initialValues .put(KEY_ALTITUDE, observation.getLocation().getAltitude()); initialValues .put(KEY_ACCURACY, observation.getLocation().getAccuracy()); initialValues .put(KEY_PROVIDER, observation.getLocation().getProvider()); initialValues.put(KEY_OBSERVATION_TYPE, observation.getObservationType()); initialValues.put(KEY_OBSERVATION_UNIT, observation.getObservationUnit()); initialValues.put(KEY_OBSERVATION_VALUE, observation.getObservationValue()); initialValues.put(KEY_SHARING, observation.getSharing()); initialValues.put(KEY_TIME, observation.getTime()); initialValues.put(KEY_TIMEZONE, observation.getTimeZoneOffset()); initialValues.put(KEY_USERID, observation.getUser_id()); /* listValues.put(KEY_LATITUDE, observation.getLocation().getLatitude()); listValues.put(KEY_LONGITUDE, observation.getLocation().getLongitude()); listValues.put(KEY_TIME, observation.getTime()); listValues.put(KEY_OBSERVATION_VALUE, observation.getObservationValue()); mDB.insert(API_LIST_TABLE, null, listValues); */ return mDB.insert(OBSERVATIONS_TABLE, null, initialValues); } catch(NullPointerException npe) { //npe.printStackTrace(); } return -1; } /** * Add new registration for an application * * @param packageName * @param registrationTime * @return */ public long addRegistration(String packageName, long registrationTime) { ContentValues initialValues = new ContentValues(); initialValues.put(KEY_PACKAGE_NAME, packageName); initialValues.put(KEY_REGISTRATION_TIME, registrationTime); long row = mDB.insert(APP_REGISTRATION_TABLE, null, initialValues); log("SDKTESTS: CbDb adding app " + packageName); return row; } /** * Add new settings for an application * * @param appID * @param dataCollectionFrequency * @return */ public long addSetting(String appID, long dataCollectionFrequency, String serverURL, boolean onlyWhenCharging, boolean collectingData, boolean sharingData, String shareLevel, boolean sendNotifications, boolean useGPS) { ContentValues initialValues = new ContentValues(); initialValues.put(KEY_APP_ID, appID); initialValues.put(KEY_DATA_COLLECTION_FREQUENCY, dataCollectionFrequency); initialValues.put(KEY_SERVER_URL, serverURL); initialValues.put(KEY_ONLY_WHEN_CHARGING, onlyWhenCharging); initialValues.put(KEY_COLLECTING_DATA, collectingData); initialValues.put(KEY_SHARING_DATA, sharingData); initialValues.put(KEY_SHARE_LEVEL, shareLevel); initialValues.put(KEY_SEND_NOTIFICATIONS, sendNotifications); initialValues.put(KEY_USE_GPS, useGPS); return mDB.insert(SETTINGS_TABLE, null, initialValues); } /** * * @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 CbDb open() throws SQLException { mDbHelper = new DatabaseHelper(mContext); mDB = mDbHelper.getWritableDatabase(); return this; } public void close() { mDbHelper.close(); } /** * Constructor - takes the context to allow the database to be * opened/created * * @param ctx * the Context within which to work */ public CbDb(Context ctx) { this.mContext = ctx; } }