package com.evancharlton.mileage.tasks; import com.evancharlton.mileage.VehicleStatisticsActivity; import com.evancharlton.mileage.dao.CachedValue; import com.evancharlton.mileage.dao.Fillup; import com.evancharlton.mileage.dao.FillupSeries; import com.evancharlton.mileage.dao.Vehicle; import com.evancharlton.mileage.exceptions.InvalidFieldException; import com.evancharlton.mileage.math.Calculator; import com.evancharlton.mileage.provider.Statistic; import com.evancharlton.mileage.provider.Statistics; import com.evancharlton.mileage.provider.tables.CacheTable; import com.evancharlton.mileage.provider.tables.FillupsTable; import android.content.ContentResolver; import android.database.Cursor; import android.util.Log; public class VehicleStatisticsTask extends AttachableAsyncTask<VehicleStatisticsActivity, Cursor, Integer, Integer> { private static final String TAG = "VehicleStatisticsTask"; private ContentResolver mContentResolver; private int mProgress = 0; private int mTotal = 0; @Override public void attach(VehicleStatisticsActivity activity) { super.attach(activity); mContentResolver = activity.getContentResolver(); } @Override protected void onPreExecute() { Log.d(TAG, "Calculation starting..."); getParent().startCalculations(); } @Override protected Integer doInBackground(Cursor... cursors) { synchronized (VehicleStatisticsTask.class) { // delete the cache String[] args = new String[] { String.valueOf(getParent().getVehicle().getId()) }; mContentResolver.delete(CacheTable.BASE_URI, CachedValue.ITEM + " = ?", args); String selection = Fillup.VEHICLE_ID + " = ?"; args = new String[] { String.valueOf(getParent().getVehicle().getId()) }; Cursor cursor = mContentResolver.query(FillupsTable.BASE_URI, FillupsTable.PROJECTION, selection, args, Fillup.ODOMETER + " asc"); mTotal = cursor.getCount(); if (mTotal <= 1) { Log.d(TAG, "Not enough fillups to calculate statistics"); return 0; } Log.d("CalculateTask", "Recalculating..."); // recalculate a whole bunch of shit FillupSeries series = new FillupSeries(); // math gets understandably funky around Double.(MAX|MIN)_VALUE final double MAX = 10000D; final double MIN = -10000D; double totalVolume = 0D; double firstVolume = -1D; double minVolume = MAX; double maxVolume = MIN; double minDistance = MAX; double maxDistance = MIN; double totalCost = 0D; double minCost = MAX; double maxCost = MIN; double minEconomy = MAX; double maxEconomy = MIN; double minCostPerDistance = MAX; double maxCostPerDistance = MIN; double minPrice = MAX; double maxPrice = MIN; double minLatitude = MAX; double maxLatitude = MIN; double minLongitude = MAX; double maxLongitude = MIN; double lastMonthCost = 0D; double lastYearCost = 0D; final long lastYear = System.currentTimeMillis() - Calculator.YEAR_MS; final long lastMonth = System.currentTimeMillis() - Calculator.MONTH_MS; final Vehicle vehicle = getParent().getVehicle(); switch (vehicle.getEconomyUnits()) { case Calculator.GALLONS_PER_100KM: case Calculator.LITRES_PER_100KM: case Calculator.IMP_GAL_PER_100KM: // Have to flip over the best/worst minEconomy = MIN; maxEconomy = MAX; break; } while (cursor.moveToNext()) { if (isCancelled()) { getParent().stopCalculations(); break; } Fillup fillup = new Fillup(cursor); series.add(fillup); if (fillup.hasPrevious()) { double distance = fillup.getDistance(); if (distance > maxDistance) { maxDistance = distance; update(Statistics.MAX_DISTANCE, maxDistance); } else { publishProgress(); } if (distance < minDistance) { minDistance = distance; update(Statistics.MIN_DISTANCE, minDistance); } else { publishProgress(); } double economy = Calculator.fillupEconomy(vehicle, series); if (economy > 0D) { if (Calculator.isBetterEconomy(vehicle, economy, maxEconomy)) { maxEconomy = economy; update(Statistics.MAX_ECONOMY, maxEconomy); } else { publishProgress(); } if (!Calculator.isBetterEconomy(vehicle, economy, minEconomy)) { minEconomy = economy; update(Statistics.MIN_ECONOMY, minEconomy); } else { publishProgress(); } } double costPerDistance = fillup.getCostPerDistance(); if (costPerDistance > maxCostPerDistance) { maxCostPerDistance = costPerDistance; update(Statistics.MAX_COST_PER_DISTANCE, maxCostPerDistance); } else { publishProgress(); } if (costPerDistance < minCostPerDistance) { minCostPerDistance = costPerDistance; update(Statistics.MIN_COST_PER_DISTANCE, minCostPerDistance); } else { publishProgress(); } long timestamp = fillup.getTimestamp(); if (timestamp >= lastMonth) { lastMonthCost += fillup.getTotalCost(); update(Statistics.LAST_MONTH_COST, lastMonthCost); } else { publishProgress(); } if (timestamp >= lastYear) { lastYearCost += fillup.getTotalCost(); update(Statistics.LAST_YEAR_COST, lastYearCost); } else { publishProgress(); } } else { publishProgress(8); } double volume = fillup.getVolume(); if (firstVolume == -1D) { firstVolume = volume; } else { publishProgress(); } if (volume > maxVolume) { maxVolume = volume; update(Statistics.MAX_FUEL, maxVolume); } else { publishProgress(); } if (volume < minVolume) { minVolume = volume; update(Statistics.MIN_FUEL, minVolume); } else { publishProgress(); } totalVolume += volume; update(Statistics.TOTAL_FUEL, totalVolume); double cost = fillup.getTotalCost(); if (cost > maxCost) { maxCost = cost; update(Statistics.MAX_COST, maxCost); } else { publishProgress(); } if (cost < minCost) { minCost = cost; update(Statistics.MIN_COST, minCost); } else { publishProgress(); } totalCost += cost; update(Statistics.TOTAL_COST, totalCost); double price = fillup.getUnitPrice(); if (price > maxPrice) { maxPrice = price; update(Statistics.MAX_PRICE, maxPrice); } else { publishProgress(); } if (price < minPrice) { minPrice = price; update(Statistics.MIN_PRICE, minPrice); } else { publishProgress(); } double latitude = fillup.getLatitude(); if (latitude > maxLatitude && latitude != 0) { maxLatitude = latitude; update(Statistics.NORTH, maxLatitude); } else { publishProgress(); } if (latitude < minLatitude && latitude != 0) { minLatitude = latitude; update(Statistics.SOUTH, minLatitude); } else { publishProgress(); } double longitude = fillup.getLongitude(); if (longitude > maxLongitude && longitude != 0) { maxLongitude = longitude; update(Statistics.EAST, maxLongitude); } else { publishProgress(); } if (longitude < minLongitude && longitude != 0) { minLongitude = longitude; update(Statistics.WEST, minLongitude); } else { publishProgress(); } double avgFuel = totalVolume / series.size(); update(Statistics.AVG_FUEL, avgFuel); double avgEconomy = Calculator.averageEconomy(vehicle, series); update(Statistics.AVG_ECONOMY, avgEconomy); double avgDistance = Calculator.averageDistanceBetweenFillups(series); update(Statistics.AVG_DISTANCE, avgDistance); double avgCost = Calculator.averageFillupCost(series); update(Statistics.AVG_COST, avgCost); double avgCostPerDistance = Calculator.averageCostPerDistance(series); update(Statistics.AVG_COST_PER_DISTANCE, avgCostPerDistance); double avgPrice = Calculator.averagePrice(series); update(Statistics.AVG_PRICE, avgPrice); double fuelPerDay = Calculator.averageFuelPerDay(series); update(Statistics.FUEL_PER_YEAR, fuelPerDay * 365); double costPerDay = Calculator.averageCostPerDay(series); update(Statistics.AVG_MONTHLY_COST, costPerDay * 30); update(Statistics.AVG_YEARLY_COST, costPerDay * 365); try { fillup.saveIfChanged(getParent()); } catch (InvalidFieldException e) { Log.e(TAG, "Couldn't save in-memory changes.", e); } } // Force an update for conditional statistics to ensure they happen update(Statistics.MAX_DISTANCE, maxDistance); update(Statistics.MIN_DISTANCE, minDistance); update(Statistics.MAX_ECONOMY, maxEconomy); update(Statistics.MIN_ECONOMY, minEconomy); update(Statistics.MAX_COST_PER_DISTANCE, maxCostPerDistance); update(Statistics.MIN_COST_PER_DISTANCE, minCostPerDistance); update(Statistics.LAST_MONTH_COST, lastMonthCost); update(Statistics.LAST_YEAR_COST, lastYearCost); update(Statistics.NORTH, maxLatitude); update(Statistics.SOUTH, minLatitude); update(Statistics.EAST, maxLongitude); update(Statistics.WEST, minLongitude); cursor.close(); } return 0; } private void update(Statistic statistic, double value) { statistic.setValue(value); getParent().getAdapter().setValue(statistic, value); publishProgress(); } @Override protected void onProgressUpdate(Integer... updates) { if (mTotal > 0) { getParent().getProgressBar().setIndeterminate(false); getParent().getProgressBar().setMax(mTotal * Statistics.STATISTICS.size()); mTotal = 0; } if (updates.length > 0) { mProgress += updates[0]; } else { mProgress += 1; } getParent().getProgressBar().setProgress(mProgress); getParent().getAdapter().notifyDataSetChanged(); } @Override protected void onPostExecute(Integer done) { if (getParent().getAdapter() == null) { getParent().setAdapter(getParent().getCacheCursor()); } else { getParent().getAdapter().notifyDataSetChanged(); } getParent().stopCalculations(); Log.d(TAG, "Done recalculating!"); getParent().getAdapter().flush(); } }