Merge branch 'master' into db-refactoring

master
cpfeiffer 2016-04-08 23:06:20 +02:00
commit 22a5aef7d7
59 changed files with 929 additions and 218 deletions

View File

@ -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

View File

@ -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

View File

@ -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 {

View File

@ -0,0 +1,149 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
version="1.1"
id="svg2"
viewBox="0 0 1814.1732 1814.1732"
height="512mm"
width="512mm"
inkscape:version="0.91 r13725"
sodipodi:docname="211ad552-a817-11e5-98b9-385bef8cd413.svg">
<sodipodi:namedview
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1"
objecttolerance="10"
gridtolerance="10"
guidetolerance="10"
inkscape:pageopacity="0"
inkscape:pageshadow="2"
inkscape:window-width="1600"
inkscape:window-height="876"
id="namedview4212"
showgrid="false"
inkscape:zoom="0.4040408"
inkscape:cx="498.78758"
inkscape:cy="883.13571"
inkscape:window-x="1024"
inkscape:window-y="24"
inkscape:window-maximized="1"
inkscape:current-layer="svg2" />
<defs
id="defs4">
<marker
orient="auto"
refY="0.0"
refX="0.0"
id="marker4512"
style="overflow:visible">
<path
id="path4514"
d="M 0.0,0.0 L 5.0,-5.0 L -12.5,0.0 L 5.0,5.0 L 0.0,0.0 z "
style="fill-rule:evenodd;stroke:#1976d2;stroke-width:1pt;stroke-opacity:1;fill:#ffffff;fill-opacity:1"
transform="scale(0.8) translate(12.5,0)" />
</marker>
<marker
style="overflow:visible"
id="Arrow1Lstart"
refX="0.0"
refY="0.0"
orient="auto">
<path
transform="scale(0.8) translate(12.5,0)"
style="fill-rule:evenodd;stroke:#1976d2;stroke-width:1pt;stroke-opacity:1;fill:#ffffff;fill-opacity:1"
d="M 0.0,0.0 L 5.0,-5.0 L -12.5,0.0 L 5.0,5.0 L 0.0,0.0 z "
id="path4208" />
</marker>
</defs>
<metadata
id="metadata7">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title />
</cc:Work>
</rdf:RDF>
</metadata>
<circle
style="fill:#1976d2;fill-opacity:1;stroke:none;stroke-width:5.9000001;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
id="path4178"
cx="899.03583"
cy="935.34052"
r="652.55853" />
<path
inkscape:connector-curvature="0"
style="fill:#000000;fill-opacity:0.24025975;stroke:none;stroke-width:5.9000001;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="m 1180.6138,761.25155 -397.79692,586.48635 230.40242,230.4024 a 652.55853,652.55853 0 0 0 515.1523,-469.1328 L 1180.6138,761.25155 Z"
id="path4178-3" />
<path
inkscape:connector-curvature="0"
style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:#1976d2;stroke-width:13.60000038;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="m 703.29568,640.10033 c 0,0 -62.09786,90.61312 -84.48779,144.65142 -26.49785,122.73311 44.15742,168.66683 151.63581,319.05845 l -2.08379,245.8099 251.87809,0 0,-245.8099 c 0,0 77.6015,-28.5779 110.2687,-109.23097 30.2773,-74.75286 27.0683,-193.76648 27.0683,-193.76648 z"
id="path4176" />
<rect
style="fill:#ffffff;fill-opacity:1;stroke:#1976d2;stroke-width:13.60000038;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
id="rect4136"
width="109.0837"
height="219.8989"
x="1289.9961"
y="43.667068"
ry="43.04707"
transform="matrix(0.8660254,0.5,-0.5,0.8660254,0,0)" />
<rect
style="fill:#ffffff;fill-opacity:1;stroke:#1976d2;stroke-width:13.60000038;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
id="rect4136-5"
width="109.0837"
height="252.79712"
x="1180.0483"
y="40.667068"
ry="49.487179"
transform="matrix(0.8660254,0.5,-0.5,0.8660254,0,0)" />
<rect
style="fill:#ffffff;fill-opacity:1;stroke:#1976d2;stroke-width:13.60000038;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
id="rect4136-8"
width="109.0837"
height="297.81583"
x="1070.1006"
y="37.667068"
ry="58.299976"
transform="matrix(0.8660254,0.5,-0.5,0.8660254,0,0)" />
<rect
style="fill:#ffffff;fill-opacity:1;stroke:#1976d2;stroke-width:13.60000038;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
id="rect4136-1"
width="109.0837"
height="219.8989"
x="958.4209"
y="35.667068"
ry="43.04707"
transform="matrix(0.8660254,0.5,-0.5,0.8660254,0,0)" />
<path
inkscape:connector-curvature="0"
style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:#1976d2;stroke-width:12.19999981;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="m 707.67725,640.85267 143.17855,38.54881 c 0,0 39.4733,34.66779 27.90054,81.29964 -10.45038,42.10926 -58.66289,44.51639 -58.66289,44.51639 L 705.87743,773.82104"
id="path4186" />
<rect
style="fill:#ff9800;fill-opacity:1;stroke:#ff9800;stroke-width:12.48825073;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:0"
id="rect4628"
width="314.25906"
height="96.066086"
x="738.79462"
y="1163.3024"
ry="7.8193355" />
<rect
style="fill:#1976d2;fill-opacity:1;stroke:#1976d2;stroke-width:8.70396328;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
id="rect4624"
width="171.0025"
height="47.28043"
x="810.42285"
y="1187.136"
ry="15.011105" />
</svg>

After

Width:  |  Height:  |  Size: 6.0 KiB

View File

@ -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

View File

@ -80,7 +80,7 @@ public class AppManagerActivity extends Activity {
List<GBDeviceApp> 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));
}

View File

@ -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);
}
});

View File

@ -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<ItemWithDetails> mItems = new ArrayList<>();
private ItemWithDetailsAdapter mItemAdapter;
private ListView detailsListView;
private ItemWithDetailsAdapter mDetailsItemAdapter;
private ArrayList<ItemWithDetails> 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<ItemWithDetails> 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();
}
}

View File

@ -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);

View File

@ -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);
}

View File

@ -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);
}

View File

@ -18,8 +18,12 @@ import nodomain.freeyourgadget.gadgetbridge.model.ItemWithDetails;
*/
public class ItemWithDetailsAdapter extends ArrayAdapter<ItemWithDetails> {
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<ItemWithDetails> items) {
super(context, 0, items);
@ -42,7 +46,14 @@ public class ItemWithDetailsAdapter extends ArrayAdapter<ItemWithDetails> {
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<ItemWithDetails> {
return view;
}
public void setSize(int size) {
this.size = size;
}
public int getSize() {
return size;
}
}

View File

@ -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()) {

View File

@ -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);
}

View File

@ -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;
}
}

View File

@ -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);
}

View File

@ -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);
}

View File

@ -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";

View File

@ -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"));

View File

@ -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

View File

@ -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);
}
}

View File

@ -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);

View File

@ -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;
}

View File

@ -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<ItemWithDetails> 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);
}

View File

@ -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);
}
}

View File

@ -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;
}

View File

@ -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";

View File

@ -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;
}

View File

@ -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
}

View File

@ -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);
}
}

View File

@ -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<Alarm> 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();

View File

@ -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);
}
}

View File

@ -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

View File

@ -113,6 +113,10 @@ public abstract class AbstractBTLEOperation<T extends AbstractBTLEDeviceSupport>
return operationStatus == OperationStatus.RUNNING;
}
public boolean isOperationFinished() {
return operationStatus == OperationStatus.FINISHED;
}
public T getSupport() {
return mSupport;
}

View File

@ -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);
}

View File

@ -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

View File

@ -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.
* <p/>
* 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();
}

View File

@ -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;

View File

@ -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;
}
}
}

View File

@ -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() {

View File

@ -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) {

View File

@ -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;
}
}

View File

@ -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;
}
}
}

View File

@ -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;
}
}

View File

@ -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) {

View File

@ -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));

View File

@ -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

View File

@ -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);
}
}

View File

@ -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;
}

View File

@ -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);

View File

@ -62,6 +62,14 @@
android:layout_below="@+id/installProgressBar"
android:layout_marginTop="10dp" />
<ListView
android:id="@+id/detailsListView"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_below="@+id/installButton"
android:layout_alignParentEnd="false">
</ListView>
<android.widget.Space
android:layout_width="wrap_content"
android:layout_height="wrap_content"

View File

@ -0,0 +1,42 @@
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="?android:attr/activatedBackgroundIndicator"
android:padding="4dp" >
<ImageView
android:id="@+id/item_image"
android:layout_width="24dp"
android:layout_height="24dp"
android:layout_alignParentStart="true"
android:contentDescription="@string/candidate_item_device_image" />
<LinearLayout
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_centerVertical="true"
android:layout_toEndOf="@+id/item_image"
android:orientation="vertical"
android:paddingStart="4dp"
android:paddingEnd="4dp">
<TextView
android:id="@+id/item_name"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:scrollHorizontally="false"
style="@style/Base.TextAppearance.AppCompat.Body1"
android:text="Item Name"
android:singleLine="true" />
<TextView
android:id="@+id/item_details"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
style="@style/Base.TextAppearance.AppCompat.Body2"
android:text="Item Description"
/>
</LinearLayout>
</RelativeLayout>

View File

@ -102,7 +102,7 @@
<string name="miband_pairing_using_dummy_userdata">有効なユーザーデータはありません。今のところ、ダミーのユーザーデータを使用します。</string>
<string name="miband_pairing_tap_hint">お使いのMi Bandが振動と点滅したとき、それを連続して数回タップしてください。</string>
<string name="appinstaller_install">インストール</string>
<string name="discovery_connected_devices_hint">お使いのデバイスを検出可能にしてください。現在、接続されたデバイスは、おそらく検出されません。</string>
<string name="discovery_connected_devices_hint">お使いのデバイスを検出可能にしてください。現在、接続されたデバイスは、おそらく検出されません。お使いのデバイスが 2 分しても表示されない場合は、モバイルデバイスを再起動した後にもう一度試してください。</string>
<string name="discovery_note">注:</string>
<string name="candidate_item_device_image">デバイスイメージ</string>
<string name="miband_prefs_alias">名前/別名</string>
@ -197,6 +197,7 @@
<string name="miband_fwinstaller_incompatible_version">非互換性のファームウェア</string>
<string name="fwinstaller_firmware_not_compatible_to_device">このファームウェアは、デバイスと互換性がありません</string>
<string name="miband_prefs_reserve_alarm_calendar">今後のイベントのために予約するアラーム</string>
<string name="miband_prefs_hr_sleep_detection">睡眠の検出を改善するために心拍センサーを使用する</string>
<string name="waiting_for_reconnect">再接続の待機中</string>
<string name="appmananger_app_reinstall">再インストール</string>
<string name="activity_prefs_about_you">あなたについて</string>
@ -217,4 +218,6 @@
<string name="device_fw">FW: %1$s</string>
<string name="error_creating_directory_for_logfiles">ログファイルのディレクトリを作成中にエラー: %1$s</string>
<string name="DEVINFO_HR_VER">HR: </string>
<string name="updatefirmwareoperation_update_in_progress">ファームウェアを更新しています</string>
<string name="updatefirmwareoperation_firmware_not_sent">ファームウェアを送信しませんでした</string>
</resources>

View File

@ -11,7 +11,7 @@
<color name="secondarytext" type="color">#ff808080</color>
<color name="divider" type="color">#1f000000</color>
<color name="chart_heartrate" type="color">#b40000</color>
<color name="chart_heartrate" type="color">#ffab40</color>
<color name="chart_deep_sleep_light" type="color">#0071b7</color>
<color name="chart_deep_sleep_dark" type="color">#4c5aff</color>

View File

@ -213,6 +213,8 @@
<string name="miband_fwinstaller_incompatible_version">Incompatible firmware</string>
<string name="fwinstaller_firmware_not_compatible_to_device">This firmware is not compatible with the device</string>
<string name="miband_prefs_reserve_alarm_calendar">Alarms to reserve for upcoming events</string>
<string name="miband_prefs_hr_sleep_detection">Use Heartrate Sensor to improve sleep detection</string>
<string name="waiting_for_reconnect">waiting for reconnect</string>
<string name="appmananger_app_reinstall">Reinstall</string>
@ -237,5 +239,6 @@
<string name="DEVINFO_HR_VER">"HR: "</string>
<string name="updatefirmwareoperation_update_in_progress">Firmware update in progress</string>
<string name="updatefirmwareoperation_firmware_not_sent">Firmware not sent</string>
<string name="charts_legend_heartrate">Heart Rate</string>
</resources>

View File

@ -1,5 +1,16 @@
<?xml version="1.0" encoding="utf-8"?>
<changelog>
<release version="0.9.4" versioncode="48">
<change>Pebble: support pebble health datalog messages of firmware 3.11 (this adds support for deep sleep!)</change>
<change>Pebble: try to reconnect on new notifications and phone calls when connection was lost unexpectedly</change>
<change>Pebble: delay between reconnection attempts (from 1 up to 64 seconds)</change>
</release>
<release version="0.9.3" versioncode="47">
<change>Pebble: Fix Pebble Health activation (was not available in the App Manager)</change>
<change>Simplify connection state display (only connecting->connected)</change>
<change>Small improvements to the pairing activity</change>
<change>Mi Band 1S: Fix for mi band firmware update</change>
</release>
<release version="0.9.2" versioncode="46">
<change>Mi Band: Fix update of second (HR) firmware on Mi1S (#234)</change>
<change>Fix ordering issue of device infos being displayed</change>

View File

@ -30,6 +30,10 @@
android:maxLength="1"
android:digits="0123"
android:title="@string/miband_prefs_reserve_alarm_calendar" />
<CheckBoxPreference
android:defaultValue="false"
android:key="mi_hr_sleep_detection"
android:title="@string/miband_prefs_hr_sleep_detection" />
</PreferenceCategory>
<PreferenceCategory

View File

@ -3,15 +3,15 @@ package nodomain.freeyourgadget.gadgetbridge.service;
import android.bluetooth.BluetoothAdapter;
import android.content.Context;
import android.net.Uri;
import android.support.annotation.Nullable;
import java.util.ArrayList;
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;
public class TestDeviceSupport extends AbstractDeviceSupport {
@ -61,12 +61,12 @@ public class TestDeviceSupport extends AbstractDeviceSupport {
}
@Override
public void onSetCallState(@Nullable String number, @Nullable String name, ServiceCommand command) {
public void onSetCallState(CallSpec callSpec) {
}
@Override
public void onSetMusicInfo(String artist, String album, String track, int duration, int trackCount, int trackNr) {
public void onSetMusicInfo(MusicSpec musicSpec) {
}
@ -124,4 +124,9 @@ public class TestDeviceSupport extends AbstractDeviceSupport {
public void onEnableRealtimeSteps(boolean enable) {
}
@Override
public void onEnableHeartRateSleepSupport(boolean enable) {
}
}

View File

@ -5,7 +5,7 @@ buildscript {
jcenter()
}
dependencies {
classpath 'com.android.tools.build:gradle:1.5.0'
classpath 'com.android.tools.build:gradle:2.0.0'
// NOTE: Do not place your application dependencies here; they belong
// in the individual module build.gradle files