mirror of
https://codeberg.org/Freeyourgadget/Gadgetbridge
synced 2024-11-27 20:36:51 +01:00
Merge branch 'master' into db-refactoring
This commit is contained in:
commit
22a5aef7d7
@ -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
|
||||
|
11
CHANGELOG.md
11
CHANGELOG.md
@ -1,5 +1,16 @@
|
||||
###Changelog
|
||||
|
||||
####Version (next)
|
||||
* Pebble: support pebble health datalog messages of firmware 3.11 (this adds support for deep sleep!)
|
||||
* Pebble: try to reconnect on new notifications and phone calls when connection was lost unexpectedly
|
||||
* Pebble: delay between reconnection attempts (from 1 up to 64 seconds)
|
||||
|
||||
####Version 0.9.3
|
||||
* Pebble: Fix Pebble Health activation (was not available in the App Manager)
|
||||
* Simplify connection state display (only connecting->connected)
|
||||
* Small improvements to the pairing activity
|
||||
* Mi Band 1S: Fix for mi band firmware update
|
||||
|
||||
####Version 0.9.2
|
||||
* Mi Band: Fix update of second (HR) firmware on Mi1S (#234)
|
||||
* Fix ordering issue of device infos being displayed
|
||||
|
@ -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 {
|
||||
|
149
app/src/main/assets/ic_launcher.svg
Normal file
149
app/src/main/assets/ic_launcher.svg
Normal 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 |
@ -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
|
||||
|
@ -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));
|
||||
}
|
||||
|
||||
|
@ -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);
|
||||
}
|
||||
});
|
||||
|
||||
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
@ -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()) {
|
||||
|
@ -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);
|
||||
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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";
|
||||
|
@ -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"));
|
||||
|
@ -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
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
@ -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";
|
||||
|
||||
|
@ -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;
|
||||
}
|
@ -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
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
@ -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();
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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
|
@ -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();
|
||||
}
|
@ -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;
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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() {
|
||||
|
@ -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) {
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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) {
|
||||
|
@ -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));
|
||||
|
@ -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
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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);
|
||||
|
@ -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"
|
||||
|
42
app/src/main/res/layout/item_with_details_small.xml
Normal file
42
app/src/main/res/layout/item_with_details_small.xml
Normal 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>
|
@ -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>
|
||||
|
@ -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>
|
||||
|
||||
|
@ -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>
|
||||
|
@ -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>
|
||||
|
@ -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
|
||||
|
@ -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) {
|
||||
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
|
Loading…
Reference in New Issue
Block a user