/* * Copyright (C) 2013 jonas.oreland@gmail.com * * This program 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. * * This program 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 this program. If not, see <http://www.gnu.org/licenses/>. */ package org.runnerup.db; import android.annotation.TargetApi; import android.content.ContentValues; import android.database.Cursor; import android.database.sqlite.SQLiteDatabase; import android.location.Location; import android.os.Build; import android.util.Log; import org.runnerup.common.util.Constants; import java.util.ArrayList; @TargetApi(Build.VERSION_CODES.FROYO) public class ActivityCleaner implements Constants { long _totalSumHr = 0; int _totalCount = 0; int _totalMaxHr = 0; /** * recompute laps aggregates based on locations */ private void recomputeLaps(SQLiteDatabase db, long activityId) { final String[] cols = new String[] { DB.LAP.LAP }; ArrayList<Long> laps = new ArrayList<Long>(); Cursor c = db.query(DB.LAP.TABLE, cols, DB.LAP.ACTIVITY + " = " + activityId, null, null, null, "_id", null); if (c.moveToFirst()) { do { laps.add(c.getLong(0)); } while (c.moveToNext()); } c.close(); for (long lap : laps) { recomputeLap(db, activityId, lap); } } /** * recompute a lap aggregate based on locations */ private void recomputeLap(SQLiteDatabase db, long activityId, long lap) { long sum_time = 0; long sum_hr = 0; double sum_distance = 0; int count = 0; int max_hr = 0; final String[] cols = new String[] { DB.LOCATION.TIME, DB.LOCATION.LATITUDE, DB.LOCATION.LONGITUDE, DB.LOCATION.TYPE, DB.LOCATION.HR, //DB.LOCATION.CADENCE, //DB.LOCATION.TEMPERATURE, //DB.LOCATION.PRESSURE, "_id" }; Cursor c = db.query(DB.LOCATION.TABLE, cols, DB.LOCATION.ACTIVITY + " = " + activityId + " and " + DB.LOCATION.LAP + " = " + lap, null, null, null, "_id", null); if (c.moveToFirst()) { Location lastLocation = null; do { Location l = new Location("Dill poh"); l.setTime(c.getLong(0)); l.setLatitude(c.getDouble(1)); l.setLongitude(c.getDouble(2)); l.setProvider("" + c.getLong(3)); int type = c.getInt(3); switch (type) { case DB.LOCATION.TYPE_START: case DB.LOCATION.TYPE_RESUME: lastLocation = l; break; case DB.LOCATION.TYPE_END: case DB.LOCATION.TYPE_PAUSE: case DB.LOCATION.TYPE_GPS: if (lastLocation == null) { lastLocation = l; break; } sum_distance += l.distanceTo(lastLocation); sum_time += l.getTime() - lastLocation.getTime(); int hr = c.getInt(4); sum_hr += hr; max_hr = Math.max(max_hr, hr); _totalMaxHr = Math.max(_totalMaxHr, hr); count++; _totalCount++; _totalSumHr += hr; lastLocation = l; break; } } while (c.moveToNext()); } c.close(); ContentValues tmp = new ContentValues(); tmp.put(DB.LAP.DISTANCE, sum_distance); tmp.put(DB.LAP.TIME, (sum_time / 1000)); if (sum_hr > 0) { int hr = Math.round(sum_hr / count); tmp.put(DB.LAP.AVG_HR, hr); tmp.put(DB.LAP.MAX_HR, max_hr); } db.update(DB.LAP.TABLE, tmp, DB.LAP.ACTIVITY + " = " + activityId + " and " + DB.LAP.LAP + " = " + lap, null); } /** * recompute an activity summary based on laps */ private void recomputeSummary(SQLiteDatabase db, long activityId) { long sum_time = 0; double sum_distance = 0; final String[] cols = new String[] { DB.LAP.DISTANCE, DB.LAP.TIME }; Cursor c = db.query(DB.LAP.TABLE, cols, DB.LAP.ACTIVITY + " = " + activityId, null, null, null, "_id", null); if (c.moveToFirst()) { do { sum_distance += c.getDouble(0); sum_time += c.getLong(1); } while (c.moveToNext()); } c.close(); ContentValues tmp = new ContentValues(); if (_totalSumHr > 0) { int hr = Math.round(_totalSumHr / _totalCount); tmp.put(DB.ACTIVITY.AVG_HR, hr); tmp.put(DB.ACTIVITY.MAX_HR, _totalMaxHr); } tmp.put(DB.ACTIVITY.DISTANCE, sum_distance); tmp.put(DB.ACTIVITY.TIME, sum_time); // also used as a flag for conditionalRecompute db.update(DB.ACTIVITY.TABLE, tmp, "_id = " + activityId, null); } public void conditionalRecompute(SQLiteDatabase db){ // get last activity long id = db.compileStatement("SELECT MAX(_id) FROM " + DB.ACTIVITY.TABLE).simpleQueryForLong(); // check its TIME field - recompute if it isn't set String[] cols = new String[]{DB.ACTIVITY.TIME}; Cursor c = db.query(DB.ACTIVITY.TABLE, cols, "_id = " + id, null, null, null, null); if (c.moveToFirst()) { if (c.isNull(0)) { recompute(db, id); } } c.close(); } public void recompute(SQLiteDatabase db, long activityId) { recomputeLaps(db, activityId); recomputeSummary(db, activityId); } public static void trim(SQLiteDatabase db, long activityId) { final String[] cols = new String[] { DB.LAP.LAP }; ArrayList<Long> laps = new ArrayList<Long>(); Cursor c = db.query(DB.LOCATION.LAP, cols, DB.LAP.ACTIVITY + " = " + activityId, null, null, null, "_id", null); if (c.moveToFirst()) { do { laps.add(c.getLong(0)); } while (c.moveToNext()); } c.close(); for (long lap : laps) { int res = trimLap(db, activityId, lap); Log.e("ActivityCleaner", "lap " + lap + " removed " + res + " locations"); } } private static final float MIN_DISTANCE = 2f; private static int trimLap(SQLiteDatabase db, long activityId, long lap) { int cnt = 0; final String[] cols = new String[] { DB.LOCATION.TIME, DB.LOCATION.LATITUDE, DB.LOCATION.LONGITUDE, DB.LOCATION.TYPE, "_id" }; Cursor c = db.query(DB.LOCATION.TABLE, cols, DB.LOCATION.ACTIVITY + " = " + activityId + " and " + DB.LOCATION.LAP + " = " + lap, null, null, null, "_id", null); if (c.moveToFirst()) { Location p[] = { null, null }; do { Location l = new Location("Dill poh"); l.setTime(c.getLong(0)); l.setLatitude(c.getDouble(1)); l.setLongitude(c.getDouble(2)); l.setProvider("" + c.getLong(3)); int type = c.getInt(3); switch (type) { case DB.LOCATION.TYPE_START: case DB.LOCATION.TYPE_RESUME: p[0] = l; p[1] = null; break; case DB.LOCATION.TYPE_END: case DB.LOCATION.TYPE_PAUSE: case DB.LOCATION.TYPE_GPS: if (p[0] == null) { p[0] = l; p[1] = null; break; } else if (p[1] == null) { p[1] = l; } else { float d1 = p[0].distanceTo(p[1]); float d2 = p[0].distanceTo(l); if (Math.abs(d1 - d2) <= MIN_DISTANCE) { // p[1] is redundant...prune it p[1] = l; cnt++; } else { p[0] = p[1]; p[1] = null; } } break; } } while (c.moveToNext()); } c.close(); return cnt; } }