diff --git a/app/build.gradle b/app/build.gradle
index 4e201fdf7..57031425f 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -26,4 +26,5 @@ dependencies {
// compile 'com.noveogroup.android:android-logger:1.3.5'
compile 'com.github.tony19:logback-android-classic:1.1.1-3'
compile 'org.slf4j:slf4j-api:1.7.7'
+ compile 'com.github.PhilJay:MPAndroidChart:2.1.0'
}
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index 5313245ea..0c2e1803e 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -62,6 +62,13 @@
android:name="android.support.PARENT_ACTIVITY"
android:value=".ControlCenter" />
+
+
+
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/ControlCenter.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/ControlCenter.java
index 3a82649a2..df7537fa7 100644
--- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/ControlCenter.java
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/ControlCenter.java
@@ -27,6 +27,7 @@ import java.util.ArrayList;
import java.util.List;
import java.util.Set;
+import nodomain.freeyourgadget.gadgetbridge.activities.SleepChartActivity;
import nodomain.freeyourgadget.gadgetbridge.adapter.GBDeviceAdapter;
import nodomain.freeyourgadget.gadgetbridge.discovery.DiscoveryActivity;
import nodomain.freeyourgadget.gadgetbridge.miband.MiBandConst;
@@ -176,8 +177,8 @@ public class ControlCenter extends Activity {
switch (item.getItemId()) {
case R.id.controlcenter_start_sleepmonitor:
if (selectedDevice != null) {
- Intent startIntent = new Intent(ControlCenter.this, SleepMonitorActivity.class);
-// Intent startIntent = new Intent(ControlCenter.this, AbstractChartActivity.class);
+// Intent startIntent = new Intent(ControlCenter.this, SleepMonitorActivity.class);
+ Intent startIntent = new Intent(ControlCenter.this, SleepChartActivity.class);
startIntent.putExtra("device", selectedDevice);
startActivity(startIntent);
}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/SleepChartActivity.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/SleepChartActivity.java
new file mode 100644
index 000000000..d45981219
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/SleepChartActivity.java
@@ -0,0 +1,382 @@
+package nodomain.freeyourgadget.gadgetbridge.activities;
+
+import android.app.Activity;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.graphics.Color;
+import android.os.Bundle;
+import android.support.v4.app.NavUtils;
+import android.support.v4.content.LocalBroadcastManager;
+import android.view.MenuItem;
+
+import com.github.mikephil.charting.animation.Easing;
+import com.github.mikephil.charting.charts.BarLineChartBase;
+import com.github.mikephil.charting.components.XAxis;
+import com.github.mikephil.charting.components.YAxis;
+import com.github.mikephil.charting.data.BarData;
+import com.github.mikephil.charting.data.BarDataSet;
+import com.github.mikephil.charting.data.BarEntry;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.text.SimpleDateFormat;
+import java.util.ArrayList;
+import java.util.Calendar;
+import java.util.Date;
+import java.util.List;
+
+import nodomain.freeyourgadget.gadgetbridge.ControlCenter;
+import nodomain.freeyourgadget.gadgetbridge.GBActivitySample;
+import nodomain.freeyourgadget.gadgetbridge.GBApplication;
+import nodomain.freeyourgadget.gadgetbridge.GBDevice;
+import nodomain.freeyourgadget.gadgetbridge.R;
+
+
+public class SleepChartActivity extends Activity {
+ public static final String ACTION_REFRESH
+ = "nodomain.freeyourgadget.gadgetbride.chart.action.refresh";
+ protected static final Logger LOG = LoggerFactory.getLogger(SleepChartActivity.class);
+
+ private BarLineChartBase mChart;
+
+ private int mSmartAlarmFrom = -1;
+ private int mSmartAlarmTo = -1;
+ private int mTimestampFrom = -1;
+ private int mSmartAlarmGoneOff = -1;
+ private GBDevice mGBDevice = null;
+
+ private BroadcastReceiver mReceiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ String action = intent.getAction();
+ if (action.equals(ControlCenter.ACTION_QUIT)) {
+ finish();
+ } else if (action.equals(ACTION_REFRESH)) {
+ // TODO: use LimitLines to visualize smart alarms?
+ mSmartAlarmFrom = intent.getIntExtra("smartalarm_from", -1);
+ mSmartAlarmTo = intent.getIntExtra("smartalarm_to", -1);
+ mTimestampFrom = intent.getIntExtra("recording_base_timestamp", -1);
+ mSmartAlarmGoneOff = intent.getIntExtra("alarm_gone_off", -1);
+ refresh();
+ }
+ }
+ };
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ Bundle extras = getIntent().getExtras();
+ if (extras != null) {
+ mGBDevice = extras.getParcelable("device");
+ }
+
+ getActionBar().setDisplayHomeAsUpEnabled(true);
+
+ IntentFilter filter = new IntentFilter();
+ filter.addAction(ControlCenter.ACTION_QUIT);
+ filter.addAction(ACTION_REFRESH);
+
+ LocalBroadcastManager.getInstance(this).registerReceiver(mReceiver, filter);
+
+// getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN,
+// WindowManager.LayoutParams.FLAG_FULLSCREEN);
+ setContentView(R.layout.activity_sleepmonitor2);
+
+ mChart = (BarLineChartBase) findViewById(R.id.sleepchart2);
+ mChart.setBackgroundColor(Color.rgb(24, 22, 24));
+ mChart.setDescriptionColor(Color.WHITE);
+
+ // if enabled, the chart will always start at zero on the y-axis
+
+ // disable value highlighting
+ mChart.setHighlightEnabled(false);
+
+ // enable touch gestures
+ mChart.setTouchEnabled(true);
+
+ // enable scaling and dragging
+ mChart.setDragEnabled(true);
+ mChart.setScaleEnabled(true);
+
+ // if disabled, scaling can be done on x- and y-axis separately
+ mChart.setPinchZoom(true);
+
+ mChart.setDrawGridBackground(false);
+
+// tf = Typeface.createFromAsset(getAssets(), "OpenSans-Regular.ttf");
+
+ XAxis x = mChart.getXAxis();
+ x.setDrawLabels(true);
+ x.setDrawGridLines(false);
+// x.setTypeface(tf);
+ x.setEnabled(true);
+ x.setTextColor(Color.WHITE);
+
+ YAxis y = mChart.getAxisLeft();
+ y.setDrawGridLines(false);
+ y.setDrawLabels(false);
+ y.setDrawTopYLabelEntry(false);
+ y.setTextColor(Color.WHITE);
+
+// y.setTypeface(tf);
+// y.setLabelCount(5);
+ y.setEnabled(true);
+
+ YAxis yAxisRight = mChart.getAxisRight();
+ yAxisRight.setDrawGridLines(false);
+ yAxisRight.setEnabled(false);
+ yAxisRight.setDrawLabels(false);
+ yAxisRight.setDrawTopYLabelEntry(false);
+ yAxisRight.setTextColor(Color.WHITE);
+
+ refresh();
+
+ mChart.getLegend().setTextColor(Color.WHITE);
+// mChart.getLegend().setEnabled(false);
+//
+// mChart.animateXY(2000, 2000);
+
+ // dont forget to refresh the drawing
+ mChart.invalidate();
+ }
+
+ @Override
+ protected void onDestroy() {
+ LocalBroadcastManager.getInstance(this).unregisterReceiver(mReceiver);
+ super.onDestroy();
+ }
+
+ private byte getProvider(GBDevice device) {
+ byte provider = -1;
+ switch (device.getType()) {
+ case MIBAND:
+ provider = GBActivitySample.PROVIDER_MIBAND;
+ break;
+ case PEBBLE:
+ provider = GBActivitySample.PROVIDER_PEBBLE_MORPHEUZ; // FIXME
+ break;
+ }
+ return provider;
+ }
+
+ private ArrayList getSamples(GBDevice device, int tsFrom, int tsTo) {
+ if (tsFrom == -1) {
+ long ts = System.currentTimeMillis();
+ tsFrom = (int) ((ts / 1000) - (24 * 60 * 60) & 0xffffffff); // -24 hours
+ }
+
+ byte provider = getProvider(device);
+ return GBApplication.getActivityDatabaseHandler().getGBActivitySamples(tsFrom, tsTo, provider);
+ }
+
+ private ArrayList getTestSamples(GBDevice device, int tsFrom, int tsTo) {
+ Calendar cal = Calendar.getInstance();
+ cal.clear();
+ cal.set(2015, Calendar.JUNE, 10, 6, 40);
+ // ignore provided date ranges
+ tsTo = (int) ((cal.getTimeInMillis() / 1000) & 0xffffffff);
+ tsFrom = tsTo - (24 * 60 * 60);
+
+ byte provider = getProvider(device);
+ return GBApplication.getActivityDatabaseHandler().getGBActivitySamples(tsFrom, tsTo, provider);
+ }
+
+ private void refresh() {
+ if (mGBDevice == null) {
+ return;
+ }
+
+// ArrayList samples = getTestSamples(mGBDevice, -1, -1);
+ ArrayList samples = getSamples(mGBDevice, -1, -1);
+
+ Calendar cal = Calendar.getInstance();
+ cal.clear();
+ Date date;
+ String dateStringFrom = "";
+ String dateStringTo = "";
+
+ LOG.info("number of samples:" + samples.size());
+ if (samples.size() > 1) {
+ float movement_divisor;
+ boolean annotate;
+ boolean use_steps_as_movement;
+ switch (getProvider(mGBDevice)) {
+ case GBActivitySample.PROVIDER_MIBAND:
+ movement_divisor = 256.0f;
+ annotate = true; // sample density to high?
+ use_steps_as_movement = true;
+ break;
+ default: // Morpheuz
+ movement_divisor = 5000.0f;
+ annotate = true;
+ use_steps_as_movement = false;
+ break;
+ }
+
+ byte last_type = GBActivitySample.TYPE_UNKNOWN;
+
+ SimpleDateFormat dateFormat = new SimpleDateFormat("dd.MM.yyyy HH:mm");
+ SimpleDateFormat annotationDateFormat = new SimpleDateFormat("HH:mm");
+
+ int numEntries = samples.size();
+ List xLabels = new ArrayList<>(numEntries);
+ List deepSleepEntries = new ArrayList<>(numEntries / 4);
+ List lightSleepEntries = new ArrayList<>(numEntries / 4);
+ List activityEntries = new ArrayList<>(numEntries);
+
+ for (int i = 0; i < numEntries; i++) {
+ GBActivitySample sample = samples.get(i);
+ byte type = sample.getType();
+
+ // determine start and end dates
+ if (i == 0) {
+ cal.setTimeInMillis(sample.getTimestamp() * 1000L); // make sure it's converted to long
+ date = cal.getTime();
+ dateStringFrom = dateFormat.format(date);
+ } else if (i == samples.size() - 1) {
+ cal.setTimeInMillis(sample.getTimestamp() * 1000L); // same here
+ date = cal.getTime();
+ dateStringTo = dateFormat.format(date);
+ }
+
+ short movement = sample.getIntensity();
+
+
+ BarEntry emptyEntry = createEntry(0, i);
+ float value;
+ if (type == GBActivitySample.TYPE_DEEP_SLEEP) {
+ value = 0.01f;
+ deepSleepEntries.add(createEntry(value, i));
+ lightSleepEntries.add(emptyEntry);
+ activityEntries.add(emptyEntry);
+ } else {
+ if (type == GBActivitySample.TYPE_LIGHT_SLEEP) {
+ value = ((float) movement / movement_divisor);
+ lightSleepEntries.add(createEntry(value, i));
+ deepSleepEntries.add(emptyEntry);
+ activityEntries.add(emptyEntry);
+ } else {
+ byte steps = sample.getSteps();
+ if (use_steps_as_movement && steps != 0) {
+ // I'm not sure using steps for this is actually a good idea
+ movement = steps;
+ }
+ value = ((float) movement / movement_divisor);
+ activityEntries.add(createEntry(value, i));
+ lightSleepEntries.add(emptyEntry);
+ deepSleepEntries.add(emptyEntry);
+ }
+ }
+
+ String xLabel = "";
+ boolean annotate_this = false;
+ if (annotate) {
+ if (true || type != GBActivitySample.TYPE_DEEP_SLEEP && type != GBActivitySample.TYPE_LIGHT_SLEEP &&
+ (last_type == GBActivitySample.TYPE_DEEP_SLEEP || last_type == GBActivitySample.TYPE_LIGHT_SLEEP)) {
+ // seems that we woke up
+ annotate_this = true;
+ }
+ if (annotate_this) {
+ cal.setTimeInMillis(sample.getTimestamp() * 1000L);
+ date = cal.getTime();
+ String dateString = annotationDateFormat.format(date);
+ xLabel = dateString;
+ }
+ last_type = type;
+ }
+ xLabels.add(xLabel);
+ }
+
+ mChart.getXAxis().setValues(xLabels);
+
+ BarDataSet deepSleepSet = createDeepSleepSet(deepSleepEntries, "Deep Sleep");
+ BarDataSet lightSleepSet = createLightSleepSet(lightSleepEntries, "Light Sleep");
+ BarDataSet activitySet = createActivitySet(activityEntries, "Activity");
+
+ ArrayList dataSets = new ArrayList<>();
+ dataSets.add(deepSleepSet);
+ dataSets.add(lightSleepSet);
+ dataSets.add(activitySet);
+
+ // create a data object with the datasets
+ BarData data = new BarData(xLabels, dataSets);
+
+ mChart.setDescription(getString(R.string.sleep_activity_date_range, dateStringFrom, dateStringTo));
+// mChart.setDescriptionPosition(?, ?);
+ // set data
+ mChart.setData(data);
+
+ mChart.animateX(1000, Easing.EasingOption.EaseInOutQuart);
+
+// textView.setText(dateStringFrom + " to " + dateStringTo);
+ }
+ }
+
+ private BarEntry createEntry(float value, int index) {
+ return new BarEntry(value, index);
+ }
+
+ @Override
+ public boolean onOptionsItemSelected(MenuItem item) {
+ switch (item.getItemId()) {
+ case android.R.id.home:
+ NavUtils.navigateUpFromSameTask(this);
+ return true;
+ }
+ return super.onOptionsItemSelected(item);
+ }
+
+ private BarDataSet createActivitySet(List values, String label) {
+ BarDataSet set1 = new BarDataSet(values, label);
+// set1.setDrawCubic(true);
+// set1.setCubicIntensity(0.2f);
+// //set1.setDrawFilled(true);
+// set1.setDrawCircles(false);
+// set1.setLineWidth(2f);
+// set1.setCircleSize(5f);
+// set1.setFillColor(ColorTemplate.getHoloBlue());
+ set1.setDrawValues(false);
+// set1.setHighLightColor(Color.rgb(128, 0, 255));
+ set1.setColor(Color.rgb(128, 0, 255));
+ set1.setValueTextColor(Color.WHITE);
+ return set1;
+ }
+
+ private BarDataSet createDeepSleepSet(List values, String label) {
+ BarDataSet set1 = new BarDataSet(values, label);
+// set1.setDrawCubic(true);
+// set1.setCubicIntensity(0.2f);
+// //set1.setDrawFilled(true);
+// set1.setDrawCircles(false);
+// set1.setLineWidth(2f);
+// set1.setCircleSize(5f);
+// set1.setFillColor(ColorTemplate.getHoloBlue());
+ set1.setDrawValues(false);
+// set1.setHighLightColor(Color.rgb(244, 117, 117));
+ set1.setColor(Color.rgb(255, 64, 0));
+ set1.setValueTextColor(Color.WHITE);
+ return set1;
+ }
+
+ private BarDataSet createLightSleepSet(List values, String label) {
+ BarDataSet set1 = new BarDataSet(values, label);
+
+// set1.setDrawCubic(true);
+// set1.setCubicIntensity(0.2f);
+// //set1.setDrawFilled(true);
+// set1.setDrawCircles(false);
+// set1.setLineWidth(2f);
+// set1.setCircleSize(5f);
+// set1.setFillColor(ColorTemplate.getHoloBlue());
+ set1.setDrawValues(false);
+// set1.setHighLightColor(Color.rgb(244, 117, 117));
+ set1.setColor(Color.rgb(255, 199, 0));
+ set1.setValueTextColor(Color.WHITE);
+// set1.setColor(Color.CYAN);
+ return set1;
+ }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/miband/MiBandSupport.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/miband/MiBandSupport.java
index 13b559ba9..33bcf6e0e 100644
--- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/miband/MiBandSupport.java
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/miband/MiBandSupport.java
@@ -51,8 +51,8 @@ public class MiBandSupport extends AbstractBTLEDeviceSupport {
private static final Logger LOG = LoggerFactory.getLogger(MiBandSupport.class);
private static final Logger ACTIVITYLOG = LoggerFactory.getLogger("activity");
- //temporary buffer, size is 60 because we want to store complete minutes (1 minute = 3 bytes)
- private static final int activityDataHolderSize = 60;
+ //temporary buffer, size is a multiple of 60 because we want to store complete minutes (1 minute = 3 bytes)
+ private static final int activityDataHolderSize = 60 * 24; // 8h
private byte[] activityDataHolder = new byte[activityDataHolderSize];
//index of the buffer above
private int activityDataHolderProgress = 0;
diff --git a/app/src/main/res/layout/activity_sleepmonitor2.xml b/app/src/main/res/layout/activity_sleepmonitor2.xml
new file mode 100644
index 000000000..7f2f993cc
--- /dev/null
+++ b/app/src/main/res/layout/activity_sleepmonitor2.xml
@@ -0,0 +1,13 @@
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index 56dece173..37e33edc6 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -108,5 +108,6 @@
Fetching Activity Data
Fetch Activity Data
Disconnect
+ From %1$s to %2$s
diff --git a/build.gradle b/build.gradle
index 6356aabdc..a39c58637 100644
--- a/build.gradle
+++ b/build.gradle
@@ -15,5 +15,8 @@ buildscript {
allprojects {
repositories {
jcenter()
+ maven {
+ url "https://jitpack.io"
+ }
}
}