diff --git a/.travis.yml b/.travis.yml
index a73f784f7..38045ed07 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -1,7 +1,12 @@
language: android
+
jdk:
- oraclejdk8
- oraclejdk7
+
+env:
+ - GRADLE_OPTS="-XX:MaxPermSize=256m"
+
android:
components:
# Uncomment the lines below if you want to
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 8c5bc3d5e..6d4d3b66f 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,16 @@
###Changelog
+####Version (next)
+* Pebble: support pebble health datalog messages of firmware 3.11 (this adds support for deep sleep!)
+* Pebble: try to reconnect on new notifications and phone calls when connection was lost unexpectedly
+* Pebble: delay between reconnection attempts (from 1 up to 64 seconds)
+
+####Version 0.9.3
+* Pebble: Fix Pebble Health activation (was not available in the App Manager)
+* Simplify connection state display (only connecting->connected)
+* Small improvements to the pairing activity
+* Mi Band 1S: Fix for mi band firmware update
+
####Version 0.9.2
* Mi Band: Fix update of second (HR) firmware on Mi1S (#234)
* Fix ordering issue of device infos being displayed
diff --git a/app/build.gradle b/app/build.gradle
index 2da01cac0..f01ac6b4f 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -16,8 +16,8 @@ android {
targetSdkVersion 23
// note: always bump BOTH versionCode and versionName!
- versionName "0.9.2"
- versionCode 46
+ versionName "0.9.4"
+ versionCode 48
}
buildTypes {
release {
diff --git a/app/src/main/assets/ic_launcher.svg b/app/src/main/assets/ic_launcher.svg
new file mode 100644
index 000000000..03b479948
--- /dev/null
+++ b/app/src/main/assets/ic_launcher.svg
@@ -0,0 +1,149 @@
+
+
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/AbstractSettingsActivity.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/AbstractSettingsActivity.java
index d12fee966..4d3c48a13 100644
--- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/AbstractSettingsActivity.java
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/AbstractSettingsActivity.java
@@ -33,7 +33,7 @@ public abstract class AbstractSettingsActivity extends PreferenceActivity {
}
public void updateSummary(Preference preference, Object value) {
- String stringValue = value.toString();
+ String stringValue = String.valueOf(value);
if (preference instanceof ListPreference) {
// For list preferences, look up the correct display value in
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/AppManagerActivity.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/AppManagerActivity.java
index e2a1d56dc..6b566f3e2 100644
--- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/AppManagerActivity.java
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/AppManagerActivity.java
@@ -80,7 +80,7 @@ public class AppManagerActivity extends Activity {
List systemApps = new ArrayList<>();
systemApps.add(new GBDeviceApp(UUID.fromString("4dab81a6-d2fc-458a-992c-7a1f3b96a970"), "Sports (System)", "Pebble Inc.", "", GBDeviceApp.Type.APP_SYSTEM));
systemApps.add(new GBDeviceApp(UUID.fromString("cf1e816a-9db0-4511-bbb8-f60c48ca8fac"), "Golf (System)", "Pebble Inc.", "", GBDeviceApp.Type.APP_SYSTEM));
- if (mGBDevice != null && !"aplite".equals(PebbleUtils.getPlatformName(mGBDevice.getFirmwareVersion()))) {
+ if (mGBDevice != null && !"aplite".equals(PebbleUtils.getPlatformName(mGBDevice.getHardwareVersion()))) {
systemApps.add(new GBDeviceApp(PebbleProtocol.UUID_PEBBLE_HEALTH, "Health (System)", "Pebble Inc.", "", GBDeviceApp.Type.APP_SYSTEM));
}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/DebugActivity.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/DebugActivity.java
index 3d438bcdc..dc7eb2f5f 100644
--- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/DebugActivity.java
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/DebugActivity.java
@@ -29,9 +29,10 @@ import nodomain.freeyourgadget.gadgetbridge.GBApplication;
import nodomain.freeyourgadget.gadgetbridge.R;
import nodomain.freeyourgadget.gadgetbridge.database.DBHandler;
import nodomain.freeyourgadget.gadgetbridge.database.DBHelper;
+import nodomain.freeyourgadget.gadgetbridge.model.CallSpec;
+import nodomain.freeyourgadget.gadgetbridge.model.MusicSpec;
import nodomain.freeyourgadget.gadgetbridge.model.NotificationSpec;
import nodomain.freeyourgadget.gadgetbridge.model.NotificationType;
-import nodomain.freeyourgadget.gadgetbridge.model.ServiceCommand;
import nodomain.freeyourgadget.gadgetbridge.util.FileUtils;
import nodomain.freeyourgadget.gadgetbridge.util.GB;
@@ -118,20 +119,20 @@ public class DebugActivity extends Activity {
incomingCallButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
- GBApplication.deviceService().onSetCallState(
- editContent.getText().toString(),
- null,
- ServiceCommand.CALL_INCOMING);
+ CallSpec callSpec = new CallSpec();
+ callSpec.command = CallSpec.CALL_INCOMING;
+ callSpec.number = editContent.getText().toString();
+ GBApplication.deviceService().onSetCallState(callSpec);
}
});
outgoingCallButton = (Button) findViewById(R.id.outgoingCallButton);
outgoingCallButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
- GBApplication.deviceService().onSetCallState(
- editContent.getText().toString(),
- null,
- ServiceCommand.CALL_OUTGOING);
+ CallSpec callSpec = new CallSpec();
+ callSpec.command = CallSpec.CALL_OUTGOING;
+ callSpec.number = editContent.getText().toString();
+ GBApplication.deviceService().onSetCallState(callSpec);
}
});
@@ -139,20 +140,18 @@ public class DebugActivity extends Activity {
startCallButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
- GBApplication.deviceService().onSetCallState(
- null,
- null,
- ServiceCommand.CALL_START);
+ CallSpec callSpec = new CallSpec();
+ callSpec.command = CallSpec.CALL_START;
+ GBApplication.deviceService().onSetCallState(callSpec);
}
});
endCallButton = (Button) findViewById(R.id.endCallButton);
endCallButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
- GBApplication.deviceService().onSetCallState(
- null,
- null,
- ServiceCommand.CALL_END);
+ CallSpec callSpec = new CallSpec();
+ callSpec.command = CallSpec.CALL_END;
+ GBApplication.deviceService().onSetCallState(callSpec);
}
});
@@ -199,10 +198,15 @@ public class DebugActivity extends Activity {
setMusicInfoButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
- GBApplication.deviceService().onSetMusicInfo(
- editContent.getText().toString() + "(artist)",
- editContent.getText().toString() + "(album)",
- editContent.getText().toString() + "(track)", 20, 10, 2);
+ MusicSpec musicSpec = new MusicSpec();
+ musicSpec.artist = editContent.getText().toString() + "(artist)";
+ musicSpec.album = editContent.getText().toString() + "(album)";
+ musicSpec.track = editContent.getText().toString() + "(track)";
+ musicSpec.duration = 10;
+ musicSpec.trackCount = 5;
+ musicSpec.trackNr = 2;
+
+ GBApplication.deviceService().onSetMusicInfo(musicSpec);
}
});
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/FwAppInstallerActivity.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/FwAppInstallerActivity.java
index 0c7b88cdf..7f8d507f1 100644
--- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/FwAppInstallerActivity.java
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/FwAppInstallerActivity.java
@@ -29,6 +29,7 @@ import nodomain.freeyourgadget.gadgetbridge.adapter.ItemWithDetailsAdapter;
import nodomain.freeyourgadget.gadgetbridge.devices.DeviceCoordinator;
import nodomain.freeyourgadget.gadgetbridge.devices.InstallHandler;
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+import nodomain.freeyourgadget.gadgetbridge.model.GenericItem;
import nodomain.freeyourgadget.gadgetbridge.model.ItemWithDetails;
import nodomain.freeyourgadget.gadgetbridge.util.DeviceHelper;
import nodomain.freeyourgadget.gadgetbridge.util.GB;
@@ -37,6 +38,7 @@ import nodomain.freeyourgadget.gadgetbridge.util.GB;
public class FwAppInstallerActivity extends Activity implements InstallActivity {
private static final Logger LOG = LoggerFactory.getLogger(FwAppInstallerActivity.class);
+ private static final String ITEM_DETAILS = "details";
private TextView fwAppInstallTextView;
private Button installButton;
@@ -45,13 +47,22 @@ public class FwAppInstallerActivity extends Activity implements InstallActivity
private InstallHandler installHandler;
private boolean mayConnect;
+ private ProgressBar mProgressBar;
+ private ListView itemListView;
+ private final List mItems = new ArrayList<>();
+ private ItemWithDetailsAdapter mItemAdapter;
+
+ private ListView detailsListView;
+ private ItemWithDetailsAdapter mDetailsItemAdapter;
+ private ArrayList mDetails = new ArrayList<>();
+
private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
String action = intent.getAction();
- if (action.equals(GBApplication.ACTION_QUIT)) {
+ if (GBApplication.ACTION_QUIT.equals(action)) {
finish();
- } else if (action.equals(GBDevice.ACTION_DEVICE_CHANGED)) {
+ } else if (GBDevice.ACTION_DEVICE_CHANGED.equals(action)) {
device = intent.getParcelableExtra(GBDevice.EXTRA_DEVICE);
if (device != null) {
refreshBusyState(device);
@@ -67,13 +78,13 @@ public class FwAppInstallerActivity extends Activity implements InstallActivity
validateInstallation();
}
}
+ } else if (GB.ACTION_DISPLAY_MESSAGE.equals(action)) {
+ String message = intent.getStringExtra(GB.DISPLAY_MESSAGE_MESSAGE);
+ int severity = intent.getIntExtra(GB.DISPLAY_MESSAGE_SEVERITY, GB.INFO);
+ addMessage(message, severity);
}
}
};
- private ProgressBar mProgressBar;
- private ListView itemListView;
- private final List mItems = new ArrayList<>();
- private ItemWithDetailsAdapter mItemAdapter;
private void refreshBusyState(GBDevice dev) {
if (dev.isConnecting() || dev.isBusy()) {
@@ -107,6 +118,13 @@ public class FwAppInstallerActivity extends Activity implements InstallActivity
if (dev != null) {
device = dev;
}
+ if (savedInstanceState != null) {
+ mDetails = savedInstanceState.getParcelableArrayList(ITEM_DETAILS);
+ if (mDetails == null) {
+ mDetails = new ArrayList<>();
+ }
+ }
+
mayConnect = true;
itemListView = (ListView) findViewById(R.id.itemListView);
mItemAdapter = new ItemWithDetailsAdapter(this, mItems);
@@ -114,10 +132,15 @@ public class FwAppInstallerActivity extends Activity implements InstallActivity
fwAppInstallTextView = (TextView) findViewById(R.id.infoTextView);
installButton = (Button) findViewById(R.id.installButton);
mProgressBar = (ProgressBar) findViewById(R.id.installProgressBar);
+ detailsListView = (ListView) findViewById(R.id.detailsListView);
+ mDetailsItemAdapter = new ItemWithDetailsAdapter(this, mDetails);
+ mDetailsItemAdapter.setSize(ItemWithDetailsAdapter.SIZE_SMALL);
+ detailsListView.setAdapter(mDetailsItemAdapter);
setInstallEnabled(false);
IntentFilter filter = new IntentFilter();
filter.addAction(GBApplication.ACTION_QUIT);
filter.addAction(GBDevice.ACTION_DEVICE_CHANGED);
+ filter.addAction(GB.ACTION_DISPLAY_MESSAGE);
LocalBroadcastManager.getInstance(this).registerReceiver(mReceiver, filter);
installButton.setOnClickListener(new View.OnClickListener() {
@@ -145,6 +168,12 @@ public class FwAppInstallerActivity extends Activity implements InstallActivity
}
}
+ @Override
+ protected void onSaveInstanceState(Bundle outState) {
+ super.onSaveInstanceState(outState);
+ outState.putParcelableArrayList(ITEM_DETAILS, mDetails);
+ }
+
private InstallHandler findInstallHandlerFor(Uri uri) {
for (DeviceCoordinator coordinator : DeviceHelper.getInstance().getAllCoordinators()) {
InstallHandler handler = coordinator.findInstallHandler(uri, this);
@@ -195,4 +224,9 @@ public class FwAppInstallerActivity extends Activity implements InstallActivity
mItems.add(item);
mItemAdapter.notifyDataSetChanged();
}
+
+ private void addMessage(String message, int severity) {
+ mDetails.add(new GenericItem(message));
+ mDetailsItemAdapter.notifyDataSetChanged();
+ }
}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/charts/AbstractChartFragment.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/charts/AbstractChartFragment.java
index 21f7774f5..f163ba657 100644
--- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/charts/AbstractChartFragment.java
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/charts/AbstractChartFragment.java
@@ -4,6 +4,7 @@ import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
+import android.os.AsyncTask;
import android.os.Bundle;
import android.support.annotation.Nullable;
import android.support.v4.app.FragmentActivity;
@@ -79,7 +80,8 @@ public abstract class AbstractChartFragment extends AbstractGBFragment {
}
};
private boolean mChartDirty = true;
- private boolean supportsHeartrateChart = false;
+ private boolean supportsHeartrateChart = true;
+ private AsyncTask refreshTask;
public boolean isChartDirty() {
return mChartDirty;
@@ -119,6 +121,8 @@ public abstract class AbstractChartFragment extends AbstractGBFragment {
protected int AK_LIGHT_SLEEP_COLOR;
protected int AK_NOT_WORN_COLOR;
+ protected String HEARTRATE_LABEL;
+
protected AbstractChartFragment(String... intentFilterActions) {
mIntentFilterActions = new HashSet<>();
if (intentFilterActions != null) {
@@ -153,6 +157,8 @@ public abstract class AbstractChartFragment extends AbstractGBFragment {
AK_LIGHT_SLEEP_COLOR = getResources().getColor(R.color.chart_deep_sleep_light);
AK_NOT_WORN_COLOR = getResources().getColor(R.color.chart_not_worn_light);
+ HEARTRATE_LABEL = getContext().getString(R.string.charts_legend_heartrate);
+
akActivity = new ActivityConfig(ActivityKind.TYPE_ACTIVITY, getString(R.string.abstract_chart_fragment_kind_activity), AK_ACTIVITY_COLOR);
akLightSleep = new ActivityConfig(ActivityKind.TYPE_LIGHT_SLEEP, getString(R.string.abstract_chart_fragment_kind_light_sleep), AK_LIGHT_SLEEP_COLOR);
akDeepSleep = new ActivityConfig(ActivityKind.TYPE_DEEP_SLEEP, getString(R.string.abstract_chart_fragment_kind_deep_sleep), AK_DEEP_SLEEP_COLOR);
@@ -363,7 +369,10 @@ public abstract class AbstractChartFragment extends AbstractGBFragment {
if (chartsHost.getDevice() != null) {
mChartDirty = false;
updateDateInfo(getStartDate(), getEndDate());
- createRefreshTask("Visualizing data", getActivity()).execute();
+ if (refreshTask != null && refreshTask.getStatus() != AsyncTask.Status.FINISHED) {
+ refreshTask.cancel(true);
+ }
+ refreshTask = createRefreshTask("Visualizing data", getActivity()).execute();
}
}
}
@@ -445,7 +454,7 @@ public abstract class AbstractChartFragment extends AbstractGBFragment {
colors.add(akActivity.color);
}
activityEntries.add(createBarEntry(value, i));
- if (hr) {
+ if (hr && isValidHeartRateValue(sample.getCustomValue())) {
heartrateEntries.add(createLineEntry(sample.getCustomValue(), i));
}
@@ -488,7 +497,7 @@ public abstract class AbstractChartFragment extends AbstractGBFragment {
barData.setGroupSpace(0);
combinedData.setData(barData);
- if (hr) {
+ if (hr && heartrateEntries.size() > 0) {
LineDataSet heartrateSet = createHeartrateSet(heartrateEntries, "Heart Rate");
LineData lineData = new LineData(xLabels, heartrateSet);
combinedData.setData(lineData);
@@ -507,6 +516,10 @@ public abstract class AbstractChartFragment extends AbstractGBFragment {
}
}
+ protected boolean isValidHeartRateValue(int value) {
+ return value > 0 && value < 255;
+ }
+
/**
* Implement this to supply the samples to be displayed.
*
@@ -550,14 +563,19 @@ public abstract class AbstractChartFragment extends AbstractGBFragment {
LineDataSet set1 = new LineDataSet(values, label);
set1.setColor(HEARTRATE_COLOR);
// set1.setColors(colors);
-// set1.setDrawCubic(true);
-// set1.setCubicIntensity(0.2f);
+ set1.setDrawCubic(true);
+ set1.setCubicIntensity(0.1f);
// //set1.setDrawFilled(true);
// set1.setDrawCircles(false);
- set1.setLineWidth(2f);
-// set1.setCircleSize(5f);
+// set1.setLineWidth(2f);
+
+ set1.setDrawCircles(false);
+// set1.setCircleRadius(2f);
+// set1.setDrawFilled(true);
+
+ set1.setLineWidth(0.8f);
// set1.setFillColor(ColorTemplate.getHoloBlue());
- set1.setDrawValues(false);
+ set1.setDrawValues(true);
// set1.setHighLightColor(Color.rgb(128, 0, 255));
// set1.setColor(Color.rgb(89, 178, 44));
set1.setValueTextColor(CHART_TEXT_COLOR);
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/charts/ActivitySleepChartFragment.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/charts/ActivitySleepChartFragment.java
index 608a6f2a9..cd45452de 100644
--- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/charts/ActivitySleepChartFragment.java
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/charts/ActivitySleepChartFragment.java
@@ -70,6 +70,7 @@ public class ActivitySleepChartFragment extends AbstractChartFragment {
// y.setDrawLabels(false);
// TODO: make fixed max value optional
y.setAxisMaxValue(1f);
+ y.setAxisMinValue(0);
y.setDrawTopYLabelEntry(false);
y.setTextColor(CHART_TEXT_COLOR);
@@ -82,6 +83,8 @@ public class ActivitySleepChartFragment extends AbstractChartFragment {
yAxisRight.setDrawLabels(true);
yAxisRight.setDrawTopYLabelEntry(true);
yAxisRight.setTextColor(CHART_TEXT_COLOR);
+ yAxisRight.setAxisMaxValue(250);
+ yAxisRight.setAxisMinValue(0);
// refresh immediately instead of use refreshIfVisible(), for perceived performance
refresh();
@@ -125,6 +128,10 @@ public class ActivitySleepChartFragment extends AbstractChartFragment {
legendLabels.add(akDeepSleep.label);
legendColors.add(akNotWorn.color);
legendLabels.add(akNotWorn.label);
+ if (supportsHeartrate()) {
+ legendColors.add(HEARTRATE_COLOR);
+ legendLabels.add(HEARTRATE_LABEL);
+ }
chart.getLegend().setCustom(legendColors, legendLabels);
}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/charts/SleepChartFragment.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/charts/SleepChartFragment.java
index 6b9a75619..b0836eebb 100644
--- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/charts/SleepChartFragment.java
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/charts/SleepChartFragment.java
@@ -151,6 +151,7 @@ public class SleepChartFragment extends AbstractChartFragment {
// y.setDrawLabels(false);
// TODO: make fixed max value optional
y.setAxisMaxValue(1f);
+ y.setAxisMinValue(0);
y.setDrawTopYLabelEntry(false);
y.setTextColor(CHART_TEXT_COLOR);
@@ -159,10 +160,12 @@ public class SleepChartFragment extends AbstractChartFragment {
YAxis yAxisRight = mActivityChart.getAxisRight();
yAxisRight.setDrawGridLines(false);
- yAxisRight.setEnabled(false);
- yAxisRight.setDrawLabels(false);
- yAxisRight.setDrawTopYLabelEntry(false);
+ yAxisRight.setEnabled(supportsHeartrate());
+ yAxisRight.setDrawLabels(true);
+ yAxisRight.setDrawTopYLabelEntry(true);
yAxisRight.setTextColor(CHART_TEXT_COLOR);
+ yAxisRight.setAxisMaxValue(250);
+ yAxisRight.setAxisMinValue(0);
}
protected void setupLegend(Chart chart) {
@@ -172,6 +175,10 @@ public class SleepChartFragment extends AbstractChartFragment {
legendLabels.add(akLightSleep.label);
legendColors.add(akDeepSleep.color);
legendLabels.add(akDeepSleep.label);
+ if (supportsHeartrate()) {
+ legendColors.add(HEARTRATE_COLOR);
+ legendLabels.add(HEARTRATE_LABEL);
+ }
chart.getLegend().setCustom(legendColors, legendLabels);
chart.getLegend().setTextColor(LEGEND_TEXT_COLOR);
}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/adapter/ItemWithDetailsAdapter.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/adapter/ItemWithDetailsAdapter.java
index 92448d9c0..dcfee994f 100644
--- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/adapter/ItemWithDetailsAdapter.java
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/adapter/ItemWithDetailsAdapter.java
@@ -18,8 +18,12 @@ import nodomain.freeyourgadget.gadgetbridge.model.ItemWithDetails;
*/
public class ItemWithDetailsAdapter extends ArrayAdapter {
+ public static final int SIZE_SMALL = 1;
+ public static final int SIZE_MEDIUM = 2;
+ public static final int SIZE_LARGE = 3;
private final Context context;
private boolean horizontalAlignment;
+ private int size = SIZE_MEDIUM;
public ItemWithDetailsAdapter(Context context, List items) {
super(context, 0, items);
@@ -42,7 +46,14 @@ public class ItemWithDetailsAdapter extends ArrayAdapter {
if (horizontalAlignment) {
view = inflater.inflate(R.layout.item_with_details_horizontal, parent, false);
} else {
- view = inflater.inflate(R.layout.item_with_details, parent, false);
+ switch (size) {
+ case SIZE_SMALL:
+ view = inflater.inflate(R.layout.item_with_details_small, parent, false);
+ break;
+ default:
+ view = inflater.inflate(R.layout.item_with_details, parent, false);
+ break;
+ }
}
}
ImageView iconView = (ImageView) view.findViewById(R.id.item_image);
@@ -55,4 +66,12 @@ public class ItemWithDetailsAdapter extends ArrayAdapter {
return view;
}
+
+ public void setSize(int size) {
+ this.size = size;
+ }
+
+ public int getSize() {
+ return size;
+ }
}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/database/ActivityDatabaseHandler.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/database/ActivityDatabaseHandler.java
index f8812b308..ce9e1ae46 100644
--- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/database/ActivityDatabaseHandler.java
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/database/ActivityDatabaseHandler.java
@@ -275,6 +275,24 @@ public class ActivityDatabaseHandler extends SQLiteOpenHelper implements DBHandl
}
}
+ @Override
+ public void changeStoredSamplesType(int timestampFrom, int timestampTo, int fromKind, int toKind, SampleProvider provider) {
+ try (SQLiteDatabase db = this.getReadableDatabase()) {
+ String sql = "UPDATE " + TABLE_GBACTIVITYSAMPLES + " SET " + KEY_TYPE + "= ? WHERE "
+ + KEY_TYPE + " = ? AND "
+ + KEY_PROVIDER + " = ? AND "
+ + KEY_TIMESTAMP + " >= ? AND " + KEY_TIMESTAMP + " < ? ;"; //do not use BETWEEN because the range is inclusive in that case!
+
+ SQLiteStatement statement = db.compileStatement(sql);
+ statement.bindLong(1, toKind);
+ statement.bindLong(2, fromKind);
+ statement.bindLong(3, provider.getID());
+ statement.bindLong(4, timestampFrom);
+ statement.bindLong(5, timestampTo);
+ statement.execute();
+ }
+ }
+
@Override
public int fetchLatestTimestamp(SampleProvider provider) {
try (SQLiteDatabase db = this.getReadableDatabase()) {
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/database/DBHandler.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/database/DBHandler.java
index 42aa64de9..49dbbd400 100644
--- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/database/DBHandler.java
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/database/DBHandler.java
@@ -32,6 +32,8 @@ public interface DBHandler {
void changeStoredSamplesType(int timestampFrom, int timestampTo, int kind, SampleProvider provider);
+ void changeStoredSamplesType(int timestampFrom, int timestampTo, int fromKind, int toKind, SampleProvider provider);
+
int fetchLatestTimestamp(SampleProvider provider);
}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/deviceevents/GBDeviceEventDisplayMessage.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/deviceevents/GBDeviceEventDisplayMessage.java
new file mode 100644
index 000000000..29e5ed12f
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/deviceevents/GBDeviceEventDisplayMessage.java
@@ -0,0 +1,22 @@
+package nodomain.freeyourgadget.gadgetbridge.deviceevents;
+
+public class GBDeviceEventDisplayMessage {
+ public String message;
+ public int duration;
+ public int severity;
+
+ /**
+ * An event for displaying a message to the user. How the message is displayed
+ * is a detail of the current activity, which needs to listen to the Intent
+ * GB.ACTION_DISPLAY_MESSAGE.
+ *
+ * @param message
+ * @param duration
+ * @param severity
+ */
+ public GBDeviceEventDisplayMessage(String message, int duration, int severity) {
+ this.message = message;
+ this.duration = duration;
+ this.severity = severity;
+ }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/EventHandler.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/EventHandler.java
index f03413afa..5ddad49e3 100644
--- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/EventHandler.java
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/EventHandler.java
@@ -1,14 +1,14 @@
package nodomain.freeyourgadget.gadgetbridge.devices;
import android.net.Uri;
-import android.support.annotation.Nullable;
import java.util.ArrayList;
import java.util.UUID;
import nodomain.freeyourgadget.gadgetbridge.model.Alarm;
+import nodomain.freeyourgadget.gadgetbridge.model.CallSpec;
+import nodomain.freeyourgadget.gadgetbridge.model.MusicSpec;
import nodomain.freeyourgadget.gadgetbridge.model.NotificationSpec;
-import nodomain.freeyourgadget.gadgetbridge.model.ServiceCommand;
/**
* Specifies all events that GadgetBridge intends to send to the gadget device.
@@ -22,9 +22,9 @@ public interface EventHandler {
void onSetAlarms(ArrayList extends Alarm> alarms);
- void onSetCallState(@Nullable String number, @Nullable String name, ServiceCommand command);
+ void onSetCallState(CallSpec callSpec);
- void onSetMusicInfo(String artist, String album, String track, int duration, int trackCount, int trackNr);
+ void onSetMusicInfo(MusicSpec musicSpec);
void onEnableRealtimeSteps(boolean enable);
@@ -48,4 +48,5 @@ public interface EventHandler {
void onScreenshotReq();
+ void onEnableHeartRateSleepSupport(boolean enable);
}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/InstallHandler.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/InstallHandler.java
index 1314dc0e0..e83815e62 100644
--- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/InstallHandler.java
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/InstallHandler.java
@@ -28,7 +28,7 @@ public interface InstallHandler {
void validateInstallation(InstallActivity installActivity, GBDevice device);
/**
- * Allows device specivic code to be execute just before the installation starts
+ * Allows device specific code to be executed just before the installation starts
*/
void onStartInstall(GBDevice device);
}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/miband/MiBandConst.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/miband/MiBandConst.java
index 6f3aa77f3..511de9ddd 100644
--- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/miband/MiBandConst.java
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/miband/MiBandConst.java
@@ -15,6 +15,7 @@ public final class MiBandConst {
public static final String PREF_MIBAND_FITNESS_GOAL = "mi_fitness_goal";
public static final String PREF_MIBAND_DONT_ACK_TRANSFER = "mi_dont_ack_transfer";
public static final String PREF_MIBAND_RESERVE_ALARM_FOR_CALENDAR = "mi_reserve_alarm_calendar";
+ public static final String PREF_MIBAND_USE_HR_FOR_SLEEP_DETECTION = "mi_hr_sleep_detection";
public static final String ORIGIN_SMS = "sms";
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/miband/MiBandCoordinator.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/miband/MiBandCoordinator.java
index 3ecf4c655..86ea93303 100644
--- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/miband/MiBandCoordinator.java
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/miband/MiBandCoordinator.java
@@ -135,6 +135,11 @@ public class MiBandCoordinator extends AbstractDeviceCoordinator {
return location;
}
+ public static boolean getHeartrateSleepSupport(String miBandAddress) throws IllegalArgumentException {
+ SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(GBApplication.getContext());
+ return prefs.getBoolean(MiBandConst.PREF_MIBAND_USE_HR_FOR_SLEEP_DETECTION, false);
+ }
+
public static int getFitnessGoal(String miBandAddress) throws IllegalArgumentException {
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(GBApplication.getContext());
return Integer.parseInt(prefs.getString(MiBandConst.PREF_MIBAND_FITNESS_GOAL, "10000"));
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/miband/MiBandPreferencesActivity.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/miband/MiBandPreferencesActivity.java
index 3cb8343fc..7ef19b80d 100644
--- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/miband/MiBandPreferencesActivity.java
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/miband/MiBandPreferencesActivity.java
@@ -5,6 +5,7 @@ import android.os.Bundle;
import android.preference.Preference;
import android.support.v4.content.LocalBroadcastManager;
+import nodomain.freeyourgadget.gadgetbridge.GBApplication;
import nodomain.freeyourgadget.gadgetbridge.R;
import nodomain.freeyourgadget.gadgetbridge.activities.AbstractSettingsActivity;
import nodomain.freeyourgadget.gadgetbridge.activities.ControlCenter;
@@ -18,6 +19,7 @@ import static nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBandConst.PR
import static nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBandConst.PREF_MIBAND_DONT_ACK_TRANSFER;
import static nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBandConst.PREF_MIBAND_FITNESS_GOAL;
import static nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBandConst.PREF_MIBAND_RESERVE_ALARM_FOR_CALENDAR;
+import static nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBandConst.PREF_MIBAND_USE_HR_FOR_SLEEP_DETECTION;
import static nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBandConst.PREF_MIBAND_WEARSIDE;
import static nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBandConst.PREF_USER_ALIAS;
import static nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBandConst.VIBRATION_COUNT;
@@ -43,6 +45,14 @@ public class MiBandPreferencesActivity extends AbstractSettingsActivity {
});
+ final Preference enableHeartrateSleepSupport = findPreference(PREF_MIBAND_USE_HR_FOR_SLEEP_DETECTION);
+ enableHeartrateSleepSupport.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() {
+ @Override
+ public boolean onPreferenceChange(Preference preference, Object newVal) {
+ GBApplication.deviceService().onEnableHeartRateSleepSupport(Boolean.TRUE.equals(newVal));
+ return true;
+ }
+ });
}
@Override
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/externalevents/MusicPlaybackReceiver.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/externalevents/MusicPlaybackReceiver.java
index 1b4520303..d910e75eb 100644
--- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/externalevents/MusicPlaybackReceiver.java
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/externalevents/MusicPlaybackReceiver.java
@@ -8,12 +8,11 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import nodomain.freeyourgadget.gadgetbridge.GBApplication;
+import nodomain.freeyourgadget.gadgetbridge.model.MusicSpec;
public class MusicPlaybackReceiver extends BroadcastReceiver {
private static final Logger LOG = LoggerFactory.getLogger(MusicPlaybackReceiver.class);
- private static String mLastSource;
-
@Override
public void onReceive(Context context, Intent intent) {
String artist = intent.getStringExtra("artist");
@@ -24,11 +23,16 @@ public class MusicPlaybackReceiver extends BroadcastReceiver {
for (String key : bundle.keySet()) {
Object value = bundle.get(key);
LOG.info(String.format("%s %s (%s)", key,
- value.toString(), value.getClass().getName()));
+ value != null ? value.toString() : "null", value != null ? value.getClass().getName() : "no class"));
}
*/
LOG.info("Current track: " + artist + ", " + album + ", " + track);
- GBApplication.deviceService().onSetMusicInfo(artist, album, track, 0, 0, 0);
+ MusicSpec musicSpec = new MusicSpec();
+ musicSpec.artist = artist;
+ musicSpec.artist = album;
+ musicSpec.artist = track;
+
+ GBApplication.deviceService().onSetMusicInfo(musicSpec);
}
}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/externalevents/NotificationListener.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/externalevents/NotificationListener.java
index d4f6ed63d..124c0b99d 100644
--- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/externalevents/NotificationListener.java
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/externalevents/NotificationListener.java
@@ -54,6 +54,9 @@ public class NotificationListener extends NotificationListenerService {
public void onReceive(Context context, Intent intent) {
String action = intent.getAction();
switch (action) {
+ case GBApplication.ACTION_QUIT:
+ stopSelf();
+ break;
case ACTION_MUTE:
case ACTION_OPEN: {
StatusBarNotification[] sbns = NotificationListener.this.getActiveNotifications();
@@ -130,6 +133,7 @@ public class NotificationListener extends NotificationListenerService {
public void onCreate() {
super.onCreate();
IntentFilter filterLocal = new IntentFilter();
+ filterLocal.addAction(GBApplication.ACTION_QUIT);
filterLocal.addAction(ACTION_OPEN);
filterLocal.addAction(ACTION_DISMISS);
filterLocal.addAction(ACTION_DISMISS_ALL);
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/externalevents/PhoneCallReceiver.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/externalevents/PhoneCallReceiver.java
index 26513a067..010b5fb82 100644
--- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/externalevents/PhoneCallReceiver.java
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/externalevents/PhoneCallReceiver.java
@@ -8,7 +8,7 @@ import android.preference.PreferenceManager;
import android.telephony.TelephonyManager;
import nodomain.freeyourgadget.gadgetbridge.GBApplication;
-import nodomain.freeyourgadget.gadgetbridge.model.ServiceCommand;
+import nodomain.freeyourgadget.gadgetbridge.model.CallSpec;
public class PhoneCallReceiver extends BroadcastReceiver {
@@ -40,35 +40,38 @@ public class PhoneCallReceiver extends BroadcastReceiver {
return;
}
- ServiceCommand callCommand = null;
+ int callCommand = CallSpec.CALL_UNDEFINED;
switch (state) {
case TelephonyManager.CALL_STATE_RINGING:
mSavedNumber = number;
- callCommand = ServiceCommand.CALL_INCOMING;
+ callCommand = CallSpec.CALL_INCOMING;
break;
case TelephonyManager.CALL_STATE_OFFHOOK:
if (mLastState == TelephonyManager.CALL_STATE_RINGING) {
- callCommand = ServiceCommand.CALL_START;
+ callCommand = CallSpec.CALL_START;
} else {
- callCommand = ServiceCommand.CALL_OUTGOING;
+ callCommand = CallSpec.CALL_OUTGOING;
mSavedNumber = number;
}
break;
case TelephonyManager.CALL_STATE_IDLE:
if (mLastState == TelephonyManager.CALL_STATE_RINGING) {
//missed call would be correct here
- callCommand = ServiceCommand.CALL_END;
+ callCommand = CallSpec.CALL_END;
} else {
- callCommand = ServiceCommand.CALL_END;
+ callCommand = CallSpec.CALL_END;
}
break;
}
- if (callCommand != null) {
+ if (callCommand != CallSpec.CALL_UNDEFINED) {
SharedPreferences sharedPrefs = PreferenceManager.getDefaultSharedPreferences(context);
if ("never".equals(sharedPrefs.getString("notification_mode_calls", "always"))) {
return;
}
- GBApplication.deviceService().onSetCallState(mSavedNumber, null, callCommand);
+ CallSpec callSpec = new CallSpec();
+ callSpec.number = mSavedNumber;
+ callSpec.command = callCommand;
+ GBApplication.deviceService().onSetCallState(callSpec);
}
mLastState = state;
}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/impl/GBDevice.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/impl/GBDevice.java
index 763e63750..1e46d9782 100644
--- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/impl/GBDevice.java
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/impl/GBDevice.java
@@ -53,7 +53,6 @@ public class GBDevice implements Parcelable {
private BatteryState mBatteryState;
private short mRssi = RSSI_UNKNOWN;
private String mBusyTask;
- private String infoString;
private List mDeviceInfos;
public GBDevice(String address, String name, DeviceType deviceType) {
@@ -204,23 +203,25 @@ public class GBDevice implements Parcelable {
}
public String getStateString() {
+ /*
+ * for simplicity the user wont see all internal states, just connecting -> connected
+ * instead of connecting->connected->initializing->initialized
+ */
switch (mState) {
case NOT_CONNECTED:
return GBApplication.getContext().getString(R.string.not_connected);
case WAITING_FOR_RECONNECT:
return GBApplication.getContext().getString(R.string.waiting_for_reconnect);
case CONNECTING:
- return GBApplication.getContext().getString(R.string.connecting);
case CONNECTED:
- return GBApplication.getContext().getString(R.string.connected);
case INITIALIZING:
- return GBApplication.getContext().getString(R.string.initializing);
+ return GBApplication.getContext().getString(R.string.connecting);
case AUTHENTICATION_REQUIRED:
return GBApplication.getContext().getString(R.string.authentication_required);
case AUTHENTICATING:
return GBApplication.getContext().getString(R.string.authenticating);
case INITIALIZED:
- return GBApplication.getContext().getString(R.string.initialized);
+ return GBApplication.getContext().getString(R.string.connected);
}
return GBApplication.getContext().getString(R.string.unknown_state);
}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/impl/GBDeviceService.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/impl/GBDeviceService.java
index 62d7f15cf..add9f676e 100644
--- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/impl/GBDeviceService.java
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/impl/GBDeviceService.java
@@ -10,9 +10,10 @@ import java.util.ArrayList;
import java.util.UUID;
import nodomain.freeyourgadget.gadgetbridge.model.Alarm;
+import nodomain.freeyourgadget.gadgetbridge.model.CallSpec;
import nodomain.freeyourgadget.gadgetbridge.model.DeviceService;
+import nodomain.freeyourgadget.gadgetbridge.model.MusicSpec;
import nodomain.freeyourgadget.gadgetbridge.model.NotificationSpec;
-import nodomain.freeyourgadget.gadgetbridge.model.ServiceCommand;
import nodomain.freeyourgadget.gadgetbridge.service.DeviceCommunicationService;
public class GBDeviceService implements DeviceService {
@@ -115,23 +116,23 @@ public class GBDeviceService implements DeviceService {
}
@Override
- public void onSetCallState(String number, String name, ServiceCommand command) {
+ public void onSetCallState(CallSpec callSpec) {
// name is actually ignored and provided by the service itself...
Intent intent = createIntent().setAction(ACTION_CALLSTATE)
- .putExtra(EXTRA_CALL_PHONENUMBER, number)
- .putExtra(EXTRA_CALL_COMMAND, command);
+ .putExtra(EXTRA_CALL_PHONENUMBER, callSpec.number)
+ .putExtra(EXTRA_CALL_COMMAND, callSpec.command);
invokeService(intent);
}
@Override
- public void onSetMusicInfo(String artist, String album, String track, int duration, int trackCount, int trackNr) {
+ public void onSetMusicInfo(MusicSpec musicSpec) {
Intent intent = createIntent().setAction(ACTION_SETMUSICINFO)
- .putExtra(EXTRA_MUSIC_ARTIST, artist)
- .putExtra(EXTRA_MUSIC_ALBUM, album)
- .putExtra(EXTRA_MUSIC_TRACK, track)
- .putExtra(EXTRA_MUSIC_DURATION, duration)
- .putExtra(EXTRA_MUSIC_TRACKCOUNT, trackCount)
- .putExtra(EXTRA_MUSIC_TRACKNR, trackNr);
+ .putExtra(EXTRA_MUSIC_ARTIST, musicSpec.artist)
+ .putExtra(EXTRA_MUSIC_ALBUM, musicSpec.album)
+ .putExtra(EXTRA_MUSIC_TRACK, musicSpec.track)
+ .putExtra(EXTRA_MUSIC_DURATION, musicSpec.duration)
+ .putExtra(EXTRA_MUSIC_TRACKCOUNT, musicSpec.trackCount)
+ .putExtra(EXTRA_MUSIC_TRACKNR, musicSpec.trackNr);
invokeService(intent);
}
@@ -205,7 +206,14 @@ public class GBDeviceService implements DeviceService {
@Override
public void onEnableRealtimeSteps(boolean enable) {
Intent intent = createIntent().setAction(ACTION_ENABLE_REALTIME_STEPS)
- .putExtra(EXTRA_ENABLE_REALTIME_STEPS, enable);
+ .putExtra(EXTRA_BOOLEAN_ENABLE, enable);
+ invokeService(intent);
+ }
+
+ @Override
+ public void onEnableHeartRateSleepSupport(boolean enable) {
+ Intent intent = createIntent().setAction(ACTION_ENABLE_HEARTRATE_SLEEP_SUPPORT)
+ .putExtra(EXTRA_BOOLEAN_ENABLE, enable);
invokeService(intent);
}
}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/model/CallSpec.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/model/CallSpec.java
new file mode 100644
index 000000000..1d088a493
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/model/CallSpec.java
@@ -0,0 +1,15 @@
+package nodomain.freeyourgadget.gadgetbridge.model;
+
+public class CallSpec {
+ public static final int CALL_UNDEFINED = 1;
+ public static final int CALL_ACCEPT = 1;
+ public static final int CALL_INCOMING = 2;
+ public static final int CALL_OUTGOING = 3;
+ public static final int CALL_REJECT = 4;
+ public static final int CALL_START = 5;
+ public static final int CALL_END = 6;
+
+ public String number;
+ public String name;
+ public int command;
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/model/DeviceService.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/model/DeviceService.java
index 8217f6d4c..7c38088f4 100644
--- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/model/DeviceService.java
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/model/DeviceService.java
@@ -33,6 +33,7 @@ public interface DeviceService extends EventHandler {
String ACTION_SET_ALARMS = PREFIX + ".action.set_alarms";
String ACTION_ENABLE_REALTIME_STEPS = PREFIX + ".action.enable_realtime_steps";
String ACTION_REALTIME_STEPS = PREFIX + ".action.realtime_steps";
+ String ACTION_ENABLE_HEARTRATE_SLEEP_SUPPORT = PREFIX + ".action.enable_heartrate_sleep_support";
String EXTRA_DEVICE_ADDRESS = "device_address";
String EXTRA_NOTIFICATION_BODY = "notification_body";
String EXTRA_NOTIFICATION_FLAGS = "notification_flags";
@@ -58,7 +59,7 @@ public interface DeviceService extends EventHandler {
String EXTRA_URI = "uri";
String EXTRA_ALARMS = "alarms";
String EXTRA_PERFORM_PAIR = "perform_pair";
- String EXTRA_ENABLE_REALTIME_STEPS = "enable_realtime_steps";
+ String EXTRA_BOOLEAN_ENABLE = "enable_realtime_steps";
String EXTRA_REALTIME_STEPS = "realtime_steps";
String EXTRA_TIMESTAMP = "timestamp";
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/model/MusicSpec.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/model/MusicSpec.java
new file mode 100644
index 000000000..af52f02f1
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/model/MusicSpec.java
@@ -0,0 +1,17 @@
+package nodomain.freeyourgadget.gadgetbridge.model;
+
+public class MusicSpec {
+ public static final int MUSIC_UNDEFINED = 0;
+ public static final int MUSIC_PLAY = 1;
+ public static final int MUSIC_PAUSE = 2;
+ public static final int MUSIC_PLAYPAUSE = 3;
+ public static final int MUSIC_NEXT = 4;
+ public static final int MUSIC_PREVIOUS = 5;
+
+ public String artist;
+ public String album;
+ public String track;
+ public int duration;
+ public int trackCount;
+ public int trackNr;
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/model/ServiceCommand.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/model/ServiceCommand.java
deleted file mode 100644
index 5cb945119..000000000
--- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/model/ServiceCommand.java
+++ /dev/null
@@ -1,22 +0,0 @@
-package nodomain.freeyourgadget.gadgetbridge.model;
-
-public enum ServiceCommand {
-
- UNDEFINED,
-
- CALL_ACCEPT,
- CALL_END,
- CALL_INCOMING,
- CALL_OUTGOING,
- CALL_REJECT,
- CALL_START,
-
- MUSIC_PLAY,
- MUSIC_PAUSE,
- MUSIC_PLAYPAUSE,
- MUSIC_NEXT,
- MUSIC_PREVIOUS,
-
- APP_INFO_NAME,
- VERSION_FIRMWARE
-}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/AbstractDeviceSupport.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/AbstractDeviceSupport.java
index ad88c1797..d103011ab 100644
--- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/AbstractDeviceSupport.java
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/AbstractDeviceSupport.java
@@ -32,6 +32,7 @@ import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEvent;
import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventAppInfo;
import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventBatteryInfo;
import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventCallControl;
+import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventDisplayMessage;
import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventMusicControl;
import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventNotificationControl;
import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventScreenshot;
@@ -280,4 +281,14 @@ public abstract class AbstractDeviceSupport implements DeviceSupport {
gbDevice.sendDeviceUpdateIntent(context);
}
+ public void handleGBDeviceEvent(GBDeviceEventDisplayMessage message) {
+ GB.log(message.message, message.severity, null);
+
+ Intent messageIntent = new Intent(GB.ACTION_DISPLAY_MESSAGE);
+ messageIntent.putExtra(GB.DISPLAY_MESSAGE_MESSAGE, message.message);
+ messageIntent.putExtra(GB.DISPLAY_MESSAGE_DURATION, message.duration);
+ messageIntent.putExtra(GB.DISPLAY_MESSAGE_SEVERITY, message.severity);
+
+ LocalBroadcastManager.getInstance(context).sendBroadcast(messageIntent);
+ }
}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/DeviceCommunicationService.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/DeviceCommunicationService.java
index a9261f7ba..6ade401de 100644
--- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/DeviceCommunicationService.java
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/DeviceCommunicationService.java
@@ -33,9 +33,10 @@ import nodomain.freeyourgadget.gadgetbridge.externalevents.SMSReceiver;
import nodomain.freeyourgadget.gadgetbridge.externalevents.TimeChangeReceiver;
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
import nodomain.freeyourgadget.gadgetbridge.model.Alarm;
+import nodomain.freeyourgadget.gadgetbridge.model.CallSpec;
+import nodomain.freeyourgadget.gadgetbridge.model.MusicSpec;
import nodomain.freeyourgadget.gadgetbridge.model.NotificationSpec;
import nodomain.freeyourgadget.gadgetbridge.model.NotificationType;
-import nodomain.freeyourgadget.gadgetbridge.model.ServiceCommand;
import nodomain.freeyourgadget.gadgetbridge.util.DeviceHelper;
import nodomain.freeyourgadget.gadgetbridge.util.GB;
@@ -44,6 +45,7 @@ import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.ACTION_CA
import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.ACTION_CONNECT;
import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.ACTION_DELETEAPP;
import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.ACTION_DISCONNECT;
+import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.ACTION_ENABLE_HEARTRATE_SLEEP_SUPPORT;
import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.ACTION_ENABLE_REALTIME_STEPS;
import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.ACTION_FETCH_ACTIVITY_DATA;
import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.ACTION_FIND_DEVICE;
@@ -63,10 +65,10 @@ import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.EXTRA_ALA
import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.EXTRA_APP_CONFIG;
import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.EXTRA_APP_START;
import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.EXTRA_APP_UUID;
+import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.EXTRA_BOOLEAN_ENABLE;
import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.EXTRA_CALL_COMMAND;
import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.EXTRA_CALL_PHONENUMBER;
import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.EXTRA_DEVICE_ADDRESS;
-import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.EXTRA_ENABLE_REALTIME_STEPS;
import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.EXTRA_FIND_START;
import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.EXTRA_MUSIC_ALBUM;
import static nodomain.freeyourgadget.gadgetbridge.model.DeviceService.EXTRA_MUSIC_ARTIST;
@@ -277,26 +279,32 @@ public class DeviceCommunicationService extends Service {
break;
}
case ACTION_CALLSTATE:
- ServiceCommand command = (ServiceCommand) intent.getSerializableExtra(EXTRA_CALL_COMMAND);
+ int command = intent.getIntExtra(EXTRA_CALL_COMMAND, CallSpec.CALL_UNDEFINED);
String phoneNumber = intent.getStringExtra(EXTRA_CALL_PHONENUMBER);
String callerName = null;
if (phoneNumber != null) {
callerName = getContactDisplayNameByNumber(phoneNumber);
}
- mDeviceSupport.onSetCallState(phoneNumber, callerName, command);
+
+ CallSpec callSpec = new CallSpec();
+ callSpec.command = command;
+ callSpec.number = phoneNumber;
+ callSpec.name = callerName;
+ mDeviceSupport.onSetCallState(callSpec);
break;
case ACTION_SETTIME:
mDeviceSupport.onSetTime();
break;
case ACTION_SETMUSICINFO:
- String artist = intent.getStringExtra(EXTRA_MUSIC_ARTIST);
- String album = intent.getStringExtra(EXTRA_MUSIC_ALBUM);
- String track = intent.getStringExtra(EXTRA_MUSIC_TRACK);
- int duration = intent.getIntExtra(EXTRA_MUSIC_DURATION, 0);
- int trackCount = intent.getIntExtra(EXTRA_MUSIC_TRACKCOUNT, 0);
- int trackNr = intent.getIntExtra(EXTRA_MUSIC_TRACKNR, 0);
- mDeviceSupport.onSetMusicInfo(artist, album, track, duration, trackCount, trackNr);
+ MusicSpec musicSpec = new MusicSpec();
+ musicSpec.artist = intent.getStringExtra(EXTRA_MUSIC_ARTIST);
+ musicSpec.album = intent.getStringExtra(EXTRA_MUSIC_ALBUM);
+ musicSpec.track = intent.getStringExtra(EXTRA_MUSIC_TRACK);
+ musicSpec.duration = intent.getIntExtra(EXTRA_MUSIC_DURATION, 0);
+ musicSpec.trackCount = intent.getIntExtra(EXTRA_MUSIC_TRACKCOUNT, 0);
+ musicSpec.trackNr = intent.getIntExtra(EXTRA_MUSIC_TRACKNR, 0);
+ mDeviceSupport.onSetMusicInfo(musicSpec);
break;
case ACTION_REQUEST_APPINFO:
mDeviceSupport.onAppInfoReq();
@@ -331,10 +339,16 @@ public class DeviceCommunicationService extends Service {
ArrayList alarms = intent.getParcelableArrayListExtra(EXTRA_ALARMS);
mDeviceSupport.onSetAlarms(alarms);
break;
- case ACTION_ENABLE_REALTIME_STEPS:
- boolean enable = intent.getBooleanExtra(EXTRA_ENABLE_REALTIME_STEPS, false);
+ case ACTION_ENABLE_REALTIME_STEPS: {
+ boolean enable = intent.getBooleanExtra(EXTRA_BOOLEAN_ENABLE, false);
mDeviceSupport.onEnableRealtimeSteps(enable);
break;
+ }
+ case ACTION_ENABLE_HEARTRATE_SLEEP_SUPPORT: {
+ boolean enable = intent.getBooleanExtra(EXTRA_BOOLEAN_ENABLE, false);
+ mDeviceSupport.onEnableHeartRateSleepSupport(enable);
+ break;
+ }
}
return START_STICKY;
@@ -417,7 +431,10 @@ public class DeviceCommunicationService extends Service {
}
if (mMusicPlaybackReceiver == null) {
mMusicPlaybackReceiver = new MusicPlaybackReceiver();
- registerReceiver(mMusicPlaybackReceiver, new IntentFilter("com.android.music.metachanged"));
+ IntentFilter filter = new IntentFilter();
+ filter.addAction("com.android.music.metachanged");
+ //filter.addAction("com.android.music.playstatechanged");
+ registerReceiver(mMusicPlaybackReceiver, filter);
}
if (mTimeChangeReceiver == null) {
mTimeChangeReceiver = new TimeChangeReceiver();
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/ServiceDeviceSupport.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/ServiceDeviceSupport.java
index 993b03190..e54e61bd3 100644
--- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/ServiceDeviceSupport.java
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/ServiceDeviceSupport.java
@@ -13,8 +13,9 @@ import java.util.UUID;
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
import nodomain.freeyourgadget.gadgetbridge.model.Alarm;
+import nodomain.freeyourgadget.gadgetbridge.model.CallSpec;
+import nodomain.freeyourgadget.gadgetbridge.model.MusicSpec;
import nodomain.freeyourgadget.gadgetbridge.model.NotificationSpec;
-import nodomain.freeyourgadget.gadgetbridge.model.ServiceCommand;
/**
* Wraps another device support instance and supports busy-checking and throttling of events.
@@ -131,19 +132,19 @@ public class ServiceDeviceSupport implements DeviceSupport {
// No throttling for the other events
@Override
- public void onSetCallState(String number, String name, ServiceCommand command) {
+ public void onSetCallState(CallSpec callSpec) {
if (checkBusy("set call state")) {
return;
}
- delegate.onSetCallState(number, name, command);
+ delegate.onSetCallState(callSpec);
}
@Override
- public void onSetMusicInfo(String artist, String album, String track, int duration, int trackCount, int trackNr) {
+ public void onSetMusicInfo(MusicSpec musicSpec) {
if (checkBusy("set music info")) {
return;
}
- delegate.onSetMusicInfo(artist, album, track, duration, trackCount, trackNr);
+ delegate.onSetMusicInfo(musicSpec);
}
@Override
@@ -241,4 +242,12 @@ public class ServiceDeviceSupport implements DeviceSupport {
}
delegate.onEnableRealtimeSteps(enable);
}
+
+ @Override
+ public void onEnableHeartRateSleepSupport(boolean enable) {
+ if (checkBusy("enable heartrate sleep support: " + enable)) {
+ return;
+ }
+ delegate.onEnableHeartRateSleepSupport(enable);
+ }
}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/btle/AbstractBTLEDeviceSupport.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/btle/AbstractBTLEDeviceSupport.java
index 051c8ce61..6361431a9 100644
--- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/btle/AbstractBTLEDeviceSupport.java
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/btle/AbstractBTLEDeviceSupport.java
@@ -16,6 +16,7 @@ import java.util.Set;
import java.util.UUID;
import nodomain.freeyourgadget.gadgetbridge.service.AbstractDeviceSupport;
+import nodomain.freeyourgadget.gadgetbridge.service.btle.actions.CheckInitializedAction;
/**
* Abstract base class for all devices connected through Bluetooth Low Energy (LE) aka
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/btle/AbstractBTLEOperation.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/btle/AbstractBTLEOperation.java
index b7a7e9389..258199ad8 100644
--- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/btle/AbstractBTLEOperation.java
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/btle/AbstractBTLEOperation.java
@@ -113,6 +113,10 @@ public abstract class AbstractBTLEOperation
return operationStatus == OperationStatus.RUNNING;
}
+ public boolean isOperationFinished() {
+ return operationStatus == OperationStatus.FINISHED;
+ }
+
public T getSupport() {
return mSupport;
}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/btle/BtLEQueue.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/btle/BtLEQueue.java
index b74cdbfdb..4839a9924 100644
--- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/btle/BtLEQueue.java
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/btle/BtLEQueue.java
@@ -157,11 +157,12 @@ public final class BtLEQueue {
LOG.info("Attempting to connect to " + mGbDevice.getName());
mBluetoothAdapter.cancelDiscovery();
BluetoothDevice remoteDevice = mBluetoothAdapter.getRemoteDevice(mGbDevice.getAddress());
+ boolean result;
synchronized (mGattMonitor) {
- mBluetoothGatt = remoteDevice.connectGatt(mContext, false, internalGattCallback);
-// result = mBluetoothGatt.connect();
+ mBluetoothGatt = remoteDevice.connectGatt(mContext, true, internalGattCallback);
+ result = mBluetoothGatt.connect();
}
- boolean result = mBluetoothGatt != null;
+// boolean result = mBluetoothGatt != null;
if (result) {
setDeviceConnectionState(State.CONNECTING);
}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/btle/CheckInitializedAction.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/btle/actions/CheckInitializedAction.java
similarity index 85%
rename from app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/btle/CheckInitializedAction.java
rename to app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/btle/actions/CheckInitializedAction.java
index 21bc0be87..414d983fc 100644
--- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/btle/CheckInitializedAction.java
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/btle/actions/CheckInitializedAction.java
@@ -1,10 +1,9 @@
-package nodomain.freeyourgadget.gadgetbridge.service.btle;
+package nodomain.freeyourgadget.gadgetbridge.service.btle.actions;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
-import nodomain.freeyourgadget.gadgetbridge.service.btle.actions.AbortTransactionAction;
/**
* A special action that is executed at the very front of the initialization
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/btle/actions/ConditionalWriteAction.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/btle/actions/ConditionalWriteAction.java
new file mode 100644
index 000000000..6d373fafb
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/btle/actions/ConditionalWriteAction.java
@@ -0,0 +1,30 @@
+package nodomain.freeyourgadget.gadgetbridge.service.btle.actions;
+
+import android.bluetooth.BluetoothGatt;
+import android.bluetooth.BluetoothGattCharacteristic;
+
+public abstract class ConditionalWriteAction extends WriteAction {
+ public ConditionalWriteAction(BluetoothGattCharacteristic characteristic) {
+ super(characteristic, null);
+ }
+
+ @Override
+ protected boolean writeValue(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, byte[] value) {
+ byte[] conditionalValue = checkCondition();
+ if (conditionalValue != null) {
+ return super.writeValue(gatt, characteristic, conditionalValue);
+ }
+ return true;
+ }
+
+ /**
+ * Checks the condition whether the write shall happen or not.
+ * Returns the actual value to be written or null in case nothing shall be written.
+ *
+ * Note that returning null will not cause run() to return false, in other words,
+ * the rest of the queue will still be executed.
+ *
+ * @return the value to be written or null to not write anything
+ */
+ protected abstract byte[] checkCondition();
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/btle/actions/WriteAction.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/btle/actions/WriteAction.java
index b4ec95489..5cecee06d 100644
--- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/btle/actions/WriteAction.java
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/btle/actions/WriteAction.java
@@ -22,16 +22,26 @@ public class WriteAction extends BtLEAction {
@Override
public boolean run(BluetoothGatt gatt) {
- int properties = getCharacteristic().getProperties();
+ BluetoothGattCharacteristic characteristic = getCharacteristic();
+ int properties = characteristic.getProperties();
//TODO: expectsResult should return false if PROPERTY_WRITE_NO_RESPONSE is true, but this yelds to timing issues
if ((properties & BluetoothGattCharacteristic.PROPERTY_WRITE) > 0 || ((properties & BluetoothGattCharacteristic.PROPERTY_WRITE_NO_RESPONSE) > 0)) {
- if (getCharacteristic().setValue(value)) {
- return gatt.writeCharacteristic(getCharacteristic());
- }
+ return writeValue(gatt, characteristic, value);
}
return false;
}
+ protected boolean writeValue(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, byte[] value) {
+ if (characteristic.setValue(value)) {
+ return gatt.writeCharacteristic(characteristic);
+ }
+ return false;
+ }
+
+ protected final byte[] getValue() {
+ return value;
+ }
+
@Override
public boolean expectsResult() {
return true;
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/miband/CheckAuthenticationNeededAction.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/miband/CheckAuthenticationNeededAction.java
index 6f25b1634..8415c6958 100644
--- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/miband/CheckAuthenticationNeededAction.java
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/miband/CheckAuthenticationNeededAction.java
@@ -1,11 +1,9 @@
package nodomain.freeyourgadget.gadgetbridge.service.devices.miband;
-import android.bluetooth.BluetoothGatt;
-
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
-import nodomain.freeyourgadget.gadgetbridge.service.btle.actions.PlainAction;
+import nodomain.freeyourgadget.gadgetbridge.service.btle.actions.AbortTransactionAction;
-public class CheckAuthenticationNeededAction extends PlainAction {
+public class CheckAuthenticationNeededAction extends AbortTransactionAction {
private final GBDevice mDevice;
public CheckAuthenticationNeededAction(GBDevice device) {
@@ -14,14 +12,14 @@ public class CheckAuthenticationNeededAction extends PlainAction {
}
@Override
- public boolean run(BluetoothGatt gatt) {
+ protected boolean shouldAbort() {
// the state is set in MiBandSupport.handleNotificationNotif()
switch (mDevice.getState()) {
case AUTHENTICATION_REQUIRED: // fall through
case AUTHENTICATING:
- return false; // abort the whole thing
+ return true; // abort the whole thing
default:
- return true;
+ return false;
}
}
}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/miband/DeviceInfo.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/miband/DeviceInfo.java
index 217988781..1072ff50c 100644
--- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/miband/DeviceInfo.java
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/miband/DeviceInfo.java
@@ -109,7 +109,7 @@ public class DeviceInfo extends AbstractInfo {
public boolean isMili1S() {
// TODO: this is probably not quite correct, but hopefully sufficient for early 1S support
- return feature == 4 && appearance == 0 || feature == 4 && hwVersion == 4;
+ return (feature == 4 && appearance == 0) || hwVersion == 4;
}
public String getHwVersion() {
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/miband/MiBandSupport.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/miband/MiBandSupport.java
index 733b89acb..37b225919 100644
--- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/miband/MiBandSupport.java
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/miband/MiBandSupport.java
@@ -34,16 +34,18 @@ import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice.State;
import nodomain.freeyourgadget.gadgetbridge.model.Alarm;
import nodomain.freeyourgadget.gadgetbridge.model.CalendarEvents;
+import nodomain.freeyourgadget.gadgetbridge.model.CallSpec;
import nodomain.freeyourgadget.gadgetbridge.model.DeviceService;
import nodomain.freeyourgadget.gadgetbridge.model.GenericItem;
+import nodomain.freeyourgadget.gadgetbridge.model.MusicSpec;
import nodomain.freeyourgadget.gadgetbridge.model.NotificationSpec;
-import nodomain.freeyourgadget.gadgetbridge.model.ServiceCommand;
import nodomain.freeyourgadget.gadgetbridge.service.btle.AbstractBTLEDeviceSupport;
import nodomain.freeyourgadget.gadgetbridge.service.btle.BtLEAction;
import nodomain.freeyourgadget.gadgetbridge.service.btle.GattCharacteristic;
import nodomain.freeyourgadget.gadgetbridge.service.btle.GattService;
import nodomain.freeyourgadget.gadgetbridge.service.btle.TransactionBuilder;
import nodomain.freeyourgadget.gadgetbridge.service.btle.actions.AbortTransactionAction;
+import nodomain.freeyourgadget.gadgetbridge.service.btle.actions.ConditionalWriteAction;
import nodomain.freeyourgadget.gadgetbridge.service.btle.actions.SetDeviceStateAction;
import nodomain.freeyourgadget.gadgetbridge.service.btle.actions.WriteAction;
import nodomain.freeyourgadget.gadgetbridge.service.devices.miband.operations.FetchActivityOperation;
@@ -109,6 +111,7 @@ public class MiBandSupport extends AbstractBTLEDeviceSupport {
.sendUserInfo(builder)
.checkAuthenticationNeeded(builder, getDevice())
.setWearLocation(builder)
+ .setHeartrateSleepSupport(builder)
.setFitnessGoal(builder)
.enableFurtherNotifications(builder, true)
.setCurrentTime(builder)
@@ -368,6 +371,44 @@ public class MiBandSupport extends AbstractBTLEDeviceSupport {
return this;
}
+ @Override
+ public void onEnableHeartRateSleepSupport(boolean enable) {
+ try {
+ TransactionBuilder builder = performInitialized("enable heart rate sleep support: " + enable);
+ setHeartrateSleepSupport(builder);
+ builder.queue(getQueue());
+ } catch (IOException e) {
+ GB.toast(getContext(), "Error toggling heart rate sleep support: " + e.getLocalizedMessage(), Toast.LENGTH_LONG, GB.ERROR);
+ }
+ }
+
+ /**
+ * Part of device initialization process. Do not call manually.
+ *
+ * @param builder
+ */
+ private MiBandSupport setHeartrateSleepSupport(TransactionBuilder builder) {
+ BluetoothGattCharacteristic characteristic = getCharacteristic(MiBandService.UUID_CHARACTERISTIC_HEART_RATE_CONTROL_POINT);
+ if (characteristic != null) {
+ builder.add(new ConditionalWriteAction(characteristic) {
+ @Override
+ protected byte[] checkCondition() {
+ if (!supportsHeartRate()) {
+ return null;
+ }
+ if (MiBandCoordinator.getHeartrateSleepSupport(getDevice().getAddress())) {
+ LOG.info("Enabling heartrate sleep support...");
+ return startHeartMeasurementSleep;
+ } else {
+ LOG.info("Disabling heartrate sleep support...");
+ return stopHeartMeasurementSleep;
+ }
+ }
+ });
+ }
+ return this;
+ }
+
private void performDefaultNotification(String task, short repeat, BtLEAction extraAction) {
try {
TransactionBuilder builder = performInitialized(task);
@@ -519,8 +560,8 @@ public class MiBandSupport extends AbstractBTLEDeviceSupport {
}
@Override
- public void onSetCallState(String number, String name, ServiceCommand command) {
- if (ServiceCommand.CALL_INCOMING.equals(command)) {
+ public void onSetCallState(CallSpec callSpec) {
+ if (callSpec.command == CallSpec.CALL_INCOMING) {
telephoneRinging = true;
AbortTransactionAction abortAction = new AbortTransactionAction() {
@Override
@@ -529,7 +570,7 @@ public class MiBandSupport extends AbstractBTLEDeviceSupport {
}
};
performPreferredNotification("incoming call", MiBandConst.ORIGIN_INCOMING_CALL, abortAction);
- } else if (ServiceCommand.CALL_START.equals(command) || ServiceCommand.CALL_END.equals(command)) {
+ } else if ((callSpec.command == CallSpec.CALL_START) || (callSpec.command == CallSpec.CALL_END)) {
telephoneRinging = false;
}
}
@@ -540,7 +581,7 @@ public class MiBandSupport extends AbstractBTLEDeviceSupport {
}
@Override
- public void onSetMusicInfo(String artist, String album, String track, int duration, int trackCount, int trackNr) {
+ public void onSetMusicInfo(MusicSpec musicSpec) {
// not supported
}
@@ -725,6 +766,8 @@ public class MiBandSupport extends AbstractBTLEDeviceSupport {
handleBatteryInfo(characteristic.getValue(), status);
} else if (MiBandService.UUID_CHARACTERISTIC_HEART_RATE_MEASUREMENT.equals(characteristicUUID)) {
logHeartrate(characteristic.getValue());
+ } else if (MiBandService.UUID_CHARACTERISTIC_DATE_TIME.equals(characteristicUUID)) {
+ logDate(characteristic.getValue());
} else {
LOG.info("Unhandled characteristic read: " + characteristicUUID);
logMessageContent(characteristic.getValue());
@@ -756,6 +799,11 @@ public class MiBandSupport extends AbstractBTLEDeviceSupport {
}
}
+ public void logDate(byte[] value) {
+ GregorianCalendar calendar = MiBandDateConverter.rawBytesToCalendar(value);
+ LOG.info("Got Mi Band Date: " + DateTimeUtils.formatDateTime(calendar.getTime()));
+ }
+
public void logHeartrate(byte[] value) {
LOG.info("Got heartrate:");
if (value.length == 2 && value[0] == 6) {
@@ -889,7 +937,7 @@ public class MiBandSupport extends AbstractBTLEDeviceSupport {
if (status != BluetoothGatt.GATT_SUCCESS) {
LOG.warn("Could not write to the control point.");
}
- LOG.info("handleControlPoint write status:" + status);
+ LOG.info("handleControlPoint write status:" + status + "; length: " + (value != null ? value.length : "(null)"));
if (value != null) {
for (byte b : value) {
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/miband/operations/UpdateFirmwareOperation.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/miband/operations/UpdateFirmwareOperation.java
index 3acff6e6d..4bb7d6ba3 100644
--- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/miband/operations/UpdateFirmwareOperation.java
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/miband/operations/UpdateFirmwareOperation.java
@@ -2,6 +2,7 @@ package nodomain.freeyourgadget.gadgetbridge.service.devices.miband.operations;
import android.bluetooth.BluetoothGatt;
import android.bluetooth.BluetoothGattCharacteristic;
+import android.content.Context;
import android.net.Uri;
import android.widget.Toast;
@@ -13,6 +14,7 @@ import java.util.Arrays;
import java.util.UUID;
import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventDisplayMessage;
import nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBandFWHelper;
import nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBandService;
import nodomain.freeyourgadget.gadgetbridge.service.btle.TransactionBuilder;
@@ -53,13 +55,14 @@ public class UpdateFirmwareOperation extends AbstractMiBandOperation {
updateCoordinator.initNextOperation();
if (!updateCoordinator.sendFwInfo()) {
- GB.toast(getContext(), "Error sending firmware info, aborting.", Toast.LENGTH_LONG, GB.ERROR);
+ displayMessage(getContext(), "Error sending firmware info, aborting.", Toast.LENGTH_LONG, GB.ERROR);
done();
}
//the firmware will be sent by the notification listener if the band confirms that the metadata are ok.
}
private void done() {
+ LOG.info("Operation done.");
updateCoordinator = null;
operationFinished();
unsetBusy();
@@ -93,36 +96,39 @@ public class UpdateFirmwareOperation extends AbstractMiBandOperation {
return;
}
if (updateCoordinator == null) {
- LOG.error("received notification when updateCoordinator is null, ignoring!");
+ LOG.error("received notification when updateCoordinator is null, ignoring (notification content follows):");
+ getSupport().logMessageContent(value);
return;
}
switch (value[0]) {
case MiBandService.NOTIFY_FW_CHECK_SUCCESS:
if (firmwareInfoSent) {
- GB.toast(getContext(), "Firmware metadata successfully sent.", Toast.LENGTH_LONG, GB.INFO);
+ displayMessage(getContext(), "Firmware metadata successfully sent.", Toast.LENGTH_LONG, GB.INFO);
if (!updateCoordinator.sendFwData()) {
- GB.toast(getContext().getString(R.string.updatefirmwareoperation_updateproblem_do_not_reboot), Toast.LENGTH_LONG, GB.ERROR);
+ displayMessage(getContext(), getContext().getString(R.string.updatefirmwareoperation_updateproblem_do_not_reboot), Toast.LENGTH_LONG, GB.ERROR);
done();
}
firmwareInfoSent = false;
+ } else {
+ LOG.warn("firmwareInfoSent is false -- not sending firmware data even though we got meta data success notification");
}
break;
case MiBandService.NOTIFY_FW_CHECK_FAILED:
- GB.toast(getContext().getString(R.string.updatefirmwareoperation_metadata_updateproblem), Toast.LENGTH_LONG, GB.ERROR);
+ displayMessage(getContext(), getContext().getString(R.string.updatefirmwareoperation_metadata_updateproblem), Toast.LENGTH_LONG, GB.ERROR);
firmwareInfoSent = false;
done();
break;
case MiBandService.NOTIFY_FIRMWARE_UPDATE_SUCCESS:
if (updateCoordinator.initNextOperation()) {
- GB.toast(getContext(), "Heart Rate Firmware successfully updated, now updating Mi Band Firmware", Toast.LENGTH_LONG, GB.INFO);
+ displayMessage(getContext(), "Heart Rate Firmware successfully updated, now updating Mi Band Firmware", Toast.LENGTH_LONG, GB.INFO);
if (!updateCoordinator.sendFwInfo()) {
- GB.toast(getContext(), "Error sending firmware info, aborting.", Toast.LENGTH_LONG, GB.ERROR);
+ displayMessage(getContext(), "Error sending firmware info, aborting.", Toast.LENGTH_LONG, GB.ERROR);
done();
}
break;
} else if (updateCoordinator.needsReboot()) {
- GB.toast(getContext(), getContext().getString(R.string.updatefirmwareoperation_update_complete_rebooting), Toast.LENGTH_LONG, GB.INFO);
+ displayMessage(getContext(), getContext().getString(R.string.updatefirmwareoperation_update_complete_rebooting), Toast.LENGTH_LONG, GB.INFO);
GB.updateInstallNotification(getContext().getString(R.string.updatefirmwareoperation_update_complete), false, 100, getContext());
getSupport().onReboot();
} else {
@@ -132,7 +138,7 @@ public class UpdateFirmwareOperation extends AbstractMiBandOperation {
break;
case MiBandService.NOTIFY_FIRMWARE_UPDATE_FAILED:
//TODO: the firmware transfer failed, but the miband should be still functional with the old firmware. What should we do?
- GB.toast(getContext().getString(R.string.updatefirmwareoperation_updateproblem_do_not_reboot), Toast.LENGTH_LONG, GB.ERROR);
+ displayMessage(getContext(), getContext().getString(R.string.updatefirmwareoperation_updateproblem_do_not_reboot), Toast.LENGTH_LONG, GB.ERROR);
GB.updateInstallNotification(getContext().getString(R.string.updatefirmwareoperation_write_failed), false, 0, getContext());
done();
break;
@@ -143,6 +149,10 @@ public class UpdateFirmwareOperation extends AbstractMiBandOperation {
}
}
+ private void displayMessage(Context context, String message, int duration, int severity) {
+ getSupport().handleGBDeviceEvent(new GBDeviceEventDisplayMessage(message, duration, severity));
+ }
+
/**
* Prepare the MiBand to receive the new firmware data.
* Some information about the new firmware version have to be pushed to the MiBand before sending
@@ -193,7 +203,7 @@ public class UpdateFirmwareOperation extends AbstractMiBandOperation {
} else {
LOG.info("is multi Mi Band firmware, sending fw2 (hr) first");
byte[] fw2Info = prepareFirmwareInfo(fw2Bytes, fw2OldVersion, fw2Version, fw2Checksum, 1, rebootWhenFinished /*, progress monitor */);
- byte[] fw1Info = prepareFirmwareInfo(fw1Bytes, fw1OldVersion, fw1Version, fw1Checksum, 1, rebootWhenFinished /*, progress monitor */);
+ byte[] fw1Info = prepareFirmwareInfo(fw1Bytes, fw1OldVersion, fw1Version, fw1Checksum, 0, rebootWhenFinished /*, progress monitor */);
return new DoubleUpdateCoordinator(fw1Info, fw1Bytes, fw2Info, fw2Bytes, rebootWhenFinished);
}
}
@@ -268,31 +278,34 @@ public class UpdateFirmwareOperation extends AbstractMiBandOperation {
final int packetLength = 20;
int packets = len / packetLength;
- // going from 0 to len
- int firmwareProgress = 0;
-
+ BluetoothGattCharacteristic characteristicControlPoint = getCharacteristic(MiBandService.UUID_CHARACTERISTIC_CONTROL_POINT);
+ BluetoothGattCharacteristic characteristicFWData = getCharacteristic(MiBandService.UUID_CHARACTERISTIC_FIRMWARE_DATA);
try {
+ // going from 0 to len
+ int firmwareProgress = 0;
+
TransactionBuilder builder = performInitialized("send firmware packet");
+// getSupport().setLowLatency(builder);
for (int i = 0; i < packets; i++) {
byte[] fwChunk = Arrays.copyOfRange(fwbytes, i * packetLength, i * packetLength + packetLength);
- builder.write(getCharacteristic(MiBandService.UUID_CHARACTERISTIC_FIRMWARE_DATA), fwChunk);
+ builder.write(characteristicFWData, fwChunk);
firmwareProgress += packetLength;
- int progressPercent = (int) (((float) firmwareProgress) / len) * 100;
+ int progressPercent = (int) ((((float) firmwareProgress) / len) * 100);
if ((i > 0) && (i % 50 == 0)) {
- builder.write(getCharacteristic(MiBandService.UUID_CHARACTERISTIC_CONTROL_POINT), new byte[]{MiBandService.COMMAND_SYNC});
+ builder.write(characteristicControlPoint, new byte[]{MiBandService.COMMAND_SYNC});
builder.add(new SetProgressAction(getContext().getString(R.string.updatefirmwareoperation_update_in_progress), true, progressPercent, getContext()));
}
}
if (firmwareProgress < len) {
byte[] lastChunk = Arrays.copyOfRange(fwbytes, packets * packetLength, len);
- builder.write(getCharacteristic(MiBandService.UUID_CHARACTERISTIC_FIRMWARE_DATA), lastChunk);
+ builder.write(characteristicFWData, lastChunk);
firmwareProgress = len;
}
- builder.write(getCharacteristic(MiBandService.UUID_CHARACTERISTIC_CONTROL_POINT), new byte[]{MiBandService.COMMAND_SYNC});
+ builder.write(characteristicControlPoint, new byte[]{MiBandService.COMMAND_SYNC});
builder.queue(getQueue());
} catch (IOException ex) {
@@ -319,9 +332,10 @@ public class UpdateFirmwareOperation extends AbstractMiBandOperation {
public boolean sendFwInfo() {
try {
TransactionBuilder builder = performInitialized("send firmware info");
+// getSupport().setLowLatency(builder);
builder.add(new SetDeviceBusyAction(getDevice(), getContext().getString(R.string.updating_firmware), getContext()));
+ builder.add(new FirmwareInfoSentAction()); // Note: *before* actually sending the info, otherwise it's too late!
builder.write(getCharacteristic(MiBandService.UUID_CHARACTERISTIC_CONTROL_POINT), getFirmwareInfo());
- builder.add(new FirmwareInfoSucceededAction());
builder.queue(getQueue());
return true;
} catch (IOException e) {
@@ -437,10 +451,12 @@ public class UpdateFirmwareOperation extends AbstractMiBandOperation {
}
}
- private class FirmwareInfoSucceededAction extends PlainAction {
+ private class FirmwareInfoSentAction extends PlainAction {
@Override
public boolean run(BluetoothGatt gatt) {
- firmwareInfoSent = true;
+ if (isOperationRunning()) {
+ firmwareInfoSent = true;
+ }
return true;
}
}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/pebble/DatalogSessionHealthSleep.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/pebble/DatalogSessionHealthSleep.java
index 8528287fd..7d06876fd 100644
--- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/pebble/DatalogSessionHealthSleep.java
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/pebble/DatalogSessionHealthSleep.java
@@ -21,22 +21,33 @@ class DatalogSessionHealthSleep extends DatalogSession {
public DatalogSessionHealthSleep(byte id, UUID uuid, int tag, byte item_type, short item_size) {
super(id, uuid, tag, item_type, item_size);
- taginfo = "(health - sleep)";
+ taginfo = "(health - sleep " + tag + " )";
}
@Override
public boolean handleMessage(ByteBuffer datalogMessage, int length) {
LOG.info("DATALOG " + taginfo + GB.hexdump(datalogMessage.array(), datalogMessage.position(), length));
+ switch (this.tag) {
+ case 83:
+ return handleMessage83(datalogMessage, length);
+ case 84:
+ return handleMessage84(datalogMessage, length);
+ default:
+ return false;
+ }
+ }
+ private boolean handleMessage84(ByteBuffer datalogMessage, int length) {
int initialPosition = datalogMessage.position();
int beginOfRecordPosition;
short recordVersion; //probably
+ short recordType; //probably: 1=sleep, 2=deep sleep
if (0 != (length % itemSize))
return false;//malformed message?
int recordCount = length / itemSize;
- SleepRecord[] sleepRecords = new SleepRecord[recordCount];
+ SleepRecord84[] sleepRecords = new SleepRecord84[recordCount];
for (int recordIdx = 0; recordIdx < recordCount; recordIdx++) {
beginOfRecordPosition = initialPosition + recordIdx * itemSize;
@@ -45,26 +56,30 @@ class DatalogSessionHealthSleep extends DatalogSession {
if (recordVersion != 1)
return false;//we don't know how to deal with the data TODO: this is not ideal because we will get the same message again and again since we NACK it
- sleepRecords[recordIdx] = new SleepRecord(datalogMessage.getInt(),
- datalogMessage.getInt(),
- datalogMessage.getInt(),
- datalogMessage.getInt());
+ datalogMessage.getShort();//throwaway, unknown
+ recordType = datalogMessage.getShort();
+
+ sleepRecords[recordIdx] = new SleepRecord84(recordType, datalogMessage.getInt(), datalogMessage.getInt(), datalogMessage.getInt());
}
- return store(sleepRecords);//NACK if we cannot store the data yet, the watch will send the sleep records again.
+ return store84(sleepRecords);//NACK if we cannot store the data yet, the watch will send the sleep records again.
}
- private boolean store(SleepRecord[] sleepRecords) {
+ private boolean store84(SleepRecord84[] sleepRecords) {
DBHandler dbHandler = null;
SampleProvider sampleProvider = new HealthSampleProvider();
- GB.toast("We don't know how to store deep sleep from the pebble yet.", Toast.LENGTH_LONG, GB.INFO);
try {
dbHandler = GBApplication.acquireDB();
int latestTimestamp = dbHandler.fetchLatestTimestamp(sampleProvider);
- for (SleepRecord sleepRecord : sleepRecords) {
- if (latestTimestamp < sleepRecord.bedTimeEnd)
+ for (SleepRecord84 sleepRecord : sleepRecords) {
+ if (latestTimestamp < (sleepRecord.timestampStart + sleepRecord.durationSeconds))
return false;
- dbHandler.changeStoredSamplesType(sleepRecord.bedTimeStart, sleepRecord.bedTimeEnd, sampleProvider.toRawActivityKind(ActivityKind.TYPE_LIGHT_SLEEP), sampleProvider);
+ if (sleepRecord.type == 2) {
+ dbHandler.changeStoredSamplesType(sleepRecord.timestampStart, (sleepRecord.timestampStart + sleepRecord.durationSeconds), sampleProvider.toRawActivityKind(ActivityKind.TYPE_DEEP_SLEEP), sampleProvider);
+ } else {
+ dbHandler.changeStoredSamplesType(sleepRecord.timestampStart, (sleepRecord.timestampStart + sleepRecord.durationSeconds), sampleProvider.toRawActivityKind(ActivityKind.TYPE_ACTIVITY), sampleProvider.toRawActivityKind(ActivityKind.TYPE_LIGHT_SLEEP), sampleProvider);
+ }
+
}
} catch (Exception ex) {
LOG.debug(ex.getMessage());
@@ -76,17 +91,80 @@ class DatalogSessionHealthSleep extends DatalogSession {
return true;
}
- private class SleepRecord {
+ private boolean handleMessage83(ByteBuffer datalogMessage, int length) {
+ int initialPosition = datalogMessage.position();
+ int beginOfRecordPosition;
+ short recordVersion; //probably
+
+ if (0 != (length % itemSize))
+ return false;//malformed message?
+
+ int recordCount = length / itemSize;
+ SleepRecord83[] sleepRecords = new SleepRecord83[recordCount];
+
+ for (int recordIdx = 0; recordIdx < recordCount; recordIdx++) {
+ beginOfRecordPosition = initialPosition + recordIdx * itemSize;
+ datalogMessage.position(beginOfRecordPosition);//we may not consume all the bytes of a record
+ recordVersion = datalogMessage.getShort();
+ if (recordVersion != 1)
+ return false;//we don't know how to deal with the data TODO: this is not ideal because we will get the same message again and again since we NACK it
+
+ sleepRecords[recordIdx] = new SleepRecord83(datalogMessage.getInt(),
+ datalogMessage.getInt(),
+ datalogMessage.getInt(),
+ datalogMessage.getInt());
+ }
+
+ return store83(sleepRecords);//NACK if we cannot store the data yet, the watch will send the sleep records again.
+ }
+
+ private boolean store83(SleepRecord83[] sleepRecords) {
+ DBHandler dbHandler = null;
+ SampleProvider sampleProvider = new HealthSampleProvider();
+ GB.toast("Deep sleep is supported only from firmware 3.11 onwards.", Toast.LENGTH_LONG, GB.INFO);
+ try {
+ dbHandler = GBApplication.acquireDB();
+ int latestTimestamp = dbHandler.fetchLatestTimestamp(sampleProvider);
+ for (SleepRecord83 sleepRecord : sleepRecords) {
+ if (latestTimestamp < sleepRecord.bedTimeEnd)
+ return false;
+ dbHandler.changeStoredSamplesType(sleepRecord.bedTimeStart, sleepRecord.bedTimeEnd, sampleProvider.toRawActivityKind(ActivityKind.TYPE_ACTIVITY), sampleProvider.toRawActivityKind(ActivityKind.TYPE_LIGHT_SLEEP), sampleProvider);
+ }
+ } catch (Exception ex) {
+ LOG.debug(ex.getMessage());
+ } finally {
+ if (dbHandler != null) {
+ dbHandler.release();
+ }
+ }
+ return true;
+ }
+
+ private class SleepRecord83 {
int offsetUTC; //probably
int bedTimeStart;
int bedTimeEnd;
int deepSleepSeconds;
- public SleepRecord(int offsetUTC, int bedTimeStart, int bedTimeEnd, int deepSleepSeconds) {
+ public SleepRecord83(int offsetUTC, int bedTimeStart, int bedTimeEnd, int deepSleepSeconds) {
this.offsetUTC = offsetUTC;
this.bedTimeStart = bedTimeStart;
this.bedTimeEnd = bedTimeEnd;
this.deepSleepSeconds = deepSleepSeconds;
}
}
+
+ private class SleepRecord84 {
+ int type; //1=sleep, 2=deep sleep
+ int offsetUTC; //probably
+ int timestampStart;
+ int durationSeconds;
+
+ public SleepRecord84(int type, int offsetUTC, int timestampStart, int durationSeconds) {
+ this.type = type;
+ this.offsetUTC = offsetUTC;
+ this.timestampStart = timestampStart;
+ this.durationSeconds = durationSeconds;
+ }
+ }
}
\ No newline at end of file
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/pebble/DatalogSessionHealthSteps.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/pebble/DatalogSessionHealthSteps.java
index 81bb43192..f89f03639 100644
--- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/pebble/DatalogSessionHealthSteps.java
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/pebble/DatalogSessionHealthSteps.java
@@ -46,7 +46,7 @@ public class DatalogSessionHealthSteps extends DatalogSession {
recordVersion = datalogMessage.getShort();
- if (recordVersion != 5)
+ if ((recordVersion != 5) && (recordVersion != 6))
return false; //we don't know how to deal with the data TODO: this is not ideal because we will get the same message again and again since we NACK it
timestamp = datalogMessage.getInt();
@@ -59,8 +59,7 @@ public class DatalogSessionHealthSteps extends DatalogSession {
for (int recordIdx = 0; recordIdx < recordNum; recordIdx++) {
datalogMessage.position(beginOfRecordPosition + recordIdx * recordLength); //we may not consume all the bytes of a record
- stepsRecords[recordIdx] = new StepsRecord(timestamp, datalogMessage.get() & 0xff, datalogMessage.get() & 0xff, datalogMessage.getShort() & 0xffff);
- datalogMessage.getShort(); // skip
+ stepsRecords[recordIdx] = new StepsRecord(timestamp, datalogMessage.get() & 0xff, datalogMessage.get() & 0xff, datalogMessage.getShort() & 0xffff, datalogMessage.get() & 0xff);
timestamp += 60;
}
@@ -102,12 +101,14 @@ public class DatalogSessionHealthSteps extends DatalogSession {
int steps;
int orientation;
int intensity;
+ int light_intensity;
- public StepsRecord(int timestamp, int steps, int orientation, int intensity) {
+ public StepsRecord(int timestamp, int steps, int orientation, int intensity, int light_intensity) {
this.timestamp = timestamp;
this.steps = steps;
this.orientation = orientation;
this.intensity = intensity;
+ this.light_intensity = light_intensity;
}
}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/pebble/PebbleIoThread.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/pebble/PebbleIoThread.java
index b5bfe222d..b2a42630a 100644
--- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/pebble/PebbleIoThread.java
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/pebble/PebbleIoThread.java
@@ -200,11 +200,16 @@ public class PebbleIoThread extends GBDeviceIoThread {
}
mPebbleProtocol.setForceProtocol(sharedPrefs.getBoolean("pebble_force_protocol", false));
- gbDevice.setState(GBDevice.State.CONNECTED);
- gbDevice.sendDeviceUpdateIntent(getContext());
mIsConnected = true;
- write(mPebbleProtocol.encodeFirmwareVersionReq());
+ if (originalState == GBDevice.State.WAITING_FOR_RECONNECT) {
+ gbDevice.setState(GBDevice.State.INITIALIZED);
+ } else {
+ gbDevice.setState(GBDevice.State.CONNECTED);
+ write(mPebbleProtocol.encodeFirmwareVersionReq());
+ }
+ gbDevice.sendDeviceUpdateIntent(getContext());
+
return true;
}
@@ -363,9 +368,19 @@ public class PebbleIoThread extends GBDeviceIoThread {
if (reconnectAttempts > 0) {
gbDevice.setState(GBDevice.State.CONNECTING);
gbDevice.sendDeviceUpdateIntent(getContext());
+ int delaySeconds = 1;
while (reconnectAttempts-- > 0 && !mQuit && !mIsConnected) {
LOG.info("Trying to reconnect (attempts left " + reconnectAttempts + ")");
mIsConnected = connect(gbDevice.getAddress());
+ if (!mIsConnected) {
+ try {
+ Thread.sleep(delaySeconds * 1000);
+ } catch (InterruptedException ignored) {
+ }
+ if (delaySeconds < 64) {
+ delaySeconds *= 2;
+ }
+ }
}
}
if (!mIsConnected && !mQuit) {
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/pebble/PebbleProtocol.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/pebble/PebbleProtocol.java
index fc00f3274..5d261e6a0 100644
--- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/pebble/PebbleProtocol.java
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/pebble/PebbleProtocol.java
@@ -33,9 +33,9 @@ import nodomain.freeyourgadget.gadgetbridge.devices.pebble.PebbleColor;
import nodomain.freeyourgadget.gadgetbridge.devices.pebble.PebbleIconID;
import nodomain.freeyourgadget.gadgetbridge.impl.GBDeviceApp;
import nodomain.freeyourgadget.gadgetbridge.model.ActivityUser;
+import nodomain.freeyourgadget.gadgetbridge.model.CallSpec;
import nodomain.freeyourgadget.gadgetbridge.model.NotificationSpec;
import nodomain.freeyourgadget.gadgetbridge.model.NotificationType;
-import nodomain.freeyourgadget.gadgetbridge.model.ServiceCommand;
import nodomain.freeyourgadget.gadgetbridge.service.serial.GBDeviceProtocol;
public class PebbleProtocol extends GBDeviceProtocol {
@@ -495,7 +495,7 @@ public class PebbleProtocol extends GBDeviceProtocol {
@Override
public byte[] encodeFindDevice(boolean start) {
- return encodeSetCallState("Where are you?", "Gadgetbridge", start ? ServiceCommand.CALL_INCOMING : ServiceCommand.CALL_END);
+ return encodeSetCallState("Where are you?", "Gadgetbridge", start ? CallSpec.CALL_INCOMING : CallSpec.CALL_END);
}
private static byte[] encodeExtensibleNotification(int id, int timestamp, String title, String subtitle, String body, String sourceName, boolean hasHandle, String[] cannedReplies) {
@@ -1044,20 +1044,20 @@ public class PebbleProtocol extends GBDeviceProtocol {
}
@Override
- public byte[] encodeSetCallState(String number, String name, ServiceCommand command) {
+ public byte[] encodeSetCallState(String number, String name, int command) {
String[] parts = {number, name};
byte pebbleCmd;
switch (command) {
- case CALL_START:
+ case CallSpec.CALL_START:
pebbleCmd = PHONECONTROL_START;
break;
- case CALL_END:
+ case CallSpec.CALL_END:
pebbleCmd = PHONECONTROL_END;
break;
- case CALL_INCOMING:
+ case CallSpec.CALL_INCOMING:
pebbleCmd = PHONECONTROL_INCOMINGCALL;
break;
- case CALL_OUTGOING:
+ case CallSpec.CALL_OUTGOING:
// pebbleCmd = PHONECONTROL_OUTGOINGCALL;
/*
* HACK/WORKAROUND for non-working outgoing call display.
@@ -1876,12 +1876,12 @@ public class PebbleProtocol extends GBDeviceProtocol {
int timestamp = buf.getInt();
int log_tag = buf.getInt();
byte item_type = buf.get();
- short item_size = buf.get();
+ short item_size = buf.getShort();
LOG.info("DATALOG OPENSESSION. id=" + (id & 0xff) + ", App UUID=" + uuid.toString() + ", log_tag=" + log_tag + ", item_type=" + item_type + ", itemSize=" + item_size);
if (!mDatalogSessions.containsKey(id)) {
if (uuid.equals(UUID_ZERO) && log_tag == 81) {
mDatalogSessions.put(id, new DatalogSessionHealthSteps(id, uuid, log_tag, item_type, item_size));
- } else if (uuid.equals(UUID_ZERO) && log_tag == 83) {
+ } else if (uuid.equals(UUID_ZERO) && (log_tag == 83 || log_tag == 84)) {
mDatalogSessions.put(id, new DatalogSessionHealthSleep(id, uuid, log_tag, item_type, item_size));
} else {
mDatalogSessions.put(id, new DatalogSession(id, uuid, log_tag, item_type, item_size));
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/pebble/PebbleSupport.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/pebble/PebbleSupport.java
index 66560c0d0..74e4736d9 100644
--- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/pebble/PebbleSupport.java
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/pebble/PebbleSupport.java
@@ -10,7 +10,11 @@ import java.util.ArrayList;
import java.util.Iterator;
import java.util.UUID;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
import nodomain.freeyourgadget.gadgetbridge.model.Alarm;
+import nodomain.freeyourgadget.gadgetbridge.model.CallSpec;
+import nodomain.freeyourgadget.gadgetbridge.model.MusicSpec;
+import nodomain.freeyourgadget.gadgetbridge.model.NotificationSpec;
import nodomain.freeyourgadget.gadgetbridge.service.serial.AbstractSerialDeviceSupport;
import nodomain.freeyourgadget.gadgetbridge.service.serial.GBDeviceIoThread;
import nodomain.freeyourgadget.gadgetbridge.service.serial.GBDeviceProtocol;
@@ -35,7 +39,7 @@ public class PebbleSupport extends AbstractSerialDeviceSupport {
@Override
public boolean useAutoConnect() {
- return false;
+ return true;
}
@Override
@@ -71,6 +75,45 @@ public class PebbleSupport extends AbstractSerialDeviceSupport {
return (PebbleIoThread) super.getDeviceIOThread();
}
+ private boolean reconnect() {
+ if (!isConnected() && useAutoConnect()) {
+ if (getDevice().getState() == GBDevice.State.WAITING_FOR_RECONNECT) {
+ gbDeviceIOThread.interrupt();
+ gbDeviceIOThread = null;
+ if (!connect()) {
+ return false;
+ }
+ try {
+ Thread.sleep(4000); // this is about the time the connect takes, so the notification can come though
+ } catch (InterruptedException ignored) {
+ }
+ }
+ }
+ return true;
+ }
+
+ @Override
+ public void onNotification(NotificationSpec notificationSpec) {
+ if (reconnect()) {
+ super.onNotification(notificationSpec);
+ }
+ }
+
+ @Override
+ public void onSetCallState(CallSpec callSpec) {
+ if (reconnect()) {
+ super.onSetCallState(callSpec);
+ }
+ }
+
+ @Override
+ public void onSetMusicInfo(MusicSpec musicSpec) {
+ if (reconnect()) {
+ super.onSetMusicInfo(musicSpec);
+ }
+ }
+
+
@Override
public void onSetAlarms(ArrayList extends Alarm> alarms) {
//nothing to do ATM
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/serial/AbstractSerialDeviceSupport.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/serial/AbstractSerialDeviceSupport.java
index 5563de599..aa78a3936 100644
--- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/serial/AbstractSerialDeviceSupport.java
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/serial/AbstractSerialDeviceSupport.java
@@ -8,8 +8,9 @@ import java.util.UUID;
import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEvent;
import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventSendBytes;
import nodomain.freeyourgadget.gadgetbridge.devices.EventHandler;
+import nodomain.freeyourgadget.gadgetbridge.model.CallSpec;
+import nodomain.freeyourgadget.gadgetbridge.model.MusicSpec;
import nodomain.freeyourgadget.gadgetbridge.model.NotificationSpec;
-import nodomain.freeyourgadget.gadgetbridge.model.ServiceCommand;
import nodomain.freeyourgadget.gadgetbridge.service.AbstractDeviceSupport;
/**
@@ -29,8 +30,8 @@ public abstract class AbstractSerialDeviceSupport extends AbstractDeviceSupport
private static final Logger LOG = LoggerFactory.getLogger(AbstractDeviceSupport.class);
- private GBDeviceProtocol gbDeviceProtocol;
- private GBDeviceIoThread gbDeviceIOThread;
+ protected GBDeviceProtocol gbDeviceProtocol;
+ protected GBDeviceIoThread gbDeviceIOThread;
/**
* Factory method to create the device specific GBDeviceProtocol instance to be used.
@@ -47,11 +48,7 @@ public abstract class AbstractSerialDeviceSupport extends AbstractDeviceSupport
// currently only one thread allowed
if (gbDeviceIOThread != null) {
gbDeviceIOThread.quit();
- try {
- gbDeviceIOThread.join();
- } catch (InterruptedException e) {
- e.printStackTrace();
- }
+ gbDeviceIOThread.interrupt();
gbDeviceIOThread = null;
}
}
@@ -120,14 +117,14 @@ public abstract class AbstractSerialDeviceSupport extends AbstractDeviceSupport
}
@Override
- public void onSetCallState(String number, String name, ServiceCommand command) {
- byte[] bytes = gbDeviceProtocol.encodeSetCallState(number, name, command);
+ public void onSetCallState(CallSpec callSpec) {
+ byte[] bytes = gbDeviceProtocol.encodeSetCallState(callSpec.number, callSpec.name, callSpec.command);
sendToDevice(bytes);
}
@Override
- public void onSetMusicInfo(String artist, String album, String track, int duration, int trackCount, int trackNr) {
- byte[] bytes = gbDeviceProtocol.encodeSetMusicInfo(artist, album, track, duration, trackCount, trackNr);
+ public void onSetMusicInfo(MusicSpec musicSpec) {
+ byte[] bytes = gbDeviceProtocol.encodeSetMusicInfo(musicSpec.artist, musicSpec.album, musicSpec.track, musicSpec.duration, musicSpec.trackCount, musicSpec.trackNr);
sendToDevice(bytes);
}
@@ -178,4 +175,10 @@ public abstract class AbstractSerialDeviceSupport extends AbstractDeviceSupport
byte[] bytes = gbDeviceProtocol.encodeEnableRealtimeSteps(enable);
sendToDevice(bytes);
}
+
+ @Override
+ public void onEnableHeartRateSleepSupport(boolean enable) {
+ byte[] bytes = gbDeviceProtocol.encodeEnableHeartRateSleepSupport(enable);
+ sendToDevice(bytes);
+ }
}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/serial/GBDeviceProtocol.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/serial/GBDeviceProtocol.java
index b08b105d5..2aa1fb660 100644
--- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/serial/GBDeviceProtocol.java
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/serial/GBDeviceProtocol.java
@@ -4,7 +4,6 @@ import java.util.UUID;
import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEvent;
import nodomain.freeyourgadget.gadgetbridge.model.NotificationSpec;
-import nodomain.freeyourgadget.gadgetbridge.model.ServiceCommand;
public abstract class GBDeviceProtocol {
@@ -16,7 +15,7 @@ public abstract class GBDeviceProtocol {
return null;
}
- public byte[] encodeSetCallState(String number, String name, ServiceCommand command) {
+ public byte[] encodeSetCallState(String number, String name, int command) {
return null;
}
@@ -60,6 +59,10 @@ public abstract class GBDeviceProtocol {
return null;
}
+ public byte[] encodeEnableHeartRateSleepSupport(boolean enable) {
+ return null;
+ }
+
public GBDeviceEvent[] decodeResponse(byte[] responseData) {
return null;
}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/GB.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/GB.java
index c6f87cff6..a20e0f2eb 100644
--- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/GB.java
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/util/GB.java
@@ -39,6 +39,10 @@ public class GB {
public static final int INFO = 1;
public static final int WARN = 2;
public static final int ERROR = 3;
+ public static final String ACTION_DISPLAY_MESSAGE = "GB_Display_Message";
+ public static final String DISPLAY_MESSAGE_MESSAGE = "message";
+ public static final String DISPLAY_MESSAGE_DURATION = "duration";
+ public static final String DISPLAY_MESSAGE_SEVERITY = "severity";
public static GBEnvironment environment;
public static Notification createNotification(String text, Context context) {
@@ -225,7 +229,7 @@ public class GB {
}
}
- private static void log(String message, int severity, Throwable ex) {
+ public static void log(String message, int severity, Throwable ex) {
switch (severity) {
case INFO:
LOG.info(message, ex);
diff --git a/app/src/main/res/layout/activity_appinstaller.xml b/app/src/main/res/layout/activity_appinstaller.xml
index 8846623b0..eb13abc1b 100644
--- a/app/src/main/res/layout/activity_appinstaller.xml
+++ b/app/src/main/res/layout/activity_appinstaller.xml
@@ -62,6 +62,14 @@
android:layout_below="@+id/installProgressBar"
android:layout_marginTop="10dp" />
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/values-ja/strings.xml b/app/src/main/res/values-ja/strings.xml
index 1ca2b0ed2..6cf3ba015 100644
--- a/app/src/main/res/values-ja/strings.xml
+++ b/app/src/main/res/values-ja/strings.xml
@@ -102,7 +102,7 @@
有効なユーザーデータはありません。今のところ、ダミーのユーザーデータを使用します。
お使いのMi Bandが振動と点滅したとき、それを連続して数回タップしてください。
インストール
- お使いのデバイスを検出可能にしてください。現在、接続されたデバイスは、おそらく検出されません。
+ お使いのデバイスを検出可能にしてください。現在、接続されたデバイスは、おそらく検出されません。お使いのデバイスが 2 分しても表示されない場合は、モバイルデバイスを再起動した後にもう一度試してください。
注:
デバイスイメージ
名前/別名
@@ -197,6 +197,7 @@
非互換性のファームウェア
このファームウェアは、デバイスと互換性がありません
今後のイベントのために予約するアラーム
+ 睡眠の検出を改善するために心拍センサーを使用する
再接続の待機中
再インストール
あなたについて
@@ -217,4 +218,6 @@
FW: %1$s
ログファイルのディレクトリを作成中にエラー: %1$s
HR:
+ ファームウェアを更新しています
+ ファームウェアを送信しませんでした
diff --git a/app/src/main/res/values/colors.xml b/app/src/main/res/values/colors.xml
index f5a0bc4f9..8d5f07fd3 100644
--- a/app/src/main/res/values/colors.xml
+++ b/app/src/main/res/values/colors.xml
@@ -11,7 +11,7 @@
#ff808080
#1f000000
- #b40000
+ #ffab40
#0071b7
#4c5aff
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index 3bcbddd84..407d428f3 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -213,6 +213,8 @@
Incompatible firmware
This firmware is not compatible with the device
Alarms to reserve for upcoming events
+ Use Heartrate Sensor to improve sleep detection
+
waiting for reconnect
Reinstall
@@ -237,5 +239,6 @@
"HR: "
Firmware update in progress
Firmware not sent
+ Heart Rate
diff --git a/app/src/main/res/xml/changelog_master.xml b/app/src/main/res/xml/changelog_master.xml
index 608a543c8..dddd9bab6 100644
--- a/app/src/main/res/xml/changelog_master.xml
+++ b/app/src/main/res/xml/changelog_master.xml
@@ -1,5 +1,16 @@
+
+ Pebble: support pebble health datalog messages of firmware 3.11 (this adds support for deep sleep!)
+ Pebble: try to reconnect on new notifications and phone calls when connection was lost unexpectedly
+ Pebble: delay between reconnection attempts (from 1 up to 64 seconds)
+
+
+ Pebble: Fix Pebble Health activation (was not available in the App Manager)
+ Simplify connection state display (only connecting->connected)
+ Small improvements to the pairing activity
+ Mi Band 1S: Fix for mi band firmware update
+
Mi Band: Fix update of second (HR) firmware on Mi1S (#234)
Fix ordering issue of device infos being displayed
diff --git a/app/src/main/res/xml/miband_preferences.xml b/app/src/main/res/xml/miband_preferences.xml
index 65997e955..042cd8e1e 100644
--- a/app/src/main/res/xml/miband_preferences.xml
+++ b/app/src/main/res/xml/miband_preferences.xml
@@ -30,6 +30,10 @@
android:maxLength="1"
android:digits="0123"
android:title="@string/miband_prefs_reserve_alarm_calendar" />
+