Refactoring of database access

- the db is now exclusively locked for one thread
- the charts read from the db and analyze data in a background thread

+ some small cleanups
This commit is contained in:
cpfeiffer 2015-08-03 01:17:02 +02:00
parent 9ca595a5cb
commit 802f48011d
17 changed files with 342 additions and 105 deletions

View File

@ -201,9 +201,8 @@ public class ControlCenter extends Activity {
case R.id.controlcenter_start_sleepmonitor:
if (selectedDevice != null) {
Intent startIntent;
// startIntent = new Intent(ControlCenter.this, SleepChartActivity.class);
startIntent = new Intent(ControlCenter.this, ChartsActivity.class);
startIntent.putExtra("device", selectedDevice);
startIntent.putExtra(GBDevice.EXTRA_DEVICE, selectedDevice);
startActivity(startIntent);
}
return true;

View File

@ -6,6 +6,7 @@ import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.database.sqlite.SQLiteOpenHelper;
import android.os.Bundle;
import android.support.v4.app.NavUtils;
import android.support.v4.app.NotificationCompat;
@ -20,7 +21,7 @@ import org.slf4j.LoggerFactory;
import java.io.File;
import nodomain.freeyourgadget.gadgetbridge.database.ActivityDatabaseHandler;
import nodomain.freeyourgadget.gadgetbridge.database.DBHandler;
import nodomain.freeyourgadget.gadgetbridge.database.DBHelper;
import nodomain.freeyourgadget.gadgetbridge.util.FileUtils;
@ -186,30 +187,40 @@ public class DebugActivity extends Activity {
}
private void exportDB() {
DBHandler dbHandler = null;
try {
ActivityDatabaseHandler dbHandler = GBApplication.getActivityDatabaseHandler();
dbHandler = GBApplication.acquireDB();
DBHelper helper = new DBHelper(this);
File dir = FileUtils.getExternalFilesDir();
File destFile = helper.exportDB(dbHandler, dir);
Toast.makeText(this, "Exported to: " + destFile.getAbsolutePath(), Toast.LENGTH_LONG).show();
File destFile = helper.exportDB(dbHandler.getHelper(), dir);
GB.toast(this, "Exported to: " + destFile.getAbsolutePath(), Toast.LENGTH_LONG, GB.INFO);
} catch (Exception ex) {
LOG.error("Unable to export db", ex);
Toast.makeText(this, "Error exporting DB: " + ex.getMessage(), Toast.LENGTH_LONG).show();
} finally {
if (dbHandler != null) {
dbHandler.release();
}
}
}
private void importDB() {
DBHandler dbHandler = null;
try {
ActivityDatabaseHandler dbHandler = GBApplication.getActivityDatabaseHandler();
dbHandler = GBApplication.acquireDB();
DBHelper helper = new DBHelper(this);
File dir = FileUtils.getExternalFilesDir();
File sourceFile = new File(dir, dbHandler.getDatabaseName());
helper.importDB(dbHandler, sourceFile);
helper.validateDB(dbHandler);
Toast.makeText(this, "Import successful.", Toast.LENGTH_LONG).show();
SQLiteOpenHelper sqLiteOpenHelper = dbHandler.getHelper();
File sourceFile = new File(dir, sqLiteOpenHelper.getDatabaseName());
helper.importDB(sqLiteOpenHelper, sourceFile);
helper.validateDB(sqLiteOpenHelper);
GB.toast(this, "Import successful.", Toast.LENGTH_LONG, GB.INFO);
} catch (Exception ex) {
LOG.error("Unable to import db", ex);
Toast.makeText(this, "Error importing DB: " + ex.getMessage(), Toast.LENGTH_LONG).show();
GB.toast(this, "Error importing DB: " + ex.getMessage(), Toast.LENGTH_LONG, GB.ERROR);
} finally {
if (dbHandler != null) {
dbHandler.release();
}
}
}

View File

@ -7,19 +7,27 @@ import android.os.Build;
import android.os.Build.VERSION;
import android.preference.PreferenceManager;
import android.util.Log;
import android.widget.Toast;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.File;
import java.io.IOException;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
import nodomain.freeyourgadget.gadgetbridge.database.ActivityDatabaseHandler;
import nodomain.freeyourgadget.gadgetbridge.database.DBHandler;
import nodomain.freeyourgadget.gadgetbridge.util.FileUtils;
public class GBApplication extends Application {
// Since this class must not log to slf4j, we use plain android.util.Log
private static final String TAG = "GBApplication";
private static GBApplication context;
private static ActivityDatabaseHandler mActivityDatabaseHandler;
private static Lock dbLock = new ReentrantLock();
public GBApplication() {
context = this;
@ -75,8 +83,33 @@ public class GBApplication extends Application {
return context;
}
public static ActivityDatabaseHandler getActivityDatabaseHandler() {
return mActivityDatabaseHandler;
/**
* Returns the DBHandler instance for reading/writing or throws GBException
* when that was not successful
* If acquiring was successful, callers must call #releaseDB when they
* are done (from the same thread that acquired the lock!
* @return the DBHandler
* @see #releaseDB()
* @throws GBException
*/
public static DBHandler acquireDB() throws GBException {
try {
if (dbLock.tryLock(30, TimeUnit.SECONDS)) {
return mActivityDatabaseHandler;
}
} catch (InterruptedException ex) {
Log.i(TAG, "Interrupted while waiting for DB lock");
}
throw new GBException("Unable to access the database.");
}
/**
* Releases the database lock.
* @throws IllegalMonitorStateException if the current thread is not owning the lock
* @see #acquireDB()
*/
public static void releaseDB() {
dbLock.unlock();
}
public static boolean isRunningLollipopOrLater() {

View File

@ -0,0 +1,16 @@
package nodomain.freeyourgadget.gadgetbridge;
public class GBException extends Exception {
public GBException(String message, Throwable cause) {
super(message, cause);
}
public GBException(String message) {
super(message);
}
public GBException(Throwable cause) {
super(cause);
}
public GBException() {
super();
}
}

View File

@ -1,9 +1,9 @@
package nodomain.freeyourgadget.gadgetbridge.activities;
import android.content.Context;
import android.graphics.Color;
import android.support.v4.app.Fragment;
import com.github.mikephil.charting.animation.Easing;
import com.github.mikephil.charting.charts.BarLineChartBase;
import com.github.mikephil.charting.charts.Chart;
import com.github.mikephil.charting.data.BarData;
@ -22,11 +22,12 @@ import java.util.List;
import nodomain.freeyourgadget.gadgetbridge.DeviceCoordinator;
import nodomain.freeyourgadget.gadgetbridge.DeviceHelper;
import nodomain.freeyourgadget.gadgetbridge.GBApplication;
import nodomain.freeyourgadget.gadgetbridge.GBDevice;
import nodomain.freeyourgadget.gadgetbridge.R;
import nodomain.freeyourgadget.gadgetbridge.charts.ActivityKind;
import nodomain.freeyourgadget.gadgetbridge.charts.SleepUtils;
import nodomain.freeyourgadget.gadgetbridge.database.DBAccess;
import nodomain.freeyourgadget.gadgetbridge.database.DBHandler;
import nodomain.freeyourgadget.gadgetbridge.model.ActivitySample;
import nodomain.freeyourgadget.gadgetbridge.model.SampleProvider;
@ -75,12 +76,19 @@ public abstract class AbstractChartFragment extends Fragment {
return coordinator.getSampleProvider();
}
protected List<ActivitySample> getAllSamples(GBDevice device, int tsFrom, int tsTo) {
/**
* Returns all kinds of samples for the given device.
* To be called from a background thread.
* @param device
* @param tsFrom
* @param tsTo
*/
protected List<ActivitySample> getAllSamples(DBHandler db, GBDevice device, int tsFrom, int tsTo) {
if (tsFrom == -1) {
tsFrom = getTSLast24Hours();
}
SampleProvider provider = getProvider(device);
return GBApplication.getActivityDatabaseHandler().getAllActivitySamples(tsFrom, tsTo, provider);
return db.getAllActivitySamples(tsFrom, tsTo, provider);
}
private int getTSLast24Hours() {
@ -88,24 +96,24 @@ public abstract class AbstractChartFragment extends Fragment {
return (int) ((now / 1000) - (24 * 60 * 60) & 0xffffffff); // -24 hours
}
protected List<ActivitySample> getActivitySamples(GBDevice device, int tsFrom, int tsTo) {
protected List<ActivitySample> getActivitySamples(DBHandler db, GBDevice device, int tsFrom, int tsTo) {
if (tsFrom == -1) {
tsFrom = getTSLast24Hours();
}
SampleProvider provider = getProvider(device);
return GBApplication.getActivityDatabaseHandler().getActivitySamples(tsFrom, tsTo, provider);
return db.getActivitySamples(tsFrom, tsTo, provider);
}
protected List<ActivitySample> getSleepSamples(GBDevice device, int tsFrom, int tsTo) {
protected List<ActivitySample> getSleepSamples(DBHandler db, GBDevice device, int tsFrom, int tsTo) {
if (tsFrom == -1) {
tsFrom = getTSLast24Hours();
}
SampleProvider provider = getProvider(device);
return GBApplication.getActivityDatabaseHandler().getSleepSamples(tsFrom, tsTo, provider);
return db.getSleepSamples(tsFrom, tsTo, provider);
}
protected List<ActivitySample> getTestSamples(GBDevice device, int tsFrom, int tsTo) {
protected List<ActivitySample> getTestSamples(DBHandler db, GBDevice device, int tsFrom, int tsTo) {
Calendar cal = Calendar.getInstance();
cal.clear();
cal.set(2015, Calendar.JUNE, 10, 6, 40);
@ -114,7 +122,7 @@ public abstract class AbstractChartFragment extends Fragment {
tsFrom = tsTo - (24 * 60 * 60);
SampleProvider provider = getProvider(device);
return GBApplication.getActivityDatabaseHandler().getAllActivitySamples(tsFrom, tsTo, provider);
return db.getAllActivitySamples(tsFrom, tsTo, provider);
}
protected void configureChartDefaults(Chart<?> chart) {
@ -141,6 +149,28 @@ public abstract class AbstractChartFragment extends Fragment {
chart.setDrawGridBackground(false);
}
/**
* This method will invoke a background task to read the data from the
* database, analyze it, prepare it for the charts and eventually call
* #renderCharts
*/
protected void refresh() {
createRefreshTask("Visualizing data", getActivity()).execute();
}
/**
* This method reads the data from the database, analyzes and prepares it for
* the charts. This will be called from a background task, so there must not be
* any UI access. #renderCharts will be automatically called after this method.
*/
protected abstract void refreshInBackground(DBHandler db);
/**
* Performs a re-rendering of the chart.
* Always called from the UI thread.
*/
protected abstract void renderCharts();
protected void refresh(GBDevice gbDevice, BarLineChartBase chart, List<ActivitySample> samples) {
if (gbDevice == null) {
return;
@ -252,12 +282,10 @@ public abstract class AbstractChartFragment extends Fragment {
setupLegend(chart);
chart.setData(data);
chart.animateX(500, Easing.EasingOption.EaseInOutQuart);
}
}
protected abstract List<ActivitySample> getSamples(GBDevice device, int tsFrom, int tsTo);
protected abstract List<ActivitySample> getSamples(DBHandler db, GBDevice device, int tsFrom, int tsTo);
protected abstract void setupLegend(Chart chart);
@ -315,4 +343,25 @@ public abstract class AbstractChartFragment extends Fragment {
// set1.setColor(Color.CYAN);
return set1;
}
protected RefreshTask createRefreshTask(String task, Context context) {
return new RefreshTask(task, context);
}
public class RefreshTask extends DBAccess {
public RefreshTask(String task, Context context) {
super(task, context);
}
@Override
protected void doInBackground(DBHandler db) {
refreshInBackground(db);
}
@Override
protected void onPostExecute(Object o) {
super.onPostExecute(o);
renderCharts();
}
}
}

View File

@ -10,6 +10,7 @@ import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import com.github.mikephil.charting.animation.Easing;
import com.github.mikephil.charting.charts.BarLineChartBase;
import com.github.mikephil.charting.charts.Chart;
import com.github.mikephil.charting.components.XAxis;
@ -24,6 +25,7 @@ import java.util.List;
import nodomain.freeyourgadget.gadgetbridge.ControlCenter;
import nodomain.freeyourgadget.gadgetbridge.GBDevice;
import nodomain.freeyourgadget.gadgetbridge.R;
import nodomain.freeyourgadget.gadgetbridge.database.DBHandler;
import nodomain.freeyourgadget.gadgetbridge.model.ActivitySample;
@ -48,8 +50,7 @@ public class ActivitySleepChartFragment extends AbstractChartFragment {
mSmartAlarmTo = intent.getIntExtra("smartalarm_to", -1);
mTimestampFrom = intent.getIntExtra("recording_base_timestamp", -1);
mSmartAlarmGoneOff = intent.getIntExtra("alarm_gone_off", -1);
List<ActivitySample> samples = getSamples(mGBDevice, -1, -1);
refresh(mGBDevice, mChart, samples);
refresh();
}
}
};
@ -108,16 +109,7 @@ public class ActivitySleepChartFragment extends AbstractChartFragment {
yAxisRight.setDrawTopYLabelEntry(false);
yAxisRight.setTextColor(CHART_TEXT_COLOR);
List<ActivitySample> samples = getSamples(mGBDevice, -1, -1);
refresh(mGBDevice, mChart, samples);
mChart.getLegend().setTextColor(LEGEND_TEXT_COLOR);
// mChart.getLegend().setEnabled(false);
//
// mChart.animateXY(2000, 2000);
// don't forget to refresh the drawing
mChart.invalidate();
refresh();
}
@Override
@ -126,6 +118,18 @@ public class ActivitySleepChartFragment extends AbstractChartFragment {
super.onDestroy();
}
@Override
protected void refreshInBackground(DBHandler db) {
List<ActivitySample> samples = getSamples(db, mGBDevice, -1, -1);
refresh(mGBDevice, mChart, samples);
mChart.getLegend().setTextColor(LEGEND_TEXT_COLOR);
}
protected void renderCharts() {
mChart.animateX(500, Easing.EasingOption.EaseInOutQuart);
}
protected void setupLegend(Chart chart) {
List<Integer> legendColors = new ArrayList<>(3);
List<String> legendLabels = new ArrayList<>(3);
@ -140,7 +144,7 @@ public class ActivitySleepChartFragment extends AbstractChartFragment {
}
@Override
protected List<ActivitySample> getSamples(GBDevice device, int tsFrom, int tsTo) {
return getAllSamples(device, tsFrom, tsTo);
protected List<ActivitySample> getSamples(DBHandler db, GBDevice device, int tsFrom, int tsTo) {
return getAllSamples(db, device, tsFrom, tsTo);
}
}

View File

@ -10,6 +10,7 @@ import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import com.github.mikephil.charting.animation.Easing;
import com.github.mikephil.charting.charts.BarLineChartBase;
import com.github.mikephil.charting.charts.Chart;
import com.github.mikephil.charting.charts.PieChart;
@ -34,6 +35,7 @@ import nodomain.freeyourgadget.gadgetbridge.R;
import nodomain.freeyourgadget.gadgetbridge.charts.ActivityAmount;
import nodomain.freeyourgadget.gadgetbridge.charts.ActivityAmounts;
import nodomain.freeyourgadget.gadgetbridge.charts.ActivityAnalysis;
import nodomain.freeyourgadget.gadgetbridge.database.DBHandler;
import nodomain.freeyourgadget.gadgetbridge.model.ActivitySample;
@ -64,17 +66,15 @@ public class SleepChartFragment extends AbstractChartFragment {
}
};
private void refresh() {
List<ActivitySample> samples = getSamples();
refresh(mGBDevice, mActivityChart, getSamples());
@Override
protected void refreshInBackground(DBHandler db) {
List<ActivitySample> samples = getSamples(db);
refresh(mGBDevice, mActivityChart, samples);
refreshSleepAmounts(mGBDevice, mSleepAmountChart, samples);
mActivityChart.invalidate();
mSleepAmountChart.invalidate();
}
private List<ActivitySample> getSamples() {
return getSamples(mGBDevice, -1, -1);
private List<ActivitySample> getSamples(DBHandler db) {
return getSamples(db, mGBDevice, -1, -1);
}
private void refreshSleepAmounts(GBDevice mGBDevice, PieChart pieChart, List<ActivitySample> samples) {
@ -105,8 +105,6 @@ public class SleepChartFragment extends AbstractChartFragment {
pieChart.getLegend().setEnabled(false);
//setupLegend(pieChart);
pieChart.invalidate();
}
@Override
@ -180,13 +178,6 @@ public class SleepChartFragment extends AbstractChartFragment {
yAxisRight.setDrawLabels(false);
yAxisRight.setDrawTopYLabelEntry(false);
yAxisRight.setTextColor(CHART_TEXT_COLOR);
// mActivityChart.getLegend().setEnabled(false);
//
// mActivityChart.animateXY(2000, 2000);
// don't forget to refresh the drawing
// mActivityChart.invalidate();
}
protected void setupLegend(Chart chart) {
@ -202,7 +193,12 @@ public class SleepChartFragment extends AbstractChartFragment {
}
@Override
protected List<ActivitySample> getSamples(GBDevice device, int tsFrom, int tsTo) {
return super.getSleepSamples(device, tsFrom, tsTo);
protected List<ActivitySample> getSamples(DBHandler db, GBDevice device, int tsFrom, int tsTo) {
return super.getSleepSamples(db, device, tsFrom, tsTo);
}
}
protected void renderCharts() {
mActivityChart.animateX(500, Easing.EasingOption.EaseInOutQuart);
mSleepAmountChart.invalidate();
}
}

View File

@ -37,6 +37,7 @@ import nodomain.freeyourgadget.gadgetbridge.ControlCenter;
import nodomain.freeyourgadget.gadgetbridge.GBDevice;
import nodomain.freeyourgadget.gadgetbridge.R;
import nodomain.freeyourgadget.gadgetbridge.charts.ActivityAnalysis;
import nodomain.freeyourgadget.gadgetbridge.database.DBHandler;
import nodomain.freeyourgadget.gadgetbridge.miband.MiBandCoordinator;
import nodomain.freeyourgadget.gadgetbridge.model.ActivitySample;
@ -62,19 +63,21 @@ public class WeekStepsChartFragment extends AbstractChartFragment {
}
};
private void refresh() {
@Override
protected void refreshInBackground(DBHandler db) {
Calendar day = Calendar.getInstance();
//NB: we could have omitted the day, but this way we can move things to the past easily
refreshDaySteps(mTodayStepsChart, day);
refreshWeekBeforeSteps(mWeekStepsChart, day);
refreshDaySteps(db, mTodayStepsChart, day);
refreshWeekBeforeSteps(db, mWeekStepsChart, day);
}
@Override
protected void renderCharts() {
mWeekStepsChart.invalidate();
mTodayStepsChart.invalidate();
}
private void refreshWeekBeforeSteps(BarLineChartBase barChart, Calendar day) {
private void refreshWeekBeforeSteps(DBHandler db, BarLineChartBase barChart, Calendar day) {
ActivityAnalysis analysis = new ActivityAnalysis();
@ -83,7 +86,7 @@ public class WeekStepsChartFragment extends AbstractChartFragment {
List<String> labels = new ArrayList<>();
for (int counter = 0; counter < 7; counter++) {
entries.add(new BarEntry(analysis.calculateTotalSteps(getSamplesOfDay(day)), counter));
entries.add(new BarEntry(analysis.calculateTotalSteps(getSamplesOfDay(db, day)), counter));
labels.add(day.getDisplayName(Calendar.DAY_OF_WEEK, Calendar.SHORT, mLocale));
day.add(Calendar.DATE, 1);
}
@ -102,10 +105,10 @@ public class WeekStepsChartFragment extends AbstractChartFragment {
barChart.getLegend().setEnabled(false);
}
private void refreshDaySteps(PieChart pieChart, Calendar day) {
private void refreshDaySteps(DBHandler db, PieChart pieChart, Calendar day) {
ActivityAnalysis analysis = new ActivityAnalysis();
int totalSteps = analysis.calculateTotalSteps(getSamplesOfDay(day));
int totalSteps = analysis.calculateTotalSteps(getSamplesOfDay(db, day));
pieChart.setCenterText(NumberFormat.getNumberInstance(mLocale).format(totalSteps));
PieData data = new PieData();
@ -222,7 +225,7 @@ public class WeekStepsChartFragment extends AbstractChartFragment {
chart.getLegend().setTextColor(LEGEND_TEXT_COLOR);
}
private List<ActivitySample> getSamplesOfDay(Calendar day) {
private List<ActivitySample> getSamplesOfDay(DBHandler db, Calendar day) {
int startTs;
int endTs;
@ -236,11 +239,11 @@ public class WeekStepsChartFragment extends AbstractChartFragment {
day.set(Calendar.SECOND, 59);
endTs = (int) (day.getTimeInMillis() / 1000);
return getSamples(mGBDevice, startTs, endTs);
return getSamples(db, mGBDevice, startTs, endTs);
}
@Override
protected List<ActivitySample> getSamples(GBDevice device, int tsFrom, int tsTo) {
return super.getAllSamples(device, tsFrom, tsTo);
protected List<ActivitySample> getSamples(DBHandler db, GBDevice device, int tsFrom, int tsTo) {
return super.getAllSamples(db, device, tsFrom, tsTo);
}
}

View File

@ -14,6 +14,7 @@ import java.util.ArrayList;
import nodomain.freeyourgadget.gadgetbridge.GB;
import nodomain.freeyourgadget.gadgetbridge.GBActivitySample;
import nodomain.freeyourgadget.gadgetbridge.GBApplication;
import nodomain.freeyourgadget.gadgetbridge.charts.ActivityKind;
import nodomain.freeyourgadget.gadgetbridge.database.schema.ActivityDBCreationScript;
import nodomain.freeyourgadget.gadgetbridge.model.ActivitySample;
@ -27,7 +28,7 @@ import static nodomain.freeyourgadget.gadgetbridge.database.DBConstants.KEY_TIME
import static nodomain.freeyourgadget.gadgetbridge.database.DBConstants.KEY_TYPE;
import static nodomain.freeyourgadget.gadgetbridge.database.DBConstants.TABLE_GBACTIVITYSAMPLES;
public class ActivityDatabaseHandler extends SQLiteOpenHelper {
public class ActivityDatabaseHandler extends SQLiteOpenHelper implements DBHandler {
private static final Logger LOG = LoggerFactory.getLogger(ActivityDatabaseHandler.class);
@ -135,6 +136,16 @@ public class ActivityDatabaseHandler extends SQLiteOpenHelper {
return getGBActivitySamples(timestamp_from, timestamp_to, ActivityKind.TYPE_ACTIVITY, provider);
}
@Override
public SQLiteOpenHelper getHelper() {
return this;
}
@Override
public void release() {
GBApplication.releaseDB();
}
public ArrayList<ActivitySample> getAllActivitySamples(int timestamp_from, int timestamp_to, SampleProvider provider) {
return getGBActivitySamples(timestamp_from, timestamp_to, ActivityKind.TYPE_ALL, provider);
}

View File

@ -0,0 +1,53 @@
package nodomain.freeyourgadget.gadgetbridge.database;
import android.content.Context;
import android.os.AsyncTask;
import android.widget.Toast;
import nodomain.freeyourgadget.gadgetbridge.GB;
import nodomain.freeyourgadget.gadgetbridge.GBApplication;
import nodomain.freeyourgadget.gadgetbridge.R;
public abstract class DBAccess extends AsyncTask {
private final String mTask;
private final Context mContext;
private Exception mError;
public DBAccess(String task, Context context) {
mTask = task;
mContext = context;
}
public Context getContext() {
return mContext;
}
protected abstract void doInBackground(DBHandler handler);
@Override
protected Object doInBackground(Object[] params) {
DBHandler handler = null;
try {
handler = GBApplication.acquireDB();
doInBackground(handler);
} catch (Exception e) {
mError = e;
} finally {
if (handler != null) {
handler.release();
}
}
return null;
}
@Override
protected void onPostExecute(Object o) {
if (mError != null) {
displayError(mError);
}
}
protected void displayError(Throwable error) {
GB.toast(getContext(), getContext().getString(R.string.dbaccess_error_executing, error.getMessage()), Toast.LENGTH_LONG, GB.ERROR, error);
}
}

View File

@ -0,0 +1,30 @@
package nodomain.freeyourgadget.gadgetbridge.database;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;
import java.util.List;
import nodomain.freeyourgadget.gadgetbridge.GBApplication;
import nodomain.freeyourgadget.gadgetbridge.model.ActivitySample;
import nodomain.freeyourgadget.gadgetbridge.model.SampleProvider;
public interface DBHandler {
public SQLiteOpenHelper getHelper();
/**
* Releases the DB handler. No access may be performed after calling this method.
* Same as calling {@link GBApplication#releaseDB()}
*/
void release();
List<ActivitySample> getAllActivitySamples(int tsFrom, int tsTo, SampleProvider provider);
List<ActivitySample> getActivitySamples(int tsFrom, int tsTo, SampleProvider provider);
List<ActivitySample> getSleepSamples(int tsFrom, int tsTo, SampleProvider provider);
void addGBActivitySample(int timestamp, byte provider, short intensity, byte steps, byte kind);
SQLiteDatabase getWritableDatabase();
}

View File

@ -23,8 +23,8 @@ public class DBHelper {
private String getClosedDBPath(SQLiteOpenHelper dbHandler) throws IllegalStateException {
SQLiteDatabase db = dbHandler.getReadableDatabase();
String path = db.getPath();
db.close(); // reference counted, so may still be open
if (db.isOpen()) {
db.close();
if (db.isOpen()) { // reference counted, so may still be open
throw new IllegalStateException("Database must be closed");
}
return path;
@ -82,6 +82,4 @@ public class DBHelper {
}
return "";
}
}

View File

@ -32,7 +32,7 @@ import nodomain.freeyourgadget.gadgetbridge.btle.BtLEAction;
import nodomain.freeyourgadget.gadgetbridge.btle.SetDeviceBusyAction;
import nodomain.freeyourgadget.gadgetbridge.btle.SetProgressAction;
import nodomain.freeyourgadget.gadgetbridge.btle.TransactionBuilder;
import nodomain.freeyourgadget.gadgetbridge.database.ActivityDatabaseHandler;
import nodomain.freeyourgadget.gadgetbridge.database.DBHandler;
import nodomain.freeyourgadget.gadgetbridge.model.SampleProvider;
import static nodomain.freeyourgadget.gadgetbridge.miband.MiBandConst.DEFAULT_VALUE_FLASH_COLOUR;
@ -840,24 +840,32 @@ public class MiBandSupport extends AbstractBTLEDeviceSupport {
LOG.debug("flushing activity data holder");
byte category, intensity, steps;
ActivityDatabaseHandler dbHandler = GBApplication.getActivityDatabaseHandler();
DBHandler dbHandler = null;
try {
dbHandler = GBApplication.acquireDB();
try (SQLiteDatabase db = dbHandler.getWritableDatabase()) { // explicitly keep the db open while looping over the samples
for (int i = 0; i < activityStruct.activityDataHolderProgress; i += 3) { //TODO: check if multiple of 3, if not something is wrong
category = activityStruct.activityDataHolder[i];
intensity = activityStruct.activityDataHolder[i + 1];
steps = activityStruct.activityDataHolder[i + 2];
try (SQLiteDatabase db = dbHandler.getWritableDatabase()) { // explicitly keep the db open while looping over the samples
for (int i = 0; i < activityStruct.activityDataHolderProgress; i += 3) { //TODO: check if multiple of 3, if not something is wrong
category = activityStruct.activityDataHolder[i];
intensity = activityStruct.activityDataHolder[i + 1];
steps = activityStruct.activityDataHolder[i + 2];
dbHandler.addGBActivitySample(
(int) (activityStruct.activityDataTimestampProgress.getTimeInMillis() / 1000),
SampleProvider.PROVIDER_MIBAND,
intensity,
steps,
category);
activityStruct.activityDataTimestampProgress.add(Calendar.MINUTE, 1);
dbHandler.addGBActivitySample(
(int) (activityStruct.activityDataTimestampProgress.getTimeInMillis() / 1000),
SampleProvider.PROVIDER_MIBAND,
intensity,
steps,
category);
activityStruct.activityDataTimestampProgress.add(Calendar.MINUTE, 1);
}
} finally {
activityStruct.activityDataHolderProgress = 0;
}
} catch (Exception ex) {
GB.toast(getContext(), ex.getMessage(), Toast.LENGTH_LONG, GB.ERROR);
} finally {
activityStruct.activityDataHolderProgress = 0;
if (dbHandler != null) {
dbHandler.release();
}
}
}

View File

@ -13,6 +13,8 @@ import java.util.TimeZone;
import java.util.UUID;
import nodomain.freeyourgadget.gadgetbridge.GBApplication;
import nodomain.freeyourgadget.gadgetbridge.GBException;
import nodomain.freeyourgadget.gadgetbridge.database.DBHandler;
import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEvent;
import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventSendBytes;
import nodomain.freeyourgadget.gadgetbridge.model.SampleProvider;
@ -47,13 +49,23 @@ public class GadgetbridgePblSupport {
int samples_remaining = samples.length / 2;
LOG.info("got " + samples_remaining + " samples");
int offset_seconds = 0;
while (samples_remaining-- > 0) {
short sample = samplesBuffer.getShort();
byte type = (byte) ((sample & 0xe000) >>> 13);
byte intensity = (byte) ((sample & 0x1f80) >>> 7);
byte steps = (byte) (sample & 0x007f);
GBApplication.getActivityDatabaseHandler().addGBActivitySample(timestamp + offset_seconds, SampleProvider.PROVIDER_PEBBLE_GADGETBRIDGE, intensity, steps, type);
offset_seconds += 60;
DBHandler db = null;
try {
db = GBApplication.acquireDB();
while (samples_remaining-- > 0) {
short sample = samplesBuffer.getShort();
byte type = (byte) ((sample & 0xe000) >>> 13);
byte intensity = (byte) ((sample & 0x1f80) >>> 7);
byte steps = (byte) (sample & 0x007f);
db.addGBActivitySample(timestamp + offset_seconds, SampleProvider.PROVIDER_PEBBLE_GADGETBRIDGE, intensity, steps, type);
offset_seconds += 60;
}
} catch (GBException e) {
LOG.error("Error acquiring database", e);
} finally {
if (db != null) {
db.release();
}
}
break;
default:

View File

@ -12,6 +12,8 @@ import java.util.TimeZone;
import java.util.UUID;
import nodomain.freeyourgadget.gadgetbridge.GBApplication;
import nodomain.freeyourgadget.gadgetbridge.GBException;
import nodomain.freeyourgadget.gadgetbridge.database.DBHandler;
import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEvent;
import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventSendBytes;
import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventSleepMonitorResult;
@ -108,7 +110,17 @@ public class MorpheuzSupport {
type = MorpheuzSampleProvider.TYPE_LIGHT_SLEEP;
}
if (index >= 0) {
GBApplication.getActivityDatabaseHandler().addGBActivitySample(recording_base_timestamp + index * 600, SampleProvider.PROVIDER_PEBBLE_MORPHEUZ, intensity, (byte) 0, type);
DBHandler db = null;
try {
db = GBApplication.acquireDB();
db.addGBActivitySample(recording_base_timestamp + index * 600, SampleProvider.PROVIDER_PEBBLE_MORPHEUZ, intensity, (byte) 0, type);
} catch (GBException e) {
LOG.error("Error acquiring database", e);
} finally {
if (db != null) {
db.release();
}
}
}
ctrl_message = MorpheuzSupport.CTRL_VERSION_DONE | MorpheuzSupport.CTRL_SET_LAST_SENT | MorpheuzSupport.CTRL_DO_NEXT;

View File

@ -5,7 +5,7 @@
android:title="@string/controlcenter_fetch_activity_data"/>
<item
android:id="@+id/controlcenter_start_sleepmonitor"
android:title="@string/controlcenter_start_sleepmonitor"/>
android:title="@string/controlcenter_start_activitymonitor"/>
<item
android:id="@+id/controlcenter_configure_alarms"
android:title="@string/controlcenter_start_configure_alarms"/>

View File

@ -158,4 +158,6 @@
<string name="chart_no_data_synchronize">No data. Synchronize device?</string>
<string name="user_feedback_miband_activity_data_transfer">About to transfer %1$s of data starting from %2$s</string>
<string name="miband_prefs_fitness_goal">Target steps for each day</string>
<string name="dbaccess_error_executing">Error executing \'%1$s\'</string>
<string name="controlcenter_start_activitymonitor">Your Activity (ALPHA)</string>
</resources>