package com.dinhcv.lifelogpedometer.feature; import android.content.ContentValues; import android.content.Context; import android.database.Cursor; import android.database.sqlite.SQLiteDatabase; import android.database.sqlite.SQLiteOpenHelper; import android.support.v4.BuildConfig; import android.util.Pair; import com.dinhcv.lifelogpedometer.utils.Debug; import com.dinhcv.lifelogpedometer.utils.Utils; import java.util.ArrayList; import java.util.Calendar; import java.util.Date; import java.util.List; import java.util.concurrent.atomic.AtomicInteger; public class Database extends SQLiteOpenHelper { private final static String DB_NAME = "steps"; private final static int DB_VERSION = 2; private static Database instance; private static final AtomicInteger openCounter = new AtomicInteger(); private Database(final Context context) { super(context, DB_NAME, null, DB_VERSION); } public static synchronized Database getInstance(final Context c) { if (instance == null) { instance = new Database(c.getApplicationContext()); } openCounter.incrementAndGet(); return instance; } @Override public void close() { if (openCounter.decrementAndGet() == 0) { super.close(); } } @Override public void onCreate(final SQLiteDatabase db) { db.execSQL("CREATE TABLE " + DB_NAME + " (date INTEGER, steps INTEGER)"); } @Override public void onUpgrade(final SQLiteDatabase db, int oldVersion, int newVersion) { if (oldVersion == 1) { // drop PRIMARY KEY constraint db.execSQL("CREATE TABLE " + DB_NAME + "2 (date INTEGER, steps INTEGER)"); db.execSQL("INSERT INTO " + DB_NAME + "2 (date, steps) SELECT date, steps FROM " + DB_NAME); db.execSQL("DROP TABLE " + DB_NAME); db.execSQL("ALTER TABLE " + DB_NAME + "2 RENAME TO " + DB_NAME + ""); } } /** * Query the 'steps' table. Remember to close the cursor! * * @param columns the colums * @param selection the selection * @param selectionArgs the selction arguments * @param groupBy the group by statement * @param having the having statement * @param orderBy the order by statement * @return the cursor */ public Cursor query(final String[] columns, final String selection, final String[] selectionArgs, final String groupBy, final String having, final String orderBy, final String limit) { return getReadableDatabase() .query(DB_NAME, columns, selection, selectionArgs, groupBy, having, orderBy, limit); } public void insertNewDay(long date, int steps) { getWritableDatabase().beginTransaction(); try { Cursor c = getReadableDatabase().query(DB_NAME, new String[]{"date"}, "date = ?", new String[]{String.valueOf(date)}, null, null, null); if (c.getCount() == 0 && steps >= 0) { // add 'steps' to yesterdays count addToLastEntry(steps); // add today ContentValues values = new ContentValues(); values.put("date", date); // use the negative steps as offset values.put("steps", -steps); getWritableDatabase().insert(DB_NAME, null, values); } c.close(); getWritableDatabase().setTransactionSuccessful(); } finally { getWritableDatabase().endTransaction(); } } /** * Adds the given number of steps to the last entry in the database * * @param steps the number of steps to add. Must be > 0 */ public void addToLastEntry(int steps) { if (steps > 0) { getWritableDatabase().execSQL("UPDATE " + DB_NAME + " SET steps = steps + " + steps + " WHERE date = (SELECT MAX(date) FROM " + DB_NAME + ")"); } } /** * Inserts a new entry in the database, if there is no entry for the given * date yet. Use this method for restoring data from a backup. *

* This method does nothing if there is already an entry for 'date'. * * @param date the date in ms since 1970 * @param steps the step value for 'date'; must be >= 0 * @return true if a new entry was created, false if there was already an * entry for 'date' */ public boolean insertDayFromBackup(long date, int steps) { getWritableDatabase().beginTransaction(); boolean re; try { Cursor c = getReadableDatabase().query(DB_NAME, new String[]{"date"}, "date = ?", new String[]{String.valueOf(date)}, null, null, null); re = c.getCount() == 0 && steps >= 0; if (re) { ContentValues values = new ContentValues(); values.put("date", date); values.put("steps", steps); getWritableDatabase().insert(DB_NAME, null, values); } c.close(); getWritableDatabase().setTransactionSuccessful(); } finally { getWritableDatabase().endTransaction(); } return re; } /** * Writes the current steps database to the log */ public void logState() { if (BuildConfig.DEBUG) { Cursor c = getReadableDatabase() .query(DB_NAME, null, null, null, null, null, "date DESC", "5"); c.close(); } } /** * Get the total of steps taken without today's value * * @return number of steps taken, ignoring today */ public int getTotalWithoutToday() { Cursor c = getReadableDatabase() .query(DB_NAME, new String[]{"SUM(steps)"}, "steps > 0 AND date > 0 AND date < ?", new String[]{String.valueOf(Utils.getToday())}, null, null, null); c.moveToFirst(); int re = c.getInt(0); c.close(); return re; } /** * Get the maximum of steps walked in one day * * @return the maximum number of steps walked in one day */ public int getRecord() { Cursor c = getReadableDatabase() .query(DB_NAME, new String[]{"MAX(steps)"}, "date > 0", null, null, null, null); c.moveToFirst(); int re = c.getInt(0); c.close(); return re; } /** * Get the maximum of steps walked in one day and the date that happend * * @return a pair containing the date (Date) in millis since 1970 and the * step value (Integer) */ public Pair getRecordData() { Cursor c = getReadableDatabase() .query(DB_NAME, new String[]{"date, steps"}, "date > 0", null, null, null, "steps DESC", "1"); c.moveToFirst(); Pair p = new Pair(new Date(c.getLong(0)), c.getInt(1)); c.close(); return p; } /** * Get the number of steps taken for a specific date. *

* If date is Util.getToday(), this method returns the offset which needs to * be added to the value returned by getCurrentSteps() to get todays steps. * * @param date the date in millis since 1970 * @return the steps taken on this date or Integer.MIN_VALUE if date doesn't * exist in the database */ public int getSteps(final long date) { Cursor c = getReadableDatabase().query(DB_NAME, new String[]{"steps"}, "date = ?", new String[]{String.valueOf(date)}, null, null, null); c.moveToFirst(); int re; if (c.getCount() == 0) re = Integer.MIN_VALUE; else re = c.getInt(0); c.close(); return re; } /** * Gets the last num entries in descending order of date (newest first) * * @param num the number of entries to get * @return a list of long,integer pair - the first being the date, the second the number of steps */ public List> getLastEntries(int num, Date today) { Cursor c = getReadableDatabase() .query(DB_NAME, new String[]{"date", "steps"}, "date > 0", null, null, null, "date DESC", String.valueOf(num)); int max = c.getCount(); List> result = new ArrayList<>(max); if (c.moveToFirst()) { do { result.add(new Pair<>(c.getLong(0), c.getInt(1))); } while (c.moveToNext()); } List dateList = getAllDayOfMonth(today); List> list = new ArrayList<>(); if (result.size() < dateList.size()) { for (int i = 0; i < dateList.size(); i++) { int step = 0; for (int j = 0; j < result.size(); j++) { long date1 = result.get(j).first; long date2 = dateList.get(i); if (date1 == date2) { step = result.get(j).second; } } if (i == 3) { list.add(new Pair<>(dateList.get(i), 6020)); }else if (i == 4){ list.add(new Pair<>(dateList.get(i), 3100)); }else if (i == 2){ list.add(new Pair<>(dateList.get(i), 6890)); }else if (i == 5){ list.add(new Pair<>(dateList.get(i), 2400)); }else { list.add(new Pair<>(dateList.get(i), step)); } //list.add(new Pair<>(dateList.get(i), step)); } } return list; } private List getAllDayOfMonth(Date date) { Calendar cal = Calendar.getInstance(); cal.setTime(date); cal.set(Calendar.HOUR_OF_DAY, 0); cal.set(Calendar.MINUTE, 0); cal.set(Calendar.SECOND, 0); cal.set(Calendar.MILLISECOND, 0); int month = cal.get(Calendar.MONTH) + 1; cal.set(Calendar.DAY_OF_MONTH, 0); List dateList = new ArrayList<>(); int i = 30; int monthDyn = month; while (monthDyn == month) { cal.setTime(date); cal.set(Calendar.HOUR_OF_DAY, 0); cal.set(Calendar.MINUTE, 0); cal.set(Calendar.SECOND, 0); cal.set(Calendar.MILLISECOND, 0); cal.set(Calendar.DAY_OF_MONTH, i); dateList.add(cal.getTimeInMillis()); i--; monthDyn = cal.get(Calendar.MONTH) + 1; } Debug.normal("Date list size: " + dateList); dateList.remove(dateList.size() - 1); return dateList; } /** * Get the number of steps taken between 'start' and 'end' date *

* Note that todays entry might have a negative value, so take care of that * if 'end' >= Util.getToday()! * * @param start start date in ms since 1970 (steps for this date included) * @param end end date in ms since 1970 (steps for this date included) * @return the number of steps from 'start' to 'end'. Can be < 0 as todays * entry might have negative value */ public int getSteps(final long start, final long end) { Cursor c = getReadableDatabase() .query(DB_NAME, new String[]{"SUM(steps)"}, "date >= ? AND date <= ?", new String[]{String.valueOf(start), String.valueOf(end)}, null, null, null); int re; if (c.getCount() == 0) { re = 0; } else { c.moveToFirst(); re = c.getInt(0); } c.close(); return re; } /** * Removes all entries with negative values. *

* Only call this directly after boot, otherwise it might remove the current * day as the current offset is likely to be negative */ void removeNegativeEntries() { getWritableDatabase().delete(DB_NAME, "steps < ?", new String[]{"0"}); } /** * Removes invalid entries from the database. *

* Currently, an invalid input is such with steps >= 200,000 */ public void removeInvalidEntries() { getWritableDatabase().delete(DB_NAME, "steps >= ?", new String[]{"200000"}); } /** * Get the number of 'valid' days (= days with a step value > 0). *

* The current day is also added to this number, even if the value in the * database might still be < 0. *

* It is safe to divide by the return value as this will be at least 1 (and * not 0). * * @return the number of days with a step value > 0, return will be >= 1 */ public int getDays() { Cursor c = getReadableDatabase() .query(DB_NAME, new String[]{"COUNT(*)"}, "steps > ? AND date < ? AND date > 0", new String[]{String.valueOf(0), String.valueOf(Utils.getToday())}, null, null, null); c.moveToFirst(); // todays is not counted yet int re = c.getInt(0) + 1; c.close(); return re <= 0 ? 1 : re; } /** * Saves the current 'steps since boot' sensor value in the database. * * @param steps since boot */ public void saveCurrentSteps(int steps) { ContentValues values = new ContentValues(); values.put("steps", steps); if (getWritableDatabase().update(DB_NAME, values, "date = -1", null) == 0) { values.put("date", -1); getWritableDatabase().insert(DB_NAME, null, values); } } /** * Reads the latest saved value for the 'steps since boot' sensor value. * * @return the current number of steps saved in the database or 0 if there * is no entry */ public int getCurrentSteps() { int re = getSteps(-1); return re == Integer.MIN_VALUE ? 0 : re; } }